Issue #22: respawn resets ammo + repairs weapons (the +0x28 mislabel)
ROOT CAUSE [T1]: Mech::Reset's subsystem sweep (@0049fb74 loop-2) calls
vtable slot +0x28, which the DATA-section vtables prove is
ResetToInitialState (0050f9d8/00511d2c/00512078/0051242c all carry the
class RTIS at +0x28) -- our reconstruction mislabeled it DeathReset,
the EMPTY engine base virtual, so the whole per-subsystem reset was a
silent no-op ("ammo and weapons do not reset on respawn", playtest
night; matchlogs: 3 of 7 players fired zero projectiles after their
first death).
SECOND LAYER: the binary never refilled ammo at all -- its respawn
severed the vehicle and DropZoneReply built a NEW mech (fresh ctor =
full bins, pristine subsystems: the pod behavior testers remember).
Our port deliberately reuses the entity (the new-mech path was the
two-mech/camera-glitch family), so Reset now EMULATES ctor-freshness:
- the sweep starts at i=2 (binary-faithful: skips mapper + voltage
bus) and dispatches the engine DeathReset virtual as the vehicle;
per-class overrides chain each class's own authentic slot-10 RTIS
body (the recon RTIS namesakes are statically bound -- no common
virtual): MechWeapon @004b96ec, Emitter @004ba4d0 (charge cycle
restarts from Loading), ProjectileWeapon @004bbaf8 (unjams).
- MechSubsystem::RespawnRepair (PORT): heals the subsystem's PRIVATE
crit damage zone + status alarm -- the mech-zone heal never touched
those, which kept destroyed weapons dead across respawns.
- AmmoBin::DeathReset (PORT): refills to initialAmmoCount (@0x220, the
ctor value) + Loaded alarm + idle feed + cook-off disarmed;
ResetToInitialState stays authentically EMPTY per AMMOBIN.TCP.
Verified (2-node force-damage kill/respawn rig, BT_DEATH_LOG): every
respawn logs [respawn] ammo bin refills for both bins to streamed
capacity across 4 death cycles; mission un-regressed. KB corrected +
swept (multiplayer.md task #52 notes). Awaiting live tester
verification of depleted-bin refill + revived weapons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8368163b04
commit
5f115eca19
+11
-2
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) ---
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user