From 454102ef35830e89aaaee5fe4a097466455ebe1e Mon Sep 17 00:00:00 2001 From: Cyd Date: Tue, 4 Aug 2026 08:49:13 -0500 Subject: [PATCH] BT410 5.3.116: the spectator camera direats -- BTCameraDirector reconstructed from a disassembly gap, and the donor's inverted pick caught The two Performance bodies fell in a Ghidra gap (@004c11b3..@004c1408), so BeABTDirector came straight off ndisasm of the shipped CODE section. The verdict corrects the BT411 donor twice more: FUN_0049fb54 is not 'IsLiveMech' -- it is the mech DEATH-TRANSITION test (simulation state 2 or 9; its other callers strip update-record motion bits and halt motion integration while it holds, and our own MovementMode ledger already said so) -- and the donor's pick loop breaks on a DYING vehicle where the binary's jz breaks on a LIVE one. The true 1995 behavior: the camera lingers on its mech while the 30s timer runs, cuts away THE MOMENT the followed mech starts dying, picks the first of four ranked players with a live vehicle, and refreshes the lock during mission end. The 'BT-specific 0x15/0x34 track message' the port invented an engine substitute for IS the engine's CameraShip::DirectionMessage -- ID 0x15 == Entity::NextMessageID, 0x34 bytes, byte-for-byte; the RP engine's own BeADirector source (DIRECTOR.CPP survives) is the literal structural template BT overrode (rank-0-only, 10s, no offset -> four ranks, 30s, (0,5,0) eye). Mech::InDeathTransition lands in mech.cpp per the census (@0049fb54, 32 bytes). The ctor's replicant gate uses the binary's own flag test -- entity-side simulationFlags arrive live via the MakeMessage, unlike the subsystem masters still waiting on the stream builders. Stubs: 11 across 9 files (the 8 authoring CSS fns + the L4NET trio). Pod-role soak clean; the director's live test rides the operator's camera-egg / mission-review session. Co-Authored-By: Claude Fable 5 --- restoration/source410/BT/BTDIRECT.CPP | 277 +++++++++++++++++++++----- restoration/source410/BT/MECH.CPP | 14 ++ restoration/source410/BT/MECH.HPP | 13 ++ 3 files changed, 255 insertions(+), 49 deletions(-) diff --git a/restoration/source410/BT/BTDIRECT.CPP b/restoration/source410/BT/BTDIRECT.CPP index aa076937..f1b0d9dd 100644 --- a/restoration/source410/BT/BTDIRECT.CPP +++ b/restoration/source410/BT/BTDIRECT.CPP @@ -1,49 +1,228 @@ -//===========================================================================// -// Project: BattleTech // -//---------------------------------------------------------------------------// -// Copyright (C) 1995, Virtual World Entertainment, Inc. // -// All Rights reserved worldwide // -// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // -//===========================================================================// - -#include -#pragma hdrstop - -#if !defined(BTDIRECT_HPP) -# include -#endif - -Derivation - BTCameraDirector::ClassDerivations( - CameraDirector::ClassDerivations, - "BTCameraDirector" - ); - -BTCameraDirector::SharedData - BTCameraDirector::DefaultData( - BTCameraDirector::ClassDerivations, - CameraDirector::MessageHandlers, - CameraDirector::AttributeIndex, - 1, - (Entity::MakeHandler)BTCameraDirector::Make - ); - -BTCameraDirector::BTCameraDirector( - MakeMessage *creation_message, - SharedData &shared_data -): - CameraDirector(creation_message, shared_data) -{ - Fail("BTCameraDirector -- btdirect.cpp not yet reconstructed"); -} - -BTCameraDirector::~BTCameraDirector() -{ -} - -BTCameraDirector* - BTCameraDirector::Make(CameraDirector::MakeMessage *) -{ - Fail("BTCameraDirector::Make -- btdirect.cpp not yet reconstructed"); - return NULL; -} +//===========================================================================// +// Project: BattleTech // +//---------------------------------------------------------------------------// +// Copyright (C) 1995, Virtual World Entertainment, Inc. // +// All Rights reserved worldwide // +// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // +//===========================================================================// +// +// BTCameraDirector -- the spectator/live-cam director (camera-role eggs). +// The surviving BTDIRECT.HPP is ground truth for the interface. The two +// Performance bodies fell in a Ghidra gap (@004c11b3..@004c1408) and were +// recovered by direct disassembly of the shipped exe (ndisasm of the CODE +// section); the construction family comes from the decomp (part_013.c). +// +// ctor @004c1408 (chains CameraDirector @0042eec8) +// Make @004c1454 (new 0x238 + ctor) +// ~BTCameraDirector @004c1484 +// TestInstance @004c14b0 +// CreateBTCameraShip @004c13d4 initial Performance +// BeABTDirector @004c1230 steady Performance +// +// The engine base (RP DIRECTOR.CPP survives) is the literal template: +// CameraDirector::BeADirector = PlayerSimulation + UpdateHUD + a 10s +// rotation onto FindPlayerByRank(0)'s vehicle. The BT override widens the +// scan to the first four ranks, stretches the rotation to 30s, offsets the +// camera eye (0, 5, 0), refreshes the lock while the mission ends -- and +// CUTS AWAY from the followed mech the moment it enters the death +// transition (Mech::InDeathTransition, binary @0049fb54). NOTE: the BT411 +// donor's pick loop is inverted here (it breaks on a DYING vehicle); the +// disassembly's pick breaks on jz -- the first LIVE vehicle wins. +// +// The "BT-specific" track message turns out to be the engine's own +// CameraShip::DirectionMessage (ID 0x15 == Entity::NextMessageID, 0x34 +// bytes) -- no BT message class exists. +// + +#include +#pragma hdrstop + +#if !defined(BTDIRECT_HPP) +# include +#endif + +#if !defined(CAMSHIP_HPP) +# include // CameraShip::DirectionMessage (the track) +#endif + +#if !defined(APP_HPP) +# include // application state gates +#endif + +#define CAMERA_SWITCH_INTERVAL 30.0f // 0x41f00000 -- BT stretches the base 10s +#define CAMERA_EYE_HEIGHT 5.0f // 0x40a00000 -- fixed eye offset + +Derivation + BTCameraDirector::ClassDerivations( + CameraDirector::ClassDerivations, + "BTCameraDirector" + ); + +BTCameraDirector::SharedData + BTCameraDirector::DefaultData( + BTCameraDirector::ClassDerivations, + CameraDirector::MessageHandlers, + CameraDirector::AttributeIndex, + 1, + (Entity::MakeHandler)BTCameraDirector::Make + ); + +// +//############################################################################# +// Construction / Destruction +// +// @004c1408 -- chains the engine CameraDirector ctor; a non-replicant copy +// gets CreateBTCameraShip as its initial Performance. The replicant test is +// the entity flag pair (simulationFlags & 0xC) == 4 -- these flags arrive +// via the MakeMessage's instance flags, so the binary's own gate works here +// (unlike the subsystem masters, whose 0xC/0x100 seeding still waits on the +// mech stream builders). +//############################################################################# +// +BTCameraDirector::BTCameraDirector( + MakeMessage *creation_message, + SharedData &shared_data +): + CameraDirector(creation_message, shared_data) +{ + Check(this); + + if ((simulationFlags & 0xC) != 4) + { + SetPerformance(&BTCameraDirector::CreateBTCameraShip); + } + + Check_Fpu(); +} + +BTCameraDirector::~BTCameraDirector() +{ +} + +// +// @004c1454 -- the registered MakeHandler: allocate (0x238) and construct. +// +BTCameraDirector* + BTCameraDirector::Make(CameraDirector::MakeMessage *creation_message) +{ + BTCameraDirector + *director = new BTCameraDirector(creation_message); + Register_Object(director); + return director; +} + +// +// @004c14b0 +// +Logical + BTCameraDirector::TestInstance() const +{ + return IsDerivedFrom(ClassDerivations); +} + +// +//############################################################################# +// CreateBTCameraShip -- @004c13d4, the initial Performance. One-shot: let +// the engine base spawn the spectator camera ship and wire its viewport, +// then hand the per-frame work to the steady director. +//############################################################################# +// +void + BTCameraDirector::CreateBTCameraShip(Scalar time_slice) +{ + Check(this); + + CameraDirector::CreateCameraShip(time_slice); + + SetPerformance(&BTCameraDirector::BeABTDirector); +} + +// +//############################################################################# +// BeABTDirector -- @004c1230, the steady Performance (disassembly-verified). +// +// Per frame: the inherited player-side update and viewpoint/HUD work, then +// the pick. While the rotation timer runs during a live mission the camera +// stays put -- UNLESS the followed mech has entered its death transition, +// in which case the re-pick happens immediately (the cut away from the +// kill). On a re-pick the first of the four ranked players whose vehicle +// is alive wins; a switch restarts the 30s timer and sends the camera ship +// a DirectionMessage with the (0, 5, 0) eye offset. While the mission is +// ending the lock refreshes even if the target is unchanged. +//############################################################################# +// +void + BTCameraDirector::BeABTDirector(Scalar time_slice) +{ + Check(this); + + Player::PlayerSimulation(time_slice); + UpdateHUD(time_slice); + + Logical + mission_ending = False; + + if ( + timeLeftOnPlayer > 0.0f && + application->GetApplicationState() == Application::RunningMission + ) + { + if (!Mech::InDeathTransition(GetGoalEntity())) + { + timeLeftOnPlayer -= time_slice; + return; + } + // the followed mech is dying -- fall through and cut now + } + else if (application->GetApplicationState() == Application::EndingMission) + { + mission_ending = True; + } + + // + // The first ranked player (0..3) with a live vehicle wins the camera. + // + Player + *player = NULL; + Entity + *vehicle = NULL; + int + rank; + for (rank = 0; rank < 4; rank++) + { + player = FindPlayerByRank(rank); + if (player != NULL) + { + vehicle = player->GetPlayerVehicle(); + if (vehicle != NULL && !Mech::InDeathTransition(vehicle)) + { + break; + } + } + } + if (player == NULL || vehicle == NULL) + { + return; + } + + if (vehicle == GetGoalEntity() && !mission_ending) + { + return; + } + + SetGoalEntity(vehicle); + timeLeftOnPlayer = CAMERA_SWITCH_INTERVAL; + + CameraShip::DirectionMessage + track( + CameraShip::DirectionMessageID, + sizeof(CameraShip::DirectionMessage), + GetGoalEntity()->GetEntityID(), + CAMERA_SWITCH_INTERVAL, + Point3D(0.0f, CAMERA_EYE_HEIGHT, 0.0f) + ); + Check(cameraShip); + cameraShip->Dispatch(&track); + + Check_Fpu(); +} diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index 1a011fd8..bf14fe63 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -796,6 +796,20 @@ Mech::~Mech() { } +// +//############################################################################# +// InDeathTransition -- binary @0049fb54 (32 bytes): simulation state 2 or 9. +//############################################################################# +// +Logical + Mech::InDeathTransition(Entity *vehicle) +{ + Check(vehicle); + return + vehicle->GetSimulationState() == 2 || + vehicle->GetSimulationState() == 9; +} + // //############################################################################# // TakeDamageMessageHandler -- the Mech override of the Entity damage entry diff --git a/restoration/source410/BT/MECH.HPP b/restoration/source410/BT/MECH.HPP index 7b1a5a0a..c20c34f7 100644 --- a/restoration/source410/BT/MECH.HPP +++ b/restoration/source410/BT/MECH.HPP @@ -597,6 +597,19 @@ Subsystem* GetSensorSubsystem() { Check(this); return sensorSubsystem; } + // + // The death-transition test (binary @0049fb54, mech.cpp): True while + // the simulation state is 2 or 9 -- the two death-transition states + // in the MovementMode ledger. Multiple families key off it: update + // records strip their motion bits while it holds (@004a4c54), motion + // stops integrating (mech4 dead-reckon head), and the spectator + // camera director cuts AWAY from a mech the moment it starts dying + // (BeABTDirector @004c1230). Takes the ENTITY view because callers + // hold vehicles as Entity* (the state field is the Simulation base's). + // + static Logical + InDeathTransition(Entity *vehicle); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Locomotion parameters (read by MechControlsMapper::InterpretControls to // shape the demands, and by the drive in Simulate). reverseStrideLength is