d78e77bf84ce5d8041d2f198cd7ce1cd146f600d
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
530bbeb959 |
Fix BT_SPAWN_AT terrain embed + add BT_COOL_LOG probe
BT_SPAWN_AT teleport set only X/Z, keeping the old (dropzone) Y -- so teleporting to a new spot embedded the mech in sloped terrain (user-reported). Lift the teleport +500 above the old elevation so the authentic per-frame ground SNAP (BoxTree FindBoundingBoxUnder, no gravity) settles it onto the surface next frame (the snap only lowers, so it must start above the surface). Diagnostic only. Also BT_COOL_LOG (heat.cpp): logs a heat subsystem's damageLevel/heatLoad/coolantDraw when it carries damage -- for the 'do damaged mechs leak coolant' investigation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4e63a7b6c3 |
Heat: THE AUTHENTIC ELECTRICAL MODEL -- weapons recharge from generators (task #10)
Task #10 set out to fix "scrambled linked-sink routing"; a [heat-link] attach log proved the routing was NEVER scrambled (every subsystem links its authored condenser exactly; the "pools in Condenser1" read was the diagnostic-sampler aliasing trap). The real defect: emitter.cpp's local FUN_00417ab4 stub returned NULL, so the whole electrical model was inert and the E7 force-charge recharged every emitter in ONE frame (~0.3s cycle, 1501 fires/90s, ~1.7e9 heat/s -- the "runaway"). Landed authentically [T1: disasm + byte-verified constants]: - Emitter ctor @004bb120: seekVoltage = authored fraction x generator ratedVoltage (10000); EC = energyTotal/(seekV^2 x 0.5); voltageScale@0x310 = (RechargeRate / -ln(1 - 1e-4 x seekV)) / EC -- charge reaches seekV[rec] in EXACTLY the authored RechargeRate (PPC 5s, ERL 4s, SRM 3s, ERM 2s) cold. Owner-flags ctor gate (the usual gotcha; the this-flags read never armed). - PoweredSubsystem::ChargeTimeScale (@004b0d50, was a =1.0 stub): voltageScale x (1 + thermalResistivity x srcTempRise) -- hot generators charge slower. ("voltageScale is never read back" was wrong; corrected + swept.) - TrackSeekVoltage @004ba838: charging I^2R -> the GENERATOR's pendingHeat (~3.5e8/full PPC charge) -- generators self-heat, conduct to their authored condensers, and throttle further charging. The feedback economy closes. - FailureHeat consumers found: this+0x184 == 2 gates BOTH weapon families (@004baa88 emitter: reset firing + hold charge 0; @004bbd36 ballistic: recoil=rechargeRate + alarm 7). Emitter::GetFaultState un-stubbed. - ProjectileWeaponSimulation @004bbd04 opens with call 0x4b0bd0 (disasm) -- launchers now run the powered/heat step (their firing heat previously accumulated in pendingHeat forever). - heat.cpp: per-instance BT_HEAT_LOG census (the old shared-static 1-Hz sampler aliased); [heat-link] attach log; the stolen-else Verify restored. Verified live (120s max-rate autofire): PPC ~470-500 (was 55,000), bank plateaus ~600 and sheds to ambient, generators 1100-1400, ERMLaser self-regulates at the authored 2000 failure threshold (shutdown-cool-resume). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4ed2bbc293 |
Heat: authentic 1e7-unit heat + the ambient radiator land (task #9)
- Emitters: heatPortion closed form = heatCostToFire x 1e7 x (charge/seekV)^2 (PPC 1.1e8 -> +632K on its own sink); the missing projectile/missile heat adds (raw -- resources author pre-scaled 1e7 units). - The bank's AMBIENT RADIATOR @4ae73c reconstructed (the system's ONLY heat exit; conductance x 0.1 x HeatSinkCount -- _DAT_004ae974 float80 = 0.1) + the link-attach guard corrected (skip = the 0xBBE bank, not Condenser; the inversion blocked condenser->bank links and closed the system). - Constant corrections (all byte-verified float80s): coolant epsilons 0.0025/0.003/1e-4 (was a single 1e-4 serving three sites); CoolantCapacityScale 0.05 (was 1.738). - Verified: heat flows + exits; max-rate autofire overheats weapons into the authentic heatLoad range-cutout (thermal spam unsustainable by design). - KNOWN REMAINING: linked-sink routing scrambled (heat pools in Condenser1; authored map says PPC->C4/C6) -- the heat-stream offset audit, filed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b91057aadc |
gauge-complete P4c: Condenser dedup + RecomputeCondenserValves -> ValveSetting gauge reads authentic 1/N
Two coupled fixes so the condenser valve gauge (ValveSetting -> coolantFlowScale @0x15C) reads its authentic value instead of garbage/zero. STEP 7 (gating dedup): heat.cpp carried STUB Condenser ctor/dtor/TestClass/ TestInstance/CreateStreamedSubsystem that ODR-duplicated the REAL bodies in heatfamily_reslice.cpp and WON under /FORCE (heat.obj links first) -> the real ctor (which sets valveState=1) was shadowed, leaving valveState=0xCDCDCDCD. #if 0'd the heat.cpp stubs so the reslice ctor (@4ae568, byte-verified: valveState=1, coolantFlowScale=0, massScale=refrigerationFactor, condenserNumber from name) is the sole definition. DefaultData/GetClassDerivations/ResetToInitialState (not duplicated) stay in heat.cpp. STEP 9 (the real writer): FinishConstruction() at the Mech ctor tail was a no-op template stub in place of FUN_0049f788 = RecomputeCondenserValves -> coolantFlowScale was never written (stayed 0). Reconstructed it (byte-verified vs part_012.c:9264): distribute coolant flow across the mech's condensers, coolantFlowScale_i = valveState_i / sum(valveState), with the condenserAlarm@0x1DC change pulse (2-if-flow<=old-else-1, then 0). Walks the populated subsystem roster filtering Condensers via IsDerivedFrom (behaviorally identical to the binary's @mech+0x7cc condenser chain, GUID 0x50e4fc). Wired as BTRecomputeCondenserValves(this) at the Mech ctor post-init pass (binary @9457). _DAT_0049f850 fallback confirmed 0.0f (PE read). Verified: [valve] the Blackhawk's 6 condensers each read flow=0.166667 (=1/6, total=6) for both player + spawned enemy, no every-mech crash; combat TARGET DESTROYED, un-regressed. Diagnostic BT_VALVE_LOG added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
16f5f545ce |
gauge-complete P4b: AggregateHeatSink 0xBBE -> HeatSink/AmbientTemperature bound (last config NULL cleared)
The 0xBBE heat-sink BANK was built as a plain HeatSink, so the numeric-R cockpit gauge binding HeatSink/AmbientTemperature (L4GAUGE.CFG:4552) had no publisher -> the LAST unresolved config attribute. Reconstruct AggregateHeatSink : HeatSink (ctor @4ae8d0, own GUID 0x50e590), byte-exact + static_assert-locked (heatSinkCount@0x1D0, ambientTemperature @0x1D4=300, helper@0x1D8 0xC link node, sizeof 0x1E4 == factory alloc @9993). Publish HeatSinkCount + AmbientTemperature via a dense-prefix attribute table chained to HeatSink::NextAttributeID (shared HeatSink table unchanged, so CoolantMass/CoolantCapacity keep resolving). Move CreateHeatSinkBankSubsystem into heatfamily_reslice.cpp (needs the class def) and build the real class at factory case 0xBBE; mech.cpp unchanged. DELIBERATE DEVIATION (documented in the class): keep the base HeatSinkSimulation the HeatSink ctor installs; do NOT reimplement the authentic Performance @4ae73c -- it derefs a raw self+0xE0 -> [+0x158] that does not map in our compiled layout (AV/NaN, and runs for EVERY mech), and ambientTemperature is a frozen constant so the gauge reads 300 either way. Authentic relaxation model deferred. Verified: [attr] HeatSink/AmbientTemperature OK; all 50 config attribute bindings resolve (0 NULL); no every-mech crash; combat TARGET DESTROYED, FIRED #41+, un-regressed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
12c4254aae |
gauge-complete P4a: heat-leaf data fixes (radar heat-degradation authentic)
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> |
||
|
|
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> |
||
|
|
64975ec22f |
gauge wave P1b: Searchlight/LightOn + fix coolant/refrigeration source misread
- Searchlight publishes "LightOn" -> lightState@0x1D8 (the button-5 searchlight lamp; the empty default-constructed index resolved NOTHING). The config binds the name "LightOn", so the enum id is renamed LightOnAttributeID and the table is chained to PowerWatcher's dense index. Verified: LightOn resolves OK. - FIX a genuine reconstruction bug the heatmodel decode caught (heat.cpp UpdateCoolant + heatfamily_reslice RefrigerationSimulation): both read linkedSinks.Resolve()->heatEnergy where the binary reads *(this[0x38]+0x158) = the subsystem's OWN DamageZone.damageLevel (the engine base zone @0xE0). Consequences of the misread: coolantDraw = master.heatEnergy(~1.3e7) * heatLoad would SLAM the live CoolantMass bars to empty whenever the link resolved; and RefrigerationSimulation clamped massScale permanently to 1.0 (minimum) instead of the authentic 3.0, plus null-deref'd a Condenser whose linkedSinks is skipped. Now both read this->Subsystem::damageZone->damageLevel (qualified past the MechSubsystem shadow, per mechweap.cpp:252): undamaged 0 -> coolantDraw 0 (no leak, coolant bars stay full = authentic pristine) / massScale = 3.0, rising only as the sink/condenser itself takes battle damage. After this only HeatSink/AmbientTemperature remains NULL (the #if 0'd aggregate HeatSink bank -> P3). Verified DBASE+dev gauges: no crash, combat un-regressed (TARGET DESTROYED). (Also noted: the Condenser dup-ctor links to the REAL heatfamily_reslice definition -- LNK4006 keeps the first/real one; the heat.cpp stub is ignored -- so no bug today; #if 0 cleanup deferred to the Condenser table.) 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> |
||
|
|
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>
|