Issue #33/#34: relaunch-storm + duplicate-seat fixes (pod-grade session stability)

Night-2 capture: one flapping pod cascaded 3 ROUND ABORTs in 8s; every
client relaunch-churned (~1 gen/s) until the D3D device-creation 20s
deadline fired on every machine ('could not connect direct 3d'), and
claim-less restarts minted duplicate roster seats.

Console (relay):
- Round abort clears stale console-conn ACK flags and HOLDS the next
  egg release for an 8s settle window (_tick_settle re-releases when
  quiet) -- the abort->instant-re-release->drop->abort cycle is broken.
- HELLO from a stale identity gets a REJOIN reply (identity resync via
  the seat-request path, 5s/IP rate limit) instead of a bare drop.
- STATIC SEATS: seats remember (IP + callsign); a claim-less restart
  from the same machine+name RECLAIMS its own seat (displacing its
  zombie beacon) instead of minting a duplicate -- the pod contract:
  same box, same seat, always.
- Operator local instances log with BT_LOG_APPEND (the night-2 crash
  loop left a 2-line file; generations now leave a full trail).

Client:
- Relaunch storm damper: a generation that lived <15s sleeps 5s before
  respawning (storm decays to a gentle cadence; normal multi-minute
  round relaunches untouched; menu clicks explicitly never delayed).

Rig-verified: normal round + mid-MISSION drop = no abort (correct);
mid-LOAD kill = exactly ONE abort + 8s hold + seat reclaimed + no
cascade.  The night-2 'host 7' identity-derivation subtlety remains
under forensics (append logs + first-breath line will capture it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-23 23:35:25 -05:00
co-authored by Claude Opus 4.8
parent 26439c2e1a
commit d3ede2e635
4 changed files with 163 additions and 4 deletions
+13
View File
@@ -165,6 +165,13 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
// Boot tick for the relaunch storm damper (btl4console.cpp, issue #33):
// generations that die young relaunch with a 5 s pause.
{
extern unsigned long gBTBootTick;
gBTBootTick = GetTickCount();
}
// Heap-corruption hunt (env BT_HEAPCHECK=1; default OFF -- ~100x slower): validate
// the whole debug heap on EVERY alloc/free, so an out-of-bounds write is caught at
// the very next heap op (a stack near the culprit) instead of much later at some
@@ -526,6 +533,12 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
sprintf(fe_arguments, "-net %d -platform glass", fe_spec.consolePort);
break;
}
{
// The user just clicked JOIN/LAUNCH in the menu -- never
// storm-damp a human (btl4console.cpp, issue #33).
extern int gBTRelaunchUserInitiated;
gBTRelaunchUserInitiated = 1;
}
BTFE_RelaunchSelfAndExit(fe_arguments); // never returns
}
#ifdef BT_STEAM
+35
View File
@@ -120,9 +120,44 @@ static void
//###########################################################################
//
// Storm-damper support: btl4main stamps the boot tick at WinMain entry;
// user-initiated relaunches (the menu's JOIN/LAUNCH click) set the
// user-initiated flag so a fast human is never made to wait.
//
unsigned long gBTBootTick = 0;
int gBTRelaunchUserInitiated = 0;
static unsigned long
BTBootTickOf()
{
return gBTBootTick != 0 ? gBTBootTick : GetTickCount();
}
void
BTFE_RelaunchSelfAndExit(const char *arguments)
{
//
// RELAUNCH-STORM DAMPER (issue #33, night-2 capture): a client whose
// generations die young (stale identity -> HELLO reject -> drop ->
// relaunch) used to cycle ~1/second -- the overlapping instances fought
// for the graphics adapter until every machine hit the D3D 20 s
// deadline, and each bounce re-triggered the relay's round abort. If
// THIS generation lived under 15 s, sleep 5 s before spawning the next
// one: a storm decays to a gentle 5 s cadence while normal multi-minute
// round relaunches are untouched.
//
{
unsigned long uptime_ms = GetTickCount() - BTBootTickOf();
if (uptime_ms < 15000UL && !gBTRelaunchUserInitiated)
{
MarshalLog("generation lived only %lu ms -- relaunch storm "
"damper: waiting 5 s", uptime_ms);
Sleep(5000);
}
gBTRelaunchUserInitiated = 0;
}
//
// A MENU relaunch (empty arguments) must not leak the mission
// process's marshal handoff into the child -- the child inherits our