From 3692b59ccc8dcdef9c2e8c35f76ad01410914c9b Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Sun, 26 Apr 2026 23:22:17 +0800 Subject: [PATCH] fix(chase): enforce distinct start positions in round generation Made-with: Cursor --- src/game/chase/generator.ts | 9 ++++++++- tests/chase/chaseGenerator.test.ts | 23 +++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/game/chase/generator.ts b/src/game/chase/generator.ts index 4f4abdb..3321983 100644 --- a/src/game/chase/generator.ts +++ b/src/game/chase/generator.ts @@ -174,7 +174,13 @@ function generateGraphAttempt(seed: number, difficulty: Difficulty): ChaseRound const exitNodeId = farthest.id; const pathLength = shortestPathLength(graph, thiefStartNodeId, exitNodeId); const farthestFromExit = getFarthestNode(graph, exitNodeId); - const guardStartNodeId = farthestFromExit.id; + let guardStartNodeId = farthestFromExit.id; + if (guardStartNodeId === thiefStartNodeId) { + const fallbackCandidates = existingNodeIds.filter( + (nodeId) => nodeId !== thiefStartNodeId, + ); + guardStartNodeId = fallbackCandidates[0] ?? thiefStartNodeId; + } return { snapshot: { @@ -206,6 +212,7 @@ export function generateChaseRound(input: GenerateChaseRoundInput): ChaseRound { const isPlayable = isConnected(round.snapshot.graph) && round.meta.hasEscapePath && + round.snapshot.guardStartNodeId !== round.snapshot.thiefStartNodeId && round.meta.pathLength >= minPathLength; if (isPlayable) { diff --git a/tests/chase/chaseGenerator.test.ts b/tests/chase/chaseGenerator.test.ts index 72c7a16..b261c78 100644 --- a/tests/chase/chaseGenerator.test.ts +++ b/tests/chase/chaseGenerator.test.ts @@ -40,8 +40,8 @@ describe("chase round generator", () => { "2,-1", "1,-2", ], - "guardNodeId": "4,-5", - "guardStartNodeId": "4,-5", + "guardNodeId": "0,0", + "guardStartNodeId": "0,0", "hasEscapePath": true, "nodeCount": 20, "seed": 424242, @@ -71,4 +71,23 @@ describe("chase round generator", () => { expect(pathLength).toBeGreaterThan(0); expect(pathLength).toBeLessThan(Infinity); }); + + it("ensures thief and guard do not start on the same node", () => { + const round = generateChaseRound({ seed: 123456, difficulty: "normal" }); + expect(round.snapshot.thiefStartNodeId).not.toBe(round.snapshot.guardStartNodeId); + }); + + it("stays playable and stable across many seeds", () => { + for (let seed = 1; seed <= 60; seed += 1) { + const round = generateChaseRound({ seed, difficulty: "normal" }); + const nodeCount = Object.keys(round.snapshot.graph.nodes).length; + + expect(nodeCount).toBeGreaterThanOrEqual(20); + expect(nodeCount).toBeLessThanOrEqual(30); + expect(round.meta.hasEscapePath).toBe(true); + expect(round.snapshot.thiefStartNodeId).not.toBe( + round.snapshot.guardStartNodeId, + ); + } + }); });