Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)

TASK #6 -- the pod's in-cockpit weapon regrouping, fully reconstructed and
live-verified (hold-configure -> tap fire button -> toggle membership ->
release-commit):
- MechWeapon handlers id 9 ConfigureMappables @004b9550 / id 10 ChooseButton
  @004b95b8 (binary table @0x511860; the old "Myomers fns" mislabel swept)
  + the GetMessageHandlers() accessor chain through Emitter/ProjectileWeapon/
  MissileLauncher/GAUSS/PPC (empty per-class sets swallowed dispatch).
- Mapper vtable truth: +0x38 EnterConfiguration / +0x3C ExitConfiguration /
  +0x40,+0x44 AddOrErase evt/dir.  No "secondary vtable @0050f498", no
  "CreateTemporaryEventMappings" virtual (RP-name drift; swept incl CLASSMAP).
  L4 Enter/Exit rebuilt complete: StartMappableButtonsConfigure (the
  NonMapping 0x10000 <-> Mapping 0x8000 mode flip + the gauge's active-weapon
  latch) + the held-button re-arm + the 4 fire-button temp maps; only the RIO
  mapper implements the toggle (Thrustmaster no-ops = can't regroup).
- MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID
  (ex hostEntity/subsystemId2 mislabel); MechWeapon ctor defaults the
  destination to &fireImpulse (@004b99a8).
- ConfigMapGauge state loop reconstructed (PE-recovered DAT_00518eb4 table;
  buttonGroup GetMapState sampler -- the "needs ModeManager" guard rationale
  was wrong).  Finding [T1]: the shipped binary NEVER enables this gauge (no
  SetColor caller) -- authentically dormant; BT_CONFIGMAP=1 is the dev enable.
- Dev harness: HOLD 'G' opens the session (BT_CONFIG_SLOT picks the weapon);
  BT_CONFIG_TEST scripts a headless verify.  @004afbc4 corrected to its real
  Fail-trap body (the guessed AddOrErase(NULL) body would corrupt groups).
- Bonus [T1]: the binary MechWeapon ATTRIBUTE table has ELEVEN entries
  (PercentDone..WeaponState) -- "TriggerState is the only one" was wrong.

TASK #11 -- the user-reported live-play regressions (both real bugs):
- PHANTOM FIRE: the mech4 bring-up shot block painted an explosion at the
  victim on its OWN 0.3s cadence whenever fire was held -- desynced once the
  authentic recharges landed.  Retired (the real impact visual flows from
  each discharge via the messmgr SubmitExplosion).
- WEAPON BRICKING (the "cuts out" bug): the Loading->Loaded snap window
  (+-0.01 around seekV) assumes the pod's LOCKED 60 fps; one port dt-spike
  jumps it, the byte-verified >1.0 clamp (_DAT_004ba830=0) zeroes readiness,
  and the weapon sticks in Loading at level ~10000 forever (observed live).
  Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick
  -- also fixes the I^2R integral over-heating generators on big steps.
  NEW GOTCHA CLASS recorded: reconstruction-gotchas #12 (frame-pacing trap).
- Duty-cycle measured (max-rate autofire): generators equilibrate in the
  degradation band (recharge stretch ~3.5-4.4x), the FailureHeat breaker
  never trips solo, PPCs sustain ~11.8-damage full-charge fire.  The slow
  kill pacing and recharge gaps are the AUTHENTIC heat economy; the pilot's
  counters (this regrouping UI, generator reassignment, coolant valves) are
  the reconstruction queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 19:39:35 -05:00
co-authored by Claude Fable 5
parent 4e63a7b6c3
commit 092408041c
26 changed files with 748 additions and 209 deletions
+48 -16
View File
@@ -143,13 +143,22 @@ Derivation
"Emitter"
);
Receiver::MessageHandlerSet Emitter::MessageHandlers;
// task #6: inherit MechWeapon's handler set (ids 9/10 ConfigureMappables/
// ChooseButton) -- dispatch resolves through the CONCRETE class's SharedData,
// so an empty own-set here silently swallowed the config messages.
Receiver::MessageHandlerSet&
Emitter::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(
MechWeapon::GetMessageHandlers()); // copy-inherit
return messageHandlers;
}
Simulation::AttributeIndexSet Emitter::AttributeIndex;
Emitter::SharedData
Emitter::DefaultData(
&Emitter::ClassDerivations,
Emitter::MessageHandlers,
Emitter::GetMessageHandlers(),
MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty)
Emitter::StateCount
);
@@ -390,21 +399,44 @@ void
weaponAlarm.SetLevel(4); // TriggerDuringLoad blip
weaponAlarm.SetLevel(3);
}
// E7 RETIRED (task #10): the authentic charge path is live -- the
// PoweredSubsystem ctor attaches the authored generator, the Emitter
// ctor calibrates voltageScale/energyCoefficient, and TrackSeekVoltage
// integrates the charge over the authored RechargeRate seconds. The
// old unconditional snap-to-seek recharged every emitter in ONE frame
// (~0.3s cycle vs the authored 2-6s), multiplying the heat input ~16x
// (the "runaway": the routing itself was verified link-correct).
if (GetVoltageState() == 4) // this+0x278 == Ready
// E7 RETIRED (task #10): the authentic charge path is live (authored
// generator + calibrated voltageScale/EC; TrackSeekVoltage integrates
// the charge over the authored RechargeRate seconds).
//
// POD-FRAME SUB-STEPPING (task #11, THE WEAPON-BRICK FIX): the binary's
// Loading tick assumes the pod's LOCKED 60 fps -- the charge integrates
// toward the generator's 10000V and the Loaded transition only fires
// while rechargeLevel crosses the +-0.01 snap window around seekV
// (level in [7920, 8080]; ~15 pod frames wide). Under the port's
// variable dt, one spike frame (dt >= ~0.25s) jumps the whole window:
// the level overshoots, ComputeOutputVoltage's byte-verified >1.0
// clamp (_DAT_004ba830 = 0.0) zeroes rechargeLevel, and the weapon is
// PERMANENTLY stuck Loading at level ~10000 (observed live:
// "level=9999.68 alarm=3 edge=1"). Fix: run the binary's own
// tick -- TrackSeekVoltage + ComputeOutputVoltage + the Loaded test --
// at the binary's own frame rate. This also keeps the I^2R generator
// feed integral accurate (big steps evaluate seekRate at a stale level
// and OVER-heat the generators). Sub-step count is bounded; leftover
// time under extreme throttling just resumes next frame.
{
TrackSeekVoltage(time_slice); // @004ba838
}
ComputeOutputVoltage(); // (*vtable+0x44)()
if (rechargeLevel == 1.0f) // _DAT_004bac04 -- fully charged (== inherited @0x320)
{
weaponAlarm.SetLevel(2); // -> Loaded
static const Scalar kPodFrame = 1.0f / 60.0f;
Scalar remaining = time_slice;
int guard = 600; // bound: 10s of sim per frame
while (remaining > 0.0f && guard-- > 0)
{
Scalar slice = (remaining < kPodFrame) ? remaining : kPodFrame;
remaining -= slice;
if (GetVoltageState() == 4) // this+0x278 == Ready
{
TrackSeekVoltage(slice); // @004ba838
}
ComputeOutputVoltage(); // (*vtable+0x44)()
if (rechargeLevel == 1.0f) // _DAT_004bac04 -- fully charged
{
weaponAlarm.SetLevel(2); // -> Loaded
break;
}
}
}
break;