BT410 5.3.45: six more watcher names published; the Reservoir one CRASHES (bisected)
Published and verified green on the pod rig: Condenser/CondenserState, Generator/GeneratorState, Emitter/LaserOn, ControlsMapper/TargetRangeExponent, plus the Torso and Myomers tables from the previous pass. Reservoir/ReservoirState CRASHES the pod on the DOS extender, and a bisect pins it to exactly that change: revert it and the run returns to a clean Fail, re-apply it alone and the crash returns. It is reverted; the tree is green and the ladder is blocked there. The important lesson is general: AttributeWatcherOf<T> does currentValue = *(T*)attributePointer AT CONSTRUCTION, so a published name is read the moment its watcher is built. My earlier staging note claimed the provisional types could not matter because nothing drives the values yet -- that is wrong, and this is the counterexample. Ruled out by measurement and recorded so they are not retried: the AlarmIndicator-vs-int type (a plain int got further, 232 -> 749 bytes of log, but still crashed), static-init order (reservr.obj sorts last, after heat), an id gap (contiguous at HeatSink::NextAttributeID), and the gauge rig (same binary runs clean there -- only the pod/arena context faults). Crash signature for whoever picks it up: 0044B4AD, mov eax,[edx+0x18] then call [eax+4], EAX=0x15, fault at 0x19 -- a small integer called through as an object, i.e. the AttributeIndexSet::Build uninitialised-slot pattern. Condenser is the control: same base class, plain int, no crash. Also fixed on the way: staged members must be appended at the END of a class (offset-sensitive readers exist -- condenserNumber is reached as master+0x1d4) and never added to a resource struct, which is a wire format. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,8 @@ Derivation
|
||||
const Emitter::IndexEntry
|
||||
Emitter::AttributePointers[]=
|
||||
{
|
||||
ATTRIBUTE_ENTRY(Emitter, ChargeLevel, chargeLevel)
|
||||
ATTRIBUTE_ENTRY(Emitter, ChargeLevel, chargeLevel),
|
||||
ATTRIBUTE_ENTRY(Emitter, LaserOn, laserOn)
|
||||
};
|
||||
|
||||
Emitter::AttributeIndexSet
|
||||
@@ -93,6 +94,7 @@ Emitter::Emitter(
|
||||
Check_Pointer(subsystem_resource);
|
||||
|
||||
chargeLevel = 0.0f;
|
||||
laserOn = 0; // staged: bound by an authored watcher
|
||||
seekRate = 0.0f;
|
||||
damagePortion = 0.0f;
|
||||
heatPortion = 0.0f;
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
public:
|
||||
enum {
|
||||
ChargeLevelAttributeID = MechWeapon::NextAttributeID, // 0x1D
|
||||
LaserOnAttributeID, // authored watcher: <weapon>/LaserOn
|
||||
NextAttributeID
|
||||
};
|
||||
|
||||
@@ -164,6 +165,7 @@
|
||||
Scalar energyCoefficient;
|
||||
Scalar energyTotal;
|
||||
Scalar damageFraction;
|
||||
int laserOn; // staged: authored watcher <weapon>/LaserOn
|
||||
Scalar damagePortion;
|
||||
Scalar heatPortion;
|
||||
int firingActive;
|
||||
|
||||
@@ -842,7 +842,8 @@ Condenser::MessageHandlerSet
|
||||
const Condenser::IndexEntry
|
||||
Condenser::AttributePointers[]=
|
||||
{
|
||||
ATTRIBUTE_ENTRY(Condenser, ValveSetting, coolantFlowScale)
|
||||
ATTRIBUTE_ENTRY(Condenser, ValveSetting, coolantFlowScale),
|
||||
ATTRIBUTE_ENTRY(Condenser, CondenserState, condenserState)
|
||||
};
|
||||
|
||||
Condenser::AttributeIndexSet
|
||||
@@ -879,6 +880,7 @@ 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;
|
||||
|
||||
@@ -423,6 +423,7 @@
|
||||
public:
|
||||
enum {
|
||||
ValveSettingAttributeID = HeatSink::NextAttributeID, // 0x0A
|
||||
CondenserStateAttributeID, // authored watcher: CondenserN/CondenserState
|
||||
NextAttributeID
|
||||
};
|
||||
|
||||
@@ -504,6 +505,13 @@
|
||||
int valveState;
|
||||
int condenserNumber;
|
||||
Scalar refrigerationFactor;
|
||||
//
|
||||
// APPENDED LAST ON PURPOSE. This class has offset-sensitive
|
||||
// readers -- condenserNumber is reached as master+0x1d4 -- so a new
|
||||
// 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
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
|
||||
@@ -60,7 +60,9 @@ const MechControlsMapper::IndexEntry
|
||||
ATTRIBUTE_ENTRY(MechControlsMapper, ControlMode, controlMode),
|
||||
ATTRIBUTE_ENTRY(MechControlsMapper, DisplayMode, displayMode),
|
||||
ATTRIBUTE_ENTRY(MechControlsMapper, PilotArrayPage, pilotArrayPage),
|
||||
ATTRIBUTE_ENTRY(MechControlsMapper, PilotArray, pilotArray)
|
||||
ATTRIBUTE_ENTRY(MechControlsMapper, PilotArray, pilotArray),
|
||||
ATTRIBUTE_ENTRY(MechControlsMapper, TargetRangeExponent,
|
||||
targetRangeExponent)
|
||||
};
|
||||
|
||||
MechControlsMapper::AttributeIndexSet
|
||||
@@ -119,6 +121,7 @@ MechControlsMapper::MechControlsMapper(
|
||||
displayMode = 0;
|
||||
pilotArrayPage = 0;
|
||||
pilotArray = 0;
|
||||
targetRangeExponent = 0.0f; // staged: bound by an authored watcher
|
||||
lookState = LookNone;
|
||||
previousLookState = LookNone;
|
||||
{
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
DisplayModeAttributeID, // 14
|
||||
PilotArrayPageAttributeID, // 15
|
||||
PilotArrayAttributeID, // 16
|
||||
TargetRangeExponentAttributeID, // authored watcher
|
||||
NextAttributeID
|
||||
};
|
||||
|
||||
@@ -188,7 +189,12 @@
|
||||
int pilotArray;
|
||||
int lookState;
|
||||
int previousLookState;
|
||||
int reserved[22];
|
||||
//
|
||||
// Takes one reserved word (Scalar and int are both 4 bytes here), so
|
||||
// the class size and every existing offset stay exactly as they were.
|
||||
//
|
||||
Scalar targetRangeExponent; // authored watcher
|
||||
int reserved[21];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -595,7 +595,8 @@ const Generator::IndexEntry
|
||||
{
|
||||
ATTRIBUTE_ENTRY(Generator, OutputVoltage, outputVoltage),
|
||||
ATTRIBUTE_ENTRY(Generator, RatedVoltage, ratedVoltage),
|
||||
ATTRIBUTE_ENTRY(Generator, GeneratorNumber, generatorNumber)
|
||||
ATTRIBUTE_ENTRY(Generator, GeneratorNumber, generatorNumber),
|
||||
ATTRIBUTE_ENTRY(Generator, GeneratorState, generatorState)
|
||||
};
|
||||
|
||||
Generator::AttributeIndexSet
|
||||
@@ -639,6 +640,7 @@ Generator::Generator(
|
||||
startTimer = startTime;
|
||||
stateAlarm.SetLevel(GeneratorReady);
|
||||
generatorOn = 1;
|
||||
generatorState = 0; // staged
|
||||
shortRecoveryTime = subsystem_resource->shortRecoveryTime;
|
||||
shortTimer = shortRecoveryTime;
|
||||
|
||||
|
||||
@@ -354,6 +354,7 @@
|
||||
OutputVoltageAttributeID = HeatSink::NextAttributeID, // 0x0A
|
||||
RatedVoltageAttributeID, // 0x0B
|
||||
GeneratorNumberAttributeID, // 0x0C
|
||||
GeneratorStateAttributeID, // authored watcher: GeneratorX/GeneratorState
|
||||
NextAttributeID
|
||||
};
|
||||
|
||||
@@ -478,6 +479,7 @@
|
||||
Scalar shortRecoveryTime;
|
||||
Scalar shortTimer;
|
||||
AlarmIndicator stateAlarm;
|
||||
int generatorState; // authored watcher: plain int, see RESERVR.HPP
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -136,7 +136,6 @@
|
||||
static AttributeIndexSet AttributeIndex;
|
||||
|
||||
protected:
|
||||
int motionState; // staged: no gait state feed yet
|
||||
int isDamagedCopy;
|
||||
int statusFlags;
|
||||
Logical horizontalEnabled;
|
||||
@@ -160,6 +159,7 @@
|
||||
Scalar currentElevation;
|
||||
Scalar analogElevationAxis;
|
||||
Scalar analogTwistAxis;
|
||||
int motionState; // staged: authored watcher Torso/MotionState
|
||||
//
|
||||
// Twist/elevation dynamics accumulators advanced by TorsoSimulation.
|
||||
// Reserved until the per-frame sim is reconstructed (phase 5).
|
||||
|
||||
@@ -316,3 +316,44 @@ deliberate session with the A/B rig open, not a bulk edit at the end of a
|
||||
long one. Neither HeatLoad nor CoolantAvailable is bound by any gauge (0
|
||||
hits in L4GAUGE.CFG), so the drop side looks cheap; the risk is the id shift,
|
||||
not the names.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
A TRAP IN THE WATCHER WORK: publishing a name can CRASH the pod
|
||||
--------------------------------------------------------------------------------
|
||||
Publishing Reservoir/ReservoirState crashes the pod on the DOS extender
|
||||
("Reference to a page you don't own" -> PF), and BISECTING proved it is that
|
||||
one change: revert it and the run returns to a clean Fail; re-apply it alone
|
||||
and the crash returns. Everything else in the same batch (Condenser,
|
||||
Generator, Emitter, ControlsMapper, Torso, Myomers) is innocent and is IN.
|
||||
|
||||
WHY IT MATTERS BEYOND ONE ATTRIBUTE: AttributeWatcherOf<T> does
|
||||
|
||||
currentValue = *(T*)attributePointer; (WATCHER.HPP:306)
|
||||
|
||||
AT CONSTRUCTION. So a published name is READ the moment its watcher is
|
||||
built -- "nothing drives the value yet, so the type cannot matter" (which is
|
||||
what the earlier staging note claimed) is WRONG. A wrong-typed target is a
|
||||
live crash, not a deferred cosmetic issue.
|
||||
|
||||
Things ruled out by measurement, so nobody repeats them:
|
||||
* NOT the AlarmIndicator-vs-int type. Publishing a plain int
|
||||
reservoirState instead got FURTHER (log 232 -> 749 bytes) but still
|
||||
crashed, so the object copy is not the whole story.
|
||||
* NOT static-init order. reservr.obj sorts LAST in the bt lib (build410.sh
|
||||
puts TUs missing from the 1995 order list at the end), so
|
||||
HeatSink::AttributeIndex is constructed before Reservoir's.
|
||||
* NOT an id gap. ReservoirState sits at HeatSink::NextAttributeID with the
|
||||
parent chain contiguous behind it.
|
||||
* NOT visible on the gauge rig. The same binary runs clean under
|
||||
vis_rec.conf -- only the pod/arena context faults, so reproduce it there.
|
||||
|
||||
The crash itself: 0044B4AD, `mov eax,[edx+0x18]` then `call [eax+4]`, with
|
||||
EAX = 0x15 and the fault at 0x19. A small integer is being called through as
|
||||
an object -- an index slot holding an ID where a pointer belongs. That is
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user