Files
MobileGL/docs/Disaggregated/notes/p5e/BRIEF-P5E.md
T
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

28 KiB
Raw Blame History

BRIEF-P5E — the package plan for retiring the lockstep on the Espryt draw path

Contract: CONTRACT-P5E-draft.md (same directory; becomes MG_Remote/CONTRACT-P5E.md at c0e). Base feat/disaggregated @ 2fde7034. Paths under MobileGL/. Eight packages; seven of them can be implemented in parallel worktrees once c0e and id have landed. Every line-range below is at the base head and names the ONLY region the package may edit in a shared file; a package that needs a line outside its range asks the integrator, who either widens the range or lands the edit in c0e.

0 The shape of the landing, in one paragraph

Nothing on Minecraft's steady draw path can go fire-and-forget until EVERY per-draw family reads records instead of the frontend: PrepareForDraw touches the VAO (vi), the program (pg), the unit textures and samplers (tx2), the bound FBO and its attachment lists (fb) and the UBO points (sb) on every draw, hit or miss. So the smallest first landing that lets the client stop waiting at draws is c0e + id + {vi, sb, pg, tx2, fb} + ra, with the caps bit flipped in ONE integration commit after all of them. What makes it small is what is left barriered and trailing (§5): XFB, client vertex arrays, CopyTex / GetTexImage / readbacks, set_storage_block_binding, the texture half of resource_subdata, set_texture_params' reply — none of which is on the steady path. The contract's barriered predicate (§2.1) is what lets those keep P5C's semantics while draws run ahead, and the unconditional detector (§3.3) is what makes a missed site a named abort instead of a wrong picture.

1 Dependency order

c0e  (the contract, the wire rows, the knobs; inert)          -- lands FIRST, no behaviour change
 └─ id  (registry rekey, by-handle resolvers, the guard)      -- lands SECOND, lockstep unchanged
     ├─ vi   (VAO / VBO / IBO)                       ┐
     ├─ sb   (set_shader_buffers, binding points)    │  parallel worktrees, any order,
     ├─ pg   (program twin, archive, bindings)       │  each rebased on id
     ├─ tx2  (textures, samplers, unit windows)      │
     └─ fb   (FBO, attachments, images, blit)        ┘
 └─ ra  (the wait rule, credit, gPipeInputs, flow control)  -- parallel from day one on c0e;
                                                                its LAST commit is the integration
                                                                commit: kMGPipeP5eRunAheadReady=true

Cross-package couplings that must be known before branching: pg's BlockBindings tail is what sb's UBO loop indexes with (DirectGLES.cpp:5463 vs :5464); tx2's SyncMipmapsToBackendByHandle / SyncTextureToBackendByHandle are what fb's attachment sync and image sync call; fb's reverse index is what tx2's ScopedDetachedTextureFramebufferAttachments needs; sb's deletion of the backend GPU-write marks and fb's deletion of MarkWritableImageBufferTexturesGpuWritten are one edit split across two files. Each such seam is a declared signature in c0e (a header declaration with a body that asserts "not landed") so both sides compile from day one.

2 Packages

c0e — the contract and the wire (integrator; lands first)

Scope. MG_Remote/CONTRACT-P5E.md; MG_Pipe/PipeCalls.def (the WaitClass column on every row, SetProgramBindings opcode 80 appended); scripts/gen_pipe_wire.py (or whichever generator emits PipeWire.inc) + regenerated MG_Pipe/generated/*.inc (MGPipeWaitClassFor, the new op's thunk / table slot / codec layout, route row null); MG_Pipe/MGPipeTypes.h (kCapRunAheadApply, MGPProgramBindings + tail PODs, MGPProgramDesc::LinkStatus, kDrawClientArrays, kMGPipeMaxBufferBindingPoints); MG_Pipe/MGPipeValueTypes.h (MGPipeImageAccess enum, encode/ decode); MG_Pipe/MGPipe.h (bit 13, default mask 0x3fff); MG_Impl/Pipe/SetHashSuppressor.h (four slots); MG_Impl/Pipe/Tracker.h:97-99, 204-209 (bits 15-17 → subsystem 13); Config.h IpcTable::RunAhead/PresentCredit; ConfigLoader.cpp:356-403; MG_Pipe/PipeApply.h declarations of every cross-package seam (MGPipeApplierCurrentRecordIsBarriered(), MGPipeBarriered(...), the sb/pg applier fields, the by-handle sync signatures) with asserting bodies; MG_Pipe/FieldOwnership.def re-annotations; MG_Pipe/Coverage.def rows; MG_Test/Pipe/PipeCatalogueTest.cpp (sizes, opcode 80, the null route pins); MG_Test/Pipe/TrackerTest.cpp:385-387 (bit 13); docs/Disaggregated/ ROADMAP.md row; MG_Backend/Init.cpp (kMGPipeP5eRunAheadReady = false, the DirectGLES arm that will publish bit 10). Red-once. The ABI fingerprint (SessionRings.h:498-524) changes with the new opcode and PipeCatalogueTest pins the new sizes; MagmaPipeIdentityTest pins no bit 10 on DirectVulkan. Quick gate. Unit green; integration-split green (nothing behaves differently); G1 0/0/0/0.

id — identity (S7's spine; lands second)

Scope. MG_Backend/DirectGLES/SlotTables.h (composite band m_band; ForEachLive re-typed as fn(MGPipeHandle, const BackendPtr&); the frontend-keyed members kept as monolith glue, each asserting !MGPipeApplierIsUnbarrieredApply() in split builds); Managers.h:309-640 (registry forwarding, ResolveVaoTwin(handle) / ResolveProgramTwin(handle) / ResolveTextureTwin(handle) declarations in the ResolveSamplerCsoTwin shape, AdoptTwinByHandle as the body); Managers.cpp:188-372 (teardown, OnFrontendStateObjectDestroyed — delete the NoSession hop :224-246 and the scope at :256; ReleaseTwinsForWireObjectDeath); MG_Impl/Pipe/SlotAllocator.{h,cpp} (the guard: exemption iff scope AND barriered record; MGPipeFrontendKeyedRegistryScope kept as a class until the family packages delete their sites; MGPipeReverseAnnouncementScope → MagmaP7AllocatorDebtScope, backend-kind-keyed); MG_Backend/DirectVulkan/VulkanRenderer.cpp:4554, 4634, 8986 + MG_Impl/Pipe/ResourceTracker.h:723 (the rename only); MG_Remote/Server/PipeApplier.cpp ApplyOne sets CurrentRecordBarriered from MGPipeBarriered (the one line ra also touches — id owns it, ra rebases); MG_Test/SanityTest.cpp:3499-3960, 4061, 4241, 4378, 4898 (cases moved to the legacy arm or rewritten by handle; new ACompositeHandleDoesNotGrowTheOrdinaryTable); MG_Test/Wire/RemoteClientTest.cpp:1866-1913 (...IsAllowed deleted; new ...FromTheApplyThreadIsFatalEvenAtTheOldDebtSites posting HandleOf through RunOnApplyThread with the barriered flag forced false by a test hook). Red-once. With the hook forcing "unbarriered", HandleOf from the apply thread aborts Fatal{RoleViolation, "MGPipeSlots"}; put the exemption back → the case is green → red by expectation. Quick gate. Unit + integration-split green (lockstep behaviour unchanged: every record is barriered until ra). New names (G2/G14): ResolveVaoTwin/ResolveProgramTwin/ResolveTextureTwin(MGPipeHandle), MagmaP7AllocatorDebtScope, MGPipeApplierCurrentRecordIsBarriered — all MOBILEGL_BUILD_DISAGGREGATED.

vi — VAO / vertex input / index (S1)

Scope. DirectGLES.cpp:699-925 (SyncVaoAttributeBuffersByHandle, the IBO arm), :1602-1719 (ResolveVaoTwin handle arm, SyncCurrentVAO, SyncCurrentVertexAttributeValues), :4770-4776 (the VAO lines of PrepareForDraw ONLY), :6447-6480 (restart helpers), :6718-6782 (DrawArrays/MultiDrawArrays arms: monolith-only); Managers.h:1204-1300 (ResolvedDrawBuffers: frontend leaves the split arm, iboSerial added), :1286-1290; Managers.cpp:4881-4892, :5160-5350 (SyncToBackendFromApplier — signature only), :5364-5468 (SyncClientSideAttributesForDrawArrays: monolith-only); MG_Remote/Client/EmitTables.cpp:484-525, 640-690 (kDrawClientArrays + the run-ahead refusal beside CLIENT_INDICES); MG_Test/Pipe/VertexInputEmitTest.cpp + a SanityTest.cpp sibling beside :4083. Trailing inside vi: ResolvedDrawBuffers deletion after a measurement. Red-once. (1) Revert ResolveVaoTwin to Find(vao.get()) → Fatal{RoleViolation, "MGPipeSlots"} on the first integration-split draw once ra flips the bit; before that, the id test hook. (2) Publish set_vertex_buffers for A,B, repoint the frontend VAO at C without re-emitting, sync → A,B ensured; the revert ensures C. (3) Same on set_index_buffer. (4) glDrawArrays with a client array under run-ahead → the named refusal; revert → red under integration-split-strict (DoublePrecisionScenario if it uses client arrays, else a new scenario). Quick gate. Unit + integration-split green; VertexAttribBindingScenario, VertexArrayEnableDisableScenario, PrimitiveRestartScenario, ResidentIndexScenario, MultiDrawScenario, DrawParametersScenario, HandleRecycleScenario, ObjectSubsystemControlScenario (the 0x0ff/0x17f A/B keeps its verdicts). New names: ResolveVaoTwinByHandle (or the id resolver), SyncVaoAttributeBuffersByHandle's new signature.

sb — buffer binding points (S5)

Scope. MG_State/GLState/BufferState/BufferState.h:69-75 (bind-point generations, push-only); MG_Impl/GLImpl/Buffer/GL_Buffer.cpp:1501-1520, 1624-1650 + SetNamedTransformFeedbackBinding (the bumps); MG_Impl/Pipe/Tracker.h:520-560, 630-640 (bits 15/16/17 shutters); new MG_Impl/Pipe/ShaderBufferEmit.h; MG_Impl/Pipe/PipeFill.cpp (SubsystemForEmitter :1989-2028 row + pairing asserts :2104-2126, the validate order :3225-3233, the reset list :3129-3143, the kMGPipeMaxBufferBindingPoints/84 pin); MG_Pipe/PipeApply.{h,cpp} (MGPipeApplySetShaderBuffers, the applier fields declared by c0e); MG_Pipe/PipeRoute.{h,cpp} + MG_Remote/Client/WireTables.cpp (route rows for op 38); MG_Remote/Server/PipeApplier.cpp (the one sink method OnSetShaderBuffers); DirectGLES.cpp:477-673 (SyncBufferBindingPointsByRecord, MarkShaderStorageBuffersGpuWritten deleted under transport, SyncAtomicCounterBuffers), :979-990 (call lines), :5464-5497 (the point half of the UBO loop; :5463 is pg's); scripts/gen_pipe_dirty_surface.py:87-90 + DirtySurface.def rows; MG_Test/Pipe/TrackerTest.cpp, new ShaderBufferEmitTest.cpp, PipeCatalogueTest.cpp:170-171, 242-243 (invert); MG_IntegrationTest A/B with bit 13 cleared. Red-once. (a) Revert the handle walk under a live wire → Fatal{UnmigratedPipeInput, "GetBufferBindingPoint@DrawVbo"} on SsboArrayDynamicIndexScenario / StorageBufferRegrowScenario / AtomicCounterScenario under strict. (b) TrackerTest: glBindBufferBase(UNIFORM,1,A); Update(); glBindBufferBase(UNIFORM,1,B); Update() sets bit 15 on the second update — red against Tracker.h:636 today. (c) GetBufferBindingPointCount's rsp non-zero before, 0 after, on AtomicCounterScenario. (d) Delete the backend marks and ProducerMarkCount(ShaderStorageBinding) stays non-zero while SsboArrayLengthScenario's readback sees the writes. Quick gate. Unit + integration-split + strict on the four SSBO/atomic scenarios and the three XFB ones; the bit-13-cleared A/B reproduces today's picture. New names: MGPipeApplySetShaderBuffers, MGPipeShaderBufferEmitterInstance, SyncBufferBindingPointsByRecord (file-local), BufferState::NoteBindPointChanged.

pg — programs (S4)

Scope. MG_Impl/Pipe/ProgramEmit.h (archive encode per link via ProgramArtifactsCodec into SEG_STAGE; EmitProgramBindings; LinkStatus; the failed-relink rule); MG_State/GLState/ProgramState/ ProgramArtifactsCodec.{h,cpp} (completeness: name :242's skipped member); MG_Pipe/PipeApply.{h,cpp} (MGPipeApplyCreateShaderState adopts the blobs into record-owned artefacts — the companion pointers go under a transport; MGPipeApplySetProgramBindings); MG_Pipe/PipeRoute.* + WireTables.cpp (op 80 route); PipeApplier.cpp (OnSetProgramBindings; trailing: set_storage_block_binding by ShaderCso); DirectGLES.cpp:4097-4110 (the stash), :4230-4318 (ResolveGlobalConstantsRecord(handle), MapUBO refusal), :4320-4510 (SyncCurrentProgram(handle) incl. the broadcast decline arm :4383-4404 → Fatal under transport), :4781 (PrepareForDraw's program line), :5317-5560 except :5464-5497 (sb) and :5561-5632 (tx2), :5670-5740 (GetCurrentBackendProgram), :6120-6130 (PrepareForCompute's program line), :10018-10042 (ShaderStorageBlockBinding: monolith body kept; trailing handle arm); Managers.h:2338-2541; Managers.cpp:10757-13080 (SyncToBackend(h, record), CacheResourceLocations, ReseedShaderStorageBlockBindings, the signature); MG_Test/Pipe/ProgramEmitTest.cpp (beside :251). Red-once. (1) Empty the applier's block-binding tail → UniformInitializerScenario / Glsl420DeclarationScenario wrong pixels + RefusedObjectCalls. (2) Leave the blob refs at Size 0 with the companions removed → Fatal{ProtocolCorruption} (PipeApply.cpp:2858-2871). (3) Delete the five scopes without re-keying → Fatal{RoleViolation, "MGPipeSlots"} (after ra). (4) Strict lane names GetProgramForDraw@draw_vbo with the row still pulled; green after. (5) Force bit 12 off under run-ahead → Fatal{UnmigratedVerb, "set_global_constants"}. Quick gate. Unit + integration-split; RelinkStageSetScenario, PostLinkAttachScenario, ProgramPipelineScenario, AsyncCompileScenario, CsoContentAddressingScenario, HandleRecycleScenario; the archive cost measured (links/frame, bytes/link, ms on the GL thread) on MC + a shader pack and reported before the package is declared done. New names: MGPipeApplySetProgramBindings, EmitProgramBindings, SyncCurrentProgramByHandle.

tx2 — textures and samplers (S2)

Scope. DirectGLES.cpp:1723-1806 (SyncTextureToBackendByHandle), :1867-2098 (unit epoch/capture: monolith-only; decline → refusal), :2127-2140 (UnitTextureSyncEntry re-shape, push-only under #if), :2321-2391 (the unit half), :4834-4948 (ResolveAndBindUnitTextures), :4985-5026, :5051-5170 (BindCurrentUnitSamplers, decline → refusal), :5245-5305 (BindCurrentTextures with ruling 6's key), :5561-5632 (the sampler pass; the mint deleted), :8725-8760 + :9560-9600 (GenerateMipmap by VerbMipRes), :9161, 9216 (mip shape HandleOf → handle), trailing :9714-9735 (CopyTex endpoint) and :11884 (GetTexImage); Managers.cpp:5816-5975 (RequireImageBindableStorage entry refusal), :6902-6985, :7049-7400 (SyncMipmapsToBackend(h, rec)), :8472-8560, :8712-8900, :13184-13290 (sampler identity family deleted under transport), :13457-13486; Managers.h:1626-1660 (IsDrawSyncCleanByRecord), :1785-1812, :2678-2700; MG_Backend/DirectGLES/MipmapStorage.cpp:39-51 (guard list widened); PipeApplier.cpp (the window checks at draw/dispatch dispatch — ONE function CheckUnitWindows(st) called from OnDrawVbo/OnLaunchGrid); MG_Test/Wire/RemoteClientTest.cpp:1938-1978; MG_Test/SanityTest.cpp:3075-3144 (+ the ABA sibling); SampledSetStalenessScenario, TextureParamsWithoutASamplerViewScenario kept. Red-once. (1) Revert any handle arm → the widened guard aborts by accessor name. (2) Revert the window arm of ResolveAndBindUnitTextures → strict lane Fatal{UnmigratedPipeInput, "GetTextureUnitObject@DrawVbo"}. (3) ABA: recycle a slot to {s, g+1} while the old entry is in the work list → the twin is re-minted; skip the Gen reset → red. (4) Narrow SamplerEmit.h:735-737's count by one → Fatal{ProtocolCorruption, "SetSamplerViews.Count"}. (5) GenerateMipmap with the row kWaitNone and the unit walk restored → Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@...} (F1WireScenario.GenerateMipmap*Pixels). Quick gate. Unit + integration-split; PipeSlotPeek asserts zero registry-scope entries per frame for this family. New names: SyncTextureToBackendByHandle, SyncMipmapsToBackendByHandle, ResolvePushedTextureParams/BuiltinSampler(MGPipeHandle), IsDrawSyncCleanByRecord.

fb — framebuffers, attachments, images, blit (S3)

Scope. DirectGLES.cpp:179-241 (the doors), :2140-2180 (list keys), :2227-2331 (SyncReadFramebufferTextureAttachments), :2405-2470 (the draw-FBO list), :2519-2914 (images: SyncImageTextureBinding(const MGPImageView&), ResolveShaderImageRecord deleted, MarkWritableImageBufferTexturesGpuWritten deleted under transport, the sweep gate), :3017-3377 (SyncCurrentFBO*), :4514-4754 (BindCurrentFBO, SyncAndBind*, ForceBindCurrentFBO), :6180-6192 (Clear), :8182-8420 (both blit arms), :8837-8935 (the detach walk + reverse index), :9688-9740 (SyncRenderbufferObjectToBackend by handle); Managers.cpp:9395-9405, 9585-9742, 9834-9953, 10044-10429, 10847-10850, 13530-13584; Managers.h:1857, 1943-1970, 2819-2830 (reverse index texture → FBO slots); MG_Test/Pipe/FramebufferEmitTest.cpp (server twin of :558), MG_Test/Pipe/ImageEmitTest.cpp (beside :452: the three constants on both sides), a new default-framebuffer-resize re-emit case. Red-once. (1) SyncAttachmentSurface takes the frontend attachment again → strict Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot"} on SnormAttachmentScenario. (2) The draw-FBO list key back to the pointer compare → Fatal{RoleViolation, "MGPipeSlots"} (after ra). (3) MGPipeDecodeImageAccess mapped 1↔2 → FormatlessImageBakeScenario / ImageLoadStoreSsoScenario red + the unit case. (4) The named blit back on the frontend objects → CopyImageLayeredScenario, LayeredAttachmentShapeScenario. (5) The per-point version memo kept as a second gate → CrossFrameBufferScenario / P4aSeamAuditScenario F-3 must stay green WITHOUT it. Quick gate. Unit + integration-split with the framebuffer/sampler bits set; the seam greps (DirectGLES.cpp:3086-3106, 2607-2610, 2636-2638) silent; the object A/B re-baselined for SyncReadFramebufferTextureAttachments. New names: SyncToBackendByHandle (RBO/FBO overloads), MarkBufferGpuWrittenByHandle (declared; body deleted under transport with sb), the reverse index.

ra — the transport mechanics (S6; parallel from day one; lands last)

Scope. MG_Remote/Client/ClientSession.{h,cpp} (RunAheadArmed, EmitAndWaitTails §2.3, ReleaseFillPins, DrainEventRing at every park exit, Finish, RefusePipeInputsTouch... §3.5, the stats line credit-waits); MG_Remote/Client/EmitTables.cpp:977-1053 (EmitPresent credit + FrameSerial) and the Finish slot; MG_Remote/Client/BackendObject_Remote.cpp (wait before every Server* EGL forwarder); MG_Remote/Client/WireTables.cpp:332-352 + MG_Pipe/PipeRoute.h:270-276 (wantReply for the buffer half); MG_Remote/Client/CapsMirror.* (latch); MG_Impl/Pipe/PipeFill.cpp:84-86, 546-556, 1955+, 2962-3306 (§3.1, §3.4); MG_Backend/MGPipe/PipeInputs.{h,cpp} (server-private stamp, §3.2-3.3); MG_Remote/Server/PipeApplier.cpp (OnPresent credit return, StampVerbBoundary, MGPipeBarriered body; ApplyOne's flag line is id's); MG_Remote/Server/ServerSession.{h,cpp} (present credit, event producer flow control); MG_Remote/Server/ServerLoop.cpp:375-392 (park predicate); MG_Remote/Transport/EventRing.h, SessionRings.h; MG_Backend/Init.cpp (flip kMGPipeP5eRunAheadReady — the integration commit); MG_Impl/Pipe/PipeMutation.h:216-236 (the unbarriered-enqueue finding); .github/workflows/test.yml:1140-1206 (the strict lane's allowlist step); docs/Disaggregated/ARCHITECTURE.md §11, §14, §17; MG_Test/Wire/{RemoteClientTest, SessionTest, ServerLoopTest}.cpp. Red-once. (1) Encode present without WaitForPresentAck → the SessionTest.cpp:522-549 twin in RemoteClientTest parks a credit-1 client behind a stalled apply thread; without the wait the third present publishes and Waits() is unchanged → red by count. (2) Leave one CopyField in an unbarriered fill → Fatal{RoleViolation, "gPipeInputs"} by name (RemoteGuards...FillWhileTheApplierOwnsIt's shape with RunAhead=1). (3) Count instead of Fatal under an unbarriered record → the strict lane's rewritten step turns from green to red on the first pulled row. (4) Keep Fatal{EventRingOverflow} → a unit case publishing 300 KiB of kEventGpuWritten behind an unwaited sequence aborts; with flow control it completes. (5) Publish bit 10 for Magma → MagmaPipeIdentityTest red. (6) MOBILEGL_IPC_RUN_AHEAD=0 renders identically (SSIM); MOBILEGL_IPC_VERB_BARRIER=0 under run-ahead is the documented red. Quick gate (before the flip). Unit + integration-split green with the bit unpublished (every branch inert); RemoteGuards strict arms green. New names: MGPipeBarriered, MGPipeWaitClassFor (c0e), ClientSession::RunAheadArmed/ReleaseFillPins, MGPipeServerStampVerbBoundary's new signature, IpcTable::RunAhead/PresentCredit.

3 The integration gate (the commit that flips kMGPipeP5eRunAheadReady)

  1. Unit green under both MOBILEGL_IPC_STRICT_ERRORS arms.
  2. integration-split green (111+ entries plus the new ones), broad inproc census with zero regressions vs 2fde7034, the 79 traces with no new first blocker.
  3. integration-split-strict GREEN as a hard lane: the only admitted [BARRIER-PULLED, ...] markers are the §7 allowlist (<field>@<verb> for readbacks, XFB, CopyTex, set_storage_block_binding); any other marker fails the lane. Every scenario summary shows rsp = 0 on unbarriered records.
  4. The three negative controls behave as documented: RUN_AHEAD=0 identical SSIM; VERB_BARRIER=0 red at a barriered row; RUN_AHEAD=1 on Magma logs once and runs lockstep.
  5. Every package's red-once re-run on the integrated tree (a revert of any one handle arm goes red in the strict lane by field and verb).
  6. G1 0/0/0/0, G2, G14, G5; the push build under Transport=monolith renders the four A/B traces with unchanged SSIM.
  7. MOBILEGL_IPC_AUDIT=1 clean (no 0xDD read) with the client a present ahead — the pmap push without a reply is the new consumer of retired staging.

4 The device exit (Redmi, MC 26.3 VD12, 30 s in-world window)

Arms: monolith; inproc lockstep (MOBILEGL_IPC_RUN_AHEAD=0); run-ahead credit 1; run-ahead credit 2. Per arm: fps; per-thread CPU ms/frame (client GL thread, mgl-srv-apply); cliwait/clipark, credit-waits, srv/srvpark from the wire ledger and the stats line (ClientSession.cpp:1161-1189, ServerLoop.cpp:410-414); simpleperf on both threads — the exit condition is that WaitForApplied self on the client falls from 27.9% to the present-credit wait alone and DrainRing's real work is unchanged; the per-op wait tally must show NO kWaitApplied row and no reply row firing per draw on the steady path (what remains per frame — the texture subdata for the lightmap, set_texture_params replies, fences — is listed by op in the report as the trailing inventory); rsp = 0 on unbarriered records; SSIM of the four A/B traces vs monolith unchanged. The P5D report's method section (P5D-INPROC-PERFORMANCE.md:58-71) is the template; the bench scripts the brief named are not on this machine and are re-created beside it. Recorded, and GATED on: fps ≥ lockstep's, client-thread ms/frame < lockstep's, SSIM unchanged.

5 What trails (after the integration commit, each its own small package)

item owner why it can trail
GenerateMipmap kWaitApplied → kWaitNone (if tx2 lands the VerbMipRes arm late) tx2 not on the steady path
set_storage_block_binding by ShaderCso pg after-link only
CopyTex endpoint / GetTexImage / readbacks by handle (the barriered scope sites 6166, 8837, 9161, 9216, 10029, 11884) tx2 / fb barriered rows keep P5C's semantics (P8/P9 phases)
resource_subdata texture half without a reply (D-D5 accept-by-construction) ra + tx2 one wait per frame for the lightmap today
set_texture_params without a reply (the default-texture self-heal moved client-side) tx2 creation-time traffic
client vertex arrays staged ({BindingIndex, MGHostSpan} tail) P8 refused under run-ahead
XFB migration (set_stream_output_targets, the program row, the targets) P3b/P4b/P9 lockstep by escalation (i)
ResolvedDrawBuffers deletion vi measurement first
composite pipelines' uniform mirror vs set_program_bindings fire rate pg unmeasured; pipeline workloads only
program-derived set_shader_buffers window sb narrowing, not correctness
MOBILEGL_IPC_EVENT_KB (ring size under SSBO-heavy kEventGpuWritten) ra Iris-class workloads
Flush as the doorbell point (ARCHITECTURE §11.6); credit-2 latency study ra measurement
thread placement — out of scope by instruction; the per-thread numbers survive it

6 The rulings the integrator makes (conflicts between scout designs, with the resolution)

# conflict resolution (contract §9)
1 S7: handle arms run under Transport=monolith too (family bit selects); S1–S5: Transport != Monolith keeps the frontend arms under monolith S1–S5's shape. The push-monolith build keeps its bytes and behaviour, the verify comparator keeps its frontend arm, and RUN_AHEAD=0 becomes a pure wait-rule A/B on identical server code.
2 Brief/S7 assumed client vertex arrays are refused under split; S1 read the code: they are not (EmitTables.cpp:651-685) Verified S1 is right. Refuse under RunAheadArmed with kDrawClientArrays; lockstep unchanged; staging is P8's.
3 S5: XFB/atomic waits decided from client state at emit; S6: a static per-op wait table The wire-derived predicate MGPipeBarriered(op, payload, st) — static column + XFB-active from MGPContextValues + the draw flag — computed identically on both sides.
4 S6 §4(6): a WAITED op may no longer read the frontend; S1–S5 keep readback/CopyTex/GetTexImage sites barriered S6's argument is wrong for a single-context client parked in its own wait; barriered records keep P5C's semantics (rule F is scoped to unbarriered records). This is what keeps the first landing small.
5 S4 §4.6: ShaderStorageBlockBinding is a GL-thread entry; S6: set_storage_block_binding is a kCtxVerb applied on the apply thread through GetProgramObject(GlName) Verified S6 (PipeCalls.def:243, PipeApplier.cpp:805-808). kWaitApplied now; pg's trailing item resolves by ShaderCso.
6 S2: BindCurrentTextures memo on SamplerViewsSerial alone; S4: on the program handle + serials Both (a relink with unchanged resolution still needs the twin's new unit assignments).
7 S3 R1: the image sweep gate needs S2's serial design (ShaderImagesSerial, TextureShutterSerial, ContextSerial, g_backendContextGeneration); keep the union walk.
8 S4 R1: encode the archive per link vs pin a SharedPtr Encode (rule B); measure; lazy encode at first bind as the fallback.
9 S4 R2: a failed relink of a bound program Re-issue with LinkStatus = 0 iff the frontend reports it unlinked; pg verifies the frontend's actual behaviour; never object_death.
10 S5 R2: high-water vs program-derived window High-water (TouchedBufferBindingPointCount, already carried); 84 on the wire, backend clamps.
11 S5 R1: one suppressor slot for three classes Three slots.
12 S7 R1: Magma's four apply-thread allocator sites vs an unconditional guard One renamed scope, backend-kind-keyed; Magma is lockstep and every record is barriered there.
13 S6 §3(9)/S7: the deferred-destroy queue "loses its producer" It does not (barriered fills pin objects); the queue stays live; an unbarriered enqueue is the finding.
14 S6 §3(3)/S2: GenerateMipmap waits until the unit read is gone Verified (ClientSession.cpp:900-914; VerbMipRes already exists at DirectGLES.cpp:8736). tx2 flips the row.
15 S3 R5: does an EGL resize re-emit the default-framebuffer record Verified yes (ClientSession.cpp:210-221 → TextureObject.cpp:84-102 → Tracker.h:594); fb pins it.
16 S3 vs the brief: Access "has no encoding" Verified S3: MGPipeEncodeImageAccess exists (ImageEmit.h:163-176); the server comment is stale; the decode moves to MGPipeValueTypes.h.
17 S6 §7: resource_subdata's texture half Buffer half kWaitNone in ra; texture half keeps its reply; trailing.
18 present credit default 1.
19 S1 R4 / S2 R1 (A8): the var-tail windows are not pinned The window rule in §5.3 with the sink check at every draw/dispatch; set_vertex_buffers' window is pinned by a test in vi (same class, same validate step).

Open items that need a ruling the scouts could not settle and this brief does not: (a) whether DoublePrecisionScenario uses client arrays under split (decides whether vi's refusal needs a new scenario as its red-once); (b) the archive cost number (ruling 8's fallback trigger); (c) the NoSession server-only fixtures that lose death delivery when id deletes the hop (grep RunOnApplyThread MG_Test/Wire) — id enumerates them in its first commit and the integrator rules keep/rewrite.