// Generated by dts-bundle-generator v9.5.1 /** Entry shape passed to {@link AudioBase.register}. */ export interface RegisterData { /** URL or relative path to the audio file. */ path: string; /** Identifier used by `play(name)` / `fade(name)`. */ name: string; /** Per-song volume override in `[0, 1]`. Falls back to `register`'s `defaultVolume`. */ volume?: number; } /** * 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. */ export declare abstract class AudioBase { /** Registered audio elements keyed by name. Subclasses read this; mutate via {@link register}. */ protected songs: Map; private _enabled; private registered; /** Whether playback is permitted. Setting to `false` invokes {@link stop} immediately. */ get enabled(): boolean; /** Setting to `false` invokes {@link stop} immediately; subclasses (notably {@link Music}) may re-start playback when flipped back to `true`. */ set enabled(value: boolean); constructor(enabled?: boolean); /** 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. */ register(defaultVolume?: number, ...songs: (RegisterData | string)[]): void; /** 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()`. */ stop(): void; private throwOnBadVolume; } /** * Quadratic ease-in (slow start, accelerates). */ export declare function easeIn(t: number): number; /** * Quadratic ease-in-out (accelerates, then decelerates). */ export declare function easeInOut(t: number): number; /** * Quadratic ease-out (fast start, decelerates). */ export declare function easeOut(t: number): number; /** * Identity (constant rate of change). */ export declare function linear(t: number): number; /** Names of the built-in easing curves. Use as a key into {@link EASINGS}. */ export type EasingName = "ease-in" | "ease-in-out" | "ease-out" | "linear"; /** Lookup table from {@link EasingName} to its easing function (`t ∈ [0, 1] → eased t`). */ export declare const EASINGS: Record number>; /** * 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}. * * 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. */ export declare class Music extends AudioBase { private current; private fadeCancel; private last; private next; /** `true` while a fade is in progress OR the current track is actively playing. */ get isPlaying(): boolean; /** The track currently playing — the incoming one during a cross-fade, or `null` when nothing is playing (mirrors {@link isPlaying}). Read `.id` for its registered name; treat as read-only, mutating it desyncs Music. */ get song(): HTMLAudioElement | null; /** Whether music playback is permitted (inherited from {@link AudioBase}). */ get enabled(): boolean; /** Flipping from `false` to `true` while no music is playing auto-starts a fade-in to a random track. */ set enabled(value: boolean); /** * 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. 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. * * Returns `true` if a fade (or single-track loop) was started, `false` for a no-op: when disabled, or when `name` is already the current or incoming track (so calling it repeatedly with the same track won't restart it). */ fade(name?: string | null, fadeTime?: number, easing?: { cur: EasingName; next: EasingName; }): boolean; /** 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}. */ stop(): void; private getRandom; } /** 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}. */ export declare class Sound extends AudioBase { private currentSounds; /** 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. */ play(name: string): Promise; /** Stop and forget every currently-playing clone. Also calls the base-class teardown. */ stop(): void; } /** Components returned by {@link Color.toHSLObject}. */ export interface HSLObject { /** Hue in degrees, `[0, 360]`. */ h: number; /** Saturation in percent, `[0, 100]`. */ s: number; /** Lightness in percent, `[0, 100]`. */ l: number; /** Alpha in `[0, 1]`. */ a: number; } /** * 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. * * **Hue unit gotcha**: {@link fromHSL} takes hue in **degrees** (CSS convention), but {@link hueRotate} takes **radians** (codebase convention). Use `Math.PI` etc. for hueRotate. * * All `to*` methods return CSS-compatible strings; `toHSLObject` returns the components as numbers if you need to mutate them. */ export declare class Color { /** Parse `#rgb`, `#rgba`, `#rrggbb`, or `#rrggbbaa` (case-insensitive, `#` optional). Throws on any other shape or on non-hex characters. */ static fromHex(hex: string): Color; /** 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. */ static fromHSL(h: number, s: number, l: number, a?: number): Color; private _r; private _g; private _b; private _alpha; /** Red channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */ get r(): number; /** Green channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */ get g(): number; /** Blue channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */ get b(): number; /** Alpha channel, `[0, 1]`. Read-only; mutate via {@link set} (pass the fourth arg). */ get alpha(): number; constructor(r: number, g: number, b: number, a?: number); /** 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. */ set(r: number, g: number, b: number, a?: number): this; /** 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`. */ applyMatrix(m1: number, m2: number, m3: number, m4: number, m5: number, m6: number, m7: number, m8: number, m9: number): this; /** Multiply each channel by `factor`. `factor < 1` darkens, `factor > 1` brightens (clamped at 255). Mutates and returns `this`. */ brightness(factor: number): this; /** 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`. */ contrast(factor: number): this; /** Desaturate via the standard luminance-preserving matrix. `value` in `[0, 1]`: `0` is a no-op, `1` is full grayscale. Mutates and returns `this`. */ grayscale(value?: number): this; /** Rotate hue by `radians` (use `Math.PI / 2` etc.). Unlike {@link fromHSL}, this takes radians, not degrees. Mutates and returns `this`. */ hueRotate(radians: number): this; /** Interpolate each channel toward its inverse (`255 - c`). `factor` in `[0, 1]`: `0` is unchanged, `1` is fully inverted. Mutates and returns `this`. */ invert(factor?: number): this; /** Linear blend toward `other`. `amount` in `[0, 1]`: `0` keeps `this`, `1` becomes `other`. Mixes alpha too. Mutates and returns `this`. */ mix(other: Color, amount: number): this; /** Round each RGB channel to the nearest integer. Alpha is untouched. Mutates and returns `this`. */ round(): this; /** 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`. */ saturate(value?: number): this; /** Sepia matrix. `value` in `[0, 1]`: `0` is unchanged, `1` is full sepia. Mutates and returns `this`. */ sepia(value?: number): this; /** Tint or shade. `percent` in `[-1, 1]`: negative shades toward black, positive tints toward white, magnitude is the amount. Mutates and returns `this`. */ shade(percent: number): this; /** CSS hex string. `#rrggbb` when alpha is exactly `1`, `#rrggbbaa` otherwise. Channels are rounded. */ toHex(): string; /** CSS HSL string. `hsl(h, s%, l%)` when alpha is exactly `1`, `hsla(...)` otherwise. Hue is in degrees. */ toHSL(): string; /** HSL(A) components as numbers — see {@link HSLObject}. Use when you need to compute against the values rather than render them as a string. */ toHSLObject(): HSLObject; /** CSS RGB string. `rgb(r, g, b)` when alpha is exactly `1`, `rgba(r, g, b, a)` otherwise. Channels are rounded. */ toRGB(): string; /** New `Color` with the same channels. */ clone(): Color; /** Approximate equality (within `approxEqual` tolerance). Pass `compareAlpha: false` to ignore the alpha channel. */ equals(other: Color, compareAlpha?: boolean): boolean; } /** `[r, g, b]` tuple of integer channel values, each in `[0, 255]`. */ export type RGB = [ number, number, number ]; /** * Convert an `(r, g, b)` triple (0-255) to a `#rrggbb` hex string. Low-level; prefer `Color.toHex()` outside hot per-pixel loops. */ export declare function rgb2hex(red: number, green: number, blue: number): string; /** * Pack `(r, g, b[, a])` channels into a single integer key. RGB are 0-255. Alpha is 0-1 (CSS convention); divide canvas byte-alpha by 255 before passing. Without alpha: 24-bit `RGB`. With alpha: 32-bit `RGBA` (forced unsigned). Useful for fast per-pixel lookups: cheaper than building a hex string. */ export declare function rgb2Int(red: number, green: number, blue: number, alpha?: number): number; /** * Format a packed 24-bit `RGB` integer (see {@link rgb2Int}) as a `#rrggbb` hex string. Low-level; prefer `Color.toHex()` outside hot per-pixel loops. */ export declare function int2hex(int: number): string; /** * Convert a `#rgb` or `#rrggbb` hex string to an `[r, g, b]` integer tuple. Low-level; prefer `Color.fromHex()` outside hot per-pixel loops. Caller must pass a valid hex string. */ export declare function hex2rgb(hex: string): RGB; /** * HSL → RGB channel helper used by `Color.fromHSL`. Inputs are normalized to `[0, 1]`. */ export declare function hue2rgb(p: number, q: number, t: number): number; /** * Random `#rgb` short hex color. */ export declare function randomHex(): string; /** * Random `[r, g, b]` integer tuple; each channel uniform in `[min, max]`. */ export declare function randomRgb(min?: number, max?: number): RGB; /** * Random `#rrggbb` with a random hue at a controlled vividness: saturation is fixed at `s`, lightness is uniform in `[lMin, lMax]` (pass equal bounds for a fixed lightness). Unlike {@link randomHex}, the defaults stay vivid and readable on dark backgrounds instead of landing near-black. Uses the same HSL→RGB math as `Color.fromHSL`. */ export declare function randomHslHex(s?: number, lMin?: number, lMax?: number): string; /** * Used when hue-rotate does not work e.g. on dark images. * * Based on https://codepen.io/sosuke/pen/Pjoqqp * https://stackoverflow.com/questions/42966641/how-to-transform-black-into-any-given-color-using-only-css-filters/43960991#43960991 * * As the result does vary because of a Math.random(), I would suggest console.log some filters, pick nice ones and hardcode them instead: * @example * console.log(colorShifter(randomRgb(1, 10))); */ export declare function colorShifter(rgb: RGB): string; /** Result of a {@link Polygon.collide} call. */ export interface PolygonCollisionResult { /** `true` if the polygons currently overlap. */ intersect: boolean; /** Smallest displacement that would separate the polygons (zero `Vec2` when they're disjoint). */ minimumTranslationVector: Vec2; /** `true` if applying the `velocity` passed to `collide` would put the polygons into contact. */ willIntersect: boolean; } /** * 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. */ export declare class Polygon { /** * 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. * * **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. */ static fromCanvas(canvas: HTMLCanvasElement, detail: number, angle: number): Polygon; /** Regular convex polygon with `edges` vertices, inscribed in a bounding box of `size` (number = square). */ static fromEdges(edges: number, size: Vec2 | number): Polygon; /** Polygon from the four corners of `rect`. */ static fromRect(rect: Rect): Polygon; private _center; private _points; private edges; /** Centroid (mean of vertex positions). Recomputed whenever the vertex set changes. */ get center(): Readonly; /** Read-only view of the current vertex list. Use `addPoint`/`offset`/`rotate` to mutate. */ get points(): Readonly; constructor(...points: Vec2[]); /** Stroke the polygon to `context`, shifted by `offset`. Coordinates are truncated to integers (via `| 0`) for crisp lines. */ draw(context: CanvasRenderingContext2D, offset?: Vec2): void; /** Append a single vertex at `(x, y)`. Mutates and returns `this`. */ addPoint(x: number, y: number): Polygon; /** Append cloned copies of every passed vertex. Mutates and returns `this`. */ addPoints(...points: Vec2[]): Polygon; /** Translate every vertex by `(x, y)`. Mutates and returns `this`. */ offset(x?: number, y?: number): Polygon; /** Rotate by `angle` radians around `pos` (defaults to the centroid). Mutates and returns `this`. */ rotate(angle: number, pos?: Readonly): this; /** 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. */ collide(otherPolygon: Polygon, velocity?: Vec2): PolygonCollisionResult; /** New `Polygon` with the same vertices. */ clone(): Polygon; private update; } /** Derived geometry returned by {@link Rect.sides}. */ export interface Sides { /** Lower edge (`y + h`). */ bottom: number; /** Center point. */ centerPos: Vec2; /** Half the width and height. */ halfSize: Vec2; /** Right edge (`x + w`). */ right: number; } /** Axis-aligned 2D rectangle (`x`, `y`, `w`, `h`). */ export declare class Rect { /** Build from an `HTMLElement` (via `getBoundingClientRect`) or a `DOMRect`. */ static fromBoundingClientRect(rect: DOMRect | HTMLElement): Rect; /** Axis-aligned bounding box of a polygon's points. Throws if the polygon has no points. */ static fromPolygon(polygon: Polygon): Rect; private _x; private _y; private _w; private _h; private _sides; private sideIsDirty; /** Top-left x. */ get x(): number; /** Top-left x. */ set x(value: number); /** Top-left y. */ get y(): number; /** Top-left y. */ set y(value: number); /** Width. */ get w(): number; /** Width. */ set w(value: number); /** Height. */ get h(): number; /** Height. */ set h(value: number); /** Derived sides/center/halfSize. Lazily recomputed after any `x`/`y`/`w`/`h` change. */ get sides(): Readonly; constructor(x?: number, y?: number, w?: number, h?: number); /** 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`. */ inflate(delta: number): Rect; /** Round `x` and `y` to the nearest integer. `w`/`h` are unchanged. Mutates and returns `this`. */ round(): Rect; /** * 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`. */ set(x?: Vector4 | Vector2 | number, y?: number, w?: number, h?: number): Rect; /** AABB-vs-AABB overlap test (inclusive of touching edges). */ collide(rect: Rect): boolean; /** `true` when `rect` is fully inside `this`. */ collideFull(rect: Rect): boolean; /** `true` when `vec` lies inside `this` (inclusive of edges). */ collidePoint(vec: Vector2): boolean; /** Side of `this` that `rect` overlaps from, or `"none"` if disjoint. Useful for picking a bounce axis. */ collideSide(rect: Rect): "none" | "top" | "bottom" | "left" | "right"; /** Top-left corner as a new `Vec2`. */ pos(): Vec2; /** Width and height as a new `Vec2`. */ size(): Vec2; /** Debug string like `"Rect [x: 0, y: 0, w: 10, h: 20]"`. */ toString(): string; /** New `Rect` with the same values. */ clone(): Rect; /** Approximate equality. Pass `withSize: false` to compare position only. */ equals(other: Rect, withSize?: boolean): boolean; } /** Object literal compatible with `Vec2` (e.g. `{ x, y }`). */ export interface Vector2 { /** Horizontal component. */ x: number; /** Vertical component. */ y: number; } /** {@link Vector2} plus `w`/`h` for AABB-style values. */ export interface Vector4 extends Vector2 { /** Width. */ w: number; /** Height. */ h: number; } /** * 2D vector. Scalar args to `set`/`add`/`sub`/`mult`/`div`/`rem`/`mod`/`equals` broadcast to both axes: `vec.add(5)` adds 5 to x and y, `vec.mult(-1)` negates both. Pass `(x, y)` or a `Vector2` for per-axis values. */ export declare class Vec2 { /** Unit vector at angle `rad` (radians), scaled per-axis. `scaleY` defaults to `scaleX`. */ static fromAngle(rad: number, scaleX?: number, scaleY?: number): Vec2; /** Horizontal component. */ x: number; /** Vertical component. */ y: number; constructor(x?: Vector2 | number, y?: number); /** Replace components. Mutates and returns `this`. */ set(v: Vector2): Vec2; set(x: number, y?: number): Vec2; /** Set each component to its absolute value. Mutates and returns `this`. */ abs(): Vec2; /** Per-axis add. Mutates and returns `this`. */ add(v: Vector2): Vec2; add(x: number, y?: number): Vec2; /** Round each component up. Mutates and returns `this`. */ ceil(): Vec2; /** Clamp each axis to its `[min, max]` range. `y` defaults to `x`. Mutates and returns `this`. */ clamp(x: [ number, number ], y?: [ number, number ]): Vec2; /** Per-axis divide. Mutates and returns `this`. */ div(v: Vector2): Vec2; div(x: number, y?: number): Vec2; /** Round each component down. Mutates and returns `this`. */ floor(): Vec2; /** * Apply `callback` to each component (`index` is `0` for x, `1` for y). Mutates and returns `this`. * * @example * ```ts * new Vec2(3.6, -2.1).map(Math.trunc); // Vec2 { x: 3, y: -2 } * new Vec2(2, 5).map((v, i) => v * (i + 1)); // Vec2 { x: 2, y: 10 } * ``` */ map(callback: (value: number, index: number) => number): Vec2; /** Per-axis Euclidean modulo (result sign matches the divisor). Mutates and returns `this`. */ mod(v: Vector2): Vec2; mod(x: number, y?: number): Vec2; /** Per-axis multiply. Mutates and returns `this`. */ mult(v: Vector2): Vec2; mult(x: number, y?: number): Vec2; /** Flip the sign of both components (same as `mult(-1)`). Mutates and returns `this`. */ negate(): Vec2; /** Scale to unit length. Zero-length vectors are left untouched and warn (throttled). Mutates and returns `this`. */ normalize(): Vec2; /** Scale so `|x| + |y| === 1`. Zero-length vectors are left untouched. Mutates and returns `this`. */ normalizeManhattan(): Vec2; /** Per-axis remainder (JavaScript `%`, sign follows the dividend). Mutates and returns `this`. */ rem(v: Vector2): Vec2; rem(x: number, y?: number): Vec2; /** Round each component to the nearest integer. Mutates and returns `this`. */ round(): Vec2; /** Per-axis subtract. Mutates and returns `this`. */ sub(v: Vector2): Vec2; sub(x: number, y?: number): Vec2; /** Angle in radians. No arg: angle of `this` from origin. With `other`: angle from `this` toward `other`. */ angle(other?: Vector2): number; /** Euclidean distance to `other`. */ distance(other: Vector2): number; /** Manhattan distance (`|dx| + |dy|`) to `other`. */ distanceManhattan(other: Vector2): number; /** Dot product with `other`. */ dotProduct(other: Vector2): number; /** `true` when both components are finite (rules out `NaN` and `±Infinity`). */ isValid(): boolean; /** Euclidean magnitude (`sqrt(x² + y²)`). */ length(): number; /** Manhattan magnitude (`|x| + |y|`). */ lengthManhattan(): number; /** Larger of the two components. */ max(): number; /** Smaller of the two components. */ min(): number; /** Tuple `[x, y]`. */ toArray(): [ number, number ]; /** Build a `Rect` with the argument as position and `this` as size. */ toRectAddPos(v: Vector2): Rect; toRectAddPos(x: number, y?: number): Rect; /** Build a `Rect` with `this` as position and the argument as size. */ toRectAddSize(v: Vector2): Rect; toRectAddSize(x: number, y?: number): Rect; /** Debug string like `"Vec2 [x: 1, y: 2]"`. */ toString(): string; /** New `Vec2` with the same components. */ clone(): Vec2; /** Approximate equality (within `approxEqual` tolerance). Scalar broadcasts. */ equals(v: Vector2): boolean; equals(x: number, y?: number): boolean; private calculate; private concat; private getValues; } /** A named animation: a list of frame sprites and the per-frame `timing` (seconds). */ export interface SpriteAnimation { /** Mark this animation as the default — played by {@link Animator.reset} and right after {@link Animator.add} when registered. At most one per Animator. */ default?: boolean; /** Unique identifier. Pass to {@link Animator.play}. */ name: string; /** Frame images in playback order. Uniform size is assumed within a single animation. */ sprites: HTMLCanvasElement[] | HTMLImageElement[]; /** Seconds each frame stays visible before advancing. */ timing: number; } /** Fires once after the last frame of an animation plays. Cleared after firing. */ export type onEndType = () => void; /** Map of `frameIndex → callback`. The callback for a given frame fires once when that frame becomes active, then is removed from the map. */ export type onFrameType = Record void>; /** Minimum shape an entity must satisfy to be animated by {@link Animator}. */ export interface BaseEntity { /** Top-left position used as the draw anchor. */ pos: Vec2; /** Optional horizontal anchor offset for flipped frames. Defaults to the current sprite width on first render. */ flipX?: number; } /** * 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. * * 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. */ export declare class Animator { private static spriteCache; /** * Drop cached rendered sprites. Pass a namespace to evict only that prefix; omit to clear all. */ static clearSpriteCache(namespace?: string): void; /** 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. */ active: boolean; /** Current rendered frame (after flip processing). Pulled from the cache by {@link setImage} every time the frame advances. */ image: HTMLCanvasElement; /** Index of the current frame within the current animation's `sprites` array. */ imageId: number; /** Flip the rendered sprite horizontally. Caches a separate "flipped" bucket so toggling is cheap. */ lookLeft: boolean; /** One-shot callback that fires when the current animation's last frame finishes. Cleared after firing. */ onEnd: onEndType | undefined; /** Current sprite's `(width, height)`. Updated by {@link setImage}. */ size: Vec2; private animations; private currentAnimation; private entity; private lastPlayed; private namespace; private onFrame?; private playVersion; private timer; /** The currently-playing animation. `undefined` if no animations have been added yet. */ get current(): SpriteAnimation; /** * 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. */ constructor(entity: BaseEntity, namespace: string); /** Blit the current cached frame at `entity.pos + offset`, shifted left by `size.x` to compensate for the 2×-wide cache canvas. */ draw(context: CanvasRenderingContext2D, offset?: Vec2): void; /** 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`. */ update(dt: number): void; /** 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`. */ add(name: string, sprites: HTMLCanvasElement[] | HTMLImageElement[], timing: number, defaultAnim?: boolean): void; /** Convenience wrapper around {@link add} that takes a packed {@link SpriteAnimation}. `defaultAnim` is OR'd with `anim.default`. */ addAnimation(anim: SpriteAnimation, defaultAnim?: boolean): void; /** 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. */ drawRotated(context: CanvasRenderingContext2D, angle: number, offset?: Vec2): void; /** 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. */ play(name: string, onEnd?: onEndType, onFrame?: onFrameType): void; /** {@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. */ playIfNot(name: string, onEnd?: onEndType, onFrame?: onFrameType): boolean; /** Queue an animation to play once the current one finishes. Pass `undefined` to cancel the queue. Used internally by {@link playOnce}. */ playNextOnce(name: string | undefined): void; /** * Play `name` once, then return to the previously-playing animation. Calling with the currently-playing name re-loops it indefinitely (lastPlayed restores to itself). */ playOnce(name: string, onEnd?: onEndType, onFrame?: onFrameType): void; /** Randomize the frame timer to a value in `[0, current.timing)`. Useful when spawning many instances of the same animation to break phase lockstep. */ randomTimer(): void; /** Drop every registered animation and clear this instance's cached frames. {@link active} resets to `true`; pending callbacks are cleared. */ removeAllAnimations(): void; /** Switch back to the default animation if one was registered (marked via `defaultAnim`); otherwise just stop animating ({@link active} = `false`). */ reset(): void; /** `true` when `name` matches the currently-playing animation. */ isPlaying(name: string): boolean; /** * 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. */ protected setImage(): void; } /** * Shared contract for the digital (on/off) facet of an input source: a set of addressable controls — keys, mouse/gamepad buttons — each identified by `T` that can be queried, consumed, and reset. Implemented by {@link Keyboard} (`T` = key code), {@link Pointer}, and {@link Controller} (`T` = button index). Analog state (pointer position, gamepad sticks) lives on the implementers, not here. * * Note: distinct from the {@link Controller} class (the gamepad). A `Control` is one addressable input on any source; a `Controller` is one such source. */ export interface Control { /** Mark every tracked control as released so held state doesn't stay live across focus loss. Called automatically on the input source's lifecycle events (`window` blur and others) — which events fire it is the input source's concern and is documented on each implementer's override. */ reset(): void; /** Request that `id` stop surfacing as active — the one-shot "consume" so a still-held control doesn't re-trigger an action every tick. How this is achieved, how long it holds before the live state re-asserts it, and whether it's reliably possible at all are the input source's concern and are documented on each implementer's override. */ stop(id: T): void; /** `true` when the control is active. Safe for untouched unknown controls (returns `false` rather than `undefined`). */ isActive(id: T): boolean; } /** Button indices for the standard gamepad mapping (W3C Gamepad spec). Pass to {@link Controller.isActive} / {@link Controller.stop}. */ export declare const CONTROLLER_KEYS: { /** Bottom face button — `A` on Xbox, `×` on PlayStation. */ readonly A: 0; /** Right face button — `B` on Xbox, `○` on PlayStation. */ readonly B: 1; /** Left face button — `X` on Xbox, `□` on PlayStation. */ readonly X: 2; /** Top face button — `Y` on Xbox, `△` on PlayStation. */ readonly Y: 3; /** Left bumper / shoulder. */ readonly LB: 4; /** Right bumper / shoulder. */ readonly RB: 5; /** Left trigger. The digital pressed-state lives here; the analog value is on the underlying `Gamepad.buttons[6].value`. */ readonly LT: 6; /** Right trigger. The digital pressed-state lives here; the analog value is on the underlying `Gamepad.buttons[7].value`. */ readonly RT: 7; /** Back / Select / Share. */ readonly SELECT: 8; /** Start / Options / Menu. */ readonly START: 9; /** Left stick click (L3). */ readonly LEFT_STICK: 10; /** Right stick click (R3). */ readonly RIGHT_STICK: 11; /** D-pad up. */ readonly UP: 12; /** D-pad down. */ readonly DOWN: 13; /** D-pad left. */ readonly LEFT: 14; /** D-pad right. */ readonly RIGHT: 15; /** Guide / Home / PS button. Not exposed by all browsers. */ readonly GUIDE: 16; }; /** A gamepad button index — one of the values of {@link CONTROLLER_KEYS} (`0`–`16`). */ export type ControllerKey = (typeof CONTROLLER_KEYS)[keyof typeof CONTROLLER_KEYS]; /** * Gamepad input. The first connected gamepad becomes "our" gamepad; later ones are ignored until ours disconnects. Connection / disconnection fires {@link EventSystem} `"inputControllerConnected"` / `"inputControllerDisconnected"`. * * Read buttons via {@link isActive} (indexed by {@link CONTROLLER_KEYS}) and call {@link poll} for the sticks 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. * * Logs to `console.error` if the browser doesn't expose the Gamepad API. */ export declare class Controller implements Control { private axes; /** 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. */ private buttons; private index; private lastTime; constructor(); /** 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 the button state read by {@link isActive}. Returns the cached array unchanged when the gamepad timestamp hasn't advanced. */ poll(): Vec2[]; /** Mark every tracked control as released and clear the cached stick axes. Called automatically on `window` blur and on gamepad disconnect. */ reset(): void; /** Force `button` to read as released. Only holds until the next {@link poll}: `buttons` is rebuilt from the live gamepad each poll, so a still-held button re-surfaces on the following frame. Reliable only if the button is released before the next poll. */ stop(button: ControllerKey): void; /** `true` when the control is active. Safe for untouched unknown controls (returns `false` rather than `undefined`). */ isActive(button: ControllerKey): boolean; /** 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. */ vibrate(): boolean; private getGamepad; } /** * 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`, …). * * {@link update} polls the controller for you; don't call {@link Controller.poll} again from the same `update` step. */ export declare class ControllerCursor { private controller; private crosshair; private range; private sticks; /** * @param controller Gamepad input source. * @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. * @param sticks Anchor positions, one per stick to track. Cloned at construction so caller mutation is harmless. * @param range Max pixel deflection from anchor at full stick. Default `80`. */ constructor(controller: Controller, crosshair: CanvasImageSource, sticks: Vec2[], range?: number); /** Draw a crosshair at `anchor + offset` for each tracked stick. */ draw(context: CanvasRenderingContext2D): void; /** 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. */ update(dt: number): void; } /** * 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`. */ export declare class Particle { /** CSS color string passed to `context.fillStyle` in {@link draw}. */ protected color: string; /** Accumulated time (seconds) since spawn or last {@link resetLifetime}. */ protected lifetime: number; /** Lifetime cap in seconds, randomized to `[0.5, 1.5]` at construction. */ protected maxLifeTime: number; /** Top-left position. Cloned from the constructor arg so the caller's `Vec2` isn't aliased. */ protected pos: Vec2; /** Circle radius (pixels). */ protected size: number; /** Velocity in px/s. Seeded randomly by the constructor (random angle, random magnitude per axis). */ protected vel: Vec2; /** Backing storage for the {@link rect} getter. Subclasses can read it; the public-facing accessor is {@link rect}. */ protected _rect: Rect; /** `false` once {@link lifetime} reaches {@link maxLifeTime}. Owners typically filter dead particles out of their list each frame, or call {@link resetLifetime} to recycle. */ get alive(): boolean; /** Read-only AABB tracking `pos` and the particle's `size`. Recomputed each {@link update}. */ get rect(): Readonly; constructor(pos: Vec2, color: string, size?: number); /** Fill a circle at `pos + offset` using {@link color}. `offset` is useful for shifting by a camera/world transform without mutating `pos`. */ draw(context: CanvasRenderingContext2D, offset?: Vec2): void; /** Integrate lifetime, position, and bounding rect. */ update(dt: number): void; /** 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. */ resetLifetime(): void; } /** * 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. * * The `T` generic types the optional {@link payload} so callers can attach typed metadata (damage, owner, etc.) without losing inference. */ export declare class Projectile { /** Seconds after which {@link alive} flips to `false`. Defaults to `Infinity` — no natural expiry. */ maxLifetime: number; /** Caller-supplied data. Typed via the class generic so consumers can read `projectile.payload` without casting. */ payload?: T; /** Magnitude multiplier applied to `vel` each update: `pos += vel * speed * dt`. Pass a unit-length `vel` to make this read as "pixels per second". */ speed: number; /** Current pre-rotated sprite. Re-baked by {@link rebuildRotation} from the un-rotated `originalImage`. */ protected image: HTMLCanvasElement; /** Accumulated time (seconds). Drives the {@link alive} check against {@link maxLifetime}. */ protected lifetime: number; /** Top-left position. Cloned from the constructor arg so the caller's `Vec2` isn't aliased. */ protected pos: Vec2; /** Current sprite rotation in radians, kept in sync with `vel` by {@link rebuildRotation}. */ protected rotation: number; /** 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. */ protected vel: Vec2; /** Backing storage for the {@link rect} getter. Subclasses can read it; mutate via `pos`/{@link rebuildRotation} instead of touching it directly. */ protected _rect: Rect; private originalImage; /** `false` once {@link lifetime} reaches {@link maxLifetime}. Owners typically filter dead projectiles out of their list each frame. */ get alive(): boolean; /** Read-only AABB tracking `pos` and the (rotated) image size. Recomputed in {@link update} and {@link rebuildRotation}. */ get rect(): Readonly; constructor(pos: Vec2, image: HTMLCanvasElement, vel?: Vec2); /** Blit the pre-rotated image at `pos + offset`. `offset` is useful for shifting by a camera/world transform without mutating `pos`. */ draw(context: CanvasRenderingContext2D, offset?: Vec2): void; /** Integrate motion and advance lifetime. Doesn't re-bake the rotation — call {@link rebuildRotation} after mutating `vel`. */ update(dt: number): void; /** * 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. */ rebuildRotation(): void; /** Force {@link alive} to `false` immediately (sets `lifetime` past `maxLifetime`). Use when the projectile should die on collision/impact, not from natural expiry. */ remove(): void; } declare global { interface HTMLCanvasElement { /** `true` if any pixel has a non-zero RGBA byte. */ hasAnyColor(): boolean; } } declare global { interface HTMLCanvasElement { /** * Read the pixel at `(x, y)`. Out-of-bounds reads return zero/transparent. Not for hot paths — each call issues a fresh `getImageData`. For bulk reads, call `getImageData` once and index into the buffer. * @param output return format. Default `"integer"`. */ getPixelAt(x: number, y: number, output?: "integer"): number; getPixelAt(x: number, y: number, output: "array"): [ ...RGB, number ]; getPixelAt(x: number, y: number, output: "json"): { r: number; g: number; b: number; a: number; }; getPixelAt(x: number, y: number, output: "string"): string; } } declare global { interface HTMLCanvasElement { /** * Replace pixel colors by RGB hex key. Alpha is ignored — fully-transparent pixels are skipped, semi-transparent pixels keep their alpha. */ replaceColors(replacements: Record): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** Rotate around the center into a new square canvas sized to fit any rotation (`diam = ceil(sqrt(w² + h²))`). */ rotateBy(radians: number): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** Rotate around the center within the original `width × height`; corners that fall outside are clipped. */ rotateByAligned(radians: number): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** Trim fully-transparent borders. Returns a new canvas cropped to the bounding box of opaque pixels. */ autoCrop(): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** * Scale into a new canvas. Throws if any factor is `≤ 0`. * @param scaleX horizontal scale factor. Default `1`. * @param scaleY vertical scale factor. Default = `scaleX`. */ scaleBy(scaleX?: number, scaleY?: number): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** * Scale into a new canvas so the chosen axis equals `size`, preserving aspect ratio. * @param isWidth match `size` against width when `true`, height when `false`. Default `true`. */ resize(size: number, isWidth?: boolean): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** * Mirror horizontally into a new canvas. * @param offsetX horizontal shift applied after flipping. Default `0`. */ flipX(offsetX?: number): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** * Mirror vertically into a new canvas. * @param offsetY vertical shift applied after flipping. Default `0`. */ flipY(offsetY?: number): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** * Crop a `(w, h)` sub-region starting at `(x, y)` into a new canvas. * @param w default `this.width` * @param h default `this.height` */ subImage(x: number, y: number, w?: number, h?: number): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** Copy this canvas (same dimensions, content, `id`, and `dataset`) into a new canvas. */ clone(): HTMLCanvasElement; } } declare global { interface HTMLCanvasElement { /** Convert image canvas to `Promise` via `toDataURL`. */ toImage(): Promise; } } /** A `` element paired with its 2D rendering context. Returned by {@link createNewCanvas} / {@link getCanvasConstruct} and inherited by {@link CanvasHolder}. */ export interface CanvasConstruct { /** The `` element. */ canvas: HTMLCanvasElement; /** Its 2D rendering context. */ context: CanvasRenderingContext2D; } /** * Create a new `` of the given size with its 2D context. Antialiasing defaults to `Settings.antialias`. */ export declare function createNewCanvas(width: number, height: number, antialias?: boolean): CanvasConstruct; /** * Look up an existing canvas by CSS selector and return it with its 2D context. */ export declare function getCanvasConstruct(selector: string): CanvasConstruct; /** * Apply a CSS `filter` string to an image and return the result as a new canvas. * @param width default `image.width` * @param height default `image.height` */ export declare function applyFilterOnCanvas(image: HTMLCanvasElement | HTMLImageElement, filter: string, width?: number, height?: number): HTMLCanvasElement; /** * Rotate the hue of an image by `hue` degrees via CSS `hue-rotate(...)` filter. */ export declare function rotateHue(image: HTMLCanvasElement | HTMLImageElement, hue: number, width?: number, height?: number): HTMLCanvasElement; /** * Recolor an opaque canvas in place using composite operations, preserving the alpha mask of the source image. Wraps the body in `save`/`restore` so `fillStyle` / `globalCompositeOperation` writes don't leak to the caller's context. * https://stackoverflow.com/a/45201094 */ export declare function changeColor(context: CanvasRenderingContext2D, oriImg: HTMLCanvasElement, newColor: string): void; /** * Split a sprite-sheet image into individual sprite canvases laid out as `elementsX × elementsY`. Throws if the image dimensions don't divide evenly — sheets are expected to be authored that way. */ export declare function splitSpriteSheet(img: HTMLCanvasElement, elementsX: number, elementsY: number): HTMLCanvasElement[]; /** * Count occurrences of each color in an image, keyed by `#rrggbb`. `pixelAmount` multiplies each count and floors to int; values < 1 will drop low-count colors entirely (count rounds to 0). `removeLowerThan` / `removeHigherThan` drop entries outside the range; `0` disables either bound. */ export declare function getUsedColors(image: HTMLCanvasElement, pixelAmount?: number, removeLowerThan?: number, removeHigherThan?: number): Map; /** 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. */ export declare const CANVAS_TYPES: { /** Catch-all tag for canvases without a specific role. */ readonly ANY: symbol; /** Generic placeholder tag — distinct from `ANY` so consumers can differentiate. */ readonly DEFAULT: symbol; /** Background canvas (drawn behind the main one). */ readonly BACKGROUND: symbol; /** Primary render target. Exactly one canvas must be registered with this type before {@link CanvasManager.finishSetup}. */ readonly MAIN: symbol; }; /** Entry stored in {@link CanvasManager.canvasHolder} for each registered canvas. */ export interface CanvasHolder extends CanvasConstruct { /** Selector the canvas was registered under (also the map key). */ id: string; /** Whether this canvas participates in {@link CanvasManager.resize} (rescaled to fit the window while preserving its buffer aspect ratio). */ resize: boolean; /** Role tag — one of {@link CANVAS_TYPES}. */ type: symbol; } /** * 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. * * Owned by `Game` (`game.canman`). Lifecycle: subclass registers canvases via {@link setupCanvas} in its constructor, then `preInit()` calls {@link finishSetup}. */ export declare class CanvasManager { /** Cached `getBoundingClientRect()` of the main canvas. Refreshed in {@link resize}. Used to map pointer client coords into canvas space. */ canvasBoundingClientRect: DOMRect; /** Registry of every {@link setupCanvas}-registered canvas, keyed by selector. */ canvasHolder: Record; /** Display-to-buffer scale factor after the last {@link resize} (`displayWidth / bufferWidth`). `1` until the first resize. */ ratio: number; /** Display (CSS-pixel) size of the main canvas after the last {@link resize}. Independent of the buffer dimensions in {@link width}/{@link height}. */ resizedSize: Vec2; private mainHolder; /** Main canvas element (the one registered with `CANVAS_TYPES.MAIN`). */ get canvas(): HTMLCanvasElement; /** Main canvas 2D rendering context. */ get canvasContext(): CanvasRenderingContext2D; /** Main canvas **buffer** height (the drawing surface, not the CSS display size). */ get height(): number; /** Main canvas **buffer** height (the drawing surface, not the CSS display size). */ set height(height: number); /** Main canvas buffer dimensions as a new `Vec2`. */ get size(): Vec2; /** Main canvas **buffer** width (the drawing surface, not the CSS display size). */ get width(): number; /** Main canvas **buffer** width (the drawing surface, not the CSS display size). */ set width(width: number); /** 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 listeners to keep that rect fresh: a `"scroll"` refresh always, plus a `"resized"` listener that runs the full {@link resize} when `Settings.enableResize` or just a bounding-rect refresh otherwise. Throws on duplicate calls or invalid registry state. */ finishSetup(): void; /** Re-read the main canvas's `getBoundingClientRect()` into {@link canvasBoundingClientRect}. Kept fresh on scroll/resize so pointer coordinate mapping stays correct; also called by {@link resize}. */ refreshBoundingRect(): void; /** 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. */ resize(): void; /** Set the main context's `font` to `${size}px "${font}"`. Defaults the family to `Settings.font`. */ setFontSize(size: number, font?: string): void; /** 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. */ setupCanvas(canvasType: symbol, selector: string, resize?: boolean): CanvasHolder; } /** 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. */ export declare const MAX_DT_SECONDS = 0.25; /** 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. */ export declare const MAX_STEPS_PER_FRAME = 5; /** * Fixed-step game loop. Owned by `Game` (`game.gameloop`) — usually started automatically by `preInit()` when `Settings.autoloop` is `true`. Each rendered frame: * * 1. Accumulates real time * 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) * 3. Clears the canvas (per `Settings.doNotClear` / `Settings.useClearRect`) and calls `game.draw(context)` * * Fires the {@link EventSystem} `"gameloopStopped"` event when teardown completes (not when {@link stopLoop} is called). */ export declare class Gameloop { /** 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. */ levelTime: number; private _isLooping; private accumulator; private game; private stop; /** `true` while the rAF callback is registered. Goes `false` only after the final frame fires the `"gameloopStopped"` event. */ get isLooping(): boolean; constructor(game: Game); /** Begin the rAF loop. Throws if {@link stopLoop} was called but teardown hasn't completed yet — wait for the `"gameloopStopped"` event before restarting. */ startLoop(): void; /** Request that the loop stop on its next tick. Asynchronous — the loop tears down on the following frame and dispatches `"gameloopStopped"` when done. */ stopLoop(): void; private draw; private looper; } /** `KeyboardEvent.code` constants for the keys Gleam tracks by name. Pass to {@link Keyboard.isActive} / {@link Keyboard.stop}. */ export declare const KEYBOARD_KEYS: { /** Digit row `0`. */ readonly KEY_0: "Digit0"; /** Digit row `1`. */ readonly KEY_1: "Digit1"; /** Digit row `2`. */ readonly KEY_2: "Digit2"; /** Digit row `3`. */ readonly KEY_3: "Digit3"; /** Digit row `4`. */ readonly KEY_4: "Digit4"; /** Digit row `5`. */ readonly KEY_5: "Digit5"; /** Digit row `6`. */ readonly KEY_6: "Digit6"; /** Digit row `7`. */ readonly KEY_7: "Digit7"; /** Digit row `8`. */ readonly KEY_8: "Digit8"; /** Digit row `9`. */ readonly KEY_9: "Digit9"; /** Letter `A`. */ readonly KEY_A: "KeyA"; /** Letter `B`. */ readonly KEY_B: "KeyB"; /** Letter `C`. */ readonly KEY_C: "KeyC"; /** Letter `D`. */ readonly KEY_D: "KeyD"; /** Down arrow. */ readonly KEY_DOWN: "ArrowDown"; /** Letter `E`. */ readonly KEY_E: "KeyE"; /** Enter / Return. */ readonly KEY_ENTER: "Enter"; /** Escape. Note: in `Settings.debug` mode this stops the gameloop. */ readonly KEY_ESCAPE: "Escape"; /** Letter `F`. */ readonly KEY_F: "KeyF"; /** Letter `G`. */ readonly KEY_G: "KeyG"; /** Letter `H`. */ readonly KEY_H: "KeyH"; /** Letter `I`. */ readonly KEY_I: "KeyI"; /** Letter `J`. */ readonly KEY_J: "KeyJ"; /** Letter `K`. */ readonly KEY_K: "KeyK"; /** Letter `L`. */ readonly KEY_L: "KeyL"; /** Left arrow. */ readonly KEY_LEFT: "ArrowLeft"; /** Letter `M`. */ readonly KEY_M: "KeyM"; /** Letter `N`. */ readonly KEY_N: "KeyN"; /** Letter `O`. */ readonly KEY_O: "KeyO"; /** Letter `P`. */ readonly KEY_P: "KeyP"; /** Letter `Q`. */ readonly KEY_Q: "KeyQ"; /** Letter `R`. */ readonly KEY_R: "KeyR"; /** Right arrow. */ readonly KEY_RIGHT: "ArrowRight"; /** Letter `S`. */ readonly KEY_S: "KeyS"; /** Space bar. */ readonly KEY_SPACE: "Space"; /** Letter `T`. */ readonly KEY_T: "KeyT"; /** Tab. */ readonly KEY_TAB: "Tab"; /** Letter `U`. */ readonly KEY_U: "KeyU"; /** Up arrow. */ readonly KEY_UP: "ArrowUp"; /** Letter `V`. */ readonly KEY_V: "KeyV"; /** Letter `W`. */ readonly KEY_W: "KeyW"; /** Letter `X`. */ readonly KEY_X: "KeyX"; /** Letter `Y`. */ readonly KEY_Y: "KeyY"; /** Letter `Z`. */ readonly KEY_Z: "KeyZ"; }; /** A tracked key's `KeyboardEvent.code` — one of the values of {@link KEYBOARD_KEYS}. */ export type KeyboardKey = (typeof KEYBOARD_KEYS)[keyof typeof KEYBOARD_KEYS]; /** * Keyboard state. Wired into `Game` automatically. The preferred way to consume input is to poll {@link isActive} from `update` — game input is held-state-based ("is W held this frame?"), and combining with {@link stop} handles one-shot actions cleanly. The {@link EventSystem} `"inputKeyboard"` event (payload: `(keys, event)`) is available for cases that genuinely need edge-triggered handling. * * 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. */ export declare class Keyboard implements Control { /** 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 isActive} for a safe `boolean`). */ private keys; constructor(game: Game); /** Mark every tracked control as released. Called automatically on `window` blur and on `gameloopStopped`. */ reset(): void; /** Force `code` to read as released. Holds until the next `keydown` for that key — so a momentary tap is consumed until it's pressed again, but a physically held key re-surfaces on the next OS auto-repeat `keydown`. */ stop(code: KeyboardKey): void; /** `true` when the control is active. Safe for untouched unknown controls (returns `false` rather than `undefined`). */ isActive(code: KeyboardKey): boolean; } /** 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}. */ export type SettingsOverrides = Partial>; /** Shape of the persisted localStorage blob — the type of {@link Settings.localStorage} and the keys {@link Settings.setLocalStorage} accepts. */ export interface LocalStorage { /** Active language code (e.g. `"en"`), seeded from `navigator.language` in {@link Settings.init}. */ language: string; } /** 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. */ export declare class Settings { /** Default image-smoothing state for canvas contexts Gleam sets up — those registered via {@link CanvasManager.setupCanvas} and those built by {@link createNewCanvas}. Default `false` for crisp pixel art. */ static antialias: boolean; /** Start the gameloop automatically after `init()` resolves. Disable to drive `gameloop.startLoop()` manually. Default `true`. */ static autoloop: boolean; /** CSS color used when {@link useClearRect} is `false`. Default `"#444"`. */ static backgroundColor: string; /** Debug mode: assigns the `Game` instance to `window.game` and lets {@link Keyboard} Escape stop the loop. Default `false`. */ static debug: boolean; /** Skip the per-frame canvas clear. Use for trail/decay effects where you manage clearing yourself. Default `false`. */ static doNotClear: boolean; /** Stretch the main canvas to fill the window on resize while preserving its aspect ratio. Default `true`. */ static enableResize: boolean; /** Default font family for `canman.setFontSize`. Default `"Arial"`. */ static font: string; /** **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. */ static fps: number; /** Callback invoked from the `beforeunload` handler when {@link warnBeforeClose} is `true`. Useful for "are you sure?" autosave logic. */ static triedToClose?: () => void; /** Clear the canvas with `clearRect` (transparent) when `true`, or `fillRect` with {@link backgroundColor} when `false`. Default `true`. */ static useClearRect: boolean; /** Show a browser "are you sure?" dialog on tab close. Required for {@link triedToClose} to fire. Default `false`. */ static warnBeforeClose: boolean; private static initialized; private static readonly _localStorage; /** Read-only view of the persisted localStorage blob. Writes go through {@link setLocalStorage}. */ static get localStorage(): Readonly; /** 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. */ static init(overrides: SettingsOverrides, game: Game): void; /** 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. */ static setLocalStorage(key: K, value: LocalStorage[K]): void; } declare global { interface Window { /** Translate `key` for the active language. Throws until `prepareLanguage` has run. */ t(key: string): string; } } /** Translation tables keyed by `languageCode → translationKey → text` (e.g. `{ en: { hello: "Hi" }, de: { hello: "Hallo" } }`). */ export type Languages = Record>; /** * 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`. */ export declare function prepareLanguage(languages: Languages, defaultLanguage?: string): void; declare global { interface HTMLAudioElement { /** Deep-clone this audio element, preserving the current `volume`. */ clone(): HTMLAudioElement; } } declare global { interface HTMLAudioElement { /** Volume restored by `stop()` after pausing. If unset, `stop()` leaves `volume` as-is. */ defaultVolume?: number; /** Pause playback, reset `currentTime` to 0, and restore `volume` to `defaultVolume` if set. */ stop(): void; } } export type Mode = "fill" | "stroke"; declare global { interface CanvasRenderingContext2D { /** * Fill a two-color bar sized by `amount`. Writes `fillStyle`; persists on the context — wrap in `save()`/`restore()` to preserve prior state. * @param c1 background, default `"white"` * @param c2 foreground, default `"black"` */ fillBar(rect: Vector4, amount: number, c1?: string, c2?: string): void; } } declare global { interface CanvasRenderingContext2D { /** * Fill a three-layer framed bar (outer band, frame, fill scaled by `amount`). Writes `fillStyle`; persists on the context — wrap in `save()`/`restore()` to preserve prior state. * @param amount fill fraction in `[0, 1]`. Default `0.8`. `≤ 0` skips the fill layer. * @param padding frame inset on all sides. Default `4`. * @param colors outer band, frame, fill. Default `["white", "black", "red"]`. */ fillFramedBar(rect: Vector4, amount?: number, padding?: number, colors?: [ string, string, string ]): void; } } declare global { interface CanvasRenderingContext2D { /** * Draws a circle centered at `vecPos`. * @param amount sweep fraction in `[0, 1]`. Default `1` (full circle). */ drawCircle(vecPos: Vector2, rad: number, mode: Mode, amount?: number): void; } } declare global { interface CanvasRenderingContext2D { /** Draws a rectangle (from `Vector4` or `x, y, w, h`). */ drawRect(rect: Vector4, mode: Mode): void; drawRect(x: number, y: number, w: number, h: number, mode: Mode): void; } } export interface DrawRoundRectOptions { /** Inset from the rect edges. Default `0` (no inset). */ padding?: number; /** Corner radius. Default `16`. */ radius?: number; } declare global { interface CanvasRenderingContext2D { /** Draw a rounded rectangle from a `Vector4` or `x, y, w, h` plus an options bag. Delegates the corner path to native `roundRect` (Safari 16+ / Chrome 99+ / Firefox 113+). */ drawRoundRect(rect: Vector4, mode: Mode, options?: DrawRoundRectOptions): void; drawRoundRect(x: number, y: number, w: number, h: number, mode: Mode, options?: DrawRoundRectOptions): void; } } declare global { interface CanvasRenderingContext2D { /** Stroke a dotted rectangle. Writes `lineWidth` and `setLineDash` — wrap in `save()`/`restore()` to preserve prior state. */ strokeDottedRect(rect: Vector4): void; } } declare global { interface CanvasRenderingContext2D { /** Draw a line segment from `(x1, y1)` to `(x2, y2)`. */ strokeLine(x1: number, y1: number, x2: number, y2: number): void; } } declare global { interface CanvasRenderingContext2D { /** * Draw a regular polygon centered in `rect`, with vertices on a circle of radius `min(rect.w, rect.h) * 0.5`. */ drawPolygon(sides: number, rect: Vector4, mode: Mode): void; } } declare global { interface CanvasRenderingContext2D { /** Draw a triangle: top-left → top-right → bottom-center. */ drawTriangle(rect: Vector4, mode: Mode): void; } } declare global { interface CanvasRenderingContext2D { /** * Write text horizontally offset around `x` by `measureTextOffset` of its measured width. * @param measureTextOffset in `[0, 1]`. Default `0.5` (centered around `x`). */ writeText(text: string, x: number, y: number, measureTextOffset?: number): void; } } declare global { interface CanvasRenderingContext2D { /** * Word-wrap `text` into lines of pixel width `width`, drawing each line via `writeText`. Returns `false` and logs to `console.error` if `maxAttempts` is reached. * @param lineOffset vertical spacing between lines in px. Default `50`. * @param maxAttempts safety cap on wrap iterations. Default `50`. */ writeMultilineText(text: string, x: number, y: number, width: number, lineOffset?: number, maxAttempts?: number): boolean; } } declare global { interface CanvasRenderingContext2D { /** * Draw `image` rotated by `radians` around the center of its placement at `(x, y)`. Saves and restores the transform. * @param radians clockwise positive, in radians. */ drawImageRotated(image: HTMLCanvasElement, x: number, y: number, radians: number): void; } } declare global { interface CanvasRenderingContext2D { /** Build a colored rounded-rect stencil with a `drawPartialRoundRect` helper. The returned helper writes `fillStyle = "white"` on the caller's context — wrap in `save()`/`restore()` to preserve prior state. */ generateColor(size: number, color: string): { colors: [ number, number ][]; image: HTMLCanvasElement; drawPartialRoundRect: (rect: Rect, amount: number, offsetX?: number, offsetY?: number) => void; }; } } declare global { interface HTMLImageElement { /** * Crop a `(w, h)` sub-region starting at `(x, y)` into a new canvas. * @param w default `this.width` * @param h default `this.height` */ subImage(x: number, y: number, w?: number, h?: number): HTMLCanvasElement; } } /** * 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. * * **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. */ export declare abstract class Game { /** Canvas registry + 2D context exposure. Register canvases here from the constructor (`canman.setupCanvas(CANVAS_TYPES.MAIN, "#game")`) before calling {@link preInit}. */ canman: CanvasManager; /** The fixed-step driver. Started automatically by `preInit` when `Settings.autoloop` is `true`. */ gameloop: Gameloop; /** Live keyboard state. See {@link Keyboard}. */ keyboard: Keyboard; /** Live pointer (mouse / pen / touch) state. See {@link Pointer}. */ pointer: Pointer; private initialized; constructor(settingOverrides?: SettingsOverrides); /** Render the current frame. Called by {@link Gameloop} after the canvas is cleared. Subclasses must override — the default throws. */ draw(_context: CanvasRenderingContext2D): void; /** 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. */ update(_dt: number): void; /** 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. */ init(): Promise; /** 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). */ protected preInit(doInit?: boolean): Promise; } /** Button indices that match `PointerEvent.button`; pass to {@link Pointer.isActive} / {@link Pointer.stop}. */ export declare const POINTER_KEYS: { /** Primary button (left for right-handers). */ readonly LEFT: 0; /** Middle button / wheel click. */ readonly MIDDLE: 1; /** Secondary button (right for right-handers). */ readonly RIGHT: 2; /** "Back" side button (browser back). */ readonly PREV: 3; /** "Forward" side button. */ readonly FORWARD: 4; }; /** A pointer button index — one of the values of {@link POINTER_KEYS} (`0`–`4`). */ export type PointerKey = (typeof POINTER_KEYS)[keyof typeof POINTER_KEYS]; /** * 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.isActive(POINTER_KEYS.LEFT)` from `update`. * * Suppresses the browser context menu on right-click globally. */ export declare class Pointer implements Control { /** 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". */ hasMoved: boolean; /** Last raw `PointerEvent` received. `null` until any pointer event fires. Use for properties not surfaced as Vec2/booleans (pressure, pointerType, etc.). */ lastEvent: PointerEvent | null; /** Viewport-space coordinates (`event.clientX/Y` — CSS pixels relative to the page). */ posReal: Vec2; /** Previous tick's {@link posReal}. Subtract for a per-frame delta. */ posRealLast: Vec2; /** 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. */ posScaled: Vec2; /** Previous tick's {@link posScaled}. */ posScaledLast: Vec2; private game; /** Per-button pressed state. Index with {@link POINTER_KEYS} (e.g. `pressed[POINTER_KEYS.LEFT]`). Rebuilt in full from `PointerEvent.buttons` on every pointer event, so all five entries are real booleans once any event has fired (the array is empty — reads `undefined` — only before the first event and after {@link reset}). */ private pressed; constructor(game: Game); /** Clear all pressed-button state. Called automatically on `window` blur so held buttons don't stay "pressed" forever when focus is lost. */ reset(): void; /** Force `button` to read as released. Only holds until the next pointer event: `pressed` is rebuilt from `PointerEvent.buttons` on every move/up/down, so a still-held button re-surfaces on the next mouse move. Reliable only if the button is released before the pointer next moves. */ stop(button: PointerKey): void; /** `true` when the control is active. Safe for untouched unknown controls (returns `false` rather than `undefined`). */ isActive(button: PointerKey): boolean; private update; } /** * 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. */ export interface GameEventMap { /** Fired by {@link Gameloop} once teardown completes, after `stopLoop()` is called. */ gameloopStopped: [ ]; /** Fired by {@link Controller} when a gamepad is connected. Payload is the native `Gamepad`. */ inputControllerConnected: [ event: Gamepad ]; /** Fired by {@link Controller} when *our* tracked gamepad disconnects. Other gamepads disconnecting are logged but don't dispatch. */ inputControllerDisconnected: [ ]; /** Fired by {@link Keyboard} on every key down/up with the live `keys` map and the event. */ inputKeyboard: [ keys: Record, event: KeyboardEvent ]; /** Fired by {@link Pointer} on every move and button transition. Payload is the `Pointer` instance — read `posScaled`/`pressed` from it. */ inputPointer: [ pointer: Pointer ]; /** Fired by {@link Game.preInit} once at startup and on every debounced `window.resize` thereafter. The canonical "viewport changed" signal. */ resized: [ ]; } /** Options for {@link EventSystem.addEventListener}. */ export interface EventSystemOptions { /** Index signature for forward-compatible options: extra fields are accepted and preserved verbatim on the stored listener, so callers can attach metadata without a type change here. */ [key: string]: unknown; /** Auto-dispose the listener after the first dispatch. */ once?: boolean; /** Fire this listener before non-priority listeners on every dispatch, regardless of when it was registered. Reserved for engine-internal wiring so the engine's own reaction (e.g. canvas resize) always precedes user listeners for the same event. */ priority?: boolean; /** Dispose the listener when the signal aborts. Already-aborted signals make `addEventListener` a no-op. */ signal?: AbortSignal; } /** * 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}. * * Guarantees: * * - `priority` listeners (engine-internal wiring) run before non-priority ones on every dispatch, independent of registration order; within each tier, registration order (FIFO) is preserved. * - Listeners registered during a dispatch are deferred to the next dispatch (won't fire in the round that registered them). * - `once` listeners are removed *before* their callback runs, so nested dispatches and throwing callbacks can't double-fire them. * - A throwing callback is caught and logged (throttled per `eventName:message`); siblings still receive the event. */ export declare class EventSystem { private static eventListener; private static logListenerError; private static nextId; /** * 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. */ static addEventListener(eventName: K, callback: (...args: GameEventMap[K]) => void, options?: EventSystemOptions): () => void; /** Synchronously fire `eventName` with the typed payload. `priority` (engine-internal) listeners run first, then the rest; within each tier registration order is preserved. Nested dispatches and self-disposing listeners are handled safely. */ static dispatchEvent(eventName: K, ...params: GameEventMap[K]): void; } /** String-valued keys of `CSSStyleDeclaration` — the ones safe to assign a `string` value to via {@link CssProxy}. */ export type CssStyleKey = { [K in keyof CSSStyleDeclaration]: K extends string ? CSSStyleDeclaration[K] extends string ? K : never : never; }[keyof CSSStyleDeclaration]; /** Setter handed to {@link ShakeType.update} and {@link ShakeType.reset} that writes a CSS value. */ export type CssProxy = (key: CssStyleKey, value: string) => void; /** Shape for a custom shake recipe. */ export interface ShakeType { /** Decay rate applied each frame: `timer -= step * dt`. Higher = shorter shake (3 ≈ ⅓s, 15 ≈ 1/15s). */ step: number; /** Per-frame mutator. `time` decays from `1` to `0` over the shake — multiply your intensity by it for a natural fall-off. */ update: (updateCss: CssProxy, time: number) => void; /** Cleanup run once when the shake ends. Clear every key {@link update} writes (assign `""`) so nothing is left on the element. */ reset: (updateCss: CssProxy) => void; } /** Built-in shake recipes. Pass one to {@link Screenshake.shake}. */ export declare const SHAKE_TYPES: { /** ~0.33 s wobble combining a small random rotation with a blur fall-off. */ NORMAL: { /** Decay rate — see {@link ShakeType.step}. */ step: number; /** Per-frame mutator — see {@link ShakeType.update}. */ update(updateCss: CssProxy, time: number): void; /** Cleanup — see {@link ShakeType.reset}. */ reset(updateCss: CssProxy): void; }; /** ~0.07 s impact: blur-only fall-off, no rotation. */ FAST: { /** Decay rate — see {@link ShakeType.step}. */ step: number; /** Per-frame mutator — see {@link ShakeType.update}. */ update(updateCss: CssProxy, time: number): void; /** Cleanup — see {@link ShakeType.reset}. */ reset(updateCss: CssProxy): void; }; }; /** * 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) runs the shake type's {@link ShakeType.reset} to clear the keys it wrote. * * 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. */ export declare class Screenshake { private isShaking; private shakeType; private style; constructor(element: HTMLElement); /** 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. */ shake(shakeType?: ShakeType): null | (() => void); } /** * Validates URL format and protocol before any side effects. Throws on invalid URLs or disallowed protocols. */ export declare function validateUrl(url: string): void; /** * Safe loading wrapper with global timeout and error handling. Use this for any async loading operation that needs timeout protection. Timeout rejects the returned promise but does not cancel the underlying fetch/Image — the request continues until natural completion. Adding AbortSignal support would require a full rewrite (factory-based API). Maybe a future feature, though. */ export declare function safeLoad(promise: Promise, url: string, operationName: string): Promise; /** * Loads an image with timeout and error handling */ export declare function loadImage(url: string): Promise; /** * Loads a canvas from image URL with error handling */ export declare function loadCanvas(url: string): Promise; /** * Loads text content from URL with error handling */ export declare function loadText(url: string): Promise; /** * Loads JSON data from URL with error handling */ export declare function loadJson(url: string): Promise; /** * Loads JSON with inline comments */ export declare function loadJsonCommented(url: string): Promise; /** * Loads image sprites from JSON configuration with error handling */ export declare function loadImageFromJson(baseUrl: string, filenameOrJson: string, jsonInput?: boolean): Promise>; /** * Loads multiple resources concurrently with error handling */ export declare function loadBunch>>(bunch: T): Promise<{ [K in keyof T]: Awaited; }>; /** * Split an array into chunks of at most `maxLength` elements each. Throws when `maxLength < 1`. */ export declare function chunk(array: ReadonlyArray, maxLength: number): T[][]; /** * Pick a uniformly random element from `array`. Throws if `array` is empty — guard at the call site if that's possible. */ export declare function randomItem(array: ReadonlyArray): T; /** * Remove an entry of an Array */ export declare function remove(arr: T[], item: T): void; /** * Shuffle array using Fisher-Yates algorithm with custom random signer */ export declare function shuffle(arr: ReadonlyArray): T[]; /** `get`/`set` helpers for CSS custom properties (`--name`) on `:root`. Build via {@link initCSSVariables}. */ export interface CSSVariables { /** The `:root` element these helpers read from / write to. */ root: HTMLElement; /** Read the computed value of `--${name}`. */ get(name: string): string; /** Write `value` to `--${name}` on the root element's inline style. */ set(name: string, value: string): void; } /** * `querySelector` variant that throws when no element matches. Optionally narrow the return type per tag, e.g. `getElement("canvas")`. */ export declare function getElement(query: string, parent?: ParentNode): T; /** * Apply a partial `CSSStyleDeclaration` to an element. */ export declare function styleElement(element: HTMLElement, styles: Partial): void; /** * Toggle `element.style.display` between `""` (active) and `"none"` (inactive). */ export declare function setDisplay(element: HTMLElement, active: boolean): void; /** * Toggle `element.style.visibility` between `""` (active) and `"hidden"` (inactive). */ export declare function setVisibility(element: HTMLElement, active: boolean): void; /** * Returns `get` / `set` helpers for CSS custom properties (`--name`) on the `:root` element. */ export declare function initCSSVariables(): CSSVariables; /** * Calls `callback` on pointerdown of the matched element, then keeps calling it every `delay` ms until pointerup or pointercancel. Uses pointer capture so the action persists while the cursor drags off the element (and over descendants). Unifies mouse, touch, and pen. Throws if no element matches. Returns a dispose function that removes the listeners and stops any in-flight interval. */ export declare function doWhilePressed(querySelector: string, callback: () => void, delay?: number): () => void; /** * Resolves the next time `type` fires on `element` (one-shot listener). Pass an `AbortSignal` to cancel — rejects with `signal.reason` and removes the listener. */ export declare function waitForEvent(element: HTMLElement, type: K, signal?: AbortSignal): Promise; /** * Read a fetch `Response` body to completion while reporting progress, then return a fresh `Response` backed by the fully buffered bytes. `onProgress` fires once per received chunk with the cumulative bytes loaded and the total from the `Content-Length` header, or `null` when it is absent. Throws if the response is not `ok`. A bodyless response (e.g. `204`, `HEAD`) is returned unchanged and `onProgress` never fires. */ export declare function download(fetchResponse: Response, onProgress: (loaded: number, total: number | null) => void): Promise; /** * Returns a debounced wrapper that runs `callback` only after `delay` ms of silence (trailing edge). */ export declare function debounce(callback: (...args: T) => void, delay: number): (...args: T) => void; /** * Promise that resolves after `time` milliseconds. */ export declare function delay(time: number): Promise; /** * Returns `true` when touch is the primary input right now (game-UI question: show touch controls?). Mode-aware: a convertible in laptop mode returns `false`, in tablet mode returns `true`. Snapshot at call time — won't auto-update if the user switches modes mid-game. */ export declare function isTouchPrimary(): boolean; /** * Run `tick(dt)` on every animation frame; `dt` is seconds since the previous frame (0 on the first call). Returns a cancel function that stops the loop — no further ticks fire after it's called, even if one was already queued. */ export declare function rafLoop(tick: (dt: number) => void): () => void; /** * Returns a throttled wrapper that runs `callback` at most once per `delay` ms (leading edge). The callback receives the number of wrapper calls since the previous firing (inclusive of this one). */ export declare function throttle(callback: (callCount: number) => void, delay?: number): () => void; /** * Like `throttle`, but tracks the last firing independently per `key` — different keys never throttle each other. Use for de-duplicating repeated error/warning logs by message identity. When the throttle fires, args from the most recent call for that key are passed through alongside `callCount`. */ export declare function throttleByKey(callback: (callCount: number, ...args: T) => void, delay?: number): (key: string, ...args: T) => void; /** Discriminated union of an object's `[key, value]` entry tuples, correlating each key with its own value type. Return type of {@link typedEntries}. */ export type Entries = { [K in keyof T]-?: [ K, T[K] ]; }[keyof T]; /** * `Object.entries` retyped so each `[key, value]` tuple keeps the key↔value relationship. Narrowing `key` in a branch narrows `value` to that key's type — unlike the built-in `Object.entries`, which widens `value` to `any`. * * Runtime-identical to `Object.entries` (it *is* `Object.entries`); only the return type is narrowed. * * @example * ```ts * interface LevelData { * rects: { x: number; y: number }[]; * name?: string; * } * * const levelData: LevelData = { rects: [{ x: 0, y: 0 }], name: "Level 1" }; * * for (const [key, value] of typedEntries(levelData)) { * if (key === "rects") { * value; // { x: number; y: number }[] * } * * if (key === "name") { * value; // string | undefined * } * } * ``` */ export declare const typedEntries: (obj: T) => Entries[]; /** * Filename component of a URL/path, without directory, extension, or query string. Returns `null` when no usable name can be derived: a path ending in `/`, or a stem containing a malformed percent-escape that `decodeURIComponent` rejects. */ export declare function urlBasename(path: string): string | null; /** * Clone a 2D grid; the outer array and each row become independent copies. Row cells are kept as-is (suitable for primitive cells; for nested structures use `deepClone`). */ export declare function cloneGrid(grid: ReadonlyArray>): T[][]; /** * Convert a 1D index to `{x, y}` for a 2D grid of the given row width. */ export declare function convert1DTo2D(index: number, width: number): Vector2; /** * Convert 2D `(x, y)` coordinates to a 1D index for a grid of the given row width. */ export declare function convert2DTo1D(indexX: number, indexY: number, width: number): number; /** 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). */ export type GridPrimitive = string | number | boolean | bigint | symbol | null | undefined; /** * 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. */ export declare function generateGrid(height: number, width: number, defaultValue: T): T[][]; /** * Generate a `height × width` 2D grid by invoking `factory(x, y)` for each cell. Use this overload for object/array cells (and for any per-cell computation). */ export declare function generateGrid(height: number, width: number, factory: (x: number, y: number) => T): T[][]; /** * Recursively clone a value. Returns primitives and functions as-is (no copy). Cyclic references resolve via an internal `WeakMap`. Explicit branches preserve `Date`, `RegExp`, `Map`, `Set`, `Array`, `ArrayBuffer`, typed arrays, and `DataView`. For plain objects / class instances the prototype is preserved via `Object.create(...)` — the original constructor is *not* called, so no side effects fire. Symbol keys and non-enumerable data properties are carried via descriptors. Own accessor properties (`get`/`set`) are snapshotted to a data property by invoking the getter on the source; this severs any closure binding to the original instance but also drops live computation on the clone. */ export declare function deepClone(obj: T): T; /** * Check if a value is a finite number or a string holding one. Accepts optional leading sign, decimal forms (`.5`, `5.`, `3.14`), and scientific notation (`1e5`, `-3.14e-2`). */ export declare function isNumeric(value: unknown): boolean; /** * Generate a random angle between 0 and 2-PI in radians */ export declare function random2Pi(): number; /** * Generate a random float between two values. Bounds may be passed in either order. */ export declare function randomBetweenFloat(min: number, max: number): number; /** * Generate a random integer in `[min, max]` (both bounds inclusive). Bounds may be passed in either order. */ export declare function randomBetweenInt(min: number, max: number): number; /** * Generate a random boolean value */ export declare function randomBoolean(): boolean; /** * Generate a random sign (1 or -1) */ export declare function randomSign(): number; /** * Format time in seconds as HH:MM:SS string */ export declare function toHHMMSS(time: number): string; /** * Convert radians to degrees */ export declare function toDegrees(radians: number): number; /** * Convert degrees to radians */ export declare function toRadians(degrees: number): number; /** * Wrap an angle in radians into `[-PI, PI)`. */ export declare function wrapRadians(angle: number): number; /** * Wrap an angle in degrees into `[-180, 180)`. */ export declare function wrapDegrees(angle: number): number; /** * Round a number to a specified number of decimal places */ export declare function roundTo(number: number, digitsAfterPoint: number): number; /** * Calculate factorial of a non-negative integer. Memoized — repeat calls reuse cached intermediates. Returns `Infinity` once `n!` overflows the IEEE 754 double range (around `n = 171`). */ export declare function getFactorial(n: number): number; /** * Compare two numbers with float tolerance. Default epsilon absorbs typical accumulated rounding error from normalize/rotate/divide chains. */ export declare function approxEqual(a: number, b: number, epsilon?: number): boolean; /** * Clamp values between two values */ export declare function clamp(value: number, min: number, max: number): number; /** * Map value from one range to another. Returns `low2` when the source range is degenerate (`low1 === high1`). */ export declare function mapUnclamped(value: number, low1: number, high1: number, low2: number, high2: number): number; /** * Map value from one range to another (with clamping). Output range allows to be inverted (`high2 < low2`). */ export declare function map(value: number, low1: number, high1: number, low2: number, high2: number): number; /** * Zero out values with `|value| < cutoff`; pass the rest through unchanged. */ export declare function threshold(value: number, cutoff: number): number; /** * Format number with dot separators (e.g., 1.000.000). Rounds to the nearest integer via `Math.round`; non-finite values pass through as their `toString()` form. */ export declare function toDotted(value: number): string; /** * Wrap `value` into `[min, max)` modulo the range size. Useful for cyclic ranges like angles. Caller steps via `value + n`; this function handles the wrap-around. Bounds are swapped if passed in reverse order. Throws when `min ≈ max` (degenerate range, via `approxEqual`). */ export declare function wrapValue(value: number, min: number, max: number): number; /** 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. */ export type Method = T[K] extends (...args: infer A) => infer R ? (this: T, ...args: A) => R : never; /** * Define `name` as a non-enumerable method on `proto`. Carries the declared signature so impl `this` and parameters are inferred from the merged declaration. */ export declare function defineMethod(proto: T, name: K, value: Method): void; /** * Trim `str` and collapse internal whitespace runs to a single space. */ export declare function compact(str: string): string; /** * Replace the single character at `index` in `str`. Indexes by code point, so emoji and other supplementary-plane characters count as one. Throws on out-of-range index or multi-character `char`. */ export declare function replaceCharAt(str: string, index: number, char: string): string; export {};