ecspresso 0.10.2 → 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 (94) hide show
  1. package/README.md +256 -148
  2. package/dist/asset-manager.d.ts +16 -16
  3. package/dist/asset-types.d.ts +18 -16
  4. package/dist/command-buffer.d.ts +30 -20
  5. package/dist/ecspresso-builder.d.ts +193 -0
  6. package/dist/ecspresso.d.ts +323 -209
  7. package/dist/entity-manager.d.ts +76 -30
  8. package/dist/event-bus.d.ts +6 -1
  9. package/dist/index.d.ts +6 -13
  10. package/dist/plugin.d.ts +61 -0
  11. package/dist/plugins/audio.d.ts +273 -0
  12. package/dist/{bundles/utils → plugins}/bounds.d.ts +20 -26
  13. package/dist/plugins/camera.d.ts +88 -0
  14. package/dist/plugins/collision.d.ts +285 -0
  15. package/dist/plugins/coroutine.d.ts +126 -0
  16. package/dist/plugins/diagnostics.d.ts +49 -0
  17. package/dist/{bundles/utils → plugins}/input.d.ts +22 -29
  18. package/dist/plugins/particles.d.ts +225 -0
  19. package/dist/plugins/physics2D.d.ts +163 -0
  20. package/dist/plugins/renderers/renderer2D.d.ts +262 -0
  21. package/dist/plugins/spatial-index.d.ts +58 -0
  22. package/dist/plugins/sprite-animation.d.ts +150 -0
  23. package/dist/plugins/state-machine.d.ts +244 -0
  24. package/dist/plugins/timers.d.ts +151 -0
  25. package/dist/{bundles/utils → plugins}/transform.d.ts +21 -22
  26. package/dist/plugins/tween.d.ts +162 -0
  27. package/dist/reactive-query-manager.d.ts +14 -3
  28. package/dist/resource-manager.d.ts +64 -23
  29. package/dist/screen-manager.d.ts +21 -15
  30. package/dist/screen-types.d.ts +15 -11
  31. package/dist/src/index.js +4 -0
  32. package/dist/src/index.js.map +25 -0
  33. package/dist/src/plugins/audio.js +4 -0
  34. package/dist/src/plugins/audio.js.map +10 -0
  35. package/dist/src/plugins/bounds.js +4 -0
  36. package/dist/src/plugins/bounds.js.map +10 -0
  37. package/dist/src/plugins/camera.js +4 -0
  38. package/dist/src/plugins/camera.js.map +10 -0
  39. package/dist/src/plugins/collision.js +4 -0
  40. package/dist/src/plugins/collision.js.map +11 -0
  41. package/dist/src/plugins/coroutine.js +4 -0
  42. package/dist/src/plugins/coroutine.js.map +10 -0
  43. package/dist/src/plugins/diagnostics.js +5 -0
  44. package/dist/src/plugins/diagnostics.js.map +10 -0
  45. package/dist/src/plugins/input.js +4 -0
  46. package/dist/src/plugins/input.js.map +10 -0
  47. package/dist/src/plugins/particles.js +4 -0
  48. package/dist/src/plugins/particles.js.map +10 -0
  49. package/dist/src/plugins/physics2D.js +4 -0
  50. package/dist/src/plugins/physics2D.js.map +11 -0
  51. package/dist/src/plugins/renderers/renderer2D.js +4 -0
  52. package/dist/src/plugins/renderers/renderer2D.js.map +10 -0
  53. package/dist/src/plugins/spatial-index.js +4 -0
  54. package/dist/src/plugins/spatial-index.js.map +11 -0
  55. package/dist/src/plugins/sprite-animation.js +4 -0
  56. package/dist/src/plugins/sprite-animation.js.map +10 -0
  57. package/dist/src/plugins/state-machine.js +4 -0
  58. package/dist/src/plugins/state-machine.js.map +10 -0
  59. package/dist/src/plugins/timers.js +4 -0
  60. package/dist/src/plugins/timers.js.map +10 -0
  61. package/dist/src/plugins/transform.js +4 -0
  62. package/dist/src/plugins/transform.js.map +10 -0
  63. package/dist/src/plugins/tween.js +4 -0
  64. package/dist/src/plugins/tween.js.map +11 -0
  65. package/dist/system-builder.d.ts +75 -112
  66. package/dist/type-utils.d.ts +247 -7
  67. package/dist/types.d.ts +58 -39
  68. package/dist/utils/check-required-cycle.d.ts +12 -0
  69. package/dist/utils/easing.d.ts +71 -0
  70. package/dist/utils/math.d.ts +67 -0
  71. package/dist/utils/narrowphase.d.ts +63 -0
  72. package/dist/utils/spatial-hash.d.ts +53 -0
  73. package/package.json +65 -27
  74. package/dist/bundle.d.ts +0 -123
  75. package/dist/bundles/renderers/renderer2D.d.ts +0 -220
  76. package/dist/bundles/renderers/renderer2D.js +0 -4
  77. package/dist/bundles/renderers/renderer2D.js.map +0 -10
  78. package/dist/bundles/utils/bounds.js +0 -4
  79. package/dist/bundles/utils/bounds.js.map +0 -10
  80. package/dist/bundles/utils/collision.d.ts +0 -204
  81. package/dist/bundles/utils/collision.js +0 -4
  82. package/dist/bundles/utils/collision.js.map +0 -10
  83. package/dist/bundles/utils/input.js +0 -4
  84. package/dist/bundles/utils/input.js.map +0 -10
  85. package/dist/bundles/utils/movement.d.ts +0 -86
  86. package/dist/bundles/utils/movement.js +0 -4
  87. package/dist/bundles/utils/movement.js.map +0 -10
  88. package/dist/bundles/utils/timers.d.ts +0 -172
  89. package/dist/bundles/utils/timers.js +0 -4
  90. package/dist/bundles/utils/timers.js.map +0 -10
  91. package/dist/bundles/utils/transform.js +0 -4
  92. package/dist/bundles/utils/transform.js.map +0 -10
  93. package/dist/index.js +0 -4
  94. package/dist/index.js.map +0 -22
@@ -2,6 +2,7 @@
2
2
  * Utility types for ECSpresso ECS framework
3
3
  * This file contains reusable type helpers used across the codebase
4
4
  */
5
+ import type { ScreenDefinition } from './screen-types';
5
6
  /**
6
7
  * Check if two types are exactly the same for overlapping keys
7
8
  */
@@ -16,11 +17,89 @@ export type TypesAreCompatible<T extends Record<string, any>, U extends Record<s
16
17
  [K in keyof T & keyof U]: ExactlyCompatible<T[K], U[K]>;
17
18
  }[keyof T & keyof U] extends false ? false : true;
18
19
  /**
19
- * Bundle compatibility checker.
20
- * Returns true if bundles can be merged without type conflicts.
21
- * All overlapping keys across all type categories must have identical types.
20
+ * Single config object that bundles all 5 world type dimensions.
21
+ * Replaces the 5 positional type params (ComponentTypes, EventTypes,
22
+ * ResourceTypes, AssetTypes, ScreenStates) throughout the codebase.
22
23
  */
23
- export type BundlesAreCompatible<C1 extends Record<string, any>, C2 extends Record<string, any>, E1 extends Record<string, any>, E2 extends Record<string, any>, R1 extends Record<string, any>, R2 extends Record<string, any>, A1 extends Record<string, unknown> = {}, A2 extends Record<string, unknown> = {}, S1 extends Record<string, any> = {}, S2 extends Record<string, any> = {}> = TypesAreCompatible<C1, C2> extends true ? TypesAreCompatible<E1, E2> extends true ? TypesAreCompatible<R1, R2> extends true ? TypesAreCompatible<A1, A2> extends true ? TypesAreCompatible<S1, S2> : false : false : false : false;
24
+ export interface WorldConfig {
25
+ readonly components: Record<string, any>;
26
+ readonly events: Record<string, any>;
27
+ readonly resources: Record<string, any>;
28
+ readonly assets: Record<string, unknown>;
29
+ readonly screens: Record<string, ScreenDefinition<any, any>>;
30
+ }
31
+ /**
32
+ * Construct a WorldConfig from individual type dimensions.
33
+ * All parameters default to empty records.
34
+ */
35
+ export type WorldConfigFrom<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>> = {}> = {
36
+ readonly components: C;
37
+ readonly events: E;
38
+ readonly resources: R;
39
+ readonly assets: A;
40
+ readonly screens: S;
41
+ };
42
+ /**
43
+ * Empty WorldConfig with all slots defaulting to {}.
44
+ */
45
+ export type EmptyConfig = WorldConfigFrom;
46
+ /**
47
+ * Merge two WorldConfig types by intersecting each slot.
48
+ */
49
+ export type MergeConfigs<A extends WorldConfig, B extends WorldConfig> = {
50
+ readonly components: A['components'] & B['components'];
51
+ readonly events: A['events'] & B['events'];
52
+ readonly resources: A['resources'] & B['resources'];
53
+ readonly assets: A['assets'] & B['assets'];
54
+ readonly screens: A['screens'] & B['screens'];
55
+ };
56
+ export type WithComponents<Cfg extends WorldConfig, T> = {
57
+ readonly components: Cfg['components'] & T;
58
+ readonly events: Cfg['events'];
59
+ readonly resources: Cfg['resources'];
60
+ readonly assets: Cfg['assets'];
61
+ readonly screens: Cfg['screens'];
62
+ };
63
+ export type WithEvents<Cfg extends WorldConfig, T> = {
64
+ readonly components: Cfg['components'];
65
+ readonly events: Cfg['events'] & T;
66
+ readonly resources: Cfg['resources'];
67
+ readonly assets: Cfg['assets'];
68
+ readonly screens: Cfg['screens'];
69
+ };
70
+ export type WithResources<Cfg extends WorldConfig, T> = {
71
+ readonly components: Cfg['components'];
72
+ readonly events: Cfg['events'];
73
+ readonly resources: Cfg['resources'] & T;
74
+ readonly assets: Cfg['assets'];
75
+ readonly screens: Cfg['screens'];
76
+ };
77
+ export type WithAssets<Cfg extends WorldConfig, T> = {
78
+ readonly components: Cfg['components'];
79
+ readonly events: Cfg['events'];
80
+ readonly resources: Cfg['resources'];
81
+ readonly assets: Cfg['assets'] & T;
82
+ readonly screens: Cfg['screens'];
83
+ };
84
+ export type WithScreens<Cfg extends WorldConfig, T> = {
85
+ readonly components: Cfg['components'];
86
+ readonly events: Cfg['events'];
87
+ readonly resources: Cfg['resources'];
88
+ readonly assets: Cfg['assets'];
89
+ readonly screens: Cfg['screens'] & T;
90
+ };
91
+ /**
92
+ * Check if two WorldConfig types are compatible (no conflicting keys
93
+ * across any slot).
94
+ */
95
+ export type ConfigsAreCompatible<A extends WorldConfig, B extends WorldConfig> = TypesAreCompatible<A['components'], B['components']> extends true ? TypesAreCompatible<A['events'], B['events']> extends true ? TypesAreCompatible<A['resources'], B['resources']> extends true ? TypesAreCompatible<A['assets'], B['assets']> extends true ? TypesAreCompatible<A['screens'], B['screens']> : false : false : false : false;
96
+ /**
97
+ * Check if a Requires config is satisfied by an Accumulated config.
98
+ * Checks all five WorldConfig slots (components, events, resources, assets, screens).
99
+ * When Required is EmptyConfig, all slots have `keyof {} = never`,
100
+ * and `never extends X = true`, so empty requirements are always satisfied.
101
+ */
102
+ export type RequirementsSatisfied<Accumulated extends WorldConfig, Required extends WorldConfig> = keyof Required['components'] extends keyof Accumulated['components'] ? keyof Required['events'] extends keyof Accumulated['events'] ? keyof Required['resources'] extends keyof Accumulated['resources'] ? keyof Required['assets'] extends keyof Accumulated['assets'] ? keyof Required['screens'] extends keyof Accumulated['screens'] ? true : false : false : false : false : false;
24
103
  /**
25
104
  * Utility type for merging two types
26
105
  */
@@ -29,7 +108,168 @@ export type Merge<T1, T2> = T1 & T2;
29
108
  * Utility type for merging an array of types
30
109
  */
31
110
  export type MergeAll<T extends any[]> = T extends [infer First, ...infer Rest] ? Rest extends [] ? First : Merge<First, MergeAll<Rest>> : {};
32
- export {};
33
111
  /**
34
- * Basic utility types that can be used independently
35
- */
112
+ * Wildcard ECSpresso type that any concrete instance is assignable to.
113
+ * Use as a generic constraint for functions that accept any ECSpresso world.
114
+ * Matches the phantom _cfg property declared on the ECSpresso class.
115
+ */
116
+ export type AnyECSpresso = {
117
+ readonly _cfg: WorldConfig;
118
+ };
119
+ /**
120
+ * Wildcard Plugin type that any concrete plugin is assignable to.
121
+ * Matches the phantom _cfg and _requires properties declared on the Plugin interface.
122
+ */
123
+ export type AnyPlugin = {
124
+ readonly _cfg?: WorldConfig;
125
+ readonly _requires?: WorldConfig;
126
+ };
127
+ /**
128
+ * Extract the full WorldConfig from a Plugin or ECSpresso instance
129
+ */
130
+ export type ConfigOf<B> = B extends {
131
+ readonly _cfg: infer Cfg extends WorldConfig;
132
+ } ? Cfg : never;
133
+ /**
134
+ * Extract the ComponentTypes from a Plugin or ECSpresso instance
135
+ */
136
+ export type ComponentsOf<B> = B extends {
137
+ readonly _cfg: {
138
+ components: infer C extends Record<string, any>;
139
+ };
140
+ } ? C : B extends {
141
+ readonly _cfg?: {
142
+ components: infer C extends Record<string, any>;
143
+ };
144
+ } ? C : never;
145
+ /**
146
+ * Extract the EventTypes from a Plugin or ECSpresso instance
147
+ */
148
+ export type EventsOf<B> = B extends {
149
+ readonly _cfg: {
150
+ events: infer E extends Record<string, any>;
151
+ };
152
+ } ? E : B extends {
153
+ readonly _cfg?: {
154
+ events: infer E extends Record<string, any>;
155
+ };
156
+ } ? E : never;
157
+ /**
158
+ * Extract the ResourceTypes from a Plugin or ECSpresso instance
159
+ */
160
+ export type ResourcesOf<B> = B extends {
161
+ readonly _cfg: {
162
+ resources: infer R extends Record<string, any>;
163
+ };
164
+ } ? R : B extends {
165
+ readonly _cfg?: {
166
+ resources: infer R extends Record<string, any>;
167
+ };
168
+ } ? R : never;
169
+ /**
170
+ * Extract AssetTypes from a Plugin or ECSpresso instance
171
+ */
172
+ export type AssetTypesOf<B> = B extends {
173
+ readonly _cfg: {
174
+ assets: infer A extends Record<string, unknown>;
175
+ };
176
+ } ? A : B extends {
177
+ readonly _cfg?: {
178
+ assets: infer A extends Record<string, unknown>;
179
+ };
180
+ } ? A : never;
181
+ /**
182
+ * Extract ScreenStates from a Plugin or ECSpresso instance
183
+ */
184
+ export type ScreenStatesOf<B> = B extends {
185
+ readonly _cfg: {
186
+ screens: infer S extends Record<string, ScreenDefinition<any, any>>;
187
+ };
188
+ } ? S : B extends {
189
+ readonly _cfg?: {
190
+ screens: infer S extends Record<string, ScreenDefinition<any, any>>;
191
+ };
192
+ } ? S : never;
193
+ /**
194
+ * Extract the system Labels from a Plugin instance
195
+ */
196
+ export type LabelsOf<B> = B extends {
197
+ readonly _labels?: infer L;
198
+ } ? L extends string ? L : never : never;
199
+ /**
200
+ * Extract the system Groups from a Plugin instance
201
+ */
202
+ export type GroupsOf<B> = B extends {
203
+ readonly _groups?: infer G;
204
+ } ? G extends string ? G : never : never;
205
+ /**
206
+ * Extract the AssetGroupNames from a Plugin instance
207
+ */
208
+ export type AssetGroupNamesOf<B> = B extends {
209
+ readonly _assetGroupNames?: infer AG;
210
+ } ? AG extends string ? AG : never : never;
211
+ /**
212
+ * Extract the ReactiveQueryNames from a Plugin instance
213
+ */
214
+ export type ReactiveQueryNamesOf<B> = B extends {
215
+ readonly _reactiveQueryNames?: infer RQ;
216
+ } ? RQ extends string ? RQ : never : never;
217
+ /**
218
+ * Extract ComponentTypes from an ECSpresso world instance type.
219
+ */
220
+ export type ComponentsOfWorld<W> = W extends {
221
+ readonly _cfg: {
222
+ components: infer C extends Record<string, any>;
223
+ };
224
+ } ? C : never;
225
+ /**
226
+ * Extract EventTypes from an ECSpresso world instance type.
227
+ */
228
+ export type EventsOfWorld<W> = W extends {
229
+ readonly _cfg: {
230
+ events: infer E extends Record<string, any>;
231
+ };
232
+ } ? E : never;
233
+ /**
234
+ * Extract AssetTypes from an ECSpresso world instance type.
235
+ */
236
+ export type AssetsOfWorld<W> = W extends {
237
+ readonly _cfg: {
238
+ assets: infer A extends Record<string, unknown>;
239
+ };
240
+ } ? A : never;
241
+ /**
242
+ * Extract ScreenStates from an ECSpresso world instance type
243
+ */
244
+ export type ScreenStatesOfWorld<W> = W extends {
245
+ readonly _cfg: {
246
+ screens: infer S extends Record<string, ScreenDefinition<any, any>>;
247
+ };
248
+ } ? S : never;
249
+ /**
250
+ * Extract event names from an EventTypes record whose payload extends the given shape.
251
+ * Eliminates the need for each plugin to define its own mapped filter type.
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * interface MyEventData { entityId: number }
256
+ * type MyEventName<ET> = EventNameMatching<ET, MyEventData>;
257
+ * ```
258
+ */
259
+ export type EventNameMatching<ET extends Record<string, any>, Payload> = {
260
+ [K in keyof ET & string]: ET[K] extends Payload ? K : never;
261
+ }[keyof ET & string];
262
+ /**
263
+ * Extract the channel type from a world's AudioSource component.
264
+ * Falls back to `string` if the world has no audioSource component.
265
+ */
266
+ export type ChannelOfWorld<W> = W extends {
267
+ readonly _cfg: {
268
+ components: {
269
+ audioSource: {
270
+ channel: infer Ch extends string;
271
+ };
272
+ };
273
+ };
274
+ } ? Ch : string;
275
+ export {};
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import ECSpresso from "./ecspresso";
2
+ import type { WorldConfig, EmptyConfig, WorldConfigFrom } from "./type-utils";
2
3
  /**
3
4
  * Execution phase for systems. Systems are grouped by phase and executed
4
5
  * in this fixed order: preUpdate -> fixedUpdate -> update -> postUpdate -> render.
@@ -18,17 +19,6 @@ export interface RemoveEntityOptions {
18
19
  */
19
20
  cascade?: boolean;
20
21
  }
21
- /**
22
- * Event data emitted when an entity's parent changes
23
- */
24
- export interface HierarchyChangedEvent {
25
- /** The entity whose parent changed */
26
- entityId: number;
27
- /** The previous parent, or null if entity had no parent */
28
- oldParent: number | null;
29
- /** The new parent, or null if entity was orphaned */
30
- newParent: number | null;
31
- }
32
22
  /**
33
23
  * Options for hierarchy traversal methods
34
24
  */
@@ -47,10 +37,6 @@ export interface HierarchyEntry {
47
37
  /** Depth in the hierarchy (0 for roots) */
48
38
  depth: number;
49
39
  }
50
- export interface EventHandler<T> {
51
- callback: (data: T) => void;
52
- once: boolean;
53
- }
54
40
  export interface FilteredEntity<ComponentTypes, WithComponents extends keyof ComponentTypes = never, WithoutComponents extends keyof ComponentTypes = never, OptionalComponents extends keyof ComponentTypes = never> {
55
41
  id: number;
56
42
  components: Omit<Partial<ComponentTypes>, WithoutComponents | OptionalComponents> & {
@@ -124,7 +110,7 @@ export type QueryDefinition<ComponentTypes extends Record<string, any>, WithComp
124
110
  *
125
111
  * world.addSystem('movement')
126
112
  * .addQuery('entities', movingEntitiesQuery)
127
- * .setProcess((queries) => {
113
+ * .setProcess(({ queries }) => {
128
114
  * for (const entity of queries.entities) {
129
115
  * updatePosition(entity);
130
116
  * }
@@ -138,7 +124,7 @@ export declare function createQueryDefinition<ComponentTypes extends Record<stri
138
124
  optional?: ReadonlyArray<keyof ComponentTypes>;
139
125
  parentHas?: ReadonlyArray<keyof ComponentTypes>;
140
126
  }>(queryDef: QueryDef): QueryDef;
141
- export interface System<ComponentTypes extends Record<string, any> = {}, WithComponents extends keyof ComponentTypes = never, WithoutComponents extends keyof ComponentTypes = never, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, any> = {}> {
127
+ export interface System<Cfg extends WorldConfig = EmptyConfig, WithComponents extends keyof Cfg['components'] = never, WithoutComponents extends keyof Cfg['components'] = never> {
142
128
  label: string;
143
129
  /**
144
130
  * System priority - higher values execute first (default: 0)
@@ -159,53 +145,86 @@ export interface System<ComponentTypes extends Record<string, any> = {}, WithCom
159
145
  * Screens where this system should run. If specified, system only runs
160
146
  * when current screen is in this list.
161
147
  */
162
- inScreens?: string[];
148
+ inScreens?: ReadonlyArray<keyof Cfg['screens'] & string>;
163
149
  /**
164
150
  * Screens where this system should NOT run. If specified, system skips
165
151
  * when current screen is in this list.
166
152
  */
167
- excludeScreens?: string[];
153
+ excludeScreens?: ReadonlyArray<keyof Cfg['screens'] & string>;
168
154
  /**
169
155
  * Assets that must be loaded for this system to run.
170
156
  * System will be skipped if any required asset is not loaded.
171
157
  */
172
- requiredAssets?: string[];
158
+ requiredAssets?: ReadonlyArray<keyof Cfg['assets'] & string>;
159
+ /**
160
+ * When true, the system's process function runs even when all queries
161
+ * return zero entities. Default is false (system is skipped when all
162
+ * queries are empty).
163
+ */
164
+ runWhenEmpty?: boolean;
173
165
  entityQueries?: {
174
- [queryName: string]: QueryConfig<ComponentTypes, WithComponents, WithoutComponents>;
166
+ [queryName: string]: QueryConfig<Cfg['components'], WithComponents, WithoutComponents>;
175
167
  };
176
168
  /**
177
- * Process method that runs during each update cycle
178
- * @param queries The entity queries results based on system's entityQueries definition
179
- * @param deltaTime Time elapsed since the last update in seconds
180
- * @param ecs The ECSpresso instance providing access to all ECS functionality
169
+ * Process method that runs during each update cycle.
170
+ * Receives a single context object with queries, dt, and ecs.
181
171
  */
182
- process?(queries: {
183
- [queryName: string]: Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>;
184
- } | Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>, deltaTime: number, ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
172
+ process?(ctx: {
173
+ queries: {
174
+ [queryName: string]: Array<FilteredEntity<Cfg['components'], WithComponents, WithoutComponents>>;
175
+ };
176
+ dt: number;
177
+ ecs: ECSpresso<Cfg>;
178
+ }): void;
185
179
  /**
186
180
  * Lifecycle hook called when the system is initialized
187
181
  * This is called when ECSpresso.initialize() is invoked, after resources are initialized
188
182
  * Use this for one-time initialization that depends on resources
189
183
  * @param ecs The ECSpresso instance providing access to all ECS functionality
190
184
  */
191
- onInitialize?(ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void | Promise<void>;
185
+ onInitialize?(ecs: ECSpresso<Cfg>): void | Promise<void>;
192
186
  /**
193
187
  * Lifecycle hook called when the system is detached from the ECS
194
188
  * @param ecs The ECSpresso instance providing access to all ECS functionality
195
189
  */
196
- onDetach?(ecs: import("./ecspresso").default<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
190
+ onDetach?(ecs: import("./ecspresso").default<Cfg>): void;
191
+ /**
192
+ * Per-query callbacks that fire once per entity the first time it appears
193
+ * in a query's results. Fires before process. Automatic cleanup when
194
+ * entity leaves query (component removed, entity destroyed) so re-entry
195
+ * fires the callback again.
196
+ */
197
+ onEntityEnter?: Record<string, (ctx: {
198
+ entity: FilteredEntity<Cfg['components'], WithComponents, WithoutComponents>;
199
+ ecs: ECSpresso<Cfg>;
200
+ }) => void>;
197
201
  /**
198
202
  * Event handlers for specific event types
199
203
  */
200
204
  eventHandlers?: {
201
- [EventName in keyof EventTypes]?: {
202
- /**
203
- * Event handler function
204
- * @param data The event data specific to this event type
205
- * @param ecs The ECSpresso instance providing access to all ECS functionality
206
- */
207
- handler(data: EventTypes[EventName], ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
208
- };
205
+ [EventName in keyof Cfg['events']]?: (ctx: {
206
+ data: Cfg['events'][EventName];
207
+ ecs: ECSpresso<Cfg>;
208
+ }) => void;
209
209
  };
210
210
  }
211
- export type { Merge, MergeAll } from './type-utils';
211
+ /**
212
+ * Typed world interface for plugin helpers and structural typing.
213
+ *
214
+ * Generic over component types `C`:
215
+ * - `BaseWorld` (no param): defaults to `{}`, meaning component-accessing methods
216
+ * cannot be called (keys resolve to `never`). Use for functions that only need
217
+ * `removeEntity`, `getResource`, etc.
218
+ * - `BaseWorld<MyComponents>`: narrows `getComponent`, `hasComponent`, `markChanged`,
219
+ * `spawn`, and command buffer methods to the declared component map.
220
+ *
221
+ * Structural typing ensures any `ECSpresso<Cfg>` where `Cfg['components']` is a
222
+ * superset of `C` satisfies `BaseWorld<C>`.
223
+ */
224
+ type _BaseWorldCfg<C extends Record<string, any>> = WorldConfigFrom<C, Record<string, any>, Record<string, any>, Record<string, unknown>, Record<string, any>>;
225
+ type _EventBus = import("./event-bus").default<Record<string, any>>;
226
+ export type BaseWorld<C extends Record<string, any> = {}> = Pick<ECSpresso<_BaseWorldCfg<C>>, 'getComponent' | 'hasComponent' | 'removeEntity' | 'spawn' | 'markChanged' | 'getResource' | 'hasResource'> & {
227
+ eventBus: Pick<_EventBus, 'publish'>;
228
+ commands: Pick<import("./command-buffer").default<_BaseWorldCfg<C>>, 'spawn' | 'removeEntity' | 'addComponent' | 'removeComponent'>;
229
+ };
230
+ export type { Merge, MergeAll, TypesAreCompatible, ComponentsOf, EventsOf, ResourcesOf, LabelsOf, GroupsOf, AssetGroupNamesOf, ReactiveQueryNamesOf, AssetTypesOf, ScreenStatesOf, ComponentsOfWorld, EventsOfWorld, AssetsOfWorld, ScreenStatesOfWorld, AnyECSpresso, AnyPlugin, EventNameMatching, ChannelOfWorld, WorldConfig, EmptyConfig, WorldConfigFrom, MergeConfigs, ConfigsAreCompatible, ConfigOf, WithComponents, WithEvents, WithResources, WithAssets, WithScreens } from './type-utils';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * BFS cycle detection for required component graphs.
3
+ * Shared by ECSpresso and Plugin.
4
+ *
5
+ * @param trigger The component that triggers the requirement
6
+ * @param newRequired The component about to be added as required
7
+ * @param getRequirements Callback returning the existing requirements for a component
8
+ * @throws Error if adding trigger→newRequired would create a cycle
9
+ */
10
+ export declare function checkRequiredCycle<K>(trigger: K, newRequired: K, getRequirements: (component: K) => Iterable<{
11
+ component: K;
12
+ }> | undefined): void;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Easing Functions
3
+ *
4
+ * 31 standard easing functions for animation. Pure math, no dependencies.
5
+ */
6
+ export type EasingFn = (t: number) => number;
7
+ export declare function linear(t: number): number;
8
+ export declare function easeInQuad(t: number): number;
9
+ export declare function easeOutQuad(t: number): number;
10
+ export declare function easeInOutQuad(t: number): number;
11
+ export declare function easeInCubic(t: number): number;
12
+ export declare function easeOutCubic(t: number): number;
13
+ export declare function easeInOutCubic(t: number): number;
14
+ export declare function easeInQuart(t: number): number;
15
+ export declare function easeOutQuart(t: number): number;
16
+ export declare function easeInOutQuart(t: number): number;
17
+ export declare function easeInQuint(t: number): number;
18
+ export declare function easeOutQuint(t: number): number;
19
+ export declare function easeInOutQuint(t: number): number;
20
+ export declare function easeInSine(t: number): number;
21
+ export declare function easeOutSine(t: number): number;
22
+ export declare function easeInOutSine(t: number): number;
23
+ export declare function easeInExpo(t: number): number;
24
+ export declare function easeOutExpo(t: number): number;
25
+ export declare function easeInOutExpo(t: number): number;
26
+ export declare function easeInCirc(t: number): number;
27
+ export declare function easeOutCirc(t: number): number;
28
+ export declare function easeInOutCirc(t: number): number;
29
+ export declare function easeInBack(t: number): number;
30
+ export declare function easeOutBack(t: number): number;
31
+ export declare function easeInOutBack(t: number): number;
32
+ export declare function easeInElastic(t: number): number;
33
+ export declare function easeOutElastic(t: number): number;
34
+ export declare function easeInOutElastic(t: number): number;
35
+ export declare function easeOutBounce(t: number): number;
36
+ export declare function easeInBounce(t: number): number;
37
+ export declare function easeInOutBounce(t: number): number;
38
+ /** Runtime lookup of all easing functions by name */
39
+ export declare const easings: {
40
+ readonly linear: typeof linear;
41
+ readonly easeInQuad: typeof easeInQuad;
42
+ readonly easeOutQuad: typeof easeOutQuad;
43
+ readonly easeInOutQuad: typeof easeInOutQuad;
44
+ readonly easeInCubic: typeof easeInCubic;
45
+ readonly easeOutCubic: typeof easeOutCubic;
46
+ readonly easeInOutCubic: typeof easeInOutCubic;
47
+ readonly easeInQuart: typeof easeInQuart;
48
+ readonly easeOutQuart: typeof easeOutQuart;
49
+ readonly easeInOutQuart: typeof easeInOutQuart;
50
+ readonly easeInQuint: typeof easeInQuint;
51
+ readonly easeOutQuint: typeof easeOutQuint;
52
+ readonly easeInOutQuint: typeof easeInOutQuint;
53
+ readonly easeInSine: typeof easeInSine;
54
+ readonly easeOutSine: typeof easeOutSine;
55
+ readonly easeInOutSine: typeof easeInOutSine;
56
+ readonly easeInExpo: typeof easeInExpo;
57
+ readonly easeOutExpo: typeof easeOutExpo;
58
+ readonly easeInOutExpo: typeof easeInOutExpo;
59
+ readonly easeInCirc: typeof easeInCirc;
60
+ readonly easeOutCirc: typeof easeOutCirc;
61
+ readonly easeInOutCirc: typeof easeInOutCirc;
62
+ readonly easeInBack: typeof easeInBack;
63
+ readonly easeOutBack: typeof easeOutBack;
64
+ readonly easeInOutBack: typeof easeInOutBack;
65
+ readonly easeInElastic: typeof easeInElastic;
66
+ readonly easeOutElastic: typeof easeOutElastic;
67
+ readonly easeInOutElastic: typeof easeInOutElastic;
68
+ readonly easeInBounce: typeof easeInBounce;
69
+ readonly easeOutBounce: typeof easeOutBounce;
70
+ readonly easeInOutBounce: typeof easeInOutBounce;
71
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared 2D vector math utilities for ECSpresso bundles.
3
+ * All functions are pure — they return new vectors, never mutate inputs.
4
+ */
5
+ /**
6
+ * A 2D vector with x and y components.
7
+ */
8
+ export interface Vector2D {
9
+ x: number;
10
+ y: number;
11
+ }
12
+ /**
13
+ * Create a Vector2D from x and y components.
14
+ */
15
+ export declare function vec2(x: number, y: number): Vector2D;
16
+ /**
17
+ * Return a zero vector {x: 0, y: 0}.
18
+ */
19
+ export declare function vec2Zero(): Vector2D;
20
+ /**
21
+ * Add two vectors component-wise.
22
+ */
23
+ export declare function vec2Add(a: Vector2D, b: Vector2D): Vector2D;
24
+ /**
25
+ * Subtract b from a component-wise.
26
+ */
27
+ export declare function vec2Sub(a: Vector2D, b: Vector2D): Vector2D;
28
+ /**
29
+ * Scale a vector by a scalar.
30
+ */
31
+ export declare function vec2Scale(v: Vector2D, scalar: number): Vector2D;
32
+ /**
33
+ * Negate a vector (flip both components).
34
+ */
35
+ export declare function vec2Negate(v: Vector2D): Vector2D;
36
+ /**
37
+ * Compute the dot product of two vectors.
38
+ */
39
+ export declare function vec2Dot(a: Vector2D, b: Vector2D): number;
40
+ /**
41
+ * Compute the 2D cross product (scalar z-component of the 3D cross product).
42
+ */
43
+ export declare function vec2Cross(a: Vector2D, b: Vector2D): number;
44
+ /**
45
+ * Compute the squared length of a vector. Avoids sqrt when only comparing magnitudes.
46
+ */
47
+ export declare function vec2LengthSq(v: Vector2D): number;
48
+ /**
49
+ * Compute the length (magnitude) of a vector.
50
+ */
51
+ export declare function vec2Length(v: Vector2D): number;
52
+ /**
53
+ * Return a unit vector in the same direction. Returns zero vector if input is zero-length.
54
+ */
55
+ export declare function vec2Normalize(v: Vector2D): Vector2D;
56
+ /**
57
+ * Compute the squared distance between two points. Avoids sqrt when only comparing.
58
+ */
59
+ export declare function vec2DistanceSq(a: Vector2D, b: Vector2D): number;
60
+ /**
61
+ * Compute the distance between two points.
62
+ */
63
+ export declare function vec2Distance(a: Vector2D, b: Vector2D): number;
64
+ /**
65
+ * Check if two vectors are approximately equal within an epsilon tolerance.
66
+ */
67
+ export declare function vec2Equals(a: Vector2D, b: Vector2D, epsilon?: number): boolean;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared Narrowphase Module
3
+ *
4
+ * Provides contact-computing narrowphase tests and a generic collision
5
+ * iteration pipeline used by both the collision plugin (event-only) and
6
+ * the physics2D plugin (impulse response).
7
+ */
8
+ import type { SpatialIndex } from './spatial-hash';
9
+ /** Contact result from a narrowphase test. Normal points from A toward B. */
10
+ export interface Contact {
11
+ normalX: number;
12
+ normalY: number;
13
+ /** Penetration depth (positive = overlapping) */
14
+ depth: number;
15
+ }
16
+ /** Minimum collider data shared by collision and physics bundles. */
17
+ export interface BaseColliderInfo<L extends string = string> {
18
+ entityId: number;
19
+ x: number;
20
+ y: number;
21
+ layer: L;
22
+ collidesWith: readonly L[];
23
+ aabb?: {
24
+ halfWidth: number;
25
+ halfHeight: number;
26
+ };
27
+ circle?: {
28
+ radius: number;
29
+ };
30
+ }
31
+ /**
32
+ * Build a BaseColliderInfo from raw entity/collider component data.
33
+ * Returns null if the entity has neither an AABB nor circle collider.
34
+ * Shared by collision plugin (event-only) and physics2D plugin (impulse response).
35
+ */
36
+ export declare function buildBaseColliderInfo<L extends string>(entityId: number, x: number, y: number, layer: L, collidesWith: readonly L[], aabb: {
37
+ width: number;
38
+ height: number;
39
+ offsetX?: number;
40
+ offsetY?: number;
41
+ } | undefined, circle: {
42
+ radius: number;
43
+ offsetX?: number;
44
+ offsetY?: number;
45
+ } | undefined): BaseColliderInfo<L> | null;
46
+ /**
47
+ * Retrieve the optional spatialIndex resource, returning undefined when absent.
48
+ * Centralizes the cross-plugin typed lookup so individual plugins don't each
49
+ * need to import SpatialIndex or repeat the tryGetResource pattern.
50
+ */
51
+ export declare function tryGetSpatialIndex(tryGetResource: <T>(key: string) => T | undefined): SpatialIndex | undefined;
52
+ export declare function computeAABBvsAABB(ax: number, ay: number, ahw: number, ahh: number, bx: number, by: number, bhw: number, bhh: number): Contact | null;
53
+ export declare function computeCircleVsCircle(ax: number, ay: number, ar: number, bx: number, by: number, br: number): Contact | null;
54
+ export declare function computeAABBvsCircle(aabbX: number, aabbY: number, ahw: number, ahh: number, circleX: number, circleY: number, radius: number): Contact | null;
55
+ export declare function computeContact(a: BaseColliderInfo, b: BaseColliderInfo): Contact | null;
56
+ /**
57
+ * Generic collision detection pipeline: brute-force or broadphase,
58
+ * with layer filtering and contact computation.
59
+ *
60
+ * Uses a context parameter forwarded to the callback to avoid
61
+ * per-frame closure allocation.
62
+ */
63
+ export declare function detectCollisions<I extends BaseColliderInfo, C>(colliders: I[], spatialIndex: SpatialIndex | undefined, onContact: (a: I, b: I, contact: Contact, context: C) => void, context: C): void;