Skip to content

fix(core): gate all client spawn intents on the spawn phase (follow-up to #4657)#4660

Open
iiamlewis wants to merge 2 commits into
mainfrom
fix/spawn-teleport-client-intent
Open

fix(core): gate all client spawn intents on the spawn phase (follow-up to #4657)#4660
iiamlewis wants to merge 2 commits into
mainfrom
fix/spawn-teleport-client-intent

Conversation

@iiamlewis

Copy link
Copy Markdown
Contributor

Follow-up to #4657.

Problem

#4657 blocked an already-spawned player from re-spawning after the spawn phase (the teleport exploit), using !inSpawnPhase() && player.hasSpawned(). But that still lets a never-spawned player spawn for the first time once the phase has ended — spawning fresh, mid-game, at a chosen tile.

The rule should be: once the spawn phase is over, a player can neither spawn nor re-spawn.

Why not just !inSpawnPhase()

A naive gate on !inSpawnPhase() alone regresses legitimate spawning. Nations, bots, and random-spawn placement queue a SpawnExecution during the spawn phase, but it can execute a tick or two later — e.g. a singleplayer human's own spawn calls endSpawnPhase() immediately, so AI entities that spawn afterwards would be wrongly rejected. (NationExecution only enqueues its spawn while inSpawnPhase(), then it lands on a later tick.)

Fix

SpawnExecution takes a fromIntent flag, set true only for client spawn intents (in ExecutionManager). The phase gate applies to those alone:

if (this.fromIntent && !this.mg.inSpawnPhase()) {
  return; // deterministic no-op on every client
}

Every internal construction (NationExecution, TribeSpawner, PlayerSpawner) leaves fromIntent false and is unaffected.

Tests

tests/core/execution/SpawnExecution.test.ts:

  • Client spawn intent is accepted during the spawn phase.
  • Client spawn intent after the phase is ignored (anti-teleport) — spawn location & territory unchanged.
  • Client spawn intent after the phase cannot spawn a never-spawned player.
  • Internal spawns are not gated by the phase.

Full suite green (2094 tests), including the singleplayer nation-spawn ordering tests.

🤖 Generated with Claude Code

…p to #4657)

#4657 blocked an already-spawned player from re-spawning after the spawn
phase (the teleport exploit), but a player could still spawn for the
first time once the phase had ended. Tighten the rule so any client
"spawn" intent is rejected once the spawn phase is over — a player can
neither spawn nor re-spawn mid-game.

A naive `!inSpawnPhase()` gate would break internal spawns: nations,
bots, and random-spawn placement queue a SpawnExecution during the spawn
phase, but it can execute a tick later (a singleplayer human's own spawn
ends the phase early). So SpawnExecution takes a `fromIntent` flag —
true only for client "spawn" intents (ExecutionManager) — and the phase
gate applies to those alone. Trusted internal spawns 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

Spawn executions now distinguish client spawn intents from internal spawns. Client intents are blocked after the spawn phase, while trusted internal spawns remain allowed. Tests cover relocation prevention, repeated spawning, never-spawned players, execution-manager wiring, and random spawn behavior.

Changes

Spawn intent gating

Layer / File(s) Summary
Mark and gate spawn intents
src/core/execution/ExecutionManager.ts, src/core/execution/SpawnExecution.ts
Client spawn intents pass fromIntent = true; post-phase intents are ignored, while internal spawns continue through execution.
Validate spawn behavior
tests/core/execution/SpawnExecution.test.ts, tests/core/execution/ExecutionManager.test.ts
Tests cover active-phase spawning, anti-teleport behavior, repeated spawns, never-spawned players, trusted spawns, execution-manager creation, and random spawn handling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ExecutionManager
  participant SpawnExecution
  participant GameState
  Client->>ExecutionManager: Submit spawn intent
  ExecutionManager->>SpawnExecution: Create with fromIntent=true
  SpawnExecution->>GameState: Check spawn phase
  SpawnExecution->>GameState: Apply or ignore spawn
Loading

Possibly related PRs

Suggested labels: Bugfix

Suggested reviewers: evanpelle

Poem

A spawn intent knocks at the gate,
Phase open? Enter—don’t wait.
Phase closed? The tile stays still,
Trusted spawns may climb the hill.
Tests keep teleporting at bay.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: client spawn intents are gated on the spawn phase.
Description check ✅ Passed The description matches the code changes and explains the spawn-phase gating fix and tests.
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.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/execution/ExecutionManager.ts`:
- Around line 73-78: Add a focused test for the stamped "spawn" intent branch in
ExecutionManager.createExec, verifying it returns a SpawnExecution with
fromIntent set to true and passes the expected game, player, and tile data. Keep
the existing direct SpawnExecution tests unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e79eefcf-e848-4f41-b83f-c06b314c8ba1

📥 Commits

Reviewing files that changed from the base of the PR and between fabd1bd and 4f5a4b3.

📒 Files selected for processing (3)
  • src/core/execution/ExecutionManager.ts
  • src/core/execution/SpawnExecution.ts
  • tests/core/execution/SpawnExecution.test.ts

Comment on lines +73 to +78
return new SpawnExecution(
this.gameID,
player.info(),
intent.tile,
true,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for any existing test exercising Executor.createExec with a "spawn" intent.
rg -nP -C3 '\bcreateExec(s)?\s*\(' --type=ts -g '*.test.ts'
rg -nP -C3 '"spawn"' --type=ts -g '*.test.ts'

Repository: openfrontio/OpenFrontIO

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Relevant files\n'
git ls-files 'src/core/execution/*' 'tests/core/execution/*' 'tests/core/*' | sed -n '1,200p'

printf '\n## Find Executor.createExec and spawn handling\n'
rg -n -C 3 'createExec|spawn|fromIntent' src/core/execution tests/core/execution tests/core --type ts

printf '\n## Show ExecutionManager.ts around spawn branch\n'
sed -n '1,140p' src/core/execution/ExecutionManager.ts

printf '\n## Show SpawnExecution tests\n'
sed -n '1,220p' tests/core/execution/SpawnExecution.test.ts

Repository: openfrontio/OpenFrontIO

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file list ---'
git ls-files 'src/core/execution/*' 'tests/core/execution/*' 'tests/core/*' | sed -n '1,200p'

echo
echo '--- createExec / spawn / fromIntent references ---'
rg -n -C 3 'createExec|spawn|fromIntent' src/core/execution tests/core/execution tests/core --type ts || true

echo
echo '--- ExecutionManager.ts ---'
sed -n '1,160p' src/core/execution/ExecutionManager.ts

echo
echo '--- SpawnExecution.test.ts ---'
sed -n '1,240p' tests/core/execution/SpawnExecution.test.ts

Repository: openfrontio/OpenFrontIO

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- tests mentioning ExecutionManager / StampedIntent / createExec ---'
rg -n -C 2 'ExecutionManager|StampedIntent|createExec|createExecs|fromIntent' tests src/core --type ts | sed -n '1,220p'

echo
echo '--- ExecutionManager.ts spawn branch only ---'
sed -n '45,82p' src/core/execution/ExecutionManager.ts

echo
echo '--- SpawnExecution.test.ts spawn-intent tests only ---'
sed -n '120,250p' tests/core/execution/SpawnExecution.test.ts

Repository: openfrontio/OpenFrontIO

Length of output: 11590


Add a small test for the "spawn" intent path.

tests/core/execution/SpawnExecution.test.ts covers fromIntent = true by building SpawnExecution directly, but it does not exercise ExecutionManager.createExec for a stamped "spawn" intent. Add one test there so this wiring cannot regress.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/execution/ExecutionManager.ts` around lines 73 - 78, Add a focused
test for the stamped "spawn" intent branch in ExecutionManager.createExec,
verifying it returns a SpawnExecution with fromIntent set to true and passes the
expected game, player, and tile data. Keep the existing direct SpawnExecution
tests unchanged.

Source: Coding guidelines

@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management Jul 21, 2026
@iiamlewis iiamlewis added this to the v33 milestone Jul 22, 2026
Add a focused test that Executor.createExec builds a SpawnExecution for a
"spawn" intent, carries the intent tile, and — because it originated from
a client intent — is a no-op when executed after the spawn phase. Guards
against the fromIntent flag being dropped from the ExecutionManager wiring.

Addresses review feedback on #4660.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/core/execution/ExecutionManager.test.ts`:
- Around line 33-35: Update the assertion for playerByClientID("client_id") so
it first verifies that the returned player exists, then separately asserts that
the existing player's hasSpawned() value is false. Remove the nullish fallback
to ensure a missing player causes the test to fail.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4658dafa-1b40-4346-a543-bace938429a8

📥 Commits

Reviewing files that changed from the base of the PR and between 4f5a4b3 and 49c3cf0.

📒 Files selected for processing (1)
  • tests/core/execution/ExecutionManager.test.ts

Comment on lines +33 to +35
expect(game.playerByClientID("client_id")?.hasSpawned() ?? false).toBe(
false,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not mask a missing player in this assertion.

?? false makes the test pass when playerByClientID("client_id") returns undefined, so it may not verify the intended player at all. Assert that the player exists, then check hasSpawned().

Suggested fix
-    expect(game.playerByClientID("client_id")?.hasSpawned() ?? false).toBe(
-      false,
-    );
+    const player = game.playerByClientID("client_id");
+    expect(player).toBeDefined();
+    expect(player!.hasSpawned()).toBe(false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(game.playerByClientID("client_id")?.hasSpawned() ?? false).toBe(
false,
);
const player = game.playerByClientID("client_id");
expect(player).toBeDefined();
expect(player!.hasSpawned()).toBe(false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/core/execution/ExecutionManager.test.ts` around lines 33 - 35, Update
the assertion for playerByClientID("client_id") so it first verifies that the
returned player exists, then separately asserts that the existing player's
hasSpawned() value is false. Remove the nullish fallback to ensure a missing
player causes the test to fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

1 participant