acdream/docs/research/2026-06-19-lighting-a7-fixD-round2-torch-reach-CHECKPOINT.md
Erik c83fd02642 merge: bring main (UN-7, #140 filing, D.2b UI rows) into A7 Fix D round-2 branch
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>
2026-06-20 09:29:53 +02:00

11 KiB
Raw Blame History

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 faithfullyLightInfoLoader 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):

  1. EnvCell walls → static bake (calc_point_light, range falloff×1.3, wrap, capped), no sun. → acdream mode 1 (EnvCell). ✓ already correct.
  2. Indoor objects (useSunlight==0) → torches (hardware, no sun). → acdream mode 0 indoor.
  3. 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:

  1. 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).
  2. 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 WorldEntity with IsBuildingShell=true, ParentCellId=null, built from BuildingInfo.ModelId (LandblockLoader.cs:79-90), drawn by WbDrawDispatcher which hard-sets uLightingMode=0 (WbDrawDispatcher.cs:898). It is NOT an EnvCell — so D-4 (EnvCell walls get no sun) never touched it.
  • Characters/creatures/players = ordinary WorldEntity dynamics, also drawn by WbDrawDispatcher at uLightingMode=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 — never info.Buildings, never characters.
  • Render loop: in-world frames go through RetailPViewRenderer.DrawInside; the bare WbDrawDispatcher.Draw (GameWindow.cs:8230) is the no-viewer-cell fallback. Both share the ONE _meshShader (mesh_modern) program (GameWindow.cs:1845-1857), so uLightingMode is 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. acdream sun=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_light makes 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) and range=6.5 (Falloff 5). acdream Range = 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.Falloff path) 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.