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
+23
View File
@@ -151,6 +151,29 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
---
### The CONFIG-MODE session chain (task #6) [T1]
- MechWeapon handler table @0x511860: {9,"ConfigureMappables"→@004b9550}, {10,"ChooseButton"→@004b95b8}
(strings @0x511994/@0x5119A7). PoweredSubsystem table @0x50F4EC: ids 4-8 = SelectGeneratorA-D /
ToggleGeneratorMode → @004b099c/@004b09e4/@004b0a2c/@004b0a74/@004b0abc.
- MechWeapon ATTRIBUTE table @0x511890: ELEVEN entries — 0x12 PercentDone@0x320, 0x13 TriggerState@0x31C,
0x14 DistanceToTarget@0x324, 0x15 TargetWithinRange@0x34C, 0x16 WeaponRange@0x328, 0x17 PipPosition,
0x18 PipColor, 0x19 PipExtendedRange, 0x1A EstimatedReadyTime@0x330, 0x1B RearFiring@0x334,
0x1C WeaponState@0x350.
- Mapper vtables: base @0050F45C (+0x38 Enter Fail@004b0280 / +0x3C Exit Fail@004b029c / +0x40
AddOrErase-evt Fail@004b02b8 / +0x44 AddOrErase-dir Fail@004b02d4 / +0x48,+0x4C Notify @004b048c/@004b0494);
L4 @0051E440 (+0x38=@004d1840, +0x3C=@004d18dc, +0x40/+0x44 = RET no-ops @004d195c/@004d1964,
+0x48/+0x4C=@004d1acc/@004d1ae4); Thrustmaster @0051E3F0 (inherits the no-ops); RIO @0051E3A0
(+0x40=@004d25e8, +0x44=@004d262c). @004afbc4 = a pure Fail thunk ("Unhandled button mapping!", 0x7a).
- ControlsManager vtbl: +0x20 StartMappableButtonsConfigure / +0x24 Stop / +0x28 AddOrEraseMappableButton
(T0 CONTROLS.cpp:291-351). Group vtbl: +0x0C Remove, +0x10 Update, +0x14 Add(evt), +0x18 Add(dir),
+0x1C AddOrErase(evt), +0x20 AddOrErase(dir), +0x24 GetMapState (unmapped=0/others=1/mine=2).
- Mode masks (T0 BTL4MODE.HPP): ModeMapping=0x8000 (bit 15) / ModeNonMapping=0x10000 (bit 16), mutually
exclusive. MechSubsystem +0xE8/+0xEC = controlDestination/controlMessageID (ctor pass-throughs;
MechWeapon defaults destination=&fireImpulse@0x31C per @004b99a8). Subsystem+0x110: 0/-1 config-session
flag written by handler 9 (reader unknown).
- ConfigMapGauge @004c6d80/@004c6f1c: table DAT_00518eb4 = {y,btn} {0x0d,0x45}{0x25,0x46}{0x3d,0x40}
{0x55,0x47}, blit x=0xc; SetColor @004c6ee0 has NO caller (gauge dormant in the shipped build).
## 6. Env-var gates (runtime; default OFF unless noted)
The authentic stack is DEFAULT-ON (`BTEnvOn`, mechrecon.hpp): `BT_GAIT_CUTOVER`, `BT_GAIT_SM`,
+10
View File
@@ -68,6 +68,16 @@ over one shared `SVGA16` pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they
intact). The `overlay` port (SectorDisplay lives there) shares the `sec` physical surface via a
different bit-plane (0x00C0). [T2]
## ConfigMapGauge (the weapon panel's regroup lamp column) — authentically DORMANT
The per-weapon btjoy.pcc joystick image + 4 cm_* state lamps (off/other/only/both) showing,
for each mappable fire button (Pinky/ThumbLow/Trigger/ThumbHigh), whether THIS panel's weapon
is bound to it. **The shipped binary never enables it [T1, task #6]:** no caller of SetColor
@004c6ee0 exists, so color==0 and Execute early-outs. The state loop is reconstructed
(btl4gau2.cpp; table DAT_00518eb4 PE-recovered; sampler = LBE4ControlsManager::
buttonGroup[btn].GetMapState — NOT the ModeManager, the old guard note was wrong) behind the
PORT dev enable `BT_CONFIGMAP=1`. The regroup MECHANISM itself (ConfigureMappables/
ChooseButton, task #6) is live regardless of the gauge.
## Authentically-static (do NOT "fix")
Degradation/Failure temps are fixed markers; AmbientTemperature 300; CoolantMassLeakRate 0 on a
pristine mech (damage-gated); cmArmor/cmCrit all-green (undamaged solo player — BT is PvP-only);
+62 -6
View File
@@ -133,12 +133,50 @@ authentic path scoped.
ThumbHigh; BT_AUTOFIRE pulses the Trigger); the gBT*Trigger bypasses + pulse hack are retired.
VERIFIED 2-node at the BINDING level (⚠ honest correction, task #8: those kills still flowed
through the retired mech4 bring-up block -- the emitter DISCHARGE chain was latched shut by
the NaN edge bug below until 2026-07-11; the full weapon-fire chain is now verified solo). **Remaining (the config-mode session):** handlers
id 9/10 (@4b9550/@4b95b8 — corrected: they were mislabeled as Myomers fns), the L4 mapper
Enter/ExitConfiguration completion (StartMappableButtonsConfigure + mode 0x8000), the
ConfigMapGauge unguard — the pod's in-cockpit REGROUPING UI (pilot heat-management choice);
mapper vtable corrections: +0x38 Enter / +0x3c Exit / +0x44 RIO AddOrErase (Thrustmaster
no-ops it).
the NaN edge bug below until 2026-07-11; the full weapon-fire chain is now verified solo).
- **✅ THE CONFIG-MODE SESSION — RECONSTRUCTED (task #6, 2026-07-11) [T1 anchors].** The
pod's in-cockpit weapon-REGROUPING UI, fully decoded from the binary + T0 engine:
* **The flow**: the pilot HOLDS a weapon's configure button (a per-weapon type-6
EventMapping, id 9, on the MFD-quadrant aux panels; e.g. BTL4.RES @0xAA3DC) →
MechWeapon handler id 9 "ConfigureMappables" @004b9550 (table @0x511860; the old
"Myomers fns" label swept) → mapper(slot 0)->EnterConfiguration @004d1840 (vtbl+0x38):
StartMappableButtonsConfigure (T0 CONTROLS.cpp:291 -- latches the active weapon for the
gauge, flips the GLOBAL mode NonMapping 0x10000 → Mapping 0x8000, so every normal fire
mapping goes dormant), re-arms the held button (+0x8000, id 9), temp-maps the four fire
buttons 0x40/0x45/0x46/0x47 → id 10. Tapping a fire button → handler id 10
"ChooseButton" @004b95b8 → RIO AddOrErase(direct) @004d262c: buttonGroup[v-1].AddOrErase
(0x10000, &fireImpulse) — a TOGGLE; the permanent 0x10000 instances ARE the commit.
Release → ExitConfiguration @004d18dc: StopMappableButtonsConfigure + strip the 0x8000
layer. Only the RIO (pod) mapper implements AddOrErase; L4/Thrustmaster carry RET
no-ops @004d195c/@004d1964 (a joystick cockpit cannot regroup).
* **Vtable truth (swept)**: base @0050F45C +0x38 Enter(Fail @004b0280) / +0x3C Exit(Fail
@004b029c) / +0x40 AddOrErase-event(Fail @004b02b8) / +0x44 AddOrErase-direct(Fail
@004b02d4) / +0x48,+0x4C Notify pair. There is NO "secondary vtable @0050f498" and NO
"CreateTemporaryEventMappings" virtual (RP-name drift). @004afbc4 (the mapper's shared
aux/zoom handler) is a pure Fail thunk ("Unhandled button mapping!", line 0x7a), not an
AddOrErase router.
* **Port landed**: MechWeapon handlers 9/10 + the GetMessageHandlers() accessor chain
(MechWeapon → Emitter/ProjectileWeapon → MissileLauncher, + GAUSS/PPC.CPP; dispatch
resolves through the CONCRETE class's SharedData — the empty per-class sets silently
swallowed the messages); the 6-arg Enter/1-arg Exit reshape; the L4 Enter/Exit full
bodies; MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID (the
old hostEntity/subsystemId2 labels were wrong; MechWeapon ctor defaults the destination
to &fireImpulse per @004b99a8); dev harness = HOLD 'G' (BT_CONFIG_SLOT selects the
weapon; BT_CONFIG_TEST scripts a headless session).
* **ConfigMapGauge finding [T1]**: the shipped binary NEVER enables it — no caller of
SetColor @004c6ee0 exists (the only child accesses are BecameActive/SetEnable forwards),
so color stays 0 and Execute early-outs: the gauge is authentically DORMANT in BTL4OPT.
The state loop is now reconstructed anyway (@004c6f1c: 4 {y,button} pairs from
DAT_00518eb4 = {13,Pinky}{37,ThumbLow}{61,Trigger}{85,ThumbHigh}, x=0xc;
buttonGroup[btn].GetMapState(controlDestination, subsys, controlMessageID, 0x10000) →
cm_off/cm_other/cm_only/cm_both), behind the PORT dev enable BT_CONFIGMAP=1.
* **Bonus [T1]**: the binary MechWeapon ATTRIBUTE table @0x511890 has ELEVEN entries
(0x12 PercentDone .. 0x1C WeaponState) — future gauge-wave feeds; the "TriggerState is
the only MechWeapon attribute" claim was wrong (fixed in mechweap.hpp).
* Tails: weapon+0x110 (write 0/-1 on session open/close; port name vitalSubsystemIndex)
has NO confirmed reader yet; @004d1acc (+0x48 L4 Notify override) is unexported —
re-export from Ghidra; the PoweredSubsystem generator-select handlers (ids 4-8,
@004b099c..@004b0abc) are decoded but not yet registered in the port.
- **Myomers authentic coupling** — the structural un-stub is INERT (mover feed + heat-gen no-op).
Real coupling needs the advanced-damage gate (`OwnerAdvancedDamage`/`FUN_004ad7d4` → the owning
**BTPlayer** `mech+0x190`+0x260, NOT 0xBD3 — see the mech+0x190 item above) + `MoverAttach` routing
@@ -215,6 +253,24 @@ authentic path scoped.
handlers (@004b099c..@004b0abc: assign weapon to Generator 1-4 + auto/manual toggle via
modeAlarm@0x2CC -- wired to buttons, not yet reachable).
- **Task #11 -- the live-play regressions (2026-07-11) [T2].** User-reported after the
electrical model landed. THREE distinct causes, all resolved:
(1) *Phantom fire on the enemy*: the mech4 bring-up shot block painted an explosion at the
victim on its OWN 0.3s cadence whenever fire was held -- desynced once real recharges landed.
RETIRED (the authentic impact visual flows from each real discharge via the messmgr's
SubmitExplosion). (2) *Weapons bricking one by one* ("fire ~1s then cuts out"): the
FRAME-PACING TRAP -- see [[reconstruction-gotchas]] §12: the Loading->Loaded snap window
(+-0.01 around seekV) assumes the pod's locked 60 fps; a dt spike jumps it, the overshoot
clamp zeroes rechargeLevel, and the weapon sticks in Loading at level ~10000 forever.
Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick (also fixes the
I^2R integral overfeeding generator heat on big steps). (3) *Slow kills / short fire
windows*: AUTHENTIC -- beams last the authored DischargeTime, recharges take the authored
2-5s stretched by hot generators (ChargeTimeScale), and max-rate autofire equilibrates
generators in the degradation band (1100-1500 K; the FailureHeat breaker never trips in
solo max-abuse -- duty-cycle measured). The pilot's authentic counters are the systems
under reconstruction: config-mode regrouping (task #6), generator reassignment (ids 4-8),
coolant valves.
## Locomotion / combat polish (non-gating)
- Authentic per-mech TURN-RATE constant (currently a bring-up constant rate).
- Body-callback gimp handlers (states 16-19, targets 0x70b2/0x7161 undecoded → fall back to stand);
+18 -1
View File
@@ -176,7 +176,24 @@ published attribute table MUST be a **dense prefix** from the parent's `NextAttr
AVs. Fill gaps with a shared read-only pad member. Same for a class's `<Name>AttributeID` enum.
[T2]
## 12. Verification gotchas (don't fool yourself)
## 12. Frame-pacing trap — the binary assumes a LOCKED 60 fps (task #11)
The 1995 pod ran frame-locked; reconstructed per-frame logic can carry HIDDEN frame-rate
assumptions that variable dt violates. Archetype: the Emitter Loading tick — the charge
integrates toward the generator's 10000V and the Loading→Loaded transition only fires while
`rechargeLevel` crosses the ±0.01 snap window around seekV (~0.25s of travel ≈ 15 pod frames —
never missed at 60 fps). One port dt-spike (0.24s observed live) jumps the whole window; the
byte-verified >1.0 overshoot clamp (`_DAT_004ba830` = 0.0) then zeroes readiness and the weapon
is PERMANENTLY bricked in Loading at level ~10000 (user-visible: "weapons cut out one by one").
Big steps also corrupt integrals evaluated at stale state (the I²R generator feed overheated).
**Fix pattern: pod-frame sub-stepping** — run the binary's own tick verbatim inside a
`while (remaining) { slice = min(remaining, 1/60); … }` loop (bounded; leftover time resumes
next frame). This reproduces the pod's exact trajectory instead of redesigning the logic.
Suspect ANY reconstructed per-frame code with narrow equality/window tests or `x == 1.0f`
state transitions: charge/seek loops, snap comparisons, timers compared with `==`.
## 13. Verification gotchas (don't fool yourself)
- **Lazy gauge build:** `GaugeRenderer::BuildConfigurationFile` runs LAZILY. A too-early process
kill shows `[gskip]=0` / "not built" even though the widget is fine — **wait for the gauge
+15 -5
View File
@@ -84,9 +84,15 @@ except `SeekVoltageGraph`)
(VertNormalSlider-ish) + connection `FUN_004c3288` (vt `0x518e90`) at `+0x2d`. 2nd ctor `0x4c72ac`
(uses the engine `FUN_00474855` scalar-attr connection). dtor `0x4c733c`.
- **`ConfigMapGauge`** @`0x4c6d80` (vt `0x51a1b8`) : GraphicGauge — 5 pcc names (btjoy/cm_off/cm_other/
cm_only/cm_both) interned into the warehouse; Execute `0x4c6f1c` reads the control-mapper state from
`app->GetModeManager()` (`DAT_004efc94+0x3c`, group `0x1c0 + i*0x20`) and blits the matching frame.
SetColor `0x4c6ee0` (writes `this+0x94`). dtor `0x4c6e54`.
cm_only/cm_both) interned into the warehouse; Execute `0x4c6f1c` reads the mapping state from
`app->GetControlsManager()` (`DAT_004efc94+0x3c` == the LBE4ControlsManager — NOT the ModeManager,
which is `+0x50`): `buttonGroup[btn]` (array base `+0x1c0`, stride `0x20`) `->GetMapState(*(subsys+0xE8),
subsys, *(subsys+0xEC), 0x10000)` [T0 CONTROLS.h `ControlsUpdateManager<T>::GetMapState`, returns
0 unmapped / 1 mappedByOthers / 2 mappedByMe / 3 both == the cm_off/other/only/both frame index].
The 4-slot table `DAT_00518eb4..d0` is RECOVERED from the image ({y,button} pairs, blit x=0xc):
{0x0d,0x45 Pinky} {0x25,0x46 ThumbLow} {0x3d,0x40 Trigger} {0x55,0x47 ThumbHigh} [T1 PE bytes].
SetColor `0x4c6ee0` (writes `this+0x94`; Execute is inert while 0 — the activation caller is
unrecovered in the export). dtor `0x4c6e54`.
Engine primitives it calls (ALREADY in `munga_engine.lib`, callable — do NOT reconstruct): `TwoState`
(`FUN_004739d8`, vt `0x4fb740`), `NumericDisplayScalar` (`0x470888`, vt `0x4fbb40`),
@@ -142,8 +148,12 @@ the resource parse, verifying combat/heat un-regressed. **This is the final, sig
are affected. The authentic fix is the heat-leaf-branch re-base (the same §10 deferred item for the Sensor
RadarPercent gauge) — a large, regression-risky change to the working combat/heat subsystem code — or a set
of per-field bridges like the aux-screen one.
- **`ConfigMapGauge`'s control-mapper state overlay** — guarded off (raw App ModeManager offsets, `DAT_00518eb8`
unrecovered); the base joystick bitmap draws.
- **`ConfigMapGauge`'s control-mapper state overlay** — ✅ RECONSTRUCTED (task #6): the state loop is
live in btl4gau2.cpp (recovered `DAT_00518eb4` table; `buttonGroup[btn].GetMapState(...,0x10000)` via
the complete-type bridge `BTSubsystemControlFeed`; `MechSubsystem` +0xE8/+0xEC fixed to
controlDestination/controlMessageID, MechWeapon ctor defaults the destination to `&fireImpulse`).
The gauge stays authentically DORMANT (the shipped binary never calls SetColor `0x4c6ee0` — no caller
exists); `BT_CONFIGMAP=1` is the port's dev enable.
- The **separate** dev-gauge window (960×384) didn't appear in the headless test run (the panels render in
`BT_DEV_GAUGES_DOCK=1` docked mode); the separate-window path is existing dev-gauge infra, unrelated to
this reconstruction.
+2 -1
View File
@@ -26,7 +26,8 @@
GaussRifle::SharedData
GaussRifle::DefaultData(
&GaussRifle::ClassDerivations,
GaussRifle::MessageHandlers,
Emitter::GetMessageHandlers(), // task #6: the port's accessor idiom -- inherits
// MechWeapon's ConfigureMappables/ChooseButton
MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty)
GaussRifle::StateCount
);
+2 -1
View File
@@ -32,7 +32,8 @@
PPC::SharedData
PPC::DefaultData(
&PPC::ClassDerivations,
PPC::MessageHandlers,
Emitter::GetMessageHandlers(), // task #6: the port's accessor idiom -- inherits
// MechWeapon's ConfigureMappables/ChooseButton
MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty)
PPC::StateCount
);
+3 -3
View File
@@ -147,10 +147,10 @@ Subsystem (MUNGA base — have source: RP/MUNGA/SUBSYSTM.HPP)
│ throttleForward@0x1ac/throttleReverse@0x1b0/leftPedal@0x1b4/rightPedal@0x1b8 (cleared by derived ctors).
│ InterpretControls@4d196c (throttle detent snap 1.0±0.05, BuildPilotArray, review-mode bit 0x200000,
│ target-range ramp range=250*2^exp, exp clamp[0,5]; then chains MechControlsMapper::InterpretControls @4afd10).
│ ZoomTargetRangeIn@4d1b64 / ZoomTargetRangeOut@4d1b9c (±1.0). SetControlMode@4d1ae4 (mode bits18-20,
│ ZoomTargetRangeIn@4d1b64 / ZoomTargetRangeOut@4d1b9c (±1.0). NotifyOfDisplayModeChange-override@4d1ae4 (vtbl+0x4C; ex-"SetControlMode", (mode bits18-20,
│ table @0051dbe4={0x40000,0x80000,0x100000}). SetPresetMode@4d1b24 (group*5+item -> {clear,set} mask table @0051dbf0[15]).
CreateTemporaryEventMappings@4d1840 / RemoveTemporaryEventMappings@4d18dc (buttons 0x40..0x47, hat-skip 0x41->0x45,
│ mode 0x8000=ConfigReady). NotifyOfControl/ConfigModeChange = no-ops @4d195c/@4d1964. KeypressMessageHandler@4d1bf0
EnterConfiguration@4d1840 (+0x38) / ExitConfiguration@4d18dc (+0x3C) (task #6 relabel (buttons 0x40..0x47, hat-skip 0x41->0x45,
│ mode 0x8000=ConfigReady). AddOrErase evt/dir RET no-ops @4d195c/@4d1964 (+0x40/+0x44; TM can't regroup). KeypressMessageHandler@4d1bf0
│ (shared dispatcher: zoom/preset/pilot-cycle/look keys + fake-button dev keys; default -> @4d141c).
│ file-scope helpers: SendFakeButtonEvent@4d13f0, SendFakeButtonEventPair@4d133c, DispatchDeveloperFakeEvent@4d141c (GeneratorA-D/Condenser1/LRM15/AmmoBinLRM15/Avionics/Hud test keys).
│ ├─ MechThrustmasterMapper vtable 0051e3f0 ctor @4d21d0 dtor @4d22b4 TestInstance @4d22e0 ClassDerivations @0051dc78
+68 -12
View File
@@ -66,6 +66,10 @@
#define BT_DEBUGSTREAM_DEFINED
#include <bt.hpp>
// task #6 (ConfigMapGauge state loop): the LBE4 buttonGroup sampler.
#include <btl4app.hpp>
#include <btl4mode.hpp>
#include <l4ctrl.hpp>
#pragma hdrstop
#if !defined(BTL4GAU2_HPP)
@@ -385,6 +389,13 @@ ConfigMapGauge::ConfigMapGauge(
localView.SetOrigin(x, y); // vtbl+0x10 (this+0x48)
color = 0; // @0x94 this[0x25]
// task #6 finding [T1]: NO caller of SetColor (@004c6ee0) exists in the
// shipped binary -- the only accesses to the WeaponCluster's configMap
// child (@0xD4) are BecameActive forwards and SetEnable -- so the gauge is
// authentically DORMANT in BTL4OPT (color stays 0; Execute early-outs).
// BT_CONFIGMAP=1 is the PORT's dev enable to watch the state loop live.
if (getenv("BT_CONFIGMAP"))
color = 0xff;
subsystem = subsystem_in; // @0x90 this[0x24]
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
@@ -432,28 +443,35 @@ void
//
// @004c6f1c -- Execute. When enabled (color != 0): on the dirty flag, blit the
// base joystick bitmap; then for each of the 4 config-map slots read the control-
// mapper state and, if changed, stretch-blit the matching cm_* pixmap.
// base joystick bitmap; then for each of the 4 config-map slots read the fire
// button's map state and, if changed, blit the matching cm_* pixmap.
//
// BRING-UP (marked): the per-slot control-mapper state comes from the App's
// ModeManager sampled through a .data port/colour table (DAT_00518eb8) at raw
// offsets (app+0x3c -> +0x1c0 + slot*0x20, sampler vtbl+0x24). That is the same
// raw-App-offset read class that AVs before the ModeManager layout is
// reconstructed (see the FillPilotArray fix, §10), and DAT_00518eb8 was not
// recovered from the optimised image -- so the mapper-state loop is guarded off
// and only the base-bitmap blit runs. The full loop lands with the ModeManager
// reconstruction.
// task #6 UNGUARD: the old deferral rationale was wrong on both counts --
// (a) the sampler is NOT the App ModeManager; it is
// LBE4ControlsManager::buttonGroup[btn].GetMapState (group vtbl+0x24, all
// T0 engine, callable by name -- the same pattern btl4mppr.cpp compiles),
// (b) DAT_00518eb4 IS recoverable from the PE image: 4 {y, button} pairs
// {0x0d,0x45 Pinky} {0x25,0x46 ThumbLow} {0x3d,0x40 Trigger}
// {0x55,0x47 ThumbHigh}, blit x = 0xc.
// GetMapState(direct_destination, event_receiver, message_id, 0x10000) returns
// unmapped=0 / mappedByOthers=1 / mappedByMe=2 / both=3 == the cm_off/cm_other/
// cm_only/cm_both frame index -- the per-weapon lamp column over the btjoy
// joystick image. The feed (subsys+0xE8/+0xEC = &fireImpulse/0 for weapons)
// comes through the complete-type bridge BTSubsystemControlFeed (mechsub.cpp).
//
extern void BTSubsystemControlFeed(void *subsystem_in, void **destination_out,
int *message_id_out, void **receiver_out);
void
ConfigMapGauge::Execute()
{
if (color == 0)
return;
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
if (dirty)
{
dirty = False;
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
BitMap *base = warehouse->bitMapBin.Get(joystickImage); // FUN_00442aec
if (base != NULL)
{
@@ -462,7 +480,45 @@ void
}
warehouse->bitMapBin.Release(joystickImage);
}
// (control-mapper state loop deferred -- see note above.)
// The 4-slot state loop (DAT_00518eb4, PE-recovered {y, button} pairs).
static const struct { int y; int button; } kSlot[4] =
{
{ 0x0d, 0x45 }, // ButtonJoystickPinky
{ 0x25, 0x46 }, // ButtonJoystickThumbLow
{ 0x3d, 0x40 }, // ButtonJoystickTrigger
{ 0x55, 0x47 }, // ButtonJoystickThumbHigh
};
LBE4ControlsManager *controls =
(LBE4ControlsManager *)application->GetControlsManager(); // DAT_004efc94+0x3c
if (controls == NULL || subsystem == NULL)
return;
void *destination = 0;
int messageID = 0;
void *receiver = 0;
BTSubsystemControlFeed(subsystem, &destination, &messageID, &receiver);
for (int slot = 0; slot < 4; slot++)
{
int state = controls->buttonGroup[kSlot[slot].button].GetMapState( // group vtbl+0x24
(ControlsButton *)destination, // *(subsys+0xE8)
(Receiver *)receiver, // the subsystem itself
messageID, // *(subsys+0xEC)
0x10000 /*ModeNonMapping -- the persistent groups*/);
if (state != previousState[slot])
{
previousState[slot] = state;
PixelMap8 *pix =
(PixelMap8 *)warehouse->pixelMap8Bin.Get(stateImage[state]); // FUN_00442d2b
if (pix != NULL)
{
localView.MoveToAbsolute(0xc, kSlot[slot].y); // vtbl+0x24
localView.DrawPixelMap8(0 /*opaque*/, 0 /*rotation*/, pix); // vtbl+0x5c
}
warehouse->pixelMap8Bin.Release(stateImage[state]); // FUN_00442e51
}
}
}
+75 -20
View File
@@ -497,25 +497,45 @@ L4MechControlsMapper::MessageHandlerSet&
//
//#############################################################################
// CreateTemporaryEventMappings @004d1840
// EnterConfiguration @004d1840
//#############################################################################
//
// During configuration, route every mappable button (the 0x40..0x47 block,
// skipping the joystick hat 0x41..0x44) at the configuration receiver so the
// pilot can press a button to bind it. Mode mask 0x8000 == ModeConfigReady.
// THE CONFIG-MODE SESSION OPEN (task #6; the old reconstruction named this
// "CreateTemporaryEventMappings" and carried only the fire-button loop --
// missing the mode flip, so config mode never actually ENGAGED). Called by
// MechWeapon handler id 9 (ConfigureMappables, @004b9550) when the pilot
// presses-and-HOLDS a weapon's configure button:
// 1. StartMappableButtonsConfigure (engine T0, ControlsManager vtbl+0x20):
// latches the active weapon (destination/receiver) for the ConfigMap
// gauge and flips the GLOBAL mode NonMapping(0x10000) -> Mapping(0x8000)
// -- every permanent fire mapping goes dormant for the session.
// 2. Re-arm the HELD configure button under 0x8000 so its RELEASE still
// reaches the weapon (-> ExitConfiguration).
// 3. Temp-map the four mappable fire buttons (0x40 Trigger, 0x45 Pinky,
// 0x46 ThumbLow, 0x47 ThumbHigh -- the 0x41..0x44 hat is skipped) to
// send ChooseButton (id 10) to the configuring weapon.
//
void
L4MechControlsMapper::CreateTemporaryEventMappings(
L4MechControlsMapper::EnterConfiguration(
int held_element,
ControlsButton *destination,
Receiver *receiver,
Receiver::MessageID config_message_id
Receiver::MessageID choose_message_id,
Receiver::MessageID configure_message_id,
Receiver::MessageID active_message_id
)
{
LBE4ControlsManager *controls =
(LBE4ControlsManager*)application->GetControlsManager(); // DAT_004efc94+0x3c
// Route every mappable button (0x40..0x47, skipping the joystick hat
// 0x41..0x44) at the configuration receiver; mode 0x8000 == ModeConfigReady.
// (Loop bounds mirror the RP twin L4VTVControlsMapper.)
controls->StartMappableButtonsConfigure( // vtbl+0x20 (CONTROLS.cpp:291)
destination, receiver, receiver, active_message_id,
/*remove*/ 0x10000 /*ModeNonMapping*/,
/*add*/ 0x8000 /*ModeMapping*/);
controls->buttonGroup[held_element].Add( // keep the held button live
0x8000, receiver, configure_message_id, receiver);
for (int i = LBE4ControlsManager::FirstMappableButton;
i <= LBE4ControlsManager::LastMappableButton; ++i)
{
@@ -524,22 +544,33 @@ L4MechControlsMapper::MessageHandlerSet&
i = LBE4ControlsManager::ButtonJoystickPinky; // 0x45
}
controls->buttonGroup[i].Add(
0x8000, receiver, config_message_id, receiver);
0x8000, receiver, choose_message_id, receiver);
}
Check_Fpu();
}
//
//#############################################################################
// RemoveTemporaryEventMappings @004d18dc
// ExitConfiguration @004d18dc
//#############################################################################
//
// THE SESSION CLOSE (the configure button's RELEASE): restore the mode masks
// (Mapping -> NonMapping; the engine also clears the gauge's active* latch),
// then strip the 0x8000 temp layer off the held button + the four mappables.
// The regrouping itself needs no commit -- the RIO AddOrErase toggled the
// permanent 0x10000 mappings directly.
//
void
L4MechControlsMapper::RemoveTemporaryEventMappings()
L4MechControlsMapper::ExitConfiguration(int held_element)
{
LBE4ControlsManager *controls =
(LBE4ControlsManager*)application->GetControlsManager();
controls->StopMappableButtonsConfigure( // vtbl+0x24 (CONTROLS.cpp:319)
/*remove*/ 0x8000, /*add*/ 0x10000);
controls->buttonGroup[held_element].Remove(0x8000);
for (int i = LBE4ControlsManager::FirstMappableButton;
i <= LBE4ControlsManager::LastMappableButton; ++i)
{
@@ -554,20 +585,31 @@ L4MechControlsMapper::MessageHandlerSet&
//
//#############################################################################
// NotifyOfControlModeChange / NotifyOfConfigurationModeChange
// AddOrErase (event / direct) -- the L4/Thrustmaster no-ops
// @004d195c / @004d1964
//#############################################################################
//
// No-ops at the L4 layer (the gauges are driven directly off the mode masks),
// exactly like RP's L4VTVControlsMapper::NotifyOf* pair.
// task #6 CORRECTION: these two RET stubs occupy the +0x40/+0x44 AddOrErase
// vtable slots (section_dump @0051e440/@0051e3f0) -- the old reconstruction
// mislabeled them NotifyOfControlModeChange/NotifyOfConfigurationModeChange.
// A joystick (Thrustmaster) cockpit inherits them: pressing a fire button
// during a config session does NOTHING there. Only the pod RIO variant
// (@004d25e8/@004d262c) implements the group toggle.
//
void
L4MechControlsMapper::NotifyOfControlModeChange(int /*new_mode*/)
L4MechControlsMapper::AddOrErase(
unsigned int /*button_ID*/,
Receiver * /*receiver*/,
Receiver::MessageID /*message_ID*/
)
{
}
void
L4MechControlsMapper::NotifyOfConfigurationModeChange(Logical /*new_state*/)
L4MechControlsMapper::AddOrErase(
unsigned int /*button_ID*/,
ControlsButton * /*destination*/
)
{
}
@@ -969,9 +1011,13 @@ RIO_AUX_HANDLER(Aux3Eng4, 2, 4) // @004d24bc
// AddOrErase (event / direct) @004d25e8 / @004d262c
//#############################################################################
//
// During configuration these bind the just-pressed button (1-based ID) either
// to a receiver+message or to a direct ControlsButton sink, under the base
// (ModeBasic, 0x10000) mode mask. Mirrors RP VTVRIOMapper::AddOrErase.
// THE REGROUP EDIT (task #6): during a config session these TOGGLE the
// just-pressed fire button's (1-based ID) permanent mapping under the
// ModeNonMapping (0x10000) mask -- the engine ControlsUpdateManager AddOrErase
// (CONTROLS.h:431-597) deletes a matching instance or creates one, so the
// weapon JOINS the button's fire group if absent and LEAVES it if present.
// The toggled 0x10000 instances ARE the commit; there is no save step.
// Mirrors RP VTVRIOMapper::AddOrErase.
//
void
MechRIOMapper::AddOrErase(
@@ -997,6 +1043,15 @@ RIO_AUX_HANDLER(Aux3Eng4, 2, 4) // @004d24bc
(LBE4ControlsManager*)application->GetControlsManager();
controls->buttonGroup[button_ID - 1]
.AddOrErase(0x10000, destination, this); // vtbl+0x20
if (getenv("BT_FIRE_LOG"))
{
int state = controls->buttonGroup[button_ID - 1].GetMapState(
destination, 0, 0, 0x10000);
DEBUG_STREAM << "[rio-map] toggle btn=0x" << std::hex << (button_ID - 1)
<< std::dec << " dest=" << (void *)destination
<< " -> " << (((state & 2) != 0) ? "JOINED" : "LEFT")
<< " (state=" << state << ")" << std::endl;
}
Check_Fpu();
}
+33 -14
View File
@@ -112,20 +112,39 @@
void
SetPresetMode(int group, int item); // @004d1b24 (preset mode-mask)
void
NotifyOfControlModeChange(int new_mode), // @004d195c (no-op)
NotifyOfConfigurationModeChange(Logical new_state); // @004d1964 (no-op)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Configuration support (overrides MechControlsMapper traps)
// Configuration support (overrides the MechControlsMapper traps; task #6
// slot truth: @004d1840/+0x38 Enter, @004d18dc/+0x3C Exit, and the two
// RET-stub AddOrErase no-ops @004d195c/+0x40 / @004d1964/+0x44 that the
// old reconstruction mislabeled "NotifyOfControlModeChange /
// NotifyOfConfigurationModeChange". Only MechRIOMapper overrides the
// AddOrErase pair with real bodies -- a Thrustmaster cockpit cannot
// regroup. (The REAL NotifyOf* L4 overrides are @004d1acc/@004d1ae4,
// vtable +0x48/+0x4C -- out of scope here.)
//
public:
void
CreateTemporaryEventMappings( // @004d1840
virtual void
EnterConfiguration( // @004d1840 (+0x38)
int held_element,
ControlsButton *destination,
Receiver *receiver,
Receiver::MessageID config_message_id
),
RemoveTemporaryEventMappings(); // @004d18dc
Receiver::MessageID choose_message_id,
Receiver::MessageID configure_message_id,
Receiver::MessageID active_message_id
);
virtual void
ExitConfiguration(int held_element); // @004d18dc (+0x3C)
virtual void
AddOrErase( // @004d195c (+0x40, ret-stub no-op)
unsigned int button_ID,
Receiver *receiver,
Receiver::MessageID message_ID
);
virtual void
AddOrErase( // @004d1964 (+0x44, ret-stub no-op)
unsigned int button_ID,
ControlsButton *destination
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction Support
@@ -285,14 +304,14 @@
activePerformance = (Simulation::Performance)performance;
}
void
AddOrErase( // @004d25e8
virtual void
AddOrErase( // @004d25e8 (+0x40 override)
unsigned int button_ID,
Receiver *target,
Receiver::MessageID message_ID
);
void
AddOrErase( // @004d262c
virtual void
AddOrErase( // @004d262c (+0x44 override)
unsigned int button_ID,
ControlsButton *destination
);
+43 -11
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).
// 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.
{
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(time_slice); // @004ba838
TrackSeekVoltage(slice); // @004ba838
}
ComputeOutputVoltage(); // (*vtable+0x44)()
if (rechargeLevel == 1.0f) // _DAT_004bac04 -- fully charged (== inherited @0x320)
if (rechargeLevel == 1.0f) // _DAT_004bac04 -- fully charged
{
weaponAlarm.SetLevel(2); // -> Loaded
break;
}
}
}
break;
+1 -1
View File
@@ -88,7 +88,7 @@ class NotationFile;
//
public:
static Derivation ClassDerivations;
static Receiver::MessageHandlerSet MessageHandlers;
static Receiver::MessageHandlerSet& GetMessageHandlers(); // inherits MechWeapon's (task #6)
static AttributeIndexSet AttributeIndex;
static SharedData DefaultData;
+116 -71
View File
@@ -644,6 +644,7 @@ int gBTMissileTrigger = 0; // channel 3: missiles (key 3 / CTRL)
static int gBTLaserKey = 0; // raw key states (set by the keyboard poll)
static int gBTPPCKey = 0;
static int gBTMissileKey = 0;
static int gBTConfigKey = 0; // task #6: HOLD 'G' = the weapon-configure button
// Damage: each shot dispatches a REAL Entity::TakeDamageMessage to the target. Now
// that the damage zones are constructed (mech.cpp Pass-3 zone build), the engine base
@@ -1524,6 +1525,10 @@ void
gBTLaserKey = focused && ((pAsync('1') | pAsync(0x20 /*VK_SPACE*/)) & dn) ? 1 : 0;
gBTPPCKey = focused && (pAsync('2') & dn) ? 1 : 0;
gBTMissileKey = focused && ((pAsync('3') | pAsync(0x11 /*VK_CONTROL*/)) & dn) ? 1 : 0;
// task #6: HOLD 'G' opens the config session on the selected
// weapon (BT_CONFIG_SLOT, default: the first weapon); while
// held, the fire keys TOGGLE its group membership.
gBTConfigKey = focused && (pAsync('G') & dn) ? 1 : 0;
// gBTDrive.fire = "any weapon trigger down" (feeds the bring-up
// damage dispatcher + the beam-visual keepalive)
gBTDrive.fire = (gBTLaserKey || gBTPPCKey || gBTMissileKey) ? 1 : 0;
@@ -3001,6 +3006,34 @@ void
gBTHudPrimary = ((int)targetReticle.reticleElementMask & 0x20) != 0;
}
// task #6 ORDER FIX: the scripted block must run BEFORE the fire-push
// block below -- it writes gBTLaserKey/gBTConfigKey, which the earlier
// keyboard poll zeroes each frame while the window is unfocused; the
// old placement AFTER the push block meant the push never saw the
// scripted press.
// task #6 scripted verify (BT_CONFIG_TEST=1, headless): drives the same
// gBTConfigKey/gBTLaserKey states the keyboard would --
// t=[8,11) hold the configure button (session open)
// t=[9.5,9.7) one Trigger press edge -> ChooseButton toggle
// t>=13 0.2s Trigger pulses -> does the weapon fire now?
if ((Entity *)this == application->GetViewpointEntity()
&& getenv("BT_CONFIG_TEST"))
{
// frame-count driven (this env throttles background windows to a
// few fps -- wall/sim seconds are useless for scripting)
static int s_cfgFrame = 0;
++s_cfgFrame;
(void)dt;
// Schedule LATE (frame 2000+): the sim ticks many frames per second
// and the first-run schedule (30-120) fired before the enemy spawn
// completed -- targetInArc false -> the fire press never pushed.
gBTConfigKey = (s_cfgFrame >= 2000 && s_cfgFrame < 2600) ? 1 : 0;
int fireOn = (s_cfgFrame >= 2300 && s_cfgFrame < 2320) ? 1 : 0; // ONE press edge
if (s_cfgFrame >= 3000)
fireOn = ((s_cfgFrame / 60) & 1); // fire pulses after exit
gBTLaserKey = fireOn;
}
// E8: pulse the three fire channels per frame (1,0,1,0...) so each
// weapon sim's CheckFireEdge sees clean rising edges. UNCONDITIONAL --
// NOT inside the enemy block: firing needs only the world PICK (task
@@ -3067,6 +3100,77 @@ void
}
}
// task #6 dev harness: the config-session BRACKET. 'G' edge ->
// dispatch ConfigureMappables (id 9) press/release to the selected
// weapon -- the exact ReceiverDataMessageOf<ControlsButton> payload
// (+-(element+1)) the pod's streamed aux-panel EventMapping delivers.
// Everything downstream is the AUTHENTIC chain: EnterConfiguration
// flips the global mode to Mapping(0x8000) (normal fire mappings go
// dormant for the hold), the fire keys deliver ChooseButton (id 10)
// through the temp 0x8000 event mappings pushed by the SAME live-mode
// button path above, and the release restores NonMapping(0x10000).
if ((Entity *)this == application->GetViewpointEntity())
{
static int s_prevCfg = 0;
if (gBTConfigKey != s_prevCfg)
{
s_prevCfg = gBTConfigKey;
Subsystem *weapon = 0;
const char *slotEnv = getenv("BT_CONFIG_SLOT");
int wantSlot = (slotEnv != 0) ? atoi(slotEnv) : -1;
for (int s = 1; s < GetSubsystemCount(); ++s) // slot 0 = the MAPPER, never a weapon
{
Subsystem *sub = GetSubsystem(s);
if (sub == 0)
continue;
// Pick by WEAPON classID (0xBC8 Emitter / 0xBCA ProjectileWeapon /
// 0xBCE GaussRifle / 0xBD0 MissileLauncher / 0xBD4 PPC). The old
// (simulationFlags & 0x8) heuristic matched the MAPPER at slot 0:
// its MakeViewpointEntity resource is stack-built with only
// name/classID/size set, so its flags are STACK GARBAGE -- and a
// config message dispatched to the mapper lands in the mapper's
// OWN id space (the aux-preset Fail trap) = the abort popup.
int cid = (int)sub->GetClassID();
if (wantSlot >= 0 ? (s == wantSlot)
: (cid == 0xBC8 || cid == 0xBCA || cid == 0xBCE
|| cid == 0xBD0 || cid == 0xBD4))
{
weapon = sub;
break;
}
}
if (weapon == 0 && getenv("BT_FIRE_LOG"))
{
DEBUG_STREAM << "[config] NO WEAPON matched (count="
<< GetSubsystemCount() << ")" << std::endl;
for (int s2 = 0; s2 < GetSubsystemCount() && s2 < 33; ++s2)
{
Subsystem *p = GetSubsystem(s2);
if (p != 0)
DEBUG_STREAM << " [cfg-scan] " << s2 << " " << p->GetName()
<< " flags=0x" << std::hex << (int)p->simulationFlags
<< std::dec << std::endl;
}
}
if (weapon != 0)
{
const int kElem = 0x0e; // an aux-panel element (as authored)
ReceiverDataMessageOf<ControlsButton> msg(
9 /*MechWeapon::ConfigureMappablesMessageID (mechweap.hpp)*/,
sizeof(ReceiverDataMessageOf<ControlsButton>),
(ControlsButton)(gBTConfigKey ? (kElem + 1) : -(kElem + 1)));
weapon->Dispatch(&msg);
if (getenv("BT_FIRE_LOG"))
DEBUG_STREAM << "[config] " << (gBTConfigKey ? "ENTER" : "EXIT")
<< " session on " << weapon->GetName()
<< " mode now=0x" << std::hex
<< (int)application->GetModeManager()->GetModeMask()
<< std::dec << std::endl;
}
}
}
{
// The VICTIM under the boresight (task #46: any peer mech, not just
// the solo gEnemyMech). Range/log/fire all key off THIS mech.
@@ -3115,11 +3219,6 @@ void
gAimHits = 0; gAimNoRay = 0; gAimGround = 0;
}
if (gFireCooldown > 0.0f)
gFireCooldown -= dt;
const int fireWanted = gBTDrive.fireForced || gBTDrive.fire;
// Resolve the "explode" effect resource once.
if (gExplodeReady == 0)
{
@@ -3143,72 +3242,18 @@ void
}
}
// FIRE + DAMAGE: only vs a MECH under the boresight (terrain/sky picks
// draw beams but deal no damage). Dispatch routes to the picked victim
// -- for a REPLICANT this is a cross-pod hit: Entity::Dispatch reroutes
// the TakeDamageMessage to the owning master over the wire (task #46).
if (fireWanted && victim != 0 && targetInArc && gFireCooldown <= 0.0f
&& anyWeaponInRange && gExplodeReady == 1)
{
gFireCooldown = kFireCooldown;
++gShotCount;
// Impact = the PICKED HULL POINT -> the STEP-6 cylinder lookup
// resolves the zone under the boresight (aimed fire).
Point3D impact = hotPoint;
Origin exp_origin = ((Mech *)victim)->localOrigin;
exp_origin.linearPosition = impact; // at the hit point
// IMPACT FRAME: -Z aimed from the victim at the shooter (the .PFX
// hit spray convention; yaw = atan2(ddx, ddz)). [T0]
exp_origin.angularPosition =
EulerAngles(0.0f, (Scalar)atan2((double)ddx, (double)ddz), 0.0f);
Explosion::MakeMessage exp_message(
Explosion::MakeMessageID,
sizeof(Explosion::MakeMessage),
(Entity::ClassID)RegisteredClass::ExplosionClassID,
EntityID::Null,
gExplodeRes,
Explosion::DefaultFlags,
exp_origin,
victim->GetEntityID(), // entity hit
GetEntityID()); // shooter (this mech)
Explosion *shot = Explosion::Make(&exp_message);
if (shot)
{
Register_Object(shot);
DEBUG_STREAM << "[fire] SHOT #" << gShotCount
<< " -> explosion at target (range=" << range << ")\n" << std::flush;
}
// --- DAMAGE (real, STEP 6): dispatch UNAIMED (zone == -1) with the
// beam's world entry point. Mech::TakeDamageMessageHandler
// resolves the zone from the cylinder table; the base handler
// routes it to Mech__DamageZone::TakeDamage. Dispatch on a
// REPLICANT is rerouted by Entity::Dispatch to the owning
// master over the wire -- the cross-pod damage path.
// DAMAGE DELIVERY MOVED (task #8): the AUTHENTIC submitter is
// each firing weapon's own SendDamageMessage (@004b9728, called
// from Emitter::FireWeapon with the authored per-shot
// damagePortion + damageForce + impact) into the
// SubsystemMessageManager. This block keeps only the
// presentation roles: the boresight pick feeds the target
// slots the weapons read (mech+0x388/0x37C/0x38C), the [fire]
// beam-entry explosion visual above, and the shot log. The
// flat kShotDamage build + shooter-side score moved with the
// submission (mechweap.cpp).
if (getenv("BT_FIRE_LOG") && ((Mech *)victim)->damageZoneCount > 0)
{
DEBUG_STREAM << "[damage] beam hit " << (void*)victim
<< (((Mech *)victim)->GetInstance() == ReplicantInstance
? " (REPLICANT -> cross-pod via the weapon submit)" : "")
<< "\n" << std::flush;
}
// DEATH effects fire at the VICTIM's own death transition
// (UpdateDeathState, task #42) -- MP-correct: the master runs it.
}
// THE PHANTOM-SHOT BLOCK IS RETIRED (task #11, user-reported): this
// bring-up stand-in spawned an explosion at the victim on its OWN
// 0.3s cadence whenever the fire key was down -- matched the old
// one-frame recharge, but once the AUTHENTIC electrical model
// landed (task #10: 2-5s recharges + the generator thermal breaker)
// it kept painting hits while no weapon was discharging ("enemy
// shows fire even though I'm not producing lasers"). The authentic
// impact visual flows from each REAL discharge: Emitter::FireWeapon
// -> SendDamageMessage -> SubsystemMessageManager explosion
// bundling -> SubmitExplosion (messmgr.cpp). This block now keeps
// only its presentation roles above (boresight pick -> the target
// slots the weapons read, and the [target] log).
}
}
+27 -23
View File
@@ -408,25 +408,21 @@ Logical
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// @004afbc4 -- shared handler for the auxiliary-equipment and zoom buttons.
// The decomp body for this thunk was not captured; per the VTVControlsMapper
// pattern it routes the press through the configuration / mapping interface.
// BEST-EFFORT.
// task #6 CORRECTION (disasm-verified): the base body is a pure FAIL TRAP --
// push 0x7a / "Unhandled button mapping!" @0x50f24a / MECHMPPR.CPP @0x50f265 /
// call Fail -- i.e. "not overridden". The real aux/zoom behavior lives in the
// derived L4 layer's own handlers. The old guessed body called
// AddOrErase(value, NULL) -- with the RIO mapper live that would TOGGLE a
// NULL-destination direct mapping into the fire groups.
//
void
MechControlsMapper::ConfigureMappableMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
ReceiverDataMessageOf<ControlsButton> * /*message*/
)
{
Check(this);
Check(message);
if (message->dataContents > 0)
{
// TODO: verify against @004afbc4 -- expected to add or erase a
// temporary mapping for the pressed mappable button.
AddOrErase(message->dataContents, (ControlsButton *)0);
}
Check_Fpu();
DEBUG_STREAM << "[FAIL] Unhandled button mapping (base ConfigureMappableMessageHandler)"
<< std::endl;
Fail("Unhandled button mapping!\n"); // MECHMPPR.CPP:0x7a
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1149,39 +1145,46 @@ Pilot *
// @004b029c
//
void
MechControlsMapper::ExitConfiguration()
MechControlsMapper::ExitConfiguration(int /*held_element*/)
{
Fail("ExitConfiguration not overridden!\n"); // MECHMPPR.CPP:0x245
DEBUG_STREAM << "[FAIL] ExitConfiguration not overridden (base mapper in slot 0)" << std::endl;
Fail("ExitConfiguration not overridden!\n"); // @004b029c MECHMPPR.CPP:0x245
}
//
// (companion to the above; "EnterConfiguration not overridden!" @0050f370)
// @004b0280 (vtable +0x38; "EnterConfiguration not overridden!" @0050f370)
//
void
MechControlsMapper::EnterConfiguration(
int /*held_element*/,
ControlsButton * /*destination*/,
Receiver * /*receiver*/,
Receiver::MessageID /*activation_message_id*/,
Receiver::MessageID /*configuration_message_id*/
Receiver::MessageID /*choose_message_id*/,
Receiver::MessageID /*configure_message_id*/,
Receiver::MessageID /*active_message_id*/
)
{
DEBUG_STREAM << "[FAIL] EnterConfiguration not overridden (base mapper in slot 0)" << std::endl;
Fail("EnterConfiguration not overridden!\n");
}
//
// @004b02b8
// @004b02b8 (vtable +0x40; the EVENT AddOrErase overload -- the old
// "CreateTemporaryEventMappings" name was RP drift, task #6)
//
void
MechControlsMapper::CreateTemporaryEventMappings(
MechControlsMapper::AddOrErase(
unsigned int /*button_ID*/,
Receiver * /*receiver*/,
Receiver::MessageID /*config_message_id*/
Receiver::MessageID /*message_ID*/
)
{
DEBUG_STREAM << "[FAIL] Unhandled mapping (base AddOrErase event)" << std::endl;
Fail("Unhandled mapping!\n"); // MECHMPPR.CPP:0x24f
}
//
// @004b02d4
// @004b02d4 (vtable +0x44; the DIRECT AddOrErase overload)
//
void
MechControlsMapper::AddOrErase(
@@ -1189,6 +1192,7 @@ void
ControlsButton * /*destination*/
)
{
DEBUG_STREAM << "[FAIL] Unhandled mapping (base AddOrErase direct)" << std::endl;
Fail("Unhandled mapping!\n"); // MECHMPPR.CPP:0x25a
}
+26 -10
View File
@@ -259,27 +259,43 @@ class Pilot;
FillPilotArray(); // @004b06cc
//
// Configuration / event-mapping interface (the secondary vtable @0050f498).
// Platform-specific derived mappers override these; the base mapper traps
// any unrouted call.
// Configuration / event-mapping interface -- the CONFIG-MODE session
// virtuals (task #6, vtable slots verified from the raw tables in
// section_dump: base @0050F45C, L4 @0051E440, Thrustmaster @0051E3F0,
// RIO @0051E3A0). There is NO "secondary vtable @0050f498" -- that
// address is simply slot 15 INSIDE the primary base vtable. Slot map:
// +0x38 EnterConfiguration (base Fail stub @004b0280; L4 @004d1840)
// +0x3C ExitConfiguration (base Fail stub @004b029c; L4 @004d18dc)
// +0x40 AddOrErase(event) (base Fail @004b02b8; L4/TM no-op @004d195c; RIO @004d25e8)
// +0x44 AddOrErase(direct) (base Fail @004b02d4; L4/TM no-op @004d1964; RIO @004d262c)
// +0x48/+0x4C NotifyOf* pair (base no-ops @004b048c/@004b0494)
// The old reconstruction here ("CreateTemporaryEventMappings", a 4-arg
// Enter, a 0-arg Exit) was RP-name drift; the binary signatures come from
// the ONLY callers -- MechWeapon handlers id 9/10 @004b9550/@004b95b8:
// Enter(|v|-1, &weapon.fireImpulse, weapon, /*choose*/10, /*configure*/9, /*active*/0)
// Exit(|v|-1)
// AddOrErase(v /*1-based*/, &weapon.fireImpulse)
//
public:
virtual void
EnterConfiguration(
EnterConfiguration( // +0x38 @004b0280
int held_element,
ControlsButton *destination,
Receiver *receiver,
Receiver::MessageID activation_message_id,
Receiver::MessageID configuration_message_id
Receiver::MessageID choose_message_id,
Receiver::MessageID configure_message_id,
Receiver::MessageID active_message_id
);
virtual void
ExitConfiguration(); // @004b029c
ExitConfiguration(int held_element); // +0x3C @004b029c
virtual void
CreateTemporaryEventMappings( // @004b02b8
AddOrErase( // +0x40 @004b02b8 (event overload)
unsigned int button_ID,
Receiver *receiver,
Receiver::MessageID config_message_id
Receiver::MessageID message_ID
);
virtual void
AddOrErase( // @004b02d4
AddOrErase( // +0x44 @004b02d4 (direct overload)
unsigned int button_ID,
ControlsButton *destination
);
+28 -4
View File
@@ -124,8 +124,8 @@ MechSubsystem::MechSubsystem(
{
refCount = NewRefCount(0x10); // this[0x3d]
this->owner = owner; // this[0x34] -- the owning Mech (canonical)
hostEntity = owner; // this[0x3a] = param_7
subsystemId2 = subsystem_ID; // this[0x3b] = param_8
controlDestination = 0; // this[0x3a] = param_7 (ctor pass-through;
controlMessageID = 0; // this[0x3b] = param_8 weapons override -- task #6)
vitalSubsystem = 0; // this[0x39]
BindName(refCount, "None"); // FUN_00402a98(.,this+0x3d,"None") @0x50df7a
alarmModel = 0; // identity ResourceID // FUN_00408440(this+0x3e, identity)
@@ -159,8 +159,8 @@ MechSubsystem::MechSubsystem(
// ctor only set hostEntity; subclasses read
// `owner` widely -> must be set or it is garbage.)
statusAlarm.SetLevel(0); // FUN_0041bbd8(this+0xb, 0)
hostEntity = (Mech *)subsystem_resource->segmentIndex; // this[0x3a]=param_6
subsystemId2 = subsystem_ID; // this[0x3b]=param_7
controlDestination = 0; // this[0x3a]=param_6 (ctor pass-through;
controlMessageID = 0; // this[0x3b]=param_7 weapons override -- task #6)
resource = subsystem_resource; // this[0x3c]
vitalSubsystemIndex = -1; // this[0x44]
vitalSubsystem = (subsystem_resource->vitalSubsystemIndex == 1); // res+0x48==1
@@ -487,3 +487,27 @@ int
Check_Fpu();
return True;
}
//#############################################################################
// BTSubsystemControlFeed -- complete-type bridge (task #6)
//
// ConfigMapGauge::Execute (@004c6f1c) reads the subsystem's controls feed
// (*(subsys+0xE8) destination, *(subsys+0xEC) message id) for GetMapState.
// The gauge TU has no complete MechSubsystem (databinding-trap rules: never
// raw-read a compiled object's offsets) -- this bridge reads the NAMED members
// and hands back the Receiver upcast alongside.
//
void
BTSubsystemControlFeed(
void *subsystem_in,
void **destination_out,
int *message_id_out,
void **receiver_out
)
{
MechSubsystem *sub = (MechSubsystem *)subsystem_in;
*destination_out = sub->controlDestination; // @0xE8
*message_id_out = sub->controlMessageID; // @0xEC
*receiver_out = (Receiver *)sub;
}
+11 -2
View File
@@ -289,8 +289,17 @@ class Damage;
// subsystem KILLS the mech (ctor: res+0x48
// "VitalSubsystem"==1; was misnamed
// videoObjectFlag) [T1 part_012.c:15776]
Mech *hostEntity; // @0xE8 this[0x3a]
int subsystemId2; // @0xEC this[0x3b]
// task #6 CORRECTION: these two are the CONTROLS feed, not identity --
// the binary ctor chain stores its destination/message ctor pass-throughs
// here (FUN_004ac644 part_012.c:15772-15773), and ConfigMapGauge::Execute
// @004c6f1c reads BOTH for buttonGroup GetMapState. MechWeapon's ctor
// defaults the destination to &fireImpulse (@004b99a8 part_013.c:6886-9).
// The old names hostEntity/subsystemId2 (populated with owner/subsystem_ID)
// were a mislabel -- any reader would have classified every weapon as
// "mappedByOthers". (No port reader existed; verified before the rename.)
void *controlDestination; // @0xE8 this[0x3a] (ControlsButton* the direct maps write)
int controlMessageID; // @0xEC this[0x3b] (event-mapping message id; weapons 0)
friend void BTSubsystemControlFeed(void *, void **, int *, void **); // gauge bridge (task #6)
SubsystemResource *resource; // @0xF0 this[0x3c]
void *refCount; // @0xF4 this[0x3d]
ResourceDescription::ResourceID
+106 -2
View File
@@ -59,6 +59,7 @@
#include <bt.hpp>
#include <messmgr.hpp> // SubsystemMessageManager (task #8 damage submission)
#include <mechmppr.hpp> // MechControlsMapper -- the config-session virtuals (task #6)
#pragma hdrstop
#if !defined(MECHWEAP_HPP)
@@ -81,7 +82,27 @@ Derivation
"MechWeapon"
);
Receiver::MessageHandlerSet MechWeapon::MessageHandlers;
// task #6 -- the binary's MechWeapon handler table @0x511860 (section_dump:
// {9, "ConfigureMappables"@0x511994, @004b9550}, {10, "ChooseButton"@0x5119A7,
// @004b95b8}); chained onto the PoweredSubsystem set.
const Receiver::HandlerEntry
MechWeapon::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(MechWeapon, ConfigureMappables), // id 9 @004b9550
MESSAGE_ENTRY(MechWeapon, ChooseButton) // id 10 @004b95b8
};
Receiver::MessageHandlerSet&
MechWeapon::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(
ELEMENTS(MechWeapon::MessageHandlerEntries),
MechWeapon::MessageHandlerEntries,
PoweredSubsystem::GetMessageHandlers()
);
return messageHandlers;
}
Simulation::AttributeIndexSet MechWeapon::AttributeIndex; // legacy empty member (superseded by GetAttributeIndex)
// gauge data-binding wave: OutputVoltage/PercentDone -> rechargeLevel (1.0 ==
@@ -112,12 +133,89 @@ MechWeapon::AttributeIndexSet&
MechWeapon::SharedData
MechWeapon::DefaultData(
&MechWeapon::ClassDerivations,
MechWeapon::MessageHandlers,
MechWeapon::GetMessageHandlers(),
MechWeapon::GetAttributeIndex(),
MechWeapon::StateCount
);
//#############################################################################
// The CONFIG-MODE session handlers (task #6)
//
//
// @004b9550 -- id 9 "ConfigureMappables": the press-and-HOLD session bracket.
// dataContents = +-(element+1) (press/release, the L4 button value convention).
// Press: open the session on THIS weapon -- the roster-slot-0 mapper flips the
// global mode NonMapping->Mapping, latches &fireImpulse as the active configure
// target, and temp-maps the fire buttons to send ChooseButton (id 10) here.
// Release: close the session (mode restored, temp layer stripped).
// The +0x110 write (0 open / -1 idle) is the binary's config-session flag
// (ctor default -1, part_012.c:15775); the port field carries the older name
// vitalSubsystemIndex -- no confirmed reader yet (read-site survey pending).
//
void
MechWeapon::ConfigureMappablesMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
)
{
Check(this);
Check_Pointer(message);
int value = message->dataContents; // +-(element+1)
int element = ((value < 0) ? -value : value) - 1;
MechControlsMapper *mapper = ((Mech *)owner)->MappingMapper(); // **(mech+0x128)
if (mapper == 0)
{
return; // (binary assumes slot 0 is live)
}
if (value < 1)
{
mapper->ExitConfiguration(element); // vtbl+0x3C
vitalSubsystemIndex = -1; // this+0x110 -> idle
}
else
{
vitalSubsystemIndex = 0; // this+0x110 -> session open
mapper->EnterConfiguration( // vtbl+0x38
element,
(ControlsButton *)&fireImpulse, // this+0x31C (TriggerState)
this,
ChooseButtonMessageID, // 10
ConfigureMappablesMessageID, // 9
0); // active_message_id (direct bind)
}
}
//
// @004b95b8 -- id 10 "ChooseButton": while the session is open, a tapped fire
// button sends its RAW 1-based value here; the mapper's direct AddOrErase
// TOGGLES buttonGroup[value-1]'s permanent (0x10000) mapping onto
// &fireImpulse -- join the group if absent, leave it if present. Press only.
//
void
MechWeapon::ChooseButtonMessageHandler(
ReceiverDataMessageOf<ControlsButton> *message
)
{
Check(this);
Check_Pointer(message);
if (message->dataContents > 0)
{
MechControlsMapper *mapper = ((Mech *)owner)->MappingMapper();
if (mapper != 0)
{
mapper->AddOrErase( // vtbl+0x44 (direct overload)
(unsigned int)message->dataContents, // RAW 1-based
(ControlsButton *)&fireImpulse);
}
}
}
//#############################################################################
// Firing -- abstract base body
//
@@ -195,6 +293,12 @@ MechWeapon::MechWeapon(
usesExternalModel = (strstr(GetName(), "EXT") != 0) ? True : False;
fireImpulse = 0.0f; // 0x31C
// task #6: the binary MechWeapon ctor defaults its controls-destination
// pass-through to &fireImpulse when the caller passes NULL (@004b99a8
// `if (param_7==0) param_7 = this+0x31C`); the chain stores it at
// MechSubsystem+0xE8/+0xEC where ConfigMapGauge's GetMapState reads it.
controlDestination = (void *)&fireImpulse; // 0xE8
controlMessageID = 0; // 0xEC
rechargeLevel = 1.0f; // 0x320 (fully charged)
previousFireImpulse = 0.0f; // 0x3A4
recoil = 0.0f; // 0x3E8
+38 -3
View File
@@ -105,10 +105,39 @@ class CockpitHud;
//
public:
static Derivation ClassDerivations;
static Receiver::MessageHandlerSet MessageHandlers;
// task #6: the binary registers TWO handlers ({9,"ConfigureMappables"},
// {10,"ChooseButton"} -- table @0x511860); the old empty static set
// meant no pilot input could ever reach the config session. The
// accessor idiom (function-local static) dodges cross-TU init order.
static const Receiver::HandlerEntry MessageHandlerEntries[];
static Receiver::MessageHandlerSet& GetMessageHandlers();
static AttributeIndexSet AttributeIndex;
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Messaging Support -- the CONFIG-MODE session (task #6)
//
// Ids PINNED to the binary table @0x511860 (PoweredSubsystem owns 4-8,
// the generator-select handlers). The pilot HOLDS a weapon's authored
// configure button (a type-6 EventMapping, id 9, under the MFD-quadrant
// aux-panel mode masks) to open the session; taps fire buttons to toggle
// this weapon's group membership (id 10, temp-mapped by
// EnterConfiguration); releases to close.
//
public:
enum {
ConfigureMappablesMessageID = 9, // @004b9550 (was mislabeled a Myomers fn)
ChooseButtonMessageID = 10 // @004b95b8
};
void
ConfigureMappablesMessageHandler( // @004b9550
ReceiverDataMessageOf<ControlsButton> *message
);
void
ChooseButtonMessageHandler( // @004b95b8
ReceiverDataMessageOf<ControlsButton> *message
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support (gauge data-binding wave)
//
@@ -122,8 +151,14 @@ class CockpitHud;
//
public:
enum {
// TriggerState is the BINARY's only MechWeapon attribute
// ({0x13, "TriggerState", +0x31d} @0x511890 -- fireImpulse@0x31C).
// TriggerState ({0x13, "TriggerState"} @0x511890 -- fireImpulse
// @0x31C). task #6 CORRECTION: the binary MechWeapon attribute
// table has ELEVEN entries, not one -- 0x12 PercentDone@0x320,
// 0x14 DistanceToTarget@0x324, 0x15 TargetWithinRange@0x34C,
// 0x16 WeaponRange@0x328(=effectiveRange), 0x17 PipPosition,
// 0x18 PipColor, 0x19 PipExtendedRange, 0x1A EstimatedReadyTime
// @0x330, 0x1B RearFiring@0x334, 0x1C WeaponState@0x350 (future
// gauge-wave feeds). The pinning below still holds:
// The streamed per-mech controls maps (type-6 resources) bind the
// fire buttons DIRECTLY onto it by this NUMERIC id, so it is
// PINNED to the binary value: the binary's subsystem chain reaches
+9 -2
View File
@@ -86,13 +86,20 @@ Derivation
"MissileLauncher"
);
Receiver::MessageHandlerSet MissileLauncher::MessageHandlers;
// task #6: inherit ProjectileWeapon's handler set (see emitter.cpp note).
Receiver::MessageHandlerSet&
MissileLauncher::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(
ProjectileWeapon::GetMessageHandlers()); // copy-inherit
return messageHandlers;
}
Simulation::AttributeIndexSet MissileLauncher::AttributeIndex;
MissileLauncher::SharedData
MissileLauncher::DefaultData(
&MissileLauncher::ClassDerivations,
MissileLauncher::MessageHandlers,
MissileLauncher::GetMessageHandlers(),
MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty)
MissileLauncher::StateCount
);
+1 -1
View File
@@ -75,7 +75,7 @@ class Missile;
//
public:
static Derivation ClassDerivations;
static Receiver::MessageHandlerSet MessageHandlers;
static Receiver::MessageHandlerSet& GetMessageHandlers(); // inherits ProjectileWeapon's (task #6)
static AttributeIndexSet AttributeIndex;
static SharedData DefaultData;
+1 -1
View File
@@ -497,7 +497,7 @@ void Myomers::ConnectToMover(SubsystemMessage &message)
//***************************************************************************
// Myomers::DisconnectFromMover @004b95b8
// Myomers::DisconnectFromMover (stand-in; no binary address)
//***************************************************************************
// Counterpart of ConnectToMover: detaches the SpeedEffect feed.
// if (message->value > 0)
+8
View File
@@ -999,6 +999,11 @@ void
}
if (heatAlarm.GetLevel() == HeatSink::FailureHeat) // this+0x184 == 2
{
// The THERMAL BREAKER: output collapses to 0 until the generator cools
// past NormalHeat and re-spins (task #11 diag below shows the duty cycle).
if (stateAlarm.GetLevel() != GeneratorRecovered && getenv("BT_HEAT_LOG"))
DEBUG_STREAM << "[gen] " << GetName() << " TRIPPED (T="
<< currentTemperature << ")" << std::endl;
stateAlarm.SetLevel(GeneratorRecovered); // SetLevel 4
}
@@ -1038,6 +1043,9 @@ void
}
else
{
if (getenv("BT_HEAT_LOG"))
DEBUG_STREAM << "[gen] " << GetName() << " RESTARTING (T="
<< currentTemperature << ")" << std::endl;
startTimer = 0.0f;
stateAlarm.SetLevel(GeneratorStarting); // 0
}
+9 -2
View File
@@ -123,13 +123,20 @@ Derivation
"ProjectileWeapon" // DAT_00512280
);
Receiver::MessageHandlerSet ProjectileWeapon::MessageHandlers;
// task #6: inherit MechWeapon's handler set (see emitter.cpp note).
Receiver::MessageHandlerSet&
ProjectileWeapon::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(
MechWeapon::GetMessageHandlers()); // copy-inherit
return messageHandlers;
}
Simulation::AttributeIndexSet ProjectileWeapon::AttributeIndex;
ProjectileWeapon::SharedData
ProjectileWeapon::DefaultData(
&ProjectileWeapon::ClassDerivations,
ProjectileWeapon::MessageHandlers,
ProjectileWeapon::GetMessageHandlers(),
MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty)
ProjectileWeapon::StateCount
);
+1 -1
View File
@@ -102,7 +102,7 @@ class NotationFile;
public:
static Derivation ClassDerivations; // parent = MechWeapon::ClassDerivations,
// name = "ProjectileWeapon" (DAT_00512280)
static Receiver::MessageHandlerSet MessageHandlers;
static Receiver::MessageHandlerSet& GetMessageHandlers(); // inherits MechWeapon's (task #6)
static AttributeIndexSet AttributeIndex;
static SharedData DefaultData;