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

12 KiB
Raw Permalink Blame History

name, description
name description
phaser-rich-game 教 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.jsscaffold_init 已经给你铺好先天过 boot 的骨架scene 树脚手架 + 三系统骨架 + 留空数据表)。典型文件树:

src/
  main.js              # 入口esbuild 从这里起手。默认导出工厂 (opts?) => PhaserGameInstance
  scenes/
    BootScene.js       # 载资产、建系统、置全局上下文,然后 start PlayScene
    PlayScene.js       # 主玩法画面 + 输入(棋盘、点击命中、合成动画)
    UIScene.js         # 叠层 HUD / 订单面板(与 PlayScene 并行运行,不是切换)
  systems/
    resource-system.js # 纯逻辑:货币 + 库存,命令式 APIaddCoins/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 字段fileTreemain.js=entryscenes/=scenesystems/=systemdata/*=configGameInstance.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 调系统的命令式 APImergeSystem.mergeAt(i, j)),拿返回值决定怎么播反馈,绝不在 scene 里重写逻辑或直接改系统内部状态

参照 tier2/fixtures/mini-fei-e/src/systems/ 三个已预建的纯逻辑系统——它们就是这条分离的样板(命令式 API + {ok,reason} + 调用计数 + 受控随机)。你写表现层时调它们,不重写它们。

三、scene 树怎么搭

// 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.registryPhaser 的跨 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 要返回一个实现这四个方法的对象:

// 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.Gamectx.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.jsbuildProfile.globalName,默认沿用 __GameBundle)。你只管写对 import 关系:

  • 用 ES module importimport 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.startscene.launchHUD 切换掉了玩法画面)→ 画面只剩 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} + 调用计数范式,把这套分离推广到任意经营题面。