The heat economy now bites back, verified under sustained fire:
- Ballistic gate 1 (@4bbd36): destroyed-state or own-sink FailureHeat pins
full recoil + the unavailable alarm (the NoAmmo roach-motel re-asserts).
- CheckForJam (@4bbfcc): p = 0.41*T/failT clamped [minJamChance, 1.0], rolled
per granted shot against the MUNGA uniform Random. Interim heat-degraded
gate stands in for the deferred LiveFireEnabled novice switch (the exact
spurious-cold-jam trap the BT411 port documented). VERIFIED: SRM6s jam at
T~1200-1320 after riding past the authored 1000-degree threshold, and stay
jammed (mission-reset recovery only) -- authentic.
- Emitter hard gate (@4baab9): FailureHeat drops the beam state + charge each
frame; SELF-RECOVERING once conduction cools the sink below failure.
VERIFIED: the PPC settles into an emergent thermal duty cycle -- fire at
~1930K, spike to 2562K, shutdown, cool, refire -- firing exactly as fast as
its sink sheds heat; the lasers oscillate around ~2200K.
Zero Fail. Deferred: LiveFireEnabled/HeatModelOff experience gates,
mech-disabled gate halves, coolant depletion, jam recovery via mission reset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Weapons dump firing heat into their own sinks, sinks conduct through the
Condenser bank into the central HeatSink, temperatures drive the degradation/
failure alarms, and every powered subsystem tracks its generator through the
electrical state machine. Verified: the authentic fire-discipline game -- a PPC
spikes 77->709K per shot, relaxes to 441K across its 5s reload, and sustained
fire climbs 441->674->838->960K, brushing the authored 1000K degradation
threshold. All calibration values match the BT411 audit exactly (central bank
1.39e6, PPC sink 174000, thresholds 77/1000/2000).
- HEAT: HeatSinkSimulation (@004ad924 absorb->T->load->conduct->alarm),
ConductHeat/ComputeHeatFlow (@004ad8ac/@004ad9ec two-body equilibrium
relaxation; flow==0 exactly at uniform T), UpdateHeatLoad (15-sample filter);
heat-state enum + accessors; installed by the HeatSink ctor.
- WIRE-VERIFIED: HeatSink resource gains linkedSinkIndex -- THE missing
ancestry int that shifted every descendant block +1 (voltageSourceIndex had
been reading the linked-sink index: "power sources" appeared to be
Condensers). Conduction topology wired from it at construction:
weapons->Condensers1-6->central; Generators->Condensers; Reservoir->central.
- MECHWEAP resource: authentic pip tail (pipPosition int + pipColor 3 floats +
pipExtendedRange int) per the BT411 verified overlay; with linkedSinkIndex
this closes the whole +3 alignment mystery (ProjectileWeapon pad deleted).
True bhk1 reads: PPC recharge 5.0s (not 1.0), discharge 0.99s, range 900.
- POWERSUB: ctor resolves voltageSourceIndex -> GeneratorA-D (wire-verified),
taps via Generator::TapVoltageSource (-1 when full); PoweredSubsystemSimulation
(@004b0bd0) electrical FSM (Starting/NoVoltage/Shorted/GeneratorOff/Ready);
GeneratorSimulation partial (start/short-recovery timers).
- Weapons: power step at sim head; Loading recharge gated on electrical Ready
(authentic @4bbdf5); FireWeapon dumps firing heat. HEAT UNITS: the chain is
1e7-native with TWO authoring conventions -- energy weapons store small
(PPC=11, x1e7 from the energy algebra), ballistics store native (SRM6=5.06e7,
dumped raw; double-scaling it was the bring-up runaway-temperature bug).
- SENSOR: authentic gating (@004b1c4c) -- power step, electrical-Ready gate,
heat-state switch (Degradation x0.5 / Failure 0). Verified Ready + 100%.
Deferred: coolant depletion/venting, the novice HeatModelOff gate, the charge
model (TrackSeekVoltage/voltage sag/I2R), weapon gate 1 + jam roll, the central
sink's forward-linked drain. Zero Fail across fire + neutral runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SRM6 MissileLaunchers now run the authentic ballistic fire cycle against
their linked AmmoBins, wire-verified end to end.
- PROJWEAP ProjectileWeaponSimulation (PARTIAL, binary @004bbd04 -- fully
recovered in the BT411 RE): one trigger-edge sample; the dry-bin gate pins
NoAmmo(7) every frame; Loaded(2) -- the AMMO PULL LIVES IN THE CALLER (a
denied shot does the 4->2 denial blip with NO ammo pulled -- early-returning
gates from FireWeapon while the caller cycles the alarm is the 1995 "denied
shot fakes a full firing cycle" defect class); granted -> Firing ->
FireWeapon -> Loading (or NoAmmo on the emptying pull), recoil=rechargeRate;
Loading bleeds recoil -> Loaded when a round is chambered, dial animates;
Jammed(5) held; NoAmmo(7) = the roach-motel (re-asserts unconditionally).
Deferred: electrical step, gate 1, eject, jam roll, HasActiveTarget.
- AMMOBIN: FeedAmmo() + GetAmmoState() (1 round-ready / 2 EMPTY).
- ammoBinLink wired from the resource's ammoBinIndex (the bin's roster slot).
- WIRE-VERIFIED RESOURCE ALIGNMENT: a raw-stream dump pinned the
ProjectileWeapon field block +3 ints past our staged struct (the MechWeapon
resource ancestry runs 3 ints short -- the pip-family fields). Fixed with
resourceAlignPad[3]; phantom muzzleVelocity/missileCount tail dropped.
Confirmed on-wire: ammoBinIndex=27/29 (the exact bin slots), tracerInterval=1,
minTOF=0.5, minVolt%=0.3, minJam=0.05, missileCount=6 (an SRM6!).
- MECHWEAP: weapon-state enum extended to the full 1995 set (DryTrigger 1,
Jammed 5, TriggerDuringJam 6, NoAmmo 7; count 8).
- MISLANCH FireWeapon PARTIAL (@004bcc60: heat + salvo spawn only -- spawn
needs the entity/targeting waves).
VERIFIED headlessly (BT_FORCE_FIRE): bins resolve with the authored 24 rounds;
SRM6s fire 6-missile salvos at the authored 1s cadence, rounds 24->0; after
the 24th salvo the launcher goes DRY and stays silent across ~3,000 subsequent
fire events while the energy weapons keep cycling. Zero Fail.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The energy weapons now run a live fire cycle, and the REAL trigger wiring is in
place: the 1995 MechWeapon attribute table (binary @0x511890) is published with
PINNED IDs, so the streamed per-mech fire-button mappings bind TriggerState
(0x13) -> fireImpulse and the controls push writes the trigger into the weapon.
- MECHWEAP: attribute enum + table (PercentDone 0x12, TriggerState 0x13,
DistanceToTarget 0x14, TargetWithinRange 0x15, WeaponRange 0x16,
EstimatedReadyTime 0x1A, RearFiring 0x1B, WeaponState 0x1C). Pads bridge the
chain gap 2..0x11 -- LOAD-BEARING: AttributeIndexSet::Build leaves uncovered
gap slots as uninitialized garbage, so every ID up to the max must be covered
(they shrink to the authentic 0x0F..0x11 when the parent attribute waves land;
SENSOR.HPP pins PoweredSubsystem::NextAttributeID at 0x0F).
- MECHWEAP: fire-machine members (fireImpulse/previousFireImpulse,
rechargeLevel, rangeToTarget, effectiveRange, estimatedReadyTime, recoil,
weaponAlarm 0/2/3/4); CheckFireEdge (@004b9608, BIT-PATTERN int compare --
TriggerState carries raw ControlsButton ints whose release value is a float
NaN; IEEE compare would latch the detector shut); ComputeOutputVoltage
(@004b9c9c recharge dial).
- EMITTER: EmitterSimulation (PARTIAL @004baa88) -- Firing/Loaded/Loading FSM
on the weapon alarm, viewFireEnable gate at Loaded->Firing, recoil-decay
recharge over the authored RechargeRate seconds; FireWeapon (PARTIAL
@004bace8) discharge bookkeeping; ChargeLevel re-pinned to the authentic
0x1D; index re-chained to MechWeapon's. Deferred: electrical charge model
(TrackSeekVoltage), heat dump, HasActiveTarget gate, beam/damage submission.
- BT_FORCE_FIRE dev hook: trigger pulse whenever Loaded (auto-fire cadence).
VERIFIED headlessly: forward view -- PPC_1/2 + ERMLaser_1 cycle FIRED->LOADED
at the authored 0.6s/1s cadence, rear lasers silent; BT_FORCE_LOOK=3 -- ONLY
the rear lasers (ERMLaser_2/3) fire. The view-fire x fire-FSM interlock holds
both directions. Zero Fail. (SRM6 ballistic FSM = a later increment.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes the weapon-side half of the look commit:
- MECHWEAP: real rearFiring/viewFireEnable members (binary @0x334/@0x3E0) +
accessors. Ctor resolves REAR-FIRING authentically (@004b99a8 tail): the
mount SEGMENT site name is tested for the 'b' (back) marker -- only the back
gun ports (sitelbgunport/siterbgunport) carry it. Spawn = forward-armed.
- MECH CommitLookState: re-arms every roster weapon per view (forward =
non-rear, LOOK-BACK = rear-mounted, side/down = none) and flips the HUD
reticle pip group with the authentic Reticle::Front/RearFiringWeaponsOn flags
(BT411's raw "bits 1/2" resolved to their 1995 names).
- MECHWEAP: defined MechWeapon::MessageHandlers (0-entry set inheriting the
Subsystem chain) -- it was declared-but-undefined, and the surviving CODE
PPC.CPP binds the inherited name into PPC::DefaultData; the zero-filled
common block was a latent NULL deref (same trap as Emitter::AttributeIndex).
Discovery: the TEST.EGG mech genuinely mounts TWO REAR LASERS (ERMLaser_2/3 on
the b-ports). Verified on a full-clean baseline (engine 181 + bt 42, 0 fail):
look-behind arms exactly the rear lasers, disarms the other five,
pipMask=0xfffffffe; neutral run holds forward-armed; zero Fail.
build410.sh HARDENED against the stale-obj layout-skew trap this wave exposed:
cc() skipped recompiles on obj-vs-.cpp timestamps only, so the MECHWEAP.HPP
member additions left emitter.obj on the OLD MechWeapon layout -- its
chargeLevel=0.0f write landed exactly on the new rearFiring (a silent cross-TU
struct skew, heisenbug-grade). cc() now honors header stamps: a source410
engine-header edit invalidates every bucket, a BT/BT_L4 header edit
additionally invalidates the game buckets. CODE/ is immutable, no stamp needed.
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>
Investigated the next planned binding (elevation -> gun joints jointlgun/
jointrgun) before implementing it, and found it was the wrong target: BT411's
own reverse-engineering history records that NO gun/arm elevation joint exists
in the authentic engine. The pod's stick-Y aim pitches the COCKPIT EYE and the
weapon boresight directly (mech4.cpp @~5219, pixel-calibrated against real
screenshots) -- the torso geometry never tilts for elevation on this mech
family. Reconstructing a gun-joint binding would have been invented behavior.
- MECH.HPP: EulerAngles eyepointRotation member (out of reservedState, now
[208]) + GetEyepointRotation() accessor.
- MECH.CPP: composed each frame in Simulate -- eyepointRotation =
EulerAngles(Radian(Normalize(lookPitch+elevation)), Radian(Normalize(lookYaw)),
Radian(0)), reading Torso::CurrentElevation() via sinkSourceSubsystem.
lookPitch/lookYaw are 0 until the look-button wave (BTCommitLookState) lands.
Verified: BT_FORCE_ELEV=0.8 -> torsoElev and eyePitch both read 0.349066 (the
authentic 20 degree resource limit) every frame, exact match; neutral -> 0.
Zero Fail. Corrected TORSO.NOTES.md's prior (wrong) "next: gun joints" note.
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>
The Mech ctor now reads walkingTurnRate / runningTurnRate / maxAcceleration /
throttleAdjustment from the GameModel resource (SearchList(GameModelResourceType)
-> ModelResource), replacing the bring-up-default constants for those fields.
Each read is sanity-guarded (out-of-band -> keep default) since BT411 flags parts
of the ModelResource layout as mis-decoded -- but the live values came back clean
and authentic: walkTR=75deg/s, runTR=50deg/s, maxAcc=30 u/s^2, throttleAdj=1.0
(walking turns faster than running; maxAcc matches the madcat note), confirming
our reconstructed struct layout is correct for these fields.
Verified: at walk speed the authentic 75deg/s tightens the turn circle to r~6.9
(=speed/turnRate); zero Fail. Still on defaults (pending LoadLocomotionClips):
reverseStrideLength (top speed) / walkStrideLength / reverseSpeedMax -- measured
from the walk/run animation clips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Installs Mech::Simulate as the mech's per-frame Performance (was DoNothingOnce).
Reconstructs the load-bearing spine of the 1995 Simulate (mech4.cpp @004ab430):
integrate the body velocity into localOrigin (linearPosition.AddScaled(..,
worldLinearVelocity, dt)) and commit the Origin to the world transform with the
engine idiom (localToWorld = localOrigin, per ENTITY.cpp:988 / MOVER.cpp:850).
Uses the NAMED engine base Mover fields (localOrigin/localToWorld/
worldLinearVelocity) -- BT411's MechBaseLayoutCheck proved the WinTesla decomp's
raw this+0x100/0x260 offsets actually stomp those base fields; the clean 1995
build addresses them by name.
- MECH.HPP: Mech Performance typedef + SetPerformance + Simulate() decl.
- MECH.CPP: SetPerformance(&Mech::Simulate) at ctor tail; Simulate body.
Verified headlessly (BT_MECH_LOG "[sim] mech pos=" 1Hz): BT_DRIVE="5,0,10"
advances the mech +5.0/s x, +10.0/s z (y fixed) = exactly the injected world
velocity; no BT_DRIVE -> worldLinearVelocity is 0, mech holds spawn pose. Zero
Fail/Exception either way. BT_DRIVE is a retained dev hook for headless motion.
Deferred (locomotion wave): gait-cycle self-propulsion feeding worldLinearVelocity
(IntegrateMotion->AdvanceBodyAnimation, steered by mapper throttle/turn), heading
integration, terrain-height drop, cockpit telemetry filters.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The world now executes. Entity::Execute only PerformAndWatch's entities in
application state RunningMission, reached by two RunMissionMessages
(WaitingForLaunch->LaunchingMission->RunningMission). The 2nd is dispatched by
Player::ManageApplicationStatus when the launch fade expires -- but that only
runs inside PlayerSimulation, not the launch-phase HuntForDropZone. So the
player must switch Performance onto PlayerSimulation after it spawns.
- BTPLAYER.CPP DropZoneReplyMessageHandler: after CreatePlayerVehicle +
InitializePlayerLink, choose the per-vehicle Performance by class -- Mech ->
SetPerformance(PlayerSimulation)+SetScoringPlayerFlag; else CameraShipSimulation.
- BTPLAYER.CPP PlayerSimulation: chains the authentic base Player::PlayerSimulation
(CalcRanking + ManageApplicationStatus + vehicle-pos copy + status service);
console SCORE-delta flush deferred to the scoring wave.
- SENSOR.CPP SensorSimulation: minimal-safe partial (radarPercent = 1 -
GetSubsystemDamageLevel, sensor healthy) so the roster tick path can't Fail
once RunningMission engages. The heat/electrical gating awaits the power/heat
sim wave (PoweredSubsystem/Generator/HeatSink/HeatWatcher *Simulation staged).
Verified: BT_MECH_LOG run reaches "[tick] roster live (first Sensor frame)",
two RunMissionHandler transitions, zero Fail/Exception. The mech is executed
each frame; its BODY Performance is still DoNothingOnce (real Mech::Simulate =
Phase 5.3, now the frontier).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase-5.1 crash solved + two BTPlayer bricks reconstructed. The tree now
builds AND boots end-to-end with no staged Fail() reached, into the running
per-frame game loop.
- EMITTER.CPP: define Emitter::AttributePointers[]/AttributeIndex (ChargeLevel,
chained to Subsystem::AttributeIndex) and point Emitter::DefaultData at it.
EMITTER.HPP declared the index but the .cpp never defined it, so the surviving
PPC.CPP (PPC::DefaultData binds inherited Emitter::AttributeIndex) and
GAUSS.CPP (chains it) captured a NULL activeAttributeIndex -> GetAttributePointer
faulted on the first PPC (roster slot 22). Fix clears all 33 roster slots and
the control-mapping binding block.
- BTPLAYER.CPP InitializePlayerLink: real body -- dispatch PlayerLinkMessage
(our EntityID) to playerVehicle + replicants (mirrors engine
CameraDirector::CreateCameraShip). No staging.
- BTPLAYER.CPP VehicleDeadMessageHandler: non-death flavours (-2 drop-zone probe,
>=0 respawn re-post) delegate to the authentic Player base handler; only the
-1 death cycle stays staged (needs mech4 + scoring roles).
Verified: 516-line smoke run, zero Fail/Exception/abort; boot reaches
LBE4ControlsManager::Execute per-frame, waiting only on absent RIO hardware.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Trace confirms the MechControlsMapper (slot 0) publishes its control attributes
correctly: throttleAttr/controlModeAttr resolve to non-NULL pointers. So the
mapper's AttributeIndex works. The post-construction crash (GetAttributePointer
attr=0x13) is on a DIFFERENT object (EAX heap addr after the mapper) -- a streamed
watcher targeting another simulation, not the mapper. Localizing next. (BT_MECH_LOG
mapper trace retained.)
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>
The Mech ctor fully constructs (33 subsystems / 7 weapons on real TEST.EGG data,
trace-confirmed). The crash is the control-mapping resource binding in
MakeViewpointEntity resolving subsystem attribute IDs via GetAttributePointer --
the subsystems publish none (reuse base AttributeIndex). Documented the precise
phase-5 step-1 scope (per-subsystem AttributeIndex + the ID chain) in MECH.NOTES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live trace (BT_MECH_LOG) on the pod TEST.EGG mech: '[mech] segment walk done:
subsystemCount=33 weaponCount=7' -- the segment walk instantiates the ENTIRE real
subsystem roster (31 subsystems + 2 sentinels, 7 weapons) from the actual streamed
model, no crash. The structural reconstruction is proven end-to-end on real data.
The crash is AFTER full construction, in MakeAndLinkViewpointEntity's attribute-
watcher setup (an AttributeWatcher resolving a subsystem attribute via
GetAttributePointer). Phase-5 frontier confirmed = per-subsystem AttributeIndex
tables + watcher wiring. Trace gated behind BT_MECH_LOG.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Records that the Mech constructor works live (segment walk instantiates the full
roster, mech links as viewpoint, mapper installs) and the precise phase-5
frontier: Simulation::GetAttributePointer via PerformAndWatch, because the
reconstructed subsystems reuse the base AttributeIndex and publish none of their
real attributes. Phase-5 plan (per-subsystem AttributeIndex -> watcher/chain/plug
wiring -> mech2/3/4 per-frame sim -> rendering) in MECH.NOTES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unrecognised subsystem classIDs now get a base MechSubsystem (Laser/ParticleCannon
-> Emitter) so control/damage bindings that resolve a subsystemID find a real
subsystem, not a NULL plug. This moved the boot PAST the first Link::AddToPlug
NULL-deref; it now runs to Simulation::GetAttributePointer -- the subsystem
ATTRIBUTE system. Each reconstructed subsystem currently reuses the base
Subsystem::AttributeIndex, but the streamed control-mapping / gauge bindings
reference subsystem-specific published attributes (Sensor RadarPercent, Generator
OutputVoltage, Emitter ChargeLevel, ...) that aren't in the base index -> the
attribute lookup walks off the end. Reconstructing each subsystem's AttributeIndex
(+ plug/capability-chain wiring) is the phase-5 integration. BT: 42 ok.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SetMappingSubsystem installs the control mapper into roster slot 0 (the streamed
control-mapping binds DirectMappings to subsystemID 0). MechControlsMapper ctor
constructs (per-frame InterpretControls deferred to phase 5). With RIO controls,
MakeViewpointEntity now builds the MechRIOMapper, installs it, and gets PAST the
controls check -- the mech constructs AND links as the viewpoint entity. Boot now
advances into the mission-startup / control-binding path (next: a real NULL-deref
there, deeper than the staged Fails). BT: 42 ok.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Mech ctor now finds the model's subsystem-model-stream resource
(SearchList by SubsystemModelStreamResourceType), wraps it in a MemoryStream,
and walks the segments -- switching on seg->classID to into the base Entity's subsystemArray, caching sensor/gyro/sinkSource/hud
and counting weapons.
KEY FINDING: the source410 VDATA ClassID values MATCH the 4.10 binary for the
core subsystems (0xBBD=Condenser, 0xBC3=Sensor, 0xBC5=Torso, 0xBD6=HUD, ...) --
BT411's 'mislabels' were its OWN wrong enum names. So the walk switches on the
real VDATA names cleanly. Unrecognised classIDs leave a NULL slot (roster stays
aligned) rather than aborting.
LIVE: boot now runs the full Mech ctor -- segment walk instantiates the roster,
no crash -- and advances PAST it to BTL4Application::MakeViewpointEntity, which
halts at 'Mech has no controls mapping!' (the slot-0 control mapper /
SetMappingSubsystem is the next brick). The largest function in the game
executes. BT: 42 ok; tree links clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaced MECH.HPP reserved[331] with the named Mech-own members: cached subsystem
pointers (sensor/gyro/sinkSource/hud) + messageManager + weaponCount, the 5
capability chains (ChainOf<Subsystem*>), and the embedded status/animation state
(4 AlarmIndicators, Reticle, 3 StateIndicators, 2 SequenceControllers, NameFilter,
telemetryFilter[5]=AverageOf<Scalar>, 3 CStrings) + a reservedState pad for the
phase-5 per-frame fields. The subsystem ROSTER (subsystemArray/subsystemCount)
lives in the base Entity (GetSubsystem/GetSubsystemCount), so the Mech only caches
back-pointers.
The Mech ctor now CONSTRUCTS all embedded members (alarms Initialize'd, reticle
armed, state indicators sized, telemetry filters sized, sequence controllers
Init'd) -- compile-verified -- then halts at the segment walk (the remaining
piece). BT: 42 ok; tree links clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Records the Mech-ctor segment-walk factory (mech.cpp:1146-1330) with the full
MISLABELED ClassID->real-class map (VDATA enum names differ from the real
classes). 16 roster classes done; 7 remain for a complete walk (Reservoir,
HeatSink-bank, Searchlight, MechTech, ThermalSight, SubsystemMessageManager,
Actuator). Blueprint in MECHSUB.NOTES.md guides phase 4.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ProjectileWeapon (: MechWeapon) -- ballistic (ammo-fed) base: ammo-bin link,
tracer/eject/jam/lead members. MissileLauncher (: ProjectileWeapon) -- salvo
launcher, using the surviving CODE MISLANCH.HPP (missileCount + per-salvo damage
split). AmmoBin (: HeatWatcher) -- ammo store with cook-off heat. All ctors chain
their parents + init from resource; fire paths staged.
Weapon family now COMPLETE: MechWeapon -> {Emitter -> PPC, GaussRifle;
ProjectileWeapon -> MissileLauncher} + AmmoBin. The surviving PPC.CPP/GAUSS.CPP
link against the reconstructed Emitter base. BT: 36 ok.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Emitter : MechWeapon -- energy-weapon (beam) base. Both surviving weapon leaves
(PPC.CPP, GAUSS.CPP/GaussRifle) derive from it and now compile against the real
base. Ctor chains MechWeapon + inits chargeLevel/dischargeTime; statics/dtor/test
real; FireWeapon (beam discharge) + CreateStreamedSubsystem staged. BT: 33 ok.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MechWeapon : PoweredSubsystem -- base of all weapons. Ctor chains PoweredSubsystem
+ copies resource (recharge/range/damage/heatCost) into members + damageData.
FireWeapon is the virtual abstract stub (Fail 'should not be here'; PPC/Gauss/
MissileLauncher override it); SendDamage + CreateStreamedSubsystem staged.
BT: 32 ok.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Eye/body stabilisation subsystem. Full resource struct + tuning members
(springs/damping/noise/percentages/damage multipliers) reconstructed; the
eye/body dynamics accumulators held in a reserved pad until the per-frame
GyroscopeSimulation is reconstructed (phase 5). Ctor chains PowerWatcher + copies
tuning; statics/dtor/test/reset real; sim staged. BT: 28 ok.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base of the Torso/HUD/Gyroscope cockpit subsystems. Ctor chains HeatWatcher +
watchdogAlarm(5) + minVoltage; statics/dtor/test/reset real; Simulation staged.
BT: 27 ok.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Records the full mech-subsystem hierarchy discovered while reconstructing the
roster, with per-class build status. 6 classes done + verified (MechSubsystem,
HeatableSubsystem, HeatSink, PoweredSubsystem, Sensor, Generator); the tree shows
the remaining intermediates (HeatWatcher : MechSubsystem, PowerWatcher :
HeatWatcher) that unblock the Torso/HUD/Gyroscope branch (the Mech's directly-
referenced subsystems), plus Myomers/Condenser leaves and the separate weapon
subtree. The reconstruction pattern is now a proven template (documented). Enables
efficient continuation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generator : public HeatSink (classID 0xBC3) -- the voltage SOURCE that
PoweredSubsystems tap (currentTapCount = attached loads). Reconstructed from the
BT411 decomp + the surviving GNRATOR.TCP partial: ctor chains HeatSink, seeds
ratedVoltage/outputVoltage/maxTapCount/startTime/shortRecoveryTime + stateAlarm(5)
= AlarmIndicator, derives generatorNumber from the segment-name suffix. Statics,
dtor, TestClass/TestInstance/ResetToInitialState real (ResetToInitialState mirrors
the .TCP); GeneratorSimulation (per-frame voltage/short) staged. Master-instance
SetPerformance install deferred (per-frame sim staged anyway).
Power system pair now in place: Generator (source) + PoweredSubsystem (load).
Roster classes so far: MechSubsystem, HeatableSubsystem, HeatSink,
PoweredSubsystem, Sensor, Generator. BT: 27 ok; tree links clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sensor : public PoweredSubsystem -- the radar/targeting subsystem, using the
SURVIVING 4.10 interface (CODE/BT/BT/SENSOR.HPP) with a reconstructed SENSOR.CPP
body (from BT411 decomp). Ctor chains PoweredSubsystem, inits radarPercent/
selfTest/badVoltage, and installs the per-frame performance unless the owner is a
replicant (owner->GetInstance() != ReplicantInstance -- the clean form of the
binary's (flags & 0xC) != 4). Statics, dtor, TestClass/TestInstance,
ResetToInitialState, TakeDamage, DeathReset real; SensorSimulation (per-frame
radar) + CreateStreamedSubsystem staged.
Completes the first full vertical slice of the subsystem roster, all
compile-verified: MechSubsystem -> HeatableSubsystem -> HeatSink ->
PoweredSubsystem -> Sensor. The hierarchy backbone is proven; remaining leaves
(Generator, Gyro, Torso, HUD, Myomers, weapons) follow the same pattern.
BT: 27 ok; tree links clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the staged POWERSUB.HPP base (HeatableSubsystem -> HeatSink, the real
hierarchy) and reconstructs PoweredSubsystem: a HeatSink that draws electrical
power. Added members (voltageSource = SubsystemConnection, electricalStateAlarm =
AlarmIndicator(5), modeAlarm = AlarmIndicator(3), input/output/ratedVoltage) and
corrected the resource struct (voltageSourceIndex + thermalResistivityCoefficient
+ startTime). Ctor chains HeatSink and primes the electrical state; statics, dtor,
ResetToInitialState, TestClass/TestInstance real.
The voltage-source resolution (indexing the owner mech's subsystem ROSTER for the
powering generator) + master-instance electrical sim + AttachToVoltageSource are
deferred to the segment-walk phase (phase 4) where the roster is populated -- the
subsystem constructs with no attached source until then. Verified slice now:
MechSubsystem -> HeatableSubsystem -> HeatSink -> PoweredSubsystem. Compile-verified
(BT: 26 ok); tree links clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HeatSink : public HeatableSubsystem (confirmed hierarchy) -- a thermal mass with
a coolant loop. Added to HEAT.HPP/.CPP: the class + its SubsystemConnection slot
helper + HeatSink__SubsystemResource. Ctor chains HeatableSubsystem and seeds the
thermal state from the resource (startingTemperature/degradation/failure/
thermalConductance/thermalMass), heatAlarm(3) = AlarmIndicator, heatFilter =
AverageOf<Scalar>(15). Statics, dtor, ResetToInitialState, TestClass/TestInstance
real; the per-frame thermal sim (HeatSinkSimulation/DrawCoolant) staged.
Also reconciled HeatableSubsystem to the 4.10 fields: its resource struct now
carries startingTemperature/degradationTemperature/failureTemperature/
thermalConductance/thermalMass (was the BT411-mislabeled thermalMass/degrade/fail/
coolingLoop set), and degradationTemperature/failureTemperature are base members
(HeatSink reads them). Verified slice now: MechSubsystem -> HeatableSubsystem ->
HeatSink. Compile-verified (BT: 25 ok); tree links clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HeatableSubsystem (HEAT.CPP) reconstructed + compile-verified (BT: 25 ok):
statics, ctor (chains MechSubsystem resource form + ResetToInitialState ->
currentTemperature=300, heatLoad=0), dtor, TestClass/TestInstance,
CreateStreamedSubsystem staged. Verified slice now: Subsystem -> MechSubsystem
-> HeatableSubsystem.
Also mapped the roster hierarchy from BT411, which is DEEPER than the staged
headers (recorded in MECHSUB.NOTES.md):
MechSubsystem -> HeatableSubsystem -> HeatSink -> PoweredSubsystem -> {Sensor,
Generator, ...}
Findings for the next steps: (1) HeatSink is MISSING entirely from the staged
tree and must be added between HeatableSubsystem and PoweredSubsystem;
(2) staged POWERSUB.HPP wrongly derives PoweredSubsystem from HeatableSubsystem
(real base = HeatSink) -- fix it; (3) PoweredSubsystem has inter-subsystem wiring
(resolves its voltage-source generator from the owner mech's subsystem roster),
imposing a segment-walk ordering constraint for the phase-4 ctor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The base all ~30 mech subsystems derive from. Reconstructed against the 4.10
structure per the MECHSUB.NOTES analysis (NOT backdated from BT411's divergent
version):
- Header: expanded with the real mech-specific member set (statusAlarm =
AlarmIndicator, vitalSubsystem, alarmModel, criticalReference,
collisionCriticalHitWeight, printSimulationState, configureActivePress,
resource). Dropped the speculative MessageHandlers static -- MechSubsystem
reuses the base Subsystem sets (inherited Simulation handlers/attrs/statecount).
- CPP: statics (ClassDerivations/DefaultData), both ctors (light name+classID and
resource), dtor, TestClass/TestInstance, and the damage API on the 4.10
per-type model -- GetSubsystemDamageLevel/SetSubsystemDamageLevel read/write the
real DamageZone::damageLevel [0,1]; ForceCriticalFailure; TakeDamage delegates
to the zone. Both ctors build the damage zone the base leaves NULL.
Deferred (documented): the Mech__DamageZone per-type armour STREAM wiring (ctors
use a trivial armour-free base DamageZone placeholder for now) and
CreateStreamedSubsystem (model-load stream format). These are refinements above
the ctor frontier, not boot blockers.
Compile-verified (BT: 24 ok); full tree still links clean. Next: the subsystem
roster (GAUSS/PPC/SENSOR source + .TCP partials + decomp).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Begins phase 2 (MechSubsystem base + subsystem roster) with the base-class
analysis that keeps the reconstruction on the 4.10 damage model. Key findings:
- All base deps survive: Subsystem (SUBSYSTM.HPP + staged SUBSYSTM.CPP),
DamageZone (DAMAGE), Mech__DamageZone + BT damage model (MECHDMG.HPP),
AlarmIndicator (just built).
- Finding 1: the staged base Subsystem ctor sets damageZone=NULL, so the derived
MechSubsystem creates the Mech__DamageZone (BT411's zone-creation is right).
- Finding 2 (CRITICAL): the damage model DIVERGED. 4.10 is per-damage-TYPE --
DamageZone has defaultArmorPoints + damageScale[5] {collision/ballistic/
explosive/laser/energy} and STREAMS its own armour (DAMAGE.CPP:335,413).
BT411 reconstructed a per-FACING armorByFacing[5]/structureReference model that
MechSubsystem hand-seeds -- those fields exist only in BT411's ReconDamageZone
proxy, NOT in the 4.10 zone. So BT411's MechSubsystem ctor CANNOT be backdated
verbatim; doing so would install a damage model the 4.10 engine doesn't
implement, corrupting every subsystem.
Corrected approach (now fully scoped, ready to write): chain base Subsystem,
create Mech__DamageZone, add mech-specific members + TakeDamage override, let the
zone stream per-type armour. Full detail in MECHSUB.NOTES.md; the staged
MechSubsystem__SubsystemResource (per-facing, BT411-copied) is flagged for
re-derivation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NameFilter is the Mech's mechNameFilter (@0x36c), a small fixed debounce record
the ctor initialises. Reconstructed from FUN_00435a7c: a 10-word (0x28) struct,
Initialize() sets [2]=1 / [8]=[9]=-1 / rest 0 exactly. Header-only. Field
semantics unresolved (only caller is Initialize; reader is the unbuilt HUD) --
names best-effort, but size + init values are binary-exact, which is all the
Mech layout + ctor need.
All 3 missing embedded helper classes now reconstructed + compile-verified under
BC4.52 (AlarmIndicator, SequenceController struct+Init, NameFilter). MECH-LAYOUT.md
step 1 marked done. Next: MechSubsystem base + the subsystem roster, then finalize
MECH.HPP (probe sizeof==0x854), then the ctor + segment-table walk.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The BT keyframe-animation player embedded in the Mech as legAnimation/
bodyAnimation. Struct field layout (from BT411's binary-offset map), default
ctor, dtor, and Init(Mech*) reconstructed and compile-verified; Init is
ctor-time (Mech ctor calls legAnimation.Init(this)/bodyAnimation.Init(this)) so
it's real (binds owner + caches owner->GetJointSubsystem()). The per-frame gait
engine (SelectSequence/Advance/Reset) is staged with Fail bodies -- it fires
only once the mech is ticking, past the current ctor frontier.
BT stage 23 ok; full tree still links clean. Evidence + real/staged split in
SEQCTL.NOTES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full-archive re-check: AverageOf DOES survive (AVERAGE.HPP, the SIGNATURED
lineage BT411 preserved), so telemetryFilter[5]'s type is available. The
genuinely-missing embedded helpers are just AlarmIndicator, SequenceController,
and NameFilter (confirmed absent from all of CODE; no alarm/filter/sequence
headers) -- to be reconstructed from BT411 decomp before the Mech layout finalizes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Begins the Mech reconstruction milestone (analysis phase). Extends the existing
MECH-LAYOUT.md worksheet with the findings needed to build MECH.HPP + the ctor:
- Resolved the BT411 mechrecon.hpp proxy layer to real 1995 types (ReconAlarm ->
AlarmIndicator, ReconSeq -> SequenceController, ReconFiltered -> AverageOf<
Scalar>/NameFilter, ReconChain -> ChainOf, ReconMatrix -> AffineMatrix;
StateIndicator/Reticle survive as-is; BTVal mostly base-class pose fields).
- CRITICAL: AlarmIndicator, SequenceController, NameFilter, AverageOf are ALSO
absent from the 4.10 archive -- additional helper classes the milestone must
reconstruct first (their sizes set the embedded-member offsets).
- Subsystem source availability mapped: full (GAUSS/PPC/SENSOR), .TCP partials
(AMMOBIN/EMITTER/GNRATOR/HEAT/MISSILE/PROJTILE/PROJWEAP), decomp-only (rest).
- Key tractability insight: StateIndicator is fixed-size (level count stored, not
arrayed), so a FUNCTIONAL fresh build needs only the right member set/types in
order -- byte-exact offset matching is a later refinement for network wire
format, not required to boot.
- Revised reconstruction order: helpers -> MechSubsystem+roster -> finalize
MECH.HPP (probe sizeof==0x854) -> ctor+segment walk -> per-frame path.
Everything below the Mech ctor runs real reconstructed code; the ctor is the
live frontier (spawn factory validated last commit).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BTPlayer::CreatePlayerVehicle reconstructed (faithful; BT411's BT_SPAWN_XZ/
BT_SPAWN_ENEMY bring-up scaffolding dropped): builds the Mech::MakeMessage from
the mission game-model resource and hands it to MakeAndLinkViewpointEntity.
Mech::Make = new Mech(creation_message); a staged Mech ctor chains the real
JointedMover base (so the model skeleton/segments stream from BTL4.RES) then
Fails.
This validates the entire spawn factory chain live: CreatePlayerVehicle ->
MakeAndLinkViewpointEntity -> registry -> Mech::Make -> new Mech -> JointedMover
base ctor (streams the mech model) -> halts at the Mech ctor (mech.cpp:66).
The Mech ctor (@004a1674, 5690 bytes -- the largest function in the game) plus
the subsystem roster is the next MAJOR milestone, not a brick: it needs the full
1995 Mech member layout derived first (MECH.HPP is still reserved[331]; BT411's
decomp uses raw offset pokes that can't compile fresh), the subsystem class
hierarchy (only GAUSS/PPC/SENSOR survive in the 4.10 archive; the rest are the
measured "917 missing functions"), and the segment-table walk. Plan + scope in
MECH.NOTES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the incorrect DefaultData reuse of Player::MessageHandlers -- BTPlayer now
has its own handler table (DropZoneReply, VehicleDead, Score, ScoreInflicted,
ScoreUpdate; the last two add BT message IDs) chained onto the base set, matching
the .data table recovered in BT411. A mission-start DropZoneReply now routes to
BTPlayer::DropZoneReplyMessageHandler (the real spawn/respawn dispatch skeleton)
instead of the base "should not be handled by base player class" Fail stub.
Boot ladder now runs the full engine + app + network + mission + player path and
reaches the spawn handshake, halting at CreatePlayerVehicle -> Mech::Make -- the
Mech subsystem frontier (Mech ctor + mech2/3/4 + subsystems + weapons), the next
major reconstruction milestone. Scoring/death handlers, PlayerSimulation, and the
mech-placement tail staged with documented Fail bodies (BTPLAYER.NOTES.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Boot ladder advances past player creation. BTPlayer ctor/dtor/Make/
TestInstance/GetExperienceLevel reconstructed from BT411's Ghidra-recovered
body (@004c0bc8) backdated to 1995 idiom; the reserved[38] placeholder in
BTPLAYER.HPP replaced with the real BT-own members.
Key fidelity call: the game-mode flags are derived from
btMission->ExperienceLevel() (the egg's per-pilot experience key) with the
binary-accurate switch rows from the [T1] disassembly -- NOT from the scenario
role's returnFromDeath, which BT411's port uses as a [T3] stand-in. Mapping and
residual naming/[T4] uncertainty documented in BTPLAYER.NOTES.md.
Live boot now runs: banner -> resources -> app ctor -> network single-user ->
egg -> mission -> SetPlayerData -> BTRegistry::MakePlayer -> BTPlayer ctor
(resolves team, seeds experience flags) -> halts at the base
Player::DropZoneReplyMessageHandler stub (BTPlayer's own message-handler table
is the next brick -- DefaultData still reuses Player::MessageHandlers).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reconstructed tree now produces a runnable binary with the authentic
1995 toolchain (BC4.52 / tlink32 / DPMI32):
- BTL4.CPP main TU reconstructed from the 4.11 Ghidra decomp (FUN_0040109c)
+ the 4.10 binary's own string pool + surviving RPL4TOOL.CPP house style;
probe_main.cpp scaffold retired (BTL4.NOTES.md documents every decoded call)
- L4NET.CPP staged: L4NetworkManager ctor/dtor + 10 vtable-pulled virtuals
(standalone-benign ones no-op, network ones Fail loudly) + NetNub client
globals (Net_Common_Ptr=NULL routes L4File to its plain-DOS path)
- build410.sh: libs now built in the AUTHENTIC makefile member order
(MUNGA.MAK / mungal4.mak / BT.MAK / BTL4.MAK). Order is load-bearing:
tlink emits static-init records in module pull order, and alphabetical
order booted into a null-vptr crash (IcomManager::ClassDerivations
constructing before parent NetworkClient::ClassDerivations). Also fixed
stage_link to the proven 32-bit lib set (SOSDBXC+SOSMBXC, no WATTCPLG)
- BOXTREE.HPP MemoryBlock unify, BTL4GRND notify stubs, remaining engine
backfills (audio/gauge/resource/stream TUs) that closed the deep ledger
Smoke test (DOSBox-X + 32RTM, copy of the pod BT tree, our exe swapped in):
BattleTech v4.10
BTL4Application::BTL4Application
l4net.cpp(22): L4NetworkManager -- l4net.cpp not yet reconstructed
Static init, main, -egg parse, BTL4.RES load (version 1.0.6 check passes),
ApplicationManager and the BTL4Application ctor chain all execute real
reconstructed code; boot halts at the first staged Fail() as designed.
Next brick: the real l4net.cpp body.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>