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.
82 KiB
P4a integrator decisions (feat/disaggregated, 2026-09-08)
ID-1 Base ref 37da3c3a (= P3a docs commit; code fde5fda3), baselines captured there
BRIEF-P4A.md was verified at 37da3c3a. ~/w7/p4a-before-* (pull lib, ctest names, RenderStateImpl sha,
dirty-surface) are the pull build of 37da3c3a; P4a's admitted-resize set is EMPTY (G1 0/0/0/0 after every merge).
P3a's eleven G5 functions stay byte-identical (scripts/p3a_untouched_regions.sh 37da3c3a HEAD); P4a names its
own byte-identical set per the brief's D-N (package F adds the script).
ID-2 Order: contract -> wire -> clientfb -> clientsp -> esprytobj -> esprytdraw -> gates
Every package branches from the tag p4a/contract; branch refs are spelled refs/heads/... (the tag makes the
short name ambiguous). Each Espryt package gets a real-path verification round on the integrated tree after the
client packages land (P3a found three seam defects that way), then a focused re-review; a final whole-diff
review closes the phase. wire is behaviour-neutral (nothing consumes its records until Espryt lands).
ID-3 Tools (all phase-parameterised, in ~/w7/notes/tools)
wsl_tree.sh p4a <slug> <base> [verify], wsl_integrate.sh p4a <slug>..., wsl_capture_baseline.sh p4a,
wsl_p3a_gate.sh -> wsl_p4a_gate.sh (integrator writes it per the brief's D.3 delta), wsl_build_trace_apks.sh p4a (Release + debug-signed; prints the lib size - Release .text ~9.4 MB, ID-17 of P3a), p3a_ab.sh ->
p4a_ab.sh with the previous mask 0x1fff-vs-0x1ff arms via p3a_ab_7f.sh's shape, ab_reduce3.py,
wsl_p3a_bench.sh -> wsl_p4a_bench.sh (T2 = 0x1ff arm). Package agents never touch ~/w7/pipe.
ID-4 Models and rounds
Package agents and reviewers run on opus (user rule 2026-09-08). One adversarial review per package, at most two rework rounds; declared minors do not block integration.
ID-5 Durable locations
Brief ~/w7/notes/p4a/BRIEF-P4A.md; scouts ~/w7/notes/p4a/scout-*.md; results and reviews
~/w7/notes/p4a/p4a-results/<package>-v<N>.md / <package>-review-v<N>.md; A/B ~/w7/notes/p4a/ab*; bench
~/w7/notes/p4a/bench. Package logs ~/w7/p4a-<slug>-*.log are deleted by their owner when the round ends.
ID-6 Performance is recorded, not gated (user, 2026-09-08)
Against MEASUREMENTS.md §20's Release baseline (pull / 0x7f / 0x1ff); P4a adds the 0x1fff arm. Correctness gates stay hard. Device A/B on Release APKs only; the create-indirect and rd12/Magma/Adreno fixtures stay out.
ID-7 Working over UNC and WSL
Edit worktrees through //wsl.localhost/Arch/home/swung/w7/p4a-<slug>/... (files yes, mkdir no). Commands
through a script file <scratchpad>/wsl/<name>.sh (LF) run as MSYS_NO_PATHCONV=1 wsl.exe -d Arch -- bash /mnt/c/<scratchpad>/wsl/<name>.sh; never pass ~ or $vars inline; foreground under 8 minutes, longer via
setsid nohup ... & + log polling; never delete the shared wsl scratchpad directory, only your own files.
ID-8 Rulings carried from P3a that bind every P4a package
- Exit-order rule (P3a ID-18/21): no frontend destructor may run from an exit handler into pipe/backend state.
Every new static holder of frontend SharedPtrs is leak-at-exit storage; every new singleton reachable from a
destructor is never-destroyed. Prove with
GLIBC_TUNABLES=glibc.malloc.tcache_count=0on both lanes. - Backend-neutral death paths from day one (P3a final-review C-1): the client emits delete/destroy and frees the slot from the frontend destructor for EVERY kind it mints; a backend death notice is a redundant second path and must be idempotent. A leak test per new kind, run on the DirectVulkan lane too.
- Stale-generation refusal in every GetOrCreate(handle) (P3a contract-review M2).
- The ownership table lists generated/.def interfaces; a package that retires a .def row must not leave another
referencing the enumerator (P2's
bb2a236dtrap) - the contract owns the enum-coupled PipeFill.cpp block. - Files C.7 gives to nobody are granted explicitly by the integrator when a package needs them (GLImpl entry points, MG_Test fixtures such as BufferTest's ScopedBackendOps that must scope BOTH op tables).
- ColorOS install dialog = topResumedActivity InstallGuideActivity (not mCurrentFocus); tapping mid-install drops adb for seconds; the Xiaomi needs cpuss-0-0 < 42 C before pinning; a crash resets the GPU pwrlevel range.
ID-9 Contract integrated: feat/disaggregated = 08192d72 (pushed) = the tag p4a/contract
Fast-forwarded (pipe was at 37da3c3a): pull symbols 0/0/0/0, unit 1635 x 3, G2 0, G14 0 removed / +13, P3a's
eleven byte-identical. contract-v1 deviations the packages must know: D1 c0 CREATED the five emit headers
MG_Impl/Pipe/{Framebuffer,Texture,Sampler,Image,Program}Emit.h with a stub emitter and kMGPipeWired*Subsystem = 0 each - packages B (Framebuffer/Texture) and C (Sampler/Image/Program) own those headers' BODIES and never
touch PipeFill.cpp; the wired bit is added in the commit that gives the emitter its body. D13 the ShaderCso
composite band has its own dense table in the allocator and the applier (band base 983040; a slot-indexed
vector would cost 236 MB per composite). D5 six death helpers, one per kind (the texture helper releases
the sampler view, not the built-in sampler CSO, which has its own lifetime). D2 MGPipeResourceTarget has a
13th enumerator TexRect. D6 two UNDECIDED dirty-surface rows (UseProgram/NEW_SHADER, BindVertexArray/
NEW_VERTEX_ELEMENTS) are marked with the tool's reason, not silenced. D7 BindProgramPipelineObject is
kPulledEveryVerb. D16 clang++-20 does not exist on the box; the gate spells /w7/p4a-trees2.log).clang++. Contract review v1
in flight; the six sibling trees were reset to the tag and are rebuilding (
ID-10 Contract review v1 = ACCEPT WITH MAJORS; c0b in flight on p4a/contract, M2 goes to wire
Six majors: M1 the birth half of the MG_State coupling is missing (PipeMutation.h declares only the six death helpers; the death helpers hard-code published=false) so B/C cannot emit create/respecify/params without touching A's files; M2 MGPipeApplyResourceSubData has no MGPSubRegion tail (D-D3/D-D5 unreachable); M3 wants() ignores kMGPipeWiredSubsystems so P4a's emitters are live under 0x1fff before their bodies exist; M4 the sampler-view death helper skips the death notice; M5 HighWater(ShaderCso) returns the band top (leak test vacuous); M6 check_include_closure.py exits 0 without a compiler. Disposition: M1/M3/M4/M5/M6 = commit(s) c0b on p4a/contract by a fix agent touching ONLY PipeMutation.h, PipeFill.{h,cpp}, SlotAllocator.*, the closure script and an A-owned test (no overlap with the six in-flight packages); M2 = wire's rework (it owns PipeApply after the tag). The tag stays at c0; B..F rebase onto pipe (c0 + c0b) at their rework/verification round; B/C v1 will show M1-driven deviations that the rework reconciles against contract-v2.md's exact birth-hook declarations.
ID-11 c0b integrated: feat/disaggregated = 2cb44039 (pushed); wire v1 landed on p4a/wire
c0b (32033d69 + 2cb44039): PipeMutation.h now declares the birth half (4 mints, 9 emissions, a 3-call
publication latch read by both halves; if constexpr seams keyed on each family's wired constant forward to
the entry points B/C must provide - contract-v2.md lists them exactly); wants() consults kMGPipeWiredSubsystems;
the sampler-view helper raises the notice; composites are counted apart (CompositeHighWater/LiveCount/FreeCount);
check_include_closure.py validates --compiler in every mode (test.yml:774's clang++-20 is fine in CI, where
clang-20 is installed; the local gate spells clang++). On pipe: G1 0/0/0/0, unit 1636 x 3, G2 0, G14 0/+14.
wire v1 (86835b50 / bc9abfbd / 0f990633 / 3c07c8c3): W1 closes M2 with a defaulted trailing const MGPSubRegion*
tail; W3 fixed a real destroy-dispatch defect; hazard H1 (BuiltinSampler == null is Fatal but D-K2 allows bit 10
without bit 11) awaits the wire review's assignment (contract dependency rule vs client minting). Every package
rework/verification round rebases onto refs/heads/feat/disaggregated (c0 + c0b).
ID-12 esprytobj v1 landed (9bb95335, six commits on p4a/esprytobj); four contract seams ruled, c0c in flight
- DV-3 (
MGPSubData::Target): the brief's D-D3 names BOTH halves (resource target + cube-face upload target). clientfb v1 packslow byte = MGPipeResourceTarget, high byte = TextureUploadTarget; wire'sSubDataNamesABufferkeys on the whole field== 0;TextureUploadTarget::Texture1Dis 0, so the bare enumerator Espryt v1 matches on would collide with Buffer. RULING: the packed form is the contract encoding. c0c movesMGPipePackSubDataTarget/MGPipeSubDataResourceTargetOf/MGPipeSubDataUploadTargetOfinto MGPipeTypes.h (Uint32 arguments, backend- neutral); B's rework deletes its copies; D's rework decodes the HIGH BYTE at everyPendingUploadsmatch (FindPipeTextureUploadand the consume site); wire is unchanged (it matches the field verbatim). - DV-2 (
DepthStencilMode): 0 = GL_DEPTH_COMPONENT, 1 = GL_STENCIL_INDEX; both sides already agree. c0c mintskMGPipeDepthStencilModeDepth/Stencilin MGPipeTypes.h; B's rework drops its copies (the GLenum->byte helper is B's). - DV-4 (
MGPSurface::Kind): the numericMGPipeKind::{None,Texture,Renderbuffer}values (what B emits). c0c mintskMGPipeSurfaceKindNone/Texture/Renderbuffer; B's rework drops its copies. - DV-5 (
MGPSurface::Pad0) ->Uint16 TextureTarget=static_cast<Uint16>(MobileGL::TextureTarget), 0xFFFF (= Unknown) on every non-texture point, consulted only whenKind == Texture; listed inMGP_FIELDS_MGPSurface; size stays 24. B's rework fills it; D's rework moves the four cross-object masks onto it; E'sSyncAttachmentObjectreads it. - DV-1/DV-7/DV-8 accepted as C.7 boundary notes (E owns the resolution sites); DV-6 harmless as argued; DV-9 macros
accepted as G1-forced (the review checks
#undefhygiene and pull-text identity); DV-10/11/12 fine. The 183 named "no applier record" refusals on the default arm are the expected pre-client shape and must go to ZERO on the integrated tree (a residual one is a seam defect, P3a's lesson). - c0c = one commit on p4a/contract by a fix agent touching only MGPipeTypes.h + PipeFields.def (+ an A-owned unit test); integrated (fast-forward) BEFORE wire; every package rework rebases onto it.
ID-13 clientsp v1 landed (ecd5b2e7, four commits on p4a/clientsp); the LFS fixture trap
- Every lane green on its tree (G1 0/0/0/0, unit x3, integration-gpu 966/966 on three arms, verify 844/844 zero
Fatal under tcache_count=0, retrace 79/79, +38 test names). Its findings F2 (wants() ignores the wired constants)
and F3 (death helpers hard-code published=false) are contract-review M3/M1, CLOSED by c0b (contract-v2.md): the
rework rebases onto pipe and reconciles against the exact birth-hook declarations, nothing more. F1 (member-wise
assignment of SamplerParameters leaves padding unspecified; the cache rejected its own entries, hit rate zero,
suite-order dependent) is a real bug the design predicted; the fix is memset+memcpy and is pinned - the review
verifies it. LFS trap: the 12:24 WSL restart left tools/trace_replay/fixtures/*.tgz as LFS POINTERS (132 bytes)
in the wire / esprytobj / esprytdraw / gates trees (clientfb and contract were fine); a retrace on such a tree reads
2/79. I ran
git lfs checkoutin wire/esprytobj/gates (pointers 0 afterwards; esprytdraw and pipe were already clean). Every private worktree a reviewer creates must rungit lfs checkoutbefore any retrace.
ID-14 The built-in sampler seam between B and C is ruled: B acquires it through C's cache
clientfb v1 (TextureEmit.h ~636) mints MGPipeSlots().Acquire(MGPipeKind::SamplerCso, sampler->GetLifetimeId())
per sampler OBJECT; clientsp content-addresses sampler CSOs (D-F1, MGPipeSamplerCsoCache::Acquire(params, payloadBytes), SamplerEmit.h ~186 names this as B's seam) and c0b's hook comment says the handle rule for this kind
is THE EMITTER'S. B's handle would never receive a create_sampler_state record, so on the integrated tree every
texture's params would be refused (or, with a null, Fatal{ProtocolCorruption}) - the seam C called the highest
post-rebase risk. RULING: B's rework obtains MGPTextureParams::BuiltinSampler from C's cache seam (the exact call
is named by the two reviews; TextureEmit.h may include SamplerEmit.h - the include-closure gate must be re-run) and
re-emits set_texture_params whenever the built-in sampler's parameters change (the content-addressed handle changes
with content; D-E2's SamplerResync covers the re-sync). B keeps NO per-texture death path for the sampler CSO: LRU
eviction in C's cache is the only delete_sampler_state emitter (contract D5/M4 unchanged). Hazard H1 (wire): the
Fatal on a null BuiltinSampler stays; the D-K2 "bit 10 without bit 11" arm must therefore be refused at the CONTRACT
dependency rule (texture bit implies sampler bit), which is package F's 0x9ff-style dependency-refusal scenario.
ID-15 wire review v1 = REWORK (C1 + five minors); H1 becomes a fourth D-K2 dependency row; esprytdraw v1 landed
- wire C1 (PipeApply.cpp:1485): resource_respecify clears the WHOLE PendingUploads vector, but AllocateStorage / MarkStorageDirty are per (uploadTarget, level), so defining level 1 (or glGenerateMipmap) after level 0's accepted upload was kept by Espryt's incomplete-texture bail loses level 0's texels silently. Fix as specified: scope the clear to the redefined level via a trailing defaulted pointer (null = whole resource), one case + one mutation. Minors m1-m5 (ReleaseObjectRecords leaves framebuffer records + three unit windows populated - contract-review m2 assigned to wire; the PipeApply.h:422-428 framebuffer-refusal promise; the Renderbuffer enumerator accepted as an upload target; no acceptance signal to the emitter; unmap_persistent's inert kind check) are fixed or declared in the same rework. W1-W6/W8 accepted; W7 rejected (= C1). Rework rebases onto refs/heads/feat/disaggregated.
- H1 ruling (refines ID-14): the brief's D-K2 says "bit 10 without 11 is fine"; that sentence is WRONG for P4a as built - MGPTextureParams::BuiltinSampler is a SamplerCso handle, only bit 11 mints sampler CSOs (c0b's four unconditional mints deliberately exclude it), and a null there is Fatal. D-K2 gains a FOURTH row: bit 10 requires bit 11. Sites: PipeFill.cpp:1062-1069's precedent (contract, folded into c0c), Managers.cpp:2393-2410's mirror in the texture family's Resolve*SubsystemArm (package D's rework), and package F's dependency-refusal scenario gains the 0x5ff-shaped arm. Client-side minting of the built-in sampler is REJECTED (it would run the content-addressed cache in the arm meant to exclude it and crosses the B/C boundary).
- esprytdraw v1 (98bdfa3e..0b00f7f0, five commits, DirectGLES.cpp only): every arm green on its tree because its handle arms DECLINE TO THE PRE-HANDLE ARM when the applier holds no record (a serial is never an "emitted" test - MGPipeApplierReset advances every P4a serial unconditionally). On the integrated tree that shape is the silent fallback P3a's lesson forbids: a missing record must be a LOUD refusal (MGLOG_E_ONCE + decline to draw nothing / Fatal per family), never a quiet legacy read. The review judges every decline site; E's verification round flips them once B/C records exist. Its closing of D-E3's READ-attachment gap from SyncNeccessaryTextures may make G9's red-before obtainable (R1 of the gates review) - the E review says whether.
- ID-15 correction on WHERE the fourth D-K2 row lives: the precedent is SERVER-side (Managers.cpp ~2426, Espryt's ResolveVertexInputSubsystemArm refuses bit 8 without bit 7 and runs the legacy arm; PipeFill.cpp ~1585 only documents that the client's calls then land in RefusedResourceCalls). So "bit 10 requires bit 11" is implemented in package D's texture-family Resolve*SubsystemArm (its rework), pinned by package F's dependency-refusal scenario (a 0x5ff-shaped arm), and recorded in the brief/docs D-K2 table. c0c's scope is unchanged (no contract code).
ID-16 gates review v1 = ACCEPT WITH MAJORS (six, all local); gates rework v2 launched
F-M1/M2/M3 G7 (scripts/p4a_descriptor_negative_control.sh): the borderColorForm patch writes 0 into a scoped enum
(never compiles), the patcher's print pollutes the captured verdict, PATCHED_HEADER is set inside a command
substitution so repair() on EXIT/INT/TERM is a no-op (a SIGINT leaves the patch in the tree) - all hidden because the
script exits at step 0 today. F-M4 the composite leak case reads HighWater(ShaderCso) which c0b redefined; use
PeekPipeCompositeSlotHighWater (contract-v2 §4.3/§7.6). F-M5 (R2) the ABA-flip probe needs both regexes in ONE
file - make it directory-wide so the flip cannot be forgotten. F-M6 the 0x9ff refusal matches lowercase "sampler"
anywhere in the log - match the exact refusal line. R1 partial: the TextureParamsWithoutASamplerView net catches
emitted-but-not-applied, not deferred-to-first-view; package D's verification-round probe must cover the second.
G5 (p4a_untouched_regions.sh) survived four attacks incl. a tail-perturbation probe - accepted as is. Rework v2
also: rebase onto refs/heads/feat/disaggregated, add the ID-15 0x5ff-shaped arm (bit 10 without bit 11 must be
refused by Espryt once D's rework lands - the scenario is written now, expected to SKIP-visibly until then), keep
R3 (four commits fine).
ID-17 c0c integrated (pipe = 17db7598, post-merge checks running); clientsp review v1 = REWORK; c0d for Tracker.h
- clientsp C-M1 (CompositeResolver.h:138/104): Reset() clears Live and the reuse branch returns before restoring it, so after any make-current ReleaseEntry early-returns forever - fix + a test that drives release THROUGH the resolver after a Reset. C-M2 (ProgramEmit.h:168 vs :200): the (Cso, Version) latch for the default uniform block is never invalidated when a re-issued create_shader_state clears the applier's block (wire W6) - a failed relink of a bound program leaves a zeroed block; invalidate the latch on every re-issue. C-M3 (SamplerEmit.h:299): LRU eviction invalidates sampler CSO handles that texture params records still name, and nothing re-emits. RULING: the cache keeps a REFERENCE COUNT per entry (B's Acquire for a texture's built-in sampler takes a ref; texture death or a parameter change that re-acquires releases it; bound sampler states hold one while bound); LRU evicts only unreferenced entries; when every entry is pinned the cache mints beyond 256 and counts it (OverCapacityMints, a recorded number, not a gate). B's rework uses Acquire/Release exactly as C's v2 report spells them.
- Two cross-package under-fires the review found in A's Tracker.h: (1) glBindSampler bumps TextureBindGeneration (bit 12) not SamplingResolutionGeneration (bit 13), so bind_sampler_states never fires for a sampler bind; (2) bits 6/7/8 read GetCurrentProgram(), null under SSO, so a re-composited pipeline never gets a ShaderCso handle. Both are contract defects -> c0d on p4a/contract (Tracker.h + an A-owned unit test), integrated before wire v2.
- Ten minors + 15 deviations judged in the review; the c0b reconciliation list (3 missing entry points, 4 latch
calls, 2 stale comments) is part of the rework. clientsp rework rebases onto pipe (
17db7598+ c0d when it lands).
ID-18 clientfb review v1 = REWORK (four majors, nine minors); two of them are wire-side rules; B's rework waits for wire v2
- M1 (TextureEmit.h:939): m_regions is built and counted but never passed - wire W1's defaulted
const MGPSubRegion*tail must receive it; every scattered upload otherwise declares N regions and supplies none. Fix + a unit case that asserts the applier's stored region list equals the emitted one. - M2: SamplerObject::BumpVersion (the 13th MGP_NOTE_AGGREGATE(TextureParams) site) has no hook, so MinLod/MaxLod/ LodBias go stale on glTexParameterf; it is ALSO the re-emit hook ID-14/ID-17 need when the built-in sampler's content changes (re-Acquire from C's cache, Release the old handle, re-emit set_texture_params). The GL_Texture.cpp grant exists for this - B takes it.
- M3: the client clears its dirty flag on DISPATCH, not on ACCEPTANCE (D-D5 step 1), even when the if-constexpr
discarded the call. RULING: the applier returns acceptance. wire v2 was asked to implement m4 "if small, else
declare"; if v2 declares, a wire v3 adds
Boolacceptance returns to MGPipeApplyResourceSubData / Respecify / Create (default-tail-compatible; the wire path is untouched because gen_pipe never parses PipeApply.h - wire review W1). B clears a level's flags only when the call returned accepted. - M4: the sticky BindMask/ImageBindableHint reach the applier only on a LATER respecify, which an immutable texture never has. RULING: a mask change after allocation emits a resource_respecify whose storage-defining fields equal the stored descriptor; the applier treats such a record as a METADATA UPDATE - no reallocation ack, no PendingUploads clear (this refines C1's level-scoped rule: identical storage fields clear nothing) - and Espryt's twin re-derives the storage flags from the new mask on its next sync (recreate only where the backend needs it). Wire side = the same v3 (or v2 if the agent read this in time); Espryt side = D's verification round.
- Nine minors: cube face resolved to targets[0], draw-buffer tokens >= 8 escape D-C3's refusal (must refuse), a dangling Entry&, unpublished entries never retired so a recycled slot inherits the mask (must retire) - fix all four; declare the rest with reasons. D4's blocked flip verified: kMGPipeWiredTextureSubsystem flips to 1 in the rework (one line) with the named expected-failing set re-run. The rework rebases onto pipe AFTER wire v2 is integrated (it builds against the applier's tail and acceptance returns); c0c's helper deletions + TextureTarget fill and ID-14/ID-17's Acquire/Release calls (clientsp-v2.md spells the API) are in the same round.
ID-19 esprytobj review v1 = REWORK (1 critical, 4 majors); esprytdraw review v1 = REWORK (3 majors + 1 for D); the framebuffer record becomes PER OBJECT
- CRITICAL (esprytobj, Managers.cpp:8216/8450): the applier holds only the two BOUND-target records (wire
DrawFramebuffer/ReadFramebuffer) and B emits only for bound slots, so on the handle arm every DSA entry point
(BlitNamedFramebuffer, the four ClearNamedFramebuffer*) blits or clears into a driver FBO that never got its
attachments; SyncToBackend binds and returns on a mismatch. RULING (design, the phase's main correction):
the framebuffer record is keyed by the FRAMEBUFFER HANDLE. (a) c0e (contract, after c0d in the same tree):
MGPipeFramebufferTarget::Named = 3= "this record describes the framebuffer it names; no binding changes", comment on MGPFramebufferState stating the per-object table and that Draw/Read/Both records ALSO set the bound handle(s); D-K2 fourth row and G9-as-white-box recorded in the brief/docs there too. (b) wire v3:MGPipeApplier().FramebufferRecordsslot-indexed by the handle's slot (gen checked on lookup - stale gen refuses; framebuffers have no wire lifetime, D-I2, so a slot is simply overwritten by its successor's record),MGPipeApplier().BoundFramebuffer[Draw|Read]= handles; set_framebuffer_state Draw/Read/Both writes the record at Fbo's slot and the bound handle(s); Named writes the record only; the FramebufferSerial advances on every write; ReleaseObjectRecords clears the table and the bound handles (wire m1).DrawFramebuffer/ReadFramebufferbecome accessors resolving through the bound handle so E's SyncCurrentFBOByRecord keeps its shape. (c) B v2: emit a Named record at every DSA entry point that hands a framebuffer to Espryt by name (Blit/ClearNamed*, and the DSA attachment/drawbuffer/readbuffer setters at their validate point) - "any framebuffer Espryt is about to receive by name has a record". (d) D v2: PushedFramebufferRecord(target, fbo) -> FramebufferRecordFor(handle); the bound-target question is answered by BoundFramebuffer[t] == handle. - esprytobj majors: M-1 the server's own re-dirty (RequireImageBindableStorage, :5127) is invisible to the pending set -> the twin re-arms the record's PendingUploads itself (server-side state); M-2 pushedStorage->Desc has zero readers - d2 must allocate storage FROM the descriptor (that is the step's purpose); M-3 SyncAttachmentObject (:7947) is D's file -> D does the attachment resolution incl. the TextureTarget masks (ID-12 DV-5); M-4 the two registry methods and AdoptTwinByHandle with no caller are removed or wired. Ten minors fixed or declared. DV-9's wording corrected (38 non-semantic preprocessed-line differences: LINE, parens, reflow - accepted).
- esprytdraw majors (E v2, launched now, rebased onto pipe): (1) SyncImageTextureBindings:2527 narrows the DRIVER
sweep to the record's window and returns, so a unit outside it keeps a deleted texture name - sweep the full
driver range; (2) BindCurrentUnitSamplers:4361
continues outside the window instead of unbinding - restore theelse UnbindSampler; (3) the image (:2369) and program (:3699) seam checks are silent - log the same line the framebuffer one logs; (4->D) InvalidateFramebufferHandleArmMemos belongs inside InvalidateFramebufferBindingCache. Decline sites: 27 classified; the two SILENT ones (SyncCurrentFBOByRecord:2709 half-described applier; ResolveGlobalConstantsRecord:3701 short block image falling back to MapUBO = protocol corruption) become loud NOW; the rest flip per the review's table at E's verification round on the integrated tree. - G9: not obtainable through public GL (readback emulation sets DEPTH_STENCIL_TEXTURE_MODE itself; IsDrawSyncClean pushes params on first sample) -> F's G9 becomes a WHITE-BOX assertion (gates v3 / F's verification round).
- Order now: c0d -> c0e -> wire v3 (v2 + table + acceptance returns + metadata respecify) -> B v2 -> C v2 (running) -> D v2 (needs wire v3's table to build) -> E verification -> D verification -> F v3.
ID-20 c0c integrated and pushed: feat/disaggregated = 17db7598 (origin)
Post-merge on pipe: G1 0/0/0/0, unit 1638 x 2 (pull/push), P3a's eleven byte-identical, names pull == push 2604,
0 removed / +16 vs baseline. Tool note: the post-merge name comparison needs LC_ALL=C on both sides (the baseline
was sorted under a different locale; the unfixed comm reported 236/252 - a false alarm). Detached WSL jobs must be
launched setsid nohup bash <script> < /dev/null > log 2>&1 & disown and verified with pgrep - with only stdout
redirected the job died before writing a line.
ID-21 wire v2 landed (6f263284, five commits, rebased on 2cb44039); wire v3 + c0e launched in parallel
wire v2: C1 fixed as specified (trailing defaulted const MGPRespecifiedLevel*, null = whole resource); m4 done -
MGPipeApplyResourceSubData returns Bool; m1/m2/m3/m5 fixed; unit 1673 x 3, applier suites 125/125, names +51,
five mutations each red on its own case. m3's packed-Target decode is LOCAL (c0c had not landed) - retired at the
rebase onto 17db7598. wire v3 (same agent lineage, new round): rebase onto 17db7598; ID-19(b) the per-object
FramebufferRecords table + BoundFramebuffer[Draw|Read] + DrawFramebuffer()/ReadFramebuffer() accessors;
ID-18 M3 acceptance Bool on Respecify and Create too; ID-18 M4 metadata respecify (storage-defining fields equal
the stored descriptor -> no ack, no PendingUploads clear, masks updated). Named = 3 comes from c0e; until it
lands wire v3 accepts 3 through a declared local constant that wire's verification round retires. The wire v2
re-review is FOLDED into the v3 review (one review over v2+v3). c0e runs in its own worktree ~/w7/p4a-c0e
(branch p4a/c0e from 17db7598) in parallel with c0d (Tracker.h, disjoint files); integration order c0d -> c0e
(rebased) -> wire v3.
ID-22 c0d integrated: pipe = 9ea44389 (post-merge running, push follows); brief amendments recorded
c0d (Tracker.h + TrackerTest.cpp): bit 13's shutter now also reads TextureBindGeneration (a texture bind changes
the sampler-state set through the built-in sampler; the shutter, not the wants() gate, because the dirty-surface
table is derived from the shutter); an else if (pipeline) arm drives bits 6/7/8/14 from the pipeline name + per-
stage {lifetimeId, linkVersion} + the four mirror counters (fields read directly - the two helpers made the bits
UNRESOLVED and broke self-test control 6b). Both UNDECIDED rows stay marked. Gates: symbols 0/0/0/0, unit 1642 x 3,
names +20, dirty-surface self-test 27 controls, integration-gpu DirectGLES 491 identical before/after.
Brief amendments (appended to BRIEF-P4A.md as section F): D-K2 fourth row "bit 10 requires bit 11" (server-side,
D's resolver; F's 0x5ff arm); G9 is a WHITE-BOX assertion (public GL cannot observe the gap: the readback emulation
sets DEPTH_STENCIL_TEXTURE_MODE itself and IsDrawSyncClean pushes params on first sample); D-C2's "per bound target"
becomes "per framebuffer object + two bound handles, Named = 3" (ID-19); D-D5's "clear on dispatch" reads "clear on
ACCEPTANCE" (ID-18 M3); a mask-only respecify is a metadata update (ID-18 M4).
- ID-22 addendum: c0d post-merge on pipe G1 0/0/0/0, unit 1642 x 2, G5 rc 0, names 2608 == 2608, 0 removed / +20;
pushed: origin feat/disaggregated =
9ea44389.
ID-23 esprytdraw v2 landed (9b8441a6, five rework commits, rebased on 17db7598)
MAJOR-1 sweep = [0, min(max(Start+Count, g_imageUnitHighWaterMark), unitCount)); MAJOR-2 UnbindSampler outside the window; MAJOR-3 all three seams share the stem "does not describe the binding it names" (the image seam latches to the validate point so the eager glBindImageTexture funnel does not spam - DEV-14, ACCEPTED); the two silent sites: F2 loud when exactly one target is recorded, P7 Fatal{ProtocolCorruption} on poison/verify and loud-once elsewhere (DEV-15, ACCEPTED - no unconditional abort on a shipped draw path); D-K2's fourth row is in E's arm too (D's resolver stays the owner); a v1 pGLContext token in a comment fixed (G13 = 1 file). Gates: G1 0/0/0/0, unit 1638 x 2, integration-gpu 491/491 on three arms, family 497/497 x 3, LEGACY_MEMOS=OFF build rc 0. The focused re-review of v2 is FOLDED into E's verification-round review on the integrated tree (after B v2 / C v2 / D v2).
ID-24 wire v3 landed (70e742a3); wire v2+v3 review, B v2 and D v2 launched on a TEMPORARY merged base
wire v3: FramebufferRecords (slot-indexed, gen-checked, StaleFramebufferRecordLookups), BoundFramebuffer[Draw|Read],
FramebufferRecordFor(handle) / DrawFramebuffer() / ReadFramebuffer() accessors, kMGPipeFramebufferTargetNamed = 3
local (retired by a static_assert when c0e lands), Bool on Create/Respecify, metadata respecify (W10: buffers
excluded, wider field set; W11 the level-pointer clause) - unit 1680 x 3, applier 130/130, names +58, nine
mutations red. Because B v2 and D v2 must build against wire's applier before wire is integrated, both were launched
on refs/heads/feat/disaggregated (9ea44389) + git merge refs/heads/p4a/wire (+ p4a/c0e when it exists) as a
TEMPORARY base; the integrator's rebase onto the pipe that contains wire/c0e drops those merge commits (identical
patch-ids are skipped). D v2 does NOT merge B/C (its verification round follows their integration). Integration
order stays c0e -> wire (with the Named-constant retirement edit done by the integrator in the wire tree after its
rebase, unit-tested, one commit) -> C v2 -> B v2 -> D v2 -> E verification -> D verification -> F v3.
ID-25 c0e integrated: pipe = e8502a61 (= 11446f35 rebased onto c0d; post-merge running, push follows)
c0e: MGPipeFramebufferTarget::Named = 3 (Count 4), the per-object-table comment on the enum and MGPFramebufferState
(only Target is binding-specific; ReadSurface = that framebuffer's own read buffer under every Target), the
metadata-respecify note beside MGPipeResourceRespecifyNeedsAck naming the storage-defining set (metadata = BindMask,
ImageBindableHint, GlNameForDiag; HasDefinedContent is storage-defining). Sizes 304 / 88 unchanged; G1 0/0/0/0;
unit 1638 x 2 on its base. The contract is now c0..c0e; the brief's section F carries the docs half.
- ID-25 addendum: post-merge on pipe G1 0/0/0/0, unit 1642 x 2, G5 rc 0, names 2608 == 2608, 0 removed / +20;
pushed: origin feat/disaggregated =
e8502a61.
ID-26 clientsp v2 landed (00a86d2a, three rework commits on 17db7598); focused re-review launched
C-M1 Live/Fresh split in the composite resolver; C-M2 the constants-key latch invalidated on re-issue + a counted
refusal in the program emitter; C-M3 per ID-17 ref-counted cache. B-facing API (clientsp-v2.md): #include <MG_Impl/Pipe/SamplerEmit.h>; MGPipeSamplerCsoCacheInstance().Acquire(sampler->GetAllSamplerParameters(), bytes)
(bytes is a Uint64& lvalue) -> handle + ref; .Release(previous) on re-acquire / texture death; B deletes its
MGPipeSlots().Acquire(SamplerCso, ...). Gates on its base: G1 0/0/0/0, unit 1668 x 3, names +46, emit suites
34/34, integration-gpu 966/966, verify 844/844, retrace 79/79, four negative controls red. C does not contain c0d
(9ea44389) or c0e - the rebase at integration brings them; the re-review names the tests to re-run.
ID-27 wire review v2 = ACCEPT WITH MAJORS (merge not blocked); wire integrating with the Named-constant retirement
Verified by the review: C1's three-arm clear (metadata -> whole-resource -> keyed erase, precedence right), the
per-object table + gen check ({0,1} admitted, {0,0} faults), acceptance Bools on every path with counters intact,
M4's field set == c0e's ruling, m1/m2/m3/m5 fixed, no bare packed decodes, no new static holders; three mutations
re-killed plus two of the reviewer's own. RULINGS: MAJOR-1 (stored State.Target is now the LAST emission's target,
Named included, so E's record.Target == Both skip at DirectGLES.cpp:2983 degrades to a redundant per-frame sync
after a DSA Named record) -> E's verification round replaces that test with BoundFramebuffer[Draw] == BoundFramebuffer[Read] (the applier's own fact). MINOR-1 (the UploadTarget half of the level-scoped erase key has
no gate - deleting it is green on all 74 tests) -> wire's verification round adds the cube-face case. The coarse
FramebufferSerial memo key (no per-record serial) is RECORDED for the optimisation list (perf, not gated).
Integration: p4a/wire rebased onto e8502a61 (head 8f1eaafa before the retirement commit), the local
kMGPipeFramebufferTargetNamed replaced by MGPipeFramebufferTarget::Named everywhere incl. PipeApply.cpp:2148/2153
(>= Count refusal) and the three test sites; the integrator's retirement commit is unit-tested and G1-checked
in the wire tree before the fast-forward.
ID-28 wire integrated and pushed: feat/disaggregated = 712c9467 (origin)
Twelve wire commits rebased onto e8502a61 + the integrator's retirement commit 712c9467. Post-merge on pipe: G1
0/0/0/0, unit 1684 x 2, P3a's eleven byte-identical, names 2650 == 2650, 0 removed / +62 vs baseline. The applier
is now real on pipe: B v2 / D v2 (built on the temporary merged base) rebase onto this head at integration - wire's
rebased commits carry the same patch-ids so the temporary merges drop out; if a rebase reports conflicts in
PipeApply.* the integrator resolves toward pipe. Next: C v2 (after its re-review) -> B v2 -> D v2.
ID-29 gates v2 landed (ecf9cb45, four rework commits on 2cb44039); focused re-review launched
All six majors proved on a scaffold: G7 exits 0 naming Layered and borderColorForm ({} not 0; verdict via a global);
SIGINT mid-rebuild -> repair ran, exit 130, tree clean; the composite leak case reads PeekPipeCompositeSlot
{HighWater,LiveCount,BandBase} and went red on all three live-allocator lanes when one composite leaked; the ABA
probe is directory-wide; the 0x9ff refusal matches one ERROR line naming both bits; the 0x5ff arm SKIPs by name.
Twelve minors fixed, F-m13 declared. Gates on its base: G1 0/0/0/0, unit 1636 x 3, names 2711 == 2711, +123,
p4a_untouched 17 rows + 8 controls, integration-gpu 1075/1075 both builds. NOT rebased on 712c9467 (moved mid-
round); the re-review reports the rebase; F integrates LAST (after D v2) and F v3 adds the white-box G9 (ID-19)
and wire's cube-face case is wire's own.
ID-30 clientsp re-review v2 = REWORK (one item, demonstrated); C v3 launched on 712c9467
Every v1 item closed (C-M2 the one-line invalidation; C-M3 exactly ID-17 - one reference per binding, ABA closed; all eight c0b items at the source; C-m7's refutation upheld). NEW C2-M1: the composite resolver is a process singleton keyed on the pipeline GL NAME, but names are per context (Core.h:658-659), so a make-current between two contexts holding the same name makes Observe emit delete_shader_state, clear the latch and free the OTHER context's live composite - probe red on v2, green with v1's Reset() restored; c0d makes it reachable. RULING: key the entry on (context identity, name) using the SAME context identity the rest of the pipe client already uses (no second notion); a destroyed context's entries released exactly once; the probe becomes a unit case + mutation. Three minors fixed in the same round. This is C's second rework (the limit); C integrates after v3 on the gates' strength plus the probe - no third review round unless v3 changes shape.
- CI: Test lane GREEN on
2cb44039(c0b) and on17db7598(c0c, two runs);9ea44389/e8502a61/712c9467queued behind the runner backlog (monitor re-armed).
ID-31 gates re-review v2 = ACCEPT WITH MINORS; F integrates last, F v3 at that turn
Re-proved by the reviewer: G7 tripped + exit 0; SIGINT -> repaired, tree clean, died by signal; the child>=128
path exits 2; one leaked band slot -> 3 failed of 8 on exactly the three live-allocator lanes (983042->983090);
the ABA conjunction directory-wide with no false positive on any in-flight tree; the F-M6 matcher matches Espryt
v2's exact sentence and rejects four near-misses; G5 17 rows + 8 controls rc 0 also on 712c9467; G14 0 removed /
+123; rebase onto 712c9467 clean (8/8, zero overlap; built: unit 1684, P4a families 165/165, ctest -N 2759).
F v3 owes: ID-19's white-box G9 (shape in the review), F-v2-m1 the symmetric refusal matcher (roles-swapped claim
unchecked), F-v2-m2 the 0x5ff arm goes GREEN not red at integration (esprytobj v2 already carries the row - fix the
wording), F-v2-m3 MagmaPipeAbaControlCoversKind uncalled, F-v2-m4 REPAIR_RC write-only, and a re-take of section 6
on the integrated tree.
ID-32 esprytobj v2 landed (ef6beb28, seven rework commits on a temporary base); focused re-review launched
Base = 9ea44389 + merge p4a/wire 70e742a3 + merge p4a/c0e 11446f35 + TEMPORARY commit cce81d19 (retires wire's
stand-in Named constant so the merged base compiles) - the integrator drops all of it with
git rebase --onto refs/heads/feat/disaggregated cce81d19 (replays only the seven rework commits onto 712c9467,
which already carries the retirement). Closed: the CRITICAL (FramebufferRecordFor + BoundFramebuffer[], refusal
before the bind), M-1 (server re-dirty arms the applier's PendingUploads), M-2 (storage from Desc + metadata
respecify), M-3/M-4 (attachments + four masks from MGPSurface; AdoptTwinByHandle wired; ReleaseByHandle removed),
ID-12, ID-15's row, E's item (4) handshake, the white-box G9 probe (green, red under mutation), ten minors. Gates:
G1 0/0/0/0 at all eight commits, unit 1686 x 2, D-N scripts rc 0, DirectVulkan 475/475, default arm 189/491 =
183 named refusals + 6 armless (0x1ff pin) - the pre-client shape; 0x1ff and 0 arms 6/491 identical sets.
ID-33 clientsp v3 landed (92dffb81) and integrated: pipe = 92dffb81 (post-merge running, push follows)
C v3 on 712c9467 (four MG_Test/Pipe files conflicted with wire's ae1a1c50 - resolved as an overlap-checked union,
0 of wire's 20 cases lost): C2-M1 keyed on (ContextId = ctx.GetTextureContextId(), the tree's never-reused per-
context id, PipelineName); a destroyed context's entries released once via ~ProgramObject's death path, Reset()
sweeps stranded ones (counter Sweeps); Fresh/HandleFor deleted; minors C2-m2/m3, nits. NEW defect fixed in C's
four test files (79b58198): the forked refusal drives unlinked the log while Initialize() held it open (child
wrote a deleted inode) - 6 verify-build failures -> 0. Gates on 712c9467: G1 0/0/0/0, unit 1717 x 3, names
2683 == 2683 / +95, emit 57/57 x 3, integration-gpu 491/491 default + 0x1ff (966/966 whole label), verify
844/844 zero Fatal, retrace 79/79. Integrated per ID-30 without a third review round (v3 kept its shape).
- Tool note (ID-33): the integrator's one-off scripts in the shared scratchpad wsl dir were deleted by an agent's
cleanup (only p4a_integrate_clientsp.sh survived). The post-merge check now lives durably as
~/w7/notes/tools/wsl_p4a_postmerge.sh+wsl_p4a_postmerge_bg.sh(detached launcher); integrator scripts go in notes/tools from now on, the scratchpad is for agents. - ID-33 addendum: post-merge on pipe G1 0/0/0/0, unit 1717 x 2, G5 rc 0, names 2683 == 2683, 0 removed / +95;
pushed: origin feat/disaggregated =
92dffb81. Next: B v2 (its result + focused review) -> D v2.
ID-34 esprytobj re-review v2 = ACCEPT WITH MINORS; integration recipe corrected; two items owed at D's verification round
All v1 items and rulings closed (FramebufferRecordFor + refusal above Bind, no record.Target test anywhere; packed-
key re-arm decoded both directions; seven storage macros incl. buffer-texture/multisample; the four masks off
MGPSurface with the D-N shas identical; G9 probe green x4). ID-32's rebase --onto cce81d19 is WRONG (it drops D's
six v1 commits, which sit below the temporary merges). CORRECT RECIPE (verified clean by the reviewer, builds, unit
1686 x 2, G1 0/0/0/0, 189/6/6 with identical arms, DirectVulkan 475/475): on a branch from pipe, git cherry-pick 9ea44389..8338c18f (D's six v1 commits as replayed on the temporary base) then git cherry-pick cce81d19..ef6beb28
(the seven rework commits). Owed before/at D's verification round: N-1 the refusal line's "left unbound" is false
(SyncAndBindFramebufferObject and BindCurrentFBO bind anyway - reword); N-2 the refusal census names six of eight
shapes - sampler (:12174) and renderbuffer (:12418) missing; the "zero refusals" check on the integrated tree must
count all EIGHT families. Seven further minors recorded for the round (AdoptTwinByHandle's refusals unreachable,
the remint-pull marker on the prevention path, raw SrcOffset, the silent empty-surface arm, high-byte-vs-packed key
asymmetry, unread HasDefinedContent, G9 in-function deferral only). D integrates AFTER B v2 (ID-2 order).
ID-35 clientfb v2 landed (d1215fbc); B and D integrate BACK-TO-BACK, the push waits for D
B's temporary base = 9ea44389 + wire 70e742a3 + c0e 11446f35 + clientsp 00a86d2a (a fourth merge - the ID-14/ID-17
cache call cannot compile against the contract's SamplerEmit.h stub; accepted). All ten items done: M1 region
tail, M2 three GL_Texture.cpp hooks + both-versions latch, M3 acceptance-gated clears, M4 metadata respecify,
ID-19(c) 16 Named sites in GL_Framebuffer.cpp (granted per ID-8), c0b/c0c reconciliation, D4 flipped with an EMPTY
expected-failing set, 6 minors fixed / 3 declared. Gates: G1 0/0/0/0, unit 1756 x 2, names +134, emit 148/148,
integration-gpu 0x1ff and 0 = 491/491, default 444/491, retrace(0x1ff) 79/79. RULING on its finding (1): the 47
default-arm failures / 109 verify / retrace 0 at the default mask have one proved cause - bit 10's acceptance-
gated dirty clear with NO server consumer (Espryt twins are not on B's base; 0x3ff/0x9ff/0x11ff are 491/491 and
removing only the clear gives 491/491). That is the designed dependency, not a defect: D lands immediately after
B; pipe's post-merge after B runs G1/unit/G5/G14 only; the full lanes and the PUSH happen after D. Finding (2) the
6 verify-lane unit failures are wire's refusal-drive cases fixed by C v3 (79b58198), absent from B's base - expected
to vanish on 92dffb81 (the re-review checks). B's replay onto pipe = the D-shaped recipe (v1 commits as they sit
on the temporary base, then the rework commits, merges skipped); the re-review verifies it and records any
test-file union.
ID-36 clientfb re-review v2 = ACCEPT WITH MINORS; B and D landing back-to-back (detached run)
Verified by replay onto 92dffb81: cherry-pick 5e87d3d2 5fac004a e2bf183a a390bb1a d013b7ec 4ec43194 d1215fbc,
SKIP 549ef30b (B's own retirement of wire's stand-in constant - already on pipe); one empty-base conflict in
e2bf183a (FramebufferEmitTest.cpp / TextureEmitTest.cpp) resolved as a union with B's block FIRST (theirs-first;
ours-first compiles but breaks 4ec43194); B's files byte-identical to d1215fbc afterwards. Lanes on the replayed
tree: unit 1759 x 3, emit 152/152, gpu default 444/491 (the 47 = B's appendix, D is the missing consumer - an
independent negative control confirmed no MG_Backend file reads PendingUploads/TextureResources), 0x1ff and 0x3ff
491/491; the six verify failures gone. Minors n1-n5 recorded (n2 framebuffer records latch on dispatch = wire's;
n3 sampler-CSO refs released only on slot recycle - D/E verification rounds watch the counters). Tooling:
notes/tools/wsl_p4a_integrate_replay.sh <slug> <range>... (+ UNION_THEIRS_FIRST=1 with
union_theirs_first.py), wsl_p4a_land_bd.sh chains B -> D -> post-merge into ~/w7/p4a-land-bd.log.
ID-37 B replay: the theirs-first union was wrong for the second conflict; reconciled from d1215fbc instead
The replay hit e2bf183a (FramebufferEmitTest.cpp) AND 4ec43194 (TextureEmitTest.cpp) - not the reviewer's single
e2bf183a in both - and the blind union of the second duplicated a block (redefinition errors). Rule: after replaying
a client package whose test suites share a file with wire's cases, take the package's FINAL version of those files
(d1215fbc is a pure superset of pipe's: +817 / +566, zero deletions) and re-apply the Named-spelling retirement;
that is what wsl_p4a_land_bd2.sh does, as one reconciliation commit on top of the seven replayed ones, unit- and
suite-tested before the fast-forward. CI: Test lane GREEN on 92dffb81 (clientsp v3).
ID-38 B and D landed: pipe = 17396216 (B = seven replayed + reconciliation 568d587a; D = thirteen replayed); quick gate running; NOT pushed yet
Post-merge on pipe: G1 0/0/0/0, unit 1761 x 2, P3a's eleven byte-identical, names 2727 == 2727, 0 removed / +139.
The push waits for the quick gate's verdict (the six armless .Handles aborts from the 0x1ff itest pins are F's
and stay until F lands - a known transient, so the decision is: push after F v3 lands unless the gate is otherwise
clean, in which case push now and let CI show the six). Verification rounds launched on the integrated tree:
E (p4a/esprytdraw rebased onto 17396216: the 27 decline-site flips, ID-27's BoundFramebuffer comparison, the
assumptions reconciled against D v2's real API, the family lanes, the eight-family refusal census) and D (new
worktree ~/w7/p4a-esprytobj3 on branch p4a/esprytobj3 from 17396216: N-1 wording, N-2 eight-family census, the
seven minors, the verification checklist, and the first real-path run whose default arm must reach ZERO named
refusals - a residual is a seam defect). F v3 (rebase, white-box G9, four minors, wire's cube-face erase-key case
under an integrator grant, section-6 re-take) follows E/D; then the final whole-diff review, the full gate, APKs,
device A/B, bench, docs.
ID-39 quick gate on 17396216: push default arm 74/966 red = 66 DirectVulkan + 6 pinned HandleRecycle + 2 DirectGLES sampler seams; c0f launched
Static half clean (three builds rc 0, unit 1761 x 3, pull integration-gpu 966/966, emitter/CSO suites 166/166,
G2 0, G14 +139, dirty-surface 27 controls). Push arm at 0x1fff: 74 failed - ALL 66 DirectVulkan failures are
texture-upload-shaped (ImageTargetKind loads, CopyImage*, NonCoreImageFormat, TextureView, PackedWordReadback,
GuiBatch, LayeredAttachment*, ClearTexImage...). ROOT CAUSE (B's own finding (1) applied to Magma): the P4a texture
family has NO "a backend registered the consumer" gate - P3a's buffers have one (PipeFill.cpp ~656:
MGPipeResourceSubsystemEnabled() = bit 7 AND a backend registered MGPipeResourceOps; Magma registers none, so
buffers stay legacy there) - so with kMGPipeWiredTextureSubsystem = 1 the client emits, the applier ACCEPTS, the
client clears its dirty flags on acceptance, and Magma's legacy path finds nothing to upload. RULING (c0f, one
commit on a branch from 17396216 by a fix agent with an integrator grant on BOTH files): (a) PipeFill.cpp - the
four P4a families' enable predicates and wants() rows require bit N AND wired AND "a backend registered
MGPipeResourceOps" (the existing per-backend signal; the texture family rides the resource rows per D-D1, and the
framebuffer/sampler/program families name texture handles so they follow); no emission at all on a backend
without the consumer; (b) PipeApply.cpp - belt: every P4a-family entry point returns accepted = false and counts
RefusedNoConsumer when no ops are registered, so a stray record can never clear a client flag. Tests: a unit case
per half; the DirectVulkan lane must return to all-green. The two DirectGLES failures (SampledSetStaleness
...ASamplerObjectCompletesTheTexture, IntegerBorderColor ...SamplerParameterIivBorderColourSurvives...) are the
B/C/D built-in-sampler seam - the D and E verification rounds classify them. The six HandleRecycle are the 0x1ff
pins (F). No push until c0f + F land.
- ID-39 addendum (gate complete): 0x1ff / 0x7f / 0 arms = 6/966 (the pinned HandleRecycle aborts only, so every DirectVulkan case passes with bit 10 off - the diagnosis holds); ESPRYT_DISABLE_INVALIDATE_FLUSH=1 = the same 74 (not flush-related); family regex 51/497 = the same DirectVulkan set; integration-verify 68/844 = the same 66 + 2 with ZERO Fatal; G7 and P3a-G7 controls trip and recover; CSO/ResourceSubsystem/ObjectSubsystem controls 10/10; G9's scenario and p4a_untouched_regions.sh are F's (land with it). G13 lists MG_State names in PipeApply.h at :37/:40 (forward declarations) and :999-:1000 (LinkArtifacts/SpirvArtifacts pointers on the shader-state entry point, D-H3's "the server reads the frontend's archive") - both OUTSIDE the ops block; accepted.
ID-40 c0f integrated: pipe = 29d51ab9 (post-merge running; still NOT pushed - F v3 next)
c0f (PipeFill.{h,cpp} + PipeApply.{h,cpp} + tests, one commit): kMGPipeP4aFamilySubsystems + P4aFamilyHasItsConsumer()
joined into FamilyIsLive / wants() / the DrainTextureSubData gate / the residual supplied; MGPipeP4aFamilyEmits
exported; the applier's NoP4aConsumer() counts RefusedNoConsumer at 15 entry points (silent - on Magma it is
the designed state); death paths not belted; g_resourceOps untouched by Reset/ReleaseObjectRecords so gate and
belt cannot disagree mid-frame. Gates on its base: symbols 0/0/0/0, unit 1762/1763, DirectVulkan 475/475 (from
409/475), DirectGLES 483/491 = the gate's exact eight unchanged. Mutations reported (walk-side conjuncts alone are
defence-in-depth). TextureEmitTest/FramebufferEmitTest register an empty ops table in main() (consumer-present arm).
E and D verification rounds run on 17396216-based trees and rebase onto this head at their integration (no file
overlap with c0f).
- ID-40 addendum: c0f's post-merge showed G2 pull 2728 != push 2729 - the no-consumer texture case lacked its
pull-build skip twin (TextureEmitTest.cpp's MGL_TEXTURE_EMIT_CLIENT_TEST_LIST). Integrator commits
245daca0(twin added, but a nested-heredoc escape wrote a literal backslash-n and the PULL build of the suite did not compile - the fast-forward went through on a green push suite only) andee31944b(the fix): both suites 34/34, the name present in both builds; pipe =ee31944b, post-merge re-running. Lesson: never edit sources through a quoted heredoc inside a quoted heredoc; write the editing script to its own file, and gate a fast-forward on BOTH builds' compile, not one.
ID-41 D verification round landed: pipe = 588b2277 (p4a/esprytobj3 56453191 rebased onto ee31944b); c0g launched
On 17396216 the default arm reached 485/491 - the six pinned .Handles aborts only - and the EIGHT-family refusal
census is ZERO over 477 per-test logs (notes/tools/wsl_p4a_refusal_census.sh: the console sink is compiled out
and the file sink truncates, so the census runs one ctest per name with its own log); StaleFramebufferRecordLookups
0; G9 4/4; verify zero Fatal; DirectVulkan still 409/475 on that tree = c0f, already on pipe. Seam defects: S-1
FIXED in D (BackendSamplerObject::SyncToBackend resolved the record by the twin's IDENTITY handle while C content-
addresses - every bound sampler's params refused, the two DirectGLES sampler scenarios of ID-39) - D now takes the
handle from the caller (push-only defaulted parameter, G1-safe) with a loud fallback; E owes ONE LINE at
DirectGLES.cpp:4021 (pass the handle, #if MOBILEGL_PIPE_PUSH guarded) - proved to close it, reverted, applied at
E's integration if E's round did not. S-2 = c0f (landed). S-3 (contract, c0g now): D-K2's fourth row refuses on
the SERVER but the client already cleared its flags, so 0x7ff = 438/491 - the client's texture family gate must
require every D-K2 dependency bit of the family (bit 11 and bit 7) as well as the consumer; generalised: a family
is live on the client only if all of its dependency bits are set (PipeFill.cpp ~885). S-4 fixed (ParamsSerial == 0
read as a malformed null BuiltinSampler). Follow-up recorded: G4's retain-mode comparison cannot run -
PipeTexelRetainMb has no reader (post-P4a item).
ID-42 c0g integrated (post-merge running): the client's D-K2 dependency table
c0g (29bd4d2c on p4a/c0f, rebased on 588b2277): kMGPipeP4aFamilyDependencies (bit 9 -> 10; bit 10 -> 7|11;
bit 11 -> 10; bit 12 -> none; NON-transitive, mirroring Espryt's resolvers bit for bit; mirror pairs stated;
five static_asserts) consulted at c0f's four sites; every mask arm (0x7ff 438 -> 485, 0x1fff/0x3ff/0x5ff/0x9ff/
0xdff all 485/491 with the identical pinned-six list) and DirectVulkan 475/475; unit 1766 x 2, names 2732 == 2732;
M2 mutation fails to compile (static_assert). NOTE: TextureScope/FramebufferScope fixtures were arming masks
D-K2 forbids (no bit 7) - both now arm kMGPipeSubsystemResources.
- ID-42 addendum: post-merge on pipe
29bd4d2cG1 0/0/0/0, unit 1766 x 2, G5 rc 0, names 2732 == 2732, 0 removed / +144. Still unpushed (E round + F v3 pending). (A comment-condensing request was queued here and then CANCELLED by the user.)
ID-43 E verification round landed on p4a/esprytdraw (0cd0d1ec, four commits on ee31944b); landing with two integrator patches; fable seam audit launched
E: A4 + wire v3 accessors, ID-27's bound-handle comparison (no record.Target reader left), F2/F4/F5/F7 loud, the
remaining flips (T2/I2/S2/P2/P3/P4/P6; T2/S2 scoped to draws touching a unit). Default arm 483/491 on ee31944b
= the pinned six + the two sampler scenarios (fixed by D3's S-1 once E's line lands), 0x7ff 438 (= c0g),
DirectVulkan 475/475, verify 842/844 zero Fatal, unit 1763 x 3, G1 0/0/0/0, both untouched scripts rc 0.
Findings: SD-0 (E's own regression, bisected to commit e3 and to the Level field): NewShaderImages' shutter
mixes only textureContent/textureParams/programImages, none of which moves when glBindImageTexture re-binds
the same texture with a different level, so the applier keeps the previous bind's Level and e3 (the first reader
of the field) paints the wrong mip (create-indirect ssim 0.887) - VERIFIED four-line patch for Tracker.h (mix
ctx.GetTextureBindGeneration() in, the NewSamplers shape) - applied by the integrator (contract grant) together
with S-1's DirectGLES.cpp:4021 call-site line (wsl_p4a_land_e.sh / p4a_e_patches.py, one commit on top of
E's rebased four, gated on both builds + unit + the two sampler scenarios + the create-indirect retrace + the
dirty-surface check). SD-1/SD-3 ("C never emits create_sampler_state"; built-in sampler CSO refuses x3) = the
S-1 seam as seen from E's base (no D3 there) - re-checked after landing. SD-4: set_shader_images never emitted
for imageBuffer x5 -> C's (final-review fix list). SD-5 = c0g. The ctest -V census is a FALSE ZERO (console
sink compiled out) - D's per-test-log tool is the valid one, and its phrase list is short by two shapes whose
sentence straddles a string-literal line break (:7584, :7895) - fix in the census tool. Handed up: retrace
62/79 at the default mask WITHOUT E on ee31944b (17 Iris shader-pack traces wrong, no refusal) - D3's S-1 is
the prime suspect (shader packs are sampler-heavy); the fable seam audit (user rule 2026-09-08: too many
reworks -> a fable research agent) verifies on the landed head.
- TRAP (E, cost two runs):
git lfs checkoutis DESTRUCTIVE on this box when the LFS content is not local - it replaces every real fixture with its 130-byte pointer (retrace 1/79, ssim=None). The ONLY safe recipe is wsl_tree.sh's: copy from ~/w7/pipe/tools/trace_replay/fixtures/ +git update-index --assume-unchanged; andgit reset --hardundoes assume-unchanged, so re-copy immediately before every retrace. ID-13's instruction to reviewers is WITHDRAWN. - ID-43 addendum, USER RULE (2026-09-08, binding for every agent): GitHub LFS can hit its quota - do not pull LFS at
all unless a fixture is genuinely needed; when it is, FIRST copy from the hydrated local copy
(~/w7/pipe/tools/trace_replay/fixtures/ +
git update-index --assume-unchanged, wsl_tree.sh's recipe), SECOND use the CI script.github/scripts/fetch-trace-fixture-lfs.sh <case> [dir](mirrors git.hit.moe then repo.miawa.cn before any LFS fallback).git lfs checkout/git lfs pullare forbidden in package and review trees. Pointer test: the first 80 bytes start withversion https://git-lfs.github.com/spec/v1.
ID-44 E landed with the two integrator patches: pipe = 41b79050 (E's four rebased on 29bd4d2c + 41b79050)
The patch commit (S-1's DirectGLES.cpp call site under #if MOBILEGL_PIPE_PUSH, passing
MGPipeApplier().BoundSamplerStates[unit]; SD-0's NewShaderImages shutter mixing ctx.GetTextureBindGeneration()):
dirty-surface --check rc 0 (no table change), G1 0/0/0/0, unit 1766 x 2, the two sampler scenarios' suites 16/16,
create-indirect retrace 2/2 (was ssim 0.887 on E's tree). Post-merge on pipe: G1 0, unit 1766 x 2, G5 rc 0, names
2732 == 2732, 0 removed / +144. Remaining before the push: F v3 (pins, G9 white-box, minors) + the full gate with
the three retrace sweeps on the final tree; the fable seam audit feeds the final whole-diff review.
- USER RULE (2026-09-08): a package that needs more than two rework rounds goes to a fable agent (a read-only root-cause study or the round itself), not another opus rework. In P4a that threshold was crossed by the contract (c0b..c0g) - the fable seam audit is the response; any package needing a v4 goes to fable directly.
ID-45 fable seam audit (fable-seam-audit.md): four seams to fix before close, three recorded; Iris = S-1, now 20/20; fable fix round launched
Proven (class 6 = a setter changes a record field no emitter-read shutter sees; class 2 = identity vs content):
F-3 set_framebuffer_state inlines each attachment's InternalFormat at emission (FramebufferEmit.h:115-144); a
texture/renderbuffer respecify WHILE ATTACHED moves TextureContent/TextureParams, never bit 9's aggregate, so D's
four cross-object masks (Managers.cpp:8678-8723, :9328-9353) read a stale surface while the legacy arm reads the
frontend at the same re-sync - public GL: glTexImage2D(RGB8) -> attach -> draw -> glTexImage2D(RGBA8) -> alpha
diverges; no scenario. F-1/F-1b bit 12's shutter (Tracker.h:494) is textureContent xor bindGen but EmitSamplerViews
resolves the set for the CURRENT PROGRAM (SamplerEmit.h:742-767): a glUseProgram alone never re-emits; E's epoch
(DirectGLES.cpp:1868) keys the texture sync list on it -> an empty-slot bind under P1 then a switch to P2 leaves
the texture never synced, no refusal. F-4 BindCurrentUnitSamplers' record arm FindByHandle(csoHandle) on the
identity-keyed twin registry (DirectGLES.cpp:4847) never finds - latent until the pre-handle program pass is
retired (S-1 one loop over). F-2 bit 14's plain-program arm mixes a per-program counter without the lifetime id
(program switch invisible; loud-ish fallback). Recorded for later: SD-5's non-transitive dependency table on both
sides, three encodings living only in package headers, the latch-before-acceptance of set_texture_params.
Iris: bisect ee31944b 3/20 -> 1e8cdc59 (D3's S-1) 20/20; every failing log carried "sampler N has no applier
record" - the backend-minted raw-depth-fetch sampler refused by the identity lookup; E's "zero refusals" was
itest-only. 41b79050: 20/20, zero refusal lines. Brief root causes: D-K4's "shutters are already computed" (no
record-field -> setter -> shutter table), no per-kind handle-rule table for BOTH sides, dependency rules stated
about masks not emissions, no census recipe for retrace runs -> the P4b brief states all four up front.
Per the user's rule (rework > 2 -> fable): ONE fable fix agent takes F-1/F-1b, F-2, F-3, F-4 + E's SD-4
(imageBuffer set_shader_images) + the census tool's two missing phrases, on a branch from 41b79050, with an
integrator grant on Tracker.h / SamplerEmit.h / ImageEmit.h / FramebufferEmit.h / DirectGLES.cpp / Managers.cpp
and a NEW scenario file (to stay clear of F v3's files). The final whole-diff review will be fable as well.
ID-46 gates v3 landed: pipe = 4678519f (15 commits on 41b79050; post-merge running); fable fix round launched from it
F v3: 0x1fff pins (the six .Handles aborts closed), G9's white-box half (new Harness/PipeApplyPeek.{h,cpp};
mutation 4/4 red), wire MINOR-1's cube-face case, c0f's belt pinned on both backends, F-v2-m1..m4. On 41b79050:
integration-gpu 1079/1079 at 0x1fff / 0x1ff / 0 and pull (DirectVulkan 540/540, DirectGLES 539/539), verify
884/884 zero Fatal, unit 1767 x 3, G1 0/0/0/0, G2 2846 == 2846, G14 +258, G5 17 rows + 8 controls, all three
negative controls rc 0, HandleRecycle 144/180/144 + .Handles 36/36, ObjectSubsystemControl 14/14. Four defects
the gates caught during the round were fixed upstream already (0x5ff emit disagreement = c0g, the G2-breaking
c0f name = ee31944b, the two-case-wider sampler seam = S-1, D-E3's read-attachment gap = E). The fable fix round
(p4a/fablefix from 4678519f: F-1/F-1b, F-2, F-3, F-4, SD-4, the census phrases, the shutter table comment) is the
last code round; then the full gate with the three retrace sweeps, the push, the fable whole-diff review, APKs,
device A/B, bench, docs.
- ID-46 addendum: post-merge on pipe
4678519fG1 0/0/0/0, unit 1767 x 2, G5 rc 0, names 2846 == 2846, 0 removed / +258. Worktrees left: p4a-c0f (c0g's branch), p4a-esprytdraw, p4a-gates, p4a-fablefix (in flight). - USER (2026-09-08): after P4a closes (gate, push, final review, APKs, device A/B, bench, docs) STOP - do not start P5.
ID-47 fable fix round landed: pipe = 6035c9d7 = the P4a CODE HEAD; full gate + fable whole-diff review + Release APK build launched in parallel
fablefix (seven commits from 4678519f): F-3 the framebuffer aggregate bumped from PipePublishDescriptor (texture +
renderbuffer, no counter added) + the pre-handle arm's in-place regeneration / renderbuffer re-storage bump compiled
PUSH-ONLY (G1 caught the pull-side version as 0/0/2/0 - the pre-P4a monolith hole stays in the pull build and is
RECOMMENDED AS A SEPARATE dev COMMIT after P4a; the texture cases decline by name there, the renderbuffer case
asserts on the handle arm only); F-1/F-1b bit 12 mixes program identity x link x backend state (pipeline-aware) +
the params aggregate (F-1b reproduced black 144/144, closed without widening E's epoch); F-2/SD-4 bit 14's plain
arm mixes lifetime id x link version (SD-4 was this, not reflection); F-4 ResolveSamplerCsoTwin on the content-
addressed record shared by the record arm and the program pass; the record-field -> setter -> shutter table in
Tracker.h; the census tool's two straddling shapes. On 6035c9d7: G1 0/0/0/0, both untouched scripts rc 0 (+8
controls), gen_pipe / dirty-surface (27 controls) / include closure rc 0, unit 1772 x 3, G2 2863 == 2863, G14
+275, integration-gpu 1091/1091 at 0x1fff / 0x1ff / 0 / pull, DirectVulkan 546/546, verify 896/896 zero Fatal,
retrace 79/79, retrace census 0 on 13 needles, itest census 0. Deviations accepted: its own peek TU (second CMake
line, F's file untouched); seven commits. USER: after P4a closes, STOP (no P5).
ID-48 full gate on 6035c9d7 green through verify; APKs built; device A/B running; the Oppo install flow fixed (pipe = a38bdab4, tools only)
Gate so far: builds x3 rc 0; G1 0/0/0/0; G5 P3a eleven + P4a 17 rows (8 controls) rc 0; HandleRecycle (verify)
180/180; emitter/CSO suites 172/172; G2 0; G14 +275; unit 1772 x 3; pull integration-gpu 1091/1091; push at
0x1fff / 0 / 0x1ff / 0x7f / ESPRYT_DISABLE_INVALIDATE_FLUSH=1 all 1091/1091 (the "rerun 53" lines are a stale
LastTestsFailed.log - nothing failed on the first pass); family 497/497; G9 8/8; TextureUploadShape 3/3
(recorded); three negative controls rc 0 (G7 exit 0 naming Layered + borderColorForm); CSO/Resource/
ObjectSubsystemControl 24/24 + 0x9ff arm 14/14; controls 184/184; integration-verify 896/896, zero Fatal. The
three retrace sweeps are running. APKs: ~/w7/notes/p4a/apk/trace-{pull,push}.apk (8504077 / 8622861 bytes,
debug-signed, MGPipe strings 3 / 107, mpr present in push) - Release size class (not the -O0 trap).
Device A/B: Xiaomi running (session pinned 21:38:16 after cool-down; 26.3 GLES pull nofinish p50 10.76 ms). Oppo's
first attempt died the P3a way - the tapper hit the ColorOS dialog while the streamed install was in flight and adb
dropped (device not found for runs 2/3). FIX: MOBILEGL_TRACE_SKIP_INSTALL=1 in android-plugin/trace-replay-ci.sh
(pipe a38bdab4, tools-only commit on top of the gated 6035c9d7; the Windows runner tree fast-forwarded with
GIT_LFS_SKIP_SMUDGE=1) and, on the Oppo only, the A/B script installs each arm itself (tapper handles the dialog,
adb wait-for-device, pm path poll up to 90 s) before running the case with the install skipped. Both A/B
scripts also gained the Xiaomi cool-down wait (< 42 C), the rd12/Magma-on-Xiaomi skip (ID-6) and the tapper.
- ID-48 addendum (tooling lesson):
pkill -ffrom the Git Bash tool does NOT kill a detached A/B script on Windows (the old Oppo run kept going, holding the device lock while the restarted one waited); enumerate with PowerShellGet-CimInstance Win32_Processfiltered on the command line (excluding the query's own process) and kill withtaskkill /PID <pid> /T /F; awmic ... like '%<pattern>%'query from the Bash tool matches the tool's own shell command line and killing it aborts the tool call. Remove the device lock dir by hand afterwards. - A/B note: Xiaomi rd12 GLES push nofinish ran with pin=1/1 (DRIFT before and after - the thermal clamp, ID-8); p50 12.94 vs pull 8.24 ms is NOT a valid pair - re-run the rd12 push arms on the Xiaomi after a cool-down at the end of the session (single-case re-run, package left installed); ab_reduce3.py must exclude drifted samples.
ID-49 FULL GATE GREEN on 6035c9d7; pushed: origin feat/disaggregated = a38bdab4 (= 6035c9d7 + the tools-only harness commit)
Gate: every part green (ID-48's static + lanes) plus the three retrace sweeps - verify 79/79 with 79 armed and zero
Fatal, push 79/79, G3b named 12/12. Device A/B: Xiaomi session 1 finished with the thermal clamp drifting every
sample from rd12 push onwards (pin 1/1) - 14 of 36 samples clean; the scripts gained a per-case cool-down (< 42 C,
up to 15 min) + re-pin and a clean-pin skip, and session 2 is redoing the 22 drifted samples. Oppo: session 2 (pre-
install flow) running. Desktop bench (wsl_p4a_bench.sh, idle box) launched; the fable final review is relaunched
(the first attempt died at start on an API ECONNRESET) with its build/mutation experiments deferred until the bench
is done so the bench stays on an idle box.
- A/B tooling (ID-49 addendum): the Xiaomi cool-down loop timed out once at a reading below the threshold (the
raw
su -c catoutput is not always clean digits right after a reboot); both scripts now read the zone withtr -cd "0-9"and compare${T:-99999} -lt 42000. Session 2 killed by PID (PowerShell), lock released, session 3 launched (log ab-35d0befa-3.log); already-clean samples are skipped, drifted ones redone.
ID-50 desktop DriverBench on 6035c9d7 (recorded, not gated; ~/w7/notes/p4a/bench/driverbench.{csv,md})
mc_vanilla_draw ns/draw: espryt T1 = push(0x1fff) - pull = +1269.7; T2 = push(0x1ff) - pull = +1135.8; T1 - T2 (what P4a added) = +133.9; PIPE_PUSH=0 - pull = +697.8; no-CSO-addressing - push = +276.6. magma T1 = +139.1; T2 = -191.7; T1 - T2 = +330.8 (Magma emits nothing for P4a families after c0f - the delta is the tracker's extra shutter work plus noise; the pull/push spread on magma is within its repeat noise). mc_state_toggle: espryt pull 24695 / push 26265 (+1570), magma 36225 / 37149 (+923); mc_pass_switch: espryt +7695, magma -5609. Compared with P3a's table (espryt T1 +1047.7, T2 +348.5): the P3a-boundary arm itself moved (+1136 vs +349) because the 0x1ff arm now also runs P4a's tracker/shutter work with the families off - to be stated in MEASUREMENTS.md as a caveat (T2 is no longer a pure P3a number).
ID-51 device A/B, Xiaomi (Adreno 830) main pairs complete - 36/36 clean pins after session 3; Release APKs; push - pull p50 (ms, nofinish / finish)
26.3 GLES +1.67 / +1.57 (10.76 -> 12.43; +15%); 26.3 Magma +0.91 / +0.86 (Magma emits nothing for P4a families after c0f: this is the P2+P3a boundary + the tracker); rd12 GLES +2.97 / +3.03 (8.24 -> 11.20; +36% - the largest; the 0x1ff arm will say how much is P4a's); sodium GLES +0.06 / +0.16, sodium Magma -0.42 / +0.08; vanilla GLES +0.50 / +0.49 (2.34 -> 2.84; +21%); vanilla Magma +0.69 / -0.16 (noisy); iris-bsl GLES +0.64 / -0.36 (p50 ~1.3-1.9 ms with 300+ ms p99 spikes on both arms - the shader-pack compile stalls dominate). Oppo main session running (pre-install flow OK); Xiaomi 0x1ff arm running; then the three-arm reduce (ab_reduce3) and the per-case MGPipe stats. Performance is recorded, not gated (user rule); rd12's +36% goes on the optimisation list with the P3a +17-19 pts it stacks on.
ID-52 final whole-diff review (fable) = REWORK: two criticals + one major; fable fix round launched (B is past two reworks)
C-1 (critical, TextureEmit.h:955-960): every respecify calls MGPipeApplyResourceRespecify(desc, nullptr) - no
emitter builds an MGPRespecifiedLevel - so a per-level glTexImageD / glGenerateMipmap grow takes the whole-
resource arm (PipeApply.cpp:1684) and drops every accepted-but-unconsumed pending upload while the client flag
is already clear: L0; draw(other); L1; draw(T) and L0; draw(other); glGenerateMipmap; draw(T) read 100% black
at 0x1fff, red at 0x1ff (three controls red on both) - wire-v3 §5 item 5 required B to pass the level and no review
checked it. C-2 (critical): a dead-but-unrecycled texture handle still resolves to the freed ITextureObject
(ResolveTexture checks GenOfSlot, which moves only at re-acquire; the death helper does not forward to the emitter;
the drain list survives death): glTexImage2D; glDeleteTextures; draw -> SIGABRT "pure virtual method called" on
the handle arm. M-A (major): no producer of kMGPipeBindSampler / kMGPipeBindShaderImage - ImageBindableHint is
always 0, D-A4's remint mitigation undelivered, ROADMAP.md:85's number unmeasurable. Minors: F-7 latch-before-
acceptance for set_texture_params; Config.h omits the 10 -> 11 dependency row; "ORed" vs replace-whole comment;
F-6 encodings in package headers; the CPU three-channel mipmap re-arm gap; G9 blind when row + mark drop together.
Verified green by the review: G1 at TU granularity, G5 red on a statement mutation, G7 both controls, G9 red on a
decidable row, first-attempt mutations red, TEMPORARY test.yml:9-12 trigger still present (remove before dev), all
D.5 placeholders unfilled (§6). RULING: one fable fix round on a branch from a38bdab4 - C-1 (B passes the level;
a unit case + the two llvmpipe scenarios as itest), C-2 (death path: the death helper forwards to the emitter,
the drain list drops the dead entry, ResolveTexture refuses a dead slot; the delete-then-draw scenario), M-A (the
bind-bit producers at glBindSampler / glBindImageTexture and wherever D-A4 names; ImageBindableHint measurable),
the cheap minors; then the review's §8 re-runs + quick gate + push retrace, a focused fable re-review, and the
device A/B GLES pairs redone on APKs built from the fixed head (C-1 drops uploads, which biases the push arm).
- CI: Test lane GREEN on
a38bdab4(the pushed P4a head + harness switch).
ID-53 Xiaomi 0x1ff arm complete; Oppo session hung on a re-install - hardened and relaunched (session 3)
Xiaomi push(0x1ff) p50 nofinish (ms): 26.3 GLES 11.86 (pull 10.76 / push 12.43 -> P4a's own share +0.57 of +1.67),
26.3 Magma 11.53 (= its 0x1fff arm, no P4a consumer), rd12 GLES 10.84 (pull 8.24 / push 11.20 -> P4a +0.36 of
+2.96; the P2+P3a boundary carries +2.60), sodium GLES 1.40 / Magma 1.01, vanilla GLES 2.72 (pull 2.34 / push 2.84
-> P4a +0.12 of +0.50), vanilla Magma 1.15, iris-bsl GLES 1.86 / Magma 0.78. Oppo session 2 hung at 21:49:45 in
the pre-install adb install -r of the push arm - the ColorOS dialog for a REPLACE install was never tapped (the
tapper matched InstallGuideActivity only) and the streamed install session dangled for 1.5 h with the device
back on its launcher. Hardening (both scripts): the tapper matches InstallGuide|PackageInstaller|installer|appdetail;
the pre-install runs under timeout 150 with three attempts (Back key + wait-for-device between them). The hung
tree killed by PID (54088 adb + 48100/48936/48268), lock released, mixed results moved to 3B159D009VZ00000-old;
session 3 launched (log ab-3B159D009VZ00000-2.log). Monitors for the dead logs are left idle.
ID-54 Xiaomi three-arm table (Release APKs from 6035c9d7, pre-finalfix; nofinish p50 ms: pull / 0x1ff / 0x1fff, d P3a-boundary / d P4a total)
26.3 GLES 10.76 / 11.86 / 12.43 (+10.2% / +15.5%); 26.3 Magma 10.67 / 11.53 / 11.58 (+8.0% / +8.5%); rd12 GLES 8.24 / 10.84 / 11.20 (+31.6% / +36.0%); sodium GLES 1.38 / 1.40 / 1.44 (+1.3% / +4.3%); vanilla GLES 2.34 / 2.72 / 2.84 (+16.4% / +21.4%); iris-bsl GLES 1.30 / 1.86 / 1.95 (+42.7% / +49.2% on a ~1.5 ms p50 with 317 ms p99 compile stalls - shape-dominated). Magma rows other than 26.3 are noise-dominated at these frame times (sodium Magma push 0.52 vs 1ff 1.01; vanilla Magma pull-nofinish 1.05 vs pull-finish 1.97) and are reported with that caveat; Magma emits nothing for P4a families (c0f), so its "P4a" arm is the tracker only. So P4a's own share on Espryt is +0.5-0.6 ms on 26.3 and +0.1-0.4 ms elsewhere; the P2+P3a boundary carries the rest (rd12 +2.6 ms). Raw results and the table copied to ~/w7/notes/p4a/ab/prefix-a38bdab4/ (tool: ab_reduce3_p4a.py, the P3a reducer re-labelled for push1ff). These are the PRE-finalfix numbers: the GLES pairs will be re-measured on APKs built from the fixed head (C-1 dropped uploads on the push arm, which flatters it).
ID-55 final fix round integrated: pipe = 8c458cd5 (five commits; post-merge running; fable re-review running; APKs rebuilding)
finalfix (from a38bdab4): 173f1dd2 C-1 (storage entry points state their scope - one level / chain cut / whole
resource - the emitter builds MGPRespecifiedLevel with the drain's packed target, per-level calls never deduped,
the applier drops exactly the named level), a690032f C-2 (every death helper forwards to its emitter between the
wire delete and the free; ResolveTexture refuses a dead slot loudly; sticky-mask producers stamp the generation;
delete-then-use pinned per kind, for a recycled slot, under MALLOC_PERTURB_ on both backends), 9f60aadc M-A (the
SAMPLER bit from view resolution, SHADER_IMAGE at glBindImageTexture through a contract door; ImageBindableHint
precedes the first sync; tex-remint-pulls = ROADMAP:85's number), c2c6a655 minors (F-7 latch-on-acceptance with
a Bool return, Config.h row, the comment), 8c458cd5 (census residual: default textures born before consumer
registration heal from their first set_texture_params - the re-review judges whether that is a legitimate late
birth). Gate on 8c458cd5 (the agent's): G1 0/0/0/0, G5 x4 rc 0, gen tools rc 0, unit 1785 x 3, names 2902 ==
2902, +314, integration-gpu 1117/1117 on seven arms (DirectVulkan 559/559), verify 920/920 zero Fatal, retrace
79/79, retrace census 0 on 16 needles, itest census 0/13, negative controls tripped. Declared m-4 (P4b), m-5,
m-6, m-7 benign, m-8 not reproduced. No Co-Authored-By anywhere (user rule re-confirmed 2026-09-09). Pre-fix APKs
kept at ~/w7/notes/p4a/apk-prefix/; the Release APKs are being rebuilt from 8c458cd5 for the GLES A/B redo.
Lesson (memory subagent-parked-notifications): a "finished" notification whose result says "waiting on the
build/gate" is a PARKED agent, not a dead one - the continuation agent launched for it was stopped.
- ID-55 addendum: post-merge on pipe G1 0/0/0/0, unit 1785 x 2, G5 rc 0, names 2902 == 2902, 0 removed / +314;
pushed: origin feat/disaggregated =
8c458cd5; the Windows runner tree fast-forwarded to it (LFS smudge off).
ID-56 STOPPED HERE by the user (2026-09-09 ~01:20): origin feat/disaggregated = 8c458cd5
Done: contract c0..c0g, wire v3, clientsp v3, clientfb v2, esprytobj v3, esprytdraw v3 (+ the two integrator
patches), gates v3, the fable seam-audit fix round, the final-review fix round - all integrated, the full gate
green on 6035c9d7 and the fix agent's own full gate green on 8c458cd5 (itest 1117/1117 on seven arms, verify
920/920, retrace 79/79, census 0); CI green through a38bdab4 (8c458cd5's run pending); Release APKs from 6035c9d7;
device three-arm A/B on both devices (pre-fix APKs) in ~/w7/notes/p4a/ab/prefix-a38bdab4/ + three-arm-prefix.md;
desktop bench in ~/w7/notes/p4a/bench/. NOT done, by the user's decision to stop: the fable focused re-review of the
five fix commits (stopped mid-read), the GLES A/B redo on post-fix APKs (the rebuild was stopped; the pre-fix
numbers flatter the push arm slightly because C-1 dropped uploads), docs D.5 (README status line, ROADMAP P4a row,
MEASUREMENTS §22+ - all placeholders unfilled), the TEMPORARY CI trigger branch in test.yml/apk.yml (must be
removed before merging to dev), worktrees p4a-{c0f,esprytdraw,fablefix,finalfix,gates} and 47 logs under ~/w7
left in place. Known open items are listed in the closing summary to the user and in final-review-v1.md §recorded,
fable-seam-audit.md §D, finalfix-v1.md §declared.
ID-57 P4a 收尾(2026-09-11,新设备):docs D.5 写作中,红米上重测
用户接了一台红米 2f7cbe2e(Redmi M332BF "warsaw",SM8750 / Adreno 830v2、Magisk root、带主动风扇)并规定:之后实验只用它,其他 adb 设备一律不碰;同时指出本分支的活应当在 MobileGL-disagg worktree(feat/disaggregated)而不是主 dev 树上做——dev 树上那两处调试 fprintf 插桩已按用户指示丢弃。
- 设备工装:
pin_device.sh加了2f7cbe2e条目。同 SoC 同 OPP(1958400/1555200两个战役定频点都在),唯一差别是 policy6 的 stock 上限 3072000(旧机 2841600)。GPU 定频点是 1050 MHz 不是 1100:厂商把kgsl-3d0/thermal_pwrlevel永久钉在 1,max_gpuclk因此 1050,root 写 0 无效(33 °C + 风扇 16k rpm 下验证过)——旧小米那个间歇性的"pwrlevel 0 但 gpuclk=1050 DRIFT"几乎肯定就是它。 - 风扇:
/sys/class/xm_power/hw_monitor/pwm_fan,A/B 全程 level 2(~14.5k rpm)恒定——它是常量不是处理项,两臂同风量。降温从 20–30 分钟变成 <1 分钟。档位表与两个坑(101–104 debug 锁存要写 100 退出、pwm_duty是绕过档位表的原始旋钮、状态只能看real_speed)记在 memoryfcl-device-testing。 - 新 A/B 驱动:
notes/tools/p4a_ab_redmi.sh(序列号硬保护,只认2f7cbe2e;风扇常开;无安装点击器——这台adb install直接过;不跳过 rd12/Magma)。实测:rd12 在 DirectVulkan 上 rc=1 照样崩,和旧小米一致,确认是dev侧问题不是设备个例。 - APK 重建自
8c458cd5:pull 8504077 B(与修复前同字节,正是 G1 要的 pull 不动)、push 8626957 B / 109 条 MGPipe 串。修复前那对存~/w7/notes/p4a/apk-prefix/。 - 补测:79 例 retrace 带
MOBILEGL_PIPE_STATS=1各跑一遍 push/pull 臂,为的是 ROADMAP 开放问题 2(trp= 780 窗口里 2 次,只在两条 Iris 光影用例的 DirectGLES 侧)、§20 读法 (5)(新的csob-blob字节类:DirectGLES 27 例中位 2928 B/帧、rd12 峰值 1.06 MB/帧、DirectVulkan 恒 0 = 消费者门的量化)与 D.4.3 的上传形状头条。DriverBench 在8c458cd5上重跑(原 ID-50 的数出自6035c9d7)。 - docs D.5 进度:README 状态行与文件地图、ROADMAP 的 P4a 行/P3b-P4b 行/39 天检查点/通用纪律(补上"不顺手修 dev bug",让课题 17 的
ROADMAP.md:7引用成真)/开放问题 2、8、17、ARCHITECTURE 十二处、MEASUREMENTS 读法 (5) 与g_uploadRing旁的第二条同类项、新增 §22(五部分门两个头对照)与 §23(缝的分类——九类,每类给症状与预防,是这一波最值钱的产出)均已写;§24(设备三臂表 + p99 + 上传形状)与 §25(T1/T2/T3)等测完补。check_doc_citations.py97 处引用 0 问题。 - 清理踩坑:
rm ~/w7/p4a-*.log把两个正在被写的 retrace 日志删了,导致以日志标记为条件的 bench 等待者永远等待(输出目录无损);改成while pgrep -f retrace_gate...; do sleep 30; done等进程。已记进 memorytemp-file-hygiene。 - ID-57 addendum: P4a 收官,docs D.5 已推送:origin feat/disaggregated =
a29807cc(8c458cd5代码 + 一条 docs 提交,citation lint 100 处 0 问题,无任何 attribution 行)。红米三臂表与原始结果在~/w7/notes/p4a/ab-redmi/(40/40 干净 pin),bench 在~/w7/notes/p4a/bench/(重跑于 8c458cd5)。桌面 bench 分辨不出 P4a 自身(两轮 T1−T2 = +133.9 与 +11.4 ns/draw,而两臂绝对值各自漂 ~200 ns),可引用的是 T1 ≈ +1.1 µs/draw 的总边界与设备侧三臂表。设备侧 P4a 自身 = +3.4~+5.7 个百分点(0.05–0.38 ms/帧),Magma 两臂差在噪声内 = c0f 消费者门的读数。上传形状 pull/push 全语料只差 +2(正好是 2 次 trp)。留给优化阶段的第一条线索:设备上 26.3 Espryt 的sve≈ draw 数(9143/9138),桌面同 fixture 只有 ~0.07/draw。