fix(core): reject spawn intents after the spawn phase - #4831
Conversation
A modified client could relay a `{ type: "spawn", tile }` intent after the
spawn phase ended and materialize a base on the running map (or teleport an
existing one by relinquishing and re-conquering elsewhere). The client-side
guard in ClientGameRunner is UX only — a modified client skips it — so the
simulation accepted the intent on every client.
Gate the spawn intent where it becomes an execution: Executor.createExec now
drops a spawn intent to a NoOpExecution once inSpawnPhase() is false. This is
the single authoritative choke point for untrusted client input and runs
identically on every client, so the drop is deterministic. Internal spawns
(nations, bots, random spawn) construct SpawnExecution directly and are
unaffected, as is a human's last-tick click, whose intent is still created
while the phase is active.
Adds tests/SpawnAfterPhase.test.ts covering both the blocked post-phase intent
and the still-allowed in-phase intent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
WalkthroughThe execution manager now converts out-of-phase client spawn intents into ChangesSpawn phase enforcement
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
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. Comment Warning |
|
Hi @Foulks-Plb, thanks for the contribution. This PR was automatically closed because it doesn't fit our contribution workflow:
To contribute to OpenFront:
If you believe this was closed in error, please reach out on our Discord or comment below. See CONTRIBUTING.md for the full contribution process. — Automated PR gate. Source. |
Problem
The spawn phase is only enforced on the client.
ClientGameRunnershows aspawn_failedmodal and drops the click once the phase ends, but that guard isUX only — a modified client skips it and relays a
{ type: "spawn", tile }intent after the phase. The server relays spawn intents without a phase check
(it has no
inSpawnPhasenotion), and the simulation'sSpawnExecutiononlyblocks re-spawns by already-spawned players:
A player who never spawned during the phase has
hasSpawned() === false, so theintent falls through and places starting territory on the already-running map.
The same path can relocate an existing base (relinquish + re-conquer elsewhere)
after the map has developed.
Fix
Gate the intent at the single choke point where untrusted client input becomes
an execution —
Executor.createExec. OnceinSpawnPhase()is false aspawnintent becomes a
NoOpExecution:This runs identically on every client, so the drop is deterministic (no desync).
Internal spawns — nations, bots, random spawn — construct
SpawnExecutiondirectly and are unaffected, and a human's last-tick click still works because
its intent is created while the phase is still active.
Tests
tests/SpawnAfterPhase.test.ts:Full test suite shows no new failures vs.
main.🤖 Generated with Claude Code