@skewedaspect/sage 0.9.6 → 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 (74) hide show
  1. package/dist/behaviors/sound.d.ts +12 -127
  2. package/dist/classes/debugConsole.d.ts +1 -0
  3. package/dist/classes/gameEngine.d.ts +31 -46
  4. package/dist/classes/gameLevel.d.ts +21 -12
  5. package/dist/classes/level.d.ts +24 -29
  6. package/dist/entities/constants.d.ts +1 -0
  7. package/dist/entities/defineEntity.d.ts +17 -0
  8. package/dist/entities/entityPicking.d.ts +15 -0
  9. package/dist/entities/meshConfig.d.ts +14 -0
  10. package/dist/entities/nodeEntity.d.ts +43 -0
  11. package/dist/entities/nodePool.d.ts +8 -0
  12. package/dist/entities/nodeSimulation.d.ts +30 -0
  13. package/dist/entities/nodeSimulationContext.d.ts +17 -0
  14. package/dist/entities/standardMeshSource.d.ts +8 -0
  15. package/dist/events/payloads.d.ts +0 -42
  16. package/dist/events/types.d.ts +2 -23
  17. package/dist/input/actionContext.d.ts +13 -0
  18. package/dist/input/actionRegistry.d.ts +7 -0
  19. package/dist/input/binding.d.ts +33 -0
  20. package/dist/input/bindings/delta.d.ts +23 -0
  21. package/dist/input/bindings/toggle.d.ts +25 -0
  22. package/dist/input/bindings/trigger.d.ts +23 -0
  23. package/dist/input/bindings/value.d.ts +26 -0
  24. package/dist/input/configuration.d.ts +15 -0
  25. package/dist/input/deliverAction.d.ts +4 -0
  26. package/dist/input/deviceReader.d.ts +5 -0
  27. package/dist/input/deviceReaders/gamepad.d.ts +9 -0
  28. package/dist/input/deviceReaders/keyboard.d.ts +8 -0
  29. package/dist/input/deviceReaders/mouse.d.ts +9 -0
  30. package/dist/input/deviceState.d.ts +24 -0
  31. package/dist/input/inputCapture.d.ts +21 -0
  32. package/dist/input/resolveBinding.d.ts +4 -0
  33. package/dist/interfaces/game.d.ts +21 -5
  34. package/dist/interfaces/level.d.ts +21 -3
  35. package/dist/managers/asset.d.ts +17 -3
  36. package/dist/managers/game.d.ts +15 -5
  37. package/dist/managers/input.d.ts +31 -51
  38. package/dist/managers/level.d.ts +20 -4
  39. package/dist/managers/outline.d.ts +4 -4
  40. package/dist/managers/simulationSave.d.ts +38 -0
  41. package/dist/{postProcessingFrameGraph-CaMgQzR-.js → postProcessingFrameGraph-C3Og4kaS.js} +34 -26
  42. package/dist/postProcessingFrameGraph-C3Og4kaS.js.map +1 -0
  43. package/dist/sage.d.ts +54 -16
  44. package/dist/sage.es.js +1868 -2424
  45. package/dist/sage.es.js.map +1 -1
  46. package/dist/sage.umd.js +1 -1
  47. package/dist/sage.umd.js.map +1 -1
  48. package/dist/utils/bounds.d.ts +11 -0
  49. package/dist/utils/physics.d.ts +4 -1
  50. package/package.json +29 -66
  51. package/LICENSE +0 -21
  52. package/Readme.md +0 -73
  53. package/dist/behaviors/index.d.ts +0 -2
  54. package/dist/behaviors/stateMachine.d.ts +0 -76
  55. package/dist/classes/bindings/toggle.d.ts +0 -116
  56. package/dist/classes/bindings/trigger.d.ts +0 -81
  57. package/dist/classes/bindings/value.d.ts +0 -104
  58. package/dist/classes/entity.d.ts +0 -332
  59. package/dist/classes/input/gamepad.d.ts +0 -94
  60. package/dist/classes/input/keyboard.d.ts +0 -66
  61. package/dist/classes/input/mouse.d.ts +0 -91
  62. package/dist/classes/input/readers/gamepad.d.ts +0 -73
  63. package/dist/classes/input/readers/keyboard.d.ts +0 -56
  64. package/dist/classes/input/readers/mouse.d.ts +0 -41
  65. package/dist/interfaces/action.d.ts +0 -22
  66. package/dist/interfaces/binding.d.ts +0 -123
  67. package/dist/interfaces/entity.d.ts +0 -97
  68. package/dist/interfaces/input.d.ts +0 -180
  69. package/dist/managers/binding.d.ts +0 -233
  70. package/dist/managers/entity.d.ts +0 -264
  71. package/dist/managers/save.d.ts +0 -75
  72. package/dist/postProcessingFrameGraph-CaMgQzR-.js.map +0 -1
  73. package/dist/utils/entityProxy.d.ts +0 -2
  74. package/dist/utils/raycast.d.ts +0 -26
@@ -1,137 +1,22 @@
1
- import { TransformNode } from '@babylonjs/core';
2
- import { GameEntityBehavior } from "../classes/entity.d.ts";
3
- import { GameEngine } from "../classes/gameEngine.d.ts";
4
- import { GameEvent } from "../classes/eventBus.d.ts";
5
- /**
6
- * Configuration for a single sound.
7
- */
1
+ import { Behavior } from '@skewedaspect/sage-core';
2
+ import { NodeBehaviorHooks } from "../entities/nodeEntity.d.ts";
8
3
  export interface SoundConfig {
9
- /** Path to the sound file */
10
4
  url: string;
11
- /** Volume level (0-1), default 1 */
12
5
  volume?: number;
13
- /** Whether to loop the sound, default false */
14
6
  loop?: boolean;
15
- /** Whether to use spatial 3D audio, default false */
16
7
  spatial?: boolean;
17
- /** Maximum distance for spatial audio, default 100 */
18
8
  maxDistance?: number;
19
- /** Whether to autoplay when registered, default false */
20
- autoplay?: boolean;
21
- /** Audio channel name (e.g., 'sfx', 'music'). Used with AudioManager. */
22
9
  channel?: string;
23
10
  }
24
- /**
25
- * State interface for entities using SoundBehavior.
26
- * Entity state should include a `sounds` property with sound configurations.
27
- */
28
- export interface SoundEntityState {
29
- /** Map of sound name to configuration */
30
- sounds?: Record<string, SoundConfig>;
11
+ export interface SoundState {
12
+ sounds: Record<string, SoundConfig>;
31
13
  }
32
- /**
33
- * Behavior for managing sounds on an entity.
34
- *
35
- * Sounds are configured in the entity's state under the `sounds` property.
36
- * The behavior creates sounds via AudioManager (BabylonJS AudioV2).
37
- *
38
- * Requires AudioManager to be configured on the GameEngine. If no AudioManager is found,
39
- * a warning is logged and no sounds are created.
40
- *
41
- * @example
42
- * ```typescript
43
- * // Entity definition
44
- * {
45
- * type: 'player',
46
- * behaviors: [SoundBehavior, ...],
47
- * defaultState: {
48
- * sounds: {
49
- * jump: { url: 'sounds/jump.mp3', volume: 0.8, channel: 'sfx' },
50
- * footstep: { url: 'sounds/footstep.mp3', loop: true, channel: 'sfx' }
51
- * }
52
- * }
53
- * }
54
- *
55
- * // In another behavior, get the sound behavior and control sounds
56
- * const soundBehavior = entity.getBehavior(SoundBehavior);
57
- * soundBehavior?.play('jump');
58
- * ```
59
- */
60
- export declare class SoundBehavior extends GameEntityBehavior<SoundEntityState> {
61
- name: string;
62
- eventSubscriptions: string[];
63
- /** Map of sound name to StaticSound instance */
64
- private _sounds;
65
- /** Whether sounds have been initialized */
66
- private _initialized;
67
- /** AudioManager reference */
68
- private _audioManager;
69
- /** Node reference for spatial detach on cleanup */
70
- private _node;
71
- private _log;
72
- onNodeAttached(node: TransformNode, gameEngine: GameEngine): void;
73
- processEvent(_event: GameEvent, _state: SoundEntityState): boolean;
74
- onReset(_state: SoundEntityState): void;
75
- destroy(): Promise<void>;
76
- private _createSound;
77
- private _disposeSounds;
78
- /**
79
- * Play a sound by name.
80
- *
81
- * @param name - The name of the sound to play. If omitted and only one sound exists, plays that sound.
82
- * @param time - Optional time offset in seconds to start playing from
83
- */
84
- play(name?: string, time?: number): void;
85
- /**
86
- * Stop a sound by name.
87
- *
88
- * @param name - The name of the sound to stop. If omitted, stops all sounds.
89
- */
90
- stop(name?: string): void;
91
- /**
92
- * Pause a sound by name.
93
- *
94
- * @param name - The name of the sound to pause. If omitted and only one sound exists, pauses that sound.
95
- */
96
- pause(name?: string): void;
97
- /**
98
- * Set the volume of a sound.
99
- *
100
- * @param volume - Volume level (0-1)
101
- * @param name - The name of the sound. If omitted, sets volume for all sounds.
102
- */
103
- setVolume(volume: number, name?: string): void;
104
- /**
105
- * Check if a sound is currently playing.
106
- *
107
- * @param name - The name of the sound. If omitted and only one sound exists, checks that sound.
108
- */
109
- isPlaying(name?: string): boolean;
110
- /**
111
- * Get all registered sound names.
112
- */
113
- getSoundNames(): string[];
114
- /**
115
- * Check if a sound with the given name exists.
116
- *
117
- * @param name
118
- */
119
- hasSound(name: string): boolean;
120
- /**
121
- * Register a new sound at runtime. Replaces any existing sound with the same name.
122
- *
123
- * @param name
124
- * @param config
125
- */
126
- registerSound(name: string, config: SoundConfig): void;
127
- /**
128
- * Unregister and dispose a sound.
129
- *
130
- * @param name
131
- */
132
- unregisterSound(name: string): void;
133
- /**
134
- * Get a sound by name. If name is omitted and only one sound exists, returns that sound.
135
- */
136
- private _getSound;
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;
137
22
  }
@@ -14,6 +14,7 @@ export declare class DebugConsole implements Disposable {
14
14
  $teardown(): Promise<void>;
15
15
  private _isDynamicGetter;
16
16
  private _getNamespace;
17
+ private _assertNamespaceFree;
17
18
  private _installNamespace;
18
19
  private _removeNamespace;
19
20
  private _defineGetter;
@@ -1,28 +1,28 @@
1
- import { AbstractEngine, HavokPlugin } from '@babylonjs/core';
2
- import { GameCanvas } from "../interfaces/game.d.ts";
1
+ import { AbstractEngine, HavokPlugin, Scene } from '@babylonjs/core';
2
+ import { EntityMeshLoader, GameCanvas } from "../interfaces/game.d.ts";
3
3
  import { Disposable } from "../interfaces/lifecycle.d.ts";
4
+ import { NodeSimulation } from "../entities/nodeSimulation.d.ts";
4
5
  import { SceneEngine } from "../engines/scene.d.ts";
5
6
  import { AudioEngine } from "../engines/audio.d.ts";
6
7
  import { AssetManager } from "../managers/asset.d.ts";
7
- import { BindingManager } from "../managers/binding.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 { GameEntityManager } from "../managers/entity.d.ts";
11
+ import { InputManager } from "../managers/input.d.ts";
12
12
  import { LevelManager } from "../managers/level.d.ts";
13
- import { SaveManager } from "../managers/save.d.ts";
14
- import { UserInputManager } from "../managers/input.d.ts";
13
+ import { SimulationSaveManager } from "../managers/simulationSave.d.ts";
15
14
  import { AudioManager } from "../managers/audio.d.ts";
16
- import { GameEvent, GameEventBus, GameEventCallback, Unsubscribe } from "./eventBus.d.ts";
17
- import { ActionPayload, LibraryEventPayloadMap } from "../events/payloads.d.ts";
15
+ import { GameEventBus, GameEventCallback, Unsubscribe } from "./eventBus.d.ts";
16
+ import { LibraryEventPayloadMap } from "../events/payloads.d.ts";
18
17
  import { WildcardPattern } from "../events/types.d.ts";
19
18
  import { LoggingUtility } from "../utils/logger.d.ts";
20
- import { RaycastHelper } from "../utils/raycast.d.ts";
21
19
  import { GameTimer } from "../utils/timer.d.ts";
22
20
  /**
23
21
  * Type definition for game lifecycle hook functions
24
22
  */
25
23
  export type GameHook = (gameEngine: GameEngine) => Promise<void>;
24
+ /** Builds a NodeSimulation bound to a scene and a mesh source -- a fresh one is built for every level that loads. */
25
+ export type SimulationFactory = (scene: Scene, meshSource?: EntityMeshLoader) => NodeSimulation;
26
26
  /**
27
27
  * Interface representing the engines used in the game.
28
28
  * All engines must implement Disposable for proper cleanup.
@@ -37,12 +37,10 @@ interface Engines extends Record<string, Disposable | undefined> {
37
37
  */
38
38
  interface Managers extends Record<string, Disposable | undefined> {
39
39
  assetManager: AssetManager;
40
- bindingManager: BindingManager;
41
40
  gameManager: GameManager;
42
- entityManager: GameEntityManager;
43
- inputManager: UserInputManager;
41
+ inputManager: InputManager;
44
42
  levelManager: LevelManager;
45
- saveManager: SaveManager;
43
+ saveManager: SimulationSaveManager;
46
44
  audioManager?: AudioManager;
47
45
  }
48
46
  /**
@@ -65,12 +63,14 @@ export declare class GameEngine {
65
63
  engines: Engines;
66
64
  eventBus: GameEventBus;
67
65
  logger: LoggingUtility;
68
- raycast: RaycastHelper;
66
+ simulation: NodeSimulation | null;
69
67
  timer: GameTimer;
70
68
  debug: DebugTools;
71
69
  largeWorldRendering: boolean;
72
70
  started: boolean;
71
+ private _stopped;
73
72
  private _log;
73
+ private _buildSimulation;
74
74
  private _beforeStartHook;
75
75
  private _onStartHook;
76
76
  private _onTeardownHook;
@@ -81,10 +81,18 @@ export declare class GameEngine {
81
81
  * @param physics
82
82
  * @param eventBus
83
83
  * @param logger
84
+ * @param timer
85
+ * @param buildSimulation - Builds a fresh simulation for a scene, once rebuildSimulation is called with one.
84
86
  * @param engines
85
87
  * @param managers
86
88
  */
87
- constructor(canvas: GameCanvas, renderEngine: AbstractEngine, physics: HavokPlugin, eventBus: GameEventBus, logger: LoggingUtility, raycast: RaycastHelper, timer: GameTimer, engines: Engines, managers: Managers, largeWorldRendering?: boolean, debug?: DebugTools);
89
+ constructor(canvas: GameCanvas, renderEngine: AbstractEngine, physics: HavokPlugin, eventBus: GameEventBus, logger: LoggingUtility, timer: GameTimer, buildSimulation: SimulationFactory, engines: Engines, managers: Managers, largeWorldRendering?: boolean, debug?: DebugTools);
90
+ /**
91
+ * Builds a fresh NodeSimulation bound to the given scene and mesh source, and swaps it in. A level calls
92
+ * this once its own scene is ready, so every NodeEntity it spawns gets a node in that scene and loads its
93
+ * mesh through that source. Left unset, a spawned entity gets a node with no mesh.
94
+ */
95
+ rebuildSimulation(scene: Scene, meshSource?: EntityMeshLoader): NodeSimulation;
88
96
  /**
89
97
  * Register a function to be called before the game engine starts
90
98
  * @param hook
@@ -108,7 +116,7 @@ export declare class GameEngine {
108
116
  *
109
117
  * This is a convenience method that forwards to `eventBus.subscribe()`.
110
118
  *
111
- * @param eventType - Exact event type string, wildcard pattern (e.g. 'input:*'), or RegExp
119
+ * @param eventType - Exact event type string, wildcard pattern (e.g. 'level:*'), or RegExp
112
120
  * @param callback
113
121
  * @returns A function that removes this subscription when called
114
122
  *
@@ -118,45 +126,22 @@ export declare class GameEngine {
118
126
  * console.log('Level complete:', event.payload.levelName);
119
127
  * });
120
128
  *
121
- * // Subscribe to all input events
122
- * engine.subscribe('input:*', (event) => {
123
- * console.log('Input event:', event.type);
129
+ * // Subscribe to every level event
130
+ * engine.subscribe('level:*', (event) => {
131
+ * console.log('Level event:', event.type);
124
132
  * });
125
133
  */
126
134
  subscribe<T extends keyof LibraryEventPayloadMap & string>(eventType: T | WildcardPattern | RegExp, callback: GameEventCallback<T, LibraryEventPayloadMap>): Unsubscribe;
127
- /**
128
- * Subscribe to an action event with full type safety.
129
- *
130
- * Action events are fired by the binding system when inputs trigger registered actions.
131
- * All action events have the same payload structure (`ActionPayload`), containing:
132
- * - `value`: The action value (number for analog, boolean for digital)
133
- * - `deviceId`: The ID of the input device that triggered the action
134
- * - `context`: The binding context that was active (optional)
135
- *
136
- * @param actionName - The action name (without the 'action:' prefix)
137
- * @param callback
138
- * @returns A function that removes this subscription when called
139
- *
140
- * @example
141
- * // Subscribe to a specific action
142
- * engine.subscribeAction('jump', (event) => {
143
- * console.log('Jump triggered with value:', event.payload.value);
144
- * console.log('From device:', event.payload.deviceId);
145
- * });
146
- *
147
- * // Subscribe to movement action
148
- * engine.subscribeAction('moveForward', (event) => {
149
- * const velocity = Number(event.payload.value);
150
- * player.move(velocity);
151
- * });
152
- */
153
- subscribeAction(actionName: string, callback: (event: GameEvent<`action:${string}`, Record<`action:${string}`, ActionPayload>>) => void): Unsubscribe;
154
135
  /**
155
136
  * Start the engine: runs beforeStart hook, starts the game manager, then runs onStart hook.
156
137
  */
157
138
  start(): Promise<void>;
158
139
  /**
159
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.
160
145
  */
161
146
  stop(): Promise<void>;
162
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
- import { GameEntity } from "./entity.d.ts";
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.
@@ -33,7 +34,7 @@ export declare class GameLevel extends Level {
33
34
  /** Collected entity nodes from the scene */
34
35
  protected _entityNodes: EntityNodeData[];
35
36
  /** Entities spawned by this level */
36
- protected _spawnedEntities: GameEntity[];
37
+ protected _spawnedEntities: NodeEntity[];
37
38
  /** Level-scoped sounds created from config */
38
39
  protected _levelSounds: Map<string, StaticSound>;
39
40
  /** Sounds that were playing before deactivation (for resume on activate) */
@@ -46,7 +47,12 @@ export declare class GameLevel extends Level {
46
47
  */
47
48
  constructor(config: LevelConfig, context: LevelContext);
48
49
  get config(): LevelConfig;
49
- get spawnedEntities(): readonly GameEntity[];
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
  */
@@ -124,22 +130,25 @@ export declare class GameLevel extends Level {
124
130
  */
125
131
  private _processSpawnPoint;
126
132
  /**
127
- * Apply debug collider visualization to an entity if configured.
128
- * Level/spawn config takes priority over the entity definition's debugCollider.
129
- */
130
- private _applyDebugCollider;
131
- /**
132
- * Create an entity from a spawn definition, inheriting position/rotation/scaling from the spawn point.
133
+ * Spawn an entity through the simulation, inheriting position and rotation from the spawn point. The node
134
+ * and its mesh are the simulation's own business, built by NodeEntity's constructor from the class name and
135
+ * state -- the level does no mesh building of its own.
136
+ *
137
+ * A spawn point's scaling has nowhere to go: NodeEntityState declares position and rotation, not scaling,
138
+ * and inventing a state field for it is not this level's call to make. It is dropped.
133
139
  */
134
140
  private _spawnEntity;
135
141
  /**
136
- * Create game entities for all nodes marked with the 'entity' metadata property.
142
+ * Match collected entity nodes against the level config's entity definitions and spawn entities adopting
143
+ * their marked node.
137
144
  */
138
145
  private _processEntityNodes;
139
146
  /**
140
- * Create a game entity for an entity-tagged node, attaching the existing scene node to it.
147
+ * Spawn an entity adopting an entity marker's node. `nodeName` is the marked node's own name, written by
148
+ * the level rather than by configuration, since it is the binding key adoption resolves through
149
+ * (design/authoring.md#the-binding-key).
141
150
  */
142
- private _processEntityNode;
151
+ private _spawnEntityNode;
143
152
  /**
144
153
  * Create level-scoped sounds from YAML config. Sounds are tracked for lifecycle management
145
154
  * (pause on deactivate, resume on activate, dispose on unload).
@@ -1,28 +1,15 @@
1
- import { Color3, Quaternion, Scene, TransformNode, Vector3 } from '@babylonjs/core';
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
- import { ColorConfig, LevelConfig, LevelContext, LevelInstance, PropertyHandler } from "../interfaces/level.d.ts";
5
- import { GameEntity } from "./entity.d.ts";
5
+ import { ColorConfig, LevelConfig, LevelContext, LevelInstance, LevelLoadOptions, PropertyHandler } from "../interfaces/level.d.ts";
6
6
  import { GameEngine } from "./gameEngine.d.ts";
7
7
  import { OutlineManager } from "../managers/outline.d.ts";
8
- /**
9
- * Transform and visibility overrides applied to a mesh built by `Level.createEntityMesh()`.
10
- */
11
- export interface CreateEntityMeshOptions {
12
- /** World position for the entity's root node. Left at the node default when omitted. */
13
- position?: Vector3;
14
- /** World rotation for the entity's root node. Left at the node default when omitted. */
15
- rotation?: Quaternion;
16
- /** Scaling for the entity's root node. Left at the node default when omitted. */
17
- scaling?: Vector3;
18
- /** When false, the node is disabled immediately on creation so nothing ever renders. Default true. */
19
- enabled?: boolean;
20
- }
21
8
  /**
22
9
  * Abstract base class for game levels.
23
10
  *
24
11
  * Levels are responsible for creating and configuring their own scenes via the
25
- * `buildScene()` method. All runtime dependencies (eventBus, sceneEngine, entityManager)
12
+ * `buildScene()` method. All runtime dependencies (eventBus, sceneEngine, simulation)
26
13
  * are injected via the constructor and are immediately available.
27
14
  *
28
15
  * Users should not instantiate Level subclasses directly. Instead, use LevelManager
@@ -41,6 +28,13 @@ export declare abstract class Level implements LevelInstance {
41
28
  protected readonly _log: LoggerInterface;
42
29
  protected readonly _context: LevelContext;
43
30
  protected _scene: Scene | null;
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;
44
38
  clusteredLights: ClusteredLightContainer | null;
45
39
  outlines: OutlineManager | null;
46
40
  /**
@@ -60,6 +54,18 @@ export declare abstract class Level implements LevelInstance {
60
54
  * @param progress - Percentage (0-100)
61
55
  * @param message
62
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;
63
69
  protected $emitProgress(progress: number, message: string): void;
64
70
  protected _toColor3(color: ColorConfig): Color3;
65
71
  /**
@@ -77,18 +83,7 @@ export declare abstract class Level implements LevelInstance {
77
83
  * Load and create the scene for this level.
78
84
  * Progress events will be emitted during loading.
79
85
  */
80
- load(): Promise<Scene>;
81
- /**
82
- * Build or import a mesh from the entity definition and attach it to the entity.
83
- * Supports primitive shapes (box/sphere/capsule/cylinder) and GLB/GLTF file paths.
84
- *
85
- * @param entity - The entity whose definition supplies the mesh spec
86
- * @param options - Transform overrides and initial enabled state
87
- *
88
- * @returns The root node the mesh was built under, or null if the level has no scene, the definition has no
89
- * mesh spec, or the GLB import produced no meshes.
90
- */
91
- createEntityMesh(entity: GameEntity, options?: CreateEntityMeshOptions): Promise<TransformNode | null>;
86
+ load(options?: LevelLoadOptions): Promise<Scene>;
92
87
  /**
93
88
  * Abstract method for building the scene content.
94
89
  * Concrete levels must implement this to create their specific content.
@@ -0,0 +1 @@
1
+ export declare const poolSizeLimit = 8;
@@ -0,0 +1,17 @@
1
+ import { Action, BehaviorClass, StateFor, ValidTuple } from '@skewedaspect/sage-core';
2
+ import { NodeEntity } from "./nodeEntity.d.ts";
3
+ import { MeshConfig } from "./meshConfig.d.ts";
4
+ export interface GameEntityDefinition<Behaviors extends readonly BehaviorClass[]> {
5
+ type: string;
6
+ tags?: string[];
7
+ defaultState?: Partial<StateFor<Behaviors>>;
8
+ behaviors: Behaviors;
9
+ mesh?: MeshConfig;
10
+ actions?: readonly Action[];
11
+ }
12
+ export type GameEntityDefinitionClass<Behaviors extends readonly BehaviorClass[]> = typeof NodeEntity & {
13
+ readonly behaviors: Behaviors;
14
+ };
15
+ export declare function defineEntity<Behaviors extends readonly BehaviorClass[]>(definition: GameEntityDefinition<Behaviors> & {
16
+ behaviors: ValidTuple<Behaviors>;
17
+ }): GameEntityDefinitionClass<Behaviors>;
@@ -0,0 +1,15 @@
1
+ import { Vector3, AbstractMesh, PickingInfo } from '@babylonjs/core';
2
+ import { GameEntity } from '@skewedaspect/sage-core';
3
+ import { NodeEntity } from "./nodeEntity.d.ts";
4
+ export interface EntityPickFilter {
5
+ tags?: readonly string[];
6
+ }
7
+ export interface EntityPickResult {
8
+ entity: NodeEntity;
9
+ mesh: AbstractMesh;
10
+ point: Vector3;
11
+ distance: number;
12
+ normal: Vector3;
13
+ }
14
+ export declare function findEntityForMesh(entities: readonly GameEntity[], mesh: AbstractMesh): NodeEntity | undefined;
15
+ export declare function toEntityPickResult(entities: readonly GameEntity[], info: PickingInfo | null, filter?: EntityPickFilter): EntityPickResult | undefined;
@@ -0,0 +1,14 @@
1
+ import { ColorConfig } from "../interfaces/level.d.ts";
2
+ export interface MeshMaterialConfig {
3
+ type?: 'standard' | 'pbr';
4
+ color?: ColorConfig;
5
+ emissive?: ColorConfig;
6
+ metallic?: number;
7
+ roughness?: number;
8
+ }
9
+ export interface MeshConfig {
10
+ source: string;
11
+ params?: Record<string, unknown>;
12
+ material?: MeshMaterialConfig;
13
+ visible?: boolean;
14
+ }
@@ -0,0 +1,43 @@
1
+ import { TransformNode, AbstractMesh } from '@babylonjs/core';
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";
5
+ import { MeshConfig } from "./meshConfig.d.ts";
6
+ export interface Vector3State {
7
+ x: number;
8
+ y: number;
9
+ z: number;
10
+ }
11
+ export interface QuaternionState {
12
+ x: number;
13
+ y: number;
14
+ z: number;
15
+ w: number;
16
+ }
17
+ export interface NodeEntityState extends GameEntityState {
18
+ parentID?: string;
19
+ nodeName?: string;
20
+ position: Vector3State;
21
+ rotation: QuaternionState;
22
+ debugCollider?: ColliderDebugConfig;
23
+ }
24
+ export interface NodeBehaviorHooks {
25
+ onMeshLoaded?(): void;
26
+ onDestroy?(): void;
27
+ }
28
+ export declare abstract class NodeEntity extends GameEntity {
29
+ #private;
30
+ static readonly poolable: boolean;
31
+ static readonly poolSize?: number;
32
+ static readonly meshConfig?: MeshConfig;
33
+ static readonly actions?: readonly Action[];
34
+ get node(): TransformNode;
35
+ get mesh(): AbstractMesh | undefined;
36
+ set mesh(mesh: AbstractMesh | undefined);
37
+ get audio(): AudioManager | undefined;
38
+ showCollider(config?: ColliderDebugConfig, color?: string): void;
39
+ hideCollider(name?: string): void;
40
+ protected constructor(id: string, context: GameSimulationContext, providedState: object, runCreateHooks: boolean, name?: string);
41
+ update(dt: number): void;
42
+ destroy(): void;
43
+ }
@@ -0,0 +1,8 @@
1
+ import { NodeSet } from "./nodeSimulationContext.d.ts";
2
+ export declare class NodePool {
3
+ #private;
4
+ take(name: string): NodeSet | undefined;
5
+ give(name: string, set: NodeSet, limit?: number): void;
6
+ clear(name: string): void;
7
+ clearAll(): void;
8
+ }
@@ -0,0 +1,30 @@
1
+ import { GameSimulationManager, GameSimulationOptions, StateBag } from '@skewedaspect/sage-core';
2
+ import { AbstractMesh, Camera, Ray, Scene } from '@babylonjs/core';
3
+ import { EntityPickFilter, EntityPickResult } from "./entityPicking.d.ts";
4
+ import { NodeEntity } from "./nodeEntity.d.ts";
5
+ import { NodePool } from "./nodePool.d.ts";
6
+ import { MeshLoader, NodeSimulationContext } from "./nodeSimulationContext.d.ts";
7
+ import { AudioManager } from "../managers/audio.d.ts";
8
+ import { ColliderDebugManager } from "../managers/colliderDebug.d.ts";
9
+ export interface NodeSimulationOptions extends GameSimulationOptions {
10
+ scene: Scene;
11
+ loadMesh: MeshLoader;
12
+ pool: NodePool;
13
+ audio?: AudioManager;
14
+ colliders: ColliderDebugManager;
15
+ }
16
+ export declare class NodeSimulation extends GameSimulationManager implements NodeSimulationContext {
17
+ #private;
18
+ readonly scene: Scene;
19
+ readonly pool: NodePool;
20
+ readonly loadMesh: MeshLoader;
21
+ readonly audio: AudioManager | undefined;
22
+ readonly colliders: ColliderDebugManager;
23
+ constructor(options: NodeSimulationOptions);
24
+ prewarm(name: string, count: number, state?: StateBag): Promise<void>;
25
+ pickEntityWithRay(ray: Ray, filter?: EntityPickFilter): EntityPickResult | undefined;
26
+ pickEntity(x: number, y: number, filter?: EntityPickFilter): EntityPickResult | undefined;
27
+ pickEntityForward(camera: Camera, range: number, filter?: EntityPickFilter): EntityPickResult | undefined;
28
+ getEntityForMesh(mesh: AbstractMesh): NodeEntity | undefined;
29
+ protected buildReplacementSimulation(options: GameSimulationOptions): NodeSimulation;
30
+ }
@@ -0,0 +1,17 @@
1
+ import { AbstractMesh, Scene, TransformNode } from '@babylonjs/core';
2
+ import { GameSimulationContext, StateBag } from '@skewedaspect/sage-core';
3
+ import { AudioManager } from "../managers/audio.d.ts";
4
+ import { ColliderDebugManager } from "../managers/colliderDebug.d.ts";
5
+ import { NodePool } from "./nodePool.d.ts";
6
+ export interface NodeSet {
7
+ node: TransformNode;
8
+ mesh: AbstractMesh | undefined;
9
+ }
10
+ export type MeshLoader = (name: string, state: Readonly<StateBag>) => Promise<AbstractMesh | undefined>;
11
+ export interface NodeSimulationContext extends GameSimulationContext {
12
+ readonly scene: Scene;
13
+ readonly pool: NodePool;
14
+ readonly loadMesh: MeshLoader;
15
+ readonly audio?: AudioManager;
16
+ readonly colliders: ColliderDebugManager;
17
+ }
@@ -0,0 +1,8 @@
1
+ import { AbstractMesh, AssetContainer, Mesh } from '@babylonjs/core';
2
+ import { GameEntityType } from '@skewedaspect/sage-core';
3
+ import { EntityMeshLoader } from "../interfaces/game.d.ts";
4
+ export interface MeshAssetSource {
5
+ load(path: string): Promise<AssetContainer | AbstractMesh>;
6
+ clone(path: string): Mesh;
7
+ }
8
+ export declare function buildStandardMeshSource(entityClasses: Readonly<Record<string, GameEntityType>>, assets: MeshAssetSource): EntityMeshLoader;