Files
TeslaRel410/restoration/source410/BT/BTPLAYER.NOTES.md
T
CydandClaude Fable 5 d4f0aef6e1 BT410 Phase 5.3.17: the destruction cascade -- zone deaths DESTROY things
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>
2026-07-24 11:13:28 -05:00

182 lines
10 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).
## Experience flags renamed to TRUE semantics (2026-07-22, Phase 5.3.12)
The `show*` / `roleReturn*` block was the old "per-role display toggles"
misread (the memory note flagged it). Renamed + retyped to the binary truth:
`simLive` @0x25c (novice lockout: jams/lights/powersub short events),
`heatModelOn` @0x260 (THE heat-model master switch, FUN_004ad7d4),
`advancedDamageOn`/`advancedDamageOn2` (the egg technician flag pair),
`levelFlag26c` (0/1/1/0, consumer unlocated), `levelFlag270` (0/1/1/1),
`experienceLevel` (raw 0..3). The ctor rows were already binary-accurate.
Public accessors IsSimLive()/IsHeatModelOn()/IsAdvancedDamageOn(); a gated
`[exp]` sentinel prints the derived flags per master player. Consumers:
HeatSink::HeatModelActive() (HEAT.CPP), ProjectileWeapon::LiveFireEnabled()
(PROJWEAP.CPP) -- both walk mech->GetPlayerLink(), NULL-permissive.
## 5.3.16 — BT_SPAWN_ENEMY dev harness
DropZoneReplyMessageHandler, after the fresh-spawn block: `BT_SPAWN_ENEMY=1`
clones the CreatePlayerVehicle MakeMessage recipe (same game-model resource,
badge/color/patch, `Mech::DefaultFlags`) at dropZone+150 on Z, calls
`Mech::Make()` directly (the Entity ctor self-registers + uniquifies the ID)
and sets it as the player mech's target. The enemy has NO player link, so its
experience gates read dev-permissive; it never fires back (no target of its
own — set one for a mutual fight). DEV-ONLY: the real opponent path is the
network mesh / mission roster.
## 5.3.17 — BT_SPAWN_ENEMY is now a MUTUAL fight
The harness also sets the enemy's target back to the player: under
BT_FORCE_FIRE the enemy returns fire (its gates read dev-permissive with no
player link), so the damage cascade gets a live incoming side — used to
verify the player's own weapons fall silent when destroyed.