Resolves the divergence-register conflict: kept the accurate per-VERTEX AP-35
(Fix A shipped per-vertex; main's row was the stale pre-Fix-A per-pixel text),
kept main's UI rows AP-37..AP-42, and renumbered this branch's torch-gate row
AP-37 -> AP-43 (AP-37 was taken by main's LayoutDesc row). AP count 41 -> 42.
Retargeted the AP-37 references in WbDrawDispatcher + the CHECKPOINT to AP-43.
Marked ISSUES #140 RESOLVED (b7d655b) with the corrected root cause.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
A7 Fix D round 2 — REAL cause found (object sun+ambient + torch REACH), CHECKPOINT
Date: 2026-06-19 Branch: claude/thirsty-goldberg-51bb9b (NOT merged — held at the visual gate)
Predecessor: docs/research/2026-06-18-lighting-a7-fixABC-shipped-fixD-handoff.md
Status: checkpointed at user request after pinning the root cause. D-1..D-4 are committed +
correct but did NOT fix the visible symptom — they were the wrong subsystem.
✅ RESOLVED 2026-06-19 (second session) — the "torch REACH" theory was WRONG; real cause = retail does NOT torch-light OUTDOOR objects at all
The open question is settled, and it overturns this checkpoint's own hypothesis. The fix is NOT "shorten torch reach" — it is "outdoor objects receive NO torches."
Empirical (acdream side, headless dat dump HoltburgTorchFalloffProbeTests): the Holtburg
neighbourhood has 27 static lights, raw dat Falloff ∈ {3,5,6} — the dominant orange entrance
torch (setup 0x020005D8, colour (1,0.588,0.314)) is Falloff 6 (17 of 27). acdream reads
this faithfully — LightInfoLoader just copies info.Falloff, no stray ×1.5. There is NO
Falloff-4 torch anywhere in Holtburg, so the predecessor's "retail orange = falloff 4" could not
be a different falloff-4 torch. Both clients read the same dat float → acdream's reach is NOT
inflated. So "acdream 6 vs retail 4" was a red herring.
Decomp (retail side, read verbatim + corroborated by an independent adversarial workflow
wf_07289ba4): retail's per-object torch binder minimize_object_lighting (0x0054d480) is
gated in RenderDeviceD3D::DrawMeshInternal (0x0059f398) by if (Render::useSunlight == 0).
The OUTDOOR landscape stage runs Render::useSunlightSet(1) (PView::DrawCells 0x005a485a, right
before LScape::draw), so when the building EXTERIOR shell is drawn
(LScape::draw → DrawBlock 0x005a17c0 → DrawSortCell 0x0059f140 → DrawBuilding 0x0059f2a0 → CPhysicsPart::Draw → DrawMeshInternal), torches are SKIPPED — the only active light is the
sun (useSunlightSet(1) enables add_active_light(0xffffffff, 0) = sun + ambient only). The
static vertex bake (SetStaticLightingVertexColors 0x0059cfe0) is EnvCell-only (sole caller
DrawEnvCell 0x0059f1f6). So retail lights outdoor objects with SUN + ambient ONLY — never the
wall torches. This exactly explains the checkpoint's own isolation result ("object point lights
OFF → building matches retail"): retail's outdoor facade gets ZERO torch energy. (Confirming the
non-bug nature of reach: retail's free-object hardware path config_hardware_light 0x0059ad30
uses Range = falloff × rangeAdjust(1.5) = LONGER than acdream's ×1.3, with Diffuse = color×100
and att 1/d — that would blow the facade WHITE if enabled, which is further proof retail never
enables it outdoors.)
The three retail lighting regimes (now all mapped):
- EnvCell walls → static bake (
calc_point_light, rangefalloff×1.3, wrap, capped), no sun. → acdream mode 1 (EnvCell). ✓ already correct. - Indoor objects (
useSunlight==0) → torches (hardware, no sun). → acdream mode 0 indoor. - Outdoor objects (
useSunlight==1) → sun + ambient, NO torches. → acdream mode 0 outdoor. acdream's mode-0 path applied sun AND torches to ALL objects — wrong for both 2 and 3.
THE FIX (shipped this session): in WbDrawDispatcher.ComputeEntityLightSet, gate per-object
torch selection on the object being INDOOR (ParentCellId is an EnvCell, (id&0xFFFF)>=0x0100)
via the pure predicate IndoorObjectReceivesTorches. Outdoor objects (building shells — ParentCellId
null; outdoor scenery; outdoor creatures) keep the all-(-1) light set ⇒ sun + ambient only = retail.
The indoor "no sun" half is already handled by the global sun-kill when the player is inside a cell
(UpdateSunFromSky, intensity 0). Divergence-register row AP-43 added (documents the residual:
acdream keys sun/torch on the object's own cell + a per-frame player-inside sun-kill, vs retail's
per-draw-STAGE useSunlight — only matters for through-doorway look-ins). Tests:
WbDrawDispatcherTorchGateTests (7✓), HoltburgTorchFalloffProbeTests (dat dump). Build green;
App 280✓/1skip, Core 1486✓/2skip. Awaiting the user visual side-by-side at Holtburg before merge.
DO-NOT-RETRY (this session's corrections to the checkpoint below): do NOT shorten torch reach /
change Falloff×1.3 — acdream reads the dat faithfully and the bake reach is correct for EnvCells.
The building is an OUTDOOR object; retail gives it no torches. The original checkpoint's "tighten reach
to ~5m, keep torches ON" plan (below) is SUPERSEDED — keeping torches ON for the outdoor shell at any
reach is the bug.
TL;DR — what the visible bug actually is (and is NOT)
The user's symptom (Holtburg meeting-hall facade too bright/warm/washed-out + character backs lit) is NOT the EnvCell bake, the per-channel clamp, the half-Lambert wrap, or the SSBO leak. Those are the D-1..D-4 path. The visible surfaces are mode-0 OBJECTS, and the cause is:
- Building facade over-bright = the torch REACH is too long (acdream ~7.8 m vs retail ~5.2 m), so each entrance torch floods the whole small facade instead of a tight pool. CONFIRMED by isolation: gating object (mode-0) point lights OFF made the building match retail ("looks much better", user 2026-06-19).
- Character backs / slight object over-bright = the sun + ambient on objects (mode 0 runs both). Ambient is NOT the culprit (it MATCHES retail exactly — see values). The residual is small for the character (it ~matches retail), so the dominant visible bug is #1 (torches).
Render-path facts (source-verified, workflow wf_c4ad8cf8)
- Building EXTERIOR = a flat-mesh
WorldEntitywithIsBuildingShell=true,ParentCellId=null, built fromBuildingInfo.ModelId(LandblockLoader.cs:79-90), drawn by WbDrawDispatcher which hard-setsuLightingMode=0(WbDrawDispatcher.cs:898). It is NOT an EnvCell — so D-4 (EnvCell walls get no sun) never touched it. - Characters/creatures/players = ordinary
WorldEntitydynamics, also drawn by WbDrawDispatcher atuLightingMode=0(plain Lambert + sun). The mode plumbing is CORRECT (mode-0 plain Lambert already zeroes a torch behind a back-face — that part of D-3 works). - EnvCellRenderer (
uLightingMode=1, no-sun, wrap) only ever draws interior cell shells from the dat EnvCell list — neverinfo.Buildings, never characters. - Render loop: in-world frames go through
RetailPViewRenderer.DrawInside; the bareWbDrawDispatcher.Draw(GameWindow.cs:8230) is the no-viewer-cell fallback. Both share the ONE_meshShader(mesh_modern) program (GameWindow.cs:1845-1857), souLightingModeis one shared uniform; each renderer re-sets it before its draws.
Ground truth (live cdb retail + acdream probe, SAME-INSTANT)
- Ambient MATCHES exactly: acdream
(0.447,0.447,0.495)== retail(0.4465,0.4465,0.4951). → same sky keyframe → same time of day; NO time desync (the earlier "retail 0.3 / acdream purple" was sequential-capture drift + acdream's un-synced spawn frame; ignore it). - retail sun (
world_lights.sunlight@ 0x008672a0+0x18) =(0.573, ~0, 0.445), magnitude 0.725, colour(0.98,0.84,0.59)warm. acdreamsun=1(active, derived from the same sky state via Fix C|sunVec|=DirBright). Sun is NOT zero — retail DOES sun-light objects. - retail torches (golden, a7-fixd-golden2): static,
intensity=100,falloff 3/4/5, warm(1,0.588,0.314)orange +(0.98,0.843,0.612)cream.calc_point_lightmakes a BRIGHT TIGHT pool (saturates to full warm to ~4 m, gone by ~5.2 m). Faithful in acdream (LightBake.cs). - acdream torches ([light-detail]):
range=7.8(Falloff 6×1.3) andrange=6.5(Falloff 5). acdreamRange = info.Falloff * 1.3f(LightInfoLoader.cs:90) — the 1.3 is correct, NO stray 1.5.
The OPEN question to resolve FIRST on resume (don't guess)
acdream's orange torch reads Falloff 6 (range 7.8); retail's orange torch was captured at
Falloff 4 (range 5.2). 6 = 4 × 1.5 (smells like rangeAdjust) BUT they might be different
torches (38 static torches, several orange). Resolve by comparing the SAME torch's Falloff in
acdream vs retail, matched by world position (one focused capture): break/dump acdream's torch
Falloff for a specific Holtburg torch and the retail world_lights.static_lights[i].info.falloff
for the same one. Then:
- If acdream reads a too-large Falloff for the same torch → fix the dat read / conversion
(the DatReaderWriter
LightInfo.Falloffpath) so acdream's reach == retail's. - If the Falloff matches and reach is genuinely ~7.8 → the building-shell-as-one-object spill is the issue; tighten how building shells receive torches (the per-vertex range gate already localises, so this is unlikely — favour the Falloff hypothesis).
Proposed fix (after the falloff is confirmed)
Tighten acdream's torch reach to match retail (≈5 m), keep torches ON. Building facade then shows
a tight warm pool by each flame + dark stone elsewhere (retail-faithful). Files: LightInfoLoader.cs
(the Falloff→Range conversion), possibly the DatReaderWriter light read. Add a divergence-register
row if any conversion deviates. Re-verify visually (the diagnostic that confirmed the cause:
object point lights OFF == retail-match).
State of the committed work (KEEP — all correct, just off-target for the visible bug)
| Commit | What | Verdict |
|---|---|---|
180b4af |
D-1 clamp point sum on its own | faithful; keep |
39c70f0 |
D-2 prep — LightBake conformance test | keep |
cf62793 |
D-1 shader clamp | keep |
c62da82 |
D-2 EnvCell shell binds own light set (real leak fix) | keep |
b57a53e/156dc45 |
register AP-35/AP-16 corrections | keep |
0980bea |
D-3 objects plain-Lambert / D-4 EnvCell no-sun | keep; correct but doesn't touch the building (it's an object) |
tools/cdb/a7-fixd-*.cdb capture scripts are committed. Diagnostic shader hack reverted
(working tree clean). Branch NOT merged — finish the torch-reach fix, visual-verify, then merge.
DO-NOT-RETRY (cost a lot this session)
- Don't re-tune the EnvCell bake / per-channel clamp / wrap / SSBO binding for the building — the building is a mode-0 OBJECT, none of that path lights it.
- Don't chase a time-of-day / ambient desync — ambient + time MATCH retail exactly (0.446).
- Don't "remove the sun" globally — retail DOES sun-light objects (sun 0.725).
- The visible building bug is the torch REACH (confirmed by isolation); start there.