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>
This commit is contained in:
Cyd
2026-08-03 15:54:31 -05:00
co-authored by Claude Fable 5
parent f85e5f5bcd
commit d2be54a3b9
4 changed files with 647 additions and 157 deletions
+43 -6
View File
@@ -27,6 +27,14 @@
# include <btl4ver.hpp> # include <btl4ver.hpp>
#endif #endif
#if !defined(BTL4PB_HPP)
# include <btl4pb.hpp> // BTL4PlaybackApplication -- the review app
#endif
#if !defined(SPOOLER_HPP)
# include <spooler.hpp> // MissionReviewApplicationManager / SpoolFile
#endif
//########################################################################## //##########################################################################
//#################### BTL4Application Globals ####################### //#################### BTL4Application Globals #######################
//########################################################################## //##########################################################################
@@ -73,14 +81,43 @@ int
{ {
// //
//------------------------------------------------------------------- //-------------------------------------------------------------------
// Mission review / live-camera launch (missionReviewMode >= 1): // Mission review / live-camera launch (mode 1 = camera, 2 = review).
// a MissionReviewApplicationManager with a SPOOLSIZE-byte packet //
// spool plus BTL4PlaybackApplication -- those bricks are not yet // The manager owns SPOOLSIZE-byte packet spools (default 8MB); a
// reconstructed (see BTL4.NOTES.md for the decompiled structure). // STORED spool starts the playback application over it, and camera
// mode additionally starts the engine's spooling application -- the
// recorder -- as a second app (the binary's FUN_0047c3fc ctor is
// L4SpoolingApplication's, ApplicationID 1 == BTL4).
//------------------------------------------------------------------- //-------------------------------------------------------------------
// //
Fail("btl4.cpp -- mission review/camera launch not yet reconstructed"); int
return 1; spool_size =
(getenv("SPOOLSIZE") != NULL)
? atoi(getenv("SPOOLSIZE"))
: 0x800000;
DEBUG_STREAM << "Spool size = " << spool_size << endl;
MissionReviewApplicationManager
*review_manager =
new MissionReviewApplicationManager(
GetTicksPerSecond(),
(L4Application::GetMissionReviewMode() != 2) ? 2 : 1,
spool_size
);
application_manager = review_manager;
SpoolFile
*spool_file = review_manager->GetStoredSpoolFile();
if (spool_file != NULL)
{
review_manager->StartApplication(
new BTL4PlaybackApplication(&resources, spool_file));
}
if (L4Application::GetMissionReviewMode() == 1)
{
review_manager->StartApplication(
new L4SpoolingApplication(&resources, (ApplicationID)1));
}
} }
application_manager->RunMissions(); application_manager->RunMissions();
+10 -5
View File
@@ -1,6 +1,6 @@
# BTL4.CPP — reconstruction notes # BTL4.CPP — reconstruction notes
**Status: RECONSTRUCTED (normal launch path); review/camera branch staged with Fail().** **Status: FULLY RECONSTRUCTED (2026-08-03) — both launch paths. The review/camera branch links against the real BTL4PB + L4SPLR bricks; no Fail() remains in this TU.**
The 4.10 main TU. No original survives anywhere in the archive (OPT.MAK links The 4.10 main TU. No original survives anywhere in the archive (OPT.MAK links
`btl4.obj` as a bare object next to the libs — it was never a lib member). `btl4.obj` as a bare object next to the libs — it was never a lib member).
@@ -51,10 +51,15 @@ if (mode == 1)
mgr->StartApplication(new(0xd8) <camera app, FUN_0047c3fc>(&resources, 1)); mgr->StartApplication(new(0xd8) <camera app, FUN_0047c3fc>(&resources, 1));
``` ```
Class declarations survive: `BTL4PlaybackApplication` (CODE/BT/BT_L4/BTL4PB.HPP), RECONSTRUCTED 2026-08-03: the branch is real. BTL4PB.CPP reconstructs the
spooler bricks in CODE/RP/MUNGA_L4/L4SPLR.HPP (`MissionReviewApplicationManager` playback app against its authentic surviving header; L4SPLR.CPP back-dates the
referenced there). Their .CPP bodies are not staged, so the branch Fails loudly RP411 descendant onto the authentic 1995 engine header (see each sidecar). Two
instead of linking half a subsystem. identifications closed it: the `if (spool_file)` guard means the getter is
GetStoredSpoolFile (the empty-spool getter cannot fail), and the camera app's
ctor FUN_0047c3fc sits in the engine range with the (ResourceFile*,
ApplicationID) shape — the camera app IS the engine's L4SpoolingApplication,
started with ApplicationID 1 == BTL4. RUNTIME TEST PENDING: needs a
camera/review egg and a recorded spool on the rig (CAMERA-REVIEW-NOTES.md).
## Deliberate differences from the binary ## Deliberate differences from the binary
+444
View File
@@ -0,0 +1,444 @@
//===========================================================================//
// 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();
}
+5 -1
View File
@@ -202,8 +202,12 @@ void
//###################### L4PlaybackNetworkManager ######################### //###################### L4PlaybackNetworkManager #########################
//############################################################################# //#############################################################################
//
// The 1995 header declares no statics of its own (the DefaultData reference
// in the RP411 descendant was later-era drift); chain the base's.
//
L4PlaybackNetworkManager::L4PlaybackNetworkManager(): L4PlaybackNetworkManager::L4PlaybackNetworkManager():
NetworkManager(L4PlaybackNetworkManager::DefaultData) NetworkManager(NetworkManager::DefaultData)
{ {
} }