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 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,34 @@
|
|||||||
// All Rights reserved worldwide //
|
// All Rights reserved worldwide //
|
||||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
// 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 <bt.hpp>
|
#include <bt.hpp>
|
||||||
#pragma hdrstop
|
#pragma hdrstop
|
||||||
@@ -13,6 +41,17 @@
|
|||||||
# include <btdirect.hpp>
|
# include <btdirect.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(CAMSHIP_HPP)
|
||||||
|
# include <camship.hpp> // CameraShip::DirectionMessage (the track)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(APP_HPP)
|
||||||
|
# include <app.hpp> // 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
|
Derivation
|
||||||
BTCameraDirector::ClassDerivations(
|
BTCameraDirector::ClassDerivations(
|
||||||
CameraDirector::ClassDerivations,
|
CameraDirector::ClassDerivations,
|
||||||
@@ -28,22 +67,162 @@ BTCameraDirector::SharedData
|
|||||||
(Entity::MakeHandler)BTCameraDirector::Make
|
(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(
|
BTCameraDirector::BTCameraDirector(
|
||||||
MakeMessage *creation_message,
|
MakeMessage *creation_message,
|
||||||
SharedData &shared_data
|
SharedData &shared_data
|
||||||
):
|
):
|
||||||
CameraDirector(creation_message, shared_data)
|
CameraDirector(creation_message, shared_data)
|
||||||
{
|
{
|
||||||
Fail("BTCameraDirector -- btdirect.cpp not yet reconstructed");
|
Check(this);
|
||||||
|
|
||||||
|
if ((simulationFlags & 0xC) != 4)
|
||||||
|
{
|
||||||
|
SetPerformance(&BTCameraDirector::CreateBTCameraShip);
|
||||||
|
}
|
||||||
|
|
||||||
|
Check_Fpu();
|
||||||
}
|
}
|
||||||
|
|
||||||
BTCameraDirector::~BTCameraDirector()
|
BTCameraDirector::~BTCameraDirector()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @004c1454 -- the registered MakeHandler: allocate (0x238) and construct.
|
||||||
|
//
|
||||||
BTCameraDirector*
|
BTCameraDirector*
|
||||||
BTCameraDirector::Make(CameraDirector::MakeMessage *)
|
BTCameraDirector::Make(CameraDirector::MakeMessage *creation_message)
|
||||||
{
|
{
|
||||||
Fail("BTCameraDirector::Make -- btdirect.cpp not yet reconstructed");
|
BTCameraDirector
|
||||||
return NULL;
|
*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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// TakeDamageMessageHandler -- the Mech override of the Entity damage entry
|
||||||
|
|||||||
@@ -597,6 +597,19 @@
|
|||||||
Subsystem*
|
Subsystem*
|
||||||
GetSensorSubsystem() { Check(this); return sensorSubsystem; }
|
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
|
// Locomotion parameters (read by MechControlsMapper::InterpretControls to
|
||||||
// shape the demands, and by the drive in Simulate). reverseStrideLength is
|
// shape the demands, and by the drive in Simulate). reverseStrideLength is
|
||||||
|
|||||||
Reference in New Issue
Block a user