import { EmitterSettings, PylinkaProject, System } from '@pylinka/graph'; export { a as KnobBus, K as KnobStore } from './knobs-CVFP2Gxg.js'; export { A as AtlasPlay, p as playCode } from './atlas-D83bp0cn.js'; /** * SpawnScheduler (REQUIREMENTS.md §13.7). Pure CPU spawn accounting — the only * per-frame CPU work besides uniform writes. Allocation-free after construction. * * Produces the per-frame `spawnCount` for the emit dispatch. `emitterDist` is * the distance the emitter travelled this frame (drives rate-over-distance). */ declare class SpawnScheduler { private emitter; private readonly capacity; private acc; private pendingBurst; private burstClock; private startedOnce; constructor(emitter: EmitterSettings, capacity: number); /** * Swap the emitter settings while keeping the accumulators. * * Live editing re-reads the project on every change, and a fresh scheduler * would drop the fractional spawn debt each time — a 30/s flow emitter puts * 0.5 particles in `acc` per frame, so restarting it every frame floors to * zero forever and the effect silently dies while you drag a slider. Burst * emitters lose `burstClock` the same way and stop firing. Keeping the * accumulators is what makes a live-edit preview behave. * * Switching *into* `once` mode is the one thing that should re-arm: the user * asked for a one-shot they have not seen yet. */ setEmitter(next: EmitterSettings): void; /** Advance one frame; returns the spawn count (clamped to capacity). */ tick(dt: number, emitterDist: number): number; /** Queue an extra burst for the next frame (§7.4). */ spawnBurst(n: number): void; /** Reset all accumulators (restart). */ reset(): void; } /** * Frame-time policy (REQUIREMENTS.md §7.2, §13.7). Host rAF dt is clamped * (default 50 ms) to survive tab-switch spikes; fixed-step mode accumulates real * dt and runs whole steps of size `h` for deterministic capture/replay. */ declare const DEFAULT_MAX_DT = 0.05; /** Clamp a raw frame dt into [0, maxDt]. */ declare function clampDt(dt: number, maxDt?: number): number; /** * Fixed-step accumulator. Feed it the real (clamped) dt each frame; it returns * how many steps of size `h` to run. No render interpolation in v1. */ declare class FixedStepDriver { private readonly h; private readonly maxDt; private acc; constructor(h: number, maxDt?: number); /** Number of whole steps to run this frame. */ steps(realDt: number): number; reset(): void; } /** * Emitter trajectory splines: drive an emitter along a Catmull-Rom path. * CPU-side and renderer-independent — call `driver.at(elapsedSeconds)` each * frame and feed the result to `handle.setEmitter(x, y)`. * * The curve is a centripetal Catmull-Rom through the control points * (endpoint-clamped, or wrapped when `closed`), re-parameterized by arc length * via a dense LUT so travel speed is uniform regardless of point spacing. */ interface PathDriverOptions { /** seconds for one full traversal (default 4) */ duration?: number; /** what happens after a traversal: restart, reverse, or hold the end (default 'loop') */ mode?: 'loop' | 'pingpong' | 'once'; /** join the last point back to the first */ closed?: boolean; } interface PathDriver { /** position at `time` seconds since start */ at(time: number): [number, number]; /** total arc length of the path (same units as the points) */ readonly length: number; } declare function createPathDriver(points: readonly (readonly [number, number])[], opts?: PathDriverOptions): PathDriver; /** * Building a whole project, links and all. * * `createParticles` and `createCompiledParticles` each build ONE system, and a * sub-emitter has to be handed its parent's live handle as an option — the two * share GPU buffers, so the parent must exist first. That left every consumer * writing the same topological sort, and most of them not writing it at all: a * project exported from the editor with a sub-emitter came back as a set of * unrelated systems, the child spawning at its own emitter instead of on its * parent's deaths. It looked like the link had not been saved. It had; nothing * on the loading side was reading it. */ /** * Enabled systems, parents before children. * * A link to a system that is missing or disabled is dropped rather than * throwing: muting one emitter should not take its children down with it. A * cycle keeps its systems, in declaration order — unbuildable as a chain, but * dropping them would be worse than ignoring the link. */ declare function systemsInBuildOrder(project: PylinkaProject): { systems: System[]; /** child system id → parent system id, filtered to what is actually there */ links: Map; }; /** A project's systems, running together. */ interface ProjectParticles { /** by system id, in build order */ readonly handles: H[]; /** system id → handle */ readonly byId: Map; /** Step every system, parents first, and draw. */ update(dtSeconds: number): void; destroy(): void; } /** * Build every enabled system of a project with its sub-emitter links wired. * * `create` is the per-system constructor — `createParticles` for the * interpreted backend, `createCompiledParticles` for a compiled one — called * with the parent's handle where the project declares one. */ declare function buildProject(project: PylinkaProject, create: (system: System, parent: H | undefined) => H | Promise): Promise>; export { DEFAULT_MAX_DT, FixedStepDriver, type PathDriver, type PathDriverOptions, type ProjectParticles, SpawnScheduler, buildProject, clampDt, createPathDriver, systemsInBuildOrder };