docs(overhaul): contract S5 c2 shadow visibility

This commit is contained in:
Erik 2026-09-04 17:24:33 +02:00
parent 5e418600e5
commit 5c106bcdff
2 changed files with 200 additions and 2 deletions

View file

@ -1,6 +1,7 @@
# Campaign OVERHAUL v2 — S5 consumers, material, and closeout packet
**Status:** S5-c1 IMPLEMENTED 2026-09-04; sequential review pending.
**Status:** S5-c1 LANDED + lead graphical gate PROVISIONAL PASS 2026-09-04;
S5-c2 CONTRACTED below.
**Branch:** `claude/campaign-w-retail-frame-walk`.
**Gate:** G4 remains unpassed. Nothing merges to `main` before G4.
@ -535,3 +536,199 @@ materialization particles at 400 ms, the particle effect clears normally by
the later frames, and Facility/Holtburg geometry and actors remain intact with
no obvious visibility regression. This is not owner acceptance. G4 remains
unpassed, nothing may merge to `main`, and S5-c2 is next.
## 14. S5-c2 contract — exact landscape visibility for the opt-in shadow pack
### 14.1 Retail and deviation boundary (lead-verified 2026-09-04)
This chunk ports one retail **input**, not a retail shadow-map algorithm.
Retail has no cascaded real-time directional shadow map; acdream's selected
atmospheric pack is the explicit IA-24 deviation. Pack-off remains the retail
path and must not build, select, upload, or draw directional-shadow work.
The retail input is the completed landscape `in_view` product:
- named `LScape::draw_check_blocks @0x00505F80` writes a non-`OUTSIDE` block
result and calls `LScape::landcell_check @0x005050A0`; the latter stamps each
`CLandCell::in_view` from the same portal-view interval walk;
- named `CLandCell::IsInView @0x00532CB0` returns that field at `+0x104`;
- the paired Sept-2013 executable is SHA-256
`006FFEADC5D679C871497112A5BD1F87714D0E273E2166BAE5052DDE369297B1`.
Its CodeView GUID `{9E847E2F-777C-4BD9-886C-22256BB87F32}`, age 1, matches
the PDB, and its relevant bytes are `8B 81 04 01 00 00 C3` at
`0x00532CB0`; `landcell_check` begins `55 8B EC 83 EC 1C 53 56` at
`0x005050A0`. The ICF body used by the `CEnvCell::IsInView` vtable remains
`B8 01 00 00 00 C3` at `0x005269F0`.
Because the directional-shadow prepass runs after enhanced-world preparation
but before the current frame's PView walk, it consumes the **previous
successfully completed** landscape product, exactly as S5-c1's retained owner
already exposes to update-time consumers. An aborted frame preserves that
product; completion replaces it; reset removes it. A first/login/portal-space
frame has no completed world view. A completed null-root safety frame has an
empty set and may not manufacture a resident/frustum fallback.
IA-24 must be amended in the implementation commit to state this selection
adaptation plainly: the opt-in non-retail shadow map borrows retail's prior
completed landscape visibility and exact S2 object membership. That amendment
does not make the shadow map retail behavior and does not authorize a new
fallback or a pack-off change.
### 14.2 C1 — borrow the one completed visibility product
Publish a typed one-frame `RetailLandscapeVisibilityFrame` (name may vary only
to fit existing conventions) from the existing completed visibility owner. It
contains:
- the exact completed `HashSet<uint>`/`IReadOnlySet<uint>` by reference, with
no copy, union, sort, reconstruction, or second retained set; and
- the completed-world-view flag needed to distinguish an authoritative empty
answer from no answer.
`WorldRenderFrameBuilder` captures that borrowed fact before the current frame
can complete and attaches it to `WorldRenderFrame` as a pack-only input. The
reference remains valid through the shadow prepass and is not retained after
the prepared world is consumed/cancelled. Do not rename the product into a
current-camera claim: on camera-only movement it is deliberately one completed
frame behind.
The membership side of the selector borrows S2's existing
`ShadowObjectRegistry.TryGetRetailCellArray`; do not copy the registry rows
into the frame and do not add a render-owned cell dictionary. A narrow typed
read seam over that owner is allowed so tests need not construct `PhysicsEngine`.
### 14.3 C2 — exact terrain and object selection
Build one reusable, allocation-free per-frame selection over the retained
shadow topology:
1. If `HasCompletedWorldView` is false, select zero terrain commands and zero
world casters. An authoritative empty landscape set also selects zero.
2. A loaded terrain slot is selected iff at least one of its authored 8x8
outdoor land-cell ids occurs in the exact completed set. Scan the borrowed
set/slot facts directly; no visible-landblock `HashSet` is permitted.
3. A non-building caster is selected iff its non-empty retail CELLARRAY from
`TryGetRetailCellArray(LocalEntityId)` contains at least one outdoor land
cell in the exact completed set. A missing/empty registry product fails
closed. `ParentCellId`, `EffectCellId`, render bounds, origin cell, resident
landblock, and camera frustum are not substitutes.
4. Building shells are intentionally absent from `ShadowObjectRegistry` in
retail's building channel. Select a shell by its exact outdoor placement
cell, `RenderSourceMetadata.EffectCellId`; do not test its EnvCell
`BuildingShellAnchorCellId`. Zero/non-land placement fails closed.
5. Multi-cell objects select once, stable retained caster order is preserved,
and a visibility-only change does not touch membership, render-scene
registration, caster classification, material classification, mesh lookup,
or transform-journal ownership.
Reusable bool/generation-mark arrays keyed by existing caster/terrain slots
are selection scratch, not a second visibility answer, and are allowed.
Another `HashSet<uint>`, dictionary keyed by cell, LINQ materialization, or
per-frame collection allocation is not.
### 14.4 C3 — project visibility without rebuilding retained topology
`DirectionalShadowCasterFrame.BuildSequence` and the prepared
material/mesh-topology `DirectionalShadowPreparedDraws.BuildSequence` remain
keyed only by their existing scene/mesh/fade topology inputs. A camera-only
visibility change must leave both sequences unchanged and report zero topology
classification/mesh-resolution work.
Project the selection into reusable active draw products instead. For world
draws, arbitrary selected/unselected instances inside one prepared material
group must be represented exactly. A permitted implementation keeps the
retained transform array and emits allocation-free contiguous selected runs
with adjusted `BaseInstance`/`InstanceCount`, duplicating the already-classified
batch metadata for split runs. Equivalent retained-index/mask machinery is
acceptable only if GPU tests prove unselected instances cannot reach the
depth map. Merely changing one group's prefix count while leaving interleaved
unselected instances addressable is incorrect.
The active command/batch/run product gets its own per-frame selection sequence
for GPU publication. Rebuilding or re-uploading that bounded indirect product
is not a topology rebuild; re-running `TryBegin` classification, mesh lookup,
or changing the topology build sequence is. Preserve the one N.5 transform
address space and all existing dynamic-transform journal/replay semantics.
Terrain may rebuild its bounded indirect command list from selected retained
slots each frame, but must not rebuild/upload terrain geometry or slot
topology.
Diagnostics must distinguish resident/prepared topology counts from active
selected caster/instance/command counts. Existing celestial direction,
cascade fit/quality, day/night/indoor/portal gates, cutout/translucency/fade,
foliage wind, transform churn, and two-frame topology-deferral policy remain
unchanged.
### 14.5 Allowed files and explicit non-changes
Production changes are limited to the directly affected owners:
- `Rendering/Vfx/ParticleVisibilityController.cs`;
- `Rendering/WorldRenderFrameBuilder.cs` and composition compile fallout;
- `Rendering/Scene/DirectionalShadowCasterFrame.cs` (selection only; existing
topology admission and revision semantics stay fixed);
- `Rendering/Wb/WbDrawDispatcher.DirectionalShadows.cs`;
- `Rendering/TerrainModernRenderer.DirectionalShadows.cs`;
- `Rendering/DirectionalSunShadowRenderer.cs`;
- the built-in and declared directional-shadow pack graphs.
Tests may change only in the corresponding frame-builder, visibility,
directional-shadow caster/prepared/GPU/terrain/pack test files and compile-
fallout fakes. Documentation is limited to this packet, the S5 ledger row,
the architecture statement if its shared visibility-owner wording requires a
truth correction, and IA-24. If exact selection requires a production owner
outside this list, a shader/pipeline-layout change, a new divergence, or any
change to `ShadowObjectRegistry`, stop and return the fact before expanding.
Do not change `WalkLandscape`, PView ordering/products, terrain/world ordinary
draws, S2 membership mutation, render-scene indices, streaming, portal/depth/
alpha, particle/light behavior, shadow source/quality/cascade math, shader ABI,
RHI contracts, DAT/package formats, or any pack-off pixels/work. No graphical
client is launched from implementation or review worktrees.
### 14.6 Tests, mutations, automated return, and reviews
Production-facing tests must prove:
1. the frame builder borrows the exact prior completed set reference and flag;
current Begin does not turn it into a current-camera product;
2. complete/abort/reset and first/login/portal/null-root cases have the stated
transaction semantics;
3. exact CELLARRAY intersection selects multi-cell outdoor static, dynamic,
and child casters once, while missing membership and ParentCell-only impostors
fail closed;
4. a building shell selects by outdoor `EffectCellId`, not its EnvCell anchor;
5. terrain selects only loaded slots reached by exact land cells;
6. alternating visible/invisible instances in one material group produce only
the correct active instance ranges and stable order;
7. camera-only visibility changes alter active output while both retained
topology build sequences, classification counts, mesh lookups, geometry,
and dynamic-transform mappings remain unchanged;
8. topology rebuild, transform journal refresh, fade/mesh-availability retry,
generation reset, and the existing two-frame deferral still converge;
9. built-in and declared packs consume the same typed selector; pack-off does
no shadow work;
10. the warmed production selection path allocates 0 managed bytes and retained
scratch remains streaming-bounded.
At minimum sabotage and restore exactly: replace CELLARRAY with ParentCell;
select all resident terrain; use building anchor instead of effect cell; admit
missing membership; feed current building scratch instead of the completed
product; change a visibility-only frame so it advances the retained topology
sequence; and collapse an alternating group into a prefix `InstanceCount`.
Record each exact first failure in the implementer commit body.
Implementer return: `git diff --check`; Release solution build 0W/0E; focused
visibility/frame/shadow caster/prepared/GPU/terrain/pack tests; the real warmed
0-B pin; official hermetic lane; inclusive InstalledDat lane with exactly the
documented global failure/skip identities; one clean commit and exact file/
count/mutation report.
Sequential review 1 — retail/deviation fidelity: re-check §14.1 against the
named decomp and paired executable/PDB, prior-completed timing, CELLARRAY and
building-channel sources, IA-24 truth, and pack-off non-participation.
Sequential review 2 — architecture/GPU/gate honesty: one borrowed set, no cell
reconstruction, arbitrary-instance correctness, no topology rebuild on camera
motion, transform/GPU flight safety, 0-B steady state, lifecycle/deferral,
allowed scope, counts, and at least three mutation claims. A failed lens gets
one bounded fix contract; a third fix round stops and is written up.