diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md index 82fb4c85..acb72103 100644 --- a/docs/architecture/retail-divergence-register.md +++ b/docs/architecture/retail-divergence-register.md @@ -144,7 +144,7 @@ readiness/requeue adaptation. See | AD-13 | 1-second dedup window for identical system chat messages (retail has none) | `src/AcDream.Core/Chat/ChatLog.cs:29` | ACE dual-sends the same system text (0xF7E0 + 0x02EB) for back-compat; without dedup every line doubled (Phase J compromise) | Two genuinely distinct but textually identical system messages within 1 s collapse to one line where retail shows both | ACE dual-send 0xF7E0 + 0x02EB | | AD-15 | `IsEnv` masks low-16 of the cell id (`(Id & 0xFFFF) >= 0x100`) where retail tests the full id | `src/AcDream.Core/World/Cells/ObjCell.cs:25` | Every real prefixed EnvCell id has low-16 ≥ 0x100 and every outdoor cell ≤ 0x40 — identical answers for all real dat ids, works for both bare and prefixed forms | None for real dat data; a hypothetical convention-violating id would route to the wrong (BSP vs terrain) point-in-cell logic | `CObjCell::GetVisible` pc:308215 | | AD-16 | Building-flood gate is a CPU frustum test on each building's `PortalBounds` AABB; retail floods exactly when the shell draws and an aperture survives (no bounds constant anywhere) | `src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs` (`RuntimeWorldFrameBuildingSource.Gather`) | Documented as the tight equivalent of the shell viewconeCheck for flood purposes (the FPS fix the Chebyshev≤1 hack approximated); per-portal admission still goes through BuildFromExterior's screen clip; missing-bounds buildings always flood (safe over-include) | A too-small/stale PortalBounds AABB means the interior never floods — doorway shows a hole/black aperture from outside (inverse of the vanishing-staircase class) | `DrawBuilding` 0x0059f2a0; `BSPPORTAL::portal_draw_portals_only` 0x53d870 | -| AD-17 | **REWRITTEN 2026-09-03 at Campaign OVERHAUL v2 S3 chunk 4 (rounds `ff607a1e0` + `7df0b94c9`).** Only two GPU clips remain, both retail `PView::DrawCells` portal-polygon draws: the exit seals and the punch fans. Each carries ≤8 `gl_ClipDistance` half-planes per view region (`ClipPlaneSet.MaxPlanes`; `Render::copy_view` caps a view at 31 vertices). Overflow handling differs by consumer: a >8-edge exit-seal view uses its convex NDC AABB as a conservative four-plane gate (`WalkFrameDriver.AppendClipSlot`); a >8-edge or multi-polygon OUTSIDE view yields a zero-plane slice (`ClipFrameAssembler.AppendOutsideSlice`'s fallback: slot 0, empty `Planes`, `ScissorFallbacks` counted) and that view's punch fan draws UNCLIPPED. Retail CPU-clips its portal polygons exactly. The former union-AABB scissor fallback, the sky's doorway scissor bracket, the per-slice terrain/sky/weather clips, the TerrainClip UBO and `NdcScissorRect` are DELETED: sky, terrain, weather and particles are never GPU-clipped, matching retail (`LScape::draw` installs no view before any of them). | `src/AcDream.App/Rendering/ClipPlaneSet.cs:23`; `src/AcDream.App/Rendering/ClipFrameAssembler.cs` (`AppendOutsideSlice`); `src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs` (`AppendClipSlot`); `src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs` (`DrawWalkPunchFan` reads `OutsideViewSlices[i].Planes`) | Vulkan's `VkPhysicalDeviceLimits::maxClipDistances` floor is 8; over-inclusion is the safe direction for a depth-only seal/punch (it can cover at most the whole aperture fan), under-inclusion is the bug class | A >8-edge exit-seal view seals its AABB instead of the exact polygon and a >8-edge outside view punches its whole fan — depth-only over-include with no color bleed (the former **#130** strip family died with the scissor); an interior root's aperture exactness now rests on the depth clear + exit seals + interior repaint, never on a GPU clip of the landscape | `ACRender::polyClipFinish` decomp:702749; `PView::DrawCells` 0x005a4840 portal_view slices; `Render::copy_view` 0x0054dfc0; `LScape::draw` 0x00506330 | +| AD-17 | **REWRITTEN 2026-09-03 at Campaign OVERHAUL v2 S3 chunk 4 (rounds `ff607a1e0` + `7df0b94c9`).** Only two GPU clips remain, both retail `PView::DrawCells` portal-polygon draws: the exit seals and the punch fans. Each carries ≤8 `gl_ClipDistance` half-planes per view region (`ClipPlaneSet.MaxPlanes`; `Render::copy_view` caps a view at 31 vertices). Overflow handling differs by consumer: a >8-edge exit-seal view uses its convex NDC AABB as a conservative four-plane gate (`WalkFrameDriver.AppendClipSlot`); a >8-edge or multi-polygon OUTSIDE view yields a zero-plane slice (`ClipFrameAssembler.AppendOutsideSlice`'s fallback: slot 0, empty `Planes`, `ScissorFallbacks` counted) and that view's punch fan draws UNCLIPPED. Retail CPU-clips its portal polygons exactly. The former union-AABB scissor fallback, the sky's doorway scissor bracket, the per-slice terrain/sky/weather clips, the TerrainClip UBO and `NdcScissorRect` are DELETED: sky, terrain and weather are never GPU-clipped, matching retail (`LScape::draw` installs no view before any of them). The mesh and particle shaders KEEP a per-cell `clipRegions[aClipSlot]` input, but the walk feeds every ordinary part (`WbDrawDispatcher.WalkClassify.ResolvePartVisible` — a Boolean sphere test, whole-mesh slot 0) and every particle (`ParticleRenderer`'s `clipSlot` default 0) the no-clip slot, matching retail's `DrawMeshInternal` sphere test; that machinery is dormant in the walk path and live only for the non-walk flat path. | `src/AcDream.App/Rendering/ClipPlaneSet.cs:23`; `src/AcDream.App/Rendering/ClipFrameAssembler.cs` (`AppendOutsideSlice`); `src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs` (`AppendClipSlot`); `src/AcDream.App/Rendering/RetailPViewPassExecutor.WalkLeaf.cs` (`DrawWalkPunchFan` reads `OutsideViewSlices[i].Planes`) | Vulkan's `VkPhysicalDeviceLimits::maxClipDistances` floor is 8; over-inclusion is the safe direction for a depth-only seal/punch (it can cover at most the whole aperture fan), under-inclusion is the bug class | A >8-edge exit-seal view seals its AABB instead of the exact polygon and a >8-edge outside view punches its whole fan — depth-only over-include with no color bleed (the former **#130** strip family died with the scissor); an interior root's aperture exactness now rests on the depth clear + exit seals + interior repaint, never on a GPU clip of the landscape | `ACRender::polyClipFinish` decomp:702749; `PView::DrawCells` 0x005a4840 portal_view slices; `Render::copy_view` 0x0054dfc0; `LScape::draw` 0x00506330 | | AD-18 | Aperture far-Z punch is two-pass stencil-gated with an invented mark bias: 0.0005 NDC capped to a 0.5 m EYE-SPACE span (`MarkBiasNdc`); retail's single DEPTHTEST_ALWAYS punch is safe only under painter's far→near order we don't have | `src/AcDream.App/Rendering/PortalDepthMaskRenderer.cs:149` | **#117** (2026-06-11): the unconditional punch erased nearer occluders, painting interiors through them; the two-pass form is the z-buffered equivalent of retail's ordering safety. **#129** (2026-06-12): the constant-NDC bias spanned ~190 m of eye depth at a landblock (non-linear depth) → distant occluders punched; the eye-space cap bounds the reach (`Issue129PunchBiasTests`). DO-NOT-RETRY: punch must stay depth-gated (ISSUES #108) | Door-plane-hugging geometry beyond the 0.5 m cap re-occludes the aperture (a **#108**-class regression at >10 m viewing range); an occluder within the cap in front of a distant aperture still punches through | `D3DPolyRender::DrawPortalPolyInternal` 0x0059bc90 (maxZ1=7 / maxZ2=6) | | AD-19 | Under outdoor roots, ALL dynamics draw in one z-buffered final pass; retail draws objects painter-ordered per landcell inside the landscape pass (interior roots route per **#118**) | `src/AcDream.App/Rendering/RetailPViewRenderer.cs:126` | The dynamics-drawn-LAST invariant is what makes the aperture depth punch safe (first BR-2 attempt punched after dynamics and erased the player, reverted `88be519`); z-buffer substitutes for painter's order on opaque geometry | Punch/seal correctness hinges on an ordering invariant — any pass added after DrawDynamicsLast, or alpha content needing painter order, gets erased inside apertures or composites wrong | `LScape::draw` → `DrawBlock` 0x005a17c0 → DrawSortCell pc:430124; `PView::DrawCells` 0x005a4840 | | AD-20 | Camera sweep fallback seeds the eye's `AdjustPosition` from the PLAYER's cell; retail re-seats at the sought eye's own tracked cell (rest of function is a verbatim `update_viewer` port) | `src/AcDream.App/Rendering/PhysicsCameraCollisionProbe.cs:97` | acdream's camera doesn't track the sought-eye's cell separately; the eye is near the player so the player-cell stab list is assumed to cover it | An eye outside the player cell's stab-list coverage (boundary corners, cross-landblock pull-back) seats in the wrong cell — and the viewer cell roots the whole render: one-frame wrong root (flap-class flash) | `SmartBox::update_viewer` 0x00453ce0, pc:92878-92883 | diff --git a/docs/research/2026-09-01-overhaul/s3-walk-ownership-map.md b/docs/research/2026-09-01-overhaul/s3-walk-ownership-map.md index d59ca3e6..129a40c9 100644 --- a/docs/research/2026-09-01-overhaul/s3-walk-ownership-map.md +++ b/docs/research/2026-09-01-overhaul/s3-walk-ownership-map.md @@ -967,6 +967,95 @@ lead's `ACDREAM_DEVTOOLS=1` four-pose self-gate shows no validation error and no visual delta against `logs/selfgate-20260903-104516-s3-chunk3-r2` beyond actors/daylight. +### 10.6 Post-hoc three-lens verdict on round 2 and the landing-hygiene chunk (lead, 2026-09-03 evening) + +**Verdicts (all three PASS, `s3-chunk4-impl` @ `2e3ae95ff`, run after the API +overload lifted; the lead had already landed the round on its own +verification — §13 S3 row).** The findings are documentation and dead-state +hygiene, none touches admission, order or a pin's discriminating power: + +- (major, two lenses) AD-17 still described the deleted scissor fallback in + the chunk's own worktree — already rewritten on the campaign branch at + `5b43d8d55`; the rewrite's "particles never GPU-clipped" clause is + corrected in this hygiene commit (the mesh/particle shaders KEEP the + per-cell `clipRegions[aClipSlot]` input; the walk feeds every ordinary + part and every particle the whole-mesh slot 0 = no clip, so the machinery + is dormant in the walk path, live only for the non-walk flat path). +- (major) `Issue130DoorwayStripTests`' rewritten header claims the surviving + plane-gap half pins "ProjectToClip → ClipToRegion → ClipPlaneSet.From + that the punch fans consume"; only `ClipPlaneSet.From` has a production + caller — the test drives `PortalVisibilityBuilder` + `Assemble`, both + zero-caller legacy paths. +- (major) `ClipPlaneSet.cs` header (`:23-39`) and member docs (`:80-92`) + still promise a consumer that "draws the ScissorNdcAabb box"; + `UseScissorFallback`/`ScissorNdcAabb` have no production reader + (`AppendOutsideSlice` reads only `IsNothingVisible`/`Count`/`PlaneArray`). +- (minor) stale sentences: `ClipFrameAssembler.cs:14-15`, `:37-38` + (`TerrainClipMode.Scissor`, a value nothing produces), `:45-46`; + `WorldPassSurface.cs:22-26` ("Four concerns" incl. "the doorway + scissor"), `:54-58` (shaders "write 1.0 into every slot" — they no longer + write `gl_ClipDistance`), `:76-77`; `TerrainModernRenderer.cs:244-245` + dead `clipPlanes`/`ndcClipAabb` parameters of `Draw` and the + `IsAabbVisibleThroughClipRegion` scissor branch + comment `:521-524` + (no producer left); `RetailPViewRenderer.cs:418-419` computes a + `terrainUploadCount` the only `PrepareClipFrame` discards, with a GL-era + doc at `WorldPassSurface.cs:34-41`; `RetailPViewPassExecutorTests.cs:159-161` + says "unconditionally" (false since K2); the K1 pin's doc lacks the + switch-table scope note L8 put on its own; `RetailPViewRenderer.cs:647` + cites the callee address `@0x00506ff0` where §10.1 cites the call site + `@0x00506396`. +- (minor, pin limit) the L1 condition pin accepts a CONJOINED gate + `if (clipAssembly.OutsideViewSlices.Length != 0 && walkDriver.WeatherTurnFired)` + (the extra `brfalse` lands before the getter's offset); the print/draw + divergence it would reintroduce is exactly K2's. + +**Hygiene chunk H (one Sonnet agent in `s3-hygiene` at the campaign tip; one +lens; no third round — a FAIL here is written up, not fixed again):** + +- **H1** `ClipPlaneSet`: delete `ScissorNdcAabb`; rename `UseScissorFallback` + → `IsPlaneOverflow` with the doc "Count == 0 because the region needs more + than `MaxPlanes` half-planes (or is multi-polygon); the consumer draws + UNCLIPPED (`AppendOutsideSlice` emits an empty plane array; the punch fan + covers the whole fan) — over-include, never a scissor"; rewrite the file + header (`:23-39`) to the three states that exist (Empty / overflow / + planes); update `ClipPlaneSetTests` to the new name and delete its AABB + assertions (they pinned the deleted consumer contract). +- **H2** `TerrainModernRenderer`: delete `Draw`'s `clipPlanes`/`ndcClipAabb` + parameters, the scissor branch of `IsAabbVisibleThroughClipRegion` (keep + the clip-plane test only if a caller still passes planes — grep; if none, + delete the whole helper and its comment `:521-524` so `CollectVisibleCells` + is frustum-only), and the `terrainUploadCount` computation at + `RetailPViewRenderer.cs:418-419` + `PrepareClipFrame`'s parameter and its + GL-era doc (`IWorldPassSurface.PrepareClipFrame()` takes nothing). +- **H3** `Issue130DoorwayStripTests`: DELETE the test (its remaining half + drives two zero-caller legacy paths; the one production stage it touches, + `ClipPlaneSet.From`, is pinned by the K6 punch-fan pin through the real + producers) and say so in the commit body with #130's history in one + sentence. +- **H4** comments: `ClipFrameAssembler.cs:14-15/:45-46` (no scissor + consumer), delete `TerrainClipMode.Scissor` (nothing produces it; keep + `Planes`/`Skip` for the flat path — grep the enum's readers first); + `WorldPassSurface.cs:22-26` → three concerns, `:54-58` → only + `portal_depth.vert` writes `gl_ClipDistance` in the world pass now, + `:76-77` drop the scissor sentence; `RetailPViewPassExecutorTests.cs:159-161` + → "conditional on `WalkFrameDriver.WeatherTurnFired`, see the L1 pin"; + add the switch-table note to the K1 pin's doc; `RetailPViewRenderer.cs:647` + → the call-site address `@0x00506396` (callee `@0x00506ff0`). +- **H5** strengthen the L1 pin against the conjoined gate: between the + `DrawUnattachedSceneParticles` call (the last call before the gate) and + the `DrawWeatherOnce` call there is exactly ONE conditional branch in + total, and it is the forward `brfalse` after `get_WeatherTurnFired` — + MUTATION: the conjoined gate adds a second `brfalse` → fails; record the + text. +- **H6** commit body: every mutation text (H5), the H2 grep results, the + H3 sentence; `Co-Authored-By` last. + +Gates: build 0 warnings; hermetic lane fully green; InstalledDat exactly the +four known (2× #383, TowerAscent, #458); the L6 grep from §10.5 extended +with `ScissorNdcAabb|UseScissorFallback|TerrainClipMode.Scissor| +ndcClipAabb|terrainUploadCount` returns nothing under `src`/`tests`; no +register row (AD-17's correction is the lead's, in the same landing). + ## 11. Chunk 1 contract — transcript kinds, fixtures, and the print-only emitter (lead, 2026-09-03; runs AFTER chunk 3, because `LC`/`SC` only exist once terrain is per cell) ### 11.1 What exists (verified in the tree at `542b049b`)