@energy8platform/game-engine 0.34.2 → 0.35.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 (45) hide show
  1. package/dist/audio.cjs.js +114 -59
  2. package/dist/audio.cjs.js.map +1 -1
  3. package/dist/audio.d.ts +25 -0
  4. package/dist/audio.esm.js +114 -59
  5. package/dist/audio.esm.js.map +1 -1
  6. package/dist/core.cjs.js +222 -66
  7. package/dist/core.cjs.js.map +1 -1
  8. package/dist/core.d.ts +25 -0
  9. package/dist/core.esm.js +223 -67
  10. package/dist/core.esm.js.map +1 -1
  11. package/dist/flow.cjs.js +246 -0
  12. package/dist/flow.cjs.js.map +1 -1
  13. package/dist/flow.d.ts +192 -33
  14. package/dist/flow.esm.js +238 -1
  15. package/dist/flow.esm.js.map +1 -1
  16. package/dist/host.cjs.js +343 -82
  17. package/dist/host.cjs.js.map +1 -1
  18. package/dist/host.d.ts +82 -2
  19. package/dist/host.esm.js +344 -83
  20. package/dist/host.esm.js.map +1 -1
  21. package/dist/index.cjs.js +222 -66
  22. package/dist/index.cjs.js.map +1 -1
  23. package/dist/index.d.ts +72 -0
  24. package/dist/index.esm.js +223 -67
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/scene-devtools.cjs.js +529 -115
  27. package/dist/scene-devtools.cjs.js.map +1 -1
  28. package/dist/scene-devtools.d.ts +187 -34
  29. package/dist/scene-devtools.esm.js +529 -115
  30. package/dist/scene-devtools.esm.js.map +1 -1
  31. package/dist/scene.cjs.js +704 -46
  32. package/dist/scene.cjs.js.map +1 -1
  33. package/dist/scene.d.ts +228 -41
  34. package/dist/scene.esm.js +698 -47
  35. package/dist/scene.esm.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/audio/AudioManager.ts +111 -53
  38. package/src/core/GameApplication.ts +47 -5
  39. package/src/host/buildConfig.ts +17 -4
  40. package/src/host/createSlotGame.ts +114 -12
  41. package/src/host/index.ts +3 -0
  42. package/src/host/types.ts +58 -0
  43. package/src/loading/LoadingScene.ts +76 -2
  44. package/src/loading/index.ts +6 -0
  45. package/src/types.ts +2 -0
package/dist/scene.d.ts CHANGED
@@ -6,13 +6,17 @@ interface SceneDoc {
6
6
  version: 1;
7
7
  /** Stable document id (usually the game id). */
8
8
  id: string;
9
+ /**
10
+ * Another scene this one builds on. The resolved doc is base ⊕ this, merged by node id,
11
+ * so a per-mode scene carries only its delta instead of a copy of the whole board.
12
+ * Resolved by `resolveExtends` before the doc reaches the engine.
13
+ */
14
+ extends?: string;
9
15
  /** Design space the layout rules are authored in (viewport arrives in these units). */
10
16
  design: {
11
17
  width: number;
12
18
  height: number;
13
19
  };
14
- /** Authoring modes the scene has views for (e.g. ['base','free_spins']); base is implicit. */
15
- modes?: string[];
16
20
  root: SceneNode;
17
21
  }
18
22
  interface SceneNode {
@@ -32,13 +36,6 @@ interface SceneNode {
32
36
  * frame's `anticipation` / `bonus` looks. A state is an override, not a new node.
33
37
  */
34
38
  states?: Record<string, NodeOverride>;
35
- /**
36
- * Per-GAME-MODE override, applied automatically when the runtime `mode` var matches
37
- * (base / free_spins / …). This is why base and free_spins are ONE scene, not two: a
38
- * node re-poses per mode instead of being duplicated into a separate scene. Layered
39
- * after orientation, before the active state.
40
- */
41
- modes?: Record<string, NodeOverride>;
42
39
  /** Id of another node in this doc used as this node's mask. */
43
40
  mask?: string;
44
41
  /** Filters applied to the node's view; kinds resolve through the registry (core: blur). */
@@ -158,21 +155,26 @@ interface PinLayout extends LayoutCommon {
158
155
  }
159
156
  type PinEdge = 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
160
157
  type LayoutRule = AbsoluteLayout | ViewportFractionLayout | CoverLayout | FrameFractionLayout | GridCellLayout | PinLayout;
161
- type ScenePatch =
162
- /** `mode` targets that mode's override (node.modes[mode].props); else base props. */
163
- {
158
+ type ScenePatch = {
164
159
  op: 'set-props';
165
160
  id: string;
166
161
  props: Record<string, unknown>;
167
- mode?: string;
168
162
  }
169
- /** Targets, in precedence: `mode` (node.modes[mode]) → `orientation` (responsive) → base. */
163
+ /**
164
+ * Targets `orientation` (responsive) when given, else the base rule. `layout: null` clears
165
+ * that slot — which is how undo removes an override the edit created.
166
+ */
170
167
  | {
171
168
  op: 'set-layout';
172
169
  id: string;
173
- layout: LayoutRule;
170
+ layout: LayoutRule | null;
174
171
  orientation?: Orientation;
175
- mode?: string;
172
+ }
173
+ /** Rename the outliner label. `name` is presentation only — addressing is always by id. */
174
+ | {
175
+ op: 'set-name';
176
+ id: string;
177
+ name: string | undefined;
176
178
  } | {
177
179
  op: 'set-state';
178
180
  id: string;
@@ -204,6 +206,22 @@ type ScenePatch =
204
206
  op: 'duplicate-node';
205
207
  id: string;
206
208
  };
209
+ /**
210
+ * Outcome of applying a patch. Rejections used to be console-only warnings inside the
211
+ * game, so an editor (or agent) saw "ok" for a patch that never applied — the reason now
212
+ * travels back to the caller.
213
+ */
214
+ type PatchResult = {
215
+ ok: true;
216
+ /**
217
+ * The patch that undoes this one. Applying it restores the pre-patch state exactly,
218
+ * which is what the editor's undo stack replays. Absent only when the op had no effect.
219
+ */
220
+ inverse?: ScenePatch;
221
+ } | {
222
+ ok: false;
223
+ error: string;
224
+ };
207
225
  /** Outliner row — the tree the inspector and the agent both read. */
208
226
  interface OutlineNode {
209
227
  id: string;
@@ -215,6 +233,91 @@ interface OutlineNode {
215
233
  children: OutlineNode[];
216
234
  }
217
235
 
236
+ /** A node in an extending doc may delete an inherited node instead of overriding it. */
237
+ declare const REMOVE_MARKER = "$remove";
238
+ interface ExtendOptions {
239
+ /** Resolve a parent reference (a path, an id — whatever the host uses) to its doc. */
240
+ load: (ref: string) => SceneDoc | undefined;
241
+ /** Guards against a cycle; also the practical depth limit for a chain of modes. */
242
+ maxDepth?: number;
243
+ }
244
+ /**
245
+ * Resolve a doc's `extends` chain into a single self-contained doc. Docs without `extends`
246
+ * are returned as-is (cloned), so this is safe to call on everything.
247
+ */
248
+ declare function resolveExtends(doc: SceneDoc, opts: ExtendOptions): SceneDoc;
249
+ /**
250
+ * The patches that turn `from` into `to`. Used to switch game modes without rebuilding: a
251
+ * node both docs share and agree on is never touched, so the reel system, its symbols and
252
+ * any running animation survive the transition.
253
+ *
254
+ * Order matters — removals first (ids can be reused), then additions parent-before-child,
255
+ * then property updates.
256
+ */
257
+ declare function diffDocs(from: SceneDoc, to: SceneDoc): ScenePatch[];
258
+
259
+ type FieldKind = 'number' | 'text' | 'boolean' | 'color'
260
+ /** One of `options`. */
261
+ | 'enum'
262
+ /** A texture/audio alias from the game's asset manifest — the editor offers a picker. */
263
+ | 'asset'
264
+ /** The id of another node in this doc — the editor offers the node list. */
265
+ | 'nodeRef'
266
+ /** Nested record described by `fields`. */
267
+ | 'object'
268
+ /** Anything else: edited as raw JSON. */
269
+ | 'json';
270
+ interface FieldSchema {
271
+ kind: FieldKind;
272
+ /** Human label (defaults to the key). */
273
+ label?: string;
274
+ /** What the field does — inspector tooltip AND the agent's documentation. */
275
+ doc?: string;
276
+ /**
277
+ * Value the runtime uses when the field is absent. The inspector shows it as the
278
+ * effective value so "not set" never looks like "zero".
279
+ */
280
+ default?: unknown;
281
+ /** `enum`: the allowed values. */
282
+ options?: readonly string[];
283
+ /** `number`: bounds and input step. */
284
+ min?: number;
285
+ max?: number;
286
+ step?: number;
287
+ /** `asset`: which kind of media this field accepts (filters the picker). */
288
+ accept?: 'image' | 'audio' | 'spritesheet';
289
+ /** `nodeRef`: restrict the picker to nodes of this type (e.g. `reelFrame`). */
290
+ ofType?: string;
291
+ /** `object`: the nested fields. */
292
+ fields?: PropsSchema;
293
+ /** Lower sorts first in the inspector; unordered fields follow, alphabetically. */
294
+ order?: number;
295
+ }
296
+ /** A node type's (or prefab's) authorable props, keyed by prop name. */
297
+ type PropsSchema = Record<string, FieldSchema>;
298
+ /** Shared field definitions, so every kind describes `alpha` the same way. */
299
+ declare const ALPHA_FIELD: FieldSchema;
300
+ /** Fractions of a texture (0..1), used for both `region` (crop) and `inner` (frame hollow). */
301
+ declare const fracBoxFields: (doc: string) => FieldSchema;
302
+ /**
303
+ * Flatten a schema into dot-path rows, pairing each with the value currently in `props`
304
+ * (`undefined` when unset). Nested `object` fields expand to `parent.child`, matching how
305
+ * layout rules are already flattened for the inspector.
306
+ */
307
+ interface SchemaFieldRow {
308
+ /** Dot-path into props ('src', 'sheet.alias'). */
309
+ path: string;
310
+ label: string;
311
+ schema: FieldSchema;
312
+ /** Current value, or `undefined` when the prop is not set. */
313
+ value: unknown;
314
+ /** True when the value comes from the schema default rather than the doc. */
315
+ unset: boolean;
316
+ }
317
+ declare function schemaFieldRows(schema: PropsSchema | undefined, props: Record<string, unknown>): SchemaFieldRow[];
318
+ /** Props present on the node that the schema doesn't describe (plugin extras, hand-written). */
319
+ declare function extraPropKeys(schema: PropsSchema | undefined, props: Record<string, unknown>): string[];
320
+
218
321
  interface Rect {
219
322
  x: number;
220
323
  y: number;
@@ -263,7 +366,7 @@ type LayoutResolution = {
263
366
  };
264
367
  declare function orientationOf(width: number, height: number, portraitFactor?: number): Orientation;
265
368
  /** Base node + orientation override + active state override (state wins), for one frame. */
266
- declare function effectiveNode(node: SceneNode, orientation: Orientation, state: string | null, mode?: string | null): {
369
+ declare function effectiveNode(node: SceneNode, orientation: Orientation, state: string | null): {
267
370
  layout?: LayoutRule;
268
371
  props: Record<string, unknown>;
269
372
  visible: boolean;
@@ -351,8 +454,8 @@ interface NodeDefaults {
351
454
  }
352
455
  interface NodeTypeContribution {
353
456
  kind: string;
354
- /** JSON-Schema-ish description of `props` — validation, inspector autogen, agent docs. */
355
- schema?: Record<string, unknown>;
457
+ /** Authorable `props` — drives the inspector's controls, validation and the agent's docs. */
458
+ schema?: PropsSchema;
356
459
  /** 3–10 lines for the agent: when to use this kind, patterns, anti-patterns. */
357
460
  agentDoc?: string;
358
461
  /** Palette skeleton — omit to hide the kind from the add-menu (e.g. `prefab` dispatch). */
@@ -362,7 +465,7 @@ interface NodeTypeContribution {
362
465
  /** Reusable game component (meters, HUDs) addressed as `{type:'prefab', props:{prefab:'name'}}`. */
363
466
  interface PrefabContribution {
364
467
  name: string;
365
- schema?: Record<string, unknown>;
468
+ schema?: PropsSchema;
366
469
  agentDoc?: string;
367
470
  /** Palette skeleton (props merged onto `{prefab:name}`, plus a starter layout). */
368
471
  defaults?: NodeDefaults;
@@ -371,7 +474,7 @@ interface PrefabContribution {
371
474
  /** A filter kind: `{kind:'blur', strength: 3}` in a node's `filters` resolves through this. */
372
475
  interface FilterKindContribution {
373
476
  kind: string;
374
- schema?: Record<string, unknown>;
477
+ schema?: PropsSchema;
375
478
  agentDoc?: string;
376
479
  create(spec: FilterSpec): Filter;
377
480
  }
@@ -435,11 +538,17 @@ interface SceneHandle {
435
538
  layout(width: number, height: number): void;
436
539
  setVar(name: string, value: unknown): void;
437
540
  setState(id: string, state: string | null): void;
438
- patch(patch: ScenePatch): void;
541
+ /** Apply an edit. Returns `{ok:false, error}` when the patch was rejected. */
542
+ patch(patch: ScenePatch): PatchResult;
439
543
  /** The live doc (mutated by patches) — serialize this to persist the scene. */
440
544
  doc(): SceneDoc;
441
545
  /** Spawnable palette (node kinds with defaults + prefabs) — feeds the editor add-menu. */
442
546
  palette(): PaletteEntry[];
547
+ /**
548
+ * Props schemas by node kind (prefabs keyed `prefab:<name>`) — what the inspector renders
549
+ * its controls from, and what an agent reads as the authoring contract.
550
+ */
551
+ schemas(): Record<string, PropsSchema>;
443
552
  /**
444
553
  * Re-apply every node's doc-effective props, wiping transient presentation writes
445
554
  * (flow tweens/count-ups). "Settle = the doc" — scrub replays call this first.
@@ -514,15 +623,26 @@ interface TransformGizmoOptions {
514
623
  app: Application;
515
624
  applyPatch?: (patch: ScenePatch) => void;
516
625
  getOrientation?: () => Orientation;
517
- /** Current authoring mode; when set and not 'base', edits target that mode's override. */
518
- getMode?: () => string | null;
519
626
  /** Override screen→design conversion for move drags. Default: the node's parent world scale. */
520
627
  designPerPixel?: (nodeId: string) => number;
628
+ /**
629
+ * Gesture boundaries. A drag emits a patch per pointer frame, so undo needs to know that
630
+ * the whole gesture is ONE edit — otherwise ⌘Z rewinds a drag one mouse-move at a time.
631
+ */
632
+ onGestureStart?: () => void;
633
+ onGestureEnd?: () => void;
634
+ /**
635
+ * A press inside the selection box that never moved. The body covers the whole selected
636
+ * node so it can be dragged, which also made it swallow every click there — overlapping
637
+ * siblings and deeper children became unreachable once anything was selected. The host
638
+ * re-picks at this point instead.
639
+ */
640
+ onBodyClick?: (globalX: number, globalY: number, altKey: boolean) => void;
521
641
  }
522
642
  interface TransformGizmo {
523
643
  attach(nodeId: string | null): void;
524
644
  refresh(): void;
525
- /** Run a full handle drag programmatically (keyboard/agent/tests). Screen coords. */
645
+ /** Run a full handle drag programmatically (agent/tests). Screen coords. */
526
646
  drag(handle: HandleId, from: {
527
647
  x: number;
528
648
  y: number;
@@ -530,6 +650,11 @@ interface TransformGizmo {
530
650
  x: number;
531
651
  y: number;
532
652
  }): void;
653
+ /**
654
+ * Move the selection by a discrete amount (arrow keys). Deliberately NOT a synthetic drag:
655
+ * a drag ignores travel below the click/drag threshold, which would swallow a 1px nudge.
656
+ */
657
+ nudge(dxScreen: number, dyScreen: number): void;
533
658
  destroy(): void;
534
659
  }
535
660
  declare function createTransformGizmo(opts: TransformGizmoOptions): TransformGizmo;
@@ -748,9 +873,27 @@ interface CueDef {
748
873
  jitter?: number;
749
874
  volume?: number;
750
875
  }
876
+ /**
877
+ * Fields every step carries regardless of kind.
878
+ *
879
+ * `id` exists because steps used to be addressed purely by position — three different
880
+ * positional schemes across the codebase — so any insert or delete invalidated every
881
+ * sibling's path, and a trace entry could not say WHICH step it came from. It lives on the
882
+ * step rather than in a document-level map on purpose: the editor→game doc push copies only
883
+ * `on` and `cues`, so a new top-level key would be silently dropped and never saved.
884
+ */
885
+ interface StepCommon {
886
+ /** Stable identity. Assigned by `ensureStepIds` when a hand-written doc omits it. */
887
+ id?: string;
888
+ /** Editor-only: where the node sits on the flow canvas. Absent → auto-laid-out. */
889
+ ui?: {
890
+ x: number;
891
+ y: number;
892
+ };
893
+ }
751
894
  /** Core step vocabulary; plugins contribute additional `do` kinds through the registry. */
752
895
  type Step = TweenStep | SoundStep | SetStateStep | SetVarStep | SetPropsStep | CountUpStep | WaitStep | IfStep | ParallelStep | SeqStep | ForEachStep | CodeStep | PluginStep;
753
- interface TweenStep {
896
+ interface TweenStep extends StepCommon {
754
897
  do: 'tween';
755
898
  node: string;
756
899
  /** Target numeric view properties (alpha, x, y, scale, rotation, …). */
@@ -758,28 +901,28 @@ interface TweenStep {
758
901
  ms: number;
759
902
  ease?: string;
760
903
  }
761
- interface SoundStep {
904
+ interface SoundStep extends StepCommon {
762
905
  do: 'sound';
763
906
  cue: string;
764
907
  action?: 'play' | 'stop';
765
908
  }
766
- interface SetStateStep {
909
+ interface SetStateStep extends StepCommon {
767
910
  do: 'setState';
768
911
  node: string;
769
912
  state: string | null;
770
913
  }
771
- interface SetVarStep {
914
+ interface SetVarStep extends StepCommon {
772
915
  do: 'setVar';
773
916
  name: string;
774
917
  value: unknown;
775
918
  }
776
919
  /** Transient prop write on the live instance (presentation) — does NOT touch the doc. */
777
- interface SetPropsStep {
920
+ interface SetPropsStep extends StepCommon {
778
921
  do: 'setProps';
779
922
  node: string;
780
923
  props: Record<string, unknown>;
781
924
  }
782
- interface CountUpStep {
925
+ interface CountUpStep extends StepCommon {
783
926
  do: 'countUp';
784
927
  node: string;
785
928
  /** Instance prop receiving the formatted value (default 'value' — badge prefabs). */
@@ -790,24 +933,24 @@ interface CountUpStep {
790
933
  ms?: number;
791
934
  format?: 'int' | 'space';
792
935
  }
793
- interface WaitStep {
936
+ interface WaitStep extends StepCommon {
794
937
  do: 'wait';
795
938
  ms?: number;
796
939
  until?: 'tap';
797
940
  }
798
- interface IfStep {
941
+ interface IfStep extends StepCommon {
799
942
  do: 'if';
800
943
  /** Micro-expression over the fire() ctx: `win >= 100`, `mode === 'fs'`, or a bare truthy name. */
801
944
  when: string;
802
945
  then: Step[];
803
946
  else?: Step[];
804
947
  }
805
- interface ParallelStep {
948
+ interface ParallelStep extends StepCommon {
806
949
  do: 'parallel';
807
950
  /** Independent tracks, awaited together. */
808
951
  steps: Step[][];
809
952
  }
810
- interface SeqStep {
953
+ interface SeqStep extends StepCommon {
811
954
  do: 'seq';
812
955
  steps: Step[];
813
956
  }
@@ -816,7 +959,7 @@ interface SeqStep {
816
959
  * flights, per-cell transmutes, cascade histories). Nested steps see the current element
817
960
  * as `$<as>` (default `$item`) and its index as `$<as>Index`.
818
961
  */
819
- interface ForEachStep {
962
+ interface ForEachStep extends StepCommon {
820
963
  do: 'forEach';
821
964
  /** '$path' into the ctx (or an inline array). */
822
965
  items: string | unknown[];
@@ -827,12 +970,13 @@ interface ForEachStep {
827
970
  steps: Step[];
828
971
  }
829
972
  /** Escape hatch: a named code choreography registered on the runner. */
830
- interface CodeStep {
973
+ interface CodeStep extends StepCommon {
831
974
  do: 'code';
832
975
  ref: string;
833
976
  args?: Record<string, unknown>;
834
977
  }
835
- interface PluginStep {
978
+ /** A plugin-contributed kind: open shape, but it still carries the common fields. */
979
+ interface PluginStep extends StepCommon {
836
980
  do: string;
837
981
  [key: string]: unknown;
838
982
  }
@@ -840,6 +984,12 @@ interface TraceEntry {
840
984
  /** ms since fire() (real time, unscaled). */
841
985
  t: number;
842
986
  do: string;
987
+ /**
988
+ * Which step produced this entry. Without it the trace only says a `tween` ran — two
989
+ * identical tweens are indistinguishable — so a canvas cannot highlight what is running
990
+ * and a scrub cannot point at a step.
991
+ */
992
+ stepId?: string;
843
993
  node?: string;
844
994
  info?: string;
845
995
  }
@@ -893,6 +1043,16 @@ interface FlowRunner {
893
1043
  /** Host reports a user tap (releases `wait until:'tap'`). */
894
1044
  tap(): void;
895
1045
  events(): string[];
1046
+ /**
1047
+ * The step kinds this runner accepts, with their authoring metadata. The editor lives in
1048
+ * another origin and cannot reach the registry, so the contract travels through here —
1049
+ * before this, field lists were hardcoded in the editor (twice, and already drifting).
1050
+ */
1051
+ stepKinds(): Array<{
1052
+ kind: string;
1053
+ schema?: PropsSchema;
1054
+ agentDoc?: string;
1055
+ }>;
896
1056
  destroy(): void;
897
1057
  }
898
1058
  /** Per-run runtime handed to step executors (core and plugin alike). */
@@ -926,7 +1086,8 @@ interface FlowRuntime {
926
1086
 
927
1087
  interface FlowStepContribution {
928
1088
  kind: string;
929
- schema?: Record<string, unknown>;
1089
+ /** Authorable fields — drives the editor's controls and documents the step for an agent. */
1090
+ schema?: PropsSchema;
930
1091
  /** 3–10 lines for the agent: when to use the step, patterns, anti-patterns. */
931
1092
  agentDoc?: string;
932
1093
  run(step: Step, rt: FlowRuntime): Promise<void> | void;
@@ -943,6 +1104,13 @@ interface StageDoc {
943
1104
  key: string;
944
1105
  scene: SceneDoc;
945
1106
  flow?: FlowDoc;
1107
+ /**
1108
+ * The game mode this scene is the view for (`free_spins`, `bonus`, …). Such a scene is
1109
+ * NOT a separate host scene: it is folded into the scene it extends and applied as a
1110
+ * diff when the mode starts, so the board is not rebuilt. Omit for real screens (intro,
1111
+ * an adventure map) — those stay their own host scene.
1112
+ */
1113
+ mode?: string;
946
1114
  /** Host skips this scene on replay launches (intro idiom). */
947
1115
  skipOnReplay?: boolean;
948
1116
  /** A tap anywhere advances to this scene key (intro → game). */
@@ -957,6 +1125,14 @@ declare function buildDocScenes<T extends SlotSpinResultBase = SlotSpinResultBas
957
1125
  }>;
958
1126
  interface DocSceneOptions<T extends SlotSpinResultBase = SlotSpinResultBase> {
959
1127
  scene: SceneDoc;
1128
+ /**
1129
+ * A scene doc per game mode (`free_spins`, `bonus`, …) — each authored as its own file,
1130
+ * usually `extends`-ing this one so it carries only its delta. Entering a mode applies
1131
+ * the DIFF to the live scene: shared nodes are never touched, so the reel system, its
1132
+ * symbols and any running animation survive the transition. (No game in the surveyed set
1133
+ * rebuilds its board on mode entry, and rebuilding mid-spin would be visible.)
1134
+ */
1135
+ modeScenes?: Record<string, SceneDoc>;
960
1136
  flow?: FlowDoc;
961
1137
  /** A tap anywhere advances to this scene key (uses the host-injected goto). */
962
1138
  advanceOnTap?: string;
@@ -995,14 +1171,25 @@ declare class DocScene<T extends SlotSpinResultBase = SlotSpinResultBase> extend
995
1171
  private ctxOf;
996
1172
  onCreate(api: SceneApi): void;
997
1173
  onSpinStart(): void;
1174
+ /** The resolved doc currently on screen, so a mode switch diffs from the right baseline. */
1175
+ private activeDoc_;
1176
+ private activeDocId_;
998
1177
  onSpin(result: T, ctx: RenderContext): Promise<void>;
999
1178
  onEnterMode(result: T, ctx: RenderContext): Promise<void>;
1000
1179
  onExitMode(result: T, ctx: RenderContext): Promise<void>;
1180
+ /**
1181
+ * Move the live scene to the doc for `mode` (null = the base scene) by applying the diff.
1182
+ * A no-op when the mode has no scene of its own, which is the common case: most modes
1183
+ * differ only in what flow does.
1184
+ */
1185
+ private applySceneFor;
1186
+ /** Fold a doc's `extends` chain, looking parents up among the scenes this game declared. */
1187
+ private resolveDoc;
1001
1188
  onSpinEnd(result: T, ctx: RenderContext): void;
1002
1189
  onSkip(): void;
1003
1190
  onTurboChanged(level: number): void;
1004
1191
  }
1005
1192
  declare function createDocScene<T extends SlotSpinResultBase = SlotSpinResultBase>(opts: DocSceneOptions<T>): DocScene<T>;
1006
1193
 
1007
- export { BUILTIN_FILTER_KINDS, BUILTIN_NODE_TYPES, DocScene, buildDocScenes, createDocScene, createInnerCalibrator, createSceneFromDoc, createSceneRegistry, createTransformGizmo, dependencyOf, dragInnerEdge, edgePoint, effectiveNode, evalVisibleWhen, handlePoint, nudgeRule, orientationOf, placementBounds, resizeFactors, resizeRule, resolveLayoutRule, setRuleRotation, sizeLever, validateSceneDoc };
1008
- export type { AbsoluteLayout, Box, CoverLayout, CreateSceneOptions, DocSceneOptions, FilterKindContribution, FilterSpec, FrameFractionLayout, GridCellLayout, HandleId, InnerCalibrator, InnerCalibratorOptions, InnerEdge, InnerFracs, LayoutContext, LayoutResolution, LayoutRule, NodeCreateContext, NodeDefaults, NodeInstance, NodeOverride, NodeTypeContribution, Orientation, OutlineNode, PaletteEntry, PinEdge, PinLayout, Placement, PrefabContribution, Rect, SceneDoc, SceneHandle, SceneNode, ScenePatch, ScenePlugin, SceneRegistry, SceneValidationError, Size, StageDoc, TransformGizmo, TransformGizmoOptions, ViewportFractionLayout };
1194
+ export { ALPHA_FIELD, BUILTIN_FILTER_KINDS, BUILTIN_NODE_TYPES, DocScene, REMOVE_MARKER, buildDocScenes, createDocScene, createInnerCalibrator, createSceneFromDoc, createSceneRegistry, createTransformGizmo, dependencyOf, diffDocs, dragInnerEdge, edgePoint, effectiveNode, evalVisibleWhen, extraPropKeys, fracBoxFields, handlePoint, nudgeRule, orientationOf, placementBounds, resizeFactors, resizeRule, resolveExtends, resolveLayoutRule, schemaFieldRows, setRuleRotation, sizeLever, validateSceneDoc };
1195
+ export type { AbsoluteLayout, Box, CoverLayout, CreateSceneOptions, DocSceneOptions, ExtendOptions, FieldKind, FieldSchema, FilterKindContribution, FilterSpec, FrameFractionLayout, GridCellLayout, HandleId, InnerCalibrator, InnerCalibratorOptions, InnerEdge, InnerFracs, LayoutContext, LayoutResolution, LayoutRule, NodeCreateContext, NodeDefaults, NodeInstance, NodeOverride, NodeTypeContribution, Orientation, OutlineNode, PaletteEntry, PatchResult, PinEdge, PinLayout, Placement, PrefabContribution, PropsSchema, Rect, SceneDoc, SceneHandle, SceneNode, ScenePatch, ScenePlugin, SceneRegistry, SceneValidationError, SchemaFieldRow, Size, StageDoc, TransformGizmo, TransformGizmoOptions, ViewportFractionLayout };