docs(A7): Town Network "too dark" root-caused — ambient RULED OUT (live cdb 0.2 white == retail), cause is the 463>128 light cap
Investigation handoff for the deferred A7 per-cell light-scoping fix (#79/#93/#176/#177). Ambient verified retail-faithful three ways (decomp + dat SeenOutside + live retail cdb capture: SetWorldAmbientLight level 0x3e4ccccd == 0.2f bit-exact). No fix applied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5d013dcb10
commit
388f3ed307
1 changed files with 141 additions and 0 deletions
|
|
@ -0,0 +1,141 @@
|
||||||
|
# A7 dungeon lighting — Town Network "too dark" ROOT-CAUSED to the light cap (ambient RULED OUT). Pickup handoff.
|
||||||
|
|
||||||
|
**Date:** 2026-07-09
|
||||||
|
**Status:** investigation COMPLETE (report-only). Fix NOT started — deferred to a new
|
||||||
|
implementation session (this doc + the separate pickup prompt).
|
||||||
|
**Milestone:** M1.5 — Indoor world feels right. Critical path item **A7 dungeon
|
||||||
|
lighting (#79/#93/#176/#177)**.
|
||||||
|
|
||||||
|
**Read FIRST in the new session:** `claude-memory/project_render_pipeline_digest.md`,
|
||||||
|
`claude-memory/reference_retail_ambient_values.md`, then the two 2026-07-06 A7 docs
|
||||||
|
(`docs/research/2026-07-06-176-177-handoff-A7-lighting.md` +
|
||||||
|
`docs/research/2026-07-06-a7-per-cell-lighting-pseudocode.md` — the source-confirmed
|
||||||
|
retail per-cell light model, **including its two CORRECTION banners**), then this file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The user's report
|
||||||
|
|
||||||
|
> "We are still missing some lighting indoors. It's darker than retail… Not the
|
||||||
|
> torches, the ambient lighting. I'm checking in the Town Network — we have a
|
||||||
|
> fountain in the main room but it's much darker and not lit up as in retail."
|
||||||
|
|
||||||
|
The user's *observation is correct* (the room IS too dark). The *attribution*
|
||||||
|
("ambient") is what this investigation tested — and disproved.
|
||||||
|
|
||||||
|
## VERDICT: the ambient is retail-faithful. The darkness is the LIGHT CAP.
|
||||||
|
|
||||||
|
Confirmed **three independent ways** that acdream's indoor ambient is correct:
|
||||||
|
|
||||||
|
1. **Decomp.** `CellManager::ChangePosition` (0x004559B0) sets the world ambient on a
|
||||||
|
two-way branch on the player cell's `seen_outside` flag: sealed
|
||||||
|
(`seen_outside==0`) → `SmartBox::SetWorldAmbientLight(0.2f, 0xFFFFFFFF)` (flat 0.2
|
||||||
|
white); seen-outside/outdoor → `SetWorldAmbientLight(calc_object_light(),
|
||||||
|
LScape::ambient_color)` (bright sky ambient). acdream mirrors this exactly in
|
||||||
|
`GameWindow.UpdateSunFromSky` (flat 0.2 white when `playerInsideCell`).
|
||||||
|
2. **This cell's dat flag.** The `[light]` probe in the Town Network fountain room:
|
||||||
|
`insideCell=True ambient=(0.2,0.2,0.2) sun=0 playerCell=0x00070144`. acdream reads
|
||||||
|
`SeenOutside` faithfully off the player's physics cell (`CellGraph.CurrCell`), so
|
||||||
|
`insideCell=True` ⇒ the dat flag for 0x00070144 is genuinely "sealed."
|
||||||
|
3. **Live retail cdb capture (2026-07-09).** Attached cdb to retail acclient.exe
|
||||||
|
(v11.4186, MATCHES `refs/acclient.pdb`), bp on `SmartBox::SetWorldAmbientLight`
|
||||||
|
(`?SetWorldAmbientLight@SmartBox@@QAEXMK@Z`; args thiscall: level=float `dwo(@esp+4)`,
|
||||||
|
color32 `dwo(@esp+8)`). Portalling from outdoors INTO the Town Network:
|
||||||
|
|
||||||
|
| phase | levelraw | level | color32 | |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| outdoor | `0x3ee210c9`/`0x3ee2237d` | ≈0.44 | `0xffc864ff` = (200,100,255) purple | sky ambient |
|
||||||
|
| **Town Network interior** | **`0x3e4ccccd`** | **0.20** | `0xffffffff` white | **matches acdream exactly** |
|
||||||
|
|
||||||
|
`0x3e4ccccd` == `0.2f` bit-exact. **Retail runs the Town Network on the same 0.2
|
||||||
|
flat white ambient acdream does.** Raising the ambient would make acdream *wrong*.
|
||||||
|
|
||||||
|
## The REAL cause (the deferred A7 issue, now with a bigger repro)
|
||||||
|
|
||||||
|
The `[light]` probe headline: **`registeredLights=463`, cap `MaxGlobalLights=128`,
|
||||||
|
`activeLights=8`.** The Town Network hub registers **463** fixtures;
|
||||||
|
`LightManager.BuildPointLightSnapshot(playerWorldPos)` keeps only the **128 nearest the
|
||||||
|
player** over the WHOLE resident set, evicting ~335/frame. `SelectForObject` (the
|
||||||
|
faithful per-object ≤8 pick) can only choose from the surviving 128, so the hub's fill
|
||||||
|
lighting is starved and the room falls back to mostly the (correct) 0.2 ambient →
|
||||||
|
reads dark. Retail keeps the same 0.2 ambient but scopes its light pool to the
|
||||||
|
portal-flood **visible cells** (tiny 40+7 cap never bites), so every visible cell keeps
|
||||||
|
its own fixtures → the room reads bright. This is exactly **#79/#93/#176/#177**, now
|
||||||
|
reproduced at 463 fixtures (bigger than the Facility Hub's 366 where it was found).
|
||||||
|
|
||||||
|
## Current code state (verify — this doc dates fast)
|
||||||
|
|
||||||
|
- `src/AcDream.Core/Lighting/LightManager.cs`: `MaxGlobalLights = 128`;
|
||||||
|
`BuildPointLightSnapshot(Vector3 playerWorldPos)` caps the whole `_all` resident set
|
||||||
|
at 128 **nearest-player** (camera-invariant — the camera-coupling bug is already
|
||||||
|
fixed). There is **no** visible-cell scoping right now.
|
||||||
|
- The visible-cell scoping WAS shipped once (`c500912b`) and **reverted** — the first
|
||||||
|
attempt scoped by the *camera*-flood and churned the pool as the camera turned (the
|
||||||
|
#176 seam-floor blink). See the pseudocode doc's CORRECTION #2.
|
||||||
|
- Renderers already select per-cell (`EnvCellRenderer.cs:~1088`) and per-object
|
||||||
|
(`WbDrawDispatcher.cs:~2095`) from `LightManager.PointSnapshot`. The only defect is
|
||||||
|
how `PointSnapshot` is *built*.
|
||||||
|
|
||||||
|
## The fix shape (A7.L1 — port the visibility-scoped per-frame collection)
|
||||||
|
|
||||||
|
Retail's model (source-confirmed, pseudocode doc §1): each cell owns a light list;
|
||||||
|
per frame ONLY the visible cells push their lights into a small global player-nearest
|
||||||
|
pool (40 static + 7 dynamic). The candidate pool is pre-scoped by *visibility*, so the
|
||||||
|
tiny cap never bites. Port THAT, not a bigger cap:
|
||||||
|
|
||||||
|
1. **Build the `[indoor-light]` per-cell composition probe FIRST** (the A7 handoff's
|
||||||
|
A7.L1 apparatus — prints per-cell active-light SET MEMBERSHIP: cellId → {pos, color,
|
||||||
|
attenuation, dir, isDynamic}, plus registered/pool/dropped counts). The current
|
||||||
|
`[light]` probe prints COUNTS, not membership — it can't show which cell lost which
|
||||||
|
light. Build the membership probe before touching the pool.
|
||||||
|
2. **Tag each `LightSource` with its owning cell id** (`CellId`, populated at every
|
||||||
|
registration site from `entity.ParentCellId`; viewer light stays `CellId==0`).
|
||||||
|
Retail's `add_*_light(info, cellId, frame)` carries exactly this.
|
||||||
|
3. **Feed the per-frame pool from the currently-visible cells** (the portal-flood set
|
||||||
|
the renderer already computes), not the whole `_all` set — CORRECTLY this time:
|
||||||
|
scope by the **player/visible-cell flood**, NOT the camera position (that was the
|
||||||
|
reverted attempt's bug). The pool is then naturally bounded and the 128 cap stops
|
||||||
|
biting. Un-skip
|
||||||
|
`LightManagerTests.PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant`.
|
||||||
|
4. **(Fix #2, follow-on) static curve for stationary fixtures** — a server-spawned
|
||||||
|
wall lantern is stationary → static 1/d³ (range×1.3), reserve `isDynamic`
|
||||||
|
(1/d, range×1.5) for genuinely MOVING lights. This is over-BRIGHTENING, not the
|
||||||
|
darkness — separate slice.
|
||||||
|
5. **(Fix #3) hunt the striped floor artifact** with the full pool on (see the
|
||||||
|
2026-07-06 handoff §3).
|
||||||
|
|
||||||
|
## DO-NOT-RETRY / gotchas
|
||||||
|
|
||||||
|
- **Do NOT raise the ambient.** 0.2 white is retail-verified three ways (this doc).
|
||||||
|
- **Do NOT re-scope the pool by CAMERA position** (the reverted `c500912b` bug →
|
||||||
|
#176 seam blink). Scope by the visible-cell flood / player.
|
||||||
|
- **Do NOT just raise `MaxGlobalLights`** (128→1024 was tried `4d25e04d`, reverted —
|
||||||
|
it exposed the through-floor purple wash + stripe artifacts the cap was masking).
|
||||||
|
- **cdb note for next time:** the `Position→objcell_id` offset used here
|
||||||
|
(`dwo(dwo(@esp+4))` at `ChangePosition` entry) was WRONG — the cell id printed stuck
|
||||||
|
at a constant. The `level`/`color32` capture on `SetWorldAmbientLight` was correct.
|
||||||
|
Find the real `Position` layout (or read the `Render::player_pos` global set at
|
||||||
|
0x00455ab6) before trusting a cdb-printed cell id.
|
||||||
|
|
||||||
|
## Apparatus / evidence produced this session
|
||||||
|
|
||||||
|
- Live probe (already in the tree): `ACDREAM_PROBE_LIGHT=1` → `[light]` counts;
|
||||||
|
`ACDREAM_PROBE_CELL=1` → `[cell-transit]`. Fountain-room reading:
|
||||||
|
`insideCell=True ambient=(0.2,0.2,0.2) registeredLights=463 activeLights=8
|
||||||
|
playerCell=0x00070144`.
|
||||||
|
- Retail cdb scripts (throwaway, in the session scratchpad): captured retail interior
|
||||||
|
ambient = 0.2 white. The `SetWorldAmbientLight` bp recipe above is reusable.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `docs/research/2026-07-06-a7-per-cell-lighting-pseudocode.md` — the retail model
|
||||||
|
(source-confirmed) + BOTH correction banners. **The spec for the port.**
|
||||||
|
- `docs/research/2026-07-06-176-177-handoff-A7-lighting.md` — the #176/#177 root cause,
|
||||||
|
the reverted cap-raise, the tooling built.
|
||||||
|
- `claude-memory/reference_retail_ambient_values.md` — ambient SSOT (now with the
|
||||||
|
2026-07-09 live-verified 0.2 interior confirmation).
|
||||||
|
- Retail anchors: `CellManager::ChangePosition` 0x004559B0;
|
||||||
|
`SmartBox::SetWorldAmbientLight` 0x004530a0; `CObjCell::add_light` 0x0052b1d0;
|
||||||
|
`CEnvCell::add_dynamic_lights` 0x0052d410; `Render::insert_light` 0x0054d1b0
|
||||||
|
(cap 40+7); `Render::minimize_object_lighting` 0x0054d480; `calc_point_light`
|
||||||
|
0x0059c8b0 (static 1/d³ curve).
|
||||||
Loading…
Add table
Add a link
Reference in a new issue