import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-B_245qRy.js"; import { t as Rng } from "./rng-BsXZg3D6.js"; //#region src/core/boot-failure.d.ts /** * Put a boot failure on the SCREEN, where the player is looking. * * Every shipped starter preloads its assets behind a `#loading` overlay and * removes that overlay on the line after `await createGame*`. So anything that * rejects — no WebGL context, a scene that will not load, a 404 the preload * insisted on — leaves the overlay standing at "100%" forever, with the reason * in a console the player will never open. Measured on a pristine scaffold with * WebGL denied: * * ``` * control #loading gone "Talk to the Elder [E] …" * no webgl #loading present "Emberwood 100%" … and still there 8.6 s later * ``` * * A bar that reaches 100% and stops reads as a hang, which is the one failure * mode a player cannot report usefully. This turns it into a sentence. * * Headless it does nothing but rethrow-safe logging, so a test that boots a * game without a document is unaffected. */ declare function showBootFailure(error: unknown, opts?: { into?: string; }): void; //#endregion //#region src/core/webgl-unavailable.d.ts /** * Can this document make a WebGL context at all? * * The question a game wants BEFORE it boots, so the answer can be a screen * instead of an exception. Uses a throwaway canvas and never touches yours; * `false` outside a browser, where the question is not meaningful. */ declare function isWebGLAvailable(): boolean; //#endregion //#region src/core/particle-sim.d.ts /** * Deterministic particle pool — pure math, renderer-agnostic, headless- * testable. 2D uses the xy plane (y-down, directionDeg 0 = +x, -90 = up); * 3D feeds the same sim with a z spread. */ interface ParticleSimConfig { /** Particles per second (0 = burst-only). */ rate: number; /** Seconds, [min, max]. */ lifetime: [number, number]; /** Initial speed, [min, max] (units/sec — px in 2D, meters in 3D). */ speed: [number, number]; /** Emission direction center, degrees (0 = +x, -90 = up in y-down 2D). */ directionDeg: number; /** Cone width, degrees (360 = all directions). */ spreadDeg: number; /** Constant acceleration (y-down in 2D). */ gravity: [number, number, number]; /** Exponential velocity damping per second (0 = none). */ drag: number; maxParticles: number; /** Spread emission into the z axis too (3D). */ spreadZ?: boolean; /** Born anywhere inside a box of these HALF-extents around the emitter (rain from a cloud, dust over a field); absent = a point. */ emitBox?: [number, number, number]; /** A constant velocity every particle is born with on top of its own — wind on smoke, a current in water. */ drift?: [number, number, number]; } interface ParticleView { x: number; y: number; z: number; vx: number; vy: number; vz: number; /** Seconds alive. */ age: number; /** Total lifetime in seconds. */ life: number; /** age/life in [0, 1] — drives size/color/alpha ramps. */ t: number; /** Per-particle random in [0, 1) — stable for the particle's lifetime. */ seed: number; } declare class ParticleSim { private readonly config; private readonly rng; private readonly data; private alive; private spawnAccumulator; private everSpawned; constructor(config: ParticleSimConfig, rng: Rng); /** * Shift every live particle — how world-space emission is done. * * The pool is in the emitter's LOCAL space, so a moving emitter drags its * whole plume with it: dust glued to a running player instead of left behind, * and the documented "move the emitter and replay" recipe for a one-shot * teleporting the previous explosion across the level. Counter-translating by * the emitter's own motion each frame leaves the particles where they were * born, exactly, and costs one pass over the live ones. */ translateAll(dx: number, dy: number, dz: number): void; /** Change the emission rate LIVE (particles/sec; 0 pauses emission) — lets an * emitter toggle on/off at runtime (drift smoke, throttle flames). */ setRate(rate: number): void; get count(): number; /** True when nothing is alive and at least one particle has ever spawned. */ get done(): boolean; /** Spawn n particles immediately (fireworks, explosions, flashes). */ burst(n: number): void; update(dt: number): void; forEach(fn: (p: ParticleView) => void): void; private spawn; private kill; } //#endregion //#region src/core/nodes/audio-player.d.ts /** * The per-frame spatial feed the 3D adapter pushes onto a spatial AudioPlayer: * the emitter's world position + the active listener (Camera3D) pose. Detected * structurally by `syncTree` (like `_applySunDirection`), so headless trees pay * nothing. */ interface SpatialPose { position: [number, number, number]; listener: Listener; } /** The slice of HTMLAudioElement the node drives (injectable for tests). */ interface AudioElementLike { src: string; volume: number; loop: boolean; currentTime: number; play(): Promise; pause(): void; addEventListener(type: string, cb: () => void): void; } /** * One sound. `play()` (or `autoplay: true`) starts it; `finished` fires when * it ends — for a procedural preset as well as a `src` clip. Browsers block audio before the first user gesture — a blocked * play marks `pendingGesture`; `createGame` retries pending players on the * first pointer/key gesture automatically. * * Two playback modes: * - `preset: 'custom'` (default) → plays the audio file at `src` via an HTMLAudio * element (good for music / long clips; supports `loop`). * - `preset: 'coin' | 'jump' | …` (a procedural SFX preset) → synthesizes a * zero-asset sound through WebAudio: instant, deterministic, overlap-friendly * for rapid-fire SFX. `pitch`/`seed` vary it. The art-free audio analog of the * particle presets. See `incanto-audio.md` for the full preset list. * * Volume routes through the engine's buses: `engine.audio.master × bus(sfx|music) * × volume`. Set `engine.audio.master`/`sfx`/`music`/`muted` for global control. */ declare class AudioPlayer extends Node { static override readonly typeName: string; static override readonly signals: readonly string[]; static readonly props: PropSchema; /** Audio file url (same resolution rules as scene asset urls). Used when * `preset === 'custom'`; ignored for procedural presets. */ src: string; /** A procedural SFX preset name (zero-asset), or 'custom' to use `src`. */ preset: string; volume: number; /** Pitch multiplier for procedural presets (1 = unchanged). */ pitch: number; /** Variation seed for noisy presets (e.g. explosion/hit/step). */ seed: number; /** Which volume bus this routes through. */ bus: string; loop: boolean; /** Start on the first frame in the tree (subject to the gesture policy). */ autoplay: boolean; /** * Positional audio: the sound pans + attenuates by the emitter's world * position relative to the active camera (the listener). Default false → * identical non-spatial behavior. * * Both dimensions. A 2D scene measures in PIXELS, so the distances below — * metre-shaped defaults — need setting there; `auditScene` says so. */ spatial: boolean; /** Distance at which spatial gain is full; closer never gets louder. */ refDistance: number; /** Distance past which spatial gain stops falling. */ maxDistance: number; /** Spatial attenuation curve: 'inverse' | 'linear' | 'exponential'. */ rolloff: string; /** Loader hook: unknown presets / buses fail at LOAD (agents self-correct). */ static validateJson(node: Node): void; /** A play() was blocked by the browser's autoplay policy. */ pendingGesture: boolean; /** * Why this player is silent, when the reason is the FILE rather than the * autoplay gate. Read by `game.assetErrors()`. */ loadError: string | null; private element; private _playing; /** * `engine.unscaledTime` at which the preset currently sounding ends. * * A preset used to be fire-and-forget: `playing` stayed false through a sound * that was audibly playing, and `finished` — a signal this node DECLARES, so * a connection to it loads clean — never fired at all. Wiring "when the coin * chime ends, free the pickup" to a preset player produced a wire that is * dead for the life of the game and says nothing. * * The end is known exactly: `attack + sustain + decay` is the length * `synthSfx` sizes its buffer to. Measured on the UNSCALED clock because a * sound does not slow down when the game does, and a paused game still hears * the tail of the hit that paused it. */ private presetEndsAt; private autoplayed; /** Last spatial pose pushed by the 3D adapter (null until/unless spatial). */ private _spatialPose; get playing(): boolean; /** * @internal Per-frame spatial feed from the 3D adapter (detected structurally * by syncTree). Stores the emitter world position + listener pose; ignored * unless `spatial` is on. Headless / 2D scenes never call this. */ _setSpatialPose(pose: SpatialPose): void; /** * Where this sound arrives from, for the record: gain, side and distance. * * The renderer feeds a pose every frame it draws; headless there is none, and * that is exactly where the question matters — a harness asking "was the bell * on my left" had nothing to read. So when no pose has been pushed, one is * computed HERE, from the scene's own current camera: the same listener the * panner would use, without a renderer to ask. * * Null when the sound is not spatial, or when the scene has no camera to hear * it — the record then says nothing rather than inventing a centred sound. */ private spatialAnswer; /** The listener the scene implies: its current camera, posed from the tree. */ private poseFromScene; /** Where this player SOUNDS from: its nearest spatial ancestor (or itself). */ private emitterPosition; /** * Write this sound into the engine's audio record. * * The CALL happens even where the backend does not (headless, the verify VM), * which is exactly why the record is worth keeping: it answers "did the coin * sound fire when the coin was collected" in places no sound can be heard. */ private note; /** Final gain = engine buses × this volume (1 when not in a tree). */ private gain; /** Distance gain for the src/element path (1 when not spatial / no pose). */ private spatialElementGain; /** The `spatial` option for the SFX path, or undefined when not spatial. */ private spatialPlay; /** * Start the sound, now. * * **Takes no arguments, on purpose.** This is the method scenes wire signals * to — `incanto-audio.md` teaches * `{"signal": "collected", "from": "Player/Collector", "to": "Coin", * "handler": "play"}` — and a signal hands its handler whatever it carries. * `collected` leads with a NUMBER. When this briefly took an optional * scheduling time, that wiring became `play(10)`, which scheduled the pickup * sound at ABSOLUTE audio-clock second 10: inaudible for the first ten * seconds of the game and fine thereafter. The same optional parameter also * made `Function.length` 1, so the engine's own arity checker cried wolf on * the shipped `platformer-2d` template's own harness. * * Scheduling has its own name: {@link playAt}. */ /** * Start the sound at `when` on the AUDIO clock (`engine.sfx.now` + a lead). * * A frame is a 16.67 ms grid at 60 Hz and 33.33 ms at 30, so a sound fired * from `update()` cannot land closer than one frame to where a chart wants * it — measured 0 of 64 notes given a scheduled start. Queue them a lead * ahead and the same notes land at |mean| 0.000000 ms: * * ```ts * const LEAD = 0.08; * for (const note of dueSoon(engine.sfx.now + LEAD)) { * hit.playAt(startedAt + note.atSec); * } * ``` * * PRESETS only — a `src` clip goes through an `