zizi 856a583325 feat(tier2): Phaser 自治生成引擎全栈首落(workflow 9 agent 建·6c6g 静态全过)
override spike-first(创始人 06-23 裁),全栈铺 Phaser tier2 引擎。recon→契约→6模块并行→集成 workflow 产出 ~60 文件,全 6c6g 静态校验过、红线零碰 Tier0/1 产线:
- 契约(6):tier2-source-project.schema / tier2-verdict.schema(fork·round放开·三层校验+富游戏门)/ toolkit 签名 / 探针钩子 / boot-phaser-host.d.ts / mini-肥鹅 fixture 规格
- M1 Phaser scaffold+装载 host+引擎能力面(19;logic-smoke 13/13:五耦合点真接线+赢输双路径+latch不回弹)
- M2 CDP 探针 Phaser 重写+business-sim driver+九门+富游戏三门(5;verdict 过 schema)
- M3 Python 单写 ReAct agent loop+9 工具 toolkit+M3 Anthropic接法+四熔断(9;mini-desktop 真 2.0.2 venv 验:import/9工具/中间件注册 OK)
- M4 datatable schema+金标+资产占位(4)/ M5 prompt+Phaser skill+rag(13)/ M6 成本 RecordingChatModel+trace adapter(3)
- 集成:run_engine.py 入口 + 接线断点已修 + RUN-ON-MINI-DESKTOP.md

待 mini-desktop 真跑(esbuild build + CDP 九门 + M3 生成 = 本质即 0号 spike)。AgentScope 2.0.2 API 逐条对 /root/oss/agentscope 源码核验。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:13:44 +00:00

157 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: phaser-rich-game
description: 教 agent 在 Phaser 3 上写多系统经营富游戏(合成/订单/资源耦合)——scene 树搭建、输入命中、纯逻辑系统与渲染分离、与 tier2 第二装载分支宿主契约对接(boot/readState/destroy、latch 终态、语义 state 导出)、esbuild 多文件构建、避开十类常见崩点。tier2 单写 ReAct agent 写 Phaser 工程时按名取它。
---
# 在 Phaser 3 上写经营富游戏
这份 skill 教你把一款多系统经营富游戏(合成 / 订单 / 资源相互咬合)写成一个 **Phaser 3 多文件 src/ 源工程**,并让它对接 tier2 第二装载分支宿主、过验收门。它假设你已经读了 `code-writer-system` 的工作纪律(九工具、ReAct 循环、五条铁律);本 skill 补的是「具体到 Phaser 怎么写」那一层。
拿不准某个 Phaser API 的确切签名时,查本 skill 的 `rag/` 语料(精选的 Phaser 关键 API 与范式片段),别凭记忆编不存在的 API——Phaser 的 API 面很大,编出来的 API 跑不起来会浪费一整轮真玩。
## 一、工程骨架长什么样
tier2 的 Phaser 工程是多文件 `src/`,入口恒为 `src/main.js`。`scaffold_init` 已经给你铺好先天过 boot 的骨架(scene 树脚手架 + 三系统骨架 + 留空数据表)。典型文件树:
```
src/
main.js # 入口:esbuild 从这里起手。默认导出工厂 (opts?) => PhaserGameInstance
scenes/
BootScene.js # 载资产、建系统、置全局上下文,然后 start PlayScene
PlayScene.js # 主玩法画面 + 输入(棋盘、点击命中、合成动画)
UIScene.js # 叠层 HUD / 订单面板(与 PlayScene 并行运行,不是切换)
systems/
resource-system.js # 纯逻辑:货币 + 库存,命令式 API(addCoins/spendCoins/consume/add)
merge-system.js # 纯逻辑:3×3 棋盘合成判定,真调 resource 命令
order-system.js # 纯逻辑:订单 patience 倒计时,完单真调 resource.addCoins
data/
tables.js # 数据表:mergeChains / orders / 物品 / 平衡数值
GameInstance.js # 实现宿主契约 boot/readState/injectInput?/destroy 的工厂主体
```
`role` 字段(fileTree):main.js=entry,scenes/*=scene,systems/*=system,data/*=config,GameInstance.js=lib。
## 二、最该守住的一条:纯逻辑系统与渲染彻底分离
这是富游戏写对的命门,也是三联动门、经济门、latch 门能不能过的根。
**systems/ 下的文件是纯逻辑**:零 DOM、零 canvas、零 Phaser import、零裸随机。它们维护游戏状态、暴露命令式 API、返回 `{ok, reason}`,全程不知道有没有屏幕。这样三件事才成立:
1. **真接线可被机器验证**:合成消耗真调 `resource.consumeIngredient(itemId, n)`、完单真调 `resource.addCoins(n)`——系统内部记调用计数,三联动门据计数证「真调了资源系统」,而非自绘伪装。如果你在 scene 里直接 `this.coins += 10`,三联动门会判你没真接线。
2. **确定性可重放**:随机经注入的受控源(`{ next: () => number }`),不裸 `Math.random()`。harness 用固定 seed 驱动,每次跑出一样的结果。
3. **逻辑可静态检查**:数据表可达性(每个订单要的物品都能被合成链产出)、合成 DAG 无环,都能脱离渲染静态算出来。
**scenes/ 下的文件是渲染 + 输入**:画棋盘格、画 HUD、把点击坐标映射到棋盘格、播合成动画。scene 调系统的命令式 API(`mergeSystem.mergeAt(i, j)`),拿返回值决定怎么播反馈,**绝不在 scene 里重写逻辑或直接改系统内部状态**。
参照 `tier2/fixtures/mini-fei-e/src/systems/` 三个已预建的纯逻辑系统——它们就是这条分离的样板(命令式 API + `{ok,reason}` + 调用计数 + 受控随机)。你写表现层时调它们,不重写它们。
## 三、scene 树怎么搭
```js
// src/main.js —— 工厂:返回实现宿主契约的实例。esbuild 入口。
import Phaser from 'phaser';
import { createGameInstance } from './GameInstance.js';
// 默认导出工厂;esbuild 打包后挂 buildProfile.globalName,宿主取它建实例。
export default function gameFactory(opts) {
return createGameInstance(opts);
}
```
三个要点:
- **BootScene 建系统、置全局上下文**:在 BootScene 里 `new` 出三个纯逻辑系统,按 `game.registry`(Phaser 的跨 scene 数据仓)或挂到一个共享上下文对象上,让 PlayScene / UIScene 都拿得到同一组系统实例。系统是 single source of truth,多个 scene 共享它。
- **UIScene 叠层并行,不是切换**:`this.scene.launch('UIScene')` 让 UIScene 与 PlayScene 同时运行(HUD 浮在玩法画面上);`this.scene.start('Xxx')` 是切换(停当前、起新的)。订单面板 / 金币 HUD 用 launch 叠层。
- **资产在 preload 载、create 里建对象**:`preload()` 里 `this.load.image(key, url)`,`create()` 里 `this.add.image/sprite/rectangle`。资产从 boot 注入的 `assets` 来(按 ref 键),缺失则降级用 `this.add.rectangle` 画色块占位——别因为缺图就崩。
## 四、输入命中:把点击坐标映射到棋盘格
经营富游戏最常见的输入是「点棋盘格」「点订单」。Phaser 的命中有两条路,挑一条用:
- **交互对象路(推荐)**:给每个棋盘格建一个 `this.add.rectangle(x, y, w, h).setInteractive()`,监听 `'pointerdown'`,回调里已知这是哪个格(闭包捕获 i,j)。命中由 Phaser 算,不用自己做坐标反算。
- **全局指针路**:监听 `this.input.on('pointerdown', p => ...)`,用 `p.x, p.y` 自己反算落在哪个格(`col = Math.floor((p.x - boardX) / cellW)`)。灵活但易错,注意 Phaser 指针坐标已是逻辑像素(scale 处理过),别再除像素比。
命中到格后,调系统命令(`mergeSystem.selectCell(i,j)` 或 `mergeSystem.mergeAt(...)`),据返回值播反馈。**点击只触发逻辑命令,不直接改状态。**
逻辑像素 390×844 竖屏:Phaser scale 用 `{ mode: Phaser.Scale.FIT, width: 390, height: 844 }`,这样你按 390×844 布局,Phaser 自动缩放适配真实屏幕,指针坐标也归一到这个逻辑空间。
## 五、与宿主契约对接(A4 第二装载分支)
宿主契约在 `tier2/contracts/boot-phaser-host.d.ts`。你的 `GameInstance.js` 要返回一个实现这四个方法的对象:
```js
// src/GameInstance.js —— 实现 PhaserGameInstance 契约。
export function createGameInstance(opts) {
let phaserGame = null;
let systems = null; // { resource, merge, order } —— 纯逻辑系统,readState 的真相源
return {
// boot(ctx):一次性启动。ctx = { game?, canvas, seed, assets? }。
// 注意:tier2 宿主可能注入它已建好的 Phaser.Game(ctx.game),也可能让你自建——
// 按宿主实际约定来(看 boot-phaser-host.js)。用 ctx.seed 派生所有随机,禁裸 Math.random。
async boot(ctx) {
systems = buildSystems(ctx.seed); // 建三个纯逻辑系统(注入受控随机)
phaserGame = new Phaser.Game({ // 或用 ctx.game(按宿主约定)
type: Phaser.AUTO,
scale: { mode: Phaser.Scale.FIT, width: 390, height: 844 },
scene: [BootScene, PlayScene, UIScene],
parent: ctx.canvas?.parentElement,
callbacks: { postBoot: g => g.registry.set('systems', systems) }, // 共享系统给各 scene
});
},
// readState():返回可观测语义 state。phase 是跨品类终态不变量;其余 per-品类。
// 只读、只供测试——渲染层与玩家界面绝不可显示它、不得据它给提示。
readState() {
return {
phase: systems.order.phase, // 'title'|'play'|'win'|'lose'|'gameover'(latch 终态)
coins: systems.resource.state.coins, // per-品类语义字段
ingredients: { ...systems.resource.state.ingredients },
orders: systems.order.snapshot(),
};
},
// injectInput(ev)(可选):harness 驱动入口。若 scene 已订阅 Phaser input,可省。
injectInput(ev) { /* 把 tap/key/drag 转成对系统命令的调用,或经 Phaser input 投递 */ },
// destroy():卸载卫生,幂等。必须 game.destroy(true) 释放 Phaser 资源(多游戏复位/session 切换)。
destroy() { if (phaserGame) { phaserGame.destroy(true); phaserGame = null; } },
};
}
```
## 六、latch 终态(游戏无 emit 通道,硬约束)
游戏没有 emit 通道向宿主报「我赢了」。终态走 latch:
- 跑到赢(金币达阈值)或输(连续 3 订单流失)时,把 `readState().phase` **焊成**终态值(`'win'`/`'lose'`),并**驻留不回弹**——后续帧 readState 读到的 phase 恒定,绝不改回 `'play'`。
- 宿主每帧轮询 `readState().phase` 判终态落定。latch 门会多帧轮询验「终态不回弹」。
- 实现上:在 order-system 里维护一个 `_phase`,落定终态后加锁(`if (this._phase === 'win' || this._phase === 'lose') return;` 在更新开头早返回),后续任何输入都不改它。
## 七、esbuild 多文件构建
`build` 工具用 esbuild 把 `src/main.js` 起手的多文件工程打成一个 iife bundle(入口 main.js,挂 `buildProfile.globalName`,默认沿用 `__GameBundle`)。你只管写对 import 关系:
- 用 ES module import(`import Phaser from 'phaser'`、`import { createMergeSystem } from '../systems/merge-system.js'`)。相对路径要对、带 `.js` 后缀(esbuild 默认不补)。
- import 图必须无环(A → B → A 会让 headless_check 挂)。系统之间用依赖注入(merge 收 resource 作为构造参数),别互相 import 成环。
- phaser 是外部依赖(depLock.phaser 锁版本,如 `3.80.1`),由 buildProfile 决定打进 bundle 还是外置。别 import 不在 depLock 里的包。
构建失败时 `build` 回你完整 esbuild 报错——按报错的文件:行定位,改 import 路径 / 语法,别整文件重写。
## 八、十类常见崩点(踩过的坑,绕开)
1. **在 scene 里写逻辑 / 直接改系统状态** → 三联动门挂。逻辑只在 systems/,scene 只调命令。
2. **裸 `Math.random()`** → 确定性重放失败、harness 驱动不可复现。随机从注入 seed 派生。
3. **import 路径漏 `.js` 后缀或写错相对层级** → esbuild 报 "Could not resolve"。
4. **import 成环**(系统互相 import)→ headless_check 挂。用依赖注入破环。
5. **终态 phase 回弹**(落定 win 后又被更新改回 play)→ latch 门挂。终态加锁早返回。
6. **`scene.start` 当 `scene.launch` 用**(HUD 切换掉了玩法画面)→ 画面只剩 HUD。叠层用 launch。
7. **系统实例没在 scene 间共享**(各 scene 各 new 一份)→ HUD 显示的金币和玩法里的对不上。系统建一次,经 registry / 共享上下文给所有 scene。
8. **readState 暴露给玩家界面 / 据它给提示** → 违反「只供测试」铁律。语义 state 渲染层不显示。
9. **缺资产就崩**(preload 的图加载失败未兜底)→ A_boot 挂。缺图降级用 `add.rectangle` 色块占位。
10. **destroy 不幂等 / 不 `game.destroy(true)`** → 多游戏切换内存泄漏、canvas 残留。destroy 必须幂等且真释放 Phaser.Game。
## 九、靶子参照
mini-肥鹅 是 0号 spike 的对照靶子(`tier2/contracts/mini-fei-e-fixture-spec.md`):三系统(资源 / 合成 / 订单)+ 五耦合点 + 12 物品 / 6 合成链 / 5 订单,赢=金币 20→100,输=连续 3 流失。`tier2/fixtures/mini-fei-e/src/systems/` 是它三个纯逻辑系统的预建样板——你写 Phaser 表现层时调它们,照它的命令式 API + `{ok,reason}` + 调用计数范式,把这套分离推广到任意经营题面。