@yagejs/core 0.1.0 → 0.3.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
@@ -209,6 +209,25 @@ declare class EventToken<T = void> {
209
209
  /** Create a typed event token. */
210
210
  declare function defineEvent<T = void>(name: string): EventToken<T>;
211
211
 
212
+ /**
213
+ * A reusable entity template. Blueprints define how to assemble
214
+ * an entity from components, given optional parameters.
215
+ *
216
+ * @deprecated Prefer Entity subclasses with `setup()` for entity types.
217
+ * Blueprints still work for parametric factories but are no longer the
218
+ * recommended pattern for new code.
219
+ */
220
+ interface Blueprint<P = void> {
221
+ readonly name: string;
222
+ build(entity: Entity, params: P): void;
223
+ }
224
+ /**
225
+ * Create a blueprint from a name and a build function.
226
+ *
227
+ * @deprecated Prefer Entity subclasses with `setup()` for entity types.
228
+ */
229
+ declare function defineBlueprint<P = void>(name: string, build: (entity: Entity, params: P) => void): Blueprint<P>;
230
+
212
231
  /**
213
232
  * Passed to `afterRestore` hooks so user code can resolve entity references
214
233
  * that were captured as IDs at save time.
@@ -330,8 +349,20 @@ declare class Entity {
330
349
  private _parent;
331
350
  private _children;
332
351
  constructor(name?: string, tags?: Iterable<string>);
333
- /** The scene this entity belongs to, or null. */
334
- get scene(): Scene | null;
352
+ /**
353
+ * The scene this entity belongs to. Throws if the entity is not attached
354
+ * to a scene — which in practice only happens before `scene.spawn` /
355
+ * `addChild` wires it up, or after `destroy()` tears it down. Inside
356
+ * lifecycle methods (`setup`, component `onAdd`, `update`, etc.) this is
357
+ * always safe to access.
358
+ *
359
+ * For the rare case where you genuinely need to inspect whether an
360
+ * entity has a scene (e.g. defensive code in systems iterating a query
361
+ * result), use `tryScene` instead.
362
+ */
363
+ get scene(): Scene;
364
+ /** The scene this entity belongs to, or `null` if detached. */
365
+ get tryScene(): Scene | null;
335
366
  /** True if destroy() has been called. */
336
367
  get isDestroyed(): boolean;
337
368
  /** The parent entity, or null if this is a root entity. */
@@ -340,6 +371,28 @@ declare class Entity {
340
371
  get children(): ReadonlyMap<string, Entity>;
341
372
  /** Add a named child entity. Auto-adds to parent's scene if not already in one. */
342
373
  addChild(name: string, child: Entity): void;
374
+ /**
375
+ * Spawn a new entity in this entity's scene and add it as a named child.
376
+ * Combines `scene.spawn(...)` + `this.addChild(name, ...)` in one call —
377
+ * the idiomatic way to compose entity trees (logical root + visual body
378
+ * + UI sibling + ...).
379
+ *
380
+ * Mirrors the overload shape of `Scene.spawn`: pass an Entity subclass
381
+ * (with optional setup params), a `Blueprint`, or omit for an anonymous
382
+ * base Entity.
383
+ *
384
+ * ```ts
385
+ * this.spawnChild("body", EnemyBody, { color: 0xff6b6b });
386
+ * this.spawnChild("hp", EnemyHealthBar);
387
+ * ```
388
+ */
389
+ spawnChild(name: string): Entity;
390
+ spawnChild<E extends Entity>(name: string, Class: new () => E): E;
391
+ spawnChild<E extends Entity, P>(name: string, Class: new () => E & {
392
+ setup(params: P): void;
393
+ }, params: P): E;
394
+ spawnChild<P>(name: string, blueprint: Blueprint<P>, params: P): Entity;
395
+ spawnChild(name: string, blueprint: Blueprint<void>): Entity;
343
396
  /** Remove a named child. Returns the detached entity. */
344
397
  removeChild(name: string): Entity;
345
398
  /** Get a child by name. Throws if not found. */
@@ -390,16 +443,45 @@ declare class Entity {
390
443
  _setScene(scene: Scene | null, callbacks: EntityCallbacks | null): void;
391
444
  }
392
445
 
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
+ }
393
459
  /**
394
- * A reusable entity template. Blueprints define how to assemble
395
- * an entity from components, given optional parameters.
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`.
396
465
  */
397
- interface Blueprint<P = void> {
398
- readonly name: string;
399
- build(entity: Entity, params: P): void;
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;
400
475
  }
401
- /** Create a blueprint from a name and a build function. */
402
- declare function defineBlueprint<P = void>(name: string, build: (entity: Entity, params: P) => void): Blueprint<P>;
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;
403
485
 
404
486
  /** Filter criteria for entity queries. All fields are AND'd together. */
405
487
  interface EntityFilter {
@@ -428,6 +510,8 @@ declare abstract class Scene {
428
510
  readonly transparentBelow: boolean;
429
511
  /** Asset handles to load before onEnter(). Override in subclasses. */
430
512
  readonly preload?: readonly AssetHandle<unknown>[];
513
+ /** Default transition used when this scene is the destination of a push/pop/replace. */
514
+ readonly defaultTransition?: SceneTransition;
431
515
  /** Manual pause flag. Set by game code to pause this scene regardless of stack position. */
432
516
  paused: boolean;
433
517
  /** Time scale multiplier for this scene. 1.0 = normal, 0.5 = half speed. Default: 1. */
@@ -439,10 +523,13 @@ declare abstract class Scene {
439
523
  private queryCache;
440
524
  private bus;
441
525
  private _entityEventHandlers?;
526
+ private _scopedServices?;
442
527
  /** Access the EngineContext. */
443
528
  get context(): EngineContext;
444
529
  /** Whether this scene is effectively paused (manual pause or paused by stack). */
445
530
  get isPaused(): boolean;
531
+ /** Whether a scene transition is currently running. */
532
+ get isTransitioning(): boolean;
446
533
  /** Convenience accessor for the AssetManager. */
447
534
  get assets(): AssetManager;
448
535
  /**
@@ -507,6 +594,24 @@ declare abstract class Scene {
507
594
  serialize?(): unknown;
508
595
  /** Called after entities are restored during save/load. Rebuild non-serializable state here. */
509
596
  afterRestore?(data: unknown, resolve: SnapshotResolver): void;
597
+ /**
598
+ * Register a scene-scoped service. Called from a plugin's `beforeEnter`
599
+ * hook to make per-scene state (render tree, physics world) resolvable via
600
+ * `Component.use(key)`.
601
+ * @internal
602
+ */
603
+ _registerScoped<T>(key: ServiceKey<T>, value: T): void;
604
+ /**
605
+ * Resolve a scene-scoped service, or `undefined` if none was registered.
606
+ * @internal
607
+ */
608
+ _resolveScoped<T>(key: ServiceKey<T>): T | undefined;
609
+ /**
610
+ * Clear all scene-scoped services. Called by the SceneManager after
611
+ * `afterExit` hooks run, so plugin cleanup code still sees scoped state.
612
+ * @internal
613
+ */
614
+ _clearScopedServices(): void;
510
615
  /**
511
616
  * Set the engine context. Called by SceneManager when the scene is pushed.
512
617
  * @internal
@@ -544,7 +649,8 @@ declare abstract class Component {
544
649
  private _cleanups?;
545
650
  /**
546
651
  * Access the entity's scene. Throws if the entity is not in a scene.
547
- * Prefer this over `this.entity.scene!` in component methods.
652
+ * Prefer this over threading through `this.entity.scene` in component
653
+ * code.
548
654
  */
549
655
  get scene(): Scene;
550
656
  /**
@@ -552,12 +658,19 @@ declare abstract class Component {
552
658
  * Throws if the entity is not in a scene.
553
659
  */
554
660
  get context(): EngineContext;
555
- /** Resolve a service by key, cached after first lookup. */
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
+ */
556
668
  protected use<T>(key: ServiceKey<T>): T;
669
+ private _warnScopedFallback;
557
670
  /**
558
671
  * Lazy proxy-based service resolution. Can be used at field-declaration time:
559
672
  * ```ts
560
- * readonly camera = this.service(CameraKey);
673
+ * readonly input = this.service(InputManagerKey);
561
674
  * ```
562
675
  * The actual resolution is deferred until first property access.
563
676
  */
@@ -727,38 +840,114 @@ declare class SceneManager {
727
840
  private _context;
728
841
  private bus;
729
842
  private assetManager;
843
+ private hookRegistry;
844
+ private logger;
845
+ private _currentRun;
846
+ private _pendingChain;
847
+ private _mutationDepth;
848
+ private _destroyed;
849
+ private _autoPauseOnBlur;
850
+ private _isBlurred;
851
+ private readonly _visibilityPausedScenes;
852
+ private _visibilityListenerCleanup;
853
+ /**
854
+ * Pause all non-paused scenes when `document.hidden` becomes `true`; restore
855
+ * them on focus. Default: `false`. Only scenes paused by this mechanism are
856
+ * restored — user-paused scenes (manual `scene.paused = true` or `pauseBelow`
857
+ * cascade) are never touched.
858
+ */
859
+ get autoPauseOnBlur(): boolean;
860
+ set autoPauseOnBlur(value: boolean);
730
861
  /**
731
862
  * Set the engine context.
732
863
  * @internal
733
864
  */
734
865
  _setContext(context: EngineContext): void;
866
+ /**
867
+ * React to a visibility change. Parameterised on `hidden` so unit tests can
868
+ * drive it without a real `document`.
869
+ * @internal
870
+ */
871
+ _handleVisibilityChange(hidden: boolean): void;
872
+ private _applyBlurPause;
873
+ private _restoreBlurPause;
735
874
  /** The topmost (active) scene. */
736
875
  get active(): Scene | undefined;
737
876
  /** All scenes in the stack, bottom to top. */
738
877
  get all(): readonly Scene[];
739
878
  /** All non-paused scenes in the stack, bottom to top. */
740
879
  get activeScenes(): readonly Scene[];
880
+ /** Whether a scene transition is currently running. */
881
+ get isTransitioning(): boolean;
741
882
  /**
742
883
  * Push a scene onto the stack. Scenes below may receive onPause().
743
884
  * If the scene declares a `preload` array, assets are loaded before onEnter().
744
- * Await the returned promise when using preloaded scenes.
745
885
  */
746
- push(scene: Scene): Promise<void>;
886
+ push(scene: Scene, opts?: SceneTransitionOptions): Promise<void>;
747
887
  /** Pop the top scene. Scenes below may receive onResume(). */
748
- pop(): Scene | undefined;
888
+ pop(opts?: SceneTransitionOptions): Promise<Scene | undefined>;
749
889
  /**
750
- * Replace the top scene. Old scene receives onExit().
751
- * New scene receives onEnter() (after preload, if declared).
890
+ * Replace the top scene. Without a transition the old scene exits first,
891
+ * then the new scene enters. With a transition the new scene is pushed
892
+ * first, both scenes coexist for the transition duration, then the old
893
+ * scene is removed at the end.
752
894
  */
753
- replace(scene: Scene): Promise<void>;
754
- /** Clear all scenes. Each receives onExit() from top to bottom. */
755
- clear(): void;
895
+ replace(scene: Scene, opts?: SceneTransitionOptions): Promise<void>;
896
+ /**
897
+ * Pop every scene on the stack, top to bottom. Each receives onExit().
898
+ * Queued like push/pop/replace — runs after any in-flight transition.
899
+ * Use for "restart from menu"-style flows. Does not run transitions.
900
+ */
901
+ popAll(): Promise<void>;
902
+ /**
903
+ * Run the full scene-enter lifecycle (beforeEnter hooks, preload, onEnter)
904
+ * for a scene that is NOT placed on the stack. Used by infrastructure
905
+ * plugins like DebugPlugin that render a scene off-stack.
906
+ * @internal
907
+ */
908
+ _mountDetached(scene: Scene): Promise<void>;
909
+ /**
910
+ * Run the scene-exit lifecycle (onExit, entity destruction, afterExit
911
+ * hooks, scoped-service clear) for a detached scene.
912
+ * @internal
913
+ */
914
+ _unmountDetached(scene: Scene): void;
915
+ /**
916
+ * Mark the manager destroyed and synchronously tear down every scene.
917
+ * Called by Engine.destroy(). Any queued async work short-circuits on
918
+ * resume; in-flight transitions' pending promises are resolved via
919
+ * _cleanupRun so they don't leak.
920
+ * @internal
921
+ */
922
+ _destroy(): void;
756
923
  /**
757
924
  * Flush destroy queues for all active scenes.
758
925
  * Called by the engine during endOfFrame.
759
926
  * @internal
760
927
  */
761
928
  _flushDestroyQueues(): void;
929
+ /**
930
+ * Advance the active transition by `dt` ms. Called by Engine's earlyUpdate
931
+ * callback with raw (unscaled) wall-clock dt.
932
+ * @internal
933
+ */
934
+ _tickTransition(dt: number): void;
935
+ private _enqueue;
936
+ private _pushScene;
937
+ private _popScene;
938
+ private _replaceScene;
939
+ private _removeScene;
940
+ private _preloadScene;
941
+ private _teardownScene;
942
+ private _runTransition;
943
+ private _cleanupRun;
944
+ private _safeTick;
945
+ private _safeCall;
946
+ private _makeContext;
947
+ private _snapshotPauseStates;
948
+ private _assertNotMutating;
949
+ private _withMutation;
950
+ private _withMutationSync;
762
951
  /** Fire onPause() for scenes that transitioned from not-paused to paused. */
763
952
  private _firePauseTransitions;
764
953
  /** Fire onResume() for scenes that transitioned from paused to not-paused. */
@@ -883,6 +1072,23 @@ interface EngineEvents {
883
1072
  oldScene: SceneRef;
884
1073
  newScene: SceneRef;
885
1074
  };
1075
+ "scene:transition:started": {
1076
+ kind: SceneTransitionKind;
1077
+ fromScene: SceneRef | undefined;
1078
+ toScene: SceneRef | undefined;
1079
+ };
1080
+ "scene:transition:ended": {
1081
+ kind: SceneTransitionKind;
1082
+ fromScene: SceneRef | undefined;
1083
+ toScene: SceneRef | undefined;
1084
+ };
1085
+ "scene:loading:progress": {
1086
+ scene: Scene;
1087
+ ratio: number;
1088
+ };
1089
+ "scene:loading:done": {
1090
+ scene: Scene;
1091
+ };
886
1092
  "engine:started": undefined;
887
1093
  "engine:stopped": undefined;
888
1094
  }
@@ -899,6 +1105,39 @@ declare class EventBus<E = EventMap> {
899
1105
  clear(event?: keyof E): void;
900
1106
  }
901
1107
 
1108
+ /**
1109
+ * Plugin hooks invoked by the SceneManager at scene lifecycle points.
1110
+ * Plugins register hooks via `engine.registerSceneHooks(hooks)` to set up or
1111
+ * tear down per-scene state (e.g. render containers, physics worlds).
1112
+ */
1113
+ interface SceneHooks {
1114
+ /**
1115
+ * Runs after the scene's context is bound but before preload / `onEnter`.
1116
+ * Awaited serially so scoped services registered here are ready when the
1117
+ * scene's own code runs. Fires on `push`, `replace`, and `_mountDetached`.
1118
+ */
1119
+ beforeEnter?(scene: Scene): void | Promise<void>;
1120
+ /**
1121
+ * Runs after `onExit` + `_destroyAllEntities` and before the scene's
1122
+ * scoped-service map is cleared. Fires on `pop`, `replace`, `clear`, and
1123
+ * `_unmountDetached`.
1124
+ */
1125
+ afterExit?(scene: Scene): void;
1126
+ }
1127
+ /**
1128
+ * Registry of scene hooks. Held by the engine, consumed by the SceneManager.
1129
+ * @internal
1130
+ */
1131
+ declare class SceneHookRegistry {
1132
+ private readonly hooks;
1133
+ register(hooks: SceneHooks): () => void;
1134
+ /** Run all `beforeEnter` hooks serially. */
1135
+ runBeforeEnter(scene: Scene): Promise<void>;
1136
+ runAfterExit(scene: Scene): void;
1137
+ }
1138
+ /** DI key for the scene-hook registry. @internal */
1139
+ declare const SceneHookRegistryKey: ServiceKey<SceneHookRegistry>;
1140
+
902
1141
  /** Engine configuration. */
903
1142
  interface EngineConfig {
904
1143
  /** Enable debug mode (Inspector API, debug logging). */
@@ -930,6 +1169,7 @@ declare class Engine {
930
1169
  private readonly scheduler;
931
1170
  private readonly errorBoundary;
932
1171
  private readonly queryCache;
1172
+ private readonly sceneHooks;
933
1173
  /** The asset manager. */
934
1174
  readonly assets: AssetManager;
935
1175
  private readonly plugins;
@@ -937,6 +1177,12 @@ declare class Engine {
937
1177
  private started;
938
1178
  private readonly debug;
939
1179
  constructor(config?: EngineConfig);
1180
+ /**
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.
1184
+ */
1185
+ registerSceneHooks(hooks: SceneHooks): () => void;
940
1186
  /** Register a plugin. Must be called before start(). */
941
1187
  use(plugin: Plugin): this;
942
1188
  /** Start the engine. Installs plugins in topological order, starts the game loop. */
@@ -951,13 +1197,27 @@ declare class Engine {
951
1197
  private topologicalSort;
952
1198
  }
953
1199
 
1200
+ /** The resolution scope for a service. */
1201
+ type ServiceScope = "engine" | "scene";
1202
+ /** Options passed to `new ServiceKey(id, options)`. */
1203
+ interface ServiceKeyOptions {
1204
+ /**
1205
+ * Declared scope. `"scene"` keys are expected to be registered per-scene
1206
+ * via a `beforeEnter` hook; `Component.use` will check scene scope first
1207
+ * and warn if it falls back to engine scope.
1208
+ * Default: `"engine"`.
1209
+ */
1210
+ scope?: ServiceScope;
1211
+ }
954
1212
  /** A typed key for service registration and resolution. */
955
1213
  declare class ServiceKey<T> {
956
1214
  /** Unique string identifier for this service. */
957
1215
  readonly id: string;
1216
+ /** Declared scope (engine or scene). Defaults to `"engine"`. */
1217
+ readonly scope: ServiceScope;
958
1218
  constructor(
959
1219
  /** Unique string identifier for this service. */
960
- id: string);
1220
+ id: string, options?: ServiceKeyOptions);
961
1221
  /** Phantom field to preserve the generic type. */
962
1222
  readonly _type: T;
963
1223
  }
@@ -1072,7 +1332,7 @@ interface Plugin {
1072
1332
  /** Register systems with the scheduler. Called after install. */
1073
1333
  registerSystems?(scheduler: SystemScheduler): void;
1074
1334
  /** Called after all plugins are installed and the engine has started. */
1075
- onStart?(): void;
1335
+ onStart?(): void | Promise<void>;
1076
1336
  /** Called when the engine is destroyed. */
1077
1337
  onDestroy?(): void;
1078
1338
  }
@@ -1145,16 +1405,32 @@ declare class Vec2 implements Vec2Like {
1145
1405
  static distance(a: Vec2Like, b: Vec2Like): number;
1146
1406
  /** Linear interpolation between two vectors. */
1147
1407
  static lerp(a: Vec2Like, b: Vec2Like, t: number): Vec2;
1408
+ /** Move current toward target by at most maxDelta without overshooting. */
1409
+ static moveTowards(current: Vec2Like, target: Vec2Like, maxDelta: number): Vec2;
1148
1410
  }
1149
1411
 
1412
+ interface SmoothDampResult {
1413
+ /** Smoothed value after this step. */
1414
+ readonly value: number;
1415
+ /** Velocity to pass into the next smoothDamp step. */
1416
+ readonly velocity: number;
1417
+ }
1150
1418
  /** Common math utility functions. */
1151
1419
  declare const MathUtils: {
1152
1420
  /** Linear interpolation between a and b. */
1153
1421
  readonly lerp: (a: number, b: number, t: number) => number;
1422
+ /** Return the clamped interpolation factor that produces v between a and b. */
1423
+ readonly inverseLerp: (a: number, b: number, v: number) => number;
1424
+ /** Interpolate between angles in radians along the shortest path. */
1425
+ readonly lerpAngle: (a: number, b: number, t: number) => number;
1426
+ /** Signed shortest angular delta from a to b, in radians. */
1427
+ readonly shortestAngleBetween: (a: number, b: number) => number;
1154
1428
  /** Clamp a value between min and max. */
1155
1429
  readonly clamp: (value: number, min: number, max: number) => number;
1156
1430
  /** Remap a value from one range to another. */
1157
1431
  readonly remap: (value: number, inMin: number, inMax: number, outMin: number, outMax: number) => number;
1432
+ /** Bounce t between 0 and length. */
1433
+ readonly pingPong: (t: number, length: number) => number;
1158
1434
  /** Random float in [min, max). */
1159
1435
  readonly randomRange: (min: number, max: number) => number;
1160
1436
  /** Random integer in [min, max] (inclusive). */
@@ -1165,6 +1441,11 @@ declare const MathUtils: {
1165
1441
  readonly radToDeg: (radians: number) => number;
1166
1442
  /** Move current toward target by at most step. */
1167
1443
  readonly approach: (current: number, target: number, step: number) => number;
1444
+ /**
1445
+ * Smoothly damp current toward target without overshooting.
1446
+ * Pass the returned velocity back into the next call.
1447
+ */
1448
+ readonly smoothDamp: (current: number, target: number, velocity: number, smoothTime: number, deltaTime: number, maxSpeed?: number) => SmoothDampResult;
1168
1449
  /** Wrap value into the range [min, max). */
1169
1450
  readonly wrap: (value: number, min: number, max: number) => number;
1170
1451
  };
@@ -1265,6 +1546,112 @@ declare class ComponentUpdateSystem extends BaseComponentUpdateSystem {
1265
1546
  update(dt: number): void;
1266
1547
  }
1267
1548
 
1549
+ /**
1550
+ * Base class for a progress-bar style loading screen.
1551
+ *
1552
+ * Preloads the target scene's assets through the `AssetManager`, exposes
1553
+ * `progress` and emits `scene:loading:progress` / `scene:loading:done` on
1554
+ * the engine event bus, enforces `minDuration` to prevent flicker on cached
1555
+ * loads, then replaces itself with `target` — optionally through a
1556
+ * transition.
1557
+ *
1558
+ * LoadingScene owns orchestration only. It does not render anything. To show
1559
+ * a progress UI, spawn an entity that subscribes to the loading events (the
1560
+ * canonical default is `LoadingSceneProgressBar` in `@yagejs/ui`, or any
1561
+ * custom component). The loading scene is a normal Scene, so you can use
1562
+ * `onEnter` to spawn whatever you want.
1563
+ *
1564
+ * ```ts
1565
+ * class Boot extends LoadingScene {
1566
+ * readonly target = new GameScene();
1567
+ * readonly minDuration = 500;
1568
+ * readonly transition = fade({ duration: 300 });
1569
+ * override onEnter() {
1570
+ * this.spawn(LoadingSceneProgressBar);
1571
+ * this.startLoading();
1572
+ * }
1573
+ * }
1574
+ *
1575
+ * await engine.scenes.replace(new Boot());
1576
+ * ```
1577
+ *
1578
+ * Set `autoContinue = false` to gate the handoff behind a `continue()` call
1579
+ * — useful for "press any key to continue" flows. `scene:loading:done`
1580
+ * still fires so UI can react (show a prompt), and whoever eventually
1581
+ * calls `this.continue()` triggers the transition.
1582
+ */
1583
+ declare abstract class LoadingScene extends Scene {
1584
+ readonly name: string;
1585
+ /**
1586
+ * Scene to load and transition to. Accepts an instance or a factory —
1587
+ * use a factory when target construction should be deferred until
1588
+ * loading starts (heavy constructors, side effects). The factory runs
1589
+ * before `assets.loadAll` so `target.preload` can be inspected.
1590
+ */
1591
+ abstract readonly target: Scene | (() => Scene);
1592
+ /**
1593
+ * Minimum wall-clock ms the scene stays visible before handing off.
1594
+ * Prevents flicker on cached loads. Default 0.
1595
+ */
1596
+ readonly minDuration: number;
1597
+ /** Transition used for the loading → target handoff. */
1598
+ readonly transition?: SceneTransition;
1599
+ /**
1600
+ * When true (default), the handoff fires automatically after loading and
1601
+ * `minDuration`. Set false to gate it behind `continue()` — useful when
1602
+ * the loading scene also asks the player to press a key or click.
1603
+ */
1604
+ readonly autoContinue: boolean;
1605
+ /**
1606
+ * Optional hook; fires if asset loading rejects. The scene stays mounted
1607
+ * whether or not this is set. When set, the hook is the recovery channel:
1608
+ * draw a retry UI, push an error scene, or call `this.startLoading()`
1609
+ * again to retry the load. When unset, the error is logged via the engine
1610
+ * logger and the scene remains mounted in a failed state with no
1611
+ * automatic recovery.
1612
+ *
1613
+ * The hook may still be running when the scene is replaced externally —
1614
+ * don't assume the scene is live (check `this.context.tryResolve` rather
1615
+ * than `this.service` before touching engine services, and avoid spawning
1616
+ * new entities after an `await`).
1617
+ */
1618
+ onLoadError?(error: Error): void | Promise<void>;
1619
+ private _progress;
1620
+ private _started;
1621
+ private _active;
1622
+ private _continueRequested;
1623
+ private _continueGate?;
1624
+ private _attempt;
1625
+ /** Current load progress, 0 → 1. Updated as the AssetManager reports progress. */
1626
+ get progress(): number;
1627
+ /**
1628
+ * Kick off asset loading. While a load is in flight, subsequent calls
1629
+ * are no-ops. After a load failure the guard is released, so calling
1630
+ * `startLoading()` from `onLoadError` (or from a retry button) kicks off
1631
+ * a fresh load against the same target.
1632
+ *
1633
+ * Usually called once from `onEnter` after spawning the loading UI:
1634
+ * ```ts
1635
+ * override onEnter() {
1636
+ * this.spawn(LoadingSceneProgressBar);
1637
+ * this.startLoading();
1638
+ * }
1639
+ * ```
1640
+ *
1641
+ * Deferring the call lets you gate the start of the load behind a
1642
+ * title screen, "press any key" prompt, intro animation, etc.
1643
+ */
1644
+ startLoading(): void;
1645
+ /**
1646
+ * Trigger the handoff to `target`. No-op if already called or if
1647
+ * `autoContinue` already fired it. If called before loading finishes,
1648
+ * the handoff runs as soon as loading + `minDuration` complete.
1649
+ */
1650
+ continue(): void;
1651
+ onExit(): void;
1652
+ private _run;
1653
+ }
1654
+
1268
1655
  /** Static factory for creating tween Processes. */
1269
1656
  declare const Tween: {
1270
1657
  /** Tween a numeric property on a target object. */
@@ -1491,6 +1878,36 @@ declare class TimerEntity extends Entity {
1491
1878
  cancel(tag?: string): void;
1492
1879
  }
1493
1880
 
1881
+ /**
1882
+ * Cross-package contract for "something that owns a canvas and can map
1883
+ * canvas-relative CSS pixels into virtual-space pixels".
1884
+ *
1885
+ * Implemented by `@yagejs/renderer`'s `RendererPlugin` and consumed by
1886
+ * `@yagejs/input` for pointer-event targeting and coordinate mapping under
1887
+ * responsive fit. Foreign renderers can implement this interface and register
1888
+ * under `RendererAdapterKey` to integrate with the input plugin without
1889
+ * importing `@yagejs/renderer`.
1890
+ */
1891
+ interface RendererAdapter {
1892
+ readonly canvas: HTMLCanvasElement;
1893
+ /**
1894
+ * Convert CSS pixels relative to the canvas into virtual-space pixels.
1895
+ * Optional — when absent, consumers fall back to raw CSS pixels (correct
1896
+ * only when canvas CSS size equals virtual size).
1897
+ */
1898
+ canvasToVirtual?(x: number, y: number): {
1899
+ x: number;
1900
+ y: number;
1901
+ };
1902
+ }
1903
+ /**
1904
+ * Well-known service key for the current renderer's pointer-input adapter.
1905
+ * The canonical `@yagejs/renderer` plugin registers itself here; consumers
1906
+ * (notably `@yagejs/input`) resolve this key to auto-wire canvas targeting
1907
+ * and coordinate mapping.
1908
+ */
1909
+ declare const RendererAdapterKey: ServiceKey<RendererAdapter>;
1910
+
1494
1911
  /** Create a fully wired Engine for integration tests. */
1495
1912
  declare function createTestEngine(config?: EngineConfig): Promise<Engine>;
1496
1913
  /** Create a lightweight mock scene with EngineContext for unit tests. */
@@ -1509,4 +1926,4 @@ declare function advanceFrames(engine: Engine, n: number, dtMs?: number): void;
1509
1926
 
1510
1927
  declare const VERSION = "0.0.0";
1511
1928
 
1512
- 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, 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, SceneManager, SceneManagerKey, type SceneSnapshot, Sequence, SerializableRegistry, ServiceKey, 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, serializable, trait };
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 };