Files
BT411/game/reconstructed/btstubs.cpp
T
arcattackandClaude Opus 4.8 247e51e1e1 Reset-based respawn: reuse+heal the mech, not create-a-new-one (task #52)
The respawn glitches (2 mechs, on-fire respawn, camera-inside, can't-control,
wreck-never-disappears) all traced to one architectural divergence: our respawn
SEVERED playerVehicle on death and CREATED a new mech, leaving the old as a
permanent wreck and building a duplicate viewpoint whose old render tree was
never torn down.

The authentic engine (FUN_0049fb74 + RPPlayer) REUSES the same mech entity: on
respawn Mech::Reset heals it and moves it in place. Implemented faithfully,
adapted to our layout (the 1995 raw offsets map to different 2007 engine fields,
so reset the equivalent named members, not the offsets):
- Mech::Reset (real, was a reposition-only stub): reposition + kill dead-reckon
  (projectedOrigin/projectedVelocity/updateVelocity + our relocated gait
  accumulators) so the replicant stops lerping to the death spot; clear the
  death latch (movementMode=1, graphicAlarm=0); Heal every damage zone
  (new Mech__DamageZone::Heal: full structure, intact skin); DeathReset
  (vtable+0x28) every subsystem; ForceUpdate to broadcast.
- btplayer.cpp: VehicleDead no longer severs playerVehicle; the respawn re-post
  gates on the mech still being dead; DropZoneReply resets the EXISTING mech in
  place (heal+move) instead of creating a new one, then fires the warp. Warp
  moved to the shared placement (initial drop-in + respawn).

Verified 2-node: mech entity ID stays 3:22 across 3 deaths (reused, not a new
3:32); each Reset logs alive=1, 20 zones healed, 33 subsystems reset; A sees ONE
mech (no wreck+new pair). Warp fires each respawn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:13:44 -05:00

399 lines
18 KiB
C++

//===========================================================================//
// File: btstubs.cpp //
// Project: BattleTech port (WinTesla / btl4) //
//---------------------------------------------------------------------------//
// BRING-UP STUBS + engine data-global definitions needed to LINK btl4.exe. //
// //
// Every definition in this file is a flagged first-link placeholder. None //
// of these carry the real shipped behaviour yet -- they exist only so the //
// executable links and can be booted. Each must be replaced with the real //
// body recovered from BTL4OPT.EXE (the binary oracle) / the RP analogue. //
// //
// == RUNTIME BRING-UP WORKLIST (replace these) == //
// Mech::GetMissionReviewMode / IsAirborne / SetTargetRange / //
// SetMappingSubsystem / RaiseStatusAlarm //
// Mech__DamageZone::LoadCriticalSubsystems //
// MechSubsystem::TakeDamage / OnAlarmChanged //
// Generator::ForceShortRecovery //
// Is_Destroyed / ToggleVoiceAssist / Set_Alarm_Level / //
// Notify_Objective_Reached //
// data: FrameTimeScale, StatusMessagePool, BTPlayerVTable, //
// BTStatusMessageVTable, allPresets //
//===========================================================================//
#include <bt.hpp> // Mech, MechSubsystem, BTPlayer, Damage, Scalar, ...
#pragma hdrstop
#if !defined(POWERSUB_HPP)
# include <powersub.hpp> // Generator
#endif
#if !defined(MECHDMG_HPP)
# include <mechdmg.hpp> // Mech__DamageZone (full definition)
#endif
#include <L4AUDLVL.hpp> // PRESETINFO + extern allPresets[2][100]
#include <MEMBLOCK.hpp> // MemoryBlock
#if !defined(BTL4VID_HPP)
# include <btl4vid.hpp> // BT*Renderable hierarchy + dpl2d_* prototypes
#endif
//===========================================================================//
// Engine data globals (declared extern by the engine/game but defined in a
// .cpp that is not part of munga_engine.lib).
//===========================================================================//
// TODO(bring-up): per-frame time scale; real value is set by the sim clock.
// Default 1.0 so the mech-age divide in mech.cpp does not divide by zero.
Scalar FrameTimeScale = 1.0f;
// TODO(bring-up): audio preset bank table (real data lives in WTPresets/L4AUDLVL).
// Zero-initialised -> PRESET_isImplemented() returns false (no presets) at boot.
PRESETINFO allPresets[2][100] = {};
//===========================================================================//
// BattleTech globals (status-message pool + hand-rolled vtable pointers the
// recovered ctors splice in). Real homes were btplayer.cpp .data.
//===========================================================================//
// TODO(bring-up): BT StatusMessage MemoryBlock pool (@00512f6c). Must be a real
// MemoryBlock sized for StatusMessage before status messages are allocated.
MemoryBlock *StatusMessagePool = 0;
// TODO(bring-up): hand-built vtables the recovered BTPlayer/StatusMessage ctors
// patch into the object's first word (PTR_FUN_00513300 / PTR_LAB_00513344).
// Null for now -- any virtual call through these will fault until reconstructed.
void *BTPlayerVTable = 0;
void *BTStatusMessageVTable = 0;
//===========================================================================//
// Free-function stubs (declared extern at the call sites).
//===========================================================================//
// TODO(bring-up): true when the entity handle refers to a destroyed entity.
Logical Is_Destroyed(int /*entity_handle*/)
{
return False;
}
// TODO(bring-up): toggle the pilot voice-assist subsystem.
void ToggleVoiceAssist(int /*voice_assist_subsystem*/)
{
}
// TODO(bring-up): raise/clear a cockpit alarm indicator to the given level.
void Set_Alarm_Level(void * /*alarm_indicator*/, int /*level*/)
{
}
// TODO(bring-up): notify mission scoring that an objective subsystem was reached.
void Notify_Objective_Reached(int * /*objective_subsystem*/, Mech * /*mech*/)
{
}
//===========================================================================//
// Mech method stubs.
//===========================================================================//
// TODO(bring-up): reads mech+0x414 (mission-review playback flag).
int Mech::GetMissionReviewMode()
{
return 0;
}
// TODO(bring-up): true while the mech is off the ground (jump-jet / fall state).
int Mech::IsAirborne()
{
return 0;
}
// FUN_0049fb54 -- true when the mech is destroyed/shut down (masks control input
// and freezes gait). For the drivable bring-up the mech is always live.
// TODO(bring-up): derive from the status/master alarm once damage is wired.
// Mech::IsDisabled -- @0049fb54. Reconstructed: the mech is "disabled" (its action
// requests are masked, gait frozen) when its movementMode (mech+0x40, the gait/death
// selector) is in a death state -- 2 or 9. movementMode is driven to those values by
// the gait/death-transition code (mech2 SetLegAnimation/SetBodyAnimation family) when a
// vital zone is destroyed; that transition path is part of the locomotion/Simulate
// reconstruction (bypassed in the bring-up drive override), so this reads false until
// that lands. Faithful to the binary regardless.
Logical Mech::IsDisabled()
{
return (movementMode == 2 || movementMode == 9) ? True : False;
}
// mech+0x404 = the radar display scale. Both the radar map's currentScale and
// the overlay range readout bind Mech/RadarRange -> radarRange. The controls
// mapper slews targetRangeExponent and calls SetTargetRange(250*2^exp) every
// frame (btl4mppr.cpp:407) off the zoom buttons, so writing the member makes the
// radar map zoom + the range numeric track the zoom (default exp 2.0 -> 1000,
// matching the historic frozen value). Faithful body (the binary writes [this+0x404]).
void Mech::SetTargetRange(Scalar range)
{
radarRange = range;
}
// Install the cockpit-mapping subsystem into the Mech's reserved ControlsMapper
// roster slot (index 0), mirroring VTV::SetMappingSubsystem (RP/VTV.cpp:394) and
// VTV::ControlsMapperSubsystem==0. The streamed control-mapping resource binds
// its DirectMappings to subsystemID 0, so the mapper MUST live there for
// Entity::GetSimulation(0) to resolve it. (Previously a no-op -> slot 0 stayed
// NULL -> LBE4ControlsManager::CreateStreamedMappings dereferenced a NULL
// subsystem.) controlsMapper mirrors the same pointer for the gameplay path.
void Mech::SetMappingSubsystem(Subsystem *subsystem)
{
Check(this);
Check_Pointer(subsystemArray);
if (subsystemArray[0] != 0)
{
// re-spawn: drop the previous mapper (NULL on first spawn).
Unregister_Object(subsystemArray[0]);
delete subsystemArray[0];
}
subsystemArray[0] = subsystem;
controlsMapper = (MechControlsMapper *)subsystem;
}
// TODO(bring-up): raise a mech-level status alarm of the given id.
void Mech::RaiseStatusAlarm(int /*alarm_id*/)
{
}
// Mech::Reset (@0049fb74) is now the real faithful reconstruction in mech4.cpp
// (reposition + kill dead-reckon + clear death/damage + ResetToInitialState every
// subsystem + heal every damage zone + ForceUpdate) -- no longer the reposition-
// only stub that lived here.
// (Mech__DamageZone::LoadCriticalSubsystems was a no-op stub here; it is now the
// real type-0x1e damage-state descriptor-table loader in mechdmg.cpp.)
//===========================================================================//
// MechSubsystem method stubs.
//===========================================================================//
// TODO(bring-up): apply damage to a generic mech subsystem (virtual override).
void MechSubsystem::TakeDamage(Damage & /*damage*/)
{
}
// TODO(bring-up): react to a change in this subsystem's alarm level.
void MechSubsystem::OnAlarmChanged()
{
}
//===========================================================================//
// Generator method stub.
//===========================================================================//
// TODO(bring-up): force the generator into the short-event fast-recovery path.
void Generator::ForceShortRecovery()
{
}
//===========================================================================//
// BT subsystem<->Mech bridge accessors (declared extern in powersub.hpp).
//---------------------------------------------------------------------------//
// INTEGRATION NOTE (Task 2): the shipped binary reads these off fixed Mech byte
// offsets (msg-mgr @+0x190, bus-live @*(+0x190)+0x274, myomers @+0x374). The
// reconstructed Mech is NOT byte-exact (named members + a Wword scratch bank),
// so a raw `*(owner+0x190)` would read an unrelated member and, when chased one
// level further for the bus flag, dereference garbage. These are therefore
// implemented against intent rather than raw offsets, and kept deref-safe:
// * BT_IsBusLive -> True: the running player mech is a powered master; this
// un-gates the power-dependent subsystem paths (PoweredSubsystem/Generator)
// so they compute when those real classes are wired (today their consumers
// are RECON_SUBSYS stubs -- see RECONCILE.md -- so this is latent but safe).
// * BT_GetMessageManager -> 0: the SubsystemMessageManager IS in the roster
// (slot "MessageManager") but cannot be located here without the byte-exact
// layout; returning a bogus non-null would be WORSE (callers deref it). Left
// 0 (callers null-guard) until the roster lookup / real layout is restored.
// * BT_ClearMyomers -> no-op: no myomer back-pointer member exists at a known
// offset in the reconstructed Mech to clear.
//===========================================================================//
// Real impl reads *(owner+0x190). No byte-exact +0x190 in the reconstructed
// Mech; callers null-guard, so 0 is the safe value (see note above).
SubsystemMessageManager *BT_GetMessageManager(Mech * /*owner*/)
{
return 0;
}
// The running player mech is a live, powered master -> its electrical bus is up.
// (Real impl read *(*(owner+0x190)+0x274); see layout note above.)
Logical BT_IsBusLive(Mech * /*owner*/)
{
return True;
}
// No known-offset myomer back-pointer in the reconstructed Mech to clear.
void BT_ClearMyomers(Mech * /*owner*/)
{
}
// BT_GetSegmentFlags -- the per-subsystem segment-flag accessor the HUD (hud.cpp)
// and the segment-flag-gated subsystem ctors call. It was DECLARED extern but
// never DEFINED, so it only linked under /FORCE (an unresolved external whose
// return was undefined if ever called). The oracle-verified authoritative source
// for the master/copy + master-bit gate is the OWNER's simulationFlags
// (owner+0x28): (flags & 0xC)==0 (master, not a replicant copy) && (flags & 0x100)
// (master bit). Defining it here resolves the dangling symbol and feeds the gate
// the same authoritative value the reconciled subsystem gates now read directly.
LWord BT_GetSegmentFlags(Mech *owner)
{
return owner ? owner->simulationFlags : 0;
}
//===========================================================================//
// Legacy libDPL material-name callback.
//---------------------------------------------------------------------------//
// The dpl2d_ 2D display-list API (NewDisplayList/Begin/SetColor/Circle/
// PushMatrix/PopMatrix/MoveTo/End/Compile) was MOVED OUT of this file and
// reimplemented over Direct3D 9 in decomp/reconstructed/dpl2d.cpp (the HUD/
// reticle 2D layer). Only the 3D material-name-substitution hook -- which is
// part of the world renderer, not the 2D HUD -- remains stubbed here.
// TODO(bring-up): wire the material substitution table (FUN_00459eb8).
//===========================================================================//
void dpl_SetMaterialNameCallback(char *(*)(char *)) {}
//===========================================================================//
// BTL4VideoRenderer helper methods whose bodies were not reconstructed.
//---------------------------------------------------------------------------//
// TODO(bring-up): real bodies @FUN_0045a724 / 00498448 / 0045a994 / 00489cec
// and LoadMissionImplementation drive the D3D scene/object pipeline.
//===========================================================================//
Scene *BTL4VideoRenderer::GetScene() { return 0; }
d3d_OBJECT *BTL4VideoRenderer::LoadObject(const char * /*name*/) { return 0; }
void BTL4VideoRenderer::AttachToEyeDCS(dpl_DCS *, LinearMatrix &) {}
void BTL4VideoRenderer::AddDynamicRenderable(VideoComponent *, Entity *) {}
void BTL4VideoRenderer::LoadMissionImplementation(Mission *mission)
{
// Load the real BattleTech renderer environment for this mission: paths,
// clip range, projection, fog, ambient + directional lights, cached shapes,
// effects -- driven by BTDPL.INI (L4DPLCFG, set in btl4main.cpp). This is
// what RP does (RPL4VideoRenderer::LoadMissionImplementation -> base), and is
// what gives the cavern world its environment. DPLReadINIPage builds a valid
// RH projection from the INI viewangle/clip, so the mech view is preserved.
DPLRenderer::LoadMissionImplementation(mission);
// Safety net (mProjectionMatrix is private to DPLRenderer, so we cannot
// inspect it here): unconditionally re-assert a known-good RH projection and
// disable fog so the freshly loaded mech body + cavern geometry are not
// clipped or fogged to the background. DPLReadINIPage already set the
// background colour / paths / lights from BTDPL.INI; this only guarantees the
// view stays valid. TODO(bring-up): once the env-driven projection/fog are
// confirmed good, drop this and honour the INI fog/clip directly.
EnsureValidProjection();
}
//===========================================================================//
// BattleTech renderable hierarchy (the unreconstructed "BT renderables" module).
//---------------------------------------------------------------------------//
// TODO(bring-up): these ctors (FUN_0045xxxx) built the D3D DCS/instance graph
// for each mech part/effect. Stubbed to chain to BTRenderableBase so the object
// is a valid (empty) VideoComponent; nothing is drawn until reconstructed.
//===========================================================================//
BTWorldContainerRenderable::BTWorldContainerRenderable(
Entity *entity, int, int, Scene *)
: BTRenderableBase(entity) {}
BTRootRenderable::BTRootRenderable(
Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32)
: BTRenderableBase(entity) {}
BTDCSObjectRenderable::BTDCSObjectRenderable(
Entity *entity, Scene *, d3d_OBJECT *, int, uint32, LinearMatrix *, dpl_DCS *)
: BTRenderableBase(entity) {}
BTHingeRenderable::BTHingeRenderable(
Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *,
LinearMatrix *, const Hinge *)
: BTRenderableBase(entity) {}
BTBallJointRenderable::BTBallJointRenderable(
Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *,
LinearMatrix *, const EulerAngles *)
: BTRenderableBase(entity) {}
BTBallTranslateJointRenderable::BTBallTranslateJointRenderable(
Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *,
LinearMatrix *, const EulerAngles *, const Point3D *)
: BTRenderableBase(entity) {}
BTEyeRenderable::BTEyeRenderable(
Entity *entity, Scene *, LinearMatrix *, dpl_DCS *, dpl_VIEW *, EulerAngles *)
: BTRenderableBase(entity) {}
BTDeathEffectRenderable::BTDeathEffectRenderable(
Entity *entity, int, dpl_VIEW *, Scene *, dpl_DCS *, StateIndicator *, int)
: BTRenderableBase(entity) {}
BTMarkerWatcherRenderable::BTMarkerWatcherRenderable(
Entity *entity, int, dpl_VIEW *, dpl_DCS *)
: BTRenderableBase(entity) {}
// BTTranslocationRenderable (the "blue warp" translocation sphere) is now a real
// reconstruction in btl4vid.cpp (task #52) -- no longer a stub here.
BTPOVStartEndRenderable::BTPOVStartEndRenderable(
Entity *entity, int, dpl_VIEW *, dpl_ZONE *, dpl_ZONE *, StateIndicator *,
float, float, float, float, float, int, int)
: BTRenderableBase(entity) {}
BTTracerEffectRenderable::BTTracerEffectRenderable(
Entity *entity, int, void *, int, dpl_ZONE *, dpl_DCS *, LinearMatrix *)
: BTRenderableBase(entity) {}
BTEmitterBeamRenderable::BTEmitterBeamRenderable(
Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *,
LinearMatrix *, void *, int, void *, void *)
: BTRenderableBase(entity) {}
BTPPCBeamRenderable::BTPPCBeamRenderable(
Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *,
LinearMatrix *, void *, int, void *, void *, float)
: BTRenderableBase(entity) {}
BTLightConnection::BTLightConnection(
Entity *entity, int, dpl_INSTANCE *, int, int *)
: BTRenderableBase(entity) {}
// (BTReticleRenderable ctor/dtor/Draw now live in btl4vid.cpp -- the real
// @004cc40c reconstruction replaced the old bring-up stubs here.)
//===========================================================================//
// VideoComponent (legacy renderable base) -- declared in L4VIDRND.h but NOT
// implemented anywhere in the WinTesla engine (superseded by VideoRenderable).
// BTRenderableBase still derives from it, so provide first-link stub bodies.
//---------------------------------------------------------------------------//
// TODO(bring-up): either reimplement VideoComponent over the new D3D renderable
// hierarchy or reparent BTRenderableBase onto VideoRenderable.
//===========================================================================//
VideoComponent::VideoComponent(Entity * /*entity*/, VideoExecutionType /*type*/)
: HierarchicalDrawComponent((RegisteredClass::ClassID)0)
, videoComponentSocket((Node *)0) // TODO(bring-up): owner node when reimplemented
{
}
VideoComponent::~VideoComponent()
{
}
void VideoComponent::Add(VideoComponent * /*component_to_add*/)
{
}
void VideoComponent::Connect(VideoComponent * /*component_to_connect*/)
{
}
void VideoComponent::ReceiveControl(VideoControlID /*control_ID*/, Scalar /*control_value*/)
{
}
void VideoComponent::Execute()
{
}