@pascal-app/core 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/events/bus.d.ts +38 -2
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/scene-registry.d.ts +2 -0
  4. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  5. package/dist/hooks/scene-registry/scene-registry.js +2 -0
  6. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  7. package/dist/hooks/spatial-grid/spatial-grid-manager.js +164 -6
  8. package/dist/index.d.ts +8 -15
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +7 -14
  11. package/dist/lib/door-operation.d.ts +7 -0
  12. package/dist/lib/door-operation.d.ts.map +1 -0
  13. package/dist/lib/door-operation.js +25 -0
  14. package/dist/lib/slab-polygon.d.ts +3 -0
  15. package/dist/lib/slab-polygon.d.ts.map +1 -0
  16. package/dist/lib/slab-polygon.js +58 -0
  17. package/dist/material-library.d.ts +5 -3
  18. package/dist/material-library.d.ts.map +1 -1
  19. package/dist/material-library.js +26 -49
  20. package/dist/schema/asset-url.d.ts +34 -0
  21. package/dist/schema/asset-url.d.ts.map +1 -0
  22. package/dist/schema/asset-url.js +79 -0
  23. package/dist/schema/asset-url.test.d.ts +2 -0
  24. package/dist/schema/asset-url.test.d.ts.map +1 -0
  25. package/dist/schema/asset-url.test.js +138 -0
  26. package/dist/schema/index.d.ts +7 -5
  27. package/dist/schema/index.d.ts.map +1 -1
  28. package/dist/schema/index.js +5 -3
  29. package/dist/schema/material.d.ts +3 -2
  30. package/dist/schema/material.d.ts.map +1 -1
  31. package/dist/schema/material.js +13 -11
  32. package/dist/schema/nodes/ceiling.d.ts +1 -1
  33. package/dist/schema/nodes/column.d.ts +520 -0
  34. package/dist/schema/nodes/column.d.ts.map +1 -0
  35. package/dist/schema/nodes/column.js +385 -0
  36. package/dist/schema/nodes/door.d.ts +73 -1
  37. package/dist/schema/nodes/door.d.ts.map +1 -1
  38. package/dist/schema/nodes/door.js +39 -2
  39. package/dist/schema/nodes/fence.d.ts +1 -1
  40. package/dist/schema/nodes/guide.d.ts +17 -0
  41. package/dist/schema/nodes/guide.d.ts.map +1 -1
  42. package/dist/schema/nodes/guide.js +11 -1
  43. package/dist/schema/nodes/item.d.ts +8 -0
  44. package/dist/schema/nodes/item.d.ts.map +1 -1
  45. package/dist/schema/nodes/item.js +18 -1
  46. package/dist/schema/nodes/level.d.ts +1 -1
  47. package/dist/schema/nodes/level.d.ts.map +1 -1
  48. package/dist/schema/nodes/level.js +6 -0
  49. package/dist/schema/nodes/roof-segment.d.ts +1 -1
  50. package/dist/schema/nodes/roof.d.ts +4 -4
  51. package/dist/schema/nodes/scan.d.ts.map +1 -1
  52. package/dist/schema/nodes/scan.js +2 -1
  53. package/dist/schema/nodes/site.d.ts +1 -0
  54. package/dist/schema/nodes/site.d.ts.map +1 -1
  55. package/dist/schema/nodes/slab.d.ts +1 -1
  56. package/dist/schema/nodes/spawn.d.ts +24 -0
  57. package/dist/schema/nodes/spawn.d.ts.map +1 -0
  58. package/dist/schema/nodes/spawn.js +8 -0
  59. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  60. package/dist/schema/nodes/stair.d.ts +8 -8
  61. package/dist/schema/nodes/wall.d.ts +3 -3
  62. package/dist/schema/nodes/window.d.ts +56 -1
  63. package/dist/schema/nodes/window.d.ts.map +1 -1
  64. package/dist/schema/nodes/window.js +29 -0
  65. package/dist/schema/types.d.ts +324 -21
  66. package/dist/schema/types.d.ts.map +1 -1
  67. package/dist/schema/types.js +4 -0
  68. package/dist/store/actions/node-actions.d.ts.map +1 -1
  69. package/dist/store/actions/node-actions.js +6 -5
  70. package/dist/store/use-interactive.d.ts +43 -0
  71. package/dist/store/use-interactive.d.ts.map +1 -1
  72. package/dist/store/use-interactive.js +66 -0
  73. package/dist/store/use-scene.d.ts.map +1 -1
  74. package/dist/store/use-scene.js +60 -2
  75. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  76. package/dist/systems/stair/stair-opening-sync.js +81 -20
  77. package/dist/systems/stair/stair-opening-sync.test.d.ts +2 -0
  78. package/dist/systems/stair/stair-opening-sync.test.d.ts.map +1 -0
  79. package/dist/systems/stair/stair-opening-sync.test.js +65 -0
  80. package/dist/systems/stair/stair-system.js +1 -1
  81. package/package.json +31 -3
@@ -1,35 +1,18 @@
1
- import { MaterialTarget as MaterialTargetSchema, } from './schema/material';
2
- const WALL_TARGETS = [
3
- MaterialTargetSchema.enum.wall,
4
- ];
5
- const SLAB_TARGETS = [
6
- MaterialTargetSchema.enum.slab,
7
- ];
8
- const WALL_AND_SLAB_TARGETS = [
9
- MaterialTargetSchema.enum.wall,
10
- MaterialTargetSchema.enum.slab,
11
- ];
12
- const STAIR_TARGETS = [
13
- MaterialTargetSchema.enum.stair,
14
- MaterialTargetSchema.enum['stair-segment'],
15
- ];
16
- const STAIR_AND_FENCE_TARGETS = [
17
- ...STAIR_TARGETS,
18
- MaterialTargetSchema.enum.fence,
19
- ];
20
- const ROOF_TARGETS = [
21
- MaterialTargetSchema.enum.roof,
22
- MaterialTargetSchema.enum['roof-segment'],
23
- ];
24
- const CEILING_TARGETS = [
25
- MaterialTargetSchema.enum.ceiling,
1
+ import {} from './schema/material';
2
+ export const MATERIAL_CATEGORIES = [
3
+ 'wood',
4
+ 'wallpaper',
5
+ 'parquet',
6
+ 'granite',
7
+ 'marble',
8
+ 'other',
26
9
  ];
27
10
  export const MATERIAL_CATALOG = [
28
11
  {
29
12
  id: 'wall-wood1',
30
13
  label: 'Wood',
14
+ category: 'wood',
31
15
  description: 'Warm wood finish',
32
- targets: [...WALL_TARGETS, ...SLAB_TARGETS, ...STAIR_AND_FENCE_TARGETS, ...ROOF_TARGETS],
33
16
  previewThumbnailUrl: '/material/wood1/wood1_thumbnail.webp',
34
17
  preset: {
35
18
  maps: {
@@ -63,8 +46,8 @@ export const MATERIAL_CATALOG = [
63
46
  {
64
47
  id: 'wall-wood2',
65
48
  label: 'Wood',
49
+ category: 'wood',
66
50
  description: 'Textured wood finish',
67
- targets: [...WALL_TARGETS, ...SLAB_TARGETS, ...STAIR_AND_FENCE_TARGETS, ...ROOF_TARGETS],
68
51
  previewThumbnailUrl: '/material/wood2/wood2_thumbnail.webp',
69
52
  preset: {
70
53
  maps: {
@@ -99,8 +82,8 @@ export const MATERIAL_CATALOG = [
99
82
  {
100
83
  id: 'wall-wood3',
101
84
  label: 'Wood',
85
+ category: 'wood',
102
86
  description: 'Knotted timber finish',
103
- targets: [...WALL_TARGETS, ...SLAB_TARGETS, ...STAIR_AND_FENCE_TARGETS, ...ROOF_TARGETS],
104
87
  previewThumbnailUrl: '/material/wood3/wood3_thumbnail.webp',
105
88
  preset: {
106
89
  maps: {
@@ -133,8 +116,8 @@ export const MATERIAL_CATALOG = [
133
116
  {
134
117
  id: 'wall-wood4',
135
118
  label: 'Wood',
119
+ category: 'wood',
136
120
  description: 'Oak stretcher finish',
137
- targets: [...WALL_TARGETS, ...SLAB_TARGETS, ...STAIR_AND_FENCE_TARGETS],
138
121
  previewThumbnailUrl: '/material/wood4/wood4_thumbnail.webp',
139
122
  preset: {
140
123
  maps: {
@@ -167,8 +150,8 @@ export const MATERIAL_CATALOG = [
167
150
  {
168
151
  id: 'wall-wood5',
169
152
  label: 'Wood',
153
+ category: 'wood',
170
154
  description: 'Rich grain wood finish',
171
- targets: [...WALL_TARGETS, ...SLAB_TARGETS, ...STAIR_AND_FENCE_TARGETS],
172
155
  previewThumbnailUrl: '/material/wood5/wood5_thumnail.webp',
173
156
  preset: {
174
157
  maps: {
@@ -203,8 +186,8 @@ export const MATERIAL_CATALOG = [
203
186
  {
204
187
  id: 'wall-granite1',
205
188
  label: 'Granite',
189
+ category: 'granite',
206
190
  description: 'Polished granite finish',
207
- targets: SLAB_TARGETS,
208
191
  previewThumbnailUrl: '/material/granite1/granite_thumbnail.webp',
209
192
  preset: {
210
193
  maps: {
@@ -237,8 +220,8 @@ export const MATERIAL_CATALOG = [
237
220
  {
238
221
  id: 'wall-marble1',
239
222
  label: 'Marble',
223
+ category: 'marble',
240
224
  description: 'Smooth marble finish',
241
- targets: [...SLAB_TARGETS, ...STAIR_AND_FENCE_TARGETS],
242
225
  previewThumbnailUrl: '/material/marble1/marble1_thumbnail.webp',
243
226
  preset: {
244
227
  maps: {
@@ -271,8 +254,8 @@ export const MATERIAL_CATALOG = [
271
254
  {
272
255
  id: 'wall-marble2',
273
256
  label: 'Marble',
257
+ category: 'marble',
274
258
  description: 'Soft marble finish',
275
- targets: [...SLAB_TARGETS, ...STAIR_AND_FENCE_TARGETS],
276
259
  previewThumbnailUrl: '/material/marble2/marble2_thumbnail.webp',
277
260
  preset: {
278
261
  maps: {
@@ -305,8 +288,8 @@ export const MATERIAL_CATALOG = [
305
288
  {
306
289
  id: 'wall-parquet1',
307
290
  label: 'Parquet',
291
+ category: 'parquet',
308
292
  description: 'Parquet wood finish',
309
- targets: SLAB_TARGETS,
310
293
  previewThumbnailUrl: '/material/parquet1/parquet_thumnail.webp',
311
294
  preset: {
312
295
  maps: {
@@ -339,8 +322,8 @@ export const MATERIAL_CATALOG = [
339
322
  {
340
323
  id: 'wall-parquet2',
341
324
  label: 'Parquet',
325
+ category: 'parquet',
342
326
  description: 'Soft parquet finish',
343
- targets: SLAB_TARGETS,
344
327
  previewThumbnailUrl: '/material/parquet2/parquet2_thumbnail.webp',
345
328
  preset: {
346
329
  maps: {
@@ -373,8 +356,8 @@ export const MATERIAL_CATALOG = [
373
356
  {
374
357
  id: 'wall-wallpaper1',
375
358
  label: 'Wallpaper',
359
+ category: 'wallpaper',
376
360
  description: 'Soft wallpaper finish',
377
- targets: WALL_TARGETS,
378
361
  previewThumbnailUrl: '/material/wallpaper1/wallpaper1_thumbnail.webp',
379
362
  preset: {
380
363
  maps: {
@@ -408,8 +391,8 @@ export const MATERIAL_CATALOG = [
408
391
  {
409
392
  id: 'wall-wallpaper2',
410
393
  label: 'Wallpaper',
394
+ category: 'wallpaper',
411
395
  description: 'Decorative wallpaper finish',
412
- targets: WALL_TARGETS,
413
396
  previewThumbnailUrl: '/material/wallpaper2/wallpaper2_thumnail.webp',
414
397
  preset: {
415
398
  maps: {
@@ -442,8 +425,8 @@ export const MATERIAL_CATALOG = [
442
425
  {
443
426
  id: 'wall-wallpaper3',
444
427
  label: 'Wallpaper',
428
+ category: 'wallpaper',
445
429
  description: 'Patterned wallpaper finish',
446
- targets: WALL_TARGETS,
447
430
  previewThumbnailUrl: '/material/wallpaper3/wallpaper3_thumbnail.webp',
448
431
  preset: {
449
432
  maps: {
@@ -476,14 +459,8 @@ export const MATERIAL_CATALOG = [
476
459
  {
477
460
  id: 'preset-white',
478
461
  label: 'White',
462
+ category: 'other',
479
463
  description: 'Clean painted finish',
480
- targets: [
481
- ...WALL_TARGETS,
482
- ...SLAB_TARGETS,
483
- ...ROOF_TARGETS,
484
- ...STAIR_AND_FENCE_TARGETS,
485
- ...CEILING_TARGETS,
486
- ],
487
464
  previewColor: '#ffffff',
488
465
  preset: {
489
466
  maps: {},
@@ -514,8 +491,8 @@ export const MATERIAL_CATALOG = [
514
491
  {
515
492
  id: 'preset-metal',
516
493
  label: 'Metal',
494
+ category: 'other',
517
495
  description: 'Brushed metal finish',
518
- targets: [...WALL_TARGETS, ...SLAB_TARGETS],
519
496
  previewColor: '#c0c0c0',
520
497
  preset: {
521
498
  maps: {},
@@ -546,8 +523,8 @@ export const MATERIAL_CATALOG = [
546
523
  {
547
524
  id: 'preset-glass',
548
525
  label: 'Glass',
526
+ category: 'other',
549
527
  description: 'Light glass finish',
550
- targets: [...WALL_TARGETS, ...SLAB_TARGETS],
551
528
  previewColor: '#87ceeb',
552
529
  preset: {
553
530
  maps: {},
@@ -576,8 +553,8 @@ export const MATERIAL_CATALOG = [
576
553
  },
577
554
  },
578
555
  ];
579
- export function getMaterialsForTarget(target) {
580
- return MATERIAL_CATALOG.filter((item) => item.targets.includes(target));
556
+ export function getMaterialsForCategory(category) {
557
+ return MATERIAL_CATALOG.filter((item) => item.category === category);
581
558
  }
582
559
  export function getCatalogMaterialById(id) {
583
560
  if (!id)
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Scheme allowlist for asset-like URLs embedded in scene graphs.
4
+ *
5
+ * Phase 3 security audit: `scan.url`, `guide.url`, `material.texture.url`, and
6
+ * `item.asset.src` were previously bare `z.string()`. That meant an
7
+ * attacker-crafted scene loaded in the editor could beacon to arbitrary URLs
8
+ * (e.g. `javascript:`, `file:///etc/passwd`, `http://169.254.169.254/...`).
9
+ *
10
+ * This validator rejects URLs that don't match the scheme allowlist below.
11
+ */
12
+ declare const ALLOWED_SCHEMES: readonly ["asset:", "blob:", "https:", "data:image/"];
13
+ /**
14
+ * Optional environment variable that narrows which `https:` origins are
15
+ * accepted. Set to a comma-separated list (e.g. `https://cdn.pascal.app`).
16
+ * When unset, any `https:` origin is permitted.
17
+ */
18
+ export declare const ALLOWED_ORIGINS_ENV = "PASCAL_ALLOWED_ASSET_ORIGINS";
19
+ /**
20
+ * Zod validator for asset-style URL fields. Accepts:
21
+ * - `asset://…` internal handles
22
+ * - `blob:…` in-memory references
23
+ * - `data:image/…` inline images (not `data:text/html` or other types)
24
+ * - `/…` app-relative paths
25
+ * - `https://…` public URLs (optionally narrowed to an env allowlist)
26
+ * - `http://localhost[:port]/…` or `http://127.0.0.1/…` for local dev
27
+ *
28
+ * Rejects every other scheme, including `javascript:`, `file:`, `ftp:`,
29
+ * and `data:text/html`, as well as empty strings and non-URL garbage.
30
+ */
31
+ export declare const AssetUrl: z.ZodString;
32
+ export type AssetUrl = z.infer<typeof AssetUrl>;
33
+ export { ALLOWED_SCHEMES };
34
+ //# sourceMappingURL=asset-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../../src/schema/asset-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;GASG;AACH,QAAA,MAAM,eAAe,uDAAwD,CAAA;AAE7E;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AAuCjE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,QAAQ,aAGnB,CAAA;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAA;AAG/C,OAAO,EAAE,eAAe,EAAE,CAAA"}
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Scheme allowlist for asset-like URLs embedded in scene graphs.
4
+ *
5
+ * Phase 3 security audit: `scan.url`, `guide.url`, `material.texture.url`, and
6
+ * `item.asset.src` were previously bare `z.string()`. That meant an
7
+ * attacker-crafted scene loaded in the editor could beacon to arbitrary URLs
8
+ * (e.g. `javascript:`, `file:///etc/passwd`, `http://169.254.169.254/...`).
9
+ *
10
+ * This validator rejects URLs that don't match the scheme allowlist below.
11
+ */
12
+ const ALLOWED_SCHEMES = ['asset:', 'blob:', 'https:', 'data:image/'];
13
+ /**
14
+ * Optional environment variable that narrows which `https:` origins are
15
+ * accepted. Set to a comma-separated list (e.g. `https://cdn.pascal.app`).
16
+ * When unset, any `https:` origin is permitted.
17
+ */
18
+ export const ALLOWED_ORIGINS_ENV = 'PASCAL_ALLOWED_ASSET_ORIGINS';
19
+ // Narrow access to the environment variable without requiring @types/node in
20
+ // this package. The core package ships to both browser and Node contexts.
21
+ function readAllowedOrigins() {
22
+ const g = globalThis;
23
+ const value = g.process?.env?.[ALLOWED_ORIGINS_ENV];
24
+ if (!value)
25
+ return undefined;
26
+ const list = value
27
+ .split(',')
28
+ .map((s) => s.trim())
29
+ .filter((s) => s.length > 0);
30
+ return list.length > 0 ? list : undefined;
31
+ }
32
+ function isAllowedAssetUrl(url) {
33
+ if (typeof url !== 'string' || url.length === 0)
34
+ return false;
35
+ if (url.startsWith('asset://'))
36
+ return true; // internal handle
37
+ if (url.startsWith('blob:'))
38
+ return true; // in-memory reference
39
+ if (url.startsWith('data:image/'))
40
+ return true; // inline image only (never data:text/html)
41
+ if (url.startsWith('/'))
42
+ return true; // app-relative path
43
+ try {
44
+ const parsed = new URL(url);
45
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
46
+ return false;
47
+ // http is only permitted for localhost development
48
+ if (parsed.protocol === 'http:' && !['localhost', '127.0.0.1'].includes(parsed.hostname)) {
49
+ return false;
50
+ }
51
+ // optional env-driven origin allowlist (only enforced for https URLs)
52
+ if (parsed.protocol === 'https:') {
53
+ const allowlist = readAllowedOrigins();
54
+ if (allowlist)
55
+ return allowlist.includes(parsed.origin);
56
+ }
57
+ return true;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ /**
64
+ * Zod validator for asset-style URL fields. Accepts:
65
+ * - `asset://…` internal handles
66
+ * - `blob:…` in-memory references
67
+ * - `data:image/…` inline images (not `data:text/html` or other types)
68
+ * - `/…` app-relative paths
69
+ * - `https://…` public URLs (optionally narrowed to an env allowlist)
70
+ * - `http://localhost[:port]/…` or `http://127.0.0.1/…` for local dev
71
+ *
72
+ * Rejects every other scheme, including `javascript:`, `file:`, `ftp:`,
73
+ * and `data:text/html`, as well as empty strings and non-URL garbage.
74
+ */
75
+ export const AssetUrl = z.string().refine(isAllowedAssetUrl, {
76
+ message: 'URL must be asset://, blob:, data:image/, /path, or https://. http://localhost allowed for dev.',
77
+ });
78
+ // re-export the scheme allowlist for documentation / downstream validators
79
+ export { ALLOWED_SCHEMES };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=asset-url.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-url.test.d.ts","sourceRoot":"","sources":["../../src/schema/asset-url.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,138 @@
1
+ // @ts-expect-error — bun:test is provided by the Bun runtime; core does not
2
+ // depend on @types/bun so the import type is unresolved at compile time.
3
+ // The tsconfig in packages/core still emits this file; the @ts-expect-error
4
+ // keeps the build green while letting `bun test` pick it up normally.
5
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
6
+ import { ALLOWED_ORIGINS_ENV, AssetUrl } from './asset-url';
7
+ function isValid(url) {
8
+ return AssetUrl.safeParse(url).success;
9
+ }
10
+ describe('AssetUrl', () => {
11
+ describe('allowed URLs', () => {
12
+ const cases = [
13
+ ['asset://abc', 'internal asset handle'],
14
+ ['asset://catalog/items/chair-1', 'nested asset handle'],
15
+ ['blob:http://example.com/uuid-1234', 'blob URL with http inner'],
16
+ ['blob:https://example.com/uuid-5678', 'blob URL with https inner'],
17
+ ['https://cdn.example.com/a.glb', 'https CDN URL'],
18
+ ['https://cdn.example.com/models/chair.glb?v=2', 'https URL with query string'],
19
+ ['http://localhost:3000/x', 'http localhost with port'],
20
+ ['http://localhost/x', 'http localhost without port'],
21
+ ['http://127.0.0.1:8080/texture.png', 'http 127.0.0.1 loopback'],
22
+ ['/public/a.glb', 'app-relative path'],
23
+ ['/material/wood1/albedoMap_basecolor.jpg', 'relative path deep'],
24
+ ['data:image/png;base64,AAA', 'inline PNG data URL'],
25
+ ['data:image/jpeg;base64,/9j/', 'inline JPEG data URL'],
26
+ ['data:image/webp;base64,UklGR', 'inline WebP data URL'],
27
+ ['data:image/svg+xml,%3Csvg%3E', 'inline SVG data URL'],
28
+ ];
29
+ for (const [url, label] of cases) {
30
+ test(`accepts ${label}: ${url}`, () => {
31
+ expect(isValid(url)).toBe(true);
32
+ });
33
+ }
34
+ });
35
+ describe('rejected URLs', () => {
36
+ const cases = [
37
+ ['javascript:alert(1)', 'javascript scheme'],
38
+ ['JAVASCRIPT:alert(1)', 'javascript scheme uppercase'],
39
+ ['file:///etc/passwd', 'file scheme'],
40
+ ['file://C:/Windows/System32/config', 'file scheme Windows'],
41
+ ['http://evil.com/', 'non-loopback http'],
42
+ ['http://example.com:3000/x', 'http on non-loopback host'],
43
+ ['http://169.254.169.254/latest/meta-data/', 'http on link-local (cloud metadata)'],
44
+ ['data:text/html,<script>alert(1)</script>', 'data text/html'],
45
+ ['data:application/javascript,alert(1)', 'data application/javascript'],
46
+ ['data:text/plain,hi', 'data text/plain'],
47
+ ['ftp://a.b.com', 'ftp scheme'],
48
+ ['ws://example.com/', 'websocket scheme'],
49
+ ['vbscript:msgbox', 'vbscript scheme'],
50
+ ['', 'empty string'],
51
+ ['not a url at all', 'non-url string'],
52
+ ['://missing-scheme', 'malformed'],
53
+ ];
54
+ for (const [url, label] of cases) {
55
+ test(`rejects ${label}: ${url}`, () => {
56
+ expect(isValid(url)).toBe(false);
57
+ });
58
+ }
59
+ });
60
+ describe(`env allowlist via ${ALLOWED_ORIGINS_ENV}`, () => {
61
+ const g = globalThis;
62
+ const original = g.process?.env?.[ALLOWED_ORIGINS_ENV];
63
+ beforeEach(() => {
64
+ if (g.process?.env)
65
+ delete g.process.env[ALLOWED_ORIGINS_ENV];
66
+ });
67
+ afterEach(() => {
68
+ if (!g.process?.env)
69
+ return;
70
+ if (original === undefined) {
71
+ delete g.process.env[ALLOWED_ORIGINS_ENV];
72
+ }
73
+ else {
74
+ g.process.env[ALLOWED_ORIGINS_ENV] = original;
75
+ }
76
+ });
77
+ test('single origin allowlist accepts matching https URL', () => {
78
+ if (!g.process?.env)
79
+ return; // browser-only runtime
80
+ g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
81
+ expect(isValid('https://cdn.pascal.app/a.glb')).toBe(true);
82
+ expect(isValid('https://cdn.pascal.app/deep/path?q=1')).toBe(true);
83
+ });
84
+ test('single origin allowlist rejects non-matching https URL', () => {
85
+ if (!g.process?.env)
86
+ return;
87
+ g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
88
+ expect(isValid('https://cdn.other.com/a.glb')).toBe(false);
89
+ expect(isValid('https://attacker.example.com/x')).toBe(false);
90
+ });
91
+ test('multi-origin allowlist accepts any listed origin', () => {
92
+ if (!g.process?.env)
93
+ return;
94
+ g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app, https://assets.pascal.app';
95
+ expect(isValid('https://cdn.pascal.app/a.glb')).toBe(true);
96
+ expect(isValid('https://assets.pascal.app/tex.webp')).toBe(true);
97
+ expect(isValid('https://third.example.com/x')).toBe(false);
98
+ });
99
+ test('allowlist ignores trailing / in URL path (origin match only)', () => {
100
+ if (!g.process?.env)
101
+ return;
102
+ g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
103
+ expect(isValid('https://cdn.pascal.app/')).toBe(true);
104
+ expect(isValid('https://cdn.pascal.app')).toBe(true);
105
+ });
106
+ test('empty allowlist behaves like unset', () => {
107
+ if (!g.process?.env)
108
+ return;
109
+ g.process.env[ALLOWED_ORIGINS_ENV] = '';
110
+ expect(isValid('https://cdn.other.com/a.glb')).toBe(true);
111
+ });
112
+ test('allowlist does not restrict non-https schemes', () => {
113
+ if (!g.process?.env)
114
+ return;
115
+ g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
116
+ // these should still pass because they match earlier scheme-based branches
117
+ expect(isValid('asset://x')).toBe(true);
118
+ expect(isValid('blob:https://example.com/abc')).toBe(true);
119
+ expect(isValid('data:image/png;base64,AAA')).toBe(true);
120
+ expect(isValid('/public/a.glb')).toBe(true);
121
+ expect(isValid('http://localhost:3000/x')).toBe(true);
122
+ });
123
+ test('allowlist rejects subdomain spoofing', () => {
124
+ if (!g.process?.env)
125
+ return;
126
+ g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app';
127
+ expect(isValid('https://cdn.pascal.app.evil.com/x')).toBe(false);
128
+ expect(isValid('https://evil.com/cdn.pascal.app')).toBe(false);
129
+ });
130
+ test('allowlist respects ports', () => {
131
+ if (!g.process?.env)
132
+ return;
133
+ g.process.env[ALLOWED_ORIGINS_ENV] = 'https://cdn.pascal.app:8443';
134
+ expect(isValid('https://cdn.pascal.app:8443/x')).toBe(true);
135
+ expect(isValid('https://cdn.pascal.app/x')).toBe(false);
136
+ });
137
+ });
138
+ });
@@ -5,25 +5,27 @@ export type { MaterialMapProperties, MaterialMaps, MaterialPresetPayload, Materi
5
5
  export { DEFAULT_MATERIALS, MaterialMapPropertiesSchema, MaterialMapsSchema, MaterialPreset, MaterialPresetPayloadSchema, MaterialProperties, MaterialSchema, MaterialTarget, resolveMaterial, TextureWrapMode, } from './material';
6
6
  export { BuildingNode } from './nodes/building';
7
7
  export { CeilingNode } from './nodes/ceiling';
8
+ export { COLUMN_PRESETS, ColumnBaseStyle, ColumnCapitalStyle, ColumnCarvingPlacement, ColumnCrossSection, ColumnNode, ColumnPanelShape, type ColumnPresetId, ColumnRingPlacement, ColumnShaftDetail, ColumnShaftProfile, ColumnStyle, } from './nodes/column';
8
9
  export { DoorNode, DoorSegment } from './nodes/door';
9
10
  export { FenceBaseStyle, FenceNode, FenceStyle } from './nodes/fence';
10
- export { GuideNode } from './nodes/guide';
11
+ export { GuideNode, GuideScaleReference } from './nodes/guide';
11
12
  export type { AnimationEffect, Asset, AssetInput, Control, Effect, Interactive, LightEffect, SliderControl, TemperatureControl, ToggleControl, } from './nodes/item';
12
- export { getScaledDimensions, ItemNode } from './nodes/item';
13
+ export { getScaledDimensions, ItemNode, isLowProfileItemSurface, LOW_PROFILE_ITEM_SURFACE_MAX_HEIGHT, } from './nodes/item';
13
14
  export { LevelNode } from './nodes/level';
14
- export { getEffectiveRoofSurfaceMaterial, RoofNode } from './nodes/roof';
15
15
  export type { RoofSurfaceMaterialRole, RoofSurfaceMaterialSpec } from './nodes/roof';
16
+ export { getEffectiveRoofSurfaceMaterial, RoofNode } from './nodes/roof';
16
17
  export { RoofSegmentNode, RoofType } from './nodes/roof-segment';
17
18
  export { ScanNode } from './nodes/scan';
18
19
  export { SiteNode } from './nodes/site';
19
20
  export { SlabNode } from './nodes/slab';
20
- export { getEffectiveStairSurfaceMaterial, StairNode, StairRailingMode, StairSlabOpeningMode, StairTopLandingMode, StairType, } from './nodes/stair';
21
+ export { SpawnNode } from './nodes/spawn';
21
22
  export type { StairSurfaceMaterialRole, StairSurfaceMaterialSpec } from './nodes/stair';
23
+ export { getEffectiveStairSurfaceMaterial, StairNode, StairRailingMode, StairSlabOpeningMode, StairTopLandingMode, StairType, } from './nodes/stair';
22
24
  export { AttachmentSide, StairSegmentNode, StairSegmentType } from './nodes/stair-segment';
23
25
  export { SurfaceHoleMetadata } from './nodes/surface-hole-metadata';
24
26
  export type { WallSurfaceMaterialSpec, WallSurfaceSide } from './nodes/wall';
25
27
  export { getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, WallNode, } from './nodes/wall';
26
- export { WindowNode } from './nodes/window';
28
+ export { WindowNode, WindowType } from './nodes/window';
27
29
  export { ZoneNode } from './nodes/zone';
28
30
  export type { AnyNodeId, AnyNodeType } from './types';
29
31
  export { AnyNode } from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAEvC,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACxF,YAAY,EACV,qBAAqB,EACrB,YAAY,EACZ,qBAAqB,EACrB,cAAc,IAAI,mBAAmB,EACrC,eAAe,IAAI,oBAAoB,GACxC,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC3B,kBAAkB,EAClB,cAAc,EACd,2BAA2B,EAC3B,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,YAAY,EACV,eAAe,EACf,KAAK,EACL,UAAU,EACV,OAAO,EACP,MAAM,EACN,WAAW,EACX,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,aAAa,GACd,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,+BAA+B,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACxE,YAAY,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AACpF,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EACL,gCAAgC,EAChC,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,SAAS,GACV,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA;AACvF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AACnE,YAAY,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC5E,OAAO,EACL,+BAA+B,EAC/B,+BAA+B,EAC/B,QAAQ,GACT,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAErD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"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"}
@@ -8,10 +8,11 @@ export { generateCollectionId } from './collections';
8
8
  export { DEFAULT_MATERIALS, MaterialMapPropertiesSchema, MaterialMapsSchema, MaterialPreset, MaterialPresetPayloadSchema, MaterialProperties, MaterialSchema, MaterialTarget, resolveMaterial, TextureWrapMode, } from './material';
9
9
  export { BuildingNode } from './nodes/building';
10
10
  export { CeilingNode } from './nodes/ceiling';
11
+ export { COLUMN_PRESETS, ColumnBaseStyle, ColumnCapitalStyle, ColumnCarvingPlacement, ColumnCrossSection, ColumnNode, ColumnPanelShape, ColumnRingPlacement, ColumnShaftDetail, ColumnShaftProfile, ColumnStyle, } from './nodes/column';
11
12
  export { DoorNode, DoorSegment } from './nodes/door';
12
13
  export { FenceBaseStyle, FenceNode, FenceStyle } from './nodes/fence';
13
- export { GuideNode } from './nodes/guide';
14
- export { getScaledDimensions, ItemNode } from './nodes/item';
14
+ export { GuideNode, GuideScaleReference } from './nodes/guide';
15
+ export { getScaledDimensions, ItemNode, isLowProfileItemSurface, LOW_PROFILE_ITEM_SURFACE_MAX_HEIGHT, } from './nodes/item';
15
16
  export { LevelNode } from './nodes/level';
16
17
  export { getEffectiveRoofSurfaceMaterial, RoofNode } from './nodes/roof';
17
18
  export { RoofSegmentNode, RoofType } from './nodes/roof-segment';
@@ -19,11 +20,12 @@ export { ScanNode } from './nodes/scan';
19
20
  // Nodes
20
21
  export { SiteNode } from './nodes/site';
21
22
  export { SlabNode } from './nodes/slab';
23
+ export { SpawnNode } from './nodes/spawn';
22
24
  export { getEffectiveStairSurfaceMaterial, StairNode, StairRailingMode, StairSlabOpeningMode, StairTopLandingMode, StairType, } from './nodes/stair';
23
25
  export { AttachmentSide, StairSegmentNode, StairSegmentType } from './nodes/stair-segment';
24
26
  export { SurfaceHoleMetadata } from './nodes/surface-hole-metadata';
25
27
  export { getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, WallNode, } from './nodes/wall';
26
- export { WindowNode } from './nodes/window';
28
+ export { WindowNode, WindowType } from './nodes/window';
27
29
  export { ZoneNode } from './nodes/zone';
28
30
  // Union types
29
31
  export { AnyNode } from './types';
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  export declare const MaterialPreset: z.ZodEnum<{
3
- custom: "custom";
4
3
  white: "white";
5
4
  brick: "brick";
6
5
  concrete: "concrete";
@@ -10,6 +9,7 @@ export declare const MaterialPreset: z.ZodEnum<{
10
9
  plaster: "plaster";
11
10
  tile: "tile";
12
11
  marble: "marble";
12
+ custom: "custom";
13
13
  }>;
14
14
  export type MaterialPreset = z.infer<typeof MaterialPreset>;
15
15
  export declare const MaterialProperties: z.ZodObject<{
@@ -28,7 +28,6 @@ export type MaterialProperties = z.infer<typeof MaterialProperties>;
28
28
  export declare const MaterialSchema: z.ZodObject<{
29
29
  id: z.ZodOptional<z.ZodString>;
30
30
  preset: z.ZodOptional<z.ZodEnum<{
31
- custom: "custom";
32
31
  white: "white";
33
32
  brick: "brick";
34
33
  concrete: "concrete";
@@ -38,6 +37,7 @@ export declare const MaterialSchema: z.ZodObject<{
38
37
  plaster: "plaster";
39
38
  tile: "tile";
40
39
  marble: "marble";
40
+ custom: "custom";
41
41
  }>>;
42
42
  properties: z.ZodOptional<z.ZodObject<{
43
43
  color: z.ZodDefault<z.ZodString>;
@@ -65,6 +65,7 @@ export declare const MaterialTarget: z.ZodEnum<{
65
65
  stair: "stair";
66
66
  "stair-segment": "stair-segment";
67
67
  fence: "fence";
68
+ column: "column";
68
69
  slab: "slab";
69
70
  ceiling: "ceiling";
70
71
  door: "door";
@@ -1 +1 @@
1
- {"version":3,"file":"material.d.ts","sourceRoot":"","sources":["../../src/schema/material.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,cAAc;;;;;;;;;;;EAWzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,eAAO,MAAM,kBAAkB;;;;;;;;;;;iBAO7B,CAAA;AACF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAEnE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAWzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,eAAO,MAAM,cAAc;;;;;;;;;;;EAWzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,eAAO,MAAM,eAAe;;;;EAAsD,CAAA;AAClF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAE7D,eAAO,MAAM,kBAAkB;;;;;;;;;;;iBAW7B,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE7D,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqBtC,CAAA;AACF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAE/E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGtC,CAAA;AACF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAE/E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,cAAc,EAAE,kBAAkB,CAiFxE,CAAA;AAED,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,kBAAkB,CAiB7E"}
1
+ {"version":3,"file":"material.d.ts","sourceRoot":"","sources":["../../src/schema/material.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,cAAc;;;;;;;;;;;EAWzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,eAAO,MAAM,kBAAkB;;;;;;;;;;;iBAO7B,CAAA;AACF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAEnE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAWzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,eAAO,MAAM,cAAc;;;;;;;;;;;;EAYzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,eAAO,MAAM,eAAe;;;;EAAsD,CAAA;AAClF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAE7D,eAAO,MAAM,kBAAkB;;;;;;;;;;;iBAW7B,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE7D,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqBtC,CAAA;AACF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAE/E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGtC,CAAA;AACF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAE/E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,cAAc,EAAE,kBAAkB,CAiFxE,CAAA;AAED,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,kBAAkB,CAiB7E"}
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { AssetUrl } from './asset-url';
2
3
  export const MaterialPreset = z.enum([
3
4
  'white',
4
5
  'brick',
@@ -25,7 +26,7 @@ export const MaterialSchema = z.object({
25
26
  properties: MaterialProperties.optional(),
26
27
  texture: z
27
28
  .object({
28
- url: z.string(),
29
+ url: AssetUrl,
29
30
  repeat: z.tuple([z.number(), z.number()]).optional(),
30
31
  scale: z.number().optional(),
31
32
  })
@@ -38,6 +39,7 @@ export const MaterialTarget = z.enum([
38
39
  'stair',
39
40
  'stair-segment',
40
41
  'fence',
42
+ 'column',
41
43
  'slab',
42
44
  'ceiling',
43
45
  'door',
@@ -45,16 +47,16 @@ export const MaterialTarget = z.enum([
45
47
  ]);
46
48
  export const TextureWrapMode = z.enum(['Repeat', 'ClampToEdge', 'MirroredRepeat']);
47
49
  export const MaterialMapsSchema = z.object({
48
- albedoMap: z.string().optional(),
49
- metalnessMap: z.string().optional(),
50
- roughnessMap: z.string().optional(),
51
- normalMap: z.string().optional(),
52
- displacementMap: z.string().optional(),
53
- aoMap: z.string().optional(),
54
- emissiveMap: z.string().optional(),
55
- bumpMap: z.string().optional(),
56
- alphaMap: z.string().optional(),
57
- lightMap: z.string().optional(),
50
+ albedoMap: AssetUrl.optional(),
51
+ metalnessMap: AssetUrl.optional(),
52
+ roughnessMap: AssetUrl.optional(),
53
+ normalMap: AssetUrl.optional(),
54
+ displacementMap: AssetUrl.optional(),
55
+ aoMap: AssetUrl.optional(),
56
+ emissiveMap: AssetUrl.optional(),
57
+ bumpMap: AssetUrl.optional(),
58
+ alphaMap: AssetUrl.optional(),
59
+ lightMap: AssetUrl.optional(),
58
60
  });
59
61
  export const MaterialMapPropertiesSchema = z.object({
60
62
  color: z.string().default('#ffffff'),
@@ -21,7 +21,6 @@ export declare const CeilingNode: z.ZodObject<{
21
21
  material: z.ZodOptional<z.ZodObject<{
22
22
  id: z.ZodOptional<z.ZodString>;
23
23
  preset: z.ZodOptional<z.ZodEnum<{
24
- custom: "custom";
25
24
  white: "white";
26
25
  brick: "brick";
27
26
  concrete: "concrete";
@@ -31,6 +30,7 @@ export declare const CeilingNode: z.ZodObject<{
31
30
  plaster: "plaster";
32
31
  tile: "tile";
33
32
  marble: "marble";
33
+ custom: "custom";
34
34
  }>>;
35
35
  properties: z.ZodOptional<z.ZodObject<{
36
36
  color: z.ZodDefault<z.ZodString>;