Files
BT411/game/reconstructed/hud.hpp
T
arcattackandClaude Fable 5 904c75aff9 Gitea #10 audit fixes: HUD attr table re-based (@5110b8, one-record shift) + BT_SHOT dock capture
- hud.hpp/hud.cpp/CLASSMAP.md: the binary HUD AttributePointers table starts at
  @5110b8 with a FULL id-3 FlickerRate record (the old transcription read it as a
  label and shifted every offset one slot). Re-based member names/offsets:
  rotationOfTorsoHorizontal@0x1DC (<- Torso+0x1D8 twist), limits@0x1E0/0x1E4,
  speed@0x1E8, rangeToTarget@0x1EC, @0x1F0 unbound, Visible@0x1F4 (init 1),
  Lock@0x1F8 (init 0), HotBoxVector P3D@0x1FC, ThreatVector P3D@0x208,
  CompassHeading Scalar@0x214 (= yaw euler[0] + twist, HudSimulation :5676).
  Cross-checked vs ctor @004b7f94 + HudSimulation @004b7830 (Lock rule writes
  @0x1F8 :5622/:5633; range default 1200 + 500 m/s slide @0x1EC). SetCompassHeading
  renamed SetThreatVector (@004b7810 writes @0x208 = the threat vector; no port
  caller). No behavioral change: the reticle feeds via port globals and the CFG
  never binds HUD attrs. Resolves the gauges-hud open question (Gitea #10 entry b).
- L4VIDEO.cpp: BT_SHOT capture moved AFTER BTDrawGaugeInset -- the 2026-07-18
  reorder had left it before the dock blit, so BT_DEV_GAUGES_DOCK screenshots
  silently omitted the gauge panel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 18:33:44 -05:00

281 lines
13 KiB
C++

//===========================================================================//
// File: hud.hpp //
// Project: BattleTech //
// Contents: Cockpit Heads-Up Display subsystem //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// --/--/95 ?? Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, recovered shard
// part_013.c) cross-referenced with the sibling subsystem sources
// (powersub.hpp/.cpp, heat.hpp/.cpp, sensor.hpp/.cpp). NO HUD.HPP survived;
// the class body below is inferred from usage, the embedded string pool and
// the streamed-resource / attribute tables. Each method cites its @ADDR.
//
// ---------------------------------------------------------------------------
// IDENTIFICATION (resolves a CLASSMAP "gap")
// ---------------------------------------------------------------------------
// * mech3.cpp factory map: HUD::CreateStreamedSubsystem @004b81d4,
// DefaultData &DAT_00511078.
// * vtable @0050511510 (slot0 dtor @004b816c), ctor @004b7f94.
// * CreateStreamedSubsystem @004b81d4 stamps classID 0x0BD6 and streamed
// model size 0x104 (resource +0x20 / +0x24), and parses the HUD-specific
// model fields "FlickerRate" / "HorizontalMovementPerSecond" /
// "HorizontalLimit" plus a "SegmentPageName" index.
// * String pool @005112d0..@00511390 ("HUD", "FlickerRate",
// "RotationOfTorsoHorizontal", "HorizontalTorsoLimitRight/Left",
// "SpeedOfTorsoHorizontal", "RangeToTarget", "Visible", "Lock",
// "HotBoxVector", "ThreatVector", "CompassHeading") names the gauge
// read-out attributes.
//
// CLASSMAP.md "Resolved conflict" already flagged @004b7f94 / vtable
// @00511510 as "the HUD torso-horizon gimbal display" mis-labelled
// "MechTech" by mech.cpp's factory switch (case 0xBD6). This file confirms
// that: classID 0xBD6, base PowerWatcher.
//
// ---------------------------------------------------------------------------
// BASE CLASS
// ---------------------------------------------------------------------------
// HUD IS-A PowerWatcher (powersub.hpp): the ctor @004b7f94 chains to the
// PowerWatcher ctor FUN_004b18a4 and CreateStreamedSubsystem chains to
// PowerWatcher::CreateStreamedSubsystem FUN_004b198c. PowerWatcher is a
// HeatWatcher/HeatableSubsystem-derived *monitor* subsystem (NOT a
// PoweredSubsystem); the HUD therefore inherits the heat/voltage watchdog
// state (heat-state level @0x140, watched-voltage alarm level @0x198) used
// by the per-frame update. The shared base object occupies bytes 0..0x1D8;
// HUD's own members begin at 0x1D8. Total object size 0x2A4 (matches
// mech.cpp factory allocation for the 0xBD6 case).
//
#if !defined(HUD_HPP)
# define HUD_HPP
#if !defined(POWERSUB_HPP)
#include "powersub.hpp"
#endif
//##########################################################################
//####################### HUD::SubsystemResource #####################
//##########################################################################
//
// Extends the PowerWatcher/HeatableSubsystem resource record. The HUD fields
// begin at +0xF4 (see CreateStreamedSubsystem @004b81d4).
//
struct HUD__SubsystemResource:
public PowerWatcher::SubsystemResource
{
Scalar flickerRate; // +0xF4 "FlickerRate" (defaults to -1.0 in the SegmentPage form)
int segmentPageIndex; // +0xF8 resolved "SegmentPageName" (copied from +0x28)
Scalar horizontalMovementPerSecond; // +0xFC "HorizontalMovementPerSecond"
Scalar horizontalLimit; // +0x100 "HorizontalLimit"
};
//##########################################################################
//############################## HUD #################################
//##########################################################################
class HUD:
public PowerWatcher
{
friend struct HUDLayoutCheck; // live Watcher-branch re-base witness (hud.cpp)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
// DefaultData @00511078, ClassDerivations @00511088 ("HUD").
//
public:
static Derivation *GetClassDerivations(); // @00511088 (engine moved to accessor form)
static SharedData DefaultData; // @00511078
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support
//
// AttributePointers IndexEntry table @005110c0 (each record binds a name
// to an attribute id and an object offset, the offset encoded as
// byte_offset|1 -- the same scheme used by sensor.hpp).
//
// RE-DUMPED 2026-07-19 (Gitea #10 audit; section_dump.txt @5110b8, 16-byte
// records {id, namePtr, offset|1, 0}, terminator @511160). The table
// actually STARTS at @005110b8 with a FULL id-3 FlickerRate record (the old
// transcription had misread it as a "category label" and shifted every
// subsequent offset down one slot):
// id 3 "FlickerRate" -> @0x1D8
// id 4 "RotationOfTorsoHorizontal" -> @0x1DC
// id 5 "HorizontalTorsoLimitRight" -> @0x1E0
// id 6 "HorizontalTorsoLimitLeft" -> @0x1E4
// id 7 "SpeedOfTorsoHorizontal" -> @0x1E8
// id 8 "RangeToTarget" -> @0x1EC
// id 9 "Visible" -> @0x1F4 (init 1; @0x1F0 is unbound)
// id 10 "Lock" -> @0x1F8 (init 0)
// id 11 "HotBoxVector" -> @0x1FC (Point3D)
// id 12 "ThreatVector" -> @0x208 (Point3D)
// id 13 "CompassHeading" -> @0x214 (Scalar: yaw + torso twist)
// CROSS-CHECKED against the ctor @004b7f94 (inits @0x1F4=1 Visible-on /
// @0x1F8=0 Lock-off; @0x1F0 uninitialized) and HudSimulation @004b7830
// (per-frame @0x1DC <- linkedTorso+0x1D8 = Torso::currentTwist; @0x1EC
// range default 1200.0 + the 500 m/s display slide; the Lock rule writes
// @0x1F8 at :5622/:5633; @0x214 = EulerAngles(mech)[0] + @0x1DC at :5676;
// the hotbox Point3D copy @0x1FC at :5680). All consistent.
//
// CONFIRMED (task #37): the recovered reticle Execute @004cdcf0 binds and
// USES ids 4/5/6/8/0xA/0xB/0xC/0xD exactly per these names --
// RotationOfTorsoHorizontal drives the twist tape over the
// HorizontalTorsoLimit full-scales, RangeToTarget the range caret, Lock
// the spinning lock ring, HotBoxVector the target box (eye-space vec3),
// ThreatVector the compass threat trail, CompassHeading the rose
// rotation.
//
public:
enum {
RotationOfTorsoHorizontalAttributeID = PowerWatcher::NextAttributeID, // 4
HorizontalTorsoLimitRightAttributeID, // 5
HorizontalTorsoLimitLeftAttributeID, // 6
SpeedOfTorsoHorizontalAttributeID, // 7
RangeToTargetAttributeID, // 8
VisibleAttributeID, // 9
LockAttributeID, // 10
HotBoxVectorAttributeID, // 11
ThreatVectorAttributeID, // 12
CompassHeadingAttributeID, // 13
NextAttributeID
};
private:
static const IndexEntry AttributePointers[]; // @005110c0
protected:
static AttributeIndexSet AttributeIndex;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Local data. Offsets are byte offsets into the shipped object; names are
// best-effort from the attribute pool + ctor/HudSimulation usage.
//
// Member names re-based 2026-07-19 (Gitea #10) against the RE-DUMPED attr
// table @005110b8 -- the old names were shifted one slot (see the table
// comment above).
protected:
Scalar flickerRate; // @0x1D8 resource +0xF4 ("FlickerRate", attr id 3)
// Torso / target read-outs sampled at construction AND per-frame in
// HudSimulation from the linked Torso subsystem (owner Mech +0x438 ->
// this->linkedEntity @0x234):
Scalar rotationOfTorsoHorizontal; // @0x1DC <- linkedEntity +0x1D8 (Torso::currentTwist; attr 4)
Scalar torsoLimitRight; // @0x1E0 <- linkedEntity +0x1DC (attr 5)
Scalar torsoLimitLeft; // @0x1E4 <- linkedEntity +0x1E0 (attr 6)
Scalar torsoSpeed; // @0x1E8 <- linkedEntity +0x1E8 (attr 7 "SpeedOfTorsoHorizontal")
Scalar rangeToTarget; // @0x1EC computed target distance; 1200.0 default; 500 m/s display slide (attr 8)
int scratch1F0; // @0x1F0 unbound / not ctor-initialized in the binary
int visible; // @0x1F4 init 1 ("Visible", attr 9; heat/page gating)
int lockFlag; // @0x1F8 init 0 ("Lock", attr 10; the :5619-5634 lock rule)
Point3D hotBoxVector; // @0x1FC init (0,0,0) ("HotBoxVector", attr 11)
Point3D threatVector; // @0x208 init (0,0,0) ("ThreatVector", attr 12); see SetThreatVector
Scalar compassHeading; // @0x214 init 0 ("CompassHeading", attr 13; yaw euler[0] + torso twist @:5676)
Scalar blinkTimer; // @0x218 accumulates time, reset against flickerRate
Scalar flickerTimer; // @0x21C
int blinkState; // @0x224 init 1 (visible phase of the blink)
int transitionFlag; // @0x22C init 0
Scalar previousHorizontal; // @0x230
int linkedEntity; // @0x234 owner Mech +0x438 (active weapon/reticle source)
HeatAlarm statusAlarm; // @0x238 2-level page/status indicator (Initialize/SetLevel surface)
// Free-aim / reticle-slew demand: WRITTEN by MechControlsMapper::InterpretControls
// (@004afd10 cockpit+0x28c <- stick yaw when the torso's horizontal twist is
// disabled; zeroed in Basic mode). Was placeholder-named flickerPhase (init-only).
Scalar freeAimSlew; // @0x28C
public:
void SetFreeAimSlew(Scalar v) { freeAimSlew = v; } // mapper write (@004afd10 cockpit+0x28c)
protected:
Scalar scratch290; // @0x290 init 0
Scalar horizontalTorsoOffset; // @0x294 init 0; clamped to +/- horizontalLimit, written to owner +0x36C
Scalar horizontalMovementPerSecond; // @0x298 resource +0xFC
Scalar horizontalLimit; // @0x29C resource +0x100
int flickerActive; // @0x2A0 current flicker on/off (UpdateFlicker @004b7ed4)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Simulation Support
//
public:
typedef void
(HUD::*Performance)(Scalar time_slice);
void
SetPerformance(Performance performance)
{
Check(this);
activePerformance = (Simulation::Performance)performance;
}
void
HudSimulation(Scalar time_slice); // @004b7830 (Performance, PTR @00511170)
// @004b7810 writes the Point3D @0x208 -- which the RE-DUMPED attr table
// (2026-07-19) proves is the THREAT vector, not the compass (the compass
// is the Scalar @0x214). Renamed from the old mislabel SetCompassHeading;
// no port caller exists (the port threat-trail feed goes through the
// reticle globals, btl4vid.cpp).
void
SetThreatVector(const Point3D &threat); // @004b7810 (writes threatVector @0x208)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Subsystem virtual overrides (vtable @00511510)
//
public:
Logical
HandleMessage(int message); // slot 9, @004b7780 (body not captured)
void
ResetToInitialState(Logical powered = True); // slot 10, @004b77bc
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Class Support
//
public:
static Logical TestClass(Mech&);
Logical TestInstance() const; // @004b81b8
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
typedef HUD__SubsystemResource SubsystemResource;
HUD(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data = DefaultData
); // @004b7f94
~HUD(); // @004b816c
static int
CreateStreamedSubsystem( // @004b81d4
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes = 1
);
protected:
// Flicker helper @004b7ed4: drives flickerActive (@0x2A0) toward 0,
// returns non-zero while still flickering.
Logical UpdateFlicker(Scalar time_slice); // @004b7ed4
//
// Reconstruction accessors for the inherited watchdog state read by
// HudSimulation: heat-watcher flag (this+0x40), watched-voltage alarm
// level (@0x198) and heat-state level (@0x140). The PowerWatcher /
// HeatWatcher base in this header set does not expose these, so they are
// best-effort placeholders that let the per-frame page-gating logic
// compile; a human should bind them to the real inherited members.
// (See CROSS-FAMILY NEEDS.)
//
int HeatWatcherFlag() const { return 0; }
int WatchedVoltageLevel() const { return 0; }
int HeatStateLevel() const { return 0; }
};
#endif