BT410 5.3.43: full pod rig -- geometry loads, ladder advances to the audio watchers

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>
This commit is contained in:
Cyd
2026-07-28 01:03:36 -05:00
co-authored by Claude Fable 5
parent 91b043778e
commit 14da9489c2
8 changed files with 296 additions and 4 deletions
@@ -0,0 +1,60 @@
[sdl]
output=opengl
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
priority=higher,higher
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
# emulation-thread stalls (RIO retry recovery) don't audibly chop
rate=44100
blocksize=1024
prebuffer=60
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
# VWE fork namedpipe backend (com0com/realport retired -- COM1/COM2 gone):
# DOSBox = pipe client (retry), vRIO/vPLASMA apps = servers; an unconnected
# pipe behaves as an unplugged cable so the mission still runs. serialnamedpipe.h
serial1=namedpipe pipe:vrio rxpollus:100 rxburst:16
serial2=namedpipe pipe:vplasma
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
c:
cd \REL410\BT
set VIDEOFORMAT=svga
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
rem driver relies on; skipping it left the cards uninitialized (silent).
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
set BLASTER=A220 I5 D1 H5 P330 T6
c:\sb16\diagnose /s
set BLASTER=A240 I7 D3 H6 P300 T6
c:\sb16\diagnose /s
set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r f s p
32rtm.exe -x
BTL4REC.EXE -egg testarn.egg > OUTREC.TXT
32rtm.exe -u
echo ALPHA1-RUN-DONE
pause
+16 -1
View File
@@ -101,7 +101,16 @@ const Mech::IndexEntry
ATTRIBUTE_ENTRY(Mech, LinearSpeed, currentBodySpeed),
ATTRIBUTE_ENTRY(Mech, MaxRunSpeed, reverseStrideLength),
ATTRIBUTE_ENTRY(Mech, DuckState, duckState),
ATTRIBUTE_ENTRY(Mech, EyepointRotation, eyepointRotation)
ATTRIBUTE_ENTRY(Mech, EyepointRotation, eyepointRotation),
ATTRIBUTE_ENTRY(Mech, UnstablePercentage, unstablePercentage),
ATTRIBUTE_ENTRY(Mech, CollisionSpeed, collisionSpeed),
ATTRIBUTE_ENTRY(Mech, DistanceToMissile, distanceToMissile),
ATTRIBUTE_ENTRY(Mech, FootStep, footStep),
ATTRIBUTE_ENTRY(Mech, IncomingLock, incomingLock),
ATTRIBUTE_ENTRY(Mech, CollisionState, collisionState),
ATTRIBUTE_ENTRY(Mech, CollisionNormal, collisionNormal),
ATTRIBUTE_ENTRY(Mech, AnimationState, animationState),
ATTRIBUTE_ENTRY(Mech, ReduceButton, reduceButton)
};
Mech::AttributeIndexSet
@@ -224,6 +233,12 @@ Mech::Mech(
radarLinearPosition = &localOrigin.linearPosition;
radarAngularPosition = &localOrigin.angularPosition;
duckState = 0;
unstablePercentage = 0.0f; // staged: no instability model yet
collisionSpeed = 0.0f; // staged: audio watcher set (see MECH.HPP)
distanceToMissile = 0.0f;
footStep = 0;
incomingLock = 0;
reduceButton = 0;
lastInflictingDamage = 0.0f;
//
+54
View File
@@ -190,6 +190,26 @@
// was missing.
//
EyepointRotationAttributeID,
//
// Bound BY NAME by an authored AttributeWatcher (BTL4.RES); the
// shipped binary carries the same string in its pool.
//
UnstablePercentageAttributeID,
CollisionSpeedAttributeID,
DistanceToMissileAttributeID,
FootStepAttributeID,
IncomingLockAttributeID,
//
// The remaining names BTL4.RES binds watchers to. The two
// state indicators already existed as members -- only the
// publication was missing, same as EyepointRotation.
// AnimationState is bound 307 times in the resource: it is how
// the audio system follows the gait.
//
CollisionStateAttributeID,
CollisionNormalAttributeID,
AnimationStateAttributeID,
ReduceButtonAttributeID,
NextAttributeID
};
@@ -502,6 +522,40 @@
Point3D *radarLinearPosition;
Quaternion *radarAngularPosition;
int duckState;
//
// STAGED VALUE (the instability model is not reconstructed).
// The mech streams the authored unstable* effect constants in its
// resource (maxUnstableAcceleration and friends) but nothing yet
// computes how close the mech is to going over. It is PUBLISHED
// because an authored AttributeWatcher in BTL4.RES binds
// "UnstablePercentage" by name and the engine Fails outright when
// the name does not resolve (WATCHER.CPP:141). Holds 0 = stable
// until the model lands.
//
Scalar unstablePercentage;
//
// The AUDIO watcher set, staged alongside unstablePercentage.
// BTL4.RES binds exactly six attribute watchers by name --
// UnstablePercentage, CollisionSpeed, FootStep, IncomingLock,
// DistanceToMissile (Mech) and SpeedEffect (Myomers) -- and the
// engine Fails outright on any that does not resolve.
// These are the sound triggers: impact volume, footfalls, the
// missile-lock warning, the myomer whine.
//
// TYPES ARE PROVISIONAL. AttributeWatcherOf<T> reads
// *(T*)attributePointer (WATCHER.HPP:288) and the instantiation
// comes from the resource, which we cannot read off the string
// pool. Nothing drives these yet, so nothing ever changes and no
// watcher can fire -- settle the types WITH the models that write
// them (collision response, the gait FSM, the missile threat
// track), not before.
//
Scalar collisionSpeed;
Scalar distanceToMissile;
int footStep;
int incomingLock;
UnitVector collisionNormal; // staged with collisionSpeed
int reduceButton; // staged: cockpit button feed
//
// The model's authored look-view angles (rad; deg in the resource).
+29 -2
View File
@@ -25,11 +25,38 @@ Derivation
"Myomers"
);
const Myomers::IndexEntry
Myomers::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Myomers, SpeedEffect, speedEffect),
ATTRIBUTE_ENTRY(Myomers, CurrentSeekVoltageIndex, currentSeekVoltageIndex),
ATTRIBUTE_ENTRY(Myomers, RecommendedSeekVoltageIndex,
recommendedSeekVoltageIndex),
ATTRIBUTE_ENTRY(Myomers, MinSeekVoltageIndex, minSeekVoltageIndex),
ATTRIBUTE_ENTRY(Myomers, MaxSeekVoltageIndex, maxSeekVoltageIndex),
{ (int)Myomers::SeekVoltageAttributeID, "SeekVoltage",
(Simulation::AttributePointer)&Myomers::seekVoltage }
};
Myomers::AttributeIndexSet
Myomers::AttributeIndex(
ELEMENTS(Myomers::AttributePointers),
Myomers::AttributePointers,
PoweredSubsystem::AttributeIndex
);
//
// The shared data must carry the POWERED tables, not Subsystem's -- the
// same miswiring found on MissileLauncher (5.3.33). Myomers derives from
// PoweredSubsystem, so wiring it to Subsystem's dropped every powered
// attribute AND the powered message handlers (ToggleSeekVoltage and the
// generator-select family) on the way past.
//
Myomers::SharedData
Myomers::DefaultData(
Myomers::ClassDerivations,
Subsystem::MessageHandlers,
Subsystem::AttributeIndex,
PoweredSubsystem::MessageHandlers,
Myomers::AttributeIndex,
Subsystem::StateCount
);
+27
View File
@@ -69,6 +69,33 @@
);
~Myomers();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute publication. The shipped binary's string pool lists this
// class's attribute names contiguously, immediately after the class name
// "Myomers" and its ToggleSeekVoltage message:
//
// SpeedEffect CurrentSeekVoltageIndex RecommendedSeekVoltageIndex
// MinSeekVoltageIndex MaxSeekVoltageIndex SeekVoltage
//
// -- which is EXACTLY the order of the members below, so the
// reconstruction's layout matches the original and the ids can be
// pinned faithfully rather than guessed. SpeedEffect is the one an
// authored AttributeWatcher in BTL4.RES binds (the myomer whine).
//
public:
enum {
SpeedEffectAttributeID = PoweredSubsystem::NextAttributeID,
CurrentSeekVoltageIndexAttributeID,
RecommendedSeekVoltageIndexAttributeID,
MinSeekVoltageIndexAttributeID,
MaxSeekVoltageIndexAttributeID,
SeekVoltageAttributeID,
NextAttributeID
};
static const IndexEntry AttributePointers[];
static AttributeIndexSet AttributeIndex;
protected:
Scalar speedEffect;
int currentSeekVoltageIndex;
+19 -1
View File
@@ -29,11 +29,29 @@ Derivation
"Torso"
);
const Torso::IndexEntry
Torso::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Torso, RotationOfTorsoVertical, currentElevation),
ATTRIBUTE_ENTRY(Torso, RotationOfTorsoHorizontal, currentTwist),
ATTRIBUTE_ENTRY(Torso, HorizontalLimitRight, horizontalLimitRight),
ATTRIBUTE_ENTRY(Torso, HorizontalLimitLeft, horizontalLimitLeft),
ATTRIBUTE_ENTRY(Torso, SpeedOfTorsoVertical, baseElevationRate),
ATTRIBUTE_ENTRY(Torso, SpeedOfTorsoHorizontal, baseTwistRate)
};
Torso::AttributeIndexSet
Torso::AttributeIndex(
ELEMENTS(Torso::AttributePointers),
Torso::AttributePointers,
Subsystem::AttributeIndex
);
Torso::SharedData
Torso::DefaultData(
Torso::ClassDerivations,
Subsystem::MessageHandlers,
Subsystem::AttributeIndex,
Torso::AttributeIndex,
Subsystem::StateCount
);
+29
View File
@@ -100,6 +100,35 @@
);
~Torso();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute publication. The shipped string pool lists this class's
// attribute names contiguously after the class name "Torso":
//
// RotationOfTorsoVertical RotationOfTorsoHorizontal
// HorizontalLimitRight HorizontalLimitLeft
// SpeedOfTorsoVertical SpeedOfTorsoHorizontal
//
// (the TorsoUp/Down/Left/Right/Center that follow are MESSAGES, not
// attributes). All six map onto members we already carry. An
// authored AttributeWatcher binds SpeedOfTorsoHorizontal.
//
// The chain -- PowerWatcher -> HeatWatcher -> MechSubsystem -- publishes
// nothing of its own, so these start at Subsystem::NextAttributeID.
//
public:
enum {
RotationOfTorsoVerticalAttributeID = Subsystem::NextAttributeID,
RotationOfTorsoHorizontalAttributeID,
HorizontalLimitRightAttributeID,
HorizontalLimitLeftAttributeID,
SpeedOfTorsoVerticalAttributeID,
SpeedOfTorsoHorizontalAttributeID,
NextAttributeID
};
static const IndexEntry AttributePointers[];
static AttributeIndexSet AttributeIndex;
protected:
int isDamagedCopy;
int statusFlags;
@@ -177,3 +177,65 @@ NEXT RUNG: run under the documented full rig (render-bridge/launch_pod.ps1,
see LAUNCH.md) so the bridge is up and geometry actually loads. Only then is
it worth writing real MakeEntityRenderables content -- until the bridge is in
the loop there is nothing to see even if the scene graph is correct.
--------------------------------------------------------------------------------
THE FULL POD RIG: geometry loads, and the ladder is now the AUDIO WATCHER SET
--------------------------------------------------------------------------------
Run under the documented rig (render-bridge/launch_pod.ps1 with the GL bridge
up) the geometry complaint COUNT WENT TO ZERO -- .BGF loading was never a
reconstruction problem, only a missing bridge, exactly as predicted.
emulator/render-bridge/gauge_arena_rec.conf = gauge_arena_sound.conf with
the exe swapped to BTL4REC.EXE (our build, staged into ALPHA_1/REL410/BT
beside the shipped BTL4OPT.EXE, which is NOT touched).
Launch: .\launch_pod.ps1 -Conf ...\gauge_arena_rec.conf
What blocks now is a SERIES of authored AttributeWatchers. BTL4.RES binds
watchers BY NAME and the engine Fails outright on any that does not resolve
(WATCHER.CPP:141) -- these are the sound triggers, so every one of them is a
name our subsystems must publish.
RUNGS CLIMBED THIS PASS (each one run-verified, ~6 min per cycle):
UnstablePercentage Mech, staged member (no instability model yet)
SpeedEffect Myomers -- member existed, table published
AnimationState Mech -- member existed (bound 307x in the RES!)
CollisionState/Normal Mech -- state existed, normal staged
ReduceButton Mech, staged
SpeedOfTorsoHorizontal Torso -- all six names mapped to real members
THE METHOD THAT MAKES THIS CHEAP (use it instead of one rung per run):
The shipped binary's string pool carries each class's attribute names
CONTIGUOUSLY, in ID ORDER, right after the class name and its message names.
So: strings -a BTL4OPT.EXE | grep -n -A14 '^Torso$' gives the authentic
table for that class, and in every case so far our member declarations sit in
the SAME ORDER -- which both confirms the reconstruction's layout and lets the
ids be pinned rather than guessed. Cross-reference against the resource with
comm -12 <(strings BTL4.RES|grep -x '[A-Z][A-Za-z0-9]\{3,\}'|sort -u) <(strings BTL4OPT.EXE|...|sort -u)
which returns 59 candidate names; the attribute-like ones are the work list.
STILL TO PUBLISH (from that intersection, owner in brackets where known):
MotionState [Torso? -- sits between StickPosition and
TorsoHorizontalEnabled in the pool, but that region MIXES attribute
names with resource-field names, so confirm before publishing]
LocalVelocity, LocalAcceleration [Mover/Entity]
AmmoState [AmmoBin] CondenserState [Condenser]
ReservoirState [Reservoir] GeneratorState [Generator]
SimulationState [Subsystem] DisplayMode [ControlsMapper]
LookForward/Left/Right/Down/Behind [MechControlsMapper]
LaserOn, ReportLeak, FireCountdownStarted, TargetRangeExponent,
ConfigureActivePress, StickPosition
A THIRD MISWIRED SharedData FOUND ON THE WAY: Myomers carried
Subsystem::MessageHandlers + Subsystem::AttributeIndex where it derives from
PoweredSubsystem -- the same defect as MissileLauncher (5.3.33), dropping
every powered attribute AND the powered message handlers. Torso is wired to
Subsystem's too, but there the chain (PowerWatcher -> HeatWatcher ->
MechSubsystem) genuinely publishes nothing, so only its AttributeIndex moved.
WORTH A SWEEP: check every subsystem's DefaultData against its actual parent.
TYPES ARE PROVISIONAL on the staged members. AttributeWatcherOf<T> reads
*(T*)attributePointer (WATCHER.HPP:288) and the instantiation comes from the
resource, which the string pool does not reveal. Nothing drives these yet so
no watcher can fire -- settle the types WITH the models that write them.