acdream/docs/research/2026-06-18-lighting-a7-fixABC-shipped-fixD-handoff.md
Erik f384d036a3 docs: A7 lighting handoff — Fix A/B/C shipped, Fix D (object torch over-bright) open
Session handoff: live-cdb grounding shipped Fix A (point-light shape), Fix B
(per-object selection), Fix C (sun-vector magnitude / ~32% over-bright). Fix D
(outdoor objects too bright near torches) is fully grounded but BLOCKED on one
capture (the building's render path) — the D3D-FF math says it'd make objects
brighter, so not ported. Full cdb cheat-sheet + the contradiction + the next
capture in the doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 15:35:00 +02:00

7.3 KiB
Raw Blame History

A7 Lighting — Fix A/B/C SHIPPED, Fix D (object torch over-brightness) HANDOFF

Date: 2026-06-18 Branch: claude/thirsty-goldberg-51bb9b (merged to main) Companion memory: claude-memory/reference_retail_ambient_values.md (all captured values + cdb recipes) and reference_retail_chat_colors.md (cdb method).

This session made acdream's outdoor + ambient lighting retail-faithful by grounding everything in live cdb on the retail client (no guessing). Three fixes shipped; a fourth (Fix D — outdoor objects too bright near torches) is fully grounded but deliberately NOT implemented because the math contradicts the observed result — one more capture is needed first.

SHIPPED this session (all on main)

Fix Commit What Result
A aa94ced point-light SHAPE: per-vertex Gouraud + faithful calc_point_light (wrap + norm), per-channel cap killed the "spotlight" disc — user "way better"
B 4345e77 per-OBJECT light selection (minimize_object_lighting): each object picks its own ≤8 lights by its AABB sphere, camera-independent killed "building lights up as you approach"; a Holtburg view has 129 point lights vs the old global cap of 8
C 57c1135 sun-vector magnitude: ambient + sun were ~32% too bright ambient now matches retail within ~2%; user "general ambient better outside"

Fix B mechanism (for context): two new SSBOs in mesh_modern.vert — binding=4 GLOBAL light array (LightManager.PointSnapshot), binding=5 per-instance 8-int light set (mirrors the U.3 clip-slot SSBO). LightManager.SelectForObject + BuildPointLightSnapshot (pure, TDD). WbDrawDispatcher computes each entity's light set once per entity (like _currentEntitySlot), threads it parallel to the matrices.

Fix C mechanism: SkyStateProvider.RetailSunVector had y = cos(P) (≈1) — the PRE-transform value SkyDesc::GetLighting writes to its arg5 (0x00500ac9), before LScape::set_sky_position's world transform. cdb read retail's actual LScape::sunlight = (0.2238, ~0, 0.00352), magnitude = DirBright. Corrected to the world-space spherical form DirBright × (cos P·sin H, cos P·cos H, sin P), |sunVec| == DirBright. Feeds BOTH the ambient boost AND the sun colour, so it dims terrain + objects + sky (all read the shared SceneLighting UBO). 18/18 sky tests green (old tests pinned the inflated magnitude — updated to cdb-verified).

KEY LESSON: the "too purple" was NEVER a bug

The user's side-by-side ("acdream too purple, retail neutral") was a comparison across different times of day. Live cdb at the SAME game time + DayGroup proved acdream's time, weather (DayGroup selection), AND ambient COLOR all match retail exactly — the purple AmbColor=(200,100,255) is authored per-time-of-day in the sky dat (twilight = purple, midday = neutral (230,230,255)). Only the brightness was wrong (Fix C). Don't re-investigate the purple.


OPEN — Fix D: outdoor OBJECTS too bright near torches

Symptom (user, 2026-06-18): the Holtburg meeting-hall walls blow out warm/bright in acdream vs dim in retail. Fix A/B/C did NOT touch this. It's the per-object point-light contribution on objects.

Grounded (cdb + decomp) — retail's object point-light path

Render::config_hardware_light (0x0059ad30) builds the D3DLIGHT9:

  • Diffuse = color × intensity
  • Attenuation = (0, 1, 0)1/d (inverse-LINEAR; acdream's calc_point_light is ~1/d² via norm = distsq·d)
  • Range = falloff × rangeAdjust, rangeAdjust = 1.5 (0x00820cc4) ⇒ torch Range = 6×1.5 = 9 m (LARGER than acdream's falloff×1.3 = 7.8 m — range is NOT why we're brighter)
  • live LIGHTINFO captured: torch type=0 intensity=100 falloff=6; a 2nd light intensity=2.25 falloff=10
  • d3d_material.Diffuse = (1,1,1) white (decomp 0x00539774)

THE CONTRADICTION (resolve this FIRST next session)

By mat(1)×color×100×(N·L)×(1/d), a torch 3 m away = color×33 ⇒ retail's walls SHOULD blow to WHITE — but they're DIM. Material diffuse, range, and intensity are all captured and ruled out. So the scaling lives in the building's RENDER PATH, which is unknown. ⚠ DO NOT port the D3D-FF model — by this math it would make objects BRIGHTER (white), the opposite of the fix.

The decisive next capture

Determine the static building's ACTUAL render path:

  • Hypothesis (a) — MOST LIKELY: static buildings DON'T use D3D hardware lighting. They use the D3DPolyRender::SetStaticLightingVertexColors BAKE (0x0059cfe0 → calc_point_light), like EnvCells. The config_hardware_light lights I captured were for a DIFFERENT object (player / creature / the purple PORTAL — note the intensity=100 could be the portal, not the wall torch). If (a) holds, acdream's calc_point_light is the RIGHT model and the over-brightness is the per-channel cap (min(scale×col,col) lets several torches each reach full colour and sum to white) and/or too many torches selected per object and/or a missing clamp step.
  • Hypothesis (b): D3DRS_LIGHTING off / lights not LightEnable'd for the building draw.
  • How to capture: break at SetStaticLightingVertexColors (0x0059cfe0) and see whether it's called for the building's mesh (confirms the bake path); and/or inspect the render state around the static-object DrawIndexedPrimitive (D3DRS_LIGHTING, which lights are enabled). Also: at config_hardware_light, dump WHICH object/owner the light is being configured for to identify whether the intensity=100 light is the torch or the portal.

acdream side — where the fix lands

  • acdream runs calc_point_light (wrap/norm + per-channel cap) for ALL meshes via mesh_modern.vert pointContribution (objects AND cells — Fix A).
  • If buildings use the bake, the likely fix is in the cap / sum / count, not the attenuation model. Files: src/AcDream.App/Rendering/Shaders/mesh_modern.vert (pointContribution + accumulateLights), src/AcDream.Core/Lighting/LightManager.cs (SelectForObject), LightBake.cs (verbatim calc_point_light, still unwired).

cdb cheat-sheet (all verified this session; binary MATCHES refs/acclient.pdb)

  • bp acclient!SmartBox::SetWorldAmbientLight (0x004530a0) — arg2=level [esp+4], arg3=color32 [esp+8]
  • bp acclient!SkyDesc::GetLighting (0x00500a80) — arg2=dayFraction [esp+4]; dt acclient!SkyDesc @ecx present_day_group
  • LScape::sunlight global @ 0x00841940 (Vector3); LScape::ambient_level @ 0x00841770
  • bp acclient!PrimD3DRender::config_hardware_light (0x0059ad30) — arg4=LIGHTINFO [esp+0x10]; dt acclient!LIGHTINFO dwo(@esp+0x10) type intensity falloff color
  • rangeAdjust = 1.5 @ 0x00820cc4; D3DPolyRender::SetStaticLightingVertexColors @ 0x0059cfe0
  • Pattern: .formats poi(<addr>) for floats, dwo(<addr>) for dwords, qd after N hits to auto-detach (keeps retail alive). User must have retail in-world first.
  • acdream probes: ACDREAM_PROBE_LIGHT=1 ([light] ambient+sun line), ACDREAM_DUMP_SKY=1 (keyframes + dayFraction + DayGroup).

Build / run

dotnet build src/AcDream.App/AcDream.App.csproj -c Debug (green). Standard ACDREAM_LIVE launch env in CLAUDE.md. Close the client before rebuilding (it locks the DLLs). 18/18 sky tests + 17/17 LightManager + 36/36 dispatcher clip-slot green.