@cosmoledo/gleam 1.0.1 → 1.0.2

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.
package/dist/gleam.d.ts CHANGED
@@ -1,18 +1,30 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
+ /** Entry shape passed to {@link AudioBase.register}. */
3
4
  export interface RegisterData {
5
+ /** URL or relative path to the audio file. */
4
6
  path: string;
7
+ /** Identifier used by `play(name)` / `fade(name)`. */
5
8
  name: string;
9
+ /** Per-song volume override in `[0, 1]`. Falls back to `register`'s `defaultVolume`. */
6
10
  volume?: number;
7
11
  }
12
+ /**
13
+ * Shared registration, enable/disable, and teardown machinery for {@link Sound} and {@link Music}. Auto-subscribes to the `"gameloopStopped"` event so playback halts on loop teardown.
14
+ */
8
15
  export declare abstract class AudioBase {
16
+ /** Registered audio elements keyed by name. Subclasses read this; mutate via {@link register}. */
9
17
  protected songs: Map<string, HTMLAudioElement>;
10
18
  private _enabled;
11
19
  private registered;
20
+ /** Whether playback is permitted. Setting to `false` invokes {@link stop} immediately. */
12
21
  get enabled(): boolean;
22
+ /** Setting to `false` invokes {@link stop} immediately; subclasses (notably {@link Music}) may re-start playback when flipped back to `true`. */
13
23
  set enabled(value: boolean);
14
24
  constructor(enabled?: boolean);
25
+ /** Load and register one or more audio files. Each entry may be a bare URL string (the file's basename becomes the name) or a {@link RegisterData} object. Per-song volume falls back to `defaultVolume`. **Call once per instance** — throws on a second invocation, on non-finite volume, or on volume outside `[0, 1]`. Load failures are logged to `console.error` but don't throw. */
15
26
  register(defaultVolume?: number, ...songs: (RegisterData | string)[]): void;
27
+ /** Base hook called when {@link enabled} flips to `false` and on `"gameloopStopped"`. The default is a no-op; {@link Sound} and {@link Music} override it to cut playback. Subclass overrides should call `super.stop()`. */
16
28
  stop(): void;
17
29
  private throwOnBadVolume;
18
30
  }
@@ -32,64 +44,119 @@ export declare function easeOut(t: number): number;
32
44
  * Identity (constant rate of change).
33
45
  */
34
46
  export declare function linear(t: number): number;
47
+ /** Names of the built-in easing curves. Use as a key into {@link EASINGS}. */
35
48
  export type EasingName = "ease-in" | "ease-in-out" | "ease-out" | "linear";
49
+ /** Lookup table from {@link EasingName} to its easing function (`t ∈ [0, 1] → eased t`). */
36
50
  export declare const EASINGS: Record<EasingName, (t: number) => number>;
51
+ /**
52
+ * Background music with eased cross-fades. Tracks auto-cycle: when the current track ends, the next one fades in. Inherits registration, enable/disable, and volume from {@link AudioBase}.
53
+ *
54
+ * Random track picking excludes the previous two songs to avoid back-to-back repeats. If only one track is registered, it's looped instead of faded.
55
+ */
37
56
  export declare class Music extends AudioBase {
38
57
  private last;
39
58
  private current;
40
59
  private next;
41
60
  private fadeCancel;
61
+ /** `true` while a fade is in progress OR the current track is actively playing. */
42
62
  get isPlaying(): boolean;
63
+ /** Whether music playback is permitted (inherited from {@link AudioBase}). */
43
64
  get enabled(): boolean;
65
+ /** Flipping from `false` to `true` while no music is playing auto-starts a fade-in to a random track. */
44
66
  set enabled(value: boolean);
67
+ /**
68
+ * Cross-fade to `name` (or a random unplayed track when `null`) over `fadeTime` ms. `easing.cur` controls the outgoing track's volume curve, `easing.next` the incoming one. Cancels any in-progress fade. No-op when disabled. Throws on `fadeTime <= 0`, an empty registry, or an unknown `name`. When the new track ends, the next fade fires automatically — call {@link stop} to break the cycle.
69
+ */
45
70
  fade(name?: string | null, fadeTime?: number, easing?: {
46
71
  cur: EasingName;
47
72
  next: EasingName;
48
73
  }): void;
74
+ /** Stop everything immediately: cancels any in-flight fade, halts current and next tracks, and breaks the auto-cycle chain. Restart via {@link fade} or by flipping {@link enabled}. */
49
75
  stop(): void;
50
76
  private getRandom;
51
77
  }
78
+ /** One-shot SFX. Each {@link play} call clones the registered `HTMLAudioElement` so the same sound can overlap itself; {@link stop} cuts every in-flight clone. Inherits registration, enable/disable, and volume from {@link AudioBase}. */
52
79
  export declare class Sound extends AudioBase {
53
80
  private currentSounds;
81
+ /** Play the registered sound `name` once. Returns a promise that resolves when playback starts (or immediately if `enabled` is `false`) and rejects on autoplay/permission errors. Throws synchronously if no sounds are registered or `name` is unknown. Each call allocates a clone, so concurrent plays of the same name overlap. */
54
82
  play(name: string): Promise<void>;
83
+ /** Stop and forget every currently-playing clone. Also calls the base-class teardown. */
55
84
  stop(): void;
56
85
  }
86
+ /** Components returned by {@link Color.toHSLObject}. */
87
+ export interface HSLObject {
88
+ /** Hue in degrees, `[0, 360]`. */
89
+ h: number;
90
+ /** Saturation in percent, `[0, 100]`. */
91
+ s: number;
92
+ /** Lightness in percent, `[0, 100]`. */
93
+ l: number;
94
+ /** Alpha in `[0, 1]`. */
95
+ a: number;
96
+ }
97
+ /**
98
+ * RGBA color with chainable mutators. Channels are stored clamped (`r/g/b` ∈ `[0, 255]`, `alpha` ∈ `[0, 1]`) — every mutator routes through {@link set}, so direct field writes aren't possible and clamping/rounding is uniform.
99
+ *
100
+ * **Hue unit gotcha**: {@link fromHSL} takes hue in **degrees** (CSS convention), but {@link hueRotate} takes **radians** (codebase convention). Use `Math.PI` etc. for hueRotate.
101
+ *
102
+ * All `to*` methods return CSS-compatible strings; `toHSLObject` returns the components as numbers if you need to mutate them.
103
+ */
57
104
  export declare class Color {
105
+ /** Parse `#rgb`, `#rgba`, `#rrggbb`, or `#rrggbbaa` (case-insensitive, `#` optional). Throws on any other shape or on non-hex characters. */
58
106
  static fromHex(hex: string): Color;
107
+ /** Build from HSL(A). `h` in **degrees** (wraps mod 360), `s`/`l` in percent `[0, 100]`, `a` in `[0, 1]`. The degree convention matches CSS — note that {@link hueRotate} uses radians instead. */
59
108
  static fromHSL(h: number, s: number, l: number, a?: number): Color;
60
109
  private _r;
61
110
  private _g;
62
111
  private _b;
63
112
  private _alpha;
113
+ /** Red channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */
64
114
  get r(): number;
115
+ /** Green channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */
65
116
  get g(): number;
117
+ /** Blue channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */
66
118
  get b(): number;
119
+ /** Alpha channel, `[0, 1]`. Read-only; mutate via {@link set} (pass the fourth arg). */
67
120
  get alpha(): number;
68
121
  constructor(r: number, g: number, b: number, a?: number);
122
+ /** Primary mutator — every other transform on this class routes through it. Clamps `r`/`g`/`b` to `[0, 255]` and `a` to `[0, 1]`; alpha is snapped to exact `0` or `1` when within `approxEqual` tolerance so equality checks stay clean. Returns `this` for chaining. */
69
123
  set(r: number, g: number, b: number, a?: number): this;
124
+ /** Apply a 3×3 RGB color matrix in row-major order (`m1..m9`). Alpha is unchanged. Used by {@link grayscale}, {@link hueRotate}, {@link saturate}, {@link sepia}. Mutates and returns `this`. */
70
125
  applyMatrix(m1: number, m2: number, m3: number, m4: number, m5: number, m6: number, m7: number, m8: number, m9: number): this;
126
+ /** Multiply each channel by `factor`. `factor < 1` darkens, `factor > 1` brightens (clamped at 255). Mutates and returns `this`. */
71
127
  brightness(factor: number): this;
128
+ /** Push each channel away from `127.5` (the midtone) by `factor`. `factor < 1` flattens contrast, `> 1` increases it, `0` collapses every channel to gray. Mutates and returns `this`. */
72
129
  contrast(factor: number): this;
130
+ /** Desaturate via the standard luminance-preserving matrix. `value` in `[0, 1]`: `0` is a no-op, `1` is full grayscale. Mutates and returns `this`. */
73
131
  grayscale(value?: number): this;
132
+ /** Rotate hue by `radians` (use `Math.PI / 2` etc.). Unlike {@link fromHSL}, this takes radians, not degrees. Mutates and returns `this`. */
74
133
  hueRotate(radians: number): this;
134
+ /** Interpolate each channel toward its inverse (`255 - c`). `factor` in `[0, 1]`: `0` is unchanged, `1` is fully inverted. Mutates and returns `this`. */
75
135
  invert(factor?: number): this;
136
+ /** Linear blend toward `other`. `amount` in `[0, 1]`: `0` keeps `this`, `1` becomes `other`. Mixes alpha too. Mutates and returns `this`. */
76
137
  mix(other: Color, amount: number): this;
138
+ /** Round each RGB channel to the nearest integer. Alpha is untouched. Mutates and returns `this`. */
77
139
  round(): this;
140
+ /** Saturation matrix. `value` typically in `[0, 2]`: `0` desaturates to grayscale (same as `grayscale(1)`), `1` is a no-op, `> 1` oversaturates. Mutates and returns `this`. */
78
141
  saturate(value?: number): this;
142
+ /** Sepia matrix. `value` in `[0, 1]`: `0` is unchanged, `1` is full sepia. Mutates and returns `this`. */
79
143
  sepia(value?: number): this;
144
+ /** Tint or shade. `percent` in `[-1, 1]`: negative shades toward black, positive tints toward white, magnitude is the amount. Mutates and returns `this`. */
80
145
  shade(percent: number): this;
146
+ /** CSS hex string. `#rrggbb` when alpha is exactly `1`, `#rrggbbaa` otherwise. Channels are rounded. */
81
147
  toHex(): string;
148
+ /** CSS HSL string. `hsl(h, s%, l%)` when alpha is exactly `1`, `hsla(...)` otherwise. Hue is in degrees. */
82
149
  toHSL(): string;
83
- toHSLObject(): {
84
- h: number;
85
- s: number;
86
- l: number;
87
- a: number;
88
- };
150
+ /** HSL(A) components as numbers — see {@link HSLObject}. Use when you need to compute against the values rather than render them as a string. */
151
+ toHSLObject(): HSLObject;
152
+ /** CSS RGB string. `rgb(r, g, b)` when alpha is exactly `1`, `rgba(r, g, b, a)` otherwise. Channels are rounded. */
89
153
  toRGB(): string;
154
+ /** New `Color` with the same channels. */
90
155
  clone(): Color;
156
+ /** Approximate equality (within `approxEqual` tolerance). Pass `compareAlpha: false` to ignore the alpha channel. */
91
157
  equals(other: Color, compareAlpha?: boolean): boolean;
92
158
  }
159
+ /** `[r, g, b]` tuple of integer channel values, each in `[0, 255]`. */
93
160
  export type RGB = [
94
161
  number,
95
162
  number,
@@ -134,42 +201,69 @@ export declare function randomRgb(min?: number, max?: number): RGB;
134
201
  * console.log(colorShifter(randomRgb(1, 10)));
135
202
  */
136
203
  export declare function colorShifter(rgb: RGB): string;
204
+ /** Result of a {@link Polygon.collide} call. */
137
205
  export interface PolygonCollisionResult {
206
+ /** `true` if the polygons currently overlap. */
138
207
  intersect: boolean;
208
+ /** Smallest displacement that would separate the polygons (zero `Vec2` when they're disjoint). */
139
209
  minimumTranslationVector: Vec2;
210
+ /** `true` if applying the `velocity` passed to `collide` would put the polygons into contact. */
140
211
  willIntersect: boolean;
141
212
  }
213
+ /**
214
+ * Convex 2D polygon. **The collision API ({@link Polygon.collide}) uses SAT, which is mathematically defined only for convex polygons** — feeding it a concave polygon silently produces wrong results.
215
+ */
142
216
  export declare class Polygon {
143
217
  /**
144
- * `angle` is the simplification threshold in radians: vertices whose turn
145
- * angle wraps to within ±`angle` of straight are dropped.
218
+ * Trace an outline around the opaque pixels of `canvas` via four directional sweeps (top, right, bottom, left). `detail` is the pixel stride (≥ 2) between scanline samples — higher = faster but coarser. `angle` is the simplification threshold in radians: vertices whose turn angle wraps to within ±`angle` of straight are dropped. Throws if fewer than 3 vertices survive.
219
+ *
220
+ * **Convex shapes only.** The sweep ignores anything an outer-perimeter ray can't reach: holes (donuts), inward bays (a "C" opening sideways), or any row/column with multiple disjoint opaque spans. For those inputs the result is either a broken polygon or simply the outer hull, and {@link Polygon.collide} relies on convexity anyway.
146
221
  */
147
222
  static fromCanvas(canvas: HTMLCanvasElement, detail: number, angle: number): Polygon;
223
+ /** Regular convex polygon with `edges` vertices, inscribed in a bounding box of `size` (number = square). */
148
224
  static fromEdges(edges: number, size: Vec2 | number): Polygon;
225
+ /** Polygon from the four corners of `rect`. */
149
226
  static fromRect(rect: Rect): Polygon;
150
227
  private _center;
151
228
  private _points;
152
229
  private edges;
230
+ /** Centroid (mean of vertex positions). Recomputed whenever the vertex set changes. */
153
231
  get center(): Readonly<Vec2>;
232
+ /** Read-only view of the current vertex list. Use `addPoint`/`offset`/`rotate` to mutate. */
154
233
  get points(): Readonly<Vec2[]>;
155
234
  constructor(...points: Vec2[]);
235
+ /** Stroke the polygon to `context`, shifted by `offset`. Coordinates are truncated to integers (via `| 0`) for crisp lines. */
156
236
  draw(context: CanvasRenderingContext2D, offset?: Vec2): void;
237
+ /** Append a single vertex at `(x, y)`. Mutates and returns `this`. */
157
238
  addPoint(x: number, y: number): Polygon;
239
+ /** Append cloned copies of every passed vertex. Mutates and returns `this`. */
158
240
  addPoints(...points: Vec2[]): Polygon;
241
+ /** Translate every vertex by `(x, y)`. Mutates and returns `this`. */
159
242
  offset(x?: number, y?: number): Polygon;
243
+ /** Rotate by `angle` radians around `pos` (defaults to the centroid). Mutates and returns `this`. */
160
244
  rotate(angle: number, pos?: Readonly<Vec2>): this;
245
+ /** SAT collision against `otherPolygon`. **Both polygons must be convex** — SAT silently misses collisions for concave shapes. Pass a non-zero `velocity` to also compute whether the polygons would intersect after that displacement. Warns and returns a no-collision result if either polygon has zero edges. */
161
246
  collide(otherPolygon: Polygon, velocity?: Vec2): PolygonCollisionResult;
247
+ /** New `Polygon` with the same vertices. */
162
248
  clone(): Polygon;
163
249
  private update;
164
250
  }
251
+ /** Derived geometry returned by {@link Rect.sides}. */
165
252
  export interface Sides {
253
+ /** Lower edge (`y + h`). */
166
254
  bottom: number;
255
+ /** Center point. */
167
256
  centerPos: Vec2;
257
+ /** Half the width and height. */
168
258
  halfSize: Vec2;
259
+ /** Right edge (`x + w`). */
169
260
  right: number;
170
261
  }
262
+ /** Axis-aligned 2D rectangle (`x`, `y`, `w`, `h`). */
171
263
  export declare class Rect {
264
+ /** Build from an `HTMLElement` (via `getBoundingClientRect`) or a `DOMRect`. */
172
265
  static fromBoundingClientRect(rect: DOMRect | HTMLElement): Rect;
266
+ /** Axis-aligned bounding box of a polygon's points. Throws if the polygon has no points. */
173
267
  static fromPolygon(polygon: Polygon): Rect;
174
268
  private _h;
175
269
  private _w;
@@ -177,35 +271,64 @@ export declare class Rect {
177
271
  private _y;
178
272
  private _sides;
179
273
  private sideIsDirty;
274
+ /** Height. */
180
275
  get h(): number;
276
+ /** Height. */
181
277
  set h(value: number);
278
+ /** Width. */
182
279
  get w(): number;
280
+ /** Width. */
183
281
  set w(value: number);
282
+ /** Top-left x. */
184
283
  get x(): number;
284
+ /** Top-left x. */
185
285
  set x(value: number);
286
+ /** Top-left y. */
186
287
  get y(): number;
288
+ /** Top-left y. */
187
289
  set y(value: number);
290
+ /** Derived sides/center/halfSize. Lazily recomputed after any `x`/`y`/`w`/`h` change. */
188
291
  get sides(): Readonly<Sides>;
189
292
  constructor(x?: number, y?: number, w?: number, h?: number);
293
+ /** Grow on every side by `delta` (`x`/`y` shift in, `w`/`h` grow by `2*delta`). Pass a negative value to shrink. Mutates and returns `this`. */
190
294
  inflate(delta: number): Rect;
295
+ /** Round `x` and `y` to the nearest integer. `w`/`h` are unchanged. Mutates and returns `this`. */
191
296
  round(): Rect;
297
+ /**
298
+ * Replace fields. The first arg may be a `Vector4` (sets all four), a `Vector2` (sets `x`/`y` only, unless explicit `w`/`h` follow), or `x` as a number with separate `y`/`w`/`h`. Mutates and returns `this`.
299
+ */
192
300
  set(x?: Vector4 | Vector2 | number, y?: number, w?: number, h?: number): Rect;
301
+ /** AABB-vs-AABB overlap test (inclusive of touching edges). */
193
302
  collide(rect: Rect): boolean;
303
+ /** `true` when `rect` is fully inside `this`. */
194
304
  collideFull(rect: Rect): boolean;
305
+ /** `true` when `vec` lies inside `this` (inclusive of edges). */
195
306
  collidePoint(vec: Vector2): boolean;
307
+ /** Side of `this` that `rect` overlaps from, or `"none"` if disjoint. Useful for picking a bounce axis. */
196
308
  collideSide(rect: Rect): "none" | "top" | "bottom" | "left" | "right";
309
+ /** Top-left corner as a new `Vec2`. */
197
310
  pos(): Vec2;
311
+ /** Width and height as a new `Vec2`. */
198
312
  size(): Vec2;
313
+ /** Debug string like `"Rect [x: 0, y: 0, w: 10, h: 20]"`. */
199
314
  toString(): string;
315
+ /** New `Rect` with the same values. */
200
316
  clone(): Rect;
317
+ /** Approximate equality. Pass `withSize: false` to compare position only. */
201
318
  equals(other: Rect, withSize?: boolean): boolean;
202
319
  }
320
+ /** Object literal compatible with `Vec2` (e.g. `{ x, y }`). */
203
321
  export interface Vector2 {
322
+ /** Horizontal component. */
204
323
  x: number;
324
+ /** Vertical component. */
205
325
  y: number;
206
326
  }
327
+ /** {@link Vector2} plus `w`/`h` for AABB-style values. */
207
328
  export interface Vector4 extends Vector2 {
329
+ /** Width. */
208
330
  w: number;
331
+ /** Height. */
209
332
  h: number;
210
333
  }
211
334
  /**
@@ -214,16 +337,24 @@ export interface Vector4 extends Vector2 {
214
337
  * both. Pass `(x, y)` or a `Vector2` for per-axis values.
215
338
  */
216
339
  export declare class Vec2 {
340
+ /** Unit vector at angle `rad` (radians), scaled per-axis. `scaleY` defaults to `scaleX`. */
217
341
  static fromAngle(rad: number, scaleX?: number, scaleY?: number): Vec2;
342
+ /** Horizontal component. */
218
343
  x: number;
344
+ /** Vertical component. */
219
345
  y: number;
220
346
  constructor(x?: Vector2 | number, y?: number);
347
+ /** Replace components. Mutates and returns `this`. */
221
348
  set(v: Vector2): Vec2;
222
349
  set(x: number, y?: number): Vec2;
350
+ /** Set each component to its absolute value. Mutates and returns `this`. */
223
351
  abs(): Vec2;
352
+ /** Per-axis add. Mutates and returns `this`. */
224
353
  add(v: Vector2): Vec2;
225
354
  add(x: number, y?: number): Vec2;
355
+ /** Round each component up. Mutates and returns `this`. */
226
356
  ceil(): Vec2;
357
+ /** Clamp each axis to its `[min, max]` range. `y` defaults to `x`. Mutates and returns `this`. */
227
358
  clamp(x: [
228
359
  number,
229
360
  number
@@ -231,70 +362,125 @@ export declare class Vec2 {
231
362
  number,
232
363
  number
233
364
  ]): Vec2;
365
+ /** Per-axis divide. Mutates and returns `this`. */
234
366
  div(v: Vector2): Vec2;
235
367
  div(x: number, y?: number): Vec2;
368
+ /** Round each component down. Mutates and returns `this`. */
236
369
  floor(): Vec2;
370
+ /**
371
+ * Apply `callback` to each component (`index` is `0` for x, `1` for y). Mutates and returns `this`.
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * new Vec2(3.6, -2.1).map(Math.trunc); // Vec2 { x: 3, y: -2 }
376
+ * new Vec2(2, 5).map((v, i) => v * (i + 1)); // Vec2 { x: 2, y: 10 }
377
+ * ```
378
+ */
237
379
  map(callback: (value: number, index: number) => number): Vec2;
380
+ /** Per-axis Euclidean modulo (result sign matches the divisor). Mutates and returns `this`. */
238
381
  mod(v: Vector2): Vec2;
239
382
  mod(x: number, y?: number): Vec2;
383
+ /** Per-axis multiply. Mutates and returns `this`. */
240
384
  mult(v: Vector2): Vec2;
241
385
  mult(x: number, y?: number): Vec2;
386
+ /** Flip the sign of both components (same as `mult(-1)`). Mutates and returns `this`. */
242
387
  negate(): Vec2;
388
+ /** Scale to unit length. Zero-length vectors are left untouched and warn (throttled). Mutates and returns `this`. */
243
389
  normalize(): Vec2;
390
+ /** Scale so `|x| + |y| === 1`. Zero-length vectors are left untouched. Mutates and returns `this`. */
244
391
  normalizeManhattan(): Vec2;
392
+ /** Per-axis remainder (JavaScript `%`, sign follows the dividend). Mutates and returns `this`. */
245
393
  rem(v: Vector2): Vec2;
246
394
  rem(x: number, y?: number): Vec2;
395
+ /** Round each component to the nearest integer. Mutates and returns `this`. */
247
396
  round(): Vec2;
397
+ /** Per-axis subtract. Mutates and returns `this`. */
248
398
  sub(v: Vector2): Vec2;
249
399
  sub(x: number, y?: number): Vec2;
400
+ /** Angle in radians. No arg: angle of `this` from origin. With `other`: angle from `this` toward `other`. */
250
401
  angle(other?: Vector2): number;
402
+ /** Euclidean distance to `other`. */
251
403
  distance(other: Vector2): number;
404
+ /** Manhattan distance (`|dx| + |dy|`) to `other`. */
252
405
  distanceManhattan(other: Vector2): number;
406
+ /** Dot product with `other`. */
253
407
  dotProduct(other: Vector2): number;
408
+ /** `true` when both components are finite (rules out `NaN` and `±Infinity`). */
254
409
  isValid(): boolean;
410
+ /** Euclidean magnitude (`sqrt(x² + y²)`). */
255
411
  length(): number;
412
+ /** Manhattan magnitude (`|x| + |y|`). */
256
413
  lengthManhattan(): number;
414
+ /** Larger of the two components. */
257
415
  max(): number;
416
+ /** Smaller of the two components. */
258
417
  min(): number;
418
+ /** Tuple `[x, y]`. */
259
419
  toArray(): [
260
420
  number,
261
421
  number
262
422
  ];
423
+ /** Build a `Rect` with the argument as position and `this` as size. */
263
424
  toRectAddPos(v: Vector2): Rect;
264
425
  toRectAddPos(x: number, y?: number): Rect;
426
+ /** Build a `Rect` with `this` as position and the argument as size. */
265
427
  toRectAddSize(v: Vector2): Rect;
266
428
  toRectAddSize(x: number, y?: number): Rect;
429
+ /** Debug string like `"Vec2 [x: 1, y: 2]"`. */
267
430
  toString(): string;
431
+ /** New `Vec2` with the same components. */
268
432
  clone(): Vec2;
433
+ /** Approximate equality (within `approxEqual` tolerance). Scalar broadcasts. */
269
434
  equals(v: Vector2): boolean;
270
435
  equals(x: number, y?: number): boolean;
271
436
  private calculate;
272
437
  private concat;
273
438
  private getValues;
274
439
  }
440
+ /** A named animation: a list of frame sprites and the per-frame `timing` (seconds). */
275
441
  export interface SpriteAnimation {
442
+ /** Mark this animation as the default — played by {@link Animator.reset} and right after {@link Animator.add} when registered. At most one per Animator. */
276
443
  default?: boolean;
444
+ /** Unique identifier. Pass to {@link Animator.play}. */
277
445
  name: string;
446
+ /** Frame images in playback order. Uniform size is assumed within a single animation. */
278
447
  sprites: HTMLCanvasElement[] | HTMLImageElement[];
448
+ /** Seconds each frame stays visible before advancing. */
279
449
  timing: number;
280
450
  }
451
+ /** Fires once after the last frame of an animation plays. Cleared after firing. */
281
452
  export type onEndType = () => void;
453
+ /** Map of `frameIndex → callback`. The callback for a given frame fires once when that frame becomes active, then is removed from the map. */
282
454
  export type onFrameType = Record<number, () => void>;
455
+ /** Minimum shape an entity must satisfy to be animated by {@link Animator}. */
283
456
  export interface BaseEntity {
457
+ /** Top-left position used as the draw anchor. */
284
458
  pos: Vec2;
459
+ /** Optional horizontal anchor offset for flipped frames. Defaults to the current sprite width on first render. */
285
460
  flipX?: number;
286
461
  }
462
+ /**
463
+ * Sprite-sheet animator. Hosts a list of named animations (registered via {@link add} / {@link addAnimation}) and drives them per-frame from `update(dt)`. Pre-renders each frame to a 2×-wide cached canvas keyed by `${namespace}.${animationName}` — so multiple entities sharing a `namespace` reuse the same rendered images.
464
+ *
465
+ * Use {@link play} to switch animations, {@link playOnce} for one-shot animations that fall back to the previous one, and {@link Animator.onEnd}/`onFrame` callbacks for frame- or animation-end hooks.
466
+ */
287
467
  export declare class Animator {
288
468
  private static spriteCache;
289
469
  /**
290
470
  * Drop cached rendered sprites. Pass a namespace to evict only that prefix; omit to clear all.
291
471
  */
292
472
  static clearSpriteCache(namespace?: string): void;
473
+ /** When `false`, {@link update} is a no-op. Set automatically to `false` after a single-frame animation finishes and via {@link reset} when no default animation exists. */
293
474
  active: boolean;
475
+ /** Current rendered frame (after flip processing). Pulled from the cache by {@link setImage} every time the frame advances. */
294
476
  image: HTMLCanvasElement;
477
+ /** Index of the current frame within the current animation's `sprites` array. */
295
478
  imageId: number;
479
+ /** Flip the rendered sprite horizontally. Caches a separate "flipped" bucket so toggling is cheap. */
296
480
  lookLeft: boolean;
481
+ /** One-shot callback that fires when the current animation's last frame finishes. Cleared after firing. */
297
482
  onEnd: onEndType | undefined;
483
+ /** Current sprite's `(width, height)`. Updated by {@link setImage}. */
298
484
  size: Vec2;
299
485
  private animations;
300
486
  private currentAnimation;
@@ -304,73 +490,195 @@ export declare class Animator {
304
490
  private onFrame?;
305
491
  private playVersion;
306
492
  private timer;
493
+ /** The currently-playing animation. `undefined` if no animations have been added yet. */
307
494
  get current(): SpriteAnimation;
308
495
  /**
309
- * We store rendered images in a cache.
310
- * So if you have the same entity multiple times, the images get computed once and then shared.
311
- * This reduces overhead and frees time up for other important computations.
312
- *
313
- * `namespace` is the cache key prefix for these rendered images.
314
- * So pass a different key for different Animators; otherwise, you will see wrong images.
496
+ * Bind to `entity` and stamp `namespace` as the prefix for cache keys (`${namespace}.${animationName}`). **Use distinct namespaces for animators whose sprite sets differ** — sharing a namespace across mismatched sprite sets serves the wrong cached frames.
315
497
  */
316
498
  constructor(entity: BaseEntity, namespace: string);
499
+ /** Blit the current cached frame at `entity.pos + offset`, shifted left by `size.x` to compensate for the 2×-wide cache canvas. */
317
500
  draw(context: CanvasRenderingContext2D, offset?: Vec2): void;
501
+ /** Advance the timer; when it crosses `current.timing`, step to the next frame, fire any `onFrame[index]` callback, and on rollover fire `onEnd` and queue `lastPlayed` (set by {@link playOnce}). No-op when {@link active} is `false`. */
318
502
  update(dt: number): void;
503
+ /** Register a new animation. Logs an error (but still registers) if `defaultAnim` is true while another default already exists, or if `name` collides with an existing animation. Auto-plays the new animation when `defaultAnim` is `true`. */
319
504
  add(name: string, sprites: HTMLCanvasElement[] | HTMLImageElement[], timing: number, defaultAnim?: boolean): void;
505
+ /** Convenience wrapper around {@link add} that takes a packed {@link SpriteAnimation}. `defaultAnim` is OR'd with `anim.default`. */
320
506
  addAnimation(anim: SpriteAnimation, defaultAnim?: boolean): void;
507
+ /** Draw the current frame rotated by `angle` radians around a sprite-relative pivot (75% width, 50% height). Uses `setTransform` and resets the transform on exit. */
321
508
  drawRotated(context: CanvasRenderingContext2D, angle: number, offset?: Vec2): void;
509
+ /** Switch to the named animation, rewinding timer and frame index. Optionally register `onEnd` (fires after the last frame) and `onFrame` (frame-indexed callbacks). Any previous {@link Animator.onEnd} fires before the new one is set. Throws if `name` isn't registered. */
322
510
  play(name: string, onEnd?: onEndType, onFrame?: onFrameType): void;
511
+ /** {@link play} only if `name` isn't already the current animation. Returns `true` if it started a new playback, `false` if it was already playing. */
323
512
  playIfNot(name: string, onEnd?: onEndType, onFrame?: onFrameType): boolean;
513
+ /** Queue an animation to play once the current one finishes. Pass `undefined` to cancel the queue. Used internally by {@link playOnce}. */
324
514
  playNextOnce(name: string | undefined): void;
325
515
  /**
326
516
  * Play `name` once, then return to the previously-playing animation. Calling with the currently-playing name re-loops it indefinitely (lastPlayed restores to itself).
327
517
  */
328
518
  playOnce(name: string, onEnd?: onEndType, onFrame?: onFrameType): void;
519
+ /** Randomize the frame timer to a value in `[0, current.timing)`. Useful when spawning many instances of the same animation to break phase lockstep. */
329
520
  randomTimer(): void;
521
+ /** Drop every registered animation and clear this instance's cached frames. {@link active} resets to `true`; pending callbacks are cleared. */
330
522
  removeAllAnimations(): void;
523
+ /** Switch back to the default animation if one was registered (marked via `defaultAnim`); otherwise just stop animating ({@link active} = `false`). */
331
524
  reset(): void;
525
+ /** `true` when `name` matches the currently-playing animation. */
332
526
  isPlaying(name: string): boolean;
333
527
  /**
334
528
  * Update `image` and `size` from the current sprite. Assumes uniform sprite size within an animation. Caches rendered canvases per (animation, frame, lookLeft) — if you mutate `entity.flipX` after first render, call `removeAllAnimations()` or recreate the Animator to invalidate.
335
529
  */
336
530
  protected setImage(): void;
337
531
  }
532
+ /** Button indices for the standard gamepad mapping (W3C Gamepad spec). Use as the index into {@link Controller.buttons}. */
533
+ export declare const CONTROLLER_KEYS: {
534
+ /** Bottom face button — `A` on Xbox, `×` on PlayStation. */
535
+ readonly A: 0;
536
+ /** Right face button — `B` on Xbox, `○` on PlayStation. */
537
+ readonly B: 1;
538
+ /** Left face button — `X` on Xbox, `□` on PlayStation. */
539
+ readonly X: 2;
540
+ /** Top face button — `Y` on Xbox, `△` on PlayStation. */
541
+ readonly Y: 3;
542
+ /** Left bumper / shoulder. */
543
+ readonly LB: 4;
544
+ /** Right bumper / shoulder. */
545
+ readonly RB: 5;
546
+ /** Left trigger. The digital pressed-state lives here; the analog value is on the underlying `Gamepad.buttons[6].value`. */
547
+ readonly LT: 6;
548
+ /** Right trigger. The digital pressed-state lives here; the analog value is on the underlying `Gamepad.buttons[7].value`. */
549
+ readonly RT: 7;
550
+ /** Back / Select / Share. */
551
+ readonly SELECT: 8;
552
+ /** Start / Options / Menu. */
553
+ readonly START: 9;
554
+ /** Left stick click (L3). */
555
+ readonly LEFT_STICK: 10;
556
+ /** Right stick click (R3). */
557
+ readonly RIGHT_STICK: 11;
558
+ /** D-pad up. */
559
+ readonly UP: 12;
560
+ /** D-pad down. */
561
+ readonly DOWN: 13;
562
+ /** D-pad left. */
563
+ readonly LEFT: 14;
564
+ /** D-pad right. */
565
+ readonly RIGHT: 15;
566
+ /** Guide / Home / PS button. Not exposed by all browsers. */
567
+ readonly GUIDE: 16;
568
+ };
569
+ /**
570
+ * Gamepad input. The first connected gamepad becomes "our" gamepad; later ones are ignored until ours disconnects. Connection / disconnection fires {@link EventSystem} `"inputControllerConnected"` / `"inputControllerDisconnected"`.
571
+ *
572
+ * Poll {@link buttons} (indexed via {@link CONTROLLER_KEYS}) and {@link poll} from your `update`. State is cleared on `window` blur and on disconnect so held buttons / non-neutral sticks don't stay live across focus loss. Visualizing is not the controller's responsibility — see {@link ControllerCursor} for the built-in on-screen visualization, or read {@link poll} directly to drive your own.
573
+ *
574
+ * Logs to `console.error` if the browser doesn't expose the Gamepad API.
575
+ */
576
+ export declare class Controller {
577
+ /** Pressed-state per button, indexed in the same order as the underlying `Gamepad.buttons`. Index with {@link CONTROLLER_KEYS}. Updated by {@link poll}; empty until the first non-cached poll. */
578
+ buttons: boolean[];
579
+ private axes;
580
+ private index;
581
+ private lastTime;
582
+ constructor();
583
+ /** Read the current gamepad state and return one {@link Vec2} per stick pair with a circular deadzone applied (`0.25` inner radius, output magnitude clamped to `[0, 1]`). The returned array (and each `Vec2` in it) is reused across calls — clone if you need to retain. Also refreshes {@link buttons}. Returns the cached array unchanged when the gamepad timestamp hasn't advanced. */
584
+ poll(): Vec2[];
585
+ /** Clear {@link buttons} and the cached stick axes. Called automatically on `window` blur and on gamepad disconnect. */
586
+ reset(): void;
587
+ /** Trigger a 400 ms full-strength dual-rumble pulse. Returns `false` when no gamepad is connected or the pad has no `vibrationActuator`; `true` when the effect was dispatched. */
588
+ vibrate(): boolean;
589
+ private getGamepad;
590
+ }
591
+ /**
592
+ * On-screen crosshairs driven by a {@link Controller}'s analog sticks. Each anchor in `sticks` gets its own crosshair that follows the corresponding stick's deflection with frame-rate-independent exponential smoothing (50 ms half-life). The caller owns the anchor positions and the crosshair image — any `CanvasImageSource` works (a loaded `HTMLImageElement`, a procedurally-drawn `HTMLCanvasElement`, an `ImageBitmap`, …).
593
+ *
594
+ * {@link update} polls the controller for you; don't call {@link Controller.poll} again from the same `update` step.
595
+ */
596
+ export declare class ControllerCursor {
597
+ private controller;
598
+ private crosshair;
599
+ private range;
600
+ private sticks;
601
+ /**
602
+ * @param controller Gamepad input source.
603
+ * @param crosshair Drawn at each cursor position via `CanvasRenderingContext2D.drawImage`. The image's top-left is the draw origin — center the visible reticle inside the image, or offset the anchors to compensate.
604
+ * @param sticks Anchor positions, one per stick to track. Cloned at construction so caller mutation is harmless.
605
+ * @param range Max pixel deflection from anchor at full stick. Default `80`.
606
+ */
607
+ constructor(controller: Controller, crosshair: CanvasImageSource, sticks: Vec2[], range?: number);
608
+ /** Draw a crosshair at `anchor + offset` for each tracked stick. */
609
+ draw(context: CanvasRenderingContext2D): void;
610
+ /** Pull fresh stick state via {@link Controller.poll} and smooth each crosshair's offset toward `stickAxis * range`. Frame-rate independent — 50 ms to cover half the remaining distance. */
611
+ update(dt: number): void;
612
+ }
613
+ /**
614
+ * Single particle drawn as a filled circle. The constructor seeds a random velocity (random angle, per-axis speed in `[50, 150]` px/s) and a random `maxLifeTime` in `[0.5, 1.5]` s — spawn many at once for spark/dust effects. Each tick `update(dt)` advances `lifetime` and `pos`; the particle is "dead" when {@link alive} flips to `false`.
615
+ */
338
616
  export declare class Particle {
617
+ /** CSS color string passed to `context.fillStyle` in {@link draw}. */
339
618
  protected color: string;
619
+ /** Accumulated time (seconds) since spawn or last {@link resetLifetime}. */
340
620
  protected lifetime: number;
621
+ /** Lifetime cap in seconds, randomized to `[0.5, 1.5]` at construction. */
341
622
  protected maxLifeTime: number;
623
+ /** Top-left position. Cloned from the constructor arg so the caller's `Vec2` isn't aliased. */
342
624
  protected pos: Vec2;
625
+ /** Circle radius (pixels). */
343
626
  protected size: number;
627
+ /** Velocity in px/s. Seeded randomly by the constructor (random angle, random magnitude per axis). */
344
628
  protected vel: Vec2;
629
+ /** Backing storage for the {@link rect} getter. Subclasses can read it; the public-facing accessor is {@link rect}. */
345
630
  protected _rect: Rect;
631
+ /** `false` once {@link lifetime} reaches {@link maxLifeTime}. Owners typically filter dead particles out of their list each frame, or call {@link resetLifetime} to recycle. */
346
632
  get alive(): boolean;
633
+ /** Read-only AABB tracking `pos` and the particle's `size`. Recomputed each {@link update}. */
347
634
  get rect(): Readonly<Rect>;
348
635
  constructor(pos: Vec2, color: string, size?: number);
636
+ /** Fill a circle at `pos + offset` using {@link color}. `offset` is useful for shifting by a camera/world transform without mutating `pos`. */
349
637
  draw(context: CanvasRenderingContext2D, offset?: Vec2): void;
638
+ /** Integrate lifetime, position, and bounding rect. */
350
639
  update(dt: number): void;
640
+ /** Recycle a dead particle by subtracting `maxLifeTime` from `lifetime` — preserves any overshoot so a pool of pre-allocated particles can stay phase-stable across loops. Note this doesn't re-randomize `vel` or `pos`; mutate those externally if you want a fresh trajectory. */
351
641
  resetLifetime(): void;
352
642
  }
643
+ /**
644
+ * A self-propelled sprite — `update(dt)` advances `pos` along `vel * speed`, accumulates `lifetime`, and flips {@link alive} to `false` once `lifetime >= maxLifetime`. The image is pre-baked to a rotated canvas matching the velocity direction; call {@link rebuildRotation} after re-aiming.
645
+ *
646
+ * The `T` generic types the optional {@link payload} so callers can attach typed metadata (damage, owner, etc.) without losing inference.
647
+ */
353
648
  export declare class Projectile<T = unknown> {
649
+ /** Seconds after which {@link alive} flips to `false`. Defaults to `Infinity` — no natural expiry. */
354
650
  maxLifetime: number;
651
+ /** Caller-supplied data. Typed via the class generic so consumers can read `projectile.payload` without casting. */
355
652
  payload?: T;
653
+ /** Magnitude multiplier applied to `vel` each update: `pos += vel * speed * dt`. Pass a unit-length `vel` to make this read as "pixels per second". */
356
654
  speed: number;
655
+ /** Current pre-rotated sprite. Re-baked by {@link rebuildRotation} from the un-rotated `originalImage`. */
357
656
  protected image: HTMLCanvasElement;
657
+ /** Accumulated time (seconds). Drives the {@link alive} check against {@link maxLifetime}. */
358
658
  protected lifetime: number;
659
+ /** Top-left position. Cloned from the constructor arg so the caller's `Vec2` isn't aliased. */
359
660
  protected pos: Vec2;
661
+ /** Current sprite rotation in radians, kept in sync with `vel` by {@link rebuildRotation}. */
360
662
  protected rotation: number;
663
+ /** Velocity direction vector. Multiplied by {@link speed} each update — pass a unit vector for `speed`-as-px-per-second semantics. Cloned from the constructor arg. */
361
664
  protected vel: Vec2;
665
+ /** Backing storage for the {@link rect} getter. Subclasses can read it; mutate via `pos`/{@link rebuildRotation} instead of touching it directly. */
362
666
  protected _rect: Rect;
363
667
  private originalImage;
668
+ /** `false` once {@link lifetime} reaches {@link maxLifetime}. Owners typically filter dead projectiles out of their list each frame. */
364
669
  get alive(): boolean;
670
+ /** Read-only AABB tracking `pos` and the (rotated) image size. Recomputed in {@link update} and {@link rebuildRotation}. */
365
671
  get rect(): Readonly<Rect>;
366
672
  constructor(pos: Vec2, image: HTMLCanvasElement, vel?: Vec2);
673
+ /** Blit the pre-rotated image at `pos + offset`. `offset` is useful for shifting by a camera/world transform without mutating `pos`. */
367
674
  draw(context: CanvasRenderingContext2D, offset?: Vec2): void;
675
+ /** Integrate motion and advance lifetime. Doesn't re-bake the rotation — call {@link rebuildRotation} after mutating `vel`. */
368
676
  update(dt: number): void;
369
677
  /**
370
- * Allocates a fresh rotated canvas on each call — no internal cache.
371
- * Caching by quantized rotation could be a feature when projectiles need to re-aim every tick (homing/seeking).
678
+ * Re-bake the sprite to match the current `vel` direction (rotation = `atan2(vel.y, vel.x)`) and update `rect` to the new bounds. Allocates a fresh rotated canvas every call — no internal cache, so heavy re-aiming (homing/seeking) is a candidate for adding quantized caching.
372
679
  */
373
680
  rebuildRotation(): void;
681
+ /** Force {@link alive} to `false` immediately (sets `lifetime` past `maxLifetime`). Use when the projectile should die on collision/impact, not from natural expiry. */
374
682
  remove(): void;
375
683
  }
376
684
  declare global {
@@ -485,8 +793,11 @@ declare global {
485
793
  toImage(): Promise<HTMLImageElement>;
486
794
  }
487
795
  }
796
+ /** A `<canvas>` element paired with its 2D rendering context. Returned by {@link createNewCanvas} / {@link getCanvasConstruct} and inherited by {@link CanvasHolder}. */
488
797
  export interface CanvasConstruct {
798
+ /** The `<canvas>` element. */
489
799
  canvas: HTMLCanvasElement;
800
+ /** Its 2D rendering context. */
490
801
  context: CanvasRenderingContext2D;
491
802
  }
492
803
  /**
@@ -523,118 +834,234 @@ export declare function splitSpriteSheet(img: HTMLCanvasElement, elementsX: numb
523
834
  * `removeLowerThan` / `removeHigherThan` drop entries outside the range; `0` disables either bound.
524
835
  */
525
836
  export declare function getUsedColors(image: HTMLCanvasElement, pixelAmount?: number, removeLowerThan?: number, removeHigherThan?: number): Map<string, number>;
837
+ /** Role tags for canvases passed to {@link CanvasManager.setupCanvas}. Only `MAIN` is enforced (exactly one); the others are free-form labels you can use to group background/overlay canvases. */
526
838
  export declare const CANVAS_TYPES: {
839
+ /** Catch-all tag for canvases without a specific role. */
527
840
  readonly ANY: symbol;
841
+ /** Generic placeholder tag — distinct from `ANY` so consumers can differentiate. */
528
842
  readonly DEFAULT: symbol;
843
+ /** Background canvas (drawn behind the main one). */
529
844
  readonly BACKGROUND: symbol;
845
+ /** Primary render target. Exactly one canvas must be registered with this type before {@link CanvasManager.finishSetup}. */
530
846
  readonly MAIN: symbol;
531
847
  };
848
+ /** Entry stored in {@link CanvasManager.canvasHolder} for each registered canvas. */
532
849
  export interface CanvasHolder extends CanvasConstruct {
850
+ /** Selector the canvas was registered under (also the map key). */
533
851
  id: string;
852
+ /** Whether this canvas participates in {@link CanvasManager.resize} (rescaled to fit the window while preserving its buffer aspect ratio). */
534
853
  resize: boolean;
854
+ /** Role tag — one of {@link CANVAS_TYPES}. */
535
855
  type: symbol;
536
856
  }
857
+ /**
858
+ * Tracks registered canvases and exposes the main 2D context. The width/height accessors and `size` getter all refer to the **buffer** dimensions (the canvas's `width`/`height` attributes — drawing-space pixels), while {@link resizedSize} and {@link ratio} describe the **display** size after CSS scaling.
859
+ *
860
+ * Owned by `Game` (`game.canman`). Lifecycle: subclass registers canvases via {@link setupCanvas} in its constructor, then `preInit()` calls {@link finishSetup}.
861
+ */
537
862
  export declare class CanvasManager {
863
+ /** Cached `getBoundingClientRect()` of the main canvas. Refreshed in {@link resize}. Used to map pointer client coords into canvas space. */
538
864
  canvasBoundingClientRect: DOMRect;
865
+ /** Registry of every {@link setupCanvas}-registered canvas, keyed by selector. */
539
866
  canvasHolder: Record<string, CanvasHolder>;
867
+ /** Display-to-buffer scale factor after the last {@link resize} (`displayWidth / bufferWidth`). `1` until the first resize. */
540
868
  ratio: number;
869
+ /** Display (CSS-pixel) size of the main canvas after the last {@link resize}. Independent of the buffer dimensions in {@link width}/{@link height}. */
541
870
  resizedSize: Vec2;
542
871
  private mainHolder;
872
+ /** Main canvas element (the one registered with `CANVAS_TYPES.MAIN`). */
543
873
  get canvas(): HTMLCanvasElement;
874
+ /** Main canvas 2D rendering context. */
544
875
  get canvasContext(): CanvasRenderingContext2D;
876
+ /** Main canvas **buffer** height (the drawing surface, not the CSS display size). */
545
877
  get height(): number;
878
+ /** Main canvas **buffer** height (the drawing surface, not the CSS display size). */
546
879
  set height(height: number);
880
+ /** Main canvas buffer dimensions as a new `Vec2`. */
547
881
  get size(): Vec2;
882
+ /** Main canvas **buffer** width (the drawing surface, not the CSS display size). */
548
883
  get width(): number;
884
+ /** Main canvas **buffer** width (the drawing surface, not the CSS display size). */
549
885
  set width(width: number);
886
+ /** Finalize the canvas registry. Called once by `Game.preInit()`. Validates that exactly one `CANVAS_TYPES.MAIN` canvas is registered and that its buffer is non-zero, caches its bounding rect, and wires the `"resized"` listener if `Settings.enableResize`. Throws on duplicate calls or invalid registry state. */
550
887
  finishSetup(): void;
888
+ /** Rescale every opt-in canvas (`holder.resize === true`) to fit the window while preserving its buffer aspect ratio. Updates `style.width`/`style.height` only — buffer dimensions don't change. Refreshes {@link canvasBoundingClientRect}, {@link resizedSize}, and {@link ratio} from the main canvas. */
551
889
  resize(): void;
890
+ /** Set the main context's `font` to `${size}px "${font}"`. Defaults the family to `Settings.font`. */
552
891
  setFontSize(size: number, font?: string): void;
892
+ /** Register a canvas at `selector` with the given role tag. Initializes its context (`fillStyle`/`strokeStyle` = white, font = `12px Arial`) and returns the {@link CanvasHolder}. `resize` defaults to `Settings.enableResize`. Throws if the selector doesn't match an element or has already been registered. */
553
893
  setupCanvas(canvasType: symbol, selector: string, resize?: boolean): CanvasHolder;
554
894
  }
895
+ /** rAF gaps larger than this (s) reset the accumulator instead of running catch-up steps — keeps a backgrounded tab or paused debugger from fast-forwarding the simulation on resume. */
896
+ export declare const MAX_DT_SECONDS = 0.25;
897
+ /** Hard cap on update steps per rendered frame. If simulation can't keep up, it falls behind in `levelTime` rather than blocking the main thread. */
898
+ export declare const MAX_STEPS_PER_FRAME = 5;
899
+ /**
900
+ * Fixed-step game loop. Owned by `Game` (`game.gameloop`) — usually started automatically by `preInit()` when `Settings.autoloop` is `true`. Each rendered frame:
901
+ *
902
+ * 1. Accumulates real time
903
+ * 2. Runs `game.update(Settings.fps)` zero-or-more times until the accumulator is drained (bounded by {@link MAX_STEPS_PER_FRAME} to avoid runaway catch-up)
904
+ * 3. Clears the canvas (per `Settings.doNotClear` / `Settings.useClearRect`) and calls `game.draw(context)`
905
+ *
906
+ * Fires the {@link EventSystem} `"gameloopStopped"` event when teardown completes (not when {@link stopLoop} is called).
907
+ */
555
908
  export declare class Gameloop {
909
+ /** Simulation time in milliseconds. Advances by `Settings.fps * 1000` per update step, so it reflects simulated time, not wall-clock — paused/dropped frames don't add. Use this for time-driven spawning, animations, etc. */
556
910
  levelTime: number;
557
911
  private _isLooping;
558
912
  private accumulator;
559
913
  private game;
560
914
  private stop;
915
+ /** `true` while the rAF callback is registered. Goes `false` only after the final frame fires the `"gameloopStopped"` event. */
561
916
  get isLooping(): boolean;
562
917
  constructor(game: Game);
918
+ /** Begin the rAF loop. Throws if {@link stopLoop} was called but teardown hasn't completed yet — wait for the `"gameloopStopped"` event before restarting. */
563
919
  startLoop(): void;
920
+ /** Request that the loop stop on its next tick. Asynchronous — the loop tears down on the following frame and dispatches `"gameloopStopped"` when done. */
564
921
  stopLoop(): void;
565
922
  private draw;
566
923
  private looper;
567
924
  }
925
+ /** `KeyboardEvent.code` constants for the keys Gleam tracks by name. Use as the argument to {@link Keyboard.isPressed} / {@link Keyboard.stopPress} or as a string key into {@link Keyboard.keys}. */
568
926
  export declare const KEYBOARD_KEYS: {
927
+ /** Digit row `0`. */
569
928
  readonly KEY_0: "Digit0";
929
+ /** Digit row `1`. */
570
930
  readonly KEY_1: "Digit1";
931
+ /** Digit row `2`. */
571
932
  readonly KEY_2: "Digit2";
933
+ /** Digit row `3`. */
572
934
  readonly KEY_3: "Digit3";
935
+ /** Digit row `4`. */
573
936
  readonly KEY_4: "Digit4";
937
+ /** Digit row `5`. */
574
938
  readonly KEY_5: "Digit5";
939
+ /** Digit row `6`. */
575
940
  readonly KEY_6: "Digit6";
941
+ /** Digit row `7`. */
576
942
  readonly KEY_7: "Digit7";
943
+ /** Digit row `8`. */
577
944
  readonly KEY_8: "Digit8";
945
+ /** Digit row `9`. */
578
946
  readonly KEY_9: "Digit9";
947
+ /** Letter `A`. */
579
948
  readonly KEY_A: "KeyA";
949
+ /** Letter `B`. */
580
950
  readonly KEY_B: "KeyB";
951
+ /** Letter `C`. */
581
952
  readonly KEY_C: "KeyC";
953
+ /** Letter `D`. */
582
954
  readonly KEY_D: "KeyD";
955
+ /** Down arrow. */
583
956
  readonly KEY_DOWN: "ArrowDown";
957
+ /** Letter `E`. */
584
958
  readonly KEY_E: "KeyE";
959
+ /** Enter / Return. */
585
960
  readonly KEY_ENTER: "Enter";
961
+ /** Escape. Note: in `Settings.debug` mode this stops the gameloop. */
586
962
  readonly KEY_ESCAPE: "Escape";
963
+ /** Letter `F`. */
587
964
  readonly KEY_F: "KeyF";
965
+ /** Letter `G`. */
588
966
  readonly KEY_G: "KeyG";
967
+ /** Letter `H`. */
589
968
  readonly KEY_H: "KeyH";
969
+ /** Letter `I`. */
590
970
  readonly KEY_I: "KeyI";
971
+ /** Letter `J`. */
591
972
  readonly KEY_J: "KeyJ";
973
+ /** Letter `K`. */
592
974
  readonly KEY_K: "KeyK";
975
+ /** Letter `L`. */
593
976
  readonly KEY_L: "KeyL";
977
+ /** Left arrow. */
594
978
  readonly KEY_LEFT: "ArrowLeft";
979
+ /** Letter `M`. */
595
980
  readonly KEY_M: "KeyM";
981
+ /** Letter `N`. */
596
982
  readonly KEY_N: "KeyN";
983
+ /** Letter `O`. */
597
984
  readonly KEY_O: "KeyO";
985
+ /** Letter `P`. */
598
986
  readonly KEY_P: "KeyP";
987
+ /** Letter `Q`. */
599
988
  readonly KEY_Q: "KeyQ";
989
+ /** Letter `R`. */
600
990
  readonly KEY_R: "KeyR";
991
+ /** Right arrow. */
601
992
  readonly KEY_RIGHT: "ArrowRight";
993
+ /** Letter `S`. */
602
994
  readonly KEY_S: "KeyS";
995
+ /** Space bar. */
603
996
  readonly KEY_SPACE: "Space";
997
+ /** Letter `T`. */
604
998
  readonly KEY_T: "KeyT";
999
+ /** Tab. */
605
1000
  readonly KEY_TAB: "Tab";
1001
+ /** Letter `U`. */
606
1002
  readonly KEY_U: "KeyU";
1003
+ /** Up arrow. */
607
1004
  readonly KEY_UP: "ArrowUp";
1005
+ /** Letter `V`. */
608
1006
  readonly KEY_V: "KeyV";
1007
+ /** Letter `W`. */
609
1008
  readonly KEY_W: "KeyW";
1009
+ /** Letter `X`. */
610
1010
  readonly KEY_X: "KeyX";
1011
+ /** Letter `Y`. */
611
1012
  readonly KEY_Y: "KeyY";
1013
+ /** Letter `Z`. */
612
1014
  readonly KEY_Z: "KeyZ";
613
1015
  };
1016
+ /**
1017
+ * Keyboard state. Wired into `Game` automatically. The preferred way to consume input is to poll {@link isPressed} from `update` — game input is held-state-based ("is W held this frame?"), and combining with {@link stopPress} handles one-shot actions cleanly. The {@link EventSystem} `"inputKeyboard"` event (payload: `(keys, code, pressed)`) is available for cases that genuinely need edge-triggered handling.
1018
+ *
1019
+ * State is cleared on `window` blur and on `gameloopStopped` so held keys don't stay "pressed" when focus or the loop is lost. In `Settings.debug` mode, pressing Escape stops the gameloop.
1020
+ */
614
1021
  export declare class Keyboard {
1022
+ /** Live map of `KeyboardEvent.code` → pressed state. Codes only appear after the key has been touched at least once; missing codes read as `undefined` (use {@link isPressed} for a safe `boolean`). */
615
1023
  keys: Record<string, boolean>;
616
1024
  constructor(game: Game);
1025
+ /** Mark every tracked key as released. Called automatically on `window` blur and on `gameloopStopped`. */
617
1026
  reset(): void;
1027
+ /** Mark a single key as released — used to consume a press so subsequent ticks don't re-trigger one-shot actions while the key is still held. */
618
1028
  stopPress(code: string): void;
1029
+ /** `true` when `code` is currently held. Safe for untouched keys (returns `false` rather than `undefined`). */
619
1030
  isPressed(code: string): boolean;
620
1031
  }
1032
+ /** Subset of writable Settings fields that {@link Settings.init} accepts (everything except the methods and persisted-storage view). Pass to `super()` when subclassing {@link Game}. */
621
1033
  export type SettingsOverrides = Partial<Omit<typeof Settings, "prototype" | "init" | "setLocalStorage" | "localStorage">>;
1034
+ /** Engine-wide configuration. A static-class singleton — read/write top-level fields directly (`Settings.fps = 1 / 30`). Initialised once via {@link init} from `Game`'s constructor; calling `init` twice throws. */
622
1035
  export declare class Settings {
1036
+ /** Enable smoothing on the main canvas context. Default `false` for crisp pixel art. */
623
1037
  static antialias: boolean;
1038
+ /** Start the gameloop automatically after `init()` resolves. Disable to drive `gameloop.startLoop()` manually. Default `true`. */
624
1039
  static autoloop: boolean;
1040
+ /** CSS color used when {@link useClearRect} is `false`. Default `"#444"`. */
625
1041
  static backgroundColor: string;
1042
+ /** Debug mode: assigns the `Game` instance to `window.game` and lets {@link Keyboard} Escape stop the loop. Default `false`. */
626
1043
  static debug: boolean;
1044
+ /** Skip the per-frame canvas clear. Use for trail/decay effects where you manage clearing yourself. Default `false`. */
627
1045
  static doNotClear: boolean;
1046
+ /** Stretch the main canvas to fill the window on resize while preserving its aspect ratio. Default `true`. */
628
1047
  static enableResize: boolean;
1048
+ /** Default font family for `canman.setFontSize`. Default `"Arial"`. */
629
1049
  static font: string;
1050
+ /** **Seconds per fixed step**, not frames per second — `1 / 60` = 60 Hz, `1 / 30` = 30 Hz. Must be finite and `> 0` or {@link init} throws. */
630
1051
  static fps: number;
1052
+ /** Callback invoked from the `beforeunload` handler when {@link warnBeforeClose} is `true`. Useful for "are you sure?" autosave logic. */
631
1053
  static triedToClose?: () => void;
1054
+ /** Clear the canvas with `clearRect` (transparent) when `true`, or `fillRect` with {@link backgroundColor} when `false`. Default `true`. */
632
1055
  static useClearRect: boolean;
1056
+ /** Show a browser "are you sure?" dialog on tab close. Required for {@link triedToClose} to fire. Default `false`. */
633
1057
  static warnBeforeClose: boolean;
634
1058
  private static initialized;
635
1059
  private static readonly _localStorage;
1060
+ /** Read-only view of the persisted localStorage blob. Writes go through {@link setLocalStorage}. */
636
1061
  static get localStorage(): Readonly<typeof Settings._localStorage>;
1062
+ /** One-time setup — called by `Game`'s constructor with the overrides passed to `super()`. Validates {@link fps}, loads the persisted localStorage blob, derives `language` from `navigator.language`, and wires the close-warning handler if {@link warnBeforeClose}. Throws if called twice or if `fps` isn't a finite positive number. */
637
1063
  static init(overrides: SettingsOverrides, game: Game): void;
1064
+ /** Typed setter for the persisted localStorage blob. Writes both in-memory and to actual `localStorage` (under a single JSON key — `"gleam"`). The only supported way to mutate persisted state. */
638
1065
  static setLocalStorage<K extends keyof typeof Settings._localStorage>(key: K, value: (typeof Settings._localStorage)[K]): void;
639
1066
  }
640
1067
  declare global {
@@ -643,7 +1070,11 @@ declare global {
643
1070
  t(key: string): string;
644
1071
  }
645
1072
  }
1073
+ /** Translation tables keyed by `languageCode → translationKey → text` (e.g. `{ en: { hello: "Hi" }, de: { hello: "Hallo" } }`). */
646
1074
  export type Languages = Record<string, Record<string, string>>;
1075
+ /**
1076
+ * Install the global `window.t(key)` translator. Picks the active language from `Settings.localStorage.language` (seeded from `navigator.language` and overridable via `Settings.setLocalStorage("language", ...)`). Falls back to `defaultLanguage` when the active language isn't registered; returns the key itself when a translation is missing. Both fallback cases log a throttled `console.warn`. Logs `console.error` per missing key during preparation if some languages don't cover every key. Throws if `defaultLanguage` isn't in `languages`.
1077
+ */
647
1078
  export declare function prepareLanguage(languages: Languages, defaultLanguage?: string): void;
648
1079
  declare global {
649
1080
  interface HTMLAudioElement {
@@ -791,141 +1222,168 @@ declare global {
791
1222
  subImage(x: number, y: number, w?: number, h?: number): HTMLCanvasElement;
792
1223
  }
793
1224
  }
1225
+ /**
1226
+ * Abstract base for a Gleam game. Subclass it and implement {@link init}, {@link update}, and {@link draw}. The constructor wires up {@link CanvasManager}, {@link Gameloop}, {@link Keyboard}, and {@link Pointer}; the subclass must register at least one canvas with `canman.setupCanvas(...)` and then call {@link preInit} to start everything.
1227
+ *
1228
+ * **Singleton-per-page.** The framework registers global listeners on `window`/`document` and writes `history.scrollRestoration`; multiple instances on the same page will fight each other.
1229
+ */
794
1230
  export declare abstract class Game {
1231
+ /** Canvas registry + 2D context exposure. Register canvases here from the constructor (`canman.setupCanvas(CANVAS_TYPES.MAIN, "#game")`) before calling {@link preInit}. */
795
1232
  canman: CanvasManager;
1233
+ /** The fixed-step driver. Started automatically by `preInit` when `Settings.autoloop` is `true`. */
796
1234
  gameloop: Gameloop;
1235
+ /** Live keyboard state. See {@link Keyboard}. */
797
1236
  keyboard: Keyboard;
1237
+ /** Live pointer (mouse / pen / touch) state. See {@link Pointer}. */
798
1238
  pointer: Pointer;
799
1239
  private initialized;
800
1240
  constructor(settingOverrides?: SettingsOverrides);
1241
+ /** Render the current frame. Called by {@link Gameloop} after the canvas is cleared. Subclasses must override — the default throws. */
801
1242
  draw(_context: CanvasRenderingContext2D): void;
1243
+ /** Advance the simulation by `dt` seconds (= `Settings.fps`). Called by {@link Gameloop} zero-or-more times per frame depending on real-time accumulation. Subclasses must override — the default throws. */
802
1244
  update(_dt: number): void;
1245
+ /** One-time setup hook (assets, world build) invoked by {@link preInit}. Can be `async`; the loop waits for it to resolve before starting. **Do not call directly** — kick off via {@link preInit} from the constructor. Subclasses must override — the default throws. */
803
1246
  init(): Promise<void>;
1247
+ /** Finalise engine wiring and start the loop. Call once from the subclass constructor *after* registering canvases. Steps: `canman.finishSetup()` → install debounced `window.resize` → reset `gameloop.levelTime` → `await this.init()` (if `doInit`) → dispatch `"resized"` → start the loop if `Settings.autoloop`. Throws if called twice. Pass `doInit: false` to skip the `init()` await (useful for tests). */
804
1248
  protected preInit(doInit?: boolean): Promise<void>;
805
1249
  }
1250
+ /** Button indices that match `PointerEvent.button` and index into {@link Pointer.pressed}. */
806
1251
  export declare const POINTER_KEYS: {
1252
+ /** Primary button (left for right-handers). */
807
1253
  readonly LEFT: 0;
1254
+ /** Middle button / wheel click. */
808
1255
  readonly MIDDLE: 1;
1256
+ /** Secondary button (right for right-handers). */
809
1257
  readonly RIGHT: 2;
1258
+ /** "Back" side button (browser back). */
810
1259
  readonly PREV: 3;
1260
+ /** "Forward" side button. */
811
1261
  readonly FORWARD: 4;
812
1262
  };
1263
+ /**
1264
+ * Pointer (mouse / pen / touch) state. Wired into `Game` automatically. The preferred way to consume input is to subscribe to the {@link EventSystem} `"inputPointer"` event — it fires on every move and button transition, with this `Pointer` instance as the payload. If you need the latest state on a frame boundary instead, poll `game.pointer.posScaled` and `game.pointer.pressed[POINTER_KEYS.LEFT]` from `update`.
1265
+ *
1266
+ * Suppresses the browser context menu on right-click globally.
1267
+ */
813
1268
  export declare class Pointer {
1269
+ /** Dirty bit set to `true` on every move and never cleared by the engine — flip it back to `false` after reading to detect "moved since last check". */
814
1270
  hasMoved: boolean;
1271
+ /** Last raw `PointerEvent` received. `null` until any pointer event fires. Use for properties not surfaced as Vec2/booleans (pressure, pointerType, etc.). */
815
1272
  lastEvent: PointerEvent | null;
1273
+ /** Viewport-space coordinates (`event.clientX/Y` — CSS pixels relative to the page). */
816
1274
  posReal: Vec2;
1275
+ /** Previous tick's {@link posReal}. Subtract for a per-frame delta. */
817
1276
  posRealLast: Vec2;
1277
+ /** Canvas-space coordinates, mapped from the bounding rect into the main canvas's pixel buffer and clamped to its size. This is the position to use for in-game logic. */
818
1278
  posScaled: Vec2;
1279
+ /** Previous tick's {@link posScaled}. */
819
1280
  posScaledLast: Vec2;
1281
+ /** Per-button pressed state. Index with {@link POINTER_KEYS} (e.g. `pressed[POINTER_KEYS.LEFT]`). Sparse — unindexed entries are `undefined`, not `false`. */
820
1282
  pressed: boolean[];
821
1283
  private game;
822
1284
  constructor(game: Game);
1285
+ /** Clear all pressed-button state. Called automatically on `window` blur so held buttons don't stay "pressed" forever when focus is lost. */
823
1286
  reset(): void;
824
1287
  private update;
825
1288
  }
1289
+ /**
1290
+ * Type-safe registry of engine events and their payload tuples. Both {@link EventSystem.addEventListener} and {@link EventSystem.dispatchEvent} are generic over this map, so the listener callback and dispatched args are checked against the declared shape.
1291
+ */
826
1292
  export interface GameEventMap {
1293
+ /** Fired by {@link Gameloop} once teardown completes, after `stopLoop()` is called. */
827
1294
  gameloopStopped: [
828
1295
  ];
1296
+ /** Fired by {@link Controller} when a gamepad is connected. Payload is the native `Gamepad`. */
829
1297
  inputControllerConnected: [
830
1298
  event: Gamepad
831
1299
  ];
1300
+ /** Fired by {@link Controller} when *our* tracked gamepad disconnects. Other gamepads disconnecting are logged but don't dispatch. */
832
1301
  inputControllerDisconnected: [
833
1302
  ];
1303
+ /** Fired by {@link Keyboard} on every key down/up with the live `keys` map, the `code` that changed, and its new pressed state. */
834
1304
  inputKeyboard: [
835
1305
  keys: Record<string, boolean>,
836
1306
  code: string,
837
1307
  pressed: boolean
838
1308
  ];
1309
+ /** Fired by {@link Pointer} on every move and button transition. Payload is the `Pointer` instance — read `posScaled`/`pressed` from it. */
839
1310
  inputPointer: [
840
1311
  pointer: Pointer
841
1312
  ];
1313
+ /** Fired by {@link Game.preInit} once at startup and on every debounced `window.resize` thereafter. The canonical "viewport changed" signal. */
842
1314
  resized: [
843
1315
  ];
844
1316
  }
1317
+ /** Options for {@link EventSystem.addEventListener}. */
845
1318
  export interface EventSystemOptions {
1319
+ /** Auto-dispose the listener after the first dispatch. */
846
1320
  once?: boolean;
1321
+ /** Dispose the listener when the signal aborts. Already-aborted signals make `addEventListener` a no-op. */
847
1322
  signal?: AbortSignal;
848
1323
  }
1324
+ /**
1325
+ * Synchronous, type-safe pub/sub for engine-wide events. Static-only — call as `EventSystem.addEventListener(...)` / `EventSystem.dispatchEvent(...)`. Event names and payloads are constrained by {@link GameEventMap}.
1326
+ *
1327
+ * Guarantees:
1328
+ *
1329
+ * - Listeners registered during a dispatch are deferred to the next dispatch (won't fire in the round that registered them).
1330
+ * - `once` listeners are removed *before* their callback runs, so nested dispatches and throwing callbacks can't double-fire them.
1331
+ * - A throwing callback is caught and logged (throttled per `eventName:message`); siblings still receive the event.
1332
+ */
849
1333
  export declare class EventSystem {
850
1334
  private static eventListener;
851
1335
  private static logListenerError;
852
1336
  private static nextId;
1337
+ /**
1338
+ * Register a listener for `eventName`. Returns a dispose function — the primary teardown path. Multiple disposers (returned, `once`, `signal.abort`) are idempotent. Use {@link EventSystemOptions} for `once` and `signal` behavior.
1339
+ */
853
1340
  static addEventListener<K extends keyof GameEventMap>(eventName: K, callback: (...args: GameEventMap[K]) => void, options?: EventSystemOptions): () => void;
1341
+ /** Synchronously fire `eventName` with the typed payload. Listeners are invoked in registration order; nested dispatches and self-disposing listeners are handled safely. */
854
1342
  static dispatchEvent<K extends keyof GameEventMap>(eventName: K, ...params: GameEventMap[K]): void;
855
1343
  }
1344
+ /** String-valued keys of `CSSStyleDeclaration` — the ones safe to assign a `string` value to via {@link CssProxy}. */
856
1345
  export type CssStyleKey = {
857
1346
  [K in keyof CSSStyleDeclaration]: K extends string ? CSSStyleDeclaration[K] extends string ? K : never : never;
858
1347
  }[keyof CSSStyleDeclaration];
1348
+ /** Setter handed to {@link ShakeType.update} that writes a CSS value. Snapshots the prior value on first write per key so it can be restored on dispose. */
859
1349
  export type CssProxy = (key: CssStyleKey, value: string) => void;
1350
+ /** Shape for a custom shake recipe. */
860
1351
  export interface ShakeType {
1352
+ /** Decay rate applied each frame: `timer -= step * dt`. Higher = shorter shake (3 ≈ ⅓s, 15 ≈ 1/15s). */
861
1353
  step: number;
1354
+ /** Per-frame mutator. `time` decays from `1` to `0` over the shake — multiply your intensity by it for a natural fall-off. */
862
1355
  update: (updateCss: CssProxy, time: number) => void;
863
1356
  }
1357
+ /** Built-in shake recipes. Pass one to {@link Screenshake.shake}. */
864
1358
  export declare const SHAKE_TYPES: {
1359
+ /** ~0.33 s wobble combining a small random rotation with a blur fall-off. */
865
1360
  NORMAL: {
1361
+ /** Decay rate — see {@link ShakeType.step}. */
866
1362
  step: number;
1363
+ /** Per-frame mutator — see {@link ShakeType.update}. */
867
1364
  update(updateCss: CssProxy, time: number): void;
868
1365
  };
1366
+ /** ~0.07 s impact: blur-only fall-off, no rotation. */
869
1367
  FAST: {
1368
+ /** Decay rate — see {@link ShakeType.step}. */
870
1369
  step: number;
1370
+ /** Per-frame mutator — see {@link ShakeType.update}. */
871
1371
  update(updateCss: CssProxy, time: number): void;
872
1372
  };
873
1373
  };
874
1374
  /**
875
- * Only the built-in shake types (NORMAL, FAST) are supported today.
876
- * Letting callers define their own could be a possible feature — impact pulses, slow rumble, directional jolts.
1375
+ * Shake an element by mutating its inline CSS each rAF tick. One shake per instance — re-calling {@link shake} while one is active returns `null`. The returned dispose function (and natural timer expiry) restores every CSS key the shake touched.
1376
+ *
1377
+ * Only the built-in {@link SHAKE_TYPES} (`NORMAL`, `FAST`) are supported today. Letting callers define their own would be a natural extension — impact pulses, slow rumble, directional jolts — since {@link ShakeType} is already public.
877
1378
  */
878
1379
  export declare class Screenshake {
879
1380
  private isShaking;
880
1381
  private shakeType;
881
1382
  private style;
882
1383
  constructor(element: HTMLElement);
883
- /** Best-effort style restore: each key the shake writes is snapshotted on first write and rewritten when the shake ends or is disposed. */
1384
+ /** Start a shake of the given `shakeType`. Returns a dispose function that stops the shake early and restores every CSS key it touched, or `null` if a shake is already active on this instance. Auto-stops and restores when the timer reaches zero. */
884
1385
  shake(shakeType?: ShakeType): null | (() => void);
885
1386
  }
886
- export declare class ControllerCursor {
887
- private axisId;
888
- private controller;
889
- private game;
890
- private pos;
891
- get centerPos(): Vec2;
892
- constructor(controller: Controller, game: Game, axisId: number);
893
- draw(context: CanvasRenderingContext2D): void;
894
- update(dt: number): void;
895
- }
896
- export declare const CONTROLLER_KEYS: {
897
- readonly A: 0;
898
- readonly B: 1;
899
- readonly X: 2;
900
- readonly Y: 3;
901
- readonly LB: 4;
902
- readonly RB: 5;
903
- readonly LT: 6;
904
- readonly RT: 7;
905
- readonly SELECT: 8;
906
- readonly START: 9;
907
- readonly LEFT_STICK: 10;
908
- readonly RIGHT_STICK: 11;
909
- readonly UP: 12;
910
- readonly DOWN: 13;
911
- readonly LEFT: 14;
912
- readonly RIGHT: 15;
913
- readonly GUIDE: 16;
914
- };
915
- export declare class Controller {
916
- buttons: boolean[];
917
- cursors: ControllerCursor[];
918
- private axes;
919
- private index;
920
- private lastTime;
921
- constructor(game: Game);
922
- draw(context: CanvasRenderingContext2D): void;
923
- update(dt: number): void;
924
- reset(): void;
925
- vibrate(): boolean;
926
- stick(index: number): Vec2;
927
- private getGamepad;
928
- }
929
1387
  /**
930
1388
  * Validates URL format and protocol before any side effects.
931
1389
  * Throws on invalid URLs or disallowed protocols.
@@ -986,9 +1444,13 @@ export declare function remove<T>(arr: T[], item: T): void;
986
1444
  * Shuffle array using Fisher-Yates algorithm with custom random signer
987
1445
  */
988
1446
  export declare function shuffle<T>(arr: ReadonlyArray<T>): T[];
1447
+ /** `get`/`set` helpers for CSS custom properties (`--name`) on `:root`. Build via {@link initCSSVariables}. */
989
1448
  export interface CSSVariables {
1449
+ /** The `:root` element these helpers read from / write to. */
990
1450
  root: HTMLElement;
1451
+ /** Read the computed value of `--${name}`. */
991
1452
  get(name: string): string;
1453
+ /** Write `value` to `--${name}` on the root element's inline style. */
992
1454
  set(name: string, value: string): void;
993
1455
  }
994
1456
  /**
@@ -1074,6 +1536,7 @@ export declare function convert1DTo2D(index: number, width: number): Vector2;
1074
1536
  * Convert 2D `(x, y)` coordinates to a 1D index for a grid of the given row width.
1075
1537
  */
1076
1538
  export declare function convert2DTo1D(indexX: number, indexY: number, width: number): number;
1539
+ /** Primitive cell types accepted by {@link generateGrid}'s overload that takes a default value (used so every cell can safely share the same primitive without aliasing). */
1077
1540
  export type GridPrimitive = string | number | boolean | bigint | symbol | null | undefined;
1078
1541
  /**
1079
1542
  * Generate a `height × width` 2D grid; every cell holds `defaultValue`. Restricted to primitives at the type level — for object/array cells use the factory overload to avoid every cell sharing the same reference.
@@ -1178,6 +1641,7 @@ export declare function toDotted(value: number): string;
1178
1641
  * Bounds are swapped if passed in reverse order. Throws when `min ≈ max` (degenerate range, via `approxEqual`).
1179
1642
  */
1180
1643
  export declare function wrapValue(value: number, min: number, max: number): number;
1644
+ /** Conditional helper used by {@link defineMethod}: given `T[K]` is a function, produces a corresponding function type with `this: T` bound to the prototype owner. Non-function members resolve to `never` so prototype patches can't target accessors or fields by mistake. */
1181
1645
  export type Method<T, K extends keyof T> = T[K] extends (...args: infer A) => infer R ? (this: T, ...args: A) => R : never;
1182
1646
  /**
1183
1647
  * Define `name` as a non-enumerable method on `proto`. Carries the declared signature so impl `this` and parameters are inferred from the merged declaration.