[Docs, MG_IntegrationTest] (Disaggregated): P5f f1 report - the census ratchet's first version (6 pairs), CONTRACT-P5E section 3.2 marked landed, and the gate numbers

This commit is contained in:
2026-09-19 10:39:40 -04:00
parent cfcc12f73a
commit 418c765c58
5 changed files with 239 additions and 5 deletions
@@ -25,6 +25,32 @@
# and put the census it prints here.
#
# ---------------------------------------------------------------------------------------------
# INITIAL CENSUS (f1, measured on the WSL gate tree - see docs/Disaggregated/notes/p5f/
# f1-report.md for the run):
# INITIAL CENSUS (f1, measured on the WSL gate tree ~/w7/p5f-f1 at cfcc12f7, 2026-09-19:
# 181 lane entries [-L 'integration-split|integration-magma-split'], 155 red, EVERY red entry
# carrying one of the pairs below in its private log - zero unnamed crashes; 22 green entries
# are the monolith-arm and the no-pulled-read scenarios, 4 skips are the two PersistentMapArm-
# and two StrictArming-only cases skipping outside their own lanes, all by design).
# Entry counts are first-blocker counts: an entry aborts on its FIRST pulled read, so retiring
# a row advances those entries to their next one. See docs/Disaggregated/notes/p5f/f1-report.md.
# ---------------------------------------------------------------------------------------------
#
# Espryt's readback path reads the framebuffer binding slot out of the block (the field's
# Espryt half retired at P5e for the UNBARRIERED path only; the barriered readback pull is the
# residual). The largest block of the census by far.
# 132 entries
GetFramebufferBindingSlot@ReadPixels
# The XFB program check at draw setup (FieldOwnership.def: "P3b/P4b (Espryt), P7 (Magma)").
# 11 entries (the TessellationXfbCapture / XfbCaptureBufferReuse family)
GetTransformFeedbackProgram@DrawArrays
# CopyImageSubData resolves both textures through the sticky forward (def: "P7").
# 6 entries
GetTextureObject@CopyImageSubData
# Magma's Clear path (the two DirectVulkan.Split.NamedBlit entries) - fm's first rows.
# 2 entries
GetFramebufferBindingSlot@Clear
# CopyTexImage2D's texture-unit read (def: "P5e (Espryt unbarriered), P7 (Magma)").
# 2 entries
GetTextureUnitObject@CopyTexImage2D
# The program-name validation sticky forward at ShaderStorageBlockBinding (def: "P9").
# 2 entries
ValidateProgramName@ShaderStorageBlockBinding
+19
View File
@@ -227,6 +227,22 @@ UNBARRIERED apply is a finding: `MGLOG_E_ONCE` with the kind, Fatal under strict
transport. For a barriered record the server reads the client's fill (the client is parked) and
its own stamp; for an unbarriered one it does not touch the block. `SetIdentity` (`:3001`) moves
to `ApplyOne`, once per session. This removes the E note's hazard (b.4).
**LANDED by P5f f1 — as the dual block, not as moved fields** (`MOBILEGL_IPC_ROLE_SPLIT_STATE=1`,
`PipeInputs.h`'s `gPipeInputsClientBlock`). The client could not be stopped from writing the
server's stamp storage while both roles shared one object, so the split landed one level down:
the fill side writes the CLIENT block (`MGPipeClientInputs()`, `PipeFill.cpp`), the stamp and
every backend read keep the SERVER block (`gPipeInputs`), and "applier-owned storage" is the
server block itself. The GL-thread `MGPipeServerClearVerbBoundary()` calls are re-pointed, not
deleted: they now clear the client block's flag (`MGPipeClientClearVerbBoundary()`), which is
never raised, so withdrawing the server's stamp is the applier's job alone
(`PipeApplier::LeaveApplier`). `SetIdentity` landed as `MGPipeServerBlockNoteIdentity()`, called
from `PipeApplier::Attach` and refreshed by each server stamp from the applier's own
served-context serial. And the ra2 caveat below is discharged by the knob's other half: under
the dual block a BARRIER_PULLED read has no value to be stale WITH, so `CountBarrierPull` is an
unconditional named `Fatal{UnmigratedPipeInput}` there — never a silent stale read. The
knob-off arm keeps the shared block and the old semantics, byte for byte; it is the A/B arm and
the negative control, not a second contract.
3. **The detector.** `MGPipeInputUnfreshRead` (`PipeInputs.cpp:191-224`) under an unbarriered
record takes `StrictBarrierPullFatal` (`:60-66`) regardless of `StrictErrors`; the seven sticky
forwards (`MGPipeStickyForwardPull`, `:251-257`) the same; `CountBarrierPull` stays for barriered
@@ -270,6 +286,9 @@ UNBARRIERED apply is a finding: `MGLOG_E_ONCE` with the kind, Fatal under strict
possible, and splitting the stamp is the P11 item — but note that per-role stamps **without**
versioning the BARRIER_PULLED values would be worse than today, because it turns a loud
`Fatal{UnmigratedPipeInput}` into a silent stale read. Both, or the values retired first.
*(P5f f1: LANDED — see the LANDED note on §3.2 above. The dual block makes the server block
itself applier-owned, and the unconditional dual-block Fatal in `CountBarrierPull` is the
"both": no BARRIER_PULLED value survives to be read stale.)*
---
+2 -2
View File
@@ -229,7 +229,7 @@ p50 181(约 1.10 倍)——那一次不可配对,只能作为方向性提
| `test.yml` / `apk.yml` 的 `feat/disaggregated` 触发器是临时的,合入 dev 前必须移除 | — |
| **P5e:BRIEF §3 的三条阴性对照与逐包 red-once 重跑未执行**(ID-121 已把后者重排到车道变绿之后,现在可跑) | `~/w7/notes/p5e/FLIP-CHECKLIST.md` phase C |
| **P5e:`split_negative_controls.sh` 的 E1 对照自检失败**,且索要的 `Fatal{BarrierViolation}` 在 `BATCH_WAITS=1` 下不可能触发——一个永远打不响的对照比没有对照更坏 | ID-122 |
| **P5e:契约 §3.2 的 per-role stamp 存储未落地**,正是它让 run-ahead 的元数据竞态成立;§8 修正 8 同样 UNLANDED | ID-120 / ID-135;`CONTRACT-P5E.md` |
| ~~P5e:契约 §3.2 的 per-role stamp 存储未落地~~ **P5f f1 已落地**(双块臂:server 块即 applier 私有存储,client 的 clear 重指到自己块,SetIdentity 由 `MGPipeServerBlockNoteIdentity` 落 server 侧);§8 修正 8 仍 UNLANDED | ID-135 已由 f1 关闭 / ID-120 仍开着;`CONTRACT-P5E.md` |
| **P5e:VD12 面板上限之上谁更快未答**(本机定不住更高频);VD32 那一轮回答了一般性问题 | `MEASUREMENTS.md` §11 / §11.1 |
| **新机会(非缺陷)**:VD32 下 apply 11-12 ms 对 client 15.7 ms,两侧不平衡;把工作从 client 挪到 apply 会直接降瓶颈——lockstep 下无意义,run-ahead 才解锁 | `P5E-RUNAHEAD.md` 末节 |
@@ -237,7 +237,7 @@ p50 181(约 1.10 倍)——那一次不可配对,只能作为方向性提
0. P5d 的遗留(`P5D-INPROC-PERFORMANCE.md` "什么没完成"):R-1 序列化留给 P3b/P4b → P11(`gPipeInputs` 版本化已写进 P11 行);线程放置记录;小项随 P3b/P4b 顺手。
1. **P5e 已收官**(2026-09-19,报告 [`P5E-RUNAHEAD.md`](P5E-RUNAHEAD.md)):出口门已逐条跑过(逐项结果记在 `ROADMAP.md` 的 P5e 出口门格):unit 两臂 2256/2256、`integration-split` 179/179 零 `Fatal{`、三条阴性对照按预期、逐包 red-once 各得恰好一个具名对。**剩下的只有 E1 对照**(ID-122):修掉它两个遮蔽性缺陷后仍红,而它索要的 `Fatal{BarrierViolation}` 在整次运行里出现 0 次,需要重新定义它证伪什么而不是调阈值。契约 §3.2(per-role stamp 存储)与 §8 修正 8 仍标为 UNLANDED,随后续阶段;G1 仍由 CI 断言(ID-123)。
2. **P5f 一切状态上 wire**([`P5F-WIRE-COMPLETENESS.md`](P5F-WIRE-COMPLETENESS.md)):插在 P6 之前。下一步是 `f0` 普查(只读)与 `f1` 双块机制——让红先出现,红就是清单。
2. **P5f 一切状态上 wire**([`P5F-WIRE-COMPLETENESS.md`](P5F-WIRE-COMPLETENESS.md)):插在 P6 之前。`f0` 普查已落(`notes/p5f/` 八篇),`f1` 双块机制已落(报告 [`notes/p5f/f1-report.md`](notes/p5f/f1-report.md)):`MOBILEGL_IPC_ROLE_SPLIT_STATE=1` 给两个角色各一份 PipeInputs,`integration-dualblock-split` 车道与 `dualblock-expected-fatals.txt` 棘轮已立,红清单即 fm/fs/fr/fv 的工作量。下一步是 `fm`(Magma 的 9 个字段)等逐包消红。
3. **P6 spawn transport**(阻塞于 P5f):计划与契约草稿已起草([`P6-SPAWN-PLAN.md`](P6-SPAWN-PLAN.md)、[`P6-CONTRACT-DRAFT.md`](P6-CONTRACT-DRAFT.md)),**下一步是跑 `a6` 那次只读审计,不写代码**:“P6 只是传输替换”这句话的证据是 P5c 时代的,P5e 之后已经过期。审计要点名去查:进程级静态里语义属于 context 的那一类(`s_synced` / `g_syncedRenderStateParameters` 是已知的两个,问题是还有几个——这类缺陷`inproc` 永远看不见);十二个 EGL forwarder 对 `SurfaceOpKind` 缺几个枚举;server 是否真能不链 `MG_Impl`。
3. 剩余首阻塞一轮(Magma compute/image、rd12、RGB mip、`texture-remint-pull` 仿真槽)。
4. P5e 出口门(`~/w7/notes/p5e/BRIEF-P5E.md` §3 / §4):`integration-split-strict` 转硬绿车道、三条阴性对照、Redmi 四臂(monolith / lockstep / credit 1 / credit 2)。P6 出口门:P5b 的完整渲染路径在 `spawn` 下绿;OpenRA 在 Adreno 830 上 split SSIM ≥ 0.99。
+4 -1
View File
@@ -1,6 +1,9 @@
# P5f — 一切状态上 wire:跨进程前的最后一次归零
> 基线 `feat/disaggregated @ 7f1d2134`。路径在 `MobileGL/` 下。**未开工。**
> 基线 `feat/disaggregated @ 7f1d2134`。路径在 `MobileGL/` 下。
> **进度**:`f0` 普查已落(`notes/p5f/` 八篇);`f1` 双块机制已落(报告
> [`notes/p5f/f1-report.md`](notes/p5f/f1-report.md):旋钮 `MOBILEGL_IPC_ROLE_SPLIT_STATE`、
> 车道标签 `integration-dualblock-split`、棘轮 `Harness/dualblock-expected-fatals.txt`)。
> **P6 在 P5f 之前不开工**:P6 的前提"只是传输替换"今天不成立,§2 是不成立的清单。
---
+186
View File
@@ -0,0 +1,186 @@
# P5f / f1 — 双块演练机制:落地报告
> 落地于 `feat/disaggregated`,代码提交 `cfcc12f7`(机制 + 测试 + 车道 + 脚本),
> 棘轮初版与本报告随文档提交。WSL 门树 `~/w7/p5f-f1`(branch `p5f/f1`),
> 对照基线树 `~/w7/p5f-base` @ `e75e00cb`。设计输入:`f0-dualblock-recon.md`。
> 旋钮 `MOBILEGL_IPC_ROLE_SPLIT_STATE` 默认关;关时两臂折成同一块,行为逐字节不变(G1 实测见下)。
---
## 1 落地内容
| 文件 | 内容 |
|---|---|
| `MobileGL/Config.h` | `IpcTable::RoleSplitState`(`MOBILEGL_IPC_ROLE_SPLIT_STATE`),只在 `MOBILEGL_BUILD_DISAGGREGATED` 闭包里 |
| `MobileGL/ConfigLoader.cpp` | `InitIpc()` 解析该旋钮;与 `MOBILEGL_PIPE_VERIFY` 互斥(强制关 + 一行 WARN,照 `RunAhead` 的形状);进 IPC 汇总日志行 |
| `MobileGL/MG_Backend/MGPipe/PipeInputs.h` | `gPipeInputsClientBlock`(leak-at-exit,同 `gPipeInputs` 的论证);`MGPipeRoleSplitActive()` / `MGPipeClientInputs()` / `MGPipeClientClearVerbBoundary()` / `MGPipeServerBlockNoteIdentity()` 声明;非 disagg 的 push 构建里 `MGPipeClientInputs()` 是折回 `gPipeInputs` 的 inline |
| `MobileGL/MG_Backend/MGPipe/PipeInputs.cpp` | 四个函数的定义;**机关**:`CountBarrierPull` 在 `MGPipeRoleSplitActive()` 时无条件 `StrictBarrierPullFatal(field, verb, "MOBILEGL_IPC_ROLE_SPLIT_STATE=1")`——先于设障判定、先于 strict 旋钮、先于计数器;`MGPipeServerStampVerbBoundary` 末尾调 `MGPipeServerBlockNoteIdentity()` |
| `MobileGL/MG_Impl/Pipe/PipeFill.cpp` | 填侧 9 处拼写 `gPipeInputs` → `MGPipeClientInputs()`(:627/:629/:653/:703/:1217/:2205/:3016/:3027/:3348),两处 `MGPipeServerClearVerbBoundary()` → `MGPipeClientClearVerbBoundary()`(:2228/:3437)。`MGPipeVerifyReadHook` 的 `&self == &gPipeInputs` 判等(:663)**故意不动**:hook 钉在读侧块上,且 verify 与双块互斥 |
| `MobileGL/MG_Remote/Server/PipeApplier.cpp` | `Attach()` 调 `MGPipeServerBlockNoteIdentity()`(CONTRACT-P5E §3.2 的 "SetIdentity moves to ApplyOne") |
| `MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp` | 6 个新用例(见 §3) |
| `MobileGL/MG_IntegrationTest/Scenarios/DualBlockScenario.cpp` + `Harness/DualBlockPeek.{h,cpp}` | 确定性 distinctness 对照用例(§6) |
| `MobileGL/MG_IntegrationTest/CMakeLists.txt` | `integration-dualblock-split` 车道注册(§5) |
| `MobileGL/MG_IntegrationTest/Harness/split_log_paths.py` | 新 `expect-fatal` 模式:Fatal 对的双向棘轮 + 每条红条目必须带具名 marker + 旋钮下一处 Admitted 即红 |
| `MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt` | 红清单棘轮初版(census 生成,非手写;§4) |
| `scripts/ci/dualblock_negative_control.sh` | 阴性对照脚本(`split_negative_controls.sh` 的合同:基线必须绿、红必须带被选条目自己的失败文本) |
| `.github/workflows/test.yml` | 两个新 CI 步:双块 census(预期红 + 棘轮核对 + 棘轮非空时全绿即红)与阴性对照 |
| `MobileGL/MG_Remote/CONTRACT-P5E.md` | §3.2 标 LANDED(落地形式写明);ra2 修正段的 UNLANDED 注记同步关闭(§7) |
后端与 applier 的 379 处 `MGB_CTX` 读点、`PipeApply.cpp` 的写点零改动——`gPipeInputs` 拼写保留为 server 块。
## 2 设计决策与对侦察报告的偏差
1. **`MGB_CTX_LIVE` 不会恒 false——侦察报告这里错了**。`PipeInputs::IsLive()`(PipeFill.cpp:2100)
不读 `m_live`,而是 forward 到 `LiveContext()`(读 `MG_State::pGLContext`)。inproc 下它仍答对——
但这是一次 inproc 结构性看不见的跨角色读(P6 时 MG_Impl 不在 server 进程里)。
**真正恒 nullptr 的是 `m_contextIdentity`**,而 DirectGLES 的 fb-slot memo 缓存(DirectGLES.cpp:191)
先比 identity 再决定是否走带检查的访问器:nullptr 对零初始化缓存是**命中**,会发空槽指针——
无名崩溃,恰是演练要消灭的形态。所以 f1 落了 `MGPipeServerBlockNoteIdentity()`。
(附带:`m_live` 目前没有任何读者,见 §8。)
2. **SetIdentity 不是"Attach 一次"而是随 stamp 按 `MGPipeApplierContextSerial()` 刷新**。契约 §3.2 原文是
"moves to ApplyOne, once per session";identity 的 consumers 是 per-context 的 memo key
(fb-slot 缓存、以及 `GetTextureContextId` 已经用同一串行号回答),context 切换(applier reset)
时它必须动。实现:stamp 里比较即存,knob 关时整个函数早退——零行为变化。
3. **车道形态用 env 车道而非整批重注册**。侦察 §6.2 建议 `gtest_discover_tests` 以
`DirectGLES.Split.DualBlock.` 前缀重注册同一批 scenario。实际采用 strict 车道的同构形态
(`MOBILEGL_IPC_ROLE_SPLIT_STATE=1 MOBILEGL_IPC_STRICT_ERRORS=1 ctest -L 'integration-split|integration-magma-split'`),
只新注册 1 条对照条目。理由:重注册会让 179 条条目集合成为第二份需要手工保持同步的清单
(各块的 TEST_FILTER 排除项各不相同,逐块复制必然漂移),而 env 车道的红条目集合与
`integration-split` **恒等由构造保证**。发现数不翻倍(G14 只增不删仍满足:净增 3 条注册名,
见 §5)。Magma 由 `integration-magma-split` 并入同一 census 覆盖(2 条 NamedBlit)。
4. **对照条目不把旋钮钉进自己的 ENVIRONMENT**——这是对 N-6 钉死惯例的一次有理由偏离:
ctest 的 ENVIRONMENT 属性覆盖进程环境,钉死 `ROLE_SPLIT_STATE=1` 会让 knob=0 形态(即阴性对照本身)
经 ctest 不可达。失败方向安全:不带旋钮裸跑 `-L integration-dualblock-split` 是**红**(具名断言),
不是静默绿。条目钉了 `MOBILEGL_IPC_RUN_AHEAD=0`:串行分离断言要求每个 verb 边界都填,
run-ahead 下未设障 verb 不填,断言会变成时序相关。
5. **`VertexAttribDefaultsOf` 的镜像自查改读 client 块**(PipeFill.cpp:3016)。knob 开时它退化为
对同 verb 自家填充的恒真自检——applier 镜像比对本身是 lockstep 时代的跨角色读,正是双块要消的
形状;knob 关时两块同物,语义逐字节不变。
6. **`MGPipeClientClearVerbBoundary` 而非"删除 client 侧 clear"**(契约 §3.2 的字面是 DELETED):
调用点保留、重指到 client 块。knob 关时同一块同语义;knob 开时 client 块的 flag 从不举起,
clear 是无操作——"client 不再能撤 server 的 stamp"由块分区结构性成立,不靠删代码。
## 3 单元测试(6 个新用例,FieldOwnershipTest)
- `RoleSplitOffFoldsTheFillSideOntoTheSharedBlock`:旋钮关 → `MGPipeClientInputs()` 就是 `gPipeInputs`。
- `RoleSplitUnderMonolithTransportIsStillOneBlock`:旋钮开 + monolith → 仍折成一块(monolith 折叠)。
- `RoleSplitGivesTheFillSideADistinctBlockTheStampNeverTouches`:两块是不同对象;server stamp 只动
server 块(serial +1、flag 举起),client 块纹丝不动;`MGPipeClientClearVerbBoundary` 不撤 server 的
stamp;stamp 后 server 块 identity 非空。
- `TheClientVerbLeaveWritesTheClientBlockAlone`:`MGPipeLeaveVerb` 只推 client 块 serial。
- `DualBlockMakesABarrierPulledReadANamedAbortWithoutStrict`(fork):旋钮开、**strict 关**,
server stamp 后读 `GetBoundVertexArray` → SIGABRT 且日志带
`Fatal{UnmigratedPipeInput, "GetBoundVertexArray@DrawArrays"} [BARRIER-PULLED, MOBILEGL_IPC_ROLE_SPLIT_STATE=1, ...]`。
这是机关的 R-16:O 类 SharedPtr 字段没有 null 检查,没有这一臂它就是无名段错误。
- `DualBlockDoesNotArmUnderMonolithTransport`(fork):旋钮开 + monolith → 同一读被计数而非 Fatal。
## 4 红清单(census 实测,`~/w7/p5f-f1`,两次独立运行同一对集)
车道:`MOBILEGL_IPC_ROLE_SPLIT_STATE=1 MOBILEGL_IPC_STRICT_ERRORS=1 ctest -L 'integration-split|integration-magma-split'`。
181 条目:**155 红,红条目 155/155 带具名 Fatal,零无名崩溃**;22 绿(3 条 PushMonolithArm 单块臂 +
17 条路径上本就不读 BARRIER_PULLED 字段的 scenario + Ct 角色守卫死亡用例 + SplitLogPaths 元检查),
4 skip(两条 PersistentMapArm 专属、两条 StrictArming 专属,在自己的车道外 by design 跳过)。
**6 对首阻塞 `<字段>@<verb>`**(每条目死于自己的第一处 pull,退掉一对就推进到下一对——清单会随
fm/fs/fr/fv 缩短):
| 对 | 条目数 | FieldOwnership.def 退役归属 | f1 包归属建议 |
|---|---|---|---|
| `GetFramebufferBindingSlot@ReadPixels` | 132 | P5e (Espryt unbarriered), P7 (Magma) | Espryt readback 路径的载体(最大一块,决定 fv/收尾工作量) |
| `GetTransformFeedbackProgram@DrawArrays` | 11 | P3b/P4b (Espryt), P7 (Magma) | XFB 程序随 draw 的载体 |
| `GetTextureObject@CopyImageSubData` | 6 | P7 | sticky forward 的 handle 化 |
| `GetFramebufferBindingSlot@Clear` | 2 | P5e (Espryt), P7 (Magma) | **fm 的前两行**(两条 DirectVulkan NamedBlit) |
| `GetTextureUnitObject@CopyTexImage2D` | 2 | P5e (Espryt unbarriered), P7 (Magma) | 纹理单元指针的载体 |
| `ValidateProgramName@ShaderStorageBlockBinding` | 2 | P9 | sticky forward |
注意与 strict 车道的分工:strict 管"设障 pull 有没有被承认"(值还在共享块里),双块管"值根本不在
对面"。同一笔债的两个探针,两份棘轮独立。旋钮下 `Admitted{` 实测为零(机关先于承认臂打响)。
## 5 车道与标签(ID-131 的证据)
`ctest -N` 实测(build-split @ cfcc12f7):
- `-L integration-split` = **179**(不含新条目——`integration-dualblock-split` 里隔着 `dualblock`,
子串匹配不上);
- `-L integration-dualblock-split` = **1**(`DirectGLES.Split.DualBlock.DualBlockScenario.TheTwoRolesHaveDistinctBlocks`);
- `-L integration-magma-split` = 2(未变);
- `-L integration-gpu` = **1359**(基线 1357 + 2 条 ambient 注册 `DirectGLES./DirectVulkan.DualBlockScenario.*`——
与 F1WireScenario 的 19 条 ambient 条目同形态,运行时 monolith 臂 GTEST_SKIP)。
G14:名集合只增不删(新增 6 个单测名 + 3 条 itest 注册名)。
`SplitLogPaths.PrivateAndDistinct` 通过:新条目恰好一条私有日志且不与他人重名。
## 6 阴性对照(P5F §6,两种形态都真跑过)
`scripts/ci/dualblock_negative_control.sh`(p5f_gate.sh 的 `negctl` 步)实测:
- **旋钮=1**:`DirectGLES.Split.DualBlock.DualBlockScenario.TheTwoRolesHaveDistinctBlocks` **Passed**
(0.68s;三块断言全过:roleSplitActive、两块地址不同、client verb 边界推 client 块 serial 而
server 块 serial 不动)。
- **旋钮=0**:同一条目 **Failed**,失败文本正是该用例自己的断言:
`dual-block control: MGPipeRoleSplitActive() is false - MOBILEGL_IPC_ROLE_SPLIT_STATE is not armed ...`。
- 脚本同时坚持:基线绿(0 failed、0 skip——skip 不算绿,ID-62)、对照红且红带案文。
## 7 CONTRACT-P5E §3.2(ID-135)的核实结论
侦察的判断成立:双块落地后 §3.2 的语义**在旋钮开的臂上自动成立**——server 块就是 applier 私有存储,
stamp 写的 `m_filled/m_currentVerb/m_serverStampedVerb` 都在 server 块,client 的两处 clear 已重指到
自己块(无人读过它的 flag),`SetIdentity` 落到了 applier 侧(Attach + stamp 刷新)。ra2 的告诫
("不配版本化就更坏")由机关兑现:BARRIER_PULLED 值在 server 块里**不存在**,读它具名死,不存在
"静默旧值"。契约文本已从 UNLANDED 改为 LANDED 并写明落地形式。旋钮关的臂保持共享块旧语义,
是 A/B 与阴性对照所需,不是第二份契约。
## 8 附带发现(与本包无关,未修)
1. **`strict-expected-markers.txt` 有两行 stale**(`GetBoundVertexArray@DrawArrays`、
`GetBufferBindingSlot@DrawArrays`):strict 车道实测观测 8 对(7 admitted + 1 escalated),
双向棘轮因这两行不再出现而红。**归因:基线树 `~/w7/p5f-base` @ e75e00cb 逐字复现同一失败**
(同一观测集、同一 vanished 对),与 f1 无关——前者随 client-arrays 条目迁出
`integration-split`(ID-134 的标签搬家)而失效,后者是 MultiDrawTier 车道不再产生它。
修复是删掉这两行(属 strict 车道维护,留给下一轮车道养护,本包不顺手改别人的棘轮)。
f1 的 strict 门按任务口径判读:179/179 绿、`Fatal{` 为零、观测 Admitted 恰为既有 8 对——满足。
2. **`check_doc_citations.py --strict` 在基线即红**:CONTRACT-P5.md:349(`Init.cpp:44`)与 :524
(`Core.cpp:39`)两处 basename 歧义,base 树同 rc=1;CI 本就以 `|| true` 非 strict 跑它。
f1 新增文档没有引入新歧义(见 §9 的对照数字)。
3. **`m_live` 无读者**:`PipeInputs::IsLive()` forward 到 `LiveContext()`,从不读 `m_live`;
`SetIdentity` 写它纯属记录。inproc 下 `MGB_CTX_LIVE` 经此跨角色读到 client 的 `pGLContext`——
答得对但正是 P6 要消灭的形状。建议 fm/fs 或收尾包把 server 块的 liveness 改读自己的 `m_live`
(f1 已会在 stamp/Attach 时置它),并决定 context destroy 时谁把它打回 false
(侦察 §8.2 的开放问题,候选:applier_reset 控制记录)。
4. **census 车道与 strict/gpu 车道共享私有日志文件,绝不能并发跑**。本报告撰写期间我曾在后台并发
发起 `dualblock` 与 `gpu` 两步,gpu 车道(knob 关)覆写了 census 正在读的 18 份日志,表现为
"红而无 marker + 对子计数漂移"。单独重跑后两套数字稳定一致。p5f_gate.sh 的步序是串行的;
教训写在这里防后来者重蹈。
## 9 逐门数字汇总
| 门 | 结果 |
|---|---|
| build(split flavour) | rc=0,984 TU |
| unit(旋钮关) | **2262/2262**(基线 2256 + 新增 6) |
| integration-split(旋钮关) | **179/179**,零回归 |
| integration-split+magma(旋钮开) | 181 条目,155 红**全部具名**、零无名崩溃;6 对首阻塞落盘为 `dualblock-expected-fatals.txt` 初版 |
| flavours | pull rc=0(`MOBILEGL_PIPE_PUSH=1` absent ✓)、push rc=0(=1 ✓),compile_commands.json 断言过 |
| **G1** | pull flavour 对基线 e75e00cb:`.text` 10806051 → 10806051(+0),defined symbols 27815 → 27815,**0 added / 0 removed / 0 resized / 0 renamed**——零认定 |
| integration-gpu(旋钮关) | **1359/1359**(1357 + 2 条 ambient DualBlock skip 条目) |
| 生成器 --check/--self-test、include 闭包、dirty-surface | 全绿(gen_pipe 12 控制、field-ownership 13 控制、dirty-surface 27 控制均打响) |
| doc 引用 --strict | 基线既有 2 处 ambiguous(CONTRACT-P5.md:349/:524),f1 新增 0 处 |
| strict(旋钮关) | 179/179 绿、`Fatal{`=0、观测 8 对 admitted(=任务口径的既有八行);棘轮文件的 2 行 stale 见 §8.1 |
| 阴性对照 | 旋钮=1 绿 / 旋钮=0 红且带案文,两种形态各跑过(§6) |
## 10 复现
```bash
# WSL 侧(树 ~/w7/p5f-f1,branch p5f/f1)
bash ~/w7/notes/tools/p5f_gate.sh f1 build # configure + build-split
bash ~/w7/notes/tools/p5f_gate.sh f1 unit
bash ~/w7/notes/tools/p5f_gate.sh f1 isplit
bash ~/w7/notes/tools/p5f_gate.sh f1 strict
bash ~/w7/notes/tools/p5f_gate.sh f1 dualblock # census + 棘轮核对
bash ~/w7/notes/tools/p5f_gate.sh f1 negctl # 阴性对照两种形态
bash ~/w7/notes/tools/p5f_gate.sh f1 gpu
bash ~/w7/notes/tools/p5f_gate.sh f1 flavours
bash ~/w7/notes/tools/p5f_gate.sh f1 gens
# G1:python3 scripts/symbol_report.py --before ~/w7/p5f-base/build-pull/libMobileGL.so \
# --after build-pull/libMobileGL.so --threshold 0
```