import { type EffectOverride } from "./effectLibrary.js"; import { type EmitterConfig, type ParticleEvent, type Vec3 } from "./particles.js"; /** How a particle effect composites on screen: `additive` for sparks/fire/glow, `normal` for smoke/dust. */ export type ParticleBlendHint = "additive" | "normal"; /** Render output selected independently of particle spawn and simulation. */ export interface ParticleRenderHint { shape: "point" | "streak" | "flake" | "flame" | "smoke" | "ribbon" | "ripple"; /** Velocity streak length in seconds. Default 0.08. */ stretch?: number; } /** A one-shot burst request — consumed once by the renderer, never serialized. */ export interface ParticleBurst { /** Monotonic sequence id so a renderer can consume each burst exactly once. */ seq: number; config: EmitterConfig; /** Particles to emit immediately. */ count: number; blending?: ParticleBlendHint; emitterId?: string; options?: ParticleAttachOptions; /** Graphics-tier drop order; higher survives. Default 0. */ priority?: number; } /** A bounded collision child effect; children cannot spawn further collision children. */ export interface ParticleCollisionEffect { config: EmitterConfig; count: number; render?: ParticleRenderHint; blending?: ParticleBlendHint; } /** Options for {@link ParticleDirector.attach}. */ export interface ParticleAttachOptions { /** Scene entity id the emitter origin tracks each frame (plus `offset`). */ follow?: string; /** World offset from the followed entity (or from the config position when unfollowed). */ offset?: Vec3; blending?: ParticleBlendHint; /** Local particles move with the followed pose; world particles retain their emitted positions. */ space?: "world" | "local"; active?: boolean; render?: ParticleRenderHint; collisionEffect?: ParticleCollisionEffect; /** Explicit environment influence: windResponse in 1/second, maxAcceleration in metres/second², both 0..1e12. */ environment?: { windResponse?: number; maxAcceleration?: number; mask?: number; }; /** Graphics-tier drop order: higher survives, lower is dropped first. Default 0. */ priority?: number; } /** A keyed continuous emitter owned by the director until detached. */ export interface ParticleEmitterSpec extends ParticleAttachOptions { id: string; config: EmitterConfig; follow?: string; offset?: Vec3; blending?: ParticleBlendHint; } /** A library effect played by name: where it plays, plus per-call overrides. */ export interface EffectRequest extends EffectOverride { /** Entity id a standing emitter tracks each frame (plus `offset`). */ follow?: string; } /** A cosmetic simulation event, labelled with its emitting effect. */ export interface ParticleDirectorEvent extends ParticleEvent { emitterId: string; } /** Bounded storage policy for transient effects and event exchange. */ export interface ParticleDirectorOptions { maxEmitters?: number; maxBursts?: number; maxEvents?: number; } /** Serializable director state: the standing emitters (bursts are transient by nature). */ export interface ParticleDirectorState { nextSeq: number; emitters: ParticleEmitterSpec[]; } /** * The game-side seam for particle effects. Game logic requests one-shot bursts and * standing emitters as plain data; the shell renders them through * `createParticleSystem`/`ParticleField`, applies the graphics-quality particle cap, * and tracks `follow` entities. Nothing here touches a renderer, so commands and * `onTick` systems can drive VFX headlessly and tests can assert the requested effects. */ export interface ParticleDirector { /** Queue a one-shot burst (impact puff, debris, muzzle smoke). */ burst(config: EmitterConfig, count: number, blending?: ParticleBlendHint, options?: ParticleAttachOptions): void; /** Create or replace a standing emitter under `id` (exhaust, dust plume, torch smoke). */ attach(id: string, config: EmitterConfig, options?: ParticleAttachOptions): void; /** Patch a standing emitter's config in place (e.g. scale exhaust rate with speed). */ retune(id: string, patch: Partial, options?: Partial): void; /** Resume a named emitter while preserving its live particles. */ start(id: string): void; /** Stop spawning; the renderer lets existing particles finish. */ stop(id: string): void; /** Emit once through a named emitter's existing pool, including when stopped. */ burstNamed(id: string, count: number): void; /** Play a library effect by name once, as a one-shot burst at the request's position. */ play(name: string, request?: EffectRequest): void; /** Create or replace a standing emitter from a library effect name under `id`. */ attachEffect(id: string, name: string, request?: EffectRequest): void; /** The named effects a caller can play, from the shared library. */ effectNames(): readonly string[]; /** Renderer-to-game cosmetic event exchange, bounded by the director's event limit. */ reportEvents(id: string, events: readonly ParticleEvent[]): void; drainEvents(): ParticleDirectorEvent[]; /** Remove a standing emitter. Unknown ids are ignored. */ detach(id: string): void; /** Drop every standing emitter and any unconsumed bursts. */ clear(): void; /** The standing emitters, for renderers and assertions. */ emitters(): readonly ParticleEmitterSpec[]; /** Fixed registry and transient queue capacities for transactional preflight. */ limits(): Required; /** Return and forget the queued bursts — the renderer's consume-once read. */ drainBursts(): ParticleBurst[]; subscribe(listener: () => void): () => void; snapshot(): ParticleDirectorState; restore(state: ParticleDirectorState): void; } /** * Create the particle intent registry a `GameContext` exposes as `ctx.particles`. * State is data-only and serializable: standing emitters survive `snapshot`/`restore`, * queued bursts are transient. The renderer subscribes, drains bursts, and mirrors * the emitter list — the director never allocates particle pools itself. * * @capability particle-director named particle start/stop/burst/retune lifecycle, local/world following, render outputs, bounded cosmetic event exchange, and serializable emitter definitions */ export declare function createParticleDirector(options?: ParticleDirectorOptions): ParticleDirector; /** * Validate serializable binding, output, and bounded collision-child descriptors. * @capability particle-binding-validation validate named particle follow spaces, render outputs, environment influence, and bounded collision child effects */ export declare function validateParticleAttachOptions(value: unknown): string[];