Commit Graph
10 Commits
Author SHA1 Message Date
arcattackandClaude Opus 4.8 89b4f53046 Gauges: trigger-config joystick is LIVE -- @004c6ee0 is LinkToEntity, not SetColor
The weapon panel's btjoy trigger-config display (ConfigMapGauge) never rendered
because the reconstruction labelled @004c6ee0 as "SetColor(int)" and, finding no
caller, concluded the gauge was authentically dormant (task #6), gating it
behind a BT_CONFIGMAP dev env.

The user challenged this (the PROGRAM/TRIGGER CONFIG button implies the display
visualizes your config).  Re-verification found the old analysis failed twice:
the "no caller" search looked for direct calls to a VIRTUAL, and its slot math
used the wrong vtable copy.  The real ConfigMapGauge vtable @0051a1b8, matched
against the T0 GaugeBase virtual roster (GAUGE.h), pins @004c6ee0 at slot 9
(+0x24) == GaugeBase::LinkToEntity, and @0x94 is the linkedEntity -- the
Execute gate is "not yet linked", not "disabled".  The engine broadcasts
LinkToEntity(viewpointEntity) to every gauge at viewpoint bind (APP.cpp:1277 ->
GaugeRenderer::LinkToEntity GAUGREND.cpp:3011), arming the gate -- the joystick
DOES render in the shipped game, showing per-trigger mapping state (solid =
mapped, matching the DOSBox reference).

Port fix: SetColor(int color) -> LinkToEntity(Entity*), color -> linkedEntity,
BT_CONFIGMAP dev enable deleted (the authentic path lights it).  Render-verified
with no env override: the joystick + mapped-trigger lamp draw on weapon panels.

KB swept per the correction mandate: gauges-hud.md, decomp-reference.md,
open-questions.md, GAUGE_COMPOSITE.md, VEHICLE_SUBSYSTEMS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:49:58 -05:00
arcattackandClaude Opus 4.8 66dcdf23db Gauges: ammo count stencils out of the fire-ready dot (BallisticWeaponCluster)
The base-page missile/round count sat on the fire-ready "dot" (the cluster
image blitted green when charged) as green digits on an opaque black box --
a messy dark rectangle punched into the solid green dot.

The binary swaps the ammo count's colours with the dot state.  Verified from
the decomp: BallisticWeaponCluster overrides DrawWarningLamp (@004c9b50) --
it chains the base (@004c932c, which blits the dot in on-colour 0xff /
off-colour 0) and then calls the ammo NumericDisplayInteger's SetColors:
  dot ABSENT  -> SetColors(0, 0xff)  == green digits on black
  dot PRESENT -> SetColors(0xff, 0)  == BLACK digits cut out of the green dot
so the count is always legible (a stencil against the solid dot -- the same
black-on-green look as the loop/generator squares).  SetColors ForceUpdate()s
the numeric so it repaints over the freshly drawn dot.

The port had only the base WeaponCluster::DrawWarningLamp (non-virtual, no
swap), so the count stayed green-on-black and clashed with the green dot.
Made DrawWarningLamp virtual and added the BallisticWeaponCluster override.
Render-verified (STREAK "0024" now black-cut-out of the solid dot); energy
weapons (no ammo count) unaffected.  Both builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 00:44:38 -05:00
arcattackandClaude Opus 4.8 e634709e5d Gauges: fix blank per-weapon loop/generator lamps (4A/1B/5D boxes)
The SubsystemCluster per-weapon MFD panels render two image-strip lamps in
the TEMP/STATUS area: the cooling-loop number (btploop, 1..6) and the
generator letter (btpbus, A..D). Both drew as completely empty boxes.
Three stacked databinding bugs, all now fixed:

1. Color drop (btl4gau2.cpp): AnimatedSubsystemLamp/AnimatedSourceLamp
   ctors dropped the bg/fg color params the binary passes (bg=0xff,fg=0,
   same as the neighboring temp bar) and hardcoded 0,0 -> OneOfSeveral::
   Execute did SetColor(0)+DrawBitMapOpaque(0), i.e. black-on-black.
   Restored 0xff,0 (verified vs @004c70a4 / caller SubsystemCluster
   @004c8140).

2. Shadow-field trap (btl4gau2.hpp, gotcha #2): both lamp classes
   re-declared `int selected;` while already inheriting it from
   OneOfSeveral (@0xAC). The CoolingLoop/PowerSource connection wrote the
   derived shadow copy while OneOfSeveral::Execute read the base @0xAC
   (always 0) -> every lamp stuck on frame 0 ("OFF"). Removed the
   redeclarations so &selected binds the inherited member. Loop numbers
   now render (verified: PPC->4, Myomers->5, ERMLaser->1/6, etc.).

3. Attribute-table shift (powersub.cpp + btl4gau2.cpp, gotcha #8): the
   generator-letter lamp resolved its source via
   ResolveLink(AttributePointerOf(subsystem,"InputVoltage")), but the
   BT_DEV_GAUGES audio attribute rows shifted the chained attribute ids so
   AttributePointerOf no longer landed on voltageSource@0x1D0 -> the link
   always resolved to 0 (OFF) even though the master PoweredSubsystem ctor
   DID bind voltageSource to its Generator. New BTPowerSourceFrame bridge
   reads the NAMED member via PoweredSubsystem::ResolveVoltageSource() and
   returns Generator::generatorNumber (@0x1E0), bypassing the attribute
   table -- the same pattern as the BTCoolingLoopFrame fix. Generator
   letters now render (verified: PPC_1->GeneratorA="A", Myomers->
   GeneratorD="D", SRM6->GeneratorB="B").

Result: the "4 A / 1 B / 5 D" boxes render with both the loop number and
generator letter, matching the reference. Kept BT_LOOP_LOG-gated
[loopfeed]/[busfeed] feed diagnostics; both pod + glass builds clean.

Awaiting live playtest verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 00:08:58 -05:00
arcattackandClaude Fable 5 63b168cb92 SeekVoltageGraph: full reconstruction -- the eng-page POWER graph + top-box eraser (Gitea #11)
The #10 audit's one WRONG: the port Execute was a bring-up no-op, so the
emitter/myomer engineering pages never erased their top data box -> stale
sibling-page ghosts on the shared Eng bit-plane (SYSTEM 10 PPC showing the
Streak ammo box, etc).  Full faithful widget landed:

- btl4gau2: ctor/BecameActive/Execute/clear/ticks/cursor recovered from the
  capstone disasm of @004c6798/@004c6920/@004c6934/@004c6be4/@004c6c30/
  @004c6c6c (Ghidra dropped every x87 arg).  Plot: v=0..12000 step 1200,
  x=Round(response(v)*230), y=Round(v*(1/12000)*187) (ld80 @004c6bd0/@004c6d74
  = exactly 1/12000); change-test samples the response at 12000V vs the 9999
  activation sentinel; XOR op for tick/cursor move-by-redraw; destroyed branch
  centres edestryd.pcc and revives via own vtbl+0xC (BecameActive, slot 3 of
  PTR_0051a1fc -- vtable-dump verified).
- The vtbl+0x3C sampler identified from the binary vtables: Emitter slot 15
  @004bb42c = sqrt(P(v)/2.0e8), P @004bb3f4 = damageFraction*v^2*0.5*
  energyCoefficient; Myomers slot 15 @004b8f94 = sqrt(AvailableOutput(v)*3.6/
  350).  FUN_004dd138 == sqrt (part_015.c:4026): myomers' fabs reading
  corrected, GetSpeedReading renamed SeekVoltageResponse.  Port dispatch via
  complete-type bridges BTSeekVoltageSample/BTMyomersSeekSample +
  BTSubsystemDestroyed (databinding rule).
- Emitter's AUTHENTIC attribute table recovered (binary @0x511dd4, ids
  0x1D-0x25) and published: Laser*/Seek*/OutputVoltage@0x414 (currentLevel,
  RAW volts -- the live-cursor feed).  Field renames per the table:
  0x3F8 minSeekVoltageIndex / 0x3FC maxSeekVoltageIndex (static_assert-locked).
  The MechWeapon 0x1D OutputVoltage PORT ALIAS retired (binary table ends
  0x1C; Find walks lowest-id-first, the alias shadowed the authentic row).
  PPC::DefaultData -> Emitter::GetAttributeIndex().
- mech4: BT_PRESET_HOLD=<n> (freeze the #9 preset cycler after n pulses) for
  steady-state pixel verification; BT_SEEK_LOG diag.

Verified live (BLH, autofire): both #10 repro pairs held ghost-free for
minutes (SYS09->SYS10 PPC, SYS02->SYS04 ERMed, SYS05->SYS06 Myomers); curves
draw with live cursors; one replot per activation; quad panels/J-K-L/sec
panel un-regressed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:27:39 -05:00
arcattackandClaude Fable 5 bb795e2805 MP live-play wave: collision economy, missiles, radar transform, panel polarity, comm ticker
The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified:

COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity
per contact and ProcessCollisionList walks EVERY touched solid per frame; with
2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a
walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy).
Fix: frameEntryWorldVelocity restore per contact (damage always priced at the
real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes
the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry.
Gotcha #16 (engine-facility drift class).

MISSILES: peer-visible salvos (the launcher record extension carries a salvo
counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone
never serialized), the authentic arc (authored MuzzleVelocity vector + the
Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78),
world-impact bursts (rounds detonate on cave geometry instead of phasing
through), contact-only damage (flight-cap expiry = fizzle, no more teleport
damage), live re-lead, and ballistic (unguided) shells for autocannons.
projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6
corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded.

RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244
is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation
(never the translation); worldToView now Invert(view) built rotation-first.
CulturalIcons sorted out of the moving grid (the phantom red pips were map
props), visible-radius culls on all three draw passes, live pip verified at
|delta| x ppm px.  Gotcha #17 (verify the FUN_ body, not its call shape).

WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means
FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X
lamps, the panel look, the children enable and the ready-lamp gate (which had
NEVER executed).  Polarity chain corrected end-to-end (failedState, fed by real
damage saturation).  Root cause of the freezes: MFD page-mode gating -- the dev
composite shows ALL pages at once, so off-page dials legitimately stopped; under
BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio
untouched).  The SEH gauge guard now names its kills; repaint-heal resets the
incremental arc after panel repaints; [panel]/[arc] probes added.

COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole
Player__StatusMessage queue; wired the binary's one producer -- the kill branch,
victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS
counted via the observed-death tally (each node scores every pilot from locally
observed events, the same model as the KILLS credit) and the -2/-1 engine seed
clamped for display.

DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480,
true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering,
BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar
(first second-chassis live outing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 17:24:15 -05:00
arcattackandClaude Opus 4.8 c0d2eb0aae gauge-complete P4f: PrepEngrScreen reconstructed + registered -> 12 engineering-screen label overlays build
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>
2026-07-07 21:32:12 -05:00
arcattackandClaude Opus 4.8 88cd68aaa1 gauge wave P2d: GeneratorCluster un-gated -- the 4 generator panels render
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>
2026-07-07 17:57:20 -05:00
arcattackandClaude Opus 4.8 b44e908101 gauge wave P2c: GeneratorCluster reconstructed + infra (gated -- parent aborts)
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>
2026-07-07 17:43:47 -05:00
arcattackandClaude Opus 4.8 674c172cf8 vehicleSubSystems Phase 1: reconstruct + register the cluster-panel family
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>
2026-07-07 06:43:32 -05:00
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
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>
2026-07-05 21:03:40 -05:00