docs: #176/#177 handoff — root-caused, folded into A7 dungeon lighting
Session handoff for the next pickup. #176 (purple seam flash) + #177 (stair-room light pop-in) are ONE bug, root-caused to the camera-nearest MaxGlobalLights=128 snapshot cap evicting in-range lights of visible cells (Hub = 366 fixtures). Fix deferred to A7 because uncapping exposes unported per-cell light-reach (through-floor light) + the fixture falloff-curve misassignment + an unattributed striped-floor artifact. - New handoff: docs/research/2026-07-06-176-177-handoff-A7-lighting.md (root cause, why-deferred, the A7 fix order, tooling inventory). - Roadmap A7 updated: now owns #176/#177; the 'light visibility culling' hypothesis layer is CONFIRMED (no per-cell insert_light registration); fixture-curve layer added. Analysis pre-paid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d591e3bbe5
commit
e1746ca10d
2 changed files with 171 additions and 1 deletions
|
|
@ -293,14 +293,32 @@ successfully 2026-04-30 for the steep-roof case. Matching binaries
|
||||||
|
|
||||||
#### Phase A7 — Indoor lighting fidelity (RenderDoc + retail-decomp driven)
|
#### Phase A7 — Indoor lighting fidelity (RenderDoc + retail-decomp driven)
|
||||||
|
|
||||||
|
**Now also owns #176/#177 (2026-07-06):** the Facility Hub purple seam
|
||||||
|
flash + stair-room light pop-in are ROOT-CAUSED to this phase's "light
|
||||||
|
visibility culling" layer — a camera-nearest `MaxGlobalLights=128`
|
||||||
|
snapshot cap evicts in-range lights of visible cells (Hub has 366
|
||||||
|
fixtures), so per-cell 8-light sets churn as the camera moves.
|
||||||
|
Uncapping was live-tested and reverted because the full pool exposes the
|
||||||
|
per-cell-reach + fixture-curve defects below (through-floor light,
|
||||||
|
1/d-vs-1/d³). Analysis PRE-PAID — see
|
||||||
|
`docs/research/2026-07-06-176-177-handoff-A7-lighting.md` (the fix order:
|
||||||
|
per-cell `insert_light` registration → static fixture curve → stripe
|
||||||
|
hunt → uncap) + register AP-85.
|
||||||
|
|
||||||
**Hypothesis layers (less mapped than physics):**
|
**Hypothesis layers (less mapped than physics):**
|
||||||
- Per-cell environment-light tag association — indoor cells should
|
- Per-cell environment-light tag association — indoor cells should
|
||||||
inherit only their own env lights, not outdoor day-cycle.
|
inherit only their own env lights, not outdoor day-cycle.
|
||||||
- Light visibility culling — what lights actually contribute to each
|
- Light visibility culling — what lights actually contribute to each
|
||||||
cell's render.
|
cell's render. **CONFIRMED bug here (#176/#177): no per-cell light
|
||||||
|
registration — lights are a flat world-space sphere-overlap pool that
|
||||||
|
reaches through solid floors; retail's `insert_light` 0x0054d1b0
|
||||||
|
scopes each light to its cell.**
|
||||||
- Per-entity light direction transform — held-item-spotlight bug
|
- Per-entity light direction transform — held-item-spotlight bug
|
||||||
(#L-spotlight) is per-entity attribution gone wrong.
|
(#L-spotlight) is per-entity attribution gone wrong.
|
||||||
- Static-stab atmospheric inheritance (#81).
|
- Static-stab atmospheric inheritance (#81).
|
||||||
|
- **Fixture falloff curve (#176/#177): stationary server-spawned
|
||||||
|
fixtures ride the DYNAMIC 1/d path (#143 `isDynamic`); should be the
|
||||||
|
static 1/d³ bake (`calc_point_light` 0x0059c8b0).**
|
||||||
|
|
||||||
**Investigation methodology:** less existing infrastructure than
|
**Investigation methodology:** less existing infrastructure than
|
||||||
physics. Requires:
|
physics. Requires:
|
||||||
|
|
|
||||||
152
docs/research/2026-07-06-176-177-handoff-A7-lighting.md
Normal file
152
docs/research/2026-07-06-176-177-handoff-A7-lighting.md
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
# Pickup prompt — #176/#177 root-caused, deferred to A7 dungeon lighting (paste into a fresh session)
|
||||||
|
|
||||||
|
**Read `claude-memory/project_render_pipeline_digest.md` FIRST** (top banner
|
||||||
|
is the #176/#177 outcome + DO-NOT-RETRY), then **ISSUES #176 and #177**, then
|
||||||
|
this file. Then read **roadmap Phase A7** (`docs/plans/2026-04-11-roadmap.md`
|
||||||
|
§"Phase A7 — Indoor lighting fidelity") — this session effectively pre-paid
|
||||||
|
A7's analysis for the light-cap slice.
|
||||||
|
|
||||||
|
## Where we are (2026-07-06 end of session)
|
||||||
|
|
||||||
|
**Currently working toward: M1.5 — Indoor world feels right.** Critical path:
|
||||||
|
#137 dungeon collision (DONE, gated), #138 teleport-OUT, **A7 dungeon lighting
|
||||||
|
(#79/#93 + now #176/#177)**.
|
||||||
|
|
||||||
|
HEAD = `d591e3bb` on `main` AND branch `claude/vigorous-joliot-f0c3ad`
|
||||||
|
(fast-forwarded, in sync). Working tree clean. All suites green
|
||||||
|
(Core 2591 + 3 skip / App 719 + 2 skip / UI 425 / Net 385).
|
||||||
|
|
||||||
|
Three commits this session:
|
||||||
|
|
||||||
|
| Commit | What |
|
||||||
|
|---|---|
|
||||||
|
| `b8e9e204` | #176/#177 investigation: 12 mechanisms refuted, apparatus shipped, probe protocol staged |
|
||||||
|
| `4d25e04d` | fix attempt: `MaxGlobalLights` 128→1024 (stops the pops) — **REVERTED** |
|
||||||
|
| `d591e3bb` | revert to 128 + full deferral docs (register AP-85 rewritten, ISSUES back to OPEN, digest DO-NOT-RETRY) |
|
||||||
|
|
||||||
|
A client may still be running (`launch-176-revert-check.log`) — the user
|
||||||
|
manages lifecycle. It is on the reverted 128 baseline (rooms normal, seam
|
||||||
|
flashes present = the still-open issue).
|
||||||
|
|
||||||
|
## THE ROOT CAUSE (confirmed, not a hypothesis)
|
||||||
|
|
||||||
|
**#176 and #177 are ONE bug: per-cell 8-light SET COMPOSITION churning under a
|
||||||
|
camera-nearest snapshot cap.** `LightManager.BuildPointLightSnapshot` keeps only
|
||||||
|
the `MaxGlobalLights=128` point lights nearest THE CAMERA; the Facility Hub
|
||||||
|
registers **366** fixtures, so 238 are evicted per frame by camera distance.
|
||||||
|
`SelectForObject` (the faithful per-object 8-cap, retail
|
||||||
|
`minimize_object_lighting` 0x0054d480) can only choose from the surviving 128 —
|
||||||
|
so an in-range torch of a VISIBLE cell that ranks past the cap drops out of that
|
||||||
|
cell's 8-set, and the cell's per-vertex Gouraud lighting flips as the camera
|
||||||
|
moves.
|
||||||
|
|
||||||
|
- **#176** — the flipping unit is a CELL → discontinuities at exactly cell-seam
|
||||||
|
granularity; camera-angle dependent (the chase boom swings the camera position,
|
||||||
|
re-ranking the 128); the dominant flipping light is the under-room PORTALS'
|
||||||
|
purple → purple flashes on the floor at seams.
|
||||||
|
- **#177** — a stair room whose fixtures ALL rank past the cap renders at bare
|
||||||
|
0.2 ambient (near-black = "not visible from the corridor"); approach
|
||||||
|
re-admits them ("pops into existence"); the eviction boundary sweeping during
|
||||||
|
the descent strips the ramp's lights ("disappears on the last step"). **The
|
||||||
|
geometry never vanished — its LIGHTS did.**
|
||||||
|
|
||||||
|
**How it was confirmed (the discriminator):** the user reproduced the flash
|
||||||
|
while `[light]` (ambient branch — stable 0.2 grey) AND `[pv-input]` (portal
|
||||||
|
flood — zero drops in 54k frames) read provably healthy in the probe log. That
|
||||||
|
eliminated every CPU signal the probes COULD see and left the one they can't:
|
||||||
|
set composition (`[light]` prints counts, not membership). The log's headline
|
||||||
|
number — `registeredLights=366` vs cap 128 — closed it.
|
||||||
|
|
||||||
|
## WHY THE FIX IS DEFERRED (do not re-raise the cap alone)
|
||||||
|
|
||||||
|
Uncapping (128→1024, `4d25e04d`) stopped the pops but the full 366-fixture pool
|
||||||
|
exposed three UNPORTED retail lighting semantics that then dominated the Hub —
|
||||||
|
this is why it was reverted (user: "rooms have no textures" → actually a magenta
|
||||||
|
light wash over intact textures; then "purple stripes… something fighting to
|
||||||
|
draw the purple lightning over the floor"):
|
||||||
|
|
||||||
|
1. **Light-through-solid-floors.** Retail registers lights per-CELL
|
||||||
|
(`insert_light` 0x0054d1b0); a light belongs to a cell and only lights that
|
||||||
|
cell's geometry. Our snapshot is a flat world-space sphere-overlap with NO
|
||||||
|
reach/occlusion notion → the under-room portals' purple light washed the
|
||||||
|
corridors ABOVE them. **This is the big one.** The 128 cap accidentally
|
||||||
|
MASKED it by keeping the pool camera-local (far under-room lights fell off
|
||||||
|
the list before they could reach up).
|
||||||
|
2. **Fixture falloff curve misassignment.** Stationary weenie fixtures
|
||||||
|
(ACE serves dungeon lanterns/braziers as CreateObject weenies) register via
|
||||||
|
the `isDynamic:true` path → D3D 1/d falloff (`LightInfoLoader.cs:89`,
|
||||||
|
GameWindow weenie-light block ~3688). Retail bakes STATIONARY fixture light
|
||||||
|
with the static 1/d³ curve (`calc_point_light` 0x0059c8b0, static_light_factor
|
||||||
|
1.3). 1/d is ~9× stronger at 3 m → every pool over-broad + over-saturated.
|
||||||
|
The `isDynamic` flag should be reserved for genuinely MOVING lights (portal
|
||||||
|
swirls, projectiles); a stationary fixture — even server-spawned — is static.
|
||||||
|
3. **Striped floor z-fight-like artifact.** User's 2nd screenshot: regular
|
||||||
|
magenta bands across one floor region, "like something is fighting to draw
|
||||||
|
the purple over the floor." **NOT attributed.** Ruled out: not coincident dat
|
||||||
|
geometry (the `CorridorNeighborhood_CoplanarOverlappingDrawnPolyPairs` sweep
|
||||||
|
found only the legit z=−12 under-hall floor quad-fan, nothing near the −6
|
||||||
|
corridor floor); not a striped texture (all corridor surfaces are plain
|
||||||
|
`Base1Image` stone 0x08000375/6/7/8). Leading guess: two draws of the same
|
||||||
|
floor with DIFFERENT light sets (the per-cell-vs-per-something set assignment
|
||||||
|
splitting), or an MDI instance-order/light-set-index desync exposed only when
|
||||||
|
the purple light is stably present. **Hunt this in A7 with the full pool
|
||||||
|
temporarily on** — it's invisible at cap 128.
|
||||||
|
|
||||||
|
## THE A7 FIX SHAPE (the real fix, in order)
|
||||||
|
|
||||||
|
1. **Port per-cell light registration** (`insert_light` 0x0054d1b0 + the
|
||||||
|
per-cell light list retail keeps). A light lights its OWN cell's geometry +
|
||||||
|
cells reachable through portals — NOT arbitrary world-space overlap. This
|
||||||
|
kills #1 (through-floor) and makes the global pool cap irrelevant (per-cell
|
||||||
|
sets are naturally bounded), which is what actually lets #176/#177 close.
|
||||||
|
2. **Static curve for stationary fixtures.** Decide `isDynamic` by whether the
|
||||||
|
light MOVES, not by dat-static-vs-weenie origin. A server-spawned wall lantern
|
||||||
|
is stationary → static 1/d³. (Register AP-67/AP-44 are the weenie-light path;
|
||||||
|
AP-85 is the pool cap; #143 is the curve-by-path decision to revisit.)
|
||||||
|
3. **Hunt the stripes** with the full pool on (see #3 above).
|
||||||
|
4. **THEN uncap** `MaxGlobalLights` — un-skip
|
||||||
|
`LightManagerTests.PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant`
|
||||||
|
(it asserts the retail end-state: an in-range light of a cell is never
|
||||||
|
camera-evicted).
|
||||||
|
|
||||||
|
## Tooling built this session (reuse, don't rebuild)
|
||||||
|
|
||||||
|
- **`tests/AcDream.Core.Tests/Rendering/Issue176177DungeonSeamInspectionTests.cs`**
|
||||||
|
— dat truth for the Hub: portal-poly draw verdicts, reciprocal coincidence,
|
||||||
|
stair geometry owner (`0x8A020182`'s ramp shell, vertical portals, ZERO
|
||||||
|
statics), CellBSP containment (partitions exactly at portal planes),
|
||||||
|
under-hall + corridor drawn-poly surface colors, DXT1 alpha histograms (0
|
||||||
|
transparent texels), and `CorridorNeighborhood_CoplanarOverlappingDrawnPolyPairs`
|
||||||
|
(the stripe-geometry sweep — came back empty for the −6 floor).
|
||||||
|
- **`tests/AcDream.App.Tests/Rendering/Issue176177FacilityHubFloodReplayTests.cs`**
|
||||||
|
— production-matched portal-flood replays (approach/descent/gaze-sweep/walk +
|
||||||
|
the ScenarioE incoherent-root sensitivity pin). Flood is HEALTHY — do not
|
||||||
|
re-investigate it for these issues.
|
||||||
|
- **`tests/AcDream.Core.Tests/Physics/Issue176177SeamTransitLagTests.cs`**
|
||||||
|
— resolver cell-flip is plane-exact (membership is NOT the bug).
|
||||||
|
- **`LightManagerTests.PointSnapshot_HubScaleLightCount_*`** — Skip'd
|
||||||
|
end-state pin (RED@128, GREEN@1024).
|
||||||
|
- **Ledger**: `docs/research/2026-07-06-176-177-render-pair-investigation.md`
|
||||||
|
(13 refuted mechanisms + the probe-run discriminator + the OUTCOME banner).
|
||||||
|
|
||||||
|
## Live probe env (all zero-cost off; use for the A7 spike)
|
||||||
|
|
||||||
|
```
|
||||||
|
ACDREAM_PROBE_LIGHT=1 # [light] insideCell/ambient/sun/registeredLights/activeLights — rate-limited
|
||||||
|
ACDREAM_PROBE_PVINPUT=1 # [pv-input] one line/frame: exact flood inputs + count
|
||||||
|
ACDREAM_PROBE_CELL=1 # [cell-transit] timeline anchors
|
||||||
|
ACDREAM_PROBE_TEXFLUSH=1 # [tex-flush] staged-upload drain (proves #105 healthy: after=0)
|
||||||
|
```
|
||||||
|
|
||||||
|
A7.L1's planned `[indoor-light]` probe (per-cell active-light dump: position,
|
||||||
|
color, attenuation, direction) is the natural next apparatus — it prints exactly
|
||||||
|
the SET COMPOSITION the current `[light]` counts can't. Build it FIRST.
|
||||||
|
|
||||||
|
## Launch protocol (unchanged)
|
||||||
|
|
||||||
|
`dotnet build` green first; PowerShell launch with the CLAUDE.md env block
|
||||||
|
(+ the probes above), background + Tee to `launch-*.log`. User manages client
|
||||||
|
lifecycle (graceful close → ACE clears in ~5 s; hard kill → ~3 min). Strip
|
||||||
|
`\000` before grep (PowerShell Tee = UTF-16): `tr -d '\000' < launch.log | grep ...`.
|
||||||
|
Test char spawns near `0x8A020179` (the ramp corridor); the 015E↔017A corridor
|
||||||
|
loop is the #176 repro; look-into + descend the 0178→0182→0183 stairs for #177.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue