BT410: reconstructed source BOOTS into the live game loop (zero Fail)

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>
This commit is contained in:
Cyd
2026-07-20 11:51:04 -05:00
co-authored by Claude Fable 5
parent a89d08e1c9
commit 812ca99652
4 changed files with 141 additions and 13 deletions
+54 -3
View File
@@ -358,10 +358,33 @@ void
}
}
//
//#############################################################################
// InitializePlayerLink Tell our freshly-made vehicle (and its network
// replicants) which player owns it, by dispatching a PlayerLinkMessage that
// carries our own EntityID. Identical to the engine's own
// CameraDirector::CreateCameraShip tail (DIRECTOR.CPP): BTPlayer, Player and
// CameraDirector all derive from Entity, so PlayerLinkMessage /
// PlayerLinkMessageID / GetEntityID resolve through the Entity base.
//#############################################################################
//
void
BTPlayer::InitializePlayerLink()
{
Fail("BTPlayer::InitializePlayerLink -- btplayer.cpp not yet reconstructed");
Check(this);
Check(playerVehicle);
PlayerLinkMessage
player_link_message(
PlayerLinkMessageID,
sizeof(PlayerLinkMessage),
GetEntityID()
);
playerVehicle->Dispatch(&player_link_message);
playerVehicle->DispatchToReplicants(&player_link_message);
Check_Fpu();
}
//
@@ -369,10 +392,38 @@ void
// Scoring / death handlers -- exercised in combat, not during bring-up boot.
//#############################################################################
//
//
//#############################################################################
// VehicleDeadMessageHandler The BT death / respawn cycle. Three message
// flavours arrive here, keyed on message->deathCount:
// -1 the immediate death notification dispatched by the mech's death
// transition (mech4.cpp) -- the full cycle (scoring-role life debit,
// +5s respawn re-post, warp collapse) is downstream of the per-frame
// mech simulation + scoring roles and is staged for now;
// >= 0 our own 5s respawn re-post, and
// -2 the engine's 2s "did the drop-zone reply arrive?" probe that
// Player::HuntForDropZone posts during a normal spawn.
// For everything but -1 the authentic engine base handler does exactly the
// right thing: it re-hunts a drop zone only when this death is still current
// AND one has not been acquired, so a freshly-spawned player (bring-up boot:
// the -2 probe lands after DropZoneReply already made the mech) falls straight
// through. So delegate the non-death flavours to the base.
//#############################################################################
//
void
BTPlayer::VehicleDeadMessageHandler(VehicleDeadMessage *)
BTPlayer::VehicleDeadMessageHandler(VehicleDeadMessage *message)
{
Fail("BTPlayer::VehicleDeadMessageHandler -- btplayer.cpp not yet reconstructed");
Check(this);
Check(message);
if (message->deathCount != -1)
{
Player::VehicleDeadMessageHandler(message);
Check_Fpu();
return;
}
Fail("BTPlayer::VehicleDeadMessageHandler(-1) -- death/respawn cycle deferred (mech4/scoring)");
}
void
+31 -9
View File
@@ -76,14 +76,36 @@ not fully located ([T4]). Rename is a follow-up wiring task; the VALUES are corr
- Replicant-mission caching uses `application->GetCurrentMission()` (the real
1995 accessor, PLAYER.CPP:689) instead of BT411's raw `application+0xc8`.
## Still staged (next bricks, in boot order)
## Reconstructed 2026-07-20 (boot reaches the live game loop)
1. **CreatePlayerVehicle → Mech::Make** (CURRENT FRONTIER) — the spawn chain
pulls in the whole Mech subsystem: Mech ctor + mech2/mech3/mech4, mechsub,
the subsystems (sensor, gnrator, gyro, torso, hud, myomers), and the weapons
(mechweap, ppc, gauss, projtile, missile...). Mech::Make is still Fail-staged
in MECH.CPP. This is the large game-content frontier — a major milestone, not
a single brick.
2. InitializePlayerLink, the mech-placement tail of DropZoneReply (Mech::Reset).
3. VehicleDead / Score / ScoreInflicted / ScoreUpdate handlers (scoring wave),
- **InitializePlayerLink** (btplayer.cpp, was @004bfee0) — DONE. Dispatches a
`PlayerLinkMessage` carrying our own `GetEntityID()` to `playerVehicle` and
its network replicants, binding the freshly-made mech to its owning player.
Byte-for-byte the same tail the engine's own `CameraDirector::CreateCameraShip`
runs (DIRECTOR.CPP): `BTPlayer`/`Player`/`CameraDirector` all derive from
`Entity`, so `PlayerLinkMessage` / `PlayerLinkMessageID` / `GetEntityID`
resolve through the `Entity` base. No staging.
- **VehicleDeadMessageHandler** (btplayer.cpp) — PARTIAL. The non-death
flavours are real: the engine's `-2` "did the drop-zone reply arrive?" probe
(posted by `Player::HuntForDropZone` during every spawn) and the `>=0`
respawn re-post both delegate to the authentic base
`Player::VehicleDeadMessageHandler`, which only re-hunts a drop zone when the
death is still current and one has not been acquired — so a freshly-spawned
player falls straight through (this is what unblocks bring-up boot). Only the
`deathCount == -1` immediate death notification is still `Fail`-staged: its
full cycle (scoring-role life debit, +5 s respawn re-post, warp collapse) is
downstream of the per-frame mech sim (mech4) + scoring roles.
With these two, **the reconstructed tree boots into the running per-frame game
loop** with zero `Fail()` reached: engine → app → mission → BTPlayer →
CreatePlayerVehicle → Mech ctor (33 subsystems from the real TEST.EGG model) →
InitializePlayerLink → DropZone acquire → the `-2` probe returns harmlessly →
`LBE4ControlsManager::Execute` spins per-frame. The only thing it now waits on
is RIO cockpit hardware (absent in the headless smoke test).
## Still staged (next bricks)
1. VehicleDeadMessageHandler(`-1`) death cycle — with mech4 + scoring roles.
2. Score / ScoreInflicted / ScoreUpdate handlers (scoring wave),
PlayerSimulation (console update), CalcInflictedScore.
3. DropZoneReply respawn tail (`Mech::Reset` in-place heal/move).
+25 -1
View File
@@ -25,11 +25,35 @@ Derivation
"Emitter"
);
//
//#############################################################################
// Attribute Support. Emitter publishes the beam charge level (ChargeLevel):
// the HUD weapon-charge gauge binds to it, and -- load-bearing -- GaussRifle's
// AttributeIndex chains THIS index (GAUSS.CPP), and PPC::DefaultData binds it
// via the inherited PPC::AttributeIndex. It MUST be a real defined index
// chained to Subsystem::AttributeIndex, not the bare base (a declared-but-
// undefined Emitter::AttributeIndex leaves activeAttributeIndex NULL and
// faults the first GetAttributePointer on any PPC/Gauss).
//#############################################################################
//
const Emitter::IndexEntry
Emitter::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Emitter, ChargeLevel, chargeLevel)
};
Emitter::AttributeIndexSet
Emitter::AttributeIndex(
ELEMENTS(Emitter::AttributePointers),
Emitter::AttributePointers,
Subsystem::AttributeIndex
);
Emitter::SharedData
Emitter::DefaultData(
Emitter::ClassDerivations,
Subsystem::MessageHandlers,
Subsystem::AttributeIndex,
Emitter::AttributeIndex,
Subsystem::StateCount
);
+31
View File
@@ -119,3 +119,34 @@ Phase-5 step 1 = per-subsystem AttributeIndex, and it's a precision task:
mappings reference them by number).
Then: watcher/plug/capability-chain wiring, then the mech2/3/4 per-frame sim
(~9K lines, staged), then rendering. This is the behavioral half.
## PHASE-5.1 CRASH SOLVED + BOOT REACHES THE GAME LOOP (2026-07-20)
The `GetAttributePointer` fault was localized (per-slot probe over the roster)
to roster **slot 22 = `PPC_1`**, an energy weapon: its `GetAttributePointer`
read `entryCount` off a **NULL `activeAttributeIndex`**. Root cause: our
reconstructed `EMITTER.HPP` DECLARES `static AttributeIndexSet AttributeIndex`
(with a `ChargeLevelAttributeID`) but `EMITTER.CPP` never DEFINED it — and the
surviving 4.10 `PPC.CPP` binds `PPC::DefaultData` to the inherited
`PPC::AttributeIndex` (= `Emitter::AttributeIndex`), so the SharedData ctor's
`activeAttributeIndex(&attribute_index)` captured a null/zero symbol. The
surviving `GAUSS.CPP` also *chains* `Emitter::AttributeIndex` as its
inheritance, so the same hole corrupts GaussRifle. (Sensor et al. never
crashed: their `DefaultData` uses the defined `Subsystem::AttributeIndex`.)
**Fix (EMITTER.CPP):** define `Emitter::AttributePointers[]` (one entry,
`ATTRIBUTE_ENTRY(Emitter, ChargeLevel, chargeLevel)`) and `Emitter::AttributeIndex`
chained to `Subsystem::AttributeIndex`, and point `Emitter::DefaultData` at it.
This is the correct 1995 reconstruction (Emitter publishes the beam charge
level for the HUD gauge; PPC binds it; Gauss chains it). Static-init order is
satisfied by ORDER_bt (`... emitter ppc ... gauss`) with `subsystm` in
munga.lib (constructs first). After the fix the per-slot probe walks all 33
slots (PPC_1/2, ERMLaser_1..3, AmmoBinSRM6_1/2, SRM6_1/2, MessageManager,
MechTech, ...) cleanly, and the control-mapping binding block completes.
**Result:** with the Emitter fix + BTPlayer::InitializePlayerLink +
VehicleDeadMessageHandler(non-death), the reconstructed tree now boots with
**zero `Fail()`** all the way into the live per-frame game loop
(`LBE4ControlsManager::Execute` spinning). See BTPLAYER.NOTES.md. The remaining
gap to "playable" is behavioral (mech2/3/4 per-frame sim + renderer + real
control input), not structural — the boot path is complete.