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>
The gauge framebuffer is PLANE-PACKED: L4GAUGE.CFG gives each port a bit
mask, not a rectangle, so all six heads share the same x/y and are separated
only by bit -- the packing the VDB splits into the pod's physical screens.
Comparing RGB was therefore measuring nothing useful; the "magenta artifact"
just meant the wrong heads were lit at that pixel.
New instruments (emulator/render-bridge/gauge-ab/, with a README):
planes.py per-head scoreboard -- shipped vs ours, missing vs extra, per
port mask, recovering the 16-bit word from DOSBox's RGB565
BT_VIS_LOG a complete cockpit widget map from the single dispatch every
gauge widget is built through (MethodDescription::Execute),
printing name + port + authored position
ab.sh stages the fresh build over BTL4REC.EXE and kills any running
DOSBox before launching -- see below
The fix: the Comm page's pilot roster is the POD roster (the viewpoint mech's
ControlsMapper pilot array, mapper attributes 15/16), not the mission's
player list. It is zero in a solo mission, so the shipped page draws empty;
ours resolved the local player and painted its icon in colour 0xff. The gate
belongs in the row source, not PilotList::Execute -- Execute's empty branch
still has to run, because erasing is what the shipped page draws.
head shipped ours missing extra (was)
Eng1 17981 17981 0 0 (0 / 1030)
Eng2 17981 17981 0 0
Eng3 17981 17981 0 0
Comm 15656 13557 2099 0 (2099 / 1253)
Heat 21374 20913 566 105 (566 / 990)
Title-band magenta 404 -> 0. The 2099 Comm pixels we are missing were
missing before the gate, so it is a strict improvement.
Method note, which cost more than the bug: the rig's confs run BTL4REC.EXE
out of the mount while the build writes build410/btl4opt.exe, and nothing
connected the two. Three measurements ran an hour-old binary, so a change
that "did nothing" three times had never been tested -- and the correct first
hypothesis was discarded on that evidence. ab.sh now re-stages every launch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reconstructs the binary's five-state LOOK machine (controls mapper tail,
part_013.c:396-459) as proper members/methods:
- MECHMPPR: LookState enum (None/Left/Right/Behind/Down) + lookState/
previousLookState (out of reserved[24] -> [22]). InterpretControls picks the
state from the look buttons each frame and on a CHANGE calls
Mech::CommitLookState. BT_FORCE_LOOK=<1..4> dev hook.
- MECH: authored look angles lookLeft/Right/Front/BackAngle (deg->rad from the
GameModel resource, guarded) + lookPitch/lookYaw + CommitLookState(int):
side looks yaw by the authored angle, look-behind = yaw pi + lookBackAngle
pitch, look-down = lookFrontAngle pitch, forward = identity. The per-frame
eyepoint compose in Simulate adds the live Torso elevation on top.
(reservedState [208] -> [202].)
- Deferred inside the commit (needs MechWeapon viewFireEnable/rearFiring):
per-view weapon fire re-arm + HUD pip group-mask flip.
Verified headlessly: model reads clean authored angles (L=60 R=-60 F=-10 B=0
deg -- ModelResource layout confirmed again); BT_FORCE_LOOK=3 fires ONE commit
([look] state=3 yaw=3.14159 pitch=0) and the per-frame compose holds eyeYaw=pi
with eyePitch=0.349066 (lookBackAngle + the 20deg-clamped elevation). Zero Fail.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reconstructed the MechControlsMapper attribute table (StickPosition..PilotArray,
IDs 3-0x16 chained from Subsystem::NextAttributeID==2) + the real members
(stickPosition ControlsJoystick, throttle/pedals/speed/turn Scalars, the look/
torso ControlsButtons, control/display/pilotArray ints) + AttributePointers[] +
AttributeIndex (chains Subsystem::AttributeIndex). Fixed the mapper hierarchy
statics in BTL4MPPR.CPP: L4/RIO/Thrustmaster ClassDerivations now chain correctly
(L4 defined first for static-init order) and all use MechControlsMapper::
AttributeIndex so the mapper instance publishes the control attributes the
streamed mappings bind to.
Mech still constructs (33 subsystems). The post-construction crash is UNCHANGED
(GetAttributePointer, attribute=0x13) -> so it's NOT the mapper; a streamed
AttributeWatcher (numeric ID 19) resolves on another object via GetSimulation.
Next diagnostic: which object/subsystemID the watcher targets. BT: 42 ok.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All 10 surviving original TUs plus the reconstructed BTL4APP.CPP pilot build
clean with the authentic OPT.MAK flags (compile410.sh --sweep). First time
the 4.10 BattleTech source has compiled since 1996.
- layout_probe.cpp: the period compiler measures 1995 layouts from the
surviving headers; validated 3/3 against binary alloc sizes (BTMission
0xFC, BTL4ModeManager 0xC, BTRegistry 0x10). Base boundary: Simulation
0xD0 / Entity 0x1C4 / Mover 0x300 / JointedMover 0x328 => Mech-own region
0x328-0x854 (MECH-LAYOUT.md staging worksheet).
- 13 staged headers close the family ([T3] reserved[]-parked layouts;
interfaces PROVEN by the surviving consumers): MECH, MECHSUB, HEAT,
POWERSUB, MECHWEAP, EMITTER, MECHMPPR, BTPLAYER (BTPlayer__MakeMessage =
Player's 8 args + roleName/teamName [T1 via BTREG]), PROJTILE, MISSILE,
BTL4MPPR, BTL4VID + the round-2 BTCNSL/BTSCNRL.
- compile410.sh: PCH gate retired (bt.hpp/mungal4.hpp full include sets),
DPL SDK roots on the include line.
- BTL4APP.CPP: appmgr include + GetApplicationManager()->GetFrameRate() +
ResourceDescription:: scoping; Fail() held at its recorded line 400.
- Evidence ledgers: STAGED-HEADERS.NOTES.md, MECH-LAYOUT.md, BACKFILLS,
BTCNSL (binary-recovered console wire IDs), BTSCNRL.
Remaining for a linkable BTL4OPT: the ~40 missing TU bodies (917-function
manifest) - the header skeleton they grow into now exists and compiles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>