/** A world-space anchor `[x, y, z]` a floating-text entry rises from. */ export type FloatingTextPosition = readonly [number, number, number]; /** * One text pop to spawn: what to say, where in the world, and how it moves and * fades. Everything but `position` and `text` is optional and falls back to the * field's defaults. `kind` is a free-form tag (`"damage"`, `"heal"`, `"crit"`, * `"xp"`, `"gold"`, `"bark"`, …) the presentation layer styles — the model never * interprets it, so nothing here is combat- or genre-specific. */ export interface FloatingTextDef { /** World anchor the text rises from. */ position: FloatingTextPosition; /** The text to show. */ text: string; /** Free-form style tag the game colors/skins. Default `"default"`. */ kind?: string; /** Optional CSS color hint carried straight through to the view (presentation may override per `kind`). */ color?: string; /** Relative scale multiplier (before the birth pop-in). Default 1. */ size?: number; /** Upward world-Y speed in units/second. Default = field `defaultRise`. */ rise?: number; /** Horizontal world-X speed in units/second (signed). Default: a small seeded jitter in `±driftJitter`. */ drift?: number; /** Lifetime in seconds. Default = field `defaultLifetime`. */ lifetime?: number; } /** Construction options for {@link createFloatingTextField}. */ export interface FloatingTextFieldConfig { /** Hard cap on live entries; the pool never grows past this. When full, the oldest entry is recycled. Default 64. */ max?: number; /** Injected wall clock in ms, stamped on each entry's `bornAt`. Default `Date.now`; inject for deterministic tests. */ now?: () => number; /** Seed for the horizontal-drift jitter stream — same seed + same calls reproduce identical motion. Default `"floating-text"`. */ seed?: string | number; /** Default lifetime in seconds when a def omits `lifetime`. Default 1.4. */ defaultLifetime?: number; /** Default upward speed in units/second when a def omits `rise`. Default 1.2. */ defaultRise?: number; /** Max magnitude of the seeded horizontal drift used when a def omits `drift`. Default 0.6. */ driftJitter?: number; /** Fraction of life spent on the birth scale pop-in (0.6→1). Default 0.16. */ popIn?: number; /** Fraction of life over which alpha fades to 0 at the end. Default 0.4. */ fadeFraction?: number; } /** A live entry resolved for rendering: current world position and fade/scale over life. */ export interface FloatingTextView { /** Stable, monotonically increasing id assigned at emit — usable as a React key. */ id: number; /** Current world position with rise (Y) and drift (X) applied. */ position: FloatingTextPosition; /** The text to show. */ text: string; /** The style tag (resolved; `"default"` when the def omitted `kind`). */ kind: string; /** The def's color hint, if any. */ color?: string; /** Resolved scale: base `size` times the current birth pop-in factor. */ size: number; /** Opacity `0..1`, full until the fade window then easing to 0. */ alpha: number; /** Age as a fraction of lifetime, `0..1`. */ progress: number; /** Wall-clock ms the entry was emitted (from the injected `now`). */ bornAt: number; } /** Serializable state for save/restore and deterministic replay. */ export interface FloatingTextSnapshot { seed: number; nextId: number; count: number; /** Numeric per-entry columns, flattened `count * NUMERIC_STRIDE`. */ data: number[]; /** Per-entry text. */ text: string[]; /** Per-entry kind. */ kind: string[]; /** Per-entry color (empty string = none). */ color: string[]; } /** A live, dt-driven field of world-anchored floating text. */ export interface FloatingTextField { /** Spawn one entry; returns its id. When the pool is full the oldest live entry is recycled. */ emit(def: FloatingTextDef): number; /** Advance every entry by `dt` seconds: age, rise, drift, and reap the expired. */ update(dt: number): void; /** Snapshot of the live entries resolved for rendering (a fresh array each call). */ active(): FloatingTextView[]; /** Live entry count. */ count(): number; /** Remove every entry (keeps the seed stream and id counter). */ clear(): void; /** Subscribe to changes (emit/update/clear/restore); returns an unsubscribe. */ subscribe(listener: () => void): () => void; snapshot(): FloatingTextSnapshot; restore(snapshot: FloatingTextSnapshot): void; } /** * A generic, allocation-aware field of world-anchored floating text: damage * numbers, heals, crits, XP/gold gains, status pops, dialogue barks — anything a * game wants to say at a point in the world for a moment. It is dt-driven (call * `update(dt)` each frame) and deterministic: the only randomness is the * horizontal drift jitter, which flows from the injected `seed`, so the same seed * and the same emit/update sequence reproduce identical motion, and * `snapshot`/`restore` round-trips the live pool. The model owns only the * spawn → rise/drift → fade → reap lifecycle: `kind` is a free string and `color` * a passthrough hint, so nothing here couples to combat or any genre. Presentation * (projecting each entry to the screen and styling it per `kind`) lives in * `@jgengine/react`'s `FloatingText`. * * @capability floating-text deterministic pooled world-anchored floating combat text / damage numbers with rise, drift, fade, and serializable state */ export declare function createFloatingTextField(config?: FloatingTextFieldConfig): FloatingTextField;