Each armor zone on the cockpit schematic is now tinted by that zone's live
damage. Render-verified: the schematic shows an all-green Blackhawk (all zones
damageLevel=0 at spawn), replacing the static green+red it showed before cmArmor
was registered -- i.e. cmArmor now owns those zone colors and shows the real
undamaged state; damaged zones shift toward red.
Reuses the ColorMapper base (from cmHeat) + two pieces reconstructed from the
binary (Ghidra dropped the x87, recovered by disassembly):
- ArmorZoneConnection (@004c33a4/@004c3430): resolves one zone from the owner's
inherited Entity::damageZones[zone_index]; per-frame feed = zone==NULL ? 100
: Round(zone->damageLevel * 100) (the 0..1 damage ratio -> 0..100 percentage
-> the colour index ColorMapper::Execute pushes into the palette slot).
- ColorMapperArmor::Make/ctor (@004c3aa4/@004c3b98): Make resolves the CFG zone
name (dz_ltorso etc., the 6th param) to an index via Entity::GetDamageZoneIndex
(== the binary's FUN_0042076c, scanning damageZones[] by name); the ctor wires
the ArmorZoneConnection.
Decomp fact (corrects the cmHeat note): the ColorMapper base ctor takes NINE
args -- an owner_ID sits between renderer and graphics_port_number. Our 8-arg
ColorMapper::ColorMapper folds owner_ID=0 in (gauges have owner 0), so it is
equivalent; cmArmor's mapping matches cmHeat plus the zone name.
Verified: parses, builds, all dz_* zones resolve (0 "not found"), no /FORCE
unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes). The dynamic
red-on-damage needs the player to take damage (the passive spawn dummy never
hits back). Details: docs/GAUGE_COMPOSITE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The visible money-shot: a green compass needle that rotates with the mech's
facing plus a numeric heading in whole degrees (render-verified: the needle
swung and the number went 247 -> 182 as the mech turned).
Full class reconstruction from the binary (Make/ctor/dtor/TestInstance/
ShowInstance/BecameActive/Execute). Ghidra dropped the x87 float math feeding
Execute's needle endpoints and the ctor's NumericDisplay centering, so it was
recovered by disassembling BTL4OPT.EXE with capstone (scratchpad/disas_hp.py):
the needle is a radial line from innerRadius(20) to outerRadius(39) at the
heading angle, endpoints rounded half-up; the readout is round(360 - deg).
Two corrections vs the map:
- The header ctor was 12 args; the binary's is 14 -- widened it. Fields @0x98/
@0x9C are the needle's inner/outer RADIUS (Execute multiplies them by sin/cos),
not "color/spacing" as the old field names implied; renamed accordingly.
- ENGINE-CONVENTION FIX: the binary read the heading from EulerAngles index [0],
but the WinTesla EulerAngles(Quaternion) decomposition is ambiguous for a
yawing mech (the quaternion double-cover flips it to a pitch=roll=pi branch as
yaw sweeps past +/-pi -> the needle spins erratically). Switched to
YawPitchRoll, whose yaw-first .yaw is the clean continuous heading
(pitch=roll~0). Faithful to the binary's intent (heading), correct for this
engine's decomposition.
renderer->GetLinkedEntity() resolves the viewpoint mech (a GetViewpointEntity
fallback is kept belt-and-braces). Deps (NumericDisplay/GraphicsViewRecord/
GraphicGauge) are real engine classes -> no /FORCE risk; verified no unresolved
HeadingPointer externals, no parse desync, combat un-regressed (TARGET
DESTROYED, 0 crashes). Details + the disasm technique: docs/GAUGE_COMPOSITE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Begins the BT gauge WIDGET reconstruction -- making the cockpit instruments show
live data instead of just frame art. The BT gauge method table
(BTL4MethodDescription[]) was a chain-only stub with zero registered
methodDescriptions, so every BT-specific gauge keyword was parse-skipped.
Registers the first widget, cmHeat (ColorMapperHeat -- tints the heat/critical
schematic palette by a named subsystem's live temperature):
- Added ColorMapperHeat::methodDescription (keyword "cmHeat", param list
R,Md,C,St,St,St matching the binary .data) and registered it in
BTL4MethodDescription[] (chain link stays last).
- Rewired ColorMapperHeat::Make to read methodDescription.parameterList[]
(the interpreter restores each instance's parsed params there) instead of the
earlier unrecovered DAT_* placeholders -- so it uses the real CFG rate/mode/
colour-slot/palette/subsystem-name, and the runtime port arg as the graphics
port.
Registering it exposed the /FORCE trap: two REAL functions in the chain were
undefined (silently stubbed -> would AV once the gauge builds). Reconstructed
both from the decomp:
- ColorMapper::BecameActive (@004c395c): invalidate the cached colour index +
last-written RGB so the next Execute re-pushes the hardware palette.
- ColorMapperHeat::~ColorMapperHeat: empty -- the base chain (~ColorMapper ->
~Gauge) releases the HeatConnection + palettes.
Verified: register->parse->Make->ctor->FindSubsystem("GeneratorA") (resolves,
no "does not exist")->HeatConnection->Execute->palette-push runs end-to-end
under BT_DEV_GAUGES; no parse desync; combat un-regressed (TARGET DESTROYED,
0 crashes). Establishes the registration recipe + the /FORCE-check discipline
for every remaining widget. Details + priority order: docs/GAUGE_COMPOSITE.md.
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>