import { type StatusApplicationOutcome, type StatusInstance, type StatusStackPolicy } from "./statusApplication.js"; /** * What a status does on each tick. Damage ticks are reported with the channel the caller's damage * pipeline resolves, so a burn still meets resistances and armor; stat ticks are a per-stack delta * summed by {@link StatusRuntime.statDelta} (`slow` → move speed, `corrode` → armor). */ export type StatusEffect = { kind: "damage"; channel: string; } | { kind: "stat"; stat: string; }; /** * One status the runtime can apply: how a fresh application folds into a live one * ({@link StatusStackPolicy}), how long it lives, how often and how hard it ticks, and what a tick * does. The stack rule is the *only* thing that changes stacks — per-tick magnitude is * `magnitude × stacks`, never `magnitude ^ applications`. */ export interface StatusDef { /** Caller-owned id (`"burn"`, `"shock"`, `"slow"`, `"corrode"`) — the runtime never branches on it. */ id: string; /** Behaviour when the target already carries this status. */ stack: StatusStackPolicy; /** Base lifetime in milliseconds. */ durationMs: number; /** Milliseconds between ticks; defaults to `durationMs` (one tick as it expires). */ intervalMs?: number; /** Per-stack effect size: damage per tick, or a stat delta per stack. */ magnitude?: number; /** What each tick does. Omitted means a status with no periodic effect (HUD/predicate only). */ effect?: StatusEffect; /** Landing chance in `[0,1]` before any application scale; default `1`. */ chance?: number; } /** A damage tick, already routed through the caller's `applyDamage` when one was supplied. */ export interface StatusDamageTick { kind: "damage"; target: string; status: string; stacks: number; channel: string; /** Raw `magnitude × stacks` before the caller's resistances and armor. */ amount: number; /** What the caller's pipeline reported as dealt; equals `amount` when no pipeline was supplied. */ dealt: number; /** Who applied the status that is ticking, when known — a burn names its arsonist. */ source?: string; } /** A stat tick: a per-stack delta the caller reads while the status lives. */ export interface StatusStatTick { kind: "stat"; target: string; status: string; stacks: number; stat: string; delta: number; } /** A status ended: remove its HUD row and any stat delta it was contributing. */ export interface StatusExpiredTick { kind: "expired"; target: string; status: string; stacks: number; } /** Everything one `tick` call produced, in target-then-status order. */ export type StatusTickEvent = StatusDamageTick | StatusStatTick | StatusExpiredTick; /** Options for one application; scales compose with the definition's own base values. */ export interface StatusApplyOptions { source?: string; chanceScale?: number; durationScale?: number; magnitudeScale?: number; } /** One live status plus its tick timer, as saved and restored. */ export interface StatusRuntimeEntry { instance: StatusInstance; untilTickMs: number; } /** Serializable runtime state: carried statuses, tick timers, and the counters a probe reports. */ export interface StatusRuntimeState { targets: Array<{ id: string; entries: StatusRuntimeEntry[]; }>; ticks: number; applied: number; expired: number; } /** Tunables a game can adjust at runtime without discarding carried statuses. */ export interface StatusRuntimeOptions { /** Status definitions; re-supply through {@link StatusRuntime.retune} when they change. */ statuses: readonly StatusDef[]; /** Injected `[0,1)` stream for landing rolls. Omitting it falls back to `Math.random` and warns once. */ rng?: () => number; /** Route a damage tick through the caller's resistances/armor and return what it dealt. */ applyDamage?: (tick: StatusDamageTick) => number; /** Most ticks one status emits per call; the remainder carries to the next call, never dropped. Default `8`. */ maxTicksPerCall?: number; /** Register a one-line devtools probe under this name. */ devtoolsName?: string; } /** A pool of per-target statuses that applies stack rules, ticks effects, and hands its state out. */ export interface StatusRuntime { /** Try to apply one status to a target, folding into an existing instance per its declared stack rule. */ apply(targetId: string, statusId: string, options?: StatusApplyOptions): StatusApplicationOutcome; statusesOf(targetId: string): readonly StatusInstance[]; has(targetId: string, statusId: string): boolean; /** Sum of `magnitude × stacks` for every live status whose effect is this stat. */ statDelta(targetId: string, stat: string): number; /** Advance every live status; returns each tick's effect and each expiry. */ tick(dtSeconds: number): StatusTickEvent[]; /** Drop one status, or every status, from a target. */ clear(targetId: string, statusId?: string): void; snapshot(): StatusRuntimeState; restore(next: StatusRuntimeState): void; /** Swap the definitions (hot reload, tuning) while carried statuses keep running. */ retune(statuses: readonly StatusDef[]): void; /** Counters for a probe or a test. */ stats(): { targets: number; active: number; ticks: number; applied: number; expired: number; }; /** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */ dispose(): void; } /** Raised when a caller names a status id the runtime has no definition for. * @capability status-unknown reject a status id the runtime has no definition for */ export declare class UnknownStatusError extends Error { readonly status: string; constructor(status: string); } /** * Build a status runtime over serializable definitions. Applications reuse * {@link resolveStatusApplication} for chance and stacking, so a refresh/stack-to-N/ignore rule has * exactly one implementation; `tick` then applies each status's declared effect per stack. * * @capability status-runtime tick data-defined statuses (burn, shock, slow, corrode) with declared stacking rules and per-tick effects */ export declare function createStatusRuntime(options: StatusRuntimeOptions): StatusRuntime;