Ladder rungs, each run-verified on the pod rig: ReportLeak (HeatSink),
GeneratorOn (Generator -- member existed, publication missing),
ConfigureActivePress (MechSubsystem -- likewise), AmmoState +
FireCountdownStarted + the rest of the AmmoBin table.
THE PINNED RANGE IS NOW THE AUTHENTIC SHAPE. Publishing ReportLeak on
HeatSink and ConfigureActivePress on MechSubsystem shifts the chain down two,
and MechWeapon's bridge to its binary-pinned PercentDone (0x12) collapses
from THREE guessed pads to ONE -- which is exactly the arithmetic the shipped
string pool predicts (MechSubsystem 1, HeatableSubsystem 3, HeatSink 6,
PoweredSubsystem 5). Three pads of guesswork replaced by a real attribute
and a real base-class row. HeatableSubsystem and Torso rebase onto
MechSubsystem::AttributeIndex; MechControlsMapper does not (it derives from
Subsystem directly).
Types were chosen deliberately this time, per the AudioWatcher families the
engine instantiates (Motion / Hinge / Scalar / StateIndicator): every *State
name resolves to a StateIndicator, ReportLeak is a Scalar.
RESULT: the pod run now gets past every authored watcher and DRAWS THE FULL
COCKPIT -- sensor cluster, myomers, cooling, the weapon panels, kills/deaths
-- with the board booted and audio running. It then exits without flushing
its redirected stdout, so the exit reason is not yet known; next step is a
run with the redirect removed so the console is readable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
BTL4.RES stores each watcher as a (SUBSYSTEM, ATTRIBUTE) string pair, so the
complete list can be extracted directly instead of discovering one name per
6-minute run. Banked the extraction method and the full normalised table.
Sixth rung climbed: Torso/MotionState (staged member, appended at the end of
Torso's own range so no downstream id moves). LocalVelocity and
LocalAcceleration turn out to need nothing -- the engine's Mover already
publishes them.
Also banked, and deliberately NOT acted on: ReportLeak and
ConfigureActivePress are base-class attributes inside the pinned range, and
chasing them turned up what looks like the authentic attribute layout. The
shipped pool gives MechSubsystem 1, HeatableSubsystem 3, HeatSink 6,
PoweredSubsystem 5 -- which counted from Subsystem::NextAttributeID=2 lands
MechWeapon's real ids on the binary-pinned 0x12 with EXACTLY ONE pad, where
our tree needs three. That arithmetic is strong evidence for the original
layout, and reconciling to it would replace three guessed pads with the real
table. But it moves ids underneath a cockpit currently verified at 98.9%
pixel-identical, so it wants a deliberate session with the A/B rig open
rather than a bulk edit at the end of a long one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Under the documented rig (launch_pod.ps1 with the GL bridge up) the 'couldn't
load object' count went to ZERO. .BGF loading was never a reconstruction
problem, only a missing bridge.
What blocks now is a series of authored AttributeWatchers: BTL4.RES binds
them BY NAME and the engine Fails outright on any that does not resolve, so
each is a name our subsystems must publish. Five rungs climbed, each
run-verified: UnstablePercentage, SpeedEffect (Myomers table), AnimationState
+ CollisionState/CollisionNormal + ReduceButton (Mech), and the full Torso
table (all six authentic names mapped onto existing members).
The method that makes this cheap is banked: the shipped binary's string pool
carries each class's attribute names CONTIGUOUSLY in ID ORDER after the class
name, and in every case so far our member declarations sit in the same order
-- which confirms the layout and lets ids be pinned rather than guessed.
Intersecting those names with BTL4.RES gives the exact work list.
A THIRD miswired SharedData found on the way: Myomers carried Subsystem's
tables where it derives from PoweredSubsystem, the same defect as
MissileLauncher (5.3.33). Worth a sweep across every subsystem.
Staged member TYPES are provisional and documented as such: AttributeWatcherOf
reads *(T*)attributePointer and the instantiation comes from the resource,
which the string pool does not reveal. Nothing drives them yet, so settle the
types with the models that write them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reconstructs the shared skeleton-joint resolver and binds the first subsystem
(Torso) to the live skeleton.
- Mech::ResolveJoint(name) (mech.cpp @00424b60): GetSegment(name) -> segment
joint index -> GetJointSubsystem()->GetJoint(idx).
- Verified the JointedMover base streams the full skeleton headlessly:
jointCount=19, 40 named segments (gun joints jointlgun/jointrgun, shoulders,
hip, leg joints jointlthigh..jointrankle). BT_MECH_LOG [skel] summary;
BT_SKEL_DUMP lists every segment/jointIdx.
- Torso resolves + binds its twist joint (ResolveJoint(torsoHorizontalJoint))
and pushes currentTwist onto it via Joint::SetRotation (hinge->Radian,
ball->EulerAngles yaw). The TEST.EGG mech has a fixed torso (horizJoint='',
enabled=0) so its twist path is inert -- correct + guarded, zero Fail.
Next joint piece: elevation -> gun joints (jointlgun/jointrgun, the fixed-torso
aim mechanism) via weapon-joint binding; then gait leg animation. Both are
VISIBLE only under the renderer; headlessly the joint transforms are loggable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reconstructs Torso::TorsoSimulation (installed as the Torso's per-frame
Performance) and wires the torso aim into MechControlsMapper::InterpretControls,
so the control surface now covers aiming as well as locomotion.
- TORSO: analogElevationAxis/analogTwistAxis + SetAnalog* setters,
CurrentElevation()/GetHorizontalEnabled() accessors. TorsoSimulation slews
currentElevation += analogElevationAxis*baseElevationRate*dt clamped to the
vertical limits, and (if horizontalEnabled) currentTwist by the twist axis
clamped to the horizontal limits (authentic torso.cpp core; the skeleton-joint
application is deferred with the render wave).
- InterpretControls: routes stick pitch (stick_y, squared) into the torso
elevation every mode; Std/Vet route stick yaw into the twist. BT_FORCE_ELEV
dev hook.
Verified: BT_FORCE_ELEV=0.8 -> the elevation slews up and clamps at the authentic
verticalLimitTop = 0.349 rad = 20deg; neutral -> 0; zero Fail.
Deferred: skeleton-joint application (render wave), HUD free-aim slew,
look/eyepoint commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>