@skewedaspect/sage 0.10.0-beta.1 → 0.10.0-beta.2

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.
Files changed (36) hide show
  1. package/dist/behaviors/sound.d.ts +22 -0
  2. package/dist/classes/gameEngine.d.ts +7 -0
  3. package/dist/classes/gameLevel.d.ts +6 -0
  4. package/dist/classes/level.d.ts +19 -0
  5. package/dist/debug/builtins.d.ts +0 -4
  6. package/dist/entities/entityPicking.d.ts +1 -0
  7. package/dist/entities/nodeEntity.d.ts +10 -0
  8. package/dist/entities/nodeSimulation.d.ts +9 -1
  9. package/dist/entities/nodeSimulationContext.d.ts +4 -0
  10. package/dist/events/payloads.d.ts +0 -12
  11. package/dist/input/actionContext.d.ts +2 -0
  12. package/dist/input/binding.d.ts +3 -1
  13. package/dist/input/bindings/delta.d.ts +23 -0
  14. package/dist/input/bindings/toggle.d.ts +5 -1
  15. package/dist/input/bindings/trigger.d.ts +5 -1
  16. package/dist/input/bindings/value.d.ts +7 -4
  17. package/dist/input/deliverAction.d.ts +3 -2
  18. package/dist/input/deviceReaders/mouse.d.ts +1 -1
  19. package/dist/input/deviceState.d.ts +5 -0
  20. package/dist/interfaces/game.d.ts +1 -1
  21. package/dist/interfaces/level.d.ts +2 -2
  22. package/dist/managers/asset.d.ts +17 -3
  23. package/dist/managers/game.d.ts +8 -1
  24. package/dist/managers/input.d.ts +34 -0
  25. package/dist/managers/level.d.ts +14 -0
  26. package/dist/managers/simulationSave.d.ts +10 -2
  27. package/dist/{postProcessingFrameGraph-rr2DbgPi.js → postProcessingFrameGraph-C3Og4kaS.js} +34 -26
  28. package/dist/postProcessingFrameGraph-C3Og4kaS.js.map +1 -0
  29. package/dist/sage.d.ts +13 -5
  30. package/dist/sage.es.js +919 -429
  31. package/dist/sage.es.js.map +1 -1
  32. package/dist/sage.umd.js +1 -1
  33. package/dist/sage.umd.js.map +1 -1
  34. package/dist/utils/bounds.d.ts +11 -0
  35. package/package.json +1 -1
  36. package/dist/postProcessingFrameGraph-rr2DbgPi.js.map +0 -1
@@ -0,0 +1,22 @@
1
+ import { Behavior } from '@skewedaspect/sage-core';
2
+ import { NodeBehaviorHooks } from "../entities/nodeEntity.d.ts";
3
+ export interface SoundConfig {
4
+ url: string;
5
+ volume?: number;
6
+ loop?: boolean;
7
+ spatial?: boolean;
8
+ maxDistance?: number;
9
+ channel?: string;
10
+ }
11
+ export interface SoundState {
12
+ sounds: Record<string, SoundConfig>;
13
+ }
14
+ export declare class SoundBehavior extends Behavior<SoundState> implements NodeBehaviorHooks {
15
+ #private;
16
+ static readonly ops: readonly ["play", "stop"];
17
+ readonly defaults: SoundState;
18
+ onMeshLoaded(): void;
19
+ onDestroy(): void;
20
+ play(name: string): void;
21
+ stop(name: string): void;
22
+ }
@@ -8,6 +8,7 @@ import { AssetManager } from "../managers/asset.d.ts";
8
8
  import { ColliderDebugManager } from "../managers/colliderDebug.d.ts";
9
9
  import { DebugConsole } from "./debugConsole.d.ts";
10
10
  import { GameManager } from "../managers/game.d.ts";
11
+ import { InputManager } from "../managers/input.d.ts";
11
12
  import { LevelManager } from "../managers/level.d.ts";
12
13
  import { SimulationSaveManager } from "../managers/simulationSave.d.ts";
13
14
  import { AudioManager } from "../managers/audio.d.ts";
@@ -37,6 +38,7 @@ interface Engines extends Record<string, Disposable | undefined> {
37
38
  interface Managers extends Record<string, Disposable | undefined> {
38
39
  assetManager: AssetManager;
39
40
  gameManager: GameManager;
41
+ inputManager: InputManager;
40
42
  levelManager: LevelManager;
41
43
  saveManager: SimulationSaveManager;
42
44
  audioManager?: AudioManager;
@@ -66,6 +68,7 @@ export declare class GameEngine {
66
68
  debug: DebugTools;
67
69
  largeWorldRendering: boolean;
68
70
  started: boolean;
71
+ private _stopped;
69
72
  private _log;
70
73
  private _buildSimulation;
71
74
  private _beforeStartHook;
@@ -135,6 +138,10 @@ export declare class GameEngine {
135
138
  start(): Promise<void>;
136
139
  /**
137
140
  * Stop the engine: runs the teardown hook, then tears down all managers and engines in order.
141
+ *
142
+ * Managers claim what they need when they are built, not when the engine starts -- the input manager's
143
+ * window listeners, the debug console's namespace -- so an engine that never started still has
144
+ * everything to release. Tearing down twice is the no-op.
138
145
  */
139
146
  stop(): Promise<void>;
140
147
  }
@@ -1,6 +1,7 @@
1
1
  import { Quaternion, Scene, StaticSound, TransformNode, Vector3 } from '@babylonjs/core';
2
2
  import { LevelConfig, LevelContext } from "../interfaces/level.d.ts";
3
3
  import { NodeEntity } from "../entities/nodeEntity.d.ts";
4
+ import { NodeSimulation } from "../entities/nodeSimulation.d.ts";
4
5
  import { Level } from "./level.d.ts";
5
6
  /**
6
7
  * Metadata collected from a spawn point node.
@@ -47,6 +48,11 @@ export declare class GameLevel extends Level {
47
48
  constructor(config: LevelConfig, context: LevelContext);
48
49
  get config(): LevelConfig;
49
50
  get spawnedEntities(): readonly NodeEntity[];
51
+ /**
52
+ * The simulation this level built its scene against: the one its own spawns and despawns go through,
53
+ * whichever simulation the engine holds at the time. Throws before `buildScene` has bound one.
54
+ */
55
+ protected get simulation(): NodeSimulation;
50
56
  /**
51
57
  * Build the scene by loading assets, processing node metadata, and spawning entities.
52
58
  */
@@ -1,5 +1,6 @@
1
1
  import { Color3, Scene, TransformNode } from '@babylonjs/core';
2
2
  import { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer';
3
+ import { NodeSimulation } from "../entities/nodeSimulation.d.ts";
3
4
  import { LoggerInterface } from "../interfaces/logger.d.ts";
4
5
  import { ColorConfig, LevelConfig, LevelContext, LevelInstance, LevelLoadOptions, PropertyHandler } from "../interfaces/level.d.ts";
5
6
  import { GameEngine } from "./gameEngine.d.ts";
@@ -28,6 +29,12 @@ export declare abstract class Level implements LevelInstance {
28
29
  protected readonly _context: LevelContext;
29
30
  protected _scene: Scene | null;
30
31
  protected _forRestore: boolean;
32
+ /**
33
+ * The simulation this level's scene was built against. A transition loads the next level before
34
+ * disposing this one, so by the time this level despawns what it spawned, the engine already holds the
35
+ * next level's simulation, minting the same IDs.
36
+ */
37
+ protected _simulation: NodeSimulation | null;
31
38
  clusteredLights: ClusteredLightContainer | null;
32
39
  outlines: OutlineManager | null;
33
40
  /**
@@ -47,6 +54,18 @@ export declare abstract class Level implements LevelInstance {
47
54
  * @param progress - Percentage (0-100)
48
55
  * @param message
49
56
  */
57
+ /**
58
+ * The simulation this level owns, or nothing for a level that never built one. The level manager binds
59
+ * the engine to the current level's simulation (design/simulation.md#which-simulation-the-engine-holds).
60
+ */
61
+ get $simulation(): NodeSimulation | null;
62
+ /**
63
+ * Take over a simulation built to replace this level's own, as a save load does once it has restored
64
+ * into a fresh one.
65
+ *
66
+ * @param simulation
67
+ */
68
+ $adoptSimulation(simulation: NodeSimulation): void;
50
69
  protected $emitProgress(progress: number, message: string): void;
51
70
  protected _toColor3(color: ColorConfig): Color3;
52
71
  /**
@@ -2,9 +2,5 @@ import { DebugConsole } from "../classes/debugConsole.d.ts";
2
2
  import { GameEngine } from "../classes/gameEngine.d.ts";
3
3
  /**
4
4
  * Registers all default references and commands onto the debug console namespace.
5
- *
6
- * The entity system's class table carries no behavior list on the entity itself (entities.md#the-entity-
7
- * class-table), and a NodeEntity has no collider-debug method of its own -- so, unlike the 0.9 console, this
8
- * has no `list(type)`, no `entities` proxy, and no `showColliders`/`hideColliders`.
9
5
  */
10
6
  export declare function registerBuiltins(dc: DebugConsole, ge: GameEngine): void;
@@ -11,4 +11,5 @@ export interface EntityPickResult {
11
11
  distance: number;
12
12
  normal: Vector3;
13
13
  }
14
+ export declare function findEntityForMesh(entities: readonly GameEntity[], mesh: AbstractMesh): NodeEntity | undefined;
14
15
  export declare function toEntityPickResult(entities: readonly GameEntity[], info: PickingInfo | null, filter?: EntityPickFilter): EntityPickResult | undefined;
@@ -1,5 +1,7 @@
1
1
  import { TransformNode, AbstractMesh } from '@babylonjs/core';
2
2
  import { GameEntity, Action, GameEntityState, GameSimulationContext } from '@skewedaspect/sage-core';
3
+ import { AudioManager } from "../managers/audio.d.ts";
4
+ import { ColliderDebugConfig } from "../managers/colliderDebug.d.ts";
3
5
  import { MeshConfig } from "./meshConfig.d.ts";
4
6
  export interface Vector3State {
5
7
  x: number;
@@ -17,6 +19,11 @@ export interface NodeEntityState extends GameEntityState {
17
19
  nodeName?: string;
18
20
  position: Vector3State;
19
21
  rotation: QuaternionState;
22
+ debugCollider?: ColliderDebugConfig;
23
+ }
24
+ export interface NodeBehaviorHooks {
25
+ onMeshLoaded?(): void;
26
+ onDestroy?(): void;
20
27
  }
21
28
  export declare abstract class NodeEntity extends GameEntity {
22
29
  #private;
@@ -27,6 +34,9 @@ export declare abstract class NodeEntity extends GameEntity {
27
34
  get node(): TransformNode;
28
35
  get mesh(): AbstractMesh | undefined;
29
36
  set mesh(mesh: AbstractMesh | undefined);
37
+ get audio(): AudioManager | undefined;
38
+ showCollider(config?: ColliderDebugConfig, color?: string): void;
39
+ hideCollider(name?: string): void;
30
40
  protected constructor(id: string, context: GameSimulationContext, providedState: object, runCreateHooks: boolean, name?: string);
31
41
  update(dt: number): void;
32
42
  destroy(): void;
@@ -1,22 +1,30 @@
1
1
  import { GameSimulationManager, GameSimulationOptions, StateBag } from '@skewedaspect/sage-core';
2
- import { Camera, Ray, Scene } from '@babylonjs/core';
2
+ import { AbstractMesh, Camera, Ray, Scene } from '@babylonjs/core';
3
3
  import { EntityPickFilter, EntityPickResult } from "./entityPicking.d.ts";
4
+ import { NodeEntity } from "./nodeEntity.d.ts";
4
5
  import { NodePool } from "./nodePool.d.ts";
5
6
  import { MeshLoader, NodeSimulationContext } from "./nodeSimulationContext.d.ts";
7
+ import { AudioManager } from "../managers/audio.d.ts";
8
+ import { ColliderDebugManager } from "../managers/colliderDebug.d.ts";
6
9
  export interface NodeSimulationOptions extends GameSimulationOptions {
7
10
  scene: Scene;
8
11
  loadMesh: MeshLoader;
9
12
  pool: NodePool;
13
+ audio?: AudioManager;
14
+ colliders: ColliderDebugManager;
10
15
  }
11
16
  export declare class NodeSimulation extends GameSimulationManager implements NodeSimulationContext {
12
17
  #private;
13
18
  readonly scene: Scene;
14
19
  readonly pool: NodePool;
15
20
  readonly loadMesh: MeshLoader;
21
+ readonly audio: AudioManager | undefined;
22
+ readonly colliders: ColliderDebugManager;
16
23
  constructor(options: NodeSimulationOptions);
17
24
  prewarm(name: string, count: number, state?: StateBag): Promise<void>;
18
25
  pickEntityWithRay(ray: Ray, filter?: EntityPickFilter): EntityPickResult | undefined;
19
26
  pickEntity(x: number, y: number, filter?: EntityPickFilter): EntityPickResult | undefined;
20
27
  pickEntityForward(camera: Camera, range: number, filter?: EntityPickFilter): EntityPickResult | undefined;
28
+ getEntityForMesh(mesh: AbstractMesh): NodeEntity | undefined;
21
29
  protected buildReplacementSimulation(options: GameSimulationOptions): NodeSimulation;
22
30
  }
@@ -1,5 +1,7 @@
1
1
  import { AbstractMesh, Scene, TransformNode } from '@babylonjs/core';
2
2
  import { GameSimulationContext, StateBag } from '@skewedaspect/sage-core';
3
+ import { AudioManager } from "../managers/audio.d.ts";
4
+ import { ColliderDebugManager } from "../managers/colliderDebug.d.ts";
3
5
  import { NodePool } from "./nodePool.d.ts";
4
6
  export interface NodeSet {
5
7
  node: TransformNode;
@@ -10,4 +12,6 @@ export interface NodeSimulationContext extends GameSimulationContext {
10
12
  readonly scene: Scene;
11
13
  readonly pool: NodePool;
12
14
  readonly loadMesh: MeshLoader;
15
+ readonly audio?: AudioManager;
16
+ readonly colliders: ColliderDebugManager;
13
17
  }
@@ -48,17 +48,6 @@ export interface TriggerExitPayload {
48
48
  trigger: string;
49
49
  other: AbstractMesh;
50
50
  }
51
- /** Payload for entity:state-changed events */
52
- export interface EntityStateChangedPayload {
53
- /** The ID of the entity whose state changed */
54
- entityId: string;
55
- /** The type of the entity */
56
- entityType: string;
57
- /** The current state (optional - may include full state or just changed parts) */
58
- state?: object;
59
- /** Specific changes made (optional - alternative to full state) */
60
- changes?: Record<string, unknown>;
61
- }
62
51
  /** Payload for asset:progress events */
63
52
  export interface AssetProgressPayload {
64
53
  path: string;
@@ -98,7 +87,6 @@ export interface LibraryEventPayloadMap {
98
87
  'asset:complete': AssetCompletePayload;
99
88
  'trigger:enter': TriggerEnterPayload;
100
89
  'trigger:exit': TriggerExitPayload;
101
- 'entity:state-changed': EntityStateChangedPayload;
102
90
  'game:paused': GamePausedPayload;
103
91
  'game:resumed': GameResumedPayload;
104
92
  [key: string]: unknown;
@@ -7,5 +7,7 @@ export declare class ActionContextRegistry {
7
7
  isActive(name: string): boolean;
8
8
  isRegistered(name: string): boolean;
9
9
  addBinding(binding: Binding<unknown>): void;
10
+ removeBinding(binding: Binding<unknown>): void;
11
+ list(): readonly Binding<unknown>[];
10
12
  isLive(binding: Binding<unknown>): boolean;
11
13
  }
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@skewedaspect/sage-core';
2
- export type BindingKind = 'trigger' | 'toggle' | 'value';
2
+ export type BindingKind = 'trigger' | 'toggle' | 'value' | 'delta';
3
3
  export type DeviceType = 'keyboard' | 'mouse' | 'gamepad';
4
4
  export interface DeviceID {
5
5
  type: DeviceType;
@@ -25,6 +25,8 @@ export interface Binding<State> {
25
25
  readonly action: Action;
26
26
  readonly device: DeviceID;
27
27
  readonly context?: string;
28
+ readonly preventDefault: boolean;
29
+ readonly input: BindingInput;
28
30
  process(state: State): BindingFire | undefined;
29
31
  prime(state: State): void;
30
32
  toDefinition(): BindingDefinition;
@@ -0,0 +1,23 @@
1
+ import { Action } from '@skewedaspect/sage-core';
2
+ import { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from "../binding.d.ts";
3
+ import { DeviceReader } from "../deviceReader.d.ts";
4
+ export interface DeltaOptions {
5
+ scale?: number;
6
+ invert?: boolean;
7
+ deadzone?: number;
8
+ /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */
9
+ preventDefault?: boolean;
10
+ }
11
+ export declare class DeltaBinding<State> implements Binding<State> {
12
+ #private;
13
+ readonly kind: "delta";
14
+ readonly action: Action;
15
+ readonly device: DeviceID;
16
+ readonly context?: string;
17
+ readonly preventDefault: boolean;
18
+ constructor(action: Action, device: DeviceID, reader: DeviceReader<State>, options?: DeltaOptions, context?: string);
19
+ get input(): BindingInput;
20
+ process(state: State): BindingFire | undefined;
21
+ prime(_state: State): void;
22
+ toDefinition(): BindingDefinition;
23
+ }
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@skewedaspect/sage-core';
2
- import { Binding, BindingDefinition, BindingFire, DeviceID } from "../binding.d.ts";
2
+ import { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from "../binding.d.ts";
3
3
  import { DeviceReader } from "../deviceReader.d.ts";
4
4
  export interface ToggleOptions {
5
5
  onValue?: boolean | number;
@@ -7,6 +7,8 @@ export interface ToggleOptions {
7
7
  initialState?: boolean;
8
8
  threshold?: number;
9
9
  invert?: boolean;
10
+ /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */
11
+ preventDefault?: boolean;
10
12
  }
11
13
  export declare class ToggleBinding<State> implements Binding<State> {
12
14
  #private;
@@ -14,7 +16,9 @@ export declare class ToggleBinding<State> implements Binding<State> {
14
16
  readonly action: Action;
15
17
  readonly device: DeviceID;
16
18
  readonly context?: string;
19
+ readonly preventDefault: boolean;
17
20
  constructor(action: Action, device: DeviceID, reader: DeviceReader<State>, options?: ToggleOptions, context?: string);
21
+ get input(): BindingInput;
18
22
  process(state: State): BindingFire | undefined;
19
23
  prime(state: State): void;
20
24
  toDefinition(): BindingDefinition;
@@ -1,10 +1,12 @@
1
1
  import { Action } from '@skewedaspect/sage-core';
2
- import { Binding, BindingDefinition, BindingFire, DeviceID } from "../binding.d.ts";
2
+ import { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from "../binding.d.ts";
3
3
  import { DeviceReader } from "../deviceReader.d.ts";
4
4
  export type EdgeMode = 'rising' | 'falling' | 'both';
5
5
  export interface TriggerOptions {
6
6
  edge?: EdgeMode;
7
7
  threshold?: number;
8
+ /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */
9
+ preventDefault?: boolean;
8
10
  }
9
11
  export declare class TriggerBinding<State> implements Binding<State> {
10
12
  #private;
@@ -12,7 +14,9 @@ export declare class TriggerBinding<State> implements Binding<State> {
12
14
  readonly action: Action;
13
15
  readonly device: DeviceID;
14
16
  readonly context?: string;
17
+ readonly preventDefault: boolean;
15
18
  constructor(action: Action, device: DeviceID, reader: DeviceReader<State>, options?: TriggerOptions, context?: string);
19
+ get input(): BindingInput;
16
20
  process(state: State): BindingFire | undefined;
17
21
  prime(state: State): void;
18
22
  toDefinition(): BindingDefinition;
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@skewedaspect/sage-core';
2
- import { Binding, BindingDefinition, BindingFire, DeviceID } from "../binding.d.ts";
2
+ import { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from "../binding.d.ts";
3
3
  import { DeviceReader } from "../deviceReader.d.ts";
4
4
  export interface ValueOptions {
5
5
  scale?: number;
@@ -8,7 +8,8 @@ export interface ValueOptions {
8
8
  deadzone?: number;
9
9
  min?: number;
10
10
  max?: number;
11
- emitOnChange?: boolean;
11
+ /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */
12
+ preventDefault?: boolean;
12
13
  }
13
14
  export declare class ValueBinding<State> implements Binding<State> {
14
15
  #private;
@@ -16,8 +17,10 @@ export declare class ValueBinding<State> implements Binding<State> {
16
17
  readonly action: Action;
17
18
  readonly device: DeviceID;
18
19
  readonly context?: string;
20
+ readonly preventDefault: boolean;
19
21
  constructor(action: Action, device: DeviceID, reader: DeviceReader<State>, options?: ValueOptions, context?: string);
20
- process(state: State): BindingFire | undefined;
21
- prime(state: State): void;
22
+ get input(): BindingInput;
23
+ process(state: State): BindingFire;
24
+ prime(_state: State): void;
22
25
  toDefinition(): BindingDefinition;
23
26
  }
@@ -1,3 +1,4 @@
1
- import { GameSimulationContext } from '@skewedaspect/sage-core';
1
+ import { Action, GameSimulationContext } from '@skewedaspect/sage-core';
2
2
  import { Binding } from "./binding.d.ts";
3
- export declare function deliverAction<State>(binding: Binding<State>, state: State, context: GameSimulationContext, senderID: string, targetID: string): void;
3
+ export declare function deliverAction<State>(binding: Binding<State>, state: State, context: GameSimulationContext, senderID: string): void;
4
+ export declare function deliverValueAction(action: Action, value: number, context: GameSimulationContext, senderID: string): void;
@@ -1,6 +1,6 @@
1
1
  import { MouseState } from "../deviceState.d.ts";
2
2
  import { DeviceReader } from "../deviceReader.d.ts";
3
- export type MouseSourceType = 'button' | 'position' | 'wheel';
3
+ export type MouseSourceType = 'button' | 'position' | 'wheel' | 'movement';
4
4
  export declare class MouseReader implements DeviceReader<MouseState> {
5
5
  readonly sourceType: MouseSourceType;
6
6
  readonly sourceKey: string;
@@ -12,6 +12,11 @@ export interface MouseState {
12
12
  deltaY: number;
13
13
  deltaZ: number;
14
14
  }>;
15
+ /** How far the pointer travelled since the last routing pass, which is what a delta binding reads. */
16
+ readonly movement: Readonly<{
17
+ x: number;
18
+ y: number;
19
+ }>;
15
20
  }
16
21
  export interface GamepadState {
17
22
  readonly buttons: Readonly<Record<string, number>>;
@@ -43,7 +43,7 @@ export interface SageOptions {
43
43
  * - `{ namespace: 'myGame' }`: enabled with custom namespace
44
44
  * - `false`: disabled (production builds)
45
45
  */
46
- debug?: false | {
46
+ debug?: boolean | {
47
47
  namespace?: string;
48
48
  };
49
49
  /**
@@ -54,7 +54,7 @@ export interface SpawnDefinition {
54
54
  tags?: string[];
55
55
  /** Additional configuration passed to the entity's initial state */
56
56
  config?: Record<string, unknown>;
57
- /** Debug collider visualization config. Overrides the entity definition's debugCollider. */
57
+ /** Debug collider visualization config, written into the spawned entity's own debugCollider state field. */
58
58
  debugCollider?: ColliderDebugConfig;
59
59
  }
60
60
  /**
@@ -70,7 +70,7 @@ export interface EntityDefinition {
70
70
  tags?: string[];
71
71
  /** Additional configuration passed to the entity's initial state */
72
72
  config?: Record<string, unknown>;
73
- /** Debug collider visualization config. Overrides the entity definition's debugCollider. */
73
+ /** Debug collider visualization config, written into the spawned entity's own debugCollider state field. */
74
74
  debugCollider?: ColliderDebugConfig;
75
75
  }
76
76
  /**
@@ -17,16 +17,23 @@ export declare class AssetManager implements Disposable {
17
17
  private _containers;
18
18
  /** Extracted fragment meshes (source meshes for instancing), keyed by full fragment path */
19
19
  private _sourceMeshes;
20
+ /** Container loads still in flight, keyed by base path, so concurrent callers share one load */
21
+ private _pendingContainers;
20
22
  constructor(eventBus: GameEventBus, sceneEngine: SceneEngine, logger?: LoggingUtility);
21
23
  /**
22
24
  * Split a path on `#` into base path and optional fragment name.
23
25
  */
24
26
  private _parsePath;
25
27
  /**
26
- * Load (or retrieve from cache) the AssetContainer for a base path.
27
- * Increments refCount.
28
+ * Load (or retrieve from cache) the AssetContainer for a base path, counting one reference for this
29
+ * caller. A caller arriving while the same base path is already loading shares that load instead of
30
+ * starting a second one, which would leave the first container cached over and uncounted.
28
31
  */
29
32
  private _loadContainer;
33
+ /**
34
+ * Read one container off disk and cache it, counting no references; `_loadContainer` does the counting.
35
+ */
36
+ private _fetchContainer;
30
37
  /**
31
38
  * Find a mesh by name inside a container. Throws if not found.
32
39
  */
@@ -49,7 +56,14 @@ export declare class AssetManager implements Disposable {
49
56
  */
50
57
  clone(path: string): Mesh;
51
58
  /**
52
- * Preload a batch of asset paths. Emits `asset:progress` after each and `asset:complete` when finished.
59
+ * Preload a batch of asset paths, up to `MAX_CONCURRENT_LOADS` at a time, so a batch costs about as
60
+ * long as its slowest asset rather than the sum of them all.
61
+ *
62
+ * Emits `asset:progress` as each asset finishes loading, naming the asset that finished and counting
63
+ * one to one up to the total. Loads finish out of request order, so the paths do too.
64
+ *
65
+ * Every asset is attempted, and every load has finished before this returns. If any failed, it throws
66
+ * an `AggregateError` naming all of them and emits no `asset:complete`.
53
67
  */
54
68
  preload(paths: string[]): Promise<void>;
55
69
  /**
@@ -2,6 +2,7 @@ import { AbstractEngine, Scene } from '@babylonjs/core';
2
2
  import { Disposable } from "../interfaces/lifecycle.d.ts";
3
3
  import { GameEventBus } from "../classes/eventBus.d.ts";
4
4
  import { GameEngine } from "../classes/gameEngine.d.ts";
5
+ import { InputManager } from "./input.d.ts";
5
6
  import { LevelManager } from "./level.d.ts";
6
7
  import { LoggingUtility } from "../utils/logger.d.ts";
7
8
  /**
@@ -9,10 +10,15 @@ import { LoggingUtility } from "../utils/logger.d.ts";
9
10
  * Called once per frame with the delta time in seconds.
10
11
  */
11
12
  export type FrameCallback = (deltaTime: number) => void;
13
+ /** How a frame callback answers to pause (design/ui.md#when-a-view-observes). */
14
+ export interface FrameCallbackOptions {
15
+ suspendedByPause?: boolean;
16
+ }
12
17
  export declare class GameManager implements Disposable {
13
18
  private _engine;
14
19
  private _eventBus;
15
20
  private _gameEngine;
21
+ private _inputManager;
16
22
  private _levelManager;
17
23
  private _log;
18
24
  private _boundRenderLoop;
@@ -31,6 +37,7 @@ export declare class GameManager implements Disposable {
31
37
  * Called by createGameEngine() after the GameEngine instance is created.
32
38
  */
33
39
  $setGameEngine(gameEngine: GameEngine): void;
40
+ setInputManager(inputManager: InputManager): void;
34
41
  /**
35
42
  * Whether the game is currently paused.
36
43
  * When paused, entity updates and frame callbacks are skipped, but rendering continues.
@@ -60,7 +67,7 @@ export declare class GameManager implements Disposable {
60
67
  * unsubscribe();
61
68
  * ```
62
69
  */
63
- registerFrameCallback(callback: FrameCallback): () => void;
70
+ registerFrameCallback(callback: FrameCallback, options?: FrameCallbackOptions): () => void;
64
71
  private _renderLoop;
65
72
  private _resizeHandler;
66
73
  start(): Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { GameSimulationContext, Unsubscribe } from '@skewedaspect/sage-core';
2
+ import { Disposable } from "../interfaces/lifecycle.d.ts";
3
+ import { Binding, DeviceType } from "../input/binding.d.ts";
4
+ import { InputCapture } from "../input/inputCapture.d.ts";
5
+ import { GamepadState, KeyboardState, MouseState } from "../input/deviceState.d.ts";
6
+ import { ActionContextRegistry } from "../input/actionContext.d.ts";
7
+ import { ActionRegistry } from "../input/actionRegistry.d.ts";
8
+ declare module '@skewedaspect/sage-core' {
9
+ interface EventMap {
10
+ 'input:deviceChanged': DeviceChangedPayload;
11
+ }
12
+ }
13
+ export interface DeviceChangedPayload {
14
+ deviceType: DeviceType;
15
+ /** Undefined for the first deliberate input of a session, which replaces no device type. */
16
+ previousDeviceType: DeviceType | undefined;
17
+ }
18
+ export declare const ROUTING_LOOP_SENDER_ID = "sage-input";
19
+ export declare const DELIBERATE_THRESHOLD = 0.2;
20
+ export declare const MOVEMENT_THRESHOLD = 2;
21
+ export declare class InputManager implements Disposable {
22
+ #private;
23
+ readonly actions: ActionRegistry;
24
+ readonly contexts: ActionContextRegistry;
25
+ constructor();
26
+ get lastActiveDeviceType(): DeviceType | undefined;
27
+ deviceState(type: 'keyboard', index: number): KeyboardState;
28
+ deviceState(type: 'mouse', index: number): MouseState;
29
+ deviceState(type: 'gamepad', index: number): GamepadState;
30
+ registerCapture(capture: InputCapture): Unsubscribe;
31
+ getBindingsForAction(action: string, deviceType?: DeviceType, context?: string): readonly Binding<unknown>[];
32
+ routeCapturedInput(context: GameSimulationContext): void;
33
+ $teardown(): Promise<void>;
34
+ }
@@ -3,6 +3,7 @@ import { Disposable } from "../interfaces/lifecycle.d.ts";
3
3
  import { Level } from "../classes/level.d.ts";
4
4
  import { GameEventBus } from "../classes/eventBus.d.ts";
5
5
  import { GameEngine } from "../classes/gameEngine.d.ts";
6
+ import { NodeSimulation } from "../entities/nodeSimulation.d.ts";
6
7
  import { LoggingUtility } from "../utils/logger.d.ts";
7
8
  /**
8
9
  * Options for level transitions.
@@ -147,6 +148,19 @@ export declare class LevelManager implements Disposable {
147
148
  * @param options
148
149
  */
149
150
  loadLevel(config: LevelConfig, options?: LevelLoadOptions): Promise<Level>;
151
+ /**
152
+ * Bind the engine to a level's own simulation. A level that built none leaves the engine as it is.
153
+ *
154
+ * @param level
155
+ */
156
+ private _bindSimulation;
157
+ /**
158
+ * Hand the current level a simulation built to replace its own, and bind the engine to it. A save load
159
+ * restores into a fresh simulation, and the level that owns the scene has to follow it.
160
+ *
161
+ * @param simulation
162
+ */
163
+ $bindSimulation(simulation: NodeSimulation): void;
150
164
  /**
151
165
  * Activates a level by name, loading it if necessary.
152
166
  *
@@ -1,4 +1,6 @@
1
1
  import { GameSimulationManager, GameSimulationSnapshot } from '@skewedaspect/sage-core';
2
+ import { GameEngine } from "../classes/gameEngine.d.ts";
3
+ import { NodeSimulation } from "../entities/nodeSimulation.d.ts";
2
4
  import { Disposable } from "../interfaces/lifecycle.d.ts";
3
5
  import { LevelLoadOptions } from "../interfaces/level.d.ts";
4
6
  export interface LevelLoader {
@@ -6,6 +8,7 @@ export interface LevelLoader {
6
8
  name: string;
7
9
  } | null;
8
10
  transition(levelName: string, options?: LevelLoadOptions): Promise<void>;
11
+ $bindSimulation(simulation: NodeSimulation): void;
9
12
  }
10
13
  export interface SaveEnvelope {
11
14
  saveFormatVersion: number;
@@ -17,14 +20,19 @@ export interface SaveEnvelope {
17
20
  }
18
21
  export type BeforeSaveHook = () => Record<string, unknown>;
19
22
  export interface LoadResult {
20
- simulation: GameSimulationManager;
23
+ simulation: NodeSimulation;
21
24
  envelope: SaveEnvelope;
22
25
  }
23
26
  export declare class SimulationSaveManager implements Disposable {
24
27
  #private;
25
28
  constructor(levelLoader: LevelLoader, engineVersion: string, now?: () => number);
29
+ /**
30
+ * Late-bind the GameEngine after construction. Called by createGameEngine() after the GameEngine
31
+ * instance is created; load() needs it to reach the simulation a transition just rebuilt.
32
+ */
33
+ $setGameEngine(gameEngine: GameEngine): void;
26
34
  onBeforeSave(hook: BeforeSaveHook): void;
27
35
  save(simulation: GameSimulationManager): SaveEnvelope;
28
- load(envelope: SaveEnvelope, simulation: GameSimulationManager): Promise<LoadResult>;
36
+ load(envelope: SaveEnvelope): Promise<LoadResult>;
29
37
  $teardown(): Promise<void>;
30
38
  }