ecspresso 0.11.0 → 0.12.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.
Files changed (96) hide show
  1. package/README.md +200 -148
  2. package/dist/asset-manager.d.ts +1 -1
  3. package/dist/asset-types.d.ts +2 -2
  4. package/dist/command-buffer.d.ts +34 -24
  5. package/dist/ecspresso-builder.d.ts +100 -72
  6. package/dist/ecspresso.d.ts +257 -122
  7. package/dist/entity-manager.d.ts +57 -47
  8. package/dist/index.d.ts +5 -4
  9. package/dist/plugin.d.ts +61 -0
  10. package/dist/{bundles → plugins}/audio.d.ts +27 -47
  11. package/dist/{bundles → plugins}/bounds.d.ts +17 -25
  12. package/dist/{bundles → plugins}/camera.d.ts +8 -9
  13. package/dist/{bundles → plugins}/collision.d.ts +22 -26
  14. package/dist/plugins/coroutine.d.ts +126 -0
  15. package/dist/{bundles → plugins}/diagnostics.d.ts +5 -4
  16. package/dist/{bundles → plugins}/input.d.ts +9 -15
  17. package/dist/plugins/particles.d.ts +225 -0
  18. package/dist/{bundles → plugins}/physics2D.d.ts +27 -23
  19. package/dist/{bundles → plugins}/renderers/renderer2D.d.ts +40 -39
  20. package/dist/{bundles → plugins}/spatial-index.d.ts +11 -10
  21. package/dist/plugins/sprite-animation.d.ts +150 -0
  22. package/dist/{bundles → plugins}/state-machine.d.ts +50 -104
  23. package/dist/plugins/timers.d.ts +151 -0
  24. package/dist/{bundles → plugins}/transform.d.ts +18 -19
  25. package/dist/{bundles → plugins}/tween.d.ts +36 -71
  26. package/dist/resource-manager.d.ts +32 -7
  27. package/dist/screen-manager.d.ts +17 -11
  28. package/dist/screen-types.d.ts +5 -2
  29. package/dist/src/index.js +2 -2
  30. package/dist/src/index.js.map +17 -17
  31. package/dist/src/plugins/audio.js +4 -0
  32. package/dist/src/plugins/audio.js.map +10 -0
  33. package/dist/src/plugins/bounds.js +4 -0
  34. package/dist/src/plugins/bounds.js.map +10 -0
  35. package/dist/src/plugins/camera.js +4 -0
  36. package/dist/src/plugins/camera.js.map +10 -0
  37. package/dist/src/plugins/collision.js +4 -0
  38. package/dist/src/plugins/collision.js.map +11 -0
  39. package/dist/src/plugins/coroutine.js +4 -0
  40. package/dist/src/plugins/coroutine.js.map +10 -0
  41. package/dist/src/plugins/diagnostics.js +5 -0
  42. package/dist/src/plugins/diagnostics.js.map +10 -0
  43. package/dist/src/plugins/input.js +4 -0
  44. package/dist/src/plugins/input.js.map +10 -0
  45. package/dist/src/plugins/particles.js +4 -0
  46. package/dist/src/plugins/particles.js.map +10 -0
  47. package/dist/src/plugins/physics2D.js +4 -0
  48. package/dist/src/plugins/physics2D.js.map +11 -0
  49. package/dist/src/plugins/renderers/renderer2D.js +4 -0
  50. package/dist/src/plugins/renderers/renderer2D.js.map +10 -0
  51. package/dist/src/plugins/spatial-index.js +4 -0
  52. package/dist/src/plugins/spatial-index.js.map +11 -0
  53. package/dist/src/plugins/sprite-animation.js +4 -0
  54. package/dist/src/plugins/sprite-animation.js.map +10 -0
  55. package/dist/src/plugins/state-machine.js +4 -0
  56. package/dist/src/plugins/state-machine.js.map +10 -0
  57. package/dist/src/plugins/timers.js +4 -0
  58. package/dist/src/plugins/timers.js.map +10 -0
  59. package/dist/src/plugins/transform.js +4 -0
  60. package/dist/src/plugins/transform.js.map +10 -0
  61. package/dist/src/plugins/tween.js +4 -0
  62. package/dist/src/plugins/tween.js.map +11 -0
  63. package/dist/system-builder.d.ts +66 -97
  64. package/dist/type-utils.d.ts +218 -27
  65. package/dist/types.d.ts +52 -24
  66. package/dist/utils/check-required-cycle.d.ts +1 -1
  67. package/dist/utils/narrowphase.d.ts +7 -7
  68. package/package.json +53 -45
  69. package/dist/bundle.d.ts +0 -173
  70. package/dist/bundles/timers.d.ts +0 -173
  71. package/dist/src/bundles/audio.js +0 -4
  72. package/dist/src/bundles/audio.js.map +0 -10
  73. package/dist/src/bundles/bounds.js +0 -4
  74. package/dist/src/bundles/bounds.js.map +0 -10
  75. package/dist/src/bundles/camera.js +0 -4
  76. package/dist/src/bundles/camera.js.map +0 -10
  77. package/dist/src/bundles/collision.js +0 -4
  78. package/dist/src/bundles/collision.js.map +0 -11
  79. package/dist/src/bundles/diagnostics.js +0 -5
  80. package/dist/src/bundles/diagnostics.js.map +0 -10
  81. package/dist/src/bundles/input.js +0 -4
  82. package/dist/src/bundles/input.js.map +0 -10
  83. package/dist/src/bundles/physics2D.js +0 -4
  84. package/dist/src/bundles/physics2D.js.map +0 -11
  85. package/dist/src/bundles/renderers/renderer2D.js +0 -4
  86. package/dist/src/bundles/renderers/renderer2D.js.map +0 -10
  87. package/dist/src/bundles/spatial-index.js +0 -4
  88. package/dist/src/bundles/spatial-index.js.map +0 -11
  89. package/dist/src/bundles/state-machine.js +0 -4
  90. package/dist/src/bundles/state-machine.js.map +0 -10
  91. package/dist/src/bundles/timers.js +0 -4
  92. package/dist/src/bundles/timers.js.map +0 -10
  93. package/dist/src/bundles/transform.js +0 -4
  94. package/dist/src/bundles/transform.js.map +0 -10
  95. package/dist/src/bundles/tween.js +0 -4
  96. package/dist/src/bundles/tween.js.map +0 -11
@@ -1,30 +1,33 @@
1
1
  import EntityManager from "./entity-manager";
2
2
  import EventBus from "./event-bus";
3
- import { type ResourceFactoryWithDeps } from "./resource-manager";
3
+ import { type ResourceFactoryWithDeps, type ResourceDirectValue } from "./resource-manager";
4
4
  import AssetManager from "./asset-manager";
5
5
  import ScreenManager from "./screen-manager";
6
6
  import { type ReactiveQueryDefinition } from "./reactive-query-manager";
7
7
  import CommandBuffer from "./command-buffer";
8
8
  import type { System, SystemPhase, FilteredEntity, Entity, RemoveEntityOptions, HierarchyEntry, HierarchyIteratorOptions } from "./types";
9
- import type Bundle from "./bundle";
10
- import type { AssetHandle } from "./asset-types";
9
+ import { type Plugin } from "./plugin";
10
+ import { SystemBuilder } from "./system-builder";
11
+ import type { AssetDefinition, AssetHandle } from "./asset-types";
11
12
  import type { ScreenDefinition } from "./screen-types";
12
13
  import { ECSpressoBuilder } from "./ecspresso-builder";
14
+ import type { WorldConfig, EmptyConfig } from "./type-utils";
13
15
  /**
14
16
  * Interface declaration for ECSpresso constructor to ensure type augmentation works properly.
15
17
  * This merges with the class declaration below.
16
18
  */
17
- export default interface ECSpresso<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, ScreenDefinition<any, any>> = {}, Labels extends string = string, Groups extends string = string, AssetGroupNames extends string = string, ReactiveQueryNames extends string = string> {
19
+ export default interface ECSpresso<Cfg extends WorldConfig = EmptyConfig, Labels extends string = string, Groups extends string = string, AssetGroupNames extends string = string, ReactiveQueryNames extends string = string> {
18
20
  /**
19
21
  * Default constructor
20
22
  */
21
- new (): ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, Labels, Groups, AssetGroupNames, ReactiveQueryNames>;
23
+ new (): ECSpresso<Cfg, Labels, Groups, AssetGroupNames, ReactiveQueryNames>;
22
24
  }
23
25
  /**
24
26
  * ECSpresso is the central ECS framework class that connects all features.
25
27
  * It handles creation and management of entities, components, and systems, and provides lifecycle hooks.
26
28
  */
27
- export default class ECSpresso<ComponentTypes extends Record<string, any> = {}, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, ScreenDefinition<any, any>> = {}, Labels extends string = string, Groups extends string = string, AssetGroupNames extends string = string, ReactiveQueryNames extends string = string> {
29
+ export default class ECSpresso<Cfg extends WorldConfig = EmptyConfig, Labels extends string = string, Groups extends string = string, AssetGroupNames extends string = string, ReactiveQueryNames extends string = string> {
30
+ readonly _cfg: Cfg;
28
31
  /** Library version*/
29
32
  static readonly VERSION: string;
30
33
  /** Access/modify stored components and entities*/
@@ -39,8 +42,8 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
39
42
  private _systems;
40
43
  /** Systems grouped by execution phase, each sorted by priority */
41
44
  private _phaseSystems;
42
- /** Track installed bundles to prevent duplicates*/
43
- private _installedBundles;
45
+ /** Track installed plugins to prevent duplicates*/
46
+ private _installedPlugins;
44
47
  /** Disabled system groups */
45
48
  private _disabledGroups;
46
49
  /** Asset manager for loading and accessing assets */
@@ -67,16 +70,25 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
67
70
  private _maxFixedSteps;
68
71
  /** Registry of required component relationships: trigger -> [{component, factory}] */
69
72
  private _requiredComponents;
70
- /** Pending bundle assets awaiting manager creation at build time */
71
- private _pendingBundleAssets;
72
- /** Pending bundle screens awaiting manager creation at build time */
73
- private _pendingBundleScreens;
73
+ /** Pending plugin assets awaiting manager creation at build time */
74
+ private _pendingPluginAssets;
75
+ /** Pending plugin screens awaiting manager creation at build time */
76
+ private _pendingPluginScreens;
74
77
  /** Whether diagnostics timing collection is enabled */
75
78
  private _diagnosticsEnabled;
76
79
  /** Per-system timing in ms, populated when diagnostics enabled */
77
80
  private _systemTimings;
78
81
  /** Per-phase timing in ms, populated when diagnostics enabled */
79
82
  private _phaseTimings;
83
+ /** Per-system per-query seen entity IDs for onEntityEnter tracking */
84
+ private _entityEnterTracking;
85
+ /** Shared reusable set for per-tick entity enter comparison (avoids allocation) */
86
+ private _entityEnterFrameSet;
87
+ /** Pre-allocated process context per system (avoids per-frame allocation) */
88
+ private _systemContexts;
89
+ /** Pending system builder finalizers to run before next update/initialize */
90
+ private _pendingFinalizers;
91
+ private _batchingRegistrations;
80
92
  /**
81
93
  * Creates a new ECSpresso instance.
82
94
  */
@@ -88,9 +100,8 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
88
100
  */
89
101
  private _subscribeLifecycleHooks;
90
102
  /**
91
- * Creates a new ECSpresso builder for type-safe bundle installation.
92
- * This is the preferred way to create an ECSpresso instance with bundles.
93
- * Types are inferred from the builder chain — use `.withBundle()`,
103
+ * Creates a new ECSpresso builder for type-safe plugin installation.
104
+ * Types are inferred from the builder chain use `.withPlugin()`,
94
105
  * `.withComponentTypes<T>()`, `.withEventTypes<T>()`, and `.withResource()`
95
106
  * to accumulate types without manual aggregate interfaces.
96
107
  *
@@ -99,8 +110,8 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
99
110
  * @example
100
111
  * ```typescript
101
112
  * const ecs = ECSpresso.create()
102
- * .withBundle(createRenderer2DBundle({ ... }))
103
- * .withBundle(createPhysics2DBundle())
113
+ * .withPlugin(createRenderer2DPlugin({ ... }))
114
+ * .withPlugin(createPhysics2DPlugin())
104
115
  * .withComponentTypes<{ player: true; enemy: { type: string } }>()
105
116
  * .withEventTypes<{ gameStart: true }>()
106
117
  * .withResource('score', { value: 0 })
@@ -109,13 +120,19 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
109
120
  * type ECS = typeof ecs;
110
121
  * ```
111
122
  */
112
- static create<C extends Record<string, any> = {}, E extends Record<string, any> = {}, R extends Record<string, any> = {}, A extends Record<string, unknown> = {}, S extends Record<string, ScreenDefinition<any, any>> = {}>(): ECSpressoBuilder<C, E, R, A, S, never, never, never, never>;
123
+ static create<Cfg2 extends WorldConfig = EmptyConfig>(): ECSpressoBuilder<Cfg2, never, never, never, never>;
113
124
  /**
114
- * Adds a system directly to this ECSpresso instance
125
+ * Adds a system directly to this ECSpresso instance.
126
+ * The system is registered when initialize() or update() is next called.
115
127
  * @param label Unique name to identify the system
116
128
  * @returns A SystemBuilder instance for method chaining
117
129
  */
118
- addSystem(label: string): import("./system-builder").SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates, {}, string, never>;
130
+ addSystem(label: string): SystemBuilder<Cfg>;
131
+ /**
132
+ * Finalize and register all pending system builders.
133
+ * @private
134
+ */
135
+ private _finalizePendingBuilders;
119
136
  /**
120
137
  * Update all systems across execution phases.
121
138
  * Phases run in order: preUpdate -> fixedUpdate -> update -> postUpdate -> render.
@@ -154,7 +171,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
154
171
  * @param keys Optional array of resource keys to initialize. If not provided, all pending resources will be initialized.
155
172
  * @returns Promise that resolves when the specified resources are initialized
156
173
  */
157
- initializeResources<K extends keyof ResourceTypes>(...keys: K[]): Promise<void>;
174
+ initializeResources<K extends keyof Cfg['resources']>(...keys: K[]): Promise<void>;
158
175
  /**
159
176
  * Rebuild per-phase system arrays from the flat _systems list.
160
177
  * Each phase array is sorted by priority (higher first), with
@@ -220,11 +237,11 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
220
237
  * Internal method to register a system with this ECSpresso instance
221
238
  * @internal Used by SystemBuilder - replaces direct private property access
222
239
  */
223
- _registerSystem(system: System<ComponentTypes, any, any, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
240
+ _registerSystem(system: System<Cfg, any, any>): void;
224
241
  /**
225
242
  * Check if a resource exists
226
243
  */
227
- hasResource<K extends keyof ResourceTypes>(key: K): boolean;
244
+ hasResource<K extends keyof Cfg['resources']>(key: K): boolean;
228
245
  /**
229
246
  * Get a resource by key. Throws if the resource is not found.
230
247
  * @param key The resource key
@@ -232,42 +249,47 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
232
249
  * @throws Error if resource not found
233
250
  * @see tryGetResource — the non-throwing alternative that returns undefined
234
251
  */
235
- getResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K];
252
+ getResource<K extends keyof Cfg['resources']>(key: K): Cfg['resources'][K];
236
253
  /**
237
254
  * Try to get a resource by key. Returns undefined if the resource is not found.
238
255
  * Inspired by Bevy's `World::get_resource::<T>()` which returns `Option<&T>`.
239
256
  *
240
257
  * Two overloads:
241
258
  * 1. Known key — full type safety from `ResourceTypes`
242
- * 2. String key with explicit type param — for cross-bundle optional dependencies
259
+ * 2. String key with explicit type param — for cross-plugin optional dependencies
243
260
  *
244
261
  * @example
245
262
  * ```typescript
246
263
  * // Known key (type inferred from ResourceTypes)
247
264
  * const score = ecs.tryGetResource('score'); // ScoreResource | undefined
248
265
  *
249
- * // Cross-bundle optional dependency (caller specifies expected type)
266
+ * // Cross-plugin optional dependency (caller specifies expected type)
250
267
  * const si = ecs.tryGetResource<SpatialIndex>('spatialIndex') ?? null;
251
268
  * ```
252
269
  */
253
- tryGetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] | undefined;
270
+ tryGetResource<K extends keyof Cfg['resources']>(key: K): Cfg['resources'][K] | undefined;
254
271
  tryGetResource<T>(key: unknown extends T ? never : string): T | undefined;
255
272
  /**
256
- * Add a resource to the ECS instance
273
+ * Add a resource to the ECS instance.
274
+ *
275
+ * - Plain value → stored directly
276
+ * - Function → treated as a factory, called with this ECSpresso instance on first access
277
+ * - `{ factory, dependsOn?, onDispose? }` → factory with dependencies/disposal
278
+ * - `directValue(val)` → stores the value as-is (use to store functions/classes without invoking them)
257
279
  */
258
- addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>) => ResourceTypes[K] | Promise<ResourceTypes[K]>) | ResourceFactoryWithDeps<ResourceTypes[K], ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>, keyof ResourceTypes & string>): this;
280
+ addResource<K extends keyof Cfg['resources']>(key: K, resource: Cfg['resources'][K] | ((ecs: ECSpresso<Cfg>) => Cfg['resources'][K] | Promise<Cfg['resources'][K]>) | ResourceFactoryWithDeps<Cfg['resources'][K], ECSpresso<Cfg>, keyof Cfg['resources'] & string> | ResourceDirectValue<Cfg['resources'][K]>): this;
259
281
  /**
260
282
  * Remove a resource from the ECS instance (without calling onDispose)
261
283
  * @param key The resource key to remove
262
284
  * @returns True if the resource was removed, false if it didn't exist
263
285
  */
264
- removeResource<K extends keyof ResourceTypes>(key: K): boolean;
286
+ removeResource<K extends keyof Cfg['resources']>(key: K): boolean;
265
287
  /**
266
288
  * Dispose a single resource, calling its onDispose callback if defined
267
289
  * @param key The resource key to dispose
268
290
  * @returns True if the resource existed and was disposed, false if it didn't exist
269
291
  */
270
- disposeResource<K extends keyof ResourceTypes>(key: K): Promise<boolean>;
292
+ disposeResource<K extends keyof Cfg['resources']>(key: K): Promise<boolean>;
271
293
  /**
272
294
  * Dispose all initialized resources in reverse dependency order.
273
295
  * Resources that depend on others are disposed first.
@@ -281,34 +303,64 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
281
303
  * @returns This ECSpresso instance for chaining
282
304
  * @throws Error if the resource doesn't exist
283
305
  */
284
- updateResource<K extends keyof ResourceTypes>(key: K, updater: (current: ResourceTypes[K]) => ResourceTypes[K]): this;
306
+ updateResource<K extends keyof Cfg['resources']>(key: K, updater: (current: Cfg['resources'][K]) => Cfg['resources'][K]): this;
285
307
  /**
286
308
  * Get all resource keys that are currently registered
287
309
  * @returns Array of resource keys
288
310
  */
289
- getResourceKeys(): Array<keyof ResourceTypes>;
311
+ getResourceKeys(): Array<keyof Cfg['resources']>;
290
312
  /**
291
313
  * Check if a resource needs initialization (was added as a factory function)
292
314
  * @param key The resource key to check
293
315
  * @returns True if the resource needs initialization
294
316
  */
295
- resourceNeedsInitialization<K extends keyof ResourceTypes>(key: K): boolean;
317
+ resourceNeedsInitialization<K extends keyof Cfg['resources']>(key: K): boolean;
318
+ /**
319
+ * Get a component value from an entity.
320
+ * @param entityId The entity ID
321
+ * @param componentName The component to retrieve
322
+ * @returns The component value, or undefined if the entity doesn't have it
323
+ */
324
+ getComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K): Cfg['components'][K] | undefined;
325
+ /**
326
+ * Add or replace a component on an entity.
327
+ * Triggers component-added callbacks and marks the component as changed.
328
+ * @param entityId The entity ID
329
+ * @param componentName The component to add
330
+ * @param value The component value
331
+ */
332
+ addComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K, value: Cfg['components'][K]): void;
333
+ /**
334
+ * Add multiple components to an entity at once.
335
+ * @param entityId The entity ID
336
+ * @param components Object with component names as keys and component data as values
337
+ */
338
+ addComponents<T extends {
339
+ [K in keyof Cfg['components']]?: Cfg['components'][K];
340
+ }>(entityId: number, components: T & Record<Exclude<keyof T, keyof Cfg['components']>, never>): void;
341
+ /**
342
+ * Remove a component from an entity.
343
+ * Triggers component-removed and dispose callbacks.
344
+ * @param entityId The entity ID
345
+ * @param componentName The component to remove
346
+ */
347
+ removeComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K): void;
296
348
  /**
297
349
  * Check if an entity has a component
298
350
  */
299
- hasComponent<K extends keyof ComponentTypes>(entityId: number, componentName: K): boolean;
351
+ hasComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K): boolean;
300
352
  /**
301
353
  * Create an entity and add components to it in one call
302
354
  * @param components Object with component names as keys and component data as values
303
355
  * @returns The created entity with all components added
304
356
  */
305
357
  spawn<T extends {
306
- [K in keyof ComponentTypes]?: ComponentTypes[K];
307
- }>(components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes>;
358
+ [K in keyof Cfg['components']]?: Cfg['components'][K];
359
+ }>(components: T & Record<Exclude<keyof T, keyof Cfg['components']>, never>): FilteredEntity<Cfg['components'], keyof T & keyof Cfg['components']>;
308
360
  /**
309
361
  * Get all entities with specific components
310
362
  */
311
- getEntitiesWithQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>, changedComponents?: ReadonlyArray<keyof ComponentTypes>, parentHas?: ReadonlyArray<keyof ComponentTypes>): Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>;
363
+ getEntitiesWithQuery<WithComponents extends keyof Cfg['components'], WithoutComponents extends keyof Cfg['components'] = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>, changedComponents?: ReadonlyArray<keyof Cfg['components']>, parentHas?: ReadonlyArray<keyof Cfg['components']>): Array<FilteredEntity<Cfg['components'], WithComponents, WithoutComponents>>;
312
364
  /**
313
365
  * Get the single entity matching a query. Throws if zero or more than one match.
314
366
  * @param withComponents Components the entity must have
@@ -316,7 +368,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
316
368
  * @returns The single matching entity
317
369
  * @throws If zero or more than one entity matches
318
370
  */
319
- getSingleton<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>): FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>;
371
+ getSingleton<WithComponents extends keyof Cfg['components'], WithoutComponents extends keyof Cfg['components'] = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>): FilteredEntity<Cfg['components'], WithComponents, WithoutComponents>;
320
372
  /**
321
373
  * Get the single entity matching a query, or undefined if none match.
322
374
  * Throws if more than one entity matches.
@@ -325,99 +377,99 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
325
377
  * @returns The single matching entity, or undefined if none match
326
378
  * @throws If more than one entity matches
327
379
  */
328
- tryGetSingleton<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>): FilteredEntity<ComponentTypes, WithComponents, WithoutComponents> | undefined;
380
+ tryGetSingleton<WithComponents extends keyof Cfg['components'], WithoutComponents extends keyof Cfg['components'] = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>): FilteredEntity<Cfg['components'], WithComponents, WithoutComponents> | undefined;
329
381
  /**
330
382
  * Remove an entity (and optionally its descendants)
331
- * @param entityOrId Entity or entity ID to remove
383
+ * @param entityId Entity ID to remove
332
384
  * @param options Options for removal (cascade: true by default)
333
385
  * @returns true if entity was removed
334
386
  */
335
- removeEntity(entityOrId: number | Entity<ComponentTypes>, options?: RemoveEntityOptions): boolean;
387
+ removeEntity(entityId: number, options?: RemoveEntityOptions): boolean;
336
388
  /**
337
389
  * Create an entity as a child of another entity with initial components
338
- * @param parentOrId The parent entity or entity ID
390
+ * @param parentId The parent entity ID
339
391
  * @param components Initial components to add
340
392
  * @returns The created child entity
341
393
  */
342
394
  spawnChild<T extends {
343
- [K in keyof ComponentTypes]?: ComponentTypes[K];
344
- }>(parentOrId: number | Entity<ComponentTypes>, components: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>): FilteredEntity<ComponentTypes, keyof T & keyof ComponentTypes>;
395
+ [K in keyof Cfg['components']]?: Cfg['components'][K];
396
+ }>(parentId: number, components: T & Record<Exclude<keyof T, keyof Cfg['components']>, never>): FilteredEntity<Cfg['components'], keyof T & keyof Cfg['components']>;
345
397
  /**
346
398
  * Set the parent of an entity
347
- * @param childOrId The entity or entity ID to set as a child
348
- * @param parentOrId The entity or entity ID to set as the parent
399
+ * @param childId The entity ID to set as a child
400
+ * @param parentId The entity ID to set as the parent
349
401
  */
350
- setParent(childOrId: number | Entity<ComponentTypes>, parentOrId: number | Entity<ComponentTypes>): this;
402
+ setParent(childId: number, parentId: number): this;
351
403
  /**
352
404
  * Remove the parent relationship for an entity (orphan it)
353
- * @param childOrId The entity or entity ID to orphan
405
+ * @param childId The entity ID to orphan
354
406
  * @returns true if a parent was removed, false if entity had no parent
355
407
  */
356
- removeParent(childOrId: number | Entity<ComponentTypes>): boolean;
408
+ removeParent(childId: number): boolean;
357
409
  /**
358
410
  * Get the parent of an entity
359
- * @param entityOrId The entity or entity ID to get the parent of
411
+ * @param entityId The entity ID to get the parent of
360
412
  * @returns The parent entity ID, or null if no parent
361
413
  */
362
- getParent(entityOrId: number | Entity<ComponentTypes>): number | null;
414
+ getParent(entityId: number): number | null;
363
415
  /**
364
416
  * Get all children of an entity in insertion order
365
- * @param parentOrId The parent entity or entity ID
417
+ * @param parentId The parent entity ID
366
418
  * @returns Readonly array of child entity IDs
367
419
  */
368
- getChildren(parentOrId: number | Entity<ComponentTypes>): readonly number[];
420
+ getChildren(parentId: number): readonly number[];
369
421
  /**
370
422
  * Get a child at a specific index
371
- * @param parentOrId The parent entity or entity ID
423
+ * @param parentId The parent entity ID
372
424
  * @param index The index of the child
373
425
  * @returns The child entity ID, or null if index is out of bounds
374
426
  */
375
- getChildAt(parentOrId: number | Entity<ComponentTypes>, index: number): number | null;
427
+ getChildAt(parentId: number, index: number): number | null;
376
428
  /**
377
429
  * Get the index of a child within its parent's children list
378
- * @param parentOrId The parent entity or entity ID
379
- * @param childOrId The child entity or entity ID to find
430
+ * @param parentId The parent entity ID
431
+ * @param childId The child entity ID to find
380
432
  * @returns The index of the child, or -1 if not found
381
433
  */
382
- getChildIndex(parentOrId: number | Entity<ComponentTypes>, childOrId: number | Entity<ComponentTypes>): number;
434
+ getChildIndex(parentId: number, childId: number): number;
383
435
  /**
384
436
  * Get all ancestors of an entity in order [parent, grandparent, ...]
385
- * @param entityOrId The entity or entity ID to get ancestors of
437
+ * @param entityId The entity ID to get ancestors of
386
438
  * @returns Readonly array of ancestor entity IDs
387
439
  */
388
- getAncestors(entityOrId: number | Entity<ComponentTypes>): readonly number[];
440
+ getAncestors(entityId: number): readonly number[];
389
441
  /**
390
442
  * Get all descendants of an entity in depth-first order
391
- * @param entityOrId The entity or entity ID to get descendants of
443
+ * @param entityId The entity ID to get descendants of
392
444
  * @returns Readonly array of descendant entity IDs
393
445
  */
394
- getDescendants(entityOrId: number | Entity<ComponentTypes>): readonly number[];
446
+ getDescendants(entityId: number): readonly number[];
395
447
  /**
396
448
  * Get the root ancestor of an entity (topmost parent), or self if no parent
397
- * @param entityOrId The entity or entity ID to get the root of
449
+ * @param entityId The entity ID to get the root of
398
450
  * @returns The root entity ID
399
451
  */
400
- getRoot(entityOrId: number | Entity<ComponentTypes>): number;
452
+ getRoot(entityId: number): number;
401
453
  /**
402
454
  * Get siblings of an entity (other children of the same parent)
403
- * @param entityOrId The entity or entity ID to get siblings of
455
+ * @param entityId The entity ID to get siblings of
404
456
  * @returns Readonly array of sibling entity IDs
405
457
  */
406
- getSiblings(entityOrId: number | Entity<ComponentTypes>): readonly number[];
458
+ getSiblings(entityId: number): readonly number[];
407
459
  /**
408
460
  * Check if an entity is a descendant of another entity
409
- * @param entityOrId The potential descendant (entity or ID)
410
- * @param ancestorOrId The potential ancestor (entity or ID)
411
- * @returns true if entityOrId is a descendant of ancestorOrId
461
+ * @param entityId The potential descendant ID
462
+ * @param ancestorId The potential ancestor ID
463
+ * @returns true if entityId is a descendant of ancestorId
412
464
  */
413
- isDescendantOf(entityOrId: number | Entity<ComponentTypes>, ancestorOrId: number | Entity<ComponentTypes>): boolean;
465
+ isDescendantOf(entityId: number, ancestorId: number): boolean;
414
466
  /**
415
467
  * Check if an entity is an ancestor of another entity
416
- * @param entityOrId The potential ancestor (entity or ID)
417
- * @param descendantOrId The potential descendant (entity or ID)
418
- * @returns true if entityOrId is an ancestor of descendantOrId
468
+ * @param entityId The potential ancestor ID
469
+ * @param descendantId The potential descendant ID
470
+ * @returns true if entityId is an ancestor of descendantId
419
471
  */
420
- isAncestorOf(entityOrId: number | Entity<ComponentTypes>, descendantOrId: number | Entity<ComponentTypes>): boolean;
472
+ isAncestorOf(entityId: number, descendantId: number): boolean;
421
473
  /**
422
474
  * Get all root entities (entities that have children but no parent)
423
475
  * @returns Readonly array of root entity IDs
@@ -443,11 +495,11 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
443
495
  */
444
496
  private _emitHierarchyChanged;
445
497
  /**
446
- * Get all installed bundle IDs
498
+ * Get all installed plugin IDs
447
499
  */
448
- get installedBundles(): string[];
449
- get entityManager(): EntityManager<ComponentTypes>;
450
- get eventBus(): EventBus<EventTypes>;
500
+ get installedPlugins(): string[];
501
+ get entityManager(): EntityManager<Cfg["components"]>;
502
+ get eventBus(): EventBus<Cfg["events"]>;
451
503
  /**
452
504
  * Command buffer for queuing deferred structural changes.
453
505
  * Commands are executed automatically at the end of each update() cycle.
@@ -459,7 +511,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
459
511
  * ecs.commands.spawn({ position: { x: 0, y: 0 } });
460
512
  * ```
461
513
  */
462
- get commands(): CommandBuffer<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;
514
+ get commands(): CommandBuffer<Cfg>;
463
515
  /**
464
516
  * The current tick number, incremented at the end of each update()
465
517
  */
@@ -481,22 +533,34 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
481
533
  get systemTimings(): ReadonlyMap<string, number>;
482
534
  get phaseTimings(): Readonly<Record<SystemPhase, number>>;
483
535
  get entityCount(): number;
536
+ /**
537
+ * Mutate a component in place and automatically mark it as changed.
538
+ * Throws if the entity does not exist or does not have the component.
539
+ * @param entityId The entity ID
540
+ * @param componentName The component to mutate
541
+ * @param mutator A function that receives the component value for in-place mutation
542
+ * @returns The mutated component value
543
+ */
544
+ mutateComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K, mutator: (value: Cfg['components'][K]) => void): Cfg['components'][K];
484
545
  /**
485
546
  * Mark a component as changed on an entity.
486
547
  * Each call increments a global monotonic sequence; systems with changed
487
548
  * queries will see the mark exactly once (on their next execution).
488
- * @param entityOrId The entity or entity ID
549
+ * @param entityId The entity ID
489
550
  * @param componentName The component that was changed
490
551
  */
491
- markChanged<K extends keyof ComponentTypes>(entityOrId: number | Entity<ComponentTypes>, componentName: K): void;
552
+ markChanged<K extends keyof Cfg['components']>(entityId: number, componentName: K): void;
492
553
  /**
493
554
  * Register a dispose callback for a component type.
494
555
  * Called when a component is removed (explicit removal, entity destruction, or replacement).
495
556
  * Later registrations replace earlier ones for the same component type.
496
557
  * @param componentName The component type to register disposal for
497
- * @param callback Function receiving the component value being disposed
558
+ * @param callback Function receiving the component value being disposed and the entity ID
498
559
  */
499
- registerDispose<K extends keyof ComponentTypes>(componentName: K, callback: (value: ComponentTypes[K]) => void): void;
560
+ registerDispose<K extends keyof Cfg['components']>(componentName: K, callback: (ctx: {
561
+ value: Cfg['components'][K];
562
+ entityId: number;
563
+ }) => void): void;
500
564
  /**
501
565
  * Register a required component relationship.
502
566
  * When an entity gains `trigger`, the `required` component is auto-added
@@ -506,7 +570,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
506
570
  * @param required The component to auto-add
507
571
  * @param factory Function that creates the default value for the required component
508
572
  */
509
- registerRequired<Trigger extends keyof ComponentTypes, Required extends keyof ComponentTypes>(trigger: Trigger, required: Required, factory: (triggerValue: ComponentTypes[Trigger]) => ComponentTypes[Required]): void;
573
+ registerRequired<Trigger extends keyof Cfg['components'], Required extends keyof Cfg['components']>(trigger: Trigger, required: Required, factory: (triggerValue: Cfg['components'][Trigger]) => Cfg['components'][Required]): void;
510
574
  /**
511
575
  * Check for circular dependencies in the required components graph.
512
576
  * @throws Error if adding trigger→newRequired would create a cycle
@@ -518,20 +582,26 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
518
582
  * @param handler Function receiving the new component value and the entity
519
583
  * @returns Unsubscribe function to remove the callback
520
584
  */
521
- onComponentAdded<K extends keyof ComponentTypes>(componentName: K, handler: (value: ComponentTypes[K], entity: Entity<ComponentTypes>) => void): () => void;
585
+ onComponentAdded<K extends keyof Cfg['components']>(componentName: K, handler: (ctx: {
586
+ value: Cfg['components'][K];
587
+ entity: Entity<Cfg['components']>;
588
+ }) => void): () => void;
522
589
  /**
523
590
  * Register a callback when a specific component is removed from any entity
524
591
  * @param componentName The component key
525
592
  * @param handler Function receiving the old component value and the entity
526
593
  * @returns Unsubscribe function to remove the callback
527
594
  */
528
- onComponentRemoved<K extends keyof ComponentTypes>(componentName: K, handler: (oldValue: ComponentTypes[K], entity: Entity<ComponentTypes>) => void): () => void;
595
+ onComponentRemoved<K extends keyof Cfg['components']>(componentName: K, handler: (ctx: {
596
+ value: Cfg['components'][K];
597
+ entity: Entity<Cfg['components']>;
598
+ }) => void): () => void;
529
599
  /**
530
600
  * Add a reactive query that triggers callbacks when entities enter/exit the query match.
531
601
  * @param name Unique name for the query
532
602
  * @param definition Query definition with with/without arrays and onEnter/onExit callbacks
533
603
  */
534
- addReactiveQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never, OptionalComponents extends keyof ComponentTypes = never>(name: ReactiveQueryNames, definition: ReactiveQueryDefinition<ComponentTypes, WithComponents, WithoutComponents, OptionalComponents>): void;
604
+ addReactiveQuery<WithComponents extends keyof Cfg['components'], WithoutComponents extends keyof Cfg['components'] = never, OptionalComponents extends keyof Cfg['components'] = never>(name: ReactiveQueryNames, definition: ReactiveQueryDefinition<Cfg['components'], WithComponents, WithoutComponents, OptionalComponents>): void;
535
605
  /**
536
606
  * Remove a reactive query by name.
537
607
  * @param name Name of the query to remove
@@ -544,41 +614,44 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
544
614
  * @param callback The callback to invoke when the event is published
545
615
  * @returns An unsubscribe function
546
616
  */
547
- on<E extends keyof EventTypes>(eventType: E, callback: (data: EventTypes[E]) => void): () => void;
617
+ on<E extends keyof Cfg['events']>(eventType: E, callback: (data: Cfg['events'][E]) => void): () => void;
548
618
  /**
549
619
  * Unsubscribe from an event by callback reference (convenience wrapper for eventBus.unsubscribe)
550
620
  * @param eventType The event type to unsubscribe from
551
621
  * @param callback The callback to remove
552
622
  * @returns true if the callback was found and removed, false otherwise
553
623
  */
554
- off<E extends keyof EventTypes>(eventType: E, callback: (data: EventTypes[E]) => void): boolean;
624
+ off<E extends keyof Cfg['events']>(eventType: E, callback: (data: Cfg['events'][E]) => void): boolean;
555
625
  /**
556
626
  * Register a hook that runs after all systems in update()
557
627
  * @param callback The hook to call after all systems have processed
558
628
  * @returns An unsubscribe function to remove the hook
559
629
  */
560
- onPostUpdate(callback: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>, deltaTime: number) => void): () => void;
630
+ onPostUpdate(callback: (ctx: {
631
+ ecs: ECSpresso<Cfg>;
632
+ dt: number;
633
+ }) => void): () => void;
561
634
  private requireAssetManager;
562
635
  /**
563
636
  * Get a loaded asset by key. Throws if not loaded.
564
637
  */
565
- getAsset<K extends keyof AssetTypes>(key: K): AssetTypes[K];
638
+ getAsset<K extends keyof Cfg['assets']>(key: K): Cfg['assets'][K];
566
639
  /**
567
640
  * Get a loaded asset or undefined if not loaded
568
641
  */
569
- getAssetOrUndefined<K extends keyof AssetTypes>(key: K): AssetTypes[K] | undefined;
642
+ tryGetAsset<K extends keyof Cfg['assets']>(key: K): Cfg['assets'][K] | undefined;
570
643
  /**
571
644
  * Get a handle to an asset with status information
572
645
  */
573
- getAssetHandle<K extends keyof AssetTypes>(key: K): AssetHandle<AssetTypes[K]>;
646
+ getAssetHandle<K extends keyof Cfg['assets']>(key: K): AssetHandle<Cfg['assets'][K]>;
574
647
  /**
575
648
  * Check if an asset is loaded
576
649
  */
577
- isAssetLoaded<K extends keyof AssetTypes>(key: K): boolean;
650
+ isAssetLoaded<K extends keyof Cfg['assets']>(key: K): boolean;
578
651
  /**
579
652
  * Load a single asset
580
653
  */
581
- loadAsset<K extends keyof AssetTypes>(key: K): Promise<AssetTypes[K]>;
654
+ loadAsset<K extends keyof Cfg['assets']>(key: K): Promise<Cfg['assets'][K]>;
582
655
  /**
583
656
  * Load all assets in a group
584
657
  */
@@ -595,11 +668,11 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
595
668
  /**
596
669
  * Transition to a new screen, clearing the stack
597
670
  */
598
- setScreen<K extends keyof ScreenStates>(name: K, config: ScreenStates[K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
671
+ setScreen<K extends keyof Cfg['screens']>(name: K, config: Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
599
672
  /**
600
673
  * Push a screen onto the stack (overlay)
601
674
  */
602
- pushScreen<K extends keyof ScreenStates>(name: K, config: ScreenStates[K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
675
+ pushScreen<K extends keyof Cfg['screens']>(name: K, config: Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
603
676
  /**
604
677
  * Pop the current screen and return to the previous one
605
678
  */
@@ -607,62 +680,124 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
607
680
  /**
608
681
  * Get the current screen name
609
682
  */
610
- getCurrentScreen(): keyof ScreenStates | null;
683
+ getCurrentScreen(): keyof Cfg['screens'] | null;
684
+ /**
685
+ * Get the current screen config (immutable), narrowed to a specific screen.
686
+ * Throws if the current screen doesn't match.
687
+ */
688
+ getScreenConfig<K extends keyof Cfg['screens'] & string>(screen: K): Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
689
+ /**
690
+ * Get the current screen config (immutable).
691
+ * Returns a union of all possible config types.
692
+ */
693
+ getScreenConfig(): {
694
+ [K in keyof Cfg['screens']]: Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
695
+ }[keyof Cfg['screens']];
696
+ /**
697
+ * Get the current screen config narrowed to a specific screen, or undefined if not on that screen.
698
+ */
699
+ tryGetScreenConfig<K extends keyof Cfg['screens'] & string>(screen: K): (Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never) | undefined;
611
700
  /**
612
- * Get the current screen config (immutable)
701
+ * Get the current screen config or undefined.
702
+ * Returns a union of all possible config types, or undefined.
613
703
  */
614
- getScreenConfig<K extends keyof ScreenStates>(): ScreenStates[K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
704
+ tryGetScreenConfig(): {
705
+ [K in keyof Cfg['screens']]: Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
706
+ }[keyof Cfg['screens']] | undefined;
615
707
  /**
616
- * Get the current screen config or null
708
+ * Get the current screen state (mutable), narrowed to a specific screen.
709
+ * Throws if the current screen doesn't match.
617
710
  */
618
- getScreenConfigOrNull<K extends keyof ScreenStates>(): (ScreenStates[K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never) | null;
711
+ getScreenState<K extends keyof Cfg['screens'] & string>(screen: K): Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never;
619
712
  /**
620
- * Get the current screen state (mutable)
713
+ * Get the current screen state (mutable).
714
+ * Returns a union of all possible state types.
621
715
  */
622
- getScreenState<K extends keyof ScreenStates>(): ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never;
716
+ getScreenState(): {
717
+ [K in keyof Cfg['screens']]: Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never;
718
+ }[keyof Cfg['screens']];
623
719
  /**
624
- * Get the current screen state or null
720
+ * Get the current screen state narrowed to a specific screen, or undefined if not on that screen.
625
721
  */
626
- getScreenStateOrNull<K extends keyof ScreenStates>(): (ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never) | null;
722
+ tryGetScreenState<K extends keyof Cfg['screens'] & string>(screen: K): (Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never) | undefined;
627
723
  /**
628
- * Update the current screen state
724
+ * Get the current screen state or undefined.
725
+ * Returns a union of all possible state types, or undefined.
629
726
  */
630
- updateScreenState<K extends keyof ScreenStates>(update: Partial<ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never> | ((current: ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never) => Partial<ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never>)): void;
727
+ tryGetScreenState(): {
728
+ [K in keyof Cfg['screens']]: Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never;
729
+ }[keyof Cfg['screens']] | undefined;
730
+ /**
731
+ * Update the current screen state, narrowed to a specific screen.
732
+ * Throws if the current screen doesn't match.
733
+ */
734
+ updateScreenState<K extends keyof Cfg['screens'] & string>(screen: K, update: Partial<Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never> | ((current: Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never) => Partial<Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never>)): void;
735
+ /**
736
+ * Update the current screen state.
737
+ */
738
+ updateScreenState<K extends keyof Cfg['screens']>(update: Partial<Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never> | ((current: Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never) => Partial<Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never>)): void;
631
739
  /**
632
740
  * Check if a screen is the current screen
633
741
  */
634
- isCurrentScreen(screenName: keyof ScreenStates): boolean;
742
+ isCurrentScreen(screenName: keyof Cfg['screens']): boolean;
635
743
  /**
636
744
  * Check if a screen is active (current or in stack)
637
745
  */
638
- isScreenActive(screenName: keyof ScreenStates): boolean;
746
+ isScreenActive(screenName: keyof Cfg['screens']): boolean;
639
747
  /**
640
748
  * Get the screen stack depth
641
749
  */
642
750
  getScreenStackDepth(): number;
643
751
  /**
644
- * Internal method to set the asset manager and drain pending bundle assets
752
+ * Internal method to set the asset manager and drain pending plugin assets
645
753
  * @internal Used by ECSpressoBuilder
646
754
  */
647
- _setAssetManager(manager: AssetManager<AssetTypes>): void;
755
+ _setAssetManager(manager: AssetManager<Cfg['assets']>): void;
648
756
  /**
649
- * Internal method to set the screen manager and drain pending bundle screens
757
+ * Internal method to set the screen manager and drain pending plugin screens
650
758
  * @internal Used by ECSpressoBuilder
651
759
  */
652
- _setScreenManager(manager: ScreenManager<ScreenStates>): void;
760
+ _setScreenManager(manager: ScreenManager<Cfg['screens']>): void;
653
761
  /** @internal */
654
- _hasPendingBundleAssets(): boolean;
762
+ _hasPendingPluginAssets(): boolean;
655
763
  /** @internal */
656
- _hasPendingBundleScreens(): boolean;
764
+ _hasPendingPluginScreens(): boolean;
657
765
  /**
658
766
  * Internal method to set the fixed timestep interval
659
767
  * @internal Used by ECSpressoBuilder
660
768
  */
661
769
  _setFixedDt(dt: number): void;
662
770
  /**
663
- * Internal method to install a bundle into this ECSpresso instance.
664
- * Called by the ECSpressoBuilder during the build process.
665
- * The type safety is guaranteed by the builder's type system.
666
- */
667
- _installBundle<C extends Record<string, any>, E extends Record<string, any>, R extends Record<string, any>, A extends Record<string, unknown> = {}, S extends Record<string, ScreenDefinition<any, any>> = {}>(bundle: Bundle<C, E, R, A, S, any, any, any, any>): this;
771
+ * Register an asset definition for deferred registration.
772
+ * @internal Used by plugins that need to register assets
773
+ */
774
+ _registerAsset(key: string, definition: AssetDefinition<unknown>): void;
775
+ /**
776
+ * Register a screen definition for deferred registration.
777
+ * @internal Used by plugins that need to register screens
778
+ */
779
+ _registerScreen(name: string, definition: ScreenDefinition<any, any>): void;
780
+ /**
781
+ * Install a plugin into this ECSpresso instance.
782
+ * Deduplicates by plugin ID. Composite plugins call this in their install function.
783
+ */
784
+ installPlugin(plugin: Plugin<any, any, any, any, any, any>): this;
785
+ /**
786
+ * Create a plugin factory from the built world's types.
787
+ * Returns a definePlugin equivalent with no manual type parameters.
788
+ */
789
+ pluginFactory(): <PL extends string = never, PG extends string = never, PAG extends string = never, PRQ extends string = never>(config: {
790
+ id: string;
791
+ install: (world: ECSpresso<Cfg>) => void;
792
+ }) => Plugin<Cfg, EmptyConfig, PL, PG, PAG, PRQ>;
793
+ /**
794
+ * Call a helper factory with this world instance, inferring the full world type.
795
+ * Eliminates the need for a separate `type ECS = typeof ecs` ceremony.
796
+ *
797
+ * @example
798
+ * ```typescript
799
+ * const helpers = ecs.getHelpers(createStateMachineHelpers);
800
+ * ```
801
+ */
802
+ getHelpers<H>(factory: (world: this) => H): H;
668
803
  }