Files
TeslaRel410/restoration/source410/BT/BTPLAYER.NOTES.md
T
CydandClaude Fable 5 3610ff1ecd BT410: entity simulation goes LIVE -- mission reaches RunningMission, mech + roster tick per-frame
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>
2026-07-20 12:20:23 -05:00

150 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BTPLAYER.CPP / .HPP — reconstruction notes
**Status: constructor / dtor / Make / TestInstance / GetExperienceLevel +
message-handler table + DropZoneReply dispatch RECONSTRUCTED; the scoring/death
handlers, PlayerSimulation, and CreatePlayerVehicle/InitializePlayerLink (the
Mech-subsystem frontier) still staged.**
## Message-handler table (wired 2026-07-19)
DefaultData now uses `BTPlayer::MessageHandlers` (chained onto
`Player::MessageHandlers`) instead of reusing the base set — the earlier reuse
left BTPlayer's own handlers unwired, so a DropZoneReply reached the base
`Player::DropZoneReplyMessageHandler` stub that Fails "should not be handled by
base player class!" (PLAYER.CPP:179). Table entries (BT411 .data @005130c0):
DropZoneReply, VehicleDead, Score, ScoreInflicted, ScoreUpdate. ScoreInflicted/
ScoreUpdate add two BT message IDs (`Player::NextMessageID`+0/+1). MissionStarting/
MissionEnding are inherited from Player unchanged (not re-listed — BT411 lists
them but they point at the same base handlers).
`DropZoneReplyMessageHandler` is the real spawn/respawn dispatch skeleton; its
mech-placement tail (vehicle-class performance select, Mech::Reset respawn) is
deferred with the Mech subsystem. On the first fresh-drop reply it calls
`CreatePlayerVehicle` → the current boot frontier.
## Sources
- BT411 `game/reconstructed/btplayer.cpp` (Ghidra-recovered ctor @004c0bc8,
Make @004c0ecc, dtor @004c0efc, TestInstance @004c0f28) — byte-offset
annotated body.
- Surviving 1995 headers pin every base API: PLAYER.HPP (base members
currentScore/scenarioRole/deathCount/playerMission, GetMission), SIMULATE.HPP
(lastPerformance/lastUpdate/simulationFlags/activePerformance — `lastPerformance`
is a BASE member, not BT-own), ENTITY.HPP (GetInstance/ReplicantInstance),
NTTMGR.HPP (EntityManager::FindGroup, EntityGroup::groupMembers = ChainOf<Node*>),
TEAM.HPP (Team::teamName[64], public — BTTeam inherits it), BTMSSN.HPP
(BTMission::ExperienceLevel()/AdvancedDamageOn(), enum Novice/Standard/
Veteran/Expert = 0..3), SCNROLE.HPP.
## Member layout note
The staged header's `int reserved[38]` placeholder was replaced with the real
BT-own members (btMission, teamName[64], teamEntity, freeForAll, consoleAttached,
suppressConsole, the experience-flag block, killCount/padScore, objectiveMech,
lastConsoleUpdate, deathPending). Byte offsets do NOT need to match the shipped
0x294-byte object — this is a fresh self-consistent build; only wire-format
structs (MakeMessage) must match the binary, and those are unchanged.
## Experience-level flag mapping — the [T1] correction
The ctor derives the game-mode flags from **btMission->ExperienceLevel()**
(mission+0xe4, the egg's per-pilot "experience" key), NOT from the scenario
role's returnFromDeath — see [[bt-experience-levels]]. BT411's ported ctor uses
`scenarioRole->GetReturnFromDeath()` as a stand-in and marks it [T3]; this 1995
reconstruction encodes the binary-accurate reading instead.
Binary switch rows (member ← value per level), from the [T1] disassembly:
| level | showDamageReceived (0x25c) | showKills (0x260) | roleReturnDelay2 (0x26c) | showScore (0x270) |
|----------|:--:|:--:|:--:|:--:|
| novice | 0 | 0 | 0 | 0 |
| standard | 1 | 0 | 1 | 1 |
| veteran | 1 | 1 | 1 | 1 |
| expert | 1 | 1 | 0 | 1 |
Plus `showDamageInflicted` (0x264) = `roleReturnDelay` (0x268) = advancedDamageOn.
**Member NAMES are wrong-but-kept** (they date from the earlier scoring work):
`showKills` is really the HEAT-MODEL master switch (FUN_004ad7d4), `showDamageReceived`
is the "sim live / novice lockout" gate, `showScore`/`roleReturnDelay2` consumers
not fully located ([T4]). Rename is a follow-up wiring task; the VALUES are correct.
## Deliberate differences from BT411's port
- Make: the 1995 `return new BTPlayer(creation_message);` — BT411's replicant
`SetValidFlag()` is a P6 network-bring-up addition, dropped.
- Replicant-mission caching uses `application->GetCurrentMission()` (the real
1995 accessor, PLAYER.CPP:689) instead of BT411's raw `application+0xc8`.
## Reconstructed 2026-07-20 (boot reaches the live game loop)
- **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).
## Reconstructed 2026-07-20 (entity simulation goes LIVE)
- **DropZoneReplyMessageHandler** now, after the fresh spawn (CreatePlayerVehicle
+ InitializePlayerLink), CHOOSES the per-vehicle Performance from the vehicle
class: a Mech gets `SetPerformance(&BTPlayer::PlayerSimulation)` +
`SetScoringPlayerFlag()`; anything else (camera ship) gets
`Player::CameraShipSimulation` + rank 1. This is load-bearing for mission
flow (see below). The mech PLACEMENT tail (`Mech::Reset` heal/move to the
drop-zone origin, translocation alarm, warp-sphere reveal) stays deferred with
the mech per-frame sim; the fresh mech sits at its ctor origin.
- **PlayerSimulation** (btplayer.cpp) — chains the authentic engine base
`Player::PlayerSimulation` (CalcRanking + ManageApplicationStatus + vehicle-
position copy + StatusMessageUpdate). The BT console SCORE-delta flush
(ConsolePlayerVTVScoreUpdateMessage, only meaningful with a live console host)
is deferred to the scoring wave.
### Why this is the piece that starts the world executing
`Entity::Execute` only calls `PerformAndWatch` (i.e. simulates entities) when
`Application::GetApplicationState() == RunningMission`. The app is a state
machine advanced one step per `RunMissionMessage`:
`WaitingForLaunch → LaunchingMission → RunningMission`. With no console,
`CheckLoadMessageHandler` posts the FIRST RunMission (→ LaunchingMission, which
dispatches MissionStarting → the player starts a 3 s fade). The SECOND
RunMission (→ RunningMission) is dispatched by `Player::ManageApplicationStatus`
when that fade expires — but ManageApplicationStatus only runs inside
`PlayerSimulation`/`CameraShipSimulation`, NOT inside the launch-phase
`HuntForDropZone`. So the player must switch its Performance onto PlayerSimulation
after it spawns, or the fade never counts down and the world never runs.
That switch (above) + a non-Fail PlayerSimulation is what takes the boot from
"controls loop only" to a **live per-frame entity simulation**: verified — the
mech is executed each frame and its subsystem roster ticks (Sensor first-frame
marker), zero Fail. The mech BODY is still `DoNothingOnce` (its real
`Mech::Simulate` is Phase 5.3).
## Still staged (next bricks)
1. `Mech::Simulate` — the mech's real per-frame Performance (Phase 5.3): now
the frontier, since the entity tick path is live and calls the mech's
Performance each frame.
2. VehicleDeadMessageHandler(`-1`) death cycle — with mech4 + scoring roles.
3. Score / ScoreInflicted / ScoreUpdate handlers (scoring wave); the console
SCORE-delta flush inside PlayerSimulation.
4. DropZoneReply respawn tail (`Mech::Reset` in-place heal/move).