@zylem/game-lib 0.8.4 → 0.8.6

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 (63) hide show
  1. package/dist/actions.d.ts +7 -5
  2. package/dist/api/game-lib.api.json +3424 -514
  3. package/dist/api/game-lib.api.md +178 -10
  4. package/dist/api/game-lib.d.ts +481 -11
  5. package/dist/api/game-lib.typedoc.json +220347 -136841
  6. package/dist/base-node-idopiSw5.d.ts +96 -0
  7. package/dist/bridge.d.ts +27 -5
  8. package/dist/bridge.js +1 -1
  9. package/dist/catalog.d.ts +77 -0
  10. package/dist/catalog.js +1 -0
  11. package/dist/{chunk-CM5YRNBJ.js → chunk-7NYP4YAG.js} +1 -1
  12. package/dist/chunk-AE3CTVHY.js +1 -0
  13. package/dist/chunk-BCFFS3FP.js +1 -0
  14. package/dist/chunk-BMRFDDMR.js +1 -0
  15. package/dist/chunk-DECK4UEC.js +1 -0
  16. package/dist/chunk-DQQA3VJH.js +1 -0
  17. package/dist/chunk-FYQNZCIP.js +1 -0
  18. package/dist/chunk-GHJQ2HKI.js +1 -0
  19. package/dist/chunk-LD4KDRYL.js +24 -0
  20. package/dist/chunk-MXO4VGDB.js +1 -0
  21. package/dist/{chunk-VIPINV7V.js → chunk-Q2YDBMVY.js} +1 -1
  22. package/dist/chunk-QDQ4QMAI.js +1 -0
  23. package/dist/{chunk-TV3AZKT4.js → chunk-QVIUZEYA.js} +1 -1
  24. package/dist/chunk-SAQA4SXW.js +1 -0
  25. package/dist/chunk-VJZMSBKP.js +1 -0
  26. package/dist/chunk-YOVE25NW.js +1 -0
  27. package/dist/{collision-builder-4TVDzXsp.d.ts → collision-builder-BckxkdmA.d.ts} +1 -1
  28. package/dist/core.d.ts +14 -11
  29. package/dist/core.js +1 -1
  30. package/dist/debug-state-B5awuDHl.d.ts +89 -0
  31. package/dist/debug.d.ts +194 -7
  32. package/dist/debug.js +1 -1
  33. package/dist/{entity-DDks47JB.d.ts → entity-BIf_a4dl.d.ts} +1 -1
  34. package/dist/{entity-BI7cSlK8.d.ts → entity-XZNGBeca.d.ts} +32 -98
  35. package/dist/{entity-type-map-COHu4fkW.d.ts → entity-type-map-CkkFMSrL.d.ts} +5 -4
  36. package/dist/entity.d.ts +9 -7
  37. package/dist/entity.js +1 -1
  38. package/dist/{game-4fsy55uN.d.ts → game-Cy8f6gJm.d.ts} +181 -17
  39. package/dist/game-default-F3KPYZ7X.js +1 -0
  40. package/dist/graphics.d.ts +6 -5
  41. package/dist/graphics.js +1 -1
  42. package/dist/{material-BKPYeBC6.d.ts → material-CxxAyJYp.d.ts} +1 -1
  43. package/dist/public.d.ts +12 -10
  44. package/dist/public.js +1 -1
  45. package/dist/runtime.d.ts +3 -3
  46. package/dist/{vector-DPbwlzgG.d.ts → simulation-body-zQw-emOr.d.ts} +1 -40
  47. package/dist/vector-D3eDzvcA.d.ts +41 -0
  48. package/dist/web-components.d.ts +12 -9
  49. package/dist/web-components.js +1 -1
  50. package/dist/{zylem-camera-B73ZNcgs.d.ts → zylem-camera-DZTpPOiD.d.ts} +12 -1
  51. package/package.json +6 -2
  52. package/dist/chunk-3I537JFL.js +0 -1
  53. package/dist/chunk-4CHZUWLF.js +0 -1
  54. package/dist/chunk-6B5JP7T5.js +0 -24
  55. package/dist/chunk-C5NEGC3S.js +0 -1
  56. package/dist/chunk-D3JO4HW2.js +0 -1
  57. package/dist/chunk-LKFF6XJC.js +0 -1
  58. package/dist/chunk-OAWK2UUY.js +0 -1
  59. package/dist/chunk-PNLNC3FA.js +0 -1
  60. package/dist/chunk-W5SXED5T.js +0 -1
  61. package/dist/debug-state-s0s-qndj.d.ts +0 -49
  62. package/dist/game-default-4PPYYGAR.js +0 -1
  63. /package/dist/{chunk-DFLI7ICU.js → chunk-MSCVL5OK.js} +0 -0
@@ -28,6 +28,7 @@ import { BridgeHandler } from '@zylem/bridge';
28
28
  import { GameLoadingPayload as BridgeLoadingPayload } from '@zylem/bridge';
29
29
  import { BridgeMessages } from '@zylem/bridge';
30
30
  import { BridgeMessageType } from '@zylem/bridge';
31
+ import { BridgePose } from '@zylem/bridge';
31
32
  import { BridgeVec3 } from '@zylem/bridge';
32
33
  import { BufferGeometry } from 'three';
33
34
  import { BurstParticlePresetOptions } from '@zylem/behaviors';
@@ -88,6 +89,7 @@ import { EntityDefinition } from '@zylem/behaviors/core';
88
89
  import { EntityHandle } from '@zylem/behaviors/core';
89
90
  import { EntitySummaryPayload } from '@zylem/bridge';
90
91
  import { EntityThumbnailPayload } from '@zylem/bridge';
92
+ import { EntityTypeDescriptor } from '@zylem/bridge';
91
93
  import { exp } from 'three/tsl';
92
94
  import { FacingAngle2DEntity } from '@zylem/behaviors';
93
95
  import { fireCooldown } from '@zylem/behaviors';
@@ -273,6 +275,7 @@ import { RuntimeTriggerRegion2DEvent } from '@zylem/behaviors/runtime-2d';
273
275
  import { RuntimeTriggerRegion2DHandle } from '@zylem/behaviors/runtime-2d';
274
276
  import { RuntimeTriggerRegion2DOptions } from '@zylem/behaviors/runtime-2d';
275
277
  import { Scene } from 'three';
278
+ import { SceneOperationPayload } from '@zylem/bridge';
276
279
  import { ScreenVisibilityBehavior } from '@zylem/behaviors';
277
280
  import { ScreenVisibilityChangeContext } from '@zylem/behaviors';
278
281
  import { ScreenVisibilityFSM } from '@zylem/behaviors';
@@ -333,6 +336,7 @@ import { StageTopDownPlane } from '@zylem/behaviors/core';
333
336
  import { Static } from '@sinclair/typebox';
334
337
  import { step } from 'three/tsl';
335
338
  import { Texture } from 'three';
339
+ import * as three from 'three';
336
340
  import { ThrusterBehavior } from '@zylem/behaviors';
337
341
  import { ThrusterBehaviorOptions } from '@zylem/behaviors';
338
342
  import { ThrusterEntity } from '@zylem/behaviors';
@@ -423,13 +427,16 @@ declare type ActorOptions = BaseNode | ZylemActorOptions;
423
427
  /**
424
428
  * Owns the stage's in-world debug/editor interaction layer.
425
429
  *
426
- * Self-manages around the global `debugState`: when debug is enabled it draws
427
- * the wasm simulation's collider wireframes, attaches mouse listeners,
428
- * raycasts into the physics world to hover/select/delete/spawn entities, and
429
- * drives the `DebugEntityCursor` highlight. When disabled it tears down
430
- * visuals but stays alive to re-activate. Centralizes all debug-tool plumbing
431
- * so `ZylemStage` doesn't have to know about raycasting, DOM input, or debug
432
- * rendering.
430
+ * Self-manages around the global `debugState`, in two layers that switch on
431
+ * independently. The tool layer — mouse listeners, physics raycasts to
432
+ * hover/select/delete/spawn, the `DebugEntityCursor` highlight and the
433
+ * `PlacementGhost` — is live whenever a tool is armed, so the toolbar's buttons
434
+ * work on their own. The debug layer — the wasm simulation's collider
435
+ * wireframes and the orbit camera — is the part `debugState.enabled` gates, and
436
+ * is torn down when it goes off while the delegate stays alive to re-activate.
437
+ *
438
+ * Centralizes all of this so `ZylemStage` doesn't have to know about
439
+ * raycasting, DOM input, or debug rendering.
433
440
  */
434
441
 
435
442
  declare type AddEntityFactory = (params: {
@@ -1217,6 +1224,14 @@ declare interface CleanupFunction<T, TGlobals extends GlobalRecord = any> {
1217
1224
  */
1218
1225
  export declare function clearGlobalSubscriptions(): void;
1219
1226
 
1227
+ /**
1228
+ * Closest point along an infinite line to a ray, as a distance along the line.
1229
+ *
1230
+ * Returns `null` when the ray is close enough to parallel with the line that the
1231
+ * closest point becomes unstable; see {@link MAX_DRAG_GAIN}.
1232
+ */
1233
+ export declare function closestAxisValue(ray: GizmoRay, origin: Vector3, axis: Vector3): number | null;
1234
+
1220
1235
  /**
1221
1236
  * Builds plain body + collider definitions consumable by the behaviors
1222
1237
  * `Simulation` (`simulation.spawn(...)`). No physics-engine types appear
@@ -1345,6 +1360,31 @@ declare interface ConeMeshOptions extends BaseMeshOptions {
1345
1360
 
1346
1361
  declare type ConeOptions = BaseNode | Partial<ZylemConeOptions>;
1347
1362
 
1363
+ /**
1364
+ * The y=0 construction plane, drawn so the snap lattice is legible.
1365
+ *
1366
+ * Two overlaid grids: fine lines at the translation snap increment, and heavier
1367
+ * lines every metre. Without the coarse pass a 0.25 grid over any useful area
1368
+ * reads as flat grey noise.
1369
+ */
1370
+
1371
+ export declare class ConstructionGrid {
1372
+ private group;
1373
+ private scene;
1374
+ private fine;
1375
+ private coarse;
1376
+ private increment;
1377
+ constructor();
1378
+ addTo(scene: Scene): void;
1379
+ /** Rebuild only when the increment actually changed; this allocates. */
1380
+ setIncrement(increment: number): void;
1381
+ setVisible(visible: boolean): void;
1382
+ get visible(): boolean;
1383
+ dispose(): void;
1384
+ private rebuild;
1385
+ private clearHelpers;
1386
+ }
1387
+
1348
1388
  export { CooldownBehavior }
1349
1389
 
1350
1390
  export { CooldownConfig }
@@ -1722,8 +1762,21 @@ declare interface DebugState {
1722
1762
  * Proxy wrappers to engine code that expects real instances.
1723
1763
  */
1724
1764
  selectedEntityId: string | null;
1765
+ /**
1766
+ * Full selection, in click order. `selectedEntityId` mirrors the first
1767
+ * entry. Only ever holds one uuid today, but the gizmo and scene operations
1768
+ * are already written against the list so multi-select needs no rework here.
1769
+ */
1770
+ selectedEntityIds: string[];
1725
1771
  /** UUID of the hovered entity. Same constraint as `selectedEntityId`. */
1726
1772
  hoveredEntityId: string | null;
1773
+ snap: SnapSettings;
1774
+ /** Whether the construction-plane grid is drawn. */
1775
+ gridVisible: boolean;
1776
+ /** Catalog type the add tool will spawn, or null when disarmed. */
1777
+ addTypeId: string | null;
1778
+ /** Creation options merged over the armed type's defaults. */
1779
+ addTypeProps: Record<string, unknown> | null;
1727
1780
  flags: Set<string>;
1728
1781
  }
1729
1782
 
@@ -1731,6 +1784,8 @@ export declare const debugState: DebugState;
1731
1784
 
1732
1785
  export declare type DebugTools = 'select' | 'translate' | 'rotate' | 'scale' | 'delete' | 'add' | 'none';
1733
1786
 
1787
+ export declare const DEFAULT_SNAP_SETTINGS: SnapSettings;
1788
+
1734
1789
  /**
1735
1790
  * Builds a ready-to-use {@link GameInputConfig} that wires up themed
1736
1791
  * virtual touch controls for a player. Internally composes
@@ -1790,6 +1845,9 @@ export { defineBehavior }
1790
1845
 
1791
1846
  export { DefineBehaviorConfig }
1792
1847
 
1848
+ /** Degrees to radians. */
1849
+ export declare function degreesToRadians(degrees: number): number;
1850
+
1793
1851
  /**
1794
1852
  * Wait for a duration. Useful inside `sequence()`.
1795
1853
  *
@@ -1829,6 +1887,22 @@ export { DestructibleMesh }
1829
1887
 
1830
1888
  export { DestructiblePrebakeWorkerAPI }
1831
1889
 
1890
+ /** A detached entity plus the pose to restore it at. */
1891
+ declare interface DetachedEntity {
1892
+ entity: any;
1893
+ position: {
1894
+ x: number;
1895
+ y: number;
1896
+ z: number;
1897
+ } | null;
1898
+ rotation: {
1899
+ x: number;
1900
+ y: number;
1901
+ z: number;
1902
+ w: number;
1903
+ } | null;
1904
+ }
1905
+
1832
1906
  export declare type DeviceProfile = 'auto' | 'desktop' | 'mobile';
1833
1907
 
1834
1908
  export declare interface DirectionalLightOptions extends BaseLightOptions {
@@ -2136,6 +2210,9 @@ export declare interface EntityTypeMap {
2136
2210
  [LINE_TYPE]: ZylemLine;
2137
2211
  }
2138
2212
 
2213
+ /** Convert Euler angles (radians, XYZ order) to a quaternion. */
2214
+ export declare function eulerToQuaternion(rotation: Vec3): Quat;
2215
+
2139
2216
  /**
2140
2217
  * Reusable delegate for event emission and subscription.
2141
2218
  * Use via composition in Game, Stage, and Entity classes.
@@ -2518,6 +2595,11 @@ export declare class GameBridge {
2518
2595
  * an editor command can never echo back as a game update.
2519
2596
  */
2520
2597
  private editorKnown;
2598
+ /**
2599
+ * True while an undo/redo is being applied, so the writes it performs are
2600
+ * not themselves recorded as new operations.
2601
+ */
2602
+ private applyingSceneOperation;
2521
2603
  constructor();
2522
2604
  /**
2523
2605
  * Subscribe to editor commands and announce the game side as ready.
@@ -2581,6 +2663,17 @@ export declare class GameBridge {
2581
2663
  queueEntityRemoved(uuids: string[]): void;
2582
2664
  /** RAF-coalesced thumbnail stream; merged by uuid per frame. */
2583
2665
  queueThumbnails(thumbnails: EntityThumbnailPayload[]): void;
2666
+ /** Publish the placeable entity types the editor's Add palette offers. */
2667
+ publishCatalog(entities: EntityTypeDescriptor[]): void;
2668
+ /**
2669
+ * Publish a committed, invertible change for the editor's undo stack.
2670
+ *
2671
+ * Sent rather than queued: coalescing merges same-type payloads within a
2672
+ * frame, which would fuse two operations into one and lose an undo step.
2673
+ */
2674
+ publishSceneOperation(op: SceneOperationPayload): void;
2675
+ /** Whether an editor is tracking scene operations for undo. */
2676
+ wantsSceneOperations(): boolean;
2584
2677
  }
2585
2678
 
2586
2679
  /**
@@ -2603,6 +2696,10 @@ export declare interface GameBridgeHost {
2603
2696
  resolveEntity(uuid: string): GameEntity<any> | null;
2604
2697
  /** Write a stage variable (editor `stage:variable:set`). */
2605
2698
  setStageVariable?(key: string, value: unknown): void;
2699
+ /** Spawn a catalog entity into the current stage (editor `entity:create`). */
2700
+ createEntity?(typeId: string, props: Record<string, unknown> | undefined, pose: BridgePose | undefined): void;
2701
+ /** Undo or redo a committed scene operation (`scene:operation:apply`). */
2702
+ applySceneOperation?(op: SceneOperationPayload, direction: 'undo' | 'redo'): void;
2606
2703
  }
2607
2704
 
2608
2705
  /**
@@ -2865,6 +2962,18 @@ export declare class GameEntity<O extends GameEntityOptions> extends BaseNode<O>
2865
2962
  physicsWorldRef: unknown;
2866
2963
  colliderDesc: SimulationColliderDefinition | undefined;
2867
2964
  custom: Record<string, any>;
2965
+ /**
2966
+ * Pristine collider definitions captured before the first scale change, so
2967
+ * scaling stays absolute rather than compounding. See
2968
+ * {@link ensureColliderBaseline}.
2969
+ */
2970
+ colliderBaseline: SimulationColliderDefinition[] | null;
2971
+ /**
2972
+ * Set when {@link GameEntity.setScale} has scaled the render object but the
2973
+ * colliders have not been rebuilt yet. Cleared by
2974
+ * {@link commitColliderScale}.
2975
+ */
2976
+ colliderScaleDirty: boolean;
2868
2977
  /** All collider descriptions for this entity (including primary) */
2869
2978
  colliderDescs: SimulationColliderDefinition[];
2870
2979
  /** Additional meshes for compound visual entities (added via add()) */
@@ -2904,6 +3013,8 @@ export declare class GameEntity<O extends GameEntityOptions> extends BaseNode<O>
2904
3013
  private trackAddedComponents;
2905
3014
  private trackedComponents;
2906
3015
  transformStore: TransformState;
3016
+ /** Absolute scale, authored by the editor. Unit scale until changed. */
3017
+ private _scale;
2907
3018
  /** Hide render target until spawn placement is confirmed. */
2908
3019
  _spawnPlacementPending: boolean;
2909
3020
  /** Author passed `position` in create options. */
@@ -2975,6 +3086,17 @@ export declare class GameEntity<O extends GameEntityOptions> extends BaseNode<O>
2975
3086
  w: number;
2976
3087
  };
2977
3088
  }): boolean;
3089
+ /** Current absolute scale. */
3090
+ getScale(): Vec3;
3091
+ /**
3092
+ * Set absolute scale on the render object.
3093
+ *
3094
+ * Colliders are *not* resized here — they are baked into the wasm
3095
+ * simulation and can only be replaced by respawning the body, which is far
3096
+ * too costly to do per drag frame. The entity is flagged instead, and
3097
+ * {@link commitColliderScale} rebuilds them once the gesture commits.
3098
+ */
3099
+ setScale(x: number, y: number, z: number): void;
2978
3100
  /**
2979
3101
  * Add meshes, collision components, or child entities to this entity.
2980
3102
  * Supports fluent chaining: `entity.add(boxMesh()).add(boxCollision())`.
@@ -3403,6 +3525,12 @@ export declare function getSelectedEntity(): GameEntity<any> | null;
3403
3525
 
3404
3526
  export declare function getSelectedEntityId(): string | null;
3405
3527
 
3528
+ /** Every selected uuid, in click order. */
3529
+ export declare function getSelectedEntityIds(): string[];
3530
+
3531
+ /** Current snap increments. */
3532
+ export declare function getSnapSettings(): SnapSettings;
3533
+
3406
3534
  /**
3407
3535
  * Get a variable from an object by path.
3408
3536
  * @example getVariable(stage1, 'totalAngle') // 0.5
@@ -3414,6 +3542,30 @@ export { getVelocity }
3414
3542
 
3415
3543
  export { getZylemBridge }
3416
3544
 
3545
+ /** Single-axis, planar, and uniform handle identifiers. */
3546
+ export declare type GizmoAxis = 'x' | 'y' | 'z' | 'xy' | 'yz' | 'xz' | 'xyz';
3547
+
3548
+ /** A raw, unsnapped drag result in world space. */
3549
+ export declare type GizmoDelta = {
3550
+ kind: 'translate';
3551
+ offset: Vector3;
3552
+ } | {
3553
+ kind: 'rotate';
3554
+ axis: Vector3;
3555
+ angle: number;
3556
+ } | {
3557
+ kind: 'scale';
3558
+ factor: Vector3;
3559
+ };
3560
+
3561
+ export declare type GizmoMode = 'translate' | 'rotate' | 'scale';
3562
+
3563
+ /** A mouse ray in world space. */
3564
+ export declare interface GizmoRay {
3565
+ origin: Vector3;
3566
+ direction: Vector3;
3567
+ }
3568
+
3417
3569
  /**
3418
3570
  * Listen for a single global key change inside an onUpdate pipeline.
3419
3571
  * Usage: onUpdate(globalChange('p1Score', (value) => { ... }))
@@ -3634,6 +3786,14 @@ declare class InstanceManager {
3634
3786
  private growPack;
3635
3787
  }
3636
3788
 
3789
+ /**
3790
+ * Intersect a ray with the plane through `origin` with the given normal.
3791
+ *
3792
+ * Returns `null` for a plane seen too close to edge-on, where the hit point
3793
+ * races away across the plane for very little pointer motion.
3794
+ */
3795
+ export declare function intersectPlane(ray: GizmoRay, origin: Vector3, normal: Vector3, target: Vector3): Vector3 | null;
3796
+
3637
3797
  /**
3638
3798
  * Check if a shader is a GLSL shader
3639
3799
  */
@@ -3651,6 +3811,8 @@ declare interface IStage {
3651
3811
  onDestroy: (callback: DestroyFunction<IStage>) => void;
3652
3812
  }
3653
3813
 
3814
+ export declare function isTransformTool(tool: DebugTools): tool is TransformTool;
3815
+
3654
3816
  /**
3655
3817
  * Check if a shader is a TSL shader
3656
3818
  */
@@ -4480,6 +4642,26 @@ declare interface PyramidMeshOptions extends BaseMeshOptions {
4480
4642
 
4481
4643
  declare type PyramidOptions = BaseNode | Partial<ZylemPyramidOptions>;
4482
4644
 
4645
+ /**
4646
+ * Pure transform math shared by the editor gizmo, the bridge adapter, and
4647
+ * their tests. No THREE scene state and no simulation access, so it can be
4648
+ * exercised in a plain node environment.
4649
+ */
4650
+
4651
+ /** Quaternion as carried by the bridge and the wasm runtime. */
4652
+ export declare interface Quat {
4653
+ x: number;
4654
+ y: number;
4655
+ z: number;
4656
+ w: number;
4657
+ }
4658
+
4659
+ /** Convert a quaternion to Euler angles (radians, XYZ order). */
4660
+ export declare function quaternionToEuler(rotation: Quat): Vec3;
4661
+
4662
+ /** Radians to degrees. */
4663
+ export declare function radiansToDegrees(radians: number): number;
4664
+
4483
4665
  export declare const RECT_TYPE: unique symbol;
4484
4666
 
4485
4667
  declare type RectOptions = BaseNode | Partial<ZylemRectOptions>;
@@ -5175,6 +5357,9 @@ export { SemanticParticlePresetOptions }
5175
5357
  */
5176
5358
  export declare function sequence(...actions: Action[]): Action;
5177
5359
 
5360
+ /** Arm the add tool with a catalog type, or pass `null` to disarm it. */
5361
+ export declare function setAddType(typeId: string | null, props?: Record<string, unknown> | null): void;
5362
+
5178
5363
  /**
5179
5364
  * Display a camera's live feed on an entity's mesh surface.
5180
5365
  *
@@ -5214,6 +5399,8 @@ export declare function setDebugTool(tool: DebugTools): void;
5214
5399
  */
5215
5400
  export declare function setGlobal(path: string, value: unknown): void;
5216
5401
 
5402
+ export declare function setGridVisible(visible: boolean): void;
5403
+
5217
5404
  export declare function setHoveredEntityId(uuid: string | null): void;
5218
5405
 
5219
5406
  export declare function setPaused(paused: boolean): void;
@@ -5274,6 +5461,15 @@ export declare function setSelectedEntity(entity: GameEntity<any> | string | nul
5274
5461
 
5275
5462
  export declare function setSelectedEntityId(uuid: string | null): void;
5276
5463
 
5464
+ /**
5465
+ * Replace the selection. `selectedEntityId` follows the first entry so the
5466
+ * single-select consumers (debug cursor, orbit focus target, bridge mirror)
5467
+ * keep working unchanged.
5468
+ */
5469
+ export declare function setSelectedEntityIds(uuids: string[]): void;
5470
+
5471
+ export declare function setSnapSettings(snap: Partial<SnapSettings>): void;
5472
+
5277
5473
  export declare function setStageFrameProfilerEnabled(value: boolean): void;
5278
5474
 
5279
5475
  /** Setup */
@@ -5459,6 +5655,36 @@ export { sin }
5459
5655
 
5460
5656
  export { smoothstep }
5461
5657
 
5658
+ /**
5659
+ * Snap a scale, clamped away from zero.
5660
+ *
5661
+ * A snapped-to-zero axis would collapse the collider into a degenerate shape
5662
+ * that the physics runtime cannot solve, so the smallest representable scale
5663
+ * is one increment.
5664
+ */
5665
+ export declare function snapScale(value: Vec3, increment: number): Vec3;
5666
+
5667
+ /** Grid increments applied to gizmo drags, in world units and radians. */
5668
+ export declare interface SnapSettings {
5669
+ enabled: boolean;
5670
+ translate: number;
5671
+ /** Radians. Defaults to 15 degrees. */
5672
+ rotate: number;
5673
+ scale: number;
5674
+ }
5675
+
5676
+ /**
5677
+ * Snap a value onto an absolute lattice of `increment`-sized steps.
5678
+ *
5679
+ * Absolute rather than relative to where the drag started: the result is
5680
+ * always a multiple of the increment, so independently placed entities line up
5681
+ * with each other instead of each keeping its own arbitrary offset.
5682
+ */
5683
+ export declare function snapToIncrement(value: number, increment: number): number;
5684
+
5685
+ /** {@link snapToIncrement} across all three components. */
5686
+ export declare function snapVec3(value: Vec3, increment: number): Vec3;
5687
+
5462
5688
  export declare const SPHERE_TYPE: unique symbol;
5463
5689
 
5464
5690
  /**
@@ -5721,10 +5947,10 @@ declare class StageDebugDelegate {
5721
5947
  private options;
5722
5948
  private mouseNdc;
5723
5949
  private raycaster;
5724
- private isMouseDown;
5725
5950
  private domDisposeFns;
5726
5951
  private domListenersAttached;
5727
5952
  private debugCursor;
5953
+ private placementGhost;
5728
5954
  private debugLines;
5729
5955
  private cameraDebugDelegate;
5730
5956
  private debugStateUnsubscribe;
@@ -5732,6 +5958,11 @@ declare class StageDebugDelegate {
5732
5958
  private entityResolverUnregister;
5733
5959
  private lastDebugEnabled;
5734
5960
  private warnedMissingAddFactory;
5961
+ private transformTool;
5962
+ private grid;
5963
+ private lastTool;
5964
+ /** Element the current gizmo drag captured, so pointerup is never missed. */
5965
+ private capturedPointer;
5735
5966
  constructor(stage: ZylemStage, options?: StageDebugDelegateOptions);
5736
5967
  /**
5737
5968
  * Supply the factory the `add` tool uses to spawn entities at the clicked
@@ -5751,18 +5982,107 @@ declare class StageDebugDelegate {
5751
5982
  * rebuild the render pipeline once per frame.
5752
5983
  */
5753
5984
  private syncWithDebugState;
5985
+ /** Hand the transform tool its mode whenever the active tool changes. */
5986
+ private syncToolMode;
5987
+ private syncGrid;
5754
5988
  /** Initialize DOM listeners and switch rendering to the debug orbit camera. */
5755
5989
  private activate;
5756
5990
  /** Tear down visuals and restore the gameplay primary camera. */
5757
5991
  private deactivate;
5758
5992
  private rebindPostProcessing;
5993
+ /**
5994
+ * Collider wireframes: the one overlay that really is debug-mode-only, since
5995
+ * it draws the simulation itself rather than anything being edited.
5996
+ */
5759
5997
  private initDebugVisuals;
5760
5998
  private disposeDebugVisuals;
5999
+ /**
6000
+ * Tool feedback, built on first use and kept for the delegate's life.
6001
+ *
6002
+ * Separate from {@link initDebugVisuals} because the hover highlight and the
6003
+ * placement ghost belong to the tools, not to debug mode: they have to be
6004
+ * available when a tool is armed with debug off.
6005
+ */
6006
+ private ensureToolVisuals;
6007
+ /**
6008
+ * Hide the overlays whose tool has just gone away.
6009
+ *
6010
+ * `update()` normally does this, but it stops being called once nothing is
6011
+ * armed and debug is off — so the frame that would have hidden a disarmed
6012
+ * Add's ghost never arrives, and the preview is left floating in the scene.
6013
+ * Driven from the tool transition, which always fires.
6014
+ */
6015
+ private parkToolVisuals;
6016
+ private disposeToolVisuals;
5761
6017
  update(): void;
6018
+ /** Collider wireframes, straight from the wasm simulation. */
6019
+ private updateColliderWireframes;
6020
+ /**
6021
+ * Make sure an armed tool can actually receive the pointer.
6022
+ *
6023
+ * The listeners outlive debug mode once attached — `deactivate()` leaves them
6024
+ * in place and only `dispose()` removes them — so this only ever has to run
6025
+ * until it succeeds.
6026
+ */
6027
+ private ensureInteraction;
6028
+ /**
6029
+ * Drive the translucent preview of the armed entity type from the cursor.
6030
+ *
6031
+ * Runs while the game is paused as well as while it plays: only the
6032
+ * fixed-step `step()` is pause-gated, so this pass still ticks and the
6033
+ * ghost keeps following the mouse in a frozen scene — which is exactly when
6034
+ * a scene is usually being built. It runs with debug mode off too, for the
6035
+ * same reason: arming Add is a request to place something, not to inspect
6036
+ * colliders.
6037
+ *
6038
+ * The ghost is a scene overlay, not an entity, so it is invisible to the
6039
+ * physics raycast here and cannot preview itself.
6040
+ */
6041
+ private updatePlacementGhost;
5762
6042
  /** Full teardown — unsubscribes from debugState and cleans up all resources. */
5763
6043
  dispose(): void;
5764
- private handleActionOnHit;
6044
+ /**
6045
+ * Undo or redo a committed operation.
6046
+ *
6047
+ * The editor holds the history but cannot apply it: reverting a delete means
6048
+ * producing the original entity, which only the stage can do. So the editor
6049
+ * hands the record back and the inversion happens here.
6050
+ */
6051
+ applySceneOperation(op: SceneOperationPayload, direction: 'undo' | 'redo'): void;
6052
+ /** The mouse ray in world space, from the last pointer position. */
6053
+ private currentRay;
6054
+ private handleAction;
6055
+ /**
6056
+ * Spawn the armed entity type at the clicked point.
6057
+ *
6058
+ * Placement resolves in two steps because a click into empty sky is the
6059
+ * common case when building a scene from nothing: the physics hit is
6060
+ * preferred (the entity rests on the surface it was dropped onto), and
6061
+ * otherwise the ray is intersected with the y=0 construction plane. Before
6062
+ * this, clicking anywhere without an existing collider under the cursor did
6063
+ * nothing at all.
6064
+ */
6065
+ private placeEntity;
6066
+ /**
6067
+ * Nudge a freshly placed entity out along the surface normal by half its
6068
+ * own size, so it sits on the surface rather than half-buried in it.
6069
+ *
6070
+ * Runs after spawn because the entity's bounds are not known until its mesh
6071
+ * exists.
6072
+ */
6073
+ private restOnSurface;
6074
+ private warnMissingAddSource;
5765
6075
  private attachDomListeners;
6076
+ /**
6077
+ * Own the pointer for the duration of a gizmo drag.
6078
+ *
6079
+ * Capture keeps events coming even when the cursor leaves the canvas, which
6080
+ * happens constantly when dragging an axis toward the edge of the viewport.
6081
+ * Orbit is suspended for the same window so the drag does not also swing the
6082
+ * camera.
6083
+ */
6084
+ private beginPointerCapture;
6085
+ private endPointerCapture;
5766
6086
  }
5767
6087
 
5768
6088
  declare interface StageDebugDelegateOptions {
@@ -5805,6 +6125,11 @@ declare class StageEntityDelegate {
5805
6125
  readonly childrenMap: Map<string, BaseNode>;
5806
6126
  /** UUID → BaseNode map populated when debug mode is active. */
5807
6127
  readonly debugMap: Map<string, BaseNode>;
6128
+ /**
6129
+ * Entities detached from the stage but kept alive so an editor undo can put
6130
+ * them back. See {@link StageEntityDelegate.detachEntity}.
6131
+ */
6132
+ readonly detachedMap: Map<string, DetachedEntity>;
5808
6133
  private pendingEntities;
5809
6134
  private pendingPromises;
5810
6135
  private _isLoaded;
@@ -5854,10 +6179,48 @@ declare class StageEntityDelegate {
5854
6179
  /** Add the entity to internal maps and notify listeners. */
5855
6180
  addEntityToStage(entity: BaseNode): void;
5856
6181
  /**
5857
- * Remove an entity and its resources by its UUID.
6182
+ * Remove an entity from the stage by its UUID.
6183
+ *
6184
+ * Detaches rather than destroys, so an editor undo can put the same
6185
+ * instance back. See {@link StageEntityDelegate.detachEntity}.
6186
+ *
5858
6187
  * @returns true if removed, false if not found or stage not ready
5859
6188
  */
5860
6189
  removeEntityByUuid(uuid: string): boolean;
6190
+ /**
6191
+ * Take an entity out of the stage while keeping the instance alive.
6192
+ *
6193
+ * The editor cannot rebuild an entity — all it ever receives is a summary of
6194
+ * name, type, and transform, with no material, behaviors, or collider
6195
+ * config. So undoing a delete has to restore the original object, which
6196
+ * means holding onto it here. Keeping the same instance also keeps its uuid
6197
+ * stable, which every other entry in the undo stack depends on.
6198
+ *
6199
+ * Observers are still told the entity is gone: from the outside it has left
6200
+ * the stage, and leaving it in the editor's list or the thumbnail cache
6201
+ * would leak.
6202
+ *
6203
+ * @returns true when the entity was found and detached.
6204
+ */
6205
+ detachEntity(uuid: string): boolean;
6206
+ /**
6207
+ * Put a detached entity back into the stage at the pose it left.
6208
+ *
6209
+ * Unlike {@link StageEntityDelegate.spawnEntity} this never calls
6210
+ * `create()`: the entity is already fully built, and rebuilding it would
6211
+ * discard its meshes and colliders along with any editor scale applied to
6212
+ * them.
6213
+ *
6214
+ * @returns true when the entity was found and restored.
6215
+ */
6216
+ restoreEntity(uuid: string): boolean;
6217
+ /** Whether a uuid is currently recoverable via {@link restoreEntity}. */
6218
+ isDetached(uuid: string): boolean;
6219
+ /**
6220
+ * Destroy detached entities past the recoverable window. Insertion-ordered,
6221
+ * so the oldest detachments go first.
6222
+ */
6223
+ private evictOldestDetached;
5861
6224
  /** Get an entity by its name; returns null if not found. */
5862
6225
  getEntityByName(name: string): BaseNode | null;
5863
6226
  /** Build a serializable state snapshot for an entity. */
@@ -6575,6 +6938,9 @@ export declare const touchThemes: {
6575
6938
  readonly lagoon: TouchTheme;
6576
6939
  };
6577
6940
 
6941
+ /** Tools that manipulate a selected entity's transform via the gizmo. */
6942
+ export declare const TRANSFORM_TOOLS: readonly ["translate", "rotate", "scale"];
6943
+
6578
6944
  /**
6579
6945
  * Structural slice of the simulation body facade used to flush transform
6580
6946
  * intents. `SimulationBody` satisfies this shape.
@@ -6632,6 +6998,91 @@ declare interface TransformableEntity {
6632
6998
 
6633
6999
  export { TransformComponent }
6634
7000
 
7001
+ export declare class TransformGizmo {
7002
+ /** Root added to the scene; carries the pivot transform. */
7003
+ readonly root: Group<three.Object3DEventMap>;
7004
+ private scene;
7005
+ private modeGroups;
7006
+ private handles;
7007
+ private materials;
7008
+ private geometries;
7009
+ private mode;
7010
+ private highlighted;
7011
+ private drag;
7012
+ private raycaster;
7013
+ constructor();
7014
+ addTo(scene: Scene): void;
7015
+ removeFromScene(): void;
7016
+ dispose(): void;
7017
+ setMode(mode: GizmoMode): void;
7018
+ getMode(): GizmoMode;
7019
+ /** Place the gizmo at a pivot and show it. */
7020
+ show(pivot: Vector3): void;
7021
+ hide(): void;
7022
+ get visible(): boolean;
7023
+ get pivot(): Vector3;
7024
+ /**
7025
+ * Keep the gizmo a constant size on screen.
7026
+ *
7027
+ * A world-sized gizmo is unusable in an orbit view: it fills the screen up
7028
+ * close and disappears from a distance.
7029
+ */
7030
+ update(camera: Camera): void;
7031
+ /**
7032
+ * Which handle a ray hits, or `null`.
7033
+ *
7034
+ * Raycasts the gizmo's own meshes rather than the physics world. Handles are
7035
+ * render-only objects with no colliders, so the physics raycast used to pick
7036
+ * entities cannot see them at all.
7037
+ */
7038
+ pick(ray: GizmoRay): GizmoAxis | null;
7039
+ /**
7040
+ * Whether a handle can be dragged from the current viewing angle.
7041
+ *
7042
+ * Each handle type degenerates differently: an arrow aimed at the camera has
7043
+ * no screen-space direction left to follow, while a plane quad or rotation
7044
+ * ring seen edge-on turns a pixel of motion into a huge sweep across it.
7045
+ */
7046
+ private isHandleDraggable;
7047
+ /** Tint a handle to show it is hovered or being dragged. */
7048
+ setHighlight(axis: GizmoAxis | null): void;
7049
+ /**
7050
+ * Begin a drag on a handle.
7051
+ *
7052
+ * @returns `false` when the gesture cannot be tracked (a near-parallel view
7053
+ * of the drag axis), so the caller can ignore the press rather than start a
7054
+ * drag that would jump.
7055
+ */
7056
+ beginDrag(axis: GizmoAxis, ray: GizmoRay, camera: Camera): boolean;
7057
+ /** Raw delta for the current pointer position, or `null` when unusable. */
7058
+ updateDrag(ray: GizmoRay, camera: Camera): GizmoDelta | null;
7059
+ endDrag(): void;
7060
+ get isDragging(): boolean;
7061
+ get draggingAxis(): GizmoAxis | null;
7062
+ private isPlaneAxis;
7063
+ /** Signed angle of the ray's hit point around `normal`, in radians. */
7064
+ private angleOnPlane;
7065
+ /** Distance from pivot to the ray hit on the camera-facing plane. */
7066
+ private cameraPlaneRadius;
7067
+ private applyModeVisibility;
7068
+ private makeMaterial;
7069
+ private track;
7070
+ /**
7071
+ * An invisible, generously sized mesh used only for hit testing.
7072
+ *
7073
+ * The visible shafts and rings are a couple of pixels wide, which is
7074
+ * miserable to click. The raycaster still reaches `visible: false` meshes,
7075
+ * so picking accuracy is decoupled from how thin the handle looks.
7076
+ */
7077
+ private makePicker;
7078
+ private orientAlongAxis;
7079
+ private buildTranslate;
7080
+ private positionPlaneHandle;
7081
+ private buildRotate;
7082
+ private orientRing;
7083
+ private buildScale;
7084
+ }
7085
+
6635
7086
  /**
6636
7087
  * Minimal transform representation for pipeline targets.
6637
7088
  */
@@ -6642,6 +7093,8 @@ export declare interface TransformLike {
6642
7093
 
6643
7094
  export { TransformState }
6644
7095
 
7096
+ export declare type TransformTool = (typeof TRANSFORM_TOOLS)[number];
7097
+
6645
7098
  export { uniform }
6646
7099
 
6647
7100
  export { updateBoundaryRicochet }
@@ -7437,6 +7890,17 @@ declare class ZylemCamera {
7437
7890
  * Frame the debug orbit camera on an Object3D (AABB fit).
7438
7891
  */
7439
7892
  frameObject(object: Object3D): void;
7893
+ /**
7894
+ * Suspend or resume orbit input. Used by the debug transform tools so a
7895
+ * gizmo drag does not also swing the camera.
7896
+ */
7897
+ setOrbitInteractionEnabled(enabled: boolean): void;
7898
+ /**
7899
+ * Stop or resume tracking the debug selection with the orbit target. Used by
7900
+ * the transform tools, which cannot be dragged accurately while the camera
7901
+ * re-aims at the entity being moved.
7902
+ */
7903
+ setOrbitFollowEnabled(enabled: boolean): void;
7440
7904
  /**
7441
7905
  * Directly set the camera position.
7442
7906
  */
@@ -7771,6 +8235,8 @@ declare class ZylemGame<TGlobals extends BaseGlobals> {
7771
8235
  private lastPublishedGameConfig;
7772
8236
  /** Editor ↔ game bridge adapter (RAF-coalesced publishes, command intake). */
7773
8237
  private gameBridge;
8238
+ /** Stops republishing the entity catalog on registry changes. */
8239
+ private catalogUnsubscribe;
7774
8240
  private readonly gameUpdateParams;
7775
8241
  private readonly stageSetupParams;
7776
8242
  private readonly stageUpdateParams;
@@ -7781,6 +8247,10 @@ declare class ZylemGame<TGlobals extends BaseGlobals> {
7781
8247
  static FRAME_DURATION: number;
7782
8248
  static MAX_DELTA_SECONDS: number;
7783
8249
  constructor(options: ZylemGameOptions<TGlobals>, wrapperRef: Game<TGlobals>, displayRuntime?: ResolveGameConfigRuntime);
8250
+ /** Publish the placeable entity types the editor's Add palette lists. */
8251
+ private publishEntityCatalog;
8252
+ /** Spawn a catalog type programmatically (editor `entity:create`). */
8253
+ private createCatalogEntity;
7784
8254
  setDisplayRuntime(runtime: ResolveGameConfigRuntime): void;
7785
8255
  loadGameCanvas(config: GameConfig): void;
7786
8256
  loadDebugOptions(options: ZylemGameOptions<TGlobals>): void;
@@ -8405,7 +8875,7 @@ declare class ZylemStage extends LifeCycleBase<ZylemStage> {
8405
8875
  protected _setup(params: SetupContext<ZylemStage>): void;
8406
8876
  protected _update(params: UpdateContext<ZylemStage>): void;
8407
8877
  outOfLoop(): void;
8408
- /** Update debug overlays and helpers if enabled. */
8878
+ /** Update debug overlays and the editor's pointer tools. */
8409
8879
  debugUpdate(): void;
8410
8880
  /** Cleanup owned resources when the stage is destroyed. */
8411
8881
  protected _destroy(params: DestroyContext<ZylemStage>): void;