lili cbfd4d871b
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled
feat(acceptance): 闭合 playtest v3 与 A+ 可信消费链
固化 Match-3 生产者、视觉、音频与双 Judge 证据闭包。

将《山海行纪》r1.1 绑定新的不可变 release,并以生产预检现场核验 bundle、Registry/2 和 25 项 Writer 快照。

同步地图1平衡锁值、跨游戏回归修复、验收契约与 SoT 证据。
2026-07-28 20:16:13 -07:00
..

_template-story — A-model「剧情互动」品类黄金骨架(3 层架构)

一个可玩的完整分支剧情游戏:读场景文本 → 在 2~3 个选项里做选择 → 属性随选择积累 → 剧情沿分支推进 → 走到某个结局结算(多结局 + 结局图鉴,menu→play→over)。这份骨架已经把整套剧情推进循环接好了(含可选的调试视图)——你的活是按 brief 换故事,不是重写推进机。

怎么改 = 给这段剧情换个故事,推进结构别动:

  • 换题材/文案/分支/结局/属性轴 → 改 src/core.js 顶部的「多样性参数空间」(STORY_NODES、STORY_TITLE、STAT_NAMES)。古宅悬疑写勇气/机智,恋爱线写好感/理智,武侠写侠义/内力——同骨架讲出不雷同的故事就靠这里。
  • 换观感(氛围底/配色/文本卡样式)→ 改 src/render.js。
  • 想加机制(属性检定开选项/线索收集/隐藏结局)→ 在 src/game-logic.js 推进机里加。
  • 别推倒重写推进机:节点推进/属性结算/多结局 latch/结局图鉴持久都接好了,从它起步只换故事,远比从零写省。

写故事图(core.js STORY_NODES)三铁律:① 每个 next 必须真存在(死链=推进卡死);② 无回环(任意路径有限步必达某结局);③ 结局 ≥3 且 outcome 有 win 有 lose。文本红线:节点 text ≤3 短句、选项 label ≤10 字(手机竖屏装得下)。

3 层 + 你只碰 L3

层 文件 你
L1 固定 plumbing(write 会被拒) index.html · entry-bundle.js · src/host-config.js(已注入全 11 插件 + viewport 390×844 + wiring) · src/game.js(薄 wrapper:摊平 opts.runtime → 交 game-logic) 不碰
L3 游戏本体(已是完整分支剧情) src/game-logic.js(推进机 + 五法 + 取证契约) · src/core.js(故事数据 + 多样性参数空间) · src/render.js(观感) · src/assets.js(资产装载) 在这上面换故事

入口契约(钉死)

// src/game-logic.js —— 剧情推进机已写好,你换故事
export function createGame({ plugins, bundle, viewport }) {
  // 插件已扁平注入(全 11 件,用到才解构);本骨架剧情档用 6 件:
  const { sceneFsm, sessionScore, hudUi, save, audioMusic, juice } = plugins;
  // 全部可用键:sceneFsm sessionScore hudUi timerScheduler save gamefeel juice palettePost audioMusic collision physics
  return {
    async init(boot) { const ctx = boot.ctx; /* 载图鉴→定义场景→start;输入不订阅(L1 派发到 handleTap) */ },
    update(dt) { bundle.tick(dt); /* ★必调;再推进呼吸相位 */ },
    render(g) { /* 画在 g;读 viewport.w/h */ },
    handleTap(x, y) { /* ★输入入口:各 phase 点击逻辑;L1 在 pointerdown 时直达调用(别自己订阅输入) */ },
    destroy() {},
    _forensicsView() { return { state(){ /* phase/score/targets 实时算 */ }, measures(){} }; }, // 可选调试接口(验收不读)
  };
}

铁律(check 会拦)

  • 入参扁平 {plugins, bundle, viewport} —— 没有 opts、没有 opts.runtime、没有 ctx(L1 已摊平)。受控面 ctx 只在 init(boot).boot.ctx(时间/随机/输入/引擎)。
  • 插件直接 const {…} = plugins —— 别写 opts.runtime.plugins(L1 内部的,你见不到)。游戏只用、不 new、不 register。
  • update(dt) 必调 bundle.tick(dt) —— 否则插件 onFrame 不推进。dt 是秒:游戏内毫秒计时要 elapsedMs += dt*1000;timerScheduler.after(ms) 收毫秒(若你加限时机制,60s 写 60000)。
  • 零引擎 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() 实时导出当前节点/选项/属性等,供人工调试观测;W-AXIS-V2 起验收不再读它(验收 = 四门投影 ∧ 测试 agent 真玩)。
  • 选项命中区 = 绘制区 同源:全部经 core.layoutChoices(vw,vh,n) 一处算(render 照 rects 画、handleTap 用 hitChoice 判)。别在 render 里自己另算按钮位置——一旦分叉,玩家点中画出来的按钮却没反应。
  • 场景名约定 menu/play/over(全品类统一结构);结局到 over 后驻留(latch),绝不自动重开——「再读一次」只在玩家点按钮时触发。

你写什么 vs 调什么

  • 你写(故事身份):题材/文案/分支/结局/属性轴(core 顶部参数空间)、观感与氛围(render)、把故事接到已写好的推进机(game-logic 一般只微调)。
  • 你调(通用机制):计分 sessionScore · 场景机 sceneFsm · UI 原语 hudUi · 持久 save(结局图鉴)· 粒子 juice · 音频 audioMusic · 受控定时 timerScheduler · 手感 gamefeel · 后处理 palettePost · 碰撞/物理 collision/physics(全已注入,用到才取)。

详见 .agents/skills/littlejs-game-dev.md(11 插件 API 速查 + ❌→✅ 易犯错)与 .agents/skills/narrative-game-design.md(剧情品类「设计什么才好玩」)。