@yagejs/core 0.3.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.
@@ -443,46 +297,6 @@ declare class Entity {
443
297
  _setScene(scene: Scene | null, callbacks: EntityCallbacks | null): void;
444
298
  }
445
299
 
446
- /** Which scene op triggered this transition. */
447
- type SceneTransitionKind = "push" | "pop" | "replace";
448
- /** Context passed to a transition each frame. */
449
- interface SceneTransitionContext {
450
- /** Wall-clock ms elapsed since begin(). */
451
- readonly elapsed: number;
452
- readonly kind: SceneTransitionKind;
453
- readonly engineContext: EngineContext;
454
- /** The scene being left or removed (undefined on first push). */
455
- readonly fromScene: Scene | undefined;
456
- /** The scene being entered or revealed (undefined on last pop). */
457
- readonly toScene: Scene | undefined;
458
- }
459
- /**
460
- * A scene transition animates the handoff between scene stack states.
461
- *
462
- * `SceneManager` keeps both the outgoing and incoming scenes on the stack
463
- * for the transition's duration, then removes the outgoing scene afterward.
464
- * Transitions use raw wall-clock dt and ignore engine + scene `timeScale`.
465
- */
466
- interface SceneTransition {
467
- /** Total duration in wall-clock ms. */
468
- readonly duration: number;
469
- /** Called once when the transition starts. Set up resources here. */
470
- begin?(ctx: SceneTransitionContext): void;
471
- /** Called each frame with frame dt in ms. `ctx.elapsed` is clamped to `duration`. */
472
- tick(dt: number, ctx: SceneTransitionContext): void;
473
- /** Called when the transition ends. Tear down resources here. */
474
- end?(ctx: SceneTransitionContext): void;
475
- }
476
- /** Options accepted by `SceneManager.push/pop/replace`. */
477
- interface SceneTransitionOptions {
478
- transition?: SceneTransition;
479
- }
480
- /**
481
- * Resolve the effective transition for a scene op.
482
- * Precedence: call-site option → destination's `defaultTransition` → undefined.
483
- */
484
- declare function resolveTransition(callSite: SceneTransition | undefined, destination: Scene | undefined): SceneTransition | undefined;
485
-
486
300
  /** Filter criteria for entity queries. All fields are AND'd together. */
487
301
  interface EntityFilter {
488
302
  /** Match entities whose class implements this trait. */
@@ -523,6 +337,7 @@ declare abstract class Scene {
523
337
  private queryCache;
524
338
  private bus;
525
339
  private _entityEventHandlers?;
340
+ private _entityEventObserver?;
526
341
  private _scopedServices?;
527
342
  /** Access the EngineContext. */
528
343
  get context(): EngineContext;
@@ -580,6 +395,12 @@ declare abstract class Scene {
580
395
  * @internal
581
396
  */
582
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;
583
404
  /** Called during asset preloading with progress ratio (0→1). */
584
405
  onProgress?(ratio: number): void;
585
406
  /** Called when the scene is entered (after preload completes). */
@@ -601,6 +422,11 @@ declare abstract class Scene {
601
422
  * @internal
602
423
  */
603
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;
604
430
  /**
605
431
  * Resolve a scene-scoped service, or `undefined` if none was registered.
606
432
  * @internal
@@ -630,85 +456,118 @@ declare abstract class Scene {
630
456
  _destroyAllEntities(): void;
631
457
  }
632
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
+ }
633
472
  /**
634
- * Base class for all components.
473
+ * A scene transition animates the handoff between scene stack states.
635
474
  *
636
- * Components are the primary authoring model. Game developers write behavior
637
- * in components using optional `update(dt)` and `fixedUpdate(dt)` methods.
638
- * 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`.
639
478
  */
640
- declare abstract class Component {
641
- /**
642
- * Back-reference to the owning entity. Set by the engine when the component
643
- * is added to an entity. Do not set manually.
644
- */
645
- entity: Entity;
646
- /** Whether this component is active. Disabled components are skipped by ComponentUpdateSystem. */
647
- enabled: boolean;
648
- private _serviceCache;
649
- private _cleanups?;
650
- /**
651
- * Access the entity's scene. Throws if the entity is not in a scene.
652
- * Prefer this over threading through `this.entity.scene` in component
653
- * code.
654
- */
655
- get scene(): Scene;
656
- /**
657
- * Access the EngineContext from the entity's scene.
658
- * Throws if the entity is not in a scene.
659
- */
660
- get context(): EngineContext;
661
- /**
662
- * Resolve a service by key, cached after first lookup. Scene-scoped values
663
- * (registered via `scene._registerScoped`) take precedence over engine
664
- * scope. A key declared with `scope: "scene"` that falls back to engine
665
- * scope emits a one-shot dev warning — almost always signals a missed
666
- * `beforeEnter` hook.
667
- */
668
- protected use<T>(key: ServiceKey<T>): T;
669
- private _warnScopedFallback;
670
- /**
671
- * Lazy proxy-based service resolution. Can be used at field-declaration time:
672
- * ```ts
673
- * readonly input = this.service(InputManagerKey);
674
- * ```
675
- * The actual resolution is deferred until first property access.
676
- */
677
- protected service<T extends object>(key: ServiceKey<T>): T;
678
- /**
679
- * Lazy proxy-based sibling component resolution. Can be used at field-declaration time:
680
- * ```ts
681
- * readonly anim = this.sibling(AnimatedSpriteComponent);
682
- * ```
683
- * The actual resolution is deferred until first property access.
684
- */
685
- protected sibling<C extends Component>(cls: ComponentClass<C>): C;
686
- /** Subscribe to events on any entity, auto-unsubscribe on removal. */
687
- protected listen<T>(entity: Entity, token: EventToken<T>, handler: (data: T) => void): void;
688
- /** Subscribe to scene-level bubbled events, auto-unsubscribe on removal. */
689
- protected listenScene<T>(token: EventToken<T>, handler: (data: T, entity: Entity) => void): void;
690
- /** Register a cleanup function to run when this component is removed or destroyed. */
691
- 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;
692
564
  /**
693
- * Run and clear all registered cleanups.
694
- * Called by Entity.remove() and Entity._performDestroy() before onRemove/onDestroy.
695
- * @internal
565
+ * Observe every emitted event without affecting handler order or control
566
+ * flow. Used by tooling such as the Inspector event log.
696
567
  */
697
- _runCleanups(): void;
698
- /** Called when the component is added to an entity. */
699
- onAdd?(): void;
700
- /** Called when the component is removed from an entity. */
701
- onRemove?(): void;
702
- /** Called when the component is destroyed (entity destroyed or component removed). */
703
- onDestroy?(): void;
704
- /** Called every frame by the built-in ComponentUpdateSystem. */
705
- update?(dt: number): void;
706
- /** Called every fixed timestep by the built-in ComponentUpdateSystem. */
707
- fixedUpdate?(dt: number): void;
708
- /** Return a JSON-serializable snapshot of this component's state. Used by the save system. */
709
- serialize?(): unknown;
710
- /** Called after onAdd() during save/load restoration. Apply state that depends on onAdd() having run. */
711
- 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;
712
571
  }
713
572
 
714
573
  /** Log severity levels. */
@@ -775,63 +634,61 @@ declare class Logger {
775
634
  private log;
776
635
  }
777
636
 
778
- /**
779
- * Wraps system and component execution. On error, disables the offending
780
- * system/component and logs the error. The game loop never crashes.
781
- */
782
- declare class ErrorBoundary {
783
- private logger;
784
- private disabledSystems;
785
- private disabledComponents;
786
- constructor(logger: Logger);
787
- /** Wrap a system update call. On throw, disables the system. */
788
- wrapSystem(system: System, fn: () => void): void;
789
- /** Wrap a component lifecycle or update call. On throw, disables the component. */
790
- wrapComponent(component: Component, fn: () => void): void;
791
- /** Get all disabled systems and components for inspection. */
792
- getDisabled(): {
793
- systems: ReadonlyArray<{
794
- system: System;
795
- error: string;
796
- }>;
797
- components: ReadonlyArray<{
798
- component: Component;
799
- error: string;
800
- }>;
801
- };
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;
802
645
  }
803
-
804
- /** A filter used to register a query — an array of required component classes. */
805
- type QueryFilter = readonly ComponentClass[];
806
- /** A live, iterable set of entities matching a query filter. */
807
- declare class QueryResult {
808
- /** @internal */
809
- readonly _entities: Set<Entity>;
810
- /** @internal */
811
- readonly _filter: QueryFilter;
812
- /** @internal */
813
- constructor(filter: QueryFilter);
814
- /** Iterate matching entities. */
815
- [Symbol.iterator](): Iterator<Entity>;
816
- /** Number of matching entities. */
817
- get size(): number;
818
- /** Get the first match (useful for singleton queries). */
819
- get first(): Entity | undefined;
820
- /** Convert to array (allocates). */
821
- 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;
822
652
  }
823
- /** Incrementally maintained entity sets based on component signatures. */
824
- declare class QueryCache {
825
- private queries;
826
- /** Register a query. Returns a stable reference to a live result set. */
827
- register(filter: QueryFilter): QueryResult;
828
- /** Called by Entity when a component is added. */
829
- onComponentAdded(entity: Entity): void;
830
- /** Called by Entity when a component is removed. */
831
- onComponentRemoved(entity: Entity): void;
832
- /** Called when an entity is destroyed. */
833
- onEntityDestroyed(entity: Entity): void;
834
- 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;
835
692
  }
836
693
 
837
694
  /** Stack-based scene manager with push/pop/replace semantics. */
@@ -954,15 +811,36 @@ declare class SceneManager {
954
811
  private _fireResumeTransitions;
955
812
  }
956
813
 
957
- /** Full engine state snapshot. */
958
- interface EngineSnapshot {
959
- frameCount: number;
960
- sceneStack: SceneSnapshot[];
961
- entityCount: number;
962
- systemCount: number;
963
- errors: ErrorSnapshot;
964
- }
965
- /** 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. */
966
844
  interface EntitySnapshot {
967
845
  id: number;
968
846
  name: string;
@@ -973,7 +851,7 @@ interface EntitySnapshot {
973
851
  y: number;
974
852
  };
975
853
  }
976
- /** Snapshot of a scene in the stack. */
854
+ /** Backward-compatible scene stack summary. */
977
855
  interface SceneSnapshot {
978
856
  name: string;
979
857
  entityCount: number;
@@ -995,114 +873,284 @@ interface ErrorSnapshot {
995
873
  error: string;
996
874
  }>;
997
875
  }
998
- /** Internal engine reference to avoid circular dependency with Engine class. */
999
- interface EngineRef {
1000
- readonly context: EngineContext;
1001
- readonly scenes: SceneManager;
1002
- readonly loop: GameLoop;
876
+ interface ComponentStateSnapshot {
877
+ type: string;
878
+ state: unknown | null;
1003
879
  }
1004
- /**
1005
- * Programmatic state queries for testing and debugging.
1006
- * Exposed on `window.__yage__` in debug mode.
1007
- */
1008
- declare class Inspector {
1009
- private engine;
1010
- constructor(engine: EngineRef);
1011
- /** Full state snapshot (serializable). */
1012
- snapshot(): EngineSnapshot;
1013
- /** Find entity by name in the active scene. */
1014
- getEntityByName(name: string): EntitySnapshot | undefined;
1015
- /** Get entity position (from Transform component). */
1016
- getEntityPosition(name: string): {
880
+ interface WorldEntitySnapshot {
881
+ id: string;
882
+ type: string;
883
+ parent: string | null;
884
+ transform: {
1017
885
  x: number;
1018
886
  y: number;
1019
- } | undefined;
1020
- /** Check if an entity has a component by class name string. */
1021
- hasComponent(entityName: string, componentClass: string): boolean;
1022
- /** Get component data (serializable subset) by class name string. */
1023
- getComponentData(entityName: string, componentClass: string): unknown;
1024
- /** Get all entities in the active scene as snapshots. */
1025
- getEntities(): EntitySnapshot[];
1026
- /** Get scene stack info. */
1027
- getSceneStack(): SceneSnapshot[];
1028
- /** Get active system info. */
1029
- getSystems(): SystemSnapshot[];
1030
- /** Get disabled components/systems from error boundary. */
1031
- getErrors(): ErrorSnapshot;
1032
- private findActiveEntity;
1033
- private findComponentByName;
1034
- private entityToSnapshot;
1035
- private getTransform;
1036
- private serializeComponent;
1037
- private countEntities;
1038
- }
1039
-
1040
- type EntityRef = {
1041
- readonly id: number;
1042
- readonly name: string;
1043
- };
1044
- type SceneRef = {
1045
- readonly name: string;
1046
- };
1047
- /** Base type for event map definitions. */
1048
- type EventMap = Record<string, unknown>;
1049
- /** Well-known engine events. */
1050
- interface EngineEvents {
1051
- "entity:created": {
1052
- entity: EntityRef;
887
+ rotation: number;
888
+ scaleX: number;
889
+ scaleY: number;
1053
890
  };
1054
- "entity:destroyed": {
1055
- entity: EntityRef;
1056
- };
1057
- "component:added": {
1058
- entity: EntityRef;
1059
- component: Component;
1060
- };
1061
- "component:removed": {
1062
- entity: EntityRef;
1063
- componentClass: ComponentClass;
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;
1064
901
  };
1065
- "scene:pushed": {
1066
- scene: SceneRef;
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;
1067
954
  };
1068
- "scene:popped": {
1069
- scene: SceneRef;
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;
1070
966
  };
1071
- "scene:replaced": {
1072
- oldScene: SceneRef;
1073
- newScene: SceneRef;
967
+ gamepad: {
968
+ buttons: number[];
969
+ axes: Array<{
970
+ index: number;
971
+ value: number;
972
+ }>;
1074
973
  };
1075
- "scene:transition:started": {
1076
- kind: SceneTransitionKind;
1077
- fromScene: SceneRef | undefined;
1078
- toScene: SceneRef | undefined;
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
+ }
995
+ /** Internal engine reference to avoid circular dependency with Engine class. */
996
+ interface EngineRef {
997
+ readonly context: EngineContext;
998
+ readonly scenes: SceneManager;
999
+ readonly loop: GameLoop;
1000
+ readonly events?: EventBus<EngineEvents>;
1001
+ }
1002
+ /**
1003
+ * Programmatic runtime control and state queries for testing and debugging.
1004
+ * Exposed on `window.__yage__` in debug mode.
1005
+ */
1006
+ declare class Inspector {
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;
1079
1035
  };
1080
- "scene:transition:ended": {
1081
- kind: SceneTransitionKind;
1082
- fromScene: SceneRef | undefined;
1083
- toScene: SceneRef | undefined;
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;
1084
1048
  };
1085
- "scene:loading:progress": {
1086
- scene: Scene;
1087
- ratio: number;
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>;
1088
1057
  };
1089
- "scene:loading:done": {
1090
- scene: Scene;
1058
+ readonly capture: {
1059
+ png: () => Promise<Uint8Array>;
1060
+ dataURL: () => Promise<string>;
1061
+ pngBase64: () => Promise<string>;
1091
1062
  };
1092
- "engine:started": undefined;
1093
- "engine:stopped": undefined;
1094
- }
1095
- /** Typed publish/subscribe event bus. */
1096
- declare class EventBus<E = EventMap> {
1097
- private handlers;
1098
- /** Subscribe to an event. Returns an unsubscribe function. */
1099
- on<K extends keyof E>(event: K, handler: (data: E[K]) => void): () => void;
1100
- /** Subscribe to an event, auto-unsubscribe after first emission. */
1101
- once<K extends keyof E>(event: K, handler: (data: E[K]) => void): () => void;
1102
- /** Emit an event. Handlers are called synchronously in registration order. */
1103
- emit<K extends keyof E>(event: K, data: E[K]): void;
1104
- /** Remove all handlers for an event, or all handlers if no event specified. */
1105
- clear(event?: keyof E): void;
1063
+ constructor(engine: EngineRef);
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). */
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;
1076
+ /** Find entity by name in the active scene. */
1077
+ getEntityByName(name: string): EntitySnapshot | undefined;
1078
+ /** Get entity position (from Transform component). */
1079
+ getEntityPosition(name: string): {
1080
+ x: number;
1081
+ y: number;
1082
+ } | undefined;
1083
+ /** Check if an entity has a component by class name string. */
1084
+ hasComponent(entityName: string, componentClass: string): boolean;
1085
+ /** Get component data (serializable subset) by class name string. */
1086
+ getComponentData(entityName: string, componentClass: string): unknown;
1087
+ /** Get all entities in the active scene as lightweight snapshots. */
1088
+ getEntities(): EntitySnapshot[];
1089
+ /** Get scene stack info. */
1090
+ getSceneStack(): SceneSnapshot[];
1091
+ /** Get active system info. */
1092
+ getSystems(): SystemSnapshot[];
1093
+ /** Get disabled components/systems from error boundary. */
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;
1145
+ private findActiveEntity;
1146
+ private findComponentByName;
1147
+ private entityToQuerySnapshot;
1148
+ private getTransform;
1149
+ private serializeComponentOwnProperties;
1150
+ private countEntities;
1151
+ private getSceneId;
1152
+ private assertNonNegativeInteger;
1153
+ private assertNonEmptyString;
1106
1154
  }
1107
1155
 
1108
1156
  /**
@@ -1135,66 +1183,281 @@ declare class SceneHookRegistry {
1135
1183
  runBeforeEnter(scene: Scene): Promise<void>;
1136
1184
  runAfterExit(scene: Scene): void;
1137
1185
  }
1138
- /** DI key for the scene-hook registry. @internal */
1139
- declare const SceneHookRegistryKey: ServiceKey<SceneHookRegistry>;
1186
+ /** DI key for the scene-hook registry. @internal */
1187
+ declare const SceneHookRegistryKey: ServiceKey<SceneHookRegistry>;
1188
+
1189
+ /** Engine configuration. */
1190
+ interface EngineConfig {
1191
+ /** Enable debug mode (Inspector API, debug logging). */
1192
+ debug?: boolean;
1193
+ /** Fixed timestep in ms (default: 1000/60). */
1194
+ fixedTimestep?: number;
1195
+ /** Max fixed steps per frame to prevent spiral of death (default: 5). */
1196
+ maxFixedStepsPerFrame?: number;
1197
+ /** Logger configuration. */
1198
+ logger?: LoggerConfig;
1199
+ }
1200
+ /**
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;
1140
1372
 
1141
- /** Engine configuration. */
1142
- interface EngineConfig {
1143
- /** Enable debug mode (Inspector API, debug logging). */
1144
- debug?: boolean;
1145
- /** Fixed timestep in ms (default: 1000/60). */
1146
- fixedTimestep?: number;
1147
- /** Max fixed steps per frame to prevent spiral of death (default: 5). */
1148
- maxFixedStepsPerFrame?: number;
1149
- /** Logger configuration. */
1150
- logger?: LoggerConfig;
1151
- }
1152
1373
  /**
1153
- * The top-level entry point. Owns the plugin registry, game loop,
1154
- * scene manager, and DI container.
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.
1155
1379
  */
1156
- declare class Engine {
1157
- /** The dependency injection container. */
1158
- readonly context: EngineContext;
1159
- /** The scene manager. */
1160
- readonly scenes: SceneManager;
1161
- /** The event bus. */
1162
- readonly events: EventBus<EngineEvents>;
1163
- /** The game loop. */
1164
- readonly loop: GameLoop;
1165
- /** The logger. */
1166
- readonly logger: Logger;
1167
- /** The inspector (debug queries). */
1168
- readonly inspector: Inspector;
1169
- private readonly scheduler;
1170
- private readonly errorBoundary;
1171
- private readonly queryCache;
1172
- private readonly sceneHooks;
1173
- /** The asset manager. */
1174
- readonly assets: AssetManager;
1175
- private readonly plugins;
1176
- private sortedPlugins;
1177
- private started;
1178
- private readonly debug;
1179
- 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;
1180
1391
  /**
1181
- * Register scene lifecycle hooks. The returned function unregisters the
1182
- * hooks. Infrastructure plugins (renderer, physics, debug) register hooks
1183
- * 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.
1184
1396
  */
1185
- registerSceneHooks(hooks: SceneHooks): () => void;
1186
- /** Register a plugin. Must be called before start(). */
1187
- use(plugin: Plugin): this;
1188
- /** Start the engine. Installs plugins in topological order, starts the game loop. */
1189
- start(): Promise<void>;
1190
- /** Stop the engine. Destroys all scenes, plugins, and the game loop. */
1191
- destroy(): void;
1192
- private registerBuiltInSystems;
1397
+ add(process: Process): Process;
1193
1398
  /**
1194
- * Topological sort of plugins using Kahn's algorithm.
1195
- * 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.
1196
1403
  */
1197
- 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[];
1198
1461
  }
1199
1462
 
1200
1463
  /** The resolution scope for a service. */
@@ -1259,53 +1522,84 @@ declare const ProcessSystemKey: ServiceKey<ProcessSystem>;
1259
1522
  declare const AssetManagerKey: ServiceKey<AssetManager>;
1260
1523
 
1261
1524
  /**
1262
- * Base class for systems. Systems run in a specific game loop phase,
1263
- * query for entities matching a component signature, and operate on them.
1525
+ * Base class for all components.
1264
1526
  *
1265
- * Systems are primarily for engine plugins (physics, rendering, audio).
1266
- * 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.
1267
1530
  */
1268
- declare abstract class System {
1269
- /** The phase this system runs in. */
1270
- abstract readonly phase: Phase;
1271
- /** Execution priority within the phase. Lower = earlier. Default: 0. */
1272
- readonly priority: number;
1273
- /** 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. */
1274
1538
  enabled: boolean;
1275
- /** Reference to the engine context, set on registration. */
1276
- protected context: EngineContext;
1277
1539
  private _serviceCache;
1540
+ private _cleanups?;
1278
1541
  /**
1279
- * Set the engine context. Called by Engine during startup.
1280
- * @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.
1281
1558
  */
1282
- _setContext(context: EngineContext): void;
1283
- /** Resolve a service by key, cached after first lookup. */
1284
1559
  protected use<T>(key: ServiceKey<T>): T;
1285
- /** Called once when the system is registered with the engine. */
1286
- onRegister?(context: EngineContext): void;
1287
- /** Called every frame (or every fixed step for FixedUpdate). */
1288
- abstract update(dt: number): void;
1289
- /** Called when the system is removed. */
1290
- onUnregister?(): void;
1291
- }
1292
-
1293
- /** Manages ordered execution of systems within each phase. */
1294
- declare class SystemScheduler {
1295
- private phases;
1296
- private errorBoundary;
1297
- /** Set the error boundary for wrapping system execution. */
1298
- setErrorBoundary(boundary: ErrorBoundary): void;
1299
- /** Register a system. Sorted by priority within its phase. */
1300
- add(system: System): void;
1301
- /** Remove a system. */
1302
- remove(system: System): void;
1303
- /** Run all enabled systems in a given phase. Wraps each in ErrorBoundary if available. */
1304
- run(phase: Phase, dt: number): void;
1305
- /** Get all systems registered for a phase. */
1306
- getSystems(phase: Phase): readonly System[];
1307
- /** Get all systems across all phases. */
1308
- 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;
1309
1603
  }
1310
1604
 
1311
1605
  /** Constructor type for components. */
@@ -1431,10 +1725,6 @@ declare const MathUtils: {
1431
1725
  readonly remap: (value: number, inMin: number, inMax: number, outMin: number, outMax: number) => number;
1432
1726
  /** Bounce t between 0 and length. */
1433
1727
  readonly pingPong: (t: number, length: number) => number;
1434
- /** Random float in [min, max). */
1435
- readonly randomRange: (min: number, max: number) => number;
1436
- /** Random integer in [min, max] (inclusive). */
1437
- readonly randomInt: (min: number, max: number) => number;
1438
1728
  /** Convert degrees to radians. */
1439
1729
  readonly degToRad: (degrees: number) => number;
1440
1730
  /** Convert radians to degrees. */
@@ -1621,6 +1911,7 @@ declare abstract class LoadingScene extends Scene {
1621
1911
  private _active;
1622
1912
  private _continueRequested;
1623
1913
  private _continueGate?;
1914
+ private _pendingWaits;
1624
1915
  private _attempt;
1625
1916
  /** Current load progress, 0 → 1. Updated as the AssetManager reports progress. */
1626
1917
  get progress(): number;
@@ -1650,6 +1941,7 @@ declare abstract class LoadingScene extends Scene {
1650
1941
  continue(): void;
1651
1942
  onExit(): void;
1652
1943
  private _run;
1944
+ private _createEngineTimeDelay;
1653
1945
  }
1654
1946
 
1655
1947
  /** Static factory for creating tween Processes. */
@@ -1737,6 +2029,7 @@ declare class KeyframeAnimator<T extends string = string> extends Component {
1737
2029
  /** Whether a named animation is currently playing. */
1738
2030
  isPlaying(name: T): boolean;
1739
2031
  onDestroy(): void;
2032
+ serialize(): null;
1740
2033
  private stopInternal;
1741
2034
  }
1742
2035
 
@@ -1856,6 +2149,7 @@ declare class ProcessComponent extends Component {
1856
2149
  _tick(dt: number): void;
1857
2150
  /** Cancel all processes and slots on entity destroy. */
1858
2151
  onDestroy(): void;
2152
+ serialize(): null;
1859
2153
  }
1860
2154
 
1861
2155
  /**
@@ -1878,6 +2172,43 @@ declare class TimerEntity extends Entity {
1878
2172
  cancel(tag?: string): void;
1879
2173
  }
1880
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
+
1881
2212
  /**
1882
2213
  * Cross-package contract for "something that owns a canvas and can map
1883
2214
  * canvas-relative CSS pixels into virtual-space pixels".
@@ -1926,4 +2257,4 @@ declare function advanceFrames(engine: Engine, n: number, dtMs?: number): void;
1926
2257
 
1927
2258
  declare const VERSION = "0.0.0";
1928
2259
 
1929
- 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, type RendererAdapter, RendererAdapterKey, 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 SmoothDampResult, 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 };