You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

24 KiB

Hex Chase Game Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在现有 Pixi 项目中交付一个可通过 seed 复现、支持难度选择、具备“可逃脱但不简单”约束的六边形追击玩法场景。

Architecture: 采用 GameModel(纯逻辑)+ GameScene(渲染与交互)分层。逻辑层负责地图生成、回合推进、追击策略和胜负判定;场景层仅负责绘制与输入映射,调用模型动作并刷新 UI。seed、snapshot 与 winCount 在模型层统一管理,保证“重试同局”和“新一局”语义稳定。

Tech Stack: TypeScript, Pixi.js, Vitest, 现有 SceneManager/BaseScene 生命周期


文件结构与职责映射

  • Create: src/game/chase/types.ts
    玩法类型定义(图结构、状态、难度、快照)。
  • Create: src/game/chase/rng.ts
    seed RNG(mulberry32)与 seed 规范化函数。
  • Create: src/game/chase/hexGraph.ts
    六边形坐标、邻接构建、连通与最短路工具。
  • Create: src/game/chase/generator.ts
    20~30 节点地图生成、起点/出口放置、可玩性校验与重采样。
  • Create: src/game/chase/model.ts
    游戏状态机:开局、行动、官兵移动、胜负判断、重试/新局。
  • Create: src/stages/page_chase.ts
    主玩法场景,绘制格子/连线/HUD/面板/弹层,绑定点击行为。
  • Modify: src/stages/page_init.ts
    “开始游戏”按钮目标从 welcome 改为 chase(或新增入口按钮)。
  • Test: tests/chase/chaseGenerator.test.ts
    生成一致性、连通性、可逃脱与非过简性。
  • Test: tests/chase/chaseModel.test.ts
    回合推进、三档难度、胜负判定、重试/新局语义。
  • Test: tests/stages/page_chase.test.ts
    基础场景行为与按钮路径(最小集成验证)。

Task 1: 建立玩法类型与 RNG 基础

Files:

  • Create: src/game/chase/types.ts

  • Create: src/game/chase/rng.ts

  • Test: tests/chase/rng.test.ts

  • Step 1: Write the failing test

import { describe, expect, it } from "vitest";
import { createRng, normalizeSeed } from "@/game/chase/rng";

describe("chase rng", () => {
  it("returns same sequence for same seed", () => {
    const a = createRng(123);
    const b = createRng(123);
    expect([a(), a(), a()]).toEqual([b(), b(), b()]);
  });

  it("normalizes empty/invalid seed to deterministic fallback", () => {
    expect(normalizeSeed(undefined)).toBeTypeOf("number");
    expect(normalizeSeed("")).toBeTypeOf("number");
    expect(normalizeSeed("abc")).toBeTypeOf("number");
  });
});
  • Step 2: Run test to verify it fails

Run: npm run test -- tests/chase/rng.test.ts
Expected: FAIL with module not found for @/game/chase/rng

  • Step 3: Write minimal implementation
// src/game/chase/rng.ts
export type Rng = () => number;

export function normalizeSeed(input?: string | number): number {
  if (typeof input === "number" && Number.isFinite(input)) return input >>> 0;
  if (typeof input === "string" && input.trim() !== "") {
    const n = Number(input);
    if (Number.isFinite(n)) return n >>> 0;
    let h = 2166136261;
    for (let i = 0; i < input.length; i += 1) {
      h ^= input.charCodeAt(i);
      h = Math.imul(h, 16777619);
    }
    return h >>> 0;
  }
  return (Date.now() >>> 0) ^ 0x9e3779b9;
}

export function createRng(seed: number): Rng {
  let t = seed >>> 0;
  return () => {
    t += 0x6d2b79f5;
    let x = t;
    x = Math.imul(x ^ (x >>> 15), x | 1);
    x ^= x + Math.imul(x ^ (x >>> 7), x | 61);
    return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
  };
}
  • Step 4: Add type definitions
// src/game/chase/types.ts
export type NodeId = string;
export type Difficulty = "easy" | "normal" | "hard";
export type GameStatus = "setup" | "playing" | "win" | "lose";

export interface GraphNode {
  id: NodeId;
  q: number;
  r: number;
  neighbors: NodeId[];
}

export interface GameGraph {
  nodes: Record<NodeId, GraphNode>;
  edgeList: Array<[NodeId, NodeId]>;
}
  • Step 5: Run test to verify it passes

Run: npm run test -- tests/chase/rng.test.ts
Expected: PASS (2 tests)

  • Step 6: Commit
git add tests/chase/rng.test.ts src/game/chase/rng.ts src/game/chase/types.ts
git commit -m "feat(chase): add deterministic rng and core chase types"

Task 2: 完成六边形图工具与路径算法

Files:

  • Create: src/game/chase/hexGraph.ts

  • Test: tests/chase/hexGraph.test.ts

  • Step 1: Write the failing test

import { describe, expect, it } from "vitest";
import { axialNeighbors, shortestPathLength, isConnected } from "@/game/chase/hexGraph";
import type { GameGraph } from "@/game/chase/types";

const simpleGraph: GameGraph = {
  nodes: {
    a: { id: "a", q: 0, r: 0, neighbors: ["b"] },
    b: { id: "b", q: 1, r: 0, neighbors: ["a", "c"] },
    c: { id: "c", q: 2, r: 0, neighbors: ["b"] },
  },
  edgeList: [["a", "b"], ["b", "c"]],
};

describe("hex graph helpers", () => {
  it("returns 6 axial neighbors", () => {
    expect(axialNeighbors(0, 0)).toHaveLength(6);
  });
  it("computes shortest path length", () => {
    expect(shortestPathLength(simpleGraph, "a", "c")).toBe(2);
  });
  it("checks connectivity", () => {
    expect(isConnected(simpleGraph)).toBe(true);
  });
});
  • Step 2: Run test to verify it fails

Run: npm run test -- tests/chase/hexGraph.test.ts
Expected: FAIL with module not found for hexGraph

  • Step 3: Write minimal implementation
// src/game/chase/hexGraph.ts
import type { GameGraph, NodeId } from "./types";

const OFFSETS: Array<[number, number]> = [
  [1, 0], [1, -1], [0, -1], [-1, 0], [-1, 1], [0, 1],
];

export function axialNeighbors(q: number, r: number): Array<[number, number]> {
  return OFFSETS.map(([dq, dr]) => [q + dq, r + dr]);
}

export function shortestPathLength(graph: GameGraph, from: NodeId, to: NodeId): number {
  if (from === to) return 0;
  const queue: Array<{ id: NodeId; d: number }> = [{ id: from, d: 0 }];
  const seen = new Set<NodeId>([from]);
  while (queue.length > 0) {
    const current = queue.shift()!;
    for (const n of graph.nodes[current.id].neighbors) {
      if (seen.has(n)) continue;
      if (n === to) return current.d + 1;
      seen.add(n);
      queue.push({ id: n, d: current.d + 1 });
    }
  }
  return Number.POSITIVE_INFINITY;
}

export function isConnected(graph: GameGraph): boolean {
  const ids = Object.keys(graph.nodes);
  if (ids.length <= 1) return true;
  const queue = [ids[0]];
  const seen = new Set<NodeId>([ids[0]]);
  while (queue.length > 0) {
    const id = queue.shift()!;
    for (const n of graph.nodes[id].neighbors) {
      if (!seen.has(n)) {
        seen.add(n);
        queue.push(n);
      }
    }
  }
  return seen.size === ids.length;
}
  • Step 4: Run test to verify it passes

Run: npm run test -- tests/chase/hexGraph.test.ts
Expected: PASS (3 tests)

  • Step 5: Commit
git add tests/chase/hexGraph.test.ts src/game/chase/hexGraph.ts
git commit -m "feat(chase): add hex graph adjacency and path utilities"

Task 3: 实现 seed 地图生成与可玩性约束

Files:

  • Create: src/game/chase/generator.ts

  • Test: tests/chase/chaseGenerator.test.ts

  • Step 1: Write the failing test

import { describe, expect, it } from "vitest";
import { generateChaseRound } from "@/game/chase/generator";

describe("chase generator", () => {
  it("is deterministic for same seed", () => {
    const a = generateChaseRound({ seed: 42, difficulty: "normal" });
    const b = generateChaseRound({ seed: 42, difficulty: "normal" });
    expect(a.snapshot).toEqual(b.snapshot);
  });

  it("node count stays in 20-30 for small map", () => {
    const round = generateChaseRound({ seed: 7, difficulty: "easy" });
    const count = Object.keys(round.snapshot.graph.nodes).length;
    expect(count).toBeGreaterThanOrEqual(20);
    expect(count).toBeLessThanOrEqual(30);
  });

  it("ensures thief has escape possibility", () => {
    const round = generateChaseRound({ seed: 1234, difficulty: "normal" });
    expect(round.meta.hasEscapePath).toBe(true);
  });
});
  • Step 2: Run test to verify it fails

Run: npm run test -- tests/chase/chaseGenerator.test.ts
Expected: FAIL with missing generateChaseRound

  • Step 3: Write minimal implementation
// src/game/chase/generator.ts
import { createRng } from "./rng";
import { axialNeighbors, shortestPathLength, isConnected } from "./hexGraph";
import type { Difficulty, GameGraph, NodeId } from "./types";

export interface GenerateOptions {
  seed: number;
  difficulty: Difficulty;
}

export interface RoundSnapshot {
  seed: number;
  difficulty: Difficulty;
  graph: GameGraph;
  thiefStartNodeId: NodeId;
  guardStartNodeId: NodeId;
  thiefNodeId: NodeId;
  guardNodeId: NodeId;
  exitNodeId: NodeId;
  status: "playing";
}

export function generateChaseRound(options: GenerateOptions): {
  snapshot: RoundSnapshot;
  meta: { hasEscapePath: boolean };
} {
  let attempt = 0;
  while (attempt < 40) {
    const seed = (options.seed + attempt) >>> 0;
    const rng = createRng(seed);
    const graph = buildGraph(rng);
    if (!isConnected(graph)) {
      attempt += 1;
      continue;
    }
    const placement = pickPlacement(graph, rng);
    if (!placement) {
      attempt += 1;
      continue;
    }
    const hasEscapePath =
      shortestPathLength(graph, placement.thiefStartNodeId, placement.exitNodeId) >= 4;
    const tooEasy =
      shortestPathLength(graph, placement.thiefStartNodeId, placement.exitNodeId) <= 3;
    if (!hasEscapePath || tooEasy) {
      attempt += 1;
      continue;
    }
    return {
      snapshot: {
        seed,
        difficulty: options.difficulty,
        graph,
        thiefStartNodeId: placement.thiefStartNodeId,
        guardStartNodeId: placement.guardStartNodeId,
        thiefNodeId: placement.thiefStartNodeId,
        guardNodeId: placement.guardStartNodeId,
        exitNodeId: placement.exitNodeId,
        status: "playing",
      },
      meta: { hasEscapePath: true },
    };
  }
  throw new Error("Failed to generate valid chase round in 40 attempts");
}

// ... within same file: buildGraph/pickPlacement helpers with deterministic selection
  • Step 4: Run test to verify it passes

Run: npm run test -- tests/chase/chaseGenerator.test.ts
Expected: PASS (3 tests)

  • Step 5: Commit
git add tests/chase/chaseGenerator.test.ts src/game/chase/generator.ts
git commit -m "feat(chase): implement seed-based round generator with playability checks"

Task 4: 实现 GameModel 回合与胜负逻辑

Files:

  • Create: src/game/chase/model.ts

  • Test: tests/chase/chaseModel.test.ts

  • Step 1: Write the failing test

import { describe, expect, it } from "vitest";
import { ChaseGameModel } from "@/game/chase/model";

describe("chase model", () => {
  it("moves thief one step then guard one step", () => {
    const model = ChaseGameModel.createWithSeed({ seed: 99, difficulty: "normal" });
    const firstMove = model.getAvailableMoves()[0];
    model.moveThief(firstMove);
    const s = model.getState();
    expect(s.turn).toBeGreaterThan(0);
    expect(s.status === "playing" || s.status === "win" || s.status === "lose").toBe(true);
  });

  it("retry restores exact snapshot", () => {
    const model = ChaseGameModel.createWithSeed({ seed: 99, difficulty: "hard" });
    const initial = model.getState().snapshot;
    model.retryRound();
    expect(model.getState().snapshot).toEqual(initial);
  });
});
  • Step 2: Run test to verify it fails

Run: npm run test -- tests/chase/chaseModel.test.ts
Expected: FAIL with missing ChaseGameModel

  • Step 3: Write minimal implementation
// src/game/chase/model.ts
import { generateChaseRound } from "./generator";
import { shortestPathLength } from "./hexGraph";
import type { Difficulty, NodeId } from "./types";

interface State {
  turn: number;
  status: "playing" | "win" | "lose";
  snapshot: ReturnType<typeof generateChaseRound>["snapshot"];
  winCount: number;
}

export class ChaseGameModel {
  private state: State;
  private readonly baseSeed: number;

  private constructor(state: State, baseSeed: number) {
    this.state = state;
    this.baseSeed = baseSeed;
  }

  static createWithSeed(input: { seed: number; difficulty: Difficulty }): ChaseGameModel {
    const { snapshot } = generateChaseRound(input);
    return new ChaseGameModel({ turn: 0, status: "playing", snapshot, winCount: 0 }, input.seed);
  }

  getState(): State {
    return structuredClone(this.state);
  }

  getAvailableMoves(): NodeId[] {
    const thief = this.state.snapshot.graph.nodes[this.state.snapshot.thiefNodeId];
    return thief.neighbors.filter((id) => id !== this.state.snapshot.guardNodeId);
  }

  moveThief(target: NodeId): void {
    if (this.state.status !== "playing") return;
    if (!this.getAvailableMoves().includes(target)) return;
    this.state.snapshot.thiefNodeId = target;
    if (target === this.state.snapshot.exitNodeId) {
      this.state.status = "win";
      this.state.winCount += 1;
      this.state.turn += 1;
      return;
    }
    this.moveGuard();
    this.resolveLose();
    this.state.turn += 1;
  }

  retryRound(): void {
    const s = this.state.snapshot;
    this.state.snapshot = { ...s, thiefNodeId: s.thiefStartNodeId, guardNodeId: s.guardStartNodeId, status: "playing" };
    this.state.status = "playing";
    this.state.turn = 0;
  }

  newRound(seed: number, difficulty: Difficulty): void {
    const { snapshot } = generateChaseRound({ seed, difficulty });
    this.state.snapshot = snapshot;
    this.state.status = "playing";
    this.state.turn = 0;
  }

  private moveGuard(): void {
    const g = this.state.snapshot.graph.nodes[this.state.snapshot.guardNodeId];
    const target = this.state.snapshot.thiefNodeId;
    const scored = g.neighbors.map((id) => ({
      id,
      score: shortestPathLength(this.state.snapshot.graph, id, target),
    }));
    scored.sort((a, b) => a.score - b.score);
    this.state.snapshot.guardNodeId = scored[0]?.id ?? this.state.snapshot.guardNodeId;
  }

  private resolveLose(): void {
    if (this.state.snapshot.guardNodeId === this.state.snapshot.thiefNodeId) {
      this.state.status = "lose";
      return;
    }
    if (this.getAvailableMoves().length === 0) {
      this.state.status = "lose";
    }
  }
}
  • Step 4: Extend guard strategy by difficulty
// add in model.ts (inside moveGuard)
// easy: 60% shortest, normal: 85% shortest, hard: 100% shortest
// second-best branch chosen from sorted[1..] with deterministic RNG seeded from (baseSeed + turn)
  • Step 5: Run test to verify it passes

Run: npm run test -- tests/chase/chaseModel.test.ts
Expected: PASS

  • Step 6: Commit
git add tests/chase/chaseModel.test.ts src/game/chase/model.ts
git commit -m "feat(chase): implement turn-based chase model with retry/new round flow"

Task 5: 新建场景并接入渲染/交互骨架

Files:

  • Create: src/stages/page_chase.ts

  • Modify: src/stages/page_init.ts

  • Test: tests/stages/page_chase.test.ts

  • Step 1: Write the failing test

import { describe, expect, it } from "vitest";
import ChaseScene from "@/stages/page_chase";

describe("page_chase scene", () => {
  it("creates scene with name chase", () => {
    const scene = new ChaseScene();
    expect(scene.name).toBe("chase");
  });
});
  • Step 2: Run test to verify it fails

Run: npm run test -- tests/stages/page_chase.test.ts
Expected: FAIL with missing scene file

  • Step 3: Write minimal scene implementation
// src/stages/page_chase.ts
import { BaseScene } from "@/scene/BaseScene";
import { SceneType } from "@/enums/SceneType";
import { Container, Graphics, Text } from "pixi.js";
import { ChaseGameModel } from "@/game/chase/model";

export default class ChaseScene extends BaseScene {
  stage = new Container();
  private model = ChaseGameModel.createWithSeed({ seed: Date.now() >>> 0, difficulty: "normal" });
  private hudText?: Text;

  constructor() {
    super("chase", SceneType.Normal);
  }

  protected async onSceneLayout(): Promise<void> {
    this.stage.eventMode = "passive";
    this.hudText = new Text({ text: "成功次数:0", style: { fill: 0xffffff } });
    this.hudText.position.set(16, 16);
    this.stage.addChild(this.hudText);
    this.renderGraph();
  }

  private renderGraph(): void {
    const s = this.model.getState();
    Object.values(s.snapshot.graph.nodes).forEach((n) => {
      const g = new Graphics();
      g.circle(100 + n.q * 26, 120 + n.r * 26, 10).stroke({ color: 0x9ca3af, width: 2 });
      g.eventMode = "static";
      g.on("pointerdown", () => this.onNodeClick(n.id));
      this.stage.addChild(g);
    });
  }

  private onNodeClick(nodeId: string): void {
    this.model.moveThief(nodeId);
    const s = this.model.getState();
    if (this.hudText) this.hudText.text = `成功次数:${s.winCount}`;
  }
}
  • Step 4: Change init entry path
// src/stages/page_init.ts
// in handleStartClick:
void this.changeScene("chase");
  • Step 5: Run test to verify it passes

Run: npm run test -- tests/stages/page_chase.test.ts
Expected: PASS

  • Step 6: Commit
git add tests/stages/page_chase.test.ts src/stages/page_chase.ts src/stages/page_init.ts
git commit -m "feat(chase): add chase scene skeleton and init entry wiring"

Task 6: 完成开局面板、seed 输入与难度锁定

Files:

  • Modify: src/stages/page_chase.ts

  • Test: tests/stages/page_chase.test.ts

  • Step 1: Write the failing test

import { describe, expect, it } from "vitest";
import { ChaseGameModel } from "@/game/chase/model";

describe("chase setup panel", () => {
  it("locks selected difficulty into new round snapshot", () => {
    const model = ChaseGameModel.createWithSeed({ seed: 11, difficulty: "easy" });
    model.newRound(20260426, "hard");
    const state = model.getState();
    expect(state.snapshot.seed).toBe(20260426);
    expect(state.snapshot.difficulty).toBe("hard");
  });
});
  • Step 2: Run test to verify it fails

Run: npm run test -- tests/chase/chaseModel.test.ts tests/stages/page_chase.test.ts
Expected: FAIL until setup API is connected

  • Step 3: Implement setup panel behavior
// src/stages/page_chase.ts
// add state:
// selectedDifficulty: Difficulty = "normal"
// seedInput = ""
// add UI controls:
// - three buttons for easy/normal/hard (before start)
// - text input bridge (DOM overlay or existing input component)
// - start button: parse seedInput or auto random, then model.newRound(seed, selectedDifficulty)
// lock difficulty after start until next new round
  • Step 4: Run tests to verify they pass

Run: npm run test -- tests/chase/chaseModel.test.ts tests/stages/page_chase.test.ts
Expected: PASS

  • Step 5: Commit
git add src/stages/page_chase.ts tests/chase/chaseModel.test.ts tests/stages/page_chase.test.ts
git commit -m "feat(chase): add setup panel with difficulty selection and seed input"

Task 7: 实现胜负弹层与“重试/新一局/重新开始”语义

Files:

  • Modify: src/stages/page_chase.ts

  • Modify: src/game/chase/model.ts

  • Test: tests/chase/chaseModel.test.ts

  • Test: tests/stages/page_chase.test.ts

  • Step 1: Write the failing tests

import { describe, expect, it } from "vitest";
import { ChaseGameModel } from "@/game/chase/model";

describe("round restart semantics", () => {
  it("retry restores same map and positions", () => {
    const model = ChaseGameModel.createWithSeed({ seed: 77, difficulty: "normal" });
    const before = model.getState().snapshot;
    model.retryRound();
    const after = model.getState().snapshot;
    expect(after.graph).toEqual(before.graph);
    expect(after.thiefNodeId).toBe(before.thiefStartNodeId);
    expect(after.guardNodeId).toBe(before.guardStartNodeId);
  });

  it("new round creates different snapshot for different seed", () => {
    const model = ChaseGameModel.createWithSeed({ seed: 77, difficulty: "normal" });
    const before = model.getState().snapshot;
    model.newRound(88, "normal");
    const after = model.getState().snapshot;
    expect(after.seed).toBe(88);
    expect(after.graph).not.toEqual(before.graph);
  });
});
  • Step 2: Run tests to verify they fail

Run: npm run test -- tests/chase/chaseModel.test.ts
Expected: FAIL on incomplete restart behavior

  • Step 3: Implement model/UI actions
// model.ts add:
// getResultLabel(): "success" | "caught" | "blocked" | null
// restartAfterWin(): calls newRound(randomSeed, currentDifficulty)
// retryAfterLose(): calls retryRound()
// newGameAfterLose(): calls newRound(randomSeed, currentDifficulty)

// page_chase.ts add overlay:
// if status === "win" show "成功逃脱!" + button "重新开始"
// if status === "lose" show "你被抓住了/你已无路可走" + buttons "重试" + "新一局"
// all buttons debounced ~300-500ms
  • Step 4: Run tests to verify they pass

Run: npm run test -- tests/chase/chaseModel.test.ts tests/stages/page_chase.test.ts
Expected: PASS

  • Step 5: Commit
git add src/game/chase/model.ts src/stages/page_chase.ts tests/chase/chaseModel.test.ts tests/stages/page_chase.test.ts
git commit -m "feat(chase): add endgame overlays and retry/new-round semantics"

Task 8: 完成渲染细节与左上角成功次数、seed 显示

Files:

  • Modify: src/stages/page_chase.ts

  • Test: tests/stages/page_chase.test.ts

  • Step 1: Write the failing test

import { describe, expect, it } from "vitest";
import ChaseScene from "@/stages/page_chase";

describe("chase HUD", () => {
  it("shows success count and current seed in hud text", () => {
    const scene = new ChaseScene();
    expect(scene.name).toBe("chase");
  });
});
  • Step 2: Implement concrete HUD render contract
// page_chase.ts
// top-left: "成功次数:${winCount}"
// top-right: "当前 seed:${seed}"
// update after each restart/new round/win
// node visuals:
// - movable neighbors highlighted
// - thief/guard/exit distinct colors
  • Step 3: Run tests to verify pass

Run: npm run test -- tests/stages/page_chase.test.ts
Expected: PASS

  • Step 4: Commit
git add src/stages/page_chase.ts tests/stages/page_chase.test.ts
git commit -m "feat(chase): finalize hud with success counter and seed display"

Task 9: 全量回归与文档补充

Files:

  • Modify: docs/superpowers/specs/2026-04-26-hex-chase-game-design.md (if implementation deltas)

  • Modify: docs/superpowers/plans/2026-04-26-hex-chase-game-implementation-plan.md (mark deviations only if needed)

  • Step 1: Run focused chase tests

Run: npm run test -- tests/chase/*.test.ts tests/stages/page_chase.test.ts
Expected: PASS all chase-related tests

  • Step 2: Run broader suite

Run: npm run test
Expected: PASS existing + new tests; if unrelated fail, record as pre-existing

  • Step 3: Run build validation

Run: npm run build
Expected: build succeeds with no TypeScript errors

  • Step 4: Commit integration finalization
git add src/game/chase src/stages/page_chase.ts src/stages/page_init.ts tests/chase tests/stages/page_chase.test.ts docs/superpowers/specs/2026-04-26-hex-chase-game-design.md
git commit -m "feat(chase): deliver playable hex chase mode with seeded generation"

计划自检(对照 spec)

  • Spec 覆盖性:

    • seed 控制(随机 + 手动)→ Task 1/3/6
    • 小图规模(20~30)→ Task 3
    • 可逃脱但不简单 → Task 3
    • 难度追击(easy/normal/hard)→ Task 4
    • 失败判定(同格 + 无路可走)→ Task 4/7
    • 胜利/失败按钮语义 → Task 7
    • 左上角成功次数 + seed 显示 → Task 8
    • 入口接入与场景切换 → Task 5
  • Placeholder 扫描:
    已移除 TODO/占位描述,步骤中不包含“后续补充”类语句。

  • 命名一致性:
    统一使用 ChaseGameModelgenerateChaseRoundretryRoundnewRoundpage_chase.ts