Searchlight + ThermalSight ToggleLamp WIRED (#61) -- and a swapped table attribution ROOT-CAUSED, retracting a false "1995 latent bug"
Both classes' Receiver::MessageHandlerSet were default-constructed blackholes (input-path audit systemic cause #1): entryCount 0, no parent chain, Find() returns NullHandler for every id, Receive drops silently. The new unhandled- message trace printed it on the first press: [btntest] PRESS 0x14 at poll 400 [msg] UNHANDLED: Searchlight has no handler for message id 3 Each class now has a GetMessageHandlers() function-local static chained to PowerWatcher's (which name-resolves through HeatWatcher/MechSubsystem to Receiver's empty ROOT set, so id 3 does NOT collide with HeatSink's ToggleCooling), plus a correctly-typed forwarder -- Receiver::Handler is void(const Message*) while the decomp shape is Logical(Message&), so a cast would be UB that merely happens to work on x86 __thiscall. DefaultData re-pointed off the dead empty sets. MessageArg now reads the NAMED ReceiverDataMessageOf<int>::dataContents with a static_assert locking it to the binary's message+0xC (was a raw offset read -- databinding rule). ROOT CAUSE of a long-standing misattribution: @004b860c is **ThermalSight's** ToggleLamp (table @0x51120C), NOT Searchlight's. The two TUs emit parallel shared-data blocks with identical stride (msg entry, +0x5C attrs, +0x84 Performance triple); what pins each entry to its TU is that "ToggleLamp" is NOT pooled across them -- two copies exist, each emitted immediately before its own class-name string ("Searchlight"@0x51144B, "ThermalSight"@0x511475). [T1: reference/decomp/section_dump.txt:69661-69711] Searchlight's own handler is @004b838c, which sits in a Ghidra EXPORT GAP (#60). RECOVERED by raw disassembly of content/BTL4OPT.EXE (scratchpad/dis838c.py, the EjectAmmo technique) -- and it corrected two things I had inferred wrong: * NO ControlsAllowLights/+0x25C novice gate. Searchlight tests the press alone; that lock is ThermalSight-only. A NOVICE pilot CAN work the lamp. * `or word ptr [this+0x18],1` sits AT the jle target -> the graphics-dirty bit (updateModel == ForceUpdate(), per #59) is raised UNCONDITIONALLY. Consequence: the "ORIGINAL 1995 LATENT BUG -- the searchlight can never light" claim is RETRACTED. It compared Searchlight's Performance (@004b841c, reads requestedOn@0x1E0) against ThermalSight's toggle (0x1DC) -- two different classes -- and so invented a missing 0x1DC->0x1E0 bridge. Every sibling toggles the field its own Performance reads. The searchlight DID light in the arcade, the searchlight->fog swap was NOT inert there, and building PullFogRenderable is FAITHFUL rather than a designer-intent deviation (the 2026-07-13 "left as-is" decision is void; the sim needs no repair). Verified live, real click seam (BT_BTNTEST): 0x14 -> [light] requested ON -> reported lightState 0 -> 1 (lamp lights) 0x12 -> [light] thermal sight requested ON -> thermalActive 0 -> 1 UNHANDLED lines for both classes gone; 40 LNK2019 unchanged (the pre-existing CreateStreamedSubsystem + Entity__SharedData::DefaultData families only). Swept the misattribution out of searchlight.cpp/.hpp, thermalsight.cpp/.hpp, hud.cpp:282 (it had claimed @00511180/@004b838c as HUD's), btplayer.cpp's +0x25C consumer list, context/{decomp-reference,subsystems,rendering,open-questions, pod-hardware,experience-levels}.md, docs/{GLASS_COCKPIT,INPUT_PATH_AUDIT}.md. checkctx.py CLEAN. Still open (documented, not fixed): ThermalSight has no visible IR effect (ToggleGlobalThermalVision is a marked no-op, pvision unported, IsLocallyViewed returns False); ThermalSight publishes "LightState"->0x1D8 where the binary has "LightOn"->0x1D8 and "LightState"->0x1E0; Searchlight's commandedOn@0x1DC has no identified role and measured 21 live, hinting our SubsystemResource +0x28 differs from the binary's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
62513b2f8a
commit
f3bdb3b85a
+23
-5
@@ -41,11 +41,29 @@ 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.
|
||||
- ✅ **Searchlight + ThermalSight buttons WIRED 2026-07-25 (#61)** — both classes' handler sets were
|
||||
default-constructed blackholes (systemic cause #1, `docs/INPUT_PATH_AUDIT.md`). Each now chains
|
||||
`PowerWatcher::GetMessageHandlers()` with its own id-3 `ToggleLamp`. **Verified live**: pad `0x14`
|
||||
→ `requestedOn 0→1` → `lightState 0→1`; pad `0x12` → `requestedOn 0→1` → `thermalActive 0→1`.
|
||||
- ❌ **RETRACTED: the "ORIGINAL 1995 latent bug" (old task #63) NEVER EXISTED.** It was an artifact of
|
||||
a **swapped table attribution**: the claim compared Searchlight's Performance (@004b841c, reads
|
||||
`requestedOn`@0x1E0) against **ThermalSight's** `ToggleLamp` (@004b860c, toggles `0x1DC`) and
|
||||
concluded "no 0x1DC→0x1E0 bridge, so the lamp can never light". @004b860c is not Searchlight's.
|
||||
Searchlight's own handler is **@004b838c** (table @0x51117C) and — like every sibling — toggles the
|
||||
field its OWN Performance reads, `requestedOn`@0x1E0. Each class is internally self-consistent.
|
||||
[T1 throughout. Attribution: un-pooled `"ToggleLamp"` strings adjacent to their own class names,
|
||||
`section_dump.txt:69661-69711`. Body: @004b838c is a Ghidra EXPORT GAP (#60) but was **recovered by
|
||||
raw disasm** of `content/BTL4OPT.EXE` (`scratchpad/dis838c.py`) — it toggles 0x1E0, raises
|
||||
`updateModel` (+0x18) unconditionally, and has **NO novice gate** (ThermalSight-only).] Consequence: the searchlight→fog swap was **NOT** inert in the arcade, and
|
||||
making it work in the port is FAITHFUL, not a designer-intent deviation. See [[open-questions]] +
|
||||
[[rendering]] fog, both corrected.
|
||||
- ⚠ Still open on Searchlight: `commandedOn`@0x1DC (ctor-seeded from subsystem resource +0x28,
|
||||
@004b84dc) has **no identified role** — it is neither the toggle target nor read by @004b841c, and
|
||||
it measured **21** live (not a boolean), which hints our SubsystemResource +0x28 ≠ the binary's.
|
||||
- ⚠ ThermalSight's published attributes are mis-named: we publish `"LightState"→thermalActive@0x1D8`,
|
||||
but the binary's table @0x511268 has **`"LightOn"`(id 3)→0x1D8** and `"LightState"`(id 4)→**0x1E0**
|
||||
(the alarm). A gauge binding `ThermalSight/LightOn` currently misses. Not yet changed (gauge-binding
|
||||
change needs its own verification pass).
|
||||
- **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).
|
||||
|
||||
Reference in New Issue
Block a user