- The predicate is one function again: MGPipeResourceRespecifyWantsItsReply (PipeRoute.h) is read
by the wire emitter, so the two halves of this row cannot disagree about whether an answer is
owed. The buffer half never owes one (both call sites discard the Bool, PipeFill.cpp:1105/:1142);
the texture half owes one exactly until MGPipeHandleIsPublished, and that latch is set only by an
ACCEPTED create - which EmitResourceRespecify already sends, blocking, when it is clear
(TextureEmit.h:869-879). So the object pays once, at first use, and every record after it is
fire-and-forget.
- MEASURED on the device, same frame, same 101,421,396 B staged, stock power policy: waits
25,154 -> 4,543 and wall 130.9 s -> 31.3 s. The op split is the proof of what changed and what
did not: waits by_op 3=20611 -> absent (only 2=4536, 5=6, 9=1 remain), while the RECORDS sent
are unchanged - frame-sent still shows 3=20633 and 103,094 records total. The round trips went
away; the work did not.
- It also lands on an independent prediction: an audit run beside this work (mgl-device/scratch/
scheme-review.md) computed 4,543 remaining waits and a 24-37 s credible range (midpoint ~30 s)
from the archived logs, a host-inproc round trip of 35.4 us and a measured 43.1 MiB/s link.
Measured: 31.3 s, at the midpoint.
- Cumulative on that frame since this work started: 43,232 waits / 221.5 s -> 4,543 / 31.3 s (7.1x).
What is left is the create row (4,536 waits, ~23 s) - the confirmation itself, not waste - plus
hard floors of ~13.4 s client CPU and ~2.2 s of transfer. Going below that means batching the
create row, which keeps real answers.
- TWO DEBTS, STATED: (1) the invalidation guard for the one silent-loss case is NOT wired; the
audit confirmed independently that no production path can drop a texture record today, and if
one appears the notice it needs is protocol-surface (a tenth reverse callback). (2) No SSIM or
visual check has been taken since these rows stopped asking - frame-sent agreement is strong
evidence but it is not a picture.
- Two stale controls in older cases were fixed rather than left: the item-B gate now names the
CREATE row (its old total-based control only moved because a respecify was still waiting), and
the by-op measurement asserts records-sent for the rows that deliberately stopped waiting.
RemoteClientTest 169/169; red-once for the predicate is forcing it back to "always wait".
- 2.9 established that a record's ~5 ms is mostly the DEVICE waking out of a deep idle state.
That points at a knob that is already wired: how long the server's apply thread spins before
parking. Raised from 50 us to 2000 us (via the Activity's existing `env` extra, which
ServerEnvironment.apply parses as KEY=VALUE;KEY=VALUE and which does not scrub
MOBILEGL_IPC_SPIN_US), the same 43,232-record frame goes 221.5 s -> 182.8 s (-17%), with
rtt_mean 5,040 -> 4,145 us. Pinning the device and disabling its deep idle states is worth
166.5 s (-25%) but needs root; the spin needs neither root nor a rebuild.
- The server log proves the knob landed (`mgl-srv-apply started ... spin 2000 us`) rather than
leaving the reader to trust an env var, and the note carries the exact am start line.
- So the load stall now has three ordered levers with numbers behind them: batch the waits
(client-only, not built), keep the server hot (this commit, usable today, trades device CPU for
wall clock on a frame that is 94% waiting), and pin the device for measurement discipline.
- Three one-variable experiments on the same device, same workload (the 43k-record frame): a 40x
larger client spin moves the per-wait mean by ~10% (so it is not the client park/wake); the
same frames over adb forward - a link whose raw RTT is several times lower - by ~25-30% (so it
is not the link, which is what the handoff saw in 3.6 and could not account for); and pinning
the device (governor performance on all eight cores, cpuoff_l / clusteroff_l / mcusysoff /
s2idle disabled) by 26% per wait and 25% of the whole frame: 221.5 s -> 166.5 s for 43,218
records and 100.5 MB staged, against 43,232 and 101.4 MB in the baseline.
- The mechanism: the server's apply thread parks between records, the SoC drops into a deep idle
state, and every record pays the exit latency - milliseconds, once per record, which is the
entire shape of the frame. It also matches the server profile the handoff took (65% socket
syscalls, ~15% doorbell spin: waiting and waking, not working).
- Stated where it matters: the handoff's 249 s was taken on an UNPINNED device, so its absolute
numbers carry this idle tax - the project has a pinning discipline for exactly this reason.
- The lever list is now ordered: batch the waits (K <= 8 by the reply-slot pool, client-only),
keep the server hot between records (its own spin budget, which the APK does not wire yet), and
measure split numbers on a pinned device. The device was restored to stock afterwards.
- What was open: the handoff's 4.3 called ResourceCreate and SetTextureParams "a second problem of
the same kind, needing its own analysis" and left it there; 5.4 said the per-op split of the
55,428 waits was never quantified. The measurement committed beside this answers both.
- The ruling (CONTRACT-P5E.md 2.5): ResourceCreate, ResourceRespecify and SetTextureParams stay
kWaitReply, because each answer carries a fact about the SERVER's record table that the client's
own tracker provably cannot know. RespecifyOnce says it outright - "the applier's REFUSAL is the
only signal that says 'I hold nothing for this handle'", which is the scope
MGPipeApplierReleaseObjectRecords leaves for a served context whose frontend objects live on;
EmitTextureParams uses its own refusal as the same self-heal trigger; PublishCreate may not take
the publication latch on a refused create (D-I1/c0b) or the death path emits a resource_destroy
for a record that does not exist. Unlike the upload half these are not "the emission gate already
answered it" rows, so removing their waits removes the signal.
- The numbers, both shapes a stitcher produces: an atlas stitch goes 64 -> 0 reply waits; a texture
per sprite goes 384 -> 320 with ResourceSubData now zero and the remainder all three of those
rows. So item B hits exactly the shape it was aimed at, and what is left is per-resource /
per-call rather than per-chunk.
- notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md gains 2.4 with the table and the ruling, and its "second
problem of the same kind" entry is closed rather than carried; MEASUREMENTS.md records the two
shapes beside the 1 -> 0 single-upload number.
- notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md: the two items, their root causes with file:line anchors,
the five cases that pin them, the red each one produced when the fix was taken back out, and the
four things this round did NOT do (device end-to-end, local G1, the ResourceCreate /
SetTextureParams pair, and the sandbox's missing /dev/dri - which reds the two EventForfeitPeer
lanes for reasons that have nothing to do with the change).
- p12/README.md: a line in the summary and the new file in this directory's table.
- MEASUREMENTS.md: the P12 conclusion and row now carry the number the gate pins - one reply wait
per pure texture upload becomes zero - with the device wall clock named as the measurement still
owed rather than left implied.
- README, ARCHITECTURE, ROADMAP, MEASUREMENTS and CURRENT_STAGE_PROGRESS are rewritten as indexes
(84 KB -> 15 KB): what the project is, where it stands, and where each detail lives. ROADMAP is one
plain sentence per phase; MEASUREMENTS is the five conclusions that matter plus one line per phase;
CURRENT_STAGE_PROGRESS is the P12 goal, a checklist and the next steps.
- ARCHITECTURE's body moves unchanged into ten design/ files, keeping §1-§17 and appendices A/B, so
`ARCHITECTURE.md §N` references in code resolve through the table in ARCHITECTURE.md.
- guide/ holds build-and-run, code map, glossary and the engineering discipline (from README and
ROADMAP), and the device pinning record that used to sit in devices/.
- The open-debt table moves to notes/DEBTS.md; the detailed current-stage tables (with decision IDs,
the open line items and evidence locations) move into notes/p12/README.md. notes/README.md maps
every moved path and section.
- The documentation citation lint in test.yml also covers design/ and guide/, which describe the
current tree as the top-level files did.
- ROADMAP, MEASUREMENTS and ARCHITECTURE are rewritten as summaries (333 KB of index text -> 84 KB).
ROADMAP's phase table is one line per phase plus a link; MEASUREMENTS keeps headline numbers under
its old §1-§13 numbering (plus §14-§16 for P6.5, P7 and P12); ARCHITECTURE keeps every decided rule
under the same §1-§17 / appendix numbering, so the `ARCHITECTURE.md §N` references in code still
land, and §17 becomes an index of the phase landing shapes.
- CURRENT_STAGE_PROGRESS now describes the current stage (the P12 on-screen-server subset) and absorbs
CURRENT_STAGE_STATUS, which is removed. The P7 status tables, the P6 closing snapshot and the P6.5
first-wave snapshot move to notes/p7, notes/p6 and notes/p65.
- Each phase gets notes/<phase>/README.md: a short summary, then the verbatim material that left
the index documents (its ROADMAP row, its MEASUREMENTS section with subsection numbers kept, its
ARCHITECTURE §17 part, milestones) and a file index. P8-P11 and P13 get their planned scope;
the full open-question list moves to notes/OPEN-QUESTIONS.md.
- The phase reports that sat at the top level move into their phase folders with unchanged names:
P5D-INPROC-PERFORMANCE, P5E-RUNAHEAD, P5F-WIRE-COMPLETENESS, P6-SPAWN-PLAN, P6-CONTRACT-DRAFT,
P6-ENDSTATE-REVIEW. Their relative links, CONTRACT-P6.md's links to them and one tool docstring
are updated; notes/README.md maps every old path and section number to its new place.
- 71 files under notes/recovered/ that were byte-identical copies of phase-folder files are removed.
- Corrections made while condensing: the server process reads Transport as Spawn (CONTRACT-P6 §3.1
amended the old "child forces monolith" rule); both backends now publish kCapRunAheadApply;
MOBILEGL_IPC_RESPAWN is a named refusal and MOBILEGL_IPC_IDLE_EXIT_S is not parsed; the Redmi
pin row is in tools/device_bench/pin_device.sh.
check_doc_citations over docs/Disaggregated/*.md: 0 problems. No relative link is newly broken.
- the census and §7.2 pages were written against 1135c664 before the trailer rewrite; the
citations now name its rewrite 3c80cd62 and the other remapped commits
- 144 commits from 785fed0b0 onward were rewritten to drop the Co-Authored-By trailer the
project forbids; origin/feat/disaggregated moved from 1135c664 to 3c80cd62 (same trees)
- the old->new map is ~/w7/logs/sha-map-20260922.txt; every 7..40-hex citation in the
Disaggregated docs and the MG_Remote contracts that matched an old SHA is replaced by the
same-length prefix of its rewrite; no other content changes
Rework round 2 of 4 after the D2 review (fable, ID-P7-8), which found that this gate's
numbers were right and its SUBJECT was wrong.
The scatter wrote 40 2x2 rects at x=((i*7)%32)*2, y=((i*5)%32)*2 into a 64-px atlas.
Both strides are surjective onto 0..31 over 40 iterations - rect 9 lands at x=62, rect 19
at y=62 - so the rects' union box covered the WHOLE LEVEL, and the server's subRectEligible
(Managers.cpp:8486-8491) requires !dirtyRegion.CoversWholeLevel, a BOUNDING-BOX test
(MipmapStorage.h:30-33). Both scatter textures were rect-INELIGIBLE on every arm before
:8649's unpack-ring policy was ever consulted. The box-versus-rect decision the scenario
exists to pin was never reached for the two textures it scatters into, and the only
texture that could move under any red-once was the contiguous control band - which is
exactly what the old substitute red-once's box=6 rect=3 jobs=12 was.
kScatterMargin insets the scatter by one rect: same 40 writes, same coarse stride, union
box bounded at (2,2)..(62,54). The gold row is UNCHANGED at emit=9 box=9 rect=0 jobs=9 on
all four arms - but it now means what it says.
THE RED-ONCE IS NOW ZERO-PATCH, which is stronger than the throwaway source edit it
replaces: MOBILEGL_ESPRYT_DISABLE_UNPACK_RING=1 alone gives box=3 rect=6 jobs=183 with
bytes/f[tex] falling from 104448 to 9024, while all 26 pixel cases stay green. BOTH
SCATTER TEXTURES moved - box=3 is the contiguous band alone, rect=6 is the plain atlas
AND the atlas written through the glTextureView, and jobs=183 = 3 + 6x30 confirms thirty
rects per scatter emission. The same knob was green before the inset, which is the
evidence that CoversWholeLevel was short-circuiting the decision.
The three prose sites are rewritten accordingly: the scenario's gold-table rationale, the
MEASUREMENTS.md 13.1 reading and the package note's red-once section now explain
attempts 1-3 as mutations of a decision the client already makes correctly, and attempt 4
by CoversWholeLevel rather than by the ring.
Also PipeStatsWindow gains LastFrom{Client,Server}LogSince, and the gate reads its two
role windows since a mark taken before the counted workload. The server writes an EMPTY
teardown window after the counted one, so "the last line in the file" is one shutdown
away from reporting four zeroes as the workload's shape. Moving to a marked read exposed
a second latent defect in the same assertion: ASSERT_TRUE(clientWindow.found) over the
whole file was satisfied by the "counters ON" BANNER in all three split arms, claiming a
window had been read when none had. The assertion is now on the window that actually
carries the tex[] bracket.
P3b/P4b R-11 / D-D4, landing under the P7 program (PLAN-PH-P34B-P7.md 1.2, wave 2-D
package D2). P4a recorded the emission shape rather than gating it because the
per-storage-owner cursor with view/owner remapping was the next phase's and gating an
unfinished shape pins a non-answer. That work landed, so the RecordProperty block is
now EXPECT_EQ against a gold row MEASURED on this cursor, and the workload carries a
third texture written through a glTextureView so the remap is inside the counted
window.
The gold row, identical on monolith / inproc / spawn / tcp:
emit=9 box=9 rect=0 jobs=9, client emitter 9
keyed on the workload constants by static_assert. P4a's emit=6 box=6 rect=0 jobs=6
was a two-texture workload with no client emitter and is not comparable.
The client half is read from MGPipeTextureEmitter::SubDataCount() rather than from
ctu=, and that is a correction: under spawn and tcp the two roles are two processes
whose PipeStats windows close on different Present cadences (measured ctu=13 against
emit=9, and usually not yet written when the case reads), so ctu == emit is not a
well-posed comparison there. The log field is kept as a record and asserted only
where the transport says the two are window-aligned - asking the NUMBER was the first
mistake, because the server publishes a readable ctu=0 under spawn and the comparison
silently became 0 == 9. PipeStatsWindow gains LastFromClientLog/LastFromServerLog.
Split/spawn/tcp arms added as explicit per-arm blocks rather than through
mgl_itest_register_split_arms: the macro hands every arm the shared environment, and
this case needs a lane marker, PipeStats period 1, a push mask and above all a
MOBILEGL_LOG_FILE_PATH private to the arm - the library opens the log "w", so two
entries sharing a path truncate each other and the failure looks exactly like "the
counter was never emitted".
Red-once (R-16): the prescribed flip of summedArea*4 >= unionArea*3 leaves the gate
GREEN, and that is a finding. Two gates downstream dominate it - the server needs
dirtyRectCount >= 2, and UnpackRingAvailable() zeroes the rect list by design ("one
box, one job", the existing Mali-cliff mitigation). So rect=0 is the policy's
intended output, not an accident of the scatter pattern. The red-once that bites
splits the union box in two AND disables the unpack ring: box=6 rect=3 jobs=12
against the gold, while 26 pixel cases stay green. Four attempts and their reasons
are in the note.
The Mali frame-time delta D-D4 asks for beside the gold is NOT schedulable - one
Redmi, an Adreno, no Mali part - and is recorded as a declared deviation in the
scenario header and MEASUREMENTS.md 13.3 rather than modelled.
Also corrects the three places that said the client emitter had not landed: the
scenario header, the CMake probe's comment, and gates-v1.md's re-run table.
TextureEmit.h's EmitOneLevel has been incrementing it.
The device gate: Redmi 2f7cbe2e, reboot-clean, paired A/B in one thermal
window (arms interleaved, best-of-3 then best-of-2), rd12 + DirectGLES.
inproc vs spawn client CPU p50 7.880/7.889 vs 7.876/7.869 (-0.15%) and
7.987/7.989 vs 7.992/8.009 (+0.16%) - a tie inside the 0.2-4.2% within-
arm spread, sign flipping with arm order, recorded and not gated.
monolith is ~30% dearer. 22/22 runner exits clean, 44/44 PINNED, zero
Fatal.
The three mandatory numbers: socket doorbell vs condvar is a tie too -
client 121.1 waits/frame identical across transports, server 10 866-11
963 (inproc) vs 10 415-10 416 (spawn) waits/frame, and the contract's
'converting that into ~4 blocking reads may be cheaper' did not pay off;
the only column where the socket is worse is clipark/f (6.07 vs 5.60/5.75).
SEG_STAGE bytes/frame: 130.8 KB median / 8.01 MB steady (OpenRA), 816.2
KB / 1.53 MB (rd12). Records/frame post-chunking: 211/206 and 7 497/7
504. maxrec stays 1808 - the 8 MiB chunk budget is never reached. Both
framings reported throughout (the startup window runs ~7x the steady
state) rather than the flattering one.
MEASUREMENTS.md gains section 12 with the full protocol and tables; the
6.3 'no post-chunking distribution exists' gap now points there.
CURRENT_STAGE_PROGRESS.md marks gates 7/8 met and records the closeout
work plus two structural limits (spawn client windowed fields are zero
by construction - OnPresent runs in the server process - though its run
totals are valid stores; the stats dump is role-derived). ROADMAP marks
P6 closed in the stage table, the header and the milestone.
Evidence: notes/p6/gate7-device-ab.md, gate8-doorbell-device.md,
gate8-wire-counters.md, git-worktree-fix.md. check_doc_citations: 82
citations in the three documents, 0 problems.
MEASUREMENTS 7.3 records what 82683d4a did, so the 30 s in that row stays as
written; a note under the table now says which value is live and where the
reason for the change is argued, so a reader looking up the barrier budget by
grep does not come away with the superseded number.
Every place that stated "P5 does not chunk" as current behaviour except the
Fatal texts (previous commit) now records the stage chunk budget and what it
does not cover.
- CONTRACT-P5.md section 5: the STAGE_MB row names MGPipeStageChunkBytes() =
clamp(segment/4, 4096, segment), the two buffer walks (1e7c372e) and one
texture level's whole-width slabs (9469d48e, assembled by
StagedTextureStore::AdoptRun), and the Fatal that stays the backstop for a row
with no cut; the RING_MB row says the record bound is about the record's own
bytes.
- BRIEF-P5.md's R-10 section is no longer headed "does not chunk": it keeps the
record bound and the maxrec proof obligation unchanged and records that
content is cut, with the encoder Fatal as the fallback.
- ROADMAP.md's staging debt row and open question 11, ARCHITECTURE.md's
record-bound bullet and SEG_STAGE table note, README.md's knob row,
MEASUREMENTS.md section 6.3 and CURRENT_STAGE_PROGRESS.md's open item: the
explicit MOBILEGL_IPC_STAGE_MB=256 profile is no longer needed for the two cut
paths, and the carrier design for the rest stays P8.
- Historical results (notes/**/p5-results/, p5b/) are left exactly as recorded.
Section 11's matrix ran at VD12, where BOTH monolith and run-ahead hit the 120 Hz panel
ceiling - so that section answers "can it reach the ceiling", not "which is faster". At render
distance 32 the load rises to ~3550 draws/frame (4.2x) and neither arm is near any cap (the
game's own enableVsync:false / maxFps:260, and the measured max is only 37-71). That is the
comparable round.
- Five runs per arm, interleaved, CPU clocks pinned (checked before AND after, both PINNED),
GPU held at pwrlevel 0, fan on; 75-150 s in-world settle before a 40 s window - VD32's chunk
streaming runs far longer than VD12's and reusing the 20 s settle would have counted loading
as steady state.
- monolith p50 median 58.5 (range 56.3-67.9), inproc run-ahead p50 median 59.9 (range
56.8-61.1), client CPU 16.19 vs 15.72 ms/frame. Level, run-ahead marginally ahead, and with
half monolith's spread.
- Against the lockstep it replaces: p50 +69%, client CPU -40%, apply CPU -43%. The heavier the
load the more it is worth, which is what the mechanism predicts: rendezvous count follows
draw count, ~900 per frame at VD12 and ~3600 at VD32.
- Both arms are client-thread saturated (~94%), so the client ms/frame column is each arm's
real bottleneck and the two are directly comparable.
New section 11.1 records an OPPORTUNITY rather than a defect: apply spends 11-12 ms against
the client's 15.7 ms, so the two sides are unbalanced and moving work from client to apply
lowers the bottleneck directly. Under lockstep that was meaningless (the two sides add
serially); run-ahead is what turns it into a lever. Left to a later phase.
options.txt restored from .p5e-backup to renderDistance:12; the device is unpinned, fan off,
GPU back to governed.
- CURRENT_STAGE_PROGRESS.md gains section 2.6: the four packages (pa / mv / gl / ra2), the
admission rule's three disjuncts and which real entry forced each one, coverage going from
116 to 179 entries plus 1357, the race the flip exposed and its root cause, and the finding
that a hard-green strict lane is necessary and NOT sufficient (ID-132). Code head is now
25fba0d5.
- MEASUREMENTS.md gains section 11: the device matrix. The section leads with clock pinning
because the first matrix was run without it and had to be thrown away - monolith's OWN arm
moved 4.43 -> 7.07 ms/frame between two runs, and the SLOWER run was the COOLER one: the
governor had dropped policy0 from 2745 to 748 MHz. It records pin_redmi.sh's write order and
read-back verification, and why pin_device.sh refusing to guess this machine's serial is
correct rather than a gap. Conclusion: at both pinned tiers monolith and run-ahead each hit
the 120 Hz panel ceiling and only lockstep cannot reach it.
- ROADMAP.md's P5e row and status line now say twelve packages landed, run-ahead armed.
The phase is NOT closed: which arm is faster ABOVE the panel ceiling is unanswered (this
device cannot hold a higher clock), and BRIEF-P5E section 3's three negative controls and
section 5's per-package red-once reruns have been reordered by ID-121 to after the lane goes
green, so they have not been executed yet.
- P5D-INPROC-PERFORMANCE.md: the round-2 diagnosis is corrected against the
symbolized call graph (the apply thread's mutex time was the idle poll, not
the backend pendingMutex), the four round-3 packages and the two race fixes are
described, the numbers table carries the two device caveats (monolith is
120 Hz vsync-capped in most runs; the kernel ignores thread affinity, so the
client thread runs on a mid core), and the lockstep-feasibility research says
why the deferred draw barrier waits for P3b/P4b and P11.
- MEASUREMENTS.md section 9: the per-arm table with per-thread CPU ms/frame as
the primary metric, the interleaved session and the wait/park counters.
- ROADMAP.md: the P5d row is the three-round shape; P11 gains the gPipeInputs
versioning item the research points at.
- CURRENT_STAGE_PROGRESS.md, README.md, ARCHITECTURE.md (env table: the
generate_mipmap exception to batched waits, the calibrated spin, the affinity
readback): head 1f8de61b, the gate numbers and ruling ID-79.
ROADMAP marks P5c closed at the triage head and P6 next; CURRENT_STAGE_PROGRESS carries
the current-head gate table (unit 2187/2187, integration-split 111/111, the census
classification with every newly-red entry evidenced by its Fatal name, the three named G1
resizes, the 0xDD audit, the per-frame rsp measurements, and the two items deliberately
not done locally: the Redmi retest and the 79-trace census); MEASUREMENTS gains section 8
with the per-gate evidence. CONTRACT-P5C takes the amendments ratified at gt's landing
and at the regression triage: per-level extent is retired at the sites rather than
guarded on the accessor, the strict CI lane is two-sided (unit green gate plus
expected-red scenario lane with the marker asserted), the drain points are two classes
(the EGL RPC returns beside EmitAndWait), and oversized writebacks are client-sliced at a
quarter of the event ring.
- README status, the ROADMAP P1 row with the measured site and accessor counts, and the
ARCHITECTURE correction from 293 to the 277 arrow sites the tree actually has
- MEASUREMENTS gains the P1 scale table, the two classes of finding the verify lane
produced (nine missing fill rows, three fields a backend moves inside its own verb) with
the push-on-mutation decision and its three hooks, and the acceptance numbers
- the sentence read as if 836 of the 92 immediate calls were RecordError; 836 is RecordError's share of all 926 calls, and most of the 92 immediate ones are RecordError
- README.md: what MGPipe is, the one-paragraph architecture, the P0 status line, the file and code map, and the commit range where the design competition and the three adversarial review rounds live
- ARCHITECTURE.md: the design as decided, one reason per decision - handles and generations, the 71-call catalogue by class with flags and cap bits, record and payload conventions, the tracker, texture subdata and dirty ownership, shader state and the P0.5 header extraction, the reverse channel, the backend strangler, the server side and the index host mirror, the transport as landed in MG_Remote, the persistent-map tiers as measured, roundtrips, present and threads, process and platform delivery, build shapes and purity gates, the five-part verification gate, and the knob tables marked landed versus planned
- ROADMAP.md: P0..P13 as one table of what lands, the gate and the dependency, the two tracks and milestones, the day-43 GO/NO-GO checklist with both exits, the re-baseline checkpoints, and the questions still open after P0
- MEASUREMENTS.md: spike A on both devices, the spike B tier matrix, the four-trace boundary-counter baselines on both backends, the desktop and corpus facts, and the harness traps with the exact commands
- every file:line kept is verified at 458ccde1 and the citation lint is clean; everything else cites a symbol plus a file
- dropped on purpose: the v1/v2 revision archaeology, rejected alternatives, per-subsystem day estimates, the Feat/CS-Delta-IPC reuse audit and the reviewer back-and-forth