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>
7.3 KiB
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 × intensityAttenuation = (0, 1, 0)⇒ 1/d (inverse-LINEAR; acdream'scalc_point_lightis~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
LIGHTINFOcaptured: torchtype=0 intensity=100 falloff=6; a 2nd lightintensity=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::SetStaticLightingVertexColorsBAKE (0x0059cfe0 →calc_point_light), like EnvCells. Theconfig_hardware_lightlights I captured were for a DIFFERENT object (player / creature / the purple PORTAL — note theintensity=100could be the portal, not the wall torch). If (a) holds, acdream'scalc_point_lightis 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_LIGHTINGoff / lights notLightEnable'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-objectDrawIndexedPrimitive(D3DRS_LIGHTING, which lights are enabled). Also: atconfig_hardware_light, dump WHICH object/owner the light is being configured for to identify whether theintensity=100light 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 viamesh_modern.vertpointContribution(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_groupLScape::sunlightglobal @ 0x00841940 (Vector3);LScape::ambient_level@ 0x00841770bp acclient!PrimD3DRender::config_hardware_light(0x0059ad30) — arg4=LIGHTINFO[esp+0x10];dt acclient!LIGHTINFO dwo(@esp+0x10) type intensity falloff colorrangeAdjust = 1.5@ 0x00820cc4;D3DPolyRender::SetStaticLightingVertexColors@ 0x0059cfe0- Pattern:
.formats poi(<addr>)for floats,dwo(<addr>)for dwords,qdafter 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.