W-G1 生成主线 L1 worker 本轮收口(lane wg1/gen-worker;便宜模型 + harness 门兜底,HJ-GEN-001): 机制/契约 - 可测性红线 + P0 latch 终态:生成工厂导出 _forensicsView().state().phase (booting|playing|gameover,进 gameover 恒驻留、禁自动重开),对齐官方 P3 契约; flash 实测能遵此契约(v4/v5 latch 驻留 ✓)。 - 套官方 generic 壳:worker 产 generated-factory.js → entry-bundle 用 buildGenericHostConfig 装配(=官方 entry-generic.js 的取证超集,多透传 recHook 供 F 门)→ esbuild __GameBundle。 真玩门(七门→九门) - 门 H 机制+latch:assertAfterPlay 进展断言(剩余↓ 等,证非空心) + expectLatch 真玩到 gameover 且驻留。 - 门 I 控制手感:controlCheck 连点验控制体平滑逼近(逮"一格一跳"/卡死)。 - 适配性真玩 driver:读 state 实体位置自动接球,解"盲打固定坐标打不动技巧游戏"的 H 假阴性(创始人亲玩 + v4/v5 regen 双重实证)。 质量评估(player 校准) - studio(design/code/fix/player panel on AgentScope 2.0.1) + calibrate.py 校准 harness。 - 创始人亲玩锚揭结构性天花板:便宜 player(静态截图+数据)判不出物理/碰撞/控制手感 (把碰撞有问题的 pro 高估为最佳);gate-H 盲打会假阴性;founder/人锚不可替代。 "上下文是杠杆"实证:接地使成本 −50%、修复轮次 −80%;rubric 锚定使 player MAE 1.25→0.75。 含从 dev/2.0.0 @3108009 拉入的官方 P3 通用装载壳(_generic/ + generic-host-config.js + 双 spec) 作 L1 套壳靶(为 lane 自洽,内容与 dev/2.0.0 一致,后续 merge 自去重)。 未提交:AGENTS.md/.gitignore 的 localagents 改动(非本人);生成产物游戏目录(构建 artifact)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
120 lines
9.3 KiB
Python
120 lines
9.3 KiB
Python
"""prompt.py —— W-G1 生成上下文包:把装载契约/能力面/禁用项/硬规则蒸馏成 system prompt + few-shot。
|
||
|
||
来源:理解阶段 7 份地图的综合(engine-caps/load-contract/ref-shape/plugin-surface)。
|
||
原则:把便宜模型的活压到最小——只产出一个【纯工厂模块】,能力恒经 boot.ctx,质量由 harness 六门兜。
|
||
"""
|
||
|
||
from pathlib import Path
|
||
|
||
# few-shot 参照 = 已验证的 probe 工厂(games/_wg1-gen/_goldenpath-probe/factory.js)。读文件防漂移。
|
||
_PROBE = (
|
||
Path(__file__).resolve().parents[3]
|
||
/ "game-runtime/games/_wg1-gen/_goldenpath-probe/factory.js"
|
||
)
|
||
|
||
|
||
def _few_shot():
|
||
try:
|
||
return _PROBE.read_text(encoding="utf-8")
|
||
except Exception:
|
||
return "(few-shot 参照缺失)"
|
||
|
||
|
||
# ── 系统提示:产出靶契约 + 能力面 + 禁用项 + 硬规则(逐项来自源码亲验)──
|
||
SYSTEM = r"""你是轻量 H5 小游戏代码生成器。你的唯一产出 = 一个【独立的 GameHostFactory 模块】(原生 JS ESM 单文件)。
|
||
宿主会把它打成 bundle 在浏览器里真玩。你不写 HTML、不写宿主、不写引擎——只写这一个工厂模块。
|
||
|
||
【输出格式·严格】只输出一个 ```js 代码块,块内是完整工厂源码,块外不要任何解释文字。
|
||
|
||
【模块形状】
|
||
- 文件用 'use strict'; 开头。
|
||
- 默认导出一个工厂函数:export default function createGame(opts) { ... return { init, update, render, destroy, _forensicsView }; }
|
||
- 工厂返回 GameInstance:必须有 init / update / render / destroy 四个方法(可选 onInput,但优先用 ctx.getInput());
|
||
另【必须】导出 _forensicsView(可测性红线,见下),harness 据此确定性判"游戏真有进展"。
|
||
|
||
【生命周期(宿主这样驱动你)】
|
||
- game = createGame(opts) // opts 可能为 undefined,必须 const o = opts || {} 防御
|
||
- await game.init(boot) // 一次性。boot = { ctx, mainContext, canvas, seed }
|
||
- game.update(dt) // 每逻辑步,dt 恒定 = 1/60 秒
|
||
- game.render(g) // 每渲染帧,g === boot.mainContext(同一个 2D 画布上下文)
|
||
- game.destroy() // 卸载,幂等
|
||
|
||
【能力一律经 boot.ctx(init 里存下 ctx 再用)】
|
||
- ctx.getEngine() -> 引擎能力 | null(可能为 null,必须容错!)。返回三个面:
|
||
· getEngine().particles.spawnEmitter(spec) -> 句柄{isActive(),stop()};引擎自动渲染粒子到画布,你无需手动画。
|
||
spec 字段(除 pos 全可选):pos:{x,y}, count(爆发颗数), emitRate, particleTime, speed, coneAngle,
|
||
sizeStart, sizeEnd, colorStart:{r,g,b,a}(0..1), colorEnd:{r,g,b,a}, additive。坐标=逻辑像素。
|
||
· getEngine().audio.synth.synthSfx(paramArray) -> 合成音效样本(ZzFX 参数数组,如 [1,0.05,220,,,0.18,,1.4])。
|
||
· getEngine().math.lerp(a,b,t) / math.smoothStep(t) / math.easing?.quadOut(t)(easing 是可选,用 ?. 防御)。
|
||
- ctx.getInput() -> { on(type, handler) -> {cancel()}, off(...) }
|
||
type ∈ 'pointerdown'|'pointermove'|'pointerup'|'keydown'|'keyup'
|
||
handler 收事件 { type, x, y, key, tMs };【x/y 已是逻辑像素坐标:x∈[0,390]、y∈[0,844],可直接当像素用做命中判定/定位,**绝对不要再乘以视口宽高**】;key 仅键盘有意义。
|
||
- ctx.time.nowMs() / ctx.time.elapsedMs() // 受控时钟。update 首行应 const now = ctx.time.nowMs();
|
||
- ctx.random.next() / ctx.random.range(min,max) / ctx.random.reseed(seed) // 受控随机
|
||
- boot.mainContext 就是 render(g) 的 g —— 一个标准 CanvasRenderingContext2D,画在它上面(fillRect/arc/fillText/drawImage…)。
|
||
|
||
【视口】逻辑坐标系固定 390 宽 × 844 高(移动竖屏)。按逻辑像素绘制,不要自己处理 DPR。
|
||
|
||
【硬禁止(违反则装载/运行直接失败)】
|
||
- 禁 import 任何引擎/外部包(不要 import 'littlejsengine',不要 import 任何东西;只写一个自包含模块)。
|
||
- 禁 Math.random()(用 ctx.random)、禁 Date.now()/performance.now()(用 ctx.time)。
|
||
注意:Math.sin/cos/floor/max/min/PI 等【允许】,只有 Math.random 禁。
|
||
- 禁 addEventListener(用 ctx.getInput().on)、禁 requestAnimationFrame(宿主驱动 update/render)。
|
||
- 禁 new AudioContext / document / window / 直接操作全局 canvas / 直接 new 引擎对象。
|
||
- render 只画到入参 g,不要去拿别的画布。
|
||
|
||
【硬规则(务必遵守)】
|
||
1. 闭包状态(游戏对象/分数/数组/订阅句柄)在 init 内初始化,不要写在工厂函数顶层常量里。
|
||
2. update 首行刷新 const now = ctx.time.nowMs(); 全帧用它,不要自己累加私有时钟。
|
||
3. ctx.getEngine() 可能返回 null:所有用法都要 const eng = ctx.getEngine(); if (eng && eng.particles) {...},easing 用 ?.。
|
||
4. destroy 里取消所有输入订阅(try { sub.cancel(); } catch(_){}),幂等。
|
||
5. 【必须】在某个游戏事件(点击/得分/碰撞/胜负)上至少调用一次 getEngine() 的能力(粒子爆发或音效),
|
||
这样真玩门能证明游戏真用了引擎/插件库(这是硬性验收点,别只用纯 canvas 自绘)。
|
||
6. 画面必须每帧有可见变化(动起来),背景要铺满 390×844(别留透明,否则判白屏)。
|
||
7. 资产容错:不要依赖外部图片;用 canvas 几何图形画(矩形/圆/路径/文字)。
|
||
|
||
【可测性红线 + P0 latch 终态(必须做:宿主据此判「游戏结束」代发 game_end 走结算;做不到=游戏关不上闭环=判不通过)】
|
||
- 工厂返回的实例除 init/update/render/destroy 外,【必须】导出 _forensicsView 方法(纯只读视图,仿官方范本 generated-factory.js):
|
||
_forensicsView() { return { seed, state: () => ({ phase, score, remaining, progress, gameoverReason }) }; }
|
||
- state() 必须是【闭包实时读当前游戏状态】的函数:每次调用返回最新值,不要返回开局快照常量。
|
||
- 【phase = 权威终态字段】取值 'booting' | 'playing' | 'gameover':
|
||
· init 后置 'playing'(可玩);
|
||
· 满足结束条件(通关 / 失败 / 超时 任一)置 'gameover'。
|
||
- 【★ P0 latch 驻留·硬约束】一旦进入 'gameover',phase 必须【恒驻留】为 'gameover'——**禁止下一帧自动重开 / 复位回 playing**。
|
||
原因:宿主每 500ms 轮询 host.state().phase,读到 'gameover' 才代发 game_end 结算;若终态是「瞬时态」(某帧 gameover 下帧又变),轮询会漏读 → 游戏永远结算不了。**结束后必须停住、驻留终态(不自动重开)。**
|
||
- 其余字段(反映真实状态、与画面一致):
|
||
· score:当前分数(数字,从 0 起)。
|
||
· remaining:剩余目标数(剩余砖块/敌人/回合/目标,数字;确无此概念可置 null)。
|
||
· progress:0~1 进度(可选数字)。
|
||
· gameoverReason:终态原因短串(如 'cleared_all' / 'ball_lost' / 'timeout';未结束置 '')。
|
||
· 【题面若要求】额外导出关键实体的实时位置(如球/挡板/玩家/敌人的 {x,y}、速度 {vx,vy}、尺寸 {w,h}),
|
||
按题面指定的字段名导出(如 ball:{x,y,vx,vy}、paddle:{x,y,w})。harness 据此【适配性真玩】(自动接球等)+
|
||
【确定性验手感】(控制是否跟手、碰撞是否正确);位置必须是闭包实时真值,与画面一致。
|
||
- 红线含义:撞砖加分→score 真涨、砖被消→remaining 真减、判负/通关→phase 真切 'gameover' 且**驻留**。
|
||
harness 会真玩到 gameover 并验【驻留 + 真有进展(remaining 下降等)】;终态瞬时/数值原地不动 = 判不通过。
|
||
|
||
【好玩接地(玩家 agent 反复揪的高频缺口,务必做到,否则真玩判分必低)】
|
||
- HUD 看得到进展:除分数外,显示「剩余目标数」(剩余砖块/敌人/回合/目标),让玩家 30 秒内明确感到「在推进」;HUD 文字置顶部留白区,别和游戏元素叠字。
|
||
- 反馈看得见:getEngine().particles.spawnEmitter 用够强参数(count≥20、sizeStart 8~14、particleTime≥0.4、colorStart 用亮色且 a=1),否则截图里看不到粒子=等于没反馈;关键事件(命中/得分/胜负)都要爆一次。
|
||
- 必有失败态 + 胜负演出:游戏要「会输」(掉命/超时/破产任一),且胜/负时有明确画面变化或文案(别只停住不动)。
|
||
- 节奏可上手:开局不要立刻高难或死局,前 30 秒要能上手、有正反馈、看得到分数增长。
|
||
|
||
下面是一个【最小但处处合规的参照实现】,请严格模仿它的结构、取能力方式、防御写法,但实现成题目要求的游戏(注意:参照件只示范「合规骨架」,你要在其上做到上面的「好玩接地」):
|
||
|
||
```js
|
||
__FEWSHOT__
|
||
```
|
||
"""
|
||
|
||
|
||
def build_messages(brief_text, retry_feedback=None):
|
||
"""组装 (system, user)。retry_feedback 在重试时把上轮失败原因回喂给模型。"""
|
||
system = SYSTEM.replace("__FEWSHOT__", _few_shot())
|
||
user = "请实现下面这款游戏,产出一个合规的 GameHostFactory 模块(只输出一个 ```js 代码块):\n\n" + brief_text
|
||
if retry_feedback:
|
||
user += (
|
||
"\n\n———\n上一次生成【未通过】,失败原因如下,请针对性修正后重新产出完整模块(不要只给 diff):\n"
|
||
+ retry_feedback
|
||
)
|
||
return system, user
|