/** * SplatChunkStore — Fleet-Rail Chunk Addressing for Splat Streaming * * Maps octree node IDs (from SpatialPartitionPass) to addressable chunks * that can be fetched on demand by the LODStreamer. This is the "WIRE-3" * seam: SpatialPartitionPass emits node IDs → SplatChunkStore registers * chunks keyed by those IDs → view-driven streaming fetches via * GET /chunk/{nodeId}. * * Architecture: * 1. SpatialPartitionPass.run() produces a SpatialPartitionResult with * SpatialAnchor[] (each carrying an `id` like `spa__l`). * 2. SplatChunkStore.register(result) ingests all anchors, creating a * ChunkEntry per anchor that carries provenance metadata + a * content-addressable URL. * 3. LODStreamer queries the store for visible anchors (by LOD level, * bounding box, or individual nodeId) and fetches chunks on demand. * 4. The fleet-rail URL scheme is: `/chunk/` — a stable, * cacheable address that any edge node can serve. * * Content-addressable URLs: * - Each chunk URL embeds the provenanceHash, making it verifiable: * `https://cdn.holoscript.net/chunk/spa_Terrain_l0?hash=` * - R2/fetch backends serve from `//.bin` * * Provenance (D.058 / D.059 / Paper 34): * - Each chunk carries a provenanceHash that chains into the * SpatialPartitionResult.merkleRoot. A culled subtree is a verifiable * provenance unit — "receipt rides to pixels" becomes structural. * * Research references: * W.032 — Octree-GS LOD (anchor-based level selection, TPAMI 2025) * W.034 — VR Gaussian budget (~180K total on Quest 3 at 72fps) * D.058 — Visual evidence / receipt-to-pixel capstone * D.059 — Staged matter assembly / world construction * idea-run-17 — WIRE-3 keystone card (2026-05-22) * * @module SplatChunkStore * @version 1.0.0 */ /** * A single chunk entry in the store. Maps a SpatialAnchor's node ID to * a content-addressable URL and provenance metadata. */ export interface ChunkEntry { /** Octree node ID (matches SpatialAnchor.id, e.g. `spa_Terrain_l0`). */ nodeId: string; /** LOD level (0 = coarsest, higher = finer). Matches SpatialAnchor.lodLevel. */ lodLevel: number; /** Number of Gaussians in this chunk. */ gaussianCount: number; /** Perceptual importance [0,1]. Higher = retained under budget pressure. */ importance: number; /** World-space position [x, y, z] of the chunk's anchor. */ position: [number, number, number]; /** Effective scale (max axis radius) of the Gaussian cloud. */ scale: number; /** Provenance hash (SHA-256 or deterministic content hash). */ provenanceHash: string; /** Source .ply/.splat file path, if available. */ sourceFile?: string; /** Content-addressable URL for fetching this chunk. */ url: string; /** Size estimate in bytes (for budget-aware streaming). */ estimatedSizeBytes: number; /** Composition name this chunk belongs to. */ compositionName: string; /** Registration timestamp (ISO 8601). */ registeredAt: string; } /** * Configuration for the chunk store. */ export interface SplatChunkStoreOptions { /** Base URL for fleet-rail chunk fetches (default: 'https://cdn.holoscript.net'). */ baseUrl: string; /** Composition ID (namespace for chunk URLs, e.g. 'my-scene-v1'). */ compositionId: string; /** Estimated bytes per Gaussian (for size estimation). Default: 56 (14 floats * 4 bytes). */ bytesPerGaussian: number; /** Enable provenance hash verification on fetch. Default: true. */ verifyProvenance: boolean; /** Debug logging. Default: false. */ debug: boolean; } /** * Result of a chunk query — entries matching the query criteria. */ export interface ChunkQueryResult { /** Matching chunk entries. */ entries: ChunkEntry[]; /** Total Gaussian count across matched entries. */ totalGaussians: number; /** Estimated total bytes across matched entries. */ totalBytes: number; /** Number of distinct LOD levels in the result. */ distinctLevels: number; /** Merkle root of the full composition (for provenance verification). */ merkleRoot: string; } /** * Fleet-rail URL template for a chunk fetch. Variables: * {baseUrl} — CDN base URL * {compositionId} — composition namespace * {nodeId} — octree node ID * {provenanceHash} — content hash for verification */ export interface FleetRailURL { /** Full URL: {baseUrl}/chunk/{nodeId}?hash={provenanceHash} */ full: string; /** Path only: /chunk/{nodeId} */ path: string; /** Cache key for dedup: {compositionId}/{nodeId} */ cacheKey: string; } /** * Minimal interface matching SpatialPartitionPass.SpatialAnchor. * We use a structural type so core and engine don't need a direct import * (core does NOT import from engine per the dependency arrow convention * noted in SpatialPartitionPass.ts lines 13-16). * * The actual SpatialAnchor from core has these exact fields; the * structural type allows engine to consume it without a circular dep. */ export interface SpatialAnchorLike { id: string; position: [number, number, number]; scale: number; lodLevel: number; gaussianCount: number; importance: number; provenanceHash: string; sourceFile?: string; } /** * Minimal interface matching SpatialPartitionResult. */ export interface SpatialPartitionResultLike { schema: string; compositionName: string; generatedAt: string; anchors: SpatialAnchorLike[]; bounds: { min: { x: number; y: number; z: number; }; max: { x: number; y: number; z: number; }; center: { x: number; y: number; z: number; }; halfSize: number; }; merkleRoot: string; totalGaussians: number; stats: Record; } /** * SplatChunkStore maps octree node IDs to addressable chunks for view-driven * splat streaming. It bridges SpatialPartitionPass output to the fleet-rail * URL scheme (GET /chunk/{nodeId}). * * Usage: * ```typescript * import { SplatChunkStore } from '@holoscript/engine/lod'; * import { spatialPartition } from '@holoscript/core'; * * const store = new SplatChunkStore({ * baseUrl: 'https://cdn.holoscript.net', * compositionId: 'city-scene-v2', * }); * * // Register all chunks from a SpatialPartitionResult * const result = spatialPartition(composition); * store.register(result); * * // Query chunks visible from a camera position * const visible = store.queryByLOD([0, 1, 2]); // coarsest 3 levels * for (const entry of visible.entries) { * console.log(`Fetch: ${entry.url}`); * } * ``` */ export declare class SplatChunkStore { private readonly options; private readonly entries; private readonly lodIndex; private bounds; private merkleRoot; private compositionName; private totalGaussians; private registeredAt; constructor(options?: Partial); /** * Register all anchors from a SpatialPartitionResult. * Each anchor becomes a ChunkEntry keyed by its nodeId. * Re-registering replaces all existing entries (fresh composition). */ register(result: SpatialPartitionResultLike): void; /** * Register a single anchor. Can be used for incremental updates. */ registerAnchor(anchor: SpatialAnchorLike): ChunkEntry; /** * Deregister a single anchor by nodeId. */ deregister(nodeId: string): boolean; /** * Get a chunk entry by node ID. Returns undefined if not found. */ get(nodeId: string): ChunkEntry | undefined; /** * Query chunks by LOD levels. Returns all chunks at the specified levels, * sorted coarsest-first (level ascending). */ queryByLOD(levels: number[]): ChunkQueryResult; /** * Query chunks by bounding box intersection. * Returns chunks whose anchor position falls within the AABB. */ queryByBounds(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): ChunkQueryResult; /** * Query chunks by camera distance. Returns chunks within `maxDistance` * of the camera position, sorted by distance (nearest first). */ queryByDistance(cameraX: number, cameraY: number, cameraZ: number, maxDistance: number): ChunkQueryResult; /** * Query chunks by importance threshold. Returns chunks with importance * >= minImportance, sorted by importance descending. */ queryByImportance(minImportance: number): ChunkQueryResult; /** * Get all chunk entries, sorted coarsest-first. */ getAll(): ChunkQueryResult; /** * Get all distinct LOD levels present in the store. */ getAvailableLevels(): number[]; /** * Get the composition bounds (from the registered SpatialPartitionResult). */ getBounds(): SpatialPartitionResultLike['bounds'] | null; /** * Get the merkle root of the registered composition. */ getMerkleRoot(): string; /** * Build a fleet-rail URL for a given node ID and provenance hash. */ buildFleetRailURL(nodeId: string, provenanceHash: string): FleetRailURL; /** * Parse a fleet-rail URL back to its nodeId and provenanceHash. */ parseFleetRailURL(url: string): { nodeId: string; provenanceHash?: string; } | null; /** * Select chunks for streaming within a Gaussian budget. * Prioritises by importance (higher first), then by LOD level (coarser first). * Returns as many chunks as fit within the budget. */ selectForBudget(gaussianBudget: number): ChunkQueryResult; /** * Select chunks for streaming within a byte budget. * Prioritises by importance (higher first), then by LOD level (coarser first). */ selectForByteBudget(byteBudget: number): ChunkQueryResult; /** * Verify that a fetched chunk matches its expected provenance hash. * In production, this would compute the hash over the fetched bytes and * compare against the stored provenanceHash. For now, returns true * if the hash strings match. */ verifyChunk(nodeId: string, claimedHash: string): boolean; /** * Get store metrics. */ getMetrics(): { totalChunks: number; totalGaussians: number; totalBytes: number; levels: number[]; levelDistribution: Record; compositionName: string; registeredAt: string; }; /** * Check if a node ID is registered. */ has(nodeId: string): boolean; /** * Get the number of registered chunks. */ get size(): number; /** * Clear all entries. */ clear(): void; /** * Build a ChunkQueryResult from an array of entries. * Includes the store's merkle root for provenance verification. */ private buildQueryResult; } /** * Create a SplatChunkStore with default options. */ export declare function createSplatChunkStore(options?: Partial): SplatChunkStore; /** * Create a SplatChunkStore optimised for VR streaming (Quest 3 / mobile). */ export declare function createVRSplatChunkStore(compositionId: string): SplatChunkStore; /** * Create a SplatChunkStore optimised for desktop/high-end. */ export declare function createDesktopSplatChunkStore(compositionId: string): SplatChunkStore; //# sourceMappingURL=SplatChunkStore.d.ts.map