/** * A single incoming hit to register on a {@link DamageDirectionTracker}. The * angle is relative to the player's facing (renderer-agnostic): `0` points at the * front/top of the reticle and increases clockwise, so a game passes the bearing * from the player toward the attacker without knowing anything about the screen. */ export interface HitInput { /** Direction the damage came from, in radians. `0` = front/up, increasing clockwise. */ angle: number; /** Peak strength `0..1` driving the arc's opacity/scale. Clamped. Default `1`. */ intensity?: number; /** Free-form game style tag (e.g. `"melee"`, `"fire"`, `"crit"`). Never interpreted by the model — a renderer maps it to color. Default `"default"`. */ kind?: string; } /** * A live directional indicator with its eased, time-decayed strength, produced by * {@link DamageDirectionTracker.active}. The `intensity` here is the *current* * eased value (peak faded over the elapsed lifetime), ready to drive opacity/scale. */ export interface DamageIndicator { /** Direction the damage came from, in radians (`0` = front/up, clockwise). */ angle: number; /** Style tag carried from the originating {@link HitInput}. */ kind: string; /** Current eased strength `0..1` — peak intensity decayed over its lifetime. */ intensity: number; /** Seconds elapsed since the hit was registered, on the injected clock. */ age: number; } /** Serializable state for save/restore — the clock reading plus every live indicator. */ export interface DamageDirectionSnapshot { /** Clock reading (ms) when the snapshot was taken, so ages restore correctly. */ now: number; /** Live indicators, each with its peak intensity, birth time, and fade duration. */ indicators: readonly { angle: number; kind: string; intensity: number; bornAt: number; duration: number; }[]; } /** Options for {@link createDamageDirectionTracker}. */ export interface DamageDirectionOptions { /** Injected clock (ms). Default `Date.now`. */ now?: () => number; /** How long an indicator takes to fade out, in ms. Default `1200`. */ duration?: number; /** Hard cap on simultaneous indicators; the pool never grows past this. Default `32`. */ max?: number; /** * If `> 0`, a new hit whose angle is within this many radians of a live * indicator with the same `kind` refreshes and strengthens that indicator * instead of adding a new one — so a burst from one direction reads as one * strong arc. Default `0` (every hit is its own indicator). */ mergeWindow?: number; /** * Fade curve mapping remaining-life fraction `1..0` to strength `1..0`. * Default ease-out (`1 - (1 - x) ** 2`) — holds bright, then drops off. */ easing?: (remaining: number) => number; } /** * A serializable, allocation-aware brain that tracks where recent damage came * from. Each registered hit becomes a directional indicator that fades over a * fixed duration on the injected clock; {@link DamageDirectionTracker.active} * reports the live ones with their eased current strength. */ export interface DamageDirectionTracker { /** Register an incoming hit and return the indicator's current view. */ registerHit(hit: HitInput): DamageIndicator; /** * The live indicators with eased current strength, sorted strongest-first. * The returned array and its entries are reused between calls (no per-call * allocation) — read or copy them before the next `active`/`registerHit`. */ active(): readonly DamageIndicator[]; /** Number of live (not-yet-faded) indicators. */ count(): number; /** Drop every indicator immediately. */ clear(): void; /** Observe changes (hit registered, cleared, restored). Returns an unsubscribe fn. */ subscribe(listener: () => void): () => void; /** Serializable state for a save. */ snapshot(): DamageDirectionSnapshot; /** Restore from a {@link DamageDirectionSnapshot}. */ restore(snapshot: DamageDirectionSnapshot): void; } /** * Create a damage-direction tracker: the classic "hit-from" feedback brain. A * game calls `registerHit({ angle, intensity, kind })` with the bearing from the * player toward the attacker (radians, `0` = front) and the tracker owns the fade * timers and eased strength so the renderer just draws an arc per `active()` * entry. It is renderer-free and genre-agnostic (the `kind` tag is never * interpreted here), allocation-aware (a fixed pool, no per-frame garbage), and * fully serializable via `snapshot`/`restore`. Optional angle merging collapses a * burst from one direction into a single strong arc. * * @capability damage-direction serializable hit-from damage-direction indicator brain — pooled directional arcs that flare on `registerHit(angle, intensity)` and fade on an injected clock, with optional same-direction merging */ export declare function createDamageDirectionTracker(options?: DamageDirectionOptions): DamageDirectionTracker;