Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Module data/mesh/preflight

    Stage 1 of the mesh loader's admission gate: the metadata preflight.

    Runs purely on the node attrs and each array's zarr .zarray metadata (declared shape, chunks, dtype). It touches no chunk data — every check here is decidable before a single byte of geometry is fetched, and that is the whole point of the stage existing separately.

    The writer's validate_*_for_writing family protects only stores Luxar produced. The viewer loads arbitrary ?src= URLs, and the mesh loader is whole-node (docs/specs/MESH_NODE_SPEC.md §7): it fetches and decodes every array in full, up front. A check that runs only after decode therefore arrives too late for the quantities that gate admission — a hostile or corrupt store can declare enormous arrays and exhaust tab memory before the LoaderError containment ("one node lost, not the scene") is ever reachable.

    So everything decidable from metadata is checked here, first, and the value-level checks that genuinely need materialized arrays run afterwards in Stage 2 (validate.ts).

    • n_vertices <= MAX_MESH_VERTICES — above 2^27 the pick vote key aliases across nodes (§6.5). Refused before allocation, not after a multi-gigabyte fetch.
    • Byte budget — stored bytes, decoded bytes and the largest single chunk allocation, SUMMED against MESH_DECODE_BUDGET_BYTES. Charged against the array whose bytes are actually fetched, which for an array_ref is the TARGET, not the (0, k) stub that points at it.
    • Shape and dtype cross-checks — against n_vertices/n_faces/ndim and the §3.2 array table.
    • normal_dims well-formedness — 3 distinct integers in [0, ndim).

    At the default 512 MiB budget the byte budget is far tighter than the vertex cap, and it is worth knowing which error to expect. A 3D float32 mesh runs out of budget at ~22.4M vertices and a uint16-quantized one at ~29.8M, both well under 2^27 (134.2M) — so in practice no mesh reaches the cap by growing legitimately. (Those figures halve the stored-only arithmetic because the decoded term is charged too: a float32 vertex costs 4 bytes stored AND 4 decoded. The float32 number is measured, not derived — see the boundary test.)

    The cap is still checked, and checked first, for two reasons. It gives a hostile or nonsensical declaration (n_vertices: 2^30) the message that names the real problem — pick keys aliasing, which no amount of decimation fixes — rather than blaming bytes. And it keeps the bound enforced rather than assumed if the budget is ever raised.

    The shape checks are not cosmetic tidiness. Two of them stop a panic = "abort" WASM trap, which escapes as an opaque uncatchable RuntimeError: unreachable rather than a node-scoped error:

    • a vertices width that disagrees with ndim makes the §5.4 slab kernel index out of slice bounds;
    • a faces array of the wrong shape or a float dtype either mis-strides the topology or truncates in the u32 coercion.

    And the optional-array shape checks stop a quieter failure: normals / colors / scalars bind as enabled vertex attributes on an indexed draw (§6.1). An undersized one doesn't trap — it makes drawElements read past the buffer (an invalid-operation draw or silent zeros, backend-dependent) and mis-shades every vertex it covers. A declared-undersized array is catchable here from its shape alone.

    MeshArrayHandles
    MeshPreflightResult
    TextureDeclaration
    MESH_ARRAY_NAMES
    TEXTURE_DECODE_KIND
    MAX_MESH_TEXTURE_SIZE
    DECODED_BYTES_PER_VALUE
    parseDtype
    preflightMesh