Architecture Decision Rulings — R43
Architecture Decision Rulings — R43
Date: 2026-08-13 · Produces: ADR-177 · Findings:
F-R43-01…F-R43-06The brief called this round “R42 Phase 1” and that name was already taken.
Architecture_Decision_Rulings_R42.mddefines R42 Phase 1 as “the Actions-budget work, PRs #85, #88 and #89”, which “produced no ADR by design”. Two documents both claiming to be R42 Phase 1, describing different work, is the driftP6names arriving through a label. Filed as R43 instead — theRsequence is a filing convention rather than a contiguous range, asdocs/README.mdrecords about the absentR10and R42’s own header records aboutR41. The earlier record is left untouched (F-R43-01).This is a build round and it is the first one whose product is a number. Every other round in this corpus produced decisions, code or corrections. This one produced an instrument, and the decisions below are what the instrument said.
0. Findings
| Finding | Evidence | |
|---|---|---|
F-R43-01 | The round label collides. The brief’s “R42 Phase 1” is already defined, differently, in Architecture_Decision_Rulings_R42.md | That file’s header |
F-R43-02 | The brief names a document that does not exist. It says the acting-player / spectator figure “is the input 16_room_capacity_and_spectators.md needs and cannot obtain for itself”. No such file is in this repository | grep -r across docs/; doc_reference_lint.py scans docs/ and would have caught a citation, which is why the figure went into the registry and no document was created to hold it |
F-R43-03 | Three of §5.2 Segment B’s seven derived terms cost work that does not exist. Per-silo grouping, the §10.4 per-viewer digest and §9.6.3’s allowlist recomputation are ⊙0.09 ms of a ⊙3.04 ms derivation | Search across apps/vtt-backend-silo and modules/: no bucketing step in RoomSnapshotDispatcher, no XxHash3 or digest field anywhere in the silo, no pairwise mask |
F-R43-04 | §5.2’s “the dominant cost is viewers, not world complexity” is false, and half of the evidence has been in the corpus since 2026-08-02 | That measurement refuted Q-015b’s ⊙3 ms and put the fog ladder at 293.4 ms per room-second; BENCH-03 now puts Segment B at 13.5 ms per room-second at 50 viewers. Nothing re-read the sentence resting on the first |
F-R43-05 | The supply-chain ledger points at a BASELINE.md that does not exist, and two artefacts from R42 Phase 2 tell a reader to consult it before editing them | osv-ledger.json’s _comment: “READ .github/supply-chain/BASELINE.md BEFORE EDITING THIS FILE”; osv-ledger-gate.mjs’s docstring: “BASELINE.md beside the ledger carries the per-entry reasoning”. doc_reference_lint.py scans docs/ only, so it cannot see this |
F-R43-06 | Four of this round’s own benchmarks were wrong on their first run, and every one was plausible | BENCH-01 refused its own headline case (EffectsPerBatch=1000 exceeded the reference room and the guard threw, so the table read NA for the one figure Q-007 is about); the FFI round-trip arm waited for any published mask rather than the one it dispatched and reported 440–515 ns for something whose own arithmetic says tens of milliseconds; the browser suite navigated to about:blank, where crypto.subtle is undefined because an opaque origin is not a secure context; and its first working run reported crypto.subtle at exactly 0.100 / 0.200 / 0.300 / 0.400 ms — four multiples of the 100 µs performance.now() clamp, a distribution describing the clock |
F-R43-06is the one worth dwelling on, because it is this round’s subject applied to itself. Not one of the four announced itself. The first produced a complete-looking table with one cell readingNA; the second produced a number that was wrong by five orders of magnitude and looked entirely reasonable next to the row above it. What caught the second was not an assertion — it was that two benchmarks measuring different things agreed to within a hundred nanoseconds, which does not happen. The fourth was caught by its own numbers being too round. A rig is not a source of truth by virtue of being a rig, and all four corrections are recorded in the code and in the registry rather than quietly applied.Two of the four would first have failed on a Sunday. The browser suite was written to be run by a weekly scheduled job; had it not been run by hand before merging, its first real execution would have been on a schedule nobody watches, and the timer-quantisation defect would have published a
⊙figure rather than failing. That is an argument for running a T4 suite once locally before wiring it, and it is the reasonperf.ymlcarries areport_onlydispatch input.
Three filenames above are written as prose rather than in backticks, and that is typography rather than a suppression.
doc_reference_lint.pytreats a backticked name as a document this corpus points a reader at; all three are the subject of a sentence saying they are absent, which is the opposite of a pointer. They are deliberately not added todoc_reference_allowlist.txt, whose own header states it is not a place to park a genuine dangling reference —F-R43-05is corrected at its source instead, in the two artefacts that name it.
F-R43-02is left open on purpose. The obvious response is to create 16_room_capacity_and_spectators.md and put the figure in it.P6forbids exactly that: a second document holding a number the registry owns is the failure this corpus records most often, andQuantity_Registry.md’s own entry for the fog ladder still carries the scar of citing epic_frontend_prediction_proposal.md, “no such document has ever existed in this repository”. The acting-player / spectator answer is inQ-110andQ-112where aQ-IDcan be cited, and nothing was created to hold it.
1. ADR-177 — The measurement rig, and what BENCH-01…05 decided about §5.2
Status: Accepted · Date: 2026-08-13
Context
Quantity_Registry.md has named BENCH-01…05 as the blocker on sixteen ⊙ rows since the day it
was created, and docs/README.md has listed “5 benchmarks. BENCH-02 and BENCH-03 are the
priority — they decide whether §5.2’s Segment B budget holds, which is the real capacity
bottleneck” under Blocked on measurement — cannot be advanced by document work for as long.
There was no rig. No BenchmarkDotNet reference outside the vendored core/Descent.Sandbox,
no criterion in any Cargo.toml, no load generator. tools/bench-asset-decoder.mjs and
tools/bench-hub-envelope.cs are two one-off probes, and every geometry figure in the registry —
the curve that reduced Q-012 from 512 to 256, Q-058’s path-budget table, Q-075’s permeable
multiplier, ADR-143’s 5.32× — was taken once, by hand, on one machine. That is how a
measurement becomes folklore: the number survives, the method does not, and nobody can tell a
regression from a different laptop.
Decision
1. Three rigs, at the tier §14.8 puts them.
| Where | Subject | |
|---|---|---|
| .NET | tests/core/Descent.Benchmarks | BENCH-01, 02, 03, 05, and the interop hop Table A has no row for |
| Rust | core/Descent.Geometry/benches | the fog curve, Q-075’s ratio, Q-058’s worst case, ADR-143’s two hosts |
| Browser | apps/vtt-frontend-client/bench | Q-099 / Q-100 in a real browser, and what a browser cannot say about Q-033 |
The .NET project is in Full.slnx so a PR compiles it, and is not a test project so
dotnet test never runs it — verified rather than assumed. The criterion targets carry
test = false for the same reason: a criterion bench under cargo test executes every benchmark
in single-shot mode, which is fast and is still a benchmark inside a correctness suite.
2. The rig measures §5.2 Table A’s terms one by one, and says so where a term has no code.
Table A’s whole purpose is to show its own arithmetic. Three of Segment B’s seven terms are
F-R43-03 and are left unmeasured rather than approximated: a stand-in would publish a figure
describing code nobody has written and the substitution would be invisible in the table.
3. Segment B is reported TWICE, because Table A budgets mailbox occupancy and the derivation
costed the work. ReplicateToViewers ends at _ = SendDeltas(…); the wire encode runs inside
the transport sink. 0.675 ms of work, 0.332 ms in the tick’s own frame. Whether the
fire-and-forget continuation still occupies the activation is not settled — Orleans resumes a
grain’s continuations on its own scheduler — and it is recorded as open rather than assigned to a
segment by assumption.
4. Q-110, Q-111 and Q-112 are registered, and Q-111 is registered with its entity
bound because a seat count without one is not a bound. Q-112 gets a status this table has not
used before — Normative as a lower bound — because the application half of an intent is
measured and Orleans’ dispatch is not, and neither Normative nor ⊙ would say that.
5. The T4 job gates RATIOS and publishes ABSOLUTES, weekly, never on a pull request. The
argument is Q-075’s own: it measured the impermeable advance at 12.99 ms where the reference
figure was 29.37 ms and wrote only the multiplier into the ladder, because “what transfers is the
multiplier, because both arms run on the same box against the same geometry”. A hosted runner is
shared and unpinned; an absolute-millisecond threshold on one is a threshold on the fleet’s
scheduling, §14.9 forbids retrying the flake away, and the only remaining move is to widen until
it catches nothing. The ratchet is two-sided in all three directions the corpus already uses.
What the measurements decided
| Criterion, pre-registered | Result | |
|---|---|---|
| BENCH-03 | within 0.5×–3× of ⊙3.04 ms confirms; > 3× reallocates Table A | 0.675 ms = 0.22× — the budget holds with 7.4× headroom, and the result is outside the band on the side the criterion did not anticipate |
| BENCH-02 | predicted 5×–25×; < 3× demotes D-2 to a recommendation | 3.62×–4.57× — D-2 stays normative, by a 21% margin, and the prediction was optimistic by roughly 2× |
| BENCH-01 | > ⊙3 µs per Effect lowers Q-007’s 1,000 | 0.67 µs — 1,000 holds, at 0.670 ms of Segment A’s 3 ms |
| BENCH-05 | the derivation basis for Q-008, against D-4’s ⊙0.3 ms | 0.394 µs application-layer — 760× cheaper, and a lower bound |
| BENCH-04 | demoted to a verification in 2026-08-01; not re-promoted | the FFI round trip is exercised end to end (0.264 ms empty, 24.24 ms at Q-012), which is what makes the crate’s own figures checkable from the host side |
Every budget in §5.2 Table A holds, and that is a result rather than a formality. This corpus’s R34, R35 and R38 records exist because a round that only says yes is advocacy, so it is worth being precise about what “holds” bought: not one of the four measured terms was within a factor of two of its estimate, the total was out by 4.5×, and the two normative requirements the derivation exists to justify were both confirmed — one narrowly.
Consequences
The negative ones first, because a round that confirms its own budgets must be read carefully.
- §5.2’s “dominant cost” sentence is false and is corrected here (
F-R43-04). Measured, geometry outweighs per-viewer assembly by roughly nineteen times, in the opposite direction to what the whitepaper claimed.Q-016’s composition is refuted: it attributed ⊙95% of a 50-viewer room’s expected cost to Segment B, and Segment B measures at 17% of that total. Q-016’s total is NOT re-derived, and the refusal matters more than a number would. The geometry term is an admitted allowance — 6 advances per room-second atQ-075’s 44 ms — not an expected cost, and turning it into one needs a fog-budget utilisation from real play that does not exist. Publishing a figure derived from the bucket’s ceiling would repeat the exact error that entry already records: a budget ceiling is not a capacity unit. So §4.4’s admission weight and §10.2’s cost model remain unsettled, and this round makes their composition wrong rather than making them right.- The benchmark most people would want is the one that is missing. Every figure here is the
work
AdvanceTickAsyncand its callees perform. Orleans’ own message dispatch is unmeasured, and atQ-111’s seat counts it is the term most likely to bind — ~3,980 messages per second into one single-threaded activation.Q-111is therefore not a seat ceiling and says so in its own row. Closing it needs a load generator against a real silo, which does not exist. - A garbage rate nobody had. Segment B allocates 1.42 MB per tick at 50 viewers — 28.4
MB/s per room — and a full
Q-007batch allocates 1.66 MB. Nothing in §5.2 budgets allocation, and a Gen0 collection landing inside a tick is precisely the jitter Table A’s ≥22 ms Reserve row exists to absorb. Recorded, not acted on. - The published fog-cost model does not transfer between machines.
cost ≈ 0.26 + 1.81·√nis documented as safe to interpolate with; its shape reproduces and its coefficients do not, and its intercept is an order of magnitude away from this box’s zero-occluder reading. Narrowed to interpolate within a machine, never across one. Q-075’s multiplier re-measures at 1.25× against the recorded 1.4×, and the ladder is not re-derived. At 1.25× the fog allowance would be 8 advances per room-second rather than 6, and a third of the presentation cost §9.2 andepic_presentation_smooth_fog.mdpaid could be given back. Two measurements 12% apart do not move a shipped ladder; the disagreement is in the conservative direction, the ratio is now banded at 1.10–1.60, and repetition is what will settle it.- “One LOS ray is 0.4 µs at any occluder count” is withdrawn. 2.8 ns empty, 871 ns at
Q-012. The comparison it supports survives by four orders of magnitude; the independence claim does not. - The rig itself is now a dependency surface. criterion adds 23 crates to a proprietary,
statically-linked engine’s dev graph.
cargo denyand every crate’s RustSec record were checked before adoption rather than discovered on a red CI run, and the exit cost is written intoCargo.tomlasP8requires — but the surface is real andDirectory.Packages.propsgained two Roslyn pins for a package no project references, which is ADR-176’s stated consequence arriving in practice.
And the positive ones.
- Three registry figures reproduce:
Q-075’s 12.99 ms lands at 13.065 ms,Q-058’s 6.1 ms worst case at 5.21 ms on a faster box, and ADR-143’s 5.32× at 5.47×. The decision that declined the universal WebAssembly geometry core now rests on something a scheduled job re-takes rather than on one afternoon. - D-3 has a price for the first time: 50.7× at 50 viewers, saving 1.67 ms per tick — a third of Segment B’s budget, and more than every implemented term of Segment B put together.
- Two
⊙rows became normative (Q-003,Q-007), and — more useful than the count — the remaining fourteen each now name what they are actually blocked on, which for nine of them is not a BENCH item at all.
Enforcement (ADR-045)
.github/workflows/perf.yml, weekly and on dispatch, running tools/perf-ratchet.py against
tools/perf-baseline.json. Six ratios, banded, two-sided; a ratcheted key that produces no result
is red, a baseline entry nothing produces is red, and an empty result set exits 2 rather than 0 —
a gate that passes when nothing ran is the shape ADR-174 records twice.
§11 index line
ADR-177: The measurement rig exists; §5.2 Segment B's 5ms budget HOLDS at 0.675ms — 0.22× its own derivation, outside the pre-registered band on the side nobody wrote an action for — and §5.2's "the dominant cost is viewers" is refuted, geometry outweighing per-viewer assembly by ~19× in the other direction. D-2 survives at 3.62–4.57× against a 3× demotion threshold; Q-007's 1,000 holds at 0.67µs per Effect; D-4's ⊙0.3ms intent is 0.394µs in the application layer and Orleans' dispatch is unmeasured. Ratios are gated and absolutes published, on Q-075's own reasoning. (Amends ADR-046, ADR-143; introduces Q-110, Q-111, Q-112.)
2. Write-back performed
Quantity_Registry.md: two new sections;Q-003andQ-007to Normative;Q-016’s composition refuted with the arithmetic;Q-110,Q-111,Q-112registered; the Outstanding inventory ⊙ row taken from 16 to 14 and rewritten to name each remaining blocker, because a count is the least useful thing that row can carry.- Whitepaper §5.2: Table A’s two ⊙ markers gone and both rows carrying their measured figure; the Segment B derivation block carrying the measurement beside the estimate rather than instead of it; the “dominant cost” paragraph corrected; the ⊙ footnote rewritten to say what is now settled and what is not.
- Whitepaper §11: ADR-177’s row.
docs/README.md: the Blocked on measurement list.tools/docs-lint/uncited_ceiling.txt: unchanged at 73, verified rather than assumed — every figure added to §5.2 sits inside a paragraph or table that cites aQ-ID, or inside the declared derivation block ADR-106 clause 2 exempts.