9df7dff78767ffa62d8b3559afb36d8ef529f070
22
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6f5a264835 |
Gitea #47 COMPLETE: the ENG-button attention FLASH is live (jam / bay fire) -- MechTech status scan + BTL4GaugeAlarmManager + lamp chain, all from the binary
The pod behaviour Cyd described -- "on a jam or bay fire the display eng button
for the system flashes" -- is authored data + a five-stage chain, now running:
MechTech::TechnicalAssistance (@004ad33c, per frame)
edge-scans every monitored subsystem's GetStatusFlags() 7-bit condition
mask (TechStatusType: Destroyed 0, Damaged 1, CoolantLeaking 2,
Overheating 3, AmmoBurning 4, Jammed 5, BadPower 6)
-> Start/StopEntityAlarmMessage (@00436688 id 7 size 0x20 / @004366b8 id 8
size 0x1C; broadcast @004364e4; port shape: direct
Start/StopEntityAlarmImplementation calls on the gauge renderer)
-> GaugeAlarmManager::Activate(alarmModel) -- alarmModel = MechTech+0x100 =
the 'mechalrm' ModelList (id 83) -> SearchList(83, type 31) -> the baked
GaugeAlarmStream (id 331, 11 items {condition, lampCode}), decoded:
Destroyed -> gotoEngineering + engCooling + engBusMode
CoolantLeaking -> gotoEngineering + engCooling
AmmoBurning -> gotoEngineering + engEject
Jammed -> gotoEngineering + engEject
BadPower -> gotoEngineering + engBusMode
-> BTL4GaugeAlarmManager::ReadGaugeAlarmStreamItem -- THE REAL BODY, from
@004cc2fc + helpers @004cc108/148/1a0/264/27c + tables @0051cf1c..0x51d084
(gotoEngineering 0x80 = the subsystem's QUAD-SELECT bezel button via
lamp[aux]/mode[aux] tables; 0x81+ descend the eng-page bank; Condenser /
Generator specials on CoolantLeaking; <0x80 = the heat-bank fixed map).
btl4galm.cpp's old bodies were admitted fabrications and its provenance
note ("no override body exists in the image") was wrong -- corrected.
-> LampManager::FindLamp (@00444c80) -> Lamp::SetAlertState (@00444e64, a
COUNTER so stacked alarms hold the flash) -> L4Lamp::NotifyOfStateChange
emits RIO flashFast states 0x37/0x13 (== T0 L4LAMP.cpp:234-239) -> the
pod's physical lamps AND the glass panels (PadRIO IS rioPointer there).
FOUR load-bearing defects found and fixed en route -- each independently fatal
to the feature:
1. HeatableSubsystem's Derivation chained Subsystem directly, SKIPPING
MechSubsystem (written before the WAVE-1 re-basing) -- so EVERY subsystem
on both family branches failed IsDerivedFrom(MechSubsystem), which is
exactly MechTech's monitor filter: the status scan watched NOTHING.
2. MechSubsystem::GetStatusFlags was non-virtual (binary: vtable slot 12) --
the scan's MechSubsystem* call bound statically to the base tier and the
weapon bits could never surface.
3. ProjectileWeapon::GetStatusFlags sat in a Ghidra export gap -- raw-disasm
@004bbf88: base | 0x20 (weaponAlarm==5 Jammed) | 0x10 (linked bin
cookOffArmed@0x18C AmmoBurning). The port had "defer to base".
4. Mech::Reset's respawn sweep blanket-cast every roster entry to
MechSubsystem and called RespawnRepair -- wrong for the Subsystem-level
entries (MechTech 0xBDC, SubsystemMessageManager 0xBD3). On MechTech the
recon damageZone slot lands on its subsystemMonitors chain head: NULL
while the scan was broken (fix #1's bug MASKED this one), but the moment
the monitors populated, RespawnRepair virtual-called through a
SubsystemMonitor as if it were a DamageZone -> load-time crash in
StateIndicator::SetState (caught with cdb; call [edx+14h] on code bytes).
The sweep now filters IsDerivedFrom(MechSubsystem) before the cast.
Also: GUID identities pinned -- 0x50f4bc = PoweredSubsystem::ClassDerivations
(via @004b1208 = its TestInstance; btl4gau2's two "Generator" comments were
wrong, swept), 0x50fb60 = Generator's. A shadow-field instance documented for
the deferred de-shadow: MechSubsystem::damageZone re-declares the PUBLIC engine
Subsystem::damageZone (SUBSYSTM.h:159) -- mechtech's naive `sub->damageZone`
read the never-written engine member (0 monitors again); now reads the recon
member via GetDamageZoneProxy(). Logged in open-questions.
Wiring: BTL4GaugeRenderer now constructs + assigns the BTL4GaugeAlarmManager
(the base ctor NULLs it and Activate Check()s it); MechTech's Report*/
StatusMessageSink stubs are real; alarmModel confirmed baked (= 83) at runtime.
VERIFIED LIVE (scratchpad/lampflash.py, BT_LAMP_LOG chain trace):
[techstat] MechTech id 32 monitors 29 subsystems, alarmModel 83
[techstat] LRM15_1 condition 4 SET (alarmModel 83) <- bay fire armed
[galarm] condition 4 code 0x80 -> lamp 0xd mode 0x1 FLASH
[lamp] 0xd <- 0x37 (FLASHING) <- the select button
[galarm] condition 4 code 0x85 -> lamp 0xb mode 0x4 FLASH <- engEject
[techstat] LRM15_1 condition 4 CLEARED <- detonation clears
[techstat] AmmoBinLRM15_1 condition 0 SET <- bin Destroyed
Regressions: baytest PASS (bay fire kills), baypurge PASS (purge extinguishes),
sim3 3-pod brawl PASS with ZERO crashes (6 kills, organic heat-route bay fires,
respawns clean through the new sweep filter).
Env: BT_LAMP_LOG ([techstat]/[galarm]/[lamp]). KB: gauges-hud (the flash
section), decomp-reference (the GaugeAlarm closure + tables + GUIDs),
open-questions (built-note + the de-shadow deferred item), btl4gau2 comment
sweep. checkctx CLEAN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
62513b2f8a |
Gitea #53: generators can be switched off -- ToggleGeneratorOnOff reconstructed from @004b1ed0
TWO missing pieces, both now in place.
1. Generator had NO handler set of its own, so every message aimed at it hit a
default-constructed blackhole. Proved empirically with the new
unhandled-message trace by pressing button 0x1A:
[msg] UNHANDLED: Generator has no handler for message id 4 -- silently dropped
Added Generator::GetMessageHandlers() chained to **HeatSink**, deliberately NOT
PoweredSubsystem: id 4 there is SelectGeneratorA (the weapon-side generator
SELECTION that already works), so chaining to it would have made these four
buttons invoke the WRONG function instead of doing nothing. HeatSink owns id 3
(ToggleCooling), leaving id 4 free.
2. ToggleGeneratorOnOff had no body at all (0 references in game/). Transcribed
instruction-for-instruction from @004b1ed0:
- call @004ac9c8 -> BTPlayerRoleLocksAdvanced: a NOVICE pilot cannot use it
- [msg+0xc] <= 0 -> press only, ignore the release
- ON : startTimer = 0; if heatAlarm(@0x184) < 1 also stateAlarm -> 0
(Starting, so it spins up -- a heat-faulted generator keeps its fault
level); generatorOn = 1, coolantAvailable = 1, coolantFlowScale = 1.0
- OFF : stateAlarm -> 1 (GeneratorIdle); generatorOn = 0,
coolantAvailable = 0, coolantFlowScale = 0 (its coolant share is
released back to the loop)
So taking a generator off line idles it, drops its coolant draw and stops it
feeding its taps -- the authentic heat-management trade from the manual.
VERIFIED LIVE (BT_BTNTEST=0x1A,400,900 through the real click seam):
[btntest] PRESS 0x1a at poll 400
[gensel] GeneratorA generator OFF LINE (stateAlarm 1, coolantFlowScale 0)
and the UNHANDLED warning for Generator is gone.
Answers Cyd's report from two playtests ('still cannot turn of gennies'). Note
this is the ON/OFF control -- generator SELECTION (which generator a subsystem
draws from) is a different message and already worked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg
|
||
|
|
08977ff128 |
Gitea #55: respawn now restores COOLANT / heat / generator state (+ a Generator reset re-transcribed from the binary)
THE GAP: Mech::Reset sweeps every subsystem with the virtual DeathReset (mech4.cpp:1789), but only 7 weapon/ammo classes overrode it -- the entire heat/coolant/power family fell through to the empty Subsystem::DeathReset base (SUBSYSTM.h:161), so the respawn reset was a SILENT NO-OP for them. All the ResetToInitialState bodies already existed; nothing ever called them. Hence the field report: 'respawned with drained coolant'. Added DeathReset forwarders (ResetToInitialState is not virtual, so each class with its own body needs one): HeatableSubsystem, HeatSink, Condenser, PoweredSubsystem, Generator. HeatSink's also serves Reservoir -- the coolant tank -- which has no body of its own; that is the one that refills coolant. TWO MIS-TRANSCRIPTIONS FIXED, both verified against the binary with capstone: 1. HeatSink::ResetToInitialState chained HeatableSubsystem::ResetToInitialState with the comment 'FUN_004ac22c'. But @004ac22c is Subsystem's terminus -- disassembled: it reads the damage zone at this+0xe0, clears zone+0x158, and Set_Alarm_Levels zone+0x10 and this+0x2c to 0. And the binary's HeatSink @004ad760 calls only @004ad884 (ClearHeatFilter), @004ad7f0 (UpdateHeatLoad) and @004ac22c -- never HeatableSubsystem. Worse, HeatableSubsystem's body sets currentTemperature = 300.0f, CLOBBERING the startingTemperature that HeatSink assigned four lines earlier. Dropped the call; the terminus work is already done for every subsystem by MechSubsystem::RespawnRepair (mech4.cpp:1790). 2. Generator::ResetToInitialState chained HeatableSubsystem and set outputVoltage = 0 -- which matches GNRATOR.TCP, but BT's BINARY DIVERGES from the TCP source, and the binary is what shipped. Disassembled @004b215c instruction by instruction: it chains HeatSink (so the coolant refill DOES run for a generator), then generatorOn=1, startTimer=0, stateAlarm 0 then 2, **outputVoltage = ratedVoltage**, coolantAvailable=1, coolantFlowScale=1.0, startTimer=startTime. Every offset matches our declared members exactly. The old version brought generators back from a respawn at ZERO output voltage, so energy weapons could never charge -- no recharge ring, no ready dot, nothing damaged. That is a strong candidate for #54 (David's three dead lasers). LIVE VERIFICATION (sim3.py, 3 mechs + force damage): 4 death/respawn cycles, and every heat sink logged coolant == capacity on every respawn, with temp restored to startingTemperature (77) rather than the clobbered 300. Left as a silent regression guard that only speaks if a respawn leaves coolant short. KNOWN REMAINING [T3, noted in code]: Condenser's body chains HeatableSubsystem rather than HeatSink, so a coolant loop's VALVE DETENT may still persist across a respawn -- the binary's Condenser reset is not yet decompiled. Do not claim valves are fixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg |
||
|
|
f2eaf3d0ac |
Gauges: convert generator-voltage bar to named-member bridge
The eng-page generator-voltage bar (evolt.pcc, ScalarBarGauge @004c721c) and the MyomerCluster seek-voltage graph were fed by GeneratorVoltageConnection, which walked the same dead path as the generator-letter lamp: ResolveLink(AttributePointerOf(subsystem,"InputVoltage")) -> read resolved +0x1DC == Generator::outputVoltage. The BT_DEV_GAUGES audio attribute rows shifted the chained attribute ids so AttributePointerOf no longer lands on voltageSource@0x1D0 -> the bar always read 0. New BTGeneratorVoltage(subsystem) bridge (powersub.cpp) reads the NAMED member via PoweredSubsystem::ResolveVoltageSource()->Generator::MeasuredVoltage() (outputVoltage@0x1DC), bypassing the attribute table -- same pattern as BTPowerSourceFrame/BTCoolingLoopFrame. Both GeneratorVoltageConnection callers (the ScalarBarGauge param + the MyomerCluster seekValue) now pass subsystem_in. [voltfeed]-verified: Myomers -> 10000V from GeneratorD (was 0 on the dead path). Both pod + glass builds clean. Eng-page render awaiting live playtest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
cbc5ff9532 |
Wire ToggleCooling (msg 3): restore the coolant on/off button + handler chain
Coolant priority (Lynx: weapon MFD -> Display -> Coolant) was unreachable: the ToggleCooling handler (@004ad6f8, HeatableSubsystem table @0x50E41C id 3) was undefined AND the handler chain skipped it -- PoweredSubsystem::GetMessageHandlers chained straight to the Receiver root, bypassing HeatSink/HeatableSubsystem, so a weapon's dispatch never saw id 3. Reconstructed ToggleCooling from the disassembly (tools/disas2.py 0x4ad6f8): a per-subsystem coolant on/off TOGGLE -- novice-locked (owner->BTPlayer-> roleClassIndex+0x274==0, the same guard as MoveValve), press-only, then flip coolantAvailable(+0x134) 0<->1 and coolantFlowScale(+0x15C) 0.0f<->1.0f. Cutting a system's cooling frees the shared loop for the rest -- the emergent "coolant priority" (the mechanism is a toggle, not a multi-level cycle). - heat.hpp / heatfamily_reslice.cpp: HeatSink::ToggleCoolingMessageHandler (id 3) + HeatSink::GetMessageHandlers. Registered at HeatSink (the abstract HeatableSubsystem never instantiates; every concrete heatable subsystem is a HeatSink, where the coolant fields live) -- identical coverage to the binary's base-table registration, no downcast. - powersub.cpp + Condenser/Reservoir: chain their handler sets through HeatSink::GetMessageHandlers so id 3 reaches every heatable subsystem. - mech4.cpp: BT_COOLTOGGLE_TEST scripted inject (dispatch id 3 to the first weapon) for verification. Verified (BT_COOLTOGGLE_TEST + BT_COOL_LOG, expert egg): "[cool] PPC_1 ToggleCooling reached" then coolant OFF(flow 0.0) <-> ON(flow 1.0) each press -- chain routes, handler toggles, novice guard honored. Both build/ + build-glass/ compile clean; existing ids 4-8 unaffected (found before id 3). NEXT: the #2 MFD button routing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d7158f1f82 |
Experience levels WIRED: the egg field drives the simulation tier (Gitea #2)
BTPlayer's master ctor now seeds the flag block from btMission->ExperienceLevel() + AdvancedDamageOn() (accessors from the SURVIVED 1995 BTMSSN.HPP [T0]) instead of the NULL-stubbed scenario role -- the egg experience knob works for the first time. Both switch slot errors fixed vs the binary @4c0bc8; phantom btMission duplicate member removed (+0x1f8 IS Player::playerMission). SEVEN gate stubs unified onto the real flags (two found empirically): HeatModelActive, OwnerAdvancedDamage, LiveFireEnabled, ControlsAllowLights, powersub @4b0efc, emitter's file-local FUN_004ad7d4 + FUN_004ac9c8 stubs. FUN_004ac9c8 family swept (incl. a live raw-offset databinding trap in MechSubsystem::IsDamaged). Flag block renamed to its true semantics (simLive/heatModelOn/...), scoring-era names kept as comments. Includes the #5 [aud-tail] diag in emitter.cpp. ALL 19 shipped eggs say experience=expert -> default behavior UNCHANGED (per-tier verified: novice/standard = no heat/jams, authentic; veteran/ expert = today's exact behavior). Real delta: egg advancedDamage=1 now reaches player+0x264/0x268 (was hardwired 0). Follow-up flagged: the glass FE defaults empty experience to veteran (one tier below shipped). Awaiting human verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
dcdd7dd0a7 |
Audio: register Generator.GeneratorOn -> generatorOn (@0x1D4) (task #50)
The last cleanly-backed subsystem audio attr: GeneratorOn (AudioLogicalTrigger) now resolves to the real generatorOn member instead of the inert pad. Static for now (init 1, no shutdown writer drives it to 0 yet), but bound correctly. Remaining inert attrs (ReportLeak/ConfigureActivePress/MotionState/ SpeedOfTorsoHorizontal/TargetRangeExponent) have no modeled backing member in their size-locked subsystem layouts -- they read the inert pad (silent, no crash) until those members are reconstructed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5ba541ed56 |
Audio: subsystem STATE sounds -- GaugeAlarm54 IS a StateIndicator; register Generator/Condenser/Reservoir state (task #50)
The audio subsystem binds AudioStateWatchers (class 28) to per-subsystem state attrs (GeneratorState/CondenserState/ReservoirState) BY NAME. Recovered the watcher TYPE per attribute via a MakeObjectImplementation ClassID trace. Root cause of the subsystem-state crash: the binary's 0x54-byte subsystem alarm (FUN_0041b9ec, reconstructed as GaugeAlarm54) IS a StateIndicator -- its "three sub-indicators" @0x18/0x2c/0x40 are the audio/video/gauge watcher SChains, and level@0x14 is currentState. The reconstruction had modeled that tail as an opaque `_tail`, so the sockets were never constructed -> AddAudioWatcher AV'd on 0xCDCDCDCD. - GaugeAlarm54: give it the three REAL, constructed SChainOf<Component*> sockets + AddAudioWatcher/Video/Gauge; SetLevel now fires the watchers on a level CHANGE (empty sockets = no-op, so the ~all other alarms are unaffected). levelB (weapon LevelCountB "reset source") is left untouched -- weapons unchanged. sizeof stays 0x54 (static_assert holds -> SChainOf<Component*> is 0x14, layout exact). - Register the state attrs -> the subsystem's own alarm (own AttributePointers[] chained to the parent): Generator.GeneratorState->stateAlarm, Condenser. CondenserState->condenserAlarm, Reservoir.ReservoirState->reservoirAlarm. Those alarms are already SetLevel'd by the sim, so the state audio fires on transition. - AudioStateWatcher guard now validates the +0x18 SOCKET (what AddAudioWatcher touches), not the +0 vtable -- GaugeAlarm54 is non-polymorphic at +0 (raw header) but has a real socket at +0x18. audiostate skips 85 -> 10 (only AmmoBin AmmoState/SimulationState left). The Logical/Scalar/Enum subsystem attrs (ReportLeak/GeneratorOn/ConfigureActivePress/ MotionState/SpeedOfTorsoHorizontal/TargetRangeExponent) stay inert (read 0, silent, non-crashing) -- they need backing members in size-locked layouts (a polish wave). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
dd27238ceb |
Gauges: the CLOSEOUT wave -- radar pips, Myomers dedup, attribute parity, ledger sweep (tasks #14-#17)
TASK #17 -- AUTHENTIC RADAR SYMBOLOGY (the cross-blip stand-in retired). The recon proved the 'missing pip raster set' never existed as BT game code: pips are the ENGINE's L4GaugeImage vector-stroke system (T0 source in tree, L4GAUIMA.cpp == FUN_0046f0c0 line-for-line), the 'pip table' is L4Warehouse:: gaugeImageBin keyed by Entity::resourceID, and BTL4.RES ships 110 type-0x12 shapes (every mech/vehicle/building/tree). The six btl4rdr stubs are wired to the real facilities: contacts draw their authentic model silhouettes with LOD selection, the target gets the binary's 4px-inflated box highlight, and player-name labels resolve via Mission::GetSmallNameBitmap (the prebuilt 64x16 egg rasters keyed by the player's bitmapindex). DECODED: the invented 'VideoObject' was Entity::owningPlayer all along (+0x190; nameID = playerBitmapIndex@0x1E0, target = BTPlayer::objectiveMech@0x284 -- new bridge BTPlayerObjectiveMechOf); the 'LabelledEntity' class is Landmark (cultural.h; label path dormant -- no landmark content ships, no runtime landmarkID writer). The L4GREND BT_DEV_GAUGES warehouse guard is removed (its AV had a different culprit, below); resource type 18 corrected to GaugeImageStream in decomp-reference (was mislabeled 'ModelList'). TASK #14 -- the Myomers ODR duplicate ELIMINATED: the powersub.cpp/hpp 'Myomers' (classID 0xBC3 -- actually Sensor) is retired whole; it duplicated ?DefaultData@Myomers@@ against the real class (dumpbin-verified) and /FORCE picked the winner by link order. The real Myomers (0xBC6) now chains PoweredSubsystem's handler set (ids 4-8) and publishes its SEVEN binary attributes (@00511588: SpeedEffect/Current/Recommended/Min/MaxSeekVoltage- Index/SeekVoltage/OutputVoltage) -- the old empty unchained index starved the Myomer engineering panel of every resolve. TASK #16 -- attribute parity: MechWeapon publishes the FULL binary table @0x511890 (11 entries; ids renumbered to binary truth -- the port aliases had squatted the binary's DistanceToTarget/TargetWithinRange ids; the streamed TriggerState 0x13 binding unchanged). Binary names resolved two TODO members: pipState -> estimatedReadyTime (attr 0x1A), and the EXT-model flag is the binary's RearFiring (0x1B). ThermalSight LightState published. HUD (offset conflicts) + missile-side tables (id encoding suspect) documented for a re-dump instead of publishing blind. THE CRASH THIS EXPOSED [T2, cdb-verified]: gotcha #11's dense-table gap is a LATENT AV, not a guaranteed one -- the old table's 0x0D..0x12 gap (task #5) survived on heap luck; the renumber reshuffled allocations and Find() AV'd on a garbage entryName in WeaponCluster's PercentDone resolve. Fixed with five named PAD entries (the mech.cpp attrPad idiom) + a static_assert locking the pad base to PoweredSubsystem::NextAttributeID. Gotcha #11 amended with the proof. TASK #15 -- stale-ledger sweep: GAUGE_COMPOSITE ('composite not yet built', Reservoir shadow, PlayerStatus/vehicleSubSystems 'remaining', valve-dormant- until-0xBD3, sensor guard, the superseded 'Heat MFD near-static' reframe -- all banner-corrected), gauges-hud frontmatter, L4VB16 + powersub comments. Verified live: 50/50 config attribute bindings resolve, 0 NULLs, 0 parse skips, mech spawns and simulates 31/31 subsystems, no cross-blip fallbacks, the pip cache fills through entity registration without the old guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d7b900d108 |
Cockpit: GENERATOR POWER ROUTING -- assign weapons to generators (task #12)
The pilot's second heat-management tool: the PoweredSubsystem message table
@0x50F4EC (ids 4-8) registered and implemented -- SelectGeneratorA-D
(@004b099c..@004b0a74: FindGeneratorByNumber roster walk on generatorNumber
@0x1E0 -> AttachToVoltageSource re-tap with tap accounting -> modeAlarm
Connected) and ToggleGeneratorMode (@004b0abc: Manual -> Auto -> detach+Manual
cycle). Weapons inherit via the MechWeapon handler chain. Desktop: F5-F8
assign the selected weapon (BT_CONFIG_SLOT) to Generator A-D, F9 toggles
reconnect mode; BT_GENSEL_TEST scripts a headless verify.
Verified live end-to-end: dispatch -> handler -> re-tap ("PPC_1 ->
GeneratorD (tapped)") -> the charging I^2R physically moved (GenD cold ~90K
baseline -> ~1570K carrying the PPC; GenA relieved), stable over a sustained
autofire soak with thermal-breaker trips.
FOUR defects found and fixed on the way [all T1/T2]:
- THE e17 HEAT EXPLOSION: Generator::SourceLevel misread *(this[0x38]+0x158)
as linkedSinks->heatEnergy; it is the engine-base DamageZone @0xE0 ->
damageLevel [0..1] (the same named-member pattern as the bank radiator's
zone read). A breaker-restarting generator emitted (1 - 4e8) x 10000
volts; squared through the customers' I^2R feed, one restart blew the
whole thermal network to e17. Authentic: a damaged generator yields
proportionally less voltage.
- FUN_004ac9c8 is NOT "IsDamaged": raw body = owner -> mech+0x190 player ->
roleClassIndex(+0x274) == 0 -- the ROOKIE-role lockout for advanced
cockpit systems. New bridge BTPlayerRoleLocksAdvanced (NULL player =
unlocked [T3]; bring-up role 2 = unlocked). The old stand-in gated the
handlers off permanently (healthy subsystems have simulationState==1).
- MESSAGE_ENTRY tables must be FUNCTION-LOCAL statics inside the accessor:
as namespace-scope arrays they are read by other TUs' static-init chains
before their own initializers run -- Build copies zeros and every id in
the table is silently dropped (ids 9/10 only worked by TU-order luck;
both tables relocated; gotcha recorded, reconstruction-gotchas #9).
- The AutoConnect hunt scanned GetSegment() -- the raw @004b0bd0 walks the
subsystem ROSTER (+0x124/+0x128); EntitySegments were being cast to
Subsystems (the classic +0x128 gotcha).
Also: the dense handler table's GAP slots (skipped ids) are uninitialized
heap -- the name-based Find strcmp-walks them and AVs; diagnostic probes use
the id-based Find only (the 1995 binary's own tables carry the same holes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
092408041c |
Cockpit: the CONFIG-MODE weapon-regrouping session + the live-play fire fixes (tasks #6+#11)
TASK #6 -- the pod's in-cockpit weapon regrouping, fully reconstructed and live-verified (hold-configure -> tap fire button -> toggle membership -> release-commit): - MechWeapon handlers id 9 ConfigureMappables @004b9550 / id 10 ChooseButton @004b95b8 (binary table @0x511860; the old "Myomers fns" mislabel swept) + the GetMessageHandlers() accessor chain through Emitter/ProjectileWeapon/ MissileLauncher/GAUSS/PPC (empty per-class sets swallowed dispatch). - Mapper vtable truth: +0x38 EnterConfiguration / +0x3C ExitConfiguration / +0x40,+0x44 AddOrErase evt/dir. No "secondary vtable @0050f498", no "CreateTemporaryEventMappings" virtual (RP-name drift; swept incl CLASSMAP). L4 Enter/Exit rebuilt complete: StartMappableButtonsConfigure (the NonMapping 0x10000 <-> Mapping 0x8000 mode flip + the gauge's active-weapon latch) + the held-button re-arm + the 4 fire-button temp maps; only the RIO mapper implements the toggle (Thrustmaster no-ops = can't regroup). - MechSubsystem +0xE8/+0xEC corrected to controlDestination/controlMessageID (ex hostEntity/subsystemId2 mislabel); MechWeapon ctor defaults the destination to &fireImpulse (@004b99a8). - ConfigMapGauge state loop reconstructed (PE-recovered DAT_00518eb4 table; buttonGroup GetMapState sampler -- the "needs ModeManager" guard rationale was wrong). Finding [T1]: the shipped binary NEVER enables this gauge (no SetColor caller) -- authentically dormant; BT_CONFIGMAP=1 is the dev enable. - Dev harness: HOLD 'G' opens the session (BT_CONFIG_SLOT picks the weapon); BT_CONFIG_TEST scripts a headless verify. @004afbc4 corrected to its real Fail-trap body (the guessed AddOrErase(NULL) body would corrupt groups). - Bonus [T1]: the binary MechWeapon ATTRIBUTE table has ELEVEN entries (PercentDone..WeaponState) -- "TriggerState is the only one" was wrong. TASK #11 -- the user-reported live-play regressions (both real bugs): - PHANTOM FIRE: the mech4 bring-up shot block painted an explosion at the victim on its OWN 0.3s cadence whenever fire was held -- desynced once the authentic recharges landed. Retired (the real impact visual flows from each discharge via the messmgr SubmitExplosion). - WEAPON BRICKING (the "cuts out" bug): the Loading->Loaded snap window (+-0.01 around seekV) assumes the pod's LOCKED 60 fps; one port dt-spike jumps it, the byte-verified >1.0 clamp (_DAT_004ba830=0) zeroes readiness, and the weapon sticks in Loading at level ~10000 forever (observed live). Fixed with pod-frame (1/60s) sub-stepping of the binary's own Loading tick -- also fixes the I^2R integral over-heating generators on big steps. NEW GOTCHA CLASS recorded: reconstruction-gotchas #12 (frame-pacing trap). - Duty-cycle measured (max-rate autofire): generators equilibrate in the degradation band (recharge stretch ~3.5-4.4x), the FailureHeat breaker never trips solo, PPCs sustain ~11.8-damage full-charge fire. The slow kill pacing and recharge gaps are the AUTHENTIC heat economy; the pilot's counters (this regrouping UI, generator reassignment, coolant valves) are the reconstruction queue. 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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
dc6af8cef6 |
vehicleSubSystems: reconstruct the stubbed connection helpers from decomp
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> |
||
|
|
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> |
||
|
|
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>
|