BT410 5.3.46: the Reservoir crash solved -- *State names must be StateIndicators

The linker map named the faulting function: the crash address minus the CODE
base (0x410000) looked up in btl4opt.map's Publics-by-Value landed inside
AudioStateWatcher::AudioStateWatcher +0x2D.

AudioStateWatcher is AudioWatcherOf<StateIndicator> and its ctor immediately
runs Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this).
So every authored *State name must be published as a StateIndicator --
AlarmIndicator counts, it derives from one -- and pointing one at a plain int
sends that member call through garbage.

That explains both earlier failures: the AlarmIndicator attempt was the right
type but was tested with other bugs still in the batch, and the plain-int
attempt was simply the wrong type and crashed further along.

Published as state objects: Reservoir/ReservoirState -> reservoirAlarm,
Generator/GeneratorState -> stateAlarm, plus new StateIndicator members for
Condenser/CondenserState and Torso/MotionState.  StateIndicator has no
Initialize(); the default ctor suffices because the watcher only needs the
object to exist.

Verified on the pod rig: no crash, ladder advanced to ReportLeak.

Technique worth keeping: on an extender fault, subtract 0x410000 from the
dumped address and look it up in btl4opt.map -- it names the engine function,
and for this family of work that names the watcher class and hence the
required member type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-28 07:42:44 -05:00
co-authored by Claude Fable 5
parent 0d97d8c38b
commit afb9e3f5d9
9 changed files with 73 additions and 11 deletions
-1
View File
@@ -880,7 +880,6 @@ Condenser::Condenser(
// each condenser its share of the total valve opening.
//
valveState = 1;
condenserState = 0; // staged: bound by an authored watcher
coolantFlowScale = 0.0f;
refrigerationFactor = subsystem_resource->refrigerationFactor;
massScale = refrigerationFactor;
+9 -1
View File
@@ -511,7 +511,15 @@
// member goes at the END of the CLASS (never into the resource
// struct above, which is a wire format).
//
int condenserState; // authored watcher: CondenserN/CondenserState
//
// MUST BE A StateIndicator. The authored watcher for a *State
// name is an AudioStateWatcher, which is AudioWatcherOf<StateIndicator>
// and whose ctor immediately runs
// Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this)
// (AUDWTHR.CPP:891). Point it at a plain int and that call goes
// through garbage -- the pod dies on the extender.
//
StateIndicator condenserState; // authored watcher
};
//###########################################################################
+1 -2
View File
@@ -596,7 +596,7 @@ const Generator::IndexEntry
ATTRIBUTE_ENTRY(Generator, OutputVoltage, outputVoltage),
ATTRIBUTE_ENTRY(Generator, RatedVoltage, ratedVoltage),
ATTRIBUTE_ENTRY(Generator, GeneratorNumber, generatorNumber),
ATTRIBUTE_ENTRY(Generator, GeneratorState, generatorState)
ATTRIBUTE_ENTRY(Generator, GeneratorState, stateAlarm)
};
Generator::AttributeIndexSet
@@ -640,7 +640,6 @@ Generator::Generator(
startTimer = startTime;
stateAlarm.SetLevel(GeneratorReady);
generatorOn = 1;
generatorState = 0; // staged
shortRecoveryTime = subsystem_resource->shortRecoveryTime;
shortTimer = shortRecoveryTime;
-1
View File
@@ -479,7 +479,6 @@
Scalar shortRecoveryTime;
Scalar shortTimer;
AlarmIndicator stateAlarm;
int generatorState; // authored watcher: plain int, see RESERVR.HPP
};
#endif
+14 -1
View File
@@ -48,11 +48,24 @@ Reservoir::MessageHandlerSet
HeatSink::MessageHandlers
);
const Reservoir::IndexEntry
Reservoir::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Reservoir, ReservoirState, reservoirAlarm)
};
Reservoir::AttributeIndexSet
Reservoir::AttributeIndex(
ELEMENTS(Reservoir::AttributePointers),
Reservoir::AttributePointers,
HeatSink::AttributeIndex
);
Reservoir::SharedData
Reservoir::DefaultData(
Reservoir::ClassDerivations,
Reservoir::MessageHandlers,
HeatSink::AttributeIndex,
Reservoir::AttributeIndex,
Subsystem::StateCount
);
+9
View File
@@ -111,6 +111,15 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local Data. The inject flag is the reservoir alarm's level (1 = flushing).
//
public:
enum {
ReservoirStateAttributeID = HeatSink::NextAttributeID,
NextAttributeID
};
static const IndexEntry AttributePointers[];
static AttributeIndexSet AttributeIndex;
protected:
AlarmIndicator reservoirAlarm;
Scalar coolantSquirtMass;
-1
View File
@@ -67,7 +67,6 @@ Torso::Torso(
Check(owner);
Check_Pointer(r);
motionState = 0; // staged: no gait state feed yet
isDamagedCopy = (owner->GetInstance() == Entity::ReplicantInstance);
statusFlags = 0;
+9 -1
View File
@@ -159,7 +159,15 @@
Scalar currentElevation;
Scalar analogElevationAxis;
Scalar analogTwistAxis;
int motionState; // staged: authored watcher Torso/MotionState
//
// MUST BE A StateIndicator. The authored watcher for a *State
// name is an AudioStateWatcher, which is AudioWatcherOf<StateIndicator>
// and whose ctor immediately runs
// Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this)
// (AUDWTHR.CPP:891). Point it at a plain int and that call goes
// through garbage -- the pod dies on the extender.
//
StateIndicator motionState; // authored watcher
//
// Twist/elevation dynamics accumulators advanced by TorsoSimulation.
// Reserved until the per-frame sim is reconstructed (phase 5).
+31 -3
View File
@@ -354,6 +354,34 @@ the AttributeIndexSet::Build uninitialised-slot signature, so the next step
is to dump Reservoir's built index set and compare it against a class whose
publication works (Condenser is the control: same base, plain int, no crash).
STATE: Reservoir is REVERTED, the tree is green (clean Fail at ReservoirState,
no crash), and the ladder is blocked there until this is understood. Do not
"fix" it by publishing the name some other way without re-running the bisect.
SOLVED. The linker map named the faulting function outright:
tlink32 -m writes build410/btl4opt.map; the crash address minus the CODE
base (0x410000) is the map offset, and "Publics by Value" gives the
enclosing symbol. 0x3B4AD landed inside
AudioStateWatcher::AudioStateWatcher(PlugStream*,Entity*) +0x2D.
AudioStateWatcher is AudioWatcherOf<StateIndicator> and its ctor runs
Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this)
(AUDWTHR.CPP:891)
immediately. So every authored *State name MUST be published as a
StateIndicator (AlarmIndicator counts -- it derives from one). Point it at a
plain int and that member call goes through garbage and the pod dies on the
extender. That is why BOTH earlier attempts failed: the AlarmIndicator try
was right in type but was tested while other bugs were still in the batch,
and the "plain int" try was wrong in type and merely crashed further along.
Fixed by publishing state objects: Reservoir/ReservoirState -> reservoirAlarm,
Generator/GeneratorState -> stateAlarm, and NEW StateIndicator members for
Condenser/CondenserState and Torso/MotionState. Note StateIndicator has no
Initialize() -- it takes its count in the ctor, and the default ctor is
enough here because the watcher only needs the object to exist (the socket it
attaches to is a member). Verified on the pod: no crash, ladder advanced to
ReportLeak.
THE REUSABLE TECHNIQUE: when the pod dies with an extender fault, take the
address out of the dump, subtract 0x410000, and look it up in
btl4opt.map. It names the exact engine function -- which, for this family of
work, names the watcher class and therefore the required member type.