Files
TeslaRel410/restoration/source410/BT_L4/BTL4PB.CPP
T
CydandClaude Fable 5 d2be54a3b9 BT410 5.3.106: MISSION REVIEW LINKS -- the playback app, the winners circle, and the last Fail() in main
BTL4PB.CPP reconstructed against its AUTHENTIC surviving header (BTL4PB.HPP)
from the binary's 0x4d3b74-0x4d40d8 cluster, cross-checked against the
surviving RP411 twin -- and the review/camera launch in BTL4.CPP's main() is
real code now.  btl4.lib 11 -> 12 members, link clean, zero unresolved: the
linker pulled the entire playback chain for the first time.

The launch decode closed on two identifications:

  The `if (spool_file)` guard in the decomp means the manager getter is
  GetStoredSpoolFile -- the empty-spool getter cannot fail, so a review pod
  with no recorded battle simply starts nothing, which is the right field
  behaviour.

  The camera app's ctor (FUN_0047c3fc) sits in the ENGINE range with the
  (ResourceFile*, ApplicationID) shape: the camera pod's recorder IS the
  engine's own L4SpoolingApplication -- restored one commit ago -- started
  with ApplicationID 1 == BTL4.  BT shipped no camera app of its own.

What the playback app does, all binary-anchored: BTSpoolerTask pumps
recorded packets through DispatchPacket -> the client's own
ReceiveNetworkPacket; RunMission synchronises the clock (timeBias = Now -
the recorded run packet's stamp); the frame loop runs controls ONLY in
interactive review (mode 2), then update/interest/renderer/intercom; and
StopMission plays the WINNERS CIRCLE -- release the spool, park each
player's mech on the dropzone named "win1".."winN" by finishing rank,
freeze it (DoNothing performance), and tilt the camera up 45 degrees over
the tableau.

DOS-authentic ctor shape throughout: the binary's base call is (resource,
0x3eb, DefaultData) -- no windowing parameters ever existed in 4.10; the
RP411 twin's HINSTANCE/HWND threading is port drift, dropped.

Also fixed from 5.3.105's debt: L4PlaybackNetworkManager chains
NetworkManager::DefaultData -- the 1995 header declares no statics of its
own; the RP411 self-reference was drift too.

One [T3] in source: the idle frame's renderer service (the descendant's
ExecuteIdle) has no 1995 entry point; pre-egg the idle frame spins.

RUNTIME TEST PENDING, stated plainly: exercising review needs a
camera/review egg and a recorded spool on the rig (CAMERA-REVIEW-NOTES.md
has the roles).  The normal pod path regressed clean (mode gate untouched).

The Fail() census moves: BTL4.CPP and SEQCTL.CPP are now stub-free; 22
stubs remain across 15 files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 15:54:31 -05:00

445 lines
13 KiB
C++

//===========================================================================//
// File: btl4pb.cpp //
// Project: BattleTech Brick: BattleTech LBE Application //
// Contents: BTL4PlaybackApplication -- the mission-review application, and //
// BTSpoolerTask, the pump that replays recorded NetworkPackets //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED against the AUTHENTIC surviving header (BTL4PB.HPP) from the
// shipped binary's 0x4d3b74-0x4d40d8 cluster, cross-checked against the
// surviving RP411 descendant RPL4PB.cpp (a line-for-line structural twin).
// The binary's own base-ctor call (FUN_004d34c4(this, resource, 0x3eb,
// DefaultData)) confirms the DOS ctor shape this file uses -- no windowing
// parameters ever existed in 4.10.
//
#include <btl4.hpp>
#pragma hdrstop
#if !defined(BTL4PB_HPP)
# include <btl4pb.hpp>
#endif
#if !defined(MISSION_HPP)
# include <mission.hpp>
#endif
#if !defined(L4NET_HPP)
# include <l4net.hpp>
#endif
#if !defined(UPDATE_HPP)
# include <update.hpp>
#endif
#if !defined(RENDERER_HPP)
# include <renderer.hpp>
#endif
#if !defined(ICOM_HPP)
# include <icom.hpp>
#endif
#if !defined(DROPZONE_HPP)
# include <dropzone.hpp>
#endif
#if !defined(DIRECTOR_HPP)
# include <director.hpp>
#endif
#if !defined(NTTMGR_HPP)
# include <nttmgr.hpp>
#endif
#if !defined(MECH_HPP)
# include <mech.hpp>
#endif
#if !defined(L4VIDEO_HPP)
# include <l4video.hpp>
#endif
#if !defined(L4CTRL_HPP)
# include <l4ctrl.hpp>
#endif
//#############################################################################
//########################## BTSpoolerTask ##############################
//#############################################################################
//
// @004d3b74 / @004d3bb8 -- chain-only construction; the task adds no data.
//
BTSpoolerTask::BTSpoolerTask()
{
}
BTSpoolerTask::~BTSpoolerTask()
{
Check_Fpu();
}
//
//#############################################################################
// @004d3b90 / @004d3ba8 -- the pump's two callbacks forward to the running
// playback application.
//#############################################################################
//
void
BTSpoolerTask::DispatchPacket(NetworkPacket *packet)
{
BTL4PlaybackApplication
*playback_app =
Cast_Object(BTL4PlaybackApplication *, application);
playback_app->DispatchPacket(packet);
Check_Fpu();
}
long
BTSpoolerTask::GetTimeBias()
{
BTL4PlaybackApplication
*playback_app =
Cast_Object(BTL4PlaybackApplication *, application);
return playback_app->GetTimeBias();
}
//#############################################################################
//####################### BTL4PlaybackApplication #######################
//#############################################################################
const Receiver::HandlerEntry
BTL4PlaybackApplication::MessageHandlerEntries[] =
{
MESSAGE_ENTRY(BTL4PlaybackApplication, RunMission), // @004d3ef8
MESSAGE_ENTRY(BTL4PlaybackApplication, StopMission) // @004d3f44
};
BTL4PlaybackApplication::MessageHandlerSet
BTL4PlaybackApplication::MessageHandlers(
ELEMENTS(BTL4PlaybackApplication::MessageHandlerEntries),
BTL4PlaybackApplication::MessageHandlerEntries,
BTL4Application::MessageHandlers
);
Derivation
BTL4PlaybackApplication::ClassDerivations(
BTL4Application::ClassDerivations,
"BTL4PlaybackApplication" // @0051f182
);
BTL4PlaybackApplication::SharedData
BTL4PlaybackApplication::DefaultData( // @0051ee5c
BTL4PlaybackApplication::ClassDerivations,
BTL4PlaybackApplication::MessageHandlers
);
//
//#############################################################################
// @004d3be4 -- the binary's base call is (resource, 0x3eb, DefaultData): the
// L4ApplicationClassID, no windowing. The spool lands in the inherited
// spoolFile slot.
//#############################################################################
//
BTL4PlaybackApplication::BTL4PlaybackApplication(
ResourceFile *resource_file,
SpoolFile *spool_file
):
BTL4Application(resource_file, L4ApplicationClassID, DefaultData)
{
spoolFile = spool_file;
timeBias = 0;
Check_Fpu();
}
BTL4PlaybackApplication::~BTL4PlaybackApplication()
{
Check_Fpu();
}
Logical
BTL4PlaybackApplication::TestInstance() const
{
return IsDerivedFrom(ClassDerivations);
}
//
//#############################################################################
// @004d3c14 -- playback drives the spool-fed network manager, not a live one.
//#############################################################################
//
NetworkManager *
BTL4PlaybackApplication::MakeNetworkManager()
{
Check_Fpu();
return new L4PlaybackNetworkManager;
}
//
//#############################################################################
// @004d3c34
//#############################################################################
//
void
BTL4PlaybackApplication::Initialize()
{
Check(this);
L4Application::InitializeTillConsole();
}
//
//#############################################################################
// @004d3c44 -- the playback task pipeline: our own spool pump first, then the
// stock event / gauge / cycle / audio tasks.
//#############################################################################
//
void
BTL4PlaybackApplication::LoadBackgroundTasks()
{
Check(this);
ApplicationTask
*application_task;
application_task = new BTSpoolerTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new ProcessEventTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new GaugeRendererTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new CompleteCyclesTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new AudioRendererTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
Check_Fpu();
}
//
//#############################################################################
// @004d3d40 -- the mission-review frame loop. Idle until frames execute;
// controls only in INTERACTIVE review (mode 2); then the update, interest,
// renderer and intercom managers; and while running, keep the game clock
// current from the recorded start.
//#############################################################################
//
Logical
BTL4PlaybackApplication::ExecuteForeground(
Time start_of_frame,
Scalar frame_duration
)
{
SET_FOREGROUND_PROCESSING();
Check(this);
if (!executeFrames || GetApplicationState() == WaitingForEgg)
{
//
// [T3] The binary services a renderer idle slot here (the RP411
// descendant names it ExecuteIdle); no such entry exists in the
// 1995 renderer headers, and pre-egg there is nothing to draw --
// the idle frame simply spins. Revisit if the review screen shows
// stale video before the egg arrives.
//
CLEAR_FOREGROUND_PROCESSING();
return executeFrames && !Exit_Code;
}
Time
frame_ticks;
frame_ticks = frame_duration;
if (GetMissionReviewMode() == 2)
{
Check(controlsManager);
((LBE4ControlsManager *)controlsManager)->Execute();
}
SET_UPDATE_MANAGER();
Check(updateManager);
updateManager->Execute(start_of_frame);
CLEAR_UPDATE_MANAGER();
Check(rendererManager);
rendererManager->UpdateInterestOrigins(start_of_frame);
Check(interestManager);
interestManager->Execute();
SET_RENDERER_MANAGER();
Check(rendererManager);
rendererManager->Execute(start_of_frame, frame_ticks, frame_ticks);
CLEAR_RENDERER_MANAGER();
Check(intercomManager);
intercomManager->Execute();
if (GetApplicationState() == RunningMission)
{
secondsRemainingInGame =
currentMission->GetGameLength() - (Now() - gameStarted);
}
CLEAR_FOREGROUND_PROCESSING();
Check_Fpu();
return executeFrames && !Exit_Code;
}
//
//#############################################################################
// Inherited-shape background task (the OPT build emitted no BT override; the
// body matches the L4 base, kept for the header's declaration).
//#############################################################################
//
void
BTL4PlaybackApplication::ExecuteBackgroundTask()
{
Check(this);
if (ProcessOneEvent(HighEventPriority))
{
return;
}
Check(backgroundTasks);
backgroundTasks->Execute();
}
//
//#############################################################################
// @004d3ecc -- route a recorded packet to its client.
//#############################################################################
//
void
BTL4PlaybackApplication::DispatchPacket(NetworkPacket *packet)
{
Check(this);
NetworkManager
*net_mgr = GetNetworkManager();
Check(net_mgr);
NetworkClient
*client = net_mgr->GetNetworkClientPointer(packet->clientID);
Check(client);
client->ReceiveNetworkPacket(packet, &packet->messageData);
Check_Fpu();
}
//
//#############################################################################
// @004d3ef8 -- first launch synchronises the playback clock: the bias is the
// difference between NOW and the recorded RunMission packet's timestamp, and
// every replayed packet is re-timed by it.
//#############################################################################
//
void
BTL4PlaybackApplication::RunMissionMessageHandler(RunMissionMessage *message)
{
Check(this);
if (GetApplicationState() == WaitingForLaunch)
{
SpoolFile
*spool = GetSpoolFile();
Check(spool);
NetworkPacket
*packet = (NetworkPacket *)spool->GetPointer();
timeBias = Now().ticks - packet->timeStamp.ticks;
spool->NextPacket();
}
BTL4Application::RunMissionMessageHandler(message);
Check_Fpu();
}
//
//#############################################################################
// @004d3f44 -- the WINNERS CIRCLE. When the recorded mission ends, release
// the spool, park every player's mech on the dropzone matching its finishing
// rank ("win1".."winN"), freeze them, refresh the name bitmaps and tilt the
// camera up 45 degrees for the final tableau.
//#############################################################################
//
static char
winners_spot[] = "win?"; // @0051ee63
void
BTL4PlaybackApplication::StopMissionMessageHandler(StopMissionMessage *message)
{
MissionReviewApplicationManager
*app_mgr = GetApplicationManager();
Check(app_mgr);
SpoolFile
*spool = GetSpoolFile();
if (spool)
{
app_mgr->ReleaseSpoolFile(spool);
spoolFile = NULL;
EntityManager
*entity_manager = application->GetEntityManager();
EntityGroup
*dropzones = entity_manager->FindGroup("DropZones");
char
*place = winners_spot + 3;
Player
*player;
int
rank;
for (
rank = 0;
(player = CameraDirector::FindPlayerByRank(rank)) != NULL;
++rank
)
{
*place = (char)('1' + rank);
ChainIteratorOf<Node *>
iterator(dropzones->groupMembers);
DropZone
*dropzone;
while ((dropzone = (DropZone *)iterator.ReadAndNext()) != NULL)
{
if (!strcmp(dropzone->GetDropZoneName(), winners_spot))
{
break;
}
}
if (!dropzone)
{
continue;
}
Mech
*mech = (Mech *)player->GetPlayerVehicle();
if (mech->GetClassID() == RegisteredClass::MechClassID)
{
mech->Reset(dropzone->localOrigin, False);
mech->SetPerformance(
(Mech::Performance)&Simulation::DoNothing);
mech->FlushEvents();
}
}
DPLRenderer
*dpl_renderer = (DPLRenderer *)GetVideoRenderer();
Check(dpl_renderer);
dpl_renderer->SortAndReloadNameBitmaps();
dpl_renderer->SetViewAngle(Degree(45.0f));
}
BTL4Application::StopMissionMessageHandler(message);
Check_Fpu();
}