games-development-ai/.agents/skills/game-e2e-cdp-harness.md
lili a207cb8d65
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled
docs(agents): skill 规范化双层收口 + 席位context skill 新增 + W-NSTAR/W-TPL/W-GENLOG 设计波落档
- .agents/skills 25 件全量 frontmatter 规范化与评审修入(含 prompt-governance 大修);.claude/skills 7 件薄壳按双层方案①落位
- 新增 skill:agentic-seat-context-design(agentic 席位与 context 工程设计基线,2026-07-05 探索蒸馏)
- 设计波三件落档:复杂游戏北极星件(W-NSTAR 终审稿待拍)/黄金模板规格件(W-TPL 定稿待批)/生成侧过程蒸馏回路(W-GENLOG 骨架)
- protocol/在飞板/作战清单/数据飞轮 SoT/契约 prompts 索引同步;breakout 九门证据刷新
- .gitignore 补 /localagents.md 真实忽略行(该文件自声明绝不提交,此前声明未被机器执行)
- 刻意不入库:nacos-data/ 与 _tier2-gen、c2v-*、amgen-* 生成产物(可重生成,忽略行格式待拍)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 05:32:56 -07:00

20 KiB
Raw Blame History

name, description
name description
game-e2e-cdp-harness 为插件库/生成游戏做真浏览器输入级 e2e(触摸轨迹→九门判真玩→四件套证据)时使用:play.cdp.cjs 九门(装载/掌帧/真渲染/接线/输入因果/latch/手感)+ 假绿守卫 + driver 库 + 首局体验门,mini-desktop 上跑,区别于 DOM 走查。

Canvas 游戏 e2e 证据 harness 配方CDP on mini-desktop

适用:插件库/生成游戏的真浏览器输入级 e2e(触摸轨迹→状态机推进→四件套证据),区别于 DOM 走查(那个见 ui-walkthrough-cdp.md)。 首跑实证T1b-α 参考件 7 轮调试链(教训全在 harness 侧,游戏逻辑零返工)。活样板=game-runtime/games/wanglanmei-ref/test/{e2e-input.cdp.cjs,ref-e2e-runbook.sh}。W-G1 生成游戏真玩(九门:假绿守卫 + latch 终态 + 控制手感)见 §7,样板 game-runtime/games/_wg1-gen/_shared/play.cdp.cjs

1. 编排形制runbook 骨架)

精确 SHA scratch clone → esbuild 打包 → IP 门 → systemd-run 起 serve + chrome 两单元 → 就绪等待10×0.5s 探活)做韧性探测5×6s→ 驱动器 → journal 归档 → 停单元。

  • 就绪/韧性必须分两段curl 抢在 listen() 前会把刚起的单元误判失败杀掉journal 里 Started+Stopping 同秒 = 此坑签名)。
  • chrome 旗标:--headless=new --no-sandbox --no-zygote --remote-allow-origins=* --autoplay-policy=no-user-gesture-required(最后一项缺 → AudioContext 卡 suspended
  • 长驻一律 systemd-run 具名单元,可 journal 取证可 stop不留孤儿。

2. 驱动器六律e2e-input.cdp.cjs 形制)

  1. 启动等待轮询取证全局(如 __refBooted/__refBootError/__refHost),超时给 25s——拿 bootError 文本比盲等截图有用得多。
  2. 坐标必须走 rect 映射:游戏 zone 坐标canvas 内坐标系)→ getBoundingClientRect → 页面 CSS 坐标再下发触摸。canvas 不在 (0,0) 或被缩放时直发坐标全偏。
  3. 触摸 = touchStart/touchEnd 成对 + ~90ms 间隔 + 步进若干帧;拖动(滑条)用连续 touchMove。
  4. zone/phase 名以游戏源码 computeZones 为唯一真相——驱动器猜语义名必踩坑(实证:营业期 phase 叫 open 不叫 serving,循环秒退)。
  5. 计数器要驱动器侧实时累计勿赛后读游戏内环形缓冲240 事件环溢出 → 假阴性 serve_ok=0
  6. 确定性对比须同存档态:双开比哈希前 localStorage.clear()+reload二开多画「继续」按钮属正确行为不是缺陷

3. 两条出厂红线(游戏侧,门里逮过的真缺陷)

  • 页面壳canvas 必须置 (0,0) 满幅;状态条等 UI 用 absolute overlay + pointer-events:none。flex 布局把 canvas 压下/裁底 = 驱动器坐标漂移 + 真机底部按钮不可点(双重致命)。
  • esbuild 全量预检:出厂前在 /tmp 跑一次完整 bundleconst 被赋值这类错只在打包期爆headless 单测覆盖不到所有渲染分支)。

4. 四件套证据口径

输入轨迹 JSONL / 截图+canvas 哈希 / 探针 JSONL / checklist JSON,落 games/<game>/evidence/round-N/;硬门(boot/phase 推进/确定性)与软活项(美术观感)分开判。

5. 引擎掌帧门(engine-takeover)增补(A0 门0 实证,T1b-β·2026-06-13)

接「真引擎掌帧」(littlejsengine engineInit 五回调)而非 RAF 桩时,门0 多六坑:

  1. serve 根对齐 index.html 的相对 bundle 路径:index.html 以 ./dist/host-bundle.js 加载 → 构建产物须落 <serve根>/dist/、serve 根=该 index.html 所在目录(如 host-dev/)。错位=404 白屏(α 的 serve 根是游戏目录,易照抄错)。
  2. scratch 装依赖禁 npm init -y:会覆盖含引擎依赖的 package.json 丢 littlejsengine;直接 npm i(装 deps+devDeps=引擎+esbuild)再 npm i ws(CDP harness 额外依赖,不在 package.json)。
  3. 写 /tmp 的临时 cjs:require('./x') 锚脚本目录非 cwd→ MODULE_NOT_FOUND;用 require(require('path').resolve('test/harness/x.cjs'))(cwd 锚)。
  4. 掌帧断言=帧增量区间,非 >0:time=frame++/frameRate 是引擎定义式恒等,>0/t1>t0 可被「只 update 一次的假桩」骗过;真掌帧证据=500ms 内 frame 增量落 [20,45](@60fps 理论 ~30),带上下界排单步假推进+失控空转。
  5. dev 构建引擎自带污染源(只读源码才发现):littlejsengine 开发包默认 debugWatermark=true(每帧画非确定 averageFPS 进 mainContext 右上角,毁 readback 复现)→ 取证前必 setDebugWatermark(false);canvasClearColor 默认 CLEAR_BLACK alpha=0(透明不铺底,非不透明黑)→ 须 setCanvasClearColor(BLACK)。prod min 包默 false,dev 包必显式调。WebGL 取证嫌麻烦可 setGLEnable(false)(须 engineInit 前)走纯 2D mainContext readback,避 preserveDrawingBuffer 坑。
  6. 引擎一 RAF 内追帧 N tick(N 受墙钟抖动)破异步逐像素确定性:frameCount>=target 跨 RAF 异步采样落帧不确定 → 逐像素门交「手摇定量推帧」的桩通道(?engine=stub)独占,引擎通道只证掌帧+渲染落 2D(像素非空+主色命中,不比逐像素哈希)。门0 driver=game-runtime/test/harness/gate0-engine-takeover.cjs(双通道:real 证掌帧/stub 兜确定性+回滚)。

6. 引擎能力真接线门(门②·runtime call-ID)增补(A6 实证,T1b-β·2026-06-13)

证「插件能力经 ctx.getEngine() 运行时真调引擎」(动态证据,区别于门①的 esbuild metafile 静态 import)。门 A6 6/6 实证,driver=game-runtime/test/harness/a6-engine-wiring.cjs,编排=a6-runbook.sh

  1. 门②探针形制=host-dev 侧 rec→window.__engineCalls:在 host 的能力工厂(makeEngineCaps)给每个引擎能力叶方法(particles.spawnEmitter/audio.synth.synthSfx/math.easing.*)埋一行 rec(id) push 到 window.__engineCalls,仅取证、绝不入渠道产物(工厂只活 real 通道)。再加 __hostdev.engineProbe()各插件真实公开 API(非直调引擎)触发能力调用 → 断言 __engineCalls 含 call-ID。真接线靠「插件真实 API → rec 命中」双证,不靠直调引擎自欺。
  2. 引擎渲出色=spec,非旧 sim 口径:删 sim 后粒子由引擎 ParticleEmitter 渲,颜色取 spec.colorStart(如 particles-juice burst 给 r=g=b=1)。A0 G0-7 的青蓝(b>r+30)口径对引擎渲染失效→像素门判据改「任意亮像素 max(r,g,b)>80 计数 + maxCh 全屏最亮」;maxCh≈0=确凿反证「GL 关致引擎粒子不落 2D」的全黑假象(实测 maxCh=240,引擎真落 2D,GL 关无碍 fillRect 路)。换引擎能力后,像素门的颜色假设必同步重核
  3. 音频合成需 AudioContext 非空:插件 playSfx 在 audioOrSilent()==null 时早返不合成→rec 不触发→探针须先 unlockAudio;chrome --autoplay-policy=no-user-gesture-required 下 headless 可合成(此项软门兜,headless 不可用时 deferred 不阻断,正确性由单测 mock-synth 注入兜)。
  4. mini-desktop scratch 部署:6c6g ./ rsync 会吞整个 monorepo(cwd 易被重置到仓根→tar -C <abs game-runtime> 或绝对路径,严禁裸 ./);A6 仅需 host-dev/index.html + 预建 host-bundle.js(本机 esbuild 出,含探针)+ test/harness/*.cjs + npm i ws,tar-pipe 几文件即可,无须全树同步。

7. 生成游戏真玩九门 + 假绿守卫 + latch 终态 + 手感门(W-G1 实证,2026-06-13/14)

便宜模型生成游戏的真玩门(driver=game-runtime/games/_wg1-gen/_shared/play.cdp.cjs),在 §1-6 基础上收敛为九门全过才 PASS(AG 通用假绿守卫 + H 机制/latch + I 控制手感;H/I 逐游戏可选,play-spec 未声明则 SKIP 向后兼容)。生成侧配方见 cheap-model-game-generation.md

真玩驱动两态:play-spec.driver 在=适配性真玩(读 _forensicsView().state() 的实体位置自动出招,如挡板追球);否则=play-spec.inputs 固定序列。盲打固定坐标打不动技巧游戏(球没接住→0进展)是 H 假阴性根因(创始人亲玩 + v4/v5 regen 双重实证),故技巧类必用 driver。

判据
A 装载 __genBooted 真、无 __genBootError
B 未捕获 无 page error / unhandledrejection
C 掌帧 500ms 内 frame 增量 ∈ [20,45](@60fps,排单步假推进 + 失控空转)
D 真渲染 mainContext 有亮像素(max(r,g,b)>80)
E 活性 多采样 ≥2 个不同整帧哈希(排静止假死;静态待机游戏如 Simon 须多采样,单采样会假阴)
F 真接线 __engineCalls 含期望 call-ID(插件能力经 ctx.getEngine() 真调引擎,recHook 埋点;同 §6 门②)
G 输入有效 确定性 A/B 对照:同 seed 无输入 vs 有输入两实例各推进到同帧号,比整帧哈希——必须不同才证输入真生效
H 机制+latch play-spec.assertAfterPlay[] 进展断言(如剩余↓,证非空心)全过 + expectLatch 时真玩到 state().phase==='gameover'驻留(600ms 后仍 gameover)。依赖游戏经 _forensicsView() 导出 phase(官方 latch 契约,见 cheap-model §latch)
I 控制手感 play-spec.controlCheck:连点目标 x,读控制体位置(如 paddle.x)验平滑逼近(|moved|≥阈 ∧ dist≤容差),逮「一格一跳」/卡死/不跟手

H/I = 把"能玩/手感"从主观变确定性(W-G1 2026-06-14):H 借官方 latch 终态契约(phase 驻留 gameover)同时验"真结束(关得上闭环)+ 真有进展(非空心)";I 借导出的控制体位置验"跟手"。前提=游戏导出可观测态(可测性红线)。尚未确定性覆盖:碰撞穿透(规划门 J);物理/碰撞/控制的主观手感 player agent(静态截图)判不出、须创始人/人锚(见 cheap-model §质量评估)。

play-spec 来源 = design agent 自产 gatespec(非手写 brief,405394d):exportState/driver/controlCheck/assertAfterPlay/expectLatch 由生成链的 design agent 末尾产出、studio _extract_gatespec 抽取合入(json_repair 兜 + ballPath .y→.x 归一)——故 H/I 判据随游戏自动生成、适任意游戏,确定性门覆盖面随生成规模扩张(配方见 cheap-model-game-generation.md §7)。

G 门 = 本 lane 关键发明,专逮"假绿":Pong 这类带自走动画的游戏,输入全失灵也"能动"、能骗过 C/D/E;G 门用同帧号 A/B 哈希对照隔离出"输入到底改没改变走向"。实证逮住一处输入坐标契约写错(生成 prompt 误称归一坐标、实为逻辑像素 boot-game-host.js:295)致"能编译能渲染能动但点击全失灵"的假绿。

CDP 触摸下发坑(承 §2 律3,生成游戏侧细化):tap 须镜像 ref realTapAt——裸 {x,y}(逻辑像素,经 §2 律2 rect 映射)、touchStart/touchEnd 背靠背(无 radius/force/长按),否则触摸不达 getInput()。坐标契约 = 逻辑像素 [0..390]/[0..844],非归一(便宜模型与人皆易错)。

8. scale-20 放量实证:driver 库泛化 + 门承重件 + 对抗审计硬化(W-G1 lane v3,2026-06-14)

适配 driver 库 1→8 型 + drag 输入事件(play.cdp.cjs,spec.driver.type 分发;盲打打不动技巧/实时游戏=H 假阴根因,故按机制族建 driver):

driver 机制族 读什么自动出招
paddle-intercept 挡板接球 ball.x → 挡板对齐;drainAfter 声明则跑满后【弃守排空】(挡板停 drainX 角落)让球漏光→打出 lose 终态(接球类靠一直接球永不失败→latch 不可达的解,multiball)
tap-targets 点击离散目标 state.targets[{x,y,occupied[,safe]}] → 点未占用;规避/推理族(扫雷)配 safeOnly:true 只点 safe===true、确定性避负把核心循环跑到 win(井字棋/扫雷/打地鼠/Simon/invaders…)
flap-to-gap 重力穿缝 bird.y + bird.vy 速度前瞻(位置+vy×lookahead 预测仍偏低才拍、aimBias 瞄缝心上方留下落余量)→解高位缝/快下落『追不上·过冲』(flappy);缺 vy 退化为纯位置反应式
seek-x 横向趋近接台 bird.x vs nextPlatform.x → 点左/右半
tap-pairs 相邻交换(三消) targets[k]/[k+1] 成对点
key-cycle 纯按键 循环按 keys[] 到 gameover(依赖宿主键盘桥);填充型(2048)用 2 向键(如 Up+Right)比 4 向更快逼满盘,但抗死锁类游戏仍难确定性到 latch=边界
drag-aiming 抛射瞄准(愤怒小鸟) 暴露 launch{gravity,powerScale}45° 抛解析反解拖拽矢量(确定性命中+各发±力度微抖兜离散);未暴露→退化 powers[] 力度扫描+upLead 补下坠。angrybirds pin+导出物理后 score 0→20 win=可测性边界转确定性闭口
aim-fire 射击瞄准(Asteroids) ship{x,y,angle}+targets[{x,y}] 选最近,旋转使 `

固定输入新增 {t:'drag',from,to,ms}(touchStart→插值 touchMove→touchEnd,合成 swipe/拖拽)。driver 覆盖度 = 门能否兜住的前提:shooter/填充型 latch 需足够步数驱动到终局(invaders 步数 30→50 即翻绿,实证)。

★ driver-coverage v2 闭口:H 假阴四因归因铁律(2026-06-15,5 款实测)。技巧/射击/填充类 H 假阴必先分因、禁『模型造不出』笼统归因(spec §9):

  1. 宿主门面缺失(系统性,L0 域):如键盘桥 d754b71——失败严格沿『键盘 vs 指针』分界=最强信号;宿主经 esbuild 打进 bundle,故修后须重打包 bundle 才生效(node scripts/build.mjs <id>/entry-bundle.js <id>/bundle.iife.js --global-name=__GameBundle)→实证 t2048 score 0→4、asteroids F_wiring 0→26
  2. driver 太朴素(我域,改 play.cdp.cjs):本轮增 flap-to-gap vy 前瞻 / paddle-intercept 弃守排空 / drag-aiming / aim-fire 四件;flappy・multiball 经此 + 重生成翻绿。
  3. 可测性边界(游戏 exportState 不足以确定性盲玩):asteroids 无陨石坐标、angrybirds 无发射物理常数。正解=enrich exportState 暴露所需(坐标/物理常数)+ 适配 driver:asteroids 暴露 asteroids:[{x,y}]+aim-fire 旋向瞄准;angrybirds brief pin+导出 launch:{gravity,powerScale}+drag-aiming 45° 抛解析瞄准——两者皆经此从『边界』转『确定性闭口』(asteroids score 0→6、angrybirds 0→20 清场)抗死锁类(2048)的 latch 是设计性边界(游戏意在不结束),宜放宽为强 progress(score↑∧board变)+G 兜。
  4. 生成实例缺陷 ≠ 模型能力:multiball 原件底部丢球未实现(latch 不可达)、flappy 原件缝隙贴顶,但 flash 在收紧 brief(『球越底沿即出局不反弹』/『缝心落可达区间』)下重生成即过(multiball retries=0)。区分『此实例 bug』与『模型短板』必须重生成验证,不可凭单件下结论——这是『模型造不出』的最大伪报源。

结论(scale-20 的 driver-coverage 失败款):0 个是模型能力天花板,0 个是 3D 硬界——真难点恒在 harness driver 覆盖 + 宿主门面完备 + 游戏可测性导出三者。

★ 地板真承重件 = expectLatch + G_input,不是 progress 计数(对抗审计实证):H_progress.pass = progressPass && latchPass;单条 assertAfterPlay(score/revealed/moves +1)鉴别力弱——saolei 踩雷也 revealed++、runner score 计时自增,可被"漂亮空壳"蹭过;真隔离空壳的是 latch(真到 gameover+600ms 驻留)+ G_input(同帧 A/B 哈希证输入真生效)。新游戏务必让 progress 断言带终局语义(全清/三连/真死),否则靠 latch+G 兜。

★ 宿主键盘桥缺失(L0 硬缺口,实证):boot-game-host.js attachInput() 只桥 pointer 三态、不桥 keydown/keyupctx.getInput() 键盘通道空,所有方向键游戏(2048/Tetris/Asteroids)输入死。失败严格沿"键盘 vs 指针"分界=最强系统性证据(指针款全过、键盘款全挂)。修法对齐 host-dev/host.js:427-428(已 spawn_task 交 L0)。→ 移动端轻游戏优先 pointer/tap/swipe,键盘款待宿主补桥;配套 play.cdp.cjs key() 对 Arrow 须传 code===key(原 undefined 致 event.key 空)。

★ 对抗审计硬化门(Opus build-then-exit,ultracode workflow):脚手架期用 Opus 多 agent 对抗审计九门判定(反驳每个 PASS 找假绿 + 归因每个 FAIL)——实证抓出 saolei 弱断言(门+M3 都没抓到)、把 flappy 错归"模型天花板"纠正为"driver 缺陷"、定位宿主键盘根因。生产无 Claude:门(地板)+ M3 便宜 player(视觉过滤:抓显性空心、漏截图不可见失败)+ 创始人(人锚)三层互补;Opus 审完去硬化门/driver 即退出。

9. 首局体验门(firstPlay 派生超集)+ A/B 输入因果判定 + 2 harness 红线(W-G1 组C,2026-06-16)

九门之上加首局体验门(d4f16aab,只动 play.cdp.cjs,路A 零改 gatespec/契约):verdict.firstPlay:{playableMs, firstFeedback, coreLoopReached, categoryDerived},additive 不并入 verdict.pass(不阻断九门,H 仍机制硬地板);品类从 driver 家族反推(tap-targets 无 safeOnly→放置 / +safeOnly→规避 / paddle-intercept 等→技巧·action / none→退化),零改生成侧。三断言分层、复用现成:③ 60s 品类核心闭环 = H 门(assertAfterPlay+latch)外包 60s 墙钟(复用,几乎零新判定);① 可玩≤2s = 页面侧 performance.now()window.__playableAtMs(navigationStart 为 0 点;避 CDP 轮询 ~300ms 粒度;waitBoot:81delay(800) settle 只延后读取不污染——优于"改短 settle",零碰装载检测语义、保超时兜底)。

★ playableMs = warm 稳态,不是冷启:headless 全新 Chrome 首次导航 ~24.7s(进程 JIT/GPU/bundle 解析一次性开销,非游戏本征可玩时),预热后稳态 53-58ms。pre-flight 先暖一趟、隔离冷启记 warmupMs局限诚实入档:此门守得住"boot 永不完成/装载死循环"退化,守不住"大 bundle/慢首加载"退化(warm 测不出 bundle 体积)——真机 first-load(浏览器在跑·bundle 未缓存)阈值留 staging 冷缓存(Network.clearBrowserCache)校准。

★ 首反馈即时·A/B 输入因果判定(核心技术,别用裸帧):验"首招是否真触发反馈"。裸全帧像素/帧哈希 A/B 对动画自走类(动球)会被污染——球每帧本在动 + 两趟自由运行落帧抖动,实测 breakout signal≈noise(804 vs 1156)→ 裸帧会假 SKIP(更糟:单趟"帧变了"对动球类是重言式假绿,球本就在动)。改判定阶梯(measureFirstFeedbackAB):⓪ 控制体维度(读 controlCheck.paddlePath 指的 input-controllable 位如 paddle.x,免动球/粒子抖动,最干净)→ moved >moveMin=真因果 true、≈0=真 FAIL;① state 因果(phase/score/moves/remaining 变,无歧义 true,静态盘走此);② 像素 signal≥noise×3(仅无控制体路径兜底);③ noise 超顶=非确定性→SKIP(血统债);④ signal≈noise 无控制体→SKIP(不假 FAIL)。无假 PASS/假 FAIL/假 SKIP(breakout 经控制体 paddle.x Δ=145 真判 true)。

★ 2 条新 harness 红线(组C 采到):

  1. 后台 CDP target 的 rAF 被冻结:并发开多 target,headless Chrome 只让前台 target 跑 rAF(后台冻结)→ A/B 多趟里后台趟卡 boot(frame=-1)、前台趟跑到 gameover,对照彻底失效。必须串行单实例多趟(各 fresh navigate),不可并发 target。
  2. 控制体因果探针须点"离默认位最远的边",非默认招/ball-x:首招点 ball-x 可能 ≈ 控制体默认位(实测 ball≈198≈paddle195→Δ仅3),无法证因果。改点最远 tapX(如 x=50)+ 连派 tapsPerTarget(镜像 I_control)→ 控制体大幅移开(Δ=145)干净。

★ 受控面 determinism = A/B 对照前提:seed 缺省固定(0x1234abcd)+ LittleJS 固定步 → 跨 fresh 实例同帧号(atFrame)可比(G 门已用此范式);real 引擎自由运行(host 不掌 RAF、无暂停/回退)故不强求帧精确,靠控制体维度规避抖动。无控制体维度且非确定性的 skill 游戏 → 诚实 SKIP(标血统债:需受控面 seed 或游戏导出控制体位置维度)。