diff --git a/context/multiplayer.md b/context/multiplayer.md index f54da1d..90c80e7 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -675,8 +675,17 @@ transition, HUD all landed since P6): console egg → mesh → RunningMission on SimulationState dial, independent of that sim path. Mech::Reset (@0049fb74) does the full subsystem-reset sweep (mech4.cpp:1616 loops every subsystem → `DeathReset(mode)`, restoring heat/power/ammo/charge) + heals all damage zones + ForceUpdate(0x1f). - (Per-subsystem `DeathReset` bodies are authentically trivial/empty for some classes — the shipped - build folds those overrides — so "fuller" reset behavior is per-subsystem, not a missing sweep.) + **⚠ CORRECTED (2026-07-23, issue #22): the loop-2 slot vtable+0x28 is `ResetToInitialState`, + NOT DeathReset** [T1: the DATA-section vtables 0050f9d8/00511d2c/00512078/0051242c all carry the + class RTIS at +0x28] — the old mislabel made the port sweep the EMPTY engine DeathReset virtual + (a no-op) = the field bug "ammo/weapons don't reset on respawn." Also: the binary loop starts + at **i=2** (skips mapper + voltage bus). And the binary's respawn severed the vehicle and built + a NEW mech (fresh ctor = full ammo/pristine subsystems — the pod behavior); our entity-reuse + respawn therefore adds FRESH-MECH EMULATION: per-class `DeathReset` overrides chain each class's + own authentic RTIS body (the recon RTIS namesakes are statically bound — no common virtual), and + `MechSubsystem::RespawnRepair` + `AmmoBin::DeathReset` restore the private crit zones / status + alarms / ammo (`initialAmmoCount` @0x220) the mech-zone heal never touched. See mech4.cpp + Mech::Reset + ammobin.cpp. **Three respawn bugs found + fixed on the way (all [T2], 2-node force-damage verified):** (i) **death-latch un-latch** — `IsMechDestroyed` tested `graphicAlarm>=9` alone; a later leg hit on the wreck rewrites the alarm to 4/3, un-latching → the death transition RE-RAN (double kill, diff --git a/game/reconstructed/ammobin.cpp b/game/reconstructed/ammobin.cpp index 0125e4f..ca902f0 100644 --- a/game/reconstructed/ammobin.cpp +++ b/game/reconstructed/ammobin.cpp @@ -364,6 +364,25 @@ void AmmoBin::ReadUpdateRecord(UpdateRecord *update) } } +//############################################################################# +// DeathReset -- issue #22 (PORT, fresh-mech respawn emulation) +// +// The binary respawn built a NEW mech: a fresh AmmoBin ctor = full bin, +// Loaded feed alarm, idle feed timer, cook-off disarmed. Our entity-reuse +// respawn restores exactly those ctor values. (ResetToInitialState below +// stays authentically EMPTY per AMMOBIN.TCP.) +// +void AmmoBin::DeathReset(int /*reset_command*/) +{ + if (getenv("BT_DEATH_LOG")) + DEBUG_STREAM << "[respawn] ammo bin " << subsystemID << " refilled " + << ammoCount << " -> " << initialAmmoCount << "\n" << std::flush; + ammoCount = initialAmmoCount; // @0x180 <- @0x220 (ctor value) + feedTimer = 0; // @0x184 + cookOffArmed = 0; // @0x18C + ammoAlarm.SetLevel(Loaded); // @0x1A8 (ctor: 1) +} + //############################################################################# // ResetToInitialState (slot 10) // diff --git a/game/reconstructed/ammobin.hpp b/game/reconstructed/ammobin.hpp index 93f7af7..fa67d56 100644 --- a/game/reconstructed/ammobin.hpp +++ b/game/reconstructed/ammobin.hpp @@ -184,6 +184,12 @@ ReadUpdateRecord(UpdateRecord *update); // slot 9, @004bd2c0 void ResetToInitialState(); // slot 10 (AMMOBIN.TCP: empty) + // issue #22 (PORT, fresh-mech respawn emulation): refill to the ctor + // state. AMMOBIN.TCP's RTIS is authentically EMPTY (kept so); the + // binary's respawn refilled ammo by building a NEW mech -- our + // entity-reuse respawn refills here instead. + void + DeathReset(int reset_command); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local helpers diff --git a/game/reconstructed/emitter.cpp b/game/reconstructed/emitter.cpp index a34b0be..bdf3a8d 100644 --- a/game/reconstructed/emitter.cpp +++ b/game/reconstructed/emitter.cpp @@ -902,6 +902,15 @@ void } // +// issue #22: the respawn sweep (engine DeathReset virtual) chains THIS +// class's authentic slot-10 body [T1 vtable 00512078 +0x28 = 004ba4d0] -- +// a respawned energy weapon restarts its charge cycle from Loading. +void + Emitter::DeathReset(int /*reset_command*/) +{ + ResetToInitialState(); // @004ba4d0 +} + // @004ba4d0 -- slot 10. Reset to Loading: chain to MechWeapon, set the alarm // to 3 (Loading), clear the firing/beam transients and re-prime beamScale to // (1,1,1). Matches EMITTER.TCP ResetToInitialState (SetSimulationState(Loaded) diff --git a/game/reconstructed/emitter.hpp b/game/reconstructed/emitter.hpp index 3f41932..e07f2ee 100644 --- a/game/reconstructed/emitter.hpp +++ b/game/reconstructed/emitter.hpp @@ -209,6 +209,8 @@ class NotationFile; ReadUpdateRecord(Simulation__UpdateRecord *message); // slot 6, @004ba568 void ResetToInitialState(); // slot 10, @004ba4d0 + void + DeathReset(int reset_command); // issue #22: -> RTIS @004ba4d0 void PrintState(); // slot 13, @004bb014 diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 6e5d81b..12cc607 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1757,14 +1757,32 @@ void zone->Heal(); } - // --- RESET every subsystem (the decomp's loop-2 vtable+0x28 = DeathReset, - // the base virtual symmetric with the DeathShutdown we run on death): - // restores each subsystem's heat/power/ammo/charge to the initial state --- - for (int i = 0; i < GetSubsystemCount(); ++i) + // --- RESET every subsystem (issue #22 correction, 2026-07-23). The + // binary loop (@0049fb74 loop-2) runs i=2..count (skipping the mapper + // + voltage bus) calling vtable+0x28 -- which the DATA-section vtables + // prove is ResetToInitialState [T1: 0050f9d8/00511d2c/00512078/0051242c + // all carry the class RTIS at +0x28], NOT DeathReset (the old comment's + // mislabel: DeathReset is the ENGINE base virtual, empty everywhere, + // so the whole sweep was a no-op = "ammo/weapons don't reset on + // respawn"). The recon classes' RTIS bodies are statically-bound + // namesakes (no common virtual), so the engine DeathReset virtual is + // the DISPATCH VEHICLE: each class's DeathReset override chains its + // own authentic RTIS body (+ the fresh-mech extras below). + // + // FRESH-MECH EMULATION [PORT divergence, documented]: the binary + // respawn severed the vehicle and built a NEW mech (fresh ctor = full + // ammo, pristine subsystems -- the pod behavior testers remember); + // our port REUSES the entity (the new-mech path was the two-mech/ + // camera-glitch family), so Reset must also repair each subsystem's + // PRIVATE crit damage zone + status alarm -- the mech-zone heal above + // never touches those, which kept destroyed weapons dead forever. + for (int i = 2; i < GetSubsystemCount(); ++i) // binary: i = 2 start { Subsystem *s = GetSubsystem(i); - if (s != 0) - s->DeathReset(mode); + if (s == 0) + continue; + s->DeathReset(mode); // -> per-class RTIS chain + ((MechSubsystem *)s)->RespawnRepair(); // fresh-mech: zone + status } // --- locomotion pre-run + interest gates (a reset master must tick) --- diff --git a/game/reconstructed/mechsub.cpp b/game/reconstructed/mechsub.cpp index 8396f38..ada3eb0 100644 --- a/game/reconstructed/mechsub.cpp +++ b/game/reconstructed/mechsub.cpp @@ -254,6 +254,20 @@ void MechSubsystem::ForceCriticalFailure() } } +// issue #22 (PORT): the fresh-mech respawn repair -- the exact inverse of the +// crit-failure path (statusAlarm 1 + zone state 1 + damageLevel), restoring +// the private zone to its ctor state. See mech4.cpp Mech::Reset. +void MechSubsystem::RespawnRepair() +{ + statusAlarm.SetLevel(0); // operational + if (damageZone != 0) + { + ((DamageZone *)damageZone)->damageLevel = 0.0f; + ((DamageZone *)damageZone)->SetDamageZoneState(0); + ((DamageZone *)damageZone)->SetGraphicState(0); // ExistsGraphicState + } +} + void MechSubsystem::SetSubsystemDamageLevel(Scalar level) { if (damageZone) diff --git a/game/reconstructed/mechsub.hpp b/game/reconstructed/mechsub.hpp index fd53efd..dadb28a 100644 --- a/game/reconstructed/mechsub.hpp +++ b/game/reconstructed/mechsub.hpp @@ -274,6 +274,11 @@ class Damage; // subsystem's own zone hits 1.0: statusAlarm Destroyed(1) + the // PrintState debug gate + the zone's state valve. [T1 @0049c9a8] void ForceCriticalFailure(); + // issue #22 (PORT, fresh-mech respawn emulation): undo everything + // ForceCriticalFailure/TakeDamage did to the subsystem's own private + // damage zone + status -- the binary respawn built a NEW mech, so a + // respawned subsystem was always pristine. + void RespawnRepair(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Internal helpers diff --git a/game/reconstructed/mechweap.cpp b/game/reconstructed/mechweap.cpp index f10d0f3..35e6dbc 100644 --- a/game/reconstructed/mechweap.cpp +++ b/game/reconstructed/mechweap.cpp @@ -669,6 +669,15 @@ void // CONFIRMED the authored-DamageAmount economy; kShotDamage is retired to the // kill-score and env-gated diag hooks (task #60). // +// issue #22: Mech::Reset's subsystem sweep dispatches the engine DeathReset +// virtual; the binary's sweep called vtable slot +0x28 = ResetToInitialState +// [T1 vtable 00511d2c]. Chain our own RTIS so a respawned weapon re-arms. +void + MechWeapon::DeathReset(int /*reset_command*/) +{ + ResetToInitialState(); // @004b96ec equivalent +} + void MechWeapon::SendDamageMessage(Entity *target) { diff --git a/game/reconstructed/mechweap.hpp b/game/reconstructed/mechweap.hpp index 0ac8b03..4dec870 100644 --- a/game/reconstructed/mechweap.hpp +++ b/game/reconstructed/mechweap.hpp @@ -278,6 +278,10 @@ class CockpitHud; TakeDamage(Damage &damage); // slot 9, @004b96d4 void ResetToInitialState(); // slot 10, @004b96ec + // issue #22: the respawn sweep's dispatch vehicle (engine virtual); + // chains this class's own authentic slot-10 RTIS body. + void + DeathReset(int reset_command); void PrintState(); // slot 13, @004b9d00 diff --git a/game/reconstructed/mislanch.cpp b/game/reconstructed/mislanch.cpp index 83c4b70..39c95b2 100644 --- a/game/reconstructed/mislanch.cpp +++ b/game/reconstructed/mislanch.cpp @@ -409,7 +409,10 @@ void MissileLauncher::TakeDamage(Damage &damage) void MissileLauncher::DeathReset(Logical /*full_reset*/) { - ProjectileWeapon::ResetToInitialState(); // @004bbaf8 (best-effort) + // issue #22: the respawn sweep's dispatch vehicle -- chains the authentic + // slot-10 RTIS [T1 vtable +0x28]; ammo itself refills in AmmoBin's own + // DeathReset (the bins are separate roster subsystems). + ProjectileWeapon::ResetToInitialState(); // @004bbaf8 } //############################################################################# diff --git a/game/reconstructed/projweap.cpp b/game/reconstructed/projweap.cpp index 340be71..d234bdc 100644 --- a/game/reconstructed/projweap.cpp +++ b/game/reconstructed/projweap.cpp @@ -385,6 +385,15 @@ void } // +// issue #22: the respawn sweep (engine DeathReset virtual) chains THIS +// class's authentic slot-10 body [T1 vtable 0051242c +0x28 = 004bbaf8] -- +// a respawned ballistic weapon unjams and re-arms. +void + ProjectileWeapon::DeathReset(int /*reset_command*/) +{ + ResetToInitialState(); // @004bbaf8 +} + // @004bbaf8 -- slot 10. Reset the firing/eject state, re-arm the alarm to the // "Loading" level, restore the eject countdown, and clear the jam flag, then // chain to MechWeapon. (PROJWEAP.TCP showed an empty early stub; this is the diff --git a/game/reconstructed/projweap.hpp b/game/reconstructed/projweap.hpp index cb22607..407bc21 100644 --- a/game/reconstructed/projweap.hpp +++ b/game/reconstructed/projweap.hpp @@ -196,6 +196,8 @@ class NotationFile; HandleMessage(int message); // slot 8, @004bcabc void ResetToInitialState(); // slot 10, @004bbaf8 + void + DeathReset(int reset_command); // issue #22: -> RTIS @004bbaf8 void PrintState(); // slot 13, @004bc6c8