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[]> };
    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> }

  • 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[]> }

  • 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>