@zylem/game-lib 0.8.2 → 0.8.4

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 (58) hide show
  1. package/dist/actions.d.ts +5 -5
  2. package/dist/api/game-lib.api.json +2079 -1214
  3. package/dist/api/game-lib.api.md +150 -97
  4. package/dist/api/game-lib.d.ts +321 -95
  5. package/dist/api/game-lib.typedoc.json +141279 -214520
  6. package/dist/bridge.d.ts +111 -0
  7. package/dist/bridge.js +1 -0
  8. package/dist/{chunk-KVSJE4XD.js → chunk-3I537JFL.js} +1 -1
  9. package/dist/chunk-4CHZUWLF.js +1 -0
  10. package/dist/chunk-6B5JP7T5.js +24 -0
  11. package/dist/chunk-AIQ6IVT5.js +1 -0
  12. package/dist/chunk-C5NEGC3S.js +1 -0
  13. package/dist/chunk-D3JO4HW2.js +1 -0
  14. package/dist/chunk-HNCTOJP7.js +1 -0
  15. package/dist/chunk-LKFF6XJC.js +1 -0
  16. package/dist/{chunk-CD7ZOZ35.js → chunk-OAWK2UUY.js} +1 -1
  17. package/dist/chunk-PNLNC3FA.js +1 -0
  18. package/dist/chunk-TV3AZKT4.js +9 -0
  19. package/dist/chunk-VIPINV7V.js +19 -0
  20. package/dist/chunk-W5SXED5T.js +1 -0
  21. package/dist/{chunk-YAOXFYOD.js → chunk-WBQ5TNRZ.js} +1 -1
  22. package/dist/{collision-builder-B_tm8OVy.d.ts → collision-builder-4TVDzXsp.d.ts} +1 -1
  23. package/dist/core.d.ts +11 -11
  24. package/dist/core.js +1 -1
  25. package/dist/debug-state-s0s-qndj.d.ts +49 -0
  26. package/dist/debug.d.ts +32 -7
  27. package/dist/debug.js +1 -1
  28. package/dist/{entity-BGEq1yfC.d.ts → entity-BI7cSlK8.d.ts} +3 -3
  29. package/dist/{entity-B9yZeBm4.d.ts → entity-DDks47JB.d.ts} +1 -1
  30. package/dist/{entity-type-map-CW6RHQkx.d.ts → entity-type-map-COHu4fkW.d.ts} +41 -7
  31. package/dist/entity.d.ts +7 -7
  32. package/dist/entity.js +1 -1
  33. package/dist/events.d.ts +1 -64
  34. package/dist/events.js +1 -1
  35. package/dist/{game-DbqBQiFC.d.ts → game-4fsy55uN.d.ts} +55 -27
  36. package/dist/game-default-4PPYYGAR.js +1 -0
  37. package/dist/graphics.d.ts +5 -5
  38. package/dist/{material-DbSuZBbP.d.ts → material-BKPYeBC6.d.ts} +1 -1
  39. package/dist/public.d.ts +12 -10
  40. package/dist/public.js +1 -1
  41. package/dist/runtime.d.ts +3 -3
  42. package/dist/runtime.js +1 -1
  43. package/dist/{vector-DN6zlPoE.d.ts → vector-DPbwlzgG.d.ts} +10 -2
  44. package/dist/web-components.d.ts +9 -9
  45. package/dist/web-components.js +1 -1
  46. package/dist/{zylem-camera-Du2UPW9O.d.ts → zylem-camera-B73ZNcgs.d.ts} +1 -1
  47. package/package.json +20 -14
  48. package/LICENSE +0 -21
  49. package/dist/chunk-3PRBSSTS.js +0 -24
  50. package/dist/chunk-CFVRRRZZ.js +0 -1
  51. package/dist/chunk-COM6HJ2L.js +0 -1
  52. package/dist/chunk-JQKS42G4.js +0 -1
  53. package/dist/chunk-MZZCDXFB.js +0 -1
  54. package/dist/chunk-TAUWDQZF.js +0 -9
  55. package/dist/chunk-VS2D4B3W.js +0 -1
  56. package/dist/chunk-YHEWKN7N.js +0 -1
  57. package/dist/debug-state-DRoeasG2.d.ts +0 -17
  58. package/dist/game-default-6AZZCNST.js +0 -1
@@ -1,4 +1,5 @@
1
1
  import { abs } from 'three/tsl';
2
+ import { announceBridgeReady } from '@zylem/bridge';
2
3
  import { asin } from 'three/tsl';
3
4
  import { atan } from 'three/tsl';
4
5
  import { BehaviorDescriptor } from '@zylem/behaviors';
@@ -20,6 +21,14 @@ import { BoundaryRicochet3DEntity } from '@zylem/behaviors';
20
21
  import { BoundaryRicochetCoordinator } from '@zylem/behaviors';
21
22
  import { BoundaryRicochetEntity } from '@zylem/behaviors';
22
23
  import { BoundaryRicochetUpdate } from '@zylem/behaviors';
24
+ import { BRIDGE_READY_EVENT } from '@zylem/bridge';
25
+ import { BridgeChannel } from '@zylem/bridge';
26
+ import { BridgeDebugTool } from '@zylem/bridge';
27
+ import { BridgeHandler } from '@zylem/bridge';
28
+ import { GameLoadingPayload as BridgeLoadingPayload } from '@zylem/bridge';
29
+ import { BridgeMessages } from '@zylem/bridge';
30
+ import { BridgeMessageType } from '@zylem/bridge';
31
+ import { BridgeVec3 } from '@zylem/bridge';
23
32
  import { BufferGeometry } from 'three';
24
33
  import { BurstParticlePresetOptions } from '@zylem/behaviors';
25
34
  import { Camera } from 'three';
@@ -71,11 +80,14 @@ import { Destructible3DHandle } from '@zylem/behaviors';
71
80
  import { DestructibleMesh } from '@zylem/behaviors';
72
81
  import { DestructiblePrebakeWorkerAPI } from '@zylem/behaviors';
73
82
  import { dot } from 'three/tsl';
83
+ import { EditorToGameMessages } from '@zylem/bridge';
74
84
  import { emitRuntimeGoal } from '@zylem/behaviors/runtime-2d';
75
85
  import { emitRuntimeRicochet } from '@zylem/behaviors/runtime-2d';
76
86
  import { emitRuntimeTriggerRegionEnter } from '@zylem/behaviors/runtime-2d';
77
87
  import { EntityDefinition } from '@zylem/behaviors/core';
78
88
  import { EntityHandle } from '@zylem/behaviors/core';
89
+ import { EntitySummaryPayload } from '@zylem/bridge';
90
+ import { EntityThumbnailPayload } from '@zylem/bridge';
79
91
  import { exp } from 'three/tsl';
80
92
  import { FacingAngle2DEntity } from '@zylem/behaviors';
81
93
  import { fireCooldown } from '@zylem/behaviors';
@@ -102,11 +114,16 @@ import { FogExp2 } from 'three';
102
114
  import { fract } from 'three/tsl';
103
115
  import { FractureOptions } from '@zylem/behaviors';
104
116
  import { FractureOptionsInput } from '@zylem/behaviors';
117
+ import { GameConfigPayload } from '@zylem/bridge';
118
+ import { GameNoticePayload } from '@zylem/bridge';
119
+ import { GameToEditorMessages } from '@zylem/bridge';
120
+ import { GameVariablePayload } from '@zylem/bridge';
105
121
  import { getCooldown } from '@zylem/behaviors';
106
122
  import { getCooldownStore } from '@zylem/behaviors';
107
123
  import { getEntityFacingAngle2D } from '@zylem/behaviors';
108
124
  import { getPosition } from '@zylem/behaviors/core';
109
125
  import { getVelocity } from '@zylem/behaviors/core';
126
+ import { getZylemBridge } from '@zylem/bridge';
110
127
  import { Group } from 'three';
111
128
  import { hasAnyWorldBoundary2DHit } from '@zylem/behaviors';
112
129
  import { hasAnyWorldBoundary3DHit } from '@zylem/behaviors';
@@ -302,6 +319,7 @@ import { StageBodyKindValue } from '@zylem/behaviors/core';
302
319
  import { StageBoundaryDim } from '@zylem/behaviors/core';
303
320
  import { StageColliderConfig } from '@zylem/behaviors/core';
304
321
  import { StageColliderShape } from '@zylem/behaviors/core';
322
+ import { StageConfigPayload } from '@zylem/bridge';
305
323
  import { StageDebugRender } from '@zylem/behaviors/core';
306
324
  import { StageEvent } from '@zylem/behaviors/core';
307
325
  import { StageEventType } from '@zylem/behaviors/core';
@@ -310,6 +328,7 @@ import { StageRaycastHit } from '@zylem/behaviors/core';
310
328
  import { StageRenderSlot } from '@zylem/behaviors/core';
311
329
  import { StageRicochetDim } from '@zylem/behaviors/core';
312
330
  import { StageRicochetReflection } from '@zylem/behaviors/core';
331
+ import { StageSnapshotPayload } from '@zylem/bridge';
313
332
  import { StageTopDownPlane } from '@zylem/behaviors/core';
314
333
  import { Static } from '@sinclair/typebox';
315
334
  import { step } from 'three/tsl';
@@ -374,6 +393,7 @@ import { WorldBoundary3DPosition } from '@zylem/behaviors';
374
393
  import { WorldBoundary3DState } from '@zylem/behaviors';
375
394
  import { wrapAround3D } from '@zylem/behaviors/core';
376
395
  import { wrapAroundXY } from '@zylem/behaviors/core';
396
+ import { ZylemBridge } from '@zylem/bridge';
377
397
 
378
398
  export { abs }
379
399
 
@@ -439,6 +459,8 @@ declare type AnimationOptions = {
439
459
  fadeDuration?: number;
440
460
  };
441
461
 
462
+ export { announceBridgeReady }
463
+
442
464
  declare type AnyNode = NodeLike | Promise<NodeLike>;
443
465
 
444
466
  /**
@@ -680,6 +702,22 @@ declare interface BoxMeshOptions extends BaseMeshOptions {
680
702
 
681
703
  declare type BoxOptions = BaseNode | Partial<ZylemBoxOptions>;
682
704
 
705
+ export { BRIDGE_READY_EVENT }
706
+
707
+ export { BridgeChannel }
708
+
709
+ export { BridgeDebugTool }
710
+
711
+ export { BridgeHandler }
712
+
713
+ export { BridgeLoadingPayload }
714
+
715
+ export { BridgeMessages }
716
+
717
+ export { BridgeMessageType }
718
+
719
+ export { BridgeVec3 }
720
+
683
721
  /** Cuboid collider sized as `size.xyz` (full extents — half-extents are computed). */
684
722
  export declare function buildBoxCollider(size: Vec3Input, options?: RuntimeColliderOptions): StageColliderConfig;
685
723
 
@@ -1662,6 +1700,9 @@ declare interface CylinderMeshOptions extends BaseMeshOptions {
1662
1700
 
1663
1701
  declare type CylinderOptions = BaseNode | Partial<ZylemCylinderOptions>;
1664
1702
 
1703
+ /** Resolves a UUID to the live entity owned by the active stage. */
1704
+ export declare type DebugEntityResolver = (uuid: string) => GameEntity<any> | null;
1705
+
1665
1706
  /**
1666
1707
  * Interface for entities that provide custom debug information
1667
1708
  */
@@ -1673,14 +1714,22 @@ declare interface DebugState {
1673
1714
  enabled: boolean;
1674
1715
  paused: boolean;
1675
1716
  tool: DebugTools;
1676
- selectedEntity: GameEntity<any> | null;
1677
- hoveredEntity: GameEntity<any> | null;
1717
+ /**
1718
+ * UUID of the selected entity, never the entity itself. Valtio deep-proxies
1719
+ * any object assigned into a proxy, so storing a live `GameEntity` here
1720
+ * would walk `group.parent` into the entire THREE scene graph and
1721
+ * `physicsWorldRef` into the Rapier world, freezing the tab and handing
1722
+ * Proxy wrappers to engine code that expects real instances.
1723
+ */
1724
+ selectedEntityId: string | null;
1725
+ /** UUID of the hovered entity. Same constraint as `selectedEntityId`. */
1726
+ hoveredEntityId: string | null;
1678
1727
  flags: Set<string>;
1679
1728
  }
1680
1729
 
1681
1730
  export declare const debugState: DebugState;
1682
1731
 
1683
- export declare type DebugTools = 'select' | 'translate' | 'rotate' | 'scale' | 'delete' | 'none';
1732
+ export declare type DebugTools = 'select' | 'translate' | 'rotate' | 'scale' | 'delete' | 'add' | 'none';
1684
1733
 
1685
1734
  /**
1686
1735
  * Builds a ready-to-use {@link GameInputConfig} that wires up themed
@@ -1804,6 +1853,8 @@ export declare function dispatchStageState(): void;
1804
1853
 
1805
1854
  export { dot }
1806
1855
 
1856
+ export { EditorToGameMessages }
1857
+
1807
1858
  export { emitRuntimeGoal }
1808
1859
 
1809
1860
  export { emitRuntimeRicochet }
@@ -1841,36 +1892,6 @@ declare abstract class EntityCollisionBuilder extends CollisionBuilder {
1841
1892
  abstract collider(options: GameEntityOptions): SimulationColliderDefinition;
1842
1893
  }
1843
1894
 
1844
- /** Payload for entity configuration sent to editor. */
1845
- export declare interface EntityConfigPayload {
1846
- uuid: string;
1847
- name: string;
1848
- type: string;
1849
- position: {
1850
- x: number;
1851
- y: number;
1852
- z: number;
1853
- };
1854
- rotation: {
1855
- x: number;
1856
- y: number;
1857
- z: number;
1858
- };
1859
- scale: {
1860
- x: number;
1861
- y: number;
1862
- z: number;
1863
- };
1864
- /** PNG data URL of a framed entity preview, when available. */
1865
- thumbnail?: string | null;
1866
- /** World-space AABB size used for thumbnail rulers. */
1867
- bounds?: {
1868
- width: number;
1869
- height: number;
1870
- depth: number;
1871
- };
1872
- }
1873
-
1874
1895
  declare interface EntityDebugInfo {
1875
1896
  buildInfo: () => Record<string, string>;
1876
1897
  }
@@ -2029,24 +2050,47 @@ declare interface EntitySpawner {
2029
2050
 
2030
2051
  export declare function entitySpawner(factory: (x: number, y: number) => Promise<any> | GameEntity<any>): EntitySpawner;
2031
2052
 
2053
+ export { EntitySummaryPayload }
2054
+
2032
2055
  /**
2033
2056
  * Generates and caches entity thumbnail previews for the editor entity list.
2034
2057
  * Renders a cloned Object3D into an offscreen RenderTarget on the shared WebGPU renderer.
2058
+ *
2059
+ * A rendered URL leaves this cache the moment it is read, and the editor holds
2060
+ * it for as long as the entity is listed. Dropping an entry here therefore
2061
+ * must not revoke its blob URL — the consumer owns that lifetime and releases
2062
+ * it when the thumbnail is replaced, the entity is removed, or the stage
2063
+ * changes.
2035
2064
  */
2036
2065
  export declare class EntityThumbnailCache {
2037
2066
  private cache;
2038
2067
  private inFlight;
2039
2068
  private renderer;
2040
2069
  private size;
2041
- constructor(size?: number);
2070
+ private maxEntries;
2071
+ private queue;
2072
+ private activeRenders;
2073
+ constructor(size?: number, maxEntries?: number);
2042
2074
  setRenderer(renderer: WebGPURenderer | null): void;
2043
2075
  get(uuid: string): EntityThumbnailCacheEntry | null;
2044
2076
  invalidate(uuid: string): void;
2045
2077
  clear(): void;
2078
+ /** Number of thumbnails currently cached. */
2079
+ get cacheSize(): number;
2080
+ /** Entities waiting for a render slot. */
2081
+ get pendingCount(): number;
2046
2082
  /**
2047
- * Return a cached thumbnail or generate one asynchronously.
2083
+ * Return a cached thumbnail, or schedule one. Renders are throttled to
2084
+ * {@link MAX_CONCURRENT_RENDERS} at a time behind a bounded queue, so a
2085
+ * burst of spawns cannot flood the renderer with pixel readbacks.
2048
2086
  */
2049
2087
  ensure(uuid: string, object: Object3D | null | undefined): Promise<EntityThumbnailCacheEntry | null>;
2088
+ /** Start queued renders until the concurrency budget is spent. */
2089
+ private drainQueue;
2090
+ /** Move an entry to the end of the Map so it is evicted last. */
2091
+ private touch;
2092
+ /** Evict least-recently-used entries down to the cap. */
2093
+ private evictOverflow;
2050
2094
  private render;
2051
2095
  }
2052
2096
 
@@ -2057,7 +2101,13 @@ export declare interface EntityThumbnailCacheEntry extends EntityThumbnailResult
2057
2101
  uuid: string;
2058
2102
  }
2059
2103
 
2104
+ export { EntityThumbnailPayload }
2105
+
2060
2106
  export declare interface EntityThumbnailResult {
2107
+ /**
2108
+ * Preview image URL. Blob URL when the payload worker is available
2109
+ * (encoded off-thread), PNG data URL on the synchronous fallback path.
2110
+ */
2061
2111
  dataUrl: string;
2062
2112
  bounds: Pick<ObjectBounds, 'width' | 'height' | 'depth'>;
2063
2113
  }
@@ -2458,6 +2508,103 @@ export declare class Game<TGlobals extends BaseGlobals> implements IGame<TGlobal
2458
2508
  onLoading(callback: (event: GameLoadingEvent) => void): () => void;
2459
2509
  }
2460
2510
 
2511
+ export declare class GameBridge {
2512
+ private channel;
2513
+ private unsubscribes;
2514
+ private host;
2515
+ /**
2516
+ * Values the editor is already known to hold, either because it commanded
2517
+ * them or because we published them. Anything matching here is skipped, so
2518
+ * an editor command can never echo back as a game update.
2519
+ */
2520
+ private editorKnown;
2521
+ constructor();
2522
+ /**
2523
+ * Subscribe to editor commands and announce the game side as ready.
2524
+ * @param host Live-game context for entity/stage lookups
2525
+ * @param announceTarget DOM node the `zylem:bridge:ready` event bubbles from
2526
+ */
2527
+ connect(host: GameBridgeHost, announceTarget?: EventTarget): void;
2528
+ disconnect(): void;
2529
+ /**
2530
+ * Publish only the parts of `debugState` the editor does not already know.
2531
+ * Values that arrived as editor commands are recorded on `editorKnown`
2532
+ * first, so they are never echoed back.
2533
+ */
2534
+ private publishDebugStateChanges;
2535
+ /**
2536
+ * Whether anything is listening for entity list updates. Building entity
2537
+ * summaries is pure editor overhead, so publishers skip it when no editor
2538
+ * is attached.
2539
+ */
2540
+ wantsEntityUpdates(): boolean;
2541
+ /**
2542
+ * Whether anything is listening for thumbnails. Each thumbnail costs an
2543
+ * offscreen render plus a pixel readback on the renderer the game draws
2544
+ * with, so this gate matters far more than the others.
2545
+ */
2546
+ wantsThumbnails(): boolean;
2547
+ /**
2548
+ * Run a callback when an editor first subscribes to entity data, so the
2549
+ * game can backfill state produced while nothing was listening.
2550
+ *
2551
+ * An editor takes out both entity subscriptions as it mounts, and each is
2552
+ * a first-subscriber transition. Backfilling on both would republish the
2553
+ * config, the whole stage snapshot, and every thumbnail twice per mount,
2554
+ * so a signal is ignored while the other entity type still has a
2555
+ * listener — that is the same editor finishing its setup, not a new one.
2556
+ *
2557
+ * @returns An unsubscribe function.
2558
+ */
2559
+ onEditorAttached(callback: () => void): () => void;
2560
+ publishConfig(config: GameConfigPayload): void;
2561
+ publishLoading(payload: BridgeLoadingPayload): void;
2562
+ publishStatus(status: {
2563
+ paused?: boolean;
2564
+ debug?: boolean;
2565
+ }): void;
2566
+ /**
2567
+ * Publish a global's new value.
2568
+ *
2569
+ * Sent rather than queued: a queued payload is merged into the pending one
2570
+ * for its type, and since each message names a single `path`, two globals
2571
+ * changing in the same frame would collapse into one and the first
2572
+ * variable's update would never reach the editor.
2573
+ */
2574
+ publishVariable(payload: GameVariablePayload): void;
2575
+ /** Surface a diagnostic message in the editor console. */
2576
+ publishNotice(level: GameNoticePayload['level'], message: string): void;
2577
+ publishStageSnapshot(snapshot: StageSnapshotPayload): void;
2578
+ /** RAF-coalesced entity add/update stream; merged by uuid per frame. */
2579
+ queueEntityUpsert(entities: EntitySummaryPayload[]): void;
2580
+ /** RAF-coalesced entity removal stream. */
2581
+ queueEntityRemoved(uuids: string[]): void;
2582
+ /** RAF-coalesced thumbnail stream; merged by uuid per frame. */
2583
+ queueThumbnails(thumbnails: EntityThumbnailPayload[]): void;
2584
+ }
2585
+
2586
+ /**
2587
+ * Game-side adapter for the shared editor ↔ game bridge.
2588
+ *
2589
+ * Owns the editor→game command subscriptions (debug/tool/playback, entity
2590
+ * select/focus/transform, stage variables) and exposes typed publish helpers
2591
+ * the running game uses to stream config, stage snapshots, entity upserts,
2592
+ * and thumbnails to the editor. High-frequency publishes go through the
2593
+ * channel's `queue`, which coalesces to at most one message per animation
2594
+ * frame.
2595
+ */
2596
+
2597
+ /**
2598
+ * Host context supplied by the running game so bridge commands can act on
2599
+ * live stage state without the bridge importing game internals.
2600
+ */
2601
+ export declare interface GameBridgeHost {
2602
+ /** Resolve a live entity by UUID from the current stage. */
2603
+ resolveEntity(uuid: string): GameEntity<any> | null;
2604
+ /** Write a stage variable (editor `stage:variable:set`). */
2605
+ setStageVariable?(key: string, value: unknown): void;
2606
+ }
2607
+
2461
2608
  /**
2462
2609
  * GameCanvas is a DOM delegate that owns:
2463
2610
  * - container lookup/creation and styling (including fullscreen centering)
@@ -2668,6 +2815,8 @@ export declare type GameConfigLike = Partial<{
2668
2815
  canvas: HTMLCanvasElement;
2669
2816
  }>;
2670
2817
 
2818
+ export { GameConfigPayload }
2819
+
2671
2820
  export declare type GameDeviceConfig = Partial<{
2672
2821
  /** numeric value or key in AspectRatio */
2673
2822
  aspectRatio: AspectRatioValue | keyof typeof AspectRatio;
@@ -2994,7 +3143,6 @@ export declare type GameEvents = {
2994
3143
  'debug': {
2995
3144
  enabled: boolean;
2996
3145
  };
2997
- 'state:dispatch': StateDispatchPayload;
2998
3146
  };
2999
3147
 
3000
3148
  export declare interface GameInputConfig {
@@ -3212,6 +3360,10 @@ export declare interface GameLoadingPayload extends LoadingEvent {
3212
3360
 
3213
3361
  declare type GameOptions<TGlobals extends BaseGlobals> = Array<ZylemGameConfig<Stage, any, TGlobals> | GameConfigLike | Stage | GameEntityLifeCycle | BaseNode>;
3214
3362
 
3363
+ export { GameToEditorMessages }
3364
+
3365
+ export { GameVariablePayload }
3366
+
3215
3367
  export { getCooldown }
3216
3368
 
3217
3369
  export { getCooldownStore }
@@ -3232,6 +3384,11 @@ export declare function getGlobal<T = unknown>(path: string): T | undefined;
3232
3384
  */
3233
3385
  export declare function getGlobals<T = Record<string, unknown>>(): T;
3234
3386
 
3387
+ /** The live hovered entity, resolved on demand through the active stage. */
3388
+ export declare function getHoveredEntity(): GameEntity<any> | null;
3389
+
3390
+ export declare function getHoveredEntityId(): string | null;
3391
+
3235
3392
  export { getPosition }
3236
3393
 
3237
3394
  export declare function getPresetResolution(preset: RetroPresetKey, key?: string): RetroResolution | undefined;
@@ -3241,6 +3398,11 @@ export declare function getPresetResolution(preset: RetroPresetKey, key?: string
3241
3398
  */
3242
3399
  export declare function getRotation(entity: RotatableEntity): any;
3243
3400
 
3401
+ /** The live selected entity, resolved on demand through the active stage. */
3402
+ export declare function getSelectedEntity(): GameEntity<any> | null;
3403
+
3404
+ export declare function getSelectedEntityId(): string | null;
3405
+
3244
3406
  /**
3245
3407
  * Get a variable from an object by path.
3246
3408
  * @example getVariable(stage1, 'totalAngle') // 0.5
@@ -3250,6 +3412,8 @@ export declare function getVariable<T = unknown>(target: object, path: string):
3250
3412
 
3251
3413
  export { getVelocity }
3252
3414
 
3415
+ export { getZylemBridge }
3416
+
3253
3417
  /**
3254
3418
  * Listen for a single global key change inside an onUpdate pipeline.
3255
3419
  * Usage: onUpdate(globalChange('p1Score', (value) => { ... }))
@@ -4322,6 +4486,15 @@ declare type RectOptions = BaseNode | Partial<ZylemRectOptions>;
4322
4486
 
4323
4487
  export { registerCooldown }
4324
4488
 
4489
+ /**
4490
+ * Register the active stage's entity resolver so `getSelectedEntity` /
4491
+ * `getHoveredEntity` can hand back live entities without the reactive store
4492
+ * ever holding one.
4493
+ *
4494
+ * @returns An unregister function.
4495
+ */
4496
+ export declare function registerDebugEntityResolver(resolver: DebugEntityResolver): () => void;
4497
+
4325
4498
  /**
4326
4499
  * Register the active stage's focus context. Returns an unregister function.
4327
4500
  */
@@ -4600,6 +4773,9 @@ export declare function resolveButtonAccent(theme: TouchTheme, slot: VirtualTouc
4600
4773
 
4601
4774
  export { ResolvedAim2D }
4602
4775
 
4776
+ /** Resolve a UUID through the active stage, or null when unavailable. */
4777
+ export declare function resolveDebugEntity(uuid: string | null): GameEntity<any> | null;
4778
+
4603
4779
  /** `StageTransitionConfig` with all defaults applied. */
4604
4780
  declare interface ResolvedStageTransition {
4605
4781
  duration: number;
@@ -5038,6 +5214,8 @@ export declare function setDebugTool(tool: DebugTools): void;
5038
5214
  */
5039
5215
  export declare function setGlobal(path: string, value: unknown): void;
5040
5216
 
5217
+ export declare function setHoveredEntityId(uuid: string | null): void;
5218
+
5041
5219
  export declare function setPaused(paused: boolean): void;
5042
5220
 
5043
5221
  export { setPosition }
@@ -5088,7 +5266,13 @@ export declare function setRotationY(entity: RotatableEntity, y: number): void;
5088
5266
  */
5089
5267
  export declare function setRotationZ(entity: RotatableEntity, z: number): void;
5090
5268
 
5091
- export declare function setSelectedEntity(entity: GameEntity<any> | null): void;
5269
+ /**
5270
+ * @deprecated Pass a UUID to {@link setSelectedEntityId} instead. Accepts a
5271
+ * live entity only to keep the previous public API working.
5272
+ */
5273
+ export declare function setSelectedEntity(entity: GameEntity<any> | string | null): void;
5274
+
5275
+ export declare function setSelectedEntityId(uuid: string | null): void;
5092
5276
 
5093
5277
  export declare function setStageFrameProfilerEnabled(value: boolean): void;
5094
5278
 
@@ -5174,8 +5358,16 @@ export declare class SimulationBody implements PhysicsRenderPoseProvider {
5174
5358
  */
5175
5359
  private interpolationValidAfterStep;
5176
5360
  constructor(simulation: Simulation, handle: EntityHandle, clock: SimulationStepClock);
5177
- /** Suppress interpolation until the render buffers catch up. */
5178
- private markPoseDiscontinuity;
5361
+ /**
5362
+ * Suppress interpolation until the render buffers catch up.
5363
+ *
5364
+ * Called automatically by the pose setters below. Wasm-side behaviors that
5365
+ * teleport a body during a step (e.g. `screen_wrap`) bypass those setters,
5366
+ * so their host systems must call this after the step and before render
5367
+ * poses are synced — otherwise the render buffers straddle the teleport and
5368
+ * the body is drawn sweeping between the two poses.
5369
+ */
5370
+ markPoseDiscontinuity(): void;
5179
5371
  private isInterpolationStale;
5180
5372
  translation(): PhysicsVector3;
5181
5373
  rotation(): PhysicsQuaternion;
@@ -5333,6 +5525,21 @@ export declare type SpriteImage = {
5333
5525
 
5334
5526
  declare type SpriteOptions = BaseNode | Partial<ZylemSpriteOptions>;
5335
5527
 
5528
+ /**
5529
+ * A single texture divided into a uniform grid of frames, addressed by UV
5530
+ * offset. Prefer this over {@link SpriteImage} lists for character sheets: one
5531
+ * texture and one draw call instead of one per frame.
5532
+ */
5533
+ export declare type SpriteSheet = {
5534
+ file: string;
5535
+ columns: number;
5536
+ rows: number;
5537
+ /** Frame names in row-major order. `null` marks an unused cell. */
5538
+ frames: Array<string | null>;
5539
+ /** Texture filtering. Defaults to `nearest`, which keeps pixel art crisp. */
5540
+ filter?: 'nearest' | 'linear';
5541
+ };
5542
+
5336
5543
  declare class Stage {
5337
5544
  wrappedStage: ZylemStage | null;
5338
5545
  options: StageOptionItem[];
@@ -5500,19 +5707,7 @@ export declare type StageConfigLike = Partial<{
5500
5707
  postProcessingEffects: ZylemPostEffect[];
5501
5708
  }>;
5502
5709
 
5503
- /** Payload for stage configuration sent to editor. */
5504
- export declare interface StageConfigPayload {
5505
- id: string;
5506
- backgroundColor: string;
5507
- backgroundImage: string | null;
5508
- gravity: {
5509
- x: number;
5510
- y: number;
5511
- z: number;
5512
- };
5513
- inputs: Record<string, string[]>;
5514
- variables: Record<string, unknown>;
5515
- }
5710
+ export { StageConfigPayload }
5516
5711
 
5517
5712
  /**
5518
5713
  * Self-managing debug delegate that subscribes to `debugState` internally.
@@ -5534,13 +5729,27 @@ declare class StageDebugDelegate {
5534
5729
  private cameraDebugDelegate;
5535
5730
  private debugStateUnsubscribe;
5536
5731
  private focusContextUnregister;
5732
+ private entityResolverUnregister;
5733
+ private lastDebugEnabled;
5734
+ private warnedMissingAddFactory;
5537
5735
  constructor(stage: ZylemStage, options?: StageDebugDelegateOptions);
5736
+ /**
5737
+ * Supply the factory the `add` tool uses to spawn entities at the clicked
5738
+ * point. Without one the tool warns instead of silently doing nothing.
5739
+ */
5740
+ setAddEntityFactory(factory: AddEntityFactory | null): void;
5538
5741
  /** Camera used for debug orbit / raycasts (active primary, usually `__debug__`). */
5539
5742
  private getDebugViewCamera;
5540
5743
  private resolveEntity;
5541
5744
  /** Copy all stage children into debugMap so raycast/select tools work after enabling debug mid-session. */
5542
5745
  private populateDebugMap;
5543
5746
  private ensureCameraDebugDelegate;
5747
+ /**
5748
+ * Activate/deactivate only when `debugState.enabled` actually flips.
5749
+ * Hover and selection write to `debugState` every frame, and `activate()`
5750
+ * rebuilds post-processing, so reacting to every notification would
5751
+ * rebuild the render pipeline once per frame.
5752
+ */
5544
5753
  private syncWithDebugState;
5545
5754
  /** Initialize DOM listeners and switch rendering to the debug orbit camera. */
5546
5755
  private activate;
@@ -5939,6 +6148,8 @@ export declare const StageSchema: _sinclair_typebox.TObject<{
5939
6148
  assets: _sinclair_typebox.TOptional<_sinclair_typebox.TArray<_sinclair_typebox.TString>>;
5940
6149
  }>;
5941
6150
 
6151
+ export { StageSnapshotPayload }
6152
+
5942
6153
  declare type StageState = ZylemStageConfig & {
5943
6154
  entities: GameEntityInterface[];
5944
6155
  };
@@ -6006,27 +6217,6 @@ export declare const standardShader: {
6006
6217
  fragment: string;
6007
6218
  };
6008
6219
 
6009
- /** Payload for state dispatch events from game to editor. */
6010
- export declare interface StateDispatchPayload {
6011
- scope: 'game' | 'stage' | 'entity';
6012
- path: string;
6013
- value: unknown;
6014
- previousValue?: unknown;
6015
- config?: {
6016
- id: string;
6017
- aspectRatio: number;
6018
- fullscreen: boolean;
6019
- bodyBackground: string | undefined;
6020
- internalResolution: {
6021
- width: number;
6022
- height: number;
6023
- } | undefined;
6024
- debug: boolean;
6025
- } | null;
6026
- stageConfig?: StageConfigPayload | null;
6027
- entities?: EntityConfigPayload[] | null;
6028
- }
6029
-
6030
6220
  export { step }
6031
6221
 
6032
6222
  /**
@@ -7085,6 +7275,8 @@ export declare class ZylemBox extends GameEntity<ZylemBoxOptions> {
7085
7275
 
7086
7276
  declare type ZylemBoxOptions = GameEntityOptions;
7087
7277
 
7278
+ export { ZylemBridge }
7279
+
7088
7280
  /**
7089
7281
  * ZylemCamera is a lightweight camera wrapper that manages the Three.js camera,
7090
7282
  * the camera pose pipeline, and viewport configuration. Rendering is handled
@@ -7575,16 +7767,10 @@ declare class ZylemGame<TGlobals extends BaseGlobals> {
7575
7767
  private rendererObserver;
7576
7768
  private eventBusUnsubscribes;
7577
7769
  private thumbnailUnsubscribes;
7578
- /**
7579
- * Trailing-debounce timer for entity-driven state dispatches. Building a
7580
- * `state:dispatch` payload is O(entities) (and downstream editor stores
7581
- * re-proxy/reconcile every entity), so bursts of thumbnail/destroy events
7582
- * must coalesce into a single dispatch instead of one per entity.
7583
- */
7584
- private entityDispatchTimer;
7585
- private pendingEntityDispatchPath;
7586
- /** Debounce window (ms) for coalescing entity-driven state dispatches. */
7587
- private static readonly ENTITY_DISPATCH_DEBOUNCE_MS;
7770
+ /** Serialized last-published game config, for dirty-checking resize churn. */
7771
+ private lastPublishedGameConfig;
7772
+ /** Editor ↔ game bridge adapter (RAF-coalesced publishes, command intake). */
7773
+ private gameBridge;
7588
7774
  private readonly gameUpdateParams;
7589
7775
  private readonly stageSetupParams;
7590
7776
  private readonly stageUpdateParams;
@@ -7638,27 +7824,47 @@ declare class ZylemGame<TGlobals extends BaseGlobals> {
7638
7824
  */
7639
7825
  private buildStageConfigPayload;
7640
7826
  /**
7641
- * Build the entities payload for the current stage.
7827
+ * Build a bridge entity summary for one entity, or null for entities on
7828
+ * the managed render path (instanced packs/bundles), which can number in
7829
+ * the thousands and are not useful in the editor panel.
7830
+ */
7831
+ private buildEntitySummary;
7832
+ /**
7833
+ * Build the full entities payload for the current stage.
7642
7834
  */
7643
7835
  private buildEntitiesPayload;
7644
7836
  /**
7645
- * Generate entity thumbnails as entities become available, then refresh editor state.
7837
+ * Generate entity thumbnails as entities become available, streaming
7838
+ * upserts/thumbnails/removals to the editor through the bridge
7839
+ * (RAF-coalesced, merged by uuid).
7840
+ *
7841
+ * All of this work exists only to feed the editor, and thumbnails in
7842
+ * particular cost an offscreen render plus a pixel readback on the shared
7843
+ * renderer. Each publisher therefore checks the bridge for a live listener
7844
+ * first, and a late-connecting editor is backfilled from
7845
+ * {@link wireEditorBackfill}.
7646
7846
  */
7647
7847
  private wireEntityThumbnails;
7648
- private clearEntityThumbnailWiring;
7649
7848
  /**
7650
- * Coalesce entity-driven state dispatches (thumbnail resolutions, entity
7651
- * destruction) into one trailing `emitStateDispatch` per burst.
7849
+ * Republish the current stage when an editor subscribes after the game
7850
+ * started. Without this, entities added while nothing was listening would
7851
+ * never appear, since the per-entity publishers were skipped.
7652
7852
  */
7653
- private queueEntityStateDispatch;
7853
+ private wireEditorBackfill;
7854
+ private clearEntityThumbnailWiring;
7855
+ private buildGameConfigPayload;
7856
+ /** Publish the resolved display config over the bridge. */
7654
7857
  /**
7655
- * Emit a state:dispatch event to the zylemEventBus.
7656
- * Called after stage load and on global state changes.
7858
+ * Publish the display config, skipping unchanged payloads. The
7859
+ * `ResizeObserver` on `<zylem-game>` funnels into `setDisplayRuntime`, which
7860
+ * fires on every layout tick even when nothing about the config moved.
7657
7861
  */
7658
- private emitStateDispatch;
7862
+ private publishGameConfig;
7863
+ /** Publish a full stage snapshot (config + entity list) over the bridge. */
7864
+ private publishStageSnapshot;
7659
7865
  /**
7660
- * Subscribe to the game event bus for stage loading and state events.
7661
- * Emits events to zylemEventBus for cross-package communication.
7866
+ * Subscribe to the game event bus for stage loading and state events,
7867
+ * mirroring global-variable updates to the editor over the bridge.
7662
7868
  */
7663
7869
  private subscribeToEventBus;
7664
7870
  }
@@ -8092,15 +8298,34 @@ export declare class ZylemSprite extends GameEntity<ZylemSpriteOptions> {
8092
8298
  protected spriteMap: Map<string, number>;
8093
8299
  protected currentSpriteIndex: number;
8094
8300
  protected animations: Map<string, any>;
8095
- protected currentAnimation: any;
8301
+ protected currentAnimation: string | null;
8096
8302
  protected currentAnimationFrame: string;
8097
8303
  protected currentAnimationIndex: number;
8098
8304
  protected currentAnimationTime: number;
8305
+ /** Set only in sheet mode; the shared texture whose UV window we shift. */
8306
+ protected sheetTexture: Texture | null;
8307
+ protected flippedX: boolean;
8099
8308
  constructor(options?: ZylemSpriteOptions);
8100
8309
  create(): this;
8101
8310
  protected createSpritesFromImages(images: SpriteImage[]): void;
8311
+ protected createSpriteFromSheet(sheet: SpriteSheet): void;
8102
8312
  protected createAnimations(animations: SpriteAnimation[]): void;
8103
8313
  setSprite(key: string): void;
8314
+ /**
8315
+ * Mirror the sprite horizontally, for characters whose sheet only contains
8316
+ * one facing direction.
8317
+ *
8318
+ * In sheet mode this flips the UV window rather than the sprite scale,
8319
+ * because {@link syncSpriteMaterials} rewrites scale from `options.size`
8320
+ * every frame and would immediately undo a negative scale.
8321
+ */
8322
+ setFlipX(flip: boolean): void;
8323
+ isFlippedX(): boolean;
8324
+ private applySheetFrame;
8325
+ /**
8326
+ * Advance a named clip. Call once per frame from an update loop; `delta` is
8327
+ * seconds, so scaling it scales playback rate.
8328
+ */
8104
8329
  setAnimation(name: string, delta: number): void;
8105
8330
  private getCurrentRotationQuaternion;
8106
8331
  private syncSpriteMaterials;
@@ -8112,6 +8337,7 @@ export declare class ZylemSprite extends GameEntity<ZylemSpriteOptions> {
8112
8337
 
8113
8338
  declare type ZylemSpriteOptions = GameEntityOptions & {
8114
8339
  images?: SpriteImage[];
8340
+ sheet?: SpriteSheet;
8115
8341
  animations?: SpriteAnimation[];
8116
8342
  size?: Vec3Input;
8117
8343
  collisionSize?: Vec3Input;