Commit Graph
85 Commits
Author SHA1 Message Date
swung0x48 db62d674d0 [MG_Remote] (P6/lk): regroup RingControl by writer, and make the asserts that pin it falsifiable
The shared control page both roles map had a mixed-writer cache line:
{appliedSeq, submittedSeq, retiredSeq, completedFrameSerial, presentAckSerial}
sat together, but submittedSeq is producer-written (Ring.h's "THE FIVE
WATERMARKS") while the other four are consumer-written. That is why no
single-writer progress block could be aliased over it, and it is what this
package fixes: submittedSeq moves up beside cmdHead, the other thing the
producer writes, and the four that remain become a named LinkProgress member -
the struct c6 declared in ILink.h - so a data plane with no shared page can own
the same four values and every wait predicate in SessionRings.h keeps ONE
implementation at source level.

The two event flags deliberately stay in the doorbell group. Promoting them
would put the client's per-drain exchange(0) on eventRingFull (EventRing.h:200)
onto the line the client's own spin predicate reads 1e5-1e6 times a frame.

73 call sites rewritten, 15 in the library and 58 in tests. The rewrite is
receiver-scoped rather than field-scoped, because SplitRuntimePeek.h:77 has its
own struct with a field of the same name and SplitRuntimePeek.cpp:146 has both
on one line - a field-scoped rename would have silently broken the peek struct
that MagmaRunAheadScenario reads nine times.

THE ASSERTS ARE THE PART THAT NEEDED A SECOND PASS. The first revision shipped
four aggregate asserts and an adversarial review broke them by counterexample:
swapping retiredSeq and completedFrameSerial inside LinkProgress compiled
clean, passed all four, and passed the runtime layout test too - because
appliedSeq stays at offset 0. Three wire-visible offsets on a shared page were
pinned by nothing. Nothing else in the tree covers it either: the ABI
fingerprint takes no input from this page, and sizeof(RingControl) is 4096
whatever the members do, because alignas(4096) makes it so.

So the block is now per-field as CONTRACT-P6 §8.3 actually asked, plus a
standard-layout precondition, a tail bound that alignas cannot satisfy for us,
and the LinkEventFlags mirror. Each was run red: the field swap reds the
per-field asserts by name, a fifth member inside LinkProgress reds sizeof.

One of them cannot go red, and Ring.h now says so rather than implying
otherwise. A SMALL member inserted between Progress and serverEpoch lands at
offset 288, inside Progress's own line, and serverEpoch's alignas(64) absorbs
up to 32 bytes of it - so both line asserts still pass. The review's proposed
fix does not catch it either; I tried it. A gate nobody can falsify is worse
than no gate, so the limit is written down beside the asserts.

Also: RingTest.ControlPageLayoutIsTheSharedContract said nothing about the one
field this package moved, and now asserts the producer/consumer separation and
the event flags' exclusion itself rather than borrowing the claim from Ring.h.
Ring.h grew 30 lines, so the Ring.h:NN citations this package and c6 authored
are re-anchored to named blocks instead of line numbers. ARCHITECTURE.md §11.2
is updated for the new layout, and a factual error introduced in the previous
revision of that paragraph - submittedSeq annotated as releasing staging, which
retiredSeq does - is corrected.

Gates: pull 0 added / 0 removed, .text size 10822147 unchanged; split 0/0,
.text size 11681395 unchanged; unit suite 2326/2328 with the same two
pre-existing environmental failures as the baseline. The five edits behind
MGITEST_SPLIT_RUNTIME_PEEK were proven compiled by a red-once, not assumed.

Note on the .text claim: that is a SIZE, not the bytes - field offsets moved,
so immediates moved with them. And this page's layout IS observable across
binaries; what is unobservable is behaviour within one binary. The contract
already books the layout change deliberately, before spawn exists to care.
2026-09-21 09:23:48 -04:00
swung0x48 99649a9087 [MG_Remote] (P6/lk): the pre-flight itemisation, and a correction to c6's regroup design
CONTRACT-P6 §8.5 requires lk to publish its blast radius before it starts,
because an earlier draft's ratchet baseline was wrong by a factor of three and
a ratchet installed on the wrong number is red on its first CI run for reasons
that have nothing to do with a leak. The itemisation is mechanical, per file,
with the grep spelling stated: 250 seam-identifier references outside
Transport/ across 23 files (127 production, 123 test), and 44 member-access
sites that the regroup itself must rewrite, across 9 files - 17 of them in
MG_IntegrationTest, which is why lk builds with the integration tests ON.
The earlier build did not, and a missed edit there would have been green.

THE CORRECTION. Measuring that radius turned up a mistake in c6, landed two
commits ago. §8.3 and ILink.h both said lk should promote eventRingFull and
eventDropped into the watermark group and called the result single-writer.
eventRingFull has TWO writers: the server latches it with store(1) when
SEG_EVENT fills (EventRing.h:140) and the CLIENT clears it with an exchange(0)
on EVERY DRAIN (:200). Promoting it would put a per-drain read-modify-write by
the client on the same cache line as appliedSeq - which the server
release-stores every 64 records and the client's spin predicate reads 1e5-1e6
times a frame. That is new false sharing on the hottest line in the system,
and it would land in exactly the numbers this package's gate has to hold
constant. It is also the same class of error the earlier draft made with
submittedSeq, one field over.

So LinkProgress is now the four consumer-written watermarks and nothing else -
genuinely single-writer - and the two flags stay in the doorbell/generation
group, where mixed writers already live beside consumerParked/producerParked,
crossing the seam through a separate EventFlags() accessor on its own line.
The contract records the correction and its reasoning rather than quietly
restating the rule.

ALSO CORRECTED, from running the baseline suite: a6's link-experiment report
said the silently-empty build stamp had "no log and no gate to say so". That
is false. SessionHandshakeTest.cpp:140 asserts BuildStamp[0] != '\0' and :180
asserts that perturbing it changes the fingerprint, and BOTH GO RED in this
configuration right now - the worktree's .git holds a Windows path WSL's git
cannot resolve, so GIT_COMMIT_HASH_SHORT is "". The finding is therefore
stronger, not weaker: the gate that catches it already exists and is already
red. What is silent is the BUILD, and hs owes a named configure-time failure
rather than a new gate.

Baseline for lk, recorded: 3878 tests, two failures, both environmental and
understood - the fingerprint one above, and PipeCatalogue.FrontendNeverTakes-
AnApplierAddress, which walks up from cwd looking for MobileGL/MG_Impl and so
fails for any out-of-source build directory.

No behaviour change. The headers are still included by nothing.
2026-09-21 08:54:48 -04:00
swung0x48 b6293957e9 [MG_Remote] (P6/c6): the normative contract, and the data-plane seam declared but not wired
c6 is P6's contract package. It lands CONTRACT-P6.md plus two headers and
changes nothing: no caller moves, no byte moves on any wire.

THE CONTRACT supersedes P6-CONTRACT-DRAFT.md, which was written before its own
audit and is wrong in four places - the ABI row, "the latch is implemented
unchanged", "cp is transport only", and "a transport swap and nothing else".
a6 measured all four. It carries 21 rulings, of which the load-bearing one is
D1: the spawned child keeps Transport == Spawn and anti-recursion becomes a
separate Dial axis. ARCHITECTURE.md:488's literal instruction is amended,
because 226 live lines test `Transport != Monolith` to select the SERVER arm
and forcing Monolith flips 148 MG_Backend lines to frontend glue in the one
process that has no frontend.

That ruling can be stated normatively without changing behaviour because of
something a6 found and the contract exploits: TransportMode::Spawn is
UNREACHABLE at this head. ConfigLoader.cpp:344 is the only writer for the
string "spawn" and it writes Monolith. So every rule conditioned on
Transport == Spawn constrains a state no process can enter - and the same fact
is why ConfigLoader's named refusal cannot currently falsify itself, which is
sm's to fix and §9.5's arm-proof gate to catch.

THE SEAM. ITransport says in its own header that the hot path bypasses it;
below it there was no interface of any kind, because Ring.h, ReplySlot.h and
EventRing.h each take a raw void* plus a capacity. ILink.h is that missing
thing. It is setup-time, not hot-path: no method is called per record or inside
a spin predicate, because Doorbell::Wait's predicate runs 1e5-1e6 times a frame
reading watermarks straight off the page, and because the only lane that could
measure a virtual there has four times the dispersion of the effect - a gate
that can only print "within noise" is ID-124 one level down.

StreamLink.h is declared and implemented by nobody, and that is its job: ILink's
adequacy is a claim only a compile can settle. Writing it forced three methods
the brief did not name - ResolveSpan, FlushProgress, and the reply pair - so it
has already earned its keep. Delete one ILink method it declares and the build
goes red.

Deliberately NOT here, each for a reason the contract records: Watermark::Reached
is not redefined (SessionRings.h owns it); Ring.cpp's reserve/pop bodies are not
relocated into a header that would then need the logger; RingControl is not
regrouped, because that rename is 109 production sites with five behind a
non-Android #if, so it is lk's with a published itemisation; and the schema rows
are specified but left to hs, because deprecating SurfaceReply.defaultFb while
appending `result` changes CreateSurfaceReply's arity and the existing
six-argument call would silently bind its defaultFb 0 to result.

INERTNESS IS STRUCTURAL, not measured. Zero object dependency files reference
either header, zero build rules do, and nothing in the tree includes them.
Verified anyway on both flavours: pull and split each have 0 symbols added,
0 removed, and .text byte-identical (10822147 / 11681395). check_doc_citations
resolves all 61 citations.
2026-09-21 08:35:50 -04:00
swung0x48 c25a776090 [Docs] (Disaggregated): P6 a6 close-out, and the end-state review it was re-scoped from
a6 is the read-only audit P6-SPAWN-PLAN puts first. Seven static items, each
produced by one auditor and re-resolved line by line by one adversarial
verifier; 198 rows, 65 of them contested, and only the surviving ones carry
into the report. Item 5 - can the server link without MG_Impl - is the one
experiment, and it is answered.

The headline is that P6's transport half is smaller than the plan assumed
(fc and fs really did land) and its process/role half is much larger. Four
statements in P6-SPAWN-PLAN / P6-CONTRACT-DRAFT are false at this head:

  - `spawn` is not an observable state at all. ConfigLoader.cpp:339-346
    recognises it and then writes Transport = Monolith, so "the child was
    forced to monolith" and "the parent fell back" are the same value and
    that named refusal cannot falsify itself. Meanwhile 226 live lines test
    `Transport != Monolith` to select the SERVER arm, 148 of them in
    MG_Backend - so ARCHITECTURE.md:488's rule, implemented literally, flips
    the whole backend to frontend glue in the one process with no frontend.
  - `cp` is not transport-only: the InitCapabilities REQUEST has no wire form,
    and the codec refuses to encode the kind a live client still calls.
  - the device-lost latch is not implemented; it was booked as carriage.
  - the server cannot link without MG_Impl: 184 symbols, 177 of them real
    function calls, owed 101 to P7 / 14 to P3b-P4b / 60 shared / 6 to P6.
    There is also no module target to link, so the question could not have
    been answered by editing a link line.

Four more blockers the earlier review missed: the apply thread aborts in
ConsumerDoorbell before popping its first record (contract 3.9's ruling, which
lk should retire rather than amend); neither side of the control plane is
wired to a socket; the control-reply wait has no deadline, so a frozen peer
wedges the GL thread inside eglMakeCurrent; and SurfaceReply cannot carry a
MobileGLResult, so a client cannot tell a dead server from a legitimate
refusal.

The abort census makes dl's funnel a redesign rather than a rename: 92 sites,
30 distinct family words against a 7-value wire enum, and three unbounded
peer-driven allocations with no try/catch anywhere under MG_Remote.

P6-ENDSTATE-REVIEW.md is the re-examination of P6 against the gfxstream-shaped
end state that produced a6's three added items. a6 overturned four of its own
claims - the MG_State eight's shape, the abort count, m_windowHandle's
injection points and a predicate name collision - and those are corrected in
place rather than left to be rediscovered.

Docs only. No code, no gate, no behaviour.
2026-09-21 07:49:37 -04:00
swung0x48 f99fbf717e Merge remote-tracking branch 'origin/codex/fix-github-ci' into feat/disaggregated 2026-09-21 03:24:47 -04:00
swung0x48 eb2747c966 [Docs] (Disaggregated): record the CI/inproc close-out in the roadmap and stage status 2026-09-21 03:24:31 -04:00
swung0x48 e1bf36775f Merge remote-tracking branch 'origin/codex/fix-github-ci' into feat/disaggregated 2026-09-21 03:16:31 -04:00
swung0x48 0689fc9637 [Docs] (Disaggregated): the P5b review row keeps its historical 30 s and points at the current 120 s
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.
2026-09-21 03:15:21 -04:00
swung0x48 68b55ea371 [MG_Remote] (Disaggregated): the inproc barrier watchdog is a deadlock detector, so its budget leaves room for a leg
lavapipe/llvmpipe stall the apply thread for tens of seconds inside texture-handle
registration (vkCreateImageView -> lvp_CreateImageView -> llvmpipe_register_texture,
a JIT/futex path): one measured stall is 29.3-39.7 s and a whole inproc retrace run
accumulates ~44.6 s of it. At a 30 s bound those runs red intermittently on
Fatal{BarrierTimeout, "ResourceRespecify"} while the same stall under monolith is
merely slow - monolith has no cap on that path. Since the monolith shares the stall,
the bound, not the workload, was the defect.

kBarrierTimeoutMs becomes 120000, >3x the largest observed stall, which still leaves
the watchdog job: a record that is never retired still terminates the run and the
Fatal still names the seq. The comment now says deadlock detector rather than
performance gate and carries the measurement.

The fake-clock case that pins the budget moves with it: FenceWaitBudgetHonorsGl-
TimeoutAndFiniteTransportChunks expected 30000/30001/90000 and now expects
120000/120001/180000, so the assertion still pins the value instead of drifting.
Texts that wrote the old number down are updated: ARCHITECTURE 17.1, CONTRACT-P5E
2.4 (which now names kBarrierTimeoutMs rather than the literal), and the two
bring-up comments in PipeRoute.h / WireTables.cpp that dated a self-wait to
"thirty seconds".
2026-09-21 02:54:47 -04:00
swung0x48 17bbb4482c [Build] (Android): keep the embedded-FCL arm64 pin beside the disaggregated wiring 2026-09-21 02:49:39 -04:00
swung0x48 b16531c3a8 [Docs] record the landed stage chunking in the R-10 texts
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.
2026-09-20 19:39:56 -04:00
swung0x48 6f0248048d [Docs] add CI/inproc continuation handoff (2026-09-20) 2026-09-20 18:16:45 -04:00
swung0x48 30b1bf25a4 [Docs] close Magma RD32 cache regression with host and device evidence 2026-09-20 09:34:47 -04:00
swung0x48 21d8c456e7 [Docs] (Disaggregated): record same-build RD32 four-way FCL performance 2026-09-20 08:02:44 -04:00
swung0x48 dbb70307c0 [Docs] identify SPIRV-Cross generated version metadata 2026-09-20 05:48:48 -04:00
swung0x48 06e12f933a [Docs] (Disaggregated): distinguish symbol gate from build metadata byte differences 2026-09-20 05:47:40 -04:00
swung0x48 bf762e44a4 [Docs] (Disaggregated): close Magma run-ahead with host gates and paired Redmi FCL evidence 2026-09-20 05:41:41 -04:00
swung0x48 0e4afb624f [Docs] (Disaggregated): record repaired Magma gameplay and rotated window verification 2026-09-20 03:38:44 -04:00
swung0x48 9e7cdefaf2 [Docs] (Disaggregated): record Redmi FCL gameplay results and Magma split P7 refusal 2026-09-20 02:09:38 -04:00
swung0x48 1e7de1edd6 [docs, tools] (Disaggregated): P5f close - record final host device and review evidence 2026-09-20 01:35:14 -04:00
swung0x48 de78f47df5 Merge commit '4cc4504b' into codex/p5f-final 2026-09-20 00:15:05 -04:00
swung0x48 cf75211242 Merge commit '863fd1f5' into codex/p5f-final 2026-09-20 00:15:04 -04:00
swung0x48 4cc4504bbe [Docs] (Disaggregated): P5f fv - record reverse-channel ownership and verified gates 2026-09-20 00:08:38 -04:00
swung0x48 863fd1f5ce [Docs] (Disaggregated): P5f fs - record default XFB native-identity red-once 2026-09-20 00:05:15 -04:00
swung0x48 5f7d5bf731 [Docs] (Disaggregated): P5f fs - report epoch isolation and seven red-once controls 2026-09-20 00:01:26 -04:00
swung0x48 bf5ad4704b docs: record P5f fr registry audit and guard red-once 2026-09-19 23:49:51 -04:00
swung0x48 4667e13be6 [Docs, CI] (Disaggregated): P5f wave 2 - record green integration and remaining phase gates 2026-09-19 23:26:46 -04:00
swung0x48 1b5198870f Merge commit '3cc397d3' into codex/p5f-wave2 2026-09-19 23:21:21 -04:00
swung0x48 3cc397d360 [Docs, MG_IntegrationTest] (Disaggregated): P5f fm - record verified gates and package boundaries 2026-09-19 23:17:10 -04:00
swung0x48 78ec3b6774 Merge commit '552ade5b' into codex/p5f-wave2
# Conflicts:
#	MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt
#	MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt
2026-09-19 23:11:41 -04:00
swung0x48 6c3662be9b [Docs] (Disaggregated): P5f fm - document implementation and verified gate checkpoint 2026-09-19 23:03:24 -04:00
swung0x48 eb882ebf18 Merge commit '203f59cc' into codex/p5f-wave2 2026-09-19 22:34:51 -04:00
swung0x48 203f59ccad [Docs, MG_Backend] (Disaggregated): P5f fe - report carrier retirement and verified gates 2026-09-19 22:20:57 -04:00
swung0x48 8a6e124f12 Merge commit 'b5c60d5a015c68348725ae93297b13a8fff27723' into codex/p5f-wave2 2026-09-19 22:10:26 -04:00
swung0x48 b5c60d5a01 [MG_Remote, MG_Test, Docs] (Disaggregated): fix P5f fc wire replies and control-frame guards 2026-09-19 22:03:55 -04:00
swung0x48 fa1e1cc427 Merge commit 'd5ed139cc50f75a6e7f20eb0cff4a36c7e8b46be' into codex/p5f-wave2 2026-09-19 21:52:55 -04:00
swung0x48 7d8c8b04b1 [Docs] (Disaggregated): P5f handoff - where the interrupted session stopped, and how to resume 2026-09-19 21:44:30 -04:00
swung0x48 d5ed139cc5 [MG_Remote, MG_Test] (Disaggregated): P5f fc - the EGL control plane crosses as value frames, not function pointers
The twelve Server* forwarders' one-slot mailbox carried a raw function
pointer plus a void* to a stack-local Args struct (P5c audit row G4);
neither means anything across a process boundary. The slot now carries
one SurfaceControlFrame by value: kind is the op, scalars are the
arguments, and ok/eglMajor/eglMinor come back in the same value. The
blocking handshake, the shadow, the doorbell order and the C2 exit
block are unchanged.

- protocol.fbs: append-only - SurfaceOpKind gains SetSwapInterval=8,
  ReleaseResources=9, SetWindowHandle=10; WindowKind gains MetalLayer=6;
  SurfaceOp gains readSurface and context (the MakeCurrent four-tuple).
  protocol_generated.h regenerated with the pinned flatc.
- Server/SurfaceControlFrame.{h,cpp}: the frame (trivially copyable by
  static_assert) and the op enum - ten wire kinds plus four inproc-only
  kinds (InitCapabilities is answered by CapsSnapshot on the wire,
  SwapBuffers/InitWindowSurface have no production caller - f0-egl F8,
  kept for the inproc test lane).
- Protocol/SurfaceOpCodec.{h,cpp}: the frame <-> SurfaceOp/SurfaceReply
  codec, the explicit WindowBackend <-> WindowKind table, and the wire
  entry ServerApplyWireSurfaceOp whose refusals are named:
  Fatal{UnmigratedSurface, "AndroidNativeWindow@P12"} and
  Fatal{ProtocolCorruption, "SurfaceOp"}.
- ServerLoop: RunOnApplyThread(fn, void*) -> RunSurfaceControlFrame(frame)
  plus the dispatch ApplySurfaceControlFrame; the old per-forwarder Args
  bodies moved into the dispatch unchanged (C7/ID-54, N-3, R-12 included).
  RunProbeOnApplyThreadForTesting is the test seam through the same
  channel (the hook lives in a member, never in the slot).
- Tests: RemoteClientTest/ServerLoopTest migrated to the probe seam; new
  SurfaceControlFrameTest suite (mapping tables, wire round-trips, the
  two named refusals) plus ServerLoopTest's AVoidForwarderCrossesAsOne-
  DispatchedFrame (the frame channel's red-once handle) and
  ServerLoopEglTest's TheInitializeDisplayReplyArrivesThroughTheFrame.
2026-09-19 20:30:21 -04:00
swung0x48 418c765c58 [Docs, MG_IntegrationTest] (Disaggregated): P5f f1 report - the census ratchet's first version (6 pairs), CONTRACT-P5E section 3.2 marked landed, and the gate numbers 2026-09-19 10:39:40 -04:00
swung0x48 e75e00cb7a [Docs] (Disaggregated): P5f f0 - the eight-way read-only census reports
Statics, reverse channel, Magma cross-role sites, EGL control plane,
registry rekey, build/gate mechanics, the 15 BARRIER_PULLED fields'
carrier rulings, and the dual-block mechanism's design recon.
2026-09-19 09:08:56 -04:00
swung0x48 8b68b92c76 [Docs] (Disaggregated): P5f section 1 - the pointer fact, and one draw's full lifecycle through gPipeInputs
The section explained what gPipeInputs IS and what breaks under spawn, but not the one fact
that decides the shape of the fix, and not how a call actually reaches the driver through it.
Both are now in the document rather than only in the conversation that found them.

- THE POINTER FACT, now stated first because everything else follows from it: several
  BARRIER_PULLED fields do not hold values, they hold pointers into client-owned objects.
  PipeFill.cpp:274 fills m_textureUnitBase with `&ctx.GetTextureUnitObject(0)` - a raw base
  into the frontend's array - and :245 fills m_programForDraw with a SharedPtr<ProgramObject>.
  PipeInputs.h:669 dereferences it as `m_textureUnitBase[unit]`. PipeFill.cpp:2591-2592 says
  the same of the shader-buffer field: "four raw bases into the frontend's binding-point
  table".
  The consequence gets its own paragraph in 1.4, because it is the step most likely to be got
  wrong: this CANNOT be fixed by moving the block into shared memory. Mapping the same pages
  into both processes does not make a client heap address meaningful in the server, and a
  SharedPtr's refcount does not cross at all. The 15 fields each need a different SOURCE, not
  a shared location.

- NEW 1.2, one draw end to end: glBindTexture marks a tracker bit and sends nothing, because
  push only happens at a verb boundary; glDrawArrays enters MGPipeValidateForVerb
  (PipeFill.cpp:3347) whose four steps are barrier decision, dirty walk, emission (records,
  carrying handles), and the residual fill - which is where BARRIER_PULLED happens; the record
  goes on SEG_CMD; the apply thread dispatches to the backend; PrepareForDraw reads either a
  record-carried handle against the server's own tables, or gPipeInputs; then the real driver
  call. Plus the field lifetime: stamped with verb N's serial, read during verb N's apply,
  Fatal{UnmigratedPipeInput} if read at verb N+1 without a refill - one verb, never longer.

- The same example carries the Espryt/Magma split concretely: the texture binding reaches
  Espryt as a record with a handle (P5e retired GetTextureUnitObject for it) and reaches Magma
  through gPipeInputs into the client's array. That is FieldOwnership.def's own
  "P5e (Espryt unbarriered), P7 (Magma)" made legible, and it is why section 2.1's table has
  eight rows in that state.

Sections renumbered 1.1-1.4. Doc citation lint: 129 citations, 0 problems.
2026-09-19 06:02:28 -04:00
swung0x48 465656843b [Docs] (Disaggregated): P5f - every piece of state on the wire, and P6 is blocked until it is
P6's premise is that it is "only a transport swap". That premise was bought by P5c and has
expired. Measured on this head, the two roles still share state directly, so a spawned server
would have no source for it. P5f is the phase that makes the premise true, and P6 does not
start before it.

WHAT THE MEASUREMENT FOUND

- FieldOwnership.def still carries 21 BARRIER_PULLED rows over 15 distinct fields. That class
  is defined as "the server answers by reading a value the client's residual fill left in THE
  SINGLE SHARED gPipeInputs while the verb barrier holds both threads apart". Eight of those
  fields were retired by P5e for Espryt's unbarriered path only - Magma has not moved at all.
  The strict lane measures 10 live <field>@<verb> pairs today.
- gPipeInputs (MG_Backend/MGPipe/PipeInputs.h:835) is a plain process global,
  `inline PipeInputs& gPipeInputs = *new PipeInputs();` - not a shm segment. The client fills
  it (MG_Impl/Pipe/PipeFill.cpp), the backend reads it. Under spawn the two processes have
  DIFFERENT objects, the 15 BARRIER_PULLED fields have no filler in the server's copy, and
  every read is Fatal{UnmigratedPipeInput}. MG_Impl is not in the server, so "let the server
  fill it" is not available either. The header says this itself at :279 and :902.
- The twelve Server* EGL forwarders do not use the wire at all: they post a function pointer
  plus a stack-local void* through a one-slot mailbox (ServerLoop.cpp:533-568, :645). The
  SurfaceOp / SurfaceReply / WindowKind schema is defined in protocol.fbs and unused.
- Process-level statics with per-context semantics (g_syncedRenderStateParameters,
  ScopedDefaultUnpackState::s_synced) - and the full list is unknown, because this defect class
  is structurally invisible to inproc.
- MGPipeFrontendKeyedRegistryScope is still live at ~12 sites in DirectGLES.cpp, carrying
  P3b/P4b's debt: the registry is still keyed by frontend address / lifetime id.

THE SCOPE RULE

P5f takes the cross-process-blocking subset of P3b/P4b/P7/P8/P9 and nothing else. One test:
does it still hold in another address space? No -> P5f. Yes but slow or ugly -> it stays with
the phase that owns it. So Magma's nine fields and its direct writes into client storage come
here; content-addressed CSO, internal shader baking and the fixed-width rewrite stay P7's. The
reverse channel's asynchrony and ordering stay P9's; only the parts that touch client memory
move. Class C is NOT P5f's - a named refusal behaves identically in two processes.

THE CENTRAL MECHANISM: A TWO-BLOCK REHEARSAL

P5c set the same rule (rule E) and checked it by AUDIT - a human reading 59 lines. P5f checks
it by MACHINE: give each role its own PipeInputs under inproc, and its own copy of every
per-context static, behind a knob. Anything that depends on the two being the same object
becomes Fatal{UnmigratedPipeInput, "<Field>@<Verb>"} with the field and verb named.

That rehearses the process split's SEMANTICS without a second process. It matters because
today "the server read the value the client filled" and "the server read the value a record
carried" are indistinguishable at runtime - which is exactly what P6 would discover on day
one. The package order puts the mechanism (f1) BEFORE the migrations deliberately: let the red
appear first and let it be the worklist, rather than having each package declare itself done
and then looking for a criterion.

Exit gate: the two-block lane green; strict-expected-markers.txt EMPTY with its two-sided
ratchet still armed; zero BARRIER_PULLED rows; rsp reading zero per frame; Magma judged by the
same criteria as Espryt. The negative control is that turning the two-block knob OFF must move
at least one named case from green to red - otherwise the mechanism proves nothing, which is
ID-122's lesson.

ALSO IN THIS COMMIT

- ROADMAP gains the P5f row and the IPC track becomes P5e -> P5f -> P6; P6's status cell
  becomes "blocked on P5f".
- README and CURRENT_STAGE_PROGRESS gain the same, plus the file-map entry.
- Both P6 documents gain a blocked banner naming what is not true yet.

Doc citation lint: 124 citations across docs/Disaggregated/*.md, 0 problems.
2026-09-19 05:32:38 -04:00
swung0x48 7f1d2134c3 [Docs] (Disaggregated): P6 contract draft rewritten in concise wording, 269 -> 181 lines, same rows 2026-09-19 04:39:12 -04:00
swung0x48 e125b75655 [Docs] (Disaggregated): P6 plan rewritten in concise wording, 282 -> 124 lines, same content 2026-09-18 23:40:26 -04:00
swung0x48 aba329cc53 [Docs] (Disaggregated): P6's plan and contract draft; the working notes move from a local directory into the repository
Two things land together: the plan and contract draft P6 needs before it starts, and the
working notes that until now existed on exactly one machine.

1. P6's plan and contract draft (neither started; not one line of code)

     docs/Disaggregated/P6-SPAWN-PLAN.md      the package plan
     docs/Disaggregated/P6-CONTRACT-DRAFT.md  the contract draft (moves to
                                              MG_Remote/CONTRACT-P6.md when c6 lands)

   The three findings that mattered, all written into the documents:

   a. P6 is SMALLER than its roadmap cell suggests. The transport primitives are already in
      the tree and already tested: SocketDoorbell including hangup detection and the two
      lost-wakeup fences (Doorbell.cpp:211-360, four cases in FdPassingTest), SCM_RIGHTS fd
      passing, ShmSegment::Adopt, and the SurfaceOp / WindowKind schema already defined in
      protocol.fbs. inproc uses a real second shm mapping rather than an alias, and
      SessionRings.h:22-28 says that was done so today is not the first time it runs. P6 is
      assembly plus a control plane plus a process, not "write a socket transport".

   b. The evidence for "P6 is only a transport swap" has EXPIRED. It was bought by P5c, whose
      audit found 59 direct memory accesses in a tree that already looked clean. P5e changed
      the tree again. So the first package, a6, is a read-only audit that writes no code, and
      every contract row that depends on it is marked PENDING a6 rather than decided in
      advance.

   c. P6 does not get a game running on the phone. ANativeWindow* is meaningless in the child,
      and protocol.fbs:186-196's own comment says Android transfers the window out of band -
      that band is P12. P6 lands pbuffer / surfaceless / offscreen, which is what HeadlessGL,
      the split-equivalent lane and trace replay need, and which is exactly what P6's own exit
      gate measures. A real window reaching the server is a named refusal. This gets its own
      section because "P6 is done" would otherwise read as "the game runs now".

   The contract adds rule G: a role may not name a process-local handle. It extends P5c's
   rule E (a role may not name the other role's memory) from memory to identity - EGLDisplay /
   EGLSurface / EGLContext / ANativeWindow* / fd number / pid / mapped address either cross as
   a token this contract defines or they do not cross, and a descriptor crosses only through
   SCM_RIGHTS, where the kernel renumbers it.

   What P5e left for P6 gets its own section. The latch semantics are already designed
   (ARCHITECTURE.md:504) and are copied unchanged. What is new is that after run-ahead the
   client no longer waits while the peer process can DIE: the client holds records that were
   published and will never be applied, and Android's freezer suspends the child without
   closing anything. So slow and dead must be distinguishable WITHOUT a timeout threshold.
   That is P6's only new design; everything else is carriage. It shares a predicate with E1's
   outstanding re-specification (ID-122), and the two should be designed together.

   Two gates carried over from P5e's lessons: G2 requires integration-spawn's test-name set to
   match integration-split's name for name, because a differing set means the lane is quietly
   running less; and the arm-proof gate requires every entry to record the child pid and
   transport=spawn in its OWN private log - ID-124 established that this kind of false green
   really happens, and a spawn lane that actually ran monolith would pass every other gate.
   S2's shape must be settled before it is written, and the document says why: E1 is the
   cautionary case.

2. The working notes move into the repository (docs/Disaggregated/notes/, 415 .md files)

   ROADMAP / CURRENT_STAGE_PROGRESS / MEASUREMENTS cite ruling numbers, audit line numbers and
   the provenance of gate figures from ~/w7/notes/, a directory that exists on one machine and
   is under no version control. A record cited hundreds of times that lives only locally is not
   a record, so it moves in whole, with its directory structure intact.

   Only .md moves. Scripts, run logs, census and gate result JSON, patches, APKs and nm dumps
   stay where they are: they are evidence DATA rather than documents - large, regenerated per
   run, and mostly carrying absolute paths and device serials that mean something only on the
   machine that produced them. The mapping and this split are written in
   docs/Disaggregated/notes/README.md.

   Two exceptions are recorded there too: P6's BRIEF has been promoted to a first-class
   document and keeps no copy under notes/, so the two cannot drift apart; and recovered/ holds
   material salvaged from failed workflow runs, which may duplicate the like-named phase
   directories and is kept because a few of those were the only complete version at the time.

   CI's citation lint takes docs/Disaggregated/*.md only (non-recursive), and notes/ is
   deliberately outside it: those notes' file:line citations were written against their own
   code heads, and resolving them against today's HEAD would fail wholesale - which is not an
   error, it is their correct state as a historical record. To check one, use the revision it
   declares (--rev).

3. Inconsistencies fixed in passing

   - README.md's status line still said P5e was in progress while the stage table already said
     closed.
   - CURRENT_STAGE_PROGRESS's next-step item 1 still listed the exit gate as pending, though
     (a) and (b) were run in the previous commit; rewritten to closed plus the one item that
     actually remains (E1).
   - ROADMAP's P6 row still had the pre-phase plan in three of its cells; it now carries the
     package order, the scope boundary and the two new gates.

Checked with check_doc_citations.py: 125 citations across docs/Disaggregated/*.md, 0 problems.
2026-09-18 23:31:39 -04:00
swung0x48 edd0fef5cd [Docs] (Disaggregated): P5e closed - the exit gate run item by item, with one named outstanding item
It said "in progress" because BRIEF-P5E section 3's exit gate had never been RUN, not because
the work was unfinished. It has now been run, item by item, recorded in ROADMAP's P5e
exit-gate cell:

- item 1: unit 2256/2256 under both strict arms.
- item 2/3: integration-split 179/179; the per-entry census has zero Fatal{ and exactly eight
  Admitted{ rows.
- item 4(a): RUN_AHEAD=0 as the PAIRED control, 179/179 green - same server code, same
  records, the only difference is whether the client waits.
- item 4(b): VERB_BARRIER=0 goes red (62/179 in the lane).
- item 4(c): Magma logs, verbatim, "run-ahead requested, server does not publish
  kCapRunAheadApply - running lockstep", and the Espryt control logs "run-ahead ARMED". Both
  taken from each entry's own private log.
- item 5: red-once rerun on the integrated tree, in the form ID-121 reordered it into -
  reverting pa's arm yields GetProgramForDraw@DrawArrays, reverting mv's yields
  GetBoundVertexArray@DrawArrays, EXACTLY ONE NAMED PAIR EACH, and each restores green.
- item 7: MOBILEGL_IPC_AUDIT=1, 179/179.
- item 6's G1 remains CI's to assert (ID-123).

The only item that does not pass is E1's control script itself (ID-122), and the diagnosis is
sharper than before: with its two masking defects fixed it is still red, and the
Fatal{BarrierViolation} it demands appears ZERO times in the whole run - at BATCH_WAITS's
default of 1 that overlap does not happen, and with run-ahead armed VERB_BARRIER=0 also
disarms run-ahead (ID-114), so the arm it produces is neither lockstep nor run-ahead. E1 needs
its falsification target redefined, not its threshold adjusted.

So the status is "closed, with one named outstanding item" rather than an unqualified close:
hiding a control that never passed behind the word "closed" is precisely the class of mistake
this phase kept recording.
2026-09-18 21:54:11 -04:00
swung0x48 0d747da470 [Docs] (Disaggregated): ROADMAP's P5e row had only its status cell updated; the other three were still the pre-phase plan
The previous commit changed the status cell to "twelve packages landed, run-ahead armed" and
left the scope and exit-gate cells saying eight packages, 19 rulings, "the integration commit
flips kMGPipeP5eRunAheadReady", and an exit gate drafted BEFORE the phase started - several of
whose items later proved unexecutable as written. All three cells are rewritten:

- Scope cell: twelve packages in three waves (including fix1 / pa / mv / gl / ra2), rulings
  ID-80..136, both constants flipped. It also names the largest migration (the per-draw
  GetProgramForDraw) as having changed NO wire structure at all - the record already carried
  the per-link ProgramArchive, and what was missing was a reader that still asked the frontend
  object. Links to P5E-RUNAHEAD.md.
- Exit-gate cell: rewritten into what was actually measured (unit 2256, integration-split
  179/179, integration-gpu 1357/1357, three flavours with each arm asserting its EFFECTIVE
  compile define, strict hard green with zero Fatal, the device at VD32 level with monolith and
  +69% p50 against lockstep), plus what has not been executed (the three negative controls, the
  per-package red-once reruns, E1's control failing its own self-check, G1 still asserted by
  CI), plus the criteria that are now VOID (rsp = 0 is empty by construction, rewritten by
  ID-119; VERB_BARRIER=0 may not serve as the paired control, ID-114, because it also disarms
  run-ahead).
- Debt table, rsp row: P5e rewrote what that number means. It now reads as the hard-green lane
  plus eight Admitted rows, and says why the original criterion was empty - CountBarrierPull's
  unbarriered arm sits before ++g_residualPulls and is [[noreturn]].
- Debt table, unmigrated row: client vertex arrays are a named refusal under run-ahead
  (ID-82) and have their own expected-red lane, integration-clientarrays-split (ID-134);
  staging stays P8's.
- Open question 18 added: the two sides of split are unbalanced (at VD32, apply 11-12 ms
  against client 15.7 ms, with the client ~94% saturated). Moving work from client to apply
  lowers the bottleneck directly - meaningless under lockstep, where the two add serially, and
  a lever only because of run-ahead. It needs a fresh profile first: with the wait gone, every
  tail item's denominator has changed.
- The header status line gains the P5E-RUNAHEAD.md link.

Doc citation check: seven files under docs/Disaggregated, 58 citations, 0 problems.
2026-09-18 21:35:38 -04:00
swung0x48 be345d711c [Docs] (Disaggregated): P5e progress, in full - the landed-content table, open items, ruling index and phase report
- New P5E-RUNAHEAD.md: P5e's phase report, a sibling of P5D-INPROC-PERFORMANCE.md. It carries
  the twelve-package list, the three defect classes and the lesson each one cost (a runtime arm
  no gate covered / build flavours no gate covered, where the FIRST gate added for them was
  itself a false green / a guard that could never fire), the profile and both device tiers, the
  unfinished items, and the next opportunity run-ahead unlocked.
- CURRENT_STAGE_PROGRESS.md: the stage table's P5e row becomes twelve packages + run-ahead
  armed + ID-80..136; new section 2.7, the P5e landed-content table, in the same shape as
  P5c's and P5b's; section 2.5 retitled as historical; two sections both numbered 4 fixed; five
  P5e entries added to the open items; next-step item 1 rewritten from c0e to the evidence the
  close still lacks; the ruling index extended from ID-79 to the load-bearing entries of
  ID-136.
- ROADMAP.md: the P11 row gains P5e's correction - retiring the draw lockstep did NOT need
  gPipeInputs versioning, because that race was in the metadata scalars. The row still holds
  for the general case, and contract section 3.2's per-role stamp storage is still unlanded.
- README.md: the status line gains P5e and run-ahead, and "next is P6" becomes "P6 comes after
  P5e".

Doc citation check: 3 problems, all pre-existing entries in CONTRACT-P5.md, none related to
this change.
2026-09-18 21:30:29 -04:00
swung0x48 45664725f8 [Docs] (Disaggregated): P5e - the render-distance 32 comparison, where inproc draws level with monolith
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.
2026-09-18 16:00:46 -04:00
swung0x48 8943af73e6 [Docs] (Disaggregated): P5e wave 3 - the strict lane goes hard green, run-ahead is armed, the device matrix is written back
- 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.
2026-09-18 14:57:36 -04:00