Audio: subsystem STATE sounds -- GaugeAlarm54 IS a StateIndicator; register Generator/Condenser/Reservoir state (task #50)

The audio subsystem binds AudioStateWatchers (class 28) to per-subsystem state
attrs (GeneratorState/CondenserState/ReservoirState) BY NAME.  Recovered the
watcher TYPE per attribute via a MakeObjectImplementation ClassID trace.

Root cause of the subsystem-state crash: the binary's 0x54-byte subsystem alarm
(FUN_0041b9ec, reconstructed as GaugeAlarm54) IS a StateIndicator -- its "three
sub-indicators" @0x18/0x2c/0x40 are the audio/video/gauge watcher SChains, and
level@0x14 is currentState.  The reconstruction had modeled that tail as an opaque
`_tail`, so the sockets were never constructed -> AddAudioWatcher AV'd on 0xCDCDCDCD.

- GaugeAlarm54: give it the three REAL, constructed SChainOf<Component*> sockets +
  AddAudioWatcher/Video/Gauge; SetLevel now fires the watchers on a level CHANGE
  (empty sockets = no-op, so the ~all other alarms are unaffected).  levelB (weapon
  LevelCountB "reset source") is left untouched -- weapons unchanged.  sizeof stays
  0x54 (static_assert holds -> SChainOf<Component*> is 0x14, layout exact).
- Register the state attrs -> the subsystem's own alarm (own AttributePointers[]
  chained to the parent): Generator.GeneratorState->stateAlarm, Condenser.
  CondenserState->condenserAlarm, Reservoir.ReservoirState->reservoirAlarm.  Those
  alarms are already SetLevel'd by the sim, so the state audio fires on transition.
- AudioStateWatcher guard now validates the +0x18 SOCKET (what AddAudioWatcher
  touches), not the +0 vtable -- GaugeAlarm54 is non-polymorphic at +0 (raw header)
  but has a real socket at +0x18.

audiostate skips 85 -> 10 (only AmmoBin AmmoState/SimulationState left).  The
Logical/Scalar/Enum subsystem attrs (ReportLeak/GeneratorOn/ConfigureActivePress/
MotionState/SpeedOfTorsoHorizontal/TargetRangeExponent) stay inert (read 0, silent,
non-crashing) -- they need backing members in size-locked layouts (a polish wave).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-15 21:38:15 -05:00
co-authored by Claude Opus 4.8
parent 03f0c21484
commit 5ba541ed56
9 changed files with 149 additions and 18 deletions
+50
View File
@@ -80,6 +80,37 @@ static const Scalar CoolantDrawFloor = 0.0025f; // _DAT_0050e3d8 (zero floor
static const Scalar CoolantActiveOn = 0.003f; // _DAT_0050e3d4 (ON threshold)
//###########################################################################
// GaugeAlarm54 watcher sockets. The binary's 0x54 alarm (FUN_0041b9ec) IS a
// StateIndicator -- its three sub-indicators @0x18/0x2c/0x40 are the audio/
// video/gauge watcher chains, level@0x14 is currentState. These out-of-line
// bodies let an AudioStateWatcher bind to any subsystem alarm BY NAME
// (GeneratorState / CondenserState / ReservoirState / ...); SetLevel fires the
// registered watchers on a level change, so the state audio plays on transition.
// Empty sockets iterate to nothing, so alarms with no audio watchers are a no-op.
//###########################################################################
void GaugeAlarm54::AddAudioWatcher(Component *watcher) { audioWatcherSocket.Add(watcher); }
void GaugeAlarm54::AddVideoWatcher(Component *watcher) { videoWatcherSocket.Add(watcher); }
void GaugeAlarm54::AddGaugeWatcher(Component *watcher) { gaugeWatcherSocket.Add(watcher); }
void GaugeAlarm54::NotifyWatchers()
{
Component *watcher;
{
SChainIteratorOf<Component*> iterator(audioWatcherSocket);
while ((watcher = iterator.ReadAndNext()) != NULL) watcher->Execute();
}
{
SChainIteratorOf<Component*> iterator(videoWatcherSocket);
while ((watcher = iterator.ReadAndNext()) != NULL) watcher->Execute();
}
{
SChainIteratorOf<Component*> iterator(gaugeWatcherSocket);
while ((watcher = iterator.ReadAndNext()) != NULL) watcher->Execute();
}
}
//###########################################################################
//###########################################################################
// HeatableSubsystem
@@ -236,6 +267,25 @@ int
//#############################################################################
// Shared Data Support
//
// CondenserState -> condenserAlarm (@0x1DC). Chained to HeatSink so the inherited
// coolant/temp attrs stay reachable; dense from HeatSink::NextAttributeID.
const Condenser::IndexEntry
Condenser::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Condenser, CondenserState, condenserAlarm) // @0x1DC (0x54 StateIndicator-compatible alarm)
};
Condenser::AttributeIndexSet&
Condenser::GetAttributeIndex()
{
static Condenser::AttributeIndexSet attributeIndex(
ELEMENTS(Condenser::AttributePointers),
Condenser::AttributePointers,
HeatSink::GetAttributeIndex()
);
return attributeIndex;
}
Condenser::SharedData
Condenser::DefaultData(
Condenser::GetClassDerivations(),