@pascal-app/core 0.3.3 → 0.4.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 (64) hide show
  1. package/dist/events/bus.d.ts +4 -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 +3 -0
  6. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +1 -1
  7. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  8. package/dist/hooks/spatial-grid/spatial-grid-sync.js +11 -3
  9. package/dist/index.d.ts +6 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -8
  12. package/dist/materials.d.ts +10 -0
  13. package/dist/materials.d.ts.map +1 -0
  14. package/dist/materials.js +22 -0
  15. package/dist/schema/index.d.ts +3 -1
  16. package/dist/schema/index.d.ts.map +1 -1
  17. package/dist/schema/index.js +3 -1
  18. package/dist/schema/material.d.ts +2 -2
  19. package/dist/schema/nodes/ceiling.d.ts +1 -1
  20. package/dist/schema/nodes/door.d.ts +1 -1
  21. package/dist/schema/nodes/item.d.ts +2 -2
  22. package/dist/schema/nodes/level.d.ts +1 -1
  23. package/dist/schema/nodes/level.d.ts.map +1 -1
  24. package/dist/schema/nodes/level.js +2 -0
  25. package/dist/schema/nodes/roof-segment.d.ts +1 -1
  26. package/dist/schema/nodes/roof.d.ts +1 -1
  27. package/dist/schema/nodes/site.d.ts +1 -1
  28. package/dist/schema/nodes/slab.d.ts +1 -1
  29. package/dist/schema/nodes/stair-segment.d.ts +81 -0
  30. package/dist/schema/nodes/stair-segment.d.ts.map +1 -0
  31. package/dist/schema/nodes/stair-segment.js +42 -0
  32. package/dist/schema/nodes/stair.d.ts +56 -0
  33. package/dist/schema/nodes/stair.d.ts.map +1 -0
  34. package/dist/schema/nodes/stair.js +22 -0
  35. package/dist/schema/nodes/wall.d.ts +1 -1
  36. package/dist/schema/nodes/window.d.ts +1 -1
  37. package/dist/schema/types.d.ts +128 -10
  38. package/dist/schema/types.d.ts.map +1 -1
  39. package/dist/schema/types.js +4 -0
  40. package/dist/store/actions/node-actions.d.ts.map +1 -1
  41. package/dist/store/actions/node-actions.js +25 -29
  42. package/dist/store/use-live-transforms.d.ts +14 -0
  43. package/dist/store/use-live-transforms.d.ts.map +1 -0
  44. package/dist/store/use-live-transforms.js +20 -0
  45. package/dist/store/use-scene.d.ts +2 -5
  46. package/dist/store/use-scene.d.ts.map +1 -1
  47. package/dist/store/use-scene.js +25 -15
  48. package/dist/systems/door/door-system.d.ts.map +1 -1
  49. package/dist/systems/door/door-system.js +1 -17
  50. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  51. package/dist/systems/roof/roof-system.js +18 -0
  52. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  53. package/dist/systems/slab/slab-system.js +71 -26
  54. package/dist/systems/stair/stair-system.d.ts +2 -0
  55. package/dist/systems/stair/stair-system.d.ts.map +1 -0
  56. package/dist/systems/stair/stair-system.js +354 -0
  57. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  58. package/dist/systems/wall/wall-system.js +2 -0
  59. package/dist/systems/window/window-system.d.ts.map +1 -1
  60. package/dist/systems/window/window-system.js +8 -24
  61. package/dist/utils/clone-scene-graph.d.ts +25 -1
  62. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  63. package/dist/utils/clone-scene-graph.js +160 -5
  64. package/package.json +6 -1
@@ -55,11 +55,10 @@ const useScene = create()(temporal((set, get) => ({
55
55
  dirtyNodes: new Set(),
56
56
  // 4. Collections
57
57
  collections: {},
58
+ // 5. Read-only lock
59
+ readOnly: false,
60
+ setReadOnly: (readOnly) => set({ readOnly }),
58
61
  unloadScene: () => {
59
- // Clear temporal tracking to prevent memory leaks from stale node references
60
- prevPastLength = 0;
61
- prevFutureLength = 0;
62
- prevNodesSnapshot = null;
63
62
  set({
64
63
  nodes: {},
65
64
  rootNodeIds: [],
@@ -74,14 +73,22 @@ const useScene = create()(temporal((set, get) => ({
74
73
  setScene: (nodes, rootNodeIds) => {
75
74
  // Apply backward compatibility migrations
76
75
  const patchedNodes = migrateNodes(nodes);
76
+ // Remove orphans: nodes whose parentId points to a non-existent node
77
+ const cleanedNodes = { ...patchedNodes };
78
+ for (const node of Object.values(cleanedNodes)) {
79
+ if (node.parentId && !cleanedNodes[node.parentId]) {
80
+ console.warn('[Scene] Removing orphan node', node.id, '(parentId', node.parentId, 'not found)');
81
+ delete cleanedNodes[node.id];
82
+ }
83
+ }
77
84
  set({
78
- nodes: patchedNodes,
85
+ nodes: cleanedNodes,
79
86
  rootNodeIds,
80
87
  dirtyNodes: new Set(),
81
88
  collections: {},
82
89
  });
83
90
  // Mark all nodes as dirty to trigger re-validation
84
- Object.values(patchedNodes).forEach((node) => {
91
+ Object.values(cleanedNodes).forEach((node) => {
85
92
  get().markDirty(node.id);
86
93
  });
87
94
  },
@@ -129,6 +136,8 @@ const useScene = create()(temporal((set, get) => ({
129
136
  deleteNode: (id) => nodeActions.deleteNodesAction(set, get, [id]),
130
137
  // --- COLLECTIONS ---
131
138
  createCollection: (name, nodeIds = []) => {
139
+ if (get().readOnly)
140
+ return '';
132
141
  const id = generateCollectionId();
133
142
  const collection = { id, name, nodeIds };
134
143
  set((state) => {
@@ -147,6 +156,8 @@ const useScene = create()(temporal((set, get) => ({
147
156
  return id;
148
157
  },
149
158
  deleteCollection: (id) => {
159
+ if (get().readOnly)
160
+ return;
150
161
  set((state) => {
151
162
  const col = state.collections[id];
152
163
  const nextCollections = { ...state.collections };
@@ -166,6 +177,8 @@ const useScene = create()(temporal((set, get) => ({
166
177
  });
167
178
  },
168
179
  updateCollection: (id, data) => {
180
+ if (get().readOnly)
181
+ return;
169
182
  set((state) => {
170
183
  const col = state.collections[id];
171
184
  if (!col)
@@ -174,6 +187,8 @@ const useScene = create()(temporal((set, get) => ({
174
187
  });
175
188
  },
176
189
  addToCollection: (id, nodeId) => {
190
+ if (get().readOnly)
191
+ return;
177
192
  set((state) => {
178
193
  const col = state.collections[id];
179
194
  if (!col || col.nodeIds.includes(nodeId))
@@ -194,6 +209,8 @@ const useScene = create()(temporal((set, get) => ({
194
209
  });
195
210
  },
196
211
  removeFromCollection: (id, nodeId) => {
212
+ if (get().readOnly)
213
+ return;
197
214
  set((state) => {
198
215
  const col = state.collections[id];
199
216
  if (!col)
@@ -227,19 +244,12 @@ export default useScene;
227
244
  let prevPastLength = 0;
228
245
  let prevFutureLength = 0;
229
246
  let prevNodesSnapshot = null;
230
- /**
231
- * Clears temporal history tracking variables to prevent memory leaks.
232
- * Should be called when unloading a scene to release node references.
233
- */
234
- export function clearTemporalTracking() {
247
+ export function clearSceneHistory() {
248
+ useScene.temporal.getState().clear();
235
249
  prevPastLength = 0;
236
250
  prevFutureLength = 0;
237
251
  prevNodesSnapshot = null;
238
252
  }
239
- export function clearSceneHistory() {
240
- useScene.temporal.getState().clear();
241
- clearTemporalTracking();
242
- }
243
253
  // Subscribe to the temporal store (Undo/Redo events)
244
254
  useScene.temporal.subscribe((state) => {
245
255
  const currentPastLength = state.pastStates.length;
@@ -1 +1 @@
1
- {"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,UAAU,YA2BtB,CAAA"}
1
+ {"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"AAUA,eAAO,MAAM,UAAU,YA2BtB,CAAA"}
@@ -1,24 +1,8 @@
1
1
  import { useFrame } from '@react-three/fiber';
2
2
  import * as THREE from 'three';
3
- import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
4
3
  import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
4
+ import { baseMaterial, glassMaterial } from '../../materials';
5
5
  import useScene from '../../store/use-scene';
6
- const baseMaterial = new MeshStandardNodeMaterial({
7
- name: 'door-base',
8
- color: '#f2f0ed',
9
- roughness: 0.5,
10
- metalness: 0,
11
- });
12
- const glassMaterial = new MeshStandardNodeMaterial({
13
- name: 'door-glass',
14
- color: 'lightblue',
15
- roughness: 0.05,
16
- metalness: 0.1,
17
- transparent: true,
18
- opacity: 0.35,
19
- side: DoubleSide,
20
- depthWrite: false,
21
- });
22
6
  // Invisible material for root mesh — used as selection hitbox only
23
7
  const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false });
24
8
  export const DoorSystem = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAGvE,OAAO,KAAK,EAAgC,eAAe,EAAE,MAAM,cAAc,CAAA;AAwBjF,eAAO,MAAM,UAAU,YAqFtB,CAAA;AAmLD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,eAAe,GACpB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,KAAK,CAAA;CAAE,GAAG,IAAI,CAkRlF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CAAC,cAAc,CAoDvF"}
1
+ {"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAGvE,OAAO,KAAK,EAAgC,eAAe,EAAE,MAAM,cAAc,CAAA;AA+BjF,eAAO,MAAM,UAAU,YAuFtB,CAAA;AAuLD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,eAAe,GACpB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,KAAK,CAAA;CAAE,GAAG,IAAI,CAsRlF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CAAC,cAAc,CAoDvF"}
@@ -7,7 +7,13 @@ import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
7
7
  import useScene from '../../store/use-scene';
8
8
  const csgEvaluator = new Evaluator();
9
9
  csgEvaluator.useGroups = true;
10
+ csgEvaluator.consolidateGroups = false; // shared dummyMats across brushes causes consolidation to misalign groupIndices vs groupOrder indices → crash
10
11
  csgEvaluator.attributes = ['position', 'normal'];
12
+ function prepareBrushForCSG(brush) {
13
+ brush.geometry.computeBoundsTree = computeBoundsTree;
14
+ brush.geometry.computeBoundsTree({ maxLeafSize: 10 });
15
+ brush.updateMatrixWorld();
16
+ }
11
17
  // Pooled objects to avoid per-frame allocation in updateMergedRoofGeometry
12
18
  const _matrix = new THREE.Matrix4();
13
19
  const _position = new THREE.Vector3();
@@ -71,6 +77,9 @@ export const RoofSystem = () => {
71
77
  }
72
78
  clearDirty(id);
73
79
  }
80
+ else {
81
+ clearDirty(id);
82
+ }
74
83
  // Queue the parent roof for a merged geometry update
75
84
  if (node.parentId) {
76
85
  pendingRoofUpdates.add(node.parentId);
@@ -151,6 +160,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes) {
151
160
  const next = csgEvaluator.evaluate(totalShinSlab, brushes.shinSlab, ADDITION);
152
161
  totalShinSlab.geometry.dispose();
153
162
  brushes.shinSlab.geometry.dispose();
163
+ prepareBrushForCSG(next);
154
164
  totalShinSlab = next;
155
165
  }
156
166
  else {
@@ -160,6 +170,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes) {
160
170
  const next = csgEvaluator.evaluate(totalDeckSlab, brushes.deckSlab, ADDITION);
161
171
  totalDeckSlab.geometry.dispose();
162
172
  brushes.deckSlab.geometry.dispose();
173
+ prepareBrushForCSG(next);
163
174
  totalDeckSlab = next;
164
175
  }
165
176
  else {
@@ -169,6 +180,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes) {
169
180
  const next = csgEvaluator.evaluate(totalWall, brushes.wallBrush, ADDITION);
170
181
  totalWall.geometry.dispose();
171
182
  brushes.wallBrush.geometry.dispose();
183
+ prepareBrushForCSG(next);
172
184
  totalWall = next;
173
185
  }
174
186
  else {
@@ -178,6 +190,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes) {
178
190
  const next = csgEvaluator.evaluate(totalInner, brushes.innerBrush, ADDITION);
179
191
  totalInner.geometry.dispose();
180
192
  brushes.innerBrush.geometry.dispose();
193
+ prepareBrushForCSG(next);
181
194
  totalInner = next;
182
195
  }
183
196
  else {
@@ -380,6 +393,11 @@ export function getRoofSegmentBrushes(node) {
380
393
  return null;
381
394
  if (!geo.index)
382
395
  return null;
396
+ // Strip zero-count groups — three-bvh-csg crashes with groupIndices[i] undefined
397
+ // when a group exists but covers no triangles (can happen after mergeVertices)
398
+ geo.groups = geo.groups.filter((g) => g.count > 0);
399
+ if (geo.groups.length === 0)
400
+ return null;
383
401
  geo.computeBoundsTree = computeBoundsTree;
384
402
  geo.computeBoundsTree({ maxLeafSize: 10 });
385
403
  const brush = new Brush(geo, dummyMats);
@@ -1 +1 @@
1
- {"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAOvD,eAAO,MAAM,UAAU,YAwBtB,CAAA;AAkED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC,cAAc,CAmD7E"}
1
+ {"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAOvD,eAAO,MAAM,UAAU,YAwBtB,CAAA;AAuED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC,cAAc,CAG7E"}
@@ -34,6 +34,10 @@ function updateSlabGeometry(node, mesh) {
34
34
  const newGeo = generateSlabGeometry(node);
35
35
  mesh.geometry.dispose();
36
36
  mesh.geometry = newGeo;
37
+ // For negative elevation, shift the mesh down so the top face sits at Y=elevation
38
+ // rather than at Y=0. Positive elevation stays at Y=0 (slab sits at floor level).
39
+ const elevation = node.elevation ?? 0.05;
40
+ mesh.position.y = elevation < 0 ? elevation : 0;
37
41
  }
38
42
  /** Half of default wall thickness — used to extend slab geometry under walls */
39
43
  const SLAB_OUTSET = 0.05;
@@ -89,44 +93,85 @@ function outsetPolygon(polygon, amount) {
89
93
  * Generates extruded slab geometry from polygon
90
94
  */
91
95
  export function generateSlabGeometry(slabNode) {
96
+ const elevation = slabNode.elevation ?? 0.05;
97
+ return elevation < 0 ? generatePoolGeometry(slabNode) : generatePositiveSlabGeometry(slabNode);
98
+ }
99
+ /**
100
+ * Standard slab: flat extrusion upward from Y=0 by elevation thickness.
101
+ */
102
+ function generatePositiveSlabGeometry(slabNode) {
92
103
  const polygon = outsetPolygon(slabNode.polygon, SLAB_OUTSET);
93
104
  const elevation = slabNode.elevation ?? 0.05;
94
- if (polygon.length < 3) {
105
+ if (polygon.length < 3)
95
106
  return new THREE.BufferGeometry();
96
- }
97
- // Create shape from polygon
98
- // Shape is in X-Y plane, we'll rotate to X-Z plane after extrusion
99
107
  const shape = new THREE.Shape();
100
- const firstPt = polygon[0];
101
- // Negate Y (which becomes Z) to get correct orientation after rotation
102
- shape.moveTo(firstPt[0], -firstPt[1]);
103
- for (let i = 1; i < polygon.length; i++) {
104
- const pt = polygon[i];
105
- shape.lineTo(pt[0], -pt[1]);
106
- }
108
+ shape.moveTo(polygon[0][0], -polygon[0][1]);
109
+ for (let i = 1; i < polygon.length; i++)
110
+ shape.lineTo(polygon[i][0], -polygon[i][1]);
107
111
  shape.closePath();
108
- // Add holes to the shape
109
- const holes = slabNode.holes || [];
110
- for (const holePolygon of holes) {
112
+ for (const holePolygon of slabNode.holes ?? []) {
111
113
  if (holePolygon.length < 3)
112
114
  continue;
113
115
  const holePath = new THREE.Path();
114
- const holeFirstPt = holePolygon[0];
115
- holePath.moveTo(holeFirstPt[0], -holeFirstPt[1]);
116
- for (let i = 1; i < holePolygon.length; i++) {
117
- const pt = holePolygon[i];
118
- holePath.lineTo(pt[0], -pt[1]);
119
- }
116
+ holePath.moveTo(holePolygon[0][0], -holePolygon[0][1]);
117
+ for (let i = 1; i < holePolygon.length; i++)
118
+ holePath.lineTo(holePolygon[i][0], -holePolygon[i][1]);
120
119
  holePath.closePath();
121
120
  shape.holes.push(holePath);
122
121
  }
123
- // Extrude the shape by elevation
124
- const geometry = new THREE.ExtrudeGeometry(shape, {
125
- depth: elevation,
126
- bevelEnabled: false,
127
- });
128
- // Rotate so extrusion direction (Z) becomes height direction (Y)
122
+ const geometry = new THREE.ExtrudeGeometry(shape, { depth: elevation, bevelEnabled: false });
129
123
  geometry.rotateX(-Math.PI / 2);
130
124
  geometry.computeVertexNormals();
131
125
  return geometry;
132
126
  }
127
+ /**
128
+ * Pool / recessed slab: floor cap at Y=0 (local) + inner walls up to Y=|elevation|.
129
+ * No top cap — the opening at ground level is handled by the ground occluder hole.
130
+ * mesh.position.y must be set to elevation so the floor sits at the correct world Y.
131
+ *
132
+ * Geometry is built directly in 3D (Y-up) to avoid rotation confusion:
133
+ * - floor in XZ plane at Y=0, normals pointing +Y (visible when looking down into pool)
134
+ * - walls from Y=0 to Y=depth, inward-facing normals (visible from inside pool)
135
+ */
136
+ function generatePoolGeometry(slabNode) {
137
+ const polygon = outsetPolygon(slabNode.polygon, SLAB_OUTSET);
138
+ const depth = Math.abs(slabNode.elevation ?? 0.05);
139
+ if (polygon.length < 3)
140
+ return new THREE.BufferGeometry();
141
+ const positions = [];
142
+ const indices = [];
143
+ const n = polygon.length;
144
+ // --- Floor at Y=0 ---
145
+ for (const [x, z] of polygon)
146
+ positions.push(x, 0, z);
147
+ const pts2d = polygon.map(([x, z]) => new THREE.Vector2(x, z));
148
+ const holesPts2d = (slabNode.holes ?? []).map((h) => h.map(([x, z]) => new THREE.Vector2(x, z)));
149
+ for (const hole of slabNode.holes ?? []) {
150
+ for (const [x, z] of hole)
151
+ positions.push(x, 0, z);
152
+ }
153
+ const floorTris = THREE.ShapeUtils.triangulateShape(pts2d, holesPts2d);
154
+ for (const tri of floorTris) {
155
+ // Reversed winding → normals point +Y (upward) in XZ plane
156
+ indices.push(tri[0], tri[2], tri[1]);
157
+ }
158
+ // --- Inner walls (no top cap at Y=depth) ---
159
+ // Standard winding on a CCW polygon in XZ gives inward-facing normals.
160
+ for (let i = 0; i < n; i++) {
161
+ const j = (i + 1) % n;
162
+ const [x0, z0] = polygon[i];
163
+ const [x1, z1] = polygon[j];
164
+ const vBase = positions.length / 3;
165
+ positions.push(x0, 0, z0); // v0 — floor level
166
+ positions.push(x1, 0, z1); // v1 — floor level
167
+ positions.push(x1, depth, z1); // v2 — ground level
168
+ positions.push(x0, depth, z0); // v3 — ground level
169
+ indices.push(vBase, vBase + 1, vBase + 2);
170
+ indices.push(vBase, vBase + 2, vBase + 3);
171
+ }
172
+ const geo = new THREE.BufferGeometry();
173
+ geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
174
+ geo.setIndex(indices);
175
+ geo.computeVertexNormals();
176
+ return geo;
177
+ }
@@ -0,0 +1,2 @@
1
+ export declare const StairSystem: () => null;
2
+ //# sourceMappingURL=stair-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stair-system.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-system.tsx"],"names":[],"mappings":"AAiBA,eAAO,MAAM,WAAW,YA4FvB,CAAA"}