# A7 indoor torch/lantern lighting — why acdream interiors read dark (investigation) **Date:** 2026-06-20 **Mode:** report-only investigation (no code changed) **Trigger:** after #142 sun-gate (`ef5049f`) the Agent of Arcanum interior (`0xA9B40171/0172`) still reads dark; user: "retail uses torches **and lanterns** for lighting indoors." ## Retail oracle — the static-light bake (decomp, confirmed) `SetStaticLightingVertexColors` (0x0059cfe0), per EnvCell mesh, per vertex: ``` for each vertex V: acc = (0,0,0) // from BLACK; no ambient/sun here for i in 0 .. world_lights.num_static_lights: // ALL lights — NO per-vertex/cell cap L = sorted_static_lights[i] if L.type==0: calc_point_light(V, acc, L) // 0x0059c8b0 else: acc += dot(N,dir)*scale*L.color acc = clamp(acc, 0, 1) // per-channel, AFTER summing all V.diffuse = encode(acc) ``` `calc_point_light` (0x0059c8b0): `range = falloff × static_light_factor(1.3)`; back-face/range gated; `angular = dot_n/dist` (<1 m) else `dot_n/(dist²·dist)`; `scale = angular·(1−dist/range)·intensity`; per-channel cap `min(scale·c, c)`. **Global cap:** `max_static_lights = 0x28 = 40` (`insert_light` 0x0054d1b0 keeps a distance-sorted, replace-farthest list). So the bake sees up to **40 nearest static lights — and sums ALL of them per vertex.** There is no 8-light cap on the static wall bake. (The 8-light cap is the D3D *hardware* path for *dynamic objects*, `minimize_object_lighting` 0x0054d480 — a different path.) ## acdream's torch path — three real divergences | # | Divergence | Site | Effect | |---|---|---|---| | Bug A | **Weenie fixture lights never registered.** `RegisterOwnedLight` is called ONLY from `ApplyLoadedTerrainLocked` for dat-baked landblock + EnvCell statics (Setup `0x02xxxxxx`, `Lights.Count>0`). Server `CreateObject` weenies (`OnLiveEntitySpawnedLocked`) never register lights. | `GameWindow.cs:6733-6758` (registers); `GameWindow.cs:2696/3275` (weenie path — no register) | If a lantern/torch is a server weenie, its light is **entirely absent**. | | Bug B | **Per-cell 8-light cap.** `GetCellLightSet`→`SelectForObject` keeps the **8 nearest-to-cell-center** lights (`MaxLightsPerObject=8`); 9th+ dropped. Retail sums up to 40. | `EnvCellRenderer.cs:1044`; `LightManager.cs:234-278` | Dense cells (dungeon corridors, fixture clusters) lose lights. **Not binding** for the Arcanum (only ~3 in range). | | Bug C | **128 global camera pre-filter.** `BuildPointLightSnapshot` keeps the 128 nearest-**to-camera** lights (`MaxGlobalLights=128`), drops the rest before per-cell selection. Retail has no global per-frame cap on statics. | `LightManager.cs:179,208-211` | `registeredLights=129` ⇒ already at the cap; drops farthest-from-camera. Hurts far lights, not the near interior. | Bug A·partial: the per-vertex bake is also approximated as **one 8-light set per whole cell** (uniform across all the cell's vertices), vs retail's true per-vertex bake — a fidelity gap (bright cluster smears across the cell), secondary to the source-count gaps above. The per-light shader formula (`pointContribution`) + the `min(pointAcc,1.0)` torch clamp **match** retail's `calc_point_light` + final saturate — magnitude per light is faithful. The gap is **missing / capped light SOURCES**, not per-light math. ## Ranked hypotheses (for the Agent of Arcanum specifically) 1. **H1 — the lanterns are server weenies ⇒ Bug A drops their light** (best fit for the "lanterns" hint). The cell shows only ~3 *dat* torches; lanterns absent. *Falsify:* pick a lantern in-game — guid `0x80…`/`0x7…` = weenie ⇒ confirmed. 2. **H4 — per-cell (not per-vertex) bake / magnitude** — if the Arcanum has no weenie fixtures, the 3 dat torches are all there is and the per-cell-uniform bake under-fills vs retail's per-vertex bake. 3. **H2/H3 (8-cap / 128-cap)** — real retail divergences, but **not binding** for this building (≤3 lights in range, near camera). They matter for dense dungeons. ## Fix plan (faithful, per hypothesis) - **H1 (likely):** register `Setup.Lights` for light-bearing weenies in `OnLiveEntitySpawnedLocked`, mirroring `ApplyLoadedTerrainLocked:6742-6758` (guard `setup.Lights.Count>0`); unregister on despawn (`UnregisterByOwner`). Faithful: retail registers object lights regardless of static/dynamic origin. - **H2:** raise the per-cell static bake cap from 8 toward retail's 40 (the cap is retail-correct only for the *dynamic-object hardware* path, not the wall bake). - **H3:** make the per-cell selection independent of the 128 camera pre-filter for statics (retail's bake has no global count cap). - **H4:** move the cell bake toward per-vertex (longer-horizon A7 LightBake work). ## What this is NOT NOT a #142 sun-gate regression and NOT an ambient bug (ambient matches retail; it's just dim rainy-twilight). It is missing / capped light **sources**.