master
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
02cdfd6576 |
Torso: the TWIST goes LIVE -- electrical watchdog chain, centered crosshair, coherent controls (task #57/#58)
The MadCat torso twists, the view turns with it, and targeting follows. Three reconstruction fronts closed: THE ELECTRICAL WATCHDOG CHAIN (why the torso never powered up): - PowerWatcher::UpdateWatch reconstructed (@004b181c, the REAL registered Performance -- PTR @0050f5fc; Ghidra missed the fn start): the watchdog MIRRORS the watched subsystem's electrical level (+0x278), brownout downgrade when gen output <= minVoltage% x rated. @004b1804 relabeled ResetToInitialState (slot 10) -- the old "Simulation" tag was wrong. - The factory watcher-CONNECT pass reconstructed (vtable slot +0x38, @004aee2c/@004b1a40 byte-identical, recovered from raw exe bytes): watchedLink.Add(roster[watchedSubsystem]) on the master node. Was the SubProxy::Start() no-op -- every watchdog sat at 0 forever. - MinVoltageScale = 0.01 (a 10-byte x87 literal @0x4b1924; was 1.0f = permanent brownout) and PowerWatcher's Derivation chains its REAL base HeatWatcher (the HeatableSubsystem stand-in broke IsDerivedFrom for the whole Torso/Searchlight/ThermalSight family). - KB correction swept: derivation tag 0x50e604 = HEATWATCHER (not "HeatSink"); the btl4gaug heat-widget gate now tests it via the BTIsHeatWatcher bridge. THE CROSSHAIR (task #58 forensics, 6-agent workflow + live probes): - The VIEW is TORSO-MOUNTED: jointtorso -> jointeye -> siteeyepoint in every twist-capable .SKL; the camera + canopy ride the same hinge subtree through HingeRenderable's live matrix-stack compose -- ALREADY WORKING in the port. The crosshair stays screen-centered (center IS the boresight); the twist reads on the tape carets/compass/radar. - The real bug was the port's gBTAimX = tan(twist) slew (the falsified "body-mounted view" model): the camera already carried the twist, so the crosshair counter-slid to hull-forward and the fire ray with it. Deleted; the pick ray inherits the twist from the yawing eye basis. - Two instrumentation traps documented (chase-eye-as-default-camera, BT_FORCE_TORSO clobbering real joints -> the hook now only fills unresolved ones); an over-correcting explicit eye compose was added on those false readings and retired the same day. CONTROLS + REPLICATION: - Q/E spring-center on release (the axis is a twist-RATE demand; the old hold-deflection model drifted forever); X also zeroes the axis and pulses the authentic torso Recenter (@004b6918). M cycles control mode via the real CycleControlMode body. - Torso update-record DIRECTION fixed: engine truth is Write=serialize / Read=apply; @004b6a78 is the READ (was mislabeled Write) and the missing WRITE @004b6a1c recovered from raw disasm (recordLength 0x1C, twist/vel/rate at +0x10/14/18) -- kills the replicant's 0xCDCDCDCD -140-degree ghost twist. - Marching-ghost desync: 4 Standing-case guards zero stale reverse cycleSpeed (negative cadence passed the <= ZeroSpeed stop gate). - Kill credit rerouted to the OBSERVED killer (lastInflictingID -> killer's player link) -- kills count, target K/D populates. KB: subsystems.md (watcher chain), multiplayer.md (record direction), combat-damage.md + gauges-hud.md + cockpit-view.md (torso-mounted view re-correction), decomp-reference.md (new addresses + tag fix), open-questions.md (dead capability-roster loops 2-4, snapshot CD read). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
d68284ede2 |
gauge wave P2a: build the condenser LeakGauge + valve slider (heat panel)
The gauge-widget-decode workflow reconstructed the unbuilt cockpit widgets from the binary. First two (the heat MFD's missing SET/LEAK columns): - LeakGauge (BitMapInverseWipe, keyword "LeakGauge"): the class body already existed + is byte-faithful; it was just UNREGISTERED so every LeakGauge(...) config line was parse-skipped. Added the Make factory + methodDescription + the BTL4MethodDescription[] registration. Value @6 = Condenser/CoolantMassLeakRate (already published P1); 0 undamaged -> the leak wipe is authentically empty. - VertNormalSlider (keyword "vertNormalSlider"): the condenser VALVE slider @2 was PROSE-ONLY. Reconstructed all 7 functions (Make/ctor/dtor/TestInstance/ BecameActive/Execute/Draw) from part_013.c:14051-14175 (Make @004c4b08 by disassembly) -- an XOR indicator row=Round(span*value) over the track; fixed the jumbled header member layout to the byte-exact order (sizeof 0xB0). Published ValveSetting -> coolantFlowScale@0x15C on the HeatSink table so Condenser/ValveSetting resolves (verified: all 6 condensers OK). The valve indicator renders (condenser 1 shows it near the top = coolantFlowScale 1.0 = valve open). Both /FORCE-safe (every vtable slot has a real body; link log grep clean, no unresolved VertNormalSlider/BitMapInverseWipe). Verified DBASE+dev gauges: the heat panel now shows all 3 columns per condenser (TEMP scales, SET valve indicator, LEAK track), coolant bar stays full, combat un-regressed (TARGET DESTROYED), 0 crashes. The player-valve-toggle -> coolantFlowScale drive (SetValveSetting vtable+0x48) is the remaining Phase-3 valve-mechanism work; the slider shows the static valve value today. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4ab6748563 |
vehicleSubSystems follow-up: reconstruct BitMapInverseWipe::Execute (LeakGauge)
Replace the minimal-safe Execute stub with the authentic wipe (@004c5d08): round the level, repaint `frames` segments stacked vertically, each picking its strip frame (empty/half/full via colorA/colorB) from the level. The coolant LeakGauge in every SubsystemCluster now renders faithfully from its CoolantMassLeakRate value. Build clean, 7 panels, 0 crashes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fbf5118c9c |
vehicleSubSystems follow-up: reconstruct SegmentArc270 recharge dial
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> |
||
|
|
6fceb58439 |
vehicleSubSystems Phase 2: authentic aux-screen assignment -> panels build
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> |
||
|
|
a538d6438e |
gauges: reconstruct + register oneOfSeveralPixInt -- cockpit button-state lamps
The OneOfSeveral family was declared but undefined. Reconstruct the shared base (OneOfSeveral: ctor @004c4d88, BecameActive @004c4f14, Execute @004c4f28, dtor @004c4e7c) and the OneOfSeveralPixInt subclass (Make @004c5204, ctor @004c52d8, dtor @004c5364) + its methodDescription, and register "oneOfSeveralPixInt". OneOfSeveral is a multi-frame strip selector: the base holds a BitMap or PixMap8 strip of columns*rows frames + the per-frame size (source size / columns,rows); Execute picks frame `selected` (col=selected%columns, row=selected/columns) and blits that sub-rectangle -- DrawPixelMap8(opaque) for a pixmap strip (its own palette, no SetColor), or SetColor(bg)+DrawBitMapOpaque(fg) for a bitmap strip. Added the missing OneOfSeveral::foregroundColor field (@0x9C -- the DrawBitMap Opaque background arg). OneOfSeveralPixInt forces the PixMap path (fromImageStrip =0, bg/fg=0) and wires one GaugeConnectionDirectOf<int> to the frame index. methodDescription = (rate,modeMask,string strip,integer columns,integer rows, attribute state) matching oneOfSeveralPixInt(rate,mode,strip.pcc,cols,rows,attr). Strip resource via warehouse->pixelMap8Bin (WRHOUS.h) .Get/.Release. The config binds the duck / searchlight / display-mode / piloting-mode buttons; ControlsMapper/DisplayMode already resolves (MechControlsMapper table), and DuckState/Searchlight/LightOn degrade to frame 0 (safe) until their tables land. Verified live (BT_DEV_GAUGES): the cockpit button-lamp PixMap8 icons now render (were absent). No /FORCE unresolved externals. Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes, no missing-image. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a2ee1181e6 |
gauges: reconstruct + register segmentArcRatio -- the cockpit SPEED arc
SegmentArcRatio (config keyword "segmentArcRatio") was declared but undefined. It is a thin subclass of the engine SegmentArc gauge: two Scalar connections (numerator=value, denominator=max) and an Execute override that computes the [0,1] fill fraction into the base's currentValue, then delegates drawing to SegmentArc::Execute (which lights numerator/denominator of the segments). Reconstruct Make (@004c62fc) / ctor (@004c6394) / Execute (@004c6488) / dtor + the methodDescription (rate,modeMask,scalar inner,scalar outer,scalar deg0, scalar deg1,integer segs,color bg,color fg,attribute numerator,attribute denominator -- matching the engine SegmentArcNormalized param types; the config gives inner/outer once and the Make duplicates them into inner0==inner1 / outer0==outer1). Execute recovered by disassembling the vtable override @004c6488 (Ghidra dropped the x87): if (denominator < 1) currentValue = 0; else currentValue = clamp(|numerator/denominator * segmentSpan|, 0, 1); SegmentArc::Execute(); where FUN_004dcd00 = fabs and the ctor precomputes segmentSpan = (Scalar)(|n|/(|n|-1)) * 0.75f // int division => 0.75 for n=36 (_DAT_004c6484 = 0.75f, read live from the binary). Fixed the header field type int segmentSpan -> Scalar (the ctor fstp's it and Execute fmul's it). The config binds numerator=LinearSpeed, denominator=MaxRunSpeed -- both now resolved by the Mech attribute table -- so the arc sweeps with speed. Verified live (BT_DEV_GAUGES + drive): the radar-surface SPEED dial now shows a green segmented arc filling with speed (was a plain ring). No /FORCE unresolved externals. Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
18d58f38f6 |
gauges: reconstruct + register vertBar (VertTwoPartBar) -- cockpit coolant bars
VertTwoPartBar was declared in btl4gaug.hpp (ctor/field offsets/decomp addrs) but never defined. Reconstruct Make (@004c462c) / ctor (@004c4724) / BecameActive (@004c48e0) / Execute (@004c48fc) / dtor + the "vertBar" methodDescription, and register it in BTL4MethodDescription[]. The Execute pixel math is x87 that Ghidra dropped (FUN_004dcd94 is just (int)ROUND(ST0); the operands are on the FPU stack) -- recovered by disassembling @004c4940/@004c4960: warnPix = clamp(round(height*low/high + 0.5), 0, height) valPix = clamp(round(height*value/high + 0.5), 0, height) value is first clamped to [0,high]; on a change it tile-blits the bitmap over [0,warnPix), fills [warnPix,valPix) with fillColor, clears [valPix,height) with extraColor (reusing the existing DrawTiledBitmap helper = FUN_004c2ff8; view methods SetPositionWithinPort/SetColor/MoveToAbsolute/DrawFilledRectangleTo Absolute = vtbl +0x08/+0x18/+0x24/+0x48). methodDescription param types (rate,modeMask,vector,string,color,color,color,attribute,attribute,attribute) match the config vertBar(rate,mode,(w,h),tile,bg,fill,extra,current,warn,max). For the coolant bars the config binds current=CoolantMass, warn=max=Coolant Capacity, so the warn line sits at full and the bar is a simple level gauge that empties as CoolantMass drops -- now driven by the HeatSink attribute table committed earlier. Verified live (BT_DEV_GAUGES): the Heat surface COOLANT/RES bars now render the btwarn.pcc hatch fill (was empty), reading the real coolant attributes (full hatch = coolantLevel~=capacity). No /FORCE unresolved externals on the new symbols. Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes, and the gauge build + mission load are heap-clean under BT_HEAPCHECK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d006b40927 |
gauges: reconstruct cmCrit -- subsystem-critical tint; ColorMapper family complete [widget recon 5]
Reconstructs ColorMapperCritical -- tints a schematic colour by a subsystem's operational state (the "critical" secondary display mode): - CriticalConnection (@004c3598/@004c3610): src==0 -> 0; src->simulationState==1 (DestroyedState) -> 100; else the subsystem's own damage-zone damageLevel*100. - ColorMapperCritical::Make/ctor (@004c3ddc/@004c3e40): FindSubsystem by name + wire the CriticalConnection. Resolved the subsystem damageZone@0xE0 shadow: MechSubsystem::damageZone is declared ReconDamageZone*, but the assignment (mechsub.cpp: damageZone = (ReconDamageZone*)new DamageZone(...)) shows the pointer IS a real engine DamageZone -- so cast it back and read damageLevel. Added public accessors GetSimulationState()/GetDamageZoneProxy() to MechSubsystem (those fields are protected; mirrors how HeatConnection reads the public currentTemperature). Completes the ColorMapper family (cmHeat, cmArmor, colorMapperMultiArmor, cmCrit). Verified: parses, builds, all subsystems resolve (0 warnings), no /FORCE unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes), stable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fb8c1d6ace |
gauges: colorMapperMultiArmor (worst-of-8-zones) + fix the interpreterTable overflow [widget recon 4]
Reconstructs colorMapperMultiArmor -- tints one schematic colour (a whole torso section) by the WORST of up to 8 damage zones: - MultiArmorConnection (@004c346c/@004c34f4): scans 8 zones via entity->damageZones[idx], keeps the max damageLevel, count==0 ? 100 : Round(worst*100). - ColorMapperMultiArmor::Make/ctor (@004c3c48/@004c3d60): resolves 8 named zones to indices via Entity::GetDamageZoneIndex (13-param methodDescription). ALSO fixes a latent HEAP CORRUPTION that registering this widget exposed (and that would have blocked every further widget): GaugeInterpreter's bytecode buffer is a fixed interpreterTableSize=46864 (GAUGREND.h) tuned for RP's config, and its Insert() bounds guard is a Verify() that compiles out at DEBUG_LEVEL 0. As each BT gauge widget is registered, more of BT's larger L4GAUGE.CFG resolves into bytecode (vs being parse-skipped); colorMapperMultiArmor's 13 params x hundreds of calls pushed the total past 46864 -> silent overflow past the char[] -> heap smash detected later in mission bitmap loading (not the gauge code). Fix: interpreterTableSize -> 262144 (sized for BT's full config). Verified: parses, builds, all zones resolve, no /FORCE unresolved, combat un-regressed (TARGET DESTROYED, 0 crashes), stable. Details: docs/GAUGE_COMPOSITE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1d2ddd8399 |
gauges: reconstruct cmArmor -- the per-zone ARMOR DAMAGE schematic [widget recon 3]
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> |
||
|
|
e869b00181 |
gauges: reconstruct headingPointer -- the rotating compass + heading readout [widget recon 2]
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> |
||
|
|
547f25e043 |
gauges: reconstruct + register the first BT gauge widget (cmHeat) [widget recon 1]
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>
|
||
|
|
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>
|