lili 33d9cd62ad feat(adapter): 时间压力维度——新增 elapsed 相对秒,支撑 time-based win/lose 规则
修一处潜在语义错 + 补休闲玩法时间压力维度(限时/倒计时):
- 旧 state.t = nowMs/1000 是【绝对单调时】(受控面明禁跨设备比绝对值),用作 `t >= timeLimit` 规则会因时钟原点(performance.now/Date.now)立判或漂移——错
- 新增 state.elapsed = (now - startMs)/1000(init 记 startMs,相对秒),forensics 暴露 elapsed;规则 `elapsed >= config.timeLimit → win/lose` 正确(零新增 op,正交叠加任一族)
- 单测经注入时钟(createHostDevContext({clock}))验:init@1s 起点,clockMs=3000 时 elapsed=2(相对,非绝对 3)仍 playing,clockMs=4500 时 elapsed=3.5 触发 lose——证相对非绝对

验证:src 全量 197/197 绿(+1 时间压力);additive 改动经回归九门(tap-targets 重建探针 A–I 全绿,H score:0→3+latch)确认不破坏既有 6 族,探针已清场。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:10:52 -07:00

5.9 KiB
Raw Blame History

E1 2D 适配器(声明式 gameDefinition → 可玩 GameInstance

固定游戏架构的引擎线消费侧(plan docs/plans/2026-06-17-002-feat-mac-engine-frontend-tier0-plan.md U1/E1)。 把后端 SAA 图产的 SourceProject.gameDefinition(声明式)适配成命中 ../core/game-host.d.ts 契约的可玩游戏—— 换适配器即换引擎(gameDefinition 维度无关)。

文件

文件 职责
create-game-from-definition.js adaptGameDefinition(source) → GameHostFactory:声明式 gameDefinition → GameInstance
scaffold-from-source.mjs buildFactorySource / scaffoldFromSource:SourceProject → 可被 ../../scripts/build.mjs 编译的 game 目录工件(E2)
fixtures/*.source.json 四族 archetype 的 SourceProject 样例
adapter.test.mjs / scaffold.test.mjs 零依赖 node 单测(node --test src/adapter/*.test.mjs)

契约要点

  • 输出 = GameInstance:{ init(boot), update(dt), render(g), destroy(), _forensicsView() }(../core/game-host.d.ts)。
  • 🔴 _forensicsView(){ seed?, state: () => snapshot }——state 是 thunk;宿主 boot-game-host.jshost.state() 调的是 _forensicsView().state()直接返裸 snapshot 会让 host.state() 抛错、九门读不到态(必踩坑)。
  • 受控面铁律:零引擎 import;能力经 boot.ctx.getEngine()/getInput()/time/random;render 只画到 boot.mainContext(render(g) 入参);输入/绘制坐标 = 逻辑像素 390×844;禁 Math.random/Date.now/addEventListener/rAF/new AudioContext/document/window

声明式词汇(本切片支持)

  • input op(behavior.trigger='input'):hit-nearest-target(点最近目标计分)· control-x(实体 x 跟手=连续控制)· flap(竖直上冲)· move-lr(点左/右半 → 实体左/右移一步=方向式 steer)· tap-safe(点 safe 目标计分、点 .hazards 列出的 hazard 即记 miss=辨别/规避)。
  • update op(trigger='update'):drift-targets(目标慢漂移)· fall-and-catch(下落物+接住=控制类)· flappy-gap(重力+穿缝=flappy)· reach-goal(实体 x 抵达目标 x 容差内 → 计分+目标重生=导航/收集)。
  • rule:{ condition: "<state字段> <op> <数字|config.键>", outcome: win|lose }(序前优先);胜负 → phase=gameover + latch 驻留。可引用 state 字段:score/misses/elapsed(相对秒,init 起算)。时间压力维度(限时/倒计时)= elapsed >= config.timeLimit → lose(零新增 op,复用 rule 机制);⚠ 勿用 t(绝对单调时,禁作时限判)。
  • 命中/接住/穿缝/抵达/胜负事件ctx.getEngine() 发粒子+音效(满足九门 F 真接线 + game-feel)。

六类资产消费(E3,对齐 contracts/agent-loop/source-project.schema.json 冻结 assetSpec)

assetSpec = { id, category∈[sprite,character,effect,scene,ui,music], ref, url?, provider? }——引用/url,无内联数据。消费侧(引擎线)按类:

消费路径 兜底(spec-only 无 url)
sprite / character 实体 render 组件经 .asset 引用 → 有 url 浏览器 new Image+drawImage 几何矩形(命中变暗)
ui / effect 同上(经 render 组件)/ effect 亦由命中粒子反馈体现 几何 / 粒子
scene 顶层背景(非实体引用):取首个 category==='scene',有 url → drawImage 满幅 默认底色 #0d1117
music 经 url 不可消费——冻结受控面 getEngine().audio 仅 zzfx synthSfx/synthSong(合成,非文件播放),无播 url 音频能力 静默(跨线契约缺口:需 engine audio.playUrl 或 music assetSpec 携 zzfx 歌数据;见 src/core/api.d.ts)

边界(KTD1):适配器只解释声明式部分;复杂 behavior 真逻辑由核心 code agent 产、经 build.mjs 编进 __GameBundle(非适配器解释)。适配器覆盖简单声明式游戏的主要机制族(已 6 族,见下)。 更复杂族(aim-fire 抛射瞄准 / drag-aiming 拖拽弹弓 / match-3 消除):虽受控面有 keydown/keyup/pointermove/pointerup 足以接其输入,但其抛射物理/旋转瞄准/读盘消除是复杂 behavior 逻辑——按 KTD1 归 code-agent __GameBundle,不入声明式适配器(强行内联=越界 + 过拟合)。

已验证六族(均过真九门 AI + 首局门)

覆盖休闲玩法的主要机制维度:点击 / 连续控制 / 离散方向 steer / 物理 / 导航 / 辨别(负反馈)。 时间压力维度(限时/倒计时)经 elapsed >= config.timeLimit 规则正交叠加于任一族(复用现有 driver,无新族),单测已验。

fixture driver 关键门
tap-target.source.json 静态点击(clicker) tap-targets H 进展+latch
moving-targets.source.json realtime 漂移 tap-targets E 活性 + 漂移目标命中
catch.source.json 控制类(接住) paddle-intercept I 控制跟手
flappy.source.json flappy(重力穿缝) flap-to-gap 物理穿缝
seek.source.json 导航/收集(seek) seek-x 方向式 steer(move-lr)+ 抵达计分(reach-goal)
avoid.source.json 辨别/规避(avoidance) tap-targets(safeOnly) 负反馈(tap-safe:点 safe 计分 / 点 hazard 即负)+ forensics safe 标记

加一个 op / archetype

  1. SUPPORTED_INPUT_OPS / SUPPORTED_UPDATE_OPS 注册 op 名。
  2. onTap(input)或 update 行为循环里加 op 解释(操作 entById(id) 取的实体)。
  3. 关键事件经 ctx.getEngine() 发反馈(过 F 门)。
  4. 若 driver 需读新实体态,在 _forensicsView().state() additive 暴露(如 paddle/ball/bird/nextGap)。
  5. 加 fixture + 单测 + play-spec(driver/assertAfterPlay/expectLatch);scaffoldFromSource → build → 九门验。

验证

node --test src/adapter/adapter.test.mjs src/adapter/scaffold.test.mjs   # 零依赖单测
# 九门(集成段/mini-desktop 权威;本机快走查):scaffoldFromSource → scripts/build.mjs → games/_wg1-gen/_shared/serve-and-play.sh