Metadata for the dimension (only step is read).
OptionaltruncationRadius: number
The node's truncation_radius, already through
clampTruncationRadius (see ToleranceOptions.truncationRadius);
omitted ⇒ GSPLAT_DEFAULT_TRUNCATION_RADIUS.
The epsilon in that dimension's units.
GSplats hidden CONTINUOUS-dimension tolerance: a float-safety epsilon (
max(1e-3 × step, T × 1e-5), whereTis the node's truncation radius — somax(1e-3 × step, 2.75e-5)at the defaultT), NOT a query reach.Why it is not a reach
The reach is already baked into the data.
compute_chunk_bounds_gsplatsexpands each of a chunk's AABB dims bysqrt(covariance[d,d]) × coverage_sigmawithcoverage_sigmaset to the node's owntruncation_radius, and the hidden-dim cutoff on the read side is that same radius: the projection kernel (wasm/typescript/gsplats-processing.ts::project_gsplats_nd_to_3dand its Rust twin) attenuates by(e^{-m²/2} − c)/(1 − c)withc = e^{-T²/2}, clamped at 0, which is exactly 0 atm = T = truncation_radius. (The shader'suTruncate/uTruncateSqdiscard is the DISPLAYED-dim cutoff and never sees a hidden dim.) So a chunk that fails a zero-tolerance test contains only splats the projection would attenuate to nothing. This value is a chunk-FETCH reach only: per-splat hidden-dim visibility comes from that projection math, never from this array — seedata-processor-gsplats.ts.Which dims the premise applies to
compute_chunk_bounds_gsplatsapplies the σ expansion to every dim NOT in itsslice_dimsargument, and gives the ones that ARE the fixed_BARRIER_BOUND_EPSpad plus any encoder coordinate round-trip slack. So "bounds already carrytruncation_radius · σ" is a statement about the dims OUTSIDE the write side's barrier set, and this arm is only the right rule for exactly those dims — a barrier dim has tight bounds and wants the (far wider) quarter-cell reach instead. Which dims those are is settled byisBarrierDim, from the set the writer publishes; see its docstring for the resolution rule, the legacydiscretefallback and the write-side misclassification it does NOT fix.One consequence worth naming here: the #1183 change to this function REMOVED A MASK for that write-side risk on a store with no published set. The previous
step × 3.0rule (a bare3.0when dimension metadata was absent) was wide enough to cover a mis-detected barrier dim's tight bounds, so the mis-detection stayed invisible; a1e-3 × stepepsilon does not.Why the epsilon is not a literal
0Two reasons, and the formula has exactly one term for each:
GSPLATS_CONTINUOUS_EPS_STEP_FRACTION × step— a continuous dim along which the splats have zero variance (a stacked axis declared continuous rather than discrete) gets no σ expansion, so its stored bound is the axis value plus any encoder coordinate round-trip slack. The dominant perturbation is that the bound is stored as float32 (chunk_boundsisdtype=np.float32) while the query position is a float64 — ≈1.9e-7 of disagreement at a coordinate of 5.3. Thestart + k × steparithmetic drift in the query position is real but secondary (≈9e-16 there). At tolerance0membership would be an exact float comparison and could silently select nothing.regularizedHiddenBand— the read side regularizes a degenerate pivot, so such a splat still renders out toT × 1e-5in that dim's units (≈2.75e-5 at the defaultT). Term 1 alone falls below that wheneverstep < T × 1e-2(a physical-unit axis — 10 ms frames atstep = 0.01— gives 1e-5 against a 2.75e-5 band), so splats the kernel renders at up to ~60% of full brightness (the attenuation at the 1e-5 window edge, i.e. 1 σ of the regularized pivot) would not be fetched.Two regimes, with a single crossover at
step = T × 1e-2(2.75e-2 at the defaultT):step ≥ T × 1e-2→ term 1,1e-3 × step(the_BARRIER_BOUND_EPSmirror).step < T × 1e-2→ the regularization band, ABSOLUTE in the dim's units.Below the crossover the epsilon is therefore many CELLS wide — ≈27.5 cells at
step = 1e-6, ≈27500 atstep = 1e-9— and that is deliberate, not an oversight. It is what the renderer actually shows: the regularization floor behind term 2 is an absolute variance backstop, so the rendered band does not shrink when the declared step does, and a cap at some fraction of a cell would hide renderable content from the query. (A cap was tried and removed in review: atstep = 1e-6a0.25 × stepceiling gives 2.5e-7 while the renderer shows content out to 2.75e-5, so a splat ~10 nav steps away renders at ~60% brightness with its chunk never fetched — a NARROWING even versus the oldstep × 3rule. The quarter-cell rationale belongs to the DISCRETE arm, where over-reach bleeds a neighbouring CATEGORY; a continuous axis has no categories, so a wide window there is a bandwidth question only. This file already hands mesh a FULL cell for a continuous dim, so a sub-cell ceiling is not a rule of the file.)The ordering property that holds at EVERY step: the epsilon is never smaller than the band a degenerate hidden dim can render in, so the query can never miss renderable content on such an axis. Below the crossover the absolute cost is bounded by
T × 1e-5in the dimension's own units — negligible as a distance, however many cells it spans. That bounds the DISTANCE, not the fraction of the node fetched: when the dim's REAL σ is micro-scale too (step = σ = 1e-9, a metre-declared axis carrying nanometre structure) the window the data needs isT · σ= 2.75e-9, so the epsilon over-fetches by ~1e4 and can pull the whole node. That is the one regime where this is worse than the oldstep × 3, and the fix there is the σ-plumbing named below, not a different constant.The real long-term fix is not a tolerance formula: make the kernel's regularization floor SCALE-AWARE for a degenerate hidden dim (anchored to the dimension's own step or extent, so the rendered band shrinks with the axis), or plumb the splats' actual σ per hidden dim into the query so the window is measured rather than bounded. Until then the tolerance cannot be both narrow and complete, and this errs on complete.
KNOWN LIMITS. The first two are "your axis is mis-declared", not "widen this epsilon" (widening it reintroduces the over-fetch it replaces); the third is "do not author that node":
io/_ordering/gsplats.py::compute_chunk_bounds_gsplatsaccumulateschunk_centers ± extentsin float64 and narrows it to the float32 store with OUTWARD rounding (_store_outward_f32_array), exactly as the points builder does — so an expansion smaller than half a float32 ULP is widened to a full ULP instead of being discarded, and a smallσ_don a large coordinate no longer behaves like zero variance. (It used to, and a store written before that fix still carries the tight bounds: withT = 2.75, anyσ_d ≲ (1.1–2.2)e-8 × |coord|rounded away entirely.) What remains is the stored COORDINATE, not the pad — a dim with genuinely zero variance still gets a zero-width bound (the outward step has nothing to widen), sitting on a float32 value while the query position is a float64start + k × step, and the float32 spacing at|coord|is|coord| × 1.2e-7. So a dim whose coordinates sit more than ~8000 steps from the origin can still fall outside term 1's window. Re-origin the axis, or declare it discrete.sqrt(maxDiag × CHOLESKY_RELATIVE_EPSILON), i.e.σ_max × 1e-6— whenever the hidden block is not all-zero. That block is the marginal overcontinuousHiddenDimsONLY (computeMarginalCholeskyis called with exactly that array, andclassifyHiddenDimskeeps a scene-discrete dim out of it), so a scene-discrete hidden dim's σ can never raise this floor however large it is — the other dim has to be continuous-hidden too. A node with a degenerate dim and another such dim of large σ therefore renders a band ofT × σ_max × 1e-6, which exceeds this epsilon onceσ_max > max(1e-3 × step / (T × 1e-6), 10)in that dim's units. Both branches in closed form: where term 1 dominates it is1000 × step / T(≈363.6 × step at the defaultT= 2.75, ≈166.7 × step atT= 6 — it SHRINKS as the radius grows, because the epsilon it is compared against grows), and in the band regime it is(T × 1e-5) / (T × 1e-6)= 10, independent ofTentirely. (The two agree exactly at the crossoverstep = T × 1e-2for anyT: there1000 × step / T= 10.) Not covered here on purpose: covering it would make the fetch window depend on an unrelated axis's extent.truncation_radiusfetches the whole node. The band term isT × 1e-5with no ceiling, so a node stampingT = 1e18gets a window of ~1e13 in that dim's units — the debug query log prints it as∞(formatTolerance) and effectively every chunk matches. This is not a hole in the guard above:1e18is finite,clampTruncationRadiuspasses it (its upper bound issqrt(float32.max)≈ 1.84e19,rendering/materials/gsplat/math.ts) and so does the WRITE side (MAX_TRUNCATION_RADIUS_FLOAT32, the same value, inpackages/luxar/src/luxar/validation/types.py::validate_truncation_radius), so it is a legal authored value on both sides of the contract. Capping the band here would be the wrong fix: the MATERIAL draws that band too, so a cap re-creates exactly the fetch-narrower-than-render under-fetch #1655 item 3 removed. The window is following the renderer faithfully; the remedy is not to author a radius three orders of magnitude past anything physical (the default is GSPLAT_DEFAULT_TRUNCATION_RADIUS = 2.75, and the lower boundMIN_TRUNCATION_RADIUS≈ 2.44e-4 lives beside the upper one).A third limit was closed rather than documented: a node with a much larger
truncation_radiusthan the default used to be scored against the DEFAULT band (this function saw only aDimensionInfo), leaving the window(2.75e-5, T×1e-5]uncovered for a node stamping a biggerT.truncationRadiusnow carries the node's own value, so the band term scales with it.