/** * SoA transform store and the JS reference composer. * * This is the data-layout half of the Rust/WASM transform core. `buildStore` * turns a tree into the pure Structure-of-Arrays layout the WASM kernel * (`crates/vectojs-core-rs`) requires, and `composeJS` computes world matrices * over that same layout in TypeScript. The two MUST produce bit-identical f64 * results — `composeJS` is both the correctness oracle for the differential * test and the permanent fallback when WASM is unavailable (CSP, no SIMD, * missing asset). Neither this file nor the loader touches `Entity`/`Scene`; * wiring them into the render walk is the gated integration step (see the task * plan), so this layer ships and is tested on its own. * * ## Why the layout is shaped this way * * - **Pure SoA** (one flat array per field): consecutive entities' `x` values * are adjacent, so a `v128` load fetches two at once. An interleaved record * would put them 8*N bytes apart and make SIMD unreachable. * - **Contiguous sibling runs in depth order**: WASM SIMD has no gather, so the * parent matrix must be loop-invariant across a run. `buildStore` emits every * parent's children as one contiguous run and always emits a parent before * its children, so a run's parent world matrix is already composed when the * run is processed. The scene root is index 0 at the identity transform. */ /** A node's local transform (the six animatable scalars). */ export interface LocalTransform { x: number; y: number; scaleX: number; scaleY: number; rotation: number; opacity: number; } /** * One input node. `parent` is an index into the input array, or `-1` for the * single root. Input order is arbitrary; `buildStore` re-indexes into store * order (root at 0, then depth-ordered contiguous sibling runs). */ export interface InputNode extends LocalTransform { parent: number; /** * Local render bounds `[bx, by, bw, bh]` for the world-AABB pass * ({@link computeAabbsJS} / the crate's `compute_aabbs`). Optional; defaults to * `[0, 0, 0, 0]`, matching `Entity.getWorldBounds`'s `[0,0,width,height]` * fallback when a node has no render-specific box (callers pass `width`/ * `height` as `bw`/`bh`). */ bx?: number; by?: number; bw?: number; bh?: number; } /** A composed world matrix (`a b c d e f`) plus accumulated opacity. */ export interface WorldMatrix { a: number; b: number; c: number; d: number; e: number; f: number; opacity: number; } /** * The SoA store: input fields, output world-matrix fields, and the sibling-run * table. Field arrays are sized `count + 8` so a 2-lane SIMD tail can read one * slot past the logical end without a scalar remainder loop (mirrors the * crate's `+8` padding). `storeIndexOf[k]` maps input index `k` to store index. */ export interface TransformStore { count: number; capacity: number; x: Float64Array; y: Float64Array; sx: Float64Array; sy: Float64Array; cos: Float64Array; sin: Float64Array; opacity: Float64Array; wa: Float64Array; wb: Float64Array; wc: Float64Array; wd: Float64Array; we: Float64Array; wf: Float64Array; wo: Float64Array; bx: Float64Array; by: Float64Array; bw: Float64Array; bh: Float64Array; aminx: Float64Array; aminy: Float64Array; amaxx: Float64Array; amaxy: Float64Array; runParent: Int32Array; runStart: Int32Array; runLen: Int32Array; runCount: number; storeIndexOf: Int32Array; } /** * Build the SoA store from a node list. Exactly one node must have * `parent === -1` (the root); it becomes store index 0 at the identity world * transform (its own local transform is ignored, matching how `Scene` leaves * root/overlayRoot at identity). Throws on a missing or duplicate root. * * Emission is BFS per parent: dequeue a parent, emit ALL its children as one * contiguous run, enqueue them. This guarantees (a) each parent's children are * contiguous — required for SIMD — and (b) a parent is assigned a lower store * index and composed before its children. * * Time: O(n). Space: O(n). */ export declare function buildStore(nodes: InputNode[]): TransformStore; /** * Compose world matrices over the store in TypeScript — the reference oracle * and the permanent JS fallback. Bit-identical to the crate's `compose_scalar`: * same Canvas `T * S * R` order, same operation order, same f64 arithmetic. * Seeds the root (index 0) to identity, then walks runs in order. */ export declare function composeJS(s: TransformStore): void; /** Read the world matrix at store index `i`. */ export declare function readWorld(s: TransformStore, i: number): WorldMatrix; /** A world-space axis-aligned bounding box (min/max corner). */ export interface WorldAabb { minX: number; minY: number; maxX: number; maxY: number; } /** * Transform each node's local bounds `[bx, by, bw, bh]` through its already- * composed world matrix into a world-space AABB (the min/max of the four * transformed corners), writing `aminx/aminy/amaxx/amaxy`. This is the reference * oracle and permanent JS fallback for the crate's `compute_aabbs`, and is * bit-identical to `Entity.getWorldBounds` — same corner-selection bit trick * (`i&1`/`i&2`), same `a*x + c*y + e` / `b*x + d*y + f` op order, same * `Math.min`/`Math.max` accumulation over exactly four corners. * * Must run AFTER {@link composeJS} (or a `compose_*` kernel), which fills the * world matrices this reads. A flat pass over `[0, count)` — the world matrices * already encode the hierarchy, so no run walk is needed. */ export declare function computeAabbsJS(s: TransformStore): void; /** Read the world-space AABB at store index `i` (valid after `computeAabbsJS` * or the crate's `compute_aabbs`). */ export declare function readAabb(s: TransformStore, i: number): WorldAabb;