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
@@ -0,0 +1,244 @@
1
+ /**
2
+ * State Machine Plugin for ECSpresso
3
+ *
4
+ * Provides ECS-native finite state machines with guard-based transitions,
5
+ * event-driven transitions, and lifecycle hooks (onEnter, onExit, onUpdate).
6
+ *
7
+ * Each entity gets a `stateMachine` component referencing a shared definition.
8
+ * One system processes all state machine entities each tick.
9
+ */
10
+ import { type Plugin, type BasePluginOptions } from 'ecspresso';
11
+ import type { BaseWorld } from 'ecspresso';
12
+ import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
13
+ /** BaseWorld narrowed to state-machine components for typed access in helpers. */
14
+ type StateMachineWorld = BaseWorld<StateMachineComponentTypes>;
15
+ /**
16
+ * Configuration for a single state in a state machine definition.
17
+ *
18
+ * @template S - Union of state name strings
19
+ * @template W - World interface type for hooks/guards (default: StateMachineWorld)
20
+ */
21
+ export interface StateConfig<S extends string, W extends BaseWorld<StateMachineComponentTypes> = StateMachineWorld> {
22
+ /** Called when entering this state */
23
+ onEnter?(ctx: {
24
+ ecs: W;
25
+ entityId: number;
26
+ }): void;
27
+ /** Called when exiting this state */
28
+ onExit?(ctx: {
29
+ ecs: W;
30
+ entityId: number;
31
+ }): void;
32
+ /** Called each tick while in this state */
33
+ onUpdate?(ctx: {
34
+ ecs: W;
35
+ entityId: number;
36
+ dt: number;
37
+ }): void;
38
+ /** Guard-based transitions evaluated each tick. First passing guard wins. */
39
+ transitions?: ReadonlyArray<{
40
+ target: S;
41
+ guard(ctx: {
42
+ ecs: W;
43
+ entityId: number;
44
+ }): boolean;
45
+ }>;
46
+ /** Event-based transition map: eventName → target state or guarded transition */
47
+ on?: Record<string, S | {
48
+ target: S;
49
+ guard(ctx: {
50
+ ecs: W;
51
+ entityId: number;
52
+ }): boolean;
53
+ }>;
54
+ }
55
+ /**
56
+ * Immutable definition of a state machine. Shared across entities.
57
+ *
58
+ * @template S - Union of state name strings
59
+ */
60
+ export interface StateMachineDefinition<S extends string> {
61
+ readonly id: string;
62
+ readonly initial: S;
63
+ readonly states: {
64
+ readonly [K in S]: StateConfig<S>;
65
+ };
66
+ }
67
+ /**
68
+ * Runtime state machine component stored on each entity.
69
+ *
70
+ * @template S - Union of state name strings (default: string)
71
+ */
72
+ export interface StateMachine<S extends string = string> {
73
+ readonly definition: StateMachineDefinition<string>;
74
+ current: S;
75
+ previous: S | null;
76
+ stateTime: number;
77
+ }
78
+ /**
79
+ * Component types provided by the state machine plugin.
80
+ *
81
+ * @template S - Union of state name strings (default: string)
82
+ */
83
+ export interface StateMachineComponentTypes<S extends string = string> {
84
+ stateMachine: StateMachine<S>;
85
+ }
86
+ /**
87
+ * Event published on every state transition.
88
+ *
89
+ * @template S - Union of state name strings (default: string)
90
+ */
91
+ export interface StateTransitionEvent<S extends string = string> {
92
+ entityId: number;
93
+ from: S;
94
+ to: S;
95
+ definitionId: string;
96
+ }
97
+ /**
98
+ * Event types provided by the state machine plugin.
99
+ *
100
+ * @template S - Union of state name strings (default: string)
101
+ */
102
+ export interface StateMachineEventTypes<S extends string = string> {
103
+ stateTransition: StateTransitionEvent<S>;
104
+ }
105
+ /**
106
+ * Extract the state name union from a StateMachineDefinition.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const enemyFSM = defineStateMachine('enemy', { initial: 'idle', states: { idle: {}, chase: {} } });
111
+ * type EnemyStates = StatesOf<typeof enemyFSM>; // 'idle' | 'chase'
112
+ * type AllStates = StatesOf<typeof enemyFSM> | StatesOf<typeof playerFSM>;
113
+ * ```
114
+ */
115
+ export type StatesOf<D> = D extends StateMachineDefinition<infer S> ? S : never;
116
+ /**
117
+ * Configuration options for the state machine plugin.
118
+ */
119
+ export interface StateMachinePluginOptions<G extends string = 'stateMachine'> extends BasePluginOptions<G> {
120
+ }
121
+ /**
122
+ * Define a state machine with type-safe state names.
123
+ *
124
+ * @template S - Union of state name strings, inferred from `states` keys
125
+ * @param id - Unique identifier for this definition
126
+ * @param config - Initial state and state configurations
127
+ * @returns A frozen StateMachineDefinition
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const enemyFSM = defineStateMachine('enemy', {
132
+ * initial: 'idle',
133
+ * states: {
134
+ * idle: {
135
+ * onEnter: ({ ecs, entityId }) => { ... },
136
+ * transitions: [{ target: 'chase', guard: ({ ecs, entityId }) => playerNearby(ecs, entityId) }],
137
+ * },
138
+ * chase: {
139
+ * onUpdate: ({ ecs, entityId, dt }) => { ... },
140
+ * on: { playerLost: 'idle' },
141
+ * },
142
+ * },
143
+ * });
144
+ * ```
145
+ */
146
+ export declare function defineStateMachine<S extends string>(id: string, config: {
147
+ initial: NoInfer<S>;
148
+ states: Record<S, StateConfig<NoInfer<S>>>;
149
+ }): StateMachineDefinition<S>;
150
+ /**
151
+ * Create a stateMachine component from a definition.
152
+ *
153
+ * @param definition - The state machine definition to use
154
+ * @param options - Optional overrides (e.g., initial state)
155
+ * @returns Component object suitable for spreading into spawn()
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * ecs.spawn({
160
+ * ...createStateMachine(enemyFSM),
161
+ * position: { x: 100, y: 200 },
162
+ * });
163
+ * ```
164
+ */
165
+ export declare function createStateMachine<S extends string>(definition: StateMachineDefinition<S>, options?: {
166
+ initial?: S;
167
+ }): Pick<StateMachineComponentTypes<S>, 'stateMachine'>;
168
+ /**
169
+ * Directly transition an entity's state machine to a target state.
170
+ * Fires onExit, onEnter hooks and publishes stateTransition event.
171
+ *
172
+ * @param ecs - ECS instance (structural typing)
173
+ * @param entityId - Entity to transition
174
+ * @param targetState - State to transition to
175
+ * @returns true if transition succeeded, false if entity has no stateMachine or target state doesn't exist
176
+ */
177
+ export declare function transitionTo(ecs: StateMachineWorld, entityId: number, targetState: string): boolean;
178
+ /**
179
+ * Send a named event to an entity's state machine.
180
+ * Checks the current state's `on` handlers for a matching event.
181
+ *
182
+ * @param ecs - ECS instance (structural typing)
183
+ * @param entityId - Entity to send event to
184
+ * @param eventName - Event name to match against `on` handlers
185
+ * @returns true if a transition occurred, false otherwise
186
+ */
187
+ export declare function sendEvent(ecs: StateMachineWorld, entityId: number, eventName: string): boolean;
188
+ /**
189
+ * Get the current state of an entity's state machine.
190
+ *
191
+ * @param ecs - ECS instance (structural typing)
192
+ * @param entityId - Entity to query
193
+ * @returns The current state string, or undefined if entity has no stateMachine
194
+ */
195
+ export declare function getStateMachineState(ecs: StateMachineWorld, entityId: number): string | undefined;
196
+ /**
197
+ * Typed helpers for the state machine plugin.
198
+ * Creates helpers that validate hook parameters against the world type W.
199
+ * Call after .build() using typeof ecs.
200
+ */
201
+ export interface StateMachineHelpers<W extends BaseWorld<StateMachineComponentTypes>> {
202
+ defineStateMachine: <S extends string>(id: string, config: {
203
+ initial: NoInfer<S>;
204
+ states: Record<S, StateConfig<NoInfer<S>, W>>;
205
+ }) => StateMachineDefinition<S>;
206
+ }
207
+ export declare function createStateMachineHelpers<W extends BaseWorld<StateMachineComponentTypes> = StateMachineWorld>(_world?: W): StateMachineHelpers<W>;
208
+ /**
209
+ * Create a state machine plugin for ECSpresso.
210
+ *
211
+ * Provides:
212
+ * - Lifecycle hooks (onEnter, onExit, onUpdate) per state
213
+ * - Guard-based automatic transitions evaluated each tick
214
+ * - Event-based transitions via `sendEvent()`
215
+ * - Direct transitions via `transitionTo()`
216
+ * - stateTransition events published on every transition
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * const ecs = ECSpresso.create()
221
+ * .withPlugin(createStateMachinePlugin())
222
+ * .build();
223
+ *
224
+ * const fsm = defineStateMachine('enemy', {
225
+ * initial: 'idle',
226
+ * states: {
227
+ * idle: {
228
+ * transitions: [{ target: 'chase', guard: ({ ecs, entityId }) => playerNearby(ecs, entityId) }],
229
+ * },
230
+ * chase: {
231
+ * onUpdate: ({ ecs, entityId, dt }) => moveTowardPlayer(ecs, entityId, dt),
232
+ * on: { playerLost: 'idle' },
233
+ * },
234
+ * },
235
+ * });
236
+ *
237
+ * ecs.spawn({
238
+ * ...createStateMachine(fsm),
239
+ * position: { x: 0, y: 0 },
240
+ * });
241
+ * ```
242
+ */
243
+ export declare function createStateMachinePlugin<S extends string = string, G extends string = 'stateMachine'>(options?: StateMachinePluginOptions<G>): Plugin<WorldConfigFrom<StateMachineComponentTypes<S>, StateMachineEventTypes<S>>, EmptyConfig, 'state-machine-update', G>;
244
+ export {};
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Timer Plugin for ECSpresso
3
+ *
4
+ * Provides ECS-native timers following the "data, not callbacks" philosophy.
5
+ * Timers are components processed each frame, automatically cleaned up when entities are removed.
6
+ */
7
+ import { type Plugin, type BasePluginOptions } from 'ecspresso';
8
+ import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
9
+ /**
10
+ * Data structure passed to onComplete callbacks when a timer completes.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * createTimer(1.5, {
15
+ * onComplete: (data) => {
16
+ * console.log(`Timer on entity ${data.entityId} finished after ${data.elapsed}s`);
17
+ * }
18
+ * });
19
+ * ```
20
+ */
21
+ export interface TimerEventData {
22
+ /** The entity ID that the timer belongs to */
23
+ entityId: number;
24
+ /** The timer's configured duration in seconds */
25
+ duration: number;
26
+ /** The actual elapsed time (may exceed duration slightly) */
27
+ elapsed: number;
28
+ }
29
+ /**
30
+ * Timer component data structure.
31
+ * Use `justFinished` to detect timer completion in your systems.
32
+ */
33
+ export interface Timer {
34
+ /** Time accumulated so far (seconds) */
35
+ elapsed: number;
36
+ /** Target duration (seconds) */
37
+ duration: number;
38
+ /** Whether timer repeats after completion */
39
+ repeat: boolean;
40
+ /** Whether timer is currently running */
41
+ active: boolean;
42
+ /** True for one frame after timer completes */
43
+ justFinished: boolean;
44
+ /** Optional callback invoked when timer completes */
45
+ onComplete?: (data: TimerEventData) => void;
46
+ }
47
+ /**
48
+ * Component types provided by the timer plugin.
49
+ * Included automatically via `.withPlugin(createTimerPlugin())`.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const ecs = ECSpresso.create()
54
+ * .withPlugin(createTimerPlugin())
55
+ * .withComponentTypes<{ velocity: { x: number; y: number }; player: true }>()
56
+ * .build();
57
+ * ```
58
+ */
59
+ export interface TimerComponentTypes {
60
+ timer: Timer;
61
+ }
62
+ /**
63
+ * Configuration options for the timer plugin.
64
+ */
65
+ export interface TimerPluginOptions<G extends string = 'timers'> extends BasePluginOptions<G> {
66
+ }
67
+ /**
68
+ * Options for timer creation
69
+ */
70
+ export interface TimerOptions {
71
+ /** Callback invoked when timer completes */
72
+ onComplete?: (data: TimerEventData) => void;
73
+ }
74
+ /**
75
+ * Create a one-shot timer that fires once after the specified duration.
76
+ *
77
+ * @param duration Duration in seconds until the timer completes
78
+ * @param options Optional configuration including onComplete callback
79
+ * @returns Component object suitable for spreading into spawn()
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // Timer without callback
84
+ * ecs.spawn({
85
+ * ...createTimer(2),
86
+ * explosion: true,
87
+ * });
88
+ *
89
+ * // Timer with onComplete callback
90
+ * ecs.spawn({
91
+ * ...createTimer(1.5, { onComplete: (data) => console.log('done', data.entityId) }),
92
+ * });
93
+ * ```
94
+ */
95
+ export declare function createTimer(duration: number, options?: TimerOptions): Pick<TimerComponentTypes, 'timer'>;
96
+ /**
97
+ * Create a repeating timer that fires every `duration` seconds.
98
+ *
99
+ * @param duration Duration in seconds between each timer completion
100
+ * @param options Optional configuration including onComplete callback
101
+ * @returns Component object suitable for spreading into spawn()
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * // Timer without callback
106
+ * ecs.spawn({
107
+ * ...createRepeatingTimer(5),
108
+ * spawner: true,
109
+ * });
110
+ *
111
+ * // Repeating timer with onComplete callback
112
+ * ecs.spawn({
113
+ * ...createRepeatingTimer(3, { onComplete: (data) => console.log('cycle', data.elapsed) }),
114
+ * });
115
+ * ```
116
+ */
117
+ export declare function createRepeatingTimer(duration: number, options?: TimerOptions): Pick<TimerComponentTypes, 'timer'>;
118
+ /**
119
+ * Create a timer plugin for ECSpresso.
120
+ *
121
+ * This plugin provides:
122
+ * - Timer update system that processes all timer components each frame
123
+ * - `justFinished` flag pattern for one-frame completion detection
124
+ * - Automatic cleanup when entities are removed
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const ecs = ECSpresso
129
+ * .create<Components, Events, Resources>()
130
+ * .withPlugin(createTimerPlugin())
131
+ * .build();
132
+ *
133
+ * // Spawn entity with timer
134
+ * ecs.spawn({
135
+ * ...createRepeatingTimer(5),
136
+ * spawner: true,
137
+ * });
138
+ *
139
+ * // React to timer completion in a system
140
+ * ecs.addSystem('spawn-on-timer')
141
+ * .addQuery('spawners', { with: ['timer', 'spawner'] })
142
+ * .setProcess((queries, _dt, ecs) => {
143
+ * for (const { components } of queries.spawners) {
144
+ * if (components.timer.justFinished) {
145
+ * ecs.spawn({ enemy: true });
146
+ * }
147
+ * }
148
+ * });
149
+ * ```
150
+ */
151
+ export declare function createTimerPlugin<G extends string = 'timers'>(options?: TimerPluginOptions<G>): Plugin<WorldConfigFrom<TimerComponentTypes>, EmptyConfig, 'timer-update', G>;
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Transform Bundle for ECSpresso
2
+ * Transform Plugin for ECSpresso
3
3
  *
4
4
  * Provides hierarchical transform propagation following Bevy's Transform/GlobalTransform pattern.
5
5
  * LocalTransform is modified by user code; WorldTransform is computed automatically.
6
6
  *
7
7
  * @see https://docs.rs/bevy/latest/bevy/transform/components/struct.GlobalTransform.html
8
8
  */
9
- import { Bundle } from 'ecspresso';
10
- import type { SystemPhase } from 'ecspresso';
9
+ import { type Plugin, type BasePluginOptions } from 'ecspresso';
10
+ import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
11
11
  /**
12
12
  * Local transform relative to parent (or world if no parent).
13
13
  * This is the transform you modify directly.
@@ -31,15 +31,15 @@ export interface WorldTransform {
31
31
  scaleY: number;
32
32
  }
33
33
  /**
34
- * Component types provided by the transform bundle.
35
- * Extend your component types with this interface.
34
+ * Component types provided by the transform plugin.
35
+ * Included automatically via `.withPlugin(createTransformPlugin())`.
36
36
  *
37
37
  * @example
38
38
  * ```typescript
39
- * interface GameComponents extends TransformComponentTypes {
40
- * sprite: Sprite;
41
- * velocity: { x: number; y: number };
42
- * }
39
+ * const ecs = ECSpresso.create()
40
+ * .withPlugin(createTransformPlugin())
41
+ * .withComponentTypes<{ sprite: Sprite; velocity: { x: number; y: number } }>()
42
+ * .build();
43
43
  * ```
44
44
  */
45
45
  export interface TransformComponentTypes {
@@ -47,15 +47,14 @@ export interface TransformComponentTypes {
47
47
  worldTransform: WorldTransform;
48
48
  }
49
49
  /**
50
- * Configuration options for the transform bundle.
50
+ * WorldConfig representing the transform plugin's provided components.
51
+ * Used as the `Requires` type parameter by plugins that depend on transform.
52
+ */
53
+ export type TransformWorldConfig = WorldConfigFrom<TransformComponentTypes>;
54
+ /**
55
+ * Configuration options for the transform plugin.
51
56
  */
52
- export interface TransformBundleOptions {
53
- /** System group name (default: 'transform') */
54
- systemGroup?: string;
55
- /** Priority for transform propagation (default: 500, runs after physics/movement) */
56
- priority?: number;
57
- /** Execution phase (default: 'postUpdate') */
58
- phase?: SystemPhase;
57
+ export interface TransformPluginOptions<G extends string = 'transform'> extends BasePluginOptions<G> {
59
58
  }
60
59
  /**
61
60
  * Default local transform values.
@@ -126,9 +125,9 @@ export interface TransformOptions {
126
125
  */
127
126
  export declare function createTransform(x: number, y: number, options?: TransformOptions): TransformComponentTypes;
128
127
  /**
129
- * Create a transform bundle for ECSpresso.
128
+ * Create a transform plugin for ECSpresso.
130
129
  *
131
- * This bundle provides:
130
+ * This plugin provides:
132
131
  * - Transform propagation system that computes world transforms from local transforms
133
132
  * - Parent-first traversal ensures parents are processed before children
134
133
  * - Supports full transform hierarchy (position, rotation, scale)
@@ -137,8 +136,8 @@ export declare function createTransform(x: number, y: number, options?: Transfor
137
136
  * ```typescript
138
137
  * const ecs = ECSpresso
139
138
  * .create<Components, Events, Resources>()
140
- * .withBundle(createTransformBundle())
141
- * .withBundle(createMovementBundle())
139
+ * .withPlugin(createTransformPlugin())
140
+ * .withPlugin(createPhysics2DPlugin())
142
141
  * .build();
143
142
  *
144
143
  * // Spawn entity with transform
@@ -148,4 +147,4 @@ export declare function createTransform(x: number, y: number, options?: Transfor
148
147
  * });
149
148
  * ```
150
149
  */
151
- export declare function createTransformBundle(options?: TransformBundleOptions): Bundle<TransformComponentTypes, {}, {}>;
150
+ export declare function createTransformPlugin<G extends string = 'transform'>(options?: TransformPluginOptions<G>): Plugin<WorldConfigFrom<TransformComponentTypes>, EmptyConfig, 'transform-propagation', G>;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Tween Plugin for ECSpresso
3
+ *
4
+ * Declarative property animation within the ECS. Tween any numeric component
5
+ * field over time with standard easing functions, sequences, and completion events.
6
+ */
7
+ import { type Plugin, type BasePluginOptions } from 'ecspresso';
8
+ import type { ComponentsOfWorld, AnyECSpresso } from 'ecspresso';
9
+ import type { WorldConfigFrom, EmptyConfig } from '../type-utils';
10
+ import { type EasingFn } from '../utils/easing';
11
+ /**
12
+ * Data structure published when a tween completes.
13
+ * Use this type when defining tween completion events in your EventTypes interface.
14
+ */
15
+ export interface TweenEventData {
16
+ /** The entity ID the tween belongs to */
17
+ entityId: number;
18
+ /** Number of steps in the tween */
19
+ stepCount: number;
20
+ }
21
+ export interface TweenTarget {
22
+ /** Component name on the entity */
23
+ component: string;
24
+ /** Pre-split field path (e.g., ['position', 'x']) */
25
+ path: readonly string[];
26
+ /** Starting value. null = resolve from current value on first tick */
27
+ from: number | null;
28
+ /** Target value */
29
+ to: number;
30
+ }
31
+ export interface TweenStep {
32
+ targets: TweenTarget[];
33
+ duration: number;
34
+ easing: EasingFn;
35
+ }
36
+ export interface Tween {
37
+ steps: TweenStep[];
38
+ currentStep: number;
39
+ elapsed: number;
40
+ loop: LoopMode;
41
+ totalLoops: number;
42
+ completedLoops: number;
43
+ direction: 1 | -1;
44
+ state: 'pending' | 'active' | 'complete';
45
+ onComplete?: (data: TweenEventData) => void;
46
+ justFinished: boolean;
47
+ }
48
+ export type LoopMode = 'once' | 'loop' | 'yoyo';
49
+ /**
50
+ * Component types provided by the tween plugin.
51
+ */
52
+ export interface TweenComponentTypes {
53
+ tween: Tween;
54
+ }
55
+ export interface TweenPluginOptions<G extends string = 'tweens'> extends BasePluginOptions<G> {
56
+ }
57
+ export interface TweenOptions {
58
+ /** Explicit starting value (default: captures current value on first tick) */
59
+ from?: number;
60
+ /** Easing function (default: linear) */
61
+ easing?: EasingFn;
62
+ /** Loop mode (default: 'once') */
63
+ loop?: LoopMode;
64
+ /** Number of loops. -1 = infinite (default: 1) */
65
+ loops?: number;
66
+ /** Callback invoked when tween completes */
67
+ onComplete?: (data: TweenEventData) => void;
68
+ }
69
+ /**
70
+ * Create a single-target tween component.
71
+ *
72
+ * @param component Component name on the entity
73
+ * @param field Field path (dot-separated for nested, e.g. 'position.x')
74
+ * @param to Target value
75
+ * @param duration Duration in seconds
76
+ * @param options Optional configuration
77
+ * @returns Component object suitable for spreading into spawn()
78
+ */
79
+ export declare function createTween(component: string, field: string, to: number, duration: number, options?: TweenOptions): Pick<TweenComponentTypes, 'tween'>;
80
+ export interface TweenSequenceStepInput {
81
+ targets: ReadonlyArray<{
82
+ component: string;
83
+ field: string;
84
+ to: number;
85
+ from?: number;
86
+ }>;
87
+ duration: number;
88
+ easing?: EasingFn;
89
+ }
90
+ export interface TweenSequenceOptions {
91
+ /** Loop mode (default: 'once') */
92
+ loop?: LoopMode;
93
+ /** Number of loops. -1 = infinite (default: 1) */
94
+ loops?: number;
95
+ /** Callback invoked when tween completes */
96
+ onComplete?: (data: TweenEventData) => void;
97
+ }
98
+ /**
99
+ * Create a multi-step tween sequence. Each step can have parallel targets.
100
+ *
101
+ * @param steps Array of step definitions
102
+ * @param options Optional configuration
103
+ * @returns Component object suitable for spreading into spawn()
104
+ */
105
+ export declare function createTweenSequence(steps: ReadonlyArray<TweenSequenceStepInput>, options?: TweenSequenceOptions): Pick<TweenComponentTypes, 'tween'>;
106
+ /**
107
+ * Recursively produce a union of dot-separated paths that resolve to `number`
108
+ * within type T. Depth-limited to 4 levels to prevent TS recursion errors.
109
+ *
110
+ * @example
111
+ * NumericPaths<{ x: number; y: number }> // 'x' | 'y'
112
+ * NumericPaths<{ position: { x: number }; rotation: number }> // 'position.x' | 'rotation'
113
+ */
114
+ export type NumericPaths<T, Depth extends readonly unknown[] = []> = Depth['length'] extends 4 ? never : T extends readonly unknown[] ? never : T extends Record<string, unknown> ? {
115
+ [K in keyof T & string]: NonNullable<T[K]> extends number ? K : NonNullable<T[K]> extends readonly unknown[] ? never : NonNullable<T[K]> extends Record<string, unknown> ? `${K}.${NumericPaths<NonNullable<T[K]>, [...Depth, unknown]>}` : never;
116
+ }[keyof T & string] : never;
117
+ /**
118
+ * Discriminated union over component names: each variant constrains `field`
119
+ * to the numeric paths of that component. TS narrows inline object literals
120
+ * by `component` discriminant — zero runtime overhead.
121
+ */
122
+ export type TypedTweenTargetInput<C extends Record<string, any>> = {
123
+ [K in keyof C & string]: {
124
+ component: K;
125
+ field: NumericPaths<C[K]>;
126
+ to: number;
127
+ from?: number;
128
+ };
129
+ }[keyof C & string];
130
+ export interface TypedTweenSequenceStepInput<C extends Record<string, any>> {
131
+ targets: ReadonlyArray<TypedTweenTargetInput<C>>;
132
+ duration: number;
133
+ easing?: EasingFn;
134
+ }
135
+ export interface TweenHelpers<W extends AnyECSpresso> {
136
+ createTween: <K extends keyof ComponentsOfWorld<W> & string>(component: K, field: NumericPaths<ComponentsOfWorld<W>[K]>, to: number, duration: number, options?: {
137
+ from?: number;
138
+ easing?: EasingFn;
139
+ loop?: LoopMode;
140
+ loops?: number;
141
+ onComplete?: (data: TweenEventData) => void;
142
+ }) => Pick<TweenComponentTypes, 'tween'>;
143
+ createTweenSequence: (steps: ReadonlyArray<TypedTweenSequenceStepInput<ComponentsOfWorld<W>>>, options?: {
144
+ loop?: LoopMode;
145
+ loops?: number;
146
+ onComplete?: (data: TweenEventData) => void;
147
+ }) => Pick<TweenComponentTypes, 'tween'>;
148
+ }
149
+ export declare function createTweenHelpers<W extends AnyECSpresso>(_world?: W): TweenHelpers<W>;
150
+ /**
151
+ * Create a tween plugin for ECSpresso.
152
+ *
153
+ * This plugin provides:
154
+ * - Tween system that processes all tween components each frame
155
+ * - Support for single-field, multi-target, and multi-step sequences
156
+ * - 31 standard easing functions
157
+ * - Loop modes: once, loop, yoyo
158
+ * - `justFinished` flag for one-frame completion detection
159
+ * - `onComplete` callback on completion
160
+ * - Change detection via markChanged
161
+ */
162
+ export declare function createTweenPlugin<G extends string = 'tweens'>(options?: TweenPluginOptions<G>): Plugin<WorldConfigFrom<TweenComponentTypes>, EmptyConfig, 'tween-update', G>;
@@ -20,7 +20,7 @@ export interface ReactiveQueryDefinition<ComponentTypes extends Record<string, a
20
20
  /**
21
21
  * Manages reactive queries that trigger callbacks when entities enter/exit query matches
22
22
  */
23
- export default class ReactiveQueryManager<ComponentTypes extends Record<string, any>> {
23
+ export default class ReactiveQueryManager<ComponentTypes extends Record<string, any>, QueryNames extends string = string> {
24
24
  private queries;
25
25
  private entityManager;
26
26
  /** Whether any registered query uses parentHas */
@@ -35,17 +35,22 @@ export default class ReactiveQueryManager<ComponentTypes extends Record<string,
35
35
  * @param name Unique name for the query
36
36
  * @param definition Query definition with callbacks
37
37
  */
38
- addQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never, OptionalComponents extends keyof ComponentTypes = never>(name: string, definition: ReactiveQueryDefinition<ComponentTypes, WithComponents, WithoutComponents, OptionalComponents>): void;
38
+ addQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never, OptionalComponents extends keyof ComponentTypes = never>(name: QueryNames, definition: ReactiveQueryDefinition<ComponentTypes, WithComponents, WithoutComponents, OptionalComponents>): void;
39
39
  /**
40
40
  * Remove a reactive query
41
41
  * @param name Name of the query to remove
42
42
  * @returns true if the query existed and was removed
43
43
  */
44
- removeQuery(name: string): boolean;
44
+ removeQuery(name: QueryNames): boolean;
45
45
  /**
46
46
  * Check if an entity matches a query definition
47
47
  */
48
48
  private entityMatchesQuery;
49
+ /**
50
+ * Apply enter/exit transitions for a single query against an entity.
51
+ * Fires onEnter when entity starts matching, onExit when it stops.
52
+ */
53
+ private _applyQueryTransition;
49
54
  /**
50
55
  * Called when a component is added to an entity
51
56
  * Checks all queries for potential enter/exit events
@@ -66,6 +71,12 @@ export default class ReactiveQueryManager<ComponentTypes extends Record<string,
66
71
  * Fires enter/exit callbacks as appropriate based on current state vs tracked state
67
72
  */
68
73
  recheckEntity(entity: Entity<ComponentTypes>): void;
74
+ /**
75
+ * Recheck an entity and its children against all queries.
76
+ * Used after component mutations to handle both the entity's own queries
77
+ * and parentHas queries on its children.
78
+ */
79
+ recheckEntityAndChildren(entity: Entity<ComponentTypes>): void;
69
80
  /**
70
81
  * Recheck all children of a parent entity against parentHas queries.
71
82
  * Called when a component is added/removed from a parent entity.