import { i as DrawCommand, m as Resource, n as DisplayList } from "./displayList.js"; //#region src/collapseReplacer.d.ts /** * The byte-preserving collapse-replacer (DESIGN.md §3.3 / §3.5). * * Extracted to its OWN tiny module in the 0.20 budget review so the heavy * DEV/CLI diagnostic surface (`diffDisplayLists`/`serializeDisplayList`/… in * `displayDiff.ts`, now on the `@glissade/scene/diagnostics` subpath) can leave * the base scene graph while THIS function — which IS on the render path — stays * reachable from `displayList.ts` (the §3.5 cacheKey serializer) at a few bytes. * Before the split, `displayList.ts` imported `collapseReplacer` from * `displayDiff.ts`, dragging the entire diff/snapshot module into every embed. * * Shared by the cacheKey serializer (`createDisplayListBuilder().cacheKey`), * `cacheColdAudit.hashDisplayList`, and `serializeDisplayList`. CRITICAL: this is * BYTE-PRESERVING — the cacheKey it backs stamps into pushGroup and keys the * §3.5 raster cache, so its output must not move. The three call sites previously * DUPLICATED this byte-for-byte; it lives here once. * * - ArrayBuffer / typed-array views collapse to a `ab:` / `view:` * length marker (opaque binary never belongs in a structural key). * - Functions drop (JSON would already drop them; explicit for parity). * - Non-finite numbers (`NaN`/`Infinity`/`-Infinity`) collapse to DISTINCT * string sentinels. `JSON.stringify` natively serializes all three to the * SAME token (`null`), which would collide the cacheKey of two DisplayLists * that differ only in WHICH non-finite value reaches a draw field — a stale * raster + an `auditCacheCold` false-OK. The distinct sentinels keep them * apart. This does NOT touch FINITE numbers (the common path): only the * three non-finite inputs change, so the §3.5 cacheKey bytes for every real * (finite) list are byte-identical — pinned by the regression guard. * * NOTE: `-0` is intentionally NOT normalized here. The matrix layer * (`matrix.ts`) already normalizes `-0 → 0` at the source, and adding a `-0` * pass to THIS replacer would change the cacheKey bytes for any list that ever * carried a raw `-0` — silently invalidating the cache cluster-wide. (`-0` is * finite, so the non-finite branch never touches it.) Byte preservation wins; * the regression guard pins the exact key. */ declare function collapseReplacer(_key: string, value: unknown): unknown; //#endregion //#region src/displayDiff.d.ts /** * One index-aligned, positional delta between two DisplayList command streams. * * This is the natural per-command shape that `gs verify-determinism --bisect` * (a later card) will consume to drill a cache-cold divergence down to the * exact op/field. Keep it stable. */ interface CommandDelta { /** Positional command index in BOTH lists (or the longer one for add/remove). */ index: number; /** * - `change` — same index present in both, but the commands differ. * - `add` — present in `b` only (b is longer past this index). * - `remove` — present in `a` only (a is longer past this index). */ kind: 'change' | 'add' | 'remove'; /** op of the `a` command (undefined for `add`). */ opA?: DrawCommand['op']; /** op of the `b` command (undefined for `remove`). */ opB?: DrawCommand['op']; /** * Field-level changes when both ops match (op changed → a single `op` field). * Each entry names the changed prop path with its `from`/`to` JSON values. */ fields: FieldChange[]; } interface FieldChange { /** dotted field path within the command, e.g. `paint.color`, `m`, `text`, `filters`. */ path: string; from: unknown; to: unknown; } interface DisplayDiff { /** true when the two lists are byte-identical (the §3.3 determinism contract holds). */ equal: boolean; /** per-command positional deltas, in index order (empty iff `equal`). */ deltas: CommandDelta[]; /** size mismatch, when the canvases differ (rare, but a real divergence). */ size?: { from: DisplayList['size']; to: DisplayList['size']; }; } /** * Index-aligned positional diff of two DisplayLists. Command i in `a` is * compared to command i in `b`; trailing commands become `add`/`remove`. A * single insert/remove cascades — this is the documented v1 cliff. */ declare function diffDisplayLists(a: DisplayList, b: DisplayList): DisplayDiff; /** Human-readable, multi-line rendering of a DisplayDiff (CLI command-tree). */ declare function formatDisplayDiff(diff: DisplayDiff): string; /** * The `.dl.json` snapshot interchange schema (DESIGN §7.4). Users commit * `.dl.json` baselines, so this carries the SAME break-policy obligation as * `Timeline.version` and `SidecarDoc.sidecarVersion`: bump on a breaking shape * change. Independent of the API version. */ declare const DL_SNAPSHOT_VERSION: 1; interface DlSnapshot { /** Interchange schema version (§7.4) — the third versioned interchange document. */ dlSnapshotVersion: typeof DL_SNAPSHOT_VERSION; size: DisplayList['size']; commands: DrawCommand[]; resources: Resource[]; } /** * Serialize a DisplayList to a stable `.dl.json` document string (reusing the * byte-preserving collapse-replacer). Round-trips through `parseDisplaySnapshot`. */ declare function serializeDisplayList(dl: DisplayList): string; declare class DlSnapshotError extends Error { constructor(message: string); } /** Parse a `.dl.json` snapshot back into a DisplayList (validates the version). */ declare function parseDisplaySnapshot(json: string): DisplayList; //#endregion //#region src/guards.d.ts /** * The node-level localization a violation carries when a dev locator ran on the * throw branch: the first node whose isolated emit disagrees, plus (when a leaf * localized it) the first command-level delta of that emit. */ interface ViolationDetail { readonly node?: string | undefined; readonly detail?: CommandDelta | undefined; /** * Why the locator could NOT name a node, when it ran but was defeated (e.g. * `createScene()` returned shared node instances, so the twice-eval probe * memoized an impure signal and couldn't localize). Present INSTEAD of `node` * — it makes the throw say out loud why click-to-line didn't fire, and how to * fix it, rather than silently degrading to a bare violation. */ readonly reason?: string | undefined; /** * The message fragment appended to the thrown error, PREBUILT by the locator * (off the SACRED base embed) from `node`/`reason` — so the base * `DeterminismViolationError` constructor carries no per-branch message * literals, only the interpolation. `undefined` ⇒ nothing to append. */ readonly where?: string | undefined; } /** * DEV-only callback invoked ONLY when a violation is about to be thrown, to name * the first node that disagrees. It runs AFTER the guarded globals are restored, * so it may freely re-evaluate the scene (its cold re-eval isn't re-trapped). * Returns `undefined` when it can't localize (then the bare throw stands). Wired * by callers that hold a scene factory (the CLI render path) via * `locateViolation(createScene, doc, t)`; never installed on the render hot path. */ type ViolationLocator = () => ViolationDetail | undefined; declare class DeterminismViolationError extends Error { /** The banned API whose call tripped the guard (e.g. `'Math.random'`). */ readonly api: string; /** * id of the FIRST node whose isolated emit disagrees across a cold re-eval — * the click-to-line culprit (§5.5). Set only when a dev locator ran on the * throw branch (the guarded CLI render path); `undefined` for a bare throw * with no locator wired. */ readonly node?: string | undefined; /** * The first command-level delta (op + field changes) of the divergent node's * isolated emit, when a specific leaf localized it. A locator payload — never * produced on the render hot path. */ readonly detail?: CommandDelta | undefined; /** * Why localization was unavailable, when the locator ran but couldn't name a * node (set INSTEAD of `node` — e.g. shared-instance builds defeated the cold * probe). `undefined` for a bare throw or a successfully-localized one. */ readonly reason?: string | undefined; constructor(api: string, located?: ViolationDetail | undefined); } type GuardMode = 'throw' | 'warn' | 'off'; /** * Run `fn` (a single synchronous evaluate()) with the banned globals guarded. * `throw` rejects any call (CLI/CI); `warn` warns once per API then delegates * to the real implementation (browser/dev); `off` is a no-op. Globals are * always restored, even if `fn` throws. `fn` MUST be synchronous — patching is * only valid for the sync read phase, never across an await. * * `locate` (DEV-only, `throw` mode) is invoked ONLY on the violation branch, * AFTER globals are restored, to enrich the thrown error with the first node * that disagrees (see {@link ViolationLocator}). It NEVER runs on the happy path * — a clean evaluate pays nothing for it. A locator failure is swallowed so it * can never mask the original violation. */ declare function withDeterminismGuards(mode: GuardMode, fn: () => T, locate?: ViolationLocator): T; //#endregion export { withDeterminismGuards as a, DisplayDiff as c, FieldChange as d, diffDisplayLists as f, collapseReplacer as g, serializeDisplayList as h, ViolationLocator as i, DlSnapshot as l, parseDisplaySnapshot as m, GuardMode as n, CommandDelta as o, formatDisplayDiff as p, ViolationDetail as r, DL_SNAPSHOT_VERSION as s, DeterminismViolationError as t, DlSnapshotError as u };