Skip to content

fix(core): reject spawn intents after the spawn phase (anti-teleport)#4657

Merged
evanpelle merged 1 commit into
mainfrom
fix/spawn-phase-teleport-guard
Jul 21, 2026
Merged

fix(core): reject spawn intents after the spawn phase (anti-teleport)#4657
evanpelle merged 1 commit into
mainfrom
fix/spawn-phase-teleport-guard

Conversation

@iiamlewis

@iiamlewis iiamlewis commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Problem

Patches sending the spawn intent over the game WebSocket while a match is underway:

ws.send(JSON.stringify({ type: "intent", intent: { type: "spawn", tile: tileRef } }));

The server relays it like any intent, and SpawnExecution.tick() then relinquishes the player's entire territory and re-conquers it at the target tile — instant teleport anywhere on the map.

The root cause is that SpawnExecution.tick() had no spawn-phase gate (only a random-spawn re-roll guard), and the engine ticks the execution both during and after the spawn phase (activeDuringSpawnPhase() is true, and after the phase !inSpawnPhase() is also true).

Fix

Ignore a spawn intent when the game is no longer in the spawn phase and the player has already spawned:

if (!this.mg.inSpawnPhase() && player.hasSpawned()) {
  return;
}

Because inSpawnPhase() / hasSpawned() are deterministic game state, the intent becomes a no-op identically on every client — no desync.

Unaffected:

  • First-time spawns (hasSpawned() is false)
  • In-phase spawn picks and re-picks (inSpawnPhase() is true)
  • Existing random-spawn re-roll protection (kept as-is)

Tests

  • Added Spawn intent after the spawn phase cannot relocate territory (anti-teleport) — establishes a player, then asserts a later spawn intent leaves spawnTile() and numTilesOwned() unchanged.
  • Simplified Spawn on specific tile to a single first spawn (the previous version relied on an out-of-phase re-pick, which this fix now correctly forbids).
  • Full tests/core suite green (259 tests).

🤖 Generated with Claude Code

SpawnExecution.tick() relinquished the player's territory and
re-conquered it at the intent's tile with no spawn-phase gate, and the
engine ticks the execution both during and after the spawn phase. A
crafted `spawn` intent sent mid-game therefore teleported an
already-established player anywhere on the map.

Ignore a spawn intent when the game is no longer in the spawn phase and
the player has already spawned, so the intent is a deterministic no-op
on every client. First spawns and in-phase re-picks are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a guard that ignores post-spawn relocation intents outside the spawn phase. Updates spawn coverage and adds an anti-teleport test confirming the player’s spawn tile and owned territory remain unchanged.

Changes

Spawn security

Layer / File(s) Summary
Block post-spawn relocation
src/core/execution/SpawnExecution.ts
SpawnExecution.tick now returns early when a spawned player submits an intent outside the spawn phase.
Validate spawn and anti-teleport behavior
tests/core/execution/SpawnExecution.test.ts
Tests verify spawning on tile 20 and confirm later targeting of tile 10 does not change the player’s spawn tile or owned tile count.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

Suggested labels: Bugfix

Suggested reviewers: flopinguin, evanpelle

Poem

Spawn at twenty, safely stay,
No sneaky tile ten getaway.
The guard stands firm, the borders hold,
Teleport tricks are now controlled.
🛡️ Tiles remain where they were told.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main change: rejecting post-spawn spawn intents to prevent teleportation.
Description check ✅ Passed The description matches the code change and tests, explaining the spawn-phase gate and anti-teleport behavior.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@iiamlewis iiamlewis added the approved Approved for a PR, if you assigned to the issue. label Jul 21, 2026
@iiamlewis iiamlewis added this to the v33 milestone Jul 21, 2026
@github-project-automation github-project-automation Bot moved this from Triage to Final Review in OpenFront Release Management Jul 21, 2026
@evanpelle evanpelle modified the milestones: v33, v32 Jul 21, 2026
@evanpelle
evanpelle merged commit fabd1bd into main Jul 21, 2026
17 of 20 checks passed
@evanpelle
evanpelle deleted the fix/spawn-phase-teleport-guard branch July 21, 2026 16:22
@github-project-automation github-project-automation Bot moved this from Final Review to Complete in OpenFront Release Management Jul 21, 2026
evanpelle pushed a commit that referenced this pull request Jul 21, 2026
…#4657)

## Problem
Patches sending the `spawn` intent over the game WebSocket while a match
is underway:

```js
ws.send(JSON.stringify({ type: "intent", intent: { type: "spawn", tile: tileRef } }));
```

The server relays it like any intent, and `SpawnExecution.tick()` then
**relinquishes the player's entire territory and re-conquers it at the
target tile** — instant teleport anywhere on the map.

The root cause is that `SpawnExecution.tick()` had no spawn-phase gate
(only a random-spawn re-roll guard), and the engine ticks the execution
**both during and after** the spawn phase (`activeDuringSpawnPhase()` is
`true`, and after the phase `!inSpawnPhase()` is also `true`).

## Fix

Ignore a spawn intent when the game is no longer in the spawn phase
**and** the player has already spawned:

```js
if (!this.mg.inSpawnPhase() && player.hasSpawned()) {
  return;
}
```

Because `inSpawnPhase()` / `hasSpawned()` are deterministic game state,
the intent becomes a no-op identically on every client — no desync.

Unaffected:
- First-time spawns (`hasSpawned()` is false)
- In-phase spawn picks and re-picks (`inSpawnPhase()` is true)
- Existing random-spawn re-roll protection (kept as-is)

## Tests

- Added `Spawn intent after the spawn phase cannot relocate territory
(anti-teleport)` — establishes a player, then asserts a later `spawn`
intent leaves `spawnTile()` and `numTilesOwned()` unchanged.
- Simplified `Spawn on specific tile` to a single first spawn (the
previous version relied on an out-of-phase re-pick, which this fix now
correctly forbids).
- Full `tests/core` suite green (259 tests).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Approved for a PR, if you assigned to the issue.

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

2 participants