Skip to content

Fix: Bot stack overflow after teleporting to an unwalkable map#844

Open
therpr wants to merge 1 commit into
MUnique:masterfrom
therpr:master
Open

Fix: Bot stack overflow after teleporting to an unwalkable map#844
therpr wants to merge 1 commit into
MUnique:masterfrom
therpr:master

Conversation

@therpr

@therpr therpr commented Jul 24, 2026

Copy link
Copy Markdown

Problem

A bot can crash the whole game server with a stack overflow when it warps to a map whose spawn gate places it on a
blocked (non-walkable) tile.

Root cause

Bots are connection-less (BotPlayer : OfflinePlayer). When a warp lands a player on a blocked tile,
ClientReadyAfterMapChangeAsync recovers by calling WarpToSafezoneAsync:

ClientReadyAfterMapChangeAsync
→ WarpToSafezoneAsync
→ WarpToAsync
→ IMapChangePlugIn.MapChangeAsync
→ (offline bot) OfflineMapChangePlugIn.MapChangeAsync
→ ClientReadyAfterMapChangeAsync // synchronous, inline — no network round-trip

For a real player the map-change plugin sends a packet and returns; the next ClientReadyAfterMapChangeAsync arrives
later on a fresh stack from the client's F3 12 ack — no recursion.

For a bot, OfflineMapChangePlugIn.MapChangeAsync calls ClientReadyAfterMapChangeAsync inline. If the destination's
safezone spawn gate is itself blocked, the recovery re-enters on the same growing stack and recurses until it
overflows — crashing the process.

A reactive "remember broken maps, skip them next tick" fix cannot work here: the overflow kills the process on the
first bad warp, before the bot's next navigator tick ever runs.

Fix (proactive, bot-side only)

A bot is never offered a map it cannot stand in, so the recovery recursion can never start.

BotNavigator.HasWalkableSpawnGate(map) parses the destination map's terrain and checks that its safezone spawn gate
(the gate WarpToSafezoneAsync recovers to) contains at least one walkable tile. The two candidate pickers —
TryPickEasierMap and TryPickBetterMapCore — now drop any map that fails this check, alongside the existing legal-warp
and affordability filters:

if (!candidate.TryGetRequirementError(this._player, out _)
&& this.TryGetLegalWarp(candidate, out var candidateWarp)
&& this.CanAffordWarp(candidateWarp)
&& this.HasWalkableSpawnGate(candidate)) // new

Terrain is static configuration, so the verdict is parsed once per map and cached in a static
ConcurrentDictionary<int, bool> shared across all bots — no per-tick terrain parsing, no per-bot duplication.

@therpr
therpr force-pushed the master branch 3 times, most recently from 4b052c3 to d8505e6 Compare July 24, 2026 07:17
@therpr
therpr marked this pull request as draft July 24, 2026 07:25
@therpr
therpr marked this pull request as ready for review July 24, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant