ecspresso 0.10.2 → 0.11.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 (82) hide show
  1. package/README.md +73 -17
  2. package/dist/asset-manager.d.ts +15 -15
  3. package/dist/asset-types.d.ts +16 -14
  4. package/dist/bundle.d.ts +66 -16
  5. package/dist/bundles/audio.d.ts +293 -0
  6. package/dist/bundles/{utils/bounds.d.ts → bounds.d.ts} +9 -7
  7. package/dist/bundles/camera.d.ts +89 -0
  8. package/dist/bundles/collision.d.ts +289 -0
  9. package/dist/bundles/diagnostics.d.ts +48 -0
  10. package/dist/bundles/{utils/input.d.ts → input.d.ts} +16 -17
  11. package/dist/bundles/physics2D.d.ts +159 -0
  12. package/dist/bundles/renderers/renderer2D.d.ts +65 -24
  13. package/dist/bundles/spatial-index.d.ts +57 -0
  14. package/dist/bundles/state-machine.d.ts +298 -0
  15. package/dist/bundles/{utils/timers.d.ts → timers.d.ts} +9 -8
  16. package/dist/bundles/{utils/transform.d.ts → transform.d.ts} +10 -10
  17. package/dist/bundles/tween.d.ts +197 -0
  18. package/dist/command-buffer.d.ts +20 -20
  19. package/dist/ecspresso-builder.d.ts +165 -0
  20. package/dist/ecspresso.d.ts +157 -178
  21. package/dist/entity-manager.d.ts +76 -40
  22. package/dist/event-bus.d.ts +6 -1
  23. package/dist/index.d.ts +1 -9
  24. package/dist/reactive-query-manager.d.ts +14 -3
  25. package/dist/resource-manager.d.ts +35 -19
  26. package/dist/screen-manager.d.ts +4 -4
  27. package/dist/screen-types.d.ts +12 -11
  28. package/dist/src/bundles/audio.js +4 -0
  29. package/dist/src/bundles/audio.js.map +10 -0
  30. package/dist/src/bundles/bounds.js +4 -0
  31. package/dist/src/bundles/bounds.js.map +10 -0
  32. package/dist/src/bundles/camera.js +4 -0
  33. package/dist/src/bundles/camera.js.map +10 -0
  34. package/dist/src/bundles/collision.js +4 -0
  35. package/dist/src/bundles/collision.js.map +11 -0
  36. package/dist/src/bundles/diagnostics.js +5 -0
  37. package/dist/src/bundles/diagnostics.js.map +10 -0
  38. package/dist/src/bundles/input.js +4 -0
  39. package/dist/src/bundles/input.js.map +10 -0
  40. package/dist/src/bundles/physics2D.js +4 -0
  41. package/dist/src/bundles/physics2D.js.map +11 -0
  42. package/dist/src/bundles/renderers/renderer2D.js +4 -0
  43. package/dist/src/bundles/renderers/renderer2D.js.map +10 -0
  44. package/dist/src/bundles/spatial-index.js +4 -0
  45. package/dist/src/bundles/spatial-index.js.map +11 -0
  46. package/dist/src/bundles/state-machine.js +4 -0
  47. package/dist/src/bundles/state-machine.js.map +10 -0
  48. package/dist/src/bundles/timers.js +4 -0
  49. package/dist/src/bundles/timers.js.map +10 -0
  50. package/dist/src/bundles/transform.js +4 -0
  51. package/dist/src/bundles/transform.js.map +10 -0
  52. package/dist/src/bundles/tween.js +4 -0
  53. package/dist/src/bundles/tween.js.map +11 -0
  54. package/dist/src/index.js +4 -0
  55. package/dist/src/index.js.map +25 -0
  56. package/dist/system-builder.d.ts +36 -42
  57. package/dist/type-utils.d.ts +52 -3
  58. package/dist/types.d.ts +10 -19
  59. package/dist/utils/check-required-cycle.d.ts +12 -0
  60. package/dist/utils/easing.d.ts +71 -0
  61. package/dist/utils/math.d.ts +67 -0
  62. package/dist/utils/narrowphase.d.ts +63 -0
  63. package/dist/utils/spatial-hash.d.ts +53 -0
  64. package/package.json +50 -20
  65. package/dist/bundles/renderers/renderer2D.js +0 -4
  66. package/dist/bundles/renderers/renderer2D.js.map +0 -10
  67. package/dist/bundles/utils/bounds.js +0 -4
  68. package/dist/bundles/utils/bounds.js.map +0 -10
  69. package/dist/bundles/utils/collision.d.ts +0 -204
  70. package/dist/bundles/utils/collision.js +0 -4
  71. package/dist/bundles/utils/collision.js.map +0 -10
  72. package/dist/bundles/utils/input.js +0 -4
  73. package/dist/bundles/utils/input.js.map +0 -10
  74. package/dist/bundles/utils/movement.d.ts +0 -86
  75. package/dist/bundles/utils/movement.js +0 -4
  76. package/dist/bundles/utils/movement.js.map +0 -10
  77. package/dist/bundles/utils/timers.js +0 -4
  78. package/dist/bundles/utils/timers.js.map +0 -10
  79. package/dist/bundles/utils/transform.js +0 -4
  80. package/dist/bundles/utils/transform.js.map +0 -10
  81. package/dist/index.js +0 -4
  82. package/dist/index.js.map +0 -22
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Collision Bundle for ECSpresso
3
+ *
4
+ * Provides layer-based collision detection with events.
5
+ * Uses worldTransform for position (world-space collision).
6
+ * Supports AABB and circle colliders.
7
+ */
8
+ import { Bundle } from 'ecspresso';
9
+ import type { SystemPhase } from 'ecspresso';
10
+ import type { TransformComponentTypes } from './transform';
11
+ /**
12
+ * Axis-Aligned Bounding Box collider.
13
+ */
14
+ export interface AABBCollider {
15
+ /** Width of the bounding box */
16
+ width: number;
17
+ /** Height of the bounding box */
18
+ height: number;
19
+ /** X offset from entity position (default: 0) */
20
+ offsetX?: number;
21
+ /** Y offset from entity position (default: 0) */
22
+ offsetY?: number;
23
+ }
24
+ /**
25
+ * Circle collider.
26
+ */
27
+ export interface CircleCollider {
28
+ /** Radius of the circle */
29
+ radius: number;
30
+ /** X offset from entity position (default: 0) */
31
+ offsetX?: number;
32
+ /** Y offset from entity position (default: 0) */
33
+ offsetY?: number;
34
+ }
35
+ /**
36
+ * Collision layer configuration.
37
+ */
38
+ export interface CollisionLayer<L extends string = never> {
39
+ /** The layer this entity belongs to */
40
+ layer: L;
41
+ /** Layers this entity can collide with */
42
+ collidesWith: readonly L[];
43
+ }
44
+ /**
45
+ * Component types provided by the collision bundle.
46
+ * Included automatically via `.withBundle(createCollisionBundle())`.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const ecs = ECSpresso.create()
51
+ * .withBundle(createCollisionBundle())
52
+ * .withComponentTypes<{ sprite: Sprite; enemy: boolean }>()
53
+ * .build();
54
+ * ```
55
+ */
56
+ export interface CollisionComponentTypes<L extends string = never> {
57
+ aabbCollider: AABBCollider;
58
+ circleCollider: CircleCollider;
59
+ collisionLayer: CollisionLayer<L>;
60
+ }
61
+ /**
62
+ * Event fired when two entities collide.
63
+ */
64
+ export interface CollisionEvent<L extends string = never> {
65
+ /** First entity in the collision */
66
+ entityA: number;
67
+ /** Second entity in the collision */
68
+ entityB: number;
69
+ /** Layer of the first entity */
70
+ layerA: L;
71
+ /** Layer of the second entity */
72
+ layerB: L;
73
+ /** Contact normal pointing from entityA toward entityB */
74
+ normal: {
75
+ x: number;
76
+ y: number;
77
+ };
78
+ /** Penetration depth (positive = overlapping) */
79
+ depth: number;
80
+ }
81
+ /**
82
+ * Event types provided by the collision bundle.
83
+ */
84
+ export interface CollisionEventTypes<L extends string = never> {
85
+ collision: CollisionEvent<L>;
86
+ }
87
+ /**
88
+ * Configuration options for the collision bundle.
89
+ */
90
+ export interface CollisionBundleOptions<G extends string = 'physics'> {
91
+ /** System group name (default: 'physics') */
92
+ systemGroup?: G;
93
+ /** Priority for collision system (default: 0) */
94
+ priority?: number;
95
+ /** Name of the collision event (default: 'collision') */
96
+ collisionEventName?: string;
97
+ /** Execution phase (default: 'postUpdate') */
98
+ phase?: SystemPhase;
99
+ }
100
+ /**
101
+ * Create an AABB collider component.
102
+ *
103
+ * @param width Width of the bounding box
104
+ * @param height Height of the bounding box
105
+ * @param offsetX X offset from entity position
106
+ * @param offsetY Y offset from entity position
107
+ * @returns Component object suitable for spreading into spawn()
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * ecs.spawn({
112
+ * ...createTransform(100, 200),
113
+ * ...createAABBCollider(50, 30),
114
+ * });
115
+ * ```
116
+ */
117
+ export declare function createAABBCollider(width: number, height: number, offsetX?: number, offsetY?: number): {
118
+ aabbCollider: AABBCollider;
119
+ };
120
+ /**
121
+ * Create a circle collider component.
122
+ *
123
+ * @param radius Radius of the circle
124
+ * @param offsetX X offset from entity position
125
+ * @param offsetY Y offset from entity position
126
+ * @returns Component object suitable for spreading into spawn()
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * ecs.spawn({
131
+ * ...createTransform(100, 200),
132
+ * ...createCircleCollider(25),
133
+ * });
134
+ * ```
135
+ */
136
+ export declare function createCircleCollider(radius: number, offsetX?: number, offsetY?: number): {
137
+ circleCollider: CircleCollider;
138
+ };
139
+ /**
140
+ * Create a collision layer component.
141
+ *
142
+ * @param layer The layer this entity belongs to
143
+ * @param collidesWith Layers this entity can collide with
144
+ * @returns Component object suitable for spreading into spawn()
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * ecs.spawn({
149
+ * ...createTransform(100, 200),
150
+ * ...createAABBCollider(50, 30),
151
+ * ...createCollisionLayer('player', ['enemy', 'obstacle']),
152
+ * });
153
+ * ```
154
+ */
155
+ export declare function createCollisionLayer<L extends string>(layer: L, collidesWith: readonly L[]): Pick<CollisionComponentTypes<L>, 'collisionLayer'>;
156
+ /**
157
+ * Layer factory result from defineCollisionLayers.
158
+ */
159
+ export type LayerFactories<T extends Record<string, readonly string[]>> = {
160
+ [K in keyof T]: () => Pick<CollisionComponentTypes<Extract<keyof T, string>>, 'collisionLayer'>;
161
+ };
162
+ /**
163
+ * Extract layer names from a `defineCollisionLayers` result for use with
164
+ * `createCollisionPairHandler`'s `L` type parameter.
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });
169
+ * type Layer = LayersOf<typeof layers>;
170
+ * const handler = createCollisionPairHandler<ECS, Layer>({
171
+ * 'player:enemy': (playerId, enemyId, ecs) => { ... },
172
+ * });
173
+ * ```
174
+ */
175
+ export type LayersOf<T> = Extract<keyof T, string>;
176
+ /**
177
+ * Define collision layer relationships and get factory functions.
178
+ *
179
+ * @param rules Object mapping layer names to arrays of layers they collide with
180
+ * @returns Object with factory functions for each layer
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const layers = defineCollisionLayers({
185
+ * player: ['enemy', 'enemyProjectile'],
186
+ * playerProjectile: ['enemy'],
187
+ * enemy: ['playerProjectile'],
188
+ * enemyProjectile: ['player'],
189
+ * });
190
+ *
191
+ * // Usage
192
+ * ecs.spawn({
193
+ * ...createTransform(100, 200),
194
+ * ...createAABBCollider(50, 30),
195
+ * ...layers.player(),
196
+ * });
197
+ * ```
198
+ */
199
+ /**
200
+ * Validates that all `collidesWith` values reference actual layer keys.
201
+ * Catches typos at compile time.
202
+ */
203
+ type ValidateCollidesWith<T> = {
204
+ [K in keyof T]: T[K] extends readonly (infer V)[] ? [V] extends [Extract<keyof T, string>] ? T[K] : readonly Extract<keyof T, string>[] : never;
205
+ };
206
+ export declare function defineCollisionLayers<const T extends Record<string, readonly string[]>>(rules: T & ValidateCollidesWith<T>): LayerFactories<T>;
207
+ /**
208
+ * Callback for a collision pair handler.
209
+ *
210
+ * @param firstEntityId Entity belonging to the first layer in the pair key
211
+ * @param secondEntityId Entity belonging to the second layer in the pair key
212
+ * @param ecs The ECS world instance (passed through from the subscriber)
213
+ */
214
+ export type CollisionPairCallback<W = unknown> = (firstEntityId: number, secondEntityId: number, ecs: W) => void;
215
+ /**
216
+ * Create a collision pair handler that routes collision events to
217
+ * layer-pair-specific callbacks.
218
+ *
219
+ * Registering `"a:b"` automatically handles both `(layerA=a, layerB=b)` and
220
+ * `(layerA=b, layerB=a)`. Entity arguments are swapped to match the declared
221
+ * key order. If both `"a:b"` and `"b:a"` are explicitly registered, each gets
222
+ * its own handler with no implicit reverse.
223
+ *
224
+ * @typeParam W - The ECS world type (e.g. `ECSpresso<C, E, R>`). Defaults to `unknown`.
225
+ * @typeParam L - Union of valid layer names. Defaults to `string`.
226
+ * Provide specific layer names for compile-time key validation:
227
+ * `createCollisionPairHandler<ECS, keyof typeof layers>({...})`
228
+ *
229
+ * @param pairs Object mapping `"layerA:layerB"` keys to callbacks
230
+ * @returns A dispatch function to call with collision event data and ECS instance
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * // Basic usage:
235
+ * const handler = createCollisionPairHandler<ECS>({
236
+ * 'playerProjectile:enemy': (projectileId, enemyId, ecs) => {
237
+ * ecs.commands.removeEntity(projectileId);
238
+ * },
239
+ * });
240
+ *
241
+ * // With layer name validation:
242
+ * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });
243
+ * type Layer = LayersOf<typeof layers>;
244
+ * const handler = createCollisionPairHandler<ECS, Layer>({
245
+ * 'player:enemy': (playerId, enemyId, ecs) => { ... },
246
+ * });
247
+ *
248
+ * ecs.eventBus.subscribe('collision', (data) => handler(data, ecs));
249
+ * ```
250
+ */
251
+ export declare function createCollisionPairHandler<W = unknown, L extends string = string>(pairs: {
252
+ [K in `${L}:${L}`]?: CollisionPairCallback<W>;
253
+ }): (event: CollisionEvent<L>, ecs: W) => void;
254
+ type CombinedComponentTypes<L extends string> = CollisionComponentTypes<L> & TransformComponentTypes;
255
+ /**
256
+ * Create a collision bundle for ECSpresso.
257
+ *
258
+ * This bundle provides:
259
+ * - Collision detection between entities with colliders
260
+ * - AABB-AABB, circle-circle, and AABB-circle collision
261
+ * - Layer-based filtering for collision pairs
262
+ * - Deduplication of A-B / B-A collisions
263
+ * - Automatic broadphase acceleration when spatialIndex resource is present
264
+ *
265
+ * Uses worldTransform for position (world-space collision detection).
266
+ * The `layers` parameter is required for type inference — at runtime the
267
+ * bundle does not consume it.
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });
272
+ * const ecs = ECSpresso
273
+ * .create()
274
+ * .withBundle(createTransformBundle())
275
+ * .withBundle(createCollisionBundle({ layers }))
276
+ * .build();
277
+ *
278
+ * // Entity with collision
279
+ * ecs.spawn({
280
+ * ...createTransform(100, 200),
281
+ * ...createAABBCollider(50, 30),
282
+ * ...layers.player(),
283
+ * });
284
+ * ```
285
+ */
286
+ export declare function createCollisionBundle<L extends string, G extends string = 'physics'>(options: CollisionBundleOptions<G> & {
287
+ layers: LayerFactories<Record<L, readonly string[]>>;
288
+ }): Bundle<CombinedComponentTypes<L>, CollisionEventTypes<L>, {}, {}, {}, 'collision-detection', G>;
289
+ export {};
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Diagnostics Bundle for ECSpresso
3
+ *
4
+ * Runtime diagnostics: FPS, entity count, per-system timing, per-phase timing,
5
+ * and an optional DOM overlay for visual debugging.
6
+ */
7
+ import { Bundle } from 'ecspresso';
8
+ import type { SystemPhase } from 'ecspresso';
9
+ export interface DiagnosticsData {
10
+ fps: number;
11
+ entityCount: number;
12
+ systemTimings: ReadonlyMap<string, number>;
13
+ phaseTimings: Readonly<Record<SystemPhase, number>>;
14
+ averageFrameTime: number;
15
+ }
16
+ export interface DiagnosticsResourceTypes {
17
+ diagnostics: DiagnosticsData;
18
+ }
19
+ export interface DiagnosticsBundleOptions<G extends string = 'diagnostics'> {
20
+ /** System group name (default: 'diagnostics') */
21
+ systemGroup?: G;
22
+ /** Enable timing collection on initialize (default: true) */
23
+ enableTimingOnInit?: boolean;
24
+ /** Number of frames to sample for FPS average (default: 60) */
25
+ fpsSampleCount?: number;
26
+ }
27
+ export interface DiagnosticsOverlayOptions {
28
+ /** Corner position (default: 'top-left') */
29
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
30
+ /** Milliseconds between DOM updates (default: 200) */
31
+ updateInterval?: number;
32
+ /** Show per-system timings (default: true) */
33
+ showSystemTimings?: boolean;
34
+ /** Maximum systems to show in overlay (default: 10) */
35
+ maxSystemsShown?: number;
36
+ }
37
+ export declare function createDiagnosticsBundle<G extends string = 'diagnostics'>(options?: DiagnosticsBundleOptions<G>): Bundle<{}, {}, DiagnosticsResourceTypes, {}, {}, 'diagnostics-collect', G>;
38
+ /**
39
+ * Create a DOM overlay that displays diagnostics data.
40
+ * Returns a cleanup function that removes the element and clears the interval.
41
+ *
42
+ * @param ecs An ECSpresso instance with the diagnostics resource
43
+ * @param options Overlay configuration
44
+ * @returns Cleanup function
45
+ */
46
+ export declare function createDiagnosticsOverlay<R extends DiagnosticsResourceTypes>(ecs: {
47
+ getResource<K extends keyof R>(key: K): R[K];
48
+ }, options?: DiagnosticsOverlayOptions): () => void;
@@ -48,35 +48,35 @@ export interface PointerState {
48
48
  justPressed(button: number): boolean;
49
49
  justReleased(button: number): boolean;
50
50
  }
51
- export interface ActionState {
52
- isActive(action: string): boolean;
53
- justActivated(action: string): boolean;
54
- justDeactivated(action: string): boolean;
51
+ export interface ActionState<A extends string = string> {
52
+ isActive(action: A): boolean;
53
+ justActivated(action: A): boolean;
54
+ justDeactivated(action: A): boolean;
55
55
  }
56
- export interface InputState {
56
+ export interface InputState<A extends string = string> {
57
57
  readonly keyboard: KeyboardState;
58
58
  readonly pointer: PointerState;
59
- readonly actions: ActionState;
60
- setActionMap(actions: ActionMap): void;
61
- getActionMap(): Readonly<ActionMap>;
59
+ readonly actions: ActionState<A>;
60
+ setActionMap(actions: ActionMap<A>): void;
61
+ getActionMap(): Readonly<ActionMap<A>>;
62
62
  }
63
63
  export interface ActionBinding {
64
64
  keys?: KeyCode[];
65
65
  buttons?: number[];
66
66
  }
67
- export type ActionMap = Record<string, ActionBinding>;
68
- export interface InputResourceTypes {
69
- inputState: InputState;
67
+ export type ActionMap<A extends string = string> = Record<A, ActionBinding>;
68
+ export interface InputResourceTypes<A extends string = string> {
69
+ inputState: InputState<A>;
70
70
  }
71
- export interface InputBundleOptions {
71
+ export interface InputBundleOptions<A extends string = string, G extends string = 'input'> {
72
72
  /** System group name (default: 'input') */
73
- systemGroup?: string;
73
+ systemGroup?: G;
74
74
  /** Priority for input system (default: 100) */
75
75
  priority?: number;
76
76
  /** Execution phase (default: 'preUpdate') */
77
77
  phase?: SystemPhase;
78
78
  /** Initial action mappings */
79
- actions?: ActionMap;
79
+ actions?: ActionMap<A>;
80
80
  /** EventTarget to attach listeners to (default: globalThis). Pass a custom target for testability. */
81
81
  target?: EventTarget;
82
82
  }
@@ -98,8 +98,7 @@ export declare function createActionBinding(binding: ActionBinding): ActionBindi
98
98
  *
99
99
  * @example
100
100
  * ```typescript
101
- * const ecs = ECSpresso
102
- * .create<Components, Events, Resources>()
101
+ * const ecs = ECSpresso.create()
103
102
  * .withBundle(createInputBundle({
104
103
  * actions: {
105
104
  * jump: { keys: [' ', 'ArrowUp'] },
@@ -114,5 +113,5 @@ export declare function createActionBinding(binding: ActionBinding): ActionBindi
114
113
  * if (input.keyboard.isDown('ArrowRight')) { ... }
115
114
  * ```
116
115
  */
117
- export declare function createInputBundle(options?: InputBundleOptions): Bundle<{}, {}, InputResourceTypes>;
116
+ export declare function createInputBundle<A extends string = string, G extends string = 'input'>(options?: InputBundleOptions<A, G>): Bundle<{}, {}, InputResourceTypes<A>, {}, {}, 'input-state', G>;
118
117
  export {};
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Physics 2D Bundle for ECSpresso
3
+ *
4
+ * Provides ECS-native arcade physics: gravity, forces, drag, semi-implicit Euler
5
+ * integration, and impulse-based collision response with friction.
6
+ *
7
+ * Reuses collider types from the collision bundle for shape definitions.
8
+ * Has its own collision detection in fixedUpdate for physics response;
9
+ * the existing collision bundle can still run in postUpdate for game logic events.
10
+ */
11
+ import { Bundle } from 'ecspresso';
12
+ import type { SystemPhase } from 'ecspresso';
13
+ import type { TransformComponentTypes } from './transform';
14
+ import type { CollisionComponentTypes, LayerFactories } from './collision';
15
+ import type { Vector2D } from 'ecspresso';
16
+ /**
17
+ * Rigid body types for physics simulation.
18
+ * - 'dynamic': Fully simulated (gravity, forces, collisions)
19
+ * - 'kinematic': Moves via velocity only (ignores gravity/forces, immovable in collisions)
20
+ * - 'static': Immovable (ignores gravity, forces, and velocity)
21
+ */
22
+ export type BodyType = 'dynamic' | 'kinematic' | 'static';
23
+ /**
24
+ * Rigid body component controlling physics behavior.
25
+ */
26
+ export interface RigidBody {
27
+ type: BodyType;
28
+ /** Mass in arbitrary units. Affects force→acceleration. Infinity = immovable. */
29
+ mass: number;
30
+ /** Linear velocity damping coefficient (units/sec, 0 = none) */
31
+ drag: number;
32
+ /** Bounciness 0–1 (0 = no bounce, 1 = perfectly elastic) */
33
+ restitution: number;
34
+ /** Surface friction coefficient 0–1 */
35
+ friction: number;
36
+ /** Per-entity gravity multiplier (0 = no gravity) */
37
+ gravityScale: number;
38
+ }
39
+ /**
40
+ * Component types provided by the physics bundle.
41
+ */
42
+ export interface Physics2DComponentTypes<L extends string = never> extends TransformComponentTypes, CollisionComponentTypes<L> {
43
+ rigidBody: RigidBody;
44
+ velocity: Vector2D;
45
+ force: Vector2D;
46
+ }
47
+ /**
48
+ * Physics configuration resource.
49
+ */
50
+ export interface Physics2DConfig {
51
+ gravity: Vector2D;
52
+ }
53
+ export interface Physics2DResourceTypes {
54
+ physicsConfig: Physics2DConfig;
55
+ }
56
+ /**
57
+ * Event emitted for each physics collision pair.
58
+ */
59
+ export interface Physics2DCollisionEvent {
60
+ entityA: number;
61
+ entityB: number;
62
+ /** Unit normal pointing from A toward B */
63
+ normal: Vector2D;
64
+ /** Penetration depth (positive) */
65
+ depth: number;
66
+ }
67
+ export interface Physics2DEventTypes {
68
+ physicsCollision: Physics2DCollisionEvent;
69
+ }
70
+ export interface Physics2DBundleOptions<G extends string = 'physics2D', CG extends string = never> {
71
+ /** World gravity vector (default: {x: 0, y: 0}) */
72
+ gravity?: Vector2D;
73
+ /** System group name (default: 'physics2D') */
74
+ systemGroup?: G;
75
+ /** Additional group for the collision system only (default: none).
76
+ * When set, the collision system belongs to both `systemGroup` and this group,
77
+ * allowing independent enable/disable of collision detection. */
78
+ collisionSystemGroup?: CG;
79
+ /** Priority for integration system (default: 1000) */
80
+ integrationPriority?: number;
81
+ /** Priority for collision system (default: 900) */
82
+ collisionPriority?: number;
83
+ /** Execution phase (default: 'fixedUpdate') */
84
+ phase?: SystemPhase;
85
+ }
86
+ export interface RigidBodyOptions {
87
+ mass?: number;
88
+ drag?: number;
89
+ restitution?: number;
90
+ friction?: number;
91
+ gravityScale?: number;
92
+ }
93
+ /**
94
+ * Create a rigid body + force component pair.
95
+ * Static bodies automatically get mass=Infinity.
96
+ */
97
+ export declare function createRigidBody(type: BodyType, options?: RigidBodyOptions): {
98
+ rigidBody: RigidBody;
99
+ force: Vector2D;
100
+ };
101
+ /**
102
+ * Create a force component with initial values.
103
+ */
104
+ export declare function createForce(x: number, y: number): {
105
+ force: Vector2D;
106
+ };
107
+ /**
108
+ * Accumulate a force onto an entity's force component.
109
+ */
110
+ export declare function applyForce(ecs: {
111
+ entityManager: {
112
+ getComponent(id: number, name: 'force'): Vector2D | undefined;
113
+ };
114
+ }, entityId: number, fx: number, fy: number): void;
115
+ /**
116
+ * Apply an instantaneous impulse: velocity += impulse / mass.
117
+ */
118
+ export declare function applyImpulse(ecs: {
119
+ entityManager: {
120
+ getComponent(id: number, name: 'velocity'): Vector2D | undefined;
121
+ getComponent(id: number, name: 'rigidBody'): RigidBody | undefined;
122
+ };
123
+ }, entityId: number, ix: number, iy: number): void;
124
+ /**
125
+ * Directly set an entity's velocity.
126
+ */
127
+ export declare function setVelocity(ecs: {
128
+ entityManager: {
129
+ getComponent(id: number, name: 'velocity'): Vector2D | undefined;
130
+ };
131
+ }, entityId: number, vx: number, vy: number): void;
132
+ /**
133
+ * Create a 2D physics bundle for ECSpresso.
134
+ *
135
+ * Provides:
136
+ * - Semi-implicit Euler integration (gravity, forces, drag → velocity → position)
137
+ * - Impulse-based collision response with restitution and friction
138
+ * - physicsCollision events with contact normal and depth
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const ecs = ECSpresso.create()
143
+ * .withBundle(createTransformBundle())
144
+ * .withBundle(createPhysics2DBundle({ gravity: { x: 0, y: 980 } }))
145
+ * .withFixedTimestep(1/60)
146
+ * .build();
147
+ *
148
+ * ecs.spawn({
149
+ * ...createTransform(100, 200),
150
+ * ...createRigidBody('dynamic', { mass: 1, restitution: 0.5 }),
151
+ * velocity: { x: 0, y: 0 },
152
+ * ...createAABBCollider(32, 32),
153
+ * ...createCollisionLayer('player', ['ground']),
154
+ * });
155
+ * ```
156
+ */
157
+ export declare function createPhysics2DBundle<L extends string = never, G extends string = 'physics2D', CG extends string = never>(options?: Physics2DBundleOptions<G, CG> & {
158
+ layers?: LayerFactories<Record<L, readonly string[]>>;
159
+ }): Bundle<Physics2DComponentTypes<L>, Physics2DEventTypes, Physics2DResourceTypes, {}, {}, 'physics2D-integration' | 'physics2D-collision', G | CG>;