docs(overhaul): contract issue 469 lighting ownership

This commit is contained in:
Erik 2026-09-05 03:29:21 +02:00
parent 2f1e625fea
commit 8098875240
3 changed files with 174 additions and 10 deletions

View file

@ -2687,3 +2687,168 @@ frame. High retains the separately registered #469 lighting-direction shift;
that known blocker is neither waived nor attributed to #470. #470 changes no
retail-visible policy and requires no divergence-register row. Next is #469,
then S5-c5, closeout, and G4. Nothing has merged to `main`.
## 31. S5-#469 contract — keep retail world lighting stable across the opt-in shadow gate
### 31.1 Lead-recovered retail and binary boundary
Issue #469 is not an ambiguous art-direction choice. Retail owns exactly one
outdoor directional-light channel, authored by `SkyTimeOfDay`; the atmospheric
pack's visible sun/moon choice is an acdream-only shadow-projection input and
must not replace that lighting channel.
The lead re-read the named retail decomp and its verbatim type header before
writing this contract:
- `SkyTimeOfDay` contains `dir_bright`, `dir_heading`, `dir_pitch`, and
`dir_color` (`acclient.h` lines 5312453137).
- `SkyDesc::GetLighting @0x00500A80` interpolates those fields between the two
surrounding authored time-of-day records, constructs one directional vector,
and returns one directional colour.
- `LScape::set_landscape_lighting @0x005054D0` copies that vector and colour to
`LScape::sunlight` and then `Render::world_lights.sunlight`; it has no shadow-
gate or celestial-mesh selection input.
- `PrimD3DRender::UpdateLightsInternal @0x0059AED0`, specifically
`0x0059B4ED0x0059B5CE`, derives the D3D sunlight colour and direction from
that same `Render::world_lights.sunlight`. It does not replace the direction
when any drawing feature is enabled or disabled.
The paired executable was independently verified again with
`tools/pdb-extract/check_exe_pdb.py`: `C:\Users\erikn\Downloads\acclient.exe`
is PE32 v11.4186, whole-file SHA-256
`006FFEADC5D679C871497112A5BD1F87714D0E273E2166BAE5052DDE369297B1`, CodeView
GUID `{9E847E2F-777C-4BD9-886C-22256BB87F32}`, age 1, and matches
`C:\Users\erikn\source\repos\acdream\refs\acclient.pdb`. Exact function-range
SHA-256 anchors are:
| Function | File range | Bytes | SHA-256 |
|---|---:|---:|---|
| `SkyDesc::GetLighting` | `0x00100A80..0x00100CDF` | 608 | `9D82E8EDDBD66C52DBD3F08D07D661482DB545C4F513E81AB4F16E4EDF410188` |
| `LScape::set_landscape_lighting` | `0x001054D0..0x0010565F` | 400 | `DF3FD816845AE0F6E2423442A325CE43A44A8204E1B857D7674BBB8F4640D0CB` |
| `PrimD3DRender::UpdateLightsInternal` | `0x0019AED0..0x0019B5DF` | 1,808 | `E18FD7AD9A8EB819AB9602D25DE5D1BB99FADD1D439B35140E443EB1E0437F84` |
This agrees with the pre-existing static source study
`docs/research/2026-08-22-dereth-celestial-shadow-sources.md`: celestial meshes
supply only the opt-in shadow **direction**; colour and energy remain retail's
single authored channel; existing retail scene lighting remains driven by
`SkyStateProvider`.
The current atmospheric receiver shaders violate that declared boundary.
When shadow flag bit 0 is clear they use the plain pipeline's authored
`-uLights[].dirAndRange.xyz`; when it is set they abruptly use normalized
`uShadowLightDirectionAndSource.xyz`. Shadow strength is blended later, so this
binary vertex-light switch produces the whole-world relight captured in both
the exact parent and S5-c2 candidate A/Bs. It is not absent shadow work and it
is not caused by S5-c2.
### 31.2 Bounded production repair and IA-24 correction
Restore one stable lighting owner in both atmospheric vertex receivers:
1. `mesh_atmospheric.vert` computes every outdoor directional-light `Ldir`
with the exact plain-mesh expression `-uLights[i].dirAndRange.xyz` in every
shadow-gate state. Preserve its unnormalized value and its per-light loop.
2. `terrain_atmospheric.vert` computes `surfaceToLight` with the exact plain-
terrain expression `-uLights[0].dirAndRange.xyz` in every shadow-gate state.
Preserve `MIN_FACTOR`, authored colour/energy, and the split directional
varying used by the fragment receiver.
3. Delete the obsolete `shadowGatedOff` branches and replace their comments
with the retail ownership above.
4. Keep `uShadowLightDirectionAndSource` unchanged for cascade projection,
receiver depth/bias/PCF, and atmospheric volumetrics. The optional shadows
may therefore be geometrically directed at the selected visible celestial
source while the receiver's base lighting remains the authored retail
direction. That separation is deliberate and was already the original
IA-24 research boundary.
5. Recompile the committed shader set and update only the generated atmospheric
vertex SPIR-V and manifest/hash entries required by those source changes.
6. Amend IA-24 in the **same implementation commit**: remove its statement that
receiver lighting switches to the celestial direction while shadows render;
state instead that authored `uLights` remains the sole base-light direction
in every gate state, while the selected celestial source drives only the
opt-in shadow/volumetric projection. Explicitly record the possible visual
light/shadow-angle separation as the cost of preserving retail lighting and
eliminating the binary relight. This is the required deviation-register
update; add no second row.
Do not change `SceneLighting`, `SkyStateProvider`, the celestial-source
resolver or its sun/moon priority, shadow strength/fades, cascade matrices,
shadow caster selection, receiver binding/descriptor ABI, fragment shadow
lookup, PCF/bias, volumetric policy, render-pack selection, plain shaders,
materials, fog, tone mapping, colour/energy, terrain `MIN_FACTOR`, indoor
lighting, point/spot lights, visibility, geometry, package recipe, or any
unrelated renderer. Do not normalize the authored direction, blend the two
directions, invent another runtime state owner, hide the switch behind a
threshold, or add per-frame work/allocation. If any wider production change is
needed, stop and return the exact dependency before expanding scope.
Allowed production scope is the two atmospheric vertex shader sources, their
regenerated SPIR-V/manifest pins, and the IA-24 row. Tests may add only the
smallest structural and real-shader proof below. The implementer changes no
packet, plan, issue, package, gate artifact, architecture text outside IA-24,
or campaign memory.
### 31.3 Discriminating proof, mutations, and return
Permanent proof must establish both halves of the boundary rather than merely
matching a copied CPU formula:
1. compile and inspect the committed production atmospheric vertex SPIR-V and
prove mesh and terrain take their diffuse-light direction only from the
corresponding authored `uLights` member, with the exact unnormalized plain-
shader expressions and no shadow-flag/celestial-direction selection;
2. exercise the committed production atmospheric receiver shaders in a real
offscreen Vulkan pixel witness for both mesh and terrain. Holding authored
light/material/geometry constant while toggling only shadow flag and
celestial direction must leave the unshadowed base directional-light result
invariant; choose inputs for which the pre-fix branch gives a different
pixel. The witness is explicitly `Lane=Vulkan`, uses the existing dedicated
lavapipe/local lane, and fails rather than skips when that lane lacks Vulkan;
3. prove the committed fragment receiver and volumetric shaders still consume
`uShadowLightDirectionAndSource` for opt-in shadow projection/bias/visibility,
so the repair cannot silently disable or redirect shadows;
4. prove active and inactive receiver states still select/bind the same
atmospheric/plain pipelines and descriptors as before;
5. pin that neither plain shader source/SPIR-V nor any pipeline-selection or
renderer source changed from the contract parent; and
6. read the live IA-24 row and fail if it again claims celestial direction owns
base receiver lighting or omits the explicit light/shadow separation.
Apply and independently restore at least these mutations, recording the first
discriminating failure for each in the implementation commit body:
1. restore the shadow-flag/celestial-direction branch in
`mesh_atmospheric.vert`;
2. restore it in `terrain_atmospheric.vert`;
3. normalize either authored vertex-light direction;
4. redirect a fragment shadow/volumetric use from the celestial source to the
authored light, or remove its celestial-source read; and
5. restore IA-24's old assertion that pack-active base lighting follows the
celestial source.
Return one clean implementation commit atop this contract; exact changed-file
and line counts; all mutation first failures; both `git diff --check` forms;
exact shader compiler command and generated hashes; the dedicated physical
`Lane=Vulkan` result; focused atmospheric shader/receiver/manifest/source-
resolver tests; Release solution build with zero warnings/errors; official
hermetic lane; and canonical-inclusive InstalledDat with every accepted
nonpassing identity named. Recipe 10 and prepared content are unchanged, so no
bake is required. No graphical client launch.
Review is sequential. Retail/deviation pass 1/10 first verifies the three
named-retail functions and paired bytes, the stable authored-light rule, IA-24
truth, pack-off preservation, and that the pixel/structural tests actually fail
on both old branches. Only after PASS does production/gate-honesty pass 2/10
inspect the complete source-to-SPIR-V-to-pipeline path, run the real Vulkan
witness, reproduce at least mutations 14, and rule on portable/dedicated-lane
ownership and full gate sufficiency. A failed pass receives one bounded fix
contract. The owner-authorized ceiling is ten review passes. Nothing lands or
launches graphically before both lenses pass.
After landing, the lead repeats the exact fixed-pose retail/off -> High ->
retail/off plus dusk transition used to file #469, with Release green, a clean
tree, UDP/client preflight, one background command, and graceful self-owned
shutdown. PASS requires no whole-world base-light jump as shadow work gates,
while pack-active shadow direction/visibility still changes and the final
retail frame restores. Record exact PNG paths; the result remains provisional
until owner inspection.