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.
5.3 KiB
Addendum to PACKAGE-PREAMBLE-P5E.md for wave 3 (pa / mv / gl), 2026-09-18
The preamble is still correct except where this file overrides it. Read both.
1 The contract is landed, the draft is gone
CONTRACT-P5E-draft.md was landed by c0e as MobileGL/MG_Remote/CONTRACT-P5E.md in your tree.
Read the landed file, not the draft. INTEGRATOR-DECISIONS-P5E.md now runs to ID-110 and still
overrides both where they differ.
2 The gate has a sixth step, and it is not optional
bash ~/w7/notes/tools/p5e_gate.sh <slug> gpu # ctest -L integration-gpu, ~10 min at -j 4
integration-split entries ALL export MOBILEGL_TRANSPORT=inproc. The push build has a second
server arm (Transport == Monolith) selected at runtime, and until 2026-09-18 it had no gated
entry at all. A P5e package landed a null dereference on it under 2250 green unit entries and 113
green split entries: the game SIGSEGV'd at startup on the phone, and ctest -L integration-gpu on
the same head was 1157/1294 — 137 SEGFAULTs across 38 scenarios. See rulings ID-107 / ID-109 /
ID-110, fix commit 66621767, and docs/Disaggregated/CURRENT_STAGE_PROGRESS.md §2.5.
Run gpu before you report. 1294/1294 or explain.
3 The arm-selection rule, stated as a checklist
Ruling ID-81: handle arms are selected by MG_Config::Transport != Monolith; the push-monolith
build keeps its frontend arms token for token. Apply this to your own diff:
- "A handle was noted" is NOT the statement "the record arm is selected." They are different
claims and conflating them is exactly what cost the phase 137 scenarios. A seat that is safe only
because every writer of a note happens to be transport-gated is safe by a chain, not by its own
statement. State the transport test where you rely on it.
MGB_TEXTURE_RECORD_ARM_SELECTED()(MG_Backend/DirectGLES/Managers.cpp, ID-110) is the worked example. - Never infer the arm from a pointer being null. Decide with
MG_Config::Transport(or your family's selector, which must itself reduce to it) and pass the pointer as glue. - A null/missing record on the handle arm is a NAMED refusal, never a quiet fall-back to the
frontend.
RefuseNullFrontendTextureOffTheHandleArm(Managers.cpp) is the shape: it aborts withFatal{RoleViolation, "<name>"}and folds to a constant in the pull build so no call is added there. - If you pass a null object into a body that can still read one, you have written the fix1 bug. Either the body cannot reach a frontend read on that arm, or the refusal fires first.
4 Reading the strict lane properly
ctest does NOT carry MGLOG_F into its captured stdout, and the lane's entries write no log file,
so p5e_gate.sh <slug> strict can only give you a pass count. The pass count is not your
deliverable. Produce the per-entry marker census:
bash ~/w7/notes/p5e/strict_census2.sh ~/w7/p5e-<slug>
It re-runs each red entry alone against the test binary with MOBILEGL_LOG_FILE_PATH set and writes
~/w7/p5e-logs/strict-census/census2.tsv (marker TAB entry). Copy it to a per-slug path first if
you do not want to clobber the shared one. Report the table before and after your change.
One known blind spot in that script, which you must not repeat as a finding: it derives the
gtest filter from the last two dot-components of the ctest entry name, so entries with a third
segment (Ct., F1., SmallRing., PersistentMapArm., NamedBlit.) are re-run WITHOUT the extra
ENVIRONMENT their lane block sets. DirectGLES.Split.PersistentMapArm.PersistentCoherentMapScenario.TheMapLandsInTheArmItsLaneDeclares
is the one entry whose marker the census failed to capture for this reason. If your family touches
such an entry, take its marker under its own lane environment (ctest -V -N -R '^<entry>$' prints
the block's environment).
5 The baseline you are changing, measured
Head 7c6f6886. integration-split 116/116 green; under MOBILEGL_IPC_STRICT_ERRORS=1, 7 pass and
109 abort with:
| marker | entries | owner |
|---|---|---|
GetProgramForDraw@DrawArrays |
61 | pa |
GetFramebufferBindingSlot@ReadPixels |
21 | allowlist (readback) |
GetBoundVertexArray@DrawArrays |
9 | mv |
GetProgramForDispatch@DispatchCompute |
7 | pa |
GetTextureObject@CopyImageSubData |
6 | allowlist (retires P7) |
ValidateProgramName@ShaderStorageBlockBinding |
2 | allowlist (retires P9) |
GetTextureUnitObject@CopyTexImage2D |
2 | allowlist (CopyTex) |
| (census artifact, §4) | 1 | — |
Touch only your own rows. If your change removes or adds a row that is not yours, that is a finding for the report, not something to quietly absorb.
6 Device numbers, for context only — you do not run the device
Redmi, Minecraft 26.3-rc-3, ~849 draws/frame, GPU pinned, fan on, 30 s windows, interleaved:
| arm | fps p50 | client GL thread ms/frame | apply thread ms/frame |
|---|---|---|---|
| monolith ×3 | 267.9 / 195.1 / 254.2 | 3.898 / 4.781 / 4.694 | — |
| inproc ×2 | 166.4 / 143.5 | 6.098 / 6.958 | 5.305 / 5.653 |
The split arm's client thread costs about 1.5× the monolith arm's single thread, and the device stats line shows the client entering the doorbell wait about 916 times per frame against 849 draws. That is the lockstep your package is clearing the way to remove. It is context, not a target: your deliverable is the marker table and the gate.