The "prepEngr" cockpit primitive (the per-engineering-screen static label overlay,
12 CFG calls at L4GAUGE.CFG:4595-4759) was a MISLABELED stub (base Gauge, an Execute
override, mech/screenNumber SWAPPED) with no methodDescription -> parse-SKIPPED, none built.
Reconstructed byte-verified (ctor @4c7bf0, BecameActive @4c7e48, Make @4c7b30; vtable
0051a06c). THREE corrections vs the stub (decode workflow + vtables.tsv proof):
(1) base = GraphicGaugeBackground, not Gauge -- vtable-identical shape to the engine's
BackgroundBitmap (only dtor + BecameActive overridden); (2) the overridden slot is
BecameActive (a paint-on-activation), NOT Execute -- GraphicGaugeBackground has no Execute
virtual, so the Gauge::Execute->Fail->abort hazard doesn't apply; (3) screenNumber@0x6C /
mech@0x70 were swapped. sizeof 0x90.
BecameActive walks the mech roster (databinding-safe GetSubsystemCount/GetSubsystem +
BTGetSubsystemAuxScreen bridge, like VehicleSubSystems::Make), finds the subsystem whose
auxScreenNumber == this screen (1..12), and paints the screen-number numeric + subsystem
label + type-specific label cells dispatched on GetClassID (Sensor/Myomers/Emitter/PPC/
Projectile/Missile/GaussRifle). methodDescription = 9 params (mode, screen, 7 .pcc names).
Registered in BTL4MethodDescription[]. Two raw subsystem reads (heat-sink #, +0x224 label)
are guarded fail-soft best-efforts (marked; the aux-screen bridge label64 substitutes).
VERIFIED: the skip list drops from {prepEngr x12, messageBoard} to just {messageBoard} --
all 12 prepEngr gauges build; no screen-range/crash; boot+sim+combat run; gauge composite
renders identically (COOLANT 300/77, clusters, radar all un-regressed). The prepEngr labels
paint on the Eng (ModeMFD*Eng*) screens, not the default composite view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The gauge-complete-decode workflow found the "deep heat-leaf byte-exact re-base" is
STALE -- the leaf layout already shipped byte-exact (alarm-unification/P7), and the
real work is a small misidentified-field data fix (highest value, lowest risk):
- Sensor radarPercent read the INHERITED heatEnergy (~1.3e7) -> hugely negative ->
needed a bring-up guard forcing it to 1.0. The binary (@004b1c4c:1829) reads
*(this[0x38]+0x158) = the subsystem's OWN DamageZone::damageLevel [0,1] (0 intact ..
1 destroyed). Fixed: radarPercent = RadarBaseline - GetSubsystemDamageLevel(); guard
DELETED (damageLevel is engine-clamped). So the radar authentically degrades with
sensor structural damage (RadarBaseline-damage, x0.5 on DegradationHeat, 0 on Failure).
- New databinding-SAFE accessor MechSubsystem::GetSubsystemDamageLevel()/Set... reads the
ENGINE DamageZone::damageLevel NAMED member (NOT the ReconDamageZone proxy, whose
offset-0 structureLevel aliases the vtable ptr as a float = garbage).
- HeatSink::HandleMessage msg==1: same misidentification -> SetSubsystemDamageLevel(0.5)
(was linkedSinks->heatEnergy=0.5, wrong object+field).
- MyomerCluster seek lamp: subsys+0x800 (OOB for 0x358 Myomers) -> currentSeekVoltageIndex
@0x320 (the Emitter+0x3f0 analog; INFERRED -- ctor not in the assert-anchored export).
- numericSpeed (~225 vs 61.5): verified NOT A BUG (NumericDisplaySpeed applies x3.6; the
feed is correct u/s) -- closed, no code.
No layout change (already byte-exact); pure data-read retargets. Verified DBASE+dev
gauges: no crash, combat un-regressed (TARGET DESTROYED). (radarPercent degrade is only
OBSERVABLE once per-subsystem damage routes to each engine DamageZone -- a separate task;
today the sensor is undamaged so radarPercent = full 1.0, which is correct.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root-caused the P2c abort with a cdb attach to the frozen abort dialog: the stack
was Gauge::Execute (GAUGE.cpp:598) -> abort, via GuardedExecute <- Update <-
ProcessOneActiveGauge. The engine base Gauge::Execute is Fail("not overridden")
-> abort, so an ACTIVE container that doesn't override Execute aborts by design;
GuardedExecute's SEH can't catch an abort(). The decode's "GeneratorCluster
overrides ONLY the dtor / renders via its children" was wrong -- the 1995 base
Gauge::Execute was a no-op, the 2007 WinTesla engine's aborts, so the container
MUST override Execute (the sibling SubsystemCluster does).
FIX: give GeneratorCluster the two container overrides (mirroring SubsystemCluster):
- BecameActive() -- blit the panel's static background pixmap (DrawPixelMap8) and,
by overriding the default, DON'T self-inactivate so the panel stays active.
- Execute() -- no-op (the 5 self-registered child gauges draw the dynamic content;
the override exists only to avoid the base Fail->abort).
Re-registered in BTL4MethodDescription[]. Verified DBASE+dev gauges: all 4
generator panels (A/B/C/D) render -- blue OutputVoltage bars (the @004c72ac
ScalarBarGauge Scalar* variant), labels, status lamps -- 0 "BecameActive not
defined" warnings, NO abort, combat un-regressed (TARGET DESTROYED). Phase 2 is
now COMPLETE (4/4 widgets: LeakGauge, VertNormalSlider, PilotList, GeneratorCluster).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconstructed the 4th unbuilt widget (the 4 generator engineering panels, buttons
9-12) + its companions, but left it UNREGISTERED pending a runtime-abort fix:
- Generator OutputVoltage attribute table (powersub) -> outputVoltage@0x1DC, chained
to HeatSink's dense index (temps already reach it). Correct + reusable.
- ScalarBarGauge @004c72ac plain-Scalar variant (btl4gau2) -- the DIRECT-Scalar* bar
the GeneratorCluster voltage bar needs (the existing @004c721c takes a resolved
source object). Correct + harmless.
- GeneratorCluster Make/ctor/dtor + 15-param methodDescription reconstructed from
part_014.c:891-1013 (replacing the prose + placeholder Make + the bogus DAT pool);
builds the 5 children (temp bar / voltage bar / leak wipe / 2 lamps). Header fixed
to the binary ctor param order (secondaryColor@0x94, +_reserved0xB0 -> sizeof 0xB4).
ChildRate forward-declared.
⚠ REGISTRATION GATED (commented out in BTL4MethodDescription[]): registering it
aborts at runtime -- the 4 panels build (the 4 "BecameActive not defined" warnings
fire) then abort(). ISOLATED to the PARENT lifecycle: the abort PERSISTS with all 5
children nulled; there are no pure virtuals; Execute is SEH-guarded under BT_DEV_GAUGES
so the fault is in the unguarded ctor/lifecycle path. The sibling SubsystemCluster
overrides BecameActive/Execute/TestInstance whereas the decode has GeneratorCluster
overriding ONLY the dtor -- that "inherits only the dtor" claim is the prime suspect.
Deferred; the 3 other P2 widgets (LeakGauge/VertNormalSlider/PilotList) ship clean.
Verified: unregistered -> parse-skips -> no abort; combat un-regressed (DESTROYED),
0 crashes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the return-0 stubs with the real functions:
- ResolveLink (FUN_00417ab4) = SubsystemConnection::Resolve (two-level deref of the
plug's bound link); an unbound plug resolves to 0, the authentic no-source branch.
- FindLinkedSubsystem (FUN_0041bf44) = GetAttributePointer(index) via the engine
Simulation attribute-index system (the cooling-loop master at slot 3).
- IsGeneratorDerived (FUN_0041a1a4) = the real IsDerivedFrom(PoweredSubsystem
ClassDerivations 0x50f4bc) via a new powersub.cpp bridge BTIsPoweredSubsystem
(shared with the aux-screen filter) -> the generator stateLamp now builds.
So the cooling/power/voltage connections resolve real data when the plug is bound,
instead of always reading 0. Build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconstruct SegmentArc270 (@004c6244): engine SegmentArc base + a Scalar connection
driving currentValue + the |n|/(|n|-1)*0.75 segment-span factor. Wire it into
WeaponCluster as the recharge dial (was a deferred NULL), reading the weapon's real
PercentDone attribute. Render-verified (BT_DEV_GAUGES_DOCK): the green segmented
recharge circles draw in the weapon panels; 7 panels build, 0 crashes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The engineering-screen cluster panels now build for each subsystem on its real
aux-screen position. Two pieces:
1. Aux-screen bridge (powersub.cpp BTGetSubsystemAuxScreen): the assignment lives
on PoweredSubsystem (auxScreenNumber/Placement/Label, resource +0x104/108/10C)
which our ctor already populates -- but the reconstructed heat-leaf branch is
NOT byte-exact, so the Make's raw sub[0x1dc]/[0x1e0]/[0x1e4] reads were garbage
(0xCDCDCDCD). The bridge casts through the real PoweredSubsystem type + returns
the NAMED fields (and is the FUN_0041a1a4/0x50f4bc type filter). Make + Subsystem
Cluster now read via the bridge.
2. Reconstruct the 4 child gauges the clusters build that were declared-but-undefined
(only VertTwoPartBar/OneOfSeveral/PixInt existed): HorizTwoPartBar (full, mirrors
Vert), OneOfSeveralInt (ctor), OneOfSeveralStates (ctor + BecameActive + State
Connection @004c3324), BitMapInverseWipe/LeakGauge (full) + SeekVoltageGraph's
Execute/BecameActive/dtor -- their /FORCE-stubbed ctors were segfaulting when a
cluster built them.
VERIFIED (BT_DEV_GAUGES + BT_VSS_LOG): 7 authentic panels build -- Sensor(scr5,
qsensors) HeatSinkCluster, Myomers(scr6,qmyomers) MyomerCluster, PPC(scr1,10) +
Emitter(scr4,8,7) Energy/Ballistic clusters -- with real labels + placements, 0
crashes, combat un-regressed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a BT_VSS_LOG diagnostic (Make entry + per-subsystem classID/auxScreen dump +
the ConfigureForModel path). Verified under BT_DEV_GAUGES: the gauge renderer
builds, ConfigureForModel(Init) runs bhk1Init, and VehicleSubSystems::Make is
called with the full 33-subsystem roster -- the dispatcher iterates every
subsystem and its classID switch resolves the displayable types (0xBC3 Sensor,
0xBC6 Myomers, 0xBC8/0xBD4 Emitter/PPC, 0xBCD/0xBD0 Proj/Missile).
CONFIRMED (empirically) the Phase-2 blocker: subsystem[0x1dc] (the aux-screen
position the Make gates on) reads 0xCDCDCDCD (uninitialized) / garbage for the
displayable subsystems -> every panel hits the "Illegal aux screen position"
skip, so nothing renders yet. Phase 2 = populate the aux-screen field from the
subsystem resource. Combat remains un-regressed (the skip path is inert).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconstruct the whole engineering-screen (MFD) subsystem cluster-panel system in
btl4gau2 (from the Ghidra decomp of BTL4OPT.EXE) and register the vehicleSubSystems
config primitive:
- Sub-gauges: AnimatedSubsystemLamp/AnimatedSourceLamp (OneOfSeveral + cooling/
power connections), ScalarBarGauge (BarGraphBitMapScalar + generator-voltage
connection), ConfigMapGauge, plus the 3 file-private GaugeConnection classes
(@004c3134/31ec/3288 samplers).
- SubsystemCluster base (ctor builds ~11 child gauges across the MFD + engineering
ports; Execute/dtor/frame-draw/enable helpers) + the 5 subclasses HeatSink/
Myomer/Weapon/Energy/Ballistic (ctors + Executes).
- VehicleSubSystems::Make dispatcher (@004cbaf0): per-subsystem factory keyed on
classID (HeatSink 0xBC3 / Myomer 0xBC6 / Energy 0xBC8,0xBD4 / Ballistic
0xBCD,0xBD0), placed via the 12-row aux-screen geometry table, + registered in
BTL4MethodDescription[].
SAFE + gated: gauges only build under BT_DEV_GAUGES/pod, and until Phase 2
populates the subsystem aux-screen field (subsystem[0x1dc]) the Make hits its
"aux screen = zero" skip -> combat untouched. Two minor children deferred as
marked NULLs (SegmentArc270 recharge dial + BitMapInverseWipeScalar eject wipe --
their ctors are btl4gaug placeholders). Raw-offset subsystem reads (0x1dc/0x1e0/
0x1e4/0x31c/0x334/0x364/0x43c...) marked BEST-EFFORT pending Phase 2.
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>