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,293 @@
1
+ /**
2
+ * Audio Bundle for ECSpresso
3
+ *
4
+ * Web Audio API integration via Howler.js for sound effects and music playback.
5
+ * User-defined channels with type-safe volume control, hybrid resource + component API,
6
+ * and asset manager integration.
7
+ */
8
+ import { Bundle } from 'ecspresso';
9
+ import type { SystemPhase, AssetsOfWorld } from 'ecspresso';
10
+ import type { Howl } from 'howler';
11
+ /**
12
+ * Configuration for a single audio channel.
13
+ */
14
+ export interface AudioChannelConfig {
15
+ readonly volume: number;
16
+ }
17
+ /**
18
+ * Define audio channels with type-safe names and initial volumes.
19
+ * Mirrors `defineCollisionLayers` pattern.
20
+ *
21
+ * @param channels Object mapping channel names to their configuration
22
+ * @returns Frozen channel configuration with inferred channel name union
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const channels = defineAudioChannels({
27
+ * sfx: { volume: 1 },
28
+ * music: { volume: 0.7 },
29
+ * ui: { volume: 0.8 },
30
+ * });
31
+ * type Ch = ChannelsOf<typeof channels>; // 'sfx' | 'music' | 'ui'
32
+ * ```
33
+ */
34
+ export declare function defineAudioChannels<const T extends Record<string, AudioChannelConfig>>(channels: T): Readonly<T>;
35
+ /**
36
+ * Extract channel name union from a `defineAudioChannels` result.
37
+ */
38
+ export type ChannelsOf<T> = T extends Record<infer K extends string, AudioChannelConfig> ? K : never;
39
+ /**
40
+ * Audio source component attached to entities for positional/entity-bound audio.
41
+ */
42
+ export interface AudioSource<Ch extends string = string> {
43
+ /** Asset key for the sound */
44
+ readonly sound: string;
45
+ /** Channel this sound plays on */
46
+ readonly channel: Ch;
47
+ /** Individual volume (0-1) */
48
+ volume: number;
49
+ /** Whether sound loops */
50
+ loop: boolean;
51
+ /** Remove entity when sound ends (like timer autoRemove) */
52
+ autoRemove: boolean;
53
+ /** Whether sound is currently playing (system-managed) */
54
+ playing: boolean;
55
+ /** Howler sound ID (system-managed, -1 = not started) */
56
+ _soundId: number;
57
+ }
58
+ /**
59
+ * Component types provided by the audio bundle.
60
+ */
61
+ export interface AudioComponentTypes<Ch extends string = string> {
62
+ audioSource: AudioSource<Ch>;
63
+ }
64
+ /**
65
+ * Event to trigger fire-and-forget sound playback from any system.
66
+ */
67
+ export interface PlaySoundEvent<Ch extends string = string> {
68
+ /** Asset key for the sound */
69
+ sound: string;
70
+ /** Channel to play on */
71
+ channel?: Ch;
72
+ /** Individual volume (0-1) */
73
+ volume?: number;
74
+ /** Whether sound loops */
75
+ loop?: boolean;
76
+ }
77
+ /**
78
+ * Event to stop music on a channel.
79
+ */
80
+ export interface StopMusicEvent<Ch extends string = string> {
81
+ /** Channel to stop music on. If omitted, stops all music. */
82
+ channel?: Ch;
83
+ }
84
+ /**
85
+ * Event published when a sound finishes playing.
86
+ */
87
+ export interface SoundEndedEvent {
88
+ /** Entity ID if sound was entity-attached, -1 for fire-and-forget */
89
+ entityId: number;
90
+ /** Howler sound ID */
91
+ soundId: number;
92
+ /** Asset key of the sound */
93
+ sound: string;
94
+ }
95
+ /**
96
+ * Event types provided by the audio bundle.
97
+ */
98
+ export interface AudioEventTypes<Ch extends string = string> {
99
+ playSound: PlaySoundEvent<Ch>;
100
+ stopMusic: StopMusicEvent<Ch>;
101
+ soundEnded: SoundEndedEvent;
102
+ }
103
+ /**
104
+ * Play options for fire-and-forget sound effects.
105
+ */
106
+ export interface PlayOptions<Ch extends string = string> {
107
+ /** Channel to play on (uses first defined channel if omitted) */
108
+ channel?: Ch;
109
+ /** Individual volume (0-1, default: 1) */
110
+ volume?: number;
111
+ /** Whether to loop (default: false) */
112
+ loop?: boolean;
113
+ }
114
+ /**
115
+ * Music playback options.
116
+ */
117
+ export interface MusicOptions<Ch extends string = string> {
118
+ /** Channel to play music on (uses first defined channel if omitted) */
119
+ channel?: Ch;
120
+ /** Volume (0-1, default: 1) */
121
+ volume?: number;
122
+ /** Whether to loop (default: true) */
123
+ loop?: boolean;
124
+ }
125
+ /**
126
+ * Audio state resource providing fire-and-forget SFX and music control.
127
+ * Effective volume = individual * channel * master.
128
+ */
129
+ export interface AudioState<Ch extends string = string> {
130
+ /** Play a fire-and-forget sound effect. Returns the Howler sound ID. */
131
+ play(sound: string, options?: PlayOptions<Ch>): number;
132
+ /** Stop a specific sound by its Howler sound ID. */
133
+ stop(soundId: number): void;
134
+ /** Play music on a channel. Stops any existing music on that channel first. */
135
+ playMusic(sound: string, options?: MusicOptions<Ch>): void;
136
+ /** Stop music on a channel. If omitted, stops all music. */
137
+ stopMusic(channel?: Ch): void;
138
+ /** Pause music on a channel. If omitted, pauses all music. */
139
+ pauseMusic(channel?: Ch): void;
140
+ /** Resume music on a channel. If omitted, resumes all music. */
141
+ resumeMusic(channel?: Ch): void;
142
+ /** Set volume for a channel (0-1). */
143
+ setChannelVolume(channel: Ch, volume: number): void;
144
+ /** Get current volume for a channel. */
145
+ getChannelVolume(channel: Ch): number;
146
+ /** Set master volume (0-1). */
147
+ setMasterVolume(volume: number): void;
148
+ /** Get current master volume. */
149
+ getMasterVolume(): number;
150
+ /** Mute all audio. */
151
+ mute(): void;
152
+ /** Unmute all audio. */
153
+ unmute(): void;
154
+ /** Toggle mute state. */
155
+ toggleMute(): void;
156
+ /** Check if audio is muted. */
157
+ isMuted(): boolean;
158
+ }
159
+ /**
160
+ * Resource types provided by the audio bundle.
161
+ */
162
+ export interface AudioResourceTypes<Ch extends string = string> {
163
+ audioState: AudioState<Ch>;
164
+ }
165
+ /**
166
+ * Configuration options for the audio bundle.
167
+ */
168
+ export interface AudioBundleOptions<Ch extends string, G extends string = 'audio'> {
169
+ /** Channel definitions from defineAudioChannels */
170
+ channels: Readonly<Record<Ch, AudioChannelConfig>>;
171
+ /** System group name (default: 'audio') */
172
+ systemGroup?: G;
173
+ /** Priority for audio sync system (default: 0) */
174
+ priority?: number;
175
+ /** Execution phase (default: 'update') */
176
+ phase?: SystemPhase;
177
+ }
178
+ /**
179
+ * Create an audioSource component for entity-attached audio.
180
+ *
181
+ * @param sound Asset key for the sound
182
+ * @param channel Channel to play on
183
+ * @param options Optional configuration
184
+ * @returns Component object suitable for spreading into spawn()
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * ecs.spawn({
189
+ * ...createAudioSource('explosion', 'sfx'),
190
+ * ...createTransform(100, 200),
191
+ * });
192
+ * ```
193
+ */
194
+ export declare function createAudioSource<Ch extends string>(sound: string, channel: Ch, options?: {
195
+ volume?: number;
196
+ loop?: boolean;
197
+ autoRemove?: boolean;
198
+ }): Pick<AudioComponentTypes<Ch>, 'audioSource'>;
199
+ /**
200
+ * Create a loader function for use with the asset manager.
201
+ * Returns a factory function that loads a Howl when called.
202
+ *
203
+ * @param src URL(s) for the sound file
204
+ * @param options Optional Howl configuration
205
+ * @returns Factory function compatible with asset manager's loader parameter
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * const ecs = ECSpresso.create()
210
+ * .withAssets(a => a
211
+ * .add('explosion', loadSound('/sounds/explosion.mp3'))
212
+ * .add('bgm', loadSound(['/sounds/bgm.webm', '/sounds/bgm.mp3']))
213
+ * )
214
+ * .build();
215
+ * ```
216
+ */
217
+ export declare function loadSound(src: string | string[], options?: {
218
+ html5?: boolean;
219
+ preload?: boolean;
220
+ }): () => Promise<Howl>;
221
+ /**
222
+ * Create an audio bundle for ECSpresso.
223
+ *
224
+ * Provides:
225
+ * - `audioState` resource for fire-and-forget SFX and music
226
+ * - `audioSource` component for entity-attached sounds
227
+ * - Volume hierarchy: individual * channel * master
228
+ * - `playSound` / `stopMusic` event handlers
229
+ * - `soundEnded` event on completion
230
+ * - Automatic cleanup on entity removal (dispose callback)
231
+ *
232
+ * Sounds must be preloaded through the asset pipeline (`loadSound` helper).
233
+ *
234
+ * @example
235
+ * ```typescript
236
+ * const channels = defineAudioChannels({
237
+ * sfx: { volume: 1 },
238
+ * music: { volume: 0.7 },
239
+ * });
240
+ *
241
+ * const ecs = ECSpresso.create()
242
+ * .withAssets(a => a.add('explosion', loadSound('/sfx/boom.mp3')))
243
+ * .withBundle(createAudioBundle({ channels }))
244
+ * .build();
245
+ *
246
+ * await ecs.initialize();
247
+ * const audio = ecs.getResource('audioState');
248
+ * audio.play('explosion', { channel: 'sfx' });
249
+ * ```
250
+ */
251
+ export declare function createAudioBundle<Ch extends string, G extends string = 'audio'>(options: AudioBundleOptions<Ch, G>): Bundle<AudioComponentTypes<Ch>, AudioEventTypes<Ch>, AudioResourceTypes<Ch>, {}, {}, 'audio-sync', G, never, 'audio-sources'>;
252
+ type AnyECSpresso = import('ecspresso').default<any, any, any, any, any, any, any>;
253
+ /**
254
+ * Kit result from createAudioKit.
255
+ */
256
+ export interface AudioKit<W extends AnyECSpresso, Ch extends string, G extends string = 'audio'> {
257
+ bundle: Bundle<AudioComponentTypes<Ch>, AudioEventTypes<Ch>, AudioResourceTypes<Ch>, {}, {}, 'audio-sync', G, never, 'audio-sources'>;
258
+ createAudioSource: (sound: keyof AssetsOfWorld<W> & string, channel: Ch, options?: {
259
+ volume?: number;
260
+ loop?: boolean;
261
+ autoRemove?: boolean;
262
+ }) => Pick<AudioComponentTypes<Ch>, 'audioSource'>;
263
+ loadSound: typeof loadSound;
264
+ }
265
+ /**
266
+ * Create a typed audio kit that captures the world type W and channel type Ch.
267
+ *
268
+ * The returned `createAudioSource` validates sound keys against the world's
269
+ * asset types at compile time.
270
+ *
271
+ * @template W - Concrete ECS world type (e.g. `typeof ecs`)
272
+ * @template Ch - Channel name union from defineAudioChannels
273
+ * @template G - System group name (default: 'audio')
274
+ * @param options - Bundle configuration including channel definitions
275
+ * @returns A kit object with bundle, createAudioSource, loadSound
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * const channels = defineAudioChannels({ sfx: { volume: 1 }, music: { volume: 0.7 } });
280
+ * type Ch = ChannelsOf<typeof channels>;
281
+ * const kit = createAudioKit<typeof ecs, Ch>({ channels });
282
+ *
283
+ * const ecs = ECSpresso.create()
284
+ * .withBundle(kit.bundle)
285
+ * .withAssets(a => a.add('boom', loadSound('/sfx/boom.mp3')))
286
+ * .build();
287
+ *
288
+ * // Type-safe: 'boom' must be a registered asset
289
+ * kit.createAudioSource('boom', 'sfx');
290
+ * ```
291
+ */
292
+ export declare function createAudioKit<W extends AnyECSpresso, Ch extends string, G extends string = 'audio'>(options: AudioBundleOptions<Ch, G>): AudioKit<W, Ch, G>;
293
+ export {};
@@ -31,13 +31,15 @@ export interface WrapAtBounds {
31
31
  }
32
32
  /**
33
33
  * Component types provided by the bounds bundle.
34
- * Extend your component types with this interface.
34
+ * Included automatically via `.withBundle(createBoundsBundle())`.
35
35
  *
36
36
  * @example
37
37
  * ```typescript
38
- * interface GameComponents extends TransformComponentTypes, BoundsComponentTypes {
39
- * sprite: Sprite;
40
- * }
38
+ * const ecs = ECSpresso.create()
39
+ * .withBundle(createTransformBundle())
40
+ * .withBundle(createBoundsBundle({ width: 800, height: 600 }))
41
+ * .withComponentTypes<{ sprite: Sprite }>()
42
+ * .build();
41
43
  * ```
42
44
  */
43
45
  export interface BoundsComponentTypes {
@@ -82,9 +84,9 @@ export interface BoundsEventTypes {
82
84
  /**
83
85
  * Configuration options for the bounds bundle.
84
86
  */
85
- export interface BoundsBundleOptions {
87
+ export interface BoundsBundleOptions<G extends string = 'physics'> {
86
88
  /** System group name (default: 'physics') */
87
- systemGroup?: string;
89
+ systemGroup?: G;
88
90
  /** Priority for bounds systems (default: 50) */
89
91
  priority?: number;
90
92
  /** Resource key for bounds rectangle (default: 'bounds') */
@@ -185,5 +187,5 @@ type CombinedComponentTypes = BoundsComponentTypes & TransformComponentTypes;
185
187
  * });
186
188
  * ```
187
189
  */
188
- export declare function createBoundsBundle<ResourceTypes extends BoundsResourceTypes = BoundsResourceTypes>(options?: BoundsBundleOptions): Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes>;
190
+ export declare function createBoundsBundle<ResourceTypes extends BoundsResourceTypes = BoundsResourceTypes, G extends string = 'physics'>(options?: BoundsBundleOptions<G>): Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes, {}, {}, 'bounds-destroy' | 'bounds-clamp' | 'bounds-wrap', G>;
189
191
  export {};
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Camera / Viewport Bundle for ECSpresso
3
+ *
4
+ * Provides a camera entity with world/screen coordinate conversion, smooth follow,
5
+ * trauma-based shake, bounds clamping, and logical viewport dimensions.
6
+ *
7
+ * This bundle is renderer-agnostic. PixiJS or other renderer integration (applying
8
+ * cameraState to a container/stage transform) is the consumer's responsibility.
9
+ *
10
+ * Camera uses its own x/y/zoom/rotation rather than localTransform/worldTransform.
11
+ * It reads the target entity's worldTransform for follow, but doesn't participate
12
+ * in the transform hierarchy itself.
13
+ */
14
+ import { Bundle } from 'ecspresso';
15
+ import type { SystemPhase } from 'ecspresso';
16
+ import type ECSpresso from 'ecspresso';
17
+ import type { TransformComponentTypes } from './transform';
18
+ export interface Camera {
19
+ x: number;
20
+ y: number;
21
+ zoom: number;
22
+ rotation: number;
23
+ }
24
+ export interface CameraFollow {
25
+ target: number;
26
+ smoothing: number;
27
+ deadzoneX: number;
28
+ deadzoneY: number;
29
+ offsetX: number;
30
+ offsetY: number;
31
+ }
32
+ export interface CameraShake {
33
+ trauma: number;
34
+ traumaDecay: number;
35
+ maxOffsetX: number;
36
+ maxOffsetY: number;
37
+ maxRotation: number;
38
+ }
39
+ export interface CameraBounds {
40
+ minX: number;
41
+ minY: number;
42
+ maxX: number;
43
+ maxY: number;
44
+ }
45
+ export interface CameraComponentTypes {
46
+ camera: Camera;
47
+ cameraFollow: CameraFollow;
48
+ cameraShake: CameraShake;
49
+ cameraBounds: CameraBounds;
50
+ }
51
+ type CombinedComponentTypes = CameraComponentTypes & TransformComponentTypes;
52
+ export interface CameraState {
53
+ x: number;
54
+ y: number;
55
+ zoom: number;
56
+ rotation: number;
57
+ shakeOffsetX: number;
58
+ shakeOffsetY: number;
59
+ shakeRotation: number;
60
+ viewportWidth: number;
61
+ viewportHeight: number;
62
+ }
63
+ export interface CameraResourceTypes {
64
+ cameraState: CameraState;
65
+ }
66
+ export interface CameraBundleOptions<G extends string = 'camera'> {
67
+ viewportWidth?: number;
68
+ viewportHeight?: number;
69
+ systemGroup?: G;
70
+ phase?: SystemPhase;
71
+ randomFn?: () => number;
72
+ }
73
+ export declare const DEFAULT_CAMERA: Readonly<Camera>;
74
+ export declare const DEFAULT_CAMERA_STATE: Readonly<CameraState>;
75
+ export declare function createCamera(x?: number, y?: number, zoom?: number, rotation?: number): Pick<CameraComponentTypes, 'camera'>;
76
+ export declare function createCameraFollow(target: number, options?: Partial<Omit<CameraFollow, 'target'>>): Pick<CameraComponentTypes, 'cameraFollow'>;
77
+ export declare function createCameraShake(options?: Partial<CameraShake>): Pick<CameraComponentTypes, 'cameraShake'>;
78
+ export declare function createCameraBounds(minX: number, minY: number, maxX: number, maxY: number): Pick<CameraComponentTypes, 'cameraBounds'>;
79
+ export declare function addTrauma<C extends CombinedComponentTypes, R extends CameraResourceTypes>(ecs: ECSpresso<C, any, R>, entityId: number, amount: number): void;
80
+ export declare function worldToScreen(worldX: number, worldY: number, state: CameraState): {
81
+ x: number;
82
+ y: number;
83
+ };
84
+ export declare function screenToWorld(screenX: number, screenY: number, state: CameraState): {
85
+ x: number;
86
+ y: number;
87
+ };
88
+ export declare function createCameraBundle<G extends string = 'camera'>(options?: CameraBundleOptions<G>): Bundle<CombinedComponentTypes, {}, CameraResourceTypes, {}, {}, 'camera-follow' | 'camera-shake-update' | 'camera-bounds' | 'camera-state-sync', G>;
89
+ export {};