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.
29 KiB
P4a final-review fix round - finalfix-v1 (2026-09-09)
Branch refs/heads/p4a/finalfix, worktree ~/w7/p4a-finalfix, base refs/heads/feat/disaggregated =
a38bdab4 (= the gated P4a code head 6035c9d7 + the tools-only harness commit). HEAD 8c458cd5, five
commits, not pushed, working tree clean. Charter: final-review-v1.md C-1 / C-2 / M-A / the minors and
INTEGRATOR-DECISIONS ID-52 (B is past two reworks, so the round is fable's with an integrator grant across
B's files, the contract, the applier, Espryt and one new scenario file). Every fix has a scenario that was
RED on the unfixed tree and is GREEN with the fix, taken on this tree (section 2); the full gate on the head
is in section 4.
0. Verdict in brief
- C-1 fixed. The storage entry points state WHICH storage a respecify replaces (one level, a chain cut,
the whole resource); the emitter builds wire's
MGPRespecifiedLevelwith the drain's own packed target and passes it; a per-level respecify is never deduped on the descriptor; the applier drops a named level whether or not the descriptor moved (a refinement of wire's W11 clause, section 3). The review's two llvmpipe scenarios read a 100 % black level 0 before and red after; the three controls are red on every arm. - C-2 fixed. The six death helpers forward to the emitters between the wire delete and the slot free
(ID-8's order): the texture emitter drops the dead handle's drain entries, its raw pointer, the built-in
sampler's cache reference and its latches; the renderbuffer, framebuffer, sampler-view and shader-CSO
memos are retired the same way;
ResolveTexturerefuses a dead slot loudly onIsLive.glTexImage2D; glDeleteTextures; drawwas SIGABRT (pure virtual method called) / SegFault underMALLOC_PERTURB_and is green on both backends, for every P4a kind and for a recycled slot. - M-A fixed.
kMGPipeBindSampleris produced by the sampler-view resolution andkMGPipeBindShaderImagebyglBindImageTexture's state setter (so the hint precedes the first sync) and by the image walk; both through a new contract door because neither caller may includeTextureEmit.h.ImageBindableHintreaches the applier as ID-18 M4's metadata respecify (pinned end to end, kept upload included), and ROADMAP open question 2 is measurable: PipeStatstex-remint-pulls,trp=on the summary line - one per texture Espryt had already allocated and had to re-mint image-bindable. - Minors: m-1/F-7 done (
MGPipeApplySetTextureParamsreturnsBool, the emitter latches on acceptance), m-2 (Config.h's fourth row), m-3 (the "ORed" comment). Going loud on m-1 surfaced one real residual in the retrace census - the context's default textures are born before the backend registers its consumer, so their firstglTexParameter*was refused and silently latched away - fixed by healing the record from the params path (commit 5). m-4, m-5, m-6, m-7, m-8 declared (section 5). - Gate on
8c458cd5(section 4): G1 0/0/0/0, G5 rc 0 x 4, purity rc 0 x 5, unit 1785 x 3, G2 2902 == 2902, G14 0 removed / +314, integration-gpu 1117/1117 at 0x1fff, 0x1ff, 0, 0x9ff, 0x5ff, pull andESPRYT_DISABLE_INVALIDATE_FLUSH=1(DirectVulkan 559/559), verify 920/920 zero Fatal, retrace 79/79, retrace refusal census 0 on 16 needles, itest eight-family census 0 lines / 0 tests on 13 rows, every negative control tripped.
1. Commits (branch p4a/finalfix, from a38bdab4)
| # | hash | what |
|---|---|---|
| 1 | 173f1dd2 |
C-1: the level scope of a texture respecify (contract hook + enum, PipeFill forward, TextureObject{,2DCube} call sites, the emitter's three-scope body, the applier's precedence, W11's test clause, 3 unit cases, the scenario file + peek TU + 5 itest cases) |
| 2 | a690032f |
C-2: the death path forwards to every emitter; loud dead-slot refusal; gen-stamped mask producers; 6 unit cases (texture x4, framebuffer, program), 6 itest cases + the MALLOC_PERTURB_=165 lanes |
| 3 | 9f60aadc |
M-A: the two bind-bit producers through MGPipeNoteTextureBoundAs / MGPipeNoteTextureImageBound; tex-remint-pulls; 2 unit cases (sampler, image), 1 itest case, 2 peeks |
| 4 | c2c6a655 |
m-1/F-7 (Bool from set_texture_params, latch on acceptance, RefusedParamCount), m-2 (Config.h), m-3 (MGPipeTypes.h); 1 unit case |
| 5 | 8c458cd5 |
the retrace census's residual once m-1 went loud: set_texture_params heals a record the applier never received (create, storage if any, params); the F-7 case reshaped around a refusal on the merits; 1 unit case |
Five single-line [Type] (Scope): description messages, no attribution trailer. Each commit was compiled
and run with ONLY its own tests (the shared test files were trimmed per commit before git add and the
trimmed suites run: TextureEmit 41/45/45/46/47 on both builds, the itest lane 10/24/26/26/26), so every
intermediate commit is green on its own.
2. Finding by finding: root cause, change, scenario, red/green
2.1 C-1 - the client never passed the level a respecify redefines
Root cause. TextureEmit.h's ApplyRespecify called MGPipeApplyResourceRespecify(desc, nullptr) at
every texture respecify; the applier's whole-resource arm then cleared the record's entire pending-upload
set. AllocateStorage is per (uploadTarget, level) and the descriptor carries only the base extent and
the level count, so a level the applier had ACCEPTED at one verb (client flag already clear, D-D5 step 1)
and that the next verb's glTexImage2D(level 1) or glGenerateMipmap grow defined AROUND was owed by
nobody. wire-v3 §5 item 5 required B to pass the key; no review checked it.
What changed.
MG_Pipe/PipeMutation.h:334-352-enum class MGPipeTextureRespecifyScope { WholeResource, OneLevel, LevelsFrom }andMGPipeEmitTextureResourceRespecify(ITextureObject&, scope, Uint32 uploadTarget, Uint32 level); every caller states its scope, "whole resource" is said, never defaulted.MG_Impl/Pipe/PipeFill.cpp:1258- the forward.MG_State/GLState/TextureState/TextureObject.h:239-240,TextureObject.cpp:87-104-PipePublishLevelDescriptor(uploadTarget, level)/PipePublishTruncatedDescriptor(uploadTarget, levelCount)beside the whole-resourcePipePublishDescriptor();AllocateStorage(:517) andTruncateMipmapLevels(:527) call them; the cube twins atTextureObject2DCube.cpp:39/:47(the face rides inuploadTarget). The view forwards to its owner'sAllocateStorage, so a per-level definition through a view keys the OWNER's level; the backend's own generate-mipmap grow (DirectGLES.cpp:8074) goes through the sameAllocateStorageand is level-scoped for free.MG_Impl/Pipe/TextureEmit.h:626-720-EmitResourceRespecify(texture, scope, uploadTarget, level):OneLevelbuilds one key{MGPipePackSubDataTarget(resourceTarget, uploadTarget), level}- the SAME packedMGPSubData::Targetthe drain writes at the sub-data emission - and is NEVER deduped on the descriptor (a non-base level redefined at a new size moves no descriptor field, and the applier's box against the old level would be uploaded past the end of the new one);LevelsFromspells the cut as one call per removed level[level, LastDesc.Levels)(the applier's key is one level; a cut at 0 is the whole resource; a cut that removes nothing the applier could hold is deduped like the whole-resource form);WholeResourcekeeps the byte dedupe.RespecifyOnce(:1124) carries the refusal self-heal;ApplyRespecify(desc, level)(:1107) takes the key;RepublishMaskpassesnullptrdeliberately (wire-v3 §5 item 6) and says so; the renderbuffer twin passesnullptr(no levels).MG_Pipe/PipeApply.cpp:1691-1703- precedence: a named level is dropped whether or not the descriptor moved; a null level with identical storage fields is the metadata update that drops nothing; a null level with moved fields clears all.PipeApply.h:787andMGPipeTypes.h:1103-1108state the rule; the W11 test clause is rewritten to the same rule (section 3).
Scenario and proof (MG_IntegrationTest/Scenarios/P4aFinalFixScenario.cpp, DirectGLES-asserted,
skipped by name on DirectVulkan where no P4a consumer exists; MG_Test/Pipe/TextureEmitTest.cpp).
| case | before (a38bdab4 + the tests) |
after (173f1dd2) |
|---|---|---|
PerLevelDefinitionAcrossAnUnrelatedDraw (L0(red); draw(other); L1; draw(T)) |
black, 11625/11625 px | red |
GenerateMipmapAcrossAnUnrelatedDraw (L0(red); draw(other); glGenerateMipmap; draw(T)) |
black, 11625/11625 px | red |
controls ConsecutiveDefinitionsNoVerbBetween, LevelZeroConsumedBeforeLevelOne, GenerateMipmapImmediately |
red | red |
unit ALevelDefinedAfterAnEmittedButUnconsumedUploadKeepsThatUpload (:1300, the review's probe through the real AllocateStorage) |
Failed (level 0 not pending, flag CLEAR) | Passed: both levels stand after the next drain |
unit AChainTruncationKeepsTheSurvivingLevelsPendingUploads (:1346) |
Failed (the cut took level 0's upload) | Passed: level 0 kept, level 2 gone |
unit ARedefinitionOfANonBaseLevelAtANewSizeDropsOnlyThatLevelsPendingUpload (:1377) |
Failed (deduped: the 8x8 box survived onto a 4x4 level) | Passed: level 1 gone, level 0 kept, serial moved |
The five itest cases run at 0x1fff, 0x1ff, 0 and on the pull build (26/26 each arm at the head).
2.2 C-2 - a dead handle resolved to freed memory in the drain
Root cause. ResolveTexture guarded with GenOfSlot == handle.Gen, but the allocator moves a slot's
generation only at the NEXT hand-out; between a death and a recycle the dead handle compared equal, the
emitter still held the freed ITextureObject*, and nothing removed the slot's drain entries - the six
death helpers freed the slot and told no emitter. EmitOneLevel then called virtual GetStorageType()
on the freed object from the next validate point.
What changed.
MG_Impl/Pipe/PipeFill.cpp:1405/1429/1467/1493/1525- the sampler-view, texture, renderbuffer, framebuffer and shader-CSO helpers forward to their emitter AFTER the wire delete and BEFORENotifyAndFree(ID-8: delete, notice, free). The sampler CSO forwards to nothing, stated: content- addressed, no per-object entry (ID-17). Unconditional in a push build, like the mints (the entries exist whether or not the family bit is set; the 0x1ff / 0 arms run the same path).MG_Impl/Pipe/TextureEmit.h:457-513-ResolveTexturerefuses on!MGPipeSlots().IsLive(...), counted (DeadResolveCount(),:974) andMGLOG_E_ONCE- reaching it means a death path skipped the emitter;NoteTextureDied(handle)(:487) drops the handle'sm_drainentries andDrainKeys, releases the built-in sampler's cache reference (at the death now, no longer first at the recycle), resets the entry;NoteRenderbufferDied(:509);MaskOfrefuses a stale generation; the two mask producers stamp the generation they write under (:423-425,:434-435,:545-546,:564-565) so a texture born while the family bit was clear has an entry a gen-keyed reader can see.RetireIfRecycledstays as the belt.MG_Impl/Pipe/FramebufferEmit.h:400-410-NoteFramebufferDied(the per-object Named latch) andNamedRecordIsLatchedfor the case.SamplerEmit.h/ProgramEmit.h's existingNoteRecordDestroyed("no production caller today") now have one; comments say so.MG_IntegrationTest/CMakeLists.txt-DirectGLES.MallocPerturb./DirectVulkan.MallocPerturb.registrations of the dirty-then-delete case withMALLOC_PERTURB_=165(the review's §8 item 1).
Scenario and proof (both backends, every arm).
| case | before | after (a690032f) |
|---|---|---|
ADirtyTextureDeletedBeforeAnyVerbIsWalkedByTheNextDrain (8 x glTexImage2D; glDeleteTextures; draw(other)) |
SIGABRT pure virtual method called (DirectGLES); SegFault under MALLOC_PERTURB_=165 |
white 8/8 on both backends, both lanes |
ATextureRecycledOntoTheDeadSlotDoesNotInheritItsDrainEntry (ABA) |
pass (the recycle's Gen already refused it) | pass |
ARenderbufferAndItsFramebufferDeletedAfterAClearLeaveTheNextDrawIntact, ASamplerObjectDeletedWhileBoundLeavesTheNextDrawIntact, AProgramDeletedWhileInUseLeavesTheNextDrawIntact, AFramebufferDeletedAfterADsaClearLeavesItsAttachmentIntact |
pass | pass |
unit ADeadTexturesHandleResolvesToNothingAndLeavesTheDrainList (:1410, the review's probe) |
SegFault (the freed pointer resolved) | Passed: null, drain list 0, DeadResolveCount 0 |
unit ATextureRecycledOntoADeadSlotDoesNotInheritTheDrainEntry (:1458) |
Failed (drain list still 1 after the death) | Passed: 0 after the death, the successor's own level goes out |
unit ADeadRenderbuffersEntryIsRetiredWithItsSlot (:1484) |
Failed (the dead handle still read its sticky mask) | Passed |
unit ADeadTexturesSamplerViewLatchIsRetiredAtItsDeath (:1440), FramebufferEmit.ADeadFramebuffersNamedRecordLatchIsRetired (:782), ProgramEmit.ADeadProgramsRecordLatchIsRetiredAtItsDeath (:781) |
(added with the fix; the stale latches read as published between death and recycle on the unfixed tree) | Passed |
ARecycledTextureSlotDoesNotInheritItsPredecessorsBindMask gains: the cache reference is back at the DEATH and the dead handle reads mask 0 |
- | Passed |
2.3 M-A - no producer of kMGPipeBindSampler / kMGPipeBindShaderImage
Root cause. NoteTextureBoundAs had one caller (the framebuffer emitter, the two attachment bits).
SamplerEmit.h and ImageEmit.h are included BY TextureEmit.h and could not include it back;
TextureState.h is MG_State and may include no emit header. Nothing set either bit, ImageBindableHint
was always 0, the metadata respecify had no live trigger, and the remint pull was neither prevented nor
counted.
What changed.
MG_Pipe/PipeMutation.h:379/384,MG_Impl/Pipe/PipeFill.cpp:1293-1307-MGPipeNoteTextureBoundAs( MGPipeHandle, Uint32 bindBit)andMGPipeNoteTextureImageBound(ITextureObject&), the birth hooks' shape (declared in the one door, defined beside the other hooks, forwarded throughForwardWhenWired<kMGPipeWiredTextureSubsystem>); not gated onFamilyIsLivebecause the mask is client state and the emission it causes is gated inside the emitter.MG_Impl/Pipe/SamplerEmit.h:774- the sampler-view resolution notes SAMPLER on every texture it names in an emittedMGPBoundView(D-A4's letter).MG_Impl/Pipe/ImageEmit.h:106- the image walk notes SHADER_IMAGE.MG_State/GLState/TextureState/TextureState.h:43-ImageTextureBinding::Bindnotes SHADER_IMAGE atglBindImageTextureitself (push-only), so the hint reaches the applier BEFORE the texture's first sync - the prevention half's whole point.MG_Util/Metrics/PipeStats.h:166,PipeStats.cpp:178/459,MG_Backend/DirectGLES/Managers.cpp:5260, 5338-5340-CallClass::TextureRemintPulls("tex-remint-pulls",trp=on the summary line, push- only so the pull enum is unchanged), counted inRequireImageBindableStorageonce per transition that replays a level of storage the backend ALREADY HELD (hadBackendStorage); a texture reaching the transition uninitialised is allocated image-bindable up front and pulls nothing. This is the counter for ROADMAP.md:85 (open question 2): readtrp=off the periodicMGPipe stats:line (MOBILEGL_PIPE_STATS=1) on the MC / Iris fixtures.Harness/P4aFinalFixPeek.{h,cpp}-PeekPipeTextureResourceRecord(by GL name),PeekPipeStatsTextureRemintPulls,PeekPipeStatsTextureUploadEmissions(arm the counters in-process).
Scenario and proof.
| case | before | after (9f60aadc) |
|---|---|---|
unit SamplerEmit.AResolvedSamplerViewMarksItsTextureAsSamplerBound (SamplerEmitTest.cpp:1098) |
Failed (mask 0) | Passed; a texture no sampler uniform resolves keeps mask 0 |
unit ImageEmit.AnImageBoundTextureIsMarkedShaderImageBoundAtTheBind (ImageEmitTest.cpp:528) |
Failed (mask 0 after the bind and after the walk) | Passed at the bind and in the emitted set |
AnImageBindAfterAllocationReachesTheApplierAsAMetadataRespecify (DirectGLES; glTexStorage2D + red, draw, blue subimage drained by an unrelated draw, glBindImageTexture) |
Failed: ImageBindableHint 0, BindMask & (1<<6) == 0, serial 5 vs 5 unmoved |
Passed: hint 1, bit set, serial moved, picture blue, trp +1 for the texture allocated before its hint and +0 for a second texture image-bound before its first sync; then the KEPT half on that second texture: a DSA attachment's RENDER_TARGET bit (a Named record, no sync in between) reaches the record as a metadata respecify with the blue upload still standing beside it, and the next draw is blue |
A note on the case's shape: Espryt syncs a texture eagerly inside glBindImageTexture and the widening
re-mints its storage, replaying every level from the shadow, so on the first texture the standing upload
is CONSUMED by that regeneration whatever the metadata respecify did - which is why the "kept" property is
proved on the second texture (image-bindable already, a new bit, no remint in the way), with Espryt's own
upload count telling a consumed upload from a dropped one.
2.4 The minors
- m-1 / F-7 (
c2c6a655).MG_Pipe/PipeApply.{h,cpp}:1010/2482-MGPipeApplySetTextureParamsreturnsBool(false on no consumer, no record, null built-in sampler; true on store).TextureEmit.h:739-837- the latch (HasParamsLatch/ParamsVersion/SamplerVersion/ForceParamsResync) is written only after acceptance; a refusal is counted (RefusedParamCount(),:966) and logged once. UnitARefusedParamsRecordDoesNotAdvanceTheLatch(TextureEmitTest.cpp:1511): with the pre-fix latch order the refused record advanced the latch and the record never learned the LOD write (ParamCount+1 not +2,ParamsSerialunmoved,LodBias0 vs 0.5) - RED; with the fix GREEN. The case was reshaped twice during the round (its first shape never emitted the refused record -MakeTexture2D's format setter had already latched - and its second shape lost its premise to commit 5's heal); the final shape drives the emitter directly underScopedNoResourceOps, where the applier's belt refuses both the parameters and the healing create, and its red/green was re-taken with the latch-order toggle (transcript infinalfix-v1-logs/). - The residual m-1 uncovered (
8c458cd5). With the refusal loud, the retrace census found 2 lines (set_texture_params for texture 0 {slot=2, gen=0} was refused): the context's default textures are constructed (TextureState.cpp:68) before the backend registersMGPipeResourceOps(Managers.cpp:2556), so their create never went out, and an application's firstglTexParameter*on texture 0 found no record - silently latched away before m-1.TextureEmit.h:805-831heals the record from the params path the wayEmitResourceRespecifyheals it: a create with no storage for the identity, the storage itself if the texture has any (a respecify against the create's descriptor is never deduped), then the parameters;Entry&is re-fetched across the respecify (the table can grow). UnitATextureBornBeforeTheConsumerRegisteredGetsItsRecordFromItsFirstParamsPublication(:1548): RED (refused, no record healed) / GREEN (record with its 8x8 storage,ParamsSerial1, the LOD bias; and a storage-less texture heals to an identity-only record). The retrace census is 0 on the head. - m-2 (
c2c6a655).Config.h:336-341-0x400 ... requires 0x80 AND 0x800, namingkMGPipeP4aFamilyDependenciesand ID-15. - m-3 (
c2c6a655).MGPipeTypes.h:1110-1114- the applier replaces the descriptor WHOLE with the one the client sent; the mask in it is the client's sticky OR, so no bit can be lost; "ORed, never replaced" is gone.
3. Judgement calls and deviations from the charter
- The applier's precedence (C-1). wire's W11 clause read "a metadata update drops nothing, whatever
levelsays". The client's only metadata caller (the mask republish) passesnullptr, and a per-level caller cannot be deduped on the descriptor (a non-base level's extent is not in it), so a named level with identical storage fields is now that level's redefinition and drops exactly that key.ARespecifyThatRedefinesNoStorageCarriesTheStickyMaskAndKeepsThePendingUploadskeeps its null-level half and gains the named-level half (its immutable store has two levels now so level 1 exists for the validator). Contract grant; stated inPipeApply.h:787,PipeApply.cpp:1680-1690,MGPipeTypes.h. - A per-level respecify is never deduped. One applier call per
glTexImage*D/ generated-mip grow instead of a byte compare; the sub-data that follows moves the serial anyway, and on the handle arm an unchanged descriptor with a moved serial costs Espryt one bind and an empty level loop (Managers.cpp:6470-6520:needsRegenerationcompares the descriptor, not the serial). LevelsFromas N calls. The contract's key is one level; a chain cut is spelled as one call per removed level rather than a new contract type. Belt-and-braces in practice: every frontend path that cuts a chain has already redefined or is about to redefine the surviving base (DiscardMipmapChainOnBaseRespecification,glTexStorage*'s loop), but the unit case pins the rule.- Files outside the listed grant (ID-52's charter names the mechanisms, the grant list names B, the
contract, the applier, Espryt and one scenario file): C's
SamplerEmit.h/ImageEmit.h/ProgramEmit.h(the D-A4 placements and the death forward; 1 + 8 + 14 lines), B'sFramebufferEmit.h(the death forward), A'sPipeStats.{h,cpp}(the counter; push-only),MG_State/.../TextureState.h(B's directory; the eager image-bind note), C'sSamplerEmitTest.cpp/ImageEmitTest.cpp/ProgramEmitTest.cppand B'sFramebufferEmitTest.cpp(one case each, with pull twins), and F'sMG_IntegrationTest/CMakeLists.txt(two source lines + the twoMallocPerturbregistrations the review's §8 asked for). Every one is additive. - A second harness TU (
Harness/P4aFinalFixPeek.{h,cpp}, fablefix's precedent): a scenario TU cannot includePipeApply.hbeside the GL prototype headers and F'sPipeApplyPeek.cppis not mine to edit. - Five commits, not four: the heal is the census's finding on the round's own m-1 work and is kept as its own commit (with the F-7 reshape it necessitated) so the history says who found what.
- The C-1 / M-A itest cases assert on DirectGLES only (skipped by name on DirectVulkan, which registers no P4a consumer - the handle arm is inert there by design); the C-2 cases run on both backends (ID-8: the death path is backend-neutral and the DirectVulkan lane must see it).
4. Gates on 8c458cd5 (one detached script, -j 8, CCACHE_BASEDIR=/home/swung/w7, GLIBC_TUNABLES=glibc.malloc.tcache_count=0)
| gate | result |
|---|---|
| builds | build-linux / build-push / build-verify rc 0, 0 warnings each |
G1 symbol_report --threshold 0 --fail-on-symbol-set-change --fail-on-added-bytes 0 vs ~/w7/p4a-before-libMobileGL.so |
rc 0: .text 10806323 -> 10806323 (+0); 27811 -> 27811 symbols: 0 added, 0 removed, 0 resized, 0 renamed |
RenderStateImpl sha |
d8fd1c48716056c53675 == before |
scripts/p3a_untouched_regions.sh 37da3c3a HEAD / --self-test |
rc 0 byte-identical / rc 0 |
scripts/p4a_untouched_regions.sh 37da3c3a HEAD / --self-test |
rc 0 byte-identical / rc 0, 8 controls tripped and named |
gen_pipe.py --check |
up to date, rc 0 |
gen_pipe_dirty_surface.py --check / --self-test |
rc 0 / rc 0, 27 negative controls tripped, positive OK |
check_include_closure.py --compiler clang++ / text self-test |
rc 0, 4 probes, 0 problems / rc 0 |
gate C (pGLContext in MG_Backend) / G13 |
only PipeInputs.h (as before) / PipeApply.h :37 :40 :832 :1040 :1041 - forward declarations and prose, outside the ops block (ID-39's class) |
unit -L unit |
1785 / 1785 / 1785 (linux / push / verify) |
| G2 pull == push ctest names | 2902 == 2902, 0 diff lines |
G14 vs p4a-before-ctest-names.txt |
0 removed, +314 (was +275: 13 unit twins + 13 itest cases x 2 backends = 39) |
| emitter/CSO unit suites (push) | 185/185 |
| HandleRecycle (verify) / verify controls | 180/180 / 4/4 |
| integration-gpu, build-push, 0x1fff (default) | 1117/1117 (1091 + 26) |
| integration-gpu, build-push, 0x1ff / 0 / 0x9ff / 0x5ff | 1117/1117 each |
| DirectVulkan lane, 0x1fff | 559/559 |
integration-gpu, MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1 |
1117/1117 |
| integration-gpu, build-linux (pull) | 1117/1117 |
family regex / G9 TextureParamsWithoutASamplerView / TextureUploadShape (recorded) |
497/497 / 8/8 / 3/3 |
the new lane P4aFinalFix at 0x1fff / 0x1ff / 0 / pull |
26/26 each |
g7_negative_control.sh / p3a_vertex_input_negative_control.sh / p4a_descriptor_negative_control.sh |
rc 0 tripped / rc 0 (IsBgra) / rc 0 (both controls named) |
| CsoContentAddressing + ResourceSubsystemControl + ObjectSubsystemControl / the 0x9ff arm | 24/24 / 14/14 |
| controls (PoisonOmitted/VerifyCorrupted/HandleRecycle, verify) | 184/184 |
integration-verify, build-verify, -j 4 |
920/920, Fatal{ lines 0 |
retrace (retrace_gate.py --tree ~/w7/p4a-finalfix --lib build-push/libMobileGL.so --out ~/w7/retrace-out/p4a-finalfix -j 4, fixtures re-copied from ~/w7/pipe, 0 LFS pointers) |
rc 0, 79 / 79, failed [] |
retrace refusal census over the 79 mobilegl.logs |
0 for every one of 16 needles: the ten shapes, "does not describe the binding it names", Fatal{, "cannot be adopted on the handle arm", "was refused" (C-2's and m-1's tells), "is dead but the emitter still holds", "no applier record"; the deduped MGPipe: ERROR/FATAL list is empty (it held 2 lines on c2c6a655, closed by 8c458cd5) |
itest eight-family census (wsl_p4a_refusal_census.sh default, 558 names, 538 logs) |
0 lines / 0 tests on all 13 rows (the ten shapes + the three this round's tells), stems 0 / 0 / 0 |
5. Declared, with reasons
- m-4 / F-6 (
MGPipeEncodeImageAccess,MGPSamplerView::Target,DrawBuffersnarrowing stated in package headers): contract work for P4b (R-3); no behaviour to fix this round. - m-5 (
GenerateThreeChannelFloatMipmapOnCpumarks levels dirty withoutRearmPipeTextureLevelUpload, self-heals one verb late): a server-side D-M emulation site on E's file; the rearm belongs with R-7's audit of the two backend re-dirty sites, not in a client-side round. Its storage grow beside it goes throughAllocateStorageand is level-scoped by C-1 for free. - m-6 (G9 blind when a
UseProgramrow and its mark drop together): derivation work, R-4, P4b. - m-7 (
DirectGLES.cpp:4137/4174pull-pathbroadcastCountResolved): benign; G1 0/0/0/0 stands. - m-8 (nested-worktree
include/skapointer): this worktree, made with the plaingit worktree add -bthe charter spelled, shows a cleangit statusininclude/ska; every commit here used explicitgit add+git commit -F, never-a, and each was verified withgit log/show --stat. Two tooling notes of this round fornotes/tools: (1)$(...)inside a double-quotedwsl.exe -d Arch -- bash -c "..."string is expanded by the Windows Git Bash BEFORE the string reaches WSL (it reported the FCL repo'sdevhead and dirty count as if they were the worktree's); (2) a Bash-tool heredoc containing backticks fails to parse - write the.pywith the Write tool instead. - §8 items 6-7 (the two-device Release A/B re-take on the fixed head - C-1 dropped uploads and biased
the push arm -
test.yml:9-12's TEMPORARY trigger, D.5 docs andcheck_doc_citations.py) are the integrator's. For D.5:ARCHITECTURE.md:249-254's texture paragraph should carry the C-1 rule ("the client passes the level it redefines; a chain cut names its removed levels; whole-resource calls pass none; a named level is dropped whatever the descriptor says"),ARCHITECTURE.md:598the four dependency rows, andROADMAP.md:85the counter name (tex-remint-pulls,trp=). - The census tool
notes/tools/wsl_p4a_refusal_census.shalready lists this round's three tells (rows 11-13 in the head's census run); it was not edited by this round.
6. Logs
Kept in ~/w7/notes/p4a/p4a-results/finalfix-v1-logs/: the RED run on the unfixed tree
(p4a-finalfix-red.log, p4a-finalfix-red-itest.log with the black-pixel counts, the pure virtual method called abort and the M-A assertion values), the GREEN run after each fix (p4a-finalfix-green- {c1,c2,ma,minors}.log + -{c1,c2,ma,ma2,minors}-itest.log), the F-7 / heal red-green transcripts
(p4a-finalfix-f7.log, p4a-finalfix-heal.log), and the gate on 8c458cd5 (p4a-finalfix-gate.log,
-gate-symbols.md, -gate-retrace-summary.txt, -gate-census.log, -gate-iverify.log). Every other
~/w7/p4a-finalfix-* log, ~/w7/finalfix-stage/, the census output directory and the retrace output
directories (summary.txt / summary.json kept) were deleted at the end of the round. The worktree
~/w7/p4a-finalfix stays (branch p4a/finalfix, HEAD 8c458cd5, clean; fixtures are the copies from
~/w7/pipe under assume-unchanged). The shared scratchpad wsl directory is untouched except for this
round's own finalfix-* scripts.