diff --git a/engine/MUNGA/CULTURAL.cpp b/engine/MUNGA/CULTURAL.cpp index 4a53421..8517d32 100644 --- a/engine/MUNGA/CULTURAL.cpp +++ b/engine/MUNGA/CULTURAL.cpp @@ -450,6 +450,11 @@ CulturalIcon::~CulturalIcon() break; } } + // #93: an icon can be a projectile's picked target -- scrub the static + // projectile pool so a round in flight never dispatches into a freed icon + // (same teardown race as the mech case; see mech4.cpp BTProjectilesDropEntity). + extern void BTProjectilesDropEntity(void *e); + BTProjectilesDropEntity(this); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // diff --git a/game/btl4main.cpp b/game/btl4main.cpp index 664a1b9..cdf783c 100644 --- a/game/btl4main.cpp +++ b/game/btl4main.cpp @@ -1539,6 +1539,13 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine app_manager->RunMissions(); std::cout << "[boot] RunMissions returned (mission loop exited)." << std::endl << std::flush; + // #93: the projectile pool is static -- kill any rounds still in flight so + // nothing dangles across the mission boundary (the per-entity dtor scrub + // covers the in-mission teardown race; this covers everything else). + { + extern void BTProjectilesClearAll(void); + BTProjectilesClearAll(); + } // MATCHLOG AUTO-UPLOAD (2026-07-22): in relay mode, send this peer's // match forensic log back to the operator's relay (saved under the diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index ad69197..5815696 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -2151,6 +2151,9 @@ Mech::~Mech() extern void BTDeregisterMech(Entity *m); // task #46 live-mech registry BTDeregisterMech((Entity *)this); + extern void BTProjectilesDropEntity(void *e); // #93: scrub the STATIC projectile + BTProjectilesDropEntity(this); // pool -- a round in flight must not + // outlive its target/shooter pointer extern void BTUnstashClipState(const Mech *m); // task #59 clip-set registry BTUnstashClipState(this); diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index b70570f..8c97e24 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -847,6 +847,41 @@ struct BTProjectile { }; static BTProjectile gProjectiles[64]; +//########################################################################### +// Projectile-pool entity scrub (#93 -- Rajel's end-of-round crash). The pool +// is STATIC and outlives entities: at round teardown a destroyed mech is +// DEREGISTERED, so a round still in flight holding it as p.target fails +// BTIsRegisteredMech and falls into the NON-MECH impact branch -- which +// happily Dispatch()es into the freed object (its vtable slot read reused +// string bytes: the 0x65676769 "igge" call target, symbolized + disasm-pinned +// to the tgt->Dispatch at the icon branch). The liveness check itself ROUTED +// the dangling pointer into the unguarded branch. Fix at the source of +// truth: every entity scrubs itself out of the pool as it dies (~Mech and +// ~CulturalIcon call this), and mission exit clears the pool outright. +//########################################################################### +void BTProjectilesDropEntity(void *e) +{ + for (int i = 0; i < 64; ++i) + { + BTProjectile &p = gProjectiles[i]; + if (!p.active) + continue; + if (p.target == (Entity *)e) + p.target = 0; // flies on unguided; impact hits nothing + if (p.shooter == (Entity *)e) + { + p.shooter = 0; // null-guarded at every use + p.weaponSubsys = -1; + } + } +} + +void BTProjectilesClearAll(void) +{ + for (int i = 0; i < 64; ++i) + gProjectiles[i].active = 0; +} + extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float); //########################################################################### diff --git a/scratchpad/night7/mp_teardown.sh b/scratchpad/night7/mp_teardown.sh new file mode 100644 index 0000000..8b9f484 --- /dev/null +++ b/scratchpad/night7/mp_teardown.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# #93 bench: mission teardown with missiles IN FLIGHT (3 cycles). +set -x +cd /c/git/bt411/content || exit 1 +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 2 +for i in 1 2 3; do + rm -f td_$i.log + ( export BT_PLATFORM=glass BT_START_INSIDE=1 BT_DEV_GAUGES=1 \ + BT_LOG=td_$i.log BT_AFFINITY=0x03 \ + BT_GOTO=enemy BT_GOTO_STOP=100 BT_AUTOFIRE=1 BT_PROJ_LOG=1 BT_SPAWN_ENEMY=1 + ../build/Release/btl4.exe -egg SHORTMSN.EGG & + wait )& + BGPID=$! + # mission length 45s + load ~20s + menu relaunch; give it 100s then kill the relaunch + sleep 100 + taskkill //F //IM btl4.exe > /dev/null 2>&1 + sleep 2 + echo "=== cycle $i ===" + grep -c "crash" td_$i.log + grep -c "RunMissions returned" td_$i.log + grep -c "projectile" td_$i.log +done +echo "=== DONE ==="