{/* Generated from dreamdb-core by scripts/sync-spec-docs.mjs — do not edit. */}
# Spec 0027 — Progressive Geometry Items

**Status:** Normative v1 format for #316, implementing ADR 0004. Geometry is
internal to one outer Timeline Item, never an embedding SpatialBucket.

## 1. Coordinates and records

The namespaced modalities `ai.dreamlake.spatial.geometry.point`,
`ai.dreamlake.spatial.geometry.splat` and `ai.dreamlake.spatial.geometry.mesh`
have Track kind `event` and Object kind
`geometry-item`, declared in the modality registry. Unknown Object kinds must
refuse before reads, transformations or GC traversal. Schema family and the
descriptor family must match. Existing kinds/bytes are unchanged.

Coordinates are a right-handed XYZ u16 integer grid. A descriptor carries
`origin_f64le` and `step_f64le`, each bstr(24), three finite binary64 values;
steps must be positive. Physical position on axis i is origin[i]+q[i]*step[i].
Both grid endpoints must remain finite under that binary64 evaluation.
`units` is a nonempty UTF-8 label of at most 256 bytes, compared literally.
The writer API accepts already quantized coordinates, not floating-point
positions with an implicit rounding policy. Producers converting physical
positions use binary64 subtraction then division, round-to-nearest-ties-even,
and reject results outside [0,65535]; no silent clamp. Quantization is lossy
and the physical mapping is not a claim to reconstruct original coordinates.

Point records are exactly 10 bytes: x/y/z u16le at 0/2/4, then RGBA u8 at 6..10.
Splat records are exactly 46 bytes: x/y/z u16le, six f32le covariance entries
xx,xy,xz,yy,yz,zz at 6..30, then linear RGBA f32le at 30..46. All floats are
finite. RGBA is in [0,1]. Covariance is positive definite: xx>0,
xx*yy-xy*xy>0 and the symmetric determinant>0, evaluated after binary64
widening with scalar operations in the printed order, no FMA or reassociation.
Covariance units are physical units squared. No encoder/retraining or renderer
is specified; these are already prepared scene records.

Morton48 interleaves all 16 coordinate bits: bit 3i is x[i], 3i+1 is y[i],
3i+2 is z[i]. A descriptor's `depth` is in [0,16]. A cell key is the high
3*depth Morton bits (depth=0 has only key 0). Its integer AABB is obtained by
deinterleaving that prefix and filling the remaining coordinate bits; bounds
are inclusive. Spatial selection uses integer AABB intersection, inclusive on
both ends, not floating-point camera arithmetic.

Inside each cell records are sorted by reversal of exactly the 48 Morton bits,
ascending, then by all record bytes lexicographically. Byte-identical records
may repeat; their relative position cannot alter bytes. The identifier is
`morton_rev48`. Prefix detail means a deterministic progressively longer sample
of this ordering, not a certified screen-space or geometric-error bound.
No standard `importance` order is defined. Every cell is nonempty and at most
1 MiB of records. Oversize cells refuse; the producer may choose a finer depth
or smaller scene Item. Coincident records are not silently discarded.

## 2. GeometryData and packing

`geometry-data/<multihash>` holds raw immutable payload bytes. For point/splat
data the canonical writer concatenates complete cells, by increasing cell key,
into packs of at most 4 MiB, starting a new pack before a cell would overflow.
No padding or header bytes are inserted. Each cell entry is exactly
`[key:u64, count:u64, object:bstr(33), offset:u64, size:u64]`;
size=count*stride with checked arithmetic. References sharing an Object must
be disjoint and object extents are checked against authenticated length before
exposing payload. Neither a tail nor an index can manufacture bytes beyond EOF.

The format also permits a cell to occupy its own Object; this changes addresses,
not decoded records. The reference choice is packed cells. The protocol builder
measured 512 cells × 128 point records: 655,360 payload bytes, 1 packed data
Object instead of 512 cell Objects, 3 hierarchy pages, and zero padding bytes.
All records passed membership/order validation after packing. Accordingly the
outboard count also falls from 512 to 1. This is an Object-count/layout
measurement, not a latency or compression benchmark (`geometry_format` test).

Every new GeometryData Object publishes its standard Bao outboard under
`0002` §6.5.4 before the Manifest. Ranged readers verify first and expose bytes
only afterwards. No geometry-specific unauthenticated fallback exists.

## 3. CLOSED descriptors and hierarchy

`geometry-item/<hash>` is canonical CBOR, at most 1 MiB, exactly these keys:
`v` (uint 1), `family` (point/splat/mesh), `origin_f64le`, `step_f64le`, `units`,
`depth` (uint), `index` (map). The dedicated GeometryTrack is a CLOSED map of
exactly `timeline` (hash), `modality` (text), `coverage` ([lo,hi]) and
`items` (ordered unique `[anchor, descriptor_hash, descriptor_size]` triples).
It is at most 1 MiB; v1 refuses outer Track overflow rather than inventing a
page format. Nonempty coverage is [first_anchor,last_anchor+1), empty coverage
is [0,1); anchors must be less than u64::MAX. The descriptor size excludes its
transitive closure. An outer anchor selects one scene, never one point.

For point/splat, index is one of these CLOSED forms:

- `{form:"cells-v1", cells:[entry,...]}`: strictly increasing unique keys,
  at most 256 entries (an empty scene has this form with []).
- `{form:"paged-cells-v1", root:bstr(33)}`: a GeometryPage tree.

`geometry-page/<hash>` is canonical CBOR at most 1 MiB. A page is exactly
`{form:"leaf-v1",cells:[entry,...]}` (1..256 cells), or
`{form:"branch-v1",children:[[first_key,last_key,page_hash],...]}`
(1..256 children). Child key ranges are inclusive, ordered and disjoint;
first\<=last. The fetched child's actual extrema must equal its declared range.
Tree depth is at most 8; cycles, repeated pages and inconsistent kinds/counts
refuse. The canonical builder groups 256 consecutive entries per leaf and then
256 consecutive nodes per level, until one root remains; it never emits a
single-entry inline form for an index that exceeds 256 entries. Metadata may
be fetched in full to select cells; v1 makes no sublinear hierarchy-query claim.

All referenced pages and data are content-address verified. A record read must
also verify the selected records' positions belong to the declared cell, record
shape and within-prefix ordering. A complete physical audit additionally checks
every cell and all extents; a partial read does not claim to have audited unseen
records. Unknown forms, fields, duplicate keys, unsupported versions and
invalid types are errors, not empty geometry.

## 4. Mesh levels

For mesh, depth is 0 and index is exactly `{form:"mesh-lods-v1",levels:[...]}`.
There are 1..256 levels, coarsest to finest. Each level is exactly
`[error_grid:u64, vertex_count:u32, triangle_count:u32, object:bstr(33), size:u64]`.
Errors are nonincreasing and expressed in integer grid steps; the producer
asserts this error, the codec verifies ordering but does not prove the claimed
approximation error against a source surface. Both counts are positive.
Data is vertex_count XYZ u16le triples followed by triangle_count triples of
u32le vertex indices. Size=vertices*6+triangles*12, checked, at most 1 GiB.
Every triangle index is below vertex_count. Each level is independently
decodable, immutable and range-authenticated before exposure. It is read as a
whole level; arbitrary mesh prefixes are never claimed to be valid meshes.
There is no mesh simplifier, progressive-mesh stream or cluster DAG.

## 5. Reader sessions and publication

The Dataset/SDK opens an Item as a pinned geometry reader. It exposes cell
metadata, integer-AABB selection and cell prefix extension by an output-byte
budget. Only whole records fit; count is min(total,floor(budget/stride)).
A request for a shorter/equal prefix returns no new bytes; extension returns
only the newly exposed suffix. A session caches authenticated 1 KiB blocks per
Object, including range-alignment overfetch. An extension does not re-request
a cached payload block. Metadata/proof reads are not payload refetches. Budget
is exposed record bytes, not a promise that network bytes equal the budget:
Bao alignment/proofs can add overhead. The session retains its cache until
closed; v1 does not promise bounded memory for an indefinitely growing session.

Point/splat publication validates and orders all cells, constructs data packs,
pages, descriptor and the replacement outer Track, then publishes one child
Manifest/Ref CAS. An anchor already in this field refuses rather than silently
replacing a scene. Failure leaves the Dataset handle unchanged; unreferenced
Objects after a racing CAS are ordinary GC candidates. This is an in-memory
reference builder, not an out-of-core ingest claim. Geometry writes on keyed
Datasets refuse until the scene transaction can join the entity-key contract.

GC traverses every retained root's Track, descriptor, hierarchy, data/mesh
levels and Bao outboards. Unsupported or corrupt geometry aborts before sweep.
Current embedding/scalar compaction carries other geometry Tracks unchanged;
explicit geometry compaction (or a Dataset with no compactable fields) refuses
without publishing. Any future cell rewrite must preserve/re-establish cell
membership, layout and order or refuse.
Python and WASM use the same codec and session semantics; camera/refinement
policy stays in the caller. No query server or Backend API change is required.
