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
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