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.
23 KiB
P2 package B — p2/tracker — result, rework round 2
Tree ~/w7/p2-tracker, branch p2/tracker, branched from the contract commit 9c6a8a25
(the p2/contract tag). Not pushed. This file supersedes tracker-v1.md.
Round-2 scope: both majors and every minor of tracker-review-v1.md. Nothing from round 1 was
reverted; the four new commits sit on top of 7993d711.
1. Commits
Round 1 (9c6a8a25..7993d711, unchanged):
| sha | subject (abbreviated) |
|---|---|
f52dd262 |
[Feat] (State): give the frontend six aggregate generations … |
9c9df515 |
[Feat] (Pipe): compute the per-verb dirty mask at the validate point … |
8d000f7e |
[Feat] (Pipe): mint render-state CSOs on the pipeline subset … |
a0c44c4f |
[Feat] (Pipe): push pixel-pack, patch and vertex-attribute-default state … |
e54e7399 |
[Feat] (Pipe): carry what has no call of its own in the residual value block … |
e49f0ea7 |
[Feat] (Pipe): map every frontend mutator onto the aggregate generation … |
b3daa404 |
[Test] (Pipe): pin the tracker's shutters and the CSO cache's content addressing … |
7993d711 |
[Test] (Pipe): give the pull build the same ctest names as the push build … |
Round 2 (7993d711..dc452c02, new):
| sha | subject | closes |
|---|---|---|
57b85f73 |
[Fix] (Pipe): derive the render-state answers of the dirty-surface map from RenderState.cpp instead of believing them, and correct the two rows that named a publisher which does not always fire |
MAJOR 1, minor 10 |
80b861b8 |
[Fix] (Pipe): count the render-state CSO binds the cache has always declared and never incremented |
minor 3 |
d5d4e757 |
[Fix] (Pipe): carry the class a vertex-attribute default was written through, and stop the applier's lossy write from being observable |
MAJOR 2, minors 1, 2, 5, 9, 11, 12 |
dc452c02 |
[Docs] (Pipe): say what actually fails to publish a program, a pipeline and a shader dying, instead of naming a scope |
minor 10 (sharpened) |
Round-2 diffstat: 9 files, +746 / −148.
MobileGL/MG_Impl/Pipe/CsoCache.h | 9 +-
MobileGL/MG_Impl/Pipe/PipeFill.cpp | 159 ++++++--
MobileGL/MG_Impl/Pipe/PipeFill.h | 13 +
MobileGL/MG_Impl/Pipe/Tracker.h | 34 +
MobileGL/MG_Pipe/DirtySurface.def | 242 +++++++------
MobileGL/MG_State/GLState/Core.cpp | 11 +
MobileGL/MG_State/GLState/Core.h | 39 +
MobileGL/MG_Test/Pipe/TrackerTest.cpp | 209 +++++++++-
scripts/gen_pipe_dirty_surface.py | 178 +++++++--
Whole package: 26 files, +2857 / −78. Every round-2 file is one B owns in C.5 except
MG_State/GLState/Core.{h,cpp}, which the table gives to nobody (round-1 deviation 3).
No file under MG_Backend/, no CMakeLists.txt, no A/E file was touched.
2. MAJOR 1 — the dirty-surface map's render-state answers
What was wrong. X(SetCapability, NEW_PIPELINE_STATE) and
X(SetStencilFunc, NEW_PIPELINE_STATE) named a publisher that does not fire on every path:
RenderState.cpp:377-381's ClipDistance0..7 arms do ++m_version with an explicit
"Deliberately NOT BumpVersions()", and :641-649 makes ++m_pipelineStateVersion conditional
on Func moving. --check validated row existence and answer vocabulary only, so both rows
survived a green gate.
What landed (57b85f73):
- The rule is restated and the two rows corrected. A row now lists every publisher that
fires on every path, joined with
|; a publisher that fires on some paths only must not appear.SetCapabilityandSetStencilFuncareNEW_RENDER_STATE. The 18 setters that callBumpVersions()on every path carryNEW_RENDER_STATE|NEW_PIPELINE_STATE, and the patch trio carriesNEW_PATCH_STATE|NEW_RENDER_STATE|NEW_PIPELINE_STATE(it callsBumpVersions()too, and the trio really does travel in pipeline chunk P0). --checknow derives the answer instead of accepting it.render_state_publishers()readsRenderState.cpp, walks everyRenderState::Set*body and computes which counter moves on every path —BumpVersions()moves both, a bare++m_versionmoves onlyNEW_RENDER_STATE, a body with both kinds of path always-fires onlyNEW_RENDER_STATE, and a body with no bump inherits from the setter it delegates to (SetPolygonOffset→SetPolygonOffsetClamped). A row that claims a publisher the derivation does not find isUNDER-FIRING; one that omits a publisher it does find isMISSING publisher. 45 rows are checked this way.- Two more self-test negative controls, one per direction, built from the two defects that were actually in the file.
Evidence (all re-run):
| command | output |
|---|---|
python3 scripts/gen_pipe_dirty_surface.py --check |
rc 0 — 73 mutators, all mapped, no stale rows; 45 render-state answers derived from RenderState.cpp and matching |
python3 scripts/gen_pipe_dirty_surface.py --self-test |
rc 0 — 5 negative controls, all tripped |
python3 scripts/gen_pipe_dirty_surface.py --summary |
rc 0 — still works (E's CI step is unbroken) |
end-to-end control, file edited to the OLD answer (SetStencilFunc ← `… |
NEW_PIPELINE_STATE`) |
end-to-end control, other direction (SetDepthMask ← NEW_RENDER_STATE) |
rc 1 — `MISSING publisher NEW_PIPELINE_STATE for SetDepthMask … (derived: NEW_PIPELINE_STATE |
The derived table was cross-checked by hand against the review's own setter-by-setter list
(§1.2 of tracker-review-v1.md); it agrees on all 45, including the two disputed rows.
3. MAJOR 2 — set_vertex_attrib_defaults' payload
What was wrong. PipeFill.cpp hard-coded value.ValueClass = 0 and always sent the
float view's four words. GLContext converts numerically between the three views, so those
bytes cannot reproduce the value (glVertexAttrib4f(loc, 1.5f, …) → frontend intValue 1,
pushed word 0x3FC00000), and every glVertexAttribI4i/ui default was wrong. No gate could
see it, because the residual fill re-pulls the field at every kDraw verb after emission —
while at a non-kDraw verb nothing put the value back at all.
What landed (d5d4e757):
- The class is recorded and carried.
GLContext::GetCurrentVertexAttributeClass(index)(push-only) says which of the three views the lastglVertexAttrib*write filled directly;MGPipeFillAttribValue(Tracker.h) puts that class inMGPAttribValue::ValueClassand that class's own four words inData. - Where the class lives, and why not in the value. It is a parallel array on
GLContext, not a member ofCurrentVertexAttributeValue: the tree refused the obvious shape —MG_Backend/MGPipe/PipeInputs.cpp:96-99compares that storage with onememcmpbehindstatic_assert(sizeof(...) == 3*4*4, "…grew a member; update the comparator"), andMG_Backend/**is a file class B must not touch (C.1). The first attempt brokebuild-verifyon exactly that assertion. The class need not be mirrored anyway: it only decides how the three views are rebuilt, so two writes that leave the views identical rebuild identically whichever class they carried — which is also why the emitter's suppressingmemcmpover the three views alone stays correct. - The applier's lossy write is no longer observable.
MGPipeApplySetVertexAttribDefaults(package A'sPipeApply.cpp:224-226) stillmemcpys the four words into all three views. So after the call the emitter compares the mirror against the frontend's value byte for byte over the attributes the call named and, when they differ, copies the field itself and warns once — the same self-healing shape asApplierDerivesRenderStateFields. That closes the window the old code left wrong and stops by itself once A's applier switches onValueClass. - It is testable.
MGPipeVertexAttribDefaultRepairCount()(PipeFill.h) is the observable: reading the storage in that window would be the poison violationFillPoints.defforbids, so the count is what the test asserts. - The row is recorded as shape-only. The comment on
EmittedCallSuppliesTheWholeFieldnow says the pull forGetCurrentVertexAttributeis blocked on A teaching the applier to honourValueClass; the "payload bytes are real" claim oftracker-v1.md§4.2 is withdrawn (see §7, blocking follow-up 1).
New tests (7), all in MG_Test/Pipe/TrackerTest.cpp, with pull-build SKIP twins:
TrackerAttribPayload.AFloatWriteCarriesTheFloatBitsAndNamesItsClassTrackerAttribPayload.AnIntWriteCarriesTheIntWordsAndNamesItsClassTrackerAttribPayload.AUintWriteCarriesTheUintWordsAndNamesItsClassTrackerAttribPayload.TheSameNumbersWrittenThroughADifferentClassAreADifferentValueTrackerShippedEmitter.APushedAttributeDefaultTheApplierCannotReproduceIsRepairedTrackerShippedEmitter.{ABlendToggleThroughTheValidatePointMintsTwoCsos, TheSteadyStateThroughTheValidatePointEmitsNothing, AViewportThroughTheValidatePointMintsNoCso}
Negative controls run for these, not just asserted:
| control | result |
|---|---|
restore the old payload (ValueClass = 0, always the float view) |
3 of the 4 TrackerAttribPayload cases go red |
| disable the render-state emission at the validate point | TrackerShippedEmitter.ABlendToggle… goes red (the other two are "nothing was minted" assertions and stay green, which is why both now ASSERT that the priming draw minted and bound exactly one CSO first) |
4. The minors
| # | verdict |
|---|---|
1 MGPipeSubsystemForDirty had no production caller |
fixed. Step 3 gates every emission through wants(bit) → MGPipeSubsystemForDirty, and five static_asserts tie that map to SubsystemForEmitter, the field-side map it must agree with. The four hand-written subsystem names are gone. |
| 2 unit tests exercised a re-implementation | fixed. TrackerShippedEmitter drives MGPipeValidateForVerb itself and reads the real singletons (MGPipeCsoCacheInstance().GetCounters(), MGPipeTrackerInstance().LastDirty()). TrackerWalk is kept as the cheap shutter-level fixture. |
3 Counters::Binds never incremented |
fixed in Acquire, which has exactly one caller and is followed by a bind every time; the comment says so. TrackerShippedEmitter now asserts on it. |
| 4 residual trip wire is a tautology on this branch | recorded in the code (EmitResidualValueState) and in §5 below: the semantic half is deferred to the merged tree; what this branch proves is emission, size and suppression. |
5 Staged() latched on either bit |
fixed. The mirror is advanced only by the NewRenderState branch, and the invariant it used to rely on (another package's BumpVersions) is now a MOBILEGL_ASSERT rather than an assumption. |
| 6 G9 is only half a gate until E lands | recorded, §5. .github/workflows/test.yml is E's file and was not touched. |
| 7 four resized symbols, not three | recorded, §5. All four are the contract commit's; B's own pull-build delta is still zero. |
| 8 D14 / D.4.3's T1−T2 no longer isolate the tracker | recorded, §5, for MEASUREMENTS.md. |
| 9 the derivation probe is a one-field sample | recorded in the code, at the probe. |
10 three kExplicitDestroy rows name a mechanism nothing builds |
fixed: they answer kUnpublishedDestroy, a new documented answer. The reason given is the tree's, not the brief's — see §6.2. |
11 MGPipeWidenedCounter can miss a change of exactly 65536 |
recorded at the code, as the review asked; not fixed (it would mean widening MG_State's counters, which ARCHITECTURE.md 5.2 rules out). |
| 12 test-seam / reset asymmetry | fixed for the reset half: ResetTheServerSideSingletons() resets applier + cache + tracker + suppressor together in both fixtures' SetUp/TearDown. s_hashForTest stays (declared deviation 6). |
| 13 the brief's G2/G14 grep is broken | recorded, §6.1; the corrected pattern is used throughout. |
14 retrace_gate.py has no --ssim |
recorded, §6.1 (reproduced: unrecognized arguments: --ssim 0.99). |
| 15 G4's log greps inspected zero files | fixed in this run: corrected path and corrected arming string, numbers in §5. |
| 16 header-only Tracker/CsoCache/SetHashSuppressor | stands as declared deviation 1; §7 asks the integrator to land the one list(APPEND) line. |
| lane flake | reproduced and re-established, §5. |
5. Verification — every command and its actual output
All commands run in ~/w7/p2-tracker at dc452c02. Three other packages' lanes were running
on the same machine for part of this (see the flake row).
5.1 Generators and gates
| command | output |
|---|---|
python3 scripts/gen_pipe_dirty_surface.py --check |
rc 0 — 73 mutators, all mapped, no stale rows; 45 render-state answers derived from RenderState.cpp and matching |
python3 scripts/gen_pipe_dirty_surface.py --self-test |
rc 0 — 5 negative controls, all tripped |
python3 scripts/gen_pipe.py --check |
rc 0 — 63 PipeInputs fields (7 sticky, 34 emitted by a P2 call), generated files are up to date |
python3 scripts/gen_pipe.py --self-test |
rc 0 — 7 negative-control trip(s), positive control OK |
python3 scripts/check_include_closure.py |
rc 0 — 4 probes, 0 skipped, 0 problem(s) |
grep -rc 'pGLContext' MobileGL/MG_Backend | grep -v ':0$' |
empty |
5.2 Section-A gates reachable from this tree
| gate | command | output |
|---|---|---|
| G1 | symbol_report.py --before ~/w7/p2-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 |
27799 -> 27799 defined symbols: 0 added, 0 removed, 4 resized, 0 renamed; .text +160 (+0.001%). Identical to round 1 — round 2 adds zero pull-build delta; the four resized are the contract's (minor 7). |
| G2 | ctest -N name sets, pattern '^ +Test +#[0-9]+: ' |
build-linux 2404, build-push 2404, zero-line diff |
| G3 | retrace_gate.py --lib build-push/libMobileGL.so -j 4 |
rc 0 — 79 / 79, lowest SSIM 0.993475 (two cases), then 0.995426 |
| G4 | ctest --test-dir build-verify -L integration-verify -j 4 |
rc 0 — 818 / 818 |
| G4 | MOBILEGL_PIPE_VERIFY=1 retrace_gate.py --lib build-verify/libMobileGL.so -j 4 |
rc 0 — 79 / 79, lowest SSIM 0.997009 |
| G4 | log evidence on the CORRECTED path <out>/<case>/<backend>/output/mobilegl.log and the CORRECTED arming string MGPipe: verify armed |
79 logs; Fatal{ 0; unarmed 0; PipeVerifyDiffer 0; PipeResidualDiverged 0; UnmigratedPipeInput 0 |
| G5 | git show {p2/contract,HEAD}:…/DirectGLES.cpp | awk '/namespace RenderStateImpl/,…' | sha256sum |
d8fd1c48…0efe27 on both sides and equal to ~/w7/p2-before-syncrenderstate.sha |
| G9 | above | green, and now a gate over the answers as well as the rows |
| G10 (desktop half) | MOBILEGL_PIPE_STATS=1 MOBILEGL_PIPE_STATS_PERIOD=1 … ctest -R DirectGLES.CrossFrameBufferScenario |
13/13 pass; resid=8.00 in one window and resid=0.00 in the others; cso[csom=1 csob=1] once then csom=0 csob=0 — the block goes out once and is then suppressed |
| G13 | above | clean |
| G14 | comm -23 ~/w7/p2-before-ctest-names.txt <build-linux names> |
empty (0 of 2363 baseline names removed) |
Not reachable here, unchanged from round 1: G6/G7 (package A + E), G8/G12 (package E), G11 (device, the integrator).
5.3 Test lanes
| lane | result |
|---|---|
ctest --test-dir build-linux -L unit -j 8 |
rc 0 — 1526 / 1526 |
ctest --test-dir build-push -L unit -j 8 |
rc 0 — 1526 / 1526 |
ctest --test-dir build-verify -L unit -j 8 |
rc 0 — 1526 / 1526 |
ctest --test-dir build-push -L integration-gpu -j 4 |
rc 0 — 878 / 878 |
MOBILEGL_PIPE_PUSH=0 ctest --test-dir build-push -L integration-gpu -j 4 |
rc 0 — 878 / 878 (the all-pull arm, first try) |
ctest --test-dir build-linux -L integration-gpu -j 4 |
first run rc 8 — 4 failures, all *.IsActuallyArmedWhenTheEnvironmentPinsItOn; re-run of the whole lane rc 0, 878 / 878, and all four pass standalone (ctest -R IsActuallyArmedWhenTheEnvironmentPinsItOn → 10/10). This is the pull build, where this package changes nothing at all (G1), with three other packages' lanes running on the same machine — the flake family tracker-v1.md §4.4 and the review's last minor both name. |
ctest --test-dir build-verify -L integration-verify -j 4 |
rc 0 — 818 / 818 |
Unit count went 1518 → 1526: +7 new cases, +1 the round-1 SetHashSuppressor name that the
pull build had been missing until build-linux was rebuilt in this round (caught by the G2
name diff, fixed by the rebuild, not by an edit).
6. Where the tree contradicted the brief (round 2 additions)
Round 1's list (tracker-v1.md §4) still stands: MGPipeDeriveRenderStateFields is a stub on
this tag (§4.1), the two Coverage.def rows cannot retire their pull (§4.2 — now half-fixed,
see §3), MGPipeApplyDeleteRenderState takes MGPHandleOnly. New this round:
6.1 Three commands in section A do not work as written
- G2/G14's grep.
grep -E '^\s+Test #'matches only four-digit numbers, so it silently dropped ~1000 of 2404 names. Corrected pattern used here:'^ +Test +#[0-9]+: 'withsed -E 's/^ *Test +#[0-9]+: //'. - G3/C.2/C.3's
--ssim 0.99.retrace_gate.pyhas no such flag; reproduced —retrace_gate.py: error: unrecognized arguments: --ssim 0.99, and its--helplists only--tree --lib --out -j --only. The threshold is internal. As written the G3 command exits non-zero having run nothing. - G4's log greps. The logs are at
<out>/<case>/<backend>/output/mobilegl.log, not<out>/<case>/mobilegl.log, and the arming line the code emits (PipeFill.cpp:407) isMGPipe: verify armed - 63 fields, 69 verbs, fatal=1, notMGPipe verify:. Fixing only the path would turn a true green into a false red. Both corrections are used in §5.2.
6.2 D13's "six kinds" versus the tree
The review's minor 10 says D13 scopes explicit destroy to six kinds excluding programs,
pipelines and shaders. D13's prose says six kinds, but the Core.cpp line ranges it cites
(346-352 at the base ref) cover both MarkProgramForDeletion and
MarkShaderForDeletion, and the tree has a StateBackendObjectRegistry for ProgramObject.
So the brief is not self-consistent here and the "outside the six" reason is not safe to write
down. The reason that is safe, and the one the def now gives, is the tree's:
PipeCalls.def has no per-object handle for a program, a program pipeline or a shader —
resource_destroy and the delete_* family name resources and CSOs — so nothing publishes
their death and their DirectGLES twins are still reclaimed by the backend's own registry
teardown. Answer: kUnpublishedDestroy, documented as a hole.
6.3 The mirrored value's size is asserted in a file B may not edit
MG_Backend/MGPipe/PipeInputs.cpp compares CurrentVertexAttributeValue with one memcmp
under static_assert(sizeof(...) == 3 * 4 * 4). That assertion is right (it is what keeps a
NaN attribute equal to itself and has no padding to false-differ on) and it is what forced
MAJOR 2's discriminator to live beside the array rather than inside the value. Recorded because
it is a real constraint on anyone who later wants the class mirrored: it needs A's or the
integrator's hand on that file, and the argument in §3.2 says it is not needed.
7. Deviations from the brief
Round 1's nine deviations are unchanged and still declared: header-only
Tracker/CsoCache/SetHashSuppressor (1), the sixth aggregate generation (2), Core.{h,cpp}
(3), coarse shutters for bits 5–17 (4), Vector scan in the CSO cache (5), s_hashForTest
(6), the residual block emitted after the fill and held rather than dropped (7), the runtime
derivation probe (8), five stale MGPipeFillForVerb comment references in A's files (9).
New in round 2:
DirtySurface.defrows may carry several answers. D16 describes one answer per row. A single answer cannot be both true and informative for the render-state family (the coarsest true answer for all 45 setters isNEW_RENDER_STATE, which erases the pipeline half that D6 and P3a need). The|notation plus a derived check keeps both. The file's own rule paragraph is rewritten to match.GLContextgrew a push-only parallel array (m_currentVertexAttributeClasses) and one push-only accessor.Core.{h,cpp}is "nobody" in C.5 (deviation 3 already covers editing it); this is called out separately because it is state, not just a facade. All of it is#if MOBILEGL_PIPE_PUSH; G1 is byte-for-byte unchanged.MGPipeVertexAttribDefaultRepairCount()is a counter in production code read only by a test. It is not on any hot path (incremented inside the repair branch of an emission that only happens when an attribute default moved) and it is the only way to observe the repair without a poisoned read.
Blocking follow-up for the integrator (not a deviation, a hand-off):
- Package A must teach
MGPipeApplySetVertexAttribDefaultsto switch onMGPAttribValue::ValueClassand reproduceGLContext's conversions (MG_State/GLState/Core.cpp:205-250). Until thenGetCurrentVertexAttributekeeps being pulled, the client repairs the mirror after every such call, and the row must not be treated as retiring its pull. The wire half is done and the class is now truthful.
8. Unfinished
- The 26 derived render-state mirrors are still pulled on this branch (
tracker-v1.md§4.1). Self-healing: the one-shot probe flips the moment A'sc1is in the tree. The integrator's re-run ofintegration-verify+ the verify retrace on the merged tree is what proves them — and note the probe samples one derivation (minor 9), so a partialc1is caught by the verify lane, not by the probe. - The residual block's trip wire is only half an oracle here (minor 4). It becomes
independent when A's
c1lands. - The attribute-default repair never fired in the retrace corpus (
attrib repairs: 0across all 79 verify logs): no trace in the corpus moves aglVertexAttrib*default, so the coverage for that path is the unit test, not the corpus. Worth one integration entry with aglVertexAttrib4fin it when package E next touches the lane. MEASUREMENTS.mditems the integrator owns, restated from the review: the admitted resized-symbol set has to be widened to the four the contract actually produces (minor 7); D14/D.4.3's T1/T2 must be restated before the run, because the dirty walk is unconditional and only emission is behind the bitmask, soT2already contains the tracker cost (minor 8); the*.IsActuallyArmedWhenTheEnvironmentPinsItOnfamily belongs in the known- flake list by name (it fails under lane parallelism in the pull build too).- G9 is not a CI gate until package E lands the
pipe-gatesstep;test.ymlstill runs--summaryunder an "Informational … becomes a gate in P2" comment (minor 6). - Per-bit fire rates are still not printed (
FormatWindowLineis A'sPipeStats.cpp), the six otherSetHashSuppressorslots are still unwired (D11's own scope), and there is no device work here (G11, D.4).