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:
co-authored by
Claude Opus 4.8
parent
03f0c21484
commit
5ba541ed56
@@ -883,14 +883,19 @@ AudioStateWatcher::AudioStateWatcher(
|
||||
// at +0x18 -- and AddAudioWatcher would AV in SChainOf::Add. Skip the register
|
||||
// (that subsystem's state audio stays silent) until the subsystem is built; this
|
||||
// is SELF-CLEARING (a real StateIndicator passes). See docs: audio subsystem wave.
|
||||
// Validate the AUDIO SOCKET at +0x18 (what AddAudioWatcher touches), NOT the
|
||||
// object's +0 vtable: a real StateIndicator has a vtable at +0, but the binary's
|
||||
// 0x54 subsystem alarm (GaugeAlarm54) is non-polymorphic there (a raw header) yet
|
||||
// has a real, constructed SChainOf socket at +0x18. A skip means the socket is
|
||||
// unconstructed (null / debug-fill 0xCDCDCDCD) -- the inert pad or a subsystem not
|
||||
// yet reconstructed. Registering there would AV in SChainOf::Add.
|
||||
{
|
||||
unsigned vt = *(unsigned*)attributePointer;
|
||||
unsigned chain = *(unsigned*)((char*)attributePointer + 0x18);
|
||||
if (vt == 0 || vt == 0xCDCDCDCD || chain == 0xCDCDCDCD)
|
||||
if (chain == 0 || chain == 0xCDCDCDCD)
|
||||
{
|
||||
if (getenv("BT_AUDIO_LOG"))
|
||||
DEBUG_STREAM << "[audiostate] skip watcher on unbuilt StateIndicator "
|
||||
<< attributePointer << " (vt=" << (void*)vt << ")\n" << std::flush;
|
||||
<< attributePointer << " (chain=" << (void*)chain << ")\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,9 @@ AttributeWatcher::AttributeWatcher(
|
||||
|
||||
if (getenv("BT_ATTRBIND_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[attrbind] subsys=[" << subsystem_name
|
||||
extern int g_curAudioWatcherClass;
|
||||
DEBUG_STREAM << "[attrbind] class=" << g_curAudioWatcherClass
|
||||
<< " subsys=[" << subsystem_name
|
||||
<< "] attr=[" << attribute_name << "] -> ptr=" << attributePointer
|
||||
<< " vtbl=" << (attributePointer ? *(void**)attributePointer : (void*)0)
|
||||
<< "\n" << std::flush;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
ALuint *g_buffers;
|
||||
int g_numBuffers;
|
||||
int g_curAudioWatcherClass = 0;
|
||||
|
||||
//
|
||||
// Minimal canonical-PCM WAV reader. The repo's libsndfile-1.dll is a STUB
|
||||
@@ -140,6 +141,11 @@ RegisteredClass*
|
||||
Check(this);
|
||||
Verify(class_ID != RegisteredClass::NullClassID);
|
||||
|
||||
// DIAG (BT_ATTRBIND_LOG): record which audio-object ClassID is being built so the
|
||||
// AttributeWatcher trace can print the watcher TYPE alongside the bound attribute.
|
||||
extern int g_curAudioWatcherClass;
|
||||
g_curAudioWatcherClass = (int)class_ID;
|
||||
|
||||
//
|
||||
//-----------------------------------------------------------------------
|
||||
// HACK - Constructors should be referenced through registration, for
|
||||
|
||||
@@ -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(),
|
||||
|
||||
+47
-13
@@ -39,6 +39,8 @@
|
||||
#include <average.hpp> // AverageOf<T>
|
||||
#include <scalar.hpp> // Scalar
|
||||
#include <alarm.hpp> // GaugeAlarm
|
||||
#include <SCHAIN.hpp> // SChainOf<Component*> (GaugeAlarm54 watcher sockets)
|
||||
class Component; // fwd -- AddAudioWatcher(Component*) defined in heat.cpp
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -91,26 +93,42 @@ class Mech;
|
||||
class GaugeAlarm54
|
||||
{
|
||||
public:
|
||||
GaugeAlarm54(int levels = 0) { levelA = levelB = levels; level = 0; }
|
||||
GaugeAlarm54(int levels = 0)
|
||||
: audioWatcherSocket(0), videoWatcherSocket(0), gaugeWatcherSocket(0)
|
||||
{ levelA = levelB = levels; level = 0; }
|
||||
void Initialize(int levels) { levelA = levelB = levels; level = 0; }
|
||||
void SetLevel(int n) { level = n; }
|
||||
void SetLevel(int n) { if (n != level) { level = n; NotifyWatchers(); } }
|
||||
int GetLevel() const { return level; }
|
||||
int Level() const { return level; }
|
||||
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){ level = (int)n; }
|
||||
void SetState(unsigned n){ SetLevel((int)n); }
|
||||
unsigned GetState() const { return (unsigned)level; }
|
||||
// The binary's 0x54 alarm (FUN_0041b9ec) IS a StateIndicator: its "three
|
||||
// sub-indicators" @0x18/0x2c/0x40 are the audio/video/gauge watcher sockets,
|
||||
// and level@0x14 is currentState. So an AudioStateWatcher can bind to any
|
||||
// subsystem alarm BY NAME (GeneratorState/CondenserState/ReservoirState/...) and
|
||||
// AddAudioWatcher registers on the +0x18 socket; SetLevel fires it on change.
|
||||
// (Defined out-of-line in heat.cpp so this header stays free of Component/iterator.)
|
||||
void AddAudioWatcher(Component *watcher);
|
||||
void AddVideoWatcher(Component *watcher);
|
||||
void AddGaugeWatcher(Component *watcher);
|
||||
private:
|
||||
void NotifyWatchers(); // fire audio/video/gauge watchers (empty sockets == no-op)
|
||||
protected:
|
||||
// Interior mirrors FUN_0041b9ec: base GaugeAlarm header (+0x00), three count
|
||||
// words at +0x0c/+0x10/+0x14, three sub-indicators at +0x18/+0x2c/+0x40. The
|
||||
// STATUS level the binary reads (HeatSink+0x184 == alarm+0x14, GetStatusFlags
|
||||
// @004add30 / PrintState @004ae050) is param_1[5] at +0x14, so `level` lands
|
||||
// there -- a raw `subsystem+0x184` read now returns GetLevel().
|
||||
char _hdr[0x0c]; // +0x00 base GaugeAlarm (vtable + FUN_004178cc header)
|
||||
int levelA; // +0x0c param_1[3] (level count)
|
||||
int levelB; // +0x10 param_1[4]
|
||||
int level; // +0x14 param_1[5] -- the status level (Normal/Deg/Fail)
|
||||
char _tail[0x54 - 0x18]; // +0x18 three sub-indicators to 0x54
|
||||
// Interior mirrors FUN_0041b9ec / StateIndicator: base header (+0x00, a Node in
|
||||
// the binary), three state words @+0x0c/+0x10/+0x14 (== stateCount/oldState/
|
||||
// currentState), three watcher sockets @+0x18/+0x2c/+0x40. level@+0x14 is the
|
||||
// STATUS level the binary reads (HeatSink+0x184 == alarm+0x14). levelB@+0x10 is
|
||||
// left as the weapon "reset source" (LevelCountB) -- SetLevel does NOT touch it,
|
||||
// so weapon alarms are unchanged; only the watcher sockets are now real.
|
||||
char _hdr[0x0c]; // +0x00 base header (vtable + FUN_004178cc header)
|
||||
int levelA; // +0x0c stateCount
|
||||
int levelB; // +0x10 oldState slot / weapon LevelCountB
|
||||
int level; // +0x14 currentState (status level)
|
||||
SChainOf<Component*> audioWatcherSocket; // +0x18
|
||||
SChainOf<Component*> videoWatcherSocket; // +0x2c
|
||||
SChainOf<Component*> gaugeWatcherSocket; // +0x40
|
||||
};
|
||||
|
||||
// The 8-byte modeling type kept ONLY for HUD::statusAlarm (still on the old
|
||||
@@ -543,6 +561,22 @@ inline int
|
||||
static Receiver::MessageHandlerSet& GetMessageHandlers();
|
||||
static SharedData DefaultData;
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Attribute Support -- audio binds an AudioStateWatcher to CondenserState;
|
||||
// it resolves to condenserAlarm (@0x1DC, a 0x54 StateIndicator-compatible
|
||||
// GaugeAlarm54). RefrigerationSimulation SetLevel's it (2/1/0), so the
|
||||
// condenser cycling audio fires on the flow-change pulse. Chained to HeatSink.
|
||||
//
|
||||
public:
|
||||
enum {
|
||||
CondenserStateAttributeID = HeatSink::NextAttributeID,
|
||||
NextAttributeID
|
||||
};
|
||||
private:
|
||||
static const IndexEntry AttributePointers[];
|
||||
public:
|
||||
static AttributeIndexSet& GetAttributeIndex();
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Test Class Support
|
||||
//
|
||||
|
||||
@@ -494,6 +494,25 @@ int
|
||||
//#############################################################################
|
||||
// Shared Data Support
|
||||
//
|
||||
// ReservoirState -> reservoirAlarm (@0x1D0). Chained to HeatSink so the inherited
|
||||
// coolant/temp attrs stay reachable; dense from HeatSink::NextAttributeID.
|
||||
const Reservoir::IndexEntry
|
||||
Reservoir::AttributePointers[]=
|
||||
{
|
||||
ATTRIBUTE_ENTRY(Reservoir, ReservoirState, reservoirAlarm) // @0x1D0 (0x54 StateIndicator-compatible alarm)
|
||||
};
|
||||
|
||||
Reservoir::AttributeIndexSet&
|
||||
Reservoir::GetAttributeIndex()
|
||||
{
|
||||
static Reservoir::AttributeIndexSet attributeIndex(
|
||||
ELEMENTS(Reservoir::AttributePointers),
|
||||
Reservoir::AttributePointers,
|
||||
HeatSink::GetAttributeIndex()
|
||||
);
|
||||
return attributeIndex;
|
||||
}
|
||||
|
||||
Reservoir::SharedData
|
||||
Reservoir::DefaultData(
|
||||
Reservoir::GetClassDerivations(),
|
||||
|
||||
@@ -224,6 +224,19 @@
|
||||
static Derivation *GetClassDerivations(); // name "Reservoir"
|
||||
static SharedData DefaultData;
|
||||
|
||||
// Attribute Support -- audio binds an AudioStateWatcher to ReservoirState;
|
||||
// it resolves to reservoirAlarm (@0x1D0, a 0x54 StateIndicator-compatible
|
||||
// GaugeAlarm54; level@0x1e4 is the inject flag). CoolantSimulation SetLevel's
|
||||
// it, so the coolant-inject audio fires on the inject on/off transition.
|
||||
enum {
|
||||
ReservoirStateAttributeID = HeatSink::NextAttributeID,
|
||||
NextAttributeID
|
||||
};
|
||||
private:
|
||||
static const IndexEntry AttributePointers[];
|
||||
public:
|
||||
static AttributeIndexSet& GetAttributeIndex();
|
||||
|
||||
typedef Reservoir__SubsystemResource SubsystemResource;
|
||||
|
||||
typedef void
|
||||
|
||||
@@ -885,7 +885,8 @@ int
|
||||
const Generator::IndexEntry
|
||||
Generator::AttributePointers[]=
|
||||
{
|
||||
ATTRIBUTE_ENTRY(Generator, OutputVoltage, outputVoltage) // @0x1DC
|
||||
ATTRIBUTE_ENTRY(Generator, OutputVoltage, outputVoltage), // @0x1DC
|
||||
ATTRIBUTE_ENTRY(Generator, GeneratorState, stateAlarm) // @0x1FC (0x54 StateIndicator-compatible alarm; SetLevel fires audio)
|
||||
};
|
||||
|
||||
Generator::AttributeIndexSet&
|
||||
|
||||
@@ -342,6 +342,7 @@ class Generator;
|
||||
public:
|
||||
enum {
|
||||
OutputVoltageAttributeID = HeatSink::NextAttributeID,
|
||||
GeneratorStateAttributeID, // audio binds an AudioStateWatcher -> stateAlarm (0x54 StateIndicator)
|
||||
NextAttributeID
|
||||
};
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user