diff --git a/context/decomp-reference.md b/context/decomp-reference.md index be36662..e9964e4 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -274,6 +274,20 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a ResetFiringState); Emitter's faithful body existed as the orphaned "AdvanceSeekVoltage" (zero callers) -- both registered via MESSAGE_ENTRY (ids 9/0xb). Verify: BT_SEEKTEST=1 + BT_SEEK_LOG=1 → `[seek] -> gear N` cycling. EjectAmmo remains unwired. + **LATENT ARCADE BUG found + diverged (issue #21, 2026-07-21) [T1 byte-verified + T2 repro]:** + the Emitter charge integrates toward the GENERATOR voltage (@004ba838 `fld [src+0x1dc]`), full + is detected only inside the +-1% snap window around `seekVoltage[idx]` (ComputeOutputVoltage + @004ba738: `|rl-1|<=0.01 -> 1.0`), and OVERCHARGE reads ZERO (`rl>1.01 -> 0`, + `_DAT_004ba830 = 0.0` byte-verified). Toggle the seek gear while a charge is in flight and + the level lands ABOVE the new gear's window -> rechargeLevel pinned 0, the `==1.0` Loaded test + (`_DAT_004bac04`) never fires, charging continues to the generator ceiling where EVERY gear + reads overcharged -> the weapon is PERMANENTLY bricked (arc dark, ready-dot dark, no fire; gen + re-select cannot help). The arcade shipped this (locked 60 fps + rare seek use hid it); the + port's clickable seek button triggers it in seconds. **Deliberate divergence** (emitter.cpp + Loading tick): overcharge (`currentLevel > seekVoltage[idx]`) counts as fully charged -> + Loaded ("full == the gear's seek voltage" is the arcade's own discharge algebra). Repro + + verify: BT_SEEKTEST seek-abuse -- pre-fix deadlocks at pct=0/alarm=3; post-fix 38 rescues, + ends Loaded/pct=1. `[seek]` log prints the per-gear table + rescue events. - **MechRIOMapper** @0x51DD30: RE-REGISTERS the base ids 3..0x13 (Aux1Quad..ZoomOut) with the RIO override handlers @004d22fc..@004d24f8, + {0x19, Keypress→@004d2514} (RIO's OWN keypress, unreconstructed — we use the shared L4 0x17 @004d1bf0) + {0x1a, Hotbox→@004d2574} @0x51DE98. diff --git a/game/reconstructed/emitter.cpp b/game/reconstructed/emitter.cpp index 6988ed9..a34b0be 100644 --- a/game/reconstructed/emitter.cpp +++ b/game/reconstructed/emitter.cpp @@ -510,6 +510,32 @@ void weaponAlarm.SetLevel(2); // -> Loaded break; } + // OVERCHARGE RESCUE (issue #21, 2026-07-21) -- a DELIBERATE + // divergence from the binary, which DEADLOCKS here: change the + // seek gear while a charge is in flight and the level can land + // ABOVE the new gear's snap window (level/seekV > 1.01) -- the + // byte-verified ComputeOutputVoltage clamp then zeroes + // rechargeLevel (_DAT_004ba830 = 0.0) and the ==1.0 Loaded test + // can never fire again while TrackSeekVoltage keeps charging + // toward the generator voltage: the weapon is PERMANENTLY + // bricked (arc dark, dot dark, never fires -- reproduced + // headless, and trivial to trigger from the clickable seek + // button; the pod's locked 60 fps + rare seek use hid it). + // The arcade's own math says "full == the gear's seek voltage" + // (discharge energy computes from seekV[rec]), so an + // overcharged weapon IS fully charged -- treat it as Loaded. + if (currentLevel > seekVoltage[seekVoltageIndex] + && seekVoltage[seekVoltageIndex] > 0.0f) + { + rechargeLevel = 1.0f; // full (the display/damage ratio) + weaponAlarm.SetLevel(2); // -> Loaded + if (getenv("BT_SEEK_LOG")) + DEBUG_STREAM << "[seek] " << GetName() + << " overcharge rescue: level=" << currentLevel + << " > seekV=" << seekVoltage[seekVoltageIndex] + << " -> Loaded" << std::endl; + break; + } } } break; @@ -683,7 +709,16 @@ void ResetFiringState(); // @004ba9a8 } if (getenv("BT_SEEK_LOG")) - DEBUG_STREAM << "[seek] " << GetName() << " -> gear " << next << std::endl; + { + Generator *src = (Generator *)ResolveVoltageSource(); + DEBUG_STREAM << "[seek] " << GetName() << " -> gear " << next + << " seekV[" << next << "]=" << seekVoltage[next] + << " table={" << seekVoltage[0] << "," << seekVoltage[1] << "," + << seekVoltage[2] << "," << seekVoltage[3] << "," << seekVoltage[4] + << "} max=" << maxSeekVoltageIndex << " level=" << currentLevel + << " genV=" << (src ? src->MeasuredVoltage() : -1.0f) + << " genRated=" << (src ? src->RatedVoltageOf() : -1.0f) << std::endl; + } }