Fix expedition soft-lock: extract on run idle-timeout + auto-pick stale forks

getActiveZoneRun's 24h stale-run reaper abandoned the run but left the
wrapping expedition status='active' pointing at a dead run. The autopilot
then read run==nil and bailed while briefing/recap tickers kept firing, so
the player soft-locked at the last fork with no way to route on. The reaper
now force-extracts the wrapping active expedition (run-loss seam) when it
reaps that expedition's current run.

Root cause was a background fork: the autopilot can't pick for the player,
so the run idled all the way to the reaper. Add an 8h forkAutoPickTimeout —
a background fork left unanswered that long now auto-picks the first
unlocked route (same path as !zone go) and keeps walking; all-locked forks
are left for the player. Foreground !expedition run still always prompts.

Tests: idle-timeout extracts the expedition; stale fork takes the available
route; all-locked fork stays intact.
This commit is contained in:
prosolis
2026-06-18 00:28:05 -07:00
parent 6a47be34bc
commit f4a39b46e9
3 changed files with 209 additions and 9 deletions

View File

@@ -290,6 +290,17 @@ func getActiveZoneRun(userID id.UserID) (*DungeonRun, error) {
}
if time.Since(r.LastActionAt) > zoneRunInactivityTimeout {
_ = abandonZoneRunByID(r.RunID)
// A run reaped by the §4.3 idle timeout must also terminate the
// wrapping active expedition. Without this, the expedition is left
// status='active' pointing at a now-abandoned run: the autopilot's
// runAutopilotWalk reads run==nil and bails, but the briefing/recap
// ambient tickers keep firing — the player soft-locks at the last
// fork, "stuck" with no way to route on. Mirror the run-loss seam,
// but only when this run is the active expedition's current run so
// a standalone (non-expedition) stale run still reaps cleanly.
if exp, _ := getActiveExpedition(userID); exp != nil && exp.RunID == r.RunID {
forceExtractExpeditionForRunLoss(userID, "run idle-timeout (§4.3 stale-run reap)")
}
return nil, nil
}
return r, nil