# 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.js` 的 `host.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: " <数字|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`,**不入声明式适配器**(强行内联=越界 + 过拟合)。 ## 已验证六族(均过真九门 A–I + 首局门) > 覆盖休闲玩法的主要机制维度:点击 / 连续控制 / 离散方向 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 → 九门验。 ## 验证 ```bash 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 ```