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
@@ -188,9 +188,10 @@ So: **0x2C = the COOLANT FLUSH button** (Reservoir InjectCoolant, WORKS), **0x2F
|
||||
0x2A/0x29 = per-condenser VALVE buttons** (MoveValve, WORK), **0x1A-0x1D = Generator A-D ON/OFF**
|
||||
(`ToggleGeneratorOnOff` id 4, table @0050fb90 fn @004b1ed0 — DEAD, unreconstructed). Also new:
|
||||
0x13 → Mech **DuckRequest** (0x1a @0049fa00 — the manual's CROUCH button!), 0x28 → Mech
|
||||
**BalanceCoolant** (0x16 @0049f728), 0x12 → ThermalSight toggle (id 3), 0x14 → Searchlight +
|
||||
Searchlight2 **ToggleLamp** (id 3 @004b860c — body reconstructed but `Searchlight::
|
||||
MessageHandlers` is default-constructed EMPTY, so it never fires: the quick win). Weapon
|
||||
**BalanceCoolant** (0x16 @0049f728), 0x12 → ThermalSight **ToggleLamp** (id 3, table
|
||||
@0x51120C fn @004b860c), 0x14 → Searchlight + Searchlight2 **ToggleLamp** (id 3, table @0x51117C
|
||||
fn **@004b838c**) — ✅ both WIRED 2026-07-25 (#61); the two fn addresses were swapped here before
|
||||
that (see `context/decomp-reference.md`). Weapon
|
||||
Eng-page extras: msg 0x3 = **ToggleCooling** (HeatableSubsystem @0050e41c fn @004ad6f8, unwired),
|
||||
msg 0xb = **ToggleSeekVoltage** (energy @004ba478) / **EjectAmmo** (ammo weapons @004bb9b8),
|
||||
Myomers msg 9 = ToggleSeekVoltage (@004b8a48; body exists, unwired). Unhandled messages are
|
||||
|
||||
@@ -79,8 +79,8 @@ Ranked by impact. **Reverse thrust `0x3F` (key LALT / pad B) is FIXED** and conf
|
||||
| 8 | **Any clickable button latches if the mouse-up is lost** (worst on `0x3F` reverse, `0x40` trigger) | A momentary click always releases | Neither WndProc handles `WM_CAPTURECHANGED` or `WM_KILLFOCUS` — grep over `engine/` + `game/` returns **zero** hits. Alt-tab or a system dialog between press and release leaves `gCkPressed` set and `SetScreenButton(a,0)` never runs | **M** — trap: `btl4main.cpp:137` calls `ReleaseCapture()` itself, which re-enters `WM_CAPTURECHANGED`; guard on `gCkPressed != -1` (it is cleared before `ReleaseCapture`) | Click-hold `0x3F`, alt-tab, release outside the window: `BT_PAD_LOG=1` shows no release line and `rev` stays 1 |
|
||||
| 9 | **Every HOTAS / flight stick / rudder** (whole `joydev/joyaxis/joybutton/joyhat` grammar) | Drive the pod channels from a DirectInput device | `content/bindings.txt` is 89 lines and has **zero** joy rows; the joy poll is gated on `joyAxisBindingCount>0 \|\| …` (`L4PADRIO.cpp:775-777`) so `BTJoyInit/BTJoyPoll` are never called. `PadBindingProfile::Load` writes the default only when the file is **absent** (`L4PADBINDINGS.cpp:708-713`), and the file is gitignored (`.gitignore:23`) — so the zip ships **this machine's stale file** | **S** now (ship without it / refresh it) · **M** later (version marker + additive merge) | `content/jstest.log`: `bindings loaded: 40 keys, 10 pad buttons, 5 pad axes, 0 joy axes…`; no `[joy]` line exists in any log in `content/` |
|
||||
| 10 | **`joyconfig.bat` re-runs the wizard after it finishes** | Run once, then play | `BT_JOYCONFIG` is never cleared (`game/btl4main.cpp:287-291`), and the front end relaunches the exe inheriting the environment (`btl4console.cpp:138-174` clears only `BT_FE_EGG/PODS/SECS/LOOP`) → the mission process re-enters the wizard and blocks on `_getch()` behind the 3D window | **S** (1 line) | Run `joyconfig.bat`, finish it, watch for a second `=== BattleTech joystick setup ===` banner |
|
||||
| 11 | **`0x14`** Searchlight (+Searchlight2) | Turn the light on for night maps | `Receiver::MessageHandlerSet Searchlight::MessageHandlers;` default-constructed (`searchlight.cpp:50-51`, used `:72`) — the `ToggleLamp` body at `:158` is unreachable. **And wiring it changes nothing visible**: the 1995 binary writes `commandedOn@0x1DC` while the simulation reads `requestedOn@0x1E0` (`open-questions.md:183-200`), faithfully preserved at `searchlight.cpp:189` | **S** to wire (no visible payoff) · **L** to make the light work (needs a fidelity decision) | After wiring, `ToggleLamp` logs twice per press and `lightState` still stays 0 — that is the correct, faithful outcome. **`docs/GLASS_COCKPIT.md:192-194` calling this "the quick win" is misleading** |
|
||||
| 12 | **`0x12`** Thermal sight | IR view | Two layers dead: `ThermalSight::MessageHandlers` default-constructed (`thermalsight.cpp:52-53`) **and** no body — `ToggleGlobalThermalVision() {}` is an explicit no-op (`thermalsight.hpp:153`), plus the thermal render path is unported | **L** | Not testable until both land |
|
||||
| 11 | **`0x14`** Searchlight (+Searchlight2) | Turn the light on for night maps | ✅ **FIXED 2026-07-25 (#61).** `Searchlight::MessageHandlers` was default-constructed, so the `ToggleLamp` body was unreachable; it now chains `PowerWatcher::GetMessageHandlers()` with id 3. **My "wiring it changes nothing visible" verdict in this row was WRONG** — it rested on the swapped attribution this same report flags in §Corrections: @004b860c (which writes `commandedOn@0x1DC`) is **ThermalSight's** handler, not Searchlight's. Searchlight's is **@004b838c** → toggles `requestedOn@0x1E0`, the field its own Performance reads. There was never a 1995 latent bug | **S — done** | **Verified live**: `BT_BTNTEST=0x14,400,900` → `[light] requested ON` then `[light] reported lightState 0 -> 1`. The lamp lights. (`scratchpad/lamptest.py`) |
|
||||
| 12 | **`0x12`** Thermal sight | IR view | ✅ **handler + body FIXED 2026-07-25 (#61).** `ThermalSight::MessageHandlers` was default-constructed AND it had no `ToggleLamp` at all — the exported body @004b860c had been transcribed into `searchlight.cpp` by mistake. Both now correct: id 3 → `requestedOn@0x1DC`, read by `ThermalSightSimulation` @004b8648. ⚠ Still **no visible IR effect**: `ToggleGlobalThermalVision()` is a marked no-op and the thermal render path (pvision palette flip) is unported, and `IsLocallyViewed()` returns False | **S done** · **L** for the renderer | **Verified live**: `BT_BTNTEST=0x12,400,900` → `[light] thermal sight requested ON` then `reported thermalActive 0 -> 1`. State machine + alarm authentic; screen unchanged (`scratchpad/thermtest.py`) |
|
||||
| 13 | **Numpad cluster with NumLock OFF** | Aim the torso | `L4PADRIO.cpp:583-584` reads `VK_NUMPAD8..9` verbatim; with NumLock off Windows delivers `VK_UP`/`VK_DOWN`/`VK_LEFT`/`VK_RIGHT`, which `bindings.txt:26,27,30,31` map to **throttle and pedals** — "aim up" accelerates the mech. `NUMPAD5/7/9` → `VK_CLEAR/HOME/PRIOR`, unbound | **S** (startup warning) · **M** (extended-key flag) | NumLock off, hold NUMPAD8 with `BT_MPPR_TRACE=1`: `thr=` rises |
|
||||
| 14 | **Duplicate rows on one axis add instead of aliasing** (`W`+`UP`+`LSHIFT` = 3× lever rate; `A`+`LEFT`+`NUMPAD7` = 3× pedal) | One lever, one rate | `slewDelta[channel] += rate*dt` per **binding** with no per-channel fold (`L4PADRIO.cpp:604-620`). End state is identical (clamped `:956-961`, spring uses max `:157-168`) — only the ramp rate multiplies, so LSHIFT+W doubles throttle rate | **S** but same file as #4 — **bundle and defer** | `BT_GAIT_TRACE=1`, hold `W` alone vs `W`+`UP`, compare lever delta/second |
|
||||
|
||||
@@ -187,4 +187,4 @@ The whole class exists because **three failure modes produce zero output**. Each
|
||||
5. **Promote the auditors' `BTL4.RES` decoder to `tools/`.** Records are 24 bytes (`controlsGroup, controlsElement, mappingType, subsystemID, modeMask, attributeID|messageID` per `CONTROLS.h:783-803`); type-19 `ControlMappingStream` per mech, validated against `content/ctrlmap.log` at 121/121 records with zero mismatches. It makes "is this button mapped on this mech, on this page?" a two-second static query instead of a per-mech log hunt — and it already produced the authoritative answer, identical on all 18 mechs: **MAPPED** = `0x00-0x15, 0x18, 0x1A-0x1D, 0x20-0x2F, 0x3F, 0x40-0x44, 0x47`; **NEVER MAPPED** = `0x16, 0x17, 0x19, 0x1E, 0x1F, 0x30-0x3E, 0x45, 0x46`.
|
||||
|
||||
### KB sweep owed in the same commits (per the CLAUDE.md correction mandate)
|
||||
`context/pod-hardware.md:121-135` (false AddOrErase premise — also in `mechmppr.cpp:894-919`, `docs/GLASS_COCKPIT.md` §2a); `context/pod-hardware.md:50-61` (`0x28` BalanceCoolant, weapon `msg 0xb`, Myomers `msg 9` are all LIVE since 2026-07-21/23); the hat labels `0x41-0x44` (the shipped RES says `0x41` lookBehind, **`0x42` TORSO CENTER**, `0x43` lookRight, `0x44` lookLeft — fix `content/CONTROLS.MAP:20-22`, `L4PADBINDINGS.cpp:159-160`, `pod-hardware.md`); `context/open-questions.md:172-182` (census stale in both directions — `0x28`/ToggleCooling/ToggleSeekVoltage/EjectAmmo now live, hotbox never listed); `btl4mppr.cpp:1332` calls `0x200000` "ModePreset1" — the surviving T0 header names it **ModeEject** (`game/original/BT_L4/BTL4MODE.HPP:74`, which is authoritative and not yet cited by any context file); `searchlight.cpp:51`/`hud.cpp:282` table-address citations are +4 and mis-paired (real: Searchlight `@0x51117C → @004b838c`, ThermalSight `@0x51120C → @004b860c`); stale `//ButtonPanic=0x38` at `L4CTRL.cpp:112`.
|
||||
`context/pod-hardware.md:121-135` (false AddOrErase premise — also in `mechmppr.cpp:894-919`, `docs/GLASS_COCKPIT.md` §2a); `context/pod-hardware.md:50-61` (`0x28` BalanceCoolant, weapon `msg 0xb`, Myomers `msg 9` are all LIVE since 2026-07-21/23); the hat labels `0x41-0x44` (the shipped RES says `0x41` lookBehind, **`0x42` TORSO CENTER**, `0x43` lookRight, `0x44` lookLeft — fix `content/CONTROLS.MAP:20-22`, `L4PADBINDINGS.cpp:159-160`, `pod-hardware.md`); `context/open-questions.md:172-182` (census stale in both directions — `0x28`/ToggleCooling/ToggleSeekVoltage/EjectAmmo now live, hotbox never listed); `btl4mppr.cpp:1332` calls `0x200000` "ModePreset1" — the surviving T0 header names it **ModeEject** (`game/original/BT_L4/BTL4MODE.HPP:74`, which is authoritative and not yet cited by any context file); `searchlight.cpp:51`/`hud.cpp:282` table-address citations are +4 and mis-paired (real: Searchlight `@0x51117C → @004b838c`, ThermalSight `@0x51120C → @004b860c`) — ✅ **SWEPT 2026-07-25 (#61)** in `searchlight.cpp/.hpp`, `thermalsight.cpp/.hpp`, `hud.cpp:282`, `btplayer.cpp:1957`, `context/decomp-reference.md`, `context/pod-hardware.md`, `docs/GLASS_COCKPIT.md`; this swap had also manufactured a false "1995 latent bug" now retracted in `context/subsystems.md`, `context/rendering.md`, `context/open-questions.md`; stale `//ButtonPanic=0x38` at `L4CTRL.cpp:112`.
|
||||
Reference in New Issue
Block a user