4c54f7ef0ccdce702d04f7cd287acbce7ff38607
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
80cadc98c7 |
Cockpit: the COOLANT VALVE control -- the heat-management triad completes (task #13)
The pilot's third heat tool: Condenser::MoveValve (id 4, the Condenser handler table @0x50E52C -- PE-verified, exactly one entry) registered via the order-proof function-local accessor and GUARDED by the real FUN_004ac9c8 (task #12's BTPlayerRoleLocksAdvanced rookie-role bridge -- the old deferral note blamed the 0xBD3 messmgr, a stale misattribution, swept). Each press cycles the valve 1 -> 5 -> 50 -> 0 -> 1 and RecomputeCondenserValves re-shares the fixed total coolant flow across all six condensers -- opening one valve wide starves the others: the pilot's cooling-triage lever. Desktop: 'C' cycles the selected condenser's valve (BT_VALVE_SLOT picks the roster slot; default Condenser1). BT_VALVE_TEST scripts a headless press. Verified live: press -> "[valve] condenser#1 valveState=5 flow=0.5 (total=10)" -- exactly the authored redistribution math (5/(5+5x1)), through the real role gate. The heat-management triad is now complete and all-authentic: G + fire buttons = regroup weapons across buttons (task #6) F5-F8 / F9 = reassign weapon generators / mode (task #12) C = coolant valve triage (task #13) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
29035028fd |
Cross-pod beams: replicate emitter discharge via subsystem update records
User report: lasers only visible on the window firing them. The peer's replicant emitters never learned the master fired. THE AUTHENTIC PIPE (decomp-verified): - FUN_0041c350 (the "beam keepalive" ServiceDischarge/ContinueDischarge call) does TWO things: queue the LOCAL deferred beam-effect callback (@0x4bac0c) on the app+0x34 manager -- our per-weapon render walk already plays that role -- and set the subsystem DIRTY bit, which maps to the 2007 engine's updateModel / ForceUpdate(). - Replication rides SUBSYSTEM UPDATE RECORDS inside the mech's update message: the roster walk already hands the entity's stream to every subsystem's PerformAndWatch; Simulation::WriteSimulationUpdate serializes each requested updateModel bit; Entity::UpdateMessageHandler routes received records by subsystemID to the subsystem's ReadUpdateRecord. All engine machinery -- the missing pieces were the Emitter's serialize/apply pair + the triggers. CORRECTIONS to the dormant task-33-era transcriptions (never exercised -- nothing ever set updateModel -- so the latent misreads never surfaced): - The weapon-family VTABLE SLOT MAP was swapped: slot 6 = ReadUpdateRecord, 7 = WriteUpdateRecord, 9 = TakeDamage (evidence: Mech hierarchy symmetry + body semantics; @004ba568 resolves an EntityID at rec+0x30 through the entity index -- record semantics, not Damage). Renamed across MechWeapon / Emitter / ProjectileWeapon; the real Emitter::TakeDamage @004bafc8 is undecoded (inherits MechWeapon for now). - Emitter/MechWeapon Write: `*record = 0x38/0x18` is the record LENGTH, not recordID; rec+0x30 is the TARGET's EntityID (GetEntityID()), not a colour -- the old `CopyColor(targetEntity+0x184)` was also a databinding trap. - OVERRIDE-SIGNATURE TRAP: the decls used each class's own shadowing UpdateRecord typedef as the param type, silently NOT overriding the engine virtual (the base ran instead; nothing would ever have serialized). Base-typed params (Simulation__UpdateRecord*), casts inside. - Emitter::ReadUpdateRecord reconstructed (@004ba568): target EntityID resolve (drop unknown non-null targets), MechWeapon alarm apply chain, beam fields. - ServiceDischarge/ContinueDischarge: ForceUpdate() per keepalive tick + one final record at beam end (turns the peer's beam off). - Mech::DrawWeaponBeams extracted from the player-only drive block so the walk runs for REPLICANTS (+ per-mech gun-port cache -- the process-wide statics would have served the player's segment pointers as the replicant's muzzles). VERIFIED 2-node: A fires 57 volleys -> 225 emitter records -> B applies all 225 -> B draws 414 beams (PPC blue / laser red, from A's replicant's own gun ports). Solo un-regressed (150 beams, kill chain, no crash). Also preserved: the full Mech::WriteUpdateRecord @0x4a0c2c recovery (reference/decomp/mech_writeupdate_004a0c2c.disasm.txt) with all 9 record types decoded (pose/alarm/leg-state+heat with the body-channel write-through re-sync, knockdown, death, impact, movementMode) -- transcription deferred; it replicates remote knockdown/death/heat and was not needed for beams. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
aae3ce29d8 |
gauge-complete P4d: Condenser::MoveValve reconstructed from the REAL @0x4ae464 (was a mislabel)
The reconstructed Condenser::MoveValve carried FUN_004afbe0's body -- a CONTROLS-mapper display-mode cycler (cycles field@0x190 through 0/1/2 + repoints owner HUD gauges), NOT the condenser valve. The real valve handler @0x4ae464 was not captured by the assert-anchored exporter; disassembled it (tools/disas2.py) and reconstructed faithfully: cycle valveState (@0x1D0) 1 -> 5 -> 50 -> 0 -> 1 (byte-verified @4ae480-4ae4bf), then RecomputeCondenserValves(owner) to redistribute flow so every ValveSetting gauge updates. DORMANT by design: the binary guards on owner->messageManager(+0x190)+0x274 (FUN_004ac9c8, the 0xBD3 SubsystemMessageManager) and the message that invokes MoveValve arrives through that same manager -- both DEFERRED to WAVE 8. Reproducing the raw owner+0x190 deref would be a databinding trap in our layout, so the handler is faithful-but-uninvoked; the valve gauge shows the authentic static 1/N (RecomputeCondenserValves at ctor) until 0xBD3 lands. No V-key bring-up stand-in added. Build green; combat TARGET DESTROYED, un-regressed (MoveValve is not yet routed). Co-Authored-By: Claude Opus 4.8 (1M context) <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> |
||
|
|
070af409f7 |
gauge wave P1a: publish heat/power/weapon attributes + radar zoom
The gauge-databinding-map workflow found most cockpit gauges resolve NULL because the reconstructed subsystems publish only a fraction of the attributes the config binds. First publishing batch (attribute tables are read-only static data; ids kept a dense prefix from each parent's NextAttributeID): - HeatSink table dense-append: DegradationTemperature/FailureTemperature (the condenser temp-bar warn/max endpoints -- were NULL, so the two-part bars could not scale), NormalizedPressure/DegradationPressure/CoolantMassLeakRate, and the HeatSink link. Condenser/Reservoir inherit this -> all 6 condenser temp bars now resolve current/warn/max (verified: BT_GAUGE_ATTR_LOG all OK). - PoweredSubsystem::GetAttributeIndex() (new) publishes InputVoltage->voltageSource -- the cluster power-branch gate (the power-lamp/generator-voltage/state-lamp sub-branch is skipped when it resolves NULL). Flows to Sensor/Myomers/weapons. - MechWeapon::GetAttributeIndex() (new) publishes OutputVoltage/PercentDone-> rechargeLevel; Emitter/PPC/ProjectileWeapon/MissileLauncher/GaussRifle DefaultData re-pointed at it (they carried an EMPTY default-constructed index -> resolved NOTHING). Verified: the ER MED LASER / PPC / STREAK weapon clusters now render live recharge dials (were blank TEMP/STATUS). - Mech::SetTargetRange un-stubbed (radarRange = range) -> the radar map scale + overlay range readout track the mapper's zoom (was frozen at 1000). - GAUGREND ParseAttribute: env-gated per-binding resolution trace (BT_GAUGE_ATTR_LOG) -- durable diagnostic infra for the wave. Verified DBASE+dev gauges: no startup/gauge-construction crash (dense chain intact), combat un-regressed (TARGET DESTROYED), clusters build with InputVoltage resolving. Remaining config-binding NULLs: HeatSink/AmbientTemperature (aggregate bank, P3) + Searchlight/LightOn (P1b). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9d82be46a1 |
P7: subsystem-tree alarm unification -- the whole PoweredSubsystem weapon/power subtree is byte-exact
The reconstruction modeled the binary's 0x54 AlarmIndicator (FUN_0041b9ec) with undersized
stand-ins (AlarmIndicator==ReconAlarm==4B; HeatAlarm==8B) across the entire subsystem tree, so
every field above an alarm sat at the wrong compiled offset. Retype every such stand-in to
GaugeAlarm54(0x54) and de-phantom each class against its ctor, so the whole PoweredSubsystem
subtree becomes byte-exact. An 8-agent read-only decomp-mapping workflow decoded every ctor
first; then hands-on implementation. static_assert-locked chain (verified vs the raw ctors):
HeatSink 0x1D0
-> PoweredSubsystem 0x31C retype electricalStateAlarm@0x264 + modeAlarm@0x2B8 @004b0f74
-> MechWeapon 0x3F0 retype weaponAlarm@0x350; delete 5 phantom tail fields @004b99a8
-> Emitter 0x478 delete outputVoltage/beamLengthRatio/firingArmed aliases
+ beamHit*/beamColor/beamHitData/energyRampTime phantoms;
retype beamOrientation EulerAngles->Quaternion(16B) @004bb120
-> PPC 0x478 (no own fields) @004bb888
PoweredSubsystem -> Sensor 0x328 (no alarm; 3 own fields) @004b1d18
PoweredSubsystem -> Myomers 0x358 (no alarm) @004b8fec
New systemic bug-class instances fixed (added to the CLAUDE.md checklist):
* alias field - Emitter outputVoltage==inherited rechargeLevel@0x320; beamLengthRatio==
beamScale.z@0x434; firingArmed==inherited useConfiguredPip@0x3E0
* phantom field - MechWeapon segmentReference/pipSegment/hasTarget/targetPoint/muzzlePoint
(past 0x3F0); Emitter beamHitPoint/beamImpact/beamImpactScalar/beamColor/
beamHitData/energyRampTime (binary writes inherited damageData/voltageScale
or the value is a method local)
Non-layout fixes required in the same wave:
* outputVoltage->rechargeLevel also in the compiled GAUSS.CPP:74/93 (external readers of the
removed Emitter field -- must grep EVERY TU, not just the class's own .cpp)
* MechWeapon::GetMuzzlePoint reimplemented faithfully (removed muzzlePoint collided with
Emitter's own fields at 0x3F0) via a BTResolveWeaponMuzzle void* bridge in mech4.cpp,
resolving the weapon's mount segment (inherited this+0xdc) through the owner segment table
* DetachFromVoltageSource fixed to set electricalStateAlarm not modeAlarm (raw @004b0e30
writes the 0x264 alarm)
The vehicleSubSystems aux-screen gauge raw reads (btl4gau2.cpp:868/952 at subsystem+0x2b8/+0x278)
and the Sensor RadarPercent path now read the CORRECT byte offsets (garbage under the short layout).
Verified: combat DESTROYED-in-8, 28 shots, 0 crashes, heat heatEnergy=1.34e7, every static_assert
lock passes, heapcheck-clean through construction (the phase the isolated PoweredSubsystem retype
had overflowed). LESSON: a factory-bridge runtime Check(sizeof<=alloc) does NOT fail the build --
only a static_assert sizeof lock catches alloc overflow at compile time.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
1356870e56 |
P7: byte-exact re-base of the CORE heat leaf (HeatSink/Condenser/Reservoir/Generator/Myomers)
The reconstruction modeled the binary's shared alarm/connection types with undersized
stand-ins, sliding every field above them low (the 72-byte auxScreenNumber gap). Fix the
foundational heat-leaf classes byte-exact + static_assert-lock them, from the ctor decomp:
Shared types corrected:
* SubsystemConnection 4 -> 0xC (binary link node FUN_004af9cf; FUN_00417ab4 derefs +8)
* GaugeAlarm54 = 0x54 (real AlarmIndicator FUN_0041b9ec; STATUS level at +0x14,
so subsystem+0x184 == heatAlarm+0x14 == GetLevel())
WatcherGaugeAlarm now typedefs GaugeAlarm54 (Watcher branch locks stay valid).
Byte-exact + locked (ctor-verified):
* HeatSink heatEnergy@0x158 linkedSinks@0x164 heatAlarm@0x170 resource@0x1C4
pendingHeat@0x1C8, sizeof 0x1D0 (@004adda0)
* Condenser valveState@0x1D0 condenserAlarm@0x1DC (@004ae568)
* Reservoir reservoirAlarm@0x1D0 ... squirtEfficiency@0x22C, sizeof 0x230 (@4aef78)
* Generator stateAlarm@0x1FC, sizeof 0x250 (@004b225c)
* Myomers phantom moverConnection tail removed (fits 0x358)
Three systemic bug classes fixed (added to the checklist in CLAUDE.md / HARD_PROBLEMS.md):
* alias field - a subclass member re-declaring an inherited slot the ctor reuses
(Condenser refrigerationOutput==massScale@0x160; Reservoir
coolantCapacity==thermalCapacity@0x128) -> use the inherited name
* alarm-interior - a value read at alarm+0x14 modeled as a separate member
(HeatSink heatState@0x184, Reservoir injectActive@0x1e4)
-> route to alarm.GetLevel()
* phantom field - a member past the object end (Generator shortFlag@0x25C is really
*(owner+0x190)+0x25c the msg-manager, @004b0efc; Myomers
moverConnection@0x110 a write-only base slot) -> remove it
Heat conduction now reads the REAL heatEnergy=1.34e7 (not garbage); combat DESTROYED-in-8,
0 crashes, heapcheck-clean through construction.
REMAINING (measured; a distinct larger task): making PoweredSubsystem byte-exact grows it
+0x98 and cascades into MechWeapon/Emitter/PPC/Sensor/Myomers -- all model the 0x54
AlarmIndicator with 4-byte ReconAlarm / 8-byte HeatAlarm stand-ins and are short +
phantom-tailed; retyping without byte-exacting them overflows the Emitter alloc (heap
corruption). PoweredSubsystem kept on HeatAlarm(8) stand-ins (marked) pending a
subsystem-tree ALARM UNIFICATION. See docs/HARD_PROBLEMS.md P7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9ea4bdaf50 |
gauges: publish HeatSink attribute table (CoolantMass/CoolantCapacity/CurrentTemperature)
The cockpit gauge config binds Subsystem/Attribute names (e.g. HeatSink/CurrentTemperature) resolved by ParseAttribute -> FindSubsystem -> GetAttributePointer -> the class's activeAttributeIndex. HeatSink wired GetAttributeIndex() into DefaultData but published an EMPTY table (only the inherited SimulationState id 1), so every HeatSink/* numeric read NULL -> guarded to 0. Add HeatSink::AttributePointers[] + GetAttributeIndex() publishing CoolantMass (coolantLevel @0x12C), CoolantCapacity (thermalCapacity @0x128), and CurrentTemperature (inherited currentTemperature @0x114), chained to the parent index so SimulationState is preserved and the built index stays dense (ids run contiguously from HeatableSubsystem::NextAttributeID -- a gap would make AttributeIndexSet::Find strcmp a garbage slot). Condenser and Reservoir derive from HeatSink with no override, so they inherit this table for free. Verified live (BT_DEV_GAUGES): the Heat surface heatsink-temperature readout (numeric HeatSink/CurrentTemperature, a base engine primitive that was already drawing NULL) now shows 77 (= startTemp) instead of 0. Combat un-regressed (TARGET DESTROYED after 8 hits), 0 crashes. The coolant attributes are ready for the vertBar widget (next increment). 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>
|