Scoring roles bound (binary ctor tail @004c0bc8): the mission role dict ->
Player::scenarioRole; TEST.EGG's Role::Default = the baked dfltrole
(lives=1000, killBonus=500) -- authored data flowing. The three score
handlers are live per the decomp derivations (@004c02e4 / @004c0200 /
@004c02a8 + CalcInflictedScore @004c052c; tonnage/bias staged); the
producers post damage scores per delivered hit and the kill credit from
the death transition with the latched killing-blow magnitude. First live
kill: award=3.43, kills=1, score=123.6.
Out of lives -> the mission END: the binary's +10s review post sits in the
one decomp gap, so the staged stand-in enters the engine's own cascade
(StopMissionMessage +10s) -- EndingMission -> the 3s fade -> the second
Stop -> Application::Stop -> RunMissions exits. The BTL4/L4 stop layers
run authentically (plasma score display off, egress lamps).
VERIFIED END-TO-END: a 2-lives egg (role-page return=2 override) -- death
#1 debits and respawns healed; death #2 is OUT OF LIVES; +10s later the
mission ends and BTL4OPT.EXE exits CLEANLY to DOS (GAME-RC prints). The
full 1995 pod mission loop -- egg, boot, spawn, fight, score, die,
respawn, die, mission end, exit -- now runs from reconstructed source.
Zero faults; fight/smoke/novice regressions green. Dev knob:
BT_PLAYER_PASSIVE=1 (piloted mechs hold force-fire).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The full circuit, workflow-researched (5 parallel dossiers over BT411 + the
engine) and adversarially reviewed (3 lenses) before landing:
Mech side: the once-per-death transition in Simulate (freeze, motion kill,
DeathShutdown sweep, MASTER-only VehicleDead(-1) to the player link);
Mech::Reset @0049fb74 (reposition + heal every hull zone + the roster
DeathReset sweep + PreRun -- the reset-based respawn that REUSES the entity);
dead-owner terms in both weapon hard gates (wrecks fall silent).
Player side: the VehicleDead(-1) branch (deathPending dedup, deathCount
bump+stamp, scenario-role life debit, +5s re-post -> the engine drop-zone
hunt) and the DropZoneReply respawn branch (reset the dead mech at the
replied drop zone; probes on a live mech are moot).
Subsystem side: the engine base virtual DeathReset implemented across the
family -- MechSubsystem (zone heal + DestroyedState clear), MechWeapon (full
powered/thermal restore + fresh Loading cycle), AmmoBin (restock from the
ctor-cached count), HeatSink/HeatWatcher/PowerWatcher/Generator/Sensor/
MissileLauncher.
Review catches fixed before commit: AmmoBin restocked through the FREED
padBuffer pointer (use-after-free -> initialAmmoCount; MechSubsystem::
resource is now documented as never-deref-post-ctor); died-hot weapons
respawned at FailureHeat (now full thermal re-init); Generator tap counts
desynced across reset (preserved); Sensor skipped the base heal; PowerWatcher
inherited a statically-bound reset; cooling toggle + connect mode restored.
Verified: kill -> wreck (0 shots while dead) -> +5s -> drop-zone hunt ->
HEALED + placed at a real drop zone -> 134 shots after respawn incl. 12 SRM
(restock proof); unowned enemy wreck settles silently; fight/smoke/novice
regressions green, zero faults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mech::DamageZone::TakeDamage is the full binary cascade (@0049c690): LOD
artifact router with same-attacker clustering (@0049c40c, hysteresis 0.33),
artifact parent level = mean of children, Energy-hit generator shorts
through the crit plug binding (novice-exempt), weighted CriticalHit
(@0049ccc4), zone-death allotment push (SendSubsystemDamage @0049c9a8) and
the segment-table destruction descent (RecurseSegmentTable @0049cad4 --
SIBS + DESCEND via the engine EntitySegment iterators).
Supporting bricks: Mech's OWN handler table with the TakeDamage override
(latches lastInflictingID @0x43c; gyro feed staged; cylinder table
deferred); friend class Mech__DamageZone (authentic); subsystem private
zones ARMED (structureReference + armorByFacing[5] normalized -- crits are
measurable); ApplyDamageAndMeasure; ForceCriticalFailure sets
DestroyedState so the weapon FSMs' GetSimulationState()==1 hard gate went
live; Generator::ForceShort + PoweredSubsystem::ForceShortRecovery.
Fight-verified (mutual BT_SPAWN_ENEMY + BT_FORCE_ZONE=8 concentrated fire):
arm zone to 1.0 -> sibling searchlight zone dies (Searchlight2/ThermalSight
crits destroyed, HUD degrading) -> DESCEND kills the gun zone -> PPC_2 +
ERMLaser_2 + Condenser6 DESTROYED and FALL SILENT (0 shots after death;
undamaged PPC_1 keeps firing); vital-zone hit = MECH KILL; PPC hit shorted
GeneratorD. This art has zero artifact zones (redirect=0 across all 20 --
router verified dormant-correct). Smoke + novice regressions clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The simulation is now a FIGHT. Mech target slot (+0x388), authentic
Loaded->Firing gate (viewFireEnable && HasActiveTarget), UpdateTargeting
range refresh, and SendDamage via the engine TakeDamageMessage.
MECHDMG.CPP is BORN against the AUTHENTIC surviving CODE/BT/BT/MECHDMG.HPP:
the Mech ctor's Pass-3 hull zone fill constructs Mech__DamageZone per zone
(the Entity base only allocates the raw pointer array -- unfilled slots were
crash #1; a base-class fill parses each record short and skews the stream --
crash #2). Streamed ctor parses the full BT tail (flags / Scalar-weighted
criticals with the Master+Dynamic plug gate / LOD redirect table) and
normalizes the armor economy: scale[type]=1/(raw x armorPoints), legs
halved -- BT411-verified @0049ce50.
Live two-mech verification (BT_SPAWN_ENEMY harness in DropZoneReply):
20 named hull zones stream on both mechs; PPC lands 11.77/hit (12 x
chargeRatio^2, type 4), ER-M laser 3.43 (type 3), SRM6 salvos 5.83 x 6
rounds on independent zones (type 2, one message per missile -- burstCount
is NOT in the zone formula); repeat hits climb linearly, leg zones absorb
at half scale, zones reach 1.0 Burning/Destroyed. No-target and novice
regressions hold (weapons load but never fire without a target).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The player-experience system now gates the entire heat/jam economy, verified in
BOTH directions with a one-token egg edit (TESTNOV.EGG, experience=novice):
NOVICE = 212 fire cycles all pinned at T=77, zero jams, zero shutdowns (the
heat model authentically absent); EXPERT = the full economy (duty cycles, 119
shutdowns + 2 authentic jams under forced fire). Zero Fail in both.
- BTPLAYER: the flag block renamed to its TRUE semantics (simLive @0x25c,
heatModelOn @0x260 -- the FUN_004ad7d4 master switch, advancedDamageOn pair,
levelFlag26c/270, experienceLevel); the ctor rows were already
binary-accurate (nov 0000 / std 1011 / vet 1111 / exp 1101); accessors +
[exp] sentinel.
- HeatSink::HeatModelActive() + ProjectileWeapon::LiveFireEnabled(): owner
mech -> Entity::GetPlayerLink() -> the BTPlayer flags, NULL-permissive.
Gates: both HeatSinkSimulation phases, every weapon fire-heat dump, and
CheckForJam is now the AUTHENTIC form (LiveFireEnabled + heatLoad<=0
early-outs + the minJamChance floor; the interim heat-degraded gate retired).
- THE LOAD-BEARING FIX: Mech ctor SetValidFlag(). Every 1995 entity ctor tail
marks itself valid; ours didn't -- Entity::Dispatch routes messages to an
INVALID entity into the deferred event queue, so the PlayerLink bind (and
every directly-dispatched mech message) silently never landed and the gates
read a NULL player forever.
- THE COOLANT SYSTEM (authentic bodies, byte-verified constants: HeatLoadScale
0.002 -> heatLoad now in [0,1]; equalize eps 1e-4; draw floor/ON
0.0025/0.003): UpdateCoolant (damage-scaled draw -- an undamaged mech leaks
nothing), BalanceCoolant (full clamp chain from ConductHeat), DrawCoolant
base=0 with the RESERVOIR override as THE SOURCE; Reservoir reconstructed
(capacity overlays thermalCapacity, CoolantSimulation + the full
InjectCoolant flush distribution, BT_FORCE_FLUSH dev hook); Condenser
RefrigerationSimulation (massScale = (1-damage)*refrigerationFactor >= 1 --
the heat pump that chills the bank; valveState inits 1; digit-suffix number
fix). Deferred: AggregateHeatSink family, cockpit-button handlers
(MoveValve/ToggleCooling/InjectCoolant), TrackSeekVoltage charge model.
Research driven by a 4-agent workflow dossier over the BT411 RE (verbatim
bodies for every function above + the egg experience plumbing).
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>
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>