minimojs 1.0.0-alpha.2 → 1.0.0-alpha.21

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 (55) hide show
  1. package/README.md +82 -285
  2. package/dist/internal/AnimationSystem.js +345 -0
  3. package/dist/internal/AssetSystem.d.ts +1 -0
  4. package/dist/internal/AssetSystem.js +101 -0
  5. package/dist/internal/BackgroundSystem.d.ts +1 -0
  6. package/dist/internal/BackgroundSystem.js +26 -0
  7. package/dist/internal/CanvasSystem.d.ts +1 -0
  8. package/dist/internal/CanvasSystem.js +51 -0
  9. package/dist/internal/ExplosionSystem.d.ts +1 -0
  10. package/dist/internal/ExplosionSystem.js +540 -0
  11. package/dist/internal/InputSystem.d.ts +1 -0
  12. package/dist/internal/InputSystem.js +265 -0
  13. package/dist/internal/LoopSystem.d.ts +1 -0
  14. package/dist/internal/LoopSystem.js +61 -0
  15. package/dist/internal/PhysicsSystem.d.ts +1 -0
  16. package/dist/internal/PhysicsSystem.js +174 -0
  17. package/dist/internal/RenderSystem.d.ts +1 -0
  18. package/dist/internal/RenderSystem.js +910 -0
  19. package/dist/internal/SoundSystem.d.ts +1 -0
  20. package/dist/internal/SoundSystem.js +55 -0
  21. package/dist/internal/SpriteSystem.d.ts +1 -0
  22. package/dist/internal/SpriteSystem.js +32 -0
  23. package/dist/internal/TextSystem.d.ts +1 -0
  24. package/dist/internal/TextSystem.js +16 -0
  25. package/dist/internal/TimerSystem.d.ts +1 -0
  26. package/dist/internal/TimerSystem.js +43 -0
  27. package/dist/internal/TrailSystem.d.ts +1 -0
  28. package/dist/internal/TrailSystem.js +116 -0
  29. package/dist/internal/TransitionSystem.d.ts +1 -0
  30. package/dist/internal/TransitionSystem.js +74 -0
  31. package/dist/internal/arcade-racer/ArcadeRacerCollisionSystem.d.ts +1 -0
  32. package/dist/internal/arcade-racer/ArcadeRacerCollisionSystem.js +198 -0
  33. package/dist/internal/arcade-racer/ArcadeRacerLaneSystem.d.ts +1 -0
  34. package/dist/internal/arcade-racer/ArcadeRacerLaneSystem.js +120 -0
  35. package/dist/internal/arcade-racer/ArcadeRacerRenderSystem.d.ts +1 -0
  36. package/dist/internal/arcade-racer/ArcadeRacerRenderSystem.js +599 -0
  37. package/dist/internal/arcade-racer/ArcadeRacerRoadSprite.d.ts +1 -0
  38. package/dist/internal/arcade-racer/ArcadeRacerRoadSprite.js +13 -0
  39. package/dist/internal/arcade-racer/ArcadeRacerTrackSystem.d.ts +1 -0
  40. package/dist/internal/arcade-racer/ArcadeRacerTrackSystem.js +447 -0
  41. package/dist/minimo-arcaderacer.d.ts +1431 -0
  42. package/dist/minimo-arcaderacer.js +2060 -0
  43. package/dist/minimo.d.ts +1412 -162
  44. package/dist/minimo.js +2083 -816
  45. package/package.json +3 -2
  46. package/dist/animations.js +0 -30
  47. package/dist/audio.js +0 -17
  48. package/dist/game.js +0 -1105
  49. package/dist/input.js +0 -185
  50. package/dist/internal-types.js +0 -4
  51. package/dist/physics.js +0 -10
  52. package/dist/render.js +0 -75
  53. package/dist/sprite.js +0 -149
  54. package/dist/timers.js +0 -23
  55. /package/dist/{pointer-info.js → internal/AnimationSystem.d.ts} +0 -0
package/dist/minimo.d.ts CHANGED
@@ -8,53 +8,209 @@
8
8
  * THE ENGINE LOOP USES requestAnimationFrame ONLY.
9
9
  */
10
10
  /**
11
- * A 2D game object rendered as an emoji on the canvas.
11
+ * Optional tuning values for {@link Game.animateExplode}.
12
12
  *
13
- * Instantiate directly or extend to create custom sprite types.
14
- * Register with the engine by passing the instance to {@link Game.add}.
13
+ * All fields are optional. Omitted fields use the engine defaults.
14
+ */
15
+ export interface ExplodeOptions {
16
+ /** Number of vertical slices used to break the sprite apart. */
17
+ rows?: number;
18
+ /** Number of horizontal slices used to break the sprite apart. */
19
+ cols?: number;
20
+ /** Total effect duration in **milliseconds**. */
21
+ durationMs?: number;
22
+ /** Initial outward velocity magnitude in pixels per second. */
23
+ speed?: number;
24
+ /** Downward acceleration applied to pieces in pixels per second squared. */
25
+ gravityY?: number;
26
+ /** Maximum angular speed applied to pieces, in degrees per second. */
27
+ spin?: number;
28
+ /** Whether pieces fade out as the effect approaches completion. */
29
+ fade?: boolean;
30
+ /** Whether the original sprite is destroyed instead of restored after the effect. */
31
+ destroySprite?: boolean;
32
+ }
33
+ /**
34
+ * Cardinal flow direction used by directional visual effects.
35
+ */
36
+ export type FlowDirection = "left-to-right" | "right-to-left" | "top-to-bottom" | "bottom-to-top";
37
+ /**
38
+ * Direction used by piece-based local integration and disintegration effects.
15
39
  *
16
- * **Coordinate system:** center-based world space. `(x, y)` is the center of
17
- * the sprite. Positive X = right, positive Y = down.
40
+ * - Cardinal values apply a directional sweep across the sprite.
41
+ * - `"in-place"` keeps pieces in their final positions and only randomizes
42
+ * when each piece appears or disappears.
43
+ */
44
+ export type PieceFlowDirection = FlowDirection | "in-place";
45
+ /**
46
+ * Optional tuning values for {@link Game.animateAssemble}.
18
47
  *
19
- * **Lifecycle:** A sprite exists until {@link Game.destroySprite} is called or
20
- * {@link Game.reset} is invoked. After destruction, do not read or write its fields.
48
+ * All fields are optional. Omitted fields use the engine defaults.
49
+ */
50
+ export interface AssembleOptions {
51
+ /** Number of vertical slices used to reconstruct the sprite. */
52
+ rows?: number;
53
+ /** Number of horizontal slices used to reconstruct the sprite. */
54
+ cols?: number;
55
+ /** Total effect duration in **milliseconds**. */
56
+ durationMs?: number;
57
+ /** Initial outward distance magnitude in pixels per second equivalent. */
58
+ speed?: number;
59
+ /** Downward pull applied while pieces converge, in pixels per second squared. */
60
+ gravityY?: number;
61
+ /** Maximum angular speed applied to pieces, in degrees per second. */
62
+ spin?: number;
63
+ /** Whether pieces fade in while assembling. */
64
+ fade?: boolean;
65
+ }
66
+ /**
67
+ * Optional tuning values for {@link Game.animateDisintegrate}.
21
68
  *
22
- * @example
23
- * ```ts
24
- * // Direct instantiation
25
- * const coin = new Sprite("🪙");
26
- * coin.x = 300;
27
- * coin.y = 200;
28
- * coin.size = 32;
29
- * game.add(coin);
30
- * ```
69
+ * All fields are optional. Omitted fields use the engine defaults.
70
+ */
71
+ export interface DisintegrateOptions {
72
+ /** Number of vertical slices used to dissolve the sprite. */
73
+ rows?: number;
74
+ /** Number of horizontal slices used to dissolve the sprite. */
75
+ cols?: number;
76
+ /** Total effect duration in **milliseconds**. */
77
+ durationMs?: number;
78
+ /** Primary sweep direction across the sprite. */
79
+ direction?: PieceFlowDirection;
80
+ /** Maximum local travel distance in pixels for each piece. */
81
+ distance?: number;
82
+ /** Maximum angular speed applied to pieces, in degrees per second. */
83
+ spin?: number;
84
+ /** Whether pieces fade out as they disintegrate. */
85
+ fade?: boolean;
86
+ /** Whether the original sprite is destroyed instead of left hidden after the effect. */
87
+ destroySprite?: boolean;
88
+ }
89
+ /**
90
+ * Optional tuning values for {@link Game.animateIntegrate}.
31
91
  *
32
- * @example
33
- * ```ts
34
- * // Subclassing for custom game objects
35
- * class Player extends Sprite {
36
- * health = 3;
37
- *
38
- * constructor(x: number, y: number) {
39
- * super("🐢");
40
- * this.x = x;
41
- * this.y = y;
42
- * this.size = 48;
43
- * this.gravityScale = 1;
44
- * }
45
- * }
92
+ * All fields are optional. Omitted fields use the engine defaults.
93
+ */
94
+ export interface IntegrateOptions {
95
+ /** Number of vertical slices used to reconstruct the sprite. */
96
+ rows?: number;
97
+ /** Number of horizontal slices used to reconstruct the sprite. */
98
+ cols?: number;
99
+ /** Total effect duration in **milliseconds**. */
100
+ durationMs?: number;
101
+ /** Primary sweep direction across the sprite. */
102
+ direction?: PieceFlowDirection;
103
+ /** Maximum local travel distance in pixels for each piece. */
104
+ distance?: number;
105
+ /** Maximum angular speed applied to pieces, in degrees per second. */
106
+ spin?: number;
107
+ /** Whether pieces fade in while integrating. */
108
+ fade?: boolean;
109
+ }
110
+ /**
111
+ * Wipe mode used by {@link Game.animateWipe}.
112
+ */
113
+ export type WipeMode = "reveal" | "cover";
114
+ /**
115
+ * Full-screen transition type used by {@link Game.transitionTo}.
116
+ */
117
+ export type ScreenTransitionType = "fade" | "wipe" | "slide" | "iris" | "pixelate" | "flash";
118
+ /**
119
+ * Optional tuning values for {@link Game.animateWipe}.
46
120
  *
47
- * const player = new Player(400, 300);
48
- * game.add(player);
49
- * ```
121
+ * All fields are optional. Omitted fields use the engine defaults.
50
122
  */
51
- export declare class Sprite {
123
+ export interface WipeOptions {
124
+ /** Whether the wipe reveals the sprite or covers it away. */
125
+ mode?: WipeMode;
126
+ /** Primary wipe direction. */
127
+ direction?: FlowDirection;
128
+ /** Total effect duration in **milliseconds**. */
129
+ durationMs?: number;
130
+ /** Whether the original sprite is destroyed for `mode: "cover"`. */
131
+ destroySprite?: boolean;
132
+ }
133
+ /**
134
+ * Optional tuning values for {@link Game.transitionTo}.
135
+ *
136
+ * MinimoJS scene transitions operate on frozen snapshots of the outgoing and
137
+ * incoming scenes. The target scene is created immediately, then both
138
+ * snapshots are composited for the duration of the effect.
139
+ */
140
+ export interface SceneTransitionOptions {
141
+ /** Required transition style. */
142
+ type: ScreenTransitionType;
143
+ /** Total transition duration in **milliseconds**. */
144
+ durationMs?: number;
145
+ /** Optional color used by `fade` and `flash`. */
146
+ color?: string;
147
+ /** Direction used by `wipe` and `slide`. */
148
+ direction?: FlowDirection;
149
+ /** Optional iris center X position in canvas pixels. */
150
+ centerX?: number;
151
+ /** Optional iris center Y position in canvas pixels. */
152
+ centerY?: number;
153
+ /** Maximum pixel block size used by `pixelate`. */
154
+ pixelSize?: number;
155
+ }
156
+ /**
157
+ * Optional tuning values for {@link Game.animateTrail}.
158
+ *
159
+ * All fields are optional. Omitted fields use the engine defaults.
160
+ */
161
+ export interface TrailOptions {
162
+ /** How long the emitter spawns afterimages, in **milliseconds**. */
163
+ durationMs?: number;
164
+ /** Time between emitted afterimages, in **milliseconds**. */
165
+ spacingMs?: number;
166
+ /** Lifetime of each afterimage before it disappears, in **milliseconds**. */
167
+ fadeMs?: number;
168
+ /** Starting opacity multiplier for each emitted afterimage, in range `[0, 1]`. */
169
+ alpha?: number;
170
+ /** Final scale multiplier reached by each afterimage before it vanishes. */
171
+ scaleTo?: number;
172
+ }
173
+ /**
174
+ * How a {@link BackgroundLayer} image is fit inside its destination rectangle.
175
+ *
176
+ * - `"none"`: draws the image at its original size with no scaling.
177
+ * - `"stretch"`: fills the destination exactly, ignoring aspect ratio.
178
+ * - `"contain"`: preserves aspect ratio and keeps the entire image visible.
179
+ * - `"cover"`: preserves aspect ratio and fully covers the destination, cropping if needed.
180
+ */
181
+ export type BackgroundFit = "none" | "stretch" | "contain" | "cover";
182
+ /**
183
+ * Minimal scene contract understood by {@link Game.start} and {@link Game.reset}.
184
+ *
185
+ * Scene methods are invoked directly on the scene instance, so class-based
186
+ * scenes keep their expected `this` value.
187
+ */
188
+ export interface IScene {
52
189
  /**
53
- * The emoji character used to render this sprite.
54
- * Must be a single emoji. Change this at runtime to animate between frames.
55
- * @example "🔥", "⭐", "🐢"
190
+ * Called when the scene is created, before the first frame and again after
191
+ * each {@link Game.reset} that targets this scene.
56
192
  */
57
- sprite: string;
193
+ onCreate?(): void;
194
+ /**
195
+ * Called once per frame after timers, animation, and physics updates.
196
+ *
197
+ * @param dt - Delta time in seconds since the previous frame.
198
+ */
199
+ onUpdate?(dt: number): void;
200
+ }
201
+ /**
202
+ * Base class for all renderable MinimoJS actors.
203
+ *
204
+ * Concrete subclasses provide their own visual content while inheriting the
205
+ * shared transform, visibility, physics, and animation state required by the
206
+ * engine.
207
+ */
208
+ export declare abstract class Sprite {
209
+ protected constructor(game?: Game | null);
210
+ /**
211
+ * Game instance associated with this sprite, if any.
212
+ */
213
+ get game(): Game | null;
58
214
  /**
59
215
  * X position in world space (horizontal center of the sprite), in pixels.
60
216
  * Positive X points right. Updated each frame by: `x += vx * dt`.
@@ -68,10 +224,101 @@ export declare class Sprite {
68
224
  */
69
225
  y: number;
70
226
  /**
71
- * Width and height of the sprite's bounding square, in pixels.
72
- * Used for both canvas rendering (font size) and AABB collision detection.
227
+ * Resolved center X used internally by rendering, hit testing, and collisions.
228
+ */
229
+ get renderX(): number;
230
+ /**
231
+ * Resolved center Y used internally by rendering, hit testing, and collisions.
232
+ */
233
+ get renderY(): number;
234
+ /**
235
+ * Visual scale multiplier applied to this sprite's logical width/height.
236
+ * Default: `1`.
237
+ */
238
+ scale: number;
239
+ /**
240
+ * Base logical width in pixels before applying {@link Sprite.scale}.
73
241
  */
74
- size: number;
242
+ abstract get width(): number;
243
+ /**
244
+ * Base logical height in pixels before applying {@link Sprite.scale}.
245
+ */
246
+ abstract get height(): number;
247
+ /**
248
+ * Effective rendered/collision width in pixels.
249
+ */
250
+ get displayWidth(): number;
251
+ /**
252
+ * Effective rendered/collision height in pixels.
253
+ */
254
+ get displayHeight(): number;
255
+ /**
256
+ * Optional logical body width used by physics helpers and collision checks.
257
+ *
258
+ * When `null` (default), MinimoJS uses the sprite's visual {@link Sprite.width}.
259
+ * When set, this value is scaled by {@link Sprite.scale} the same way as the
260
+ * visual sprite size.
261
+ */
262
+ bodyWidth: number | null;
263
+ /**
264
+ * Optional logical body height used by physics helpers and collision checks.
265
+ *
266
+ * When `null` (default), MinimoJS uses the sprite's visual {@link Sprite.height}.
267
+ * When set, this value is scaled by {@link Sprite.scale} the same way as the
268
+ * visual sprite size.
269
+ */
270
+ bodyHeight: number | null;
271
+ /**
272
+ * Horizontal body offset, in local sprite pixels before scale is applied.
273
+ *
274
+ * Positive values move the collision body to the right of the sprite's rendered center.
275
+ * Negative values move it to the left.
276
+ */
277
+ bodyOffsetX: number;
278
+ /**
279
+ * Vertical body offset, in local sprite pixels before scale is applied.
280
+ *
281
+ * Positive values move the collision body downward relative to the sprite's
282
+ * rendered center. Negative values move it upward.
283
+ */
284
+ bodyOffsetY: number;
285
+ /**
286
+ * Effective collision-body width in pixels after applying {@link Sprite.scale}.
287
+ */
288
+ get bodyDisplayWidth(): number;
289
+ /**
290
+ * Effective collision-body height in pixels after applying {@link Sprite.scale}.
291
+ */
292
+ get bodyDisplayHeight(): number;
293
+ /**
294
+ * Resolved body center X used internally by physics helpers and collision checks.
295
+ */
296
+ get bodyCenterX(): number;
297
+ /**
298
+ * Resolved body center Y used internally by physics helpers and collision checks.
299
+ */
300
+ get bodyCenterY(): number;
301
+ /**
302
+ * CSS text color used when rendering this sprite.
303
+ *
304
+ * This mainly affects monochrome glyphs and symbol-style sprites.
305
+ * Full-color emoji may ignore this and render with their native colors,
306
+ * depending on browser behavior.
307
+ */
308
+ color: string;
309
+ /**
310
+ * Physics body type flag.
311
+ *
312
+ * - `false` (default): the sprite is dynamic and can be moved by velocity,
313
+ * gravity, and explicit collision resolution.
314
+ * - `true`: the sprite is static and is not moved by the engine's built-in
315
+ * velocity/gravity integration. Static sprites act as stable obstacles for
316
+ * simple platform collisions.
317
+ *
318
+ * Static sprites can still be repositioned manually by setting `x` and `y`
319
+ * directly in your own game code.
320
+ */
321
+ isStatic: boolean;
75
322
  /**
76
323
  * Visual rotation of the sprite in degrees.
77
324
  * `0` = upright. Positive values rotate clockwise.
@@ -137,23 +384,320 @@ export declare class Sprite {
137
384
  */
138
385
  gravityScale: number;
139
386
  /**
140
- * Creates a new Sprite with the given emoji and optional position.
387
+ * Stable cache key describing the sprite's rendered appearance.
388
+ *
389
+ * The renderer uses this to reuse prerendered canvases across frames.
390
+ */
391
+ abstract getRenderCacheKey(): string;
392
+ protected getAnchorOffsetX(): number;
393
+ protected getAnchorOffsetY(): number;
394
+ }
395
+ /**
396
+ * A 2D game object rendered as an emoji on the canvas.
397
+ *
398
+ * Instantiate directly or extend to create custom sprite types.
399
+ * Register with the engine by passing the instance to {@link Game.add}.
400
+ *
401
+ * **Coordinate system:** center-based world space. `(x, y)` is the center of
402
+ * the sprite. Positive X = right, positive Y = down.
403
+ *
404
+ * **Lifecycle:** A sprite exists until {@link Game.destroySprite} is called or
405
+ * {@link Game.reset} is invoked. After destruction, do not read or write its fields.
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * const coin = new EmojiSprite("🪙", 300, 200, 32);
410
+ * game.add(coin);
411
+ * ```
412
+ */
413
+ export declare class EmojiSprite extends Sprite {
414
+ /**
415
+ * The emoji character used to render this sprite.
416
+ * Must be a single emoji. Change this at runtime to animate between frames.
417
+ * @example "🔥", "⭐", "🐢"
418
+ */
419
+ sprite: string;
420
+ get size(): number;
421
+ get width(): number;
422
+ get height(): number;
423
+ get displaySize(): number;
424
+ /**
425
+ * Creates a new EmojiSprite with the given emoji, optional position, and base size.
141
426
  * All other properties use their defaults and can be set after construction.
142
427
  *
143
428
  * @param sprite - The emoji character to render. Must be a single emoji.
144
- * Image sprites are NOT supported emoji only.
429
+ * Use {@link ImageSprite} for preloaded bitmap textures.
145
430
  * @example "🔥", "⭐", "🐢", "💣", "👾"
146
431
  * @param x - Initial X position in world space (center), in pixels. Default: `0`.
147
432
  * @param y - Initial Y position in world space (center), in pixels. Default: `0`.
433
+ * @param size - Base sprite size in pixels. Default: `32`.
148
434
  *
149
435
  * @example
150
436
  * ```ts
151
- * const enemy = new Sprite("👾", 200, 100);
152
- * enemy.size = 40;
437
+ * const enemy = new EmojiSprite("👾", 200, 100, 40);
153
438
  * game.add(enemy);
154
439
  * ```
155
440
  */
156
- constructor(sprite: string, x?: number, y?: number);
441
+ constructor(sprite: string, x?: number, y?: number, size?: number);
442
+ getRenderCacheKey(): string;
443
+ }
444
+ /**
445
+ * A renderable sprite backed by a preloaded image asset.
446
+ *
447
+ * Width and height are always resolved from the current texture. To resize the
448
+ * sprite visually, use {@link Sprite.scale}.
449
+ */
450
+ export declare class ImageSprite extends Sprite {
451
+ get imageKey(): string;
452
+ get width(): number;
453
+ get height(): number;
454
+ /**
455
+ * Creates a new image-backed sprite.
456
+ *
457
+ * @param game - Game instance used to resolve the texture key.
458
+ * @param imageKey - Texture key previously registered with {@link Game.loadImage} or {@link Game.createTexture}.
459
+ * @param x - Initial X position in world space (center), in pixels. Default: `0`.
460
+ * @param y - Initial Y position in world space (center), in pixels. Default: `0`.
461
+ */
462
+ constructor(game: Game, imageKey: string, x?: number, y?: number);
463
+ /**
464
+ * Replaces the current texture with another preloaded image.
465
+ *
466
+ * @param imageKey - New texture key to render.
467
+ */
468
+ setTexture(imageKey: string): void;
469
+ getRenderCacheKey(): string;
470
+ }
471
+ /**
472
+ * A renderable sprite backed by a per-instance canvas that is repainted on demand.
473
+ *
474
+ * `DrawSprite` is useful for procedural shapes, gauges, charts, minimaps, and
475
+ * HUD widgets whose appearance changes often and is easier to express with
476
+ * Canvas 2D drawing commands than with emoji, text, or image swaps.
477
+ *
478
+ * Treat `DrawSprite` as a specialized tool, not the default sprite type.
479
+ * Because it may execute custom Canvas 2D drawing code repeatedly, overusing it
480
+ * can affect game performance much more than regular {@link EmojiSprite},
481
+ * {@link ImageSprite}, or {@link TextSprite} instances.
482
+ *
483
+ * Prefer the other sprite types whenever they can express the same result more
484
+ * simply. Reach for `DrawSprite` only when you truly need procedural drawing or
485
+ * custom per-sprite canvas rendering that the built-in sprite types cannot
486
+ * provide cleanly.
487
+ *
488
+ * MinimoJS creates and owns an internal canvas for each `DrawSprite` instance.
489
+ * Before every engine render that needs this sprite's surface, the engine:
490
+ *
491
+ * 1. Resolves the sprite's internal canvas size
492
+ * 2. Optionally clears and repaints that internal canvas
493
+ * 3. Draws the resulting canvas like any other sprite surface
494
+ *
495
+ * By default, `DrawSprite` is live-rendered: the engine clears the internal
496
+ * canvas and calls {@link DrawSprite.redraw} every frame.
497
+ *
498
+ * Set {@link DrawSprite.frozen} to `true` if you want to keep and reuse the
499
+ * last rendered canvas contents without repainting on each frame. This is
500
+ * useful for shapes or procedural art that you only want to draw once.
501
+ *
502
+ * While `frozen` is `true`, MinimoJS reuses the existing surface exactly as-is:
503
+ * it does not clear the canvas and does not call {@link DrawSprite.redraw}
504
+ * again, unless the surface does not exist yet or its size had to be rebuilt.
505
+ *
506
+ * This means freezing is a rendering optimization and content-preservation
507
+ * flag, not a separate caching system. You can switch `frozen` on or off at
508
+ * runtime whenever it makes sense for your sprite.
509
+ *
510
+ * The local drawing coordinate system uses the sprite surface itself:
511
+ * - `(0, 0)` is the top-left corner of the internal canvas
512
+ * - `width` / `height` match the sprite's logical size before `scale`
513
+ * - draw centered content yourself if you want the visual origin in the middle
514
+ *
515
+ * Override {@link DrawSprite.redraw} in a subclass, or assign your own method
516
+ * on an instance if you prefer an inline style in JavaScript.
517
+ */
518
+ export declare class DrawSprite extends Sprite {
519
+ /**
520
+ * When `false` (default), MinimoJS clears this sprite's internal canvas and
521
+ * calls {@link DrawSprite.redraw} on every render pass.
522
+ *
523
+ * When `true`, MinimoJS keeps and reuses the existing canvas contents without
524
+ * clearing or repainting them again, unless the internal surface does not yet
525
+ * exist or had to be resized.
526
+ *
527
+ * Use this when your procedural drawing becomes static after its first paint,
528
+ * or when you want explicit manual control over when the sprite is redrawn by
529
+ * toggling `frozen` at runtime.
530
+ */
531
+ frozen: boolean;
532
+ get width(): number;
533
+ get height(): number;
534
+ /**
535
+ * Creates a new dynamic canvas-backed sprite.
536
+ *
537
+ * @param width - Internal canvas width in pixels. Minimum `1`.
538
+ * @param height - Internal canvas height in pixels. Minimum `1`.
539
+ * @param x - Initial X position in world space (center), in pixels. Default: `0`.
540
+ * @param y - Initial Y position in world space (center), in pixels. Default: `0`.
541
+ */
542
+ constructor(width: number, height: number, x?: number, y?: number);
543
+ /**
544
+ * Called by the engine whenever the sprite's internal surface must be repainted.
545
+ *
546
+ * The provided context is already cleared and reset to the default 2D canvas
547
+ * state for the current surface size. MinimoJS repaints each `DrawSprite` at
548
+ * most once per render pass while it is not {@link DrawSprite.frozen}, so
549
+ * repeated snapshot reads during the same frame reuse the already-redrawn
550
+ * surface.
551
+ *
552
+ * @param ctx - The sprite's internal 2D drawing context.
553
+ */
554
+ redraw(_ctx: CanvasRenderingContext2D): void;
555
+ getRenderCacheKey(): string;
556
+ }
557
+ /**
558
+ * A renderable text actor that participates in the same animation/effects
559
+ * pipeline as regular sprites.
560
+ *
561
+ * Use `TextSprite` when the content is primarily text, or when you need text
562
+ * layout features such as wrapping, fixed button sizes, background/border, or
563
+ * text stroke.
564
+ *
565
+ * For controls that are only a single emoji, prefer {@link EmojiSprite}. Emoji-only
566
+ * buttons usually behave better as sprites because they do not need text
567
+ * padding/layout and their bounds match the rendered emoji more directly.
568
+ */
569
+ export declare class TextSprite extends Sprite {
570
+ anchorX: "left" | "center" | "right";
571
+ anchorY: "top" | "middle" | "bottom";
572
+ text: string;
573
+ fontFamily: string;
574
+ fontSize: number;
575
+ fontWeight: string;
576
+ lineHeight: number;
577
+ maxWidth: number;
578
+ fixedWidth: number;
579
+ fixedHeight: number;
580
+ paddingX: number;
581
+ paddingY: number;
582
+ backgroundColor: string | null;
583
+ borderColor: string | null;
584
+ borderWidth: number;
585
+ cornerRadius: number;
586
+ strokeColor: string | null;
587
+ strokeWidth: number;
588
+ textAlign: CanvasTextAlign;
589
+ get width(): number;
590
+ get height(): number;
591
+ constructor(text: string, x?: number, y?: number, fontSizeOrConfig?: number | TextSpriteConfig, config?: TextSpriteConfig);
592
+ getRenderCacheKey(): string;
593
+ get align(): CanvasTextAlign;
594
+ set align(value: CanvasTextAlign);
595
+ protected getAnchorOffsetX(): number;
596
+ protected getAnchorOffsetY(): number;
597
+ }
598
+ export type TextSpriteConfig = {
599
+ anchorX?: "left" | "center" | "right";
600
+ anchorY?: "top" | "middle" | "bottom";
601
+ fontFamily?: string;
602
+ fontSize?: number;
603
+ fontWeight?: string;
604
+ lineHeight?: number;
605
+ maxWidth?: number;
606
+ fixedWidth?: number;
607
+ fixedHeight?: number;
608
+ paddingX?: number;
609
+ paddingY?: number;
610
+ backgroundColor?: string | null;
611
+ borderColor?: string | null;
612
+ borderWidth?: number;
613
+ cornerRadius?: number;
614
+ strokeColor?: string | null;
615
+ strokeWidth?: number;
616
+ textAlign?: CanvasTextAlign;
617
+ align?: CanvasTextAlign;
618
+ };
619
+ export type FontRequirementOptions = {
620
+ weight?: string;
621
+ style?: "normal" | "italic" | "oblique";
622
+ size?: number;
623
+ sampleText?: string;
624
+ };
625
+ /**
626
+ * A static image-based background layer rendered behind all sprites.
627
+ *
628
+ * Background layers are visual-only: they do not participate in physics,
629
+ * collisions, input hit-testing, or sprite queries.
630
+ *
631
+ * `x` / `y` use **top-left screen-space coordinates**, unlike {@link Sprite},
632
+ * which uses center-based world space.
633
+ *
634
+ * The texture referenced by {@link BackgroundLayer.imageKey} must be registered
635
+ * with {@link Game.loadImage} during {@link Game.onPreload}, or created
636
+ * dynamically with {@link Game.createTexture}.
637
+ *
638
+ * @example
639
+ * ```ts
640
+ * game.onPreload = () => {
641
+ * game.loadImage("sky", "assets/sky.png");
642
+ * };
643
+ *
644
+ * game.onCreate = () => {
645
+ * const bg = game.addBackground(new BackgroundLayer("sky"));
646
+ * bg.fit = "cover";
647
+ * };
648
+ * ```
649
+ */
650
+ export declare class BackgroundLayer {
651
+ /**
652
+ * Key of the preloaded image to render.
653
+ */
654
+ imageKey: string;
655
+ /**
656
+ * Left edge of the destination rectangle in screen space, in pixels.
657
+ */
658
+ x: number;
659
+ /**
660
+ * Top edge of the destination rectangle in screen space, in pixels.
661
+ */
662
+ y: number;
663
+ /**
664
+ * Width of the destination rectangle in pixels.
665
+ * If `0` (default), the canvas width is used.
666
+ */
667
+ width: number;
668
+ /**
669
+ * Height of the destination rectangle in pixels.
670
+ * If `0` (default), the canvas height is used.
671
+ */
672
+ height: number;
673
+ /**
674
+ * Opacity of the layer in range `[0, 1]`.
675
+ */
676
+ alpha: number;
677
+ /**
678
+ * Controls whether the layer is drawn.
679
+ */
680
+ visible: boolean;
681
+ /**
682
+ * Orders background layers relative to each other.
683
+ * Higher values render on top of lower background layers, but still behind sprites.
684
+ */
685
+ layer: number;
686
+ /**
687
+ * How the image is fit inside the destination rectangle.
688
+ * Default: `"cover"`.
689
+ */
690
+ fit: BackgroundFit;
691
+ /**
692
+ * Creates a new background layer.
693
+ *
694
+ * @param imageKey - Preloaded image key previously registered with {@link Game.loadImage}.
695
+ * @param x - Destination rectangle left edge in screen space. Default: `0`.
696
+ * @param y - Destination rectangle top edge in screen space. Default: `0`.
697
+ * @param width - Destination rectangle width. `0` means canvas width. Default: `0`.
698
+ * @param height - Destination rectangle height. `0` means canvas height. Default: `0`.
699
+ */
700
+ constructor(imageKey: string, x?: number, y?: number, width?: number, height?: number);
157
701
  }
158
702
  /**
159
703
  * Snapshot of an active pointer tracked by the engine.
@@ -181,6 +725,36 @@ export interface PointerInfo {
181
725
  */
182
726
  pressed: boolean;
183
727
  }
728
+ /**
729
+ * Information about a resolved AABB collision.
730
+ *
731
+ * Flags are reported relative to the **first** sprite passed to
732
+ * {@link Game.collide}. For example, `bottom = true` means the bottom side of
733
+ * the first sprite contacted the top side of the second sprite.
734
+ */
735
+ export interface CollisionInfo {
736
+ /**
737
+ * `true` when the first sprite's left side hit the second sprite.
738
+ */
739
+ left: boolean;
740
+ /**
741
+ * `true` when the first sprite's right side hit the second sprite.
742
+ */
743
+ right: boolean;
744
+ /**
745
+ * `true` when the first sprite's top side hit the second sprite from below.
746
+ */
747
+ top: boolean;
748
+ /**
749
+ * `true` when the first sprite's bottom side landed on the second sprite.
750
+ */
751
+ bottom: boolean;
752
+ /**
753
+ * Convenience flag for platformers.
754
+ * Equivalent to `bottom = true`.
755
+ */
756
+ grounded: boolean;
757
+ }
184
758
  /**
185
759
  * # MinimoJS v1 — AI Agent Integration Guide
186
760
  *
@@ -190,18 +764,88 @@ export interface PointerInfo {
190
764
  *
191
765
  * ---
192
766
  *
767
+ * ## ES Module Only
768
+ *
769
+ * MinimoJS is an **ES module only** package.
770
+ * You must import it with standard ESM syntax such as:
771
+ *
772
+ * ```ts
773
+ * import { Game, EmojiSprite } from "minimojs";
774
+ * ```
775
+ *
776
+ * In the browser, use it from a module script:
777
+ *
778
+ * ```html
779
+ * <script type="module">
780
+ * import { Game, EmojiSprite } from "./dist/minimo.js";
781
+ * </script>
782
+ * ```
783
+ *
784
+ * Or from a CDN:
785
+ *
786
+ * ```ts
787
+ * import { Game, EmojiSprite } from "https://cdn.jsdelivr.net/npm/minimojs@<version>/dist/minimo.js";
788
+ * ```
789
+ *
790
+ * Do NOT use a classic `<script>` tag without `type="module"`.
791
+ * Do NOT expect a global `window.MinimoJS`.
792
+ * Do NOT use `require("minimojs")` (CommonJS is not supported).
793
+ *
794
+ * ---
795
+ *
796
+ * ## Required Font Setup
797
+ *
798
+ * `drawText()` and `TextSprite` default to `"Press Start 2P", monospace`.
799
+ * MinimoJS automatically waits for that default family before the first frame.
800
+ * If you use additional families, register them with {@link Game.requireFont}
801
+ * before calling {@link Game.start}.
802
+ *
803
+ * Prefer {@link TextSprite} for persistent UI text, interactive labels,
804
+ * buttons, fixed-size text boxes, and styled text elements. Keep
805
+ * `drawText()` for simple screen-space overlays such as HUD counters, debug
806
+ * text, or other text that is redrawn every frame.
807
+ *
808
+ * You MUST still declare the fonts yourself in your `index.html`. MinimoJS
809
+ * does NOT download, inject, or manage web fonts for you. If a font is missing,
810
+ * the browser falls back to `monospace`.
811
+ *
812
+ * Example `index.html` `<head>` setup:
813
+ *
814
+ * ```html
815
+ * <link rel="preconnect" href="https://fonts.googleapis.com" />
816
+ * <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
817
+ * <link
818
+ * href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap"
819
+ * rel="stylesheet"
820
+ * />
821
+ * ```
822
+ *
823
+ * Example custom font registration:
824
+ *
825
+ * ```ts
826
+ * const game = new Game(720, 1280);
827
+ * game.requireFont('"Bangers"', { weight: "400" });
828
+ * game.start();
829
+ * ```
830
+ *
831
+ * ---
832
+ *
193
833
  * ## Quick Start
194
834
  *
195
835
  * ```ts
196
- * import { Game, Sprite } from "minimojs";
836
+ * import { Game, ImageSprite } from "https://cdn.jsdelivr.net/npm/minimojs@<version>/dist/minimo.js";
197
837
  *
198
- * const game = new Game(800, 600);
838
+ * const game = new Game(720, 1280);
199
839
  *
200
- * const player = new Sprite("🐢");
201
- * player.x = 400;
202
- * player.y = 500;
203
- * player.size = 48;
204
- * game.add(player);
840
+ * game.onPreload = () => {
841
+ * game.loadImage("player", "assets/player.png");
842
+ * };
843
+ *
844
+ * let player: ImageSprite;
845
+ *
846
+ * game.onCreate = () => {
847
+ * player = game.add(new ImageSprite(game, "player", 400, 500));
848
+ * };
205
849
  *
206
850
  * game.onUpdate = (dt) => {
207
851
  * if (game.isKeyDown("ArrowLeft")) player.vx = -200;
@@ -214,6 +858,13 @@ export interface PointerInfo {
214
858
  * game.start();
215
859
  * ```
216
860
  *
861
+ * Prototyping, and have no art yet? Swap the texture for an emoji and drop the
862
+ * preload entirely — everything else stays the same:
863
+ *
864
+ * ```ts
865
+ * const player = game.add(new EmojiSprite("🐢", 400, 500, 48));
866
+ * ```
867
+ *
217
868
  * ---
218
869
  *
219
870
  * ## Engine Philosophy
@@ -227,7 +878,8 @@ export interface PointerInfo {
227
878
  * ## Flat API
228
879
  *
229
880
  * ALL engine functionality is on the `game` object.
230
- * Do not look for sub-objects like `game.physics`, `game.input`, etc. They do not exist.
881
+ * Do not look for nested subsystem objects like `game.input.keyboard`,
882
+ * `game.physics.world`, etc. They do not exist in MinimoJS v1.
231
883
  *
232
884
  * ---
233
885
  *
@@ -238,13 +890,28 @@ export interface PointerInfo {
238
890
  *
239
891
  * ---
240
892
  *
241
- * ## Emoji-Only Sprites
242
- *
243
- * Every sprite MUST use a single emoji character as its visual representation.
244
- * PNG, SVG, spritesheet, and image sprites are NOT supported.
245
- * Use Unicode emoji: `"🔥"`, `"⭐"`, `"💣"`, `"🐢"`, `"👾"`, `"🧱"`, etc.
246
- * AI agents can also use text-like emojis (regional indicators, symbols, letters)
247
- * to build fun title art, HUD labels, and expressive in-game text.
893
+ * ## Choosing a Visual Source
894
+ *
895
+ * A sprite's look comes from one of four concrete types, all of which extend the
896
+ * shared abstract {@link Sprite} type. Use `Sprite` as the type in your own
897
+ * signatures; never construct it directly.
898
+ *
899
+ * 1. {@link ImageSprite} the game's own images, and the first option to reach
900
+ * for. Register a texture with {@link Game.loadImage} inside
901
+ * {@link Game.onPreload}, or build one at runtime with
902
+ * {@link Game.createTexture}, then render it with
903
+ * `new ImageSprite(game, key, x, y)`. An image looks identical on every
904
+ * device and belongs to the game.
905
+ * 2. {@link EmojiSprite} — a single Unicode emoji, and the shortest path to a
906
+ * first prototype: `new EmojiSprite("🐢", x, y, size)`. Note that an
907
+ * emoji is drawn with the player's own system emoji font, so the same game
908
+ * looks different across iOS, Android, and Windows, and a glyph the platform
909
+ * lacks renders as an empty box. Prefer an image whenever the look matters.
910
+ * 3. {@link TextSprite} — text, labels, and buttons.
911
+ * 4. {@link DrawSprite} — procedural Canvas 2D drawing, for shapes and gauges
912
+ * the other three cannot express cleanly.
913
+ *
914
+ * Emoji and images mix freely in one game.
248
915
  *
249
916
  * ---
250
917
  *
@@ -260,7 +927,7 @@ export interface PointerInfo {
260
927
  *
261
928
  * ALL time parameters to engine methods are in **milliseconds (ms)**.
262
929
  * This includes: {@link Game.addTimer}, {@link Game.animateAlpha},
263
- * {@link Game.animateRotation}, and {@link Game.sound}.
930
+ * {@link Game.animateRotation}, {@link Game.animateDeform}, and {@link Game.sound}.
264
931
  *
265
932
  * The `dt` parameter in {@link Game.onUpdate} is an exception — it is in
266
933
  * **seconds** for convenient velocity math (`position += velocity * dt`).
@@ -312,10 +979,10 @@ export interface PointerInfo {
312
979
  *
313
980
  * ---
314
981
  *
315
- * ## Scene Initialization with `onCreate`
982
+ * ## Scene Initialization with `IScene`
316
983
  *
317
- * MinimoJS has NO scene system. Use {@link Game.onCreate} to build a scene.
318
- * The engine calls `onCreate`:
984
+ * MinimoJS supports simple scene objects via {@link IScene}. The engine calls
985
+ * a scene's `onCreate()`:
319
986
  * - Once before the first frame (when {@link Game.start} is called).
320
987
  * - Again after each {@link Game.reset}.
321
988
  *
@@ -327,29 +994,33 @@ export interface PointerInfo {
327
994
  * - Scroll position (scrollX and scrollY reset to 0)
328
995
  * - Per-frame input state (pressed keys/pointer)
329
996
  *
330
- * After clearing, `reset()` calls `onCreate()` so your callback can rebuild the
331
- * new scene immediately. Simply re-add sprites and re-register timers.
997
+ * After clearing, `reset()` calls the active scene's `onCreate()` so it can
998
+ * rebuild immediately. Simply re-add sprites and re-register timers.
332
999
  *
333
1000
  * ```ts
334
- * game.onCreate = () => {
335
- * // scene init: add sprites, setup timers
336
- * const skull = new Sprite("💀");
337
- * skull.x = 400; skull.y = 300; skull.size = 96;
338
- * game.add(skull);
339
- * };
1001
+ * class SkullScene implements IScene {
1002
+ * onCreate() {
1003
+ * const skull = new EmojiSprite("💀", 400, 300, 96);
1004
+ * game.add(skull);
1005
+ * }
340
1006
  *
341
- * game.onUpdate = (dt) => {
342
- * // normal per-frame update
343
- * };
1007
+ * onUpdate(dt: number) {
1008
+ * // normal per-frame update
1009
+ * }
1010
+ * }
344
1011
  *
345
- * game.start(); // calls onCreate() once before first frame
1012
+ * const scene = new SkullScene();
1013
+ * game.start(scene); // calls onCreate() once before first frame
346
1014
  * // later:
347
1015
  * game.reset(); // clears + calls onCreate() again
348
1016
  * ```
349
1017
  *
1018
+ * Legacy `game.onCreate` / `game.onUpdate` callbacks still work when no active
1019
+ * {@link IScene} is set.
1020
+ *
350
1021
  * ---
351
1022
  *
352
- * ## Sprite Lifecycle Ownership
1023
+ * ## EmojiSprite Lifecycle Ownership
353
1024
  *
354
1025
  * The `Game` instance owns all sprites.
355
1026
  * - Create sprites with {@link Game.add}.
@@ -363,7 +1034,7 @@ export interface PointerInfo {
363
1034
  * ## Forbidden Features
364
1035
  *
365
1036
  * The following do NOT exist in MinimoJS v1. Do NOT attempt to use them:
366
- * - Scene system or scene manager
1037
+ * - Full scene manager architecture (scene stacks, transitions, loaders, etc.)
367
1038
  * - Entity Component System (ECS)
368
1039
  * - Physics engine (no Box2D, Matter.js, etc.)
369
1040
  * - Camera zoom or scale
@@ -371,8 +1042,7 @@ export interface PointerInfo {
371
1042
  * - Text input / HTML form elements
372
1043
  * - Parallax layers
373
1044
  * - Multiple cameras
374
- * - Collision resolution (only detection is supported)
375
- * - Image sprites (PNG, SVG, canvas, etc.)
1045
+ * - Full physics engine (only basic explicit AABB collision helpers are provided)
376
1046
  * - `setTimeout` or `setInterval`
377
1047
  */
378
1048
  export declare class Game {
@@ -387,7 +1057,8 @@ export declare class Game {
387
1057
  * game.gravityX = 0; // no horizontal gravity
388
1058
  * ```
389
1059
  */
390
- gravityX: number;
1060
+ get gravityX(): number;
1061
+ set gravityX(value: number);
391
1062
  /**
392
1063
  * Vertical gravity acceleration in pixels per second².
393
1064
  * Applied to every sprite whose {@link Sprite.gravityScale} is non-zero.
@@ -400,7 +1071,23 @@ export declare class Game {
400
1071
  * game.gravityY = 980; // standard downward gravity
401
1072
  * ```
402
1073
  */
403
- gravityY: number;
1074
+ get gravityY(): number;
1075
+ set gravityY(value: number);
1076
+ /**
1077
+ * Enables the basic collision-resolution helpers (`collide` / `collideAny`).
1078
+ *
1079
+ * When `false` (default), overlap detection still works, but collision
1080
+ * resolution helpers are unavailable.
1081
+ *
1082
+ * Set this to `true` for simple platformer-style collision handling.
1083
+ *
1084
+ * @example
1085
+ * ```ts
1086
+ * game.physics = true;
1087
+ * ```
1088
+ */
1089
+ get physics(): boolean;
1090
+ set physics(value: boolean);
404
1091
  /**
405
1092
  * Horizontal scroll offset of the world camera, in pixels.
406
1093
  * The canvas viewport is shifted left by `scrollX` — sprites with higher `x`
@@ -448,6 +1135,20 @@ export declare class Game {
448
1135
  from: string;
449
1136
  to: string;
450
1137
  } | null;
1138
+ /**
1139
+ * When `true`, MinimoJS draws every sprite's collision body as an overlay.
1140
+ *
1141
+ * This is a runtime debugging aid only. It does not change collisions,
1142
+ * input, rendering order, or physics behavior.
1143
+ */
1144
+ debugBodies: boolean;
1145
+ /**
1146
+ * When `true`, MinimoJS draws every sprite's input area as an overlay.
1147
+ *
1148
+ * This is a runtime debugging aid only. It does not change input behavior,
1149
+ * physics, or rendering.
1150
+ */
1151
+ debugInputAreas: boolean;
451
1152
  /**
452
1153
  * Background color for the full web page (`document.body`).
453
1154
  * Set to any valid CSS color string. Default: `null` (engine leaves page background unchanged).
@@ -461,24 +1162,45 @@ export declare class Game {
461
1162
  */
462
1163
  pageBackground: string | null;
463
1164
  /**
464
- * Scene creation callback.
1165
+ * Asset registration callback invoked once before the first scene is created.
1166
+ *
1167
+ * Use this to register images with {@link Game.loadImage}. The callback is
1168
+ * synchronous: it should only queue assets, not await network work.
1169
+ *
1170
+ * After `onPreload` returns, MinimoJS loads the queued images automatically
1171
+ * and shows a default loading screen until they are ready.
1172
+ *
1173
+ * @example
1174
+ * ```ts
1175
+ * game.onPreload = () => {
1176
+ * game.loadImage("sky", "assets/sky.png");
1177
+ * game.loadImage("land", "assets/land.png");
1178
+ * };
1179
+ * ```
1180
+ */
1181
+ onPreload: (() => void) | null;
1182
+ /**
1183
+ * Legacy scene creation callback.
465
1184
  *
466
1185
  * Called once before the first frame on {@link Game.start}, and again after
467
- * each {@link Game.reset}. Use this to create sprites and timers for a scene.
1186
+ * each {@link Game.reset} when no active {@link IScene} is set.
468
1187
  *
469
1188
  * @example
470
1189
  * ```ts
471
1190
  * game.onCreate = () => {
472
- * const player = new Sprite("🐢");
1191
+ * const player = new EmojiSprite("🐢");
473
1192
  * player.x = 200;
474
1193
  * player.y = 300;
475
1194
  * game.add(player);
476
1195
  * };
477
1196
  * ```
478
1197
  */
479
- onCreate: (() => void) | null;
1198
+ get onCreate(): (() => void) | null;
1199
+ set onCreate(callback: (() => void) | null);
480
1200
  /**
481
- * Callback invoked once per frame after physics and timer updates.
1201
+ * Legacy per-frame callback invoked after physics and timer updates.
1202
+ *
1203
+ * This is used only when no active {@link IScene} is set.
482
1204
  *
483
1205
  * @param dt - Delta time in **seconds** since the last frame.
484
1206
  * Use this for velocity-based movement: `sprite.x += speed * dt`.
@@ -493,21 +1215,31 @@ export declare class Game {
493
1215
  * ```
494
1216
  */
495
1217
  onUpdate: ((dt: number) => void) | null;
1218
+ /**
1219
+ * Currently active scene object, if any.
1220
+ */
1221
+ get currentScene(): IScene | null;
1222
+ /**
1223
+ * Returns `true` while a full-screen scene transition is playing.
1224
+ */
1225
+ get isTransitioning(): boolean;
496
1226
  /**
497
1227
  * Creates a new MinimoJS game instance.
498
1228
  *
499
1229
  * The engine creates its own `<canvas>`, sets its dimensions, and appends it
500
1230
  * to `document.body`. The canvas is automatically centered and responsively
501
1231
  * scaled to use the maximum available viewport space while preserving aspect ratio.
1232
+ * For new mobile-first Minimo Games, prefer a portrait canvas such as `720x1280`.
502
1233
  *
503
- * @param width - Canvas width in pixels. Default: `800`.
504
- * @param height - Canvas height in pixels. Default: `600`.
1234
+ * @param width - Canvas width in pixels. Default: `720`.
1235
+ * @param height - Canvas height in pixels. Default: `1280`.
505
1236
  *
506
1237
  * @throws Error if a 2D context cannot be obtained.
507
1238
  *
508
1239
  * @example
509
1240
  * ```ts
510
- * const game = new Game(800, 600);
1241
+ * const game = new Game(720, 1280);
1242
+ * game.physics = true;
511
1243
  * ```
512
1244
  */
513
1245
  constructor(width?: number, height?: number);
@@ -535,14 +1267,16 @@ export declare class Game {
535
1267
  get pointerY(): number;
536
1268
  /**
537
1269
  * Registers a {@link Sprite} (or subclass instance) with the engine.
538
- * After calling `add`, the sprite is rendered and receives physics updates
539
- * every frame until {@link Game.destroySprite} or {@link Game.reset} is called.
1270
+ *
1271
+ * After calling `add`, the sprite is rendered and, if dynamic
1272
+ * (`isStatic = false`), receives built-in velocity/gravity integration every
1273
+ * frame until {@link Game.destroySprite} or {@link Game.reset} is called.
540
1274
  *
541
1275
  * **Ownership:** The game instance takes ownership of the sprite from this
542
1276
  * point forward. It will appear in {@link Game.getSprites} on the same frame.
543
1277
  *
544
1278
  * **Subclasses:** Any class that extends {@link Sprite} can be passed here.
545
- * The engine stores and processes it as a `Sprite`; your custom properties
1279
+ * The engine stores and processes it as a live sprite; your custom properties
546
1280
  * are preserved on the instance.
547
1281
  *
548
1282
  * @param sprite - A {@link Sprite} instance (or subclass) to add.
@@ -551,22 +1285,36 @@ export declare class Game {
551
1285
  * @example
552
1286
  * ```ts
553
1287
  * // Plain sprite
554
- * const coin = new Sprite("🪙");
555
- * coin.x = 300; coin.y = 200; coin.size = 32;
1288
+ * const coin = new EmojiSprite("🪙", 300, 200, 32);
556
1289
  * game.add(coin);
557
1290
  *
558
1291
  * // Custom subclass
559
- * class Enemy extends Sprite {
1292
+ * class Enemy extends EmojiSprite {
560
1293
  * speed = 150;
561
1294
  * constructor(x: number, y: number) {
562
- * super("👾");
563
- * this.x = x; this.y = y; this.size = 40;
1295
+ * super("👾", x, y, 40);
1296
+ * }
564
1297
  * }
565
- * }
566
1298
  * const enemy = game.add(new Enemy(600, 100));
567
1299
  * ```
568
1300
  */
569
- add(sprite: Sprite): Sprite;
1301
+ add<T extends Sprite>(sprite: T): T;
1302
+ /**
1303
+ * Registers a {@link BackgroundLayer} with the engine.
1304
+ *
1305
+ * Background layers are rendered behind all sprites and are not affected by
1306
+ * physics, collisions, or sprite queries.
1307
+ *
1308
+ * @param layer - The background layer to add.
1309
+ * @returns The same background layer instance.
1310
+ *
1311
+ * @example
1312
+ * ```ts
1313
+ * const sky = game.addBackground(new BackgroundLayer("sky"));
1314
+ * sky.fit = "cover";
1315
+ * ```
1316
+ */
1317
+ addBackground(layer: BackgroundLayer): BackgroundLayer;
570
1318
  /**
571
1319
  * Removes a sprite from the engine, stopping its rendering and physics updates.
572
1320
  * Also cancels any running animations targeting this sprite.
@@ -585,6 +1333,14 @@ export declare class Game {
585
1333
  * ```
586
1334
  */
587
1335
  destroySprite(sprite: Sprite): void;
1336
+ /**
1337
+ * Removes a background layer from the engine.
1338
+ *
1339
+ * If the layer is not currently registered, this is a safe no-op.
1340
+ *
1341
+ * @param layer - The background layer to remove.
1342
+ */
1343
+ destroyBackgroundLayer(layer: BackgroundLayer): void;
588
1344
  /**
589
1345
  * Returns a **read-only snapshot** of all currently active sprites.
590
1346
  * The array is a shallow copy — mutating it has no effect on the engine.
@@ -601,15 +1357,76 @@ export declare class Game {
601
1357
  * ```
602
1358
  */
603
1359
  getSprites(): readonly Sprite[];
1360
+ /**
1361
+ * Returns a read-only snapshot of all active background layers.
1362
+ *
1363
+ * Background layers are returned in creation order within each layer value.
1364
+ */
1365
+ getBackgroundLayers(): readonly BackgroundLayer[];
1366
+ /**
1367
+ * Queues an image to be loaded during {@link Game.onPreload}.
1368
+ *
1369
+ * This method registers the asset only. The actual network/image fetch begins
1370
+ * after `onPreload` returns and is managed automatically by the engine.
1371
+ *
1372
+ * `loadImage` may only be called from within {@link Game.onPreload}.
1373
+ *
1374
+ * @param key - Stable key used later by {@link BackgroundLayer.imageKey} or {@link Game.getImage}.
1375
+ * @param src - Image URL or relative path.
1376
+ *
1377
+ * @example
1378
+ * ```ts
1379
+ * game.onPreload = () => {
1380
+ * game.loadImage("sky", "assets/sky.png");
1381
+ * };
1382
+ * ```
1383
+ */
1384
+ loadImage(key: string, src: string): void;
1385
+ /**
1386
+ * Creates a dynamic texture immediately and registers it under a stable key.
1387
+ *
1388
+ * Unlike {@link Game.loadImage}, this does not require {@link Game.onPreload}.
1389
+ * The `painter` callback receives a fresh offscreen canvas and should draw the
1390
+ * full texture contents into it.
1391
+ *
1392
+ * The resulting texture can be used anywhere a normal image key is accepted,
1393
+ * including {@link ImageSprite}, background layers, and optional modules.
1394
+ *
1395
+ * @param key - Stable texture key used later by sprites and render helpers.
1396
+ * @param width - Texture width in pixels. Minimum `1`.
1397
+ * @param height - Texture height in pixels. Minimum `1`.
1398
+ * @param painter - Function that paints into the offscreen texture canvas.
1399
+ *
1400
+ * @example
1401
+ * ```ts
1402
+ * game.createTexture("checkpoint", 128, 64, (ctx, canvas) => {
1403
+ * ctx.fillStyle = "#101820";
1404
+ * ctx.fillRect(0, 0, canvas.width, canvas.height);
1405
+ * ctx.fillStyle = "#ffd54f";
1406
+ * ctx.fillRect(8, 8, canvas.width - 16, canvas.height - 16);
1407
+ * });
1408
+ * ```
1409
+ */
1410
+ createTexture(key: string, width: number, height: number, painter: (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => void): HTMLCanvasElement;
1411
+ /**
1412
+ * Returns a loaded image or created texture previously registered with
1413
+ * {@link Game.loadImage} or {@link Game.createTexture}, or `undefined` if it
1414
+ * is not available.
1415
+ */
1416
+ getImage(key: string): HTMLImageElement | HTMLCanvasElement | undefined;
1417
+ /**
1418
+ * Returns `true` if a texture key is available.
1419
+ */
1420
+ hasImage(key: string): boolean;
604
1421
  /**
605
1422
  * Tests whether two sprites overlap using **Axis-Aligned Bounding Box (AABB)**
606
1423
  * collision detection.
607
1424
  *
608
1425
  * Each sprite's bounding box is a square centered at `(x, y)` with side
609
- * length `size`. Rotation is **ignored** — the box is always axis-aligned.
1426
+ * length `displaySize`. Rotation is **ignored** — the box is always axis-aligned.
610
1427
  *
611
1428
  * **No collision resolution is performed.** This method is detection-only.
612
- * If you need bounce or push-apart behavior, implement it in `onUpdate`.
1429
+ * Use {@link Game.collide} for the engine's basic explicit push-out helper.
613
1430
  *
614
1431
  * @param a - First sprite.
615
1432
  * @param b - Second sprite.
@@ -633,7 +1450,7 @@ export declare class Game {
633
1450
  *
634
1451
  * @param listA - First group of sprites.
635
1452
  * @param listB - Second group of sprites. May share sprites with `listA`.
636
- * @returns A `[Sprite, Sprite]` tuple of the first overlapping pair,
1453
+ * @returns A `[EmojiSprite, EmojiSprite]` tuple of the first overlapping pair,
637
1454
  * or `null` if no pair overlaps.
638
1455
  *
639
1456
  * @example
@@ -647,6 +1464,57 @@ export declare class Game {
647
1464
  * ```
648
1465
  */
649
1466
  overlapAny(listA: Sprite[], listB: Sprite[]): [Sprite, Sprite] | null;
1467
+ /**
1468
+ * Tests and resolves a basic AABB collision between two sprites.
1469
+ *
1470
+ * This helper is intended for simple platformer-style collision response.
1471
+ * It uses the same axis-aligned square bounds as {@link Game.overlap}, but
1472
+ * additionally pushes one sprite out of the collision and zeroes velocity on
1473
+ * the resolved axis.
1474
+ *
1475
+ * **Resolution rules:**
1476
+ * - If the first sprite is dynamic (`isStatic = false`), the first sprite is resolved.
1477
+ * - Otherwise, if the second sprite is dynamic, the second sprite is resolved.
1478
+ * - If both sprites are static, collision is reported but no movement occurs.
1479
+ *
1480
+ * The returned flags are always reported relative to the **first** sprite.
1481
+ *
1482
+ * @param a - First sprite. Usually the moving actor (for example, the player).
1483
+ * @param b - Second sprite. Usually a static obstacle or platform.
1484
+ * @returns Collision details, or `null` if the sprites do not overlap.
1485
+ * @throws Error if the game's physics helpers are not enabled.
1486
+ *
1487
+ * @example
1488
+ * ```ts
1489
+ * const hit = game.collide(player, floorTile);
1490
+ * if (hit?.grounded) {
1491
+ * canJump = true;
1492
+ * }
1493
+ * ```
1494
+ */
1495
+ collide(a: Sprite, b: Sprite): CollisionInfo | null;
1496
+ /**
1497
+ * Tests and resolves the first collision found between two groups of sprites.
1498
+ *
1499
+ * This behaves like {@link Game.overlapAny}, but uses the explicit collision
1500
+ * rules from {@link Game.collide} and returns the collision details as the
1501
+ * third tuple item.
1502
+ *
1503
+ * @param listA - First group of sprites.
1504
+ * @param listB - Second group of sprites.
1505
+ * @returns A `[EmojiSprite, EmojiSprite, CollisionInfo]` tuple for the first collision found,
1506
+ * or `null` if no pair overlaps.
1507
+ * @throws Error if the game's physics helpers are not enabled.
1508
+ *
1509
+ * @example
1510
+ * ```ts
1511
+ * const hit = game.collideAny([player], floorTiles);
1512
+ * if (hit?.[2].grounded) {
1513
+ * canJump = true;
1514
+ * }
1515
+ * ```
1516
+ */
1517
+ collideAny(listA: Sprite[], listB: Sprite[]): [Sprite, Sprite, CollisionInfo] | null;
650
1518
  /**
651
1519
  * Returns `true` while the specified key is held down (every frame it is held).
652
1520
  * Use this for continuous actions like movement.
@@ -715,50 +1583,50 @@ export declare class Game {
715
1583
  * Returns `true` while any active pointer is held down over the target sprite.
716
1584
  * Works with both mouse input and multiple simultaneous touches.
717
1585
  *
718
- * Pointer hit testing uses a circular area centered on the sprite. The radius
719
- * is `sprite.size * radiusScale`. World-space sprites are tested against the
720
- * current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
721
- * screen space.
1586
+ * Pointer hit testing uses the sprite's exact displayed rectangular bounds.
1587
+ * World-space sprites are tested against the current camera scroll. HUD
1588
+ * sprites with `ignoreScroll = true` are tested in screen space. For
1589
+ * anchored {@link TextSprite} instances, the engine first resolves
1590
+ * `anchorX` / `anchorY` into the final rendered center, then tests against
1591
+ * that rectangle.
722
1592
  *
723
1593
  * Use this for continuous virtual buttons such as touch movement controls.
724
1594
  *
725
1595
  * @param sprite - Target sprite to test. If `null` / `undefined`, returns `false`.
726
- * @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
727
- * Default: `0.5`.
728
1596
  * @returns `true` if any currently held pointer overlaps the sprite hit area.
729
1597
  *
730
1598
  * @example
731
1599
  * ```ts
732
- * if (game.isPointerDownOverSprite(leftButton, 0.72)) {
1600
+ * if (game.isPointerDownOverSprite(leftButton)) {
733
1601
  * player.x -= 200 * dt;
734
1602
  * }
735
1603
  * ```
736
1604
  */
737
- isPointerDownOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): boolean;
1605
+ isPointerDownOverSprite(sprite: Sprite | null | undefined): boolean;
738
1606
  /**
739
1607
  * Returns `true` only on the frame any pointer first pressed over the target
740
1608
  * sprite. Works with both mouse input and multiple simultaneous touches.
741
1609
  *
742
- * Pointer hit testing uses a circular area centered on the sprite. The radius
743
- * is `sprite.size * radiusScale`. World-space sprites are tested against the
744
- * current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
745
- * screen space.
1610
+ * Pointer hit testing uses the sprite's exact displayed rectangular bounds.
1611
+ * World-space sprites are tested against the current camera scroll. HUD
1612
+ * sprites with `ignoreScroll = true` are tested in screen space. For
1613
+ * anchored {@link TextSprite} instances, the engine first resolves
1614
+ * `anchorX` / `anchorY` into the final rendered center, then tests against
1615
+ * that rectangle.
746
1616
  *
747
1617
  * Use this for one-shot virtual buttons such as menu taps.
748
1618
  *
749
1619
  * @param sprite - Target sprite to test. If `null` / `undefined`, returns `false`.
750
- * @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
751
- * Default: `0.5`.
752
1620
  * @returns `true` if any pointer began pressing this frame over the sprite hit area.
753
1621
  *
754
1622
  * @example
755
1623
  * ```ts
756
- * if (game.isPointerPressedOverSprite(startButton, 0.8)) {
1624
+ * if (game.isPointerPressedOverSprite(startButton)) {
757
1625
  * startGame();
758
1626
  * }
759
1627
  * ```
760
1628
  */
761
- isPointerPressedOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): boolean;
1629
+ isPointerPressedOverSprite(sprite: Sprite | null | undefined): boolean;
762
1630
  /**
763
1631
  * Returns a read-only snapshot of all currently active pointers.
764
1632
  *
@@ -776,7 +1644,7 @@ export declare class Game {
776
1644
  * const pointers = game.getPointers();
777
1645
  * if (pointers.length > 0) {
778
1646
  * const first = pointers[0];
779
- * game.text(`Pointer: ${first.x}, ${first.y}`, 10, 10);
1647
+ * game.drawText(`Pointer: ${first.x}, ${first.y}`, 10, 10, 14);
780
1648
  * }
781
1649
  * ```
782
1650
  */
@@ -785,29 +1653,29 @@ export declare class Game {
785
1653
  * Returns a read-only snapshot of all active pointers currently overlapping
786
1654
  * the target sprite.
787
1655
  *
788
- * Pointer hit testing uses a circular area centered on the sprite. The radius
789
- * is `sprite.size * radiusScale`. World-space sprites are tested against the
790
- * current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
791
- * screen space.
1656
+ * Pointer hit testing uses the sprite's exact displayed rectangular bounds.
1657
+ * World-space sprites are tested against the current camera scroll. HUD
1658
+ * sprites with `ignoreScroll = true` are tested in screen space. For
1659
+ * anchored {@link TextSprite} instances, the engine first resolves
1660
+ * `anchorX` / `anchorY` into the final rendered center, then tests against
1661
+ * that rectangle.
792
1662
  *
793
1663
  * Use this when you need more than a boolean result, such as reading the exact
794
1664
  * pointer position over a virtual joystick or draggable control.
795
1665
  *
796
1666
  * @param sprite - Target sprite to test. If `null` / `undefined`, returns an empty array.
797
- * @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
798
- * Default: `0.5`.
799
1667
  * @returns A read-only array of active pointer snapshots currently over the sprite.
800
1668
  *
801
1669
  * @example
802
1670
  * ```ts
803
- * const touches = game.getPointersOverSprite(joystickBase, 1.0);
1671
+ * const touches = game.getPointersOverSprite(joystickBase);
804
1672
  * if (touches.length > 0) {
805
1673
  * const p = touches[0];
806
1674
  * const dx = p.x - joystickBase.x;
807
1675
  * }
808
1676
  * ```
809
1677
  */
810
- getPointersOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): readonly PointerInfo[];
1678
+ getPointersOverSprite(sprite: Sprite | null | undefined): readonly PointerInfo[];
811
1679
  /**
812
1680
  * Returns `true` when the current device appears to be mobile/touch-first.
813
1681
  *
@@ -837,6 +1705,9 @@ export declare class Game {
837
1705
  * gesture (click, keypress) before audio can play — always call `sound()` in
838
1706
  * response to user input or a game event triggered by input.
839
1707
  *
1708
+ * If you need a melody/sequence, use {@link Game.soundSequence} instead of
1709
+ * multiple `sound()` calls in the same frame.
1710
+ *
840
1711
  * The tone fades out exponentially over `durationMs` to avoid clicks.
841
1712
  *
842
1713
  * @param freq - Frequency in Hz. Middle C = 261.6. Typical range: 100–4000 Hz.
@@ -850,6 +1721,20 @@ export declare class Game {
850
1721
  * ```
851
1722
  */
852
1723
  sound(freq: number, durationMs: number): void;
1724
+ /**
1725
+ * Plays multiple square-wave tones in strict sequence.
1726
+ *
1727
+ * Each tuple is `[frequencyHz, durationMs]`. The next tone starts when the
1728
+ * previous one ends.
1729
+ *
1730
+ * @param tones - Tone tuples in playback order.
1731
+ *
1732
+ * @example
1733
+ * ```ts
1734
+ * game.soundSequence([660, 80], [820, 90], [980, 110], [1180, 150]);
1735
+ * ```
1736
+ */
1737
+ soundSequence(...tones: Array<readonly [number, number]>): void;
853
1738
  /**
854
1739
  * Animates a sprite's {@link Sprite.alpha} from its current value to `to`
855
1740
  * over `durationMs` milliseconds using **linear interpolation**.
@@ -898,6 +1783,289 @@ export declare class Game {
898
1783
  * ```
899
1784
  */
900
1785
  animateRotation(sprite: Sprite, to: number, durationMs: number, onComplete?: () => void): void;
1786
+ /**
1787
+ * Animates a sprite's visual deformation using non-uniform scaling around a
1788
+ * normalized pivot point.
1789
+ *
1790
+ * This affects rendering only. Physics and collision bounds continue to use
1791
+ * {@link EmojiSprite.displaySize} and ignore the deform result.
1792
+ *
1793
+ * `pivotX` / `pivotY` are normalized anchors in the sprite's square display box:
1794
+ * - `0` = left/top
1795
+ * - `0.5` = center
1796
+ * - `1` = right/bottom
1797
+ *
1798
+ * For example, `pivotY = 1` keeps the bottom visually pinned while squashing
1799
+ * or stretching, which is useful for floor-contact animation.
1800
+ *
1801
+ * Like other MinimoJS animations, this animates from the sprite's current
1802
+ * deform state to the requested target and leaves the final result applied.
1803
+ *
1804
+ * @param sprite - The sprite to deform.
1805
+ * @param toScaleX - Target horizontal deform scale. `1` = unchanged.
1806
+ * @param toScaleY - Target vertical deform scale. `1` = unchanged.
1807
+ * @param pivotX - Horizontal pivot in normalized `[0, 1]` sprite space.
1808
+ * @param pivotY - Vertical pivot in normalized `[0, 1]` sprite space.
1809
+ * @param durationMs - Duration of the animation in **milliseconds**.
1810
+ * @param onComplete - Optional callback invoked when the animation finishes.
1811
+ *
1812
+ * @example
1813
+ * ```ts
1814
+ * game.animateDeform(player, 1.18, 0.85, 0.5, 1, 90, () => {
1815
+ * game.animateDeform(player, 1, 1, 0.5, 1, 120);
1816
+ * });
1817
+ * ```
1818
+ */
1819
+ animateDeform(sprite: Sprite, toScaleX: number, toScaleY: number, pivotX: number, pivotY: number, durationMs: number, onComplete?: () => void): void;
1820
+ /**
1821
+ * Convenience helper that squashes a sprite around its horizontal center.
1822
+ *
1823
+ * This is equivalent to animating toward a wider, shorter visual pose using
1824
+ * {@link Game.animateDeform}. `pivotY = 1` is the common floor-anchored case.
1825
+ *
1826
+ * @param sprite - The sprite to squash.
1827
+ * @param pivotY - Vertical pivot in normalized `[0, 1]` sprite space.
1828
+ * @param durationMs - Duration of the animation in **milliseconds**.
1829
+ * @param onComplete - Optional callback invoked when the animation finishes.
1830
+ */
1831
+ animateSquash(sprite: Sprite, pivotY: number, durationMs: number, onComplete?: () => void): void;
1832
+ /**
1833
+ * Convenience helper that stretches a sprite around its horizontal center.
1834
+ *
1835
+ * This is equivalent to animating toward a narrower, taller visual pose using
1836
+ * {@link Game.animateDeform}. `pivotY = 1` is the common floor-anchored case.
1837
+ *
1838
+ * @param sprite - The sprite to stretch.
1839
+ * @param pivotY - Vertical pivot in normalized `[0, 1]` sprite space.
1840
+ * @param durationMs - Duration of the animation in **milliseconds**.
1841
+ * @param onComplete - Optional callback invoked when the animation finishes.
1842
+ */
1843
+ animateStretch(sprite: Sprite, pivotY: number, durationMs: number, onComplete?: () => void): void;
1844
+ /**
1845
+ * Convenience helper that applies a centered uniform scale pulse and then
1846
+ * returns the sprite to its neutral shape.
1847
+ *
1848
+ * Internally this runs two deform animations back-to-back using
1849
+ * {@link Game.animateDeform}.
1850
+ *
1851
+ * @param sprite - The sprite to pulse.
1852
+ * @param scale - Peak uniform scale reached midway through the pulse. `1` leaves the sprite unchanged.
1853
+ * @param durationMs - Total pulse duration in **milliseconds**.
1854
+ * @param onComplete - Optional callback invoked after the sprite returns to neutral.
1855
+ *
1856
+ * @example
1857
+ * ```ts
1858
+ * game.animatePulse(coin, 1.25, 220);
1859
+ * ```
1860
+ */
1861
+ animatePulse(sprite: Sprite, scale: number, durationMs: number, onComplete?: () => void): void;
1862
+ /**
1863
+ * Applies a decaying screen-space shake to a sprite without changing its
1864
+ * logical position.
1865
+ *
1866
+ * This is a render-only effect. Physics, collisions, and the sprite's
1867
+ * `x` / `y` values are not modified.
1868
+ *
1869
+ * @param sprite - The sprite to shake.
1870
+ * @param intensity - Maximum shake offset in pixels.
1871
+ * @param durationMs - Duration of the shake in **milliseconds**.
1872
+ * @param onComplete - Optional callback invoked when the shake ends.
1873
+ *
1874
+ * @example
1875
+ * ```ts
1876
+ * game.animateShake(player, 18, 300);
1877
+ * ```
1878
+ */
1879
+ animateShake(sprite: Sprite, intensity: number, durationMs: number, onComplete?: () => void): void;
1880
+ /**
1881
+ * Moves a sprite upward and back down to its starting position along a simple
1882
+ * parabolic arc.
1883
+ *
1884
+ * This animation writes directly to {@link Sprite.x} / {@link Sprite.y} while
1885
+ * it is active.
1886
+ *
1887
+ * @param sprite - The sprite to bounce.
1888
+ * @param height - Peak bounce height in pixels.
1889
+ * @param durationMs - Total bounce duration in **milliseconds**.
1890
+ * @param onComplete - Optional callback invoked when the bounce ends.
1891
+ *
1892
+ * @example
1893
+ * ```ts
1894
+ * game.animateBounce(ball, 120, 600);
1895
+ * ```
1896
+ */
1897
+ animateBounce(sprite: Sprite, height: number, durationMs: number, onComplete?: () => void): void;
1898
+ /**
1899
+ * Moves a sprite upward and back down with a smooth sine-shaped float motion.
1900
+ *
1901
+ * This animation writes directly to {@link Sprite.x} / {@link Sprite.y} while
1902
+ * it is active.
1903
+ *
1904
+ * @param sprite - The sprite to float.
1905
+ * @param distance - Maximum upward travel in pixels.
1906
+ * @param durationMs - Total float duration in **milliseconds**.
1907
+ * @param onComplete - Optional callback invoked when the float ends.
1908
+ *
1909
+ * @example
1910
+ * ```ts
1911
+ * game.animateFloat(balloon, 90, 1200);
1912
+ * ```
1913
+ */
1914
+ animateFloat(sprite: Sprite, distance: number, durationMs: number, onComplete?: () => void): void;
1915
+ /**
1916
+ * Toggles a sprite's rendered opacity on and off a fixed number of times.
1917
+ *
1918
+ * This is a render-only effect. The sprite remains present in the world and
1919
+ * keeps receiving physics updates while blinking.
1920
+ *
1921
+ * @param sprite - The sprite to blink.
1922
+ * @param times - Number of visible/invisible toggle cycles.
1923
+ * @param durationMs - Total blink duration in **milliseconds**.
1924
+ * @param onComplete - Optional callback invoked when blinking ends.
1925
+ *
1926
+ * @example
1927
+ * ```ts
1928
+ * game.animateBlink(player, 4, 700);
1929
+ * ```
1930
+ */
1931
+ animateBlink(sprite: Sprite, times: number, durationMs: number, onComplete?: () => void): void;
1932
+ /**
1933
+ * Applies a rapid irregular alpha variation to create a damaged, unstable, or
1934
+ * ghost-like flicker.
1935
+ *
1936
+ * This is a render-only effect and does not change the sprite's logical
1937
+ * position or collision state.
1938
+ *
1939
+ * @param sprite - The sprite to flicker.
1940
+ * @param durationMs - Total flicker duration in **milliseconds**.
1941
+ * @param onComplete - Optional callback invoked when the flicker ends.
1942
+ *
1943
+ * @example
1944
+ * ```ts
1945
+ * game.animateFlicker(ghost, 900);
1946
+ * ```
1947
+ */
1948
+ animateFlicker(sprite: Sprite, durationMs: number, onComplete?: () => void): void;
1949
+ /**
1950
+ * Moves a sprite from its current position to a target position along a
1951
+ * parabolic arc.
1952
+ *
1953
+ * This animation writes directly to {@link Sprite.x} / {@link Sprite.y} while
1954
+ * it is active.
1955
+ *
1956
+ * @param sprite - The sprite to move.
1957
+ * @param toX - Destination X position in world space.
1958
+ * @param toY - Destination Y position in world space.
1959
+ * @param arcHeight - Maximum height of the arc above the linear path, in pixels.
1960
+ * @param durationMs - Total arc duration in **milliseconds**.
1961
+ * @param onComplete - Optional callback invoked when the arc ends.
1962
+ *
1963
+ * @example
1964
+ * ```ts
1965
+ * game.animateArc(coin, player.x, player.y, 80, 420);
1966
+ * ```
1967
+ */
1968
+ animateArc(sprite: Sprite, toX: number, toY: number, arcHeight: number, durationMs: number, onComplete?: () => void): void;
1969
+ /**
1970
+ * Emits temporary afterimages from a sprite to create a motion trail.
1971
+ *
1972
+ * This is a render-only effect. Trail ghosts are not real sprites and do not
1973
+ * appear in {@link Game.getSprites}, collide, or receive physics.
1974
+ *
1975
+ * @param sprite - The sprite to sample for trail ghosts.
1976
+ * @param options - Optional trail tuning values.
1977
+ * @param onComplete - Optional callback invoked when the emitter stops spawning ghosts.
1978
+ *
1979
+ * @example
1980
+ * ```ts
1981
+ * game.animateTrail(player, { durationMs: 300, spacingMs: 30, fadeMs: 180 });
1982
+ * ```
1983
+ */
1984
+ animateTrail(sprite: Sprite, options?: TrailOptions, onComplete?: () => void): void;
1985
+ /**
1986
+ * Breaks a sprite into visual pieces that burst outward from its current
1987
+ * rendered appearance.
1988
+ *
1989
+ * This is a render-only effect. The spawned pieces are not real sprites and do
1990
+ * not appear in {@link Game.getSprites}, receive physics, or collide.
1991
+ *
1992
+ * By default, the original sprite is destroyed immediately after the explosion
1993
+ * effect is spawned. Set `destroySprite: false` to hide the sprite during the
1994
+ * effect and restore it when the explosion ends.
1995
+ *
1996
+ * @param sprite - The sprite to explode.
1997
+ * @param options - Optional explosion tuning values.
1998
+ * @param onComplete - Optional callback invoked when the effect finishes.
1999
+ *
2000
+ * @example
2001
+ * ```ts
2002
+ * game.animateExplode(enemy, { rows: 4, cols: 4, durationMs: 650, speed: 320 });
2003
+ * ```
2004
+ */
2005
+ animateExplode(sprite: Sprite, options?: ExplodeOptions, onComplete?: () => void): void;
2006
+ /**
2007
+ * Spawns visual pieces around a sprite and converges them back into its
2008
+ * current rendered appearance.
2009
+ *
2010
+ * This is a render-only effect. The spawned pieces are not real sprites and
2011
+ * do not appear in {@link Game.getSprites}, receive physics, or collide.
2012
+ *
2013
+ * The original sprite is hidden while the assembly plays and is restored when
2014
+ * the effect finishes.
2015
+ *
2016
+ * @param sprite - The sprite to assemble.
2017
+ * @param options - Optional assembly tuning values.
2018
+ * @param onComplete - Optional callback invoked when the effect finishes.
2019
+ */
2020
+ animateAssemble(sprite: Sprite, options?: AssembleOptions, onComplete?: () => void): void;
2021
+ /**
2022
+ * Breaks a sprite apart locally inside its own bounds using a directional
2023
+ * dissolve sweep.
2024
+ *
2025
+ * This is a render-only effect. The spawned pieces are not real sprites and
2026
+ * do not appear in {@link Game.getSprites}, receive physics, or collide.
2027
+ *
2028
+ * By default, the original sprite is destroyed immediately after the effect
2029
+ * is spawned. Set `destroySprite: false` to leave the sprite hidden instead.
2030
+ *
2031
+ * @param sprite - The sprite to disintegrate.
2032
+ * @param options - Optional disintegration tuning values.
2033
+ * @param onComplete - Optional callback invoked when the effect finishes.
2034
+ */
2035
+ animateDisintegrate(sprite: Sprite, options?: DisintegrateOptions, onComplete?: () => void): void;
2036
+ /**
2037
+ * Reconstructs a sprite locally inside its own bounds using a directional
2038
+ * integration sweep.
2039
+ *
2040
+ * This is a render-only effect. The spawned pieces are not real sprites and
2041
+ * do not appear in {@link Game.getSprites}, receive physics, or collide.
2042
+ *
2043
+ * The original sprite is hidden while the integration plays and is restored
2044
+ * when the effect finishes.
2045
+ *
2046
+ * @param sprite - The sprite to integrate.
2047
+ * @param options - Optional integration tuning values.
2048
+ * @param onComplete - Optional callback invoked when the effect finishes.
2049
+ */
2050
+ animateIntegrate(sprite: Sprite, options?: IntegrateOptions, onComplete?: () => void): void;
2051
+ /**
2052
+ * Reveals or covers a sprite with a directional wipe mask.
2053
+ *
2054
+ * This is a render-only effect that captures the sprite's current appearance
2055
+ * and animates a clipping region over that snapshot.
2056
+ *
2057
+ * For `mode: "reveal"`, the original sprite is hidden during the wipe and is
2058
+ * restored when the effect finishes.
2059
+ *
2060
+ * For `mode: "cover"`, the effect hides the sprite over time. If
2061
+ * `destroySprite` is omitted or `true`, the sprite is destroyed immediately
2062
+ * after the wipe starts and only the wipe snapshot remains visible.
2063
+ *
2064
+ * @param sprite - The sprite to wipe.
2065
+ * @param options - Optional wipe tuning values.
2066
+ * @param onComplete - Optional callback invoked when the effect finishes.
2067
+ */
2068
+ animateWipe(sprite: Sprite, options?: WipeOptions, onComplete?: () => void): void;
901
2069
  /**
902
2070
  * Schedules a callback to fire after `delayMs` milliseconds, driven by the
903
2071
  * rAF loop (not `setTimeout`). Timers accumulate elapsed time each frame and
@@ -944,18 +2112,32 @@ export declare class Game {
944
2112
  */
945
2113
  clearTimer(id: number): void;
946
2114
  /**
947
- * Draws text on screen as a **screen-space overlay** this frame.
2115
+ * Draws text on screen as a **simple screen-space overlay** this frame.
948
2116
  *
949
2117
  * **Overlay behavior:** Text is drawn in canvas/screen space — it ignores
950
2118
  * `scrollX` / `scrollY`. Position `(0, 0)` is always the top-left of the canvas.
951
- * Use this for HUD elements: score, lives, timer, debug info.
2119
+ * Use this for lightweight HUD elements: score, lives, timer, debug info.
952
2120
  *
953
2121
  * **Per-frame:** `drawText` must be called every frame to keep text visible.
954
2122
  * The text overlay list is cleared after each render. Call this inside `onUpdate`.
955
2123
  *
956
2124
  * **Layer:** Text is always drawn on top of all sprites.
957
- * **Font:** Text always uses a fixed `monospace` font family.
958
- * Font family cannot be customized in MinimoJS v1.
2125
+ * **Font:** Text uses `"Press Start 2P", monospace` by default.
2126
+ * You MUST declare that font yourself in `index.html` (for example via Google
2127
+ * Fonts) before calling {@link Game.start}. MinimoJS does NOT inject or
2128
+ * download external fonts for you.
2129
+ *
2130
+ * `Game.start()` waits for the default font and for any extra families you
2131
+ * registered with {@link Game.requireFont}. If a font is unavailable, the
2132
+ * browser falls back to `monospace`.
2133
+ *
2134
+ * Prefer {@link TextSprite} for UI buttons or labels that need persistent
2135
+ * bounds, hit testing, fixed sizes, backgrounds, borders, or text stroke.
2136
+ * `drawText()` is the lightweight overlay API, not the primary UI API.
2137
+ *
2138
+ * For controls that are only a single emoji, prefer {@link EmojiSprite} over
2139
+ * {@link TextSprite}. Emoji-only buttons do not benefit from text padding and
2140
+ * usually fit more naturally in the sprite pipeline.
959
2141
  *
960
2142
  * @param text - The string to render. Supports emoji and Unicode.
961
2143
  * @param x - X position in **screen space** (pixels from canvas left edge).
@@ -976,6 +2158,41 @@ export declare class Game {
976
2158
  * ```
977
2159
  */
978
2160
  drawText(text: string, x: number, y: number, fontSize: number, color?: string, centered?: boolean): void;
2161
+ /**
2162
+ * Clears the internal sprite glyph cache.
2163
+ *
2164
+ * MinimoJS prerenders sprite glyphs into offscreen canvases for more stable
2165
+ * emoji rendering and better performance. In long-running sessions, you can
2166
+ * call this to release cached glyph variants and force them to be rebuilt on
2167
+ * the next render.
2168
+ *
2169
+ * This does not change any sprite state. It only clears cached render data.
2170
+ *
2171
+ * @example
2172
+ * ```ts
2173
+ * game.clearSpriteCache();
2174
+ * ```
2175
+ */
2176
+ clearSpriteCache(): void;
2177
+ /**
2178
+ * Registers a web font family that must be loaded before the first frame.
2179
+ *
2180
+ * Call this before {@link Game.start}, typically right after constructing the
2181
+ * game or inside {@link Game.onPreload}. MinimoJS waits for every registered
2182
+ * font via `document.fonts.load(...)` before rendering anything. Host
2183
+ * environments may also pre-register fonts by setting
2184
+ * `globalThis.__MINIMO_WEB_FONT_REQUIREMENTS__` before your game code runs.
2185
+ *
2186
+ * The font itself must still be declared by your page (for example with a
2187
+ * `<link rel="stylesheet">` in `index.html`). MinimoJS only waits for it.
2188
+ *
2189
+ * @example
2190
+ * ```ts
2191
+ * game.requireFont('"Press Start 2P"');
2192
+ * game.requireFont('"Bangers"', { weight: "700" });
2193
+ * ```
2194
+ */
2195
+ requireFont(family: string, options?: FontRequirementOptions): void;
979
2196
  /**
980
2197
  * Returns a pseudo-random floating-point number in the range `[0, 1)`.
981
2198
  * Delegates to `Math.random()`.
@@ -997,9 +2214,12 @@ export declare class Game {
997
2214
  * Performs a full engine state reset to enable scene switching.
998
2215
  *
999
2216
  * **Cleared by reset:**
2217
+ * - All background layers
1000
2218
  * - All sprites (equivalent to calling {@link Game.destroySprite} on every sprite)
1001
2219
  * - All timers (regardless of repeat state)
1002
2220
  * - All running animations
2221
+ * - All active explosion effects
2222
+ * - All active trail effects
1003
2223
  * - All pending text overlays
1004
2224
  * - Scroll position (`scrollX = 0`, `scrollY = 0`)
1005
2225
  * - Per-frame input state (pressed keys, pressed pointer)
@@ -1012,50 +2232,80 @@ export declare class Game {
1012
2232
  * - Canvas dimensions
1013
2233
  * - AudioContext
1014
2234
  *
1015
- * After clearing, `reset()` immediately calls `onCreate()` so your callback
1016
- * can rebuild the new scene synchronously.
2235
+ * After clearing, `reset()` immediately calls the active scene's
2236
+ * `onCreate()` (or legacy {@link Game.onCreate} if no scene is active) so it
2237
+ * can rebuild synchronously.
2238
+ *
2239
+ * @param scene - Optional new scene to make active before rebuilding.
1017
2240
  *
1018
2241
  * @example
1019
2242
  * ```ts
1020
- * // Switch from gameplay to game-over screen
1021
- * function gameOver() {
1022
- * game.reset(); // onCreate() is called here, rebuild inside it
1023
- * }
1024
- *
1025
- * game.onCreate = () => {
1026
- * // Scene init
1027
- * const skull = new Sprite("💀");
1028
- * skull.x = 400; skull.y = 300; skull.size = 96;
1029
- * game.add(skull);
1030
- * game.addTimer(3000, false, () => game.reset()); // auto-restart
1031
- * };
2243
+ * class GameOverScene {
2244
+ * onCreate() {
2245
+ * const skull = new EmojiSprite("💀", 400, 300, 96);
2246
+ * game.add(skull);
2247
+ * game.addTimer(3000, false, () => game.reset());
2248
+ * }
2249
+ * }
2250
+ *
2251
+ * game.reset(new GameOverScene());
1032
2252
  * ```
1033
2253
  */
1034
- reset(): void;
2254
+ reset(scene?: IScene): void;
2255
+ /**
2256
+ * Changes to a new scene using a full-screen transition between frozen scene
2257
+ * snapshots.
2258
+ *
2259
+ * `transitionTo()` does not change the behavior of {@link Game.reset}. It
2260
+ * captures the current scene as a snapshot, rebuilds the target scene
2261
+ * immediately, captures the new scene as another snapshot, and then animates
2262
+ * between those two images for the requested duration.
2263
+ *
2264
+ * While the transition is active, gameplay updates are paused.
2265
+ *
2266
+ * If the game loop is not running yet, this falls back to an immediate
2267
+ * {@link Game.reset}.
2268
+ *
2269
+ * @param scene - Target scene to make active.
2270
+ * @param options - Transition style and optional tuning values.
2271
+ * @param onComplete - Optional callback invoked when the transition finishes.
2272
+ */
2273
+ transitionTo(scene: IScene, options: SceneTransitionOptions, onComplete?: () => void): void;
1035
2274
  /**
1036
2275
  * Starts the `requestAnimationFrame` game loop.
1037
2276
  * Safe to call multiple times — does nothing if already running.
1038
- * If this is the first start (or after a reset), `onCreate()` is called
1039
- * before the first frame.
1040
- *
1041
- * The loop calls `onUpdate` once per frame, then renders all sprites and
1042
- * text overlays. Order per frame:
2277
+ * On the first start, if {@link Game.onPreload} is set, MinimoJS first runs
2278
+ * asset registration, loads queued images, waits for all fonts registered via
2279
+ * {@link Game.requireFont}, and only then, if images were queued, shows a
2280
+ * default loading screen while those image assets are still loading.
2281
+ * After preload completes, the active scene's `onCreate()` is called before
2282
+ * the first frame.
2283
+ *
2284
+ * The loop calls the active scene's `onUpdate(dt)` (or legacy
2285
+ * {@link Game.onUpdate}) once per frame, then renders all sprites and text
2286
+ * overlays. Order per frame:
1043
2287
  * 1. Accumulate timer elapsed time; fire ready callbacks.
1044
2288
  * 2. Advance animations (linear interpolation).
1045
- * 3. Apply gravity to sprite velocities.
1046
- * 4. Apply velocities to sprite positions.
1047
- * 5. Call `onUpdate(dt)`.
1048
- * 6. Render sprites (sorted by layer) with scroll offset.
1049
- * 7. Render text overlays (screen space, on top of sprites).
1050
- * 8. Clear per-frame input state.
2289
+ * 3. Advance active piece and wipe effects.
2290
+ * 4. Apply gravity to sprite velocities.
2291
+ * 5. Apply velocities to sprite positions.
2292
+ * 6. Call `onUpdate(dt)`.
2293
+ * 7. Advance active trail effects.
2294
+ * 8. Render sprites/effects (with scroll offset).
2295
+ * 9. Render text overlays (screen space, on top of sprites).
2296
+ * 10. Clear per-frame input state.
1051
2297
  *
1052
2298
  * @example
1053
2299
  * ```ts
1054
- * game.onUpdate = (dt) => { ... };
1055
- * game.start();
2300
+ * class DemoScene {
2301
+ * onCreate() {}
2302
+ * onUpdate(dt: number) {}
2303
+ * }
2304
+ *
2305
+ * game.start(new DemoScene());
1056
2306
  * ```
1057
2307
  */
1058
- start(): void;
2308
+ start(scene?: IScene): void;
1059
2309
  /**
1060
2310
  * Stops the game loop. The canvas retains its last rendered frame.
1061
2311
  * Call {@link Game.start} to resume.