# 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 [verify]`, `wsl_integrate.sh p4a ...`, `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/-v.md` / `-review-v.md`; A/B `~/w7/notes/p4a/ab*`; bench `~/w7/notes/p4a/bench`. Package logs `~/w7/p4a--*.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-/...` (files yes, mkdir no). Commands through a script file `/wsl/.sh` (LF) run as `MSYS_NO_PATHCONV=1 wsl.exe -d Arch -- bash /mnt/c//wsl/.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=0` on 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 bb2a236d trap) - 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 `clang++`. Contract review v1 in flight; the six sibling trees were reset to the tag and are rebuilding (~/w7/p4a-trees2.log). ## 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 packs `low byte = MGPipeResourceTarget, high byte = TextureUploadTarget`; wire's `SubDataNamesABuffer` keys on the whole field `== 0`; `TextureUploadTarget::Texture1D` is 0, so the bare enumerator Espryt v1 matches on would collide with Buffer. RULING: the packed form is the contract encoding. c0c moves `MGPipePackSubDataTarget` / `MGPipeSubDataResourceTargetOf` / `MGPipeSubDataUploadTargetOf` into MGPipeTypes.h (Uint32 arguments, backend- neutral); B's rework deletes its copies; D's rework decodes the HIGH BYTE at every `PendingUploads` match (`FindPipeTextureUpload` and 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 mints `kMGPipeDepthStencilModeDepth/Stencil` in MGPipeTypes.h; B's rework drops its copies (the GLenum->byte helper is B's). - **DV-4 (`MGPSurface::Kind`)**: the numeric `MGPipeKind::{None,Texture,Renderbuffer}` values (what B emits). c0c mints `kMGPipeSurfaceKindNone/Texture/Renderbuffer`; B's rework drops its copies. - **DV-5 (`MGPSurface::Pad0`)** -> `Uint16 TextureTarget` = `static_cast(MobileGL::TextureTarget)`, 0xFFFF (= Unknown) on every non-texture point, consulted only when `Kind == Texture`; listed in `MGP_FIELDS_MGPSurface`; size stays 24. B's rework fills it; D's rework moves the four cross-object masks onto it; E's `SyncAttachmentObject` reads 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 `#undef` hygiene 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 checkout` in wire/esprytobj/gates (pointers 0 afterwards; esprytdraw and pipe were already clean). Every private worktree a reviewer creates must run `git lfs checkout` before 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 `Bool` acceptance 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().FramebufferRecords` slot-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`/`ReadFramebuffer` become 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 `continue`s outside the window instead of unbinding - restore the `else 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