@energy8platform/game-engine 0.33.6 → 0.33.8
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.
- package/dist/flow.cjs.js +155 -86
- package/dist/flow.cjs.js.map +1 -1
- package/dist/flow.d.ts +92 -3
- package/dist/flow.esm.js +155 -86
- package/dist/flow.esm.js.map +1 -1
- package/dist/host.cjs.js +19 -8
- package/dist/host.cjs.js.map +1 -1
- package/dist/host.esm.js +19 -8
- package/dist/host.esm.js.map +1 -1
- package/dist/scene-devtools.cjs.js +767 -3
- package/dist/scene-devtools.cjs.js.map +1 -1
- package/dist/scene-devtools.d.ts +256 -3
- package/dist/scene-devtools.esm.js +765 -4
- package/dist/scene-devtools.esm.js.map +1 -1
- package/dist/scene.cjs.js +1359 -21
- package/dist/scene.cjs.js.map +1 -1
- package/dist/scene.d.ts +608 -4
- package/dist/scene.esm.js +1347 -22
- package/dist/scene.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/host/balanceGate.ts +21 -10
- package/src/host/createSlotGame.ts +7 -5
package/dist/scene.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Container, Texture } from 'pixi.js';
|
|
1
|
+
import { Container, Filter, Texture, Application } from 'pixi.js';
|
|
2
|
+
import { SlotSpinResultBase } from '@energy8platform/platform-core/slot-result';
|
|
2
3
|
|
|
3
4
|
type Orientation = 'landscape' | 'portrait';
|
|
4
5
|
interface SceneDoc {
|
|
@@ -31,6 +32,8 @@ interface SceneNode {
|
|
|
31
32
|
states?: Record<string, NodeOverride>;
|
|
32
33
|
/** Id of another node in this doc used as this node's mask. */
|
|
33
34
|
mask?: string;
|
|
35
|
+
/** Filters applied to the node's view; kinds resolve through the registry (core: blur). */
|
|
36
|
+
filters?: FilterSpec[];
|
|
34
37
|
/**
|
|
35
38
|
* Tiny condition over runtime vars, e.g. `"mode === 'free_spins'"`. Supported form:
|
|
36
39
|
* `<var> <op> <literal>` with `=== !== >= <= > <`. Anything richer belongs in flow/code.
|
|
@@ -41,6 +44,11 @@ interface SceneNode {
|
|
|
41
44
|
/** Order = z-order. */
|
|
42
45
|
children?: SceneNode[];
|
|
43
46
|
}
|
|
47
|
+
/** A filter instance: kind + kind-specific params (validated by the contribution). */
|
|
48
|
+
interface FilterSpec {
|
|
49
|
+
kind: string;
|
|
50
|
+
[param: string]: unknown;
|
|
51
|
+
}
|
|
44
52
|
/** Partial node applied on top of the base for an orientation or a named state. */
|
|
45
53
|
interface NodeOverride {
|
|
46
54
|
layout?: LayoutRule;
|
|
@@ -57,6 +65,13 @@ interface LayoutCommon {
|
|
|
57
65
|
};
|
|
58
66
|
/** Static rotation in radians (choreographed rotation belongs to flow/animation). */
|
|
59
67
|
rotation?: number;
|
|
68
|
+
/**
|
|
69
|
+
* `'viewport'` resolves this rule against the LIVE viewport even when the node lives
|
|
70
|
+
* inside a nested coordinate space — the engine converts the result into the space's
|
|
71
|
+
* coordinates. The paper-duel logo idiom: screen-pinned, but rendered at a diorama
|
|
72
|
+
* z-depth.
|
|
73
|
+
*/
|
|
74
|
+
space?: 'viewport';
|
|
60
75
|
}
|
|
61
76
|
/** Fixed design-space coordinates. */
|
|
62
77
|
interface AbsoluteLayout extends LayoutCommon {
|
|
@@ -153,6 +168,28 @@ type ScenePatch = {
|
|
|
153
168
|
op: 'set-var';
|
|
154
169
|
name: string;
|
|
155
170
|
value: unknown;
|
|
171
|
+
}
|
|
172
|
+
/** Insert `node` under `parent` at `index` (default: append). Ids must be unused. */
|
|
173
|
+
| {
|
|
174
|
+
op: 'add-node';
|
|
175
|
+
parent: string;
|
|
176
|
+
node: SceneNode;
|
|
177
|
+
index?: number;
|
|
178
|
+
} | {
|
|
179
|
+
op: 'remove-node';
|
|
180
|
+
id: string;
|
|
181
|
+
}
|
|
182
|
+
/** Reparent/reorder `id` under `parent` at `index` (same parent = pure reorder). */
|
|
183
|
+
| {
|
|
184
|
+
op: 'move-node';
|
|
185
|
+
id: string;
|
|
186
|
+
parent: string;
|
|
187
|
+
index?: number;
|
|
188
|
+
}
|
|
189
|
+
/** Deep-copy `id` (all descendant ids suffixed to stay unique) as its next sibling. */
|
|
190
|
+
| {
|
|
191
|
+
op: 'duplicate-node';
|
|
192
|
+
id: string;
|
|
156
193
|
};
|
|
157
194
|
/** Outliner row — the tree the inspector and the agent both read. */
|
|
158
195
|
interface OutlineNode {
|
|
@@ -252,6 +289,23 @@ interface SymbolView extends Container {
|
|
|
252
289
|
/** Game-supplied factory: build the view for a symbol id (sprite / layered sprites / Spine / composite). */
|
|
253
290
|
type SymbolResolver = (symbolId: string) => SymbolView | null;
|
|
254
291
|
|
|
292
|
+
interface IScene {
|
|
293
|
+
/** Root display container for this scene */
|
|
294
|
+
readonly container: Container;
|
|
295
|
+
/** @internal GameApplication reference — set by SceneManager */
|
|
296
|
+
__engineApp?: any;
|
|
297
|
+
/** Called when the scene is entered */
|
|
298
|
+
onEnter?(data?: unknown): Promise<void> | void;
|
|
299
|
+
/** Called when the scene is exited */
|
|
300
|
+
onExit?(): Promise<void> | void;
|
|
301
|
+
/** Called every frame */
|
|
302
|
+
onUpdate?(dt: number): void;
|
|
303
|
+
/** Called when viewport resizes */
|
|
304
|
+
onResize?(width: number, height: number): void;
|
|
305
|
+
/** Called when the scene is destroyed */
|
|
306
|
+
onDestroy?(): void;
|
|
307
|
+
}
|
|
308
|
+
|
|
255
309
|
interface NodeCreateContext {
|
|
256
310
|
registry: SceneRegistry;
|
|
257
311
|
/** Resolve a texture alias (defaults to Pixi `Texture.from`; tests/labs inject). */
|
|
@@ -274,12 +328,22 @@ interface NodeInstance {
|
|
|
274
328
|
};
|
|
275
329
|
destroy?(): void;
|
|
276
330
|
}
|
|
331
|
+
/** Skeleton a palette/agent uses to spawn a fresh node of this kind (id is assigned by the caller). */
|
|
332
|
+
interface NodeDefaults {
|
|
333
|
+
/** Human label for the palette button (defaults to the kind). */
|
|
334
|
+
label?: string;
|
|
335
|
+
name?: string;
|
|
336
|
+
props?: Record<string, unknown>;
|
|
337
|
+
layout?: SceneNode['layout'];
|
|
338
|
+
}
|
|
277
339
|
interface NodeTypeContribution {
|
|
278
340
|
kind: string;
|
|
279
341
|
/** JSON-Schema-ish description of `props` — validation, inspector autogen, agent docs. */
|
|
280
342
|
schema?: Record<string, unknown>;
|
|
281
343
|
/** 3–10 lines for the agent: when to use this kind, patterns, anti-patterns. */
|
|
282
344
|
agentDoc?: string;
|
|
345
|
+
/** Palette skeleton — omit to hide the kind from the add-menu (e.g. `prefab` dispatch). */
|
|
346
|
+
defaults?: NodeDefaults;
|
|
283
347
|
create(node: SceneNode, ctx: NodeCreateContext): NodeInstance;
|
|
284
348
|
}
|
|
285
349
|
/** Reusable game component (meters, HUDs) addressed as `{type:'prefab', props:{prefab:'name'}}`. */
|
|
@@ -287,22 +351,47 @@ interface PrefabContribution {
|
|
|
287
351
|
name: string;
|
|
288
352
|
schema?: Record<string, unknown>;
|
|
289
353
|
agentDoc?: string;
|
|
354
|
+
/** Palette skeleton (props merged onto `{prefab:name}`, plus a starter layout). */
|
|
355
|
+
defaults?: NodeDefaults;
|
|
290
356
|
create(props: Record<string, unknown>, ctx: NodeCreateContext): NodeInstance;
|
|
291
357
|
}
|
|
358
|
+
/** A filter kind: `{kind:'blur', strength: 3}` in a node's `filters` resolves through this. */
|
|
359
|
+
interface FilterKindContribution {
|
|
360
|
+
kind: string;
|
|
361
|
+
schema?: Record<string, unknown>;
|
|
362
|
+
agentDoc?: string;
|
|
363
|
+
create(spec: FilterSpec): Filter;
|
|
364
|
+
}
|
|
292
365
|
interface ScenePlugin {
|
|
293
366
|
id: string;
|
|
294
367
|
nodeTypes?: NodeTypeContribution[];
|
|
295
368
|
prefabs?: PrefabContribution[];
|
|
369
|
+
filterKinds?: FilterKindContribution[];
|
|
370
|
+
}
|
|
371
|
+
/** One palette entry — a spawnable node kind or prefab, with its skeleton. */
|
|
372
|
+
interface PaletteEntry {
|
|
373
|
+
kind: string;
|
|
374
|
+
label: string;
|
|
375
|
+
/** 'prefab' entries spawn `{type:'prefab', props:{prefab:kind}}`; others spawn `{type:kind}`. */
|
|
376
|
+
isPrefab: boolean;
|
|
377
|
+
agentDoc?: string;
|
|
378
|
+
defaults: NodeDefaults;
|
|
296
379
|
}
|
|
297
380
|
interface SceneRegistry {
|
|
298
381
|
nodeType(kind: string): NodeTypeContribution | undefined;
|
|
299
382
|
prefab(name: string): PrefabContribution | undefined;
|
|
383
|
+
filterKind(kind: string): FilterKindContribution | undefined;
|
|
300
384
|
kinds(): string[];
|
|
301
385
|
prefabNames(): string[];
|
|
386
|
+
filterKindNames(): string[];
|
|
387
|
+
/** Spawnable palette: node kinds with `defaults` + all registered prefabs. */
|
|
388
|
+
palette(): PaletteEntry[];
|
|
302
389
|
}
|
|
303
390
|
/** Merge built-ins with plugin contributions; later plugins may override earlier kinds. */
|
|
304
|
-
declare function createSceneRegistry(plugins?: ScenePlugin[], builtins?: NodeTypeContribution[]): SceneRegistry;
|
|
391
|
+
declare function createSceneRegistry(plugins?: ScenePlugin[], builtins?: NodeTypeContribution[], builtinFilters?: FilterKindContribution[]): SceneRegistry;
|
|
305
392
|
|
|
393
|
+
/** Core filter kinds; heavier looks (bloom etc.) arrive as plugins carrying their deps. */
|
|
394
|
+
declare const BUILTIN_FILTER_KINDS: FilterKindContribution[];
|
|
306
395
|
declare const BUILTIN_NODE_TYPES: NodeTypeContribution[];
|
|
307
396
|
|
|
308
397
|
interface SceneValidationError {
|
|
@@ -336,9 +425,524 @@ interface SceneHandle {
|
|
|
336
425
|
patch(patch: ScenePatch): void;
|
|
337
426
|
/** The live doc (mutated by patches) — serialize this to persist the scene. */
|
|
338
427
|
doc(): SceneDoc;
|
|
428
|
+
/** Spawnable palette (node kinds with defaults + prefabs) — feeds the editor add-menu. */
|
|
429
|
+
palette(): PaletteEntry[];
|
|
430
|
+
/**
|
|
431
|
+
* Re-apply every node's doc-effective props, wiping transient presentation writes
|
|
432
|
+
* (flow tweens/count-ups). "Settle = the doc" — scrub replays call this first.
|
|
433
|
+
*/
|
|
434
|
+
resetProps(): void;
|
|
339
435
|
destroy(): void;
|
|
340
436
|
}
|
|
341
437
|
declare function createSceneFromDoc(doc: SceneDoc, opts?: CreateSceneOptions): SceneHandle;
|
|
342
438
|
|
|
343
|
-
|
|
344
|
-
|
|
439
|
+
interface InnerFracs {
|
|
440
|
+
left: number;
|
|
441
|
+
top: number;
|
|
442
|
+
width: number;
|
|
443
|
+
height: number;
|
|
444
|
+
}
|
|
445
|
+
type InnerEdge = 'left' | 'right' | 'top' | 'bottom';
|
|
446
|
+
/** Pure edge-drag math: `frac` is the pointer position normalized to the frame rect axis. */
|
|
447
|
+
declare function dragInnerEdge(inner: InnerFracs, edge: InnerEdge, frac: number): InnerFracs;
|
|
448
|
+
interface InnerCalibratorOptions {
|
|
449
|
+
handle: SceneHandle;
|
|
450
|
+
/** The node whose props.inner is being calibrated (reelFrame or any inner-declaring node). */
|
|
451
|
+
nodeId: string;
|
|
452
|
+
/** Patch route (host may add logging/saving); defaults to handle.patch. */
|
|
453
|
+
applyPatch?: (patch: ScenePatch) => void;
|
|
454
|
+
onChange?: (inner: InnerFracs) => void;
|
|
455
|
+
}
|
|
456
|
+
interface InnerCalibrator {
|
|
457
|
+
/** Add to the stage ABOVE the scene view (canvas coordinates). */
|
|
458
|
+
view: Container;
|
|
459
|
+
/** Redraw from current bounds/props (call after resize or external patches). */
|
|
460
|
+
refresh(): void;
|
|
461
|
+
destroy(): void;
|
|
462
|
+
}
|
|
463
|
+
declare function createInnerCalibrator(opts: InnerCalibratorOptions): InnerCalibrator;
|
|
464
|
+
|
|
465
|
+
/** Which size lever a rule exposes — decides how a resize drag is interpreted. */
|
|
466
|
+
declare function sizeLever(rule: LayoutRule): 'wh-frac' | 'wh-box' | 'scale' | 'none';
|
|
467
|
+
/**
|
|
468
|
+
* Multiply a rule's size lever by (factorX, factorY). Corners pass equal factors (uniform);
|
|
469
|
+
* edges pass 1 on the untouched axis. Uniform levers (`scale`) use factorX.
|
|
470
|
+
*/
|
|
471
|
+
declare function resizeRule(rule: LayoutRule, factorX: number, factorY: number): LayoutRule;
|
|
472
|
+
declare function setRuleRotation(rule: LayoutRule, radians: number): LayoutRule;
|
|
473
|
+
/** Nudge a rule's final pixel offset by a screen delta already converted to design units. */
|
|
474
|
+
declare function nudgeRule(rule: LayoutRule, dxDesign: number, dyDesign: number): LayoutRule;
|
|
475
|
+
type HandleId = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'rotate' | 'body';
|
|
476
|
+
interface Box {
|
|
477
|
+
x: number;
|
|
478
|
+
y: number;
|
|
479
|
+
width: number;
|
|
480
|
+
height: number;
|
|
481
|
+
}
|
|
482
|
+
/** Screen position of each handle for a bounding box. */
|
|
483
|
+
declare function handlePoint(box: Box, id: Exclude<HandleId, 'body'>): {
|
|
484
|
+
x: number;
|
|
485
|
+
y: number;
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* Resize factors from dragging a handle, using the OPPOSITE edge/corner as the fixed
|
|
489
|
+
* reference — the intuitive "grab a corner, drag out, it grows" feel.
|
|
490
|
+
*/
|
|
491
|
+
declare function resizeFactors(startBox: Box, handle: HandleId, pointer: {
|
|
492
|
+
x: number;
|
|
493
|
+
y: number;
|
|
494
|
+
}): {
|
|
495
|
+
factorX: number;
|
|
496
|
+
factorY: number;
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
interface TransformGizmoOptions {
|
|
500
|
+
handle: SceneHandle;
|
|
501
|
+
app: Application;
|
|
502
|
+
applyPatch?: (patch: ScenePatch) => void;
|
|
503
|
+
getOrientation?: () => Orientation;
|
|
504
|
+
/** Override screen→design conversion for move drags. Default: the node's parent world scale. */
|
|
505
|
+
designPerPixel?: (nodeId: string) => number;
|
|
506
|
+
}
|
|
507
|
+
interface TransformGizmo {
|
|
508
|
+
attach(nodeId: string | null): void;
|
|
509
|
+
refresh(): void;
|
|
510
|
+
/** Run a full handle drag programmatically (keyboard/agent/tests). Screen coords. */
|
|
511
|
+
drag(handle: HandleId, from: {
|
|
512
|
+
x: number;
|
|
513
|
+
y: number;
|
|
514
|
+
}, to: {
|
|
515
|
+
x: number;
|
|
516
|
+
y: number;
|
|
517
|
+
}): void;
|
|
518
|
+
destroy(): void;
|
|
519
|
+
}
|
|
520
|
+
declare function createTransformGizmo(opts: TransformGizmoOptions): TransformGizmo;
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Base class for all scenes.
|
|
524
|
+
* Provides a root PixiJS Container and lifecycle hooks.
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```ts
|
|
528
|
+
* class MenuScene extends Scene {
|
|
529
|
+
* async onEnter() {
|
|
530
|
+
* const bg = Sprite.from('menu-bg');
|
|
531
|
+
* this.container.addChild(bg);
|
|
532
|
+
* }
|
|
533
|
+
*
|
|
534
|
+
* onUpdate(dt: number) {
|
|
535
|
+
* // per-frame logic
|
|
536
|
+
* }
|
|
537
|
+
*
|
|
538
|
+
* onResize(width: number, height: number) {
|
|
539
|
+
* // reposition UI
|
|
540
|
+
* }
|
|
541
|
+
* }
|
|
542
|
+
* ```
|
|
543
|
+
*/
|
|
544
|
+
declare abstract class Scene implements IScene {
|
|
545
|
+
readonly container: Container;
|
|
546
|
+
constructor();
|
|
547
|
+
/** Called when this scene becomes active. Override in subclass. */
|
|
548
|
+
onEnter?(data?: unknown): Promise<void> | void;
|
|
549
|
+
/** Called when this scene is deactivated. Override in subclass. */
|
|
550
|
+
onExit?(): Promise<void> | void;
|
|
551
|
+
/** Called every frame with delta time (in seconds). Override in subclass. */
|
|
552
|
+
onUpdate?(dt: number): void;
|
|
553
|
+
/** Called when the viewport resizes. Override in subclass. */
|
|
554
|
+
onResize?(width: number, height: number): void;
|
|
555
|
+
/** Cleanup — called when the scene is permanently removed. */
|
|
556
|
+
onDestroy?(): void;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/** Everything a scene needs to render one segment. The host builds it per segment. */
|
|
560
|
+
interface RenderContext {
|
|
561
|
+
/** Bet for this round (major units). Stable for the whole round. */
|
|
562
|
+
bet: number;
|
|
563
|
+
/** Trigger action in the game's own vocabulary ('spin' | 'ante' | 'buy_bonus' | …). */
|
|
564
|
+
action: string;
|
|
565
|
+
/** Stake bet-mode of the round ('BASE' | 'ANTE' | 'BONUS' | …). */
|
|
566
|
+
mode: string;
|
|
567
|
+
/** Currency-aware money formatter. */
|
|
568
|
+
formatAmount(value: number): string;
|
|
569
|
+
/** LIVE turbo level (0 = off, 1..3 = escalating speed). Read at access. */
|
|
570
|
+
readonly turbo: number;
|
|
571
|
+
/** Only meaningful in `onEnterMode`: true when RETURNING to a suspended parent bonus after a
|
|
572
|
+
* nested sub-bonus finished (e.g. back to free spins after an adventure), false on a fresh
|
|
573
|
+
* entry. Lets a scene restore vs rebuild. Undefined outside `onEnterMode`. */
|
|
574
|
+
resumed?: boolean;
|
|
575
|
+
/** Aborted when the player skips this segment (double-tap). The scene's async pacing can race or
|
|
576
|
+
* cancel on it; on abort the scene must collapse to the segment's final visual state. */
|
|
577
|
+
signal: AbortSignal;
|
|
578
|
+
}
|
|
579
|
+
/** Playback-only audio handle. Volume/mute are shell settings → host, never the scene. */
|
|
580
|
+
interface SceneAudio {
|
|
581
|
+
play(alias: string, opts?: {
|
|
582
|
+
volume?: number;
|
|
583
|
+
loop?: boolean;
|
|
584
|
+
speed?: number;
|
|
585
|
+
}): void;
|
|
586
|
+
playMusic(alias: string, fadeMs?: number): void;
|
|
587
|
+
stopMusic(): void;
|
|
588
|
+
duck(factor: number): void;
|
|
589
|
+
unduck(): void;
|
|
590
|
+
}
|
|
591
|
+
interface OverlayShowOptions {
|
|
592
|
+
/** Draw the overlay content into `container` (sized to the canvas). */
|
|
593
|
+
build(container: Container, size: {
|
|
594
|
+
width: number;
|
|
595
|
+
height: number;
|
|
596
|
+
}): void;
|
|
597
|
+
/** Re-layout the same `container` when the canvas resizes while the overlay is open.
|
|
598
|
+
* Receives the content container from `build` and the new size. Optional — omit for
|
|
599
|
+
* overlays that self-center or don't care about resize. */
|
|
600
|
+
onResize?(container: Container, size: {
|
|
601
|
+
width: number;
|
|
602
|
+
height: number;
|
|
603
|
+
}): void;
|
|
604
|
+
/** Auto-close after N ms (combine with closeOn — whichever fires first). */
|
|
605
|
+
autoCloseMs?: number;
|
|
606
|
+
/** Dismiss on a single tap. Default 'tap'. Set false to require an explicit close(). */
|
|
607
|
+
closeOn?: 'tap' | false;
|
|
608
|
+
/** Optional host-drawn backdrop alpha (0..1). Default: none (game draws its own). */
|
|
609
|
+
dim?: number;
|
|
610
|
+
}
|
|
611
|
+
/** Single host-owned layer above scene + shell. Eats pointer events so shell controls are
|
|
612
|
+
* unreachable while open. */
|
|
613
|
+
interface SceneOverlay {
|
|
614
|
+
/** Resolves when the overlay closes. Rejects if one is already open. */
|
|
615
|
+
show(opts: OverlayShowOptions): Promise<void>;
|
|
616
|
+
close(): void;
|
|
617
|
+
}
|
|
618
|
+
interface SceneShell {
|
|
619
|
+
/** Live insets (px). `bottom` = the shell bar height; read inside onResize. */
|
|
620
|
+
readonly safeArea: {
|
|
621
|
+
top: number;
|
|
622
|
+
right: number;
|
|
623
|
+
bottom: number;
|
|
624
|
+
left: number;
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
interface AutoplaySceneState {
|
|
628
|
+
running: boolean;
|
|
629
|
+
remaining: number;
|
|
630
|
+
}
|
|
631
|
+
/** Stable capabilities injected once via onCreate. */
|
|
632
|
+
interface SceneApi {
|
|
633
|
+
audio: SceneAudio;
|
|
634
|
+
overlay: SceneOverlay;
|
|
635
|
+
shell: SceneShell;
|
|
636
|
+
formatAmount(value: number): string;
|
|
637
|
+
readonly bet: number;
|
|
638
|
+
readonly mode: string;
|
|
639
|
+
readonly turbo: number;
|
|
640
|
+
}
|
|
641
|
+
/** The contract a slot scene implements. The HOST owns the play→present→ack→drain loop and the
|
|
642
|
+
* shell; the scene only renders + reacts. The core spin-lifecycle hooks are REQUIRED (implement
|
|
643
|
+
* them — empty bodies are fine where a game has nothing to do); the incidental reactions below
|
|
644
|
+
* stay optional. */
|
|
645
|
+
interface SlotSceneController<T extends SlotSpinResultBase = SlotSpinResultBase> {
|
|
646
|
+
/** Injected ONCE before the first round — capabilities, subscriptions, one-time setup. */
|
|
647
|
+
onCreate(api: SceneApi): void;
|
|
648
|
+
/** Fires once per round when the player presses spin (before the network result). */
|
|
649
|
+
onSpinStart(): void;
|
|
650
|
+
/** Render ONE segment (a spin or one free spin). Await your own pacing. */
|
|
651
|
+
onSpin(result: T, ctx: RenderContext): Promise<void>;
|
|
652
|
+
/** Fires when a bonus LEVEL begins. With nested bonuses this fires once per level (free spins,
|
|
653
|
+
* then adventure, …) — check `ctx.mode` for which. `ctx.resumed` is true when returning to a
|
|
654
|
+
* suspended parent after a nested sub-bonus finished, so a scene can restore instead of rebuild.
|
|
655
|
+
* A single-bonus round fires it exactly once (as before). */
|
|
656
|
+
onEnterMode(result: T, ctx: RenderContext): Promise<void>;
|
|
657
|
+
/** Fires when a bonus LEVEL ends — popping a nested sub-bonus back to its parent, or unwinding
|
|
658
|
+
* the last level back to BASE. `ctx.mode` is the level being left. Fires once per level. */
|
|
659
|
+
onExitMode(result: T, ctx: RenderContext): Promise<void>;
|
|
660
|
+
/** Fires once per round after the full drain (controls unlocked). */
|
|
661
|
+
onSpinEnd(result: T, ctx: RenderContext): void;
|
|
662
|
+
/** Shell events (may fire while idle) — optional. */
|
|
663
|
+
onBetChanged?(bet: number): void;
|
|
664
|
+
onTurboChanged?(level: number): void;
|
|
665
|
+
onAutoplayChanged?(state: AutoplaySceneState): void;
|
|
666
|
+
/** Double-tap skip during an active onSpin (gated by the skipGesture setting). */
|
|
667
|
+
onSkip?(): void;
|
|
668
|
+
/** Tab focus lost / regained. */
|
|
669
|
+
onPause?(): void;
|
|
670
|
+
onResume?(): void;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
interface FlowDoc {
|
|
674
|
+
version: 1;
|
|
675
|
+
id: string;
|
|
676
|
+
/** Sound cue registry: alias → definition. Steps refer to cues, never to files. */
|
|
677
|
+
cues?: Record<string, CueDef>;
|
|
678
|
+
/** Event name (host lifecycle: spinStart/reelStop/result/enterMode/…) → steps. */
|
|
679
|
+
on: Record<string, Step[]>;
|
|
680
|
+
}
|
|
681
|
+
interface CueDef {
|
|
682
|
+
/** One source alias, or several — with `rotate` the runner cycles them (anti-repetition). */
|
|
683
|
+
src: string | string[];
|
|
684
|
+
channel?: 'music' | 'sfx';
|
|
685
|
+
loop?: boolean;
|
|
686
|
+
rotate?: boolean;
|
|
687
|
+
/** ± random playback-rate jitter (0.06 = ±6%). */
|
|
688
|
+
jitter?: number;
|
|
689
|
+
volume?: number;
|
|
690
|
+
}
|
|
691
|
+
/** Core step vocabulary; plugins contribute additional `do` kinds through the registry. */
|
|
692
|
+
type Step = TweenStep | SoundStep | SetStateStep | SetVarStep | SetPropsStep | CountUpStep | WaitStep | IfStep | ParallelStep | SeqStep | ForEachStep | CodeStep | PluginStep;
|
|
693
|
+
interface TweenStep {
|
|
694
|
+
do: 'tween';
|
|
695
|
+
node: string;
|
|
696
|
+
/** Target numeric view properties (alpha, x, y, scale, rotation, …). */
|
|
697
|
+
to: Record<string, number>;
|
|
698
|
+
ms: number;
|
|
699
|
+
ease?: string;
|
|
700
|
+
}
|
|
701
|
+
interface SoundStep {
|
|
702
|
+
do: 'sound';
|
|
703
|
+
cue: string;
|
|
704
|
+
action?: 'play' | 'stop';
|
|
705
|
+
}
|
|
706
|
+
interface SetStateStep {
|
|
707
|
+
do: 'setState';
|
|
708
|
+
node: string;
|
|
709
|
+
state: string | null;
|
|
710
|
+
}
|
|
711
|
+
interface SetVarStep {
|
|
712
|
+
do: 'setVar';
|
|
713
|
+
name: string;
|
|
714
|
+
value: unknown;
|
|
715
|
+
}
|
|
716
|
+
/** Transient prop write on the live instance (presentation) — does NOT touch the doc. */
|
|
717
|
+
interface SetPropsStep {
|
|
718
|
+
do: 'setProps';
|
|
719
|
+
node: string;
|
|
720
|
+
props: Record<string, unknown>;
|
|
721
|
+
}
|
|
722
|
+
interface CountUpStep {
|
|
723
|
+
do: 'countUp';
|
|
724
|
+
node: string;
|
|
725
|
+
/** Instance prop receiving the formatted value (default 'value' — badge prefabs). */
|
|
726
|
+
prop?: string;
|
|
727
|
+
from?: number | string;
|
|
728
|
+
/** Number or a '$path' reference into the fire() ctx (e.g. '$win'). */
|
|
729
|
+
to: number | string;
|
|
730
|
+
ms?: number;
|
|
731
|
+
format?: 'int' | 'space';
|
|
732
|
+
}
|
|
733
|
+
interface WaitStep {
|
|
734
|
+
do: 'wait';
|
|
735
|
+
ms?: number;
|
|
736
|
+
until?: 'tap';
|
|
737
|
+
}
|
|
738
|
+
interface IfStep {
|
|
739
|
+
do: 'if';
|
|
740
|
+
/** Micro-expression over the fire() ctx: `win >= 100`, `mode === 'fs'`, or a bare truthy name. */
|
|
741
|
+
when: string;
|
|
742
|
+
then: Step[];
|
|
743
|
+
else?: Step[];
|
|
744
|
+
}
|
|
745
|
+
interface ParallelStep {
|
|
746
|
+
do: 'parallel';
|
|
747
|
+
/** Independent tracks, awaited together. */
|
|
748
|
+
steps: Step[][];
|
|
749
|
+
}
|
|
750
|
+
interface SeqStep {
|
|
751
|
+
do: 'seq';
|
|
752
|
+
steps: Step[];
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Iterate a ctx collection — THE dynamic fan-out every studied game needs (staggered jar
|
|
756
|
+
* flights, per-cell transmutes, cascade histories). Nested steps see the current element
|
|
757
|
+
* as `$<as>` (default `$item`) and its index as `$<as>Index`.
|
|
758
|
+
*/
|
|
759
|
+
interface ForEachStep {
|
|
760
|
+
do: 'forEach';
|
|
761
|
+
/** '$path' into the ctx (or an inline array). */
|
|
762
|
+
items: string | unknown[];
|
|
763
|
+
as?: string;
|
|
764
|
+
mode?: 'sequential' | 'parallel';
|
|
765
|
+
/** Per-item delay: parallel start stagger / sequential inter-item gap (turbo-scaled). */
|
|
766
|
+
staggerMs?: number;
|
|
767
|
+
steps: Step[];
|
|
768
|
+
}
|
|
769
|
+
/** Escape hatch: a named code choreography registered on the runner. */
|
|
770
|
+
interface CodeStep {
|
|
771
|
+
do: 'code';
|
|
772
|
+
ref: string;
|
|
773
|
+
args?: Record<string, unknown>;
|
|
774
|
+
}
|
|
775
|
+
interface PluginStep {
|
|
776
|
+
do: string;
|
|
777
|
+
[key: string]: unknown;
|
|
778
|
+
}
|
|
779
|
+
interface TraceEntry {
|
|
780
|
+
/** ms since fire() (real time, unscaled). */
|
|
781
|
+
t: number;
|
|
782
|
+
do: string;
|
|
783
|
+
node?: string;
|
|
784
|
+
info?: string;
|
|
785
|
+
}
|
|
786
|
+
interface FlowTrace {
|
|
787
|
+
event: string;
|
|
788
|
+
turbo: number;
|
|
789
|
+
skipped: boolean;
|
|
790
|
+
/** Set on scrub replays: the run executed instantly and halted after this entry index. */
|
|
791
|
+
haltedAtEntry?: number;
|
|
792
|
+
durationMs: number;
|
|
793
|
+
entries: TraceEntry[];
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/** What the audio adapter receives — cue already resolved (rotation/jitter applied). */
|
|
797
|
+
interface ResolvedCue {
|
|
798
|
+
cue: string;
|
|
799
|
+
src: string;
|
|
800
|
+
channel: 'music' | 'sfx';
|
|
801
|
+
loop: boolean;
|
|
802
|
+
rate: number;
|
|
803
|
+
volume: number;
|
|
804
|
+
}
|
|
805
|
+
interface FlowAudioAdapter {
|
|
806
|
+
play(cue: ResolvedCue): void;
|
|
807
|
+
stop(cueName: string): void;
|
|
808
|
+
}
|
|
809
|
+
interface CreateFlowRunnerOptions {
|
|
810
|
+
scene: SceneHandle;
|
|
811
|
+
audio?: FlowAudioAdapter;
|
|
812
|
+
plugins?: FlowPlugin[];
|
|
813
|
+
/** Named code choreographies for `{do:'code', ref}` escape hatches. */
|
|
814
|
+
code?: Record<string, (rt: FlowRuntime, args: Record<string, unknown>) => Promise<void> | void>;
|
|
815
|
+
/** Live trace feed (the log/timeline view); the full trace is also returned by fire(). */
|
|
816
|
+
onTrace?: (event: string, entry: TraceEntry) => void;
|
|
817
|
+
log?: (msg: string) => void;
|
|
818
|
+
}
|
|
819
|
+
interface FireOptions {
|
|
820
|
+
/** Scrub replay: run instantly (no waits, anims jump, audio muted) and halt after entry N. */
|
|
821
|
+
replay?: {
|
|
822
|
+
untilEntry?: number;
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
interface FlowRunner {
|
|
826
|
+
fire(event: string, ctx?: Record<string, unknown>, opts?: FireOptions): Promise<FlowTrace>;
|
|
827
|
+
/** Scrub: re-fire `event` with its LAST ctx, instantly, halting after trace entry N. */
|
|
828
|
+
replay(event: string, untilEntry: number): Promise<FlowTrace>;
|
|
829
|
+
/** Complete every active run in zero time (settle). */
|
|
830
|
+
skip(): void;
|
|
831
|
+
/** 1 = normal; 2 = twice as fast. Applies to waits, tweens, count-ups. */
|
|
832
|
+
setTurbo(factor: number): void;
|
|
833
|
+
/** Host reports a user tap (releases `wait until:'tap'`). */
|
|
834
|
+
tap(): void;
|
|
835
|
+
events(): string[];
|
|
836
|
+
destroy(): void;
|
|
837
|
+
}
|
|
838
|
+
/** Per-run runtime handed to step executors (core and plugin alike). */
|
|
839
|
+
interface FlowRuntime {
|
|
840
|
+
scene: SceneHandle;
|
|
841
|
+
ctx: Record<string, unknown>;
|
|
842
|
+
audio: FlowAudioAdapter;
|
|
843
|
+
/** Turbo-scaled, skip-aware sleep. */
|
|
844
|
+
wait(ms: number): Promise<void>;
|
|
845
|
+
waitTap(): Promise<void>;
|
|
846
|
+
/** Execute a nested step list (if/parallel/seq bodies). */
|
|
847
|
+
run(steps: Step[]): Promise<void>;
|
|
848
|
+
/** True once skip() hit this run — executors should jump straight to end states. */
|
|
849
|
+
readonly skipped: boolean;
|
|
850
|
+
/** True for scrub replays — like skipped (jump to end states), plus audio stays muted. */
|
|
851
|
+
readonly instant: boolean;
|
|
852
|
+
readonly turbo: number;
|
|
853
|
+
/** Resolve '$path' references against ctx; other values pass through. */
|
|
854
|
+
resolve(value: unknown): unknown;
|
|
855
|
+
/** Evaluate an if-condition against ctx. */
|
|
856
|
+
when(expr: string): boolean;
|
|
857
|
+
/** A runtime whose ctx is overlaid with extra values (forEach exposes `$item` this way). */
|
|
858
|
+
child(ctxPatch: Record<string, unknown>): FlowRuntime;
|
|
859
|
+
trace(entry: Omit<TraceEntry, 't'>): void;
|
|
860
|
+
/** The node's live view (tween targets). */
|
|
861
|
+
view(id: string): Container | undefined;
|
|
862
|
+
log(msg: string): void;
|
|
863
|
+
codeRef(ref: string): ((rt: FlowRuntime, args: Record<string, unknown>) => Promise<void> | void) | undefined;
|
|
864
|
+
playCue(name: string, action: 'play' | 'stop'): void;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
interface FlowStepContribution {
|
|
868
|
+
kind: string;
|
|
869
|
+
schema?: Record<string, unknown>;
|
|
870
|
+
/** 3–10 lines for the agent: when to use the step, patterns, anti-patterns. */
|
|
871
|
+
agentDoc?: string;
|
|
872
|
+
run(step: Step, rt: FlowRuntime): Promise<void> | void;
|
|
873
|
+
}
|
|
874
|
+
interface FlowPlugin {
|
|
875
|
+
id: string;
|
|
876
|
+
steps?: FlowStepContribution[];
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/** The multi-scene graph as data: which docs exist and how the player moves between them. */
|
|
880
|
+
interface StageDoc {
|
|
881
|
+
version: 1;
|
|
882
|
+
scenes: Array<{
|
|
883
|
+
key: string;
|
|
884
|
+
scene: SceneDoc;
|
|
885
|
+
flow?: FlowDoc;
|
|
886
|
+
/** Host skips this scene on replay launches (intro idiom). */
|
|
887
|
+
skipOnReplay?: boolean;
|
|
888
|
+
/** A tap anywhere advances to this scene key (intro → game). */
|
|
889
|
+
advanceOnTap?: string;
|
|
890
|
+
}>;
|
|
891
|
+
}
|
|
892
|
+
/** Build the createSlotGame `scenes` list from a StageDoc — every entry a DocScene. */
|
|
893
|
+
declare function buildDocScenes<T extends SlotSpinResultBase = SlotSpinResultBase>(stage: StageDoc, shared: Omit<DocSceneOptions<T>, 'scene' | 'flow' | 'advanceOnTap'>): Array<{
|
|
894
|
+
key: string;
|
|
895
|
+
scene: DocScene<T>;
|
|
896
|
+
skipOnReplay?: boolean;
|
|
897
|
+
}>;
|
|
898
|
+
interface DocSceneOptions<T extends SlotSpinResultBase = SlotSpinResultBase> {
|
|
899
|
+
scene: SceneDoc;
|
|
900
|
+
flow?: FlowDoc;
|
|
901
|
+
/** A tap anywhere advances to this scene key (uses the host-injected goto). */
|
|
902
|
+
advanceOnTap?: string;
|
|
903
|
+
plugins?: ScenePlugin[];
|
|
904
|
+
flowPlugins?: FlowPlugin[];
|
|
905
|
+
code?: CreateFlowRunnerOptions['code'];
|
|
906
|
+
resolveSymbol?: SymbolResolver;
|
|
907
|
+
texture?: (alias: string) => Texture;
|
|
908
|
+
vars?: Record<string, unknown>;
|
|
909
|
+
/**
|
|
910
|
+
* Build the flow ctx for result-bearing events. Default: the result object spread +
|
|
911
|
+
* `{ win: totalWin, mode, action, bet }`. Games with richer normalized data override.
|
|
912
|
+
*/
|
|
913
|
+
resultCtx?: (result: T, ctx: RenderContext) => Record<string, unknown>;
|
|
914
|
+
/** Map the shell turbo level (0..3) to a flow time factor. Default 1 + level. */
|
|
915
|
+
turboFactor?: (level: number) => number;
|
|
916
|
+
onTrace?: CreateFlowRunnerOptions['onTrace'];
|
|
917
|
+
log?: (msg: string) => void;
|
|
918
|
+
}
|
|
919
|
+
declare class DocScene<T extends SlotSpinResultBase = SlotSpinResultBase> extends Scene implements SlotSceneController<T> {
|
|
920
|
+
private opts;
|
|
921
|
+
private api;
|
|
922
|
+
private handle_;
|
|
923
|
+
private runner_;
|
|
924
|
+
private chain;
|
|
925
|
+
private lastSize;
|
|
926
|
+
private log;
|
|
927
|
+
constructor(opts: DocSceneOptions<T>);
|
|
928
|
+
/** The live scene handle (inspector/agent attachment). Null before onEnter. */
|
|
929
|
+
get scene(): SceneHandle | null;
|
|
930
|
+
get flow(): FlowRunner | null;
|
|
931
|
+
onEnter(data?: unknown): void;
|
|
932
|
+
onResize(width: number, height: number): void;
|
|
933
|
+
onDestroy(): void;
|
|
934
|
+
private fire;
|
|
935
|
+
private ctxOf;
|
|
936
|
+
onCreate(api: SceneApi): void;
|
|
937
|
+
onSpinStart(): void;
|
|
938
|
+
onSpin(result: T, ctx: RenderContext): Promise<void>;
|
|
939
|
+
onEnterMode(result: T, ctx: RenderContext): Promise<void>;
|
|
940
|
+
onExitMode(result: T, ctx: RenderContext): Promise<void>;
|
|
941
|
+
onSpinEnd(result: T, ctx: RenderContext): void;
|
|
942
|
+
onSkip(): void;
|
|
943
|
+
onTurboChanged(level: number): void;
|
|
944
|
+
}
|
|
945
|
+
declare function createDocScene<T extends SlotSpinResultBase = SlotSpinResultBase>(opts: DocSceneOptions<T>): DocScene<T>;
|
|
946
|
+
|
|
947
|
+
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 };
|
|
948
|
+
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 };
|