MapDisplay (the radar/threat-map gauge, btl4rdr.cpp) was fully reconstructed
(ctor + Execute + DrawViewWedge/DrawStatic/DrawMoving/DrawNames) but never
registered and its data attributes never published. Wire it up end-to-end:
DATA (the attributes the map binds):
* Mech table extended to the full 0x15..0x38 (was the 0x21 prefix): add
RadarRange@0x2f -> radarRange, RadarLinearPosition@0x30 -> radarLinearPosition
(Point3D* -> &localOrigin.linearPosition), RadarAngularPosition@0x31 ->
radarAngularPosition (Quaternion* -> &localOrigin.angularPosition), DuckState
@0x37 -> duckState; the rest bind the shared attrPad (kept dense so Find can't
strcmp a gap). New members set in the Mech ctor; radarRange defaults to 500
(SetTargetRange is still stubbed) so nearby contacts are on-scale. The map's
position/angle attributes resolve to Point3D**/Quaternion** (AttributePointer
is int Simulation::*, so the ATTRIBUTE_ENTRY reinterpret binds pointer members
fine -- verified live: posPtr/anglePtr resolve).
* Sensor (named "Avionics") publishes RadarPercent/SelfTest/BadVoltage: define
Sensor::AttributePointers[] + build the previously-empty Sensor::AttributeIndex
chained to PoweredSubsystem::GetAttributeIndex() (= HeatSink's dense index).
WIDGET (the registration glue -- NOT in the assert-anchored decomp, so
reconstructed from the config + ctor):
* MapDisplay::methodDescription (config "map") + MapDisplay::Make. The
offset_position keyword "center"/"bottom" is typed as a STRING and converted
in Make, avoiding a ModeManager named-constant registration. Registered in
BTL4MethodDescription[] (btl4grnd.cpp now includes btl4rdr.hpp).
Three crash fixes exposed once the map ran (each a pre-existing stub the map is
the first consumer of):
* Sensor radarPercent went hugely negative (RadarBaseline - HeatMasterEnergy(),
where the inherited heatEnergy is un-normalized in the not-byte-exact heat-leaf
branch) -> the radar reported non-operational. Guard: treat an out-of-[0,1]
heat term as no penalty (marked bring-up; real overheat penalty needs the heat
normalization).
* ResolveOperatorEntity was a stub returning NULL -> EntityPosition(NULL) crashed
CalculateBounds. Fixed to the renderer linked-entity + application viewpoint
fallback (same as HeadingPointer).
* MapName::ExtractFromEntity didn't guard a NULL entity (the binary's Verify(False)
iterator-mismatch path compiles out at DEBUG_LEVEL 0) -> EntityPosition(NULL).
Guarded.
Verified live (BT_DEV_GAUGES): the radar now draws the view wedge (the mech's
180-deg FOV cone) and completes the full wedge/static/moving/names cycle with
operating=1, cap=1, scale=500, position/angle resolved, 0 crashes. Combat un-
regressed (TARGET DESTROYED after 8 hits), heap-clean under BT_HEAPCHECK.
Permanent gated diagnostic: BT_MAP_LOG traces the phase/draw pipeline. Contacts
show 0 (the spatial-query entity classification is a follow-up); DuckState now
resolves for the duck button too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WAVE 4 (standalone readouts) -- Sensor(0xBC3)/Searchlight(0xBD8)/
ThermalSight(0xBDE)/AmmoBin(0xBCB) un-stubbed via Create<Class>Subsystem
bridges + Torso-style de-shim (drop cross-family shadow fields, redirect
accessors to the real inherited base, static_assert layout locks).
- FIX: Searchlight/ThermalSight ctors gated their Performance on the shadow
segmentFlags(=0) so it NEVER installed; switched to owner->simulationFlags.
- AmmoBin: retype ammoAlarm HeatAlarm->WatcherGaugeAlarm(0x54) + drop the
statusState@0x40 shadow -> exact 0x22C layout.
- Guard HeatWatcher::WatchSimulation against the unresolved watchedLink
(null-deref exposed once these sims run; faithful fix = resolve the link).
- Heat-leaf branch (Sensor) is not byte-exact -> overflow-lock only.
WAVE 6 (Myomers 0xBC6, mover-coupled) -- structural un-stub, gated
BT_MYOMERS (default on; =0 -> Actuator stub). Wired INERT: MyomersSimulation
early-returns (advanced-damage gate stubbed) + no-op mover feed, so the live
JointedMover is untouched and the gait cannot regress. De-shim drops the
owner*/segmentFlags shims to fit the exact-0x358 alloc. Authentic mover/heat
coupling deferred (needs messmgr 0xBD3 + reconciling the mover feed with the
gait cutover).
Verified: BLH tick 20->27, Mad Cat 24, combat DESTROYED un-regressed,
locomotion un-regressed, 0 crashes, 0 heap detections under BT_HEAPCHECK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.
Layout:
engine/ MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
models) + image codec; the minimal rp/ headers the audio HAL needs
game/ reconstructed BT logic + surviving-original BT source + fwd shims
+ WinMain launcher
content/ full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
docs/ format specs + reconstruction ledgers
reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
tools/ MP console emulator + map/resource scanners
One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>