diff --git a/docs/plans/2026-07-15-001-feat-内部狗粮上线执行-plan.md b/docs/plans/2026-07-15-001-feat-内部狗粮上线执行-plan.md new file mode 100644 index 00000000..1aa99c64 --- /dev/null +++ b/docs/plans/2026-07-15-001-feat-内部狗粮上线执行-plan.md @@ -0,0 +1,168 @@ +--- +date: 2026-07-15 +topic: 内部狗粮上线执行 +status: 已批准·独立复评通过·执行中(创始人 2026-07-15 选定“仅内部狗粮 + 10 创作者/30 玩家 + 闸门优先分波”;独立首评 P0/P1 已修,复评 PASS) +sot-impact: 不新建 canonical topic。执行收口时只回填上线主计划、MVP 进度总账、MVP 作战清单和 A1 闸门看板;生成质量口径继续以《生成线验收 v3 可信证据闭环》为在飞事实源,本计划不重写其契约。 +上级: docs/mvp/可行性方案16周-波次映射.md +依赖: + - docs/agent-specs/2026-07-13-生成线验收v3-可信证据闭环-设计.md + - docs/agent-specs/2026-07-07-内测-WU1-注册登录用户名密码-设计.md + - docs/agent-specs/2026-07-07-内测-WU2-newapi额度接入-设计.md +--- + +# 内部狗粮上线执行计划 + +## 1. 意图与完成线 + +本轮不是对外内测,也不是把既有骨架换个名字宣称上线。它只验证一件事:内部真实用户能否稳定完成“创建→生成→预览→提交审核→人工通过→进入 feed→试玩→数据回流”。 + +完成线为两波:先由 3 名创作者和 5 名玩家干跑 1 天,无 P0、无验收假阳、无审核积压后,再扩到累计 10 名创作者和 30 名玩家运行 3 天。最早目标是 2026-07-23 开始波 0、2026-07-25 开始波 1;任一硬闸未过时按 24 小时自动顺延,日期不代替闸门。 + +## 2. 边界 + +本轮只对内网和内部名单开放。不发外部邀请,不暴露公网注册,不接真广告、真收款或提现,不用 Dify 旁路代替产品主链,不根据小样本宣称留存或 99.5% 可用性达标。支付、广告、短信和律所四项人办闸门继续并行,但不是本轮内部狗粮的开闸条件。 + +```mermaid +flowchart LR + A["生成 v3 可信闸"] --> D["Go/No-Go 证据包"] + B["干净 RC + staging 可复现"] --> D + C["账号·额度·审核·feed 真链"] --> D + D -->|"全绿"| E["波 0·3+5"] + E -->|"无 P0/假阳/积压"| F["波 1·10+30"] + D -->|"任一红"| G["冻结发布·保留证据·修复复测"] +``` + +## 3. 执行波次 + +| 波次 | Owner | 产出 | 验收 | +|---|---|---|---| +| R0 设计与发布冻结 | 主 Agent + 独立评审席 | 本执行计划、RC 边界、证据目录 | 评审发现已修,实施面无未决策分支 | +| R1 生成可信度 | W-AXIS + QA | off-brief 生产可达、结构化共识、同请求重复稳定性闸 | Actor/Judge 按本节重复稳定性精确门过线;fresh25/historical11/shadow20 全过 | +| R2 干净 staging | 运维 + 后端 + 前端 | 独立干净 worktree 构建的 commit-pinned RC,恢复五个端口,配置快照和回滚包 | 改造后 smoke 验生成终态、审核、feed 与试玩;三类回滚各演练一次 | +| R3 产品主链 | Studio/Admin/后端 + 运营 | 同一参考游戏全链、人工审核、下架、角色权限和数据回流 | 项目/版本/产物/审核/feed 身份可对账;未审/驳回/产物漂移负例全拦;审核员不能改配置,只读角色写操作 403 | +| R4 内部运行保障 | 运维 + 数据 + 运营 | 值班表、单一反馈账本、真告警、删档和停服手册 | 首屏 P75<3s,P0=0,埋点完整率≥95%,告警真送达 | +| R5 波 0/1 | 创始人 + 值班 Owner | 3+5 干跑后扩至 10+30 | 无硬停条件,每日证据和配置身份已冻结 | + +## 4. 公开接口与配置决策 + +产品面不新增公开 API,复用已实现的用户名密码鉴权、new-api 额度池、project 审核和 feed 发布接口。上线账号由 ops 预置,预置完后在后端分开关闭密码注册、保留密码登录;整个 staging 只允许 Tailscale 内网进入。只隐藏 UI 不算完成:短信发码/登录、充值、提现、广告请求必须在后端狗粮开关处 fail-closed,直接调用端点也受拒;RC 启动自检要求短信 mock 为关、所有真广告/真商户配置为空或禁用,否则拒绝启动。 + +生成验收只做内部契约升级:runner 对 profile proof 与 cross-profile observation 分类,后者不得满足玩法义务;Judge 使用结构化全局结论和证据引用形成共识,不再用自然语言 hash 判一致。该差量归现行 v3 设计收口,不新立第二套证据协议。 + +重复稳定性门的统计口径锁死:前三轮 Actor schema 必须 15/15,每轮行为至少 4/5,合计至少 12/15,每个 evalKey 至少 2/3 正确且 error=0;Judge A/B 各自前三轮 schema 与行为均为 18/18,共识假阳为 0。第三轮只能申请写 baseline,不签发通过;第四轮使用同一身份独立确认并再满足单轮标准后,才能开 fresh 25。 + +额度池的开波门为 `FREE >= 当波创作者数 × 1.2`,向上取整;波 0 至少 4 个、波 1 至少 12 个 FREE 条目。重复注册不得重复 claim,池空和 quota_exhausted 必须返回不同的可读拒因,且不得扣他人额度。 + +## 5. 验收、停止与回滚 + +所有页面验收经 `/gstack` 在 mini-desktop 真浏览器执行,每条关键链同时保留截图、网络请求、应用日志和数据库 ID 四类证据。生成线的阈值完全沿用 v3 设计:fresh 25 首过至少 18/25、修后接受至少 20/25、各品类至少 3/5;shadow 20 中 tester error=0、inconclusive≤1、确认假阳=0。 + +发布负例必须在开波前全过:DRAFT、REVIEWING 和 REJECTED 项目不得发布或进入 feed;直调发布/包体接口不得绕过审核;批准必须绑定 project、version 和 artifactHash,批准后产物漂移必须拒发;下架后 feed、详情和深链包体均不可继续试玩。每个内部新发布游戏在进 feed 前 100% 人工真玩。 + +首屏性能口径固定为 mini-desktop Chrome 146、390×844@1、Tailscale 访问 staging、禁用缓存后独立导航 30 次,从 navigation commit 到首个可交互游戏帧计时,P75 < 3s;热缓存数据只单列报告,不与冷样本合并。 + +出现安全事件、跨账号访问、数据丢失、真实扣款、真广告曝光或未审内容进入 feed,立即全局停止。生成成功率低于 80%、出现任一确认假阳、同一阻断根因影响两人、埋点缺失超过 5%、待审超过 4 小时或验收成本突破 v3 上限时,停止扩容。 + +回滚顺序固定为:后端关闭新账号与提交审核→后端 feed 总闸 fail-closed→v3 退回 v3_shadow→下架受影响游戏→回退 RC→保留全部证据。开波前对本轮涉及的 MySQL 表和对象存储前缀做快照,并用隔离目标至少完成一次恢复验证。删档范围只限本轮 batchId 关联的账号、项目、版本、feed 和遥测业务数据;审核记录、安全日志、发布证据和根因工单保留至少 180 天,不随业务删档删除。每波收口按 wave-close 八步回填活账;本轮 GO 只表示内部主链可稳定运行,不等于外部内测或公开上线。 + +## 6. 2026-07-23 执行快照 + +发布工具、候选产物、账号额度底座和信任根已准备,但 staging 尚未激活,波 0 也未开始。这个快照只记录已核验的执行证据和续跑边界,不改写生成质量、上线主计划或 MVP 进度口径。 + +### 6.1 已验证事实 + +| 范围 | 证据 | 结论 | +|---|---|---| +| 代码身份 | 本地与 `origin/dev/2.0.0` 均指向 `8a81b3734819752d589e770292ed9a314e5012f6` | 候选版本可按完整 commit 追溯 | +| RC 候选 | mini-desktop 的 `/root/game-staging/releases/8a81b3734819752d589e770292ed9a314e5012f6/RC_STATUS` 记录 `ready`;检查时 `/root/game-staging/active` 不存在 | 只完成候选准备,没有切换服务 | +| 构建与回归 | 发布工具 10 项测试通过;21 个安全测试类共 195 项通过;Maven 59 模块构建成功;game-studio 与 game-admin staging 构建成功 | 候选产物达到“可进入激活前审批”状态 | +| 产物固定 | `evidence/artifacts.sha256` 的 SHA-256 为 `3c85947aa35b686ad28eb200f60ddba9ade5c5ced10b59f552e499dc81e7b0fc`;`evidence/runtime-tree.json` 为 `11a8120a373f9a354895322bee39c57af7a86ae8b113212d3158ce04878cd5f2` | 构建输出与运行时树可对账 | +| 额度底座 | `neice_051`–`neice_054` 已在 new-api 建立并导入 staging 额度池;池内 `GLOBAL_FREE=52` | 足够支撑 40 个正式分配和 12 个预留;40 个产品账号仍未创建 | +| 签名边界 | Ed25519 私钥只位于 mini-infra,权限 `0600`;公钥仓内副本 SHA-256 为 `e3cc17cd86805b053f61450b5c2c6e4e168bb01dbf1e83840124290d1196a304` | R1 与账号报告必须通过固定信任根验签 | +| 报告防伪 | mini-infra 收集器同时对账 staging MySQL 和本地 new-api PostgreSQL,并在读库前后校验 active、`RC_STATUS` 和激活证据;未激活运行已被拒绝 | 不能用命令行自报数据伪造已上线报告 | +| 安全负例底座 | 外部 HTTP 直调项目版本创建/绑定和 feed 写入已 fail-closed;preview 所有权、OAuth 凭据清理、new-api 状态对账已补回归 | 实现层安全门已有回归证据,仍需激活后真浏览器复验 | + +game-studio 生产依赖审计为 0 个已知漏洞。game-admin 没有 critical,但仍有 12 个 high、39 个 moderate 和 6 个 low;它们是本轮的已知剩余风险,不得记成依赖安全已全绿。首个 RC 没有前任版本,因此 `rollback_target=none`;激活前必须将“无可回退旧版”当作显式风险处理。 + +### 6.2 未完成与硬阻塞 + +R1 仍是激活前硬阻塞。权威活账显示:正式 `game_content_gold=0`,fresh 25、historical 11 和 production shadow 20 均未运行,生产 `runtimeEligible=false`。在生成线按现行口径签发独立 R1 PASS 前,不得创建 active 指针、不得开放产品账号、不得开始波 0。 + +除生成线外,仍需真实执行的项目是:Nacos 配置事务与候选激活、对象存储审计、MySQL 隔离恢复验证、40 个产品账号建立与额度分配、主链与安全负例的 `/gstack` 真浏览器验收、首屏 P75 和埋点完整率采样、外部告警真送达、值班与反馈账本、波 0 一天和波 1 三天的证据收口。当前没有可支撑“已上线”、“告警已送达”或“内测已完成”的证据。 + +### 6.3 续跑顺序 + +1. 先从生成线取得绑定 commit、运行时身份和证据 hash 的独立 R1 PASS,然后重新核验 RC 目录、信任根和所有临时凭据有效期。 +2. 在维护窗执行 Nacos 配置事务、MySQL/存储快照与隔离恢复验证,全绿后才激活 commit-pinned RC。 +3. 激活后再创建 40 个产品账号、分配 40 个额度条目并保留 12 个 FREE,由 mini-infra 收集器对账后签名。 +4. 用 `/gstack` 完成参考游戏主链、角色权限、未审/驳回/产物漂移/下架负例,并对每个进 feed 的新游戏先人工真玩。 +5. 收口性能、埋点、告警、值班、反馈和回滚证据;仅在无硬停条件时开波 0,再按闸门扩到波 1。 + +### 6.4 2026-07-23 续跑核验与 R1 终审 + +主代理只读漂移核验,叠加 Claude opus 独立诊断子代理的复核,两方结论一致。 + +漂移核验无变化:本地与 `origin/dev/2.0.0` 均指向 `8a81b3734819752d589e770292ed9a314e5012f6`;mini-desktop RC 候选 `RC_STATUS=ready`、`active` 指针不存在、五个 `gda-staging-*` 服务全 inactive、无 `gda-rc-*` 孤儿;信任根三处(mini-desktop 固定公钥、RC 仓内副本、mini-infra signer 公钥)SHA-256 同为 `e3cc17cd86805b053f61450b5c2c6e4e168bb01dbf1e83840124290d1196a304`;mini-infra 私钥 0600、root 持有。`evidence/runtime-tree.json` 由主代理从候选目录亲自重算,SHA-256 为 `11a8120a373f9a354895322bee39c57af7a86ae8b113212d3158ce04878cd5f2`,确为 64 位,与 §6.1 对照值相符;上一轮报告那个 62 位的值已否决,不得复用。 + +R1 终审判 BLOCKED。mini-desktop `/root/game-staging/attestations` 不存在,没有任何 `*.attestation`,即没有合法独立签名的 R1。激活器 `verify_generation_gate_evidence`(`deploy/activate-staging-rc.sh` 第 611–626 行)要求 `generation-r1-gate/2` 的八字段 `actor_prompt_eval`、`judge_prompt_eval`、`game_content_gold`、`live_prompt`、`full_gate`、`fresh_25`、`historical_11`、`production_shadow_20` 全 PASS、`runtime_eligible=true`,再叠加 Ed25519 验签、issuer=`r1-mini-infra`、24 小时有效期窗,当前无一满足。全仓检索确认 `GameContentGold/1`、`ReferenceAssetRegistry/1` 在代码层没有任何机器契约、schema 或注册表,只有 docs SoT 规则提及。 + +R1 八字段不是并列的,存在依赖树,三条共前置基线各自被不同红线封死: + +| 基线 | 字段 | 封锁原因 | 解封锁的人 | +|---|---|---|---| +| 内容金标 | `game_content_gold` | 当前为 0,连目标品类实例都未选定;第一步就是选品类、批设计意图,收口是人工签认,全程不可代理 | 创始人 | +| Actor/Judge 稳定门 | `actor_prompt_eval`、`judge_prompt_eval` | 3.0.13 三轮建线加第四轮独立确认未闭合,须 mini-desktop 真 Chrome 真模型复跑;且落在 tracked 在途脏文件 `contracts/prompts/eval_gate.py`、`registry.yaml`、`game-runtime/src·tools` | W-AXIS 在途收口 | +| W-GOLD-LIVE | `live_prompt` | `ReferenceAssetRegistry/1` 代码不存在,须从零实现并接入 cheap/tier2 live prompt;落在 tracked 在途脏文件 `cheap-worker/cheap_verify.py`、`hard_genre_batch.py`、`tier2/config/generation.yaml` | W-AXIS 在途收口 | + +三基线不闭合,下游 `full_gate → fresh_25 → historical_11 → production_shadow_20 → runtime_eligible` 全部无从开始;即便三基线全绿,终态 attestation 仍须由独立签发方 `r1-mini-infra` 用 mini-infra 私钥签名,执行代理代签属红线。因此五项硬串行全卡在 R1:R1 不解,不得激活、不得建 40 账号、不得 `/gstack` 验收、不得开 Wave0/1。执行代理侧不存在任何“不改在途文件、不需人工金标、不需代签、不需 ssh 远程”的干净可执行缺口。 + +第 4 项的 webhook 硬门同步核实:staging `/root/huijing-dev.env` 没有任何 `ALERT`、`WEBHOOK`、`NOTIFY` 端点键,仓内仅 `application-dev.yaml`、`application-local.yaml` 与 `deploy/infra/observability/grafana/provisioning/alerting/gen-alerts.yaml` 有告警配置,均非 staging 外部送达。告警真送达仍须人工提供 webhook,这与本计划 §5 的硬门口径一致。 + +治理附注:权威活账 `docs/mvp/MVP进度总账.md` 本身是 tracked 在途脏文件,工作树版本(2026-07-22)比 HEAD(2026-07-04)新,承载上述 R1 阻塞数值的恰是这份未提交版本;工作区另有 109 个 tracked 加约 13899 个 untracked 的他人生成线在途改动,集中在 `game-runtime`、`cheap-worker`、`tier2` 与 `contracts`。本轮不得覆盖这些改动,活账与作战清单的回填须由 W-AXIS 收口时一并入库。 + +解除 R1 的最小动作按依赖最靠前排序: + +1. 创始人从 Fable 三款或新设计中选定首个目标品类实例并批设计意图,实现后补落地证据并人工签认,产出首个 `game_content_gold`。这是整条链唯一不可代理的起点。 +2. W-AXIS 并行收口 Actor/Judge 3.0.13 稳定门(三轮加第四轮,mini-desktop 真跑),落地 `ReferenceAssetRegistry/1` 机器注册表并接线 live prompt。 +3. 三线闭合后重跑完整总门,再依次跑 fresh 25、historical 11、production shadow 20。 +4. 独立签发方 `r1-mini-infra` 用 mini-infra 私钥产 `generation-r1-gate/2` attestation 并签名。 +5. 主代理验 R1 PASS,再派激活事务、40 账号、`/gstack` 验收、Wave0/1。 + +本轮主代理未激活、未建号、未开波、未代签;实际动作只有只读漂移核验、R1 独立诊断终审、webhook 缺口核实,以及对明确生成产物的 `.gitignore` 收敛(子代理执行,不提交)。 + +### 6.5 2026-07-24 金标阶段二 realizationEvidence 采证进展与 juice 冻结 bug + +创始人 2026-07-23 追认《山海行纪》为首个 `game_content_gold` 候选(见《游戏内容金标》§7.1),并令先推金标阶段二。阶段二 realizationEvidence 由 opus 执行代理在开发机本地(Chrome 150,非 mini-desktop)分检查点采证,进展如下。 + +| 维度 | 状态 | 证据 / 缺口 | +|---|---|---| +| ① 身份 / ② 玩法 | verified | 实现对照充分(M0–M4 真表 + 录证) | +| ③ 主题映射 | partial→近 verified | 符箓御兽长出机制可回溯;待④⑤落地补对照 | +| ④ 美术 | partial | 四态全屏截图已采(round-1/,sha256 全),待创始人眼验全屏风格统一 | +| ⑤ 音频 | partial | SFX 8 事件结构断言 5/5;BGM 6 首代码 + 资产双 missing,需开发制作 | +| ⑥ 内容与进度 | partial | 10h 核数 11.21h ≥ 10h 落盘(evidence/content-audit.json);地图 2-5 / 敌 32 / D2-D5 需 M5 灌装 | +| ⑦ 数值与成长 | partial | build 判据①③④ PASS、② rand-4 winRate=0.9167>0.85 离群现状落盘(evidence/sim-scan-c2-convergence.json,528 局);现状中位 0.375;收敛锁值需开发调参 + 质量轨人工拍阈值 | +| ⑧ 真玩证据 | 部分 + win 受阻 | lose + restart 两路径全通、九门 A/B/D/E/G/I PASS(C 用 timeS 增量补证、F SKIP、H 无终态);**win 终态确定性受阻于 juice 冻结 bug** | +| ⑨ 已知限制 | partial | 候选阶段自述有;待①-⑧全 verified 后正式登记 + 创始人签认 | + +**重要发现:juice 顿帧冻结 bug(⑧真玩采证暴露,headless sim 测不出)。** 现象:冻结与 seed 无关,三个 seed(777/1/42)跑出字节级一致的世界轨迹,全部精确冻结在 timeS=331.4(score=188 / lvl=7 / hp=58 / enemies=219),帧照跑(frames 涨满)而世界 dt=0、无异常、非熔断,九婴(timeS=1200 出场)从未现身,死局。根因(只读定位,未下钻到行):juice 插件 `game-runtime/src/plugins/particles-juice/impl.js` 的 `timeScale` 顿帧卡死,推断 crit / bossKill hitStop 后恢复计时按缩放时间走、timeScale→0 则永不恢复;消费方 `shanhai/src/game-logic.js:264-265` 以 `juice.getTimeScale()` 缩放世界 dt(`sim.update(dt * ts)`)。影响:所有 seed 在 331.4s 必冻结,win(斩九婴)不可达;stub 下 URL `?seed=` 不生效(世界模拟完全确定),换 seed 这条路本身不成立。处置:根因落在他人插件红线区(particles-juice tracked 0 脏),本轮只读定位不可改,需 W-AXIS / 插件 owner 修复 `timeScale` 恢复;修后续跑已验证的幂等续跑驱动 `test/win-cont.mjs` 可达 1200 斩九婴。附带发现:stub 下 seed 不生效,若放量需多 seed 多样性,另交相关方确认。证据见 round-2 / round-3(冻结截图、freeze-probe、逐 seed 帧级探针、summary)。 + +采证环境口径:本轮⑧在开发机本地 Apple M1 Pro + Chrome 150 headless 跑,非 mini-desktop;放量与真机结论仍须 mini-desktop 复核,本⑧是内容金标本地人工验玩 / 真玩证据。 + +执行代理采证已到天花板:⑧win 受阻于 juice bug(需 W-AXIS 修);⑤BGM、⑥M5 灌装、⑦调参均需开发(⑥M5 与"压 M5"旧令重叠,属材料级,待创始人拍板);④眼验、⑤听感、⑧好不好玩、⑨签认均需创始人。本轮未 commit、未激活 staging、未签 R1、未碰 mini-infra;全程零误触他人在途文件(tracked 脏维持 109 他人 + 本轮 content-audit.mjs + IDE 自动的 .idea/jarRepositories.xml,共享代码 game-runtime/src 与 plugins 逐字未变)。 + +### 6.6 2026-07-24 金标阶段二采证收口与⑧win 已知限制 + +金标阶段二 realizationEvidence 采证至此冻结。执行代理能采的部分已采尽,⑧win 胜路径记为已知限制,九婴机动冲撞交 W-AXIS 做平衡判断。 + +最终九维状态:① 身份 / ② 玩法 verified;③ 主题映射 partial→近 verified;④ 美术 partial(四态截图已采,待创始人眼验);⑤ 音频 partial(SFX 结构 5/5,BGM 6 首双 missing 需开发);⑥ 内容进度 partial(10h 核数 11.21h 落盘,地图 2-5/敌 32/D2-D5 需 M5);⑦ 数值 partial(判据①③④ PASS、②rand-4=0.9167 离群,收敛需开发);⑧ 真玩 partial(负/重玩/进度全通、九门 G/A/I/E/B/H 过,**win 胜路径已知限制**);⑨ 限制登记已整理(realization-evidence-status.md)。 + +本轮两项实质工程产出(均在山海自有代码,未碰他人红线区): +1. **juice 顿帧冻结 bug 修复**:山海 game-logic.js 漏调 bundle.tick(dt) 致首 crit 后 world dt=0 死局;已补 bundle.tick(对齐其他 100 个游戏标准调用),硬证生效(世界解冻、推进过 1200、frameCount 接线),回归 13/13。 +2. **forensics 取证面扩展**:sim.js 加 bossesForensics() 只读 getter、game-logic.js _forensicsView().state() 加 bosses 字段,暴露存活 Boss 实时坐标供 CDP driver 贴脸集火;纯加法只读,回归 13/13 不破坏游戏。 + +**⑧win 已知限制(九婴机动冲撞)**:forensics 生效后 driver 读到九婴坐标并打掉 30% hp(7400→5161),但九婴是大范围机动冲撞 Boss(横扫全场),安全距 sustained dps 仅 ~112 hp/s(杀完需 ~66s)> 玩家存活窗 ~20s,玩家满血扛 20s 后被冲撞贴身秒杀;调 hugR 半径解不了(击杀需时 > 存活窗的不等式)。win 需冲撞规避 driver(逐帧坐标算速度向量、前摇闪避)或属游戏平衡问题。诊断见 evidence/round-7/lose-safedps-attempt4/FINDING-mobile-boss.md。 + +**交 W-AXIS 平衡判断**:九婴贴身必秒 + 全场冲撞 + 安全距 dps 不足是否为便宜档难度配平问题(普通玩家是否也打不过);若是,需调九婴行为/数值或提供更强 build 使 win 可达。修复后续跑已验证的 win-jiuying.mjs(比例减速逼近控制器)可推进。 + +⑧采证 round-1~7 全套证据(截图/探针/九门/仿真/FINDING)留存山海 evidence/。本阶段未 commit、未激活 staging、未签 R1;全程零误触他人在途文件(山海 tracked 脏仅 content-audit.mjs + game-logic.js + sim.js 三件,共享代码 game-runtime/src 与 plugins 逐字未变)。金标正式签认仍须①-⑧逐维 verified + 创始人签认,当前未达,山海行纪仍 candidate。 diff --git a/game-runtime/games/shanhai-xingji/evidence/juice-bug-report.md b/game-runtime/games/shanhai-xingji/evidence/juice-bug-report.md new file mode 100644 index 00000000..2764076e --- /dev/null +++ b/game-runtime/games/shanhai-xingji/evidence/juice-bug-report.md @@ -0,0 +1,219 @@ +--- +topic: shanhai-xingji-juice-freeze-defect +status: defect-report-for-waxis +sot-impact: none(只读定位产出的缺陷报告,不改任何 SoT;修复落 W-AXIS/山海自有代码) +上级: docs/architecture/产品/游戏内容金标.md(维度⑧真玩证据 win 路径受阻根因) +--- + +# 《山海行纪》顿帧冻结 bug 缺陷报告(供 W-AXIS 直接修复) + +> 本报告为**只读深挖**产出。全程未改任何代码:juice 插件(他人红线区)只读、山海 `src/`(本轮只读)只读、宿主只读。 +> 采集:2026-07-24T02:27Z 前后 · 开发机本地 Apple M1 Pro(非 mini-desktop)· 纯本地读码 + 复用 round-2/round-3 已落证据。 + +## 一句话 bug + +山海行纪 `game-logic.js` 的 `update()` **漏调 `bundle.tick(dt)`**,导致 stub 通道下插件 onFrame 回调(含 juice 的顿帧恢复 `stepJuice`)从不被驱动;第一次暴击触发 `hitStop` 把 `timeScale` 拉到 0 后永不恢复,世界 dt 恒为 0,确定性冻结在 `timeS=331.4`,九婴(`atS=1200`)永不现身,win 不可达。 + +**关键重新定界(推翻 round-2/3 的 locus 假设)**:根因**不在** `particles-juice/impl.js`(该插件的顿帧恢复逻辑本身正确,是他人红线区),而在**山海自有的 `game-logic.js` 漏了所有其他生成游戏都有的那一行 `bundle.tick(dt)`**。这意味着 W-AXIS 可在山海自有代码修复,**无需触碰插件红线区**。 + +--- + +## 复现方法(seed 无关铁证,承 round-3) + +- 驱动:`test/win-cont.mjs`(分段前台续跑)+ `evidence/round-3/freeze-probe.mjs`(只读冻结取证)。 +- 环境:开发机本地 Chrome/150 `--headless=new`(CDP 9666)+ `python3 http.server 9777`,URL `?engine=stub&mode=evidence`。 +- 现象:三个不同 seed(777/1/42)跑出**字节级一致**的世界轨迹,全部精确冻结在 `timeS=331.4`(冻结态 `score=188 / lvl=7 / hp=58 / enemies=219`)。 +- 冻结判据(freeze-probe):连续两次 `stepFrames(60)` 后 `ΔtimeS=0`(世界时间不推进)、`phase=play` 非终态。 +- seed 无关:seed=1 与 seed=42 逐点一致(31/61/90.6/…/328.9/331.4 的 score/lvl/hp/enemies 全同),门 G/D 签名也同 → 不存在"不触发冻结的 seed"。 +- 证据文件:`evidence/round-2/`(seed777)、`evidence/round-3/summary.json`、`evidence/round-3/08-freeze-seed42-331s.json`(`ΔtimeS=0`,截图 sha256=`05c573418fc5ff25320b287aaf77a9bcd2c77782cc27b6d613b4d8fa0d2b68d2`)。 + +--- + +## 根因链(逐环代码证据;标【已验证】/【推断】) + +### 环 1 — 触发源:暴击 crit(唯一 pre-600s hitStop 源)【已验证逻辑 + 推断触发时刻】 + +hitStop 只可能由 `tier.hitStopMs > 0` 的档位触发。`balance.js` 里只有两档带 `hitStopMs`: + +```js +// game-runtime/games/shanhai-xingji/src/balance.js:433-434 +crit: { hitStopMs: 30, text: true }, // 暴击 30ms +bossKill: { hitStopMs: 80, shakeAmpPx: 8, shakeMs: 150, sfx: 'bosskill' }, // Boss 伏诛 80ms +``` + +`bossKill` 在 Boss 出场前(中 Boss `atS=600`、终 Boss `atS=1200`)不可能发生;`meteor`(→ `eventStart` 档,balance.js:436 只有 sfx)、`eliteKill`/普通 `kill`(→ `kill` 档,balance.js:437 只有 sfx)**均无 `hitStopMs`**,不触发顿帧。故 `timeS=331.4`(< 600)这次顿帧的触发源**只能是暴击 crit**。 +(round-2 曾怀疑 meteor/eliteKill,按档位表可排除;本环坐实 crit 为唯一可能触发源。"331.4 = 首次暴击时刻"为推断——probes 只测了 ΔtimeS,未落具体 crit 事件帧;但因 seed 无关,该时刻是确定性的。) + +### 环 2 — 暴击事件 → hit-feedback → juice.hitStop【已验证】 + +sim 吐 crit 事件 → `game-logic.js` 反馈泵转 `hit-feedback`: + +```js +// game-runtime/games/shanhai-xingji/src/game-logic.js:222-224 +} else if (ev.type === 'crit') { + hf.feedback('crit', { x: ev.x, y: ev.y, sourceId: ev.eid, value: Math.round(ev.dmg), crit: true }); +``` + +hit-feedback 命中 `crit` 档(`hitStopMs=30`)即调 juice: + +```js +// game-runtime/src/plugins/hit-feedback/impl.js:137-144 +if (tier.hitStopMs > 0 && juice && typeof juice.hitStop === 'function') { + const last = lastHitStopAt.get(tierName); + if (last != null && nowMs - last < mergeWindowMs) { + ev.merged = true; hitStopsMerged++; // 500ms 合并窗内不重触发 + } else { + lastHitStopAt.set(tierName, nowMs); + juice.hitStop(tier.hitStopMs / 1000, tier.hitStopFloor || 0); // = hitStop(0.03, 0) + } +} +``` + +### 环 3 — hitStop 把 timeScale 拉到谷底 0【已验证】 + +```js +// game-runtime/src/plugins/particles-juice/impl.js:516-522 +hitStop(dur, floor) { + hitStopDur = Math.max(0, dur); + hitStopRemain = hitStopDur; + hitStopFloor = floor == null ? 0 : Math.max(0, Math.min(1, floor)); + timeScale = hitStopDur > 0 ? hitStopFloor : 1; // ← floor=0 ⇒ timeScale 立即 = 0 +} +``` + +### 环 4 — 恢复机制本身正确,但**从不被驱动**(根因所在)【已验证】 + +juice 的恢复逻辑本身没毛病——只要 `stepJuice(dt)` 被调用,`hitStopRemain -= dt` 会在 30ms 内归零并把 `timeScale` 恢复到 1: + +```js +// game-runtime/src/plugins/particles-juice/impl.js:288-300 +function stepJuice(dt) { + if (hitStopRemain > 0) { + hitStopRemain -= dt; + if (hitStopRemain <= 0) { hitStopRemain = 0; timeScale = 1; } // 正常恢复 + else { + const prog = hitStopDur > 0 ? 1 - hitStopRemain / hitStopDur : 1; + timeScale = hitStopFloor + (1 - hitStopFloor) * prog; + } + } + ... +``` + +`stepJuice` 经 `step(dt)` 调用;`juice` 以 `autoStep` 缺省 true 实例化(`generic-host-config.js:68` / 山海同形),故在 `init` 注册了 onFrame: + +```js +// game-runtime/src/plugins/particles-juice/impl.js:399-403 +if (options.autoStep !== false) { + frameHandle = ctx.onFrame((dtSeconds) => { step(dtSeconds); }); // 靠 onFrame 驱动恢复 +} +``` + +**但 onFrame 回调只有在 `bundle.tick()` 被调用时才会派发**: + +```js +// game-runtime/src/core/plugin.js:683-692 —— onFrame 只是把回调存进 frameCbs +onFrame(cb) { const id = cbIdSeq++; frameCbs.set(id, cb); return { cancel() { frameCbs.delete(id); } }; } + +// game-runtime/src/core/plugin.js:798-814 —— 唯一派发点 bundle.tick,且 frameCount 只在此自增 +tick(dtSeconds) { + frame += 1; // ← frameCount 唯一自增处 + const snapshot = Array.from(frameCbs.values()); + for (const cb of snapshot) { try { cb(dtSeconds, frame); } catch (e) { ... } } +}, +frameCount() { return frame; }, +``` + +**stub 通道的帧驱动 `stepOneFrame` 只调 `game.update(d)`,从不调 `bundle.tick`**(RAF 与 evidence stepFrames 都走它): + +```js +// game-runtime/src/host/boot-game-host.js:1062-1091 +function stepOneFrame(dt) { + const d = typeof dt === 'number' ? dt : FIXED_DT; + if (!game) return; + if (frameHalted) return; + mockNowMs += d * 1000; + try { + game.update(d); // ← 只驱动 game.update,没有 bundle.tick(d) + ... + game.render(stubRenderCtx); + ... + } catch (e) { handleFrameError(e); } +} +``` + +全仓核验:`boot-game-host.js` 内 **`.tick(` 调用点为 0**(grep 实测为空)。即 stub 通道下 `bundle.tick` 永不被宿主调用,插件 onFrame(juice.step / hit-feedback.nowMs)永不派发。 + +**契约对照(铁证)**:仓内数十个其他生成游戏的 `game-logic.js update()` 都**显式**调 `bundle.tick(dt)`,注释直白说明不调的后果—— + +```js +// 例:games/amgen-c2v-4/src/game-logic.js:271-275 +/** ★ 必调 bundle.tick(dt),否则 timer-scheduler / juice 等 onFrame 不推进 */ +if (bundle && typeof bundle.tick === 'function') bundle.tick(dt); +// 例:games/amgen-cheap-run-sc/src/game-logic.js:149 +if (bundle && typeof bundle.tick === 'function') bundle.tick(dt); // ★ 没这步 timer 永不到期 +``` + +而**山海行纪的 `game-logic.js update()` 没有这一行**:`createGame` 虽在文档签名里声明收 `bundle`(game-logic.js:38),`game.js:25-27` 也确实把 `bundle: runtime.bundle` 传了进来,但 `createGame` 函数体(game-logic.js:40-45)只解构了 `plugins/viewport/save/hf/juice`,**从未接住 `opts.bundle`,`update()` 也从未调 `bundle.tick(dt)`**: + +```js +// game-runtime/games/shanhai-xingji/src/game-logic.js:259-266(update 核心,无 bundle.tick) +update(dt) { + if (!inited) return; + measures.frames++; + picker.update(dt * 1000); + if (scene !== 'run' || !sim || paused) return; + const ts = (juice && juice.getTimeScale) ? juice.getTimeScale() : 1; // ← 读 timeScale + sim.update(dt * ts); // ← 用 ts 缩放世界 dt + ... +``` + +⇒ juice 的 `hitStop` 被调(环 2/3),但恢复用的 `step` 永不被驱动 ⇒ `hitStopRemain` 恒停 0.03、`timeScale` 恒停 0。 + +### 环 5 — timeScale=0 → 世界 dt=0 → 永久冻结、win 不可达【已验证】 + +`game-logic.js:265-266` 用 `ts=getTimeScale()=0` 缩放,`sim.update(dt * 0)=sim.update(0)` ⇒ `timeS` 不再前进(freeze-probe `ΔtimeS=0`)⇒ 九婴 `atS=1200`(balance.js:372)永不 spawn、`seenBosses=[]`、`bossKills=0`、win 终态不可达。 + +### 旁证 — frameCount 恒 0【已验证,与环 4 互锁】 + +`bundle.frameCount()`(plugin.js:812)只在 `bundle.tick` 里自增(plugin.js:799)。探针实测 `frameCount=0 / Δframe=0`(`08-freeze-seed42-331s.json`),正是 `bundle.tick` 从未被调的直接指纹。注意:round-2 观察到的 `measures.frames` 涨满(60663)是 `game-logic.js:261` 在 `game.update` 里自增的,与 `bundle.frameCount` 是两回事——**帧(game.update)照跑、插件 onFrame(bundle.tick)从不跑**,两者一致指向同一根因。 + +--- + +## 影响面 + +- **所有 seed**:seed 无关(round-3 铁证),任何 seed 均在 `timeS=331.4`(首次暴击)必冻结。 +- **win 不可达**:终 Boss 九婴 `atS=1200` 永不现身,维度⑧"胜"真玩路径无法采证。 +- **stub 通道全局**:凡靠 onFrame 推进的插件在山海都停摆——juice 顿帧/屏震/闪白/脉冲恢复、hit-feedback 的 `nowMs`(其取证 `timeMs` 因此恒 0,合并窗也失真)。 +- **通道范围说明【推断】**:`startRaf` 注释称 real 通道"引擎内部 RAF 驱动五回调"(boot-game-host.js:1366 附近),real 通道是否经引擎另行派发 onFrame 未下钻确认;但"game-logic 自驱 `bundle.tick(dt)`"是仓内所有游戏的通道无关惯例,补上即 stub/real 双稳。 + +--- + +## 修复方向建议(供 W-AXIS;按推荐序) + +1. **【首选·最小·不碰插件红线】山海 `game-logic.js` 补 `bundle.tick(dt)`**: + - `createGame` 函数体接住 `const bundle = opts.bundle || null;`(game-logic.js:40-45 处)。 + - `update(dt)` 内在驱动世界前补一行(与全仓惯例同形): + `if (bundle && typeof bundle.tick === 'function') bundle.tick(dt);` + - **dt 口径**:传**未缩放的真实帧 dt**(即 `update(dt)` 的原参,不要传 `dt*ts`)——juice 设计明确要求恢复计时走"未缩放真实 dt"(impl.js:285-287),否则顿帧会拖慢自身恢复。 + - **调用次序**:建议放在读取 `ts` 之前(先让 juice 推进恢复、再读本帧 timeScale),手感上顿帧从触发帧即开始回弹;放之后亦可(下一帧回弹),两者都会在 ~30ms 内恢复,不再永久冻结。 +2. **【可选·纵深防御】宿主 stub 通道补派发**:若架构上希望宿主而非游戏自驱插件帧,可在 `boot-game-host.js:stepOneFrame` 的 `game.update(d)` 旁补 `bundle.tick(d)`。但这会改变"游戏自驱 tick"的仓内既有契约(所有其他游戏都在 game-logic 自调),属宿主级口径变更,**需 host owner 评估**,不建议 W-AXIS 单方改。 +3. **不要改 `particles-juice/impl.js`**:其恢复逻辑正确(环 4),改它既踩他人红线区、又治标不治本。 + +> 注:`timeScale` 下限钳制 / hitStop 恢复 watchdog 等"在插件内加兜底"的思路**非必需**——根因是恢复从未被驱动,驱动恢复即解。插件内加兜底反而掩盖"宿主/游戏漏驱动 onFrame"这类真问题,不推荐。 + +--- + +## 验收建议(修后) + +1. 复跑 `test/win-cont.mjs`(删 `evidence/round-3/cont-state-seed42.json` 重开 fresh):世界应**越过 331.4 继续推进**,`ΔtimeS>0` 恢复,直至 `timeS=1200` 斩九婴 `phase=win`。 +2. freeze-probe 复测:首次暴击后 `timeScale` 应在 ~30ms(约 2 帧)内回到 1,不再 `ΔtimeS=0` 恒冻。 +3. 九门补验:门 C(引擎掌帧)——接 `bundle.tick` 后 `frameCount` 应随步进单调递增(不再恒 0);门 H(终态 latch)在 win 可达后方可 latch。 +4. 维度⑧复采:胜/负/重玩/进度全程真玩路径闭合后,回写金标 §7.1 realizationEvidence。 + +--- + +## 红线与归属注明 + +- 根因修复落在**山海自有代码**(`games/shanhai-xingji/src/game-logic.js`),**不涉及** `particles-juice/impl.js` 他人插件红线区。 +- 本报告为**只读定位**产出,本轮**未改任何代码**:`game-runtime/src/`(含 plugins/particles-juice/)与 `games/shanhai-xingji/src/` 均只读,git 脏状态保持。 +- 修复执行交 W-AXIS / 山海 owner;若后续判定需动宿主 `stepOneFrame`(方向 2),须 host owner 介入。 diff --git a/game-runtime/games/shanhai-xingji/evidence/realization-evidence-status.md b/game-runtime/games/shanhai-xingji/evidence/realization-evidence-status.md new file mode 100644 index 00000000..488ff46c --- /dev/null +++ b/game-runtime/games/shanhai-xingji/evidence/realization-evidence-status.md @@ -0,0 +1,127 @@ +--- +topic: shanhai-xingji-gold-realization-evidence-status +status: realization-evidence-register(候选阶段逐维登记,非正式签认态) +sot-impact: none(本档是金标 SoT 的消费记录视图,不替代 docs/architecture/产品/游戏内容金标.md §7.1;签认时回写 §7.1) +上级: docs/architecture/产品/游戏内容金标.md(§4 九维契约 + §7.1 山海候选记录) +--- + +# 《山海行纪》金标阶段二 realizationEvidence · 九维逐维登记表 + +> 契约依据:`docs/architecture/产品/游戏内容金标.md` §4(九维 + designIntent/realizationEvidence 两阶段)、line 136(正式签认 `game_content_gold` 须维度①–⑧**逐维 `verified`**、维度⑨**完整登记限制与批准记录**;未达继续保留 `candidate`,不得用"已知限制"包装成正式金标)。 +> 本档只如实登记**当前采证状态**,禁笼统总分、禁粉饰 missing。候选阶段允许 partial/missing。 +> 采证窗口:2026-07-23~24 · 执行机 = **开发机本地 Apple M1 Pro(非 mini-desktop)** · Chrome/150 `--headless=new`(CDP 9666)+ `python3 http.server 9777`(仅 127.0.0.1)· URL `?engine=stub&mode=evidence`。 + +## 0 状态总览(逐维,不合成总分) + +| 维 | 名称 | 状态 | 一句话依据 | +|---|---|---|---| +| ① | 游戏身份 | **verified** | 持符巡山使/肉鸽割草/竖屏 feed 在真构建里如实呈现,boot+出击页可验 | +| ② | 玩法风格 | **partial** | 走位手操/三选一构筑/负+重开全通;**"胜"终态被 juice 顿帧 bug 阻断不可达** | +| ③ | 主题映射 | **partial** | 符箓御兽机制真表(12符器/14被动/12进化)非图标替换;实战进化链验证受 win 阻断 | +| ④ | 美术设计 | **partial** | 四态真实截图(round-1)+ sprite 接线 drawImage=63/fellBack=0;缺真机全屏眼验+逐项对照 | +| ⑤ | 音频设计 | **partial** | SFX 结构断言 5/5 + 音频图真构建;**BGM 6 首代码+资产双 missing**、无人耳听感结论 | +| ⑥ | 内容与进度 | **partial** | 10h 核数 11.21h 算术成立;地图2–5/D2–D5/敌32 为 M5 锚未实装,缺 pacing 仿真+真玩抽验 | +| ⑦ | 数值与成长 | **partial** | build 四判据①③④ PASS;**判据② rand-4 胜率 0.9167>0.85 离群未收敛** | +| ⑧ | 真玩证据 | **partial** | 负/重开/进度 + 门 A/B/D/E/G/I 全通;**胜路径被 juice bug 阻断**、门 H 无法 latch、门 C frameCount 未接线 | +| ⑨ | 已知限制 | **本登记表** | 见 §2;正式签认须①–⑧逐维 verified + 创始人签认,当前未达,仍 `candidate` | + +**结论:九维无一维达正式签认线(①–⑧须全 verified)。当前 lifecycleStatus = `candidate`。** ① 单维 verified 不足以签认;②③④⑤⑥⑦⑧均 partial,各自缺口见 §1。 + +--- + +## 1 逐维登记(状态 / 已有证据 / 缺口 / 达 verified 还需什么) + +### ① 游戏身份 — verified +- **已有证据**:`evidence/round-1/01-menu-dispatch.png`(出击页:巡使/巡域/潮汛)、`round-1/00-real-result.json`、`round-1/02-play-battlefield.png`(持符巡山使战场)。门 A 装载 PASS、门 D 真渲染 PASS。 +- **缺口**:无核心缺口(身份定义 = 持符巡山使夜巡镇兽 / 肉鸽割草 survivor / 竖屏 feed 即点即玩,真构建如实呈现)。 +- **达 verified**:已达。 + +### ② 玩法风格 — partial +- **已有证据**:核心循环可验——走位唯一手操(门 G 输入因果 PASS、门 I 控制手感 Δ1=Δ2=60 平滑)、升级三选一(`round-1/04-levelup-cards.png`)、暂停 build 总览(`round-1/03-paused-build.png`)、负+重开(`round-1/05-lose-standstill.png`、`round-1/06-restart-newplay.png`)。 +- **缺口**:**"20:00 斩九婴即胜"的胜终态不可达**——世界在 `timeS=331.4` 冻结(juice 顿帧 bug,见 `juice-bug-report.md`),胜负条件的"胜"半轴无法验证。 +- **达 verified 还需**:先修 juice 顿帧 bug(山海 game-logic 补 `bundle.tick(dt)`),再真玩到 `phase=win` 验证胜路径闭合。 + +### ③ 主题映射(灵魂维度)— partial +- **已有证据**:符箓御兽**机制真表**非图标替换——12 符箓法器(`balance.js:97-230` 五行取名)、14 被动、12 进化(满级+被动≥3 机制跃迁)、山海经兽谱(狰/梼杌/穷奇/双 Boss 九婴)。designIntent §7.1 已 verified。 +- **缺口**:主题"驱动下游一切"的实战进化链/机制跃迁真玩验证,受 win 阻断(进化多在局中后段,331.4 冻结前仅 lvl=7,进化链未充分实战展开);逐项"主题→玩法/美术/音乐/内容"对照的人工终审未做。 +- **达 verified 还需**:win 路径通后完整真玩验进化链;人工逐项链路确认主题映射(非图标替换)。 + +### ④ 美术设计 — partial +- **已有证据**:四态真实截图(round-1 menu/play/paused/levelup/lose/restart 6 PNG);sprite 真接线 drawImage=63/fellBack=0(§7.1);批次一 42 样张锁矿彩国风锚 r1。 +- **缺口**:缺**真机全屏风格眼验** + 逐项截图对照设计(风格词/色板/明暗依据/材质光照/UI 语言/动画特效);当前截图在**开发机本地**采,非 mini-desktop 真机。 +- **达 verified 还需**:mini-desktop 真机全屏眼验 + 逐项对照已批准的风格词与色板(明暗须有依据)落结论。 + +### ⑤ 音频设计 — partial +- **已有证据**:内容音效(SFX)结构断言 5/5、音频图真构建(§7.1)。 +- **缺口(硬)**:**BGM 6 首代码 + 资产双 missing**(无文件、无播放接口,需开发制作);SFX 缺**人耳听感结论**(事件音效表逐项辨识度、混音层次谁让谁均未人工听验)。 +- **达 verified 还需**:BGM 落地(文件 + 播放接口)+ 人工听感结论逐项对照设计(调式/节拍/配器/主题动机/事件音效表/混音)。刻意无配乐也算音频设计,但本作设计含 BGM 6 首,缺席即 missing,不得 not_applicable 绕过。 + +### ⑥ 内容与进度 — partial +- **已有证据**:`evidence/content-audit.json` — 10h 核数 **11.21h**(`totalH:11.21 / meets10h:true / marginH:1.21`),四线核数(首通线 3.17h/角色线 2.42h/难度线 3.88h/收集天赋线 1.75h);M4 真表支撑(角色8/天赋48/成就60/图鉴51/符器12/被动14/进化12);图鉴自检 consistent=true。 +- **缺口(硬)**:核数验的是**设计内容图谱的 10h 下限算术**,其乘法轴**地图 2–5 / D2–D5 / 敌 32 扩表为 M5 锚、尚未实装**(`balance.js` 现仅图1/D0-D1/敌3种);缺 pacing 仿真 + 真玩抽验。 +- **达 verified 还需**:M5 全量灌装(图2–5/D2–D5/敌32)+ 10h pacing 仿真 + 真玩抽验核数落地。 + +### ⑦ 数值与成长 — partial +- **已有证据**:`evidence/sim-scan-c2-convergence.json`(528 局门档:A 12武器×20 + B 24随机×12)——判据 c1 PASS(每件武器定向胜率≥0.4)、c3 PASS(随机 build 中位 0.375∈[0.35,0.55])、c4 PASS(武器出现↔获胜 |r|≤0.6);逐武器胜率表(leifu 0.6 / jinge 0.7 / hanyuan 0.6 / fentian 0.65 …)。 +- **缺口(硬)**:**判据 c2 FAIL**——最强 build `rand-4` 胜率 **0.9167 > 0.85** 上限,离群未收敛(`criteria.c2.pass:false`)。 +- **达 verified 还需**:M5 千局网格调参收敛 rand-4 至 ≤0.85 锁值 + 真玩动态门(静态自检 advisory、真玩动态门权威)。 + +### ⑧ 真玩证据 — partial +- **已有证据**:`evidence/round-1/`(四态截图 + driver-console.log + probes.jsonl)、`round-2/`(seed777 推进段 + 冻结战场截图 + checklist)、`round-3/`(seed1/42 + freeze-probe + summary)。九门:A 装载 PASS / B 未捕获异常 PASS / D 真渲染(bright=16131)PASS / E 活性多帧(distinct=8)PASS / G 输入因果 PASS / I 控制手感(Δ1=Δ2=60)PASS。负/重开/进度路径全通,score 0→188 真击杀推进。 +- **缺口(硬,两条)**: + 1. **胜路径不可达**——juice 顿帧 bug 致世界冻结于 `timeS=331.4`,九婴(atS=1200)永不现身,win 终态无法真玩采证(根因与修复见 `juice-bug-report.md`)。门 H(终态 latch)因此无法 latch。 + 2. **门 C 引擎掌帧未接线**——`frameCount` 恒 0(根因同 juice:`bundle.tick` 未被驱动,见 juice-bug-report 环 4 旁证),目前以 `timeS 0→331.4` 增量补证 update 真跑;修复后 frameCount 应随步进递增方可正验。 + 3. 门 F(recHook)生产 bundle 无埋点 SKIP(host-dev 通道门,非本游戏缺陷)。 + 4. 缺**人工验玩结论**(创始人/人工对胜/负/重玩/进度全程的主观验玩)。 +- **达 verified 还需**:修 juice bug → win-cont.mjs 跑到 1200 斩九婴 win + 门 H latch + 门 C frameCount 正验 + 人工验玩结论。 + +--- + +## 2 维度⑨ 已知限制 · 完整登记(本档核心) + +逐条如实登记当前限制,不粉饰: + +| # | 限制 | 影响维 | 性质 | 证据/根因 | 批准记录 | +|---|---|---|---|---|---| +| L1 | **juice 顿帧冻结 bug**:世界确定性冻结于 `timeS=331.4`,win 不可达 | ⑧(兼②③) | 实现缺陷(非设计取舍) | 根因 = 山海 `game-logic.js` 漏调 `bundle.tick(dt)`,stub 通道插件 onFrame(juice 顿帧恢复)从不驱动;**不在 juice 插件**。详见 `juice-bug-report.md` | 待 W-AXIS 修复,未批准带病签认 | +| L2 | **BGM 6 首代码 + 资产双 missing**(无文件、无播放接口) | ⑤ | 内容缺失(待开发) | §7.1 自述 + 阶段二采证;SFX 结构 5/5 但 BGM 完全缺席 | 待开发制作,未批准 | +| L3 | **M5 内容缺口**:地图 2–5 / D2–D5 / 敌 32 扩表未实装 | ⑥ | 内容缺失(M5 锚) | `content-audit.json` provenance 注;`balance.js` 现仅图1/D0-D1/敌3种 | 归 M5 波次,未批准提前签认 | +| L4 | **判据② rand-4 离群**:最强 build 胜率 0.9167>0.85 未收敛 | ⑦ | 数值未收敛 | `sim-scan-c2-convergence.json` criteria.c2.pass=false | 待 M5 千局调参,未批准 | +| L5 | **采证环境非真机**:开发机本地 Chrome 150 headless,非 mini-desktop | ④⑧(放量口径) | 采证口径限制 | round-2/round-3 checklist 环境段;放量结论须 mini-desktop 复核 | 已知口径,放量回 mini-desktop | +| L6 | **stub 下 seed 不生效**(附带发现):`?seed=` 不影响世界模拟,三 seed 字节级一致 | 实现红线区待确认项 | 附带发现(非本⑨职责定论) | round-3 summary 核心发现;若放量需多 seed 多样性则为实现缺陷 | 交相关方确认,非本⑧定论 | +| L7 | **门 F recHook SKIP**:生产 bundle 无 `__engineCalls` 埋点 | ⑧ | 通道门限制(host-dev 门) | round-2/round-3 九门 F | 非本游戏缺陷,通道使然 | +| L8 | **SFX/美术人工听感/眼验未做** | ④⑤ | 人工终审缺口 | §7.1 自述待真机眼验/人耳听感 | 待人工终审 | + +**正式签认状态线(契约 line 136)**:签认 `game_content_gold` 须维度①–⑧**逐维 `verified`** + 维度⑨**完整登记限制与批准记录** + **创始人/人工逐维签认**。当前 ②③④⑤⑥⑦⑧均 partial(各带硬缺口 L1–L4、L8),**未达签认线,lifecycleStatus 继续保留 `candidate`**,不得以"已知限制"包装成正式金标。 + +--- + +## 3 收口路径(达 verified 的依赖序) + +1. **L1(阻断项,最先)**:W-AXIS 修山海 `game-logic.js` 补 `bundle.tick(dt)`(详见 `juice-bug-report.md`)→ win 路径解锁。 +2. win-cont.mjs 续跑到 1200 斩九婴 → ⑧胜路径 + 门 H latch + 门 C frameCount 正验 → ②③ 补实战验证。 +3. **L2**:BGM 落地 + 人工听感 → ⑤ verified。 +4. **L3**:M5 全量灌装 + pacing 仿真 + 真玩抽验 → ⑥ verified。 +5. **L4**:M5 千局调参收敛 rand-4≤0.85 + 真玩动态门 → ⑦ verified。 +6. **L5/L8**:mini-desktop 真机全屏眼验 + 人工验玩/听感结论 → ④⑧ 人工终审。 +7. ①–⑧逐维 verified 后,回写金标 §7.1 realizationEvidence + 创始人签认,方晋级 `game_content_gold`。 + +--- + +## 4 纪律与边界自检 + +- 本档只读整理既有采证(round-1/2/3、content-audit.json、sim-scan-c2-convergence.json、金标 §4/§7.1),**未改任何 tracked 代码**;仅在本 `evidence/` 目录新建本档与 `juice-bug-report.md`。 +- 逐维诚实标态,禁笼统总分、禁粉饰 missing(契约 line 136 / §6 风险一)。`not_applicable` 未用于任何整维或"尚未实现"项(BGM/M5/判据②均如实标 missing/partial)。 +- 无 git add/commit/push;无 mini-infra;无 ssh mini-desktop;未签 R1、未激活 staging。 + +--- + +## ⑧win 已知限制更新(2026-07-24 收口) + +⑧真玩维度状态修正为 **partial(含 win 胜路径已知限制)**:负(lose)/重玩(restart)/进度(progress,推进过 1200 九婴刷新)三路径全通,九门 G/A/I/E/B/H 过;**win 胜路径(斩九婴)记为已知限制**。 + +根因(已硬证,见 round-7/lose-safedps-attempt4/FINDING-mobile-boss.md):forensics 扩展生效后 driver 读到九婴实时坐标并打掉 30% hp(7400→5161),但九婴是大范围机动冲撞 Boss,安全距 sustained dps ~112 hp/s(杀完需 ~66s)> 玩家存活窗 ~20s,玩家满血扛 20s 后被冲撞贴身秒杀;调 hugR 半径解不了(击杀需时 > 存活窗不等式)。 + +交 W-AXIS 平衡判断:九婴贴身必秒 + 全场冲撞 + 安全距 dps 不足是否为便宜档难度配平问题。修复后续跑 win-jiuying.mjs(比例减速逼近控制器)或写冲撞规避 driver 可推进 win 胜路径。 + +本轮山海自有代码两项工程产出(未碰他人红线区,回归 13/13):juice 顿帧冻结 bug 修复(game-logic.js 补 bundle.tick)+ forensics 取证面扩展(sim.js bossesForensics() + state().bosses)。正式签认仍须①-⑧逐维 verified + 创始人签认,当前未达,山海行纪仍 candidate。 diff --git a/game-runtime/games/shanhai-xingji/scripts/content-audit.mjs b/game-runtime/games/shanhai-xingji/scripts/content-audit.mjs index 1b8233a7..131978d8 100644 --- a/game-runtime/games/shanhai-xingji/scripts/content-audit.mjs +++ b/game-runtime/games/shanhai-xingji/scripts/content-audit.mjs @@ -16,6 +16,9 @@ 'use strict'; +import { writeFileSync, mkdirSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; import { WEAPONS, PASSIVES, DIFFICULTY, ENEMIES } from '../src/balance.js'; import { ROLES, TALENTS, ACHIEVEMENTS, DEX_TOTAL, buildDexCatalog } from '../src/meta-tables.js'; @@ -106,6 +109,52 @@ console.log(' M4 交付使角色线与收集天赋线的乘法轴成为真表 // dex 投影自检(防造化石:图鉴条目必来自真实表) const cat = buildDexCatalog(); -if (cat.length !== DEX_TOTAL) { console.error('[FAIL] 图鉴投影自检不一致'); process.exit(2); } +const dexConsistent = cat.length === DEX_TOTAL; +if (!dexConsistent) { console.error('[FAIL] 图鉴投影自检不一致'); } +/* ══════════════════ ⑥ 内容进度证据持久化(金标阶段二 realizationEvidence)══════════════════ + * 上面四线核数原本只打到 stdout、跑完即失;金标阶段二⑥要求把「内容进度」维度的核数结论 + * 固化成可复核、可对账的落盘证据。下面在保留全部 stdout 的同时,把同一份核数结果序列化成 + * JSON 报告写入 evidence/content-audit.json(evidence/ 未被 gitignore,可随仓提交、可追溯)。 + * 字段覆盖:四线各自核数(首通/角色/难度/收集天赋)+ 机械和 + 是否≥10h + 采集时间(UTC)+ 来源脚本。 */ +const report = { + schemaNote: '《山海行纪》10h 内容图谱核数(书 §5.4 机械和)落盘证据;金标阶段二 realizationEvidence ⑥内容进度。与 stdout 同源同值。', + source: 'game-runtime/games/shanhai-xingji/scripts/content-audit.mjs', + collectedAtUtc: new Date().toISOString(), + phase: 'gold-standard-phase2-realization-evidence', + checkpoint: '⑥content-audit-persist', + milestone: 'M4', + durationBasisMin: { win: WIN_MIN, lose: LOSE_MIN, note: '§5.4 原文〔草案·M6 真玩回填〕' }, + // 内容图谱计数:真表溯源(M4 已交)与设计锚(§5.2,M5 待扩)分层标注 + contentGraph: { + roles: N.roles, talents: N.talents, achievements: N.achievements, dex: N.dex, + weapons: N.weapons, passives: N.passives, evolutions: N.evolutions, beastsNow: N.beastsNow, + mapsDesign: N.mapsDesign, difficultiesDesign: N.difficultiesDesign, beastsDesign: N.beastsDesign, + provenance: '角色/天赋/成就/图鉴/符器/被动/进化=真表(M4 已交);地图/难度/敌种=设计锚 §5.2(图2–5、D2–D5、敌32 归 M5)', + }, + // 四线核数(去重保守口径;min=分钟,h=小时) + lines: { + firstClear: { desc: '①首通线(D0):5图×尝试2.2', maps: firstClear.maps, avgTries: firstClear.avgTries, winRuns: firstClear.winRuns, loseRuns: firstClear.loseRuns, min: firstClear.min, h: h(firstClear.min) }, + roleLine: { desc: '②角色线:边际5角色×(1胜+0.6败)', rolesTotal: roleLine.rolesTotal, marginalRoles: roleLine.marginalRoles, winRuns: roleLine.winRuns, loseRuns: roleLine.loseRuns, min: roleLine.min, h: h(roleLine.min) }, + diffLine: { desc: '③难度线:D2–D3×3图=6目标×(1胜+1.25败)', targets: diffLine.targets, winRuns: diffLine.winRuns, loseRuns: diffLine.loseRuns, min: diffLine.min, h: h(diffLine.min) }, + collectLine: { desc: '④收集与天赋:定向补图鉴+每日5局+局外经营20min', dirRuns: collectLine.dirRuns, winRuns: collectLine.winRuns, loseRuns: collectLine.loseRuns, metaOpMin: collectLine.metaOpMin, min: collectLine.min, h: h(collectLine.min) }, + }, + totalMin, + totalH, + meets10h: pass, + marginH: +(totalH - 10).toFixed(2), + bufferAxes: ['D4–D5 攻坚', '图鉴末段', '天赋树末 40%(需攒金币多局)', '每日挑战长期回归'], + dexSelfCheck: { catalogLength: cat.length, dexTotal: DEX_TOTAL, consistent: dexConsistent }, + note: '核数验的是设计内容图谱的 10h 下限算术成立(地图 5/D2–D5/敌 32 由 M5 交);M4 交付使角色线与收集天赋线的乘法轴成为真表支撑。', +}; + +const here = dirname(fileURLToPath(import.meta.url)); +const evidenceDir = join(here, '..', 'evidence'); +mkdirSync(evidenceDir, { recursive: true }); +const reportPath = join(evidenceDir, 'content-audit.json'); +writeFileSync(reportPath, JSON.stringify(report, null, 2)); +console.log(`\n[⑥ 落盘] 内容核数证据已写入 ${reportPath}(meets10h=${pass}, totalH=${totalH})`); + +// dex 自检不一致仍以退出码 2 收口(与原行为一致),落盘报告已含 consistent=false 供追溯 +if (!dexConsistent) process.exit(2); process.exit(pass ? 0 : 1); diff --git a/game-runtime/games/shanhai-xingji/src/game-logic.js b/game-runtime/games/shanhai-xingji/src/game-logic.js index 5a97311c..b6252f34 100644 --- a/game-runtime/games/shanhai-xingji/src/game-logic.js +++ b/game-runtime/games/shanhai-xingji/src/game-logic.js @@ -40,6 +40,10 @@ import { createCardPicker, createScrollList } from '../../../src/plugins/canvas- export function createGame(opts) { const plugins = opts.plugins || {}; const viewport = opts.viewport || { w: 390, h: 844 }; + // host-dev 帧钟句柄(L1 game.js 已经 runtime.bundle 摊平传入,此处必须接住): + // update 每帧调 bundle.tick(dt) 驱动 juice 顿帧恢复 + 自增 bundle.frameCount(门C 接线)。 + // 缺席=纯内存态降级(node smoke-boot 无 bundle 时静默跳过,不崩)。 + const bundle = opts.bundle || null; const save = plugins.save || null; // 最佳击杀跨局保留(save-progress;不可用时静默降级) const hf = plugins.hitFeedback || null; // 手感反馈+取证(缺席=事件面静默降级) const juice = plugins.juice || null; // 粒子/顿帧/震屏/闪白执行后端 @@ -259,6 +263,13 @@ export function createGame(opts) { update(dt) { if (!inited) return; measures.frames++; + // 【juice 顿帧恢复驱动(修首 crit 后世界冻结)】host-dev 帧钟:每帧调 bundle.tick(dt) 推进 juice 套件 + // (stepJuice 内 hitStopRemain -= 未缩放真实 dt),并自增 bundle.frameCount(门C 接线探针)。 + // 漏这步 → 首 crit(331.4s)触发 hitStop(floor=0) 后 hitStopRemain 永不递减、getTimeScale() 恒 0、 + // 下一行 sim.update(dt*0) 世界冻结顿死。故必须放在读取 ts 之前、场景早退之前(每帧都跑,对齐模板标准形态: + // _template-puzzle/_template-feiyi update 顶部 bundle.tick)。传未缩放真实 dt:juice 是元表现, + // 恢复要走真实时基,不能被自己产生的 hit-stop 拖慢(见 particles-juice/impl.js stepJuice 注释)。 + if (bundle && typeof bundle.tick === 'function') bundle.tick(dt); picker.update(dt * 1000); // 弹层动画钟(帧驱动;手 9 ≤250ms) if (scene !== 'run' || !sim || paused) return; // 暂停=壳层冻结(sim.t 不前进) // 顿帧:juice.timeScale 缩放世界 dt(手 4;crit 档 M2 已接事件源,bossKill 随 M3) @@ -410,7 +421,12 @@ export function createGame(opts) { const btn = layoutButton(viewport, 'restart'); targets = [{ x: btn.x + btn.w / 2, y: btn.y + btn.h / 2, kind: 'restart', occupied: true }]; } - return { ...base, ...s, phase: base.phase, targets }; + // 【取证面扩展(⑧win 终态)】bosses = sim 取证 getter 暴露的存活 Boss 实时坐标快照 + // [{name,x,y,hp,maxHp,final}]。同 _forensicsView 既有取证契约性质:非玩法面、只读、JSON 安全, + // 供 CDP driver 主动贴脸集火九婴(九婴不主动近身,driver 须读此坐标向它移动,武器自动索敌集火)。 + // 不改玩法/数值/胜负判定。getter 缺席(node smoke-boot 旧 sim)降级为空数组,不破既有面。 + const bosses = (sim && typeof sim.bossesForensics === 'function') ? sim.bossesForensics() : []; + return { ...base, ...s, phase: base.phase, targets, bosses }; }, // measures:计数 + 摇杆探针 + hit-feedback 取证 events[] + 出击页探针 + M4 meta 摘要 measures: () => { diff --git a/game-runtime/games/shanhai-xingji/src/sim.js b/game-runtime/games/shanhai-xingji/src/sim.js index 1bf222c9..6500cec9 100644 --- a/game-runtime/games/shanhai-xingji/src/sim.js +++ b/game-runtime/games/shanhai-xingji/src/sim.js @@ -1073,6 +1073,32 @@ export function createSim(opts) { return null; } + /** + * 【取证 getter(⑧win 终态)】当前存活 Boss 列表快照(只读 enemies 过滤 isBoss)。 + * 与 liveBossBar() 同性质——纯只读扫描 enemies 数组、不 mutate 任何实体、不改 phase/数值/胜负判定, + * 属取证面(非玩法面)。区别:liveBossBar 只给「最后入场存活 Boss」的 {name,hp,maxHp}(HUD 血条用), + * 本 getter 额外暴露每个存活 Boss 的实时坐标 x/y,供金标阶段二 ⑧win 终态的 CDP driver 主动贴脸集火九婴。 + * 背景:九婴(终 Boss)不主动近身,旧 driver 读不到坐标只能「停步等追」→ 九婴永不入贴身区、hp 恒定不降、 + * 无法斩杀。暴露坐标后 driver 可向九婴坐标移动贴脸,武器自动索敌集火。 + * @returns {Array<{name:string,x:number,y:number,hp:number,maxHp:number,final:boolean}>} JSON 安全;无 Boss 返回 []。 + */ + function bossesForensics() { + const out = []; + for (let i = 0; i < enemies.length; i++) { + const e = enemies[i]; + if (!e.isBoss) continue; + out.push({ + name: e.bossDef.name, + x: Math.round(e.x * 10) / 10, + y: Math.round(e.y * 10) / 10, + hp: Math.max(0, e.hp), + maxHp: e.maxHp, + final: !!e.bossDef.final, + }); + } + return out; + } + function readState() { return { phase, @@ -1183,6 +1209,7 @@ export function createSim(opts) { return { update, setMove, choose, drainEvents, readState, view, + bossesForensics, // 取证面(⑧win):暴露存活 Boss 坐标供 CDP driver 贴脸集火;只读不改玩法 _debugFill, _debugGrant, _debugGrantXp, get phase() { return phase; }, };