From e1125e0c372e019d356a705bfaae22b56e1f1a38 Mon Sep 17 00:00:00 2001 From: Mattia Migliorini Date: Sun, 1 Mar 2026 22:33:41 +0100 Subject: [PATCH] Fix: Nations reject alliance requests created pre-spawn (#3314) ## Description: This PR fixes an exploit that allows the player to request alliances to Nations, mostly in impossible mode, during spawn phase, with high chances for it to be accepted due to troop count parity. Nations now reject alliance requests during the spawn phase. `GameImpl.executeNextTick()` initializes ALL pending `unInitExecs` in one batch on the first post-spawn tick ( `numSpawnPhaseTurns() + 1` ). So every alliance request submitted during spawn phase is guaranteed to be created with `createdAt = numSpawnPhaseTurns() + 1` on the very first post-spawn tick. Therefore, we check for alliance requests created on the very first post-spawn tick and reject those. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: deshack_82603 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../execution/nation/NationAllianceBehavior.ts | 7 +++++++ tests/NationAllianceBehavior.test.ts | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/core/execution/nation/NationAllianceBehavior.ts b/src/core/execution/nation/NationAllianceBehavior.ts index 186a3b556..f98958aaa 100644 --- a/src/core/execution/nation/NationAllianceBehavior.ts +++ b/src/core/execution/nation/NationAllianceBehavior.ts @@ -28,6 +28,13 @@ export class NationAllianceBehavior { handleAllianceRequests() { for (const req of this.player.incomingAllianceRequests()) { + // Alliance Request intents created during the spawn phase are executed on + // the first tick post-spawn phase. With the following condition we reject + // all requests created during the spawn phase. + if (req.createdAt() <= this.game.config().numSpawnPhaseTurns() + 1) { + req.reject(); + continue; + } if (this.getAllianceDecision(req.requestor(), true)) { req.accept(); } else { diff --git a/tests/NationAllianceBehavior.test.ts b/tests/NationAllianceBehavior.test.ts index ea2c74077..b078457cf 100644 --- a/tests/NationAllianceBehavior.test.ts +++ b/tests/NationAllianceBehavior.test.ts @@ -51,6 +51,10 @@ describe("AllianceBehavior.handleAllianceRequests", () => { player, new NationEmojiBehavior(random, game, player), ); + + while (game.inSpawnPhase()) { + game.executeNextTick(); + } }); function setupAllianceRequest({ @@ -59,6 +63,7 @@ describe("AllianceBehavior.handleAllianceRequests", () => { numTilesPlayer = 10, numTilesRequestor = 10, alliancesCount = 0, + createdAtTick = game.ticks() + 1, } = {}) { if (isTraitor) requestor.markTraitor(); @@ -82,7 +87,7 @@ describe("AllianceBehavior.handleAllianceRequests", () => { const mockRequest = { requestor: () => requestor, recipient: () => player, - createdAt: () => 0 as unknown as Tick, + createdAt: () => createdAtTick as unknown as Tick, accept: vi.fn(), reject: vi.fn(), } as unknown as AllianceRequest; @@ -92,6 +97,16 @@ describe("AllianceBehavior.handleAllianceRequests", () => { return mockRequest; } + test("should reject alliance created on first post-spawn tick", () => { + const cutoff = game.config().numSpawnPhaseTurns() + 1; + const request = setupAllianceRequest({ createdAtTick: cutoff }); + + allianceBehavior.handleAllianceRequests(); + + expect(request.accept).not.toHaveBeenCalled(); + expect(request.reject).toHaveBeenCalled(); + }); + test("should accept alliance when all conditions are met", () => { const request = setupAllianceRequest({});