HUD: missile/AC weapon pips blink on fire again -- read authentic WeaponState (attr 0x1C)
Reported: the reticle's missile pips no longer momentarily disappear when
firing. Traced it: the pip's 'loaded' flag has read MechWeapon::rechargeLevel
(>= 0.999) since the reticle Execute was recovered (task #37). That's a port
APPROXIMATION of the binary's authentic attr 0x1C (WeaponState @0x350). It
works for emitters -- rechargeLevel is charge-driven and tops off at 1.0 when
Loaded -- but projectile weapons (MissileLauncher/autocannon) never write
rechargeLevel (it's authentically static at 1.0; the recharge DIAL draws full
and never moves). So a missile pip's 'loaded' was permanently true and the pip
never blinked. NOT an audio regression -- it never worked in the port; the
audio wave (ea85554) is actually what made the projectile fire-cycle state
(weaponAlarm Loaded/Loading/Firing) correct, i.e. the very signal the pip
should have been reading.
Fix (faithful to the binary): the pip now reads attr 0x1C = the weaponAlarm
StateIndicator level (new MechWeapon::WeaponStatePtr -> GaugeAlarm54::LevelPtr)
and compares it '== stateConst2' -- the loaded constant (2) the binary itself
already stores in AddWeapon (param_10). The state cycles
Loaded(2)<->Firing(0)/Loading(3)/Jammed(5) for BOTH weapon families, so every
pip momentarily drops on fire. The authentically-static rechargeLevel dial is
untouched.
Verified live (Blackhawk solo, BT_AUTOFIRE=1 BT_AF_MISSILE=1): both SRM6
missile pips toggle loaded 2<->0/5 on each salvo (emitter pips unchanged);
game boots + runs clean. KB: context/gauges-hud.md pip entry corrected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3e79f5b61c
commit
171c993147
+12
-3
@@ -195,10 +195,19 @@ and every instrument is now live [T2]:**
|
||||
rotated frame: 0.05-unit attack-direction marks, fresh <2 s red, expiring at 6 s, 1 s blink tick.
|
||||
Port feed: the player's TakeDamage handler pushes the impact direction.
|
||||
- **Pips** (composed into subB6, master-called): hidden when the weapon's DAMAGE state == 1
|
||||
(destroyed, attr 1); LIT (A) when the FIRE-CYCLE state == 2 (loaded, attr 0x1c; port source
|
||||
rechargeLevel ≥ 1) else dark ring (B, charging); filtered by `weaponMode & elementMask&0xF`
|
||||
(destroyed, attr 1); LIT (A) when the FIRE-CYCLE state == 2 (loaded, **attr 0x1C WeaponState**
|
||||
@0x350 == 2) else dark ring (B, charging); filtered by `weaponMode & elementMask&0xF`
|
||||
(the weapon-GROUP bits Front/Rear/Left/Right). **Range plays NO part** — Execute never reads
|
||||
the stored TargetWithinRange slots.
|
||||
the stored TargetWithinRange slots. **FIX 2026-07-18 [T2]:** the pip's "loaded" was a port
|
||||
approximation reading `rechargeLevel ≥ 1` — correct for emitters (charge-driven) but
|
||||
**statically 1.0 on projectile weapons** (MissileLauncher/autocannon never write it — see
|
||||
§Launcher-panel above), so missile/AC pips were **permanently lit and never blinked on fire**.
|
||||
Now reads the AUTHENTIC attr 0x1C = the weaponAlarm StateIndicator level (`WeaponStatePtr` →
|
||||
`GaugeAlarm54::LevelPtr`), compared `== stateConst2` (the const the binary itself stores in
|
||||
AddWeapon). The state cycles Loaded(2)↔Firing(0)/Loading(3)/Jammed(5) for BOTH families, so a
|
||||
missile pip now momentarily drops when fired, exactly like the emitter's. Verified live
|
||||
(Blackhawk, `BT_AUTOFIRE=1 BT_AF_MISSILE=1`): both SRM6 pips toggle 2↔0/5 on each salvo. The
|
||||
authentically-static `rechargeLevel` recharge DIAL (§Launcher-panel) is untouched.
|
||||
- **Lock ring** = subB9 (ring+cross) at frame centre, **SPINNING 4°/frame** while the Lock attr
|
||||
(0xA) is up. The Lock PRODUCER is the authentic HudSimulation rule (part_013.c:5619-5634 [T1],
|
||||
wired task #38): lock requires a target AND your own HUD's host zone damage < **0.75** (a
|
||||
|
||||
@@ -1737,16 +1737,22 @@ void
|
||||
// the WEAPON PIPS [0xb6] (Execute @4ce2c2-4ce3e1 [T1]): the composed pip
|
||||
// list the master calls. Per weapon: skip unless its GROUP is displayed
|
||||
// (weaponMode & elementMask low bits); HIDE it when destroyed (attr 1 ==
|
||||
// 1); the LIT pip (A) when the fire cycle is LOADED (attr 0x1c == 2, our
|
||||
// source: rechargeLevel >= 1), else the dark ring (B, charging). Range
|
||||
// plays NO part -- the binary never reads TargetWithinRange here.
|
||||
// 1); the LIT pip (A) when the fire cycle is LOADED (attr 0x1C WeaponState
|
||||
// == stateConst2 == 2), else the dark ring (B, charging). The pip source
|
||||
// is the weaponAlarm StateIndicator (WeaponStatePtr), which cycles
|
||||
// Firing(0)->Loading(3)->Loaded(2) on EVERY fire -- so a missile/AC pip
|
||||
// momentarily drops when fired, exactly like the emitter's. (The old
|
||||
// rechargeLevel>=1 approximation was static on projectile weapons -- whose
|
||||
// rechargeLevel is authentically fixed at 1.0 -- so their pips never
|
||||
// blinked; see mechweap.hpp WeaponStatePtr.) Range plays NO part -- the
|
||||
// binary never reads TargetWithinRange here.
|
||||
{
|
||||
extern int gBTHudGroupMask; // element-mask low bits (0xF = all)
|
||||
int dirty = 0;
|
||||
for (int i = 0; i < weaponCount; ++i)
|
||||
{
|
||||
const int destroyed = (simStateAttr[i] != 0 && *simStateAttr[i] == 1);
|
||||
const int loaded = (cycleReady[i] != 0 && *cycleReady[i] >= 0.999f);
|
||||
const int loaded = (cycleReady[i] != 0 && *cycleReady[i] == stateConst2[i]);
|
||||
if (destroyed != simStateCache[i] || loaded != alarmCache[i])
|
||||
{
|
||||
simStateCache[i] = destroyed;
|
||||
@@ -1898,7 +1904,7 @@ BTReticleRenderable *BTBuildReticle(Entity *mech)
|
||||
(int *)wp->WithinRangePtr(),
|
||||
wp->PipExtendedRange(),
|
||||
r, g, b,
|
||||
wp->RechargeLevelPtr(), // attr 0x1c analog: loaded when >= 1
|
||||
wp->WeaponStatePtr(), // attr 0x1C WeaponState: loaded when == 2
|
||||
2, 3,
|
||||
(int *)wp->SimulationStatePtr(), // attr 1: damage state (1 = destroyed)
|
||||
1,
|
||||
@@ -1988,7 +1994,7 @@ void
|
||||
Scalar pip_red, // param_6..8 PipColor
|
||||
Scalar pip_green,
|
||||
Scalar pip_blue,
|
||||
Scalar *cycle_ready, // param_9 (port: rechargeLevel)
|
||||
const int *cycle_ready, // param_9 attr 0x1C WeaponState (== 2 loaded)
|
||||
int const2, // param_10 (2 = loaded)
|
||||
int const3, // param_11 (3 = charging)
|
||||
int *sim_state_value, // param_12 weapon attr 1
|
||||
@@ -2012,7 +2018,7 @@ void
|
||||
this->stateConst2[n] /* [0x8c+n*4] = param_10 */ = const2;
|
||||
this->stateConst1[n] /* [0xb4+n*4] = param_13 */ = const1;
|
||||
this->cycleReady[n] /* [0x130+n*4] = param_9 */ = cycle_ready;
|
||||
this->alarmCache[n] /* [0xdc+n*4] */ = (*cycle_ready >= 0.999f);
|
||||
this->alarmCache[n] /* [0xdc+n*4] */ = (*cycle_ready == const2);
|
||||
this->simStateAttr[n] /* [0x158+n*4] = param_12 */ = sim_state_value;
|
||||
this->simStateCache[n] /* [0x104+n*4] */ = (*sim_state_value == const1);
|
||||
this->withinRangePtr[n] /* [0x18c+n*4] = param_4 */ = within_range_value;
|
||||
|
||||
@@ -526,7 +526,7 @@ class BTReticleRenderable:
|
||||
Scalar pip_red, // param_6..8 PipColor
|
||||
Scalar pip_green,
|
||||
Scalar pip_blue,
|
||||
Scalar *cycle_ready, // param_9 (port: rechargeLevel)
|
||||
const int *cycle_ready, // param_9 attr 0x1C WeaponState (== 2 loaded)
|
||||
int const2, // param_10 (2 = loaded)
|
||||
int const3, // param_11 (3 = charging)
|
||||
int *sim_state_value, // param_12 weapon attr 1
|
||||
@@ -548,7 +548,7 @@ class BTReticleRenderable:
|
||||
int stateConst1[10]; // +0xb4 1 = destroyed (hidden)
|
||||
int alarmCache[10]; // +0xdc port: LOADED flag cache
|
||||
int simStateCache[10]; // +0x104 port: DESTROYED flag cache
|
||||
Scalar *cycleReady[10]; // +0x130 port: rechargeLevel source
|
||||
const int *cycleReady[10]; // +0x130 attr 0x1C WeaponState (== stateConst2 => loaded)
|
||||
int *simStateAttr[10]; // +0x158 damage state (attr 1)
|
||||
int *withinRangePtr[10]; // +0x18c param_4 (stored; Execute
|
||||
int withinRangeCache[10]; // +0x1b4 never reads it -- T1)
|
||||
|
||||
@@ -106,6 +106,7 @@ public:
|
||||
void SetLevel(int n) { levelB = level; if (n != level) { level = n; NotifyWatchers(); } }
|
||||
int GetLevel() const { return level; }
|
||||
int Level() const { return level; }
|
||||
const int *LevelPtr() const { return &level; } // stable ¤tState@+0x14 (the weapon pip's attr-0x1c source)
|
||||
int LevelCountB() const { return levelB; } // +0x10 (the weapon update-record "reset" source)
|
||||
// ReconAlarm/AlarmIndicator API aliases (callers that predate the retype use these):
|
||||
void SetState(unsigned n){ SetLevel((int)n); }
|
||||
|
||||
@@ -343,7 +343,12 @@ class CockpitHud;
|
||||
void *WithinRangePtr() { return &targetWithinRange; }
|
||||
void *WeaponAlarmPtr() { return &weaponAlarm; }
|
||||
void *SimulationStatePtr() { return &simulationState; } // attr 1 ("SimulationState" -- damage state; 1 = Destroyed)
|
||||
Scalar *RechargeLevelPtr() { return &rechargeLevel; } // the pip ready source (1.0 == loaded/ready)
|
||||
Scalar *RechargeLevelPtr() { return &rechargeLevel; } // the recharge-dial source (1.0 == fully charged); STATIC on projectile weapons
|
||||
// The reticle pip's AUTHENTIC "loaded" source: attr 0x1C WeaponState @0x350 (the
|
||||
// weaponAlarm StateIndicator level == 2 => Loaded). Unlike rechargeLevel this
|
||||
// cycles for BOTH families -- emitters Loading(3)->Loaded(2)->Firing(0), and
|
||||
// projectile weapons the same via ReadyToFire -- so missile/AC pips blink on fire.
|
||||
const int *WeaponStatePtr() { return weaponAlarm.LevelPtr(); }
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user