minimojs 1.0.0-alpha.1

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.
@@ -0,0 +1,941 @@
1
+ /**
2
+ * @module minimojs
3
+ *
4
+ * MinimoJS v1 — Ultra-minimal 2D web game engine for TypeScript.
5
+ *
6
+ * ALL TIME VALUES ARE IN MILLISECONDS.
7
+ * ALL ROTATIONS ARE IN DEGREES.
8
+ * THE ENGINE LOOP USES requestAnimationFrame ONLY.
9
+ */
10
+ /**
11
+ * A 2D game object rendered as an emoji on the canvas.
12
+ *
13
+ * Instantiate directly or extend to create custom sprite types.
14
+ * Register with the engine by passing the instance to {@link Game.add}.
15
+ *
16
+ * **Coordinate system:** center-based world space. `(x, y)` is the center of
17
+ * the sprite. Positive X = right, positive Y = down.
18
+ *
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.
21
+ *
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
+ * ```
31
+ *
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
+ * }
46
+ *
47
+ * const player = new Player(400, 300);
48
+ * game.add(player);
49
+ * ```
50
+ */
51
+ export declare class Sprite {
52
+ /**
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 "🔥", "⭐", "🐢"
56
+ */
57
+ sprite: string;
58
+ /**
59
+ * X position in world space (horizontal center of the sprite), in pixels.
60
+ * Positive X points right. Updated each frame by: `x += vx * dt`.
61
+ * May be set directly to teleport the sprite.
62
+ */
63
+ x: number;
64
+ /**
65
+ * Y position in world space (vertical center of the sprite), in pixels.
66
+ * Positive Y points down. Updated each frame by: `y += vy * dt`.
67
+ * May be set directly to teleport the sprite.
68
+ */
69
+ y: number;
70
+ /**
71
+ * Width and height of the sprite's bounding square, in pixels.
72
+ * Used for both canvas rendering (font size) and AABB collision detection.
73
+ */
74
+ size: number;
75
+ /**
76
+ * Visual rotation of the sprite in degrees.
77
+ * `0` = upright. Positive values rotate clockwise.
78
+ * **Note:** rotation does NOT affect the AABB collision box — it remains axis-aligned.
79
+ */
80
+ rotation: number;
81
+ /**
82
+ * Horizontal visual flip.
83
+ * When `true`, the sprite is mirrored left-right during rendering.
84
+ * This is visual-only and does NOT affect collision detection.
85
+ */
86
+ flipX: boolean;
87
+ /**
88
+ * Vertical visual flip.
89
+ * When `true`, the sprite is mirrored top-bottom during rendering.
90
+ * This is visual-only and does NOT affect collision detection.
91
+ */
92
+ flipY: boolean;
93
+ /**
94
+ * Camera scroll toggle for this sprite.
95
+ * When `false` (default), the sprite is rendered in world space and is affected
96
+ * by {@link Game.scrollX} and {@link Game.scrollY}.
97
+ * When `true`, the sprite is rendered in canvas/screen space and ignores camera
98
+ * scrolling. Useful for HUD-like sprites that should stay fixed on screen.
99
+ */
100
+ ignoreScroll: boolean;
101
+ /**
102
+ * Opacity of the sprite. Must be in range `[0, 1]`.
103
+ * `0` = fully transparent, `1` = fully opaque.
104
+ * Clamped to `[0, 1]` at render time.
105
+ */
106
+ alpha: number;
107
+ /**
108
+ * Controls whether this sprite is drawn each frame.
109
+ * When `false`, the sprite still receives physics updates (gravity, velocity)
110
+ * but is not rendered. Use {@link Game.destroySprite} to fully remove a sprite.
111
+ */
112
+ visible: boolean;
113
+ /**
114
+ * Render layer order. Sprites with higher `layer` values are drawn on top.
115
+ * Sprites on the same layer render in creation order (first created = bottom).
116
+ * Changing this at runtime takes effect on the next frame.
117
+ */
118
+ layer: number;
119
+ /**
120
+ * Horizontal velocity in pixels per second.
121
+ * Applied each frame: `x += vx * dt`.
122
+ * Gravity also modifies this: `vx += gravityX * gravityScale * dt`.
123
+ */
124
+ vx: number;
125
+ /**
126
+ * Vertical velocity in pixels per second.
127
+ * Applied each frame: `y += vy * dt`.
128
+ * Gravity also modifies this: `vy += gravityY * gravityScale * dt`.
129
+ */
130
+ vy: number;
131
+ /**
132
+ * Gravity multiplier for this sprite.
133
+ * Each frame: `vx += game.gravityX * gravityScale * dt`
134
+ * `vy += game.gravityY * gravityScale * dt`.
135
+ * `0` = immune to gravity (default). `1` = full gravity.
136
+ * Values > 1 amplify gravity; negative values invert it.
137
+ */
138
+ gravityScale: number;
139
+ /**
140
+ * Creates a new Sprite with the given emoji and optional position.
141
+ * All other properties use their defaults and can be set after construction.
142
+ *
143
+ * @param sprite - The emoji character to render. Must be a single emoji.
144
+ * Image sprites are NOT supported — emoji only.
145
+ * @example "🔥", "⭐", "🐢", "💣", "👾"
146
+ * @param x - Initial X position in world space (center), in pixels. Default: `0`.
147
+ * @param y - Initial Y position in world space (center), in pixels. Default: `0`.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * const enemy = new Sprite("👾", 200, 100);
152
+ * enemy.size = 40;
153
+ * game.add(enemy);
154
+ * ```
155
+ */
156
+ constructor(sprite: string, x?: number, y?: number);
157
+ }
158
+ /**
159
+ * # MinimoJS v1 — AI Agent Integration Guide
160
+ *
161
+ * `Game` is the single entry point to the entire engine.
162
+ * Every feature — sprites, input, physics, sound, timers, text — is accessed
163
+ * directly on the `game` object. There are no sub-systems or nested namespaces.
164
+ *
165
+ * ---
166
+ *
167
+ * ## Quick Start
168
+ *
169
+ * ```ts
170
+ * import { Game, Sprite } from "minimojs";
171
+ *
172
+ * const game = new Game(800, 600);
173
+ *
174
+ * const player = new Sprite("🐢");
175
+ * player.x = 400;
176
+ * player.y = 500;
177
+ * player.size = 48;
178
+ * game.add(player);
179
+ *
180
+ * game.onUpdate = (dt) => {
181
+ * if (game.isKeyDown("ArrowLeft")) player.vx = -200;
182
+ * else if (game.isKeyDown("ArrowRight")) player.vx = 200;
183
+ * else player.vx = 0;
184
+ *
185
+ * game.drawText(`x: ${Math.round(player.x)}`, 10, 10, 16);
186
+ * };
187
+ *
188
+ * game.start();
189
+ * ```
190
+ *
191
+ * ---
192
+ *
193
+ * ## Engine Philosophy
194
+ *
195
+ * MinimoJS is **flat, deterministic, and ultra-minimal**. It is intentionally
196
+ * designed to be easy for AI agents to reason about. The full API fits in a
197
+ * single file. There are no plugins, no registries, no event buses.
198
+ *
199
+ * ---
200
+ *
201
+ * ## Flat API
202
+ *
203
+ * ALL engine functionality is on the `game` object.
204
+ * Do not look for sub-objects like `game.physics`, `game.input`, etc. They do not exist.
205
+ *
206
+ * ---
207
+ *
208
+ * ## Responsive Canvas
209
+ *
210
+ * The engine auto-centers the canvas on the page and responsively scales it
211
+ * to use as much viewport space as possible while preserving aspect ratio.
212
+ *
213
+ * ---
214
+ *
215
+ * ## Emoji-Only Sprites
216
+ *
217
+ * Every sprite MUST use a single emoji character as its visual representation.
218
+ * PNG, SVG, spritesheet, and image sprites are NOT supported.
219
+ * Use Unicode emoji: `"🔥"`, `"⭐"`, `"💣"`, `"🐢"`, `"👾"`, `"🧱"`, etc.
220
+ * AI agents can also use text-like emojis (regional indicators, symbols, letters)
221
+ * to build fun title art, HUD labels, and expressive in-game text.
222
+ *
223
+ * ---
224
+ *
225
+ * ## Degrees-Only Rotation
226
+ *
227
+ * ALL rotations are in **degrees**. Never use radians.
228
+ * `0` = upright, `90` = 90° clockwise, `-90` = 90° counter-clockwise.
229
+ * This applies to {@link Sprite.rotation} and {@link Game.animateRotation}.
230
+ *
231
+ * ---
232
+ *
233
+ * ## Milliseconds-Only Timing
234
+ *
235
+ * ALL time parameters to engine methods are in **milliseconds (ms)**.
236
+ * This includes: {@link Game.addTimer}, {@link Game.animateAlpha},
237
+ * {@link Game.animateRotation}, and {@link Game.sound}.
238
+ *
239
+ * The `dt` parameter in {@link Game.onUpdate} is an exception — it is in
240
+ * **seconds** for convenient velocity math (`position += velocity * dt`).
241
+ *
242
+ * ---
243
+ *
244
+ * ## rAF-Only Loop
245
+ *
246
+ * The engine loop runs exclusively on `requestAnimationFrame`.
247
+ * There are **no** `setTimeout` or `setInterval` calls anywhere.
248
+ * All timers, animations, and physics are driven by the rAF loop.
249
+ * Delta time is automatically capped at 100ms to prevent spiral-of-death.
250
+ *
251
+ * ---
252
+ *
253
+ * ## Gravity Model
254
+ *
255
+ * Gravity is a constant per-frame acceleration in pixels/second².
256
+ * Set {@link Game.gravityX} and {@link Game.gravityY} to define the direction.
257
+ * For standard downward gravity: `game.gravityY = 980`.
258
+ *
259
+ * Per-sprite effect is controlled by {@link Sprite.gravityScale}:
260
+ * - `gravityScale = 0` (default): sprite is unaffected by gravity.
261
+ * - `gravityScale = 1`: full gravity applied.
262
+ * - `gravityScale = 0.5`: half gravity.
263
+ *
264
+ * Each frame: `vx += gravityX * gravityScale * dt`, same for Y.
265
+ *
266
+ * ---
267
+ *
268
+ * ## Timer System
269
+ *
270
+ * Timers are rAF-driven countdown callbacks — NOT `setTimeout`.
271
+ * They accumulate elapsed time each frame and fire when the delay is reached.
272
+ *
273
+ * - {@link Game.addTimer}`(delayMs, repeat, callback)` — schedule a callback.
274
+ * - {@link Game.clearTimer}`(id)` — cancel a scheduled timer.
275
+ * - ALL timers are cleared on {@link Game.reset}.
276
+ *
277
+ * ```ts
278
+ * // Fire once after 2 seconds
279
+ * game.addTimer(2000, false, () => { player.sprite = "💥"; });
280
+ *
281
+ * // Repeat every 1 second
282
+ * const id = game.addTimer(1000, true, () => { spawnEnemy(); });
283
+ * // Later:
284
+ * game.clearTimer(id);
285
+ * ```
286
+ *
287
+ * ---
288
+ *
289
+ * ## Scene Initialization with `onCreate`
290
+ *
291
+ * MinimoJS has NO scene system. Use {@link Game.onCreate} to build a scene.
292
+ * The engine calls `onCreate`:
293
+ * - Once before the first frame (when {@link Game.start} is called).
294
+ * - Again after each {@link Game.reset}.
295
+ *
296
+ * `reset()` clears:
297
+ * - All sprites
298
+ * - All timers
299
+ * - All running animations
300
+ * - All text overlays
301
+ * - Scroll position (scrollX and scrollY reset to 0)
302
+ * - Per-frame input state (pressed keys/pointer)
303
+ *
304
+ * After clearing, `reset()` calls `onCreate()` so your callback can rebuild the
305
+ * new scene immediately. Simply re-add sprites and re-register timers.
306
+ *
307
+ * ```ts
308
+ * game.onCreate = () => {
309
+ * // scene init: add sprites, setup timers
310
+ * const skull = new Sprite("💀");
311
+ * skull.x = 400; skull.y = 300; skull.size = 96;
312
+ * game.add(skull);
313
+ * };
314
+ *
315
+ * game.onUpdate = (dt) => {
316
+ * // normal per-frame update
317
+ * };
318
+ *
319
+ * game.start(); // calls onCreate() once before first frame
320
+ * // later:
321
+ * game.reset(); // clears + calls onCreate() again
322
+ * ```
323
+ *
324
+ * ---
325
+ *
326
+ * ## Sprite Lifecycle Ownership
327
+ *
328
+ * The `Game` instance owns all sprites.
329
+ * - Create sprites with {@link Game.add}.
330
+ * - Destroy sprites with {@link Game.destroySprite}.
331
+ * - Read the live sprite list with {@link Game.getSprites} (returns a read-only snapshot).
332
+ * - Do NOT hold references to destroyed sprites.
333
+ * - All sprites are destroyed on {@link Game.reset}.
334
+ *
335
+ * ---
336
+ *
337
+ * ## Forbidden Features
338
+ *
339
+ * The following do NOT exist in MinimoJS v1. Do NOT attempt to use them:
340
+ * - Scene system or scene manager
341
+ * - Entity Component System (ECS)
342
+ * - Physics engine (no Box2D, Matter.js, etc.)
343
+ * - Camera zoom or scale
344
+ * - Nested APIs or namespaces
345
+ * - Text input / HTML form elements
346
+ * - Parallax layers
347
+ * - Multiple cameras
348
+ * - Collision resolution (only detection is supported)
349
+ * - Image sprites (PNG, SVG, canvas, etc.)
350
+ * - `setTimeout` or `setInterval`
351
+ */
352
+ export declare class Game {
353
+ /**
354
+ * Horizontal gravity acceleration in pixels per second².
355
+ * Applied to every sprite whose {@link Sprite.gravityScale} is non-zero.
356
+ * Positive = accelerates right. Typical value for sideways wind: `200`.
357
+ * Default: `0`.
358
+ *
359
+ * @example
360
+ * ```ts
361
+ * game.gravityX = 0; // no horizontal gravity
362
+ * ```
363
+ */
364
+ gravityX: number;
365
+ /**
366
+ * Vertical gravity acceleration in pixels per second².
367
+ * Applied to every sprite whose {@link Sprite.gravityScale} is non-zero.
368
+ * Positive = accelerates downward (canvas Y-axis points down).
369
+ * Typical value for platformers: `980` (approximately Earth gravity in px/s²).
370
+ * Default: `0`.
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * game.gravityY = 980; // standard downward gravity
375
+ * ```
376
+ */
377
+ gravityY: number;
378
+ /**
379
+ * Horizontal scroll offset of the world camera, in pixels.
380
+ * The canvas viewport is shifted left by `scrollX` — sprites with higher `x`
381
+ * values are revealed as `scrollX` increases.
382
+ * Default: `0`. Reset to `0` by {@link Game.reset}.
383
+ *
384
+ * @example
385
+ * ```ts
386
+ * game.scrollX = player.x - game.width / 2; // center camera on player
387
+ * ```
388
+ */
389
+ scrollX: number;
390
+ /**
391
+ * Vertical scroll offset of the world camera, in pixels.
392
+ * The canvas viewport is shifted up by `scrollY` — sprites with higher `y`
393
+ * values are revealed as `scrollY` increases.
394
+ * Default: `0`. Reset to `0` by {@link Game.reset}.
395
+ */
396
+ scrollY: number;
397
+ /**
398
+ * Solid background color for the full canvas.
399
+ * Set to any valid CSS color string (e.g. `"#000"`, `"skyblue"`, `"rgba(0,0,0,0.5)"`).
400
+ * Default: `null` (transparent canvas background).
401
+ *
402
+ * If {@link Game.backgroundGradient} is set, the gradient takes priority over
403
+ * this solid color.
404
+ *
405
+ * @example
406
+ * ```ts
407
+ * game.background = "#101820";
408
+ * ```
409
+ */
410
+ background: string | null;
411
+ /**
412
+ * Vertical gradient background for the full canvas.
413
+ * `from` is the top color and `to` is the bottom color.
414
+ * Set to `null` to disable gradient mode.
415
+ *
416
+ * @example
417
+ * ```ts
418
+ * game.backgroundGradient = { from: "#92d7ff", to: "#5ca44f" };
419
+ * ```
420
+ */
421
+ backgroundGradient: {
422
+ from: string;
423
+ to: string;
424
+ } | null;
425
+ /**
426
+ * Background color for the full web page (`document.body`).
427
+ * Set to any valid CSS color string. Default: `null` (engine leaves page background unchanged).
428
+ *
429
+ * This is independent from {@link Game.background}, which affects the canvas only.
430
+ *
431
+ * @example
432
+ * ```ts
433
+ * game.pageBackground = "#f4e4bc";
434
+ * ```
435
+ */
436
+ pageBackground: string | null;
437
+ /**
438
+ * Scene creation callback.
439
+ *
440
+ * Called once before the first frame on {@link Game.start}, and again after
441
+ * each {@link Game.reset}. Use this to create sprites and timers for a scene.
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * game.onCreate = () => {
446
+ * const player = new Sprite("🐢");
447
+ * player.x = 200;
448
+ * player.y = 300;
449
+ * game.add(player);
450
+ * };
451
+ * ```
452
+ */
453
+ onCreate: (() => void) | null;
454
+ /**
455
+ * Callback invoked once per frame after physics and timer updates.
456
+ *
457
+ * @param dt - Delta time in **seconds** since the last frame.
458
+ * Use this for velocity-based movement: `sprite.x += speed * dt`.
459
+ * Capped at `0.1` seconds (100ms) to prevent spiral-of-death.
460
+ *
461
+ * @example
462
+ * ```ts
463
+ * game.onUpdate = (dt) => {
464
+ * if (game.isKeyDown("ArrowRight")) player.vx = 200;
465
+ * else player.vx = 0;
466
+ * };
467
+ * ```
468
+ */
469
+ onUpdate: ((dt: number) => void) | null;
470
+ /**
471
+ * Creates a new MinimoJS game instance.
472
+ *
473
+ * The engine creates its own `<canvas>`, sets its dimensions, and appends it
474
+ * to `document.body`. The canvas is automatically centered and responsively
475
+ * scaled to use the maximum available viewport space while preserving aspect ratio.
476
+ *
477
+ * @param width - Canvas width in pixels. Default: `800`.
478
+ * @param height - Canvas height in pixels. Default: `600`.
479
+ *
480
+ * @throws Error if a 2D context cannot be obtained.
481
+ *
482
+ * @example
483
+ * ```ts
484
+ * const game = new Game(800, 600);
485
+ * ```
486
+ */
487
+ constructor(width?: number, height?: number);
488
+ /**
489
+ * The width of the canvas in pixels. Read-only — set via constructor.
490
+ */
491
+ get width(): number;
492
+ /**
493
+ * The height of the canvas in pixels. Read-only — set via constructor.
494
+ */
495
+ get height(): number;
496
+ /**
497
+ * Current pointer (mouse or touch) X position in **canvas/screen space**,
498
+ * in pixels. Updated every `mousemove` / `touchmove` event.
499
+ * `(0, 0)` is the top-left corner of the canvas.
500
+ * To convert to world space: `worldX = game.pointerX + game.scrollX`.
501
+ */
502
+ get pointerX(): number;
503
+ /**
504
+ * Current pointer (mouse or touch) Y position in **canvas/screen space**,
505
+ * in pixels. Updated every `mousemove` / `touchmove` event.
506
+ * `(0, 0)` is the top-left corner of the canvas.
507
+ * To convert to world space: `worldY = game.pointerY + game.scrollY`.
508
+ */
509
+ get pointerY(): number;
510
+ /**
511
+ * Registers a {@link Sprite} (or subclass instance) with the engine.
512
+ * After calling `add`, the sprite is rendered and receives physics updates
513
+ * every frame until {@link Game.destroySprite} or {@link Game.reset} is called.
514
+ *
515
+ * **Ownership:** The game instance takes ownership of the sprite from this
516
+ * point forward. It will appear in {@link Game.getSprites} on the same frame.
517
+ *
518
+ * **Subclasses:** Any class that extends {@link Sprite} can be passed here.
519
+ * The engine stores and processes it as a `Sprite`; your custom properties
520
+ * are preserved on the instance.
521
+ *
522
+ * @param sprite - A {@link Sprite} instance (or subclass) to add.
523
+ * @returns The same sprite instance, for chaining or inline assignment.
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * // Plain sprite
528
+ * const coin = new Sprite("🪙");
529
+ * coin.x = 300; coin.y = 200; coin.size = 32;
530
+ * game.add(coin);
531
+ *
532
+ * // Custom subclass
533
+ * class Enemy extends Sprite {
534
+ * speed = 150;
535
+ * constructor(x: number, y: number) {
536
+ * super("👾");
537
+ * this.x = x; this.y = y; this.size = 40;
538
+ * }
539
+ * }
540
+ * const enemy = game.add(new Enemy(600, 100));
541
+ * ```
542
+ */
543
+ add(sprite: Sprite): Sprite;
544
+ /**
545
+ * Removes a sprite from the engine, stopping its rendering and physics updates.
546
+ * Also cancels any running animations targeting this sprite.
547
+ *
548
+ * **Side effects:** The sprite is removed immediately. Accessing the sprite's
549
+ * fields after destruction has no effect on the engine, but reading them
550
+ * returns stale values. Do not keep references to destroyed sprites.
551
+ *
552
+ * If `sprite` is not found (already destroyed or never added), this is a no-op.
553
+ *
554
+ * @param sprite - The sprite instance to destroy, as passed to {@link Game.add}.
555
+ *
556
+ * @example
557
+ * ```ts
558
+ * game.destroySprite(enemy); // remove enemy from game
559
+ * ```
560
+ */
561
+ destroySprite(sprite: Sprite): void;
562
+ /**
563
+ * Returns a **read-only snapshot** of all currently active sprites.
564
+ * The array is a shallow copy — mutating it has no effect on the engine.
565
+ * Individual sprite objects in the array are the live instances.
566
+ *
567
+ * **Deterministic guarantee:** Sprites appear in creation order within each
568
+ * layer. The order matches the render order (lower layer = earlier in array).
569
+ *
570
+ * @returns A read-only array of active {@link Sprite} instances.
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * const enemies = game.getSprites().filter(s => s.sprite === "👾");
575
+ * ```
576
+ */
577
+ getSprites(): readonly Sprite[];
578
+ /**
579
+ * Tests whether two sprites overlap using **Axis-Aligned Bounding Box (AABB)**
580
+ * collision detection.
581
+ *
582
+ * Each sprite's bounding box is a square centered at `(x, y)` with side
583
+ * length `size`. Rotation is **ignored** — the box is always axis-aligned.
584
+ *
585
+ * **No collision resolution is performed.** This method is detection-only.
586
+ * If you need bounce or push-apart behavior, implement it in `onUpdate`.
587
+ *
588
+ * @param a - First sprite.
589
+ * @param b - Second sprite.
590
+ * @returns `true` if the bounding boxes overlap; `false` otherwise.
591
+ *
592
+ * @example
593
+ * ```ts
594
+ * if (game.overlap(player, enemy)) {
595
+ * game.reset(); // restart on collision
596
+ * }
597
+ * ```
598
+ */
599
+ overlap(a: Sprite, b: Sprite): boolean;
600
+ /**
601
+ * Tests for any overlap between two groups of sprites.
602
+ * Performs an O(n × m) AABB check for every pair `(a, b)` where `a ∈ listA`
603
+ * and `b ∈ listB`. Returns the **first** overlapping pair found.
604
+ *
605
+ * Internally uses {@link Game.overlap} — same AABB rules apply.
606
+ * No collision resolution is performed.
607
+ *
608
+ * @param listA - First group of sprites.
609
+ * @param listB - Second group of sprites. May share sprites with `listA`.
610
+ * @returns A `[Sprite, Sprite]` tuple of the first overlapping pair,
611
+ * or `null` if no pair overlaps.
612
+ *
613
+ * @example
614
+ * ```ts
615
+ * const hit = game.overlapAny(bullets, enemies);
616
+ * if (hit) {
617
+ * const [bullet, enemy] = hit;
618
+ * game.destroySprite(bullet);
619
+ * game.destroySprite(enemy);
620
+ * }
621
+ * ```
622
+ */
623
+ overlapAny(listA: Sprite[], listB: Sprite[]): [Sprite, Sprite] | null;
624
+ /**
625
+ * Returns `true` while the specified key is held down (every frame it is held).
626
+ * Use this for continuous actions like movement.
627
+ *
628
+ * Uses the `KeyboardEvent.key` string (e.g. `"ArrowLeft"`, `"a"`, `" "`, `"Enter"`).
629
+ * Key names are case-sensitive.
630
+ *
631
+ * **Hold behavior:** returns `true` on the first frame the key is pressed AND
632
+ * every subsequent frame until the key is released.
633
+ *
634
+ * @param key - The `KeyboardEvent.key` value to check.
635
+ * @returns `true` if the key is currently held down.
636
+ *
637
+ * @example
638
+ * ```ts
639
+ * if (game.isKeyDown("ArrowRight")) player.vx = 200;
640
+ * ```
641
+ */
642
+ isKeyDown(key: string): boolean;
643
+ /**
644
+ * Returns `true` only on the **single frame** the key was first pressed.
645
+ * Use this for one-shot actions like jumping or firing.
646
+ *
647
+ * **Edge behavior:** returns `true` exactly once per press, on the frame the
648
+ * key transitions from up → down. Subsequent frames while held return `false`.
649
+ *
650
+ * @param key - The `KeyboardEvent.key` value to check.
651
+ * @returns `true` only on the first frame of the key press.
652
+ *
653
+ * @example
654
+ * ```ts
655
+ * if (game.isKeyPressed(" ")) player.vy = -500; // jump on spacebar press
656
+ * ```
657
+ */
658
+ isKeyPressed(key: string): boolean;
659
+ /**
660
+ * Returns `true` while the pointer (mouse button or touch) is held down.
661
+ * Use this for continuous pointer actions.
662
+ *
663
+ * **Hold behavior:** returns `true` every frame from press until release.
664
+ *
665
+ * @returns `true` if the pointer is currently pressed.
666
+ *
667
+ * @example
668
+ * ```ts
669
+ * if (game.isPointerDown()) fireContinuously();
670
+ * ```
671
+ */
672
+ isPointerDown(): boolean;
673
+ /**
674
+ * Returns `true` only on the **single frame** the pointer was first pressed.
675
+ * Use this for tap/click one-shot actions.
676
+ *
677
+ * **Edge behavior:** returns `true` exactly once per press, on the frame the
678
+ * pointer transitions from up → down. Subsequent frames while held return `false`.
679
+ *
680
+ * @returns `true` only on the first frame of the pointer press.
681
+ *
682
+ * @example
683
+ * ```ts
684
+ * if (game.isPointerPressed()) spawnAt(game.pointerX, game.pointerY);
685
+ * ```
686
+ */
687
+ isPointerPressed(): boolean;
688
+ /**
689
+ * Returns `true` when the current device appears to be mobile/touch-first.
690
+ *
691
+ * This is a heuristic helper intended for gameplay UI decisions (for example,
692
+ * showing on-screen touch controls). It checks user-agent/platform hints and
693
+ * coarse-pointer/touch capabilities.
694
+ *
695
+ * @returns `true` if the runtime likely corresponds to a mobile device.
696
+ *
697
+ * @example
698
+ * ```ts
699
+ * if (game.isMobileDevice()) {
700
+ * // Show touch buttons
701
+ * } else {
702
+ * // Show keyboard hints
703
+ * }
704
+ * ```
705
+ */
706
+ isMobileDevice(): boolean;
707
+ /**
708
+ * Plays a square-wave beep using the Web Audio API.
709
+ *
710
+ * **Square wave only.** MinimoJS does not support audio files, samples,
711
+ * or other waveforms. Only procedural square-wave tones.
712
+ *
713
+ * The AudioContext is created lazily on first call. Browsers require a user
714
+ * gesture (click, keypress) before audio can play — always call `sound()` in
715
+ * response to user input or a game event triggered by input.
716
+ *
717
+ * The tone fades out exponentially over `durationMs` to avoid clicks.
718
+ *
719
+ * @param freq - Frequency in Hz. Middle C = 261.6. Typical range: 100–4000 Hz.
720
+ * @param durationMs - Duration of the sound in **milliseconds**.
721
+ *
722
+ * @example
723
+ * ```ts
724
+ * game.sound(440, 100); // 440 Hz beep for 100ms
725
+ * game.sound(261, 500); // middle C for 500ms
726
+ * game.sound(880, 50); // high beep for 50ms (jump sound)
727
+ * ```
728
+ */
729
+ sound(freq: number, durationMs: number): void;
730
+ /**
731
+ * Animates a sprite's {@link Sprite.alpha} from its current value to `to`
732
+ * over `durationMs` milliseconds using **linear interpolation**.
733
+ *
734
+ * If an alpha animation is already running on this sprite, it is replaced
735
+ * by the new one immediately (no queuing).
736
+ *
737
+ * **Timing:** driven by the rAF loop, not `setTimeout`. Duration is in ms.
738
+ *
739
+ * @param sprite - The sprite to animate.
740
+ * @param to - Target alpha value (0 = transparent, 1 = opaque).
741
+ * @param durationMs - Duration of the animation in **milliseconds**.
742
+ * @param onComplete - Optional callback invoked when the animation finishes.
743
+ *
744
+ * @example
745
+ * ```ts
746
+ * // Fade out a sprite over 1 second, then destroy it
747
+ * game.animateAlpha(coin, 0, 1000, () => game.destroySprite(coin));
748
+ * ```
749
+ */
750
+ animateAlpha(sprite: Sprite, to: number, durationMs: number, onComplete?: () => void): void;
751
+ /**
752
+ * Animates a sprite's {@link Sprite.rotation} from its current value to `to`
753
+ * (in degrees) over `durationMs` milliseconds using **linear interpolation**.
754
+ *
755
+ * If a rotation animation is already running on this sprite, it is replaced
756
+ * immediately (no queuing).
757
+ *
758
+ * **Timing:** driven by the rAF loop, not `setTimeout`. Duration is in ms.
759
+ * **Units:** `to` is in **degrees**.
760
+ *
761
+ * @param sprite - The sprite to animate.
762
+ * @param to - Target rotation in **degrees**.
763
+ * @param durationMs - Duration of the animation in **milliseconds**.
764
+ * @param onComplete - Optional callback invoked when the animation finishes.
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * // Spin a sprite 360° over 2 seconds
769
+ * game.animateRotation(star, 360, 2000);
770
+ *
771
+ * // Tilt on hit, then straighten
772
+ * game.animateRotation(player, 45, 200, () => {
773
+ * game.animateRotation(player, 0, 200);
774
+ * });
775
+ * ```
776
+ */
777
+ animateRotation(sprite: Sprite, to: number, durationMs: number, onComplete?: () => void): void;
778
+ /**
779
+ * Schedules a callback to fire after `delayMs` milliseconds, driven by the
780
+ * rAF loop (not `setTimeout`). Timers accumulate elapsed time each frame and
781
+ * fire once the threshold is reached.
782
+ *
783
+ * **Repeat behavior:**
784
+ * - `repeat = false`: fires once, then auto-removes.
785
+ * - `repeat = true`: fires every `delayMs` ms until cleared with {@link Game.clearTimer}.
786
+ * The elapsed time wraps (excess time carries over), so repeat timers are
787
+ * accurate over long durations.
788
+ *
789
+ * **Reset behavior:** ALL timers are cleared on {@link Game.reset}.
790
+ *
791
+ * @param delayMs - Delay (and period for repeating timers) in **milliseconds**.
792
+ * @param repeat - If `true`, the timer fires repeatedly every `delayMs` ms.
793
+ * @param callback - Function to call when the timer fires.
794
+ * @returns A numeric timer ID for use with {@link Game.clearTimer}.
795
+ *
796
+ * @example
797
+ * ```ts
798
+ * // One-shot: explode after 3 seconds
799
+ * game.addTimer(3000, false, () => { bomb.sprite = "💥"; });
800
+ *
801
+ * // Repeating: spawn enemy every 2 seconds
802
+ * const spawnId = game.addTimer(2000, true, spawnEnemy);
803
+ * // Stop spawning:
804
+ * game.clearTimer(spawnId);
805
+ * ```
806
+ */
807
+ addTimer(delayMs: number, repeat: boolean, callback: () => void): number;
808
+ /**
809
+ * Cancels a timer previously created with {@link Game.addTimer}.
810
+ * If the ID does not exist (already fired and removed, or never created),
811
+ * this is a safe no-op.
812
+ *
813
+ * @param id - The timer ID returned by {@link Game.addTimer}.
814
+ *
815
+ * @example
816
+ * ```ts
817
+ * const id = game.addTimer(5000, true, spawnEnemy);
818
+ * // Stop spawning when player reaches the end:
819
+ * game.clearTimer(id);
820
+ * ```
821
+ */
822
+ clearTimer(id: number): void;
823
+ /**
824
+ * Draws text on screen as a **screen-space overlay** this frame.
825
+ *
826
+ * **Overlay behavior:** Text is drawn in canvas/screen space — it ignores
827
+ * `scrollX` / `scrollY`. Position `(0, 0)` is always the top-left of the canvas.
828
+ * Use this for HUD elements: score, lives, timer, debug info.
829
+ *
830
+ * **Per-frame:** `drawText` must be called every frame to keep text visible.
831
+ * The text overlay list is cleared after each render. Call this inside `onUpdate`.
832
+ *
833
+ * **Layer:** Text is always drawn on top of all sprites.
834
+ * **Font:** Text always uses a fixed `monospace` font family.
835
+ * Font family cannot be customized in MinimoJS v1.
836
+ *
837
+ * @param text - The string to render. Supports emoji and Unicode.
838
+ * @param x - X position in **screen space** (pixels from canvas left edge).
839
+ * @param y - Y position in **screen space** (pixels from canvas top edge).
840
+ * Text baseline is at the top of the text.
841
+ * @param fontSize - Font size in pixels (e.g. `16`, `24`, `32`).
842
+ * @param color - CSS color string. Default: `"#ffffff"` (white).
843
+ * @param centered - If `true`, text is centered on `(x, y)` (both axes).
844
+ * If `false`, `(x, y)` is the top-left anchor. Default: `false`.
845
+ *
846
+ * @example
847
+ * ```ts
848
+ * game.onUpdate = (dt) => {
849
+ * game.drawText(`Score: ${score}`, 10, 10, 20, "#ffff00");
850
+ * game.drawText(`Lives: ${"❤️".repeat(lives)}`, 10, 40, 20);
851
+ * game.drawText("PAUSED", game.width / 2, game.height / 2, 28, "#ffffff", true);
852
+ * };
853
+ * ```
854
+ */
855
+ drawText(text: string, x: number, y: number, fontSize: number, color?: string, centered?: boolean): void;
856
+ /**
857
+ * Returns a pseudo-random floating-point number in the range `[0, 1)`.
858
+ * Delegates to `Math.random()`.
859
+ *
860
+ * **Determinism note:** This method is NOT seeded and NOT deterministic across
861
+ * runs. The output changes every invocation. If you need reproducible randomness,
862
+ * implement a seeded PRNG (e.g. a simple LCG) in your game code.
863
+ *
864
+ * @returns A random number in `[0, 1)`.
865
+ *
866
+ * @example
867
+ * ```ts
868
+ * const x = game.random() * game.width;
869
+ * const emoji = ["⭐", "🔥", "💎"][Math.floor(game.random() * 3)];
870
+ * ```
871
+ */
872
+ random(): number;
873
+ /**
874
+ * Performs a full engine state reset to enable scene switching.
875
+ *
876
+ * **Cleared by reset:**
877
+ * - All sprites (equivalent to calling {@link Game.destroySprite} on every sprite)
878
+ * - All timers (regardless of repeat state)
879
+ * - All running animations
880
+ * - All pending text overlays
881
+ * - Scroll position (`scrollX = 0`, `scrollY = 0`)
882
+ * - Per-frame input state (pressed keys, pressed pointer)
883
+ *
884
+ * **NOT cleared by reset:**
885
+ * - `gravityX` / `gravityY` (global engine setting)
886
+ * - `background` / `backgroundGradient` / `pageBackground`
887
+ * - `onCreate` callback (your handler stays registered)
888
+ * - `onUpdate` callback (your handler stays registered)
889
+ * - Canvas dimensions
890
+ * - AudioContext
891
+ *
892
+ * After clearing, `reset()` immediately calls `onCreate()` so your callback
893
+ * can rebuild the new scene synchronously.
894
+ *
895
+ * @example
896
+ * ```ts
897
+ * // Switch from gameplay to game-over screen
898
+ * function gameOver() {
899
+ * game.reset(); // onCreate() is called here, rebuild inside it
900
+ * }
901
+ *
902
+ * game.onCreate = () => {
903
+ * // Scene init
904
+ * const skull = new Sprite("💀");
905
+ * skull.x = 400; skull.y = 300; skull.size = 96;
906
+ * game.add(skull);
907
+ * game.addTimer(3000, false, () => game.reset()); // auto-restart
908
+ * };
909
+ * ```
910
+ */
911
+ reset(): void;
912
+ /**
913
+ * Starts the `requestAnimationFrame` game loop.
914
+ * Safe to call multiple times — does nothing if already running.
915
+ * If this is the first start (or after a reset), `onCreate()` is called
916
+ * before the first frame.
917
+ *
918
+ * The loop calls `onUpdate` once per frame, then renders all sprites and
919
+ * text overlays. Order per frame:
920
+ * 1. Accumulate timer elapsed time; fire ready callbacks.
921
+ * 2. Advance animations (linear interpolation).
922
+ * 3. Apply gravity to sprite velocities.
923
+ * 4. Apply velocities to sprite positions.
924
+ * 5. Call `onUpdate(dt)`.
925
+ * 6. Render sprites (sorted by layer) with scroll offset.
926
+ * 7. Render text overlays (screen space, on top of sprites).
927
+ * 8. Clear per-frame input state.
928
+ *
929
+ * @example
930
+ * ```ts
931
+ * game.onUpdate = (dt) => { ... };
932
+ * game.start();
933
+ * ```
934
+ */
935
+ start(): void;
936
+ /**
937
+ * Stops the game loop. The canvas retains its last rendered frame.
938
+ * Call {@link Game.start} to resume.
939
+ */
940
+ stop(): void;
941
+ }