KB: document the searchlight fog-swap latent bug (task #63, left as-is by decision)

Investigation (workflow) found the searchlight-driven fog swap never worked in the
ORIGINAL 1995 binary either -- it's a latent bug, not a port regression:
SearchlightSimulation (@004b841c) reads requestedOn@0x1E0 (never written) while
ToggleLamp (@004b860c) toggles commandedOn@0x1DC; no bridge exists, so lightState
@0x1D8 is perpetually 0 (lamp never lights). The self-consistent sibling
ThermalSight reads the field it toggles (0x1DC) -- the tell. The port reproduces
the bug faithfully (searchlight.cpp:189). Additionally the port never constructs
PullFogRenderable, so even absent the bug the swap wouldn't fire.

Decision: LEAVE AS-IS + document (faithful to the buggy original); the port keeps
the static lights-ON fog= values. A working swap would DEVIATE from the shipped
binary (repair the sim to read commandedOn + construct PullFogRenderable at the
btl4vid MakeMechRenderables inside pass + a toggle input) -- fully scoped in the KB
if ever wanted. No code change this commit.

Documented in rendering.md (fog section), open-questions.md (deferred subsystems),
subsystems.md (Searchlight). Core per-map/time/weather fog (task #63) is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-13 22:04:29 -05:00
co-authored by Claude Opus 4.8
parent 225fa5951b
commit c0a0ec5e69
3 changed files with 39 additions and 9 deletions
+16
View File
@@ -59,6 +59,22 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
(task #8 "landed" vs the kShotDamage=12 bring-up residue) — audit before reworking.
## Deferred subsystems / feeds (authentic path scoped, marked in code)
- **Searchlight-driven fog swap — DEFERRED, and it is an ORIGINAL 1995 LATENT BUG (task #63,
2026-07-13) [T1 decomp].** The arcade swaps fog between `fog=` (lights on) and `nosearchlightfog=`
(off) via `PullFogRenderable` watching the Searchlight's `lightState`. It never fires — in the PORT
because `PullFogRenderable` is never constructed, AND in the BINARY ITSELF because the Searchlight
never lights: `ToggleLamp` (@004b860c) toggles `commandedOn` @0x1DC but `SearchlightSimulation`
(@004b841c) reads `requestedOn` @0x1E0, which is zeroed by the ctor and never written — no
0x1DC→0x1E0 bridge exists (self-consistent sibling ThermalSight reads the field it toggles, 0x1DC).
So `lightState` is perpetually 0. The port reproduces this faithfully (searchlight.cpp:189).
**DECISION: left as-is + documented (faithful to the buggy original); port keeps the static
lights-ON `fog=`.** A working swap is a DESIGNER-INTENT deviation, fully scoped if ever wanted:
(1) sim reads `commandedOn` (mirror ThermalSight); (2) construct `PullFogRenderable` at
btl4vid.cpp `MakeMechRenderables` reticle-build/inside pass (== arcade part_014.c:5173, Dynamic,
bound per Searchlight `lightState` via a new `LightStatePtr()` accessor); (3) a toggle input (the
authentic cockpit button-5→`ToggleLamp` dispatch is a controls-family reconstruction; a dev key
stands in). Also flags a deferred **`Mech::ControlsAllowLights()`** (searchlight.hpp:158 stub).
See [[rendering]] fog section. Verified inert live: BT_FOG_LOG shows zero `SetFogStyle(2/3)`.
- **Factory capability-roster loops 2-4 are STILL DEAD (task #57 discovery).** mech.cpp's
post-roster loops add to `heatableSubsystems`(0x51155c)/`weaponRoster`(0x511830)/
`damageableSubsystems`(0x50e4fc) through the local `SubProxy` stub whose `IsDerivedFrom`
+18 -9
View File
@@ -67,15 +67,24 @@ DECLOUDS must stay in the sky pass). Authored TEXTURE **SCROLL** (BMF TEXTURE ta
arcade. Night fog is dark BY DESIGN (distance→murk, not light haze); day pages author light haze. Env:
`BT_FOGMODE=table|vertex|off`, `BT_WEATHER=clear|fog|soup`, `BT_FOG="near far r g b"` (all branches);
`[fog] resolved`/`[fog] model`/`[fogstyle]` (BT_FOG_LOG) log lines. [T1 pipeline / T0 arcade model / T2 fix]
**⚠ SEARCHLIGHT SWAP IS INERT [T1, verified 2026-07-13].** The arcade swaps the active fog between
`fog=` (headlight ON) and `nosearchlightfog=` (OFF, darker/tighter) via `PullFogRenderable::Execute`
(L4VIDRND.cpp:4601, edge-triggered on two light attrs → `SetFogStyle(searchLightOn/OffFogStyle)`). In
the port **`PullFogRenderable` is NEVER CONSTRUCTED** (grep: only its .h decl + .cpp def; no `new`), and
`searchLightOn/Off` is called from nowhere else — so the swap never fires (BT_FOG_LOG: over 22s only
`SetFogStyle(0)`, zero style 2/3). Fog is STATIC at the searchlight-ON (`fog=`) values; the
`nosearchlightfog=` variant is dead. Reconstruction gap: wiring needs the mech's headlight/searchlight
state (Searchlight subsys 3032) bound to a `PullFogRenderable` constructed at viewpoint/mech setup.
Core per-map/time/weather fog is unaffected (uses the lights-ON set, the normal play state).
**⚠ SEARCHLIGHT SWAP IS INERT — and it was inert in the ARCADE too (task #63 workflow, 2026-07-13).**
The arcade swaps the active fog between `fog=` (headlight ON) and `nosearchlightfog=` (OFF,
darker/tighter) via `PullFogRenderable::Execute` (L4VIDRND.cpp:4601, edge-triggered on two light attrs →
`SetFogStyle(searchLightOn/OffFogStyle)`). TWO independent reasons it never fires:
(1) **Port:** `PullFogRenderable` is NEVER CONSTRUCTED (grep: only its .h decl + .cpp def; no `new`);
`searchLightOn/Off` is called from nowhere else (BT_FOG_LOG: over 22s only `SetFogStyle(0)`, zero 2/3).
(2) **Original 1995 LATENT BUG [T1, decomp]:** even if wired, the Searchlight never lights —
`ToggleLamp` (@004b860c) toggles `commandedOn` @0x1DC (part_013.c:6087) but `SearchlightSimulation`
(@004b841c) reads `requestedOn` @0x1E0 (part_013.c:5978), which the ctor zeroes and NOTHING ever writes;
no bridge 0x1DC→0x1E0 exists anywhere in the binary. So `lightState`@0x1D8 is perpetually 0. The
self-consistent sibling **ThermalSight** reads the SAME field it toggles (0x1DC) — the tell that
Searchlight's sim reads the wrong slot. The port reproduces this faithfully (searchlight.cpp:189 reads
requestedOn). **DECISION (2026-07-13): left as-is + documented** (faithful to the buggy original); the
port keeps the static lights-ON `fog=` values. To make the swap WORK is a DESIGNER-INTENT fix that
DEVIATES from the shipped binary — repair the sim to read `commandedOn` (mirror ThermalSight) + construct
`PullFogRenderable` at btl4vid.cpp `MakeMechRenderables` (the reticle-build/inside pass, == arcade
part_014.c:5173, ExecutionType Dynamic, bound to each Searchlight `lightState`) + a toggle input. See
[[open-questions]]. Core per-map/time/weather fog is unaffected.
- **Projection / stencil (task #55):** the BTDPL.INI `viewangle=60` is the **HORIZONTAL** FOV —
authentic 60×47 frustum at 4:3; fovY derived via `BTFovYFromHorizontal` (L4VIDEO.cpp), applied at
all 5 projection sites (it was mis-applied as vertical). The device depth-stencil is now
+5
View File
@@ -41,6 +41,11 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER
- **WAVE 3** — power bus (Generator/PoweredSubsystem) + Emitter/PPC fire-path (end-to-end fire; heat
conducts to the central sink via the linked-sink roster).
- **WAVE 4** — standalone readouts: Sensor/Searchlight/ThermalSight/AmmoBin (de-shim, gate fixes).
- ⚠ **Searchlight has an ORIGINAL latent bug [T1, task #63]:** `SearchlightSimulation` (@004b841c)
reads `requestedOn`@0x1E0 (never written) while `ToggleLamp` (@004b860c) toggles `commandedOn`@0x1DC
— no bridge, so `lightState`@0x1D8 is perpetually 0 (lamp never lights). Present in the 1995 binary
(sibling ThermalSight reads the field it toggles, 0x1DC); the port reproduces it faithfully. Left
as-is by decision; it makes the searchlight→fog swap inert. See [[open-questions]] + [[rendering]] fog.
- **WAVE 5** — Torso (aim twist/elevation; joint-I/O reconstructed; the BLH record disables torso →
faithfully no visible twist).
- **WAVE 6** — Myomers (mover-coupled; structural un-stub is INERT — no live mover/heat coupling).