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

114 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.