ecspresso 0.11.0 → 0.12.1

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 +24 -959
  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 -123
  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 +26 -14
  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 +61 -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.
@@ -144,7 +161,6 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
144
161
  * This is useful for game startup to ensure all resources are ready
145
162
  * and systems are properly initialized before the game loop begins.
146
163
  *
147
- * @param resourceKeys Optional array of specific resource keys to initialize
148
164
  * @returns Promise that resolves when everything is initialized
149
165
  */
150
166
  initialize(): Promise<void>;
@@ -154,7 +170,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
154
170
  * @param keys Optional array of resource keys to initialize. If not provided, all pending resources will be initialized.
155
171
  * @returns Promise that resolves when the specified resources are initialized
156
172
  */
157
- initializeResources<K extends keyof ResourceTypes>(...keys: K[]): Promise<void>;
173
+ initializeResources<K extends keyof Cfg['resources']>(...keys: K[]): Promise<void>;
158
174
  /**
159
175
  * Rebuild per-phase system arrays from the flat _systems list.
160
176
  * Each phase array is sorted by priority (higher first), with
@@ -220,11 +236,11 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
220
236
  * Internal method to register a system with this ECSpresso instance
221
237
  * @internal Used by SystemBuilder - replaces direct private property access
222
238
  */
223
- _registerSystem(system: System<ComponentTypes, any, any, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
239
+ _registerSystem(system: System<Cfg, any, any>): void;
224
240
  /**
225
241
  * Check if a resource exists
226
242
  */
227
- hasResource<K extends keyof ResourceTypes>(key: K): boolean;
243
+ hasResource<K extends keyof Cfg['resources']>(key: K): boolean;
228
244
  /**
229
245
  * Get a resource by key. Throws if the resource is not found.
230
246
  * @param key The resource key
@@ -232,42 +248,47 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
232
248
  * @throws Error if resource not found
233
249
  * @see tryGetResource — the non-throwing alternative that returns undefined
234
250
  */
235
- getResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K];
251
+ getResource<K extends keyof Cfg['resources']>(key: K): Cfg['resources'][K];
236
252
  /**
237
253
  * Try to get a resource by key. Returns undefined if the resource is not found.
238
254
  * Inspired by Bevy's `World::get_resource::<T>()` which returns `Option<&T>`.
239
255
  *
240
256
  * Two overloads:
241
257
  * 1. Known key — full type safety from `ResourceTypes`
242
- * 2. String key with explicit type param — for cross-bundle optional dependencies
258
+ * 2. String key with explicit type param — for cross-plugin optional dependencies
243
259
  *
244
260
  * @example
245
261
  * ```typescript
246
262
  * // Known key (type inferred from ResourceTypes)
247
263
  * const score = ecs.tryGetResource('score'); // ScoreResource | undefined
248
264
  *
249
- * // Cross-bundle optional dependency (caller specifies expected type)
265
+ * // Cross-plugin optional dependency (caller specifies expected type)
250
266
  * const si = ecs.tryGetResource<SpatialIndex>('spatialIndex') ?? null;
251
267
  * ```
252
268
  */
253
- tryGetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] | undefined;
269
+ tryGetResource<K extends keyof Cfg['resources']>(key: K): Cfg['resources'][K] | undefined;
254
270
  tryGetResource<T>(key: unknown extends T ? never : string): T | undefined;
255
271
  /**
256
- * Add a resource to the ECS instance
272
+ * Add a resource to the ECS instance.
273
+ *
274
+ * - Plain value → stored directly
275
+ * - Function → treated as a factory, called with this ECSpresso instance on first access
276
+ * - `{ factory, dependsOn?, onDispose? }` → factory with dependencies/disposal
277
+ * - `directValue(val)` → stores the value as-is (use to store functions/classes without invoking them)
257
278
  */
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;
279
+ 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
280
  /**
260
281
  * Remove a resource from the ECS instance (without calling onDispose)
261
282
  * @param key The resource key to remove
262
283
  * @returns True if the resource was removed, false if it didn't exist
263
284
  */
264
- removeResource<K extends keyof ResourceTypes>(key: K): boolean;
285
+ removeResource<K extends keyof Cfg['resources']>(key: K): boolean;
265
286
  /**
266
287
  * Dispose a single resource, calling its onDispose callback if defined
267
288
  * @param key The resource key to dispose
268
289
  * @returns True if the resource existed and was disposed, false if it didn't exist
269
290
  */
270
- disposeResource<K extends keyof ResourceTypes>(key: K): Promise<boolean>;
291
+ disposeResource<K extends keyof Cfg['resources']>(key: K): Promise<boolean>;
271
292
  /**
272
293
  * Dispose all initialized resources in reverse dependency order.
273
294
  * Resources that depend on others are disposed first.
@@ -281,34 +302,64 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
281
302
  * @returns This ECSpresso instance for chaining
282
303
  * @throws Error if the resource doesn't exist
283
304
  */
284
- updateResource<K extends keyof ResourceTypes>(key: K, updater: (current: ResourceTypes[K]) => ResourceTypes[K]): this;
305
+ updateResource<K extends keyof Cfg['resources']>(key: K, updater: (current: Cfg['resources'][K]) => Cfg['resources'][K]): this;
285
306
  /**
286
307
  * Get all resource keys that are currently registered
287
308
  * @returns Array of resource keys
288
309
  */
289
- getResourceKeys(): Array<keyof ResourceTypes>;
310
+ getResourceKeys(): Array<keyof Cfg['resources']>;
290
311
  /**
291
312
  * Check if a resource needs initialization (was added as a factory function)
292
313
  * @param key The resource key to check
293
314
  * @returns True if the resource needs initialization
294
315
  */
295
- resourceNeedsInitialization<K extends keyof ResourceTypes>(key: K): boolean;
316
+ resourceNeedsInitialization<K extends keyof Cfg['resources']>(key: K): boolean;
317
+ /**
318
+ * Get a component value from an entity.
319
+ * @param entityId The entity ID
320
+ * @param componentName The component to retrieve
321
+ * @returns The component value, or undefined if the entity doesn't have it
322
+ */
323
+ getComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K): Cfg['components'][K] | undefined;
324
+ /**
325
+ * Add or replace a component on an entity.
326
+ * Triggers component-added callbacks and marks the component as changed.
327
+ * @param entityId The entity ID
328
+ * @param componentName The component to add
329
+ * @param value The component value
330
+ */
331
+ addComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K, value: Cfg['components'][K]): void;
332
+ /**
333
+ * Add multiple components to an entity at once.
334
+ * @param entityId The entity ID
335
+ * @param components Object with component names as keys and component data as values
336
+ */
337
+ addComponents<T extends {
338
+ [K in keyof Cfg['components']]?: Cfg['components'][K];
339
+ }>(entityId: number, components: T & Record<Exclude<keyof T, keyof Cfg['components']>, never>): void;
340
+ /**
341
+ * Remove a component from an entity.
342
+ * Triggers component-removed and dispose callbacks.
343
+ * @param entityId The entity ID
344
+ * @param componentName The component to remove
345
+ */
346
+ removeComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K): void;
296
347
  /**
297
348
  * Check if an entity has a component
298
349
  */
299
- hasComponent<K extends keyof ComponentTypes>(entityId: number, componentName: K): boolean;
350
+ hasComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K): boolean;
300
351
  /**
301
352
  * Create an entity and add components to it in one call
302
353
  * @param components Object with component names as keys and component data as values
303
354
  * @returns The created entity with all components added
304
355
  */
305
356
  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>;
357
+ [K in keyof Cfg['components']]?: Cfg['components'][K];
358
+ }>(components: T & Record<Exclude<keyof T, keyof Cfg['components']>, never>): FilteredEntity<Cfg['components'], keyof T & keyof Cfg['components']>;
308
359
  /**
309
360
  * Get all entities with specific components
310
361
  */
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>>;
362
+ 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
363
  /**
313
364
  * Get the single entity matching a query. Throws if zero or more than one match.
314
365
  * @param withComponents Components the entity must have
@@ -316,7 +367,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
316
367
  * @returns The single matching entity
317
368
  * @throws If zero or more than one entity matches
318
369
  */
319
- getSingleton<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>): FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>;
370
+ getSingleton<WithComponents extends keyof Cfg['components'], WithoutComponents extends keyof Cfg['components'] = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>): FilteredEntity<Cfg['components'], WithComponents, WithoutComponents>;
320
371
  /**
321
372
  * Get the single entity matching a query, or undefined if none match.
322
373
  * Throws if more than one entity matches.
@@ -325,99 +376,99 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
325
376
  * @returns The single matching entity, or undefined if none match
326
377
  * @throws If more than one entity matches
327
378
  */
328
- tryGetSingleton<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never>(withComponents: ReadonlyArray<WithComponents>, withoutComponents?: ReadonlyArray<WithoutComponents>): FilteredEntity<ComponentTypes, WithComponents, WithoutComponents> | undefined;
379
+ 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
380
  /**
330
381
  * Remove an entity (and optionally its descendants)
331
- * @param entityOrId Entity or entity ID to remove
382
+ * @param entityId Entity ID to remove
332
383
  * @param options Options for removal (cascade: true by default)
333
384
  * @returns true if entity was removed
334
385
  */
335
- removeEntity(entityOrId: number | Entity<ComponentTypes>, options?: RemoveEntityOptions): boolean;
386
+ removeEntity(entityId: number, options?: RemoveEntityOptions): boolean;
336
387
  /**
337
388
  * Create an entity as a child of another entity with initial components
338
- * @param parentOrId The parent entity or entity ID
389
+ * @param parentId The parent entity ID
339
390
  * @param components Initial components to add
340
391
  * @returns The created child entity
341
392
  */
342
393
  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>;
394
+ [K in keyof Cfg['components']]?: Cfg['components'][K];
395
+ }>(parentId: number, components: T & Record<Exclude<keyof T, keyof Cfg['components']>, never>): FilteredEntity<Cfg['components'], keyof T & keyof Cfg['components']>;
345
396
  /**
346
397
  * 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
398
+ * @param childId The entity ID to set as a child
399
+ * @param parentId The entity ID to set as the parent
349
400
  */
350
- setParent(childOrId: number | Entity<ComponentTypes>, parentOrId: number | Entity<ComponentTypes>): this;
401
+ setParent(childId: number, parentId: number): this;
351
402
  /**
352
403
  * Remove the parent relationship for an entity (orphan it)
353
- * @param childOrId The entity or entity ID to orphan
404
+ * @param childId The entity ID to orphan
354
405
  * @returns true if a parent was removed, false if entity had no parent
355
406
  */
356
- removeParent(childOrId: number | Entity<ComponentTypes>): boolean;
407
+ removeParent(childId: number): boolean;
357
408
  /**
358
409
  * Get the parent of an entity
359
- * @param entityOrId The entity or entity ID to get the parent of
410
+ * @param entityId The entity ID to get the parent of
360
411
  * @returns The parent entity ID, or null if no parent
361
412
  */
362
- getParent(entityOrId: number | Entity<ComponentTypes>): number | null;
413
+ getParent(entityId: number): number | null;
363
414
  /**
364
415
  * Get all children of an entity in insertion order
365
- * @param parentOrId The parent entity or entity ID
416
+ * @param parentId The parent entity ID
366
417
  * @returns Readonly array of child entity IDs
367
418
  */
368
- getChildren(parentOrId: number | Entity<ComponentTypes>): readonly number[];
419
+ getChildren(parentId: number): readonly number[];
369
420
  /**
370
421
  * Get a child at a specific index
371
- * @param parentOrId The parent entity or entity ID
422
+ * @param parentId The parent entity ID
372
423
  * @param index The index of the child
373
424
  * @returns The child entity ID, or null if index is out of bounds
374
425
  */
375
- getChildAt(parentOrId: number | Entity<ComponentTypes>, index: number): number | null;
426
+ getChildAt(parentId: number, index: number): number | null;
376
427
  /**
377
428
  * 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
429
+ * @param parentId The parent entity ID
430
+ * @param childId The child entity ID to find
380
431
  * @returns The index of the child, or -1 if not found
381
432
  */
382
- getChildIndex(parentOrId: number | Entity<ComponentTypes>, childOrId: number | Entity<ComponentTypes>): number;
433
+ getChildIndex(parentId: number, childId: number): number;
383
434
  /**
384
435
  * Get all ancestors of an entity in order [parent, grandparent, ...]
385
- * @param entityOrId The entity or entity ID to get ancestors of
436
+ * @param entityId The entity ID to get ancestors of
386
437
  * @returns Readonly array of ancestor entity IDs
387
438
  */
388
- getAncestors(entityOrId: number | Entity<ComponentTypes>): readonly number[];
439
+ getAncestors(entityId: number): readonly number[];
389
440
  /**
390
441
  * Get all descendants of an entity in depth-first order
391
- * @param entityOrId The entity or entity ID to get descendants of
442
+ * @param entityId The entity ID to get descendants of
392
443
  * @returns Readonly array of descendant entity IDs
393
444
  */
394
- getDescendants(entityOrId: number | Entity<ComponentTypes>): readonly number[];
445
+ getDescendants(entityId: number): readonly number[];
395
446
  /**
396
447
  * 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
448
+ * @param entityId The entity ID to get the root of
398
449
  * @returns The root entity ID
399
450
  */
400
- getRoot(entityOrId: number | Entity<ComponentTypes>): number;
451
+ getRoot(entityId: number): number;
401
452
  /**
402
453
  * Get siblings of an entity (other children of the same parent)
403
- * @param entityOrId The entity or entity ID to get siblings of
454
+ * @param entityId The entity ID to get siblings of
404
455
  * @returns Readonly array of sibling entity IDs
405
456
  */
406
- getSiblings(entityOrId: number | Entity<ComponentTypes>): readonly number[];
457
+ getSiblings(entityId: number): readonly number[];
407
458
  /**
408
459
  * 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
460
+ * @param entityId The potential descendant ID
461
+ * @param ancestorId The potential ancestor ID
462
+ * @returns true if entityId is a descendant of ancestorId
412
463
  */
413
- isDescendantOf(entityOrId: number | Entity<ComponentTypes>, ancestorOrId: number | Entity<ComponentTypes>): boolean;
464
+ isDescendantOf(entityId: number, ancestorId: number): boolean;
414
465
  /**
415
466
  * 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
467
+ * @param entityId The potential ancestor ID
468
+ * @param descendantId The potential descendant ID
469
+ * @returns true if entityId is an ancestor of descendantId
419
470
  */
420
- isAncestorOf(entityOrId: number | Entity<ComponentTypes>, descendantOrId: number | Entity<ComponentTypes>): boolean;
471
+ isAncestorOf(entityId: number, descendantId: number): boolean;
421
472
  /**
422
473
  * Get all root entities (entities that have children but no parent)
423
474
  * @returns Readonly array of root entity IDs
@@ -443,11 +494,11 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
443
494
  */
444
495
  private _emitHierarchyChanged;
445
496
  /**
446
- * Get all installed bundle IDs
497
+ * Get all installed plugin IDs
447
498
  */
448
- get installedBundles(): string[];
449
- get entityManager(): EntityManager<ComponentTypes>;
450
- get eventBus(): EventBus<EventTypes>;
499
+ get installedPlugins(): string[];
500
+ get entityManager(): EntityManager<Cfg["components"]>;
501
+ get eventBus(): EventBus<Cfg["events"]>;
451
502
  /**
452
503
  * Command buffer for queuing deferred structural changes.
453
504
  * Commands are executed automatically at the end of each update() cycle.
@@ -459,7 +510,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
459
510
  * ecs.commands.spawn({ position: { x: 0, y: 0 } });
460
511
  * ```
461
512
  */
462
- get commands(): CommandBuffer<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;
513
+ get commands(): CommandBuffer<Cfg>;
463
514
  /**
464
515
  * The current tick number, incremented at the end of each update()
465
516
  */
@@ -481,22 +532,34 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
481
532
  get systemTimings(): ReadonlyMap<string, number>;
482
533
  get phaseTimings(): Readonly<Record<SystemPhase, number>>;
483
534
  get entityCount(): number;
535
+ /**
536
+ * Mutate a component in place and automatically mark it as changed.
537
+ * Throws if the entity does not exist or does not have the component.
538
+ * @param entityId The entity ID
539
+ * @param componentName The component to mutate
540
+ * @param mutator A function that receives the component value for in-place mutation
541
+ * @returns The mutated component value
542
+ */
543
+ mutateComponent<K extends keyof Cfg['components']>(entityId: number, componentName: K, mutator: (value: Cfg['components'][K]) => void): Cfg['components'][K];
484
544
  /**
485
545
  * Mark a component as changed on an entity.
486
546
  * Each call increments a global monotonic sequence; systems with changed
487
547
  * queries will see the mark exactly once (on their next execution).
488
- * @param entityOrId The entity or entity ID
548
+ * @param entityId The entity ID
489
549
  * @param componentName The component that was changed
490
550
  */
491
- markChanged<K extends keyof ComponentTypes>(entityOrId: number | Entity<ComponentTypes>, componentName: K): void;
551
+ markChanged<K extends keyof Cfg['components']>(entityId: number, componentName: K): void;
492
552
  /**
493
553
  * Register a dispose callback for a component type.
494
554
  * Called when a component is removed (explicit removal, entity destruction, or replacement).
495
555
  * Later registrations replace earlier ones for the same component type.
496
556
  * @param componentName The component type to register disposal for
497
- * @param callback Function receiving the component value being disposed
557
+ * @param callback Function receiving the component value being disposed and the entity ID
498
558
  */
499
- registerDispose<K extends keyof ComponentTypes>(componentName: K, callback: (value: ComponentTypes[K]) => void): void;
559
+ registerDispose<K extends keyof Cfg['components']>(componentName: K, callback: (ctx: {
560
+ value: Cfg['components'][K];
561
+ entityId: number;
562
+ }) => void): void;
500
563
  /**
501
564
  * Register a required component relationship.
502
565
  * When an entity gains `trigger`, the `required` component is auto-added
@@ -506,7 +569,7 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
506
569
  * @param required The component to auto-add
507
570
  * @param factory Function that creates the default value for the required component
508
571
  */
509
- registerRequired<Trigger extends keyof ComponentTypes, Required extends keyof ComponentTypes>(trigger: Trigger, required: Required, factory: (triggerValue: ComponentTypes[Trigger]) => ComponentTypes[Required]): void;
572
+ 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
573
  /**
511
574
  * Check for circular dependencies in the required components graph.
512
575
  * @throws Error if adding trigger→newRequired would create a cycle
@@ -518,20 +581,26 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
518
581
  * @param handler Function receiving the new component value and the entity
519
582
  * @returns Unsubscribe function to remove the callback
520
583
  */
521
- onComponentAdded<K extends keyof ComponentTypes>(componentName: K, handler: (value: ComponentTypes[K], entity: Entity<ComponentTypes>) => void): () => void;
584
+ onComponentAdded<K extends keyof Cfg['components']>(componentName: K, handler: (ctx: {
585
+ value: Cfg['components'][K];
586
+ entity: Entity<Cfg['components']>;
587
+ }) => void): () => void;
522
588
  /**
523
589
  * Register a callback when a specific component is removed from any entity
524
590
  * @param componentName The component key
525
591
  * @param handler Function receiving the old component value and the entity
526
592
  * @returns Unsubscribe function to remove the callback
527
593
  */
528
- onComponentRemoved<K extends keyof ComponentTypes>(componentName: K, handler: (oldValue: ComponentTypes[K], entity: Entity<ComponentTypes>) => void): () => void;
594
+ onComponentRemoved<K extends keyof Cfg['components']>(componentName: K, handler: (ctx: {
595
+ value: Cfg['components'][K];
596
+ entity: Entity<Cfg['components']>;
597
+ }) => void): () => void;
529
598
  /**
530
599
  * Add a reactive query that triggers callbacks when entities enter/exit the query match.
531
600
  * @param name Unique name for the query
532
601
  * @param definition Query definition with with/without arrays and onEnter/onExit callbacks
533
602
  */
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;
603
+ 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
604
  /**
536
605
  * Remove a reactive query by name.
537
606
  * @param name Name of the query to remove
@@ -544,41 +613,44 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
544
613
  * @param callback The callback to invoke when the event is published
545
614
  * @returns An unsubscribe function
546
615
  */
547
- on<E extends keyof EventTypes>(eventType: E, callback: (data: EventTypes[E]) => void): () => void;
616
+ on<E extends keyof Cfg['events']>(eventType: E, callback: (data: Cfg['events'][E]) => void): () => void;
548
617
  /**
549
618
  * Unsubscribe from an event by callback reference (convenience wrapper for eventBus.unsubscribe)
550
619
  * @param eventType The event type to unsubscribe from
551
620
  * @param callback The callback to remove
552
621
  * @returns true if the callback was found and removed, false otherwise
553
622
  */
554
- off<E extends keyof EventTypes>(eventType: E, callback: (data: EventTypes[E]) => void): boolean;
623
+ off<E extends keyof Cfg['events']>(eventType: E, callback: (data: Cfg['events'][E]) => void): boolean;
555
624
  /**
556
625
  * Register a hook that runs after all systems in update()
557
626
  * @param callback The hook to call after all systems have processed
558
627
  * @returns An unsubscribe function to remove the hook
559
628
  */
560
- onPostUpdate(callback: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>, deltaTime: number) => void): () => void;
629
+ onPostUpdate(callback: (ctx: {
630
+ ecs: ECSpresso<Cfg>;
631
+ dt: number;
632
+ }) => void): () => void;
561
633
  private requireAssetManager;
562
634
  /**
563
635
  * Get a loaded asset by key. Throws if not loaded.
564
636
  */
565
- getAsset<K extends keyof AssetTypes>(key: K): AssetTypes[K];
637
+ getAsset<K extends keyof Cfg['assets']>(key: K): Cfg['assets'][K];
566
638
  /**
567
639
  * Get a loaded asset or undefined if not loaded
568
640
  */
569
- getAssetOrUndefined<K extends keyof AssetTypes>(key: K): AssetTypes[K] | undefined;
641
+ tryGetAsset<K extends keyof Cfg['assets']>(key: K): Cfg['assets'][K] | undefined;
570
642
  /**
571
643
  * Get a handle to an asset with status information
572
644
  */
573
- getAssetHandle<K extends keyof AssetTypes>(key: K): AssetHandle<AssetTypes[K]>;
645
+ getAssetHandle<K extends keyof Cfg['assets']>(key: K): AssetHandle<Cfg['assets'][K]>;
574
646
  /**
575
647
  * Check if an asset is loaded
576
648
  */
577
- isAssetLoaded<K extends keyof AssetTypes>(key: K): boolean;
649
+ isAssetLoaded<K extends keyof Cfg['assets']>(key: K): boolean;
578
650
  /**
579
651
  * Load a single asset
580
652
  */
581
- loadAsset<K extends keyof AssetTypes>(key: K): Promise<AssetTypes[K]>;
653
+ loadAsset<K extends keyof Cfg['assets']>(key: K): Promise<Cfg['assets'][K]>;
582
654
  /**
583
655
  * Load all assets in a group
584
656
  */
@@ -595,11 +667,11 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
595
667
  /**
596
668
  * Transition to a new screen, clearing the stack
597
669
  */
598
- setScreen<K extends keyof ScreenStates>(name: K, config: ScreenStates[K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
670
+ setScreen<K extends keyof Cfg['screens']>(name: K, config: Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
599
671
  /**
600
672
  * Push a screen onto the stack (overlay)
601
673
  */
602
- pushScreen<K extends keyof ScreenStates>(name: K, config: ScreenStates[K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
674
+ pushScreen<K extends keyof Cfg['screens']>(name: K, config: Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? C : never): Promise<void>;
603
675
  /**
604
676
  * Pop the current screen and return to the previous one
605
677
  */
@@ -607,62 +679,124 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
607
679
  /**
608
680
  * Get the current screen name
609
681
  */
610
- getCurrentScreen(): keyof ScreenStates | null;
682
+ getCurrentScreen(): keyof Cfg['screens'] | null;
683
+ /**
684
+ * Get the current screen config (immutable), narrowed to a specific screen.
685
+ * Throws if the current screen doesn't match.
686
+ */
687
+ getScreenConfig<K extends keyof Cfg['screens'] & string>(screen: K): Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
688
+ /**
689
+ * Get the current screen config (immutable).
690
+ * Returns a union of all possible config types.
691
+ */
692
+ getScreenConfig(): {
693
+ [K in keyof Cfg['screens']]: Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
694
+ }[keyof Cfg['screens']];
695
+ /**
696
+ * Get the current screen config narrowed to a specific screen, or undefined if not on that screen.
697
+ */
698
+ tryGetScreenConfig<K extends keyof Cfg['screens'] & string>(screen: K): (Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never) | undefined;
611
699
  /**
612
- * Get the current screen config (immutable)
700
+ * Get the current screen config or undefined.
701
+ * Returns a union of all possible config types, or undefined.
613
702
  */
614
- getScreenConfig<K extends keyof ScreenStates>(): ScreenStates[K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
703
+ tryGetScreenConfig(): {
704
+ [K in keyof Cfg['screens']]: Cfg['screens'][K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never;
705
+ }[keyof Cfg['screens']] | undefined;
615
706
  /**
616
- * Get the current screen config or null
707
+ * Get the current screen state (mutable), narrowed to a specific screen.
708
+ * Throws if the current screen doesn't match.
617
709
  */
618
- getScreenConfigOrNull<K extends keyof ScreenStates>(): (ScreenStates[K] extends ScreenDefinition<infer C, any> ? Readonly<C> : never) | null;
710
+ getScreenState<K extends keyof Cfg['screens'] & string>(screen: K): Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never;
619
711
  /**
620
- * Get the current screen state (mutable)
712
+ * Get the current screen state (mutable).
713
+ * Returns a union of all possible state types.
621
714
  */
622
- getScreenState<K extends keyof ScreenStates>(): ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never;
715
+ getScreenState(): {
716
+ [K in keyof Cfg['screens']]: Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never;
717
+ }[keyof Cfg['screens']];
623
718
  /**
624
- * Get the current screen state or null
719
+ * Get the current screen state narrowed to a specific screen, or undefined if not on that screen.
625
720
  */
626
- getScreenStateOrNull<K extends keyof ScreenStates>(): (ScreenStates[K] extends ScreenDefinition<any, infer S> ? S : never) | null;
721
+ tryGetScreenState<K extends keyof Cfg['screens'] & string>(screen: K): (Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never) | undefined;
627
722
  /**
628
- * Update the current screen state
723
+ * Get the current screen state or undefined.
724
+ * Returns a union of all possible state types, or undefined.
629
725
  */
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;
726
+ tryGetScreenState(): {
727
+ [K in keyof Cfg['screens']]: Cfg['screens'][K] extends ScreenDefinition<any, infer S> ? S : never;
728
+ }[keyof Cfg['screens']] | undefined;
729
+ /**
730
+ * Update the current screen state, narrowed to a specific screen.
731
+ * Throws if the current screen doesn't match.
732
+ */
733
+ 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;
734
+ /**
735
+ * Update the current screen state.
736
+ */
737
+ 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
738
  /**
632
739
  * Check if a screen is the current screen
633
740
  */
634
- isCurrentScreen(screenName: keyof ScreenStates): boolean;
741
+ isCurrentScreen(screenName: keyof Cfg['screens']): boolean;
635
742
  /**
636
743
  * Check if a screen is active (current or in stack)
637
744
  */
638
- isScreenActive(screenName: keyof ScreenStates): boolean;
745
+ isScreenActive(screenName: keyof Cfg['screens']): boolean;
639
746
  /**
640
747
  * Get the screen stack depth
641
748
  */
642
749
  getScreenStackDepth(): number;
643
750
  /**
644
- * Internal method to set the asset manager and drain pending bundle assets
751
+ * Internal method to set the asset manager and drain pending plugin assets
645
752
  * @internal Used by ECSpressoBuilder
646
753
  */
647
- _setAssetManager(manager: AssetManager<AssetTypes>): void;
754
+ _setAssetManager(manager: AssetManager<Cfg['assets']>): void;
648
755
  /**
649
- * Internal method to set the screen manager and drain pending bundle screens
756
+ * Internal method to set the screen manager and drain pending plugin screens
650
757
  * @internal Used by ECSpressoBuilder
651
758
  */
652
- _setScreenManager(manager: ScreenManager<ScreenStates>): void;
759
+ _setScreenManager(manager: ScreenManager<Cfg['screens']>): void;
653
760
  /** @internal */
654
- _hasPendingBundleAssets(): boolean;
761
+ _hasPendingPluginAssets(): boolean;
655
762
  /** @internal */
656
- _hasPendingBundleScreens(): boolean;
763
+ _hasPendingPluginScreens(): boolean;
657
764
  /**
658
765
  * Internal method to set the fixed timestep interval
659
766
  * @internal Used by ECSpressoBuilder
660
767
  */
661
768
  _setFixedDt(dt: number): void;
662
769
  /**
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;
770
+ * Register an asset definition for deferred registration.
771
+ * @internal Used by plugins that need to register assets
772
+ */
773
+ _registerAsset(key: string, definition: AssetDefinition<unknown>): void;
774
+ /**
775
+ * Register a screen definition for deferred registration.
776
+ * @internal Used by plugins that need to register screens
777
+ */
778
+ _registerScreen(name: string, definition: ScreenDefinition<any, any>): void;
779
+ /**
780
+ * Install a plugin into this ECSpresso instance.
781
+ * Deduplicates by plugin ID. Composite plugins call this in their install function.
782
+ */
783
+ installPlugin(plugin: Plugin<any, any, any, any, any, any>): this;
784
+ /**
785
+ * Create a plugin factory from the built world's types.
786
+ * Returns a definePlugin equivalent with no manual type parameters.
787
+ */
788
+ pluginFactory(): <PL extends string = never, PG extends string = never, PAG extends string = never, PRQ extends string = never>(config: {
789
+ id: string;
790
+ install: (world: ECSpresso<Cfg>) => void;
791
+ }) => Plugin<Cfg, EmptyConfig, PL, PG, PAG, PRQ>;
792
+ /**
793
+ * Call a helper factory with this world instance, inferring the full world type.
794
+ * Eliminates the need for a separate `type ECS = typeof ecs` ceremony.
795
+ *
796
+ * @example
797
+ * ```typescript
798
+ * const helpers = ecs.getHelpers(createStateMachineHelpers);
799
+ * ```
800
+ */
801
+ getHelpers<H>(factory: (world: this) => H): H;
668
802
  }