docs(overhaul): contract S4-c2 final fix round

This commit is contained in:
Erik 2026-09-04 08:06:14 +02:00
parent 90d4ef4ac9
commit ac74fbd84e

View file

@ -516,3 +516,207 @@ sites, the register, and the gate's CI value — the list/router/flush core stan
**A8 (NOTE, no action)** — the non-deferring particle path keeps `ParticleSubmissionOrdering.Sort` (`ParticleRenderer.Rhi.cs:298`); it is outside the queue and pre-existing. M1's immediate draws for rows 1/5 must not add a sort (retail's immediate `RenderMeshSubset` draws in mesh order).
**Gate additions for this round:** the renderer-level A1 pin; the register grep proving no id collision (`grep -c "^| ~*AP-<new>"` == 1 for each new id). Allowed files unchanged except `RetailPViewRenderer.cs` (A1) and its tests.
## 12. S4-c2 fix round 2 (lead, 2026-09-04 — FINAL allowed round; any further code-fix finding stops the chunk and is written up)
**Input under review:** `048d5b12f` (chunk implementation) plus `cc8e5677a`
(fix round 1), clean in `s4-c2-impl` and directly descended from S4-c1
`766f9e749`. The lead verified the paired binary before writing this contract
(`C:\Users\erikn\Downloads\acclient.exe` matches the exact retail PDB).
**Sequential verdicts on `cc8e5677a`:** retail-faithful **FAIL** (two major,
five minor); production/gate-honesty **FAIL** (six major, three minor). The
Release build, hermetic lane, InstalledDat identities, shader lane, allowed
file scope, core two-list FIFO, strict-less valve, and the three re-performed
round-1 mutations all pass. Fix ONLY the items below. This is round 2 of the
two allowed by §8/the campaign process.
### 12.1 Retail facts that bound the fix
1. `CMaterial::CheckAlphaValues` @0x005396a0 uses `fcomp 1.0; test ah,5`:
any material-alpha channel **below** 1 sets `has_alpha`; channels at or
above 1 leave it clear (unordered does not take the claimed `else` path).
Production particle opacity is clamped to `[0,1]`, so the top-byte
`!= 0xff` predicate is behaviorally exact only under that stated domain.
`SetTranslucencySimple` @0x005396f0 writes the same `1-translucency` value
to all four channels; `Particle::Update` @0x0051c290 reaches it through
`CPhysicsPart::SetTranslucency` at the call `0x0051c79d`.
2. A row-5 opaque immediate subset reaches `RenderMeshSubset` @0x0059ca10
and `SetSurface` @0x0059c4d0. For opaque/no-material-alpha the paired bytes
at `0x0059c6e4..0x0059c6f9` and `0x0059c7b7..0x0059c806` select ONE/ZERO,
disable alpha blending, and pass depth-write 1 to `SetDepthBufferMode`
(call at `0x0059c866`). Immediate timing on an alpha/no-depth-write
pipeline is therefore NOT a faithful row-5 port.
3. `DrawEnvCell` @0x0059f170 installs environment detail at `0x0059f1c2`,
calls `DrawMesh` at `0x0059f212`, and clears detail at `0x0059f21a`
(fallback `0x0059f287`). With detail absent, list selection is still the
exact constructed **per-subset** mask. `ConstructMesh` @0x0059dfa0 maps an
alpha-family surface to `0x02`, then a pure `Base1ClipMap` surface to
`0x08`; `DrawMesh` row 3 sends `0x08` to CLIP. Canonical installed-DAT
witness: cell `0xF4180104`, surface `0x08000BFF`, type
`BASE1_CLIPMAP (0x4)`, including eight reversed-side copies (see
`oh2-cellstruct-surface-contract.md`). The extracted
`TextureBatchData.RetailSurfaceMask` already preserves this fact; the App
upload currently discards it.
4. `DrawMesh` initializes two independent first-for-list flags at entry and
clears a given flag after the first append to that list **within the same
DrawMesh call**. The capture's 9,685 `new=1` / 871 `new=0` entries prove
that it is not “true for every subset.” The port may continue to omit this
unread optimization flag, but every comment and evidence claim states the
actual quantifier.
5. `PView::DrawCells`' `LandscapeFlush` at call `0x005a4872` is inside the
nonzero outside-view branch. An outdoor-root frame has DrawBuilding
barriers and the final `SmartBox::RenderNormalMode` drain, not a
`LandscapeFlush`; no opaque world draw follows the removed call before
`RetailAlphaQueue.EndFrame` performs that final drain.
### 12.2 R2-1 — opaque particle row 5 must use opaque depth state
Add the mesh-particle opaque pipeline beside the existing mesh alpha/additive/
inverse pipelines, using the existing `particle_mesh` shaders and vertex
layout, `GpuBlendMode.None`, depth test ON, depth write ON, retail world
compare, the existing dynamic per-batch cull override, and no alpha-to-
coverage. Row-5 immediate mesh submissions bind this pipeline. Do not change
billboard behavior, queued row-4 replay, additive/inverse replay, the
pre-existing non-queue `DrawOrderedRhi` sort, or add a shader.
The production pin must drive `DeferToRetailAlphaQueue` (not only
`RouteParticleSubmission`): an opaque mesh particle with alpha `<1` appends
to ALPHA, draws once at flush, and never invokes the immediate draw; the same
mesh at alpha `1` invokes immediate once, appends nothing, and binds a
pipeline whose description proves Blend=None and Depth=(test true, write
true). Restoring the throw, deleting the immediate call, or selecting the
old `_meshAlphaPipeline` must each fail a named assertion. Correct the M1
comments: at this hardcoded scene/no-detail/no-multipass site only row 5 is
reachable as Immediate; row 1 is not.
### 12.3 R2-2 — EnvCell routing is exact per subset and replay is list-filtered
Carry `TextureBatchData.RetailSurfaceMask` into `ObjectRenderBatch` at the
existing upload boundary. The detail-off cell-shell path scans the real
transparent render batches and applies `RetailAlphaMeshRouter.Route` to each
batch's retained mask. It may coalesce only to at most one token per
`(cellId, RetailAlphaList)`; a CLIP token's replay draws only batches routed
to CLIP and an ALPHA token's replay draws only batches routed to ALPHA. A
mixed cell therefore contributes to both lists without either replay drawing
the other list's subsets. Any mask whose table result is Immediate is drawn
at the cell turn and is excluded from both deferred replays; do not replace
the table with “detail on/off” branching. With environment detail active,
eligible subsets remain immediate with their detail pass at that turn.
Use separate CLIP/ALPHA source identity, or an equivalently discriminating
retained token, so CLIP-then-ALPHA adjacency cannot lose the list filter.
Retain bounded, reusable scratch; no per-frame LINQ/closure/array allocation.
The accepted residual is **only** per-cell-per-list token granularity (rather
than retail per-subset tokens), not list selection. Rewrite AP-238 to say
that. Rewrite AP-240 because EnvCell pure-ClipMap subsets are now genuine
CLIP feeders; ordinary Wb GfxObj/scenery/building ClipMap remains the
registered alpha-to-coverage structural gap.
Production pins (not a hand-constructed source-only test):
- canonical/synthetic `RetailSurfaceMask=0x08` reaches CLIP, while `0x02`
reaches ALPHA;
- a mixed `0x08`/`0x02` cell creates one token in each list and each drain
renders only its own batch set, exactly once;
- detail OFF draws neither deferred set before the next real queue drain;
detail ON draws at the cell turn, with detail, and queues nothing;
- a particle/ordinary source between two cell tokens remains between them;
- hardcoding `MaskAlphaFamily`, inverting the production immediate predicate,
or removing the replay filter fails the named assertion. Structural IL-call
order and a manually assembled `EnvCellAlphaDrawSource` alone do not count.
### 12.4 R2-3 — rejected sources still participate in frame cleanup
All three production callers reserve source-owned payload before
`TryAppend`. When a list already holds 3,000 entries, `TryAppend` must still
register that source for the frame's reset, then return false without adding,
preparing, or drawing the rejected token. Pin with source A filling ALPHA and
source B making its first, rejected append: B is never prepared/drawn but is
reset exactly once at flush/end/abort, and its retained payload is empty for
the next frame. This test must use two sources; the existing same-source
overflow pin cannot detect the leak. Preserve retail's drop/no-immediate-
fallback behavior and the queue's warmed scratch-retention policy.
### 12.5 R2-4 — replace structural/pure claims with real production pins
1. **M3:** drive both Wb production submit sites. Building shell + detail ON
draws immediately at its `AlphaSubmitMark`, performs the detail pass, and
queues nothing; detail OFF queues and drains without a detail pass; an
ordinary non-shell translucent batch is unchanged by the building-detail
flag. Hardcoding `detailSurfaceActive=false` at the production line must
fail. No source-text/IL-only substitute.
2. **M1/M6:** the tests in §§12.212.3 call the production dispatch methods.
Keep the pure router/source tests as narrow unit coverage, but delete their
false claims that a production mutation fails “here.”
3. **A1:** add the positive renderer-level outdoor sequence pin through the
real `RetailPViewRenderer` frame owner: `[DrawBuilding x N,
RenderNormalMode]`, never `LandscapeFlush`, with pass-end after the last
outdoor particle/world contribution. Restoring the deleted direct outdoor
`FlushLandscapeAlpha` fails this test. The existing source/IL absence test
may remain but is not the positive proof.
4. Every new/changed behavior pin has a commit-body mutation report naming
the exact production mutation and the **actual first failing assertion**.
A test that still passes the mutation is not a pin and is not claimed.
### 12.6 R2-5 — evidence, register, and prose truth
- `ParticleRenderer` says CheckAlphaValues is `<1` and explicitly bounds the
equivalence to the clamped `[0,1]` production domain.
- `RetailAlphaQueue`/trace prose gives the true per-DrawMesh first-for-list
lifecycle; it does not say every append is `new=1`.
- Packet §10 names the landed `AlphaFlushSites_*` (live) and
`AlphaFlushCounts_*` (KnownFailure) split. Zero mesh content is sufficient
to explain the present count failure. If future-content residuals are
described, name only the proven ones: EnvCell's coarser one-token-per-cell-
per-list replay and ordinary-Wb CLIP's AP-240 structural gap. Do not claim
all acdream entries are per instance—Wb and mesh particles already submit
per batch/subset instance. Fix the stale removed Fact names and the packet's
off-by-one sequence multipliers.
- AP-239 says its blend mode is unchanged but overlap-dependent compositing
can be visibly different; delete “never a visual difference.” AP-238 and
AP-240 change as in §12.3. Fix `RetailPViewPassExecutor`'s stale AP-236
citation to AP-238. No new AP row is needed if the production fixes satisfy
this contract; keep the one-occurrence/id-count checks honest.
- Focused-gate prose lists the exact filters it actually runs. The round-1
claimed filter produced **123/123**, not 120, and the corrected filter must
explicitly include `EnvCellAlphaDrawSourceTests` and
`RetailPViewPassExecutorTests`. Report the new post-fix total from output,
never a forecast.
- Add a concise “§12 result” note only after every gate below passes; it lists
real output and real mutation failures, not intent.
### 12.7 Scope, gates, and stop rule
**Allowed files:** `src/AcDream.App/**`, `tests/AcDream.App.Tests/**`,
`docs/architecture/retail-divergence-register.md`, and this packet. The
Content model/serializer already retains the mask and must not be changed.
`docs/architecture/worldbuilder-inventory.md` may change only if a now-stale
S4-c2 citation introduced by round 1 must be corrected. One implementation
commit on top of `cc8e5677a` in `s4-c2-impl`.
**Must NOT:** reintroduce distance; add a flush site; alter S4-c1 punch/seal;
change ordinary Wb ClipMap alpha-to-coverage (AP-240); add alpha-to-coverage
to particles; add a shader; recover an overflow with an immediate draw; hide
a production gap behind an IL/source-text-only test; run a graphical client
from the implementation/review worktree.
**Automated gates (real output copied into the commit body/result note):**
Release solution build, 0 warnings/errors; hermetic lane wholly green;
InstalledDat exactly 255 pass / 10 fail / 1 skip with only TowerAscent,
the two #383 layout sweeps, #458 doorway, and six `AlphaFlushCounts_*`
failures (all six `AlphaFlushSites_*` pass); shaders 32/32; corrected focused
filter including queue/router/walk/driver/particle/Wb/EnvCell/PView production
tests; the new capacity-cleanup pins; register identities/counts; warmed
allocation proof for the changed immediate/filter/source paths.
**Sequential lenses after implementation:** (1) retail-faithful, narrowed to
the row-5 pipeline and exact EnvCell per-subset mask/list; then (2)
production/gate-honesty, narrowed to rejected-source cleanup, production-path
mutation discrimination, scratch bounds, gate identities, register/prose,
and allowed scope. Each returns PASS/FAIL with file:line findings. Because
this is the final allowed fix round, any finding that requires another code
change is a third round: STOP, leave the implementation unlanded, and write
the chunk failure into the packet/ledger. Comment-only clarification that
does not change a claim may be adjudicated by the lead; it may not conceal a
behavior or test defect.