gauges: publish Mech attribute table (dense prefix 0x15..0x21 -> LinearSpeed live)

mech.cpp shipped an EMPTY, unchained Mech::AttributeIndex (a default-constructed
AttributeIndexSet passed to DefaultData), so Simulation::GetAttributePointer
severed the whole parent chain -> EVERY bare Mech gauge binding (LinearSpeed /
MaxRunSpeed / DuckState / RadarRange / ...) resolved to NullAttribute.

Add the Mech AttributeID enum (full binary set 0x15..0x38, VA 0x50be84) + a
Mech::AttributePointers[] table + Mech::GetAttributeIndex() chained to
JointedMover::GetAttributeIndex(), and change DefaultData to pass it.

DENSE-TABLE HAZARD (systemic): AttributeIndexSet::Build (SIMULATE.cpp:565) sizes
the built index to max(id) and Find (SIMULATE.cpp:663) strcmps EVERY slot, and
Build does NOT zero gap slots -> an unpublished id between the parent's
NextAttributeID and the max published id holds a garbage entryName -> AV on the
next name lookup.  So the table is a DENSE PREFIX 0x15..0x21 (JointedMover's
NextAttributeID through LinearSpeed): the ids the BLH cockpit doesn't bind
(collision/eyepoint/reticle/footstep/anim-state) point at one shared read-only
attrPad member; CurrentSpeed/MaxRunSpeed bind to the existing gait members
legCycleSpeed/reverseStrideLength; LinearSpeed binds a new linearSpeed member
populated each frame in PerformAndWatch (|adv|/dt = forward ground speed).  Ids
0x22..0x38 stay declared in the enum for later extension (RadarRange/DuckState
when the map/duck widgets land) but are NOT published yet.  New members appended
to Mech's own region (no locked base/gait offset shifts); inited in the ctor.

Verified live (BT_DEV_GAUGES + drive): the radar-surface SPEED readout
(numericSpeed(LinearSpeed), a base engine primitive already drawing NULL) now
shows 225 (~63 u/s in the widget's display units) instead of 0, tracking the
mech's motion.  Heat surface CurrentTemperature=77 un-regressed.  Combat un-
regressed (TARGET DESTROYED after 8 hits), 0 attribute-not-found, and Mech
construction + attribute-index build is heap-clean under BT_HEAPCHECK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-06 18:26:58 -05:00
co-authored by Claude Opus 4.8
parent 9ea4bdaf50
commit 9e31408ba2
3 changed files with 109 additions and 4 deletions
+43 -3
View File
@@ -399,14 +399,52 @@ Derivation
Receiver::MessageHandlerSet
Mech::MessageHandlers(Entity::GetMessageHandlers());
Mech::AttributeIndexSet
Mech::AttributeIndex;
//
// Mech attribute table. DENSE PREFIX 0x15..0x21 (JointedMover::NextAttributeID
// through LinearSpeed): the built index is a dense array and Find strcmps every
// slot, so the table must be contiguous from the parent's NextAttributeID up to
// the highest id published -- no gaps. The ids the BLH cockpit does not consume
// (collision/eyepoint/reticle/footstep/anim state) bind to the shared read-only
// attrPad so the slot is a valid, named, non-crashing entry; CurrentSpeed and
// MaxRunSpeed bind to the existing gait members; LinearSpeed binds to the live
// forward-speed member the speed gauges read. (Ids 0x22..0x38 remain declared in
// the enum for later extension -- e.g. RadarRange/DuckState when the map/duck
// widgets are reconstructed -- but are NOT published yet.)
//
const Mech::IndexEntry
Mech::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Mech, MaxAcceleration, attrPad), // 0x15
ATTRIBUTE_ENTRY(Mech, CollisionState, attrPad), // 0x16
ATTRIBUTE_ENTRY(Mech, CollisionNormal, attrPad), // 0x17
ATTRIBUTE_ENTRY(Mech, CollisionSpeed, attrPad), // 0x18
ATTRIBUTE_ENTRY(Mech, CollisionMaterialType, attrPad), // 0x19
ATTRIBUTE_ENTRY(Mech, CurrentSpeed, legCycleSpeed), // 0x1a (existing @0x348)
ATTRIBUTE_ENTRY(Mech, MaxRunSpeed, reverseStrideLength), // 0x1b (existing @0x34c = run/top speed)
ATTRIBUTE_ENTRY(Mech, EyepointRotation, attrPad), // 0x1c
ATTRIBUTE_ENTRY(Mech, TargetReticle, attrPad), // 0x1d
ATTRIBUTE_ENTRY(Mech, FootStep, attrPad), // 0x1e
ATTRIBUTE_ENTRY(Mech, AnimationState, attrPad), // 0x1f
ATTRIBUTE_ENTRY(Mech, ReplicantAnimationState, attrPad), // 0x20
ATTRIBUTE_ENTRY(Mech, LinearSpeed, linearSpeed) // 0x21 (live forward speed)
};
Mech::AttributeIndexSet&
Mech::GetAttributeIndex()
{
static Mech::AttributeIndexSet attributeIndex(
ELEMENTS(Mech::AttributePointers),
Mech::AttributePointers,
JointedMover::GetAttributeIndex()
);
return attributeIndex;
}
Mech::SharedData
Mech::DefaultData( // @0050bde4
&Mech::ClassDerivations,
Mech::MessageHandlers,
Mech::AttributeIndex,
Mech::GetAttributeIndex(),
Mech::StateCount,
(Entity::MakeHandler)Mech::Make // Entity__SharedData adds the make-callback
);
@@ -532,6 +570,8 @@ Mech::Mech(
hudSubsystem = 0; // this[0x16d] (0xBD6 -> HUD; was misnamed mechTechSubsystem)
forwardThrottleScale = 1.0f; // @0x5c0 mapper speed scale (writer not in decomp; neutral)
sensorSubsystem = 0; // this[0x1f7] (0xBBE) -- same hazard, zero it too
attrPad = 0.0f; // shared read pad for un-populated gauge attributes
linearSpeed = 0.0f; // live forward ground speed (LinearSpeed gauge); set per frame
ZeroBlock(this + 0xca, 6); // this[0xca..0xcf] = 0
ZeroBlock(this + 0x153, 6); // this[0x153..0x158] = 0
+64 -1
View File
@@ -503,9 +503,72 @@ protected:
//
public:
static Receiver::MessageHandlerSet MessageHandlers; // SharedData ctor operand
static AttributeIndexSet AttributeIndex; // SharedData ctor operand
enum { StateCount = 0x21 };
//
// Attribute Support -- the named attributes the cockpit gauges bind to.
// The config (content/GAUGE/L4GAUGE.CFG) binds the bare names LinearSpeed /
// MaxRunSpeed / DuckState / RadarRange / RadarLinearPosition /
// RadarAngularPosition, resolved by Simulation::GetAttributePointer ->
// activeAttributeIndex->Find(name). The reconstruction shipped an EMPTY,
// unchained Mech::AttributeIndex, so EVERY bare Mech gauge (and even the
// inherited attributes) resolved to NullAttribute.
//
// The full binary id set (0x15..0x38, table VA 0x50be84) is declared so the
// published table can be extended without renumbering. The chain starts at
// JointedMover::NextAttributeID (0x15); ids MUST stay contiguous, because the
// built index is a dense array sized to max(id) and AttributeIndexSet::Find
// strcmps EVERY slot -- an unfilled slot holds a garbage entryName -> AV.
//
enum {
MaxAccelerationAttributeID = JointedMover::NextAttributeID, // 0x15
CollisionStateAttributeID, // 0x16
CollisionNormalAttributeID, // 0x17
CollisionSpeedAttributeID, // 0x18
CollisionMaterialTypeAttributeID, // 0x19
CurrentSpeedAttributeID, // 0x1a
MaxRunSpeedAttributeID, // 0x1b
EyepointRotationAttributeID, // 0x1c
TargetReticleAttributeID, // 0x1d
FootStepAttributeID, // 0x1e
AnimationStateAttributeID, // 0x1f
ReplicantAnimationStateAttributeID, // 0x20
LinearSpeedAttributeID, // 0x21
ClimbRateAttributeID, // 0x22
AccelerationLastFrameAttributeID, // 0x23
AngularSpeedAttributeID, // 0x24
ArmorDamageLevelAttributeID, // 0x25
SubsystemDamageLevelAttributeID, // 0x26
MyomerDamageLevelAttributeID, // 0x27
TestButton1AttributeID, // 0x28
TestButton2AttributeID, // 0x29
TestButton3AttributeID, // 0x2a
TestButton4AttributeID, // 0x2b
TestButton5AttributeID, // 0x2c
TestButton6AttributeID, // 0x2d
ReduceButtonAttributeID, // 0x2e
RadarRangeAttributeID, // 0x2f
RadarLinearPositionAttributeID, // 0x30
RadarAngularPositionAttributeID, // 0x31
RearFiringAttributeID, // 0x32
RequestDuckAnimationAttributeID, // 0x33
UnstablePercentageAttributeID, // 0x34
SuperStopAttributeID, // 0x35
IncomingLockAttributeID, // 0x36
DuckStateAttributeID, // 0x37
DistanceToMissileAttributeID, // 0x38
NextAttributeID // 0x39
};
static const IndexEntry AttributePointers[];
static AttributeIndexSet& GetAttributeIndex();
// Cockpit-gauge attribute backing members (APPENDED to Mech's own region --
// never at a locked base/gait offset). attrPad is a shared read-only 0 for
// the dense-prefix ids the BLH cockpit does not bind; linearSpeed is the live
// forward ground speed the speed readout / arc / radar consume (binary @0x81c).
Scalar attrPad;
Scalar linearSpeed;
// --- raw / engine-shim members touched by the ctor slice -------------
void *vtable; // installed Mech vtable ptr
Word instanceFlags; // entity instance flags (this+0x18 region)
+2
View File
@@ -1290,6 +1290,7 @@ void
// leg channel keeps running as the local sim it is in the binary
// (its joint writes land first and are overwritten by the body's).
const Scalar localAdv = adv * dir;
linearSpeed = (localAdv < 0.0f ? -localAdv : localAdv) * invDt; // forward ground speed -> LinearSpeed gauge
Vector3D localVel(0.0f, 0.0f, -localAdv * invDt); // exact frame distance as velocity
Matrix34 orient; // rotation from the heading (set @ line ~626)
Matrix34::FromQuaternion(&orient, &localOrigin.angularPosition);
@@ -1348,6 +1349,7 @@ void
: ((throttle < 0.0f) ? -1.0f : 1.0f);
localOrigin.linearPosition.x += fx * adv * dir;
localOrigin.linearPosition.z += fz * adv * dir;
linearSpeed = (dt > 0.0f) ? ((adv < 0.0f ? -adv : adv) / dt) : 0.0f; // LinearSpeed gauge
localToWorld = localOrigin; // rebuild with the new position
gBodyAnimLog += dt;