@yagejs/core 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -47,152 +47,6 @@ declare class AssetManager {
47
47
  private key;
48
48
  }
49
49
 
50
- /** Options for creating a Process. */
51
- interface ProcessOptions {
52
- /** Called each frame with dt (ms) and elapsed (ms). Return true to complete early. */
53
- update?: (dt: number, elapsed: number) => boolean | void;
54
- /** Called when the process completes. */
55
- onComplete?: () => void;
56
- /** Auto-complete after this duration in ms. */
57
- duration?: number;
58
- /** Loop the process. */
59
- loop?: boolean;
60
- /** Tags for process filtering. */
61
- tags?: string[];
62
- }
63
- /**
64
- * A Process represents an ongoing action updated each frame.
65
- * Used internally by Tween and Sequence, and directly for custom coroutines.
66
- */
67
- declare class Process {
68
- private readonly updateFn;
69
- private readonly onCompleteFn;
70
- private readonly duration;
71
- private readonly loop;
72
- /** Tags for filtering/grouping. */
73
- readonly tags: readonly string[];
74
- private elapsed;
75
- private _completed;
76
- private _paused;
77
- private _cancelled;
78
- private resolvePromise?;
79
- /** Create a timer that fires `onComplete` after `duration` ms. */
80
- static delay(duration: number, onComplete?: () => void, tags?: string[]): Process;
81
- constructor(options: ProcessOptions);
82
- /** Whether the process has completed. */
83
- get completed(): boolean;
84
- /** Whether the process is paused. */
85
- get paused(): boolean;
86
- /** Pause the process. */
87
- pause(): void;
88
- /** Resume the process. */
89
- resume(): void;
90
- /** Cancel the process. */
91
- cancel(): void;
92
- /** Returns a promise that resolves when the process completes or is cancelled. */
93
- toPromise(): Promise<void>;
94
- /**
95
- * Advance the process by dt milliseconds.
96
- * @internal
97
- */
98
- _update(dt: number): void;
99
- /**
100
- * Reset the process to its initial state so it can be re-run.
101
- * @internal Used by Sequence for loop/repeat with direct instances.
102
- */
103
- _reset(): void;
104
- private complete;
105
- }
106
- /** Linear easing (no easing). */
107
- declare const easeLinear: EasingFunction;
108
- /** Ease in quadratic. */
109
- declare const easeInQuad: EasingFunction;
110
- /** Ease out quadratic. */
111
- declare const easeOutQuad: EasingFunction;
112
- /** Ease in-out quadratic. */
113
- declare const easeInOutQuad: EasingFunction;
114
- /** Ease out bounce. */
115
- declare const easeOutBounce: EasingFunction;
116
-
117
- /**
118
- * Built-in system that ticks all ProcessComponents on entities in non-paused
119
- * scenes, plus a scene-level set of global processes.
120
- *
121
- * Runs at Phase.Update with priority 500, ensuring tweened values are fresh
122
- * before ComponentUpdateSystem (priority 1000) reads them.
123
- */
124
- declare class ProcessSystem extends System {
125
- readonly phase = Phase.Update;
126
- readonly priority = 500;
127
- /** Global time scale multiplier. Stacks multiplicatively with per-scene timeScale. */
128
- timeScale: number;
129
- private sceneManager;
130
- private sceneProcesses;
131
- onRegister(context: EngineContext): void;
132
- /** Add a scene-level process (not tied to any entity). */
133
- add(process: Process): Process;
134
- /** Cancel scene-level processes, optionally by tag. */
135
- cancel(tag?: string): void;
136
- update(dt: number): void;
137
- }
138
-
139
- /** Callbacks invoked by the game loop each frame. */
140
- interface GameLoopCallbacks {
141
- earlyUpdate(dt: number): void;
142
- fixedUpdate(fixedDt: number): void;
143
- update(dt: number): void;
144
- lateUpdate(dt: number): void;
145
- render(dt: number): void;
146
- endOfFrame(dt: number): void;
147
- }
148
- /** Configuration for the game loop. */
149
- interface GameLoopConfig {
150
- /** Fixed timestep in ms. Default: 1000/60. */
151
- fixedTimestep?: number;
152
- /** Max fixed steps per frame to prevent spiral of death. Default: 5. */
153
- maxFixedStepsPerFrame?: number;
154
- }
155
- /**
156
- * Game loop with fixed timestep accumulator.
157
- *
158
- * Driven by an external ticker (e.g., PixiJS Ticker) or manual `tick()` calls
159
- * for testing. Implements deterministic fixed updates with variable rendering.
160
- */
161
- declare class GameLoop {
162
- /** Fixed timestep in ms. */
163
- readonly fixedTimestep: number;
164
- /** Max fixed steps per frame. */
165
- readonly maxFixedStepsPerFrame: number;
166
- private accumulator;
167
- private running;
168
- private callbacks;
169
- private tickerUnsubscribe;
170
- private rafId;
171
- private lastTime;
172
- private _frameCount;
173
- constructor(config?: GameLoopConfig);
174
- /** Current frame count. */
175
- get frameCount(): number;
176
- /** Whether the loop is running. */
177
- get isRunning(): boolean;
178
- /** Ratio of accumulated time to fixed timestep, for physics interpolation. */
179
- get interpolationAlpha(): number;
180
- /** Provide the callbacks that the loop invokes each frame. */
181
- setCallbacks(callbacks: GameLoopCallbacks): void;
182
- /**
183
- * Attach an external ticker (e.g., PixiJS Ticker).
184
- * The ticker calls `tick(dt)` every frame.
185
- * If no ticker is attached, the loop uses requestAnimationFrame.
186
- */
187
- attachTicker(subscribe: (callback: (dt: number) => void) => () => void): void;
188
- /** Start the loop. */
189
- start(): void;
190
- /** Stop the loop. */
191
- stop(): void;
192
- /** Process one frame with the given dt in milliseconds. */
193
- tick(dtMs: number): void;
194
- }
195
-
196
50
  /**
197
51
  * A phantom-typed token for entity events.
198
52
  * Similar to ServiceKey, but used for entity-level event pub/sub.
@@ -212,12 +66,20 @@ declare function defineEvent<T = void>(name: string): EventToken<T>;
212
66
  /**
213
67
  * A reusable entity template. Blueprints define how to assemble
214
68
  * an entity from components, given optional parameters.
69
+ *
70
+ * @deprecated Prefer Entity subclasses with `setup()` for entity types.
71
+ * Blueprints still work for parametric factories but are no longer the
72
+ * recommended pattern for new code.
215
73
  */
216
74
  interface Blueprint<P = void> {
217
75
  readonly name: string;
218
76
  build(entity: Entity, params: P): void;
219
77
  }
220
- /** Create a blueprint from a name and a build function. */
78
+ /**
79
+ * Create a blueprint from a name and a build function.
80
+ *
81
+ * @deprecated Prefer Entity subclasses with `setup()` for entity types.
82
+ */
221
83
  declare function defineBlueprint<P = void>(name: string, build: (entity: Entity, params: P) => void): Blueprint<P>;
222
84
 
223
85
  /**
@@ -435,46 +297,6 @@ declare class Entity {
435
297
  _setScene(scene: Scene | null, callbacks: EntityCallbacks | null): void;
436
298
  }
437
299
 
438
- /** Which scene op triggered this transition. */
439
- type SceneTransitionKind = "push" | "pop" | "replace";
440
- /** Context passed to a transition each frame. */
441
- interface SceneTransitionContext {
442
- /** Wall-clock ms elapsed since begin(). */
443
- readonly elapsed: number;
444
- readonly kind: SceneTransitionKind;
445
- readonly engineContext: EngineContext;
446
- /** The scene being left or removed (undefined on first push). */
447
- readonly fromScene: Scene | undefined;
448
- /** The scene being entered or revealed (undefined on last pop). */
449
- readonly toScene: Scene | undefined;
450
- }
451
- /**
452
- * A scene transition animates the handoff between scene stack states.
453
- *
454
- * `SceneManager` keeps both the outgoing and incoming scenes on the stack
455
- * for the transition's duration, then removes the outgoing scene afterward.
456
- * Transitions use raw wall-clock dt and ignore engine + scene `timeScale`.
457
- */
458
- interface SceneTransition {
459
- /** Total duration in wall-clock ms. */
460
- readonly duration: number;
461
- /** Called once when the transition starts. Set up resources here. */
462
- begin?(ctx: SceneTransitionContext): void;
463
- /** Called each frame with frame dt in ms. `ctx.elapsed` is clamped to `duration`. */
464
- tick(dt: number, ctx: SceneTransitionContext): void;
465
- /** Called when the transition ends. Tear down resources here. */
466
- end?(ctx: SceneTransitionContext): void;
467
- }
468
- /** Options accepted by `SceneManager.push/pop/replace`. */
469
- interface SceneTransitionOptions {
470
- transition?: SceneTransition;
471
- }
472
- /**
473
- * Resolve the effective transition for a scene op.
474
- * Precedence: call-site option → destination's `defaultTransition` → undefined.
475
- */
476
- declare function resolveTransition(callSite: SceneTransition | undefined, destination: Scene | undefined): SceneTransition | undefined;
477
-
478
300
  /** Filter criteria for entity queries. All fields are AND'd together. */
479
301
  interface EntityFilter {
480
302
  /** Match entities whose class implements this trait. */
@@ -515,6 +337,7 @@ declare abstract class Scene {
515
337
  private queryCache;
516
338
  private bus;
517
339
  private _entityEventHandlers?;
340
+ private _entityEventObserver?;
518
341
  private _scopedServices?;
519
342
  /** Access the EngineContext. */
520
343
  get context(): EngineContext;
@@ -572,6 +395,12 @@ declare abstract class Scene {
572
395
  * @internal
573
396
  */
574
397
  _onEntityEvent(eventName: string, data: unknown, entity: Entity): void;
398
+ /**
399
+ * Observe entity-scoped event emissions after they dispatch locally and
400
+ * bubble to the scene. Tooling only; game code should keep using `on()`.
401
+ * @internal
402
+ */
403
+ _observeEntityEvent(eventName: string, data: unknown, entity: Entity): void;
575
404
  /** Called during asset preloading with progress ratio (0→1). */
576
405
  onProgress?(ratio: number): void;
577
406
  /** Called when the scene is entered (after preload completes). */
@@ -593,6 +422,11 @@ declare abstract class Scene {
593
422
  * @internal
594
423
  */
595
424
  _registerScoped<T>(key: ServiceKey<T>, value: T): void;
425
+ /**
426
+ * Install or clear a tooling-only observer for bubbled entity events.
427
+ * @internal
428
+ */
429
+ _setEntityEventObserver(observer?: (eventName: string, data: unknown, entity: Entity) => void): void;
596
430
  /**
597
431
  * Resolve a scene-scoped service, or `undefined` if none was registered.
598
432
  * @internal
@@ -622,85 +456,118 @@ declare abstract class Scene {
622
456
  _destroyAllEntities(): void;
623
457
  }
624
458
 
459
+ /** Which scene op triggered this transition. */
460
+ type SceneTransitionKind = "push" | "pop" | "replace";
461
+ /** Context passed to a transition each frame. */
462
+ interface SceneTransitionContext {
463
+ /** Wall-clock ms elapsed since begin(). */
464
+ readonly elapsed: number;
465
+ readonly kind: SceneTransitionKind;
466
+ readonly engineContext: EngineContext;
467
+ /** The scene being left or removed (undefined on first push). */
468
+ readonly fromScene: Scene | undefined;
469
+ /** The scene being entered or revealed (undefined on last pop). */
470
+ readonly toScene: Scene | undefined;
471
+ }
625
472
  /**
626
- * Base class for all components.
473
+ * A scene transition animates the handoff between scene stack states.
627
474
  *
628
- * Components are the primary authoring model. Game developers write behavior
629
- * in components using optional `update(dt)` and `fixedUpdate(dt)` methods.
630
- * The built-in ComponentUpdateSystem calls these methods automatically.
475
+ * `SceneManager` keeps both the outgoing and incoming scenes on the stack
476
+ * for the transition's duration, then removes the outgoing scene afterward.
477
+ * Transitions use raw wall-clock dt and ignore engine + scene `timeScale`.
631
478
  */
632
- declare abstract class Component {
633
- /**
634
- * Back-reference to the owning entity. Set by the engine when the component
635
- * is added to an entity. Do not set manually.
636
- */
637
- entity: Entity;
638
- /** Whether this component is active. Disabled components are skipped by ComponentUpdateSystem. */
639
- enabled: boolean;
640
- private _serviceCache;
641
- private _cleanups?;
642
- /**
643
- * Access the entity's scene. Throws if the entity is not in a scene.
644
- * Prefer this over threading through `this.entity.scene` in component
645
- * code.
646
- */
647
- get scene(): Scene;
648
- /**
649
- * Access the EngineContext from the entity's scene.
650
- * Throws if the entity is not in a scene.
651
- */
652
- get context(): EngineContext;
653
- /**
654
- * Resolve a service by key, cached after first lookup. Scene-scoped values
655
- * (registered via `scene._registerScoped`) take precedence over engine
656
- * scope. A key declared with `scope: "scene"` that falls back to engine
657
- * scope emits a one-shot dev warning — almost always signals a missed
658
- * `beforeEnter` hook.
659
- */
660
- protected use<T>(key: ServiceKey<T>): T;
661
- private _warnScopedFallback;
662
- /**
663
- * Lazy proxy-based service resolution. Can be used at field-declaration time:
664
- * ```ts
665
- * readonly input = this.service(InputManagerKey);
666
- * ```
667
- * The actual resolution is deferred until first property access.
668
- */
669
- protected service<T extends object>(key: ServiceKey<T>): T;
670
- /**
671
- * Lazy proxy-based sibling component resolution. Can be used at field-declaration time:
672
- * ```ts
673
- * readonly anim = this.sibling(AnimatedSpriteComponent);
674
- * ```
675
- * The actual resolution is deferred until first property access.
676
- */
677
- protected sibling<C extends Component>(cls: ComponentClass<C>): C;
678
- /** Subscribe to events on any entity, auto-unsubscribe on removal. */
679
- protected listen<T>(entity: Entity, token: EventToken<T>, handler: (data: T) => void): void;
680
- /** Subscribe to scene-level bubbled events, auto-unsubscribe on removal. */
681
- protected listenScene<T>(token: EventToken<T>, handler: (data: T, entity: Entity) => void): void;
682
- /** Register a cleanup function to run when this component is removed or destroyed. */
683
- protected addCleanup(fn: () => void): void;
479
+ interface SceneTransition {
480
+ /** Total duration in wall-clock ms. */
481
+ readonly duration: number;
482
+ /** Called once when the transition starts. Set up resources here. */
483
+ begin?(ctx: SceneTransitionContext): void;
484
+ /** Called each frame with frame dt in ms. `ctx.elapsed` is clamped to `duration`. */
485
+ tick(dt: number, ctx: SceneTransitionContext): void;
486
+ /** Called when the transition ends. Tear down resources here. */
487
+ end?(ctx: SceneTransitionContext): void;
488
+ }
489
+ /** Options accepted by `SceneManager.push/pop/replace`. */
490
+ interface SceneTransitionOptions {
491
+ transition?: SceneTransition;
492
+ }
493
+ /**
494
+ * Resolve the effective transition for a scene op.
495
+ * Precedence: call-site option → destination's `defaultTransition` → undefined.
496
+ */
497
+ declare function resolveTransition(callSite: SceneTransition | undefined, destination: Scene | undefined): SceneTransition | undefined;
498
+
499
+ type EntityRef = {
500
+ readonly id: number;
501
+ readonly name: string;
502
+ };
503
+ type SceneRef = {
504
+ readonly name: string;
505
+ };
506
+ /** Base type for event map definitions. */
507
+ type EventMap = Record<string, unknown>;
508
+ /** Well-known engine events. */
509
+ interface EngineEvents {
510
+ "entity:created": {
511
+ entity: EntityRef;
512
+ };
513
+ "entity:destroyed": {
514
+ entity: EntityRef;
515
+ };
516
+ "component:added": {
517
+ entity: EntityRef;
518
+ component: Component;
519
+ };
520
+ "component:removed": {
521
+ entity: EntityRef;
522
+ componentClass: ComponentClass;
523
+ };
524
+ "scene:pushed": {
525
+ scene: SceneRef;
526
+ };
527
+ "scene:popped": {
528
+ scene: SceneRef;
529
+ };
530
+ "scene:replaced": {
531
+ oldScene: SceneRef;
532
+ newScene: SceneRef;
533
+ };
534
+ "scene:transition:started": {
535
+ kind: SceneTransitionKind;
536
+ fromScene: SceneRef | undefined;
537
+ toScene: SceneRef | undefined;
538
+ };
539
+ "scene:transition:ended": {
540
+ kind: SceneTransitionKind;
541
+ fromScene: SceneRef | undefined;
542
+ toScene: SceneRef | undefined;
543
+ };
544
+ "scene:loading:progress": {
545
+ scene: Scene;
546
+ ratio: number;
547
+ };
548
+ "scene:loading:done": {
549
+ scene: Scene;
550
+ };
551
+ "engine:started": undefined;
552
+ "engine:stopped": undefined;
553
+ }
554
+ /** Typed publish/subscribe event bus. */
555
+ declare class EventBus<E = EventMap> {
556
+ private handlers;
557
+ private observers;
558
+ /** Subscribe to an event. Returns an unsubscribe function. */
559
+ on<K extends keyof E>(event: K, handler: (data: E[K]) => void): () => void;
560
+ /** Subscribe to an event, auto-unsubscribe after first emission. */
561
+ once<K extends keyof E>(event: K, handler: (data: E[K]) => void): () => void;
562
+ /** Emit an event. Handlers are called synchronously in registration order. */
563
+ emit<K extends keyof E>(event: K, data: E[K]): void;
684
564
  /**
685
- * Run and clear all registered cleanups.
686
- * Called by Entity.remove() and Entity._performDestroy() before onRemove/onDestroy.
687
- * @internal
565
+ * Observe every emitted event without affecting handler order or control
566
+ * flow. Used by tooling such as the Inspector event log.
688
567
  */
689
- _runCleanups(): void;
690
- /** Called when the component is added to an entity. */
691
- onAdd?(): void;
692
- /** Called when the component is removed from an entity. */
693
- onRemove?(): void;
694
- /** Called when the component is destroyed (entity destroyed or component removed). */
695
- onDestroy?(): void;
696
- /** Called every frame by the built-in ComponentUpdateSystem. */
697
- update?(dt: number): void;
698
- /** Called every fixed timestep by the built-in ComponentUpdateSystem. */
699
- fixedUpdate?(dt: number): void;
700
- /** Return a JSON-serializable snapshot of this component's state. Used by the save system. */
701
- serialize?(): unknown;
702
- /** Called after onAdd() during save/load restoration. Apply state that depends on onAdd() having run. */
703
- afterRestore?(data: unknown, resolve: SnapshotResolver): void;
568
+ tap(observer: (event: keyof E, data: E[keyof E]) => void): () => void;
569
+ /** Remove all handlers for an event, or all handlers if no event specified. */
570
+ clear(event?: keyof E): void;
704
571
  }
705
572
 
706
573
  /** Log severity levels. */
@@ -767,63 +634,61 @@ declare class Logger {
767
634
  private log;
768
635
  }
769
636
 
770
- /**
771
- * Wraps system and component execution. On error, disables the offending
772
- * system/component and logs the error. The game loop never crashes.
773
- */
774
- declare class ErrorBoundary {
775
- private logger;
776
- private disabledSystems;
777
- private disabledComponents;
778
- constructor(logger: Logger);
779
- /** Wrap a system update call. On throw, disables the system. */
780
- wrapSystem(system: System, fn: () => void): void;
781
- /** Wrap a component lifecycle or update call. On throw, disables the component. */
782
- wrapComponent(component: Component, fn: () => void): void;
783
- /** Get all disabled systems and components for inspection. */
784
- getDisabled(): {
785
- systems: ReadonlyArray<{
786
- system: System;
787
- error: string;
788
- }>;
789
- components: ReadonlyArray<{
790
- component: Component;
791
- error: string;
792
- }>;
793
- };
637
+ /** Callbacks invoked by the game loop each frame. */
638
+ interface GameLoopCallbacks {
639
+ earlyUpdate(dt: number): void;
640
+ fixedUpdate(fixedDt: number): void;
641
+ update(dt: number): void;
642
+ lateUpdate(dt: number): void;
643
+ render(dt: number): void;
644
+ endOfFrame(dt: number): void;
794
645
  }
795
-
796
- /** A filter used to register a query — an array of required component classes. */
797
- type QueryFilter = readonly ComponentClass[];
798
- /** A live, iterable set of entities matching a query filter. */
799
- declare class QueryResult {
800
- /** @internal */
801
- readonly _entities: Set<Entity>;
802
- /** @internal */
803
- readonly _filter: QueryFilter;
804
- /** @internal */
805
- constructor(filter: QueryFilter);
806
- /** Iterate matching entities. */
807
- [Symbol.iterator](): Iterator<Entity>;
808
- /** Number of matching entities. */
809
- get size(): number;
810
- /** Get the first match (useful for singleton queries). */
811
- get first(): Entity | undefined;
812
- /** Convert to array (allocates). */
813
- toArray(): Entity[];
646
+ /** Configuration for the game loop. */
647
+ interface GameLoopConfig {
648
+ /** Fixed timestep in ms. Default: 1000/60. */
649
+ fixedTimestep?: number;
650
+ /** Max fixed steps per frame to prevent spiral of death. Default: 5. */
651
+ maxFixedStepsPerFrame?: number;
814
652
  }
815
- /** Incrementally maintained entity sets based on component signatures. */
816
- declare class QueryCache {
817
- private queries;
818
- /** Register a query. Returns a stable reference to a live result set. */
819
- register(filter: QueryFilter): QueryResult;
820
- /** Called by Entity when a component is added. */
821
- onComponentAdded(entity: Entity): void;
822
- /** Called by Entity when a component is removed. */
823
- onComponentRemoved(entity: Entity): void;
824
- /** Called when an entity is destroyed. */
825
- onEntityDestroyed(entity: Entity): void;
826
- private matches;
653
+ /**
654
+ * Game loop with fixed timestep accumulator.
655
+ *
656
+ * Driven by an external ticker (e.g., PixiJS Ticker) or manual `tick()` calls
657
+ * for testing. Implements deterministic fixed updates with variable rendering.
658
+ */
659
+ declare class GameLoop {
660
+ /** Fixed timestep in ms. */
661
+ readonly fixedTimestep: number;
662
+ /** Max fixed steps per frame. */
663
+ readonly maxFixedStepsPerFrame: number;
664
+ private accumulator;
665
+ private running;
666
+ private callbacks;
667
+ private tickerUnsubscribe;
668
+ private rafId;
669
+ private lastTime;
670
+ private _frameCount;
671
+ constructor(config?: GameLoopConfig);
672
+ /** Current frame count. */
673
+ get frameCount(): number;
674
+ /** Whether the loop is running. */
675
+ get isRunning(): boolean;
676
+ /** Ratio of accumulated time to fixed timestep, for physics interpolation. */
677
+ get interpolationAlpha(): number;
678
+ /** Provide the callbacks that the loop invokes each frame. */
679
+ setCallbacks(callbacks: GameLoopCallbacks): void;
680
+ /**
681
+ * Attach an external ticker (e.g., PixiJS Ticker).
682
+ * The ticker calls `tick(dt)` every frame.
683
+ * If no ticker is attached, the loop uses requestAnimationFrame.
684
+ */
685
+ attachTicker(subscribe: (callback: (dt: number) => void) => () => void): void;
686
+ /** Start the loop. */
687
+ start(): void;
688
+ /** Stop the loop. */
689
+ stop(): void;
690
+ /** Process one frame with the given dt in milliseconds. */
691
+ tick(dtMs: number): void;
827
692
  }
828
693
 
829
694
  /** Stack-based scene manager with push/pop/replace semantics. */
@@ -838,11 +703,31 @@ declare class SceneManager {
838
703
  private _pendingChain;
839
704
  private _mutationDepth;
840
705
  private _destroyed;
706
+ private _autoPauseOnBlur;
707
+ private _isBlurred;
708
+ private readonly _visibilityPausedScenes;
709
+ private _visibilityListenerCleanup;
710
+ /**
711
+ * Pause all non-paused scenes when `document.hidden` becomes `true`; restore
712
+ * them on focus. Default: `false`. Only scenes paused by this mechanism are
713
+ * restored — user-paused scenes (manual `scene.paused = true` or `pauseBelow`
714
+ * cascade) are never touched.
715
+ */
716
+ get autoPauseOnBlur(): boolean;
717
+ set autoPauseOnBlur(value: boolean);
841
718
  /**
842
719
  * Set the engine context.
843
720
  * @internal
844
721
  */
845
722
  _setContext(context: EngineContext): void;
723
+ /**
724
+ * React to a visibility change. Parameterised on `hidden` so unit tests can
725
+ * drive it without a real `document`.
726
+ * @internal
727
+ */
728
+ _handleVisibilityChange(hidden: boolean): void;
729
+ private _applyBlurPause;
730
+ private _restoreBlurPause;
846
731
  /** The topmost (active) scene. */
847
732
  get active(): Scene | undefined;
848
733
  /** All scenes in the stack, bottom to top. */
@@ -926,15 +811,36 @@ declare class SceneManager {
926
811
  private _fireResumeTransitions;
927
812
  }
928
813
 
929
- /** Full engine state snapshot. */
930
- interface EngineSnapshot {
931
- frameCount: number;
932
- sceneStack: SceneSnapshot[];
933
- entityCount: number;
934
- systemCount: number;
935
- errors: ErrorSnapshot;
936
- }
937
- /** Snapshot of a single entity. */
814
+ /** Seeded random service used by runtime systems that must be deterministic. */
815
+ interface RandomService {
816
+ /** Random float in the range [0, 1). */
817
+ float(): number;
818
+ /** Random float in the range [min, max). */
819
+ range(min: number, max: number): number;
820
+ /** Random integer in the range [min, max] (inclusive). */
821
+ int(min: number, max: number): number;
822
+ /** Pick a random element from a non-empty array. */
823
+ pick<T>(arr: readonly T[]): T;
824
+ /** Shuffle an array in place and return the same array. */
825
+ shuffle<T>(arr: T[]): T[];
826
+ /** Return the seed this generator was constructed (or last reseeded) with. */
827
+ getSeed(): number;
828
+ }
829
+ /** Scene-scoped key for the active scene's deterministic RNG. */
830
+ declare const RandomKey: ServiceKey<RandomService>;
831
+ /** Normalize arbitrary numbers into the uint32 seed space. */
832
+ declare function normalizeSeed(seed: number): number;
833
+ /** Default seed for explicitly non-deterministic paths. */
834
+ declare function createDefaultRandomSeed(): number;
835
+ /** Create a deterministic random service. */
836
+ declare function createRandomService(seed?: number): RandomService;
837
+ /**
838
+ * Explicitly non-deterministic global RNG for boot-time or cross-scene code.
839
+ * Inspector seed control never touches this instance.
840
+ */
841
+ declare const globalRandom: RandomService;
842
+
843
+ /** Backward-compatible summary snapshot returned by query helpers. */
938
844
  interface EntitySnapshot {
939
845
  id: number;
940
846
  name: string;
@@ -945,7 +851,7 @@ interface EntitySnapshot {
945
851
  y: number;
946
852
  };
947
853
  }
948
- /** Snapshot of a scene in the stack. */
854
+ /** Backward-compatible scene stack summary. */
949
855
  interface SceneSnapshot {
950
856
  name: string;
951
857
  entityCount: number;
@@ -967,21 +873,206 @@ interface ErrorSnapshot {
967
873
  error: string;
968
874
  }>;
969
875
  }
876
+ interface ComponentStateSnapshot {
877
+ type: string;
878
+ state: unknown | null;
879
+ }
880
+ interface WorldEntitySnapshot {
881
+ id: string;
882
+ type: string;
883
+ parent: string | null;
884
+ transform: {
885
+ x: number;
886
+ y: number;
887
+ rotation: number;
888
+ scaleX: number;
889
+ scaleY: number;
890
+ };
891
+ components: ComponentStateSnapshot[];
892
+ }
893
+ interface UINodeSnapshot {
894
+ id: string;
895
+ type: string;
896
+ layout: {
897
+ x: number;
898
+ y: number;
899
+ width: number;
900
+ height: number;
901
+ };
902
+ children: UINodeSnapshot[];
903
+ state: unknown | null;
904
+ }
905
+ interface UITreeSnapshot {
906
+ root: UINodeSnapshot;
907
+ }
908
+ interface PhysicsSnapshot {
909
+ bodies: Array<{
910
+ entityId: string;
911
+ type: "dynamic" | "kinematic" | "static";
912
+ position: {
913
+ x: number;
914
+ y: number;
915
+ };
916
+ rotation: number;
917
+ linvel: {
918
+ x: number;
919
+ y: number;
920
+ };
921
+ angvel: number;
922
+ }>;
923
+ contacts: Array<{
924
+ a: string;
925
+ b: string;
926
+ }>;
927
+ }
928
+ interface EventLogEntry {
929
+ frame: number;
930
+ source: "bus" | "entity";
931
+ type: string;
932
+ targetId?: string;
933
+ payload: unknown | null;
934
+ }
935
+ interface WorldSceneSnapshot {
936
+ id: string;
937
+ name: string;
938
+ paused: boolean;
939
+ timeScale: number;
940
+ seed: number;
941
+ entities: WorldEntitySnapshot[];
942
+ ui: UITreeSnapshot | null;
943
+ physics: PhysicsSnapshot;
944
+ events: EventLogEntry[];
945
+ }
946
+ interface CameraSnapshot {
947
+ sceneId: string;
948
+ sceneName: string;
949
+ name: string | null;
950
+ priority: number;
951
+ position: {
952
+ x: number;
953
+ y: number;
954
+ };
955
+ zoom: number;
956
+ rotation: number;
957
+ }
958
+ interface InputStateSnapshot {
959
+ keys: string[];
960
+ actions: string[];
961
+ mouse: {
962
+ x: number;
963
+ y: number;
964
+ buttons: number[];
965
+ down: boolean;
966
+ };
967
+ gamepad: {
968
+ buttons: number[];
969
+ axes: Array<{
970
+ index: number;
971
+ value: number;
972
+ }>;
973
+ };
974
+ }
975
+ /** Full deterministic inspector snapshot. */
976
+ interface EngineSnapshot {
977
+ version: 1;
978
+ frame: number;
979
+ sceneStack: SceneSnapshot[];
980
+ entityCount: number;
981
+ systemCount: number;
982
+ errors: ErrorSnapshot;
983
+ scenes: WorldSceneSnapshot[];
984
+ camera: CameraSnapshot | null;
985
+ input: InputStateSnapshot;
986
+ }
987
+ interface InspectorTimeController {
988
+ readonly isFrozen: boolean;
989
+ freeze(): void;
990
+ thaw(): void;
991
+ stepFrames(count: number): void;
992
+ setDelta(ms: number): void;
993
+ getFrame(): number;
994
+ }
970
995
  /** Internal engine reference to avoid circular dependency with Engine class. */
971
996
  interface EngineRef {
972
997
  readonly context: EngineContext;
973
998
  readonly scenes: SceneManager;
974
999
  readonly loop: GameLoop;
1000
+ readonly events?: EventBus<EngineEvents>;
975
1001
  }
976
1002
  /**
977
- * Programmatic state queries for testing and debugging.
1003
+ * Programmatic runtime control and state queries for testing and debugging.
978
1004
  * Exposed on `window.__yage__` in debug mode.
979
1005
  */
980
1006
  declare class Inspector {
981
- private engine;
1007
+ private readonly engine;
1008
+ private readonly extensions;
1009
+ private readonly sceneIds;
1010
+ private nextSceneId;
1011
+ private defaultSceneSeed;
1012
+ private sceneSeedOverride;
1013
+ private timeController;
1014
+ private eventLogEnabled;
1015
+ private eventCapacity;
1016
+ /**
1017
+ * Ring buffer of recent events. `eventLogHead` points at the oldest slot;
1018
+ * a full ring contains exactly `eventCapacity` entries. We avoid `splice` to
1019
+ * keep `appendEvent` O(1) — the previous shift-on-overflow approach was
1020
+ * O(n) per event once the buffer was full.
1021
+ */
1022
+ private eventLog;
1023
+ private eventLogHead;
1024
+ private eventWaiters;
1025
+ private detachBusTap;
1026
+ private readonly busEventObserver;
1027
+ private readonly sceneEventObserver;
1028
+ readonly time: {
1029
+ freeze: () => void;
1030
+ thaw: () => void;
1031
+ step: (frames?: number) => void;
1032
+ setDelta: (ms: number) => void;
1033
+ isFrozen: () => boolean;
1034
+ getFrame: () => number;
1035
+ };
1036
+ readonly input: {
1037
+ keyDown: (code: string) => void;
1038
+ keyUp: (code: string) => void;
1039
+ mouseMove: (x: number, y: number) => void;
1040
+ mouseDown: (button?: 0 | 1 | 2) => void;
1041
+ mouseUp: (button?: 0 | 1 | 2) => void;
1042
+ gamepadButton: (idx: number, pressed: boolean) => void;
1043
+ gamepadAxis: (idx: number, value: number) => void;
1044
+ tap: (code: string, frames?: number) => void;
1045
+ hold: (code: string, frames: number) => void;
1046
+ fireAction: (name: string, frames?: number) => void;
1047
+ clearAll: () => void;
1048
+ };
1049
+ readonly events: {
1050
+ getLog: () => EventLogEntry[];
1051
+ clearLog: () => void;
1052
+ setCapacity: (n: number) => void;
1053
+ waitFor: (pattern: string | RegExp, options?: {
1054
+ withinFrames?: number;
1055
+ source?: "bus" | "entity";
1056
+ }) => Promise<EventLogEntry>;
1057
+ };
1058
+ readonly capture: {
1059
+ png: () => Promise<Uint8Array>;
1060
+ dataURL: () => Promise<string>;
1061
+ pngBase64: () => Promise<string>;
1062
+ };
982
1063
  constructor(engine: EngineRef);
983
- /** Full state snapshot (serializable). */
1064
+ /** Register a namespaced extension API for plugin-specific debug helpers. */
1065
+ addExtension<T extends object>(namespace: string, api: T): T;
1066
+ /** Look up a previously registered extension API by namespace. */
1067
+ getExtension<T extends object>(namespace: string): T | undefined;
1068
+ /** Remove a previously registered extension namespace. */
1069
+ removeExtension(namespace: string): void;
1070
+ /** Full deterministic state snapshot (stable ordering, serializable). */
984
1071
  snapshot(): EngineSnapshot;
1072
+ /** Stable JSON form of {@link snapshot}. */
1073
+ snapshotJSON(): string;
1074
+ /** Snapshot one scene by inspector scene id. */
1075
+ snapshotScene(id: string): WorldSceneSnapshot;
985
1076
  /** Find entity by name in the active scene. */
986
1077
  getEntityByName(name: string): EntitySnapshot | undefined;
987
1078
  /** Get entity position (from Transform component). */
@@ -993,7 +1084,7 @@ declare class Inspector {
993
1084
  hasComponent(entityName: string, componentClass: string): boolean;
994
1085
  /** Get component data (serializable subset) by class name string. */
995
1086
  getComponentData(entityName: string, componentClass: string): unknown;
996
- /** Get all entities in the active scene as snapshots. */
1087
+ /** Get all entities in the active scene as lightweight snapshots. */
997
1088
  getEntities(): EntitySnapshot[];
998
1089
  /** Get scene stack info. */
999
1090
  getSceneStack(): SceneSnapshot[];
@@ -1001,80 +1092,65 @@ declare class Inspector {
1001
1092
  getSystems(): SystemSnapshot[];
1002
1093
  /** Get disabled components/systems from error boundary. */
1003
1094
  getErrors(): ErrorSnapshot;
1095
+ /** Create a new scene-scoped RNG instance using the current inspector seed policy. */
1096
+ createSceneRandom(): RandomService;
1097
+ /** Force every current and future scene RNG to the provided seed. */
1098
+ setSeed(seed: number): void;
1099
+ /** @internal DebugPlugin installs a deterministic default seed through this hook. */
1100
+ setDefaultSceneSeed(seed: number | undefined): void;
1101
+ private resolveInternalRandom;
1102
+ /** @internal DebugPlugin attaches the frozen-time controller through this hook. */
1103
+ attachTimeController(controller: InspectorTimeController): void;
1104
+ /** @internal Clear a previously attached time controller. */
1105
+ detachTimeController(controller?: InspectorTimeController): void;
1106
+ /** @internal Enable or disable event log recording. */
1107
+ setEventLogEnabled(enabled: boolean): void;
1108
+ /** @internal Install entity-event observation for one scene. No-op if disabled. */
1109
+ attachSceneEventObserver(scene: Scene): void;
1110
+ /** @internal Clear entity-event observation for one scene. */
1111
+ detachSceneEventObserver(scene: Scene): void;
1112
+ /** @internal Scene hooks forward entity events through this method. */
1113
+ recordEntityEvent(eventName: string, data: unknown, entity: Entity): void;
1114
+ /** @internal Engine teardown releases the event-bus tap through this hook. */
1115
+ dispose(): void;
1116
+ private requireTimeController;
1117
+ private requireInputManager;
1118
+ private recordBusEvent;
1119
+ private appendEvent;
1120
+ /** Resolve waiters whose deadline has passed without a match. */
1121
+ private expireDeadlineWaiters;
1122
+ /** Resolve any waiter that matches the just-appended entry. */
1123
+ private flushMatchingWaiter;
1124
+ /**
1125
+ * Walk the ring buffer in chronological order. We avoid materializing the
1126
+ * ordered array on every event append; instead, every consumer that needs
1127
+ * order calls this helper.
1128
+ */
1129
+ private iterateLog;
1130
+ private findMatchingEvent;
1131
+ private eventMatches;
1132
+ private sceneToWorldSnapshot;
1133
+ private getSceneEntities;
1134
+ private entityToWorldSnapshot;
1135
+ private componentToSnapshot;
1136
+ private buildUISnapshot;
1137
+ private buildUINodeSnapshot;
1138
+ private buildCameraSnapshot;
1139
+ private findTopmostCamera;
1140
+ private buildInputSnapshot;
1141
+ private getSceneEvents;
1142
+ private inferSceneIdFromPayload;
1143
+ private extractScene;
1144
+ private extractSceneFromEntity;
1004
1145
  private findActiveEntity;
1005
1146
  private findComponentByName;
1006
- private entityToSnapshot;
1147
+ private entityToQuerySnapshot;
1007
1148
  private getTransform;
1008
- private serializeComponent;
1149
+ private serializeComponentOwnProperties;
1009
1150
  private countEntities;
1010
- }
1011
-
1012
- type EntityRef = {
1013
- readonly id: number;
1014
- readonly name: string;
1015
- };
1016
- type SceneRef = {
1017
- readonly name: string;
1018
- };
1019
- /** Base type for event map definitions. */
1020
- type EventMap = Record<string, unknown>;
1021
- /** Well-known engine events. */
1022
- interface EngineEvents {
1023
- "entity:created": {
1024
- entity: EntityRef;
1025
- };
1026
- "entity:destroyed": {
1027
- entity: EntityRef;
1028
- };
1029
- "component:added": {
1030
- entity: EntityRef;
1031
- component: Component;
1032
- };
1033
- "component:removed": {
1034
- entity: EntityRef;
1035
- componentClass: ComponentClass;
1036
- };
1037
- "scene:pushed": {
1038
- scene: SceneRef;
1039
- };
1040
- "scene:popped": {
1041
- scene: SceneRef;
1042
- };
1043
- "scene:replaced": {
1044
- oldScene: SceneRef;
1045
- newScene: SceneRef;
1046
- };
1047
- "scene:transition:started": {
1048
- kind: SceneTransitionKind;
1049
- fromScene: SceneRef | undefined;
1050
- toScene: SceneRef | undefined;
1051
- };
1052
- "scene:transition:ended": {
1053
- kind: SceneTransitionKind;
1054
- fromScene: SceneRef | undefined;
1055
- toScene: SceneRef | undefined;
1056
- };
1057
- "scene:loading:progress": {
1058
- scene: Scene;
1059
- ratio: number;
1060
- };
1061
- "scene:loading:done": {
1062
- scene: Scene;
1063
- };
1064
- "engine:started": undefined;
1065
- "engine:stopped": undefined;
1066
- }
1067
- /** Typed publish/subscribe event bus. */
1068
- declare class EventBus<E = EventMap> {
1069
- private handlers;
1070
- /** Subscribe to an event. Returns an unsubscribe function. */
1071
- on<K extends keyof E>(event: K, handler: (data: E[K]) => void): () => void;
1072
- /** Subscribe to an event, auto-unsubscribe after first emission. */
1073
- once<K extends keyof E>(event: K, handler: (data: E[K]) => void): () => void;
1074
- /** Emit an event. Handlers are called synchronously in registration order. */
1075
- emit<K extends keyof E>(event: K, data: E[K]): void;
1076
- /** Remove all handlers for an event, or all handlers if no event specified. */
1077
- clear(event?: keyof E): void;
1151
+ private getSceneId;
1152
+ private assertNonNegativeInteger;
1153
+ private assertNonEmptyString;
1078
1154
  }
1079
1155
 
1080
1156
  /**
@@ -1122,51 +1198,266 @@ interface EngineConfig {
1122
1198
  logger?: LoggerConfig;
1123
1199
  }
1124
1200
  /**
1125
- * The top-level entry point. Owns the plugin registry, game loop,
1126
- * scene manager, and DI container.
1201
+ * The top-level entry point. Owns the plugin registry, game loop,
1202
+ * scene manager, and DI container.
1203
+ */
1204
+ declare class Engine {
1205
+ /** The dependency injection container. */
1206
+ readonly context: EngineContext;
1207
+ /** The scene manager. */
1208
+ readonly scenes: SceneManager;
1209
+ /** The event bus. */
1210
+ readonly events: EventBus<EngineEvents>;
1211
+ /** The game loop. */
1212
+ readonly loop: GameLoop;
1213
+ /** The logger. */
1214
+ readonly logger: Logger;
1215
+ /** The inspector (debug queries). */
1216
+ readonly inspector: Inspector;
1217
+ private readonly scheduler;
1218
+ private readonly errorBoundary;
1219
+ private readonly queryCache;
1220
+ private readonly sceneHooks;
1221
+ /** The asset manager. */
1222
+ readonly assets: AssetManager;
1223
+ private readonly plugins;
1224
+ private sortedPlugins;
1225
+ private started;
1226
+ private readonly debug;
1227
+ constructor(config?: EngineConfig);
1228
+ /**
1229
+ * Register scene lifecycle hooks. The returned function unregisters the
1230
+ * hooks. Infrastructure plugins (renderer, physics, debug) register hooks
1231
+ * in their `install` or `onStart` to set up and tear down per-scene state.
1232
+ */
1233
+ registerSceneHooks(hooks: SceneHooks): () => void;
1234
+ /** Register a plugin. Must be called before start(). */
1235
+ use(plugin: Plugin): this;
1236
+ /** Start the engine. Installs plugins in topological order, starts the game loop. */
1237
+ start(): Promise<void>;
1238
+ /** Stop the engine. Destroys all scenes, plugins, and the game loop. */
1239
+ destroy(): void;
1240
+ private registerBuiltInSystems;
1241
+ /**
1242
+ * Topological sort of plugins using Kahn's algorithm.
1243
+ * Errors on missing dependencies, circular dependencies, and duplicates.
1244
+ */
1245
+ private topologicalSort;
1246
+ }
1247
+
1248
+ /**
1249
+ * Base class for systems. Systems run in a specific game loop phase,
1250
+ * query for entities matching a component signature, and operate on them.
1251
+ *
1252
+ * Systems are primarily for engine plugins (physics, rendering, audio).
1253
+ * Game developers typically write Components instead.
1254
+ */
1255
+ declare abstract class System {
1256
+ /** The phase this system runs in. */
1257
+ abstract readonly phase: Phase;
1258
+ /** Execution priority within the phase. Lower = earlier. Default: 0. */
1259
+ readonly priority: number;
1260
+ /** Whether this system is active. */
1261
+ enabled: boolean;
1262
+ /** Reference to the engine context, set on registration. */
1263
+ protected context: EngineContext;
1264
+ private _serviceCache;
1265
+ /**
1266
+ * Set the engine context. Called by Engine during startup.
1267
+ * @internal
1268
+ */
1269
+ _setContext(context: EngineContext): void;
1270
+ /** Resolve a service by key, cached after first lookup. */
1271
+ protected use<T>(key: ServiceKey<T>): T;
1272
+ /** Called once when the system is registered with the engine. */
1273
+ onRegister?(context: EngineContext): void;
1274
+ /** Called every frame (or every fixed step for FixedUpdate). */
1275
+ abstract update(dt: number): void;
1276
+ /** Called when the system is removed. */
1277
+ onUnregister?(): void;
1278
+ }
1279
+
1280
+ /**
1281
+ * Wraps system and component execution. On error, disables the offending
1282
+ * system/component and logs the error. The game loop never crashes.
1283
+ */
1284
+ declare class ErrorBoundary {
1285
+ private logger;
1286
+ private disabledSystems;
1287
+ private disabledComponents;
1288
+ constructor(logger: Logger);
1289
+ /** Wrap a system update call. On throw, disables the system. */
1290
+ wrapSystem(system: System, fn: () => void): void;
1291
+ /** Wrap a component lifecycle or update call. On throw, disables the component. */
1292
+ wrapComponent(component: Component, fn: () => void): void;
1293
+ /** Get all disabled systems and components for inspection. */
1294
+ getDisabled(): {
1295
+ systems: ReadonlyArray<{
1296
+ system: System;
1297
+ error: string;
1298
+ }>;
1299
+ components: ReadonlyArray<{
1300
+ component: Component;
1301
+ error: string;
1302
+ }>;
1303
+ };
1304
+ }
1305
+
1306
+ /** Options for creating a Process. */
1307
+ interface ProcessOptions {
1308
+ /** Called each frame with dt (ms) and elapsed (ms). Return true to complete early. */
1309
+ update?: (dt: number, elapsed: number) => boolean | void;
1310
+ /** Called when the process completes. */
1311
+ onComplete?: () => void;
1312
+ /** Auto-complete after this duration in ms. */
1313
+ duration?: number;
1314
+ /** Loop the process. */
1315
+ loop?: boolean;
1316
+ /** Tags for process filtering. */
1317
+ tags?: string[];
1318
+ }
1319
+ /**
1320
+ * A Process represents an ongoing action updated each frame.
1321
+ * Used internally by Tween and Sequence, and directly for custom coroutines.
1322
+ */
1323
+ declare class Process {
1324
+ private readonly updateFn;
1325
+ private readonly onCompleteFn;
1326
+ private readonly duration;
1327
+ private readonly loop;
1328
+ /** Tags for filtering/grouping. */
1329
+ readonly tags: readonly string[];
1330
+ private elapsed;
1331
+ private _completed;
1332
+ private _paused;
1333
+ private _cancelled;
1334
+ private resolvePromise?;
1335
+ /** Create a timer that fires `onComplete` after `duration` ms. */
1336
+ static delay(duration: number, onComplete?: () => void, tags?: string[]): Process;
1337
+ constructor(options: ProcessOptions);
1338
+ /** Whether the process has completed. */
1339
+ get completed(): boolean;
1340
+ /** Whether the process is paused. */
1341
+ get paused(): boolean;
1342
+ /** Pause the process. */
1343
+ pause(): void;
1344
+ /** Resume the process. */
1345
+ resume(): void;
1346
+ /** Cancel the process. */
1347
+ cancel(): void;
1348
+ /** Returns a promise that resolves when the process completes or is cancelled. */
1349
+ toPromise(): Promise<void>;
1350
+ /**
1351
+ * Advance the process by dt milliseconds.
1352
+ * @internal
1353
+ */
1354
+ _update(dt: number): void;
1355
+ /**
1356
+ * Reset the process to its initial state so it can be re-run.
1357
+ * @internal Used by Sequence for loop/repeat with direct instances.
1358
+ */
1359
+ _reset(): void;
1360
+ private complete;
1361
+ }
1362
+ /** Linear easing (no easing). */
1363
+ declare const easeLinear: EasingFunction;
1364
+ /** Ease in quadratic. */
1365
+ declare const easeInQuad: EasingFunction;
1366
+ /** Ease out quadratic. */
1367
+ declare const easeOutQuad: EasingFunction;
1368
+ /** Ease in-out quadratic. */
1369
+ declare const easeInOutQuad: EasingFunction;
1370
+ /** Ease out bounce. */
1371
+ declare const easeOutBounce: EasingFunction;
1372
+
1373
+ /**
1374
+ * Built-in system that ticks all ProcessComponents on entities in non-paused
1375
+ * scenes, plus a scene-level set of global processes.
1376
+ *
1377
+ * Runs at Phase.Update with priority 500, ensuring tweened values are fresh
1378
+ * before ComponentUpdateSystem (priority 1000) reads them.
1127
1379
  */
1128
- declare class Engine {
1129
- /** The dependency injection container. */
1130
- readonly context: EngineContext;
1131
- /** The scene manager. */
1132
- readonly scenes: SceneManager;
1133
- /** The event bus. */
1134
- readonly events: EventBus<EngineEvents>;
1135
- /** The game loop. */
1136
- readonly loop: GameLoop;
1137
- /** The logger. */
1138
- readonly logger: Logger;
1139
- /** The inspector (debug queries). */
1140
- readonly inspector: Inspector;
1141
- private readonly scheduler;
1142
- private readonly errorBoundary;
1143
- private readonly queryCache;
1144
- private readonly sceneHooks;
1145
- /** The asset manager. */
1146
- readonly assets: AssetManager;
1147
- private readonly plugins;
1148
- private sortedPlugins;
1149
- private started;
1150
- private readonly debug;
1151
- constructor(config?: EngineConfig);
1380
+ declare class ProcessSystem extends System {
1381
+ readonly phase = Phase.Update;
1382
+ readonly priority = 500;
1383
+ /** Global time scale multiplier. Stacks multiplicatively with per-scene timeScale. */
1384
+ timeScale: number;
1385
+ private sceneManager;
1386
+ private globalProcesses;
1387
+ private scenePools;
1388
+ private _unregisterSceneHook;
1389
+ onRegister(context: EngineContext): void;
1390
+ onUnregister(): void;
1152
1391
  /**
1153
- * Register scene lifecycle hooks. The returned function unregisters the
1154
- * hooks. Infrastructure plugins (renderer, physics, debug) register hooks
1155
- * in their `install` or `onStart` to set up and tear down per-scene state.
1392
+ * Add an engine-global process. Ticked under the global timeScale only;
1393
+ * NOT gated by per-scene pause or scaled by per-scene timeScale. Use this
1394
+ * for cross-scene effects (e.g. screen-scope filter fades on `app.stage`)
1395
+ * or processes that have no owning scene.
1156
1396
  */
1157
- registerSceneHooks(hooks: SceneHooks): () => void;
1158
- /** Register a plugin. Must be called before start(). */
1159
- use(plugin: Plugin): this;
1160
- /** Start the engine. Installs plugins in topological order, starts the game loop. */
1161
- start(): Promise<void>;
1162
- /** Stop the engine. Destroys all scenes, plugins, and the game loop. */
1163
- destroy(): void;
1164
- private registerBuiltInSystems;
1397
+ add(process: Process): Process;
1165
1398
  /**
1166
- * Topological sort of plugins using Kahn's algorithm.
1167
- * Errors on missing dependencies, circular dependencies, and duplicates.
1399
+ * Add a process bound to a specific scene's lifecycle. Ticked only while
1400
+ * the scene is active (not paused) and scaled by the scene's `timeScale`,
1401
+ * exactly like an entity-owned `ProcessComponent`. Use this for layer or
1402
+ * scene-scope effect fades that should pause with the scene.
1168
1403
  */
1169
- private topologicalSort;
1404
+ addForScene(scene: Scene, process: Process): Process;
1405
+ /** Cancel engine-global processes, optionally by tag. */
1406
+ cancel(tag?: string): void;
1407
+ /** Cancel every scene-bound process for `scene`, optionally by tag. */
1408
+ cancelForScene(scene: Scene, tag?: string): void;
1409
+ update(dt: number): void;
1410
+ }
1411
+
1412
+ /** A filter used to register a query — an array of required component classes. */
1413
+ type QueryFilter = readonly ComponentClass[];
1414
+ /** A live, iterable set of entities matching a query filter. */
1415
+ declare class QueryResult {
1416
+ /** @internal */
1417
+ readonly _entities: Set<Entity>;
1418
+ /** @internal */
1419
+ readonly _filter: QueryFilter;
1420
+ /** @internal */
1421
+ constructor(filter: QueryFilter);
1422
+ /** Iterate matching entities. */
1423
+ [Symbol.iterator](): Iterator<Entity>;
1424
+ /** Number of matching entities. */
1425
+ get size(): number;
1426
+ /** Get the first match (useful for singleton queries). */
1427
+ get first(): Entity | undefined;
1428
+ /** Convert to array (allocates). */
1429
+ toArray(): Entity[];
1430
+ }
1431
+ /** Incrementally maintained entity sets based on component signatures. */
1432
+ declare class QueryCache {
1433
+ private queries;
1434
+ /** Register a query. Returns a stable reference to a live result set. */
1435
+ register(filter: QueryFilter): QueryResult;
1436
+ /** Called by Entity when a component is added. */
1437
+ onComponentAdded(entity: Entity): void;
1438
+ /** Called by Entity when a component is removed. */
1439
+ onComponentRemoved(entity: Entity): void;
1440
+ /** Called when an entity is destroyed. */
1441
+ onEntityDestroyed(entity: Entity): void;
1442
+ private matches;
1443
+ }
1444
+
1445
+ /** Manages ordered execution of systems within each phase. */
1446
+ declare class SystemScheduler {
1447
+ private phases;
1448
+ private errorBoundary;
1449
+ /** Set the error boundary for wrapping system execution. */
1450
+ setErrorBoundary(boundary: ErrorBoundary): void;
1451
+ /** Register a system. Sorted by priority within its phase. */
1452
+ add(system: System): void;
1453
+ /** Remove a system. */
1454
+ remove(system: System): void;
1455
+ /** Run all enabled systems in a given phase. Wraps each in ErrorBoundary if available. */
1456
+ run(phase: Phase, dt: number): void;
1457
+ /** Get all systems registered for a phase. */
1458
+ getSystems(phase: Phase): readonly System[];
1459
+ /** Get all systems across all phases. */
1460
+ getAllSystems(): System[];
1170
1461
  }
1171
1462
 
1172
1463
  /** The resolution scope for a service. */
@@ -1231,53 +1522,84 @@ declare const ProcessSystemKey: ServiceKey<ProcessSystem>;
1231
1522
  declare const AssetManagerKey: ServiceKey<AssetManager>;
1232
1523
 
1233
1524
  /**
1234
- * Base class for systems. Systems run in a specific game loop phase,
1235
- * query for entities matching a component signature, and operate on them.
1525
+ * Base class for all components.
1236
1526
  *
1237
- * Systems are primarily for engine plugins (physics, rendering, audio).
1238
- * Game developers typically write Components instead.
1527
+ * Components are the primary authoring model. Game developers write behavior
1528
+ * in components using optional `update(dt)` and `fixedUpdate(dt)` methods.
1529
+ * The built-in ComponentUpdateSystem calls these methods automatically.
1239
1530
  */
1240
- declare abstract class System {
1241
- /** The phase this system runs in. */
1242
- abstract readonly phase: Phase;
1243
- /** Execution priority within the phase. Lower = earlier. Default: 0. */
1244
- readonly priority: number;
1245
- /** Whether this system is active. */
1531
+ declare abstract class Component {
1532
+ /**
1533
+ * Back-reference to the owning entity. Set by the engine when the component
1534
+ * is added to an entity. Do not set manually.
1535
+ */
1536
+ entity: Entity;
1537
+ /** Whether this component is active. Disabled components are skipped by ComponentUpdateSystem. */
1246
1538
  enabled: boolean;
1247
- /** Reference to the engine context, set on registration. */
1248
- protected context: EngineContext;
1249
1539
  private _serviceCache;
1540
+ private _cleanups?;
1250
1541
  /**
1251
- * Set the engine context. Called by Engine during startup.
1252
- * @internal
1542
+ * Access the entity's scene. Throws if the entity is not in a scene.
1543
+ * Prefer this over threading through `this.entity.scene` in component
1544
+ * code.
1545
+ */
1546
+ get scene(): Scene;
1547
+ /**
1548
+ * Access the EngineContext from the entity's scene.
1549
+ * Throws if the entity is not in a scene.
1550
+ */
1551
+ get context(): EngineContext;
1552
+ /**
1553
+ * Resolve a service by key, cached after first lookup. Scene-scoped values
1554
+ * (registered via `scene._registerScoped`) take precedence over engine
1555
+ * scope. A key declared with `scope: "scene"` that falls back to engine
1556
+ * scope emits a one-shot dev warning — almost always signals a missed
1557
+ * `beforeEnter` hook.
1253
1558
  */
1254
- _setContext(context: EngineContext): void;
1255
- /** Resolve a service by key, cached after first lookup. */
1256
1559
  protected use<T>(key: ServiceKey<T>): T;
1257
- /** Called once when the system is registered with the engine. */
1258
- onRegister?(context: EngineContext): void;
1259
- /** Called every frame (or every fixed step for FixedUpdate). */
1260
- abstract update(dt: number): void;
1261
- /** Called when the system is removed. */
1262
- onUnregister?(): void;
1263
- }
1264
-
1265
- /** Manages ordered execution of systems within each phase. */
1266
- declare class SystemScheduler {
1267
- private phases;
1268
- private errorBoundary;
1269
- /** Set the error boundary for wrapping system execution. */
1270
- setErrorBoundary(boundary: ErrorBoundary): void;
1271
- /** Register a system. Sorted by priority within its phase. */
1272
- add(system: System): void;
1273
- /** Remove a system. */
1274
- remove(system: System): void;
1275
- /** Run all enabled systems in a given phase. Wraps each in ErrorBoundary if available. */
1276
- run(phase: Phase, dt: number): void;
1277
- /** Get all systems registered for a phase. */
1278
- getSystems(phase: Phase): readonly System[];
1279
- /** Get all systems across all phases. */
1280
- getAllSystems(): System[];
1560
+ private _warnScopedFallback;
1561
+ /**
1562
+ * Lazy proxy-based service resolution. Can be used at field-declaration time:
1563
+ * ```ts
1564
+ * readonly input = this.service(InputManagerKey);
1565
+ * ```
1566
+ * The actual resolution is deferred until first property access.
1567
+ */
1568
+ protected service<T extends object>(key: ServiceKey<T>): T;
1569
+ /**
1570
+ * Lazy proxy-based sibling component resolution. Can be used at field-declaration time:
1571
+ * ```ts
1572
+ * readonly anim = this.sibling(AnimatedSpriteComponent);
1573
+ * ```
1574
+ * The actual resolution is deferred until first property access.
1575
+ */
1576
+ protected sibling<C extends Component>(cls: ComponentClass<C>): C;
1577
+ /** Subscribe to events on any entity, auto-unsubscribe on removal. */
1578
+ protected listen<T>(entity: Entity, token: EventToken<T>, handler: (data: T) => void): void;
1579
+ /** Subscribe to scene-level bubbled events, auto-unsubscribe on removal. */
1580
+ protected listenScene<T>(token: EventToken<T>, handler: (data: T, entity: Entity) => void): void;
1581
+ /** Register a cleanup function to run when this component is removed or destroyed. */
1582
+ protected addCleanup(fn: () => void): void;
1583
+ /**
1584
+ * Run and clear all registered cleanups.
1585
+ * Called by Entity.remove() and Entity._performDestroy() before onRemove/onDestroy.
1586
+ * @internal
1587
+ */
1588
+ _runCleanups(): void;
1589
+ /** Called when the component is added to an entity. */
1590
+ onAdd?(): void;
1591
+ /** Called when the component is removed from an entity. */
1592
+ onRemove?(): void;
1593
+ /** Called when the component is destroyed (entity destroyed or component removed). */
1594
+ onDestroy?(): void;
1595
+ /** Called every frame by the built-in ComponentUpdateSystem. */
1596
+ update?(dt: number): void;
1597
+ /** Called every fixed timestep by the built-in ComponentUpdateSystem. */
1598
+ fixedUpdate?(dt: number): void;
1599
+ /** Return a JSON-serializable snapshot of this component's state. Used by the save system. */
1600
+ serialize?(): unknown;
1601
+ /** Called after onAdd() during save/load restoration. Apply state that depends on onAdd() having run. */
1602
+ afterRestore?(data: unknown, resolve: SnapshotResolver): void;
1281
1603
  }
1282
1604
 
1283
1605
  /** Constructor type for components. */
@@ -1377,26 +1699,43 @@ declare class Vec2 implements Vec2Like {
1377
1699
  static distance(a: Vec2Like, b: Vec2Like): number;
1378
1700
  /** Linear interpolation between two vectors. */
1379
1701
  static lerp(a: Vec2Like, b: Vec2Like, t: number): Vec2;
1702
+ /** Move current toward target by at most maxDelta without overshooting. */
1703
+ static moveTowards(current: Vec2Like, target: Vec2Like, maxDelta: number): Vec2;
1380
1704
  }
1381
1705
 
1706
+ interface SmoothDampResult {
1707
+ /** Smoothed value after this step. */
1708
+ readonly value: number;
1709
+ /** Velocity to pass into the next smoothDamp step. */
1710
+ readonly velocity: number;
1711
+ }
1382
1712
  /** Common math utility functions. */
1383
1713
  declare const MathUtils: {
1384
1714
  /** Linear interpolation between a and b. */
1385
1715
  readonly lerp: (a: number, b: number, t: number) => number;
1716
+ /** Return the clamped interpolation factor that produces v between a and b. */
1717
+ readonly inverseLerp: (a: number, b: number, v: number) => number;
1718
+ /** Interpolate between angles in radians along the shortest path. */
1719
+ readonly lerpAngle: (a: number, b: number, t: number) => number;
1720
+ /** Signed shortest angular delta from a to b, in radians. */
1721
+ readonly shortestAngleBetween: (a: number, b: number) => number;
1386
1722
  /** Clamp a value between min and max. */
1387
1723
  readonly clamp: (value: number, min: number, max: number) => number;
1388
1724
  /** Remap a value from one range to another. */
1389
1725
  readonly remap: (value: number, inMin: number, inMax: number, outMin: number, outMax: number) => number;
1390
- /** Random float in [min, max). */
1391
- readonly randomRange: (min: number, max: number) => number;
1392
- /** Random integer in [min, max] (inclusive). */
1393
- readonly randomInt: (min: number, max: number) => number;
1726
+ /** Bounce t between 0 and length. */
1727
+ readonly pingPong: (t: number, length: number) => number;
1394
1728
  /** Convert degrees to radians. */
1395
1729
  readonly degToRad: (degrees: number) => number;
1396
1730
  /** Convert radians to degrees. */
1397
1731
  readonly radToDeg: (radians: number) => number;
1398
1732
  /** Move current toward target by at most step. */
1399
1733
  readonly approach: (current: number, target: number, step: number) => number;
1734
+ /**
1735
+ * Smoothly damp current toward target without overshooting.
1736
+ * Pass the returned velocity back into the next call.
1737
+ */
1738
+ readonly smoothDamp: (current: number, target: number, velocity: number, smoothTime: number, deltaTime: number, maxSpeed?: number) => SmoothDampResult;
1400
1739
  /** Wrap value into the range [min, max). */
1401
1740
  readonly wrap: (value: number, min: number, max: number) => number;
1402
1741
  };
@@ -1572,6 +1911,7 @@ declare abstract class LoadingScene extends Scene {
1572
1911
  private _active;
1573
1912
  private _continueRequested;
1574
1913
  private _continueGate?;
1914
+ private _pendingWaits;
1575
1915
  private _attempt;
1576
1916
  /** Current load progress, 0 → 1. Updated as the AssetManager reports progress. */
1577
1917
  get progress(): number;
@@ -1601,6 +1941,7 @@ declare abstract class LoadingScene extends Scene {
1601
1941
  continue(): void;
1602
1942
  onExit(): void;
1603
1943
  private _run;
1944
+ private _createEngineTimeDelay;
1604
1945
  }
1605
1946
 
1606
1947
  /** Static factory for creating tween Processes. */
@@ -1688,6 +2029,7 @@ declare class KeyframeAnimator<T extends string = string> extends Component {
1688
2029
  /** Whether a named animation is currently playing. */
1689
2030
  isPlaying(name: T): boolean;
1690
2031
  onDestroy(): void;
2032
+ serialize(): null;
1691
2033
  private stopInternal;
1692
2034
  }
1693
2035
 
@@ -1807,6 +2149,7 @@ declare class ProcessComponent extends Component {
1807
2149
  _tick(dt: number): void;
1808
2150
  /** Cancel all processes and slots on entity destroy. */
1809
2151
  onDestroy(): void;
2152
+ serialize(): null;
1810
2153
  }
1811
2154
 
1812
2155
  /**
@@ -1829,6 +2172,73 @@ declare class TimerEntity extends Entity {
1829
2172
  cancel(tag?: string): void;
1830
2173
  }
1831
2174
 
2175
+ /**
2176
+ * A scoped queue for `Process` instances. Tracks the processes it enqueued so
2177
+ * `cancelAll()` can tear them down without touching unrelated processes that
2178
+ * happen to share the same underlying pool (entity `ProcessComponent` or
2179
+ * engine-level `ProcessSystem`).
2180
+ *
2181
+ * Use one of the `make*ScopedQueue` factories to construct one — each picks
2182
+ * the right routing strategy and lifetime semantics for its scope.
2183
+ */
2184
+ interface ScopedProcessQueue {
2185
+ /** Enqueue a process. Returned for chaining. */
2186
+ run(p: Process): Process;
2187
+ /** Cancel every process this queue enqueued. Idempotent. */
2188
+ cancelAll(): void;
2189
+ }
2190
+ /**
2191
+ * Scoped queue that routes through the entity's `ProcessComponent`. Auto-adds
2192
+ * one if the entity doesn't already have it. `cancelAll()` only cancels the
2193
+ * processes this queue enqueued, so sharing the underlying ProcessComponent
2194
+ * with user code stays safe.
2195
+ */
2196
+ declare function makeEntityScopedQueue(entity: Entity): ScopedProcessQueue;
2197
+ /**
2198
+ * Scoped queue bound to a specific scene's lifecycle. Routes through
2199
+ * `ProcessSystem.addForScene`, so processes pause with the scene and are
2200
+ * scaled by its `timeScale` — matching the behaviour of entity-owned
2201
+ * `ProcessComponent` processes.
2202
+ */
2203
+ declare function makeSceneScopedQueue(processSystem: ProcessSystem, scene: Scene): ScopedProcessQueue;
2204
+ /**
2205
+ * Engine-global scoped queue. Routes through `ProcessSystem.add` — ticked
2206
+ * under the global timeScale only, NOT gated by per-scene pause or scaled
2207
+ * by per-scene timeScale. Right for cross-scene work that should keep
2208
+ * playing during scene transitions and across paused scenes.
2209
+ */
2210
+ declare function makeGlobalScopedQueue(processSystem: ProcessSystem): ScopedProcessQueue;
2211
+
2212
+ /**
2213
+ * Cross-package contract for "something that owns a canvas and can map
2214
+ * canvas-relative CSS pixels into virtual-space pixels".
2215
+ *
2216
+ * Implemented by `@yagejs/renderer`'s `RendererPlugin` and consumed by
2217
+ * `@yagejs/input` for pointer-event targeting and coordinate mapping under
2218
+ * responsive fit. Foreign renderers can implement this interface and register
2219
+ * under `RendererAdapterKey` to integrate with the input plugin without
2220
+ * importing `@yagejs/renderer`.
2221
+ */
2222
+ interface RendererAdapter {
2223
+ readonly canvas: HTMLCanvasElement;
2224
+ /**
2225
+ * Convert CSS pixels relative to the canvas into virtual-space pixels.
2226
+ * Optional — when absent, consumers fall back to raw CSS pixels (correct
2227
+ * only when canvas CSS size equals virtual size).
2228
+ */
2229
+ canvasToVirtual?(x: number, y: number): {
2230
+ x: number;
2231
+ y: number;
2232
+ };
2233
+ }
2234
+ /**
2235
+ * Well-known service key for the current renderer's pointer-input adapter.
2236
+ * The canonical `@yagejs/renderer` plugin registers itself here; consumers
2237
+ * (notably `@yagejs/input`) resolve this key to auto-wire canvas targeting
2238
+ * and coordinate mapping.
2239
+ */
2240
+ declare const RendererAdapterKey: ServiceKey<RendererAdapter>;
2241
+
1832
2242
  /** Create a fully wired Engine for integration tests. */
1833
2243
  declare function createTestEngine(config?: EngineConfig): Promise<Engine>;
1834
2244
  /** Create a lightweight mock scene with EngineContext for unit tests. */
@@ -1847,4 +2257,4 @@ declare function advanceFrames(engine: Engine, n: number, dtMs?: number): void;
1847
2257
 
1848
2258
  declare const VERSION = "0.0.0";
1849
2259
 
1850
- export { AssetHandle, type AssetLoader, AssetManager, AssetManagerKey, type Blueprint, Component, type ComponentClass, ComponentFixedUpdateSystem, ComponentUpdateSystem, type EasingFunction, Engine, type EngineConfig, EngineContext, type EngineEvents, EngineKey, type EngineSnapshot, Entity, type EntityCallbacks, type EntityFilter, type EntitySnapshot, ErrorBoundary, ErrorBoundaryKey, type ErrorSnapshot, EventBus, EventBusKey, type EventMap, EventToken, GameLoop, type GameLoopCallbacks, type GameLoopConfig, GameLoopKey, Inspector, InspectorKey, type Interpolatable, type Keyframe, type KeyframeAnimationDef, KeyframeAnimator, type KeyframeTrackOptions, LoadingScene, type LogEntry, LogLevel, Logger, type LoggerConfig, LoggerKey, MathUtils, Phase, type Plugin, Process, ProcessComponent, type ProcessOptions, ProcessSlot, type ProcessSlotConfig, ProcessSystem, ProcessSystemKey, QueryCache, QueryCacheKey, QueryResult, SERIALIZABLE_KEY, Scene, SceneHookRegistry, SceneHookRegistryKey, type SceneHooks, SceneManager, SceneManagerKey, type SceneSnapshot, type SceneTransition, type SceneTransitionContext, type SceneTransitionKind, type SceneTransitionOptions, Sequence, SerializableRegistry, ServiceKey, type ServiceKeyOptions, type ServiceScope, type SnapshotResolver, System, SystemScheduler, SystemSchedulerKey, type SystemSnapshot, TimerEntity, TraitToken, Transform, type TransformData, Tween, VERSION, Vec2, type Vec2Like, _resetEntityIdCounter, advanceFrames, createKeyframeTrack, createMockEntity, createMockScene, createTestEngine, defineBlueprint, defineEvent, defineTrait, easeInOutQuad, easeInQuad, easeLinear, easeOutBounce, easeOutQuad, filterEntities, getSerializableType, interpolate, isSerializable, resolveTransition, serializable, trait };
2260
+ export { AssetHandle, type AssetLoader, AssetManager, AssetManagerKey, type Blueprint, type CameraSnapshot, Component, type ComponentClass, ComponentFixedUpdateSystem, type ComponentStateSnapshot, ComponentUpdateSystem, type EasingFunction, Engine, type EngineConfig, EngineContext, type EngineEvents, EngineKey, type EngineSnapshot, Entity, type EntityCallbacks, type EntityFilter, type EntitySnapshot, ErrorBoundary, ErrorBoundaryKey, type ErrorSnapshot, EventBus, EventBusKey, type EventLogEntry, type EventMap, EventToken, GameLoop, type GameLoopCallbacks, type GameLoopConfig, GameLoopKey, type InputStateSnapshot, Inspector, InspectorKey, type InspectorTimeController, type Interpolatable, type Keyframe, type KeyframeAnimationDef, KeyframeAnimator, type KeyframeTrackOptions, LoadingScene, type LogEntry, LogLevel, Logger, type LoggerConfig, LoggerKey, MathUtils, Phase, type PhysicsSnapshot, type Plugin, Process, ProcessComponent, type ProcessOptions, ProcessSlot, type ProcessSlotConfig, ProcessSystem, ProcessSystemKey, QueryCache, QueryCacheKey, QueryResult, RandomKey, type RandomService, type RendererAdapter, RendererAdapterKey, SERIALIZABLE_KEY, Scene, SceneHookRegistry, SceneHookRegistryKey, type SceneHooks, SceneManager, SceneManagerKey, type SceneSnapshot, type SceneTransition, type SceneTransitionContext, type SceneTransitionKind, type SceneTransitionOptions, type ScopedProcessQueue, Sequence, SerializableRegistry, ServiceKey, type ServiceKeyOptions, type ServiceScope, type SmoothDampResult, type SnapshotResolver, System, SystemScheduler, SystemSchedulerKey, type SystemSnapshot, TimerEntity, TraitToken, Transform, type TransformData, Tween, type UINodeSnapshot, type UITreeSnapshot, VERSION, Vec2, type Vec2Like, type WorldEntitySnapshot, type WorldSceneSnapshot, _resetEntityIdCounter, advanceFrames, createDefaultRandomSeed, createKeyframeTrack, createMockEntity, createMockScene, createRandomService, createTestEngine, defineBlueprint, defineEvent, defineTrait, easeInOutQuad, easeInQuad, easeLinear, easeOutBounce, easeOutQuad, filterEntities, getSerializableType, globalRandom, interpolate, isSerializable, makeEntityScopedQueue, makeGlobalScopedQueue, makeSceneScopedQueue, normalizeSeed, resolveTransition, serializable, trait };