@yagejs/core 0.2.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.cts CHANGED
@@ -212,12 +212,20 @@ declare function defineEvent<T = void>(name: string): EventToken<T>;
212
212
  /**
213
213
  * A reusable entity template. Blueprints define how to assemble
214
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.
215
219
  */
216
220
  interface Blueprint<P = void> {
217
221
  readonly name: string;
218
222
  build(entity: Entity, params: P): void;
219
223
  }
220
- /** Create a blueprint from a name and a build function. */
224
+ /**
225
+ * Create a blueprint from a name and a build function.
226
+ *
227
+ * @deprecated Prefer Entity subclasses with `setup()` for entity types.
228
+ */
221
229
  declare function defineBlueprint<P = void>(name: string, build: (entity: Entity, params: P) => void): Blueprint<P>;
222
230
 
223
231
  /**
@@ -838,11 +846,31 @@ declare class SceneManager {
838
846
  private _pendingChain;
839
847
  private _mutationDepth;
840
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);
841
861
  /**
842
862
  * Set the engine context.
843
863
  * @internal
844
864
  */
845
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;
846
874
  /** The topmost (active) scene. */
847
875
  get active(): Scene | undefined;
848
876
  /** All scenes in the stack, bottom to top. */
@@ -1377,16 +1405,32 @@ declare class Vec2 implements Vec2Like {
1377
1405
  static distance(a: Vec2Like, b: Vec2Like): number;
1378
1406
  /** Linear interpolation between two vectors. */
1379
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;
1380
1410
  }
1381
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
+ }
1382
1418
  /** Common math utility functions. */
1383
1419
  declare const MathUtils: {
1384
1420
  /** Linear interpolation between a and b. */
1385
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;
1386
1428
  /** Clamp a value between min and max. */
1387
1429
  readonly clamp: (value: number, min: number, max: number) => number;
1388
1430
  /** Remap a value from one range to another. */
1389
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;
1390
1434
  /** Random float in [min, max). */
1391
1435
  readonly randomRange: (min: number, max: number) => number;
1392
1436
  /** Random integer in [min, max] (inclusive). */
@@ -1397,6 +1441,11 @@ declare const MathUtils: {
1397
1441
  readonly radToDeg: (radians: number) => number;
1398
1442
  /** Move current toward target by at most step. */
1399
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;
1400
1449
  /** Wrap value into the range [min, max). */
1401
1450
  readonly wrap: (value: number, min: number, max: number) => number;
1402
1451
  };
@@ -1829,6 +1878,36 @@ declare class TimerEntity extends Entity {
1829
1878
  cancel(tag?: string): void;
1830
1879
  }
1831
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
+
1832
1911
  /** Create a fully wired Engine for integration tests. */
1833
1912
  declare function createTestEngine(config?: EngineConfig): Promise<Engine>;
1834
1913
  /** Create a lightweight mock scene with EngineContext for unit tests. */
@@ -1847,4 +1926,4 @@ declare function advanceFrames(engine: Engine, n: number, dtMs?: number): void;
1847
1926
 
1848
1927
  declare const VERSION = "0.0.0";
1849
1928
 
1850
- export { AssetHandle, type AssetLoader, AssetManager, AssetManagerKey, type Blueprint, Component, type ComponentClass, ComponentFixedUpdateSystem, ComponentUpdateSystem, type EasingFunction, Engine, type EngineConfig, EngineContext, type EngineEvents, EngineKey, type EngineSnapshot, Entity, type EntityCallbacks, type EntityFilter, type EntitySnapshot, ErrorBoundary, ErrorBoundaryKey, type ErrorSnapshot, EventBus, EventBusKey, type EventMap, EventToken, GameLoop, type GameLoopCallbacks, type GameLoopConfig, GameLoopKey, Inspector, InspectorKey, type Interpolatable, type Keyframe, type KeyframeAnimationDef, KeyframeAnimator, type KeyframeTrackOptions, LoadingScene, type LogEntry, LogLevel, Logger, type LoggerConfig, LoggerKey, MathUtils, Phase, type Plugin, Process, ProcessComponent, type ProcessOptions, ProcessSlot, type ProcessSlotConfig, ProcessSystem, ProcessSystemKey, QueryCache, QueryCacheKey, QueryResult, SERIALIZABLE_KEY, Scene, SceneHookRegistry, SceneHookRegistryKey, type SceneHooks, SceneManager, SceneManagerKey, type SceneSnapshot, type SceneTransition, type SceneTransitionContext, type SceneTransitionKind, type SceneTransitionOptions, Sequence, SerializableRegistry, ServiceKey, type ServiceKeyOptions, type ServiceScope, type SnapshotResolver, System, SystemScheduler, SystemSchedulerKey, type SystemSnapshot, TimerEntity, TraitToken, Transform, type TransformData, Tween, VERSION, Vec2, type Vec2Like, _resetEntityIdCounter, advanceFrames, createKeyframeTrack, createMockEntity, createMockScene, createTestEngine, defineBlueprint, defineEvent, defineTrait, easeInOutQuad, easeInQuad, easeLinear, easeOutBounce, easeOutQuad, filterEntities, getSerializableType, interpolate, isSerializable, resolveTransition, serializable, trait };
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 };
package/dist/index.d.ts CHANGED
@@ -212,12 +212,20 @@ declare function defineEvent<T = void>(name: string): EventToken<T>;
212
212
  /**
213
213
  * A reusable entity template. Blueprints define how to assemble
214
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.
215
219
  */
216
220
  interface Blueprint<P = void> {
217
221
  readonly name: string;
218
222
  build(entity: Entity, params: P): void;
219
223
  }
220
- /** Create a blueprint from a name and a build function. */
224
+ /**
225
+ * Create a blueprint from a name and a build function.
226
+ *
227
+ * @deprecated Prefer Entity subclasses with `setup()` for entity types.
228
+ */
221
229
  declare function defineBlueprint<P = void>(name: string, build: (entity: Entity, params: P) => void): Blueprint<P>;
222
230
 
223
231
  /**
@@ -838,11 +846,31 @@ declare class SceneManager {
838
846
  private _pendingChain;
839
847
  private _mutationDepth;
840
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);
841
861
  /**
842
862
  * Set the engine context.
843
863
  * @internal
844
864
  */
845
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;
846
874
  /** The topmost (active) scene. */
847
875
  get active(): Scene | undefined;
848
876
  /** All scenes in the stack, bottom to top. */
@@ -1377,16 +1405,32 @@ declare class Vec2 implements Vec2Like {
1377
1405
  static distance(a: Vec2Like, b: Vec2Like): number;
1378
1406
  /** Linear interpolation between two vectors. */
1379
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;
1380
1410
  }
1381
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
+ }
1382
1418
  /** Common math utility functions. */
1383
1419
  declare const MathUtils: {
1384
1420
  /** Linear interpolation between a and b. */
1385
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;
1386
1428
  /** Clamp a value between min and max. */
1387
1429
  readonly clamp: (value: number, min: number, max: number) => number;
1388
1430
  /** Remap a value from one range to another. */
1389
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;
1390
1434
  /** Random float in [min, max). */
1391
1435
  readonly randomRange: (min: number, max: number) => number;
1392
1436
  /** Random integer in [min, max] (inclusive). */
@@ -1397,6 +1441,11 @@ declare const MathUtils: {
1397
1441
  readonly radToDeg: (radians: number) => number;
1398
1442
  /** Move current toward target by at most step. */
1399
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;
1400
1449
  /** Wrap value into the range [min, max). */
1401
1450
  readonly wrap: (value: number, min: number, max: number) => number;
1402
1451
  };
@@ -1829,6 +1878,36 @@ declare class TimerEntity extends Entity {
1829
1878
  cancel(tag?: string): void;
1830
1879
  }
1831
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
+
1832
1911
  /** Create a fully wired Engine for integration tests. */
1833
1912
  declare function createTestEngine(config?: EngineConfig): Promise<Engine>;
1834
1913
  /** Create a lightweight mock scene with EngineContext for unit tests. */
@@ -1847,4 +1926,4 @@ declare function advanceFrames(engine: Engine, n: number, dtMs?: number): void;
1847
1926
 
1848
1927
  declare const VERSION = "0.0.0";
1849
1928
 
1850
- export { AssetHandle, type AssetLoader, AssetManager, AssetManagerKey, type Blueprint, Component, type ComponentClass, ComponentFixedUpdateSystem, ComponentUpdateSystem, type EasingFunction, Engine, type EngineConfig, EngineContext, type EngineEvents, EngineKey, type EngineSnapshot, Entity, type EntityCallbacks, type EntityFilter, type EntitySnapshot, ErrorBoundary, ErrorBoundaryKey, type ErrorSnapshot, EventBus, EventBusKey, type EventMap, EventToken, GameLoop, type GameLoopCallbacks, type GameLoopConfig, GameLoopKey, Inspector, InspectorKey, type Interpolatable, type Keyframe, type KeyframeAnimationDef, KeyframeAnimator, type KeyframeTrackOptions, LoadingScene, type LogEntry, LogLevel, Logger, type LoggerConfig, LoggerKey, MathUtils, Phase, type Plugin, Process, ProcessComponent, type ProcessOptions, ProcessSlot, type ProcessSlotConfig, ProcessSystem, ProcessSystemKey, QueryCache, QueryCacheKey, QueryResult, SERIALIZABLE_KEY, Scene, SceneHookRegistry, SceneHookRegistryKey, type SceneHooks, SceneManager, SceneManagerKey, type SceneSnapshot, type SceneTransition, type SceneTransitionContext, type SceneTransitionKind, type SceneTransitionOptions, Sequence, SerializableRegistry, ServiceKey, type ServiceKeyOptions, type ServiceScope, type SnapshotResolver, System, SystemScheduler, SystemSchedulerKey, type SystemSnapshot, TimerEntity, TraitToken, Transform, type TransformData, Tween, VERSION, Vec2, type Vec2Like, _resetEntityIdCounter, advanceFrames, createKeyframeTrack, createMockEntity, createMockScene, createTestEngine, defineBlueprint, defineEvent, defineTrait, easeInOutQuad, easeInQuad, easeLinear, easeOutBounce, easeOutQuad, filterEntities, getSerializableType, interpolate, isSerializable, resolveTransition, serializable, trait };
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 };
package/dist/index.js CHANGED
@@ -170,14 +170,52 @@ var Vec2 = class _Vec2 {
170
170
  static lerp(a, b, t) {
171
171
  return new _Vec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
172
172
  }
173
+ /** Move current toward target by at most maxDelta without overshooting. */
174
+ static moveTowards(current, target, maxDelta) {
175
+ const dx = target.x - current.x;
176
+ const dy = target.y - current.y;
177
+ const distanceSq = dx * dx + dy * dy;
178
+ if (distanceSq < EPSILON * EPSILON) {
179
+ return new _Vec2(target.x, target.y);
180
+ }
181
+ if (maxDelta <= 0) {
182
+ return new _Vec2(current.x, current.y);
183
+ }
184
+ const distance = Math.sqrt(distanceSq);
185
+ if (distance <= maxDelta) {
186
+ return new _Vec2(target.x, target.y);
187
+ }
188
+ const scale = maxDelta / distance;
189
+ return new _Vec2(current.x + dx * scale, current.y + dy * scale);
190
+ }
173
191
  };
174
192
 
175
193
  // src/MathUtils.ts
194
+ var TAU = Math.PI * 2;
195
+ var MIN_SMOOTH_TIME = 1e-4;
196
+ function normalizeAngle(radians) {
197
+ const wrapped = ((radians + Math.PI) % TAU + TAU) % TAU - Math.PI;
198
+ return wrapped === -Math.PI && radians > 0 ? Math.PI : wrapped;
199
+ }
200
+ __name(normalizeAngle, "normalizeAngle");
176
201
  var MathUtils = {
177
202
  /** Linear interpolation between a and b. */
178
203
  lerp(a, b, t) {
179
204
  return a + (b - a) * t;
180
205
  },
206
+ /** Return the clamped interpolation factor that produces v between a and b. */
207
+ inverseLerp(a, b, v) {
208
+ if (a === b) return 0;
209
+ return MathUtils.clamp((v - a) / (b - a), 0, 1);
210
+ },
211
+ /** Interpolate between angles in radians along the shortest path. */
212
+ lerpAngle(a, b, t) {
213
+ return normalizeAngle(a + MathUtils.shortestAngleBetween(a, b) * t);
214
+ },
215
+ /** Signed shortest angular delta from a to b, in radians. */
216
+ shortestAngleBetween(a, b) {
217
+ return normalizeAngle(b - a);
218
+ },
181
219
  /** Clamp a value between min and max. */
182
220
  clamp(value, min, max) {
183
221
  return Math.max(min, Math.min(max, value));
@@ -187,6 +225,12 @@ var MathUtils = {
187
225
  const t = (value - inMin) / (inMax - inMin);
188
226
  return outMin + (outMax - outMin) * t;
189
227
  },
228
+ /** Bounce t between 0 and length. */
229
+ pingPong(t, length) {
230
+ if (length <= 0) return 0;
231
+ const wrapped = MathUtils.wrap(t, 0, length * 2);
232
+ return length - Math.abs(wrapped - length);
233
+ },
190
234
  /** Random float in [min, max). */
191
235
  randomRange(min, max) {
192
236
  return min + Math.random() * (max - min);
@@ -210,6 +254,34 @@ var MathUtils = {
210
254
  }
211
255
  return Math.max(current - step, target);
212
256
  },
257
+ /**
258
+ * Smoothly damp current toward target without overshooting.
259
+ * Pass the returned velocity back into the next call.
260
+ */
261
+ smoothDamp(current, target, velocity, smoothTime, deltaTime, maxSpeed = Infinity) {
262
+ if (deltaTime <= 0) {
263
+ return { value: current, velocity };
264
+ }
265
+ const safeSmoothTime = Math.max(MIN_SMOOTH_TIME, smoothTime);
266
+ const omega = 2 / safeSmoothTime;
267
+ const x = omega * deltaTime;
268
+ const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
269
+ const originalTarget = target;
270
+ const maxChange = maxSpeed * safeSmoothTime;
271
+ const change = MathUtils.clamp(current - target, -maxChange, maxChange);
272
+ const adjustedTarget = current - change;
273
+ const temp = (velocity + omega * change) * deltaTime;
274
+ const nextVelocity = (velocity - omega * temp) * exp;
275
+ let value = adjustedTarget + (change + temp) * exp;
276
+ let resultVelocity = nextVelocity;
277
+ const targetIsAboveCurrent = originalTarget - current > 0;
278
+ const valuePassedTarget = targetIsAboveCurrent ? value > originalTarget : value < originalTarget;
279
+ if (valuePassedTarget) {
280
+ value = originalTarget;
281
+ resultVelocity = 0;
282
+ }
283
+ return { value, velocity: resultVelocity };
284
+ },
213
285
  /** Wrap value into the range [min, max). */
214
286
  wrap(value, min, max) {
215
287
  const range = max - min;
@@ -1974,6 +2046,29 @@ var SceneManager = class {
1974
2046
  _pendingChain = Promise.resolve();
1975
2047
  _mutationDepth = 0;
1976
2048
  _destroyed = false;
2049
+ _autoPauseOnBlur = false;
2050
+ _isBlurred = false;
2051
+ _visibilityPausedScenes = /* @__PURE__ */ new Set();
2052
+ _visibilityListenerCleanup;
2053
+ /**
2054
+ * Pause all non-paused scenes when `document.hidden` becomes `true`; restore
2055
+ * them on focus. Default: `false`. Only scenes paused by this mechanism are
2056
+ * restored — user-paused scenes (manual `scene.paused = true` or `pauseBelow`
2057
+ * cascade) are never touched.
2058
+ */
2059
+ get autoPauseOnBlur() {
2060
+ return this._autoPauseOnBlur;
2061
+ }
2062
+ set autoPauseOnBlur(value) {
2063
+ if (this._autoPauseOnBlur === value) return;
2064
+ this._autoPauseOnBlur = value;
2065
+ if (!this._isBlurred) return;
2066
+ if (value) {
2067
+ this._applyBlurPause();
2068
+ } else if (this._visibilityPausedScenes.size > 0) {
2069
+ this._restoreBlurPause();
2070
+ }
2071
+ }
1977
2072
  /**
1978
2073
  * Set the engine context.
1979
2074
  * @internal
@@ -1984,6 +2079,40 @@ var SceneManager = class {
1984
2079
  this.assetManager = context.tryResolve(AssetManagerKey);
1985
2080
  this.hookRegistry = context.tryResolve(SceneHookRegistryKey);
1986
2081
  this.logger = context.tryResolve(LoggerKey);
2082
+ if (this._visibilityListenerCleanup || typeof document === "undefined") {
2083
+ return;
2084
+ }
2085
+ const onVisibilityChange = /* @__PURE__ */ __name(() => {
2086
+ this._handleVisibilityChange(document.hidden);
2087
+ }, "onVisibilityChange");
2088
+ document.addEventListener("visibilitychange", onVisibilityChange);
2089
+ this._visibilityListenerCleanup = () => document.removeEventListener("visibilitychange", onVisibilityChange);
2090
+ }
2091
+ /**
2092
+ * React to a visibility change. Parameterised on `hidden` so unit tests can
2093
+ * drive it without a real `document`.
2094
+ * @internal
2095
+ */
2096
+ _handleVisibilityChange(hidden) {
2097
+ if (hidden && !this._isBlurred) {
2098
+ this._isBlurred = true;
2099
+ if (this._autoPauseOnBlur) this._applyBlurPause();
2100
+ } else if (!hidden && this._isBlurred) {
2101
+ this._isBlurred = false;
2102
+ if (this._visibilityPausedScenes.size > 0) this._restoreBlurPause();
2103
+ }
2104
+ }
2105
+ _applyBlurPause() {
2106
+ for (const scene of this.activeScenes) {
2107
+ scene.paused = true;
2108
+ this._visibilityPausedScenes.add(scene);
2109
+ }
2110
+ }
2111
+ _restoreBlurPause() {
2112
+ for (const scene of this._visibilityPausedScenes) {
2113
+ scene.paused = false;
2114
+ }
2115
+ this._visibilityPausedScenes.clear();
1987
2116
  }
1988
2117
  /** The topmost (active) scene. */
1989
2118
  get active() {
@@ -2110,6 +2239,9 @@ var SceneManager = class {
2110
2239
  this._cleanupRun(this._currentRun);
2111
2240
  }
2112
2241
  this._pendingChain = Promise.resolve();
2242
+ this._visibilityListenerCleanup?.();
2243
+ this._visibilityListenerCleanup = void 0;
2244
+ this._visibilityPausedScenes.clear();
2113
2245
  this._withMutationSync(() => {
2114
2246
  while (this.stack.length > 0) {
2115
2247
  const scene = this.stack.pop();
@@ -2229,6 +2361,7 @@ var SceneManager = class {
2229
2361
  scene._destroyAllEntities();
2230
2362
  this.hookRegistry?.runAfterExit(scene);
2231
2363
  scene._clearScopedServices();
2364
+ this._visibilityPausedScenes.delete(scene);
2232
2365
  }
2233
2366
  async _runTransition(kind, transition, fromScene, toScene) {
2234
2367
  if (this._destroyed) return;
@@ -3350,6 +3483,11 @@ var Engine = class {
3350
3483
  }
3351
3484
  };
3352
3485
 
3486
+ // src/RendererAdapter.ts
3487
+ var RendererAdapterKey = new ServiceKey(
3488
+ "rendererAdapter"
3489
+ );
3490
+
3353
3491
  // src/test-utils.ts
3354
3492
  var _TestScene = class extends Scene {
3355
3493
  static {
@@ -3433,6 +3571,7 @@ export {
3433
3571
  QueryCache,
3434
3572
  QueryCacheKey,
3435
3573
  QueryResult,
3574
+ RendererAdapterKey,
3436
3575
  SERIALIZABLE_KEY,
3437
3576
  Scene,
3438
3577
  SceneHookRegistry,