feat(cheap-gen): W-AXIS-V2 波2——取证契约退役+提示全加载链清洗,四门∧测试员为唯一验收链
opus 执行位交付、主会话四步验收过(三套测试亲跑/红线 diff 亲读/冒烟截图亲眼): - 验收消费:check 新增源码级插件调用存在断言(_pluginCallPresenceErrors 词法层, 接替 F_wiring 真接线护栏);_forensicsView「必须实现」拦截摘除,降级为可选调试 接口(handleTap 输入契约与 ctx.log 日志纪律保留);gate_judge 放行措辞对齐四门 - play-spec/驱动器退役(逐件裁定 10 件):gen.mjs done 门自动产 play-spec 摘除、 cheap_gates/cheap_studio/cheap_modify/service_driver 调用摘除、_read_driver_type 改读 evidence/playtest/playtest.json;golden-spec 三工具(auto_vs_golden/bake_off/ compare_node)显式封存;ensure_play_spec 两实现保留一个版本窗口(只摘调用不删码) - prompt 全加载链清洗:cheap-system.md v1.6.3→1.7.0(删 _forensicsView 自动验收 契约红线与全部 targets/occupied 语义,五法句/品类指路同步),cheap_roles 内置 回退逐字节同步(test_roles 12/12 证 parity),modify 切片经运行时切块自动同步; 5 品类 skills+littlejs 手册+recipes×2+模板 8 件+README 6 件教学清洗,真设计 价值(单击 casual/可解性质/双层反馈/latch)保留 - 批账:hard_genre_batch/xtheme 行加 playtest 段,_row_total_cost 修 shadow 下 测试员成本漏算(v2 取 max 防双计) 验收(主会话亲验):pytest cheap-worker 512 绿 + tier2 141 绿 + node 49/49; 25 局基线新旧口径重放对照——真坏 5 局全拦(floor 拦 2/测试员拦 3)、无一例旧拦 新放,翻案 2 局均为破案坐实的仪器误杀;n=3 冒烟(mode=v2)全链绿:w2v2-sim harness verdict.pass=True 而测试员 reject(机械放行测试员逮住,盘上双证)、 w2v2-sim-fix 现象反馈两轮回喂→fix2 收敛(配方显示修成,局内截图亲眼); rg 零残留(模板残留均为「验收不读它」的降级调试接口合规形态);真跑 ¥8.4。 已知余项:repairFeedback 有产地、Service 续修环消费者未接(测试员 reject 不入 RepairMiddleware,续修判据仍四门)——是否接入随波3 校准数据一并裁;v1 配置级 回退窗口关闭为创始人知悉项,genconfig 默认 mode 仍 shadow、切 v2 归创始人。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
79313bf2f4
commit
8d851ea716
@ -34,7 +34,7 @@ description: "当为非遗/传统技艺题材 brief 产设计阶段玩法方案
|
||||
| 范式 | 一句话 | 我们运行时怎么落(插件) |
|
||||
|---|---|---|
|
||||
| **工序节拍**(品类骨架默认) | 一件作品 = 一条 ≥3 道的工序链,工序依次「就绪」,在火候窗口内点掉 → 出成品;错过窗口回炉 | `_template-feiyi` 已接好整条循环:session-score 计工钱 + timer-scheduler 限时 + hud-ui 工位/窗口条 + particles-juice 出彩 + audio-music 音效 |
|
||||
| **图样拼合** | 按纹样图谱点亮格子/部件,拼出一幅完整图样(剪纸/织纹/年画式的构图感) | core 网格状态 + hud-ui 绘格 + 点亮判定走 targets occupied |
|
||||
| **图样拼合** | 按纹样图谱点亮格子/部件,拼出一幅完整图样(剪纸/织纹/年画式的构图感) | core 网格状态 + hud-ui 绘格 + 单击命中点亮判定 |
|
||||
| **节律配合** | 跟着节拍点(舞龙鼓点/号子),命中节拍窗口得分、连中叠倍 | timer-scheduler 节拍源 + gamefeel ComboWindow + audio-music |
|
||||
| **工坊经营** | 接订单→按工序制作→交付收钱(非遗 × 经营的复合;经营范式见 sim-business §1) | 工序节拍循环 + 订单表;别超 2 主机制 |
|
||||
| **收集图鉴** | 每件成品入册,凑齐一套解锁下一套 | save-progress 存收集 + hud-ui 图鉴格 |
|
||||
@ -57,7 +57,7 @@ description: "当为非遗/传统技艺题材 brief 产设计阶段玩法方案
|
||||
|
||||
## 3. 数值与节奏(节律张力是本品类的手感命脉)
|
||||
|
||||
- **双层奖励(与自动验收解耦,工程现实)**:**基础分**——每完成一道工序都加分(盲点也能推进,保九门驱动器可过门);**技巧分**——火候窗口内的「正窗」时刻(窗口前 40%)出手给暴击倍率、连续正窗叠 combo。真人靠技巧分玩出高分,驱动器拿基础分过门,两不耽误。
|
||||
- **双层奖励(基础反馈保上手 + 技巧分给深度)**:**基础分**——每完成一道工序都加分(随手点也有正反馈,新手不至于无所适从);**技巧分**——火候窗口内的「正窗」时刻(窗口前 40%)出手给暴击倍率、连续正窗叠 combo。新手随便玩也有反馈、熟手靠技巧分玩出高分,两不耽误。
|
||||
- **工序节奏目标值(design 必给具体区间,别让 code agent 拍脑袋)**:工序就绪间隔 **~0.6–1.0s**(太慢 = 干等,太快 = 手忙);火候窗口 **~5–8s**(过窗回炉,不判死);回炉冷却 **~0.8–1.2s**;一局 **60s**,期望成品 **2–4 件**(工序链 4–6 道);后 20s 可把窗口收紧到 **~3.5–5s** 做赶工段。这些目标值交 core.js 参数区。
|
||||
- **工钱曲线**:后道工序分值高于前道(越接近成品越值钱,弃单代价自然变大);成品奖励 ≈ 单道工序分的 3–5 倍;解锁阈值取「2–3 件成品的工钱」,制造"差一件就解锁"的钩子。
|
||||
- **代价设计**:错过窗口 = 回炉重做该道(损时间不清进度),**不做「一错全毁」**——挫败 = 流失;紧张感来自时间损失与 combo 断裂,不来自惩罚清零。
|
||||
@ -132,7 +132,7 @@ description: "当为非遗/传统技艺题材 brief 产设计阶段玩法方案
|
||||
- ❌ 3D 拉坯 / 自由手势雕刻(连续轨迹判定)→ 2D 点击 + 程序化动效;手感靠窗口时机与 juice,不靠手势精度。
|
||||
- ❌ 知识问答墙 / 长文化说明打断玩法 → 文化经工序名/器物名/氛围体感传达,玩法优先。
|
||||
- ❌ 超过 2 个主机制 → 聚焦 1 条主循环 + 1 成长轴。
|
||||
- ❌ **核心动作做成「先选工具再选工位」两步**:零-LLM 自动验收 driver 只会单击 `occupied:true` 目标、跟不动两步 → 九门 H 验不到进展 = 好游戏被误判 → ✅ **核心动作单击即完成当前工序**(就绪工位 occupied:true,点它 = 该道完成);确需多步,则 `_forensicsView().state().targets` 必须随状态把"下一步该点的"标 occupied:true,让 driver 跟着推进。
|
||||
- ⚠ **核心动作优先单击直给**(点就绪工位 = 该道工序完成):「先选工具再选工位」两步流程抬高上手门槛、拖慢节拍手感——本品类的张力在火候窗口时机,不在选择流程;确需多步,给清晰视觉指引(选中态高亮 + 下一步该点的工位有 affordance),让真人一眼看懂下一步点哪。
|
||||
- ❌ **文化表述红线**:工序、器物、民俗意象用**通识**表述(制陶有塑形上釉、织造有经纬提花这一层常识粒度);**不编造具体非遗项目的失实细节**——不虚构某项目的历史年代/传承人/名录地位/地域归属,不使用"官方认证""名录在册"这类可能失实的措辞;brief 点名了具体项目时,只用其公知常识、拿不准的细节一律泛化成通识写法。
|
||||
- ✅ 留下的:工序节拍、图样拼合、节律配合、收集图鉴、技艺解锁、轻工坊经营 —— 全在 11 注入插件 + 单屏 + 便宜模型可生成范围内。
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ description: "当生成 agent 要写一款可上线 LittleJS 小游戏的 game-l
|
||||
## 0. 三句话心法
|
||||
|
||||
1. **你只写「玩法身份(WHAT)」,通用机制(HOW)一律调插件——能调就别自己实现。** 写碰撞数学/粒子/补间/计分/状态机 = 错;这些是插件。
|
||||
2. **你的唯一产出靶 = `game-logic.js` 的命名导出 `createGame({plugins,bundle,viewport})`**,返回 GameInstance:`init/update/render/destroy` + 输入方法 `handleTap(x,y)`[/`handleKey(key)`] + `_forensicsView()`。**零引擎 import**。
|
||||
2. **你的唯一产出靶 = `game-logic.js` 的命名导出 `createGame({plugins,bundle,viewport})`**,返回 GameInstance:`init/update/render/destroy` + 输入方法 `handleTap(x,y)`[/`handleKey(key)`],另可挂可选调试接口 `_forensicsView()`。**零引擎 import**。
|
||||
3. **受控面铁律**:游戏里 **绝不出现** `Date.now` / `performance.now` / `Math.random` / `setTimeout` / `requestAnimationFrame` / `new AudioContext` / `import 'littlejsengine'`。时间/随机/音频/引擎能力一律经 `boot.ctx` 或注入的插件拿;**输入不调 `ctx.getInput`**——写实例方法 `handleTap(x,y)`/`handleKey(key)`,L1 据此在 pointerdown/keydown 时派发(确定性、可取证、引擎可换)。
|
||||
|
||||
---
|
||||
@ -114,7 +114,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
- **输入收归 L1(别自己订阅)**:不写 `ctx.getInput()`、不自建点击队列、不在 `update` 里挑时机消费点击——只写实例方法 **`handleTap(x,y)`**(点击)/**`handleKey(key)`**(键盘),L1 的 game.js wrapper 在 pointerdown/keydown 时**直达**调用。〔HJ-AGI 实测:M3 把点击塞 `pendingClicks`、在 `update` 的 play 分支(menu early-return 之后)才消费 → 菜单点击永不处理 → 启动不了;收归 L1 结构性根除。check 会拦 `getInput` 与"零输入方法"。〕
|
||||
- **⚠️ 驱动插件 onFrame**:`update(dt)` 内**必须 `bundle.tick(dt)`**(`bundle` 是 createGame 入参)——否则 timer-scheduler/gamefeel 等的 onFrame 不推进、timer 永不到期、一局不结束。
|
||||
- **viewport**:画面口径读入参 `viewport.w/h`,别硬编码 390×844。
|
||||
- **取证面(`_forensicsView()` 必须实现,可测性红线)**:实例必须挂 `_forensicsView()`→`{ state(), measures() }`;`state()` 导出可观测态——至少 `{ phase, score }`,有可点目标则加 `targets:[{x,y,occupied}]`(九门据此验「真活+有进展」+ 驱动)。**targets 要反映「当前该点什么才推进」**:多步动作(如选客→选货)在选中后,把第二步目标(货架商品)也标 `occupied:true` —— 否则零-LLM driver 单击跟不动两步、H 门验不到进展(实测 wanglanmei-v4:两步成单 → driver 只选客不成单 → score 0 误判)。**最稳:核心成单做成单击即完成**。**不写则 check 拦 + 九门 E_live/H_progress 读不到状态**。另可选挂 `_handleAction(action)`(host.do() 走逻辑驱动,便于确定性 e2e)。范式见 `_template/src/game-logic.js`。
|
||||
- **调试视图(`_forensicsView()`,可选)**:挂 `_forensicsView()`→`{ state(), measures() }`,`state()` 导出可观测态(如 `{ phase, score }`)供人工调试观测——W-AXIS-V2 起验收不再读它(验收 = 四门机械投影 ∧ 测试 agent 视觉引导真玩),写不写都不影响过门;保留它是给调试与日志一个统一出口。另可选挂 `_handleAction(action)`(host.do() 走逻辑驱动,便于确定性 e2e)。范式见 `_template/src/game-logic.js`。
|
||||
- **关键事件记日志(`ctx.log`)**:在 init/场景切换/得分/出错处 调 `ctx.log('tag', 信息)`(`ctx`=init(boot).boot.ctx)→ 写 `window.__gameLog`,便于读日志诊断运行时问题。**插件调用已由 host 自动记**(你只补游戏语义事件)。
|
||||
|
||||
---
|
||||
@ -158,7 +158,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
- ✅ **起局/交互按钮的命中矩形在场景 `onEnter`(状态进入点)确定性建立、`render` 只画不写**——绝不让它只在 `render` 副作用里生成(`menuStartBtn = renderMenu(...)` 那种回写):并发渲染帧饥饿时 `render` 可能一次都没跑过 → 命中矩形恒 `null` → 菜单点不动、一局起不来(卡 menu 头号成因)。把按钮几何抽成纯函数,`onEnter` 调它设命中区、`render` 调它画,命中与绘制同源不漂移(`_template*` 六款范例的 `menuStartRect/overRestartRect` 即此写法)。
|
||||
- ❌ scene 的 update 回调臆造首参写成 `update(running, dt)` → ✅ **scene 的 `update` 回调只收一个参数 `(dt)`**(`sceneFsm.define(name, { update(dt){…} })`;`sceneFsm.update(dt)` 只透传 dt,没有 running 之类首参)。写错首参 → 真正的 dt 落到第二参=undefined → `state.elapsedMs += undefined = NaN` → 不再 spawn/计时、计时显示「NaNs」、游戏空转看着像没 bug 实则永不推进(实测 wanglanmei-v2:菜单能进 play 但无顾客、营业额恒 0)。**任何回调签名都以 `src/plugins/<name>/api.d.ts` 为准,别臆造参数。**
|
||||
- ❌ `update(dt)` 把 dt 当毫秒直接累加 `elapsedMs += dt` → ✅ **dt 单位是「秒」(≈1/60)**;游戏内毫秒计时(倒计时/spawn 间隔/耐心,常量如 60000/1300/4800)累加要 **`elapsedMs += dt * 1000`**。漏 ×1000 → 计时慢 1000 倍 → 永不到 spawn 阈值=零顾客、一局永不结束(实测 wanglanmei-v2 翻车点;`_template` 范例里 `remainMs - dt*1000` 就是这个换算)。
|
||||
- ❌ `_forensicsView()` 在**外层**先算好 phase/targets 再闭包返回(`const phase=...; return { state(){ return { phase } } }`)→ ✅ **phase/targets/score 必须在 `state()` 函数体内部实时计算**(host 只在 boot 调一次 `_forensicsView()`,外层算的值定格成 boot 快照=永远 menu、targets 永远空 → 自动验收/驱动看不到目标、永判死菜单。实测 wanglanmei-v2:游戏真在 play 但门读到的 phase 恒 menu)。照 `_template/src/game-logic.js` 的 `state: () => ({ phase: sceneFsm.current(), ... })` 写。
|
||||
- ❌ `_forensicsView()`(若写)在**外层**先算好 phase 再闭包返回(`const phase=...; return { state(){ return { phase } } }`)→ ✅ **导出值在 `state()` 函数体内部实时计算**(host 只在 boot 调一次 `_forensicsView()`,外层算的值定格成 boot 快照——调试时读到的恒是旧值,实测 wanglanmei-v2:游戏真在 play 但读到的 phase 恒 menu)。照 `_template/src/game-logic.js` 的 `state: () => ({ phase: sceneFsm.current(), ... })` 写。
|
||||
- ❌ 在 game-logic.js 取受控面用 `opts.ctx` / `opts.boot` → ✅ 受控面在**实例 `init(boot)` 的 `boot.ctx`**(`boot.ctx.time` / `boot.ctx.random` / `boot.ctx.getEngine()` / `boot.ctx.log()`);**输入不经 `boot.ctx.getInput`——写实例方法 `handleTap(x,y)`/`handleKey(key)`,L1 派发**;`createGame` 入参是扁平 `{plugins,bundle,viewport}`,**没有 ctx**。
|
||||
- ❌ 修 bug 时图省事写 `Math.random()` / `Date.now()` / `setTimeout()` → ✅ `boot.ctx.random()` 或 `boot.ctx.random.next()` / `boot.ctx.random.range(a,b)`、`boot.ctx.time()` 或 `boot.ctx.time.nowMs()`、`timer-scheduler.after(ms,cb)`(#1:time/random 既可直接调用又保留方法,两形态都对;红线 check 必拦裸 Math.random/Date.now;**越改越要守,thrash 时尤其别引入**)。
|
||||
- ✅ `sessionScore.add(n)` = `addScore(n)` 别名(#6 阶段一B:直觉名直接可用,两者等价);**其它方法名拿不准一律先 `read_file` 对应 api.d.ts**。
|
||||
|
||||
@ -87,7 +87,7 @@ description: "当为剧情互动/文字冒险/恋爱剧情类 brief 产设计阶
|
||||
|
||||
剧情游戏 UI 套路(都用 hud-ui 落):
|
||||
- **文本卡**:羊皮纸/卡片底 + 描边,正文逐字换行(中文无空格,按字宽断行);
|
||||
- **选项按钮**:纵排 2~3 个、宽 ~80% 视口、高 ≥48(手指友好);**命中区=绘制区=取证 targets 三者同源**(经 core.layoutChoices 一处算,见 §9);
|
||||
- **选项按钮**:纵排 2~3 个、宽 ~80% 视口、高 ≥48(手指友好);**命中区=绘制区同源**(选项几何经 core.layoutChoices 一处算、onEnter 设命中区、render 只画——两处各算一份迟早漂移,玩家点中画出来的按钮却没反应);
|
||||
- **属性 HUD**:顶部一行"勇气 2 机智 1",涨的瞬间配粒子(particles-juice);
|
||||
- **幕数进度**:右上角「第 N 幕」——章节推进可见(替代成长轴的可见性);
|
||||
- **结局结算屏**:结局**称号**大字(「果敢信使」)+ 结局正文卡 + 属性回顾 + **图鉴行(已解锁亮称号、未解锁 ???)** + 再读一次;
|
||||
@ -139,9 +139,8 @@ description: "当为剧情互动/文字冒险/恋爱剧情类 brief 产设计阶
|
||||
- ❌ 存档树 / 回溯任意节点 UI → 一局一路走到结局,重玩靠「再读一次」+ 图鉴钩子。
|
||||
- ❌ 自由输入对话(打字回复)→ 全部预设选项(2~3 个)。
|
||||
- ❌ 计时逼选 / QTE 限时选项 → 剧情类零时间压力(它的爽点是"想清楚再选");要紧张感用文案不用倒计时。
|
||||
- ❌ **选项做成「先选中再确认」两步**:零-LLM 自动验收 driver 只会单击 `occupied:true` 目标、跟不动两步 → 九门 H 验不到进展 = **好游戏被误判** → ✅ **选项单击即推进**;**深度靠分支/检定/图鉴,不靠交互步数**。
|
||||
- ❌ 结局后自动跳回菜单/自动重开 → **结局必须 latch**(phase 驻留 over,取证 targets 清空),「再读一次」只在玩家点按钮时触发。
|
||||
- ✅ **取证契约(九门可达,钉死)**:`_forensicsView().state()` 的 `targets` 在 play 态列**当前每个选项**(中心坐标 `occupied:true`)、menu/over 态空数组;`score` = 已做选择数,随推进单调 +1;**选项命中区=绘制区=取证 targets 必须同源一处算**(_template-story 的 `core.layoutChoices` 即此;三者一旦分叉,driver 点的坐标就不再是真命中区)。场景名钉死 `menu`/`play`/`over`。
|
||||
- ❌ **选项做成「先选中再确认」两步** → ✅ **选项单击即推进**(点了就走,读者的手不该被流程绊住);**深度靠分支/检定/图鉴,不靠交互步数**。
|
||||
- ❌ 结局后自动跳回菜单/自动重开 → **结局必须 latch**(phase 驻留 over,结局卡停在屏上给玩家看够),「再读一次」只在玩家点按钮时触发。
|
||||
- ✅ 留下的:分支选择、属性累积/检定、线索、多结局图鉴、章节推进、好感路线 —— 全在 11 插件 + 单屏 + 便宜模型可生成范围内。
|
||||
|
||||
---
|
||||
|
||||
@ -9,7 +9,7 @@ description: "当为解谜/找茬/消除/记忆类 brief 产设计阶段玩法
|
||||
>
|
||||
> **铁律(创始人 2026-06-21)**:"好玩 = 上游设计层职责",不在 runtime/插件。插件只让设计**能被 juice 出手感**;**这份 skill 负责让设计本身好玩**。
|
||||
>
|
||||
> **可达性边界**:产出必须落得到 A-model 轻量运行时——**LittleJS 2D 手机竖屏(390×844)、单/少场景、便宜模型可生成的多文件代码、11 注入插件能力域**。解谜品类还有一条**品类特有的可达红线:谜题的"解"必须能被零-LLM 自动验收 driver 点出来**(§9,设计再妙、driver 解不动就会被九门误判)。
|
||||
> **可达性边界**:产出必须落得到 A-model 轻量运行时——**LittleJS 2D 手机竖屏(390×844)、单/少场景、便宜模型可生成的多文件代码、11 注入插件能力域**。解谜品类还有一条**品类特有的可达红线:谜题的"解"必须在屏上肉眼可辨**(§9,设计再妙、玩家看不出哪里可点就是卡死)。
|
||||
>
|
||||
> **上位标准**:分层质量与验收口径 = [`docs/architecture/架构/生成引擎/游戏质量与爆火能力.md`](../../docs/architecture/架构/生成引擎/游戏质量与爆火能力.md)(L1 机制可玩/L2 内容丰富/L3 留存结构/L4 传播钩子;品类 rubric 计分制见其 §4)。本品类 rubric 机器版(喂 LLM 验证 agent)= `cheap-worker/fixtures/genre-rubrics/puzzle.json`,与本文 §10 品类条目**同源**——改任一处必须同改另一处并做金标复验。
|
||||
>
|
||||
@ -39,7 +39,7 @@ description: "当为解谜/找茬/消除/记忆类 brief 产设计阶段玩法
|
||||
| **规则匹配谜盘**(主流首选) | 每关给一条线索(如"找出所有【红宝石】"),盘面若干格,点中所有符合的即过关;规则随关卡换维度(颜色→形状→数量→组合) | core 纯逻辑产盘面+判定 + hud-ui 线索横幅/格子 + session-score 计分 + particles-juice 点中爆点 |
|
||||
| **找不同 / 找目标** | 一堆干扰项里藏 N 个目标(不同色/缺一角/多一笔),全找到过关 | core 产干扰+目标 + render 细微差异绘制 + juice 找到闪光 |
|
||||
| **顺序推理** | 按推理出的次序点(从小到大/按亮灯顺序/按字序),点错断连击 | core 产序列+当前该点项 + hud-ui 高亮 + timer-scheduler 节奏 |
|
||||
| **记忆翻牌配对** | 短暂展示后盖住,凭记忆点出成对目标 | core 记牌面 + timer-scheduler 展示倒计时;**两步操作须守 §9 的 occupied 跟随纪律** |
|
||||
| **记忆翻牌配对** | 短暂展示后盖住,凭记忆点出成对目标 | core 记牌面 + timer-scheduler 展示倒计时;**两步操作须守 §9 的下一步指引纪律** |
|
||||
| **归类整理** | 把混在一起的东西按规则归拢(点出所有"属于厨房的") | 同规则匹配谜盘,规则换成类别归属 |
|
||||
| **消除变体(点选式)** | 点走相邻同色/凑三消除,清空过关 | core 盘面+消除判定;**只用点选、不做拖拽滑动**(§9) |
|
||||
| **星级 / 收集图鉴** | 每关按用时/步数评 1–3 星,攒星解锁新章节/新主题 | session-score(best 接 save)+ save-progress 存星 + hud-ui 图鉴格 |
|
||||
@ -65,9 +65,9 @@ description: "当为解谜/找茬/消除/记忆类 brief 产设计阶段玩法
|
||||
|
||||
- **盘面与解数**:起步 **3×3 格、每关 3–5 个正确目标**;上限 4×4(竖屏手指友好,格子 ≥72px);目标太少无清屏感、太多变体力活。
|
||||
- **观察窗口**:出题后留 **0.8–1.5s** 让玩家扫一眼盘面再判定(别一出题就算超时);记忆类展示 **1.5–2.5s** 再盖住。
|
||||
- **一关时长**:**8–20s/关**为舒适区;整局限时 60–90s 或 8–12 关到终局,两种收口都可(限时更适合自动验收,见 §9)。
|
||||
- **一关时长**:**8–20s/关**为舒适区;整局限时 60–90s 或 8–12 关到终局,两种收口都可。
|
||||
- **计分骨架**:每个正确目标基分(如 +10)+ **连击倍率**(2s 内连续正确 ×1.5/×2,断连归 1)+ 过关奖励(+30 起、随关数上浮 ×1.15/关)+ 剩时奖励。让"聪明又快"肉眼变成大分差。
|
||||
- **错点惩罚(轻)**:点错 = 抖屏 + 断连击 + 短锁定 **0.3–0.5s**;**绝不倒扣到看不见涨分**(分数总体must上升,挫败=流失;真玩验收也断言 score 递增)。
|
||||
- **错点惩罚(轻)**:点错 = 抖屏 + 断连击 + 短锁定 **0.3–0.5s**;**绝不倒扣到看不见涨分**(分数总体must上升,挫败=流失)。
|
||||
- **双货币(变现预留)**:星星(过关挣,解锁章节)+ 提示券(稀缺,卡壳消耗,**经看广告或攒**)。看广告点位:卡壳要提示 · 失败续 10 秒 · 结算翻倍。包装成"福利",不打断顿悟爽点。
|
||||
|
||||
---
|
||||
@ -128,7 +128,7 @@ description: "当为解谜/找茬/消除/记忆类 brief 产设计阶段玩法
|
||||
7. **UI 布局**(线索横幅 / HUD / 连击飘字 / 结算面板,§5);
|
||||
8. **插件选择清单**(常用:sceneFsm + sessionScore + hudUi + timerScheduler + audioMusic + juice,记忆类加 save 存最佳);
|
||||
9. **首 3 分钟脚本**(逐关:示范关→舒适关→首个顿悟关→内容预告);
|
||||
10. **取证契约声明**(§9:targets 怎么列、occupied 怎么标、score 怎么涨——设计阶段就写清,别等实现时才想)。
|
||||
10. **可点性可见声明**(§9:当前该点的目标靠什么视觉特征与干扰项区分、多步范式下一步怎么指引——设计阶段就写清,别等实现时才想)。
|
||||
|
||||
> **MVP-first 铁律(关乎 code agent 能否收敛)**:首版**只做可玩核心**——1 个谜题主范式 + 3–5 关阶梯 + 基分/连击 + 基础音效,谜面元素 ≤3 种属性维度。**每日谜题 / 图鉴 / 称号系统 / 无尽模式 / 提示券商店 一律标「后续·MVP 不做」**,别一稿堆满 —— 设计太满 → code agent 实现负担过重 → read-thrash 不收敛。**先出能玩的核心,再谈丰富。**
|
||||
|
||||
@ -138,16 +138,16 @@ description: "当为解谜/找茬/消除/记忆类 brief 产设计阶段玩法
|
||||
|
||||
## 9. 可达性红线(落不到运行时的设计 = 废稿,必剔除/简化)
|
||||
|
||||
- ❌ 拖拽 / 滑动 / 连线 / 画线为核心操作(华容道滑块、连连看划线、一笔画)→ 零-LLM 自动验收 driver **只会单击**,拖不动 → 改成**点选等价形态**(点两格交换、点格子步进、点序列下一项)。
|
||||
- ❌ 拖拽 / 滑动 / 连线 / 画线为核心操作(华容道滑块、连连看划线、一笔画)→ 竖屏小格上拖拽误操作率高、单击是手机 casual 最稳的输入形态 → 改成**点选等价形态**(点两格交换、点格子步进、点序列下一项)。
|
||||
- ❌ 大段文字谜面 / 灯谜 / 阅读理解 → 竖屏小字读不动、机器也验不了;线索用**视觉属性**(颜色/形状/数量/位置)表达,横幅一行字封顶。
|
||||
- ❌ 多屏关卡地图 / 大世界章节 → 单/少场景(menu→play→over),章节感用主题轮换与图鉴表达。
|
||||
- ❌ 复杂谜题编辑器 / 用户出题 → 超出便宜档;盘面一律 core 纯逻辑程序化生成(受控随机,可复现)。
|
||||
- ❌ 超过 2 个主机制(谜盘+消除+记忆混一款)→ 聚焦 1 主范式 + 1 成长点缀。
|
||||
- ❌ **「解」不在取证面上**——这是解谜品类的头号验收翻车点:**`_forensicsView().state().targets` 必须兼职"求解器"——当前**该点的正确目标**标 `occupied:true`、其余(干扰项/已清/锁定中)标 `false`**;自动验收只点 `occupied:true` 的格子,解谜逻辑再对、occupied 标错 = 九门 H 验不到进展 = 好游戏被误判。多步范式(翻牌配对:先翻 A 再翻 B)必须**随状态把"下一步该点的"跟标 occupied**(翻开 A 后,把 A 的配对牌标 occupied);做不到就改单步范式。
|
||||
- ❌ **重写盘面生成器而丢「可解性质」**——解谜品类第二号翻车点(批产实测:自写符文生成器产出 0 解盘面 → 第一关就无解 → 永无 occupied 目标 → 九门 H 挂):脚手架的 `generateBoard` 保证**每关恰好 N 个解、至少 1 个解、干扰项绝不撞规则**;换主题/换规则维度**优先只改 THEME/LEVELS 表**、别动生成器;确要自写规则系统,必须保住同一性质(生成后自检一遍「符合规则的格数 == 预期解数、≥1」,0 解=不可解关=废稿)。
|
||||
- ❌ **「解」肉眼不可辨**——这是解谜品类的头号翻车点:**当前该点的正确目标必须有可见的视觉区分**(线索横幅指明规则 + 目标属性肉眼可判——颜色/形状/数量差异要够大),解谜逻辑再对、屏上看不出哪里该点 = 玩家与测试员一样卡死。多步范式(翻牌配对:先翻 A 再翻 B)必须**随状态给"下一步该点的"视觉指引**(翻开 A 后,提示区亮出 A 的花色);做不到就改单步范式。
|
||||
- ❌ **重写盘面生成器而丢「可解性质」**——解谜品类第二号翻车点(批产实测:自写符文生成器产出 0 解盘面 → 第一关就无解 → 谁玩都卡死):脚手架的 `generateBoard` 保证**每关恰好 N 个解、至少 1 个解、干扰项绝不撞规则**;换主题/换规则维度**优先只改 THEME/LEVELS 表**、别动生成器;确要自写规则系统,必须保住同一性质(生成后自检一遍「符合规则的格数 == 预期解数、≥1」,0 解=不可解关=废稿)。
|
||||
- ❌ **在场景 render 回调里引用未定义变量**——render 回调抛错会被 scene-fsm **隔离成告警不崩**,画面异常但局不死(批产实测:renderMenu 里一个未定义的 `cy` 曾让整局 E/G/H 三门连锁挂——当时按钮命中区还靠 render 返回值回写 `menuStartBtn = renderMenu(...)`,render 一抛错命中区永远拿不到、卡死菜单)。2026-07-03 起模板已改约定:**命中矩形在场景 onEnter 用纯几何函数确定性建立,render 只画不赋值**(cheap-system v1.6.1 硬约束),旧连锁已断;但 render 抛错仍会黑屏丢反馈,改完 render 先顺一遍所有变量来源。
|
||||
- ❌ 点错即死 / 三错清零重开 → 错点轻惩罚(§3),乱点不该赢、但点错不该死。
|
||||
- ✅ 留下的:规则匹配谜盘、找目标、顺序推理、点选消除、记忆配对(守 occupied 纪律)、星级收集、关卡阶梯、连击计分 —— 全在 11 注入插件 + 单屏 + 便宜模型可生成范围内。
|
||||
- ✅ 留下的:规则匹配谜盘、找目标、顺序推理、点选消除、记忆配对(守下一步指引纪律)、星级收集、关卡阶梯、连击计分 —— 全在 11 注入插件 + 单屏 + 便宜模型可生成范围内。
|
||||
|
||||
---
|
||||
|
||||
@ -194,8 +194,8 @@ description: "当为解谜/找茬/消除/记忆类 brief 产设计阶段玩法
|
||||
## 相关
|
||||
|
||||
- 代码层实现 → [`littlejs-game-dev.md`](littlejs-game-dev.md)(11 注入插件 API / 结构 / 资产-mmx / 工厂契约)
|
||||
- 品类轻脚手架(克隆起点)→ `game-runtime/games/_template-puzzle/`(规则匹配谜盘骨架:盘面生成/线索/判定/关卡流转已接好,取证契约齐)
|
||||
- 品类轻脚手架(克隆起点)→ `game-runtime/games/_template-puzzle/`(规则匹配谜盘骨架:盘面生成/线索/判定/关卡流转已接好)
|
||||
- 过门正例(few-shot,读它看"丰富成什么样")→ `game-runtime/games/_fewshot-puzzle/src/`(在骨架上做出规则递进+连击+星级+结算炫耀的完整款)
|
||||
- 品类 rubric 机器版(喂 LLM 验证 agent)→ `cheap-worker/fixtures/genre-rubrics/puzzle.json`;金标 play-spec → `cheap-worker/fixtures/golden-specs/puzzle.play-spec.json`
|
||||
- 品类 rubric 机器版(喂 LLM 验证 agent)→ `cheap-worker/fixtures/genre-rubrics/puzzle.json`
|
||||
- 便宜模型生成 worker / 九门 → [`cheap-model-game-generation.md`](cheap-model-game-generation.md)
|
||||
- 质量分层与档位观测线(上位标准)→ [`docs/architecture/架构/生成引擎/游戏质量与爆火能力.md`](../../docs/architecture/架构/生成引擎/游戏质量与爆火能力.md)
|
||||
|
||||
@ -32,5 +32,5 @@ render(g) { sceneFsm.render(g); }
|
||||
|
||||
**别踩**:
|
||||
- 回调表里 `update(dt)` 只收 dt、`render(g)` 只收 g——与五法同形,别臆造 `update(scene, dt)`。
|
||||
- `_forensicsView().state().phase` 必须**实时读** `sceneFsm.current()`(在 state() 函数体内),别在外层先算好闭包返回(host 只在 boot 调一次 _forensicsView,外层算的值会定格成 boot 快照=永远 menu → 自动验收永判死菜单)。
|
||||
- 若写了可选调试接口 `_forensicsView()`:`state().phase` 要**实时读** `sceneFsm.current()`(在 state() 函数体内),别在外层先算好闭包返回(host 只在 boot 调一次 _forensicsView,外层算的值会定格成 boot 快照=调试读到的恒是 menu 旧值)。
|
||||
- 状态名你自己定(menu/play/over 只是惯例);`transition(to)` 的 to 必须是你 define 过的名。
|
||||
|
||||
@ -44,4 +44,4 @@ render(g) {
|
||||
**别踩**:
|
||||
- 音效名用语义名(`coin/win/hit/jump/fail/click`)或预设(`blip/thud/chime`),未知名自动兜底 blip;`audioMusic.play(intensity)` 入参是**情绪强度 0..1**、不是曲目名。
|
||||
- `juice.spawnEmitter('burst', x, y)` 也可写 `juice.burst(x, y)`(母语别名);`hitStop`/`shakeScreen` 收毫秒/像素。
|
||||
- 结算的 `_forensicsView().state().phase` 要能读到 `gameover`(自动验收据 latch 驻留判终态可达)。
|
||||
- 结算屏必须驻留(latch):到终态后停在结算不自动重开——玩家要看清战绩,测试员与调试观测也才能确认终态可达。
|
||||
|
||||
@ -148,7 +148,7 @@ description: "当为经营模拟/放置养成类 brief 产设计阶段玩法方
|
||||
- ❌ 3D / 大量逐帧手绘动画 → 2D + 程序化动效(particles-juice/缓动)+ 少量 mmx 静态图。
|
||||
- ❌ 复杂经济(多资源链/市场波动)→ 双货币 + 简单成长曲线。
|
||||
- ❌ 超过 2 个主机制 → 便宜模型 + 单屏扛不住,聚焦 1 主 + 1 成长轴。
|
||||
- ❌ **核心成单/得分动作做成「先选 A 再选 B」两步**(如"点顾客锁定→再点货架商品"):零-LLM 自动验收 driver 只会单击 `occupied:true` 目标、跟不动两步 → 九门 H 验不到进展 = **好游戏被误判** → ✅ **核心动作单击即成单**(点等待中的顾客 = 自动按库存售卖收钱,casual 友好 + driver 可达);**深度靠进货/库存/解锁/节奏,不靠两步匹配**。若确需多步,则 `_forensicsView().state().targets` 必须**随状态把"下一步该点的"标 occupied:true**(选中顾客后,把该卖的货架商品标 occupied),让 driver 跟着两步推进。
|
||||
- ⚠ **核心成单/得分动作优先单击直给**(如"点等待中的顾客 = 自动按库存售卖收钱"):手机竖屏 casual 玩家一根手指就该玩得转,「先选 A 再选 B」两步匹配抬高上手门槛;**深度靠进货/库存/解锁/节奏,不靠交互步数**。确需多步(先锁顾客再点货架),须给清晰的视觉指引(选中态高亮 + 下一步该点的目标有 affordance),让真人一眼看懂下一步点哪。
|
||||
- ✅ 留下的:放置产出、合成升级、点击经营、阶段解锁、收集、轻决策、数值滚雪球 —— 全在 11 注入插件 + 单屏 + 便宜模型可生成范围内。
|
||||
|
||||
---
|
||||
@ -181,7 +181,7 @@ description: "当为经营模拟/放置养成类 brief 产设计阶段玩法方
|
||||
|
||||
每品类需 ≥1 金标正例 + ≥1 薄反例锚住评分尺(rubric 文本或评分 prompt 变更须金标复验,单款分组小计漂移 >±1 即回退)。经营品类的锚 2026-07-04 落定(生产口径 genre=sim-business,同输入复采样 n=3 取中位,入口 `cheap_verify.verify_richness`):
|
||||
|
||||
- **金标正例 = [`cheap-worker/fixtures/golden-samples/sim-business/bake-shop-serve-1`](../../cheap-worker/fixtures/golden-samples/sim-business/bake-shop-serve-1/)**(S5b lab 复验小批产的烘焙店 60 秒上菜 rush:三桌耐心条、700ms 连击窗叠技巧分、营收三档阈值 22/55/100 渐次解锁菜单;attempts=1 一次过九门、真玩 driver=tap-targets、¥2.00)。**通用 12 条 n=3 中位 = 10/12**(单发 11/9/10,方差 ±2 合纪律;稳定缺项只有「放置回归」——60 秒单局制无离线收益,形态使然)。第 12 条「核心操作非无脑」命中:多桌耐心排序 + 连击维护是真实决策,走客有真代价。
|
||||
- **金标正例 = [`cheap-worker/fixtures/golden-samples/sim-business/bake-shop-serve-1`](../../cheap-worker/fixtures/golden-samples/sim-business/bake-shop-serve-1/)**(S5b lab 复验小批产的烘焙店 60 秒上菜 rush:三桌耐心条、700ms 连击窗叠技巧分、营收三档阈值 22/55/100 渐次解锁菜单;attempts=1 一次过当时九门、¥2.00;锚定于 W-AXIS-V2 前,当时驱动器口径已随取证契约退役)。**通用 12 条 n=3 中位 = 10/12**(单发 11/9/10,方差 ±2 合纪律;稳定缺项只有「放置回归」——60 秒单局制无离线收益,形态使然)。第 12 条「核心操作非无脑」命中:多桌耐心排序 + 连击维护是真实决策,走客有真代价。
|
||||
- **薄反例/对照 = `_template-shop` 骨架原样**(C 波 2026-07-03 实测:通用前 11 条 4/11、第 12 条未命中——点顾客即自动上菜结算 = 无脑点击;品类段 1/4)。**正反例通用段分离 5–6 分,通用尺的判别力锚在这条差距上**;与 TRPG/puzzle 模板(富正例)相反,经营骨架天然薄,薄对照角色恰好由它担、不必再造合成材料。
|
||||
- **品类扩展段(4 条)诚实口径**:正例本款只中 2/4——serve 形态天然没有进货补货与双货币,这两条的正锚要等进货型经营款出现;品类段正例对骨架对照(2/4 vs 1/4)分离仅 1 分,**回退判定勿跨形态用品类段,以通用段 + L4 为准**。
|
||||
- **方差纪律**:M3 单发方差 ±2,取中位;锚定凭据(逐条命中理由原始记录)= 正例 `evidence/richness-n3.json`。
|
||||
|
||||
@ -132,14 +132,14 @@ description: "当为掷骰冒险(TRPG)类 brief 产设计阶段玩法方案或
|
||||
## 9. 可达性红线(落不到运行时的设计 = 废稿,必剔除/简化)
|
||||
|
||||
- ❌ **长流程重游戏**:跨局剧情存档推进 / 章节制大冒险 / 大地图自由探索 → 改单局可完(45–90s)+ 跨局只留纪录/图鉴。
|
||||
- ❌ **回合制菜单战斗**("攻击/防御/道具/逃跑"四键菜单):多步操作 + 状态菜单,零-LLM 自动验收 driver 只会单击 `occupied:true` 目标、跟不动 → ✅ **核心动作单击即结算**(点遭遇卡 = 自动掷骰+判定+结算一步完成)。
|
||||
- ❌ **回合制菜单战斗**("攻击/防御/道具/逃跑"四键菜单):多步菜单流程拖慢单局节奏、casual 玩家上手门槛高 → ✅ **核心动作单击即结算**(点遭遇卡 = 自动掷骰+判定+结算一步完成,爽点在「挑哪张」不在按菜单)。
|
||||
- ❌ **纯随机无抉择**(自动掷骰/只有一个按钮):没有"挑哪张"的掌控感 = 抽卡机不是 TRPG → 同屏 ≥2 张差异化亮牌。
|
||||
- ❌ **掷骰黑箱**(点卡直接出结果不见骰):悬念归零 → 判定式飘字必须有。
|
||||
- ❌ 联网组队 / PVP → 单机 + 本地最高纪录。
|
||||
- ❌ 3D 骰子物理 / 大量逐帧动画 → 2D 程序化(飘字+粒子+闪光足够演出)。
|
||||
- ❌ 超过 2 个主机制(战斗+锻造+钓鱼…)→ 聚焦点卡掷骰 + 1 成长轴。
|
||||
- ✅ 留下的:掷骰对抗、亮牌取舍、等级/伙伴成长、难度爬坡、连胜、限时、纪录/图鉴 —— 全在 11 注入插件 + 单屏 + 便宜模型可生成范围内。
|
||||
- **_forensicsView 契约(自动验收可达)**:`targets` 列本层可点遭遇卡(ready 才 `occupied:true`);`score` 用金币战利品且**每次结算至少 +1**(失败安慰金)——score 单调升是 H 门进展契约,断了 = 好游戏被误判。
|
||||
- **每次结算至少 +1 金币(失败安慰金)**:胜负都给正反馈,失败也不颗粒无收——保底反馈是 casual 上手缓冲,别让连败玩家零收获直接弃游。
|
||||
|
||||
---
|
||||
|
||||
@ -156,7 +156,7 @@ description: "当为掷骰冒险(TRPG)类 brief 产设计阶段玩法方案或
|
||||
5. **[L4] 冒险战报可炫耀**:结算屏讲这局冒险的故事(层数/击败/大成功/胜负因由)、可截图?(正:「爬到第 5 层·击败 7·大成功 2·勇者倒下」;反:只有一个总分数字)
|
||||
|
||||
> 命中越多越好玩;通用 12 条命中 ≤3 ≈ 能玩但无趣。**设计阶段就过这两张表,别等做出来才发现无趣。**
|
||||
> 机检硬门 vs LLM 评分维度的分界照质量模型 SoT §4 规范五:score 递增等真玩断言归 L1 硬门(play-spec assertAfterPlay);上述品类条目全是 LLM 评分维度,**不落项目代码、不进九门、不进脚手架**。
|
||||
> 机检硬门 vs LLM 评分维度的分界照质量模型 SoT §4 规范五(v2 修订:play-spec 断言载体已随 W-AXIS-V2 退役,机械可判的归四门地板与测试员转写、程度评价归 LLM 评分,分界原则不变);上述品类条目全是 LLM 评分维度,**不落项目代码、不进机械门、不进脚手架**。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
"""
|
||||
auto_vs_golden.py — M2 auto-vs-golden delta 门(Node 退役授权第三条 · 按品类 · 零 LLM)。
|
||||
|
||||
【封存(W-AXIS-V2 波2,2026-07-10)】对照范式建立在 ensure_play_spec 自动 spec 与 tap-targets 驱动器上;
|
||||
两者已随取证契约退出便宜档验收链(验收权威 = 四门投影 ∧ 测试 agent 真玩)。其使命——Node 退役授权的
|
||||
「自动 spec 不比金标驱得差」——已随 Node 路退役完结,不改挂测试员(范式不同,无对照意义)。保留代码
|
||||
供历史复跑;生产链与批跑不再调用,play-spec 生成器窗口期结束后随其一并清理。
|
||||
|
||||
对同一款生成产物,在同一份 staged src/ 上先用生产自动 spec(ensure_play_spec)、后用金标 spec
|
||||
(inject_golden)各驱动一次九门,逐门比过门率。方向单边(承 plan① M2「关键门不得低于金标基线」):
|
||||
查的是自动 spec 是否比金标**驱得差**(delta<0 退化),不是查自动比金标松——后者对断言同质的
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
"""
|
||||
bake_off.py — 便宜档 ≥80% 达标门(按品类 · 零 LLM)。
|
||||
|
||||
【封存(W-AXIS-V2 波2,2026-07-10)】本工具以 verdict.pass(driven 九门)为达标判据,依赖 ensure_play_spec
|
||||
自动 spec 驱动;驱动器已随取证契约退出便宜档验收链。v2 的达标口径 = accepted(四门投影 ∧ 测试 agent 真玩),
|
||||
批量达标测量由 hard_genre_batch / hard_genre_xtheme(已切新口径)承担。保留代码供历史复跑;不再作达标门。
|
||||
|
||||
代表样本(tap-targets occupied 三品类:点击得分/打地鼠/经营点客)各 n 款,每款:
|
||||
run_studio(run_gates=True) 生成 → 自动 spec(U2 补 prefixes)驱动九门 → 取 verdict.pass。
|
||||
按品类聚合 pass_count/total,逐品类 ≥0.8 判达标;任一品类缺样本报「覆盖不足」、任一 <0.8 整体未达标
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""cheap_gates.py — 便宜档九门收口的同步包装(供 Service 续修 check 闭包经 to_thread 调用)。
|
||||
|
||||
归并后便宜档收敛判据从「check+build 绿」升为「九门绿」:RepairMiddleware 的 check 闭包在 finish 点
|
||||
把工程收口跑一遍九门(stage→smoke→ensure_play_spec→play),经 worker.gate_judge.judge_cheap_verdict 归一。
|
||||
把工程收口跑一遍九门(stage→smoke→play(undriven)),经 worker.gate_judge.judge_cheap_verdict 归一。
|
||||
本模块只做「同步 pipeline + 端口透传」,不做判据归一(那在 gate_judge,与 tier2 并列)。
|
||||
端口由调用方按 session 派生传入(决策③ 并发避撞);shell-out/subprocess 阻塞,故封在同步函数里给 to_thread 跑。
|
||||
"""
|
||||
@ -10,7 +10,7 @@ import cheap_run
|
||||
|
||||
|
||||
def run_cheap_gates(game_id: str, port: int, cdp_port: int) -> dict:
|
||||
"""同步跑便宜档九门收口 stage→smoke→ensure_play_spec→play,返回九门 verdict({pass, guards})或 {}(未跑出)。
|
||||
"""同步跑便宜档九门收口 stage→smoke→play(undriven),返回九门 verdict({pass, guards})或 {}(未跑出)。
|
||||
|
||||
任一段失败(stage 打包不出 / play 没产 verdict)→ 返 {}(judge_cheap_verdict 判未过、feedback 落 harness 说明,
|
||||
不伪造门绿)。端口按 session 派生传入,避免并发多局撞固定 4320/9222(决策③)。
|
||||
@ -19,10 +19,10 @@ def run_cheap_gates(game_id: str, port: int, cdp_port: int) -> dict:
|
||||
if not st["ok"]:
|
||||
print(f"[cheap-gates] game={game_id} stage 失败(判未过):{st['output'][:300]}", flush=True)
|
||||
return {}
|
||||
sm = cheap_run.smoke(game_id, port=port, cdp_port=cdp_port)
|
||||
# ensure_play_spec 据 smoke 抓的 state 产 driver(已存在不覆盖),让九门 driven=true、E_live/H_progress 由 advisory 升致命,
|
||||
# 根治「裸 harness 无 driver → 假绿」。smoke 失败(state=None)时按保守 key-cycle 薄 spec,play 仍照跑。
|
||||
cheap_run.ensure_play_spec(game_id, sm.get("state"))
|
||||
# smoke 作 boot 预检(早捕装载失败,诊断更清);其 _forensicsView state 曾供 ensure_play_spec 推断驱动器,
|
||||
# W-AXIS-V2 波2 拆 tap-targets 驱动器后不再消费——便宜档验收权威改四门投影(A/B/C/D)∧ 测试 agent 真玩,
|
||||
# 九门以 undriven 硬集(A/B/C/D + F/G/I)跑,floor 投影只取 A/B/C/D。ensure_play_spec 不再调。
|
||||
cheap_run.smoke(game_id, port=port, cdp_port=cdp_port)
|
||||
# 红线③(封陈旧 verdict 假绿,镜像 tier2 run.py:866):起 play 前清残留 verdict,保证 judge 拿到的恒是本次真门产出。
|
||||
# serve-and-play.sh 早退路径(chrome/serve/cdp 未就绪)+ 超时都不写新 verdict 也不删旧的,不清则 play 读回上一局绿 verdict → 假绿放行。
|
||||
(cheap_run.wg1_game_dir(game_id) / "evidence" / "verdict.json").unlink(missing_ok=True)
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
· materialize_source_project(系统级落盘):把 base 源 files 直接写到 game_dir——**系统恢复已知良源**,
|
||||
绕 cheap_run.write_file 的 L1_FIXED 守卫(那守卫是防 *agent* 写坏 wiring;这里是系统落盘,只仍校验不越界)。
|
||||
· execute_deterministic_modify(集成):apply → scaffold 已知良 plumbing → materialize 覆盖改后源 →
|
||||
build → stage → smoke → ensure_play_spec → 九门 play,**复用 cheap_run 现有九门,不另造**。
|
||||
build → stage → smoke → 九门 play(undriven),**复用 cheap_run 现有九门,不另造**。
|
||||
apply no-op/unsupported → 直接 failed(带 manifest + error),不重建(省 esbuild/Chrome)。
|
||||
|
||||
best-effort 铁律:apply 失败 / 重建失败 → 返 failed + 诊断,绝不抛崩 worker。只改 L3(core/assets/game-logic),
|
||||
@ -290,7 +290,7 @@ def execute_deterministic_modify(game_id: str, source_project, modify_patch,
|
||||
"""确定性执行集成:apply 改一处 → 落盘已知良源 → 重建 → 九门。复用 cheap_run 九门,不另造。
|
||||
|
||||
流程:apply_deterministic_modify → 若 ok:(缺 plumbing 才 scaffold 已知良 entry-bundle/index.html)→
|
||||
materialize 覆盖改后 L3 源 → build(esbuild)→ stage → smoke → ensure_play_spec → 九门 play。
|
||||
materialize 覆盖改后 L3 源 → build(esbuild)→ stage → smoke → 九门 play(undriven)。
|
||||
apply no-op/unsupported → 直接 failed(带 manifest + error),**不重建**(省 esbuild/Chrome)。
|
||||
任一步失败 → failed + 诊断(stage 标到哪步断的),绝不抛崩。
|
||||
|
||||
@ -328,15 +328,14 @@ def execute_deterministic_modify(game_id: str, source_project, modify_patch,
|
||||
"stage": "build", "error": "build 失败:" + bd.get("output", ""),
|
||||
"smokeOk": None, "playOk": None}
|
||||
|
||||
# 5. stage → smoke → ensure_play_spec → 循环外九门 play(复用 cheap_studio 收口同一套)。
|
||||
# 5. stage → smoke → 循环外九门 play(undriven;复用 cheap_studio 收口同一套;ensure_play_spec 波2 已摘)。
|
||||
st = cheap_run.stage(game_id)
|
||||
if not st["ok"]:
|
||||
log(f"stage 失败 game_id={game_id}: {st.get('output', '')[:300]}")
|
||||
return {"status": "failed", "manifest": manifest, "verdict": None,
|
||||
"stage": "stage", "error": "stage 失败:" + st.get("output", ""),
|
||||
"smokeOk": None, "playOk": None}
|
||||
sm = cheap_run.smoke(game_id, port=port, cdp_port=cdp_port)
|
||||
cheap_run.ensure_play_spec(game_id, sm.get("state")) # 已存在不覆盖;缺则据 state 形态补 driver(防裸跑假绿)
|
||||
sm = cheap_run.smoke(game_id, port=port, cdp_port=cdp_port) # boot 预检(smokeOk 供返回);其 state 不再喂 ensure_play_spec
|
||||
pr = cheap_run.play(game_id, port=port, cdp_port=cdp_port)
|
||||
verdict = pr.get("verdict")
|
||||
# W-AXIS-V2 波1:create 与 modify 不允许两套验收标准——确定性改也过统一编排器(四门投影 ∧ 测试员真玩)。
|
||||
|
||||
@ -10,9 +10,11 @@ skill / 插件 api.d.ts / 起点范例。L3 context 由 agent 经 read_file 按
|
||||
2. 完成工具名是 **finish**(不是 Node 的 done)—— 便宜档的 done 门 = cheap_toolkit 的 finish 工具
|
||||
(工具内复核 check+build 绿才接受)。
|
||||
|
||||
system prompt 正文与 prompt.mjs 保持语义一致:入口契约 createGame({plugins,bundle,viewport}) 五法 + _forensicsView、
|
||||
红线(零裸时间随机 DOM / bundle.tick / ctx.time.nowMs() / 扁平注入插件 / 方法名以 api.d.ts 为准)、
|
||||
本周收口的 footgun(sceneFsm.define 逐场景调、真返 void 的绘制/音效 API 别接返回值;drawButton 已改返命中矩形、可直接接住判命中)、输入契约(handleTap/handleKey)。
|
||||
system prompt 正文要点:入口契约 createGame({plugins,bundle,viewport}) 五法、红线(零裸时间随机 DOM /
|
||||
bundle.tick / ctx.time.nowMs() / 扁平注入插件 / 方法名以 api.d.ts 为准)、本周收口的 footgun(sceneFsm.define
|
||||
逐场景调、真返 void 的绘制/音效 API 别接返回值;drawButton 已改返命中矩形、可直接接住判命中)、输入契约
|
||||
(handleTap/handleKey)。W-AXIS-V2 波2 起不再教 _forensicsView 取证契约——自动验收随 tap-targets 驱动器退役,
|
||||
验收 = 四门投影 ∧ 测试 agent 视觉引导真玩;模型不再为死契约写代码。
|
||||
|
||||
「生成丰富度」设计步(U-A1):除指向 littlejs-game-dev.md(怎么写代码)外,再加一个「先设计后写码」步——
|
||||
经营/养成/放置/点客类先 read sim-business-game-design.md 取「设计什么才好玩」的范式(核心循环 + 进货库存资源环
|
||||
@ -41,7 +43,7 @@ _SYSTEM_PROMPT = """你是 A-model 游戏生成 agent。目标产物 = 一款**
|
||||
- 我已把克隆起点拷到 ⟦G⟧/(⟦SCAFFOLD_DESC⟧)。你的活是据 brief **借插件能力把它改造成一款高质游戏**——别把它当"这档就该这么简单"的天花板。
|
||||
- **L1 固定 plumbing 别动(write 会被拒)**:⟦G⟧/index.html、⟦G⟧/entry-bundle.js、⟦G⟧/src/game.js(薄 wrapper)、**⟦G⟧/src/host-config.js**(host 装配:已替你注入**全 11 件能力插件** + viewport 固定 390×844 + 工厂 wiring;你无需装配任何插件)。
|
||||
- **你只写 L3 游戏本体**:**⟦G⟧/src/game-logic.js(必写,游戏核心)** + 按需 core.js(纯逻辑)/ render.js(画面)/ balance.js(数值)/ assets.js(资产)。结构简单可把逻辑/画面集中在 game-logic.js 内;多系统/多场景再拆 core/render。把脚手架起点改造成 brief 要的**高质游戏**(多借插件能力做卖相与深度);**画面读 viewport.w/h(390×844 竖屏)**。
|
||||
- **入口契约(钉死)**:game-logic.js 必须 `export function createGame({ plugins, bundle, viewport })` 返回 GameInstance 五法 init(boot)/update(dt)/render(g)/destroy()(+必须 _forensicsView)。**插件已扁平注入 plugins**,用到才解构:sceneFsm/sessionScore/hudUi/timerScheduler/save/gamefeel/juice/palettePost/audioMusic/collision/physics。
|
||||
- **入口契约(钉死)**:game-logic.js 必须 `export function createGame({ plugins, bundle, viewport })` 返回 GameInstance 五法 init(boot)/update(dt)/render(g)/destroy()。**插件已扁平注入 plugins**,用到才解构:sceneFsm/sessionScore/hudUi/timerScheduler/save/gamefeel/juice/palettePost/audioMusic/collision/physics。
|
||||
|
||||
【先读手册(必须;别凭记忆猜 API)】
|
||||
1. read_file('.agents/skills/littlejs-game-dev.md') —— code 层作业手册(结构/边界/红线/11 注入插件 API 速查)。
|
||||
@ -49,10 +51,10 @@ _SYSTEM_PROMPT = """你是 A-model 游戏生成 agent。目标产物 = 一款**
|
||||
3. 用到某插件就 read_file 它的 api.d.ts(如 game-runtime/src/plugins/scene-fsm/api.d.ts、.../session-score/api.d.ts)看精确签名。
|
||||
4. 你的起点 ⟦G⟧/src/game-logic.js 本身就是**跑通的脚手架范例**(五法齐全 + 调 4 编排插件):read_file 它,照其结构**借插件能力**改造成 brief 的高质游戏(最省事:在它基础上改、多调插件加内容,别从零写)。
|
||||
5. **若 brief 属经营 / 养成 / 放置 / 点客这类「攒资源→成长→解锁」品类**:read_file('.agents/skills/sim-business-game-design.md') 取「设计什么才好玩」的范式(6 心理引擎 / §1 玩法范式含进货补货环+决策模式清单 / §8 设计配方 / §10 反无趣自检)。littlejs-game-dev 教你「代码怎么写」、sim-business 教你「设计什么才好玩」——配对用:先想清好玩、再写正确。
|
||||
6. **若 brief 属剧情 / 互动叙事 / 文字冒险这类「读文本→做选择→推进剧情」品类**:read_file('.agents/skills/narrative-game-design.md') 取「设计什么才好玩」的范式(6 心理引擎 / §1 范式含分支选择+多结局图鉴 / §3 文本红线 / §8 设计配方 / §9 可达性红线含取证契约);再 read_file('game-runtime/games/_template-story/src/game-logic.js') 看剧情品类过门范例(分支推进 + 结局 latch + 结局图鉴的骨架写法,其故事数据在同目录 core.js 顶部参数空间)。
|
||||
6. **若 brief 属剧情 / 互动叙事 / 文字冒险这类「读文本→做选择→推进剧情」品类**:read_file('.agents/skills/narrative-game-design.md') 取「设计什么才好玩」的范式(6 心理引擎 / §1 范式含分支选择+多结局图鉴 / §3 文本红线 / §8 设计配方 / §9 可达性红线);再 read_file('game-runtime/games/_template-story/src/game-logic.js') 看剧情品类过门范例(分支推进 + 结局 latch + 结局图鉴的骨架写法,其故事数据在同目录 core.js 顶部参数空间)。
|
||||
7. **若 brief 属 TRPG / 掷骰 / 冒险 / 地牢爬塔这类「掷骰对抗→抉择→成长」品类**:read_file('.agents/skills/trpg-game-design.md') 取「设计什么才好玩」的范式(6 心理引擎 / §1 玩法范式含掷骰爬塔 / §3 掷骰概率数值 / §9 红线〔核心动作单击即结算·掷骰过程可见〕 / §10 反无趣自检)。
|
||||
8. **若 brief 属非遗 / 传统技艺 / 民俗这类「传统文化玩法化」品类**:read_file('.agents/skills/heritage-game-design.md') 取「设计什么才好玩」的范式(§1 玩法化范式含工序节拍 / §3 双层奖励与工序节奏目标值 / §9 文化表述红线 / §10 反贴皮自检);需要完整过门范例再 read_file('game-runtime/games/_fewshot-feiyi/src/game-logic.js')。
|
||||
9. **若 brief 属解谜(找规律 / 找不同 / 翻牌配对 / 顺序推理 / 点选消除这类「观察→推理→点解」品类)**:read_file('.agents/skills/puzzle-game-design.md') 取解谜设计范式(顿悟距离 / 规则递进关卡阶梯 / 卡壳兜底 / 成绩炫耀 / §9 品类可达红线——解谜的「解」必须映射到 targets occupied);想看「丰富成什么样」的完整过门正例,可 read_file('game-runtime/games/_fewshot-puzzle/src/game-logic.js') 参照。
|
||||
9. **若 brief 属解谜(找规律 / 找不同 / 翻牌配对 / 顺序推理 / 点选消除这类「观察→推理→点解」品类)**:read_file('.agents/skills/puzzle-game-design.md') 取解谜设计范式(顿悟距离 / 规则递进关卡阶梯 / 卡壳兜底 / 成绩炫耀 / §9 品类可达红线);想看「丰富成什么样」的完整过门正例,可 read_file('game-runtime/games/_fewshot-puzzle/src/game-logic.js') 参照。
|
||||
|
||||
【先设计后写码:决定这游戏好不好玩(关键,别跳过)】
|
||||
能跑 ≠ 好玩。动手写码前,先据 brief 在 **game-logic.js 顶部写一段设计注释块**(≤8 行:⑨ 判定句三个空填好 + 核心数值锚 + ⑩ 的三段难度)定下**轻量玩法设计**,再实现——设计只在心里 = 收尾自查与验收都无从对照:
|
||||
@ -75,7 +77,7 @@ A 读手册(1 + 按品类 5/6/7/8/9)+ 读你的起点 game-logic.js(4)
|
||||
- game-logic.js 必须 **export function createGame({ plugins, bundle, viewport })**(命名导出、别改名、别默认导出);**零引擎 import**(引擎经 boot.ctx.getEngine())。
|
||||
- 游戏源(game-logic / core / render)**零裸** Math.random / Date.now / setTimeout / requestAnimationFrame / new AudioContext / addEventListener —— 时间随机经 boot.ctx,定时经 timer-scheduler,音效经 plugins.audioMusic;**输入不调 ctx.getInput**(已收归 L1,见下「输入契约」)。
|
||||
- update(dt) 内**必须调 bundle.tick(dt)**(bundle 是 createGame 入参;否则插件 onFrame 不推进、timer 永不到期、一局不结束)。**dt 单位是「秒」(≈1/60)**——游戏内若用毫秒计时(倒计时 / spawn 间隔 / 顾客耐心,常量都是 ms 如 60000/1300),累加时务必「elapsedMs += dt * 1000」,漏 ×1000 → 计时慢 1000 倍 → 永不到 spawn 阈值=零顾客、一局不结束。**timerScheduler.after(ms,cb)/every(ms,cb) 同样收毫秒(#7):3 秒写 after(3000)、别写 after(3);与 dt=秒相反,别混。**
|
||||
- **入参是扁平的 { plugins, bundle, viewport }——没有 opts、没有 opts.runtime、没有 ctx**(L1 已替你摊平)。受控面 ctx 只在五法 **init(boot) 的 boot.ctx**;**调用形态**:时间 **ctx.time()** 或 ctx.time.nowMs()、随机 **ctx.random()** 或 ctx.random.next()/ctx.random.range(a,b)、画布 ctx.getContext2d()、引擎 ctx.getEngine()、日志 ctx.log(tag,msg)。**#1:ctx.time/ctx.random 既可直接调用(ctx.time()=毫秒、ctx.random()=[0,1))、又保留方法(.nowMs()/.elapsedMs()、.next()/.range(a,b)/.reseed())——两形态都对**。**但 nowMs/next 是 time/random 上的方法、不是 ctx 上的——别写 ctx.nowMs()(应 ctx.time() 或 ctx.time.nowMs())。**五法 = init(boot)/update(dt)/render(g)/destroy()(+必须 _forensicsView)+ **输入方法 handleTap(x,y)**(见下「输入契约」)。
|
||||
- **入参是扁平的 { plugins, bundle, viewport }——没有 opts、没有 opts.runtime、没有 ctx**(L1 已替你摊平)。受控面 ctx 只在五法 **init(boot) 的 boot.ctx**;**调用形态**:时间 **ctx.time()** 或 ctx.time.nowMs()、随机 **ctx.random()** 或 ctx.random.next()/ctx.random.range(a,b)、画布 ctx.getContext2d()、引擎 ctx.getEngine()、日志 ctx.log(tag,msg)。**#1:ctx.time/ctx.random 既可直接调用(ctx.time()=毫秒、ctx.random()=[0,1))、又保留方法(.nowMs()/.elapsedMs()、.next()/.range(a,b)/.reseed())——两形态都对**。**但 nowMs/next 是 time/random 上的方法、不是 ctx 上的——别写 ctx.nowMs()(应 ctx.time() 或 ctx.time.nowMs())。**五法 = init(boot)/update(dt)/render(g)/destroy() + **输入方法 handleTap(x,y)**(见下「输入契约」)。
|
||||
- **插件扁平注入:直接 const { sceneFsm, sessionScore, hudUi, timerScheduler } = plugins(用到才解构)**;**绝不写 opts.runtime.plugins**(那是 L1 内部的,你见不到 → 写了就 undefined → boot 崩 reading 'define')。游戏只用插件、不 new、不 register。
|
||||
- **可用插件键(全 11 已注入,用到才取)**:sceneFsm / sessionScore / hudUi / timerScheduler / save / gamefeel / juice / palettePost / audioMusic / collision / physics。
|
||||
- **插件方法名一律以 api.d.ts 为准、别臆测**(实测幻觉:scene-fsm 写 defineScenes〔应 define〕)。用到某插件先 read_file 它的 api.d.ts;skill「⚠️ 实测易犯的幻觉 API」逐条列 ❌→✅。
|
||||
@ -83,7 +85,6 @@ A 读手册(1 + 按品类 5/6/7/8/9)+ 读你的起点 game-logic.js(4)
|
||||
- **sceneFsm.define(name, handlers) 逐场景调一次——绝不单对象批量注册**:❌ sceneFsm.define({ menu: {...}, play: {...}, over: {...} }) → 第一参当成了场景名「[object Object]」、menu/play/over 全没真注册 → 后续 transition('play') 找不到目标态被忽略 → 卡菜单(实测合成/2048 类翻车点)。✅ **每个场景各调一次**:sceneFsm.define('menu', { onEnter(){...} }); sceneFsm.define('play', { onEnter(){...}, update(dt){...} }); sceneFsm.define('over', { onEnter(){...} });(define 第一参恒为场景名字符串、第二参才是回调表 { onEnter, onExit, update, render };**回调表里 update 只收 (dt)、render 只收 (g)、与五法同形——别臆造场景名/state 首参,绝不写 update(scene, dt)**;可链式 .define(...).define(...))。
|
||||
- **画面读 viewport.w/h**(viewport 是入参;别硬编码 390×844)。
|
||||
- **美术资产(若 assets/manifest.json 非空)**:先 read_file('assets/manifest.json') 看有哪些资产;init(boot) 里 const assets = boot.assets || {};render 里 const img = assets['ref名']?.image; if (img) g.drawImage(img, x, y, w, h); else 程序化回退。**ref = manifest 条目 file 去扩展名**(shopkeeper-idle.jpg → 'shopkeeper-idle')。缺图/加载失败时 assets[ref].image 为 null(host 已容错)→ 务必走程序化回退、别崩。BGM/音效经 plugins.audioMusic(不在 boot.assets)。无 manifest/无美术 → 全程序化绘制即可。
|
||||
- **必须实现 _forensicsView()**(可测性红线 + 自动驱动契约):返回 { state(){…}, measures(){…} };state() 至少 { phase, score }。**若游戏靠点击推进(经营点客/打地鼠/点离散目标等):state().targets:[{x,y,occupied}] 列当前屏上目标,可点的(等待的客/冒头的鼠)置 occupied:true、不可点置 false** —— 自动验收据此点 occupied:true 的目标真驱动你的游戏。**键名固定为 `targets`,不得改成 items/objects 等**:判卷驱动器靠 state 有无 `targets` 键选驱动器族(点目标族 vs 循环按键族),改名=选错族=盲驱动器驱动不了你的游戏=H 进展门挂。**score 用累计进展(营收/得分),随推进上升** —— 自动验收据 score 上升判"真有进展"。不写则验证读不到状态(check 会拦)。**钉死:phase/targets/score 必须在 state() 函数体内部实时读取/计算——绝不在 _forensicsView() 外层先算好再闭包返回**(host 只在 boot 调一次 _forensicsView,外层算的值会被定格成 boot 快照=永远 menu、targets 永远空 → 自动验收看不到顾客、永判死菜单)。
|
||||
- **关键事件用 ctx.log 记日志**:在 init/场景切换/得分/出错处 调 ctx.log('tag', 信息)。**ctx 来自 init 的入参 boot,即「const ctx = boot.ctx」——绝不是 boot.boot.ctx**(host 传入的 boot 已是 {ctx, mainContext, canvas, seed, assets},多套一层 .boot=undefined → ctx=null → 无输入、游戏点不动;check 会拦 .boot.ctx)。插件调用已自动记,你只补游戏语义事件。
|
||||
- **输入契约(钉死·根治"启动不了")**:输入订阅已由 L1(game.js wrapper)接管,**你绝不自己订阅输入**——不写 ctx.getInput、不自建 pendingClicks 队列、不在 update(dt) 里挑时机消费点击。你只写实例方法 **handleTap(x, y)**(点击主输入:经营点客 / 打地鼠 / 点按钮等;**menu/玩中/结算各 phase 的判定全写在它内部**),L1 会在每次 pointerdown 时**直达**调用它;键盘玩法(方向键 / 空格)写 **handleKey(key)**,L1 在 keydown 时调用。check 会拦「调了 getInput」与「一个输入方法都没暴露」。
|
||||
|
||||
|
||||
@ -637,6 +637,11 @@ def _validate_c5_best_effort(spec: dict) -> None:
|
||||
def ensure_play_spec(game_id: str, state: Optional[dict]) -> dict:
|
||||
"""据 _forensicsView().state() 形态自动产 _wg1-gen/<id>/play-spec.json(对 tools.mjs ensurePlaySpec)。
|
||||
|
||||
【退役·保留一个版本窗口(W-AXIS-V2 波2,2026-07-10)】生产验收链(cheap_gates/cheap_studio/cheap_modify/
|
||||
gen.mjs done 门)已全部摘除对本函数的调用——便宜档验收权威 = 四门投影(A/B/C/D)∧ 测试 agent 真玩,
|
||||
不再靠 tap-targets/key-cycle 驱动器把九门驱成 driven。实现保留仅供封存对照工具(auto_vs_golden/
|
||||
compare_node)历史复跑与单测;窗口期满随 play-spec 生成层一并清理。
|
||||
|
||||
driver 推断(照 play.cdp.cjs 的 driver 家族):有 targets 数组(点击类)→ tap-targets occupied;
|
||||
否则(按键类)→ key-cycle。让九门 driven=true、E_live/H_progress 由 advisory 升为致命,
|
||||
根治"纯 harness 裸跑无 driver → 假绿"。
|
||||
|
||||
@ -154,18 +154,25 @@ def _read_service_run_summary(game_id: str, *, wait_s: float = 5.0) -> dict:
|
||||
|
||||
|
||||
def _read_driver_type(game_id: str):
|
||||
"""读 ensure_play_spec 产的 driver 类型(_wg1-gen/<id>/play-spec.json.driver.type);缺失 → None(trace.gatespec 省略)。"""
|
||||
"""W-AXIS-V2 波2:tap-targets/key-cycle 驱动器随取证契约退役,验收改由测试 agent 视觉引导真玩驱动。
|
||||
|
||||
停读已退役的 play-spec.json.driver.type,改读测试员真相层(evidence/playtest/playtest.json):存在则回其
|
||||
模型标识(playtest:<model>),缺失(_build_summary 在 run_acceptance 跑测试员之前先组 summary,此刻尚无)则回
|
||||
常量 'playtest-agent'——两路都如实反映「v2 的驱动主体是测试 agent」。trace.driverType 现为纯观测维,D11 首局
|
||||
信号已迁 trace.playtest(见 plan §5)。
|
||||
"""
|
||||
import json # noqa: PLC0415
|
||||
|
||||
import cheap_run # noqa: PLC0415
|
||||
|
||||
p = cheap_run.wg1_game_dir(game_id) / "play-spec.json"
|
||||
p = cheap_run.wg1_game_dir(game_id) / "evidence" / "playtest" / "playtest.json"
|
||||
if not p.exists():
|
||||
return None
|
||||
return "playtest-agent"
|
||||
try:
|
||||
return ((json.loads(p.read_text(encoding="utf-8")) or {}).get("driver") or {}).get("type")
|
||||
except Exception: # noqa: BLE001
|
||||
return None
|
||||
model = (json.loads(p.read_text(encoding="utf-8")) or {}).get("model")
|
||||
return f"playtest:{model}" if model else "playtest-agent"
|
||||
except Exception: # noqa: BLE001 读不出只回通用标记,绝不抛
|
||||
return "playtest-agent"
|
||||
|
||||
|
||||
def _build_summary(game_id: str, brief: str, verdict, svc: dict, turn: dict, t0: float) -> dict:
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
cheap_studio.py — 便宜档 ReAct 主编排(spike 核心;对照源:Node gen.mjs 主循环 + tier2 studio.py:run_studio)。
|
||||
|
||||
组装:内置 Agent + ReActConfig + 外层有界 resume 循环(照 studio.py:404-458)。done 由 cheap_toolkit 的 finish
|
||||
工具触发(工具内复核 check+build 绿);finish 收敛后收口 stage → smoke → ensure_play_spec → 循环外九门 play。
|
||||
工具触发(工具内复核 check+build 绿);finish 收敛后收口 stage → smoke →循环外九门 play。
|
||||
熔断(CircuitBreakerMiddleware)/ trace(Tier2TraceMiddleware)/ 历史压缩(ContextConfig)经 import 复用 tier2,零重写。
|
||||
|
||||
为什么要外层 resume:AgentScope 2.0.3 内置 ReAct 在"模型产出无 tool_call 的纯文本回合"即退出(看一次门没绿就停)。
|
||||
@ -115,7 +115,7 @@ def _verdict_brief(v) -> dict:
|
||||
|
||||
|
||||
def _closeout_gates(game_id, *, port, cdp_port) -> dict:
|
||||
"""CLI 收口门流水线 stage → smoke → ensure_play_spec → 九门 play(一次跑齐;回喂循环与收口段共用)。
|
||||
"""CLI 收口门流水线 stage → smoke →九门 play(一次跑齐;回喂循环与收口段共用)。
|
||||
|
||||
与 Service 路 cheap_gates.run_cheap_gates 同序,多回传 staged/smoke_ok/driver_type 供 run-summary 组装
|
||||
(Service 路这些由 collector/driver 另采)。起 play 前清残留 verdict(红线③,镜像 cheap_gates.py:28):
|
||||
@ -134,18 +134,9 @@ def _closeout_gates(game_id, *, port, cdp_port) -> dict:
|
||||
# smoke 失败真因必须可见(2026-07-04 bake_off 实证:并发争用致 100% FAIL→全场 key-cycle 回退考卷,
|
||||
# 而失败原因被吞在 raw 里,烧掉一轮诊断)——打印 raw 尾供归因。
|
||||
_rec(f"smoke raw 尾:{(sm.get('raw') or '')[-300:]}")
|
||||
ps = cheap_run.ensure_play_spec(game_id, sm.get("state"))
|
||||
# driver 类型:本轮新产的直接带;复用旧 spec(hash 一致)时读盘补(spec 是判卷单一来源)。
|
||||
dt = ps.get("driverType")
|
||||
if not dt:
|
||||
try:
|
||||
spec = json.loads((cheap_run.wg1_game_dir(game_id) / "play-spec.json").read_text(encoding="utf-8"))
|
||||
dt = (spec.get("driver") or {}).get("type")
|
||||
except Exception: # noqa: BLE001 —— 读不出只降级 None
|
||||
dt = None
|
||||
out["driver_type"] = dt
|
||||
_rec(f"play-spec {'已产 driver=' + str(dt) if ps.get('wrote') else ps.get('reason', '?')}")
|
||||
(cheap_run.wg1_game_dir(game_id) / "evidence" / "verdict.json").unlink(missing_ok=True) # 红线③:清陈旧 verdict
|
||||
# W-AXIS-V2 波2:ensure_play_spec 已摘——play 以 undriven 硬集(A/B/C/D + F/G/I)跑,floor 投影只取 A/B/C/D;
|
||||
# driver_type 随 tap-targets 驱动器退役恒 None(out 初始化即 None;trace.driverType 观测维不再承 D11 首局信号,已迁 trace.playtest)。
|
||||
(cheap_run.wg1_game_dir(game_id) / "evidence" / "verdict.json").unlink(missing_ok=True) # 红线③:清陈旧 verdict,防读回上轮绿 verdict 假绿
|
||||
pr = cheap_run.play(game_id, port=port, cdp_port=cdp_port)
|
||||
out["verdict"] = pr["verdict"]
|
||||
return out
|
||||
@ -210,7 +201,7 @@ async def run_studio(game_id, brief, *, max_iters=40, max_resumes=6, max_tokens=
|
||||
judge 调用 additive 追加品类扩展条目(分母独立小计落 richness.genre);None → 按 scaffold_template
|
||||
查 GENRE_BY_TEMPLATE 推断;两路都没有/条目缺 → 自动退化为纯通用底座评分。
|
||||
|
||||
run_gates=True(默认,spike 单局):收口跑全套 stage→smoke→ensure_play_spec→九门 play。
|
||||
run_gates=True(默认,spike 单局):收口跑全套 stage→smoke→九门 play(undriven;ensure_play_spec 波2 已摘)。
|
||||
run_gates=False(generation-only,给 compare_node 对照用):收口只到 stage+smoke 为止,**不**自动产 play-spec、
|
||||
**不**跑内部九门 play —— 由调用方在生成与 play 之间注入金标 spec 再单独 play,把驱动器从对照变量里摘掉
|
||||
(Codex C1:run_studio 内部已 play,对照需把生成与 play 拆开)。
|
||||
@ -378,7 +369,7 @@ async def run_studio(game_id, brief, *, max_iters=40, max_resumes=6, max_tokens=
|
||||
breaker_tripped = {"kind": None, "reason": f"{type(e).__name__}: {e}"}
|
||||
_rec(f"未捕获异常:{type(e).__name__}: {e}")
|
||||
|
||||
# ── 收口:finish 后 stage → smoke → ensure_play_spec → 循环外九门 play ──
|
||||
# ── 收口:finish 后 stage → smoke →循环外九门 play ──
|
||||
# CLI 回喂对齐后,run_gates 路的九门收口已在循环内跑过(closeout),此处直接复用最后一轮结果,
|
||||
# 不重复跑门;对照路(run_gates=False)保持原「只 stage+smoke」行为零改动。
|
||||
finished = session.finished is not None
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
"""
|
||||
compare_node.py — 便宜档 Python 路 vs Node 旧路对照验证。
|
||||
|
||||
【封存(W-AXIS-V2 波2,2026-07-10)】对照范式以金标 play-spec 驱动九门(H_progress 为可玩门);play-spec/
|
||||
驱动器已随取证契约退出便宜档验收链,Node 旧路亦已退役——两路对照使命完结。保留代码供历史复跑;
|
||||
生产链不再调用,play-spec 生成器窗口期结束后随其一并清理。
|
||||
|
||||
两层用法:
|
||||
· 单品类对照(spike U6 起点):compare_logic / build_report / main_async —— 同 brief 各跑一次,纯逻辑见下。
|
||||
· 三品类批量对照(WU-A 后续 U2):run_multi —— 三个 tap-targets 代表品类(点击得分/打地鼠/经营点客)各 n=3–5,
|
||||
|
||||
@ -51,12 +51,22 @@ COVERED_BRIEFS = [
|
||||
|
||||
|
||||
def _row_total_cost(row: dict) -> float:
|
||||
"""一局的总成本 = 生成 costRmb + 判定 judge.costRmb(判定用独立档,与生成台账隔离,累计兜底要两者都算)。"""
|
||||
"""一局的总成本 = 生成 costRmb + 判定 judge.costRmb + 测试员 playtest.costRmb(W-AXIS-V2 波2)。
|
||||
|
||||
v2 模式下 judge 段镜像 playtest 裁决、两者 costRmb 同值——取 max 只计一次(防同一笔钱双计);
|
||||
shadow 模式下 judge 是判定器成本、playtest 是测试员成本,两笔真实分立——相加。据 acceptanceVersion 分辨。
|
||||
"""
|
||||
gen = row.get("costRmb") or 0.0
|
||||
j = row.get("judge") or {}
|
||||
pt = row.get("playtest") or {}
|
||||
jc = j.get("costRmb") or 0.0
|
||||
pc = pt.get("costRmb") or 0.0
|
||||
try:
|
||||
return float(gen) + float(jc)
|
||||
if row.get("acceptanceVersion") == "v2":
|
||||
extra = max(float(jc or 0.0), float(pc or 0.0)) # v2:judge 段镜像 playtest,同一笔钱别双计
|
||||
else:
|
||||
extra = float(jc or 0.0) + float(pc or 0.0) # shadow/v1:判定器与测试员各自真花,分立累计
|
||||
return float(gen) + extra
|
||||
except (TypeError, ValueError):
|
||||
return 0.0
|
||||
|
||||
@ -102,6 +112,7 @@ async def _run_one(genre, brief, port, cdp, round_no):
|
||||
rich = summary.get("richness") or {}
|
||||
layer = summary.get("failureLayer") or {} # classify_cheap_failure_layer 返回 {layer,reason,failedGates}
|
||||
j = summary.get("judge") or {} # run_acceptance 写入(仅 run_gates∧finished 时有)
|
||||
pt = summary.get("playtest") or {} # 测试员段(W-AXIS-V2 波2:v2/shadow 时有,批账观测口径)
|
||||
return {
|
||||
"genre": genre, "gid": gid, "round": round_no,
|
||||
"template": scaffold_template or "_template(通用)",
|
||||
@ -120,6 +131,14 @@ async def _run_one(genre, brief, port, cdp, round_no):
|
||||
"costRmb": j.get("costRmb"), "reason": j.get("reason"),
|
||||
"model": j.get("model"), "frames": j.get("frames"),
|
||||
} if j else None,
|
||||
# ── 测试员段(W-AXIS-V2 波2 §4:裁决/degraded/成本/rolls/首局三字段——批账观测口径):
|
||||
"playtest": {
|
||||
"accepted": pt.get("accepted"), "verdict": pt.get("verdict"),
|
||||
"degraded": pt.get("degraded"), "imageBlind": pt.get("imageBlind"),
|
||||
"rollCount": pt.get("rollCount"), "costRmb": pt.get("costRmb"),
|
||||
"reason": pt.get("reason"), "summary": pt.get("summary"),
|
||||
"firstPlay": pt.get("firstPlay"), "model": pt.get("model"),
|
||||
} if pt else None,
|
||||
# ── 失败归因三层(driver_contract/mechanical/gameplay/none)+ 归档指针:
|
||||
"failedGates": v.get("failedGates"),
|
||||
"failureLayer": layer.get("layer"),
|
||||
@ -170,12 +189,14 @@ async def main():
|
||||
f.write(json.dumps(r, ensure_ascii=False) + "\n")
|
||||
cumulative += _row_total_cost(r)
|
||||
j = r.get("judge") or {}
|
||||
pt = r.get("playtest") or {}
|
||||
print(f"{_mark(r):>13s} r{round_no} {genre:14s} tpl={r.get('template','?')} "
|
||||
f"accepted={r.get('accepted')} floorPass={r.get('floorPass')} ver={r.get('acceptanceVersion')} "
|
||||
f"verdictPass(对照)={r.get('verdictPass')} finished={r.get('finished')} "
|
||||
f"playtest={pt.get('verdict')}/rolls={pt.get('rollCount')} ptDegraded={pt.get('degraded')} "
|
||||
f"judge={j.get('verdict')}/{j.get('rejectClasses')} degraded={j.get('degraded')} "
|
||||
f"layer={r.get('failureLayer')} attempts={r.get('attempts')} "
|
||||
f"genCost=¥{r.get('costRmb')} judgeCost=¥{j.get('costRmb')} wall={r.get('wallSec')}s "
|
||||
f"genCost=¥{r.get('costRmb')} judgeCost=¥{j.get('costRmb')} ptCost=¥{pt.get('costRmb')} wall={r.get('wallSec')}s "
|
||||
f"累计=¥{cumulative:.2f} {('FAIL='+r['fail']) if r.get('fail') else ''}")
|
||||
# ¥200 跨局硬停:立即停机,已完成局全在 JSONL,报 PARTIAL。
|
||||
if cumulative > _HARD_STOP_RMB:
|
||||
|
||||
@ -53,6 +53,7 @@ async def _run_one(sem, tag, brief, port, cdp):
|
||||
v = summary.get("verdict") or {}
|
||||
floor = summary.get("floor") or {}
|
||||
rich = summary.get("richness") or {}
|
||||
pt = summary.get("playtest") or {} # 测试员段(W-AXIS-V2 波2 批账观测口径)
|
||||
return {
|
||||
"tag": tag, "gid": gid, "template": scaffold_template or "_template(通用)",
|
||||
"locked": write_whitelist is not None,
|
||||
@ -61,6 +62,10 @@ async def _run_one(sem, tag, brief, port, cdp):
|
||||
"floorPass": floor.get("pass"), # 预筛权威 = 四门投影(取代 verdictPass)
|
||||
"acceptanceVersion": summary.get("acceptanceVersion"),
|
||||
"verdictPass": v.get("pass"), # harness 原生九门/七门,仅对照、不作判定输入
|
||||
"playtest": { # 测试员裁决摘要(裁决/degraded/rolls/成本)
|
||||
"verdict": pt.get("verdict"), "degraded": pt.get("degraded"),
|
||||
"rollCount": pt.get("rollCount"), "costRmb": pt.get("costRmb"),
|
||||
} if pt else None,
|
||||
"failedGates": v.get("failedGates"), "attempts": summary.get("attempts"),
|
||||
"costRmb": summary.get("costRmb"), "richness": rich.get("score"),
|
||||
"wallSec": round(time.time() - t0, 1),
|
||||
@ -79,11 +84,13 @@ async def main():
|
||||
fp = r.get("floorPass") is True
|
||||
passed += 1 if acc else 0
|
||||
mark = "✅" if acc else ("⚠️测试员拒" if fp else ("⚠️FALSE-PASS" if r.get("ok") else "❌"))
|
||||
pt = r.get("playtest") or {}
|
||||
print(f"{mark} {r['tag']:16s} tpl={r.get('template')} lock={r.get('locked')} "
|
||||
f"accepted={r.get('accepted')} floorPass={r.get('floorPass')} ver={r.get('acceptanceVersion')} "
|
||||
f"verdictPass(对照)={r.get('verdictPass')} failedGates={r.get('failedGates')} "
|
||||
f"verdictPass(对照)={r.get('verdictPass')} playtest={pt.get('verdict')}/rolls={pt.get('rollCount')} "
|
||||
f"ptDegraded={pt.get('degraded')} failedGates={r.get('failedGates')} "
|
||||
f"attempts={r.get('attempts')} rich={r.get('richness')}/12 "
|
||||
f"cost={r.get('costRmb')} wall={r.get('wallSec')}s {('FAIL='+r['fail']) if r.get('fail') else ''}")
|
||||
f"cost={r.get('costRmb')} ptCost=¥{pt.get('costRmb')} wall={r.get('wallSec')}s {('FAIL='+r['fail']) if r.get('fail') else ''}")
|
||||
print(f">>> 跨主题换皮达标(accepted 最终验收 = 四门 ∧ 测试员):{passed}/{len(results)} 达标率={passed/len(results):.0%}")
|
||||
out = Path(__file__).resolve().parent / "results" / "hard-genre-xtheme.json"
|
||||
out.parent.mkdir(exist_ok=True)
|
||||
|
||||
@ -59,11 +59,15 @@ def test_puzzle_exemplars_pass_contract_check():
|
||||
|
||||
|
||||
def test_puzzle_exemplars_forensics_contract_present():
|
||||
"""静态兜底:两件范例的 game-logic.js 必须带解谜取证契约关键面(createGame/_forensicsView/targets/occupied)。"""
|
||||
"""静态兜底:两件范例的 game-logic.js 必须带现行输入契约面(createGame/handleTap/bundle.tick)。
|
||||
|
||||
W-AXIS-V2 波2:取证契约(targets/occupied)随 tap-targets 驱动器退役,不再是范例必备面;
|
||||
_forensicsView 降级为可选调试接口(范例保留实现但不作断言)。
|
||||
"""
|
||||
for tmpl in _PUZZLE_EXEMPLARS:
|
||||
src = (_GAME_RUNTIME / "games" / tmpl / "src" / "game-logic.js").read_text(encoding="utf-8")
|
||||
for needle in ("export function createGame({ plugins, bundle, viewport })",
|
||||
"_forensicsView", "targets", "occupied", "bundle.tick"):
|
||||
"handleTap", "bundle.tick"):
|
||||
assert needle in src, f"{tmpl}: game-logic.js 缺契约面「{needle}」"
|
||||
|
||||
|
||||
|
||||
@ -13,10 +13,12 @@ from cheap_roles import build_system_prompt, SKILL_PATH
|
||||
|
||||
|
||||
def test_entry_contract_present():
|
||||
"""入口契约五法 + _forensicsView 在场。"""
|
||||
"""入口契约五法在场;_forensicsView 取证契约教学已清洗(W-AXIS-V2 波2:自动验收随驱动器退役,
|
||||
验收 = 四门投影 ∧ 测试 agent 真玩,模型不再为死契约写代码)——正文必须零残留。"""
|
||||
p = build_system_prompt("t1")
|
||||
assert "createGame({ plugins, bundle, viewport })" in p
|
||||
assert "_forensicsView" in p
|
||||
assert "_forensicsView" not in p
|
||||
assert "occupied" not in p # targets/occupied 语义同批清洗
|
||||
assert "init(boot)/update(dt)/render(g)/destroy()" in p
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
id: config.cheap-system
|
||||
version: 1.6.3
|
||||
version: 1.7.0
|
||||
---
|
||||
|
||||
你是 A-model 游戏生成 agent。目标产物 = 一款**能跑能玩、有内容、不易同质化的高质小游戏**(多文件 LittleJS),落在 ⟦G⟧/。
|
||||
@ -11,7 +11,7 @@ version: 1.6.3
|
||||
- 我已把克隆起点拷到 ⟦G⟧/(⟦SCAFFOLD_DESC⟧)。你的活是据 brief **借插件能力把它改造成一款高质游戏**——别把它当"这档就该这么简单"的天花板。
|
||||
- **L1 固定 plumbing 别动(write 会被拒)**:⟦G⟧/index.html、⟦G⟧/entry-bundle.js、⟦G⟧/src/game.js(薄 wrapper)、**⟦G⟧/src/host-config.js**(host 装配:已替你注入**全 11 件能力插件** + viewport 固定 390×844 + 工厂 wiring;你无需装配任何插件)。
|
||||
- **你只写 L3 游戏本体**:**⟦G⟧/src/game-logic.js(必写,游戏核心)** + 按需 core.js(纯逻辑)/ render.js(画面)/ balance.js(数值)/ assets.js(资产)。结构简单可把逻辑/画面集中在 game-logic.js 内;多系统/多场景再拆 core/render。把脚手架起点改造成 brief 要的**高质游戏**(多借插件能力做卖相与深度);**画面读 viewport.w/h(390×844 竖屏)**。
|
||||
- **入口契约(钉死)**:game-logic.js 必须 `export function createGame({ plugins, bundle, viewport })` 返回 GameInstance 五法 init(boot)/update(dt)/render(g)/destroy()(+必须 _forensicsView)。**插件已扁平注入 plugins**,用到才解构:sceneFsm/sessionScore/hudUi/timerScheduler/save/gamefeel/juice/palettePost/audioMusic/collision/physics。
|
||||
- **入口契约(钉死)**:game-logic.js 必须 `export function createGame({ plugins, bundle, viewport })` 返回 GameInstance 五法 init(boot)/update(dt)/render(g)/destroy()。**插件已扁平注入 plugins**,用到才解构:sceneFsm/sessionScore/hudUi/timerScheduler/save/gamefeel/juice/palettePost/audioMusic/collision/physics。
|
||||
|
||||
【先读手册(必须;别凭记忆猜 API)】
|
||||
1. read_file('.agents/skills/littlejs-game-dev.md') —— code 层作业手册(结构/边界/红线/11 注入插件 API 速查)。
|
||||
@ -19,10 +19,10 @@ version: 1.6.3
|
||||
3. 用到某插件就 read_file 它的 api.d.ts(如 game-runtime/src/plugins/scene-fsm/api.d.ts、.../session-score/api.d.ts)看精确签名。
|
||||
4. 你的起点 ⟦G⟧/src/game-logic.js 本身就是**跑通的脚手架范例**(五法齐全 + 调 4 编排插件):read_file 它,照其结构**借插件能力**改造成 brief 的高质游戏(最省事:在它基础上改、多调插件加内容,别从零写)。
|
||||
5. **若 brief 属经营 / 养成 / 放置 / 点客这类「攒资源→成长→解锁」品类**:read_file('.agents/skills/sim-business-game-design.md') 取「设计什么才好玩」的范式(6 心理引擎 / §1 玩法范式含进货补货环+决策模式清单 / §8 设计配方 / §10 反无趣自检)。littlejs-game-dev 教你「代码怎么写」、sim-business 教你「设计什么才好玩」——配对用:先想清好玩、再写正确。
|
||||
6. **若 brief 属剧情 / 互动叙事 / 文字冒险这类「读文本→做选择→推进剧情」品类**:read_file('.agents/skills/narrative-game-design.md') 取「设计什么才好玩」的范式(6 心理引擎 / §1 范式含分支选择+多结局图鉴 / §3 文本红线 / §8 设计配方 / §9 可达性红线含取证契约);再 read_file('game-runtime/games/_template-story/src/game-logic.js') 看剧情品类过门范例(分支推进 + 结局 latch + 结局图鉴的骨架写法,其故事数据在同目录 core.js 顶部参数空间)。
|
||||
6. **若 brief 属剧情 / 互动叙事 / 文字冒险这类「读文本→做选择→推进剧情」品类**:read_file('.agents/skills/narrative-game-design.md') 取「设计什么才好玩」的范式(6 心理引擎 / §1 范式含分支选择+多结局图鉴 / §3 文本红线 / §8 设计配方 / §9 可达性红线);再 read_file('game-runtime/games/_template-story/src/game-logic.js') 看剧情品类过门范例(分支推进 + 结局 latch + 结局图鉴的骨架写法,其故事数据在同目录 core.js 顶部参数空间)。
|
||||
7. **若 brief 属 TRPG / 掷骰 / 冒险 / 地牢爬塔这类「掷骰对抗→抉择→成长」品类**:read_file('.agents/skills/trpg-game-design.md') 取「设计什么才好玩」的范式(6 心理引擎 / §1 玩法范式含掷骰爬塔 / §3 掷骰概率数值 / §9 红线〔核心动作单击即结算·掷骰过程可见〕 / §10 反无趣自检)。
|
||||
8. **若 brief 属非遗 / 传统技艺 / 民俗这类「传统文化玩法化」品类**:read_file('.agents/skills/heritage-game-design.md') 取「设计什么才好玩」的范式(§1 玩法化范式含工序节拍 / §3 双层奖励与工序节奏目标值 / §9 文化表述红线 / §10 反贴皮自检);需要完整过门范例再 read_file('game-runtime/games/_fewshot-feiyi/src/game-logic.js')。
|
||||
9. **若 brief 属解谜(找规律 / 找不同 / 翻牌配对 / 顺序推理 / 点选消除这类「观察→推理→点解」品类)**:read_file('.agents/skills/puzzle-game-design.md') 取解谜设计范式(顿悟距离 / 规则递进关卡阶梯 / 卡壳兜底 / 成绩炫耀 / §9 品类可达红线——解谜的「解」必须映射到 targets occupied);想看「丰富成什么样」的完整过门正例,可 read_file('game-runtime/games/_fewshot-puzzle/src/game-logic.js') 参照。
|
||||
9. **若 brief 属解谜(找规律 / 找不同 / 翻牌配对 / 顺序推理 / 点选消除这类「观察→推理→点解」品类)**:read_file('.agents/skills/puzzle-game-design.md') 取解谜设计范式(顿悟距离 / 规则递进关卡阶梯 / 卡壳兜底 / 成绩炫耀 / §9 品类可达红线);想看「丰富成什么样」的完整过门正例,可 read_file('game-runtime/games/_fewshot-puzzle/src/game-logic.js') 参照。
|
||||
|
||||
【先设计后写码:决定这游戏好不好玩(关键,别跳过)】
|
||||
能跑 ≠ 好玩。动手写码前,先据 brief 在 **game-logic.js 顶部写一段设计注释块**(≤8 行:⑨ 判定句三个空填好 + 核心数值锚 + ⑩ 的三段难度)定下**轻量玩法设计**,再实现——设计只在心里 = 收尾自查与验收都无从对照:
|
||||
@ -45,7 +45,7 @@ A 读手册(1 + 按品类 5/6/7/8/9)+ 读你的起点 game-logic.js(4)
|
||||
- game-logic.js 必须 **export function createGame({ plugins, bundle, viewport })**(命名导出、别改名、别默认导出);**零引擎 import**(引擎经 boot.ctx.getEngine())。
|
||||
- 游戏源(game-logic / core / render)**零裸** Math.random / Date.now / setTimeout / requestAnimationFrame / new AudioContext / addEventListener —— 时间随机经 boot.ctx,定时经 timer-scheduler,音效经 plugins.audioMusic;**输入不调 ctx.getInput**(已收归 L1,见下「输入契约」)。
|
||||
- update(dt) 内**必须调 bundle.tick(dt)**(bundle 是 createGame 入参;否则插件 onFrame 不推进、timer 永不到期、一局不结束)。**dt 单位是「秒」(≈1/60)**——游戏内若用毫秒计时(倒计时 / spawn 间隔 / 顾客耐心,常量都是 ms 如 60000/1300),累加时务必「elapsedMs += dt * 1000」,漏 ×1000 → 计时慢 1000 倍 → 永不到 spawn 阈值=零顾客、一局不结束。**timerScheduler.after(ms,cb)/every(ms,cb) 同样收毫秒(#7):3 秒写 after(3000)、别写 after(3);与 dt=秒相反,别混。**
|
||||
- **入参是扁平的 { plugins, bundle, viewport }——没有 opts、没有 opts.runtime、没有 ctx**(L1 已替你摊平)。受控面 ctx 只在五法 **init(boot) 的 boot.ctx**;**调用形态**:时间 **ctx.time()** 或 ctx.time.nowMs()、随机 **ctx.random()** 或 ctx.random.next()/ctx.random.range(a,b)、画布 ctx.getContext2d()、引擎 ctx.getEngine()、日志 ctx.log(tag,msg)。**#1:ctx.time/ctx.random 既可直接调用(ctx.time()=毫秒、ctx.random()=[0,1))、又保留方法(.nowMs()/.elapsedMs()、.next()/.range(a,b)/.reseed())——两形态都对**。**但 nowMs/next 是 time/random 上的方法、不是 ctx 上的——别写 ctx.nowMs()(应 ctx.time() 或 ctx.time.nowMs())。**五法 = init(boot)/update(dt)/render(g)/destroy()(+必须 _forensicsView)+ **输入方法 handleTap(x,y)**(见下「输入契约」)。
|
||||
- **入参是扁平的 { plugins, bundle, viewport }——没有 opts、没有 opts.runtime、没有 ctx**(L1 已替你摊平)。受控面 ctx 只在五法 **init(boot) 的 boot.ctx**;**调用形态**:时间 **ctx.time()** 或 ctx.time.nowMs()、随机 **ctx.random()** 或 ctx.random.next()/ctx.random.range(a,b)、画布 ctx.getContext2d()、引擎 ctx.getEngine()、日志 ctx.log(tag,msg)。**#1:ctx.time/ctx.random 既可直接调用(ctx.time()=毫秒、ctx.random()=[0,1))、又保留方法(.nowMs()/.elapsedMs()、.next()/.range(a,b)/.reseed())——两形态都对**。**但 nowMs/next 是 time/random 上的方法、不是 ctx 上的——别写 ctx.nowMs()(应 ctx.time() 或 ctx.time.nowMs())。**五法 = init(boot)/update(dt)/render(g)/destroy() + **输入方法 handleTap(x,y)**(见下「输入契约」)。
|
||||
- **插件扁平注入:直接 const { sceneFsm, sessionScore, hudUi, timerScheduler } = plugins(用到才解构)**;**绝不写 opts.runtime.plugins**(那是 L1 内部的,你见不到 → 写了就 undefined → boot 崩 reading 'define')。游戏只用插件、不 new、不 register。
|
||||
- **可用插件键(全 11 已注入,用到才取)**:sceneFsm / sessionScore / hudUi / timerScheduler / save / gamefeel / juice / palettePost / audioMusic / collision / physics。
|
||||
- **插件方法名一律以 api.d.ts 为准、别臆测**(实测幻觉:scene-fsm 写 defineScenes〔应 define〕)。用到某插件先 read_file 它的 api.d.ts;skill「⚠️ 实测易犯的幻觉 API」逐条列 ❌→✅。
|
||||
@ -53,7 +53,6 @@ A 读手册(1 + 按品类 5/6/7/8/9)+ 读你的起点 game-logic.js(4)
|
||||
- **sceneFsm.define(name, handlers) 逐场景调一次——绝不单对象批量注册**:❌ sceneFsm.define({ menu: {...}, play: {...}, over: {...} }) → 第一参当成了场景名「[object Object]」、menu/play/over 全没真注册 → 后续 transition('play') 找不到目标态被忽略 → 卡菜单(实测合成/2048 类翻车点)。✅ **每个场景各调一次**:sceneFsm.define('menu', { onEnter(){...} }); sceneFsm.define('play', { onEnter(){...}, update(dt){...} }); sceneFsm.define('over', { onEnter(){...} });(define 第一参恒为场景名字符串、第二参才是回调表 { onEnter, onExit, update, render };**回调表里 update 只收 (dt)、render 只收 (g)、与五法同形——别臆造场景名/state 首参,绝不写 update(scene, dt)**;可链式 .define(...).define(...))。
|
||||
- **画面读 viewport.w/h**(viewport 是入参;别硬编码 390×844)。
|
||||
- **美术资产(若 assets/manifest.json 非空)**:先 read_file('assets/manifest.json') 看有哪些资产;init(boot) 里 const assets = boot.assets || {};render 里 const img = assets['ref名']?.image; if (img) g.drawImage(img, x, y, w, h); else 程序化回退。**ref = manifest 条目 file 去扩展名**(shopkeeper-idle.jpg → 'shopkeeper-idle')。缺图/加载失败时 assets[ref].image 为 null(host 已容错)→ 务必走程序化回退、别崩。BGM/音效经 plugins.audioMusic(不在 boot.assets)。无 manifest/无美术 → 全程序化绘制即可。
|
||||
- **必须实现 _forensicsView()**(可测性红线 + 自动驱动契约):返回 { state(){…}, measures(){…} };state() 至少 { phase, score }。**若游戏靠点击推进(经营点客/打地鼠/点离散目标等):state().targets:[{x,y,occupied}] 列当前屏上目标,可点的(等待的客/冒头的鼠)置 occupied:true、不可点置 false** —— 自动验收据此点 occupied:true 的目标真驱动你的游戏。**键名固定为 `targets`,不得改成 items/objects 等**:判卷驱动器靠 state 有无 `targets` 键选驱动器族(点目标族 vs 循环按键族),改名=选错族=盲驱动器驱动不了你的游戏=H 进展门挂。**score 用累计进展(营收/得分),随推进上升** —— 自动验收据 score 上升判"真有进展"。不写则验证读不到状态(check 会拦)。**钉死:phase/targets/score 必须在 state() 函数体内部实时读取/计算——绝不在 _forensicsView() 外层先算好再闭包返回**(host 只在 boot 调一次 _forensicsView,外层算的值会被定格成 boot 快照=永远 menu、targets 永远空 → 自动验收看不到顾客、永判死菜单)。
|
||||
- **关键事件用 ctx.log 记日志**:在 init/场景切换/得分/出错处 调 ctx.log('tag', 信息)。**ctx 来自 init 的入参 boot,即「const ctx = boot.ctx」——绝不是 boot.boot.ctx**(host 传入的 boot 已是 {ctx, mainContext, canvas, seed, assets},多套一层 .boot=undefined → ctx=null → 无输入、游戏点不动;check 会拦 .boot.ctx)。插件调用已自动记,你只补游戏语义事件。
|
||||
- **输入契约(钉死·根治"启动不了")**:输入订阅已由 L1(game.js wrapper)接管,**你绝不自己订阅输入**——不写 ctx.getInput、不自建 pendingClicks 队列、不在 update(dt) 里挑时机消费点击。你只写实例方法 **handleTap(x, y)**(点击主输入:经营点客 / 打地鼠 / 点按钮等;**menu/玩中/结算各 phase 的判定全写在它内部**),L1 会在每次 pointerdown 时**直达**调用它;键盘玩法(方向键 / 空格)写 **handleKey(key)**,L1 在 keydown 时调用。check 会拦「调了 getInput」与「一个输入方法都没暴露」。
|
||||
|
||||
|
||||
@ -176,7 +176,7 @@ prompts:
|
||||
# 首版正文 = _SYSTEM_PROMPT 内置原文逐字节对齐(含 ⟦G⟧/⟦SCAFFOLD_DESC⟧ 占位符),默认行为字节不变;
|
||||
# 调一条 prompt = 改 04-config/cheap-system.md 正文 + 升 version,下次生成自动生效。
|
||||
- id: config.cheap-system
|
||||
version: 1.6.3 # v1.6.3(W-AXIS 波2 拆残笼):①「两层奖励」改纯游戏设计语(基础反馈保上手+技巧分给深度)、删「围盲驱动器/绕正则」措辞;②「check 正则命中」改「词法真实命中、报错带行号」(红线判定换词法);③删整文件重写熔断恐吓、edit_file 补锚点容错说明(空白归一/近似片段)。v1.6.2:正文补 edit_file 工具契约(七工具/小改优先 edit_file、别整文件重写)——修热取源停在「六工具/write_file 整体覆盖」的 stale 欠账,治大文件整写截断(便宜档死圈病根之一)。v1.6.1:起局鲁棒性 + targets 键契约进红线——①起局/交互按钮命中矩形必须在状态进入点 onEnter 确定性建立、render 只画(治并发 rAF 饥饿下卡 menu)②点目标玩法 state 键名固定 `targets`(驱动器契约 C5 依赖,改名=盲驱动器选错族=H 门挂)。v1.6.0:打磨不降档(创始人 07-04 定调「档位切 scope 不切 polish」)——删 spike「能跑能玩即可」残留、hitstop 出禁项改结算标配、设计块强制落盘+finish 前收尾自查、新增 ⑩ 难度曲线、⑨ 经营决策模式指针。v1.5.0:策划知识包 v2——⑨ 否决项加判定句式+内嵌正反例;8/9 条计数口径统一;prompt.mjs 冻结为 A/B 史料(不再双源同改)。v1.4.0:W-GENRE 品类路由并集(剧情+6/TRPG+7/非遗+8/解谜+9)
|
||||
version: 1.7.0 # v1.7.0(W-AXIS-V2 波2 契约退役):删 _forensicsView「自动验收契约」整条红线与全部 targets/occupied 语义(五法句/品类指路同步)——tap-targets 驱动器与 play-spec 退出便宜档验收链,验收 = 四门投影 ∧ 测试 agent 视觉引导真玩,模型不再为死契约写代码;handleTap 输入契约与 ctx.log 日志纪律保留(服务真人与测试员)。v1.6.3(W-AXIS 波2 拆残笼):①「两层奖励」改纯游戏设计语(基础反馈保上手+技巧分给深度)、删「围盲驱动器/绕正则」措辞;②「check 正则命中」改「词法真实命中、报错带行号」(红线判定换词法);③删整文件重写熔断恐吓、edit_file 补锚点容错说明(空白归一/近似片段)。v1.6.2:正文补 edit_file 工具契约(七工具/小改优先 edit_file、别整文件重写)——修热取源停在「六工具/write_file 整体覆盖」的 stale 欠账,治大文件整写截断(便宜档死圈病根之一)。v1.6.1:起局鲁棒性 + targets 键契约进红线——①起局/交互按钮命中矩形必须在状态进入点 onEnter 确定性建立、render 只画(治并发 rAF 饥饿下卡 menu)②点目标玩法 state 键名固定 `targets`(驱动器契约 C5 依赖,改名=盲驱动器选错族=H 门挂)。v1.6.0:打磨不降档(创始人 07-04 定调「档位切 scope 不切 polish」)——删 spike「能跑能玩即可」残留、hitstop 出禁项改结算标配、设计块强制落盘+finish 前收尾自查、新增 ⑩ 难度曲线、⑨ 经营决策模式指针。v1.5.0:策划知识包 v2——⑨ 否决项加判定句式+内嵌正反例;8/9 条计数口径统一;prompt.mjs 冻结为 A/B 史料(不再双源同改)。v1.4.0:W-GENRE 品类路由并集(剧情+6/TRPG+7/非遗+8/解谜+9)
|
||||
stage: "04-config"
|
||||
owner: WS2
|
||||
file: 04-config/cheap-system.md
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
|
||||
- **换手艺不重写循环**:工序链(刻版→上墨→铺纸→拓印)与作品表(福字/门神/财神/麒麟/童子抱鱼)全落在 core.js 参数空间;工序节拍结构(就绪→窗口内点击→出成品→回炉)原样保留。
|
||||
- **在循环上加机制**:正窗暴击 + 连击、攒工钱解锁新图样(本品类成长轴 = 工艺步骤/图样解锁)、结算屏摆出本局印成的年画(炫耀时刻)。
|
||||
- **取证契约不破**:`_forensicsView().state()` 的 `targets`(occupied=当前就绪工位)与 `score`(单调上升)语义未动——这是过九门的命脉。
|
||||
- **调试视图不破**:`_forensicsView` 保留为可选调试接口(W-AXIS-V2 起验收 = 四门投影 ∧ 测试 agent 真玩,不再读状态导出)。
|
||||
|
||||
## 复验配方(怀疑漂移时跑)
|
||||
|
||||
@ -24,8 +24,7 @@ node tools/amodel-gen/tools.mjs scaffold-saa p11d-fewshot-recheck _template-feiy
|
||||
cp games/_fewshot-feiyi/src/*.js games/amgen-p11d-fewshot-recheck/src/
|
||||
node -e "import('./tools/amodel-gen/tools.mjs').then(m=>process.exit(m.check('p11d-fewshot-recheck').ok?0:1))"
|
||||
node tools/amodel-gen/tools.mjs build-saa p11d-fewshot-recheck && node tools/amodel-gen/tools.mjs stage p11d-fewshot-recheck
|
||||
cp ../cheap-worker/fixtures/golden-specs/heritage-craft.play-spec.json games/_wg1-gen/p11d-fewshot-recheck/play-spec.json
|
||||
bash games/_wg1-gen/_shared/serve-and-play.sh p11d-fewshot-recheck
|
||||
bash games/_wg1-gen/_shared/serve-and-play.sh p11d-fewshot-recheck # undriven 九门(W-AXIS-V2:验收权威=四门投影∧测试员,此处只作机器门快检)
|
||||
```
|
||||
|
||||
## 漂移纪律(设计档 §3⑥/§6:per-genre 正例是「必须同改」清单成员)
|
||||
|
||||
@ -10,12 +10,12 @@
|
||||
* · **吉祥图样解锁**:累计工钱达标(80/160)→ 麒麟/童子抱鱼入图鉴可印
|
||||
* · 60s 到点切 over 结算,再开一印
|
||||
*
|
||||
* 已在 _forensicsView.state 实时暴露工资 + combo + 暴击 + 解锁图样列表,
|
||||
* 满足九门 H 进展门(score 单调上升)与 E_live(targets 含就绪工位)。
|
||||
* _forensicsView.state 实时暴露工资 + combo + 暴击 + 解锁图样列表——可选调试接口,
|
||||
* 供人工调试观测(W-AXIS-V2 起验收 = 四门投影 ∧ 测试 agent 真玩,不再读状态导出)。
|
||||
*
|
||||
* 入参(L1 已摊平):createGame({ plugins, bundle, viewport })
|
||||
* 受控面 ctx 在 init(boot) 的 boot.ctx。
|
||||
* 返回:init/update/render/destroy + handleTap + _forensicsView。
|
||||
* 返回:init/update/render/destroy + handleTap(+可选 _forensicsView)。
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@ -115,7 +115,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
return;
|
||||
}
|
||||
if (cur === SCENE_PLAY) {
|
||||
// 工序核心:点中就绪工位 = 完成该道(1 步成序,自动验收据此涨分)。
|
||||
// 工序核心:点中就绪工位 = 完成该道(1 步成序,张力在火候窗口时机、不在选择流程)。
|
||||
if (!work || !isStepTappable(work, elapsedMs)) return;
|
||||
const st = stations[work.stepIndex];
|
||||
if (st && hitStation(x, y, st)) completeStep(st);
|
||||
@ -286,10 +286,8 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
},
|
||||
|
||||
/**
|
||||
* 取证契约:实时算 phase/score/targets/combo/解锁等。
|
||||
* ★ 必须在函数体内实时算,不能闭包冻结(否则 host 读 boot 快照=永 menu)。
|
||||
* targets = 每工位一项;当前工序在窗口内可点 → occupied:true,
|
||||
* 其余 false(自动验收 driver 据此驱动游戏)。
|
||||
* 调试视图(可选接口,验收不读):实时算 phase/score/targets/combo/解锁等,供人工调试观测。
|
||||
* ★ 函数体内实时算是好习惯,别闭包冻结(否则读到的恒是 boot 快照=永 menu)。
|
||||
*/
|
||||
_forensicsView() {
|
||||
return {
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
| 同款钩子 | `GENRE_META`(品类原型 rule-match-board + 主题标签) | 同款钩子(L4) |
|
||||
| 卡壳兜底 | 8s 无进展剩余解脉动(卡壳不出局) | P4 卡壳兜底 |
|
||||
|
||||
**取证契约与骨架完全一致**(丰富不碰契约):`targets` 只把当前正确解标 `occupied:true`;`score` 随点对/过关单调升;menu/over 期 `targets:[]` 键在场。
|
||||
**调试视图与骨架完全一致**(丰富不碰结构层):`_forensicsView` 为可选调试接口(W-AXIS-V2 起验收 = 四门投影 ∧ 测试 agent 真玩,不再读状态导出),保留它是给调试观测一个统一出口。
|
||||
|
||||
范例签名 == 现契约:本目录随 `cheap-worker/tests/test_genre_puzzle_assets.py` 走机器校验门(scaffold + tools.mjs check),API 漂移会在 CI 挂——**改插件签名时必须同步改本范例**(防它成下一代漂移源)。
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
* · 称号 + 跨局收集:总星数换称号;累计星星经 save 插件持久(回归可见积攒,第二动机)
|
||||
* · 结算炫耀:分数/星级/称号/通关数/最高连击/累计收集/最佳纪录(不是打完黑屏)
|
||||
* · 同款钩子:GENRE_META 品类原型 + 主题标签(remix 预填元数据)
|
||||
* 取证契约与骨架一致(targets 只把当前正确解标 occupied、score 单调升)——丰富不碰契约。
|
||||
* 调试视图与骨架一致(可选 _forensicsView,验收不读它)——丰富不碰结构层。
|
||||
* ════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
@ -290,7 +290,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
inited = false;
|
||||
},
|
||||
|
||||
/** 取证契约同骨架:targets 只把当前正确解标 occupied、score 单调升;丰富层键 additive。 */
|
||||
/** 调试视图同骨架(可选接口,验收不读):targets 标当前正确解便于调试观测;丰富层键 additive。 */
|
||||
_forensicsView() {
|
||||
return {
|
||||
state: () => ({
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# _template-feiyi — A-model「非遗工序节拍」品类骨架(3 层架构)
|
||||
|
||||
一个**可玩的完整工序游戏**:一件作品 = 一条有先后的工序链,工序依次「就绪」→ 在火候窗口内点击就绪工位完成该道 → 整条链走完出一件成品 → 错过窗口回炉重做 → 60 秒限时开工结算(menu→play→over)。这份骨架**已经把整套工序循环接好了**(含让自动验收能跑通的取证契约)——你的活是按 brief **换手艺**,不是重写循环。
|
||||
一个**可玩的完整工序游戏**:一件作品 = 一条有先后的工序链,工序依次「就绪」→ 在火候窗口内点击就绪工位完成该道 → 整条链走完出一件成品 → 错过窗口回炉重做 → 60 秒限时开工结算(menu→play→over)。这份骨架**已经把整套工序循环接好了**(含可选的调试视图)——你的活是按 brief **换手艺**,不是重写循环。
|
||||
|
||||
**怎么改 = 给这间工坊换一门手艺,循环结构别动:**
|
||||
- 换手艺/工序/作品/难度 → 改 `src/core.js` 顶部的「多样性参数空间」(`CRAFT_CHAIN` 工序链、`WORKS` 作品表、就绪间隔/窗口/回炉常量)。制陶写选料/塑形/上彩,织造写纺线/上机/织纹,灯彩写扎骨/糊面/点灯——**同骨架做出不雷同的手艺**就靠这里。工序名用该门手艺的**通识**工序词,别编造具体非遗项目的失实细节(设计手册 §9 红线)。
|
||||
- 换观感(案台布景/工位长相/配色)→ 改 `src/render.js`。
|
||||
- 想加机制(正窗暴击 combo/解锁新作品/图鉴)→ 在 `src/game-logic.js` 循环里加,但**别破坏 `_forensicsView` 的 `targets`/`score` 契约**(改坏=九门 H 进展门直接挂)。
|
||||
- 想加机制(正窗暴击 combo/解锁新作品/图鉴)→ 在 `src/game-logic.js` 循环里加。
|
||||
- **别推倒重写整个循环**:工序就绪/火候窗口/回炉/出成品/限时结算都接好了,从它起步只补身份,远比从零写省。
|
||||
- 玩法设计先读 [.agents/skills/heritage-game-design.md](../../../.agents/skills/heritage-game-design.md)(这个品类什么好玩:玩法化范式/节奏目标值/双层奖励/文化表述红线)。
|
||||
|
||||
@ -30,7 +30,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
render(g) { /* 画在 g;读 viewport.w/h */ },
|
||||
handleTap(x, y) { /* ★输入入口:各 phase 点击逻辑;L1 在 pointerdown 时直达调用(别自己订阅输入) */ },
|
||||
destroy() {},
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // ★工序档自动验收契约
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // 可选调试接口(验收不读)
|
||||
};
|
||||
}
|
||||
```
|
||||
@ -43,7 +43,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
- **零引擎 import**(引擎经 `boot.ctx.getEngine()`);游戏源**零裸** Math.random/Date.now/setTimeout/new AudioContext/addEventListener —— 时间随机经 boot.ctx,定时经 timer-scheduler,音效经 plugins.audioMusic;**输入不调 ctx.getInput**——写实例方法 `handleTap(x,y)`(点击)/`handleKey(key)`(键盘),L1 在 pointerdown/keydown 时直达调用。
|
||||
- **插件方法名一律以 `src/plugins/<name>/api.d.ts` 为准**,别臆测(scene-fsm 是 `define` 不是 defineScenes)。本骨架已用对的母语写法:`sessionScore.add(n)` 计分、`juice.burst(x,y)` 爆点、`hudUi.drawButton(g,rect,opts)` 返回命中矩形可直接接住判命中。
|
||||
- **画面读 `viewport.w/h`**,别硬编码 390×844。
|
||||
- **`_forensicsView().state()` 的工序契约**:`targets` 列每工位一项、「当前就绪且窗口内可点」的工位 `occupied:true`(自动验收只点这些工位驱动工序);`score` 随完成工序单调上升(自动验收据它判真有进展)。**换手艺加机制都别动这两个键的语义。**
|
||||
- **`_forensicsView()` 是可选调试接口**:state() 实时导出工序/工钱/窗口态等,供人工调试观测;W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩)。
|
||||
|
||||
## 你写什么 vs 调什么
|
||||
|
||||
|
||||
@ -11,17 +11,17 @@
|
||||
* · 火候窗口(ready 超 windowMs)过窗回炉:该道重做、损时间不清进度 + 轻屏震
|
||||
* · 整条链走完 = 出一件成品:成品奖励 + 庆祝,自动开下一件(WORKS 轮换)
|
||||
* · 60 秒限时开工 → 到点收工结算(sceneFsm 切 over,latch 终态)
|
||||
* · _forensicsView 的 targets/score 契约(自动验收靠它驱动你的游戏,**改坏它=九门 H 直接挂**)
|
||||
* · _forensicsView 调试视图(可选:host.state() 读它做调试观测,不影响验收)
|
||||
*
|
||||
* 你按 brief 改(身份层 · 多样性源,做出不雷同的手艺):
|
||||
* · 换手艺/工序/作品/难度 → 改 core.js 顶部的多样性参数空间(CRAFT_CHAIN、WORKS、节奏常量)
|
||||
* · 画面观感(案台长啥样、工位长啥样、配色)→ 改 render.js
|
||||
* · 想加机制(正窗暴击 combo、解锁新作品、图鉴)→ 在循环里加,但**别破坏 targets/score 契约**
|
||||
* · 想加机制(正窗暴击 combo、解锁新作品、图鉴)→ 在循环里加
|
||||
* · ⚠ 改写本文件时 **import 列表必须与你用到的 core.js 导出一一对齐**:漏 import 的裸标识符
|
||||
* check/build 都拦不住(esbuild 当全局放行),运行时点一下就 ReferenceError 被隔离 → 永卡菜单、九门全挂。
|
||||
*
|
||||
* 入参(L1 已摊平):createGame({ plugins, bundle, viewport });受控面 ctx 在 init(boot) 的 boot.ctx。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap + _forensicsView。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap(+可选 _forensicsView)。
|
||||
* ════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
@ -113,7 +113,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
return;
|
||||
}
|
||||
if (cur === SCENE_PLAY) {
|
||||
// 工序核心:点中「就绪工位」= 完成当前道(1 步成序,自动验收据此涨分)。
|
||||
// 工序核心:点中「就绪工位」= 完成当前道(1 步成序,张力在火候窗口时机、不在选择流程)。
|
||||
if (!work || !isStepTappable(work, elapsedMs)) return;
|
||||
const st = stations[work.stepIndex];
|
||||
if (st && hitStation(x, y, st)) completeStep(st);
|
||||
@ -256,11 +256,9 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
},
|
||||
|
||||
/**
|
||||
* 取证视图【自动验收契约·改坏=九门挂】:state() 内**实时**算(绝不外层先算好闭包返回)。
|
||||
* · targets:每工位一项,occupied=true 表示「当前就绪、窗口内可点」的工位——自动验收只点 occupied:true 的工位。
|
||||
* **键名固定为 `targets`,不得改成 items/objects 等**:判卷驱动器(C5)靠 state 有无 `targets` 键选驱动器族,改名=选错族=盲驱动点不到=H 门挂。
|
||||
* · score:= 工钱,随完成工序单调上升——自动验收据它判「真有进展」。
|
||||
* 加机制时这两个键的语义务必保持(targets 列可点目标、score 随进展升)。
|
||||
* 取证视图(可选调试接口):host.state() 据此读工序/工钱/窗口态等,供人工调试观测。
|
||||
* W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩);state() 内实时算是好习惯
|
||||
* (外层闭包会定格成 boot 快照,调试时读到的恒是旧值)。
|
||||
*/
|
||||
_forensicsView() {
|
||||
return {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
# _template-puzzle — A-model「规则谜盘」解谜品类轻脚手架(3 层架构)
|
||||
|
||||
一个**可玩的完整解谜游戏**:每关一条线索(找出所有【红色】/【星形】/【两颗】…)→ 观察盘面 → 点中所有符合的格子过关 → 关卡逐关引入新规则维度 → 通全关提前胜利 / 限时到点结算(menu→play→over)。这份骨架**已经把整套谜盘循环接好了**(含让自动验收能跑通的取证契约)——你的活是按 brief **换主题、加深度**,不是重写循环。
|
||||
一个**可玩的完整解谜游戏**:每关一条线索(找出所有【红色】/【星形】/【两颗】…)→ 观察盘面 → 点中所有符合的格子过关 → 关卡逐关引入新规则维度 → 通全关提前胜利 / 限时到点结算(menu→play→over)。这份骨架**已经把整套谜盘循环接好了**(含可选的调试视图)——你的活是按 brief **换主题、加深度**,不是重写循环。
|
||||
|
||||
**怎么改 = 给这款解谜换身份,循环结构别动:**
|
||||
- 换主题/元素/文案 → 改 `src/core.js` 的 `THEME`(shapes/colors/countNames/标题)。宝石写圆/菱/星,水果写苹果/香蕉/葡萄,海洋写鱼/贝/星——**同骨架做出不雷同的解谜**就靠这里。
|
||||
- 调难度/关卡阶梯 → 改 `src/core.js` 的 `LEVELS`(盘面大小/解数/规则维度逐关递进)与节奏常量(观察窗/锁定/连击窗)。
|
||||
- 换观感(底景/元素画法/配色)→ 改 `src/render.js`(`drawGlyph` 把圆/菱/星换成你的元素)。
|
||||
- 想加机制(星级/图鉴/新规则维度/每日谜题)→ 在 `src/game-logic.js` 循环里加,但**别破坏 `_forensicsView` 的 `targets`/`score` 契约**(改坏=九门 H 进展门直接挂)。
|
||||
- 想加机制(星级/图鉴/新规则维度/每日谜题)→ 在 `src/game-logic.js` 循环里加。
|
||||
- **别推倒重写整个循环**:出题/观察窗/点解判定/连击/过关递进/限时结算/卡壳提示都接好了,从它起步只补身份与深度,远比从零写省。
|
||||
- 「设计什么才好玩」读 `.agents/skills/puzzle-game-design.md`(顿悟距离/规则递进/卡壳兜底/成绩炫耀);完整过门正例参照 `game-runtime/games/_fewshot-puzzle/src/`。
|
||||
|
||||
@ -31,7 +31,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
render(g) { /* 画在 g;读 viewport.w/h */ },
|
||||
handleTap(x, y) { /* ★输入入口:各 phase 点击逻辑;L1 在 pointerdown 时直达调用(别自己订阅输入) */ },
|
||||
destroy() {},
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // ★解谜档自动验收契约
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // 可选调试接口(验收不读)
|
||||
};
|
||||
}
|
||||
```
|
||||
@ -44,7 +44,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
- **零引擎 import**(引擎经 `boot.ctx.getEngine()`);游戏源**零裸** Math.random/Date.now/setTimeout/new AudioContext/addEventListener —— 时间随机经 boot.ctx,定时经 timer-scheduler,音效经 plugins.audioMusic;**输入不调 ctx.getInput**——写实例方法 `handleTap(x,y)`(点击)/`handleKey(key)`(键盘),L1 在 pointerdown/keydown 时直达调用。
|
||||
- **插件方法名一律以 `src/plugins/<name>/api.d.ts` 为准**,别臆测(scene-fsm 是 `define` 不是 defineScenes)。本骨架已用对的母语写法:`sessionScore.add(n)` 计分、`juice.burst(x,y)` 爆点、`hudUi.drawButton(g,rect,opts)` 返回命中矩形可直接接住判命中。
|
||||
- **画面读 `viewport.w/h`**,别硬编码 390×844。
|
||||
- **`_forensicsView().state()` 的解谜契约**:`targets` 每格一项、**只把「当前该点的正确解」标 `occupied:true`**(solve 阶段、未清、符合本关规则;干扰项/已清格/观察窗与锁定中一律 false)——解谜的取证面兼职"求解器",自动验收只点 occupied:true 的格驱动解谜;`score` 随点对/过关单调上升(自动验收据它判真有进展)。**换皮加机制都别动这两个键的语义。**
|
||||
- **`_forensicsView()` 是可选调试接口**:state() 实时导出盘面/得分/关卡等(targets 标当前正确解,调试时一眼看出解算对没对);W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩)。
|
||||
|
||||
## 你写什么 vs 调什么
|
||||
|
||||
|
||||
@ -161,7 +161,7 @@ export function computeTileGeometry(cols, rows, viewW, viewH) {
|
||||
row: r,
|
||||
col: c,
|
||||
size,
|
||||
x: BOARD_SIDE_MARGIN + c * (size + TILE_GAP) + size / 2, // 中心点(取证 targets 用它)
|
||||
x: BOARD_SIDE_MARGIN + c * (size + TILE_GAP) + size / 2, // 中心点(命中判定与调试视图共用)
|
||||
y: top + r * (size + TILE_GAP) + size / 2,
|
||||
});
|
||||
}
|
||||
@ -194,7 +194,7 @@ function forceMismatch(attrs, rule, theme) {
|
||||
/**
|
||||
* 生成一关盘面(纯函数):恰好 matches 个格子符合规则、其余全不符合——保证「全找到=过关」可判。
|
||||
* ★可解性质(改我之前先读):任何改动必须保住「每关恰好 N 个解、至少 1 个解、干扰项绝不撞规则」——
|
||||
* 0 解盘面 = 不可解关 = 永无 occupied 目标 = 九门 H 挂(实测翻车点)。换主题/维度优先只改 THEME/LEVELS。
|
||||
* 0 解盘面 = 不可解关 = 第一关就无解、谁玩都卡死(实测翻车点)。换主题/维度优先只改 THEME/LEVELS。
|
||||
* @param {number} levelIdx 关序(0 起)
|
||||
* @param {()=>number} rng 受控随机源(boot.ctx.random)
|
||||
* @param {number} viewW / viewH 视口
|
||||
|
||||
@ -10,16 +10,16 @@
|
||||
* · 观察窗口 → 点解:点对 = 加分(连击倍率)+ 粒子 + 音效 + 飘字;点错 = 抖屏 + 断连击 + 短锁定
|
||||
* · 全部解找齐 = 过关庆祝 → 下一关(规则维度逐关递进);通全关提前胜利,限时到点结算
|
||||
* · 卡壳 ≥9s 剩余解脉动软提示(卡壳不出局)
|
||||
* · _forensicsView 的 targets/score 契约(自动验收靠它驱动你的游戏,**改坏它=九门 H 直接挂**)
|
||||
* · _forensicsView 调试视图(可选:host.state() 读它做调试观测,不影响验收)
|
||||
*
|
||||
* 你按 brief 改(身份层 · 多样性源,做出不雷同的解谜):
|
||||
* · 换主题/元素/文案 → 改 core.js 的 THEME(宝石→水果/古文字/海洋…)
|
||||
* · 调难度/关卡阶梯 → 改 core.js 的 LEVELS(盘面大小/解数/规则维度)与节奏常量
|
||||
* · 换观感(底景/元素画法/配色)→ 改 render.js
|
||||
* · 想加机制(星级/图鉴/每日谜题/新规则维度)→ 在循环里加,但**别破坏 targets/score 契约**
|
||||
* · 想加机制(星级/图鉴/每日谜题/新规则维度)→ 在循环里加,但**别丢盘面可解性质**(见 core.js generateBoard 注)
|
||||
*
|
||||
* 入参(L1 已摊平):createGame({ plugins, bundle, viewport });受控面 ctx 在 init(boot) 的 boot.ctx。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap + _forensicsView。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap(+可选 _forensicsView)。
|
||||
* ════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
@ -293,12 +293,9 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
},
|
||||
|
||||
/**
|
||||
* 取证视图【自动验收契约·改坏=九门挂】:state() 内**实时**算(绝不外层先算好闭包返回)。
|
||||
* · targets:每格一项,**occupied=true 只标「当前该点的正确解」**(solve 阶段、未清、符合本关规则、非锁定)——
|
||||
* 解谜品类的取证面兼职"求解器",自动验收只点 occupied:true 的格、驱动解谜推进。
|
||||
* **键名固定为 `targets`,不得改成 items/objects 等**:判卷驱动器(C5)靠 state 有无 `targets` 键选驱动器族,改名=选错族=盲驱动点不到=H 门挂。
|
||||
* · score:随点对/过关单调上升——自动验收据它判「真有进展」。
|
||||
* 加机制换主题都别动这两个键的语义(干扰项/已清格/观察窗内一律 occupied:false)。
|
||||
* 取证视图(可选调试接口):host.state() 据此读盘面/得分/关卡等,供人工调试观测(targets 里
|
||||
* occupied 标出当前正确解,调试时一眼能看出「解算对没算对」)。W-AXIS-V2 起验收不再读它
|
||||
* (验收 = 四门投影 ∧ 测试 agent 真玩);state() 内实时算是好习惯(外层闭包会定格成 boot 快照)。
|
||||
*/
|
||||
_forensicsView() {
|
||||
return {
|
||||
|
||||
@ -78,7 +78,7 @@ test('无浏览器全链路:boot→起局→观察窗挡点→点解加分→逐
|
||||
assert.ok(F, '取证全局 __gameForensics 应已挂载');
|
||||
assert.equal(host.mode, 'evidence');
|
||||
|
||||
// ① 初始态:menu;targets 键在场(自动验收据键存在性判 tap-targets 族)且为空数组。
|
||||
// ① 初始态:menu;调试视图 targets 键在场且为空数组(菜单期无可点解,导出形态自洽)。
|
||||
assert.equal(F.state().phase, 'menu', 'boot 后应在 menu 场景');
|
||||
assert.deepEqual(F.state().targets, [], 'menu 期 targets 应为空数组(键必须在)');
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# _template-shop — A-model「经营点客」品类黄金骨架(3 层架构)
|
||||
|
||||
一个**可玩的完整经营游戏**:顾客陆续上门排队 → 点击在等待的顾客即上菜收钱 → 顾客有耐心条耗尽离开 → 60 秒限时营业结算(menu→play→over)。这份骨架**已经把整套经营循环接好了**(含让自动验收能跑通的取证契约)——你的活是按 brief **换皮**,不是重写循环。
|
||||
一个**可玩的完整经营游戏**:顾客陆续上门排队 → 点击在等待的顾客即上菜收钱 → 顾客有耐心条耗尽离开 → 60 秒限时营业结算(menu→play→over)。这份骨架**已经把整套经营循环接好了**(含可选的调试视图)——你的活是按 brief **换皮**,不是重写循环。
|
||||
|
||||
**怎么改 = 给这家店换身份,循环结构别动:**
|
||||
- 换主题/菜单/价格/难度 → 改 `src/core.js` 顶部的「多样性参数空间」(`DISH_MENU`、生成间隔、耐心、桌数)。咖啡店写拿铁/美式,书店写小说/绘本——**同骨架做出不雷同的店**就靠这里。
|
||||
- 换观感(店内布景/顾客长相/配色)→ 改 `src/render.js`。
|
||||
- 想加机制(连击/VIP 客/上错菜扣分)→ 在 `src/game-logic.js` 循环里加,但**别破坏 `_forensicsView` 的 `targets`/`score` 契约**(改坏=九门 H 进展门直接挂)。
|
||||
- 想加机制(连击/VIP 客/上错菜扣分)→ 在 `src/game-logic.js` 循环里加。
|
||||
- **别推倒重写整个循环**:顾客生成/桌位/耐心/上菜收钱/限时结算都接好了,从它起步只补身份,远比从零写省。
|
||||
|
||||
## 3 层 + 你只碰 L3
|
||||
@ -29,7 +29,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
render(g) { /* 画在 g;读 viewport.w/h */ },
|
||||
handleTap(x, y) { /* ★输入入口:各 phase 点击逻辑;L1 在 pointerdown 时直达调用(别自己订阅输入) */ },
|
||||
destroy() {},
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // ★经营档自动验收契约
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // 可选调试接口(验收不读)
|
||||
};
|
||||
}
|
||||
```
|
||||
@ -42,7 +42,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
- **零引擎 import**(引擎经 `boot.ctx.getEngine()`);游戏源**零裸** Math.random/Date.now/setTimeout/new AudioContext/addEventListener —— 时间随机经 boot.ctx,定时经 timer-scheduler,音效经 plugins.audioMusic;**输入不调 ctx.getInput**——写实例方法 `handleTap(x,y)`(点击)/`handleKey(key)`(键盘),L1 在 pointerdown/keydown 时直达调用。
|
||||
- **插件方法名一律以 `src/plugins/<name>/api.d.ts` 为准**,别臆测(scene-fsm 是 `define` 不是 defineScenes)。本骨架已用对的母语写法:`sessionScore.add(n)` 计分、`juice.burst(x,y)` 爆点、`hudUi.drawButton(g,rect,opts)` 返回命中矩形可直接接住判命中。
|
||||
- **画面读 `viewport.w/h`**,别硬编码 390×844。
|
||||
- **`_forensicsView().state()` 的经营契约**:`targets` 列每桌一项、有可点等待顾客的桌 `occupied:true`(自动验收只点这些桌驱动上菜);`score` 随上菜单调上升(自动验收据它判真有进展)。**换皮加机制都别动这两个键的语义。**
|
||||
- **`_forensicsView()` 是可选调试接口**:state() 实时导出桌位/营收/耐心等,供人工调试观测;W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩)。
|
||||
|
||||
## 你写什么 vs 调什么
|
||||
|
||||
|
||||
@ -9,15 +9,15 @@
|
||||
* · 顾客陆续生成、坐进桌位、有耐心条 → 耐心耗尽自动离开(算流失)
|
||||
* · 点击在等待的顾客 = 上菜:收钱(sessionScore.add)+ 粒子(juice.burst)+ 音效 + 飘字
|
||||
* · 60 秒限时营业 → 到点结算(sceneFsm 切 over,latch 终态)
|
||||
* · _forensicsView 的 targets/score 契约(自动验收靠它驱动你的游戏,**改坏它=九门 H 直接挂**)
|
||||
* · _forensicsView 调试视图(可选:host.state() 读它做调试观测,不影响验收)
|
||||
*
|
||||
* 你按 brief 改(身份层 · 多样性源,做出不雷同的店):
|
||||
* · 主题/菜单/价格/难度 → 改 core.js 顶部的多样性参数空间(DISH_MENU、生成间隔、耐心、桌数)
|
||||
* · 画面观感(店内长啥样、顾客长啥样、配色)→ 改 render.js
|
||||
* · 想加机制(连击 combo、VIP 客、上错菜)→ 在循环里加,但**别破坏 targets/score 契约**
|
||||
* · 想加机制(连击 combo、VIP 客、上错菜)→ 在循环里加
|
||||
*
|
||||
* 入参(L1 已摊平):createGame({ plugins, bundle, viewport });受控面 ctx 在 init(boot) 的 boot.ctx。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap + _forensicsView。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap(+可选 _forensicsView)。
|
||||
* ════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
@ -124,7 +124,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
return;
|
||||
}
|
||||
if (cur === SCENE_PLAY) {
|
||||
// 经营核心:点中任一还在等的顾客 = 给他上菜(1 步成交,自动验收据此涨分)。
|
||||
// 经营核心:点中任一还在等的顾客 = 给他上菜(1 步成交,单击直给好上手)。
|
||||
for (let i = 0; i < customers.length; i++) {
|
||||
const c = customers[i];
|
||||
if (c.state !== 'waiting' || !isTappable(c, elapsedMs)) continue;
|
||||
@ -238,11 +238,9 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
},
|
||||
|
||||
/**
|
||||
* 取证视图【自动验收契约·改坏=九门挂】:state() 内**实时**算(绝不外层先算好闭包返回)。
|
||||
* · targets:每桌一项,occupied=true 表示有「可点的等待顾客」——自动验收只点 occupied:true 的桌、驱动上菜。
|
||||
* **键名固定为 `targets`,不得改成 items/objects 等**:判卷驱动器(C5)靠 state 有无 `targets` 键选驱动器族,改名=选错族=盲驱动点不到=H 门挂。
|
||||
* · score:= 营收,随上菜单调上升——自动验收据它判「真有进展」。
|
||||
* 加机制时这两个键的语义务必保持(targets 列可点目标、score 随进展升)。
|
||||
* 取证视图(可选调试接口):host.state() 据此读桌位/营收/耐心等,供人工调试观测。
|
||||
* W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩);state() 内实时算是好习惯
|
||||
* (外层闭包会定格成 boot 快照,调试时读到的恒是旧值)。
|
||||
*/
|
||||
_forensicsView() {
|
||||
return {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# _template-story — A-model「剧情互动」品类黄金骨架(3 层架构)
|
||||
|
||||
一个**可玩的完整分支剧情游戏**:读场景文本 → 在 2~3 个选项里做选择 → 属性随选择积累 → 剧情沿分支推进 → 走到某个结局结算(多结局 + 结局图鉴,menu→play→over)。这份骨架**已经把整套剧情推进循环接好了**(含让自动验收能跑通的取证契约)——你的活是按 brief **换故事**,不是重写推进机。
|
||||
一个**可玩的完整分支剧情游戏**:读场景文本 → 在 2~3 个选项里做选择 → 属性随选择积累 → 剧情沿分支推进 → 走到某个结局结算(多结局 + 结局图鉴,menu→play→over)。这份骨架**已经把整套剧情推进循环接好了**(含可选的调试视图)——你的活是按 brief **换故事**,不是重写推进机。
|
||||
|
||||
**怎么改 = 给这段剧情换个故事,推进结构别动:**
|
||||
- 换题材/文案/分支/结局/属性轴 → 改 `src/core.js` 顶部的「多样性参数空间」(`STORY_NODES`、`STORY_TITLE`、`STAT_NAMES`)。古宅悬疑写勇气/机智,恋爱线写好感/理智,武侠写侠义/内力——**同骨架讲出不雷同的故事**就靠这里。
|
||||
- 换观感(氛围底/配色/文本卡样式)→ 改 `src/render.js`。
|
||||
- 想加机制(属性检定开选项/线索收集/隐藏结局)→ 在 `src/game-logic.js` 推进机里加,但**别破坏 `_forensicsView` 的 `targets`/`score` 契约**(改坏=九门 H 进展门直接挂)。
|
||||
- 想加机制(属性检定开选项/线索收集/隐藏结局)→ 在 `src/game-logic.js` 推进机里加。
|
||||
- **别推倒重写推进机**:节点推进/属性结算/多结局 latch/结局图鉴持久都接好了,从它起步只换故事,远比从零写省。
|
||||
|
||||
**写故事图(core.js `STORY_NODES`)三铁律**:① 每个 `next` 必须真存在(死链=推进卡死);② **无回环**(任意路径有限步必达某结局);③ 结局 ≥3 且 outcome 有 win 有 lose。文本红线:节点 text ≤3 短句、选项 label ≤10 字(手机竖屏装得下)。
|
||||
@ -31,7 +31,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
render(g) { /* 画在 g;读 viewport.w/h */ },
|
||||
handleTap(x, y) { /* ★输入入口:各 phase 点击逻辑;L1 在 pointerdown 时直达调用(别自己订阅输入) */ },
|
||||
destroy() {},
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // ★剧情档自动验收契约
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // 可选调试接口(验收不读)
|
||||
};
|
||||
}
|
||||
```
|
||||
@ -44,9 +44,9 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
- **零引擎 import**(引擎经 `boot.ctx.getEngine()`);游戏源**零裸** Math.random/Date.now/setTimeout/new AudioContext/addEventListener —— 时间随机经 boot.ctx,定时经 timer-scheduler,音效经 plugins.audioMusic;**输入不调 ctx.getInput**——写实例方法 `handleTap(x,y)`(点击)/`handleKey(key)`(键盘),L1 在 pointerdown/keydown 时直达调用。
|
||||
- **插件方法名一律以 `src/plugins/<name>/api.d.ts` 为准**,别臆测(scene-fsm 是 `define` 不是 defineScenes)。本骨架已用对的母语写法:`sessionScore.add(n)` 计分、`sessionScore.win()/lose()` 锁结局、`juice.burst(x,y)` 爆点、`hudUi.drawButton(g,rect,opts)` 返回命中矩形可直接接住判命中、`save.get/set` 存结局图鉴。
|
||||
- **画面读 `viewport.w/h`**,别硬编码 390×844。
|
||||
- **`_forensicsView().state()` 的剧情契约**:`targets` 在 play 态列**当前每个选项**一项(中心坐标、`occupied:true` 可点——自动验收只点这些选项驱动剧情);menu/over 态返回**空数组**(键保留、不误点);`score` 随每次选择单调 +1(自动验收据它判真有进展)。**换故事加机制都别动这两个键的语义。**
|
||||
- **选项命中区 = 绘制区 = 取证 targets 三者同源**:全部经 `core.layoutChoices(vw,vh,n)` 一处算(render 照 rects 画、handleTap 用 hitChoice 判、取证取 rects 中心)。别在 render 里自己另算按钮位置——一旦分叉,自动验收点的坐标就不再是真命中区。
|
||||
- **场景名钉死 `menu`/`play`/`over`**:自动验收认这三个名(起局扫点/进展判定/latch 终态都按它);**结局到 over 后驻留(latch),绝不自动重开**——「再读一次」只在玩家点按钮时触发。
|
||||
- **`_forensicsView()` 是可选调试接口**:state() 实时导出当前节点/选项/属性等,供人工调试观测;W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩)。
|
||||
- **选项命中区 = 绘制区 同源**:全部经 `core.layoutChoices(vw,vh,n)` 一处算(render 照 rects 画、handleTap 用 hitChoice 判)。别在 render 里自己另算按钮位置——一旦分叉,玩家点中画出来的按钮却没反应。
|
||||
- **场景名约定 `menu`/`play`/`over`**(全品类统一结构);**结局到 over 后驻留(latch),绝不自动重开**——「再读一次」只在玩家点按钮时触发。
|
||||
|
||||
## 你写什么 vs 调什么
|
||||
|
||||
|
||||
@ -96,7 +96,7 @@ export const STORY_NODES = {
|
||||
},
|
||||
};
|
||||
|
||||
/* ── 选项按钮布局常量(几何身份:想换排版改这里;命中区与取证 targets 共用同一布局)── */
|
||||
/* ── 选项按钮布局常量(几何身份:想换排版改这里;命中区与绘制共用同一布局)── */
|
||||
export const CHOICE_W_RATIO = 0.8; // 按钮宽 = 视口宽 × 此比例
|
||||
export const CHOICE_H = 62; // 按钮高(手指友好,别小于 ~48)
|
||||
export const CHOICE_GAP = 16; // 按钮纵向间距
|
||||
@ -125,8 +125,8 @@ export function allEndingIds(nodes) {
|
||||
|
||||
/**
|
||||
* 算当前节点选项按钮矩形(读 viewport,不硬编码 390×844)。
|
||||
* 命中判定(handleTap)与取证 targets(_forensicsView)都用本函数——**同一布局单一来源,
|
||||
* 保证「验收 driver 点的坐标」恒等于「真实命中区」**。
|
||||
* 命中判定(handleTap)与绘制(render)都用本函数——**同一布局单一来源,
|
||||
* 保证「画在屏上的按钮」恒等于「真实命中区」**(两处各算一份迟早漂移,点中画出来的按钮却没反应)。
|
||||
* @param {number} viewW 视口宽
|
||||
* @param {number} viewH 视口高
|
||||
* @param {number} count 选项数
|
||||
|
||||
@ -9,15 +9,15 @@
|
||||
* · 节点推进机:读场景文本 → 点选项 → 属性结算 + 计分 + 音效粒子 → 进下一节点
|
||||
* · 多结局结算:走到结局节点 → win/lose 锁定 → over 结算屏(latch 终态,不自动重开)
|
||||
* · 结局图鉴:已解锁结局经 save 跨局持久,over 屏露出「x/N + ???」的重玩钩子
|
||||
* · _forensicsView 的 targets/score 契约(自动验收靠它驱动你的游戏,**改坏它=九门 H 直接挂**)
|
||||
* · _forensicsView 调试视图(可选:host.state() 读它做调试观测,不影响验收)
|
||||
*
|
||||
* 你按 brief 改(故事身份层 · 多样性源,做出不雷同的故事):
|
||||
* · 换题材/文案/分支/结局 → 改 core.js 顶部的 STORY_NODES / STORY_TITLE / STAT_NAMES
|
||||
* · 换观感(氛围底/配色/文本卡样式)→ 改 render.js
|
||||
* · 想加机制(属性检定开选项/线索收集/隐藏结局)→ 在推进机里加,但**别破坏 targets/score 契约**
|
||||
* · 想加机制(属性检定开选项/线索收集/隐藏结局)→ 在推进机里加
|
||||
*
|
||||
* 入参(L1 已摊平):createGame({ plugins, bundle, viewport });受控面 ctx 在 init(boot) 的 boot.ctx。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap + _forensicsView。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap(+可选 _forensicsView)。
|
||||
* ════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
@ -34,7 +34,7 @@ import { renderMenu, renderPlay, renderOver, menuStartRect, overRestartRect } fr
|
||||
// 资产装载(本骨架无外部美术,容错返空集;要加美术放 assets/manifest.json 再读)。
|
||||
import { loadAssets } from './assets.js';
|
||||
|
||||
/* ── 场景名(剧情三幕:引子 → 阅读 → 结局)。钉死 menu/play/over:自动验收认这三个名。── */
|
||||
/* ── 场景名(剧情三幕:引子 → 阅读 → 结局)。约定 menu/play/over 三态命名(全品类统一结构)。── */
|
||||
const SCENE_MENU = 'menu';
|
||||
const SCENE_PLAY = 'play';
|
||||
const SCENE_OVER = 'over';
|
||||
@ -153,7 +153,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
return;
|
||||
}
|
||||
if (cur === SCENE_PLAY) {
|
||||
// 剧情核心:单击选项即推进(1 步一选,自动验收据此涨分)。
|
||||
// 剧情核心:单击选项即推进(1 步一选,点了就走、读者的手不被流程绊住)。
|
||||
const idx = hitChoice(x, y, choiceRects);
|
||||
if (idx >= 0) choose(idx);
|
||||
}
|
||||
@ -224,12 +224,9 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
},
|
||||
|
||||
/**
|
||||
* 取证视图【自动验收契约·改坏=九门挂】:state() 内**实时**算(绝不外层先算好闭包返回)。
|
||||
* · targets:play 态 = 当前每个选项一项(中心坐标,occupied=true 可点)——自动验收只点
|
||||
* occupied:true 的选项、驱动剧情推进;menu/over 态 = 空数组(键保留,值为空 → driver 不误点)。
|
||||
* **键名固定为 `targets`,不得改成 items/choices 等**:判卷驱动器(C5)靠 state 有无 `targets` 键选驱动器族,改名=选错族=盲驱动点不到=H 门挂。
|
||||
* · score:= 已做选择数,随推进单调上升——自动验收据它判「真有进展」。
|
||||
* 加机制时这两个键的语义务必保持(targets 列可点选项、score 随推进升)。
|
||||
* 取证视图(可选调试接口):host.state() 据此读当前节点/选项/属性等,供人工调试观测。
|
||||
* W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩);state() 内实时算是好习惯
|
||||
* (外层闭包会定格成 boot 快照,调试时读到的恒是旧值)。
|
||||
*/
|
||||
_forensicsView() {
|
||||
return {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# _template-trpg — A-model「掷骰爬塔」TRPG 品类黄金骨架(3 层架构)
|
||||
|
||||
一个**可玩的完整掷骰冒险游戏**:勇者限时闯一座试炼塔,每层翻出几张遭遇卡(怪物/宝箱/奇遇,难度与奖励亮牌)→ 点一张卡 = 掷 d20 加等级加成对抗难度 → 大成功/成功/失败三档结算(战利品金币、经验升级、怪物卡失败扣血)→ 清层登上一层、难度渐升 → 登顶 win / HP 耗尽 lose / 60 秒到点结算(menu→play→over)。这份骨架**已经把整套掷骰爬塔循环接好了**(含让自动验收能跑通的取证契约)——你的活是按 brief **换皮**,不是重写循环。
|
||||
一个**可玩的完整掷骰冒险游戏**:勇者限时闯一座试炼塔,每层翻出几张遭遇卡(怪物/宝箱/奇遇,难度与奖励亮牌)→ 点一张卡 = 掷 d20 加等级加成对抗难度 → 大成功/成功/失败三档结算(战利品金币、经验升级、怪物卡失败扣血)→ 清层登上一层、难度渐升 → 登顶 win / HP 耗尽 lose / 60 秒到点结算(menu→play→over)。这份骨架**已经把整套掷骰爬塔循环接好了**(含可选的调试视图)——你的活是按 brief **换皮**,不是重写循环。
|
||||
|
||||
**怎么改 = 给这场冒险换题材,循环结构别动:**
|
||||
- 换题材/遭遇/数值 → 改 `src/core.js` 顶部的「多样性参数空间」(`ENCOUNTER_DECK` 遭遇表、层数、血量、难度曲线、经验曲线)。魔塔写史莱姆/骷髅卫,西游写小妖/山神庙,星际写巡逻机甲/补给舱——**同骨架做出不雷同的冒险**就靠这里。
|
||||
- 换观感(塔长啥样/卡面画法/配色)→ 改 `src/render.js`。
|
||||
- 想加机制(道具/技能/事件链)→ 在 `src/game-logic.js` 循环里加,但**别破坏 `_forensicsView` 的 `targets`/`score` 契约**(改坏=九门 H 进展门直接挂)。
|
||||
- 想加机制(道具/技能/事件链)→ 在 `src/game-logic.js` 循环里加。
|
||||
- **别推倒重写整个循环**:翻层/掷骰判定/三档结算/升级/连胜/登塔都接好了,从它起步只补身份,远比从零写省。
|
||||
|
||||
## 3 层 + 你只碰 L3
|
||||
@ -29,7 +29,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
render(g) { /* 画在 g;读 viewport.w/h */ },
|
||||
handleTap(x, y) { /* ★输入入口:各 phase 点击逻辑;L1 在 pointerdown 时直达调用(别自己订阅输入) */ },
|
||||
destroy() {},
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // ★掷骰档自动验收契约
|
||||
_forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // 可选调试接口(验收不读)
|
||||
};
|
||||
}
|
||||
```
|
||||
@ -42,7 +42,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
- **零引擎 import**(引擎经 `boot.ctx.getEngine()`);游戏源**零裸** Math.random/Date.now/setTimeout/new AudioContext/addEventListener —— 时间随机经 boot.ctx,定时经 timer-scheduler,音效经 plugins.audioMusic;**输入不调 ctx.getInput**——写实例方法 `handleTap(x,y)`(点击)/`handleKey(key)`(键盘),L1 在 pointerdown/keydown 时直达调用。
|
||||
- **插件方法名一律以 `src/plugins/<name>/api.d.ts` 为准**,别臆测(scene-fsm 是 `define` 不是 defineScenes)。本骨架已用对的母语写法:`sessionScore.add(n)` 计分、`juice.burst(x,y)` 爆点、`hudUi.drawButton(g,rect,opts)` 返回命中矩形可直接接住判命中。
|
||||
- **画面读 `viewport.w/h`**,别硬编码 390×844。
|
||||
- **`_forensicsView().state()` 的掷骰档契约**:`targets` 列本层每张遭遇卡一项、可点(ready)的卡 `occupied:true`(自动验收只点这些卡驱动掷骰);`score` = 金币战利品,随结算单调上升——**失败也有安慰 1 金**,自动验收据它判真有进展。**换皮加机制都别动这两个键的语义。**
|
||||
- **`_forensicsView()` 是可选调试接口**:state() 实时导出层数/血量/金币/卡态等,供人工调试观测;W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩)。**失败也有安慰 1 金**(保底正反馈,连败不空手)。
|
||||
- **掷骰过程可见**:判定式(d20:骰值+加成 vs 难度 → 结果)要飘字亮给玩家——绝不黑箱出结果,「看得见的随机」是 TRPG 手感之根。
|
||||
|
||||
## 你写什么 vs 调什么
|
||||
|
||||
@ -142,8 +142,8 @@ export function buildFloor(floor, rng, viewW, viewH, idSeq) {
|
||||
/**
|
||||
* 结算一次遭遇(纯判定,不带副作用):掷骰 + 等级加成 对抗 卡难度。
|
||||
* 三档:roll=20 大成功(奖励双倍) / 总值≥难度 成功(全奖) / 否则失败(安慰 1 金币,怪物卡扣血)。
|
||||
* **两层奖励(设计钉死)**:任何结算至少 +1 金币 → 自动验收盲点也能过进展门;
|
||||
* 会看难度挑卡、吃连胜的真人拿的是好几倍——技巧分不挡门、门不封技巧。
|
||||
* **两层奖励(设计钉死)**:任何结算至少 +1 金币(保底正反馈,新手连败也不至于零收获弃游);
|
||||
* 会看难度挑卡、吃连胜的真人拿的是好几倍——保底给上手缓冲、倍率给技巧深度。
|
||||
* @param {object} card 遭遇卡
|
||||
* @param {{level:number}} hero 勇者(等级给掷骰加成:+level-1…每级+1,1 级加成 +0…见 heroBonus)
|
||||
* @param {number} roll d20 结果(1..20)
|
||||
@ -168,7 +168,7 @@ export function resolveEncounter(card, hero, roll, streak) {
|
||||
return {
|
||||
outcome: 'fail',
|
||||
total,
|
||||
gold: 1, // 安慰分:保证 score 单调上升(自动验收的进展契约),真人的差距拉在成功倍率上
|
||||
gold: 1, // 安慰分:失败也有正反馈(保底不空手),真人的差距拉在成功倍率上
|
||||
xp: 1,
|
||||
hpDelta: card.kind === 'monster' ? -FAIL_HP_LOSS : 0, // 只有怪物卡打脸;宝箱/奇遇失败只是拿得少
|
||||
};
|
||||
|
||||
@ -11,15 +11,15 @@
|
||||
* (金币 sessionScore.add + 掷骰飘字 + juice.burst + 音效;失败怪物卡扣血)
|
||||
* · 经验升级(升级 → 掷骰加成 +1,成长真实进判定公式)+ 连胜追加金币(技巧分)
|
||||
* · 清层登上一层(难度渐升、层间横幅)→ 登顶 win / HP 耗尽 lose / 60 秒到点结算
|
||||
* · _forensicsView 的 targets/score 契约(自动验收靠它驱动你的游戏,**改坏它=九门 H 直接挂**)
|
||||
* · _forensicsView 调试视图(可选:host.state() 读它做调试观测,不影响验收)
|
||||
*
|
||||
* 你按 brief 改(身份层 · 多样性源,做出不雷同的冒险):
|
||||
* · 题材/遭遇/数值 → 改 core.js 顶部的多样性参数空间(ENCOUNTER_DECK、层数、血量、难度曲线)
|
||||
* · 画面观感(塔长啥样、卡面画法、配色)→ 改 render.js
|
||||
* · 想加机制(道具/技能/事件链)→ 在循环里加,但**别破坏 targets/score 契约**
|
||||
* · 想加机制(道具/技能/事件链)→ 在循环里加
|
||||
*
|
||||
* 入参(L1 已摊平):createGame({ plugins, bundle, viewport });受控面 ctx 在 init(boot) 的 boot.ctx。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap + _forensicsView。
|
||||
* 返回 GameInstance:init/update/render/destroy + handleTap(+可选 _forensicsView)。
|
||||
* ════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
@ -144,7 +144,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
return;
|
||||
}
|
||||
if (cur === SCENE_PLAY) {
|
||||
// TRPG 核心:点中任一可点遭遇卡 = 掷骰结算(1 步完成,自动验收据此涨分)。
|
||||
// TRPG 核心:点中任一可点遭遇卡 = 掷骰结算(1 步完成,爽点在挑哪张、不在按菜单)。
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
const c = cards[i];
|
||||
if (!cardTappable(c)) continue;
|
||||
@ -160,7 +160,7 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
const r = resolveEncounter(card, hero, roll, streak);
|
||||
card.state = 'resolved';
|
||||
|
||||
// ① 金币入账(两层奖励:失败也有安慰 1 金 → score 单调上升,自动验收的进展契约)。
|
||||
// ① 金币入账(两层奖励:失败也有安慰 1 金,保底正反馈——连败玩家不至于零收获弃游)。
|
||||
gold += r.gold;
|
||||
sessionScore.add(r.gold);
|
||||
|
||||
@ -289,11 +289,9 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
},
|
||||
|
||||
/**
|
||||
* 取证视图【自动验收契约·改坏=九门挂】:state() 内**实时**算(绝不外层先算好闭包返回)。
|
||||
* · targets:本层每张卡一项,occupied=true 表示「可点的遭遇卡」——自动验收只点 occupied:true 的卡、驱动掷骰。
|
||||
* **键名固定为 `targets`,不得改成 items/cards 等**:判卷驱动器(C5)靠 state 有无 `targets` 键选驱动器族,改名=选错族=盲驱动点不到=H 门挂。
|
||||
* · score:= 金币战利品,随结算单调上升(失败也有安慰分)——自动验收据它判「真有进展」。
|
||||
* 加机制时这两个键的语义务必保持(targets 列可点目标、score 随进展升)。
|
||||
* 取证视图(可选调试接口):host.state() 据此读层数/血量/金币/卡态等,供人工调试观测。
|
||||
* W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩);state() 内实时算是好习惯
|
||||
* (外层闭包会定格成 boot 快照,调试时读到的恒是旧值)。
|
||||
*/
|
||||
_forensicsView() {
|
||||
return {
|
||||
|
||||
@ -87,12 +87,12 @@ test('resolveEncounter:总值≥难度成功(全奖)、roll=20 大成功(双倍)
|
||||
assert.equal(capped.gold, 6 * 2 + STREAK_BONUS_CAP, `连胜追加应被 cap 在 ${STREAK_BONUS_CAP}`);
|
||||
});
|
||||
|
||||
test('resolveEncounter:失败三性质——安慰 1 金(score 单调契约)、怪物卡扣血、宝箱/奇遇失败不伤人', () => {
|
||||
test('resolveEncounter:失败三性质——安慰 1 金(保底正反馈)、怪物卡扣血、宝箱/奇遇失败不伤人', () => {
|
||||
const hero = { level: 1 };
|
||||
const monster = { kind: 'monster', difficulty: 15, gold: 6, xp: 3 };
|
||||
const f = resolveEncounter(monster, hero, 3, 4);
|
||||
assert.equal(f.outcome, 'fail');
|
||||
assert.equal(f.gold, 1, '失败也有安慰 1 金——自动验收的 score 单调契约');
|
||||
assert.equal(f.gold, 1, '失败也有安慰 1 金——保底正反馈,连败不空手');
|
||||
assert.equal(f.hpDelta, -FAIL_HP_LOSS, '怪物卡失败应扣血');
|
||||
const chest = resolveEncounter({ kind: 'chest', difficulty: 15, gold: 3, xp: 1 }, hero, 3, 0);
|
||||
assert.equal(chest.hpDelta, 0, '宝箱失败只是拿得少,不伤人');
|
||||
|
||||
@ -170,11 +170,9 @@ export function createGame({ plugins, bundle, viewport }) {
|
||||
},
|
||||
|
||||
/**
|
||||
* 取证视图(可选·建议):host.state()/onReady/九门据此读状态 + 度量(JSON 安全)。
|
||||
* 【targets 键契约·C5】点目标玩法(点客/打地鼠/点离散目标)必须在 state() 里暴露 `targets` 数组
|
||||
* [{x,y,occupied}]——**键名固定为 `targets`,不得改成 items/objects 等**:判卷驱动器靠 state 有无
|
||||
* `targets` 键选驱动器族(点目标族 vs 循环按键族),改名=选错族=盲驱动器点不到你的目标=H 进展门挂。
|
||||
* 本款是单目标点击,暴露 `target`(单数,同被驱动器识别);多目标一律用 `targets` 数组。
|
||||
* 取证视图(可选调试接口):host.state() 据此读状态 + 度量(JSON 安全),供人工调试观测。
|
||||
* W-AXIS-V2 起验收不再读它——验收 = 四门机械投影(装载/异常/帧/渲染)∧ 测试 agent 视觉引导真玩;
|
||||
* 保留实现是给调试与日志一个统一出口,写不写、导出什么键都不影响过门。
|
||||
*/
|
||||
_forensicsView() {
|
||||
return {
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
import { writeFileSync, readFileSync, mkdirSync, cpSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url'; // 入口守卫用:判定本模块是否被「直接执行」(见文件末尾)
|
||||
import { scaffold, scaffoldSaa, readFileTool, writeFileTool, listDirTool, check, build, buildSaa, stage, gameDir, ensurePlaySpec } from './tools.mjs';
|
||||
import { scaffold, scaffoldSaa, readFileTool, writeFileTool, listDirTool, check, build, buildSaa, stage, gameDir } from './tools.mjs';
|
||||
import { chat, MODEL } from './m3.mjs';
|
||||
import { TOOLS, buildSystemPrompt, DESIGN_TOOLS, buildDesignSystemPrompt } from './prompt.mjs';
|
||||
import { play, smokeBoot } from './play.mjs';
|
||||
@ -251,11 +251,9 @@ async function main() {
|
||||
if (!lastSmoke.ok) { gate = false; gateMsg += ` smoke=FAIL(booted=${lastSmoke.booted} framesAdvanced=${lastSmoke.framesAdvanced} err=${lastSmoke.bootErr || '-'})`; }
|
||||
else {
|
||||
gateMsg += ' stage=OK smoke=PASS';
|
||||
// 链路真绿(终结假绿):据 smoke 抓到的 _forensicsView().state() 形态自动产 play-spec(缺失才补),
|
||||
// 让下游九门 driven=true → E_live/H_progress 由 advisory 升为致命(纯 harness 裸跑不再假绿)。
|
||||
const ps = ensurePlaySpec(id, lastSmoke.state || null);
|
||||
if (ps.wrote) rec(`done 门:自动产 play-spec(driver=${ps.driverType};state.targets=${Array.isArray(lastSmoke.state && lastSmoke.state.targets) ? '有→tap-targets' : '无→key-cycle'})`);
|
||||
else rec(`done 门:play-spec ${ps.ok ? '已存在不覆盖' : '产失败:' + ps.reason}`);
|
||||
// W-AXIS-V2 波2:play-spec 自动产已退役——便宜档验收权威改为四门投影(A/B/C/D)∧ 测试 agent 真玩,
|
||||
// 不再靠 tap-targets 驱动器把九门驱成 driven;下游九门以 undriven 硬集(A/B/C/D + F/G/I)跑,
|
||||
// floor 投影只取 A/B/C/D。ensurePlaySpec 实现在 tools.mjs 保留一个版本窗口(仅封存工具复跑用)。
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { _shapeGateErrors } from '../tools.mjs';
|
||||
import { _shapeGateErrors, _pluginCallPresenceErrors } from '../tools.mjs';
|
||||
|
||||
/** 是否报了形状门错误。 */
|
||||
const hasShape = (errs) => errs.some((e) => e.startsWith('形状门'));
|
||||
@ -116,3 +116,36 @@ test('零误杀:非插件键的同名 void 方法(自定义对象 reset)不误
|
||||
const errs = _shapeGateErrors(wrap(`const r = myThing.reset();`), 'game-logic.js');
|
||||
assert.equal(hasShape(errs), false);
|
||||
});
|
||||
|
||||
// ───────── 插件接线存在断言(W-AXIS-V2 波2,接替 F_wiring 的真接线护栏)─────────
|
||||
|
||||
test('插件接线:解构形态调用(sceneFsm.define)→ 通过', () => {
|
||||
const errs = _pluginCallPresenceErrors({ 'game-logic.js': `const { sceneFsm } = plugins; sceneFsm.define('menu', { onEnter(){} });` });
|
||||
assert.deepEqual(errs, [], '解构后调用插件应算接线');
|
||||
});
|
||||
|
||||
test('插件接线:属性形态调用(plugins.juice.hitStop)→ 通过', () => {
|
||||
const errs = _pluginCallPresenceErrors({ 'game-logic.js': `plugins.juice.hitStop(80);` });
|
||||
assert.deepEqual(errs, [], '属性链上的插件键方法调用应算接线');
|
||||
});
|
||||
|
||||
test('插件接线:调用散在 render.js 也算(三文件合并判)', () => {
|
||||
const errs = _pluginCallPresenceErrors({
|
||||
'game-logic.js': `export function createGame(){ return { handleTap(){} }; }`,
|
||||
'render.js': `export function draw(g){ hudUi.drawText(g, 'hi', 0, 0, {}); }`,
|
||||
});
|
||||
assert.deepEqual(errs, [], '任一游戏源文件出现插件调用即接线');
|
||||
});
|
||||
|
||||
test('插件接线:零插件调用(徒手裸写)→ catch', () => {
|
||||
const errs = _pluginCallPresenceErrors({ 'game-logic.js': `export function createGame(){ let s = 0; return { update(dt){ s += dt; }, handleTap(){ s += 1; } }; }` });
|
||||
assert.equal(errs.length, 1, '零插件调用必须拦');
|
||||
assert.ok(errs[0].includes('插件接线断言'), '错误应可归类到插件接线断言');
|
||||
});
|
||||
|
||||
test('插件接线:注释/字符串里的插件调用名不算(stripCode 后)→ catch', () => {
|
||||
const errs = _pluginCallPresenceErrors({ 'game-logic.js': `// 该用 sceneFsm.define('menu',{}) 的
|
||||
const tip = "juice.hitStop(80)";
|
||||
export function createGame(){ return { handleTap(){} }; }` });
|
||||
assert.equal(errs.length, 1, '注释/字符串内的调用名不应算接线');
|
||||
});
|
||||
|
||||
@ -267,6 +267,32 @@ export function _shapeGateErrors(rawSrc, f) {
|
||||
return errs;
|
||||
}
|
||||
|
||||
/**
|
||||
* _pluginCallPresenceErrors —— 源码级「插件调用存在」断言(W-AXIS-V2 波2,接替 F_wiring 的真接线护栏)。
|
||||
*
|
||||
* F_wiring 的期望前缀集唯一来源是 play-spec;play-spec/驱动器随取证契约退役后,F 坍缩成「任意引擎调用即过」,
|
||||
* 不再证真接线。「真接线」的机械半移交本断言(词法层、确定性,不判玩法):游戏源(game-logic/core/render 合并)
|
||||
* 经 stripCode 去注释字符串后,必须至少出现一次对已注入插件键的方法调用 `<插件键>.<方法>(`(键 ∈ PLUGIN_KEY_DIR
|
||||
* 的 11 键;解构形态 `sceneFsm.define(` 与属性形态 `plugins.sceneFsm.define(` 都命中)。零命中 = 模型抛开插件库
|
||||
* 徒手裸写(通常伴随裸 canvas/裸事件,红线另拦)= 未接线,拦下。运行时半(音效/计分/表现真实发生)归测试员观察。
|
||||
* 漏报优于误杀:同名局部变量的方法调用会被当成插件调用放行(存在断言方向上安全)。
|
||||
*
|
||||
* @param {Object<string,string>} sources 文件名→原始源码(存在的游戏源文件)
|
||||
* @returns {string[]} errors(零命中时一条;命中则空)
|
||||
*/
|
||||
export function _pluginCallPresenceErrors(sources) {
|
||||
const keys = Object.keys(PLUGIN_KEY_DIR);
|
||||
for (const src of Object.values(sources || {})) {
|
||||
const s = stripCode(String(src || ''));
|
||||
for (const m of s.matchAll(/\b(\w+)\.(\w+)\s*\(/g)) {
|
||||
if (keys.includes(m[1])) return []; // 任一文件出现任一插件键的方法调用即算接线
|
||||
}
|
||||
}
|
||||
return [`插件接线断言: 游戏源(${Object.keys(sources || {}).join('/') || 'game-logic.js'})未发现任何插件调用——` +
|
||||
`L1 已注入全 11 件能力插件(${keys.join('/')}),必须用它们承担场景机/计分/HUD/定时/音效等能力` +
|
||||
`(如 const { sceneFsm, sessionScore } = plugins; sceneFsm.define('menu',{...})),别抛开插件库徒手裸写`];
|
||||
}
|
||||
|
||||
// ───────────────────────── 词法真实红线判定(W-AXIS 波2)─────────────────────────
|
||||
// 动机(诊断档 §2.1/§2.4):红线八条与 .boot.ctx/getInput 的命中判定原本是「stripCode 抹字符串/注释 → 对纯文本跑
|
||||
// 正则」。stripCode 修好后(0be0f61c 单遍状态机)字符串内 // 已不 desync,但「对纯文本跑正则」仍是近似:token 间
|
||||
@ -487,10 +513,9 @@ export function check(id) {
|
||||
if (!/\bbundle\.tick\s*\(/.test(g)) {
|
||||
errors.push('game-logic.js 的 update(dt) 内必须调 bundle.tick(dt)(否则插件 onFrame 不推进、timer 永不到期、一局不结束)');
|
||||
}
|
||||
// 可测性红线:必须实现 _forensicsView()(九门据其 state() 验"真活+有进展";不写则 E_live/H_progress 读不到状态)。
|
||||
if (!/_forensicsView\s*\(/.test(g)) {
|
||||
errors.push('game-logic.js 必须实现 _forensicsView()(返回 {state(),measures()};state() 导出 phase/score 等可观测态——九门据此验真活+进展,可测性红线)');
|
||||
}
|
||||
// _forensicsView「必须实现」拦截已摘(W-AXIS-V2 波2):自动验收契约随 tap-targets 驱动器退役,验收改由
|
||||
// 测试 agent 视觉引导真玩(看截图、点所见),不再读状态导出。_forensicsView 降级为可选调试接口——写了
|
||||
// 不拦、不写也不拦;handleTap 输入契约与 ctx.log 日志纪律保留(服务真人与测试员,见下)。
|
||||
// ctx 契约硬化(治 M3 实测错:把 boot.ctx 写成 boot.boot.ctx → 多套一层 → undefined → ctx=null → 不订阅输入 →
|
||||
// 游戏点不动、卡菜单;且九门默认未驱动时 G_input skip → 漏判,带病过门)。host 传入的 boot 已是 {ctx,mainContext,canvas,seed,assets},
|
||||
// 受控面就是 boot.ctx。命中判定走词法(W-AXIS 波2):序列 .boot.ctx(boot.boot.ctx / b.boot.ctx 皆中),正确的 boot.ctx 无前导「.boot」不误伤。
|
||||
@ -567,6 +592,17 @@ export function check(id) {
|
||||
for (const e of _shapeGateErrors(readFileSync(abs, 'utf8'), f)) errors.push(e);
|
||||
}
|
||||
|
||||
// 5.5) 插件接线存在断言(W-AXIS-V2 波2,接替 F_wiring 的真接线护栏;词法层确定性,判定见 _pluginCallPresenceErrors):
|
||||
// 游戏源三文件合并后必须至少调一次已注入插件(零命中=抛开插件库徒手裸写=未接线)。
|
||||
{
|
||||
const srcs = {};
|
||||
for (const f of ['game-logic.js', 'core.js', 'render.js']) {
|
||||
const abs = join(gd, 'src', f);
|
||||
if (existsSync(abs)) srcs[f] = readFileSync(abs, 'utf8');
|
||||
}
|
||||
for (const e of _pluginCallPresenceErrors(srcs)) errors.push(e);
|
||||
}
|
||||
|
||||
// 6) 未定义标识符 + import 断链静态扫描(4 例实证同族:解谜 gen3 未定义 cy / gen6 未定义 stepUntilMs /
|
||||
// 非遗首败 import 断链——语法过 check、运行时才炸,九门才拦得住,浪费整局;node --check 只查语法不查引用)。
|
||||
// esbuild(树内现货)当作用域分析器,判定与漏报口径见 check-undef.mjs;esbuild 不可用时函数自身降级返回空。
|
||||
@ -782,6 +818,8 @@ export function stage(gameId) {
|
||||
|
||||
/**
|
||||
* ensurePlaySpec —— 据游戏 _forensicsView().state() 形态自动产 _wg1-gen/<gameId>/play-spec.json(治"纯 harness 裸跑无 driver→driven=false→E/H 降 advisory→假绿")。
|
||||
* 【退役·保留一个版本窗口(W-AXIS-V2 波2,2026-07-10)】gen.mjs done 门已摘除调用——便宜档验收权威 =
|
||||
* 四门投影(A/B/C/D)∧ 测试 agent 真玩,九门以 undriven 硬集跑。实现保留仅供封存对照工具历史复跑;窗口期满一并清理。
|
||||
* driver 推断(照 play.cdp.cjs 的 driver 家族):
|
||||
* · state 有 targets 数组(点击类:打地鼠/经营点客/点离散目标)→ tap-targets + targetMode:'occupied'(只点 occupied===true 的目标,且 phase≠play 时自动 start 起局);
|
||||
* · 否则(按键类,无 targets)→ key-cycle(循环按方向键)。
|
||||
|
||||
@ -164,7 +164,10 @@ def test_judge_backward_compatible_no_params():
|
||||
|
||||
def test_judge_green_no_sidecar(tmp_path):
|
||||
"""门全绿不产反馈对象、不落 sidecar(C6:门绿就不该有反馈)。"""
|
||||
v = {"pass": True, "guards": {"A_boot": {"pass": True}}}
|
||||
# W-AXIS-V2:放行判据 = 四门投影(A/B/C/D 全绿)∧ guards 非空,fixture 给四门全绿。
|
||||
v = {"pass": True, "guards": {
|
||||
"A_boot": {"pass": True}, "B_uncaught": {"pass": True},
|
||||
"C_frame": {"pass": True}, "D_render": {"pass": True}}}
|
||||
j = judge_cheap_verdict(v, game_id="g10", staged_dir=tmp_path)
|
||||
assert j.passed is True
|
||||
assert not (tmp_path / "evidence" / "verdict-feedback.json").exists()
|
||||
|
||||
@ -5,12 +5,26 @@ from worker.gate_judge import GateJudgment, judge_cheap_verdict
|
||||
|
||||
|
||||
def test_cheap_green_passes():
|
||||
# 顶层 pass=True 且各门 pass=True → passed=True、无失败门。
|
||||
v = {"pass": True, "guards": {"A_boot": {"pass": True}, "H_progress": {"pass": True}}}
|
||||
# W-AXIS-V2 放行判据 = 四门投影(A_boot/B_uncaught/C_frame/D_render 全绿)∧ guards 非空;
|
||||
# E/G/H/I/F 降观测不进取值(此处 H 故意 False 以证不影响放行)。
|
||||
v = {"pass": False, "guards": {
|
||||
"A_boot": {"pass": True}, "B_uncaught": {"pass": True},
|
||||
"C_frame": {"pass": True}, "D_render": {"pass": True},
|
||||
"H_progress": {"pass": False}, # 降观测门失败不拦(undriven 下 H 本就 advisory)
|
||||
}}
|
||||
j = judge_cheap_verdict(v)
|
||||
assert isinstance(j, GateJudgment)
|
||||
assert j.passed is True
|
||||
assert j.failed_gates == []
|
||||
assert j.failed_gates == ["H_progress"] # 诊断列仍如实列出,只是不作放行输入
|
||||
|
||||
|
||||
def test_cheap_floor_gate_missing_not_passed():
|
||||
# 四门任一缺失(如 D_render 没跑出)→ fail-closed 不放行(无四门证据不放行)。
|
||||
v = {"pass": True, "guards": {
|
||||
"A_boot": {"pass": True}, "B_uncaught": {"pass": True}, "C_frame": {"pass": True},
|
||||
}}
|
||||
j = judge_cheap_verdict(v)
|
||||
assert j.passed is False
|
||||
|
||||
|
||||
def test_cheap_pass_but_empty_guards_not_passed():
|
||||
|
||||
@ -490,7 +490,7 @@ def judge_cheap_verdict(play_result: dict, *, game_id=None, staged_dir=None) ->
|
||||
failed_gates = [k for k, g in guards.items() if isinstance(g, dict) and g.get("pass") is False]
|
||||
if passed:
|
||||
# 全绿:feedback 只作日志/trace 用(GateJudgment 契约:passed=True 时不回喂),给准确描述。
|
||||
return GateJudgment(passed=True, failed_gates=failed_gates, feedback="九门全绿,放行。")
|
||||
return GateJudgment(passed=True, failed_gates=failed_gates, feedback="四门地板绿(A_boot/B_uncaught/C_frame/D_render),放行。")
|
||||
obj = build_cheap_feedback_obj(v, game_id=game_id, staged_dir=staged_dir)
|
||||
if obj is not None:
|
||||
feedback = _render_cheap_feedback(obj)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user