Lazy per-column cache of derived data the inspect / fix pipeline reads from a SceneModel. Single source of truth for "expensive precomputable rows" — content hashes, vertex dedup maps, edge incidence counts, decompressed positions, reverse-reference tables — so the same work isn't repeated by every inspection that needs it (and again by every fix that touches the same geometry).

Per-column lazy. Each accessor builds and caches on first call, returns the cached value thereafter. A consumer that only needs contentHash doesn't pay for decompressedPositions or edgeToTriangles.

Where it lives. Held in a process-local WeakMap keyed by SceneModel via getInspectionIndex — no SceneModel API change, auto-GC'd, transparent to consumers. Each inspectSceneModel / applyFixes run reuses the same index across inspections + fixes.

Invalidation. Coarse, orchestrator-driven. After a fix mutates a geometry the orchestrator calls invalidateGeometry; after a fix touches references it calls invalidateReferences. Clients that mutate the SceneModel outside the framework should call invalidateAll when they're done.

interface SceneModelInspectionIndex {
    sceneModel: SceneModel;
    aabbCentroidMagSq(geometryId: string): number;
    canonicalSlots(
        geometryId: string,
    ): { canonical: Int32Array; uniqueCount: number };
    canonicalTriangleKeys(
        geometryId: string,
    ): { duplicateCount: number; kept: Uint8Array; keys: Set<string> };
    contentHash(geometryId: string): string;
    decompressedPositions(geometryId: string): Float32Array;
    directedEdgeIncidence(geometryId: string): Map<string, number>;
    edgeLengthHistogram(geometryId: string): Uint32Array<ArrayBufferLike>;
    edgeToTriangles(
        geometryId: string,
    ): { degenerate: Uint8Array; edges: Map<string, number[]> };
    geometryMeshes(geometryId: string): readonly string[];
    geometryObjects(geometryId: string): readonly string[];
    invalidateAll(): void;
    invalidateGeometry(geometryId: string): void;
    invalidateObject(objectId: string): void;
    invalidateReferences(): void;
    materialReferences(): ReadonlyMap<string, readonly string[]>;
    objectWorldAABB(objectId: string): Float32Array;
    releaseHeavyRows(): void;
    textureReferences(): ReadonlyMap<string, readonly string[]>;
    topologyKey(geometryId: string): string;
    transformReferences(): ReadonlyMap<
        string,
        { childTransforms: readonly string[]; meshes: readonly string[] },
    >;
    undirectedEdgeIncidence(geometryId: string): Map<string, number>;
    vertexUsageMask(geometryId: string): Uint8Array;
}

Properties

sceneModel: SceneModel

SceneModel this index is built against.

Methods

  • Per-vertex-slot remap: canonical[v] === v iff v is the first occurrence of its byte signature; otherwise points at the canonical slot to coalesce onto. Returns null for geometries without positionsCompressed. Used by geometryQuality's duplicate-vertex check and the mergeDuplicateVertices fix.

    Parameters

    • geometryId: string

    Returns { canonical: Int32Array; uniqueCount: number }

  • Sorted-vertex-triple keys for triangle-dedup, plus the count of duplicate (already-seen) triangles encountered during the walk and a per-triangle "kept" bitmap. Each key is a_b_c with a ≤ b ≤ c, so all rotations / reflections of the same triangle collapse to one key. Degenerate triangles (i0 === i1 || i1 === i2 || i0 === i2) are kept (they're a separate concern). Returns null for non-triangle geometries.

    • keys — set of every distinct canonical key seen.
    • duplicateCount — count of triangles whose canonical key was already in keys at walk time. Read by geometryQuality's duplicate-triangles check.
    • keptUint8Array(triCount) with kept[t] === 1 iff t was the first occurrence of its canonical key (or is degenerate). Read by the dropDuplicateTriangles fix to know which triangles to keep.

    Parameters

    • geometryId: string

    Returns { duplicateCount: number; kept: Uint8Array; keys: Set<string> }

  • FNV-1a fingerprint over typed-array bytes, primitive constant, and AABB. Includes render-significant optional attributes such as colors, edges, and gaussian splat payloads. Two geometries with the same contentHash have identical content (modulo birthday-paradox collisions, which are negligible for ≪ 10⁶ geometries). Used by duplicateGeometries.

    Parameters

    • geometryId: string

    Returns string

  • Directed edge → incidence count. Key is a_b. Manifold meshes have each shared edge traversed in opposite directions by its two adjacent triangles, so any directed edge with count > 1 indicates inconsistent winding. Returns null for non-triangle geometries. Used by geometryQuality's winding check.

    Parameters

    • geometryId: string

    Returns Map<string, number>

  • Undirected edge → list of triangle ids that include it, plus a per-triangle degeneracy bitmap. Same key shape as undirectedEdgeIncidence, but the heavier triangle-id list is only paid when a consumer actually needs adjacency (the unifyTriangleWinding fix's flood-fill walk). The degenerate bitmap has degenerate[t] === 1 iff triangle t has repeated indices (its edges are NOT registered in edges). Returns null for non-triangle geometries.

    Parameters

    • geometryId: string

    Returns { degenerate: Uint8Array; edges: Map<string, number[]> }

  • geometryId → ids of every non-destroyed mesh referencing it.

    Unlike the other reverse tables this one is mutation-aware: it builds once, then stays current by tracking onSceneMeshCreated / onSceneMeshDestroyed on the owning Scene. That lets a fix which repoints meshes across many issues (e.g. mergeSimilarGeometries) read O(1) per geometry for the whole fix pass instead of rescanning every mesh per fix. The event subscription is released by invalidateAll — a caller driving fixes outside the framework should call it when done.

    Parameters

    • geometryId: string

    Returns readonly string[]

  • geometryId → de-duplicated ids of non-destroyed SceneObjects that own a non-destroyed mesh referencing the geometry (object order follows sceneModel.meshes iteration). Built once by a single mesh walk and shared across every geometry-tied inspection, so a check populating Issue.highlight pays O(1) per geometry instead of re-scanning all meshes. Empty array when unreferenced. Backs findSceneObjectsForGeometry.

    Parameters

    • geometryId: string

    Returns readonly string[]

  • Free the heaviest cached rows (decompressed positions, directed/undirected edge maps, canonical-triangle key sets) but keep the cheap ones (content hash, topology key, canonical slots, AABB centroid). Called by the orchestrator after inspectSceneModel returns, unless the caller pinned them.

    Returns void

  • transformId → meshes that name it as parent + transforms that name it as parent.

    Returns ReadonlyMap<
        string,
        { childTransforms: readonly string[]; meshes: readonly string[] },
    >

  • Undirected edge → incidence count. Key is lo_hi with lo = min(a, b), hi = max(a, b). Watertight closed manifolds have every edge incident to exactly 2 triangles. Returns null for non-triangle geometries. Used by geometryQuality's non-watertight check.

    Parameters

    • geometryId: string

    Returns Map<string, number>