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>
This commit is contained in:
Cyd
2026-07-20 12:20:23 -05:00
co-authored by Claude Fable 5
parent 812ca99652
commit 3610ff1ecd
4 changed files with 161 additions and 14 deletions
+41 -9
View File
@@ -268,10 +268,29 @@ void
InitializePlayerLink();
//
// A piloted mech runs the combat PlayerSimulation and is a scoring
// player; a camera-ship observer runs CameraShipSimulation and never
// ranks. (Vehicle-class dispatch + mech placement deferred with the
// Mech subsystem.)
// Choose the per-vehicle Performance from the vehicle's class. A piloted
// mech runs the combat PlayerSimulation and is a scoring player; a
// camera-ship observer runs CameraShipSimulation and never ranks. This
// is load-bearing for mission flow: switching off HuntForDropZone onto
// PlayerSimulation is what starts the launch-fade countdown that advances
// the application to RunningMission (so the world begins executing).
//
if (playerVehicle->GetClassID() == (Entity::ClassID)MechClassID)
{
SetPerformance(&BTPlayer::PlayerSimulation);
SetScoringPlayerFlag();
}
else
{
SetPerformance((Performance)&Player::CameraShipSimulation);
playerRanking = -1;
}
//
// (Mech placement -- Mech::Reset heal+move to the drop-zone origin, the
// translocation cockpit alarm, and the warp-sphere reveal -- is deferred
// with the mech per-frame simulation; the fresh mech sits at its ctor
// origin for now.)
//
AlwaysExecute();
deathCount = 0;
@@ -446,13 +465,26 @@ void
//
//#############################################################################
// PlayerSimulation -- console-update performance (not yet reconstructed).
// The constructor installs this as the replicant Performance; its body is
// exercised only on a networked console-driven run.
// PlayerSimulation -- the master player's per-frame Performance (installed by
// DropZoneReply once the mech exists, and by the replicant ctor). Chains the
// authentic engine base Player::PlayerSimulation, which is what actually drives
// mission flow: CalcRanking + ManageApplicationStatus (counts down the launch
// fade and, when it expires, dispatches the second RunMissionMessage that
// advances LaunchingMission -> RunningMission so the world starts executing) +
// vehicle-position copy + status-message service.
//
// The BT-specific tail is the periodic console SCORE-delta flush
// (ConsolePlayerVTVScoreUpdateMessage over the network to the operator
// console). It is a no-op without a live console host and needs the networked
// console-message type; it is deferred to the scoring wave.
//#############################################################################
//
void
BTPlayer::PlayerSimulation(Scalar)
BTPlayer::PlayerSimulation(Scalar time_slice)
{
Fail("BTPlayer::PlayerSimulation -- btplayer.cpp not yet reconstructed");
Check(this);
Player::PlayerSimulation(time_slice);
Check_Fpu();
}
+42 -4
View File
@@ -103,9 +103,47 @@ 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. VehicleDeadMessageHandler(`-1`) death cyclewith mech4 + scoring roles.
2. Score / ScoreInflicted / ScoreUpdate handlers (scoring wave),
PlayerSimulation (console update), CalcInflictedScore.
3. DropZoneReply respawn tail (`Mech::Reset` in-place heal/move).
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).
+39 -1
View File
@@ -119,7 +119,45 @@ void
void
Sensor::SensorSimulation(Scalar)
{
Fail("Sensor::SensorSimulation -- sensor.cpp not yet reconstructed");
Check(this);
//
// Minimal safe per-frame sensor update (PARTIAL reconstruction). The
// authentic SensorSimulation (BT411 @004b1c4c) first runs
// PoweredSubsystem::PoweredSubsystemSimulation, then gates radarPercent /
// selfTest / badVoltage on the heat state (Normal / Degradation / Failure)
// and the electrical Ready state -- both of which live in the power/heat
// per-frame sim chain that is not yet reconstructed (the powersub/heat
// *Simulation methods are staged). Until that wave, derive radarPercent
// from the one reconstructed input -- this sensor's own structural damage
// level ([0,1], 0 intact .. 1 destroyed) -- and report the sensor healthy.
// This keeps the engine's roster tick path (Entity::PerformAndWatch) safe
// while producing a real radar-capability value. See SENSOR.NOTES.md.
//
radarPercent = 1.0f - GetSubsystemDamageLevel(); // RadarBaseline - zoneDamage
if (radarPercent < 0.0f)
{
radarPercent = 0.0f;
}
selfTest = True;
badVoltage = False;
{
//
// One-shot confirmation that the engine's per-frame entity/roster tick
// (Entity::PerformAndWatch) has engaged -- i.e. the mission reached
// RunningMission and the mech is being simulated each frame.
//
static int firstTick = 0;
if (!firstTick && getenv("BT_MECH_LOG"))
{
firstTick = 1;
DEBUG_STREAM << "[tick] roster live (first Sensor frame), radarPercent="
<< radarPercent << endl << flush;
}
}
Check_Fpu();
}
//
+39
View File
@@ -0,0 +1,39 @@
# SENSOR.CPP — reconstruction notes
Built against the surviving 4.10 CODE header `CODE/BT/BT/SENSOR.HPP` (Sensor :
PoweredSubsystem). Attributes RadarPercent / SelfTest / BadVoltage chain
`PoweredSubsystem::NextAttributeID`.
## Reconstructed
- Ctor: primes radarPercent=1, selfTest=False, badVoltage=False and installs
`Sensor::SensorSimulation` as the per-frame Performance (unless the owner is a
replicant copy, which is console-driven).
- `TestClass` / `TestInstance` / `ResetToInitialState` / the shared-data statics.
## PARTIAL: Sensor::SensorSimulation (per-frame)
The authentic `SensorSimulation` (BT411 @004b1c4c) does, each frame:
1. `PoweredSubsystem::PoweredSubsystemSimulation(time_slice)` (power/voltage tick),
2. `radarPercent = RadarBaseline - GetSubsystemDamageLevel()`,
3. gate on the electrical Ready state (`badVoltage`, zeroes radar if not Ready),
4. gate on the heat state — Normal / Degradation (`radar *= 0.5`) / Failure
(radar 0, selfTest off).
Steps 1/3/4 depend on the **power/heat per-frame simulation chain that is not
yet reconstructed** — `PoweredSubsystem::PoweredSubsystemSimulation`,
`Generator::GeneratorSimulation`, `HeatSink::HeatSinkSimulation`,
`HeatWatcher::WatchSimulation`, `PowerWatcher::Simulation` are all staged
`Fail`. None of those are installed as Performances yet (only Sensor installs
one), so the roster otherwise ticks `DoNothingOnce`.
Until the power/heat wave, `SensorSimulation` runs the one reconstructed input
(step 2, using `MechSubsystem::GetSubsystemDamageLevel`, clamped ≥ 0) and
reports the sensor healthy (selfTest=True, badVoltage=False). This is a
**deliberately safe partial**: it keeps the engine's `Entity::PerformAndWatch`
roster-tick path alive (it must not `Fail`, or the whole per-frame simulation
aborts the frame the mission reaches RunningMission) while producing a real
radar-capability value. Deepen it with the power/heat simulation wave.
A gated one-shot (`BT_MECH_LOG`) logs the first Sensor frame — the marker that
the entity/roster simulation has engaged (RunningMission).