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
@@ -263,9 +263,24 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
template: `Condenser::MoveValveMessageHandler` (id 4, same novice guard). See [[experience-levels]].
|
||||
- **Generator** @0x50FB90: {4, "ToggleGeneratorOnOff"→@004b1ed0} (streamed buttons 0x1A-0x1D →
|
||||
GeneratorA-D — unreconstructed).
|
||||
- **Searchlight** @0x51120C: {3, "ToggleLamp"→@004b860c}; PowerWatcher/ThermalSight-side variant
|
||||
@0x51117C: {3, "ToggleLamp"→@004b838c}. (Reconstruction has Searchlight::ToggleLamp but the
|
||||
MessageHandlers set is default-constructed EMPTY — unwired.)
|
||||
- **Searchlight** @0x51117C: {3, "ToggleLamp"→**@004b838c**}; **ThermalSight** @0x51120C: {3,
|
||||
"ToggleLamp"→**@004b860c**}. ⚠ **CORRECTED 2026-07-25 (#61)** — these were swapped here (and in
|
||||
`searchlight.cpp:51`, `hud.cpp:282`), which propagated a false "1995 latent bug" across four
|
||||
context files; see the WAVE-4 note in [[subsystems]]. The two TUs emit PARALLEL shared-data blocks
|
||||
with identical stride — msg entry, +0x5C attribute entries, +0x84 Performance triple:
|
||||
| TU | msg entry | handler | attributes | Performance |
|
||||
|---|---|---|---|---|
|
||||
| Searchlight | @0x51117C {3,"ToggleLamp"@0x511440} | @004b838c | LightOn→0x1D8, LightState→0x1E4 | @004b841c (reads **0x1E0**) |
|
||||
| ThermalSight | @0x51120C {3,"ToggleLamp"@0x51146A} | @004b860c | LightOn→0x1D8, LightState→0x1E0 | @004b8648 (reads **0x1DC**) |
|
||||
What pins each entry to its TU: **"ToggleLamp" is NOT pooled across the two** (two copies exist)
|
||||
and each sits immediately before its own class-name string — `"Searchlight"@0x51144B` and
|
||||
`"ThermalSight"@0x511475`. [T1: `reference/decomp/section_dump.txt:69661-69711`]
|
||||
**@004b838c is a Ghidra EXPORT GAP (#60 list)** — `functions_index.tsv` jumps straight to 4b83b8.
|
||||
**RECOVERED 2026-07-25 by raw disasm** of `content/BTL4OPT.EXE` (`scratchpad/dis838c.py`, the
|
||||
EjectAmmo technique) [T1]: `if (msg+0xC > 0) [this+0x1E0] = ([this+0x1E0]==0);` then
|
||||
`or word ptr [this+0x18],1` **unconditionally** (== `ForceUpdate()`, per #59). Note it has **NO**
|
||||
novice/+0x25C gate — that is ThermalSight-only; see [[experience-levels]].
|
||||
**Both sets WIRED 2026-07-25 (#61)**; both were default-constructed blackholes before.
|
||||
- **Myomers-class** @0x51158C: {9, "ToggleSeekVoltage"→@004b8a48}; **energy-weapon** variant
|
||||
@0x511DB8: {0xb, "ToggleSeekVoltage"→@004ba478}; **ammo-weapon** @0x512210: {0xb,
|
||||
"EjectAmmo"→@004bb9b8}. **Both ToggleSeekVoltage handlers WIRED 2026-07-21 (issue #19)**:
|
||||
|
||||
Reference in New Issue
Block a user