/** * WASM transform backend: uploads a {@link TransformStore}, runs the f64x2 SIMD * kernel, and reads world matrices back. This is an invisible accelerator — * {@link composeJS} computes the identical result and is the permanent fallback, * so a caller that cannot instantiate WASM (CSP, no SIMD, missing asset) simply * keeps using the JS path. Failure is the default state, not an error path. * * The seam is two batched crossings per compose (upload, readback), never per * entity: at ~12-31 ns/crossing a per-entity call at 100k would cost >1 ms/frame. */ import type { TransformStore } from './soa'; /** * Status codes returned by the crate's fallible exports, mirroring the `STATUS_*` * constants in `crates/vectojs-core-rs/src/lib.rs`. * * The kernels used to trust their arguments completely — the Safety contracts * were enforced only by this file's calling convention, and PR #136's review * found two out-of-bounds read paths that way. Now a rejected call writes nothing * and reports why, so a bad batch degrades to the JS path instead of rendering * from a half-written store. */ export declare const WASM_STATUS: { readonly OK: 0; /** A count exceeded what `init` allocated. */ readonly CAPACITY: 1; /** A kernel ran before `init`. */ readonly UNINITIALIZED: 2; /** A sibling run addressed a slot or parent outside the store. */ readonly BAD_RUN: 3; /** A requested capacity's size arithmetic overflowed; the previous store * was left untouched. */ readonly OVERFLOW: 4; }; /** * True when a backend's resident typed-array views must be rebuilt: the shared * `WebAssembly.Memory` buffer they were constructed over has been detached * (reads as `byteLength === 0`) or replaced. Every backend of a Scene shares one * instance and therefore one linear memory, so ANY backend's `*_init` can grow * it and detach the views of every other backend. The transform backend keeps * this check inline in {@link WasmTransformBackend.revalidateViews}; the anim/ * hit/particle backends reuse this helper. */ export declare function viewsStale(cap: number, probe: ArrayBufferView, memory: WebAssembly.Memory): boolean; /** Which kernel to run. `simd` is the default; `scalar` exists for A/B and for * the (theoretical) case of a build without simd128. Both are f64 and * bit-identical to {@link composeJS}. */ export type Kernel = 'simd' | 'scalar'; /** * A live WASM backend bound to one module instance. `compose` is allocation-free * after the first call at a given high-water capacity; growing past it re-`init`s * and re-views memory (a `WebAssembly.Memory.buffer` detaches on growth, so the * typed-array views must be rebuilt then — done here, never mid-compose). */ export declare class WasmTransformBackend { readonly available: true; private readonly ex; private cap; private runCap; private vx; private vy; private vsx; private vsy; private vcos; private vsin; private vop; private vwa; private vwb; private vwc; private vwd; private vwe; private vwf; private vwo; private vbx; private vby; private vbw; private vbh; private vaminx; private vaminy; private vamaxx; private vamaxy; private vrp; private vrs; private vrl; constructor(instance: WebAssembly.Instance); /** Compose world matrices for `store` in WASM, writing back into its * `wa..wo` arrays. Result is bit-identical to `composeJS(store)`. */ compose(store: TransformStore, kernel?: Kernel): void; /** * Run the kernel only, over data already resident in WASM memory — no upload, * no readback. This is the per-frame cost the *designed* integration pays: * entity accessors write `x/y/rotation` straight into the wasm input views * (via {@link inputView}) and the renderer reads world matrices straight from * the wasm output views (via {@link worldView}), so the batch copies in * {@link compose} do not happen every frame. `compose` must have run at least * once at the current capacity to size the store and set the run count; call * {@link uploadRuns} after a topology change. */ runKernel(kernel?: Kernel): number; /** Upload only the run table + count (topology), leaving per-entity inputs to * the resident views. Call when the tree structure changes, not per frame. * Returns `false` if the crate rejected the run count, in which case the * PREVIOUS topology is still published and any kernel run against it would * compose the wrong tree — the caller must not proceed. */ uploadRuns(store: TransformStore): boolean; /** * Status of the most recent kernel or run-table call. `WASM_STATUS.OK` unless * the crate rejected its arguments, in which case that call was a no-op. */ lastStatus: number; /** The resident wasm input views (`x,y,sx,sy,cos,sin,opacity`), valid until * the next capacity growth. Writing here is what makes uploads unnecessary. */ inputView(): { x: Float64Array; y: Float64Array; sx: Float64Array; sy: Float64Array; cos: Float64Array; sin: Float64Array; opacity: Float64Array; }; /** The resident wasm world-matrix output views (`wa..wo`). Reading here is * what makes readback unnecessary. */ worldView(): { wa: Float64Array; wb: Float64Array; wc: Float64Array; wd: Float64Array; we: Float64Array; wf: Float64Array; wo: Float64Array; }; /** * Compute world-space AABBs for `store` in WASM (G1+), writing back into its * `aminx/aminy/amaxx/amaxy` arrays. Uploads the local bounds, runs the AABB * pass, reads results back. Result is bit-identical to `computeAabbsJS(store)`. * `compose` (or `runKernel`) must have populated the world matrices first — * this pass reads them. For the resident (no-copy) integration, write bounds * via {@link boundsView} and read via {@link aabbView} + call * {@link runAabbs} instead. */ computeAabbs(store: TransformStore): void; /** Run the AABB pass only, over `count` entities already resident in wasm * memory (bounds written via {@link boundsView}, world matrices already * composed). No upload/readback — the per-frame resident path. Returns * `false` if the kernel rejected `count` (beyond capacity, or uninitialized), * in which case {@link aabbView} still holds the previous frame's bounds. */ runAabbs(count: number): boolean; /** Resident wasm local-bounds input views (`bx,by,bw,bh`) for the AABB pass. */ boundsView(): { bx: Float64Array; by: Float64Array; bw: Float64Array; bh: Float64Array; }; /** Resident wasm world-AABB output views (`aminx,aminy,amaxx,amaxy`). */ aabbView(): { aminx: Float64Array; aminy: Float64Array; amaxx: Float64Array; amaxy: Float64Array; }; /** * Re-create the typed-array views if the memory buffer they were built over has * been detached. * * Necessary because all backends of a Scene now share one instance, and * therefore one linear memory: another backend's allocation (notably * `hit_init`, which allocates its own grid arrays) can grow the memory and * detach every view built over the old buffer. A detached `Float64Array` reads * as length 0 and silently returns `undefined` for every index, so without this * the transform store appears empty rather than failing loudly. */ revalidateViews(): void; private ensure; /** Rebuild typed-array views after an init() (which may have grown, and thus * detached, the memory buffer). */ private refreshViews; } /** * Instantiate synchronously (Node/tests, or a worker). Rejected on the browser * main thread for modules >4 KB — use {@link instantiateAsync} there. Returns * `null` if compilation/instantiation throws, so callers fall back to JS. */ export declare function instantiateSync(bytes: BufferSource): WasmTransformBackend | null; /** * Instantiate asynchronously (browser main thread). Returns `null` on any * failure — CSP `wasm-unsafe-eval`, unsupported SIMD, corrupt/missing bytes — * so the caller keeps using the JS path. This is the loader the Scene hot-swap * (gated integration) will await. */ export declare function instantiateAsync(bytes: BufferSource): Promise; /** * Anything the transform core can be loaded from: raw bytes, a URL/path string * or {@link URL} to fetch, or a {@link Response} (or a promise of one) — e.g. * `fetch(new URL('./vectojs_core.wasm', import.meta.url))`, the shape every * bundler emits for a co-located `.wasm` asset. */ export type WasmModuleSource = BufferSource | string | URL | Response | Promise; /** * Instantiate from a URL/Response using streaming compilation when the platform * supports it (the module compiles while it downloads — the fastest cold start), * and transparently falling back to fetch → arrayBuffer → instantiate when * `WebAssembly.instantiateStreaming` is unavailable or the response's MIME type * is not `application/wasm` (some engines reject the stream in that case, e.g. a * dev server serving `application/octet-stream`). Returns `null` on any failure * so the caller keeps the JS path — loading the accelerator is never an error path. */ export declare function instantiateStreaming(source: string | URL | Response | Promise): Promise;