From f3179ef0207c464bf6ede216592ee0367c65b754 Mon Sep 17 00:00:00 2001 From: arcattack Date: Sat, 25 Jul 2026 17:58:20 -0500 Subject: [PATCH] #47 follow-up: restore the DAMAGE TIER to the heat branch -- HeatableSubsystem::GetStatusFlags was a stub returning 0 Found while auditing today's vtable change before release. The binary's HeatSink::GetStatusFlags @004add30 OPENS with `call 0x4ac144` -- MechSubsystem::GetStatusFlags, the damage tier (damageLevel >= 1.0 -> bit 0 Destroyed; > 0 -> bit 1 Damaged). There is no zero-returning HeatableSubsystem override anywhere in the image. Our port routed that call through `HeatableSubsystem::GetStatusFlags() { return 0; }` -- a "neutral default" that silently dropped bits 0 and 1 for the ENTIRE heat branch (every weapon, sensor, emitter, PPC, generator, powered subsystem). Two live consequences: * Destroyed / Damaged could never reach MechTech's annunciator scan -- gutting half the shipped mechalrm table (Destroyed -> gotoEngineering + engCooling + engBusMode) hours after #47 shipped the flash; * PoweredSubsystem's AutoConnect gate (`GetStatusFlags() == 0`, powersub.cpp:358) read a DAMAGED subsystem as pristine. One-line fix: chain the real tier, reproducing @004add30 exactly. The call site's own comment already said `// FUN_004ac144` -- the implementation had simply never matched it. Also corrected a stale comment on MechSubsystem::GetStatusFlags that read the field as a STRUCTURE level ("1.0 = intact, 0 = dead") -- backwards. The engine member is damageLevel and ACCUMULATES to 1.0 = destroyed (mechdmg's crit cascade tests `level >= StructureMax` for exactly that), which is what makes bits 0/1 line up with TechStatusType Destroyed/Damaged. Behaviour was always right; the comment was a leftover of the same structureLevel misreading #46 retired. VERIFIED (3-pod combat sim, BT_LAMP_LOG): the annunciator is genuinely live -- Jammed x7, AmmoBurning x5, BadPower x20, Overheating x207 (Overheating correctly maps to NO lamp: the shipped table has no entry for it). The lamp resolution is per-subsystem as designed -- different weapons flash their own panel buttons (lamps 0xf/0x5/0xb/0x3 under distinct mode masks), 32 flash writes across a whole battle, no thrash. Zero crashes, zero plane/OOB tripwire hits across all three pods. FILED, deliberately NOT fixed tonight (open-questions.md): vtable slot +0x40 is misattributed. The binary calls it `(this, 0)` in AutoConnect's outer gate and `(this, candidateGenerator)` in the roster walk -- a one-arg voltage query (HasVoltage(source)), not GetStatusFlags(). Modelling both as the no-arg GetStatusFlags makes the outer gate demand "no flags" and the inner "some flag" with nothing between them, so AutoConnect's loop body can never execute. Pre-existing before and after this change; it is a live power-bus behaviour change and deserves its own playtest cycle, not a release-eve patch. Co-Authored-By: Claude Fable 5 --- context/open-questions.md | 13 +++++++++++++ game/reconstructed/heat.cpp | 12 +++++++++++- game/reconstructed/mechsub.cpp | 11 +++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/context/open-questions.md b/context/open-questions.md index c0055ac..970949b 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -254,6 +254,19 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR (the databinding trap). DORMANT in-game (the review context no longer survives the viewpoint swap -- the #48 teardown fix) but LIVE the moment the mission-review screen actually runs at round end. Bridge all three through btplayer.cpp accessors before enabling the review screen. +- **`PoweredSubsystem` AutoConnect is DEAD, and vtable slot +0x40 is misattributed + (2026-07-25, found while auditing #47's GetStatusFlags change) [T1 decomp].** The binary + (@004b0bd0 tail) calls slot +0x40 with TWO shapes: `(this, 0)` in the outer gate (require + `== 0`) and `(this, candidateGenerator)` inside the roster walk (require `!= 0`). That is a + ONE-ARG voltage query -- "am I unpowered?" / "would THIS source power me?" -- i.e. the + `HasVoltage(source)` family, **not** `GetStatusFlags()`. powersub.cpp:358/372 model both as + the no-arg `GetStatusFlags()`, so the outer gate demands "no flags at all" while the inner + demands "some flag", and nothing between them can change the value -> **the loop body can + never execute and AutoConnect never attaches anything.** Pre-existing (both before and after + the #47 damage-tier fix); deliberately NOT fixed on release eve because it is a live + power-bus behaviour change that deserves its own playtest cycle. Fix = give slot +0x40 its + real signature (`Logical HasVoltage(Subsystem *source = 0)`), restore the two call shapes, + and verify a mode-Auto subsystem re-attaches after its generator drops. - **Factory capability-roster loops 2-4 are STILL DEAD (task #57 discovery).** mech.cpp's post-roster loops add to `heatableSubsystems`(0x51155c)/`weaponRoster`(0x511830)/ `damageableSubsystems`(0x50e4fc) through the local `SubProxy` stub whose `IsDerivedFrom` diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index 301953d..2e09b51 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -222,8 +222,18 @@ Logical // carry these; the heat/power families override them. Base implementations // are the neutral defaults the recovered overrides chain into. // +// (#47 follow-up, 2026-07-25) NOT a neutral default: the binary's +// HeatSink::GetStatusFlags @004add30 OPENS with `call 0x4ac144` -- +// MechSubsystem::GetStatusFlags, the DAMAGE TIER (damageLevel >= 1.0 -> bit 0 +// Destroyed; > 0 -> bit 1 Damaged). There is no zero-returning +// HeatableSubsystem override in the image; this stub silently dropped bits 0/1 +// for the ENTIRE heat branch (every weapon/sensor/generator/emitter), which +// (a) made Destroyed/Damaged unable to reach MechTech's annunciator scan -- +// gutting half the #47 mechalrm table -- and (b) let PoweredSubsystem's +// AutoConnect gate (`GetStatusFlags() == 0`, powersub.cpp:358) treat a DAMAGED +// subsystem as pristine. Chaining the real tier reproduces @004add30 exactly. LWord - HeatableSubsystem::GetStatusFlags() { return 0; } + HeatableSubsystem::GetStatusFlags() { return MechSubsystem::GetStatusFlags(); } Logical HeatableSubsystem::HandleMessage(int) { return False; } void diff --git a/game/reconstructed/mechsub.cpp b/game/reconstructed/mechsub.cpp index 5b3e3e5..846228d 100644 --- a/game/reconstructed/mechsub.cpp +++ b/game/reconstructed/mechsub.cpp @@ -282,8 +282,15 @@ void MechSubsystem::SetSubsystemDamageLevel(Scalar level) // // -// @0x4ac144 -- damage tier from the DamageZone structure level (dz+0x158): -// >= 1.0 -> 1 (intact / nominal), > 0.0 -> 2 (damaged), else 0 (dead). +// @0x4ac144 -- the DAMAGE TIER, read from the zone's damageLevel (dz+0x158): +// >= 1.0 -> bit 0 (DESTROYED), > 0.0 -> bit 1 (DAMAGED), else 0 (pristine). +// These are TechStatusType bit indices 0/1 -- the low two conditions of the +// MechTech annunciator scan (#47). The old comment read the field as a +// STRUCTURE level ("1.0 = intact / 0 = dead") -- backwards: the engine member +// is damageLevel, which ACCUMULATES to 1.0 = destroyed (mechdmg.cpp's crit +// cascade tests `level >= StructureMax` for exactly that). Behaviour was +// always right; the comment was a leftover of the same structureLevel +// misreading that #46 retired. // LWord MechSubsystem::GetStatusFlags()