Readiness verdict over a __luxarDebug.getState() snapshot — "does this
scene graph carry drawable elements, and are they the ones the store
describes?".
THREE WAYS THE SECOND HALF FAILS, and all three are refusals here. A node can
be truncated by the renderer's per-node element-texture clamp
(totalDroppedElements); progressive refinement can stop scene-wide at the
residency byte ceiling (refinementResidency), leaving a partial scene whose
composition depends on which nodes were offered first; and the GPU buffer pool
can go over its VRAM byte budget and shed pooled geometry to get back under it
(gpuPool.byteBudgetEvictions). In every case the element counts stop being a
property of the STORE and become a property of the machine and the run — so a
capture is not comparable against another build's, and an A/B is measuring the
ceiling rather than the change (royerlab/luxar#2508). The residency stop used
to surface as one console warning that nothing downstream read, which made
"grep the console before comparing two builds" a convention instead of a gate.
BOTH NEW SIGNALS ARE LOADER-SCOPED AND CUMULATIVE WITHIN THAT LIFE, AND THAT
IS DELIBERATE: each says "this happened at some point while this scene was
loaded", NOT "this is true right now", so a scene that stopped at the ceiling
and then refined fully after a view change is still refused. The contract in
full — why that is the verdict a capture tool wants, and why an in-page
dataset switch clears it without a reload — lives on RefinementResidencyStop
in data/scene-loader/progressive/residency-budget.ts.
WHAT IT DOES NOT MEASURE: whether the framebuffer will contain pixels.
The DebugState totals deliberately include HIDDEN nodes (see the
totalLines / totalTriangles field docs there) and sum EVERY level of a
substitutive kind=lod group, not just the active one. So an all-hidden
scene, or one whose only content is an inactive LOD level, reports
ok: true and the screenshot can still be blank. That is intentional: the
verdict mirrors debug-state.ts's aggregate contract rather than
re-deciding visibility here, so the two can never disagree. Filter on the
per-node visible flags in the snapshot if you need the stricter question.
WHY THIS IS A MODULE AND NOT THREE LINES INSIDE page.evaluate:
tools/capture-hires.ts used to compute the verdict inside the browser
closure, where nothing can unit-test it — and it was WRONG for every scene
ever captured. It read the totals from a state.performance sub-object that
debug-state.ts has never produced (DebugState is FLAT), so perf
was always {}, every total was undefined, and undefined > 0 made ok
false for points, lines, gsplats and mesh alike. The bug was invisible
because JSON.stringify drops undefined keys, so the printed diagnostics
simply omitted the totals rather than showing undefined
(royerlab/luxar#1579).
So the verdict lives OUT here, as a pure function over a plain object: the
browser side only has to hand back getState() verbatim. Deliberately
dependency-free — no THREE, no browser globals — so it is importable both
from a vitest suite and from a tsx-run Node tool.
Every field is read defensively (finite-number / Array.isArray guards)
because the input crosses a page.evaluate serialisation boundary and may
come from an older viewer build. A per-type total that is missing — or
present but unusable — reads as 0 AND is named in reason, because an
unannounced 0 is indistinguishable from a genuinely empty geometry type; a
snapshot carrying NO recognisable total at all is reported as a shape
mismatch rather than as an empty scene. Either way the summary never leaks a
NaN or an undefined.
Readiness verdict over a
__luxarDebug.getState()snapshot — "does this scene graph carry drawable elements, and are they the ones the store describes?".THREE WAYS THE SECOND HALF FAILS, and all three are refusals here. A node can be truncated by the renderer's per-node element-texture clamp (
totalDroppedElements); progressive refinement can stop scene-wide at the residency byte ceiling (refinementResidency), leaving a partial scene whose composition depends on which nodes were offered first; and the GPU buffer pool can go over its VRAM byte budget and shed pooled geometry to get back under it (gpuPool.byteBudgetEvictions). In every case the element counts stop being a property of the STORE and become a property of the machine and the run — so a capture is not comparable against another build's, and an A/B is measuring the ceiling rather than the change (royerlab/luxar#2508). The residency stop used to surface as one console warning that nothing downstream read, which made "grep the console before comparing two builds" a convention instead of a gate.BOTH NEW SIGNALS ARE LOADER-SCOPED AND CUMULATIVE WITHIN THAT LIFE, AND THAT IS DELIBERATE: each says "this happened at some point while this scene was loaded", NOT "this is true right now", so a scene that stopped at the ceiling and then refined fully after a view change is still refused. The contract in full — why that is the verdict a capture tool wants, and why an in-page dataset switch clears it without a reload — lives on
RefinementResidencyStopindata/scene-loader/progressive/residency-budget.ts.WHAT IT DOES NOT MEASURE: whether the framebuffer will contain pixels. The DebugState totals deliberately include HIDDEN nodes (see the
totalLines/totalTrianglesfield docs there) and sum EVERY level of a substitutivekind=lodgroup, not just the active one. So an all-hidden scene, or one whose only content is an inactive LOD level, reportsok: trueand the screenshot can still be blank. That is intentional: the verdict mirrorsdebug-state.ts's aggregate contract rather than re-deciding visibility here, so the two can never disagree. Filter on the per-nodevisibleflags in the snapshot if you need the stricter question.WHY THIS IS A MODULE AND NOT THREE LINES INSIDE
page.evaluate:tools/capture-hires.tsused to compute the verdict inside the browser closure, where nothing can unit-test it — and it was WRONG for every scene ever captured. It read the totals from astate.performancesub-object thatdebug-state.tshas never produced (DebugState is FLAT), soperfwas always{}, every total wasundefined, andundefined > 0madeokfalse for points, lines, gsplats and mesh alike. The bug was invisible becauseJSON.stringifydropsundefinedkeys, so the printed diagnostics simply omitted the totals rather than showingundefined(royerlab/luxar#1579).So the verdict lives OUT here, as a pure function over a plain object: the browser side only has to hand back
getState()verbatim. Deliberately dependency-free — no THREE, no browser globals — so it is importable both from a vitest suite and from atsx-run Node tool.Every field is read defensively (finite-number /
Array.isArrayguards) because the input crosses apage.evaluateserialisation boundary and may come from an older viewer build. A per-type total that is missing — or present but unusable — reads as 0 AND is named inreason, because an unannounced 0 is indistinguishable from a genuinely empty geometry type; a snapshot carrying NO recognisable total at all is reported as a shape mismatch rather than as an empty scene. Either way the summary never leaks aNaNor anundefined.