import { a as ParticleSimConfig, c as showBootFailure, i as ParticleSim, n as AudioPlayer, o as ParticleView, s as isWebGLAvailable, t as AudioElementLike } from "./audio-player-BRo2uvG6.js"; import { $ as T_PREFIX, A as SettingsValues, At as BusName, B as captureBehaviors, C as EngineStats, Ct as sfxDuration, Dt as MusicTrack, E as DeviceHints, Et as MusicManager, F as BehaviorState, Ft as LogLevel, G as ORDER_GROUP_BASE, H as savesWithoutUid, I as RestoreReport, It as LogManager, J as effectiveOrder, K as OrderGroup, L as SaveSlot, Lt as Signal, M as readDeviceHints, Mt as NodeLifecycle, N as suggestQuality, Nt as SceneTree, O as QualityTier, Ot as PlayMusicOptions, P as Scene, Pt as LogEntry, Q as Localization, R as SaveSlots, Rt as SignalListener, S as Scheduler, St as SynthOptions, T as RendererStats, Tt as MusicBackend, U as SaveStore, V as restoreBehaviors, W as createSaveStore, X as BASE_LOCALE, Y as resolveOrderGroups, Z as LocaleTables, _ as mergeStaticSignals, _t as spatialPan, a as clearBehaviors, at as EffectLog, b as Engine, bt as SfxParams, c as registeredBehaviors, ct as isAudioContextAvailable, d as PropSchema, dt as Listener, et as suggestLocale, f as clearRegistry, ft as ROLLOFF_MODELS, g as getNodeType, gt as spatialGain, h as getNodeSignals, ht as Vec3, i as behaviorSignals, it as EffectKind, j as qualityEnvironment, jt as Node, k as Settings, kt as AudioBuses, l as NodeCtor, lt as Voice, m as getNodeSchema, mt as SpatialParams, n as BehaviorCtor, nt as InputMap, o as getBehavior, ot as SfxEngine, p as createNode, pt as RolloffModel, q as OrderGroupTable, r as behaviorSchema, rt as EffectEvent, s as registerBehavior, st as SfxPlayOptions, t as Behavior, tt as translationKey, u as PropDef, ut as VoicePreset, v as registerNode, vt as SFX_PRESETS, w as GameStats, wt as synthSfx, x as EngineOptions, xt as SfxWave, y as registeredTypes, yt as SFX_PRESET_NAMES, z as behaviorsWithoutSave } from "./behavior-B_245qRy.js"; import { a as SceneJson, c as JsonValue, d as jsonKind, i as SCENE_FORMAT, l as jsonClone, n as ConnectionJson, o as JsonKind, r as NodeJson, s as JsonObject, t as Rng, u as jsonEquals } from "./rng-BsXZg3D6.js"; import { n as loadScene, t as LoadSceneOptions } from "./loader-CbkVdXL8.js"; import { i as resolveFrames, n as AnimationEntry, r as resolveAnimation, t as AnimationDef } from "./sprite-animation-CMr6f1K2.js"; import { i as gridFromRows, n as PathGrid, r as findPath, t as FindPathOptions } from "./pathfinding-_fGrCFmH.js"; import { a as startRecording, c as IncantoErrorDetails, i as replay, l as auditScene, n as ReplayEvent, o as IncantoError, r as ReplayJson, s as IncantoErrorCode, t as Recorder } from "./replay-W5nCw_cU.js"; //#region src/core/audio/crossfade.d.ts /** * PURE crossfade / fade envelope math for the music manager. No WebAudio: just * the gain curves over time, so the loudness behaviour is unit-testable in * `node` and the headless music state machine can be validated without a backend. * The adapter applies these gains to real GainNodes / element volumes each frame. */ /** Linear 0→1 (`in`) or 1→0 (`out`) over `duration` seconds at elapsed `t`. */ declare function fadeGain(t: number, duration: number, dir: "in" | "out"): number; interface CrossfadeGains { /** Gain for the outgoing (old) track. */ out: number; /** Gain for the incoming (new) track. */ in: number; } /** * Equal-power crossfade gains at elapsed `t` over `seconds`. The outgoing track * fades cos(¼π·p) and the incoming sin(¼π·p) so `out² + in² ≈ 1` throughout — * constant perceived loudness, no mid-fade dip (the classic equal-power law). * `p` is `t/seconds` clamped to [0,1]; a zero-second fade swaps instantly. */ declare function crossfadeGains(t: number, seconds: number): CrossfadeGains; //#endregion //#region src/core/audio/webaudio-music.d.ts declare class WebAudioMusicBackend implements MusicBackend { private ctx; private resolved; /** True once a backend is present (lazily created on first use). */ get available(): boolean; private ensure; /** Told when a track will never play. Set by the manager, which knows the * engine that owns the log. */ onTrackError: ((src: string, reason: string) => void) | null; createTrack(src: string): MusicTrack; unlock(): void; dispose(): void; } //#endregion //#region src/core/debug-draw.d.ts /** * Renderer-agnostic physics debug drawing: physics runtimes register here as * line sources; renderers of the matching dimension pull vertices each frame * when `debugDraw` is on. Default OFF — a release game never pays for it. */ interface DebugLineSource { readonly dimension: "2d" | "3d"; /** * The engine whose world these lines describe. * * A page can hold more than one at a time — the editor runs its edit engine * and a preview engine side by side — and a renderer must draw ITS world, not * whichever source happens to be first in a module-level Set. Without this, * one leaked physics instance paints its frozen colliders over everybody. */ readonly engine: object; /** Toggle at runtime: `physics.debugDraw = true`. */ debugDraw: boolean; /** * Draw only the colliders under this node (its subtree, or the nearest body * above it when the node itself carries none). Null draws every collider in * the world — which on a real map is a terrain grid burying the one shape you * meant to look at. */ debugScope: object | null; /** Flat segment vertices (xy pairs in 2D px, xyz triples in 3D meters), or null while off. */ debugLines(): Float32Array | null; } //#endregion //#region src/core/drive-script.d.ts /** * A little script for driving a game, in the words it already understands. * * `incanto-play` has driven scenes from bash for many releases — `press`, * `vector`, `step` — and cannot show you the result, because headless has no * pixels. `incanto-frame` has the pixels and cannot move. Between them an agent * could only ever look at the boot screen, and every interesting state in a * game is downstream of input: the boss room, the bridge, the moment after the * jump. * * So the same vocabulary drives the RUNNING page, and the frame comes back * after it. One language for both, or an agent has to learn two. * * Pure: parsing only. Who applies the steps is the page's business. */ type DriveStep = { kind: "press"; action: string; } | { kind: "release"; action: string; } | { kind: "vector"; action: string; x: number; y: number; } | { kind: "key"; code: string; down: boolean; } | { kind: "pointer"; dx: number; dy: number; } | { kind: "at"; x: number; y: number; } | { kind: "atNode"; path: string; } | { kind: "click"; button: number; } | { kind: "mouse"; button: number; down: boolean; } | { kind: "step"; ms: number; }; interface DriveScript { steps: DriveStep[]; /** Why nothing will run, or null. A refused script runs NONE of its steps. */ error: string | null; /** Total simulated time the script asks for, ms. */ totalMs: number; } /** * `vector move 0 1; step 2000` → steps, or an error naming the bad command. * * Semicolons and newlines both separate, so a whole plan fits in one shell * argument and a longer one fits in a file. `#` starts a comment. * * A command it does not know REFUSES THE WHOLE SCRIPT. Skipping it would leave * an agent looking at a frame that never moved, believing it had walked * somewhere — the failure mode this exists to prevent. */ declare function parseDrive(text: string): DriveScript; //#endregion //#region src/core/log-report.d.ts interface AssetFailure { ref: string; url: string; error: string; } interface LiveInput { entries: readonly LogEntry[]; stats: EngineStats; assetErrors: readonly AssetFailure[]; /** * The page is one the browser has stopped drawing — hidden, or a window * merely covered by another. It throttles such a tab to about a frame a * second, and that number says nothing about the game. */ hidden?: boolean; } /** One message, however many times the game said it. */ interface GroupedLog { level: LogLevel; message: string; count: number; /** When it was last said, in the page's own clock. */ lastMs: number; } interface LiveReport { ok: boolean; /** Frame rate low enough to be felt (running, and not merely throttled). */ slow: boolean; /** The browser is throttling this page — see {@link LiveInput.hidden}. */ hidden: boolean; counts: Record; grouped: GroupedLog[]; /** The most severe thing said, or null when nothing was. */ worst: GroupedLog | null; stats: EngineStats; assetErrors: readonly AssetFailure[]; } declare function logReport(input: LiveInput): LiveReport; /** The report as a person or an agent reads it: what is wrong, then the numbers. */ declare function logText(r: LiveReport): string; //#endregion //#region src/core/node-path.d.ts /** * Godot-style NodePath grammar: * * - `'Child/Grand'` relative descent * - `'.'` self * - `'..'` parent (may repeat: `'../../Other'`) * - `'/Level/Player'` absolute from the tree root * - `'%Player'` unique-name lookup across the whole tree */ type ParsedNodePath = { kind: "relative" | "absolute"; segments: string[]; } | { kind: "unique"; name: string; }; declare function parseNodePath(path: string): ParsedNodePath; //#endregion //#region src/core/node-refs.d.ts /** The scene-JSON shape this walks. */ interface RefNode { name?: string; type?: string; props?: JsonObject; script?: { name?: string; props?: JsonObject; }; children?: RefNode[]; } /** Why a written path leads nowhere. Structured so callers can localize it. */ type RefProblem = { kind: "grammar"; message: string; } | { kind: "noMatch"; name: string; } | { kind: "ambiguous"; name: string; count: number; } | { kind: "wrongRoot"; first: string; root: string; } | { kind: "noChild"; parent: string; segment: string; } | { kind: "aboveRoot"; }; type RefResult = { ok: true; path: number[]; } | { ok: false; problem: RefProblem; }; /** * Resolve `value` from the node at `owner`, the way the engine would. * * Note the asymmetry with connections, whose paths resolve from the SCENE ROOT: * a prop's path resolves from the node holding it, which is why `'../Skin'` is * the documented default of `CharacterController3D.skinPath` and would mean * nothing in a connection. */ declare function resolveRefInJson(root: RefNode | undefined, owner: readonly number[], value: string): RefResult; /** The problem in English, for `incanto-check` and as the editor's base text. */ declare function describeRefProblem(problem: RefProblem, value: string): string; interface RefWarningOptions { /** * This file is a PREFAB another scene drops in, so a path may legitimately * leave it. * * `/root/Bolts` is the fragment's own root only while the fragment is alone; * attached, it resolves. Four such warnings stood forever on four of five * files of a real project whose main scene audited clean. The verdict is * reworded rather than suppressed: a typo'd path is MOST likely in a file * written away from the scene that gives it meaning, so it still gets named. */ fragment?: boolean; } /** * Scene lints for node-path props that point at nothing. * * An EMPTY value is not reported: `''` is the default of most of these props and * means "not set", which is a choice rather than a typo. */ declare function nodeRefWarnings(scene: JsonObject, opts?: RefWarningOptions): string[]; //#endregion //#region src/core/nodes/hud.d.ts /** * DOM-backed HUD widgets — the layer 18 of 22 example games hand-rolled. * * These are CORE nodes (no three.js): they render into a fixed, pointer- * transparent overlay ABOVE the canvas, so the same JSON works over the 2D * and 3D renderers alike. Headless (no `document`) every widget is a silent * no-op — scenes stay fully testable in plain node. * * { "name": "HUD", "type": "HudLayer", "children": [ * { "name": "Health", "type": "UiBar", * "props": { "anchor": "topLeft", "value": 100, "max": 100, "label": "HP" } }, * { "name": "Score", "type": "UiText", * "props": { "anchor": "topRight", "text": "0", "size": 22 } }, * { "name": "Banner", "type": "UiBanner" } * ]} * * Behaviors talk to them like any node: * (this.node.getNode('%Score') as UiText).text = `${score}`; * (this.node.getNode('%Banner') as UiBanner).show('WAVE 2', { color: '#f66' }); */ type HudAnchor = "topLeft" | "top" | "topRight" | "left" | "center" | "right" | "bottomLeft" | "bottom" | "bottomRight"; /** * The overlay container. One per scene is plenty; widgets mount into its * anchor slots. `zIndex` lifts it above game canvases; the layer never eats * pointer events (widgets that need clicks opt in individually). */ declare class HudLayer extends Node { static override readonly typeName: string; static readonly props: PropSchema; zIndex: number; visible: boolean; focusNavigation: boolean; letterbox: number; private bars; /** @internal root overlay element (null headless). */ _element: HTMLElement | null; private readonly slots; override onEnterTree(): void; /** * Put the overlay where the page's owner says UI goes: `engine.uiHost`, or * the window when nobody claims it (a game — a HUD covers the screen). * * Re-checked every frame because a scene's tree is BUILT before it is handed * to an engine: at onEnterTree there is no engine to ask yet, so the first * update is where the answer actually arrives. appendChild moves the element * if it already landed somewhere else, so the HUD follows the host across a * scene swap into a differently-hosted engine (game → editor) instead of * being stranded on document.body over the inspector. */ private _mount; override onExitTree(): void; /** The two bars, made on first use and sized every frame from `letterbox`. */ private _syncLetterbox; override update(): void; /** * Is this the layer the keyboard is talking to? * * Keyboard focus is singular by nature — a browser has ONE focused element — * and `focusNavigation` is per LAYER, so a game with a HUD and a modal that * armed both had a single Enter activate the focused widget in EACH: the * pause menu's RESUME and whatever the screen underneath happened to have * focused, which is "the modal does not block the screen behind it" arriving * through the keyboard. * * The TOPMOST armed layer wins — last in document order, which is the one * drawn on top, and the one a modal is. A hidden layer is not armed at all: a * closed menu must not eat the key from the HUD it covers. With one layer, * which is the usual case, this is always true. */ private ownsFocus; /** * Arrow keys / d-pad move the focus, Enter / A activates it. * * Off by default: a game whose HUD has a button must not lose its arrow keys * to a menu the moment one exists. Turn it on for the screens that ARE menus * (`"focusNavigation": true` on the pause panel's layer), and off again when * play resumes. */ private stepFocus; /** * Focusable widgets under this layer, in tree order — **on screen only**. * * This filtered on each widget's OWN `visible` and recursed into hidden * containers anyway, so arrow-keys walked into closed panels and Enter really * pressed what it found. Measured on a title screen: 23 focus stops, 21 of * them under an ancestor with `visible = false`, and * * gold before = 150 -> Enter on the hidden Shop/Row0/Buy -> gold 120, * screen still 'title' * * The layer's own visibility was not a gate either. It is now: a HUD nobody * can see has no ring, and a subtree nobody can see is not walked. * * PUBLIC, because a game that wants to know what its ring contains could not * ask — and "why is focus on that" is exactly the question this answers. */ focusables(): HudWidgetBase[]; /** Move focus to a widget by hand — opening a menu should start somewhere. */ focus(widget: HudWidgetBase | null): void; /** @internal Widgets mount into per-anchor flex columns. */ _slot(anchor: HudAnchor): HTMLElement | null; } /** Shared plumbing: mount into the parent HudLayer's anchor slot. */ declare abstract class HudWidgetBase extends Node { /** * Wireable visibility: `noSave → Continue.hide`. * * `visible` is a prop, and a `connections[]` handler has to be a METHOD — the * same wall that kept a score off the screen until widgets grew `setText`. A * title screen greying out its own Continue button is the case that asked. */ show(..._args: unknown[]): void; hide(): void; static readonly props: PropSchema; static readonly signals: readonly string[]; anchor: HudAnchor; visible: boolean; focusable: boolean; draggable: boolean; dropTarget: boolean; /** @internal Set by the owning HudLayer while this widget has focus. */ _focused: boolean; /** * @internal Confirm (`dir` 0) or nudge (-1 left / +1 right) — what pressing * A or an arrow ON this widget means. Default: nothing. */ _activate(_dir: number): void; /** * @internal What a MOUSE CLICK on this widget does. Defaults to `_activate`. * * For a button, a toggle and a select the two are the same thing, which is * why one entry point served both for a while. For `UiDialogue` they are * not: Enter on a waiting choice ANSWERS it, and a click on the box does * not — `advance()` returns early while `current.choices` is set, with the * comment "must pick — clicks don't skip choices". * * A harness whose `click:` answered the question would pass a test the real * game fails, which is the one thing the ladder must not do. */ _click(): void; /** @internal Draw the focus ring. Overridable if a widget wants its own. */ _paintFocus(): void; /** @internal */ _element: HTMLElement | null; protected abstract _build(): HTMLElement; private layer; override onReady(): void; /** * @internal Build and attach, once a host exists. * * A widget inside a UiPanel mounts into the PANEL, not the layer's anchor * slot — that is what turns structure in the tree into structure on screen, * and it is what a menu is. Ready order does not decide it: the panel builds * on demand when a child asks (`_container`), and `update` retries for * anything attached at runtime before its host existed. */ _mountWidget(): void; /** * The gesture, without a pointer — the `press()` of drag and drop. * * All four signals lived on `_wireDrag`, which is wired to a DOM element and * therefore does not exist headless. So the one screen the HUD skill says * needs more than a click — an inventory — was the one screen no harness * could drive: `dragStarted`, `droppedOn`, `dropped` and `dragCancelled` * were unreachable from `runScript`, `incanto-playtest` and every check in * the ladder, and a game that moves items between slots could only be tested * by hand in a browser. * * Same rules as the pointer path, so a test that passes here is a test about * the game: a non-`draggable` source does nothing, a target that is not a * `dropTarget` CANCELS, and dropping a thing back where it came from — on * itself, or on the slot it is already in — is not a move. * * Returns whether the drop was taken. */ dropOnto(target: HudWidgetBase | null): boolean; /** The slot this widget is sitting in, if any — its origin for a drop. */ private dropTargetAbove; /** * Drag and drop, on the DOM the engine already builds. * * An inventory is the one screen where "click it" is not enough, and every * game that wanted one dropped out of scene JSON to hand-roll pointer * handlers. The whole gesture is four signals and no new node type: a * `draggable` widget emits `dragStarted` and, if it lands on one, * `droppedOn(target)`; a `dropTarget` emits `dropped(source)`. Who owns the * ITEM is the game's business — this reports the gesture, not a model. */ private _wireDrag; /** The nearest ancestor that holds widgets itself (a UiPanel). */ private host; override onExitTree(): void; override update(_dt: number): void; /** * The display this widget's own css asks for while shown — `grid` for a grid * panel, `flex` for a row, `''` for a widget that never says. */ private _shownDisplay; /** Which anchor slot this widget was mounted into. */ private mountedAnchor; /** * Move to the slot the anchor now names. * * `_mountWidget` read `anchor` once and `_element` is nulled only on * `onExitTree`, so moving a widget from topLeft to bottomRight — from a * behavior or from the editor's inspector, which live-patches string props — * did nothing at all, forever. */ private _followAnchor; /** * Follow the engine, and let a change made HERE reach it — with or without a * DOM element. No element access allowed: this is the half that runs * headless. */ protected _pull(): void; protected _sync(): void; /** * Resolve a text prop through the engine's locale. * * Called every frame from `_sync`, which is what makes switching language * repaint with nothing to invalidate: widgets already re-read their props, so * the new string simply appears on the next frame. A plain string costs one * `startsWith` and is returned untouched. */ protected _t(value: string): string; /** * A translation with an English wording to fall back to. * * For text the ENGINE supplies — "Music", "Mute", "Unlimited" — where there * is no author string to carry a `@t:` marker. Asks `declares()`, not * `has()`: a key only another locale declares would otherwise come back as * the raw key, which is worse than the English. */ /** * What this widget's label READS as — resolved every frame, never stored. * * `UiVolumeSlider` and `UiMuteToggle` used to compute their auto-label in * `_build()` and write the result back into `this.label`, which broke them * twice over: the per-frame `_t(this.label)` then had a plain string with no * marker left to resolve, and `_build` runs from `onReady` — inside * `loadScene`, BEFORE `setScene` merges the scene's `strings` — so the table * was empty at bake time and the English was permanent even for a game that * boots in another language. */ /** * What this widget actually PAINTS, when that differs from its props. * * A headless capture printed `text="@t:menu.start"` — byte-identical in * every language — so a passing capture proved nothing about localization * and a broken translation was invisible to every check the engine has. * `incanto-frame` reads the WebGL buffer and cannot read DOM text at all. * * `undefined` when the painted words ARE the prop: recording it twice would * be noise on every widget in a game that never localizes anything. */ _paintedText(): string | undefined; protected _labelText(): string; protected _tOr(key: string, fallback: string): string; } /** A text line (score, timer, hints). Set `.text` from behaviors. */ declare class UiText extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; text: string; /** * A template with a `{}` slot that `setText` fills — `"Gems {} / 8"`. * * Empty (the default) means `setText` replaces the whole line. Resolved at * PAINT like `text`, so `"@t:hud.gems"` works and changing locale re-reads * it: the value lives here, the words live in the strings table. */ format: string; size: number; color: string; shadow: boolean; private last; /** null until something has actually set a value — see `shown`. */ private slot; /** * Put a signal's value on screen — `scoreChanged → setText`. * * A `connections[]` handler must be a METHOD and `text` is a property, so the * one wire every game needs (score to screen) could not be declared: every * HUD began with a behavior whose whole job was one assignment. */ setText(value: unknown): void; /** * What it actually paints, after the template and the locale. * * Before the first value arrives, a formatted widget shows `text` — its * authored opening line. Filling the slot with nothing instead would put * `"Gems / 8"` on screen for every game's first frame, and a score line * that starts blank looks broken rather than empty. * * Unless there IS no opening line. `format` alone painted `""`: * * ``` * format = 'Gems {} / 8' * shown before setText "" <- nothing on screen * shown after setText "Gems 3 / 8" * ``` * * and the signals a HUD is wired to — `scoreChanged`, `healthChanged` — only * fire on CHANGE, so the row stays invisible through the whole opening of the * game and forever if the value never moves. An empty slot in a visible * template is the lesser of those two: it shows the author what they * declared, where they declared it. */ get shown(): string; override _paintedText(): string | undefined; protected _build(): HTMLElement; /** The whole appearance as one string, so re-applying is one comparison. */ private look; private lastLook; protected override _sync(): void; } /** A labeled progress bar (health, stamina, reload, boss HP). */ declare class UiBar extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; value: number; max: number; width: number; height: number; color: string; lowColor: string; lowThreshold: number; background: string; label: string; private fill; private tag; private lastLabel; private lastRatio; /** Current fill ratio 0..1 (what the bar shows). */ get ratio(): number; /** * Drive the bar from a signal — `healthChanged → setValue`. * * Takes the max as a second argument because that is the shape the signal * already has, so a bar whose ceiling moves (a max-HP upgrade) needs no * second wire. */ setValue(value: number, max?: number | undefined): void; /** Move the ceiling on its own, leaving the fill where it is. */ setMax(max: number): void; protected _build(): HTMLElement; private track; private lastTrackLook; private lastFillColor; /** The track's whole appearance, so re-applying is one comparison. */ private trackLook; protected override _sync(): void; } /** * Center-screen announcements ("WAVE 2", "YOU DIED", "LEVEL UP") with fade * in/out and a queue — call `.show(text, { color, seconds })`; `sticky:true` * (seconds: 0) keeps it until the next show(). Emits `bannerShown(text)`. */ declare class UiBanner extends HudWidgetBase { static override readonly typeName: string; static readonly signals: string[]; static override readonly props: PropSchema; override anchor: HudAnchor; size: number; /** Default display time (per-show override via options). */ seconds: number; private queue; private current; private remaining; /** What the banner is showing right now ('' when idle) — test-friendly. */ get showing(): string; show(text: string, opts?: { color?: string; seconds?: number; } | undefined): void; /** Drop everything (scene transitions). */ clear(): void; protected _build(): HTMLElement; /** The whole look, so re-applying is one comparison. `size` was baked in at * build, and a banner could never be resized. */ private look; private lastLook; override update(dt: number): void; } //#endregion //#region src/core/nodes/dialogue.d.ts /** * Interactive HUD: clickable buttons and a typewriter dialogue box — the * storytelling layer RPG/adventure games kept hand-rolling in DOM. * Same family as the other HUD widgets: children of a HudLayer, DOM-backed, * silent no-ops headless. */ /** A clickable button (menus, "START", dialog choices). Emits `pressed`. */ declare class UiButton extends HudWidgetBase { static override readonly typeName: string; static readonly signals: readonly string[]; static override readonly props: PropSchema; text: string; size: number; color: string; background: string; disabled: boolean; override focusable: boolean; /** Enter / A on a focused button is a press. */ override _activate(_dir: number): void; private lastText; /** Programmatic press — same path as a click (tests, gamepad menus). */ press(): void; protected _build(): HTMLElement; /** The whole look, so re-applying is one comparison. `size`/`color` were baked * in at build, and a button could never be restyled afterwards. */ private look; private lastLook; protected override _sync(): void; } /** * A typewriter dialogue box with a queue and choices: * * const talk = node.getNode('%Dialogue') as UiDialogue; * talk.say('Elder', 'Welcome to Lumina Village...'); * talk.say('Elder', 'Will you help us?', ['Yes', 'No']); * talk.on('choiceMade', (index) => { ... }); * * Click / `advance()` reveals the full line, then advances. Emits * `lineShown(text)`, `choiceMade(index)`, `dialogueFinished()`. */ declare class UiDialogue extends HudWidgetBase { static override readonly typeName: string; static readonly signals: readonly string[]; static override readonly props: PropSchema; override anchor: HudAnchor; /** Typewriter speed; 0 = instant lines. */ charsPerSecond: number; autoAdvanceSeconds: number; private shownFor; width: number; private queue; private current; private revealed; private speakerEl; private textEl; private choicesEl; private hintEl; /** True while a conversation is on screen (pause player input on this). */ get active(): boolean; /** What the box currently shows (grows while typing) — test-friendly. */ get showing(): string; /** Queue a line; optional `choices` renders buttons after the line types out. */ say(speaker: string, text: string, choices?: string[] | undefined): void; /** * A dialogue takes focus while it is up. * * It paints a focus ring and the generated reference advertises the prop, and * it was inert: choices are raw DOM buttons inside the widget, so * `HudLayer.focusables()` can never see them, and this class — unlike * `UiButton` a hundred lines above it — never overrode `_activate`. Eleven * keys and eight pad codes did nothing to a waiting choice while the same * Enter pressed a sibling button. Native Tab worked, and is closed by any * game that binds Tab; a pad has no Tab at all. */ override focusable: boolean; /** Which choice the keyboard/pad is on. Reset by every new line. */ private cursor; /** * Enter / A, and left / right between the options. * * `dir` is 0 for "activate" and ±1 for a nudge, the same shape `UiSelect` * uses — so one key layout drives a menu, a slider and a conversation. */ /** * A click on the BOX is `advance()`, which refuses to skip a choice. * * The DOM handler is `box.addEventListener('click', () => this.advance())` * and `advance` returns early while `current.choices` is set. Enter is a * different verb — it answers — so a harness clicking the box must not. */ override _click(): void; override _activate(dir: number): void; /** Show which option the keyboard is on — a ring nobody can see is not one. */ private paintCursor; /** * Player pressed "next": mid-typing reveals the whole line; a fully-typed * line without choices advances (choice lines wait for `choose`). */ advance(): void; /** Pick a choice on the current line (buttons call this). */ choose(index: number): void; /** Drop the conversation (cutscene interrupts). */ clear(): void; private next; protected _build(): HTMLElement; /** The locale `current` was resolved in, so a switch is one compare away. */ private resolvedIn; /** Re-read the live line from its untouched source. */ private resolveCurrent; override update(dt: number): void; } //#endregion //#region src/core/nodes/respawn.d.ts /** * Catch whatever this node hangs off when it leaves the world, and put it back. * * Nothing in the engine did this. Not a node, not a behavior, not a scene * header — and the shipped examples prove what that costs: with the `plays` * rung finally counting defects, `basic-3d-sideview` fails 8 seeded runs out of * 8, `water-lake-3d` 7, `water-ocean-3d` 5, `water-pool-3d` 4, * `water-river-3d` 1. Every one of them the same way — the player walks off the * edge of the terrain and falls forever, and the only fix available was a * per-frame `y` check hand-written in TypeScript, which is exactly the kind of * thing this engine exists to keep out of TypeScript. * * A CHILD NODE rather than a Behavior, deliberately: a node holds one behavior * and the player's is already spoken for (`PlayerControl`, `Health`, the game's * own script). Children stack, the way `CharacterController3D` already hangs * off its body. * * ```jsonc * { "name": "Player", "type": "RigidBody3D", "props": { … }, * "children": [ * { "name": "Ctl", "type": "CharacterController3D" }, * { "name": "Catch", "type": "Respawn" } // that is the whole thing * ] } * ``` * * With no props it guards its parent, uses the same line the playtest's fall * detector uses (50 m under the spawn in 3D, 1000 px in 2D), and puts the node * back where it started. `respawned` fires each time, so a life can be taken * off a `ScoreKeeper` or a `Health` with an ordinary connection — this node * does not decide what falling COSTS, which is a game-design question and not * a physics one. */ declare class Respawn extends Node { static override readonly typeName: string; static override readonly signals: readonly string[]; static readonly props: PropSchema; /** * Who is being caught. Defaults to the parent, which is the whole point of * hanging this off the thing it guards. */ target: string; /** * The line past which the target has left the world, in the scene's own * down: 3D counts down (−y), 2D counts down the screen (+y). * * `null` is AUTO — the spawn position offset by the same distance the * playtest's own fall detector uses, so "the level says it fell" and "the * ladder says it fell" cannot disagree. Author a number when the level has a * real floor to name; leave it alone otherwise. */ below: number | null; /** Where to put it back. Empty = wherever it started. */ to: number[]; /** * Zero the body's velocity on the way back. * * On by default because the alternative is invisible: a body respawned with * 40 m/s of accumulated fall still in it drops straight back through the * line on the next frame, and the level flickers instead of resuming. */ resetVelocity: boolean; /** How many times this has caught the target — for HUDs and tests. */ falls: number; private home; private line; /** +1 when "out" means y greater (2D screen-down), −1 when smaller (3D). */ private down; override onReady(): void; private spawnY; /** * Which way is out, and where the line is. * * Resolved on the first `update` rather than in `onReady`, because * `onReady` runs inside `loadScene` — BEFORE `setScene` attaches the engine — * so the scene's `dimension` is not readable there yet. Reading it there * silently gives every 2D scene the 3D answer, and a line above a falling * player fires on frame one, forever. * * Not called `resolve`: `Node` has a private method by that name and * `getNodeOrNull` calls it, so a subclass method with the same name * overrides it on the prototype — `private` is a TypeScript fiction that * costs nothing at runtime — and every path lookup this node made returned a * number. `node-method-shadowing.test.ts` refuses the whole family now. */ private fallLine; /** The node this guards, or itself if the path no longer resolves. */ private guarded; override update(_dt: number): void; } //#endregion //#region src/core/nodes/timer.d.ts /** * The canonical serializable game clock — never `setTimeout` in game logic. * Emits `timeout` every `waitTime` seconds (once with `oneShot`). */ declare class Timer extends Node { static override readonly typeName: string; static override readonly signals: readonly string[]; static readonly props: PropSchema; /** Seconds between timeouts. */ waitTime: number; oneShot: boolean; autostart: boolean; running: boolean; private remaining; /** Seconds until the next `timeout` — a countdown a HUD can draw. `0` when stopped. */ get timeLeft(): number; start(time?: number): void; stop(): void; /** * Scene-load validation: a period of zero is a timer that never fires. * * `update` guards it — a non-positive period "would spin forever on any dt", * so it calls `stop()` — and the guard is right. Being SILENT about it is * what a load error is for: a scene asking for a tick every zero seconds got * no ticks and no word said. The runtime guard stays, for code that zeroes * the period from a handler. */ static validateJson(node: Node): void; override onReady(): void; override update(dt: number): void; } //#endregion //#region src/core/nodes/ui-kit.d.ts /** * A box that holds other widgets — the thing a menu actually is. * * Widgets normally mount into their HudLayer's anchor slot, which is why a * "menu" used to be a flat pile of anchored items with no way to sit them in a * panel, space them, or lay them out in a grid. A `UiPanel` becomes the parent * for every widget under it, so structure in the TREE is structure on SCREEN. * * ```json * { "name": "Pause", "type": "UiPanel", "props": { "anchor": "center", "layout": "column", "gap": 12 }, * "children": [ * { "name": "Title", "type": "UiText", "props": { "text": "PAUSED", "size": 28 } }, * { "name": "Resume", "type": "UiButton", "props": { "label": "Resume" } } * ] } * ``` */ declare class UiPanel extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; layout: "column" | "row" | "grid"; columns: number; gap: number; padding: number; background: string; radius: number; width: number; height: number; border: string; private lastKey; protected _build(): HTMLElement; /** * @internal Children mount HERE, not in the layer's anchor slot. * * Builds on demand: a child asking for its host must not depend on whose * `onReady` the tree happened to run first. */ _container(): HTMLElement | null; private css; protected override _sync(): void; } /** A picture: an item icon, a portrait, a logo. `src` takes a url or `$assetKey`. */ declare class UiImage extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; src: string; width: number; height: number; fit: "contain" | "cover" | "fill"; tint: string; opacity: number; private last; protected _build(): HTMLElement; private resolved; private key; private css; protected override _sync(): void; } /** * A value the player sets — volume, sensitivity, difficulty. * * Emits `changed(value)`. `incanto-audio.md` promised "a settings slider needs * no extra wiring" while no slider existed; this is that slider. */ declare class UiSlider extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; static override readonly signals: readonly string[]; label: string; value: number; min: number; max: number; step: number; width: number; color: string; override focusable: boolean; /** Left/right nudge by one step — the only sane thing a d-pad can mean here. */ override _activate(dir: number): void; /** * Move it as a PERSON would: set the value and say so. * * `UiButton` has `press()` and the value widgets had nothing, because * assigning `.value` deliberately does not emit (restoring a saved setting * must not fire the handler that saved it). So a harness — or a game driving * its own menu: a preset button, "reset to defaults", a tutorial that moves a * slider for you — had one route to "the player changed this", and it was * `_activate`, which is internal and takes a direction rather than a value. */ choose(value: number): void; private input; private text; private last; protected _build(): HTMLElement; protected override _sync(): void; } /** An on/off switch — mute, invert-Y, fullscreen. Emits `changed(boolean)`. */ declare class UiToggle extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; static override readonly signals: readonly string[]; label: string; value: boolean; override focusable: boolean; /** Confirm flips it; left/right set it explicitly, which reads better on a pad. */ override _activate(dir: number): void; /** Flip it as a PERSON would: set the value and say so. See `UiSlider.choose`. */ choose(value: boolean): void; private input; private last; protected _build(): HTMLElement; private tag; private lastLabel; protected override _sync(): void; } /** One of several — quality, resolution, language. Emits `changed(value)`. */ declare class UiSelect extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; static override readonly signals: readonly string[]; label: string; options: string; value: string; override focusable: boolean; /** The options, as the list of values it actually offers. */ private choices; /** * A value the list cannot show is a setting the game and the screen disagree * about. * * The browser cannot select an option that does not exist, so it shows the * FIRST one — while `node.value` keeps answering with what it was given, and * `changed` only fires when a person moves it, so nothing ever reconciles * them. That is the shape a saved setting has after a patch removes a quality * tier, and it is silent for the rest of the session. * * A hard error at LOAD, like every other enum in this engine. An empty value * is "nothing chosen yet", which is a choice. */ static validateJson(node: Node): void; /** Left/right (and confirm) walk the list — a select has no other gesture. */ override _activate(dir: number): void; /** * Pick an option as a PERSON would: set the value and say so. See * `UiSlider.choose`. * * An option the list does not offer is refused, for the reason * `validateJson` refuses it in the file: the browser would show the first one * while the node kept answering with what it was given, and nothing would * ever reconcile them. */ choose(value: string): void; private select; private labelEl; private lastLabel; private lastOptions; private last; protected _build(): HTMLElement; /** * What an option READS as, when it differs from its value. * * The base resolves `@t:` like every other text prop: the VALUE stays exactly * what the author wrote — so a `changed` handler never has to know the active * language — and only the displayed words change. Without this a dropdown was * the one widget with no way to speak anything but English, and * `"options": "@t:diff.easy"` rendered that marker literally on screen. */ protected _optionLabel(value: string): string; private fillOptions; /** What was already reported, so a stuck value is said once and not per frame. */ private complainedAbout; /** * The runtime half of `validateJson`. * * A behaviour may legitimately write the value first and widen `options` * after — so this reports rather than refuses, and goes quiet again when the * list catches up. */ private reportMismatch; protected override _sync(): void; protected override _pull(): void; } /** * The language picker, done — because every game needs the same one. * * A plain `UiSelect` bound to the locale would take a behavior, a signal * connection, and the knowledge that `Settings` persists it: three things an * agent has to get right to ship a feature the engine already has. This is one * node. * * ```json * { "name": "Language", "type": "UiLanguageSelect", * "props": { "label": "@t:settings.language" } } * ``` * * Options are the locales the SCENE declares, so a game shipping only English * shows only English and the widget quietly costs nothing. Each is labeled with * its own endonym, because a player who cannot read the language currently on * screen still has to be able to find theirs. */ declare class UiLanguageSelect extends UiSelect { static override readonly typeName: string; static override readonly props: PropSchema; override label: string; /** Last locale we PUSHED into `value`, to tell an engine change from a player one. */ private lastLocale; protected override _pull(): void; protected override _optionLabel(value: string): string; } /** * A select bound to one persisted setting — the shape all three graphics * controls share. * * The engine owns quality tiers, the frame cap and the resolution scale, and * every one of them was reachable only from TypeScript. A vibe-coded game whose * whole world is JSON could not put a graphics menu on screen without a * behavior, a signal connection, and the knowledge that `Settings` persists — * which is how "supports low-end devices" quietly becomes "does not". * * Subclasses say which setting, what the options are, and what they read as. */ declare abstract class UiSettingSelect extends UiSelect { /** Last value we PUSHED into the select, to tell an engine change from a player one. */ private lastPushed; /** The settings key this control owns. */ protected abstract _key(): string; /** The stored value, as the string the select carries. */ protected abstract _read(engine: NonNullable>): string; /** Apply a player's pick. */ protected abstract _write(engine: NonNullable>, value: string): void; protected _engine(): Engine | null; /** * The translated option label, or the English one. * * `locale.t` returns the KEY when nothing declares it, and a menu reading * `settings.quality.high` is worse than one reading `High`. English is the * base language and the fallback, always. */ protected override _pull(): void; protected override _sync(): void; } /** * The graphics quality picker. * * ```json * { "name": "Quality", "type": "UiQualitySelect" } * ``` * * Writes through `chooseQuality`, NOT `set('quality')`: a tier a person picked * has to survive the next launch's device detection, and the two are * indistinguishable afterwards unless the choice is recorded as one. * * Shadows, bloom, post and clouds follow immediately. Antialiasing follows on * the next launch — it is a WebGL context attribute and cannot change while a * context exists. */ declare class UiQualitySelect extends UiSettingSelect { static override readonly typeName: string; static override readonly props: PropSchema; override label: string; override options: string; protected override _key(): string; protected override _read(engine: Engine): string; protected override _write(engine: Engine, value: string): void; protected override _optionLabel(value: string): string; } /** * The frame-rate cap: fewer frames, a cooler phone, a longer battery. * * ```json * { "name": "FrameCap", "type": "UiFrameCapSelect" } * ``` * * `0` is uncapped and is the default — a cap nobody asked for is a downgrade. * The cap gates the whole frame, so it saves the CPU as well as the GPU. */ declare class UiFrameCapSelect extends UiSettingSelect { static override readonly typeName: string; static override readonly props: PropSchema; override label: string; override options: string; protected override _key(): string; protected override _read(engine: Engine): string; protected override _write(engine: Engine, value: string): void; protected override _optionLabel(value: string): string; } /** * The resolution scale — the cheapest frames on a weak GPU. * * ```json * { "name": "Resolution", "type": "UiRenderScaleSelect" } * ``` * * At 75% the fragment shaders run over 56% of the pixels for a slightly softer * image and nothing else, where dropping a quality tier costs the shadows * outright. Reads as a percentage, because `0.75` means nothing to a player. */ declare class UiRenderScaleSelect extends UiSettingSelect { static override readonly typeName: string; static override readonly props: PropSchema; override label: string; override options: string; protected override _key(): string; protected override _read(engine: Engine): string; protected override _write(engine: Engine, value: string): void; protected override _optionLabel(value: string): string; } /** The three buses a player is ever offered a slider for. */ declare const VOLUME_BUSES: readonly ["master", "sfx", "music"]; type VolumeBus = (typeof VOLUME_BUSES)[number]; /** * The volume slider — the settings row every game has. * * ```json * { "name": "Music", "type": "UiVolumeSlider", "props": { "bus": "music" } } * ``` * * The graphics controls above became nodes because a game whose whole world is * JSON could not put a menu on screen without a behavior, a signal connection * and the knowledge that `Settings` persists. All of that was just as true of * volume, which is the control a player looks for FIRST and the only one some * games need at all — so an audio menu meant hand-rolled DOM, and hand-rolled * DOM is out of scene JSON, out of the editor, and out of every headless check. * * It writes the bus; the bus persists itself (`bindAudio`, which * `createGame2D/3D` wire), so there is nothing to save. */ declare class UiVolumeSlider extends UiSlider { static override readonly typeName: string; static override readonly props: PropSchema; bus: VolumeBus; /** Last value we PUSHED in, to tell an engine-side change from a player drag. */ private lastPushed; protected override _labelText(): string; protected override _pull(): void; } /** * The mute switch. * * ```json * { "name": "Mute", "type": "UiMuteToggle" } * ``` * * Checked means silent. Like the slider it writes `engine.audio`, which * persists itself. */ declare class UiMuteToggle extends UiToggle { static override readonly typeName: string; static override readonly props: PropSchema; private lastPushed; protected override _labelText(): string; protected override _pull(): void; } //#endregion //#region src/core/nodes/ui-minimap.d.ts /** One thing on the map, in canvas pixels from the top-left. */ interface MapMarker { x: number; y: number; color: string; group: string; node: Node; } /** * A minimap — the world around one node, top-down, as dots on a disc. * * ```json * { "name": "Map", "type": "UiMinimap", * "props": { "anchor": "topRight", "size": 160, "radius": 40, * "dots": { "enemy": "#ff5050", "pickup": "#ffd54f", "goal": "#3aa0ff" } } } * ``` * * It follows the first node in the `player` group unless `follow` names * one, shows every node in the groups listed in `dots` within `radius` world units * (metres in 3D, px in 2D — the plane is x/z in 3D and x/y in 2D), and can * turn with a `heading` node so "ahead" is up. Everything it draws is a * number first: `markers()` is the same list headless, so a harness can ask * what the map shows. */ declare class UiMinimap extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; size: number; radius: number; follow: string; dots: Record; heading: string; self: string; dotSize: number; background: string; shape: string; private canvas; private centreNode; private members; private scanIn; /** * Loader hook: `dots` is a name map — its KEYS are the author's group names, * so any key is right — and every VALUE must be a colour string. A number or * an object where a colour should be drew nothing, silently. */ static validateJson(node: Node): void; /** The followed node, or null when nothing is in the `player` group either. */ centre(): Node | null; /** Every dot on the map right now, in canvas px — the followed node is not among them. */ markers(): MapMarker[]; /** The heading's yaw in radians, or null for north-up. */ private headingYaw; private rescan; protected _build(): HTMLElement; protected override _sync(): void; } //#endregion //#region src/core/nodes/ui-waypoint.d.ts /** Where the marker is on screen right now — what `screen()` answers, headless too. */ interface WaypointScreen { /** Canvas px, after clamping. */ x: number; y: number; /** Direction from the screen centre to the target, radians, 0 = right, y down. */ angle: number; /** The target is behind the camera (mirrored through the centre, then clamped). */ behind: boolean; /** The target itself projects inside the canvas, in front of the camera. */ onScreen: boolean; /** The marker was moved to the margin rectangle. */ clamped: boolean; /** Metres from `from` (the player) to the target. */ distance: number; /** After `visible` and `hideWithin`. */ visible: boolean; } interface View { width: number; height: number; } /** * An objective marker — a diamond over the target, an arrow at the screen's * edge when it is off screen or behind you, the metres left under it. * * ```json * { "name": "Mark", "type": "UiWaypoint", * "props": { "target": "/root/Shop1", "label": "bakery", "hideWithin": 4, "arriveWithin": 3 } } * ``` * * It floats over `target` through `engine.toScreen` (the renderer's own * projection), so `anchor` means nothing here. Off screen it slides to the * `margin` rectangle along the line from the centre and turns its arrow that * way; behind the camera it is mirrored through the centre first, so it * still points the way to turn. `distance` is measured from `from` — the * first node in the `player` group by default. `hideWithin` hides it when * you are that close; `arrived` fires once when `from` comes within * `arriveWithin`, and again after leaving and coming back. * * `screen()` is the same answer headless, from whatever `toScreen` a test * installs — a harness can ask where the marker is and which way it points. */ declare class UiWaypoint extends HudWidgetBase { static override readonly typeName: string; static override readonly props: PropSchema; static override readonly signals: readonly string[]; target: string; label: string; color: string; size: number; distance: boolean; clamp: boolean; margin: number; hideWithin: number; arriveWithin: number; from: string; private inside; private box; private arrow; private diamond; private text; /** Metres from `from` to the target, or null when either is missing. */ distanceNow(): number | null; /** * Where the marker is, in canvas px, for a view this big (the canvas, in the * browser). Null when there is no target or no renderer has offered a * projection. */ screen(view?: View): WaypointScreen | null; override update(dt: number): void; protected _build(): HTMLElement; protected override _sync(): void; /** The canvas's size in the browser; a stand-in headless. */ private viewport; private targetNode; private fromNode; } //#endregion //#region src/core/noise.d.ts /** * Seeded 2D simplex noise (Ken Perlin's simplex, Gustavson's reference * implementation) returning values in [-1, 1]. The permutation table is a * Fisher–Yates shuffle drawn from our deterministic {@link Rng}, so an * integer seed reproduces the identical field on every machine — the same * contract as every other seeded generator in the engine. Pure math, no deps. */ declare function createNoise2D(seed: number): (x: number, y: number) => number; //#endregion //#region src/core/particle-presets.d.ts /** * Predefined particle looks. A node's `preset` seeds these values UNDER its * explicit props: schema default < preset < anything the scene JSON wrote. * Values are 2D px-space; Particles3D rescales distances to meters. */ interface ParticlePresetValues { rate?: number; burst?: number; lifetime?: [number, number]; speed?: [number, number]; directionDeg?: number; spreadDeg?: number; gravity?: [number, number]; drag?: number; sizeStart?: number; sizeEnd?: number; colorStart?: string; colorEnd?: string; alphaStart?: number; alphaEnd?: number; blend?: "add" | "normal"; } declare const PARTICLE_PRESETS: Record; declare const PARTICLE_PRESET_NAMES: readonly string[]; /** * Apply the delta rule across three layers: schema default < preset < the * scene's explicit props. A prop still equal to its SCHEMA default is * considered "unset" and takes the preset value. */ declare function applyParticlePreset(target: Record, presetName: string, schema: PropSchema): void; //#endregion //#region src/core/preload.d.ts interface PreloadResult { /** Urls whose fetch failed — show an error UI instead of pretending success. */ failed: string[]; } /** * Warm the HTTP cache for a list of asset urls with a progress callback — * the loading-bar pattern every template ships. Failures only warn and still * count toward progress (a broken url must not block the game), but they are * reported in the result so callers can tell success from 100%-with-holes. */ declare function preloadUrls(urls: string[], onProgress?: (loaded: number, total: number) => void): Promise; /** Every url found in a scene's `assets` block (preload helper). */ declare function assetUrls(assets: Record | undefined): string[]; /** * Preload every scene-asset URL behind the standard overlay, then remove it: * * await preloadSceneAssets(sceneJson.assets); */ declare function preloadSceneAssets(assets: Record | undefined, title?: string): Promise; //#endregion //#region src/core/register.d.ts /** * Register the core node types. Call once in your game entry before loading * scenes. Registration is explicit — never an import-time side effect — so * bundler tree-shaking can never silently drop node types. */ declare function registerCoreNodes(): void; //#endregion //#region src/core/rendering-options.d.ts /** * Scene-declared rendering settings (`environment.rendering`): the scene JSON * carries them, so a loaded scene LOOKS the way it was authored everywhere — * the editor's play mode included. * * Precedence: explicit renderer constructor options > scene > renderer * fallback. `pixelRatio: "device"` resolves to the device pixel ratio. */ interface ResolvedRendering { antialias: boolean; pixelRatio: number; /** * Keep the drawing buffer readable after a frame (default false). * * WebGL clears it as soon as the frame is composited, so `canvas.toDataURL()` * — and therefore ANY screenshot a driver takes — comes back blank. Turning * this on costs a buffer copy per frame, which is why it is off for games and * on for the run where something is trying to LOOK at the output. */ preserveDrawingBuffer: boolean; } declare function resolveRendering(environment: JsonObject | undefined, fallback: ResolvedRendering, devicePixelRatio: number, explicit?: { antialias?: boolean; pixelRatio?: number; preserveDrawingBuffer?: boolean; }): ResolvedRendering; //#endregion //#region src/core/scene/constants.d.ts /** The single reserved key marking a prop value as a constant reference. */ declare const CONST_REF_KEY = "@const"; /** A `{"@const": "NAME"}` reference — exactly that one string key, nothing else. */ declare function isConstRef(value: JsonValue | undefined): value is { "@const": string; }; /** * Deep-resolve every `{"@const": "NAME"}` reference in `value` to its literal * from `constants` (cloned, so the table is never aliased into node state). * Unknown names hard-fail — agents self-correct on hard errors. */ declare function resolveConstants(value: T, constants: Record | undefined): T; //#endregion //#region src/core/scene/duplicate.d.ts /** * Deep-clone a node subtree (detached — add it wherever you like; sibling * auto-rename applies on attach). Implemented as serialize → rebuild so a * duplicate is exactly what would survive a save/load round-trip — EXCEPT * uids: clones are new entities and get a fresh identity (no uid), so * duplicating a uid'd template can never mint colliding ids. Names ARE * kept — rename clones you need to look up individually. */ declare function duplicateNode(node: Node, opts?: LoadSceneOptions): Node; //#endregion //#region src/core/scene/serializer.d.ts /** * Serialize a node subtree to scene JSON with delta-only props: values equal * to the type's schema defaults are omitted (Godot PackedScene behavior — * files stay tiny and AI-readable). */ declare function serializeNode(node: Node): NodeJson; //#endregion //#region src/core/touch.d.ts /** * Pure joystick math: drag offset (px) → normalized direction, deadzoned at * the center and clamped to unit length at the rim. */ declare function joystickVector(dx: number, dy: number, radius: number): { x: number; y: number; }; interface DocumentLike { createElement(tag: string): HTMLElement; } interface TouchControlsOptions { /** Show even on non-touch environments (demos, tests). */ force?: boolean; /** @internal Test seam — replaces `document`. */ doc?: DocumentLike; } /** * On-screen touch controls driven by the scene's own input map: every action * declared with `"touch": "joystick"` gets a left-side virtual stick feeding * `setActionVector`, every `"touch": "button"` a right-side button feeding * `pressAction`/`releaseAction`. The game never knows the difference between * touch and keyboard — both arrive as actions. * * `attachTouchControls(engine, container)` shows them automatically on * coarse-pointer devices ('auto' in createGame); `force` shows them anywhere. * The container should be `position: relative/absolute` over the canvas. */ declare function attachTouchControls(engine: Engine, container: HTMLElement, opts?: TouchControlsOptions): TouchControls | null; declare class TouchControls { private readonly elements; private readonly detachers; constructor(engine: Engine, container: HTMLElement, controls: Array<{ action: string; kind: "joystick" | "button"; }>, doc: DocumentLike); dispose(): void; private buildJoystick; private buildButton; } //#endregion //#region src/core/uid-gen.d.ts /** * The ONE way to mint a node uid: crypto-strength, `n_` + 16 base36 chars * (~82 bits). Hand-written uids are forbidden by convention — they break the * collision-strength contract and read as fakes next to generated ones. */ declare function newUid(): string; //#endregion //#region src/core/viewport.d.ts type ViewportFit = "expand" | "letterbox" | "integer"; interface ResolvedViewport { /** Design resolution [width, height] — the world space the game is authored in. */ design: [number, number]; fit: ViewportFit; } /** * Parse + hard-validate a scene's `viewport` header. With a design resolution, * scene JSON owns layout again: the game is authored in fixed design pixels * and the renderer maps them onto whatever canvas size the page provides. */ declare function resolveViewport(viewport: JsonObject | undefined): ResolvedViewport | null; interface ComputedViewport { /** Design-px → canvas-px scale factor. */ scale: number; /** World pixels visible horizontally/vertically. */ viewW: number; viewH: number; /** Letterbox bar offsets in canvas px (0 for expand/integer). */ offsetX: number; offsetY: number; /** Canvas px actually covered by game content. */ contentW: number; contentH: number; } /** * Pure viewport math (headless-testable): * - expand: the design rect is always fully visible; extra world shows beyond it * - letterbox: EXACTLY the design rect, centered, bars elsewhere * - integer: expand with whole-number scaling (pixel art), floored, min 1 */ declare function computeViewport(canvasW: number, canvasH: number, viewport: { design: [number, number] | readonly number[]; fit: ViewportFit; }): ComputedViewport; //#endregion //#region src/index.d.ts /** Engine version. Kept in sync with package.json by the release pipeline. */ declare const VERSION: string; //#endregion export { type AnimationDef, type AnimationEntry, type AssetFailure, AudioBuses, type AudioElementLike, AudioPlayer, BASE_LOCALE, Behavior, type BehaviorCtor, type BehaviorState, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, type DeviceHints, type DriveScript, type DriveStep, type EffectEvent, type EffectKind, EffectLog, Engine, type EngineOptions, type EngineStats, type FindPathOptions, type GameStats, type GroupedLog, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LiveInput, type LiveReport, type LoadSceneOptions, type LocaleTables, Localization, type LogEntry, type LogLevel, LogManager, type MapMarker, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, ORDER_GROUP_BASE, type OrderGroup, type OrderGroupTable, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PathGrid, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, type QualityTier, ROLLOFF_MODELS, type Recorder, type RefNode, type RefProblem, type RefResult, type RendererStats, type ReplayEvent, type ReplayJson, type ResolvedRendering, type ResolvedViewport, Respawn, type RestoreReport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveSlot, SaveSlots, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, Settings, type SettingsValues, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, T_PREFIX, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMinimap, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, UiWaypoint, VERSION, type Vec3, type ViewportFit, type Voice, type VoicePreset, type WaypointScreen, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, isWebGLAvailable, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, showBootFailure, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };