diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index fe97d2017..2c5ad794f 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -1,5 +1,18 @@ # 决策记录 +## 2026-10-07 Fork 建项收口:成品包铺进预览根、登记可运行原型与初始工程版本 + +- 背景(真 bug,用户实测):用户在 AGC 里 Fork 了一个只发布过成品包的作品(`source: package`),项目建出来之后**运行页签不可用**——只显示「首个可运行原型尚未完成,运行视图暂不可用」,点击无反应,发布也被同一判据拦下。实物证据:`%APPDATA%/world.genarrative.ai-game-creator/projects/gameagent-a74aa1d9`(2026-10-06 15:27:58 建项)的 manifest **16 个 seed 任务全 `pending`(含 `code-prototype`)、没有 `versions[]`、没有 `preview`**,小到一个 296 B 的默认占位 `game/index.html`;父作品那套真实可玩产物(`index.html` + `assets/index-*.js` + 素材)被解到 `reference//` —— 该目录**不在预览根**,`preview::project_game_root()` 与服务预览的 `resolve_preview_path()` 都走不到,AGC 界面也没有任何入口能播它。 +- 根因:Fork 建项(`game_fork.rs` 的 `create_project_from_platform_fork_at`)只做「解压 → `init_local_game_project_at`(脚手架 + 全新 manifest)→ 写 `.agent/fork-source.json`」,**没有**像 AI 直连回合收口那样登记「本项目已有可运行原型」与工程内部版本;而运行视图与发布导出读的恰恰就是 `manifest.tasks[code-prototype] == completed`(或存在运行中的预览)这一条事实(前端 `view/project-development/index.tsx` 的 `runAvailable`、后端 `project/export.rs` 的 `project_has_runnable_prototype`)。用户自己那个正常项目 `gameagent-c3af9c7e` 恰是 `code-prototype: completed` + `versions: [initial-10]` 这一对——两者对比即根因。 +- 决策(D1 口径,产品 2026-10-07 拍板):**成品包 fork 之后允许零改动直接发布**,语义就是「fork 完即可用」——把父作品成品铺进**预览根**并在**同一次收口**里登记可运行原型,用户点运行/发布应立刻可用。同步更正 §3.5.1 / §2.5 里「成品包只能试玩、不能发布」的旧结论(那是按 M2a 落点假设写的,已过期):真正的能力边界只剩「取到的成品里没有源码,核心逻辑要在自建工程里重做」。 +- 决策(落点):成品包整包解到 **`fork_playable_root` = `preview::project_game_root(root)`(本形态即 `game/dist`)**,**不再写 `reference//` 副本**。理由:运行视图只服务预览根,铺在别处的副本用户既看不到也播不了;两份副本会让「哪份是事实源」含糊并白占磁盘;来源与形态事实由 `.agent/fork-source.json`(v2)承担。安全性:平台发行包契约保证入口固定在包根 `index.html`(服务端 `validate_release_zip` + api-server 的 `package_entry_path` + AGC 发布侧归一化三处一致),解到预览根后就是 `<预览根>/index.html`;`game/dist` 同时被工程源包排除清单覆盖(`project_bundle.rs`),**不会**把父作品产物当成作者源码重新上传。 +- 决策(收口,照既有模式、不另发明):`game_fork.rs` 新增 `register_forked_project_state_at(root)`,在 fork 已持有的项目写锁内按既有顺序写入——`update_manifest_task_status_at("code-prototype", Completed)`(**仅当预览根确有 `index.html`**)→ `advance_agent_runtime_project_revision_locked` → `append_agent_game_iteration_version_at`(首条即 `initial-1`)→ `emit_game_creator_manifest_invalidated(root, "game-fork.project")`,最后重读 manifest 返回(避免把过期快照交给进项目通道)。与 `agent/direct_runtime/mod.rs:4181-4194` 同一套模式;不使用无守卫的 `set_task_status`(`pitfalls.md` 记过它有两条竞争写入路径)。 +- 决策(D2 工程源包形态,产品 2026-10-07 拍板):**不在 fork 时自动构建**——与既有「不自动安装或覆盖导入/用户修改过的工程」策略一致(源码包本身也不含 `game/dist`)。因此该形态**如实不标记完成**:只写 `versions == [initial-1]`,`code-prototype` 保持 `pending`;**文案诚实化**:运行页签不可用时按清单事实分叉(`src/view/project-development/runUnavailableHint.ts`)——已有工程内部版本时说「已有工程内容但还没有可运行的构建产物:让智能体完成可运行原型后即可运行」,全新项目保留原句「首个可运行原型尚未完成,运行视图暂不可用」。 +- 决策(D3 既有数据,产品 2026-10-07 拍板):**不做一次性迁移**(不写补丁代码)。修复只对新 Fork 生效;既有 fork 项目(含上面那个实物)请**重新 Fork 一次**。 +- 不做:不新增 manifest 字段(manifest 是 `deny_unknown_fields` 的 v1 契约;改编来源仍只写 `.agent/fork-source.json`);不引入「本项目自己的构建已发生」这条新判据(D1 允许零改动发布,故不需要把运行门禁与发布门禁拆成两套事实);不改平台契约、`server-rs/**`、`src/**`、`scripts/**`。 +- 影响面(AGC 客户端 + 文档):`apps/ai-game-creator-shell/src-tauri/src/game_fork.rs`(落点、收口、来源校验、用例)、`src-tauri/src/project/export.rs`(`project_has_runnable_prototype` 提升为 `pub(crate)` 供断言)、`src/view/project-development/runUnavailableHint.ts`(新)+ `index.tsx`(一行接线)、`apps/ai-game-creator-shell/tests/runUnavailableHint.test.ts`(新)、`tests/appSurface/project-development.suite.ts`(两条 fork 形态投影用例)、`docs/【技术方案】游戏共创与作品Fork-2026-10-03.md`(§2.5 / §3.5 头注 / §3.5.1 / §3.5.2 下载与建项 / §3.5.4 / §5 验收矩阵 / §5.8 / §6 M2a 行)。 +- 验证方式:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- game_fork`(成品包形态断言 `game/dist/index.html` == 包内入口、`code-prototype == Completed`、`versions == [initial-1]`、`project_has_runnable_prototype == true`、无 `reference/`;工程源包形态断言 `code-prototype == Pending`、`versions == [initial-1]`、无 `game/dist`)、`cargo check --tests`、`cargo fmt --check`、AGC 两个 tsconfig `typecheck`、`npx vitest run apps/ai-game-creator-shell/tests`、全仓 eslint、`npm run check:encoding`、`git diff --check`。真机留档(截图/录屏)待客户端重建后执行:Fork 一个无工程源包的已公开作品 → 运行看到父作品画面 → 改源码 → build → 发布 → 详情页出现溯源。 + ## 2026-10-06 衍生作品的共创授权档位是终态:禁止 PUT 修改(新码 `FORK_AUTHORIZATION_INHERITED` → 409) - 背景(真漏洞):上一轮「创建时继承父作品档位」只堵住了创建,`PUT /api/game-distribution/games/{gameId}/fork-authorization` 仍是「任意作品、只限方向不限对象」。继承是「创建时快照等值」,于是一个继承到 `nonCommercial` 的衍生作品可以被作者再提成 `full`——得到一个**比祖先更宽**的子作品,把父作品在非商用授权下公开的工程内容变成可用商用。 diff --git a/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md b/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md index 9aa624bcd..a39b69314 100644 --- a/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md +++ b/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md @@ -155,11 +155,11 @@ stateDiagram-v2 | 情形 | 用户拿到什么 | 能不能再发布 | 用户可见文案 | | --- | --- | --- | --- | | 父作品有**工程源包**(AGC 发布且作者选择公开源码) | **源码级复刻**:AGC 下载工程 → 解压为新项目 → 可改源码、可重跑构建、可发布 | 能(前提是源包本身是 Phaser 4 + Vite 工程) | 「改造这个作品」 | -| 父作品只有**成品包**(网页端发布,或作者未上传工程源包) | **可玩参考 + 素材来源**:AGC 把该发行版本的成品包铺成本地副本,可对照试玩、可提取包内素材(受发行网关扩展名白名单限制,见 §3.5.1) | **不能**:成品包里既没有 `package.json` 也没有源码(`export.rs:770-804`),而 AGC 的发布入口对「已有可玩入口」的项目**同样**强制 Phaser 4 + Vite 声明(`export.rs:262-265` → `export.rs:405-470`) | 「试玩并参考改编」,面板必须写明「本作品只提供已构建成品,**无法直接重新发布**;要发布请在自建工程里重做」 | +| 父作品只有**成品包**(网页端发布,或作者未上传工程源包) | **可玩参考 + 素材来源 + 合规工程骨架**:AGC 先把该发行版本的成品包铺进项目**预览根**(`game/dist`),fork 完即可运行,并可提取包内素材(受发行网关扩展名白名单限制,见 §3.5.1) | **能**(2026-10-07 起):AGC 建项同时生成 Phaser 4 + Vite 合规脚手架并登记「可运行原型」,因此发布入口的「已有可玩入口」分支成立;发布内容是当前预览根——**作者没改就是取到的那份成品产物**(平台仍要求衍生作品填「本次核心改动说明」)。**不能**的是「在取到的成品上重建核心逻辑」:包里没有源码(`export.rs:770-804`) | 「试玩并参考改编」;面板如实写明「本作品只提供已构建成品,**没有源码**:可直接运行与发布,但核心逻辑要在自建工程里重做」 | 两条路径在**用户自建合规工程**里发布时写入**完全相同**的血缘与溯源;不设「作者必须开源才能被改造」的前置条件。 -**被否证的旧表述(保留以便对照)**:原方案写的是「成品包路径 = 产物级改造,项目无需构建步骤即可试玩与发布」、「这条形态 AGC 本来就支持」。第一句只对了一半——`validate_project_game_entry`(`verification.rs:49-66`)确实只要求入口是 HTML 文档,所以**试玩**成立;**发布**不成立,因为导出入口的静态入口分支并不豁免工程栈检查。第二句是错的:`export.rs:259-266` 的分支只说明「已有入口时不要求 build 脚本」,不说明不要求依赖声明。详见 §3.5.1 与 §3.5.4。 +**被否证与被更正的旧表述(保留以便对照)**:原方案写的是「成品包路径 = 产物级改造,项目无需构建步骤即可试玩与发布」、「这条形态 AGC 本来就支持」。2026-10-04 的只读侦察把「发布」判成不成立——依据是当时假设的落点(成品铺在项目里、项目没有 `package.json`,过不了 `ensure_publish_project_stack`)。2026-10-07 落地的 M2b 建项改成「**先建合规脚手架、再把成品铺进预览根**」,同时把「可运行原型」登记进 manifest,于是「试玩」与「发布」**都**成立(事实依据见 §3.5.1「AGC 建项」小节与 §5.8 证据)。真正的能力边界只剩一条:成品包里没有源码,核心逻辑要在自建工程里重做。 ### 2.6 关键流程 @@ -381,18 +381,21 @@ pub(crate) project_bundle_sha256: Option, ### 3.5 两条改造路径:成品包(先行)与工程源包(补强) -> **术语更正(2026-10-04)**:本节原来的标题把成品包路径称为「改造」,正文还写了「项目无需构建步骤即可试玩与发布」。按只读侦察的事实,成品包路径的实际能力是**本地试玩 + 素材来源**,**不含重新发布**;下面的 3.5.1 已按事实重写。路线取舍见 §3.5.4。 +> **术语更正(2026-10-04,2026-10-07 再更正一次)**:本节原来的标题把成品包路径称为「改造」,正文还写了「项目无需构建步骤即可试玩与发布」。2026-10-04 的只读侦察按**当时的落点假设**(成品铺进项目里、项目没有 `package.json`)把能力判成「本地试玩 + 素材来源,不含重新发布」;2026-10-07 落地的建项形态是「**先建 Phaser 4 + Vite 合规脚手架,再把成品铺进预览根**,并登记可运行原型」,于是**试玩与发布都成立**,只有「在成品上重建核心逻辑」不成立(包里没有源码)。本节下面按实现事实重写。路线取舍见 §3.5.4。 -#### 3.5.1 成品包路径(零新增上传资产,但只到「可玩参考」) +#### 3.5.1 成品包路径(零新增上传资产:可玩参考 + 素材来源) - **内容来源**:父作品当前公开版本的 `package_object_key`(已存在,无需作者做任何额外动作)。服务端按现有发行网关同一套对象读取与校验复用该 ZIP,不新造存储。包内容是纯运行产物:`collect_project_export_package_files`(`src-tauri/src/project/export.rs:770-804`)只收 `dist` 树(条目统一改名成 `game/...`)+ 根 `assets/` 兜底 + `exports/README.md`,**根 `package.json` 与源码不进 ZIP**。 - **下载通道(M2a 已实现)**:`GET /games/{gameId}/fork-source`(元数据)+ `GET /games/{gameId}/fork-source/package`(ZIP 字节)两条受鉴权路由,**要求 Bearer、不叠加发布灰度**(灰度只针对发布),校验顺序与错误码见 §3.4「登录用户」小节。服务的是**当前已公开版本**的发行包(与公开发行网关同一份对象),元数据里的 `sha256` / `bytes` 直接取该版本行,`downloadPath` 是同源相对路径、不下发对象键;本体复用发行网关的读包路径(整包进内存 + 进程内缓存,上限 4 条 / 256 MiB,`:926-958`、`:101-104`),但**不做**引用归一化与 bootstrap 注入——取件下发的是原始构建产物,客户端按摘要校验后离线解压,任何改写都会让摘要对不上。客户端侧仍是整包读取(`fetch_limited_bytes`,上限 512 MiB、无 Range 无续传;分片能力只有上传侧),大包的续传/分片下载是后续议题。**未实现**的是按版本直取任意历史版本:取件只服务当前公开版本,与发行网关同口径。 -- **AGC 建项:能试玩,不能发布。** - - 试玩成立:`validate_project_game_entry`(`src-tauri/src/project/verification.rs:49-66`)只要求入口是 HTML 文档、引用可解析(`:68-100`),与 `package.json`、构建脚本无关;把包内文件铺进可玩入口目录即可试玩。 - - 发布必然被拦:导出/发布的唯一入口 `export_local_project_package`(`src-tauri/src/commands/desktop.rs:1148-1156`)→ `export_local_project_package_for_publish_at`(`export.rs:259-306`),其中「已有可玩入口」的静态入口分支(`export.rs:262-265`)**仍然无条件调用 `ensure_publish_project_stack(root)`**(实检在 `export.rs:413-459`,调用点在 `export.rs:463-468`):`package.json` 必须声明 phaser 主版本 4(`:440-453`)且声明 vite 依赖或存在 `vite.config.*`(`:398-410`、`:454-458`)。注释直接写明「无 `package.json` 的单文件 HTML 项目不属于首版 Phaser 4 发布合同」(`:407-408`),并有反例测试(`:1239-1243`)。该分支**不要求 build 脚本**——`resolve_publish_build_plan` 只在「无入口」分支调用(`export.rs:266-273`)——所以「能试玩」确实不需要构建,但「能发布」需要依赖声明。 - - 补成合规工程也走不通:成品包里没有 `package.json` 与源码,平台又不提供包下载通道,因此「改完再发布」这一步在现役能力下断裂。 -- **能力边界**:可对照试玩、可提取包内素材(受上面扩展名白名单限制)、可阅读已压缩的运行产物;**不可**重建核心逻辑、**不可**直接发布。面板必须如实说明这两点。 -- **价值**:零新增上传资产即可跑通「授权 → 试玩 → 素材复用 → (在自建工程里)发布 → 血缘与溯源」,是工程源包缺失时的参考路径;它不再是「完整闭环」的兑现方式。 +- **AGC 建项:fork 完即可运行,也能发布(2026-10-07 起)。** + - **落点 = 预览根**:先 `init_local_game_project_at` 生成合规脚手架(Phaser 4 + Vite,`project/manifest.rs` 的 `DEFAULT_GAME_PACKAGE_JSON` / `DEFAULT_GAME_VITE_CONFIG`),再把发行包整包解到**预览根**(`game_fork.rs` 的 `fork_playable_root`,即 `preview::project_game_root()` 给出的 `game/dist`)。运行视图只服务预览根,铺在别处的副本用户既看不到也播不了——这正是「fork 之后没法直接运行」的成因。 + - **平台发行包契约保证入口在包根**:服务端 `validate_release_zip` 要求 ZIP 根有 `index.html`(`module-game-distribution/src/package.rs:98,146`),api-server 强制 `package_entry_path == "index.html"`(`api-server/src/modules/game_distribution.rs:5434`),AGC 侧的归一化同样在新 ZIP 里剥掉 `game/` 前缀并强制根 `index.html`(`project/export.rs:489-590`)。因此解到预览根后就是 `<预览根>/index.html`,`validate_project_game_entry`(`verification.rs:49-52`)与 `start_local_game_preview_for_project`(`preview.rs:403-420`)都能通过。 + - **建项收口登记「可运行原型」**:`game_fork.rs` 的 `register_forked_project_state_at` 在**同一次收口**里完成「任务状态 → 推进 project revision → 追加 `initial-` 工程内部版本 → 宣告清单失效」(与 AI 直连回合收口 `agent/direct_runtime/mod.rs:4181-4194` 同一套模式)。成品包形态因为预览根确实有 `index.html`,会把 `code-prototype` 置为已完成——运行页签与发布入口读的就是这一条事实(`view/project-development/index.tsx` 的 `runAvailable`、`project/export.rs` 的 `project_has_runnable_prototype`)。 + - **发布也成立**:导出/发布入口「已有可玩入口」的分支(`export.rs:262-265`)只要求工程栈合规(phaser 4 + vite),脚手架已满足(`ensure_publish_project_stack`,`:413-470`),于是发包内容就是**当前预览根**——作者没改就是取到的那份成品产物(平台仍要求衍生作品填「本次核心改动说明」)。**没有**「改完再发布断裂」这一步了:源码缺失只影响「能不能在取到的成品上重建核心逻辑」,不影响「能不能运行 / 能不能发布」。 + - **不再写第二份 `reference/` 副本**:可玩参考就落在预览根这一处。多写一份既让「哪份是事实源」含糊(两份都要同步),又白占一份磁盘;来源与形态事实由 `.agent/fork-source.json`(v2,`source: package`)承担,界面据此如实措辞。 + - **不会被当成作者源码外发**:`game/dist` 属于工程源包排除清单(`project_bundle.rs:88` 的 `BUNDLE_EXCLUDED_GAME_BUILD_DIRS`),fork 来的成品不会被重新包进该作者的工程源包。 +- **能力边界**:可试玩(fork 完直接运行)、可发布(发布当前预览根)、可提取包内素材(受上面扩展名白名单限制)、可阅读已压缩的运行产物;**不可**在取到的成品上重建核心逻辑(包里没有源码与 `package.json`)。面板必须如实说明最后一点。 +- **价值**:零新增上传资产即可跑通「授权 → 运行/试玩 → 素材复用 → 直接发布(或改后在自建工程里发布)→ 血缘与溯源」;工程源包仍是有源码时的正解路径(§3.5.2)。 #### 3.5.2 工程源包路径(源码级复刻,现为正解路径) @@ -404,7 +407,7 @@ pub(crate) project_bundle_sha256: Option, - **规模检查的读取层封顶**:实际读取按**该条目声明大小**封顶(只允许多读 1 字节用于判溢出,短读同样算失败),预分配同样收紧到硬上限。因此「声明说谎的 deflate 炸弹」在**读**这一层就失败关闭,不会先解完再比对;**同一处修法同步到发行包校验器**(那里原有同样的写法)。 - **压缩比口径更正**:分母是**该条目的压缩字节**(不是整包字节),而且它**不是 zip 炸弹防线**——真正的约束是单文件 64 MiB 与累计展开 500 MiB 两条上限,压缩比只是补充。文档与代码注释都按这个语义写。 - **路径形状与嗅探维度(一致性门禁新增)**:客户端过去只过 `template_library::safe_archive_relative_path`(不查结尾点/空格与 `<>"|?*`),于是 macOS/Linux 上 `src/a?.ts`、`x.` 这类名字**客户端能打、服务端必 422**(白传一趟)。现在客户端打包器自带一份与服务端逐字符对齐的路径形状检查,且 `check-project-bundle-policy-parity` 覆盖四类:① 目录/前缀/后缀/全名 + 5 项上限;② **路径形状**(哨兵段 / 结尾字符 / 禁止字符 + 两侧必须显式拒 `/` 开头与 `\`);③ **内容嗅探特征表**(`SECRET_CONTENT_SIGNATURES` / `AWS 前缀` / PEM 标记 / 文本扩展名白名单 / 嗅探窗口,两侧逐字相等);④ **嵌套包 magic bytes**(服务端 ⊆ 客户端)。抽不到一律报错,不做「抽不到就绿」。 -- **下载与建项(AGC,已落地)**:`fork-source` 返回 `source: 'project'` 时走 `GET /games/{gameId}/fork-source/project`(`package` 时走原路径)→ 校验 `sha256` 与 `bytes` → 解压到 `/forks///`(与模板安装同一套路径门禁与私有 DACL 写入)→ 按来源分支建项(源码形态可直接改源码/重跑构建;成品包形态是可玩参考副本)→ 写 `manifest.forkedFrom`。来源记录 `.agent/fork-source.json` 升到 **v2**(记录取件形态与对应摘要),**兼容 v1**(旧记录仍可读);取件成功提示按两种形态分别如实措辞。 +- **下载与建项(AGC,已落地;2026-10-07 更正落点)**:`fork-source` 返回 `source: 'project'` 时走 `GET /games/{gameId}/fork-source/project`(`package` 时走原路径)→ 校验 `sha256` 与 `bytes`(失败关闭、不落盘)→ 按来源分支建项:**工程源包**解到项目根(包就是工程本身,复用模板归档同一套条目门禁);**成品包**先 `init_local_game_project_at` 生成合规脚手架、再整包解到**预览根**(`fork_playable_root`,即 `game/dist`;先前文档写的「解压到 `/forks///`、成品包形态是可玩参考副本」与实现不符,已按代码更正——那一版落点不在预览根,导致副本播不了)。建项收口统一走 `register_forked_project_state_at`:登记「可运行原型」(仅当预览根确有 `index.html`)与 `initial-` 工程内部版本,并宣告清单失效。改编来源写在 `.agent/fork-source.json`(**不写 manifest**:manifest 是 `deny_unknown_fields` 的 v1 契约,加拓扑字段等于改契约)。来源记录为 **v2**(记录取件形态与对应摘要),**兼容 v1**(旧记录仍可读);取件成功提示按两种形态分别如实措辞。 - **优先级(已落地)**:同一版本同时存在工程源包与成品包时,`fork-source` 优先返回工程源包并标 `source: 'project'`;只有工程包缺失(或行上「字节数 > 0 但摘要为空」这种半写状态)才回落 `package`,客户端据此决定建项形态。 #### 3.5.3 发布时声明(两条路径共用) @@ -417,17 +420,19 @@ AGC 发布链路(`game_distribution_publish.rs`)读取 `manifest.forkedFrom` **进展(2026-10-05)**:产品已采纳下面的推荐口径(**不做**独立的「源码可见性」开关;作者不传工程包即退化产物级改编;`nonCommercial` 的约束力是平台规则层面)。路线 **B 两端均已落地**:服务端(§3.2.3 三列 + `module-game-distribution` 的工程包校验器 + 上行/下行路由族,见 §3.4)与 AGC 侧(确定性打包器、发布时上传、按 `source` 分支的取件建项、来源记录 v2),两端规则由 `npm run check-project-bundle-policy-parity` 机器门禁钉住一致;A 作为默认路径继续服务未上传工程包的作品;C 不做。 +**落地补记(2026-10-07)**:A 路线的**兑现度被提高了**,但**没有**走成 C(不造「dist 实体的伪工程」):成品包仍只当可玩参考(内容原样解到预览根,不改写任何字节),合规工程骨架由既有 `init_local_game_project_at` 标准脚手架提供(**不是** fork 自己拼 `package.json`),并在建项收口把「可运行原型」登记进 manifest。因此 A 路线现在是「可玩参考 + 素材来源 + 合规骨架 + 开箱可运行/可发布」,唯一没兑现的仍是「在成品上重建核心逻辑」(包里没有源码)。产品口径同步见 §2.5 与 §5.8。 + 三条候选路线: | 路线 | 用户拿到什么 | 能不能发布 | 对「一键复刻完整工程」的兑现度 | 工作量 | | --- | --- | --- | --- | --- | -| **A. 参考式改编**:成品包只当「可玩参考 + 素材来源」,AGC 侧另建合规骨架(真 Phaser 4 + Vite) | 原作副本(对照试玩)+ 新工程骨架 + 从成品包提取的素材(受扩展名白名单限制) | 能(骨架是合规工程) | 低:脚本与配置要重写,对外不能说「复刻工程」 | AGC 侧中;若要把血缘做到平台可查询则跨端大 | +| **A. 参考式改编**:成品包只当「可玩参考 + 素材来源」,AGC 侧另建合规骨架(真 Phaser 4 + Vite) | 原作副本(**预览根可直接试玩**)+ 新工程骨架 + 从成品包提取的素材(受扩展名白名单限制) | 能(骨架是合规工程;2026-10-07 起建项同时登记可运行原型) | 低:脚本与配置要重写,对外不能说「复刻工程」 | AGC 侧中;若要把血缘做到平台可查询则跨端大 | | **B. 只做工程源包**(跳过成品包路线) | 作者随版本上传的可编辑工程包,fork 后即完整可开发工程 | 能(源包本身是 Phaser 4 + Vite 时) | 高(前提是作者真的提供工程包) | 大(需新增工程包下载/授权接口,见 M2b) | | **C. 成品包 + 自动补工程栈** | 铺产物 + 生成声明 phaser 4/vite 的 `package.json` | 能过客户端校验,但产出的是「dist 实体的伪工程」 | 表面中(能发布)、实质低(不可再构建) | 小–中 | -**推荐**:第二阶段落 **A**(试玩 + 素材 + 血缘),对外口径从「复刻工程」改为「参考改编 / 素材复用」;**B 作为正解排后续**(先把「可编辑工程包」的定义与包内禁项定清楚,再谈下载与授权);**C 不做**,或仅限一次性存量兼容且需产品书面接受「不可再构建」的语义。 +**推荐(2026-10-05 已拍板;2026-10-07 按落地事实补记)**:第二阶段落 **A**(试玩 + 素材 + 血缘,现为「开箱可运行/可发布」),对外口径从「复刻工程」改为「参考改编 / 素材复用」;**B 作为正解同样已落地**(源码级复刻,`game/dist` 不在包内,所以源码形态 fork 后需要先构建才可运行——按产品口径**不自动构建**,界面如实提示);**C 不做**,或仅限一次性存量兼容且需产品书面接受「不可再构建」的语义。 -A 路线里有一个必须提前知道的互斥点:`create_npm_scaffold` 的触发条件是「没有 manifest **且** 根与 `game/` 都没有 `index.html`、都没有 `package.json`」(`apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs:609-613`)。因此「先铺成品包文件」与「先建空工程」二者互斥(先铺文件则不再生成脚手架;先建空工程则 manifest 已存在),两条都要就得由 fork 流程自己写 `package.json` / `vite.config.*`。本仓已有「先复制模板文件、再调标准初始化」的既有做法可参照(`src-tauri/src/template_library.rs:816-905`)。 +A 路线里那个必须提前知道的互斥点已经按既有做法绕开:`create_npm_scaffold` 的触发条件是「没有 manifest **且** 根与 `game/` 都没有 `index.html`、都没有 `package.json`」(`apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs:609-613`),所以 fork 流程**先**走标准初始化生成脚手架、**再**把成品铺进 `game/dist`(预览根,不在判据覆盖的两个根上),既不需要自己拼 `package.json` / `vite.config.*`,也不会让脚手架与产物互相打断。成品铺在预览根而不是 `reference//`,是因为运行视图只服务预览根(`preview::project_game_root`);`game/dist` 同时被工程源包排除清单覆盖(`project_bundle.rs:88`),不会被当成作者源码外发。 ### 3.6 前端链路 @@ -892,8 +897,9 @@ procedure 另要求**受信服务身份**(归属判定靠 `owner_user_id`, | 工程包门禁:含 `node_modules` / 符号链接 / 绝对路径 / 超限的包被拒且不产生任何对象 | 客户端 Rust 定向测试 + 服务端校验测试 | 待补 | | 补齐:提升授权后可对当前公开版本补传工程包一次,补齐后改造升级为源码级;二次补传或换内容重传被拒;历史版本不被补齐 | 定向测试 + 真实栈 | 待补 | | 源码路径闭环(工程源包):详情页 → AGC → 下载 → 解压 → 建项 → 改源码 → 重跑构建 → 发布 → 详情页出现溯源 | 真实 AGC + 真实 api-server smoke(含截图/录屏) | 待补 | -| 参考路径(成品包):详情页 → AGC → 本地副本可试玩、素材可提取;**界面明确写明不能直接重新发布**,且不伪造血缘、不出现空白或死链 | 真实栈 + 浏览器验证 | 待补 | -| 交付口径一致:对外文案不出现「成品包可直接发布 / 一键复刻完整工程」这类与 §3.5.1 事实冲突的表述 | 文案评审 + 浏览器验证 | 待补 | +| 参考路径(成品包):详情页 → AGC → **fork 完即可运行**(预览根 `game/dist/index.html` 播的就是取到的那份成品)、素材可提取;且不伪造血缘、不出现空白或死链 | 客户端 Rust 定向测试(落点与收口断言)+ 真实栈浏览器验证 | 建项断言 ✅(§5.8);真机画面 ⏳ 待补 | +| 交付口径一致:对外文案不出现「成品包可直接**发布**但不可运行」这类与 §3.5.1 新事实冲突的表述(现行口径:成品包 fork 可运行、可发布,**只有**「在成品上重建核心逻辑」不可行) | 文案评审 + 浏览器验证 | 待补 | +| Fork 建项收口:成品包 fork 后 `manifest.tasks[code-prototype] == completed`、`versions == [initial-1]`;工程源包 fork 后只写 `versions == [initial-1]`(**不**标完成,界面如实提示需先构建);运行页签不可用时的提示文案按清单事实分叉 | 客户端 Rust 定向测试 + 前端投影用例(§5.8) | ✅ 已验证 | | 公开 DTO 不泄露对象键、不泄露未公开作品信息 | DTO parity + 逐键核对 | 待补 | | 署名:公开详情/广场展示真实作者名与陶泥号,不再落到「创作者」兜底 | 真实栈 + 浏览器验证 | 待补 | | 移动端与桌面端布局可用;移动端改造入口给出桌面端提示 | 320px / 1280px 浏览器验证 | 待补 | @@ -1020,6 +1026,22 @@ procedure 另要求**受信服务身份**(归属判定靠 `owner_user_id`, | 改动清单(提交) | 外壳滚动链 `501fd0925`(`styles.css` + `tests/launcherScrollContract.test.ts`);共创列表页与版式令牌 `2010c1382`(`view/co-creation`、`platformForkCatalog`、`platformGameForkModel`、`forkPageStyles` + 用例);确认页与发布面板 `caf7db4ba`(`view/fork-confirm`、`GameDistributionPublishPanel` + 用例) | ✅ 已提交 | | 真实浏览器(本次核验方式) | 一次性夹具把两页挂进与 `WindowChrome` 同构的外壳(`#root` → `.window-chrome__content` → `main.launcher-shell > aside + section.launcher-main`),真实 `styles.css` + Vite;在 1440×900 / 1000×620 / 800×600 三档量逐层计算值、滚到底后末项与页脚可见性、横向溢出;夹具用完即删(`git status` 干净) | ✅ 已验证 | +### 5.8 Fork 建项收口:可运行原型与初始工程版本(2026-10-07)实施证据 + +**背景(实物证据)**:一次真实 Fork(`%APPDATA%/world.genarrative.ai-game-creator/projects/gameagent-a74aa1d9`,2026-10-06 15:27:58,`.agent/fork-source.json` = `source: package`)建出来的项目,manifest 里 **16 个 seed 任务全 `pending`(含 `code-prototype`)、没有 `versions[]`、没有 `preview`**,父作品那套可玩产物被解到 `reference//`(**不在预览根**)。于是运行页签命中 `run-unavailable-hint`「首个可运行原型尚未完成,运行视图暂不可用」且点击无反应;发布也被同一判据拦下。根因是 Fork 建项**没有**像 AI 直连回合收口那样登记「可运行原型」与工程内部版本,且可玩内容放错了位置。 + +| 项 | 证据 | 状态 | +| --- | --- | --- | +| 落点改为预览根 | `game_fork.rs` 新增 `fork_playable_root`(= `preview::project_game_root`,本形态即 `game/dist`),`ProjectForkSourceKind::Package` 分支把发行包整包解到这里;不再写 `reference//`(原 `fork_reference_relative_path` 与其用例随之删除,改为「可玩参考永远落在预览根」用例) | ✅ 已验证(`cargo test -- game_fork`) | +| 建项收口登记原型与初始版本 | `game_fork.rs` 新增 `register_forked_project_state_at`:预览根确有 `index.html` 时 `update_manifest_task_status_at("code-prototype", Completed)` → `advance_agent_runtime_project_revision_locked` → `append_agent_game_iteration_version_at`(得到 `initial-1`)→ `emit_game_creator_manifest_invalidated`;返回收口**之后**重读的清单,避免把过期快照交给进项目通道 | ✅ 已验证 | +| 成品包形态断言 | 建项后:`game/dist/index.html` == 包内 `index.html`、`game/dist/game/main.js` == 包内脚本、`!reference` 存在;`code-prototype == Completed`;`versions == [initial-1]`(`projectRevision 1`、`createdReason initial`、无父版本);`project_has_runnable_prototype(&manifest) == true` | ✅ 已验证 | +| 工程源包形态断言(如实不标完成) | 建项后:源码逐字节落地(`game/package.json` / `game/src/game.js` / `game/index.html`)、`!game/dist` 与 `!reference` 存在;`code-prototype == Pending`;`versions == [initial-1]`;`project_has_runnable_prototype == false` | ✅ 已验证 | +| 门禁判据可见性 | `project/export.rs` 的 `project_has_runnable_prototype` 提升为 `pub(crate)`,供建项用例直接断言「运行/发布读的就是这一条事实」 | ✅ 已验证 | +| 提示文案诚实化 | 新增 `src/view/project-development/runUnavailableHint.ts`:清单里已有工程内部版本(`versions` 非空)时说「已有工程内容但还没有可运行的构建产物:让智能体完成可运行原型后即可运行」,否则保留原句;`index.tsx` 的 `run-unavailable-hint` 改为调用它 | ✅ 已验证(`tests/runUnavailableHint.test.ts`) | +| 前端投影(fork 后直接可跑) | appSurface 新用例:`code-prototype` 完成 + `versions = [initial-1]` 渲染后提示消失、`#run-unavailable-hint` 不存在、`data-unavailable` 不置位,点运行页签 → `aria-selected=true` 且 `onPlay` 调用 1 次;另一用例钉「只有 `versions` 无完成原型」时显示新提示且点击不发播放请求 | ✅ 已验证 | +| 既有 Fork 项目 | **不迁移**(不写一次性补丁):修复只对**新 Fork** 生效,既有 fork 项目请**重新 Fork 一次** | ⏭ 明确不做 | +| 真机端到端(截图 / 录屏) | 待重建客户端后按「Fork(无工程源包的作品)→ 运行看到父作品画面 → 改源码 → build → 发布 → 详情页出现溯源」跑一遍并留证据 | ⏳ 待补 | + --- ## 6. 里程碑拆分(评审后逐个开实施计划) @@ -1027,7 +1049,7 @@ procedure 另要求**受信服务身份**(归属判定靠 `owner_user_id`, | 里程碑 | 范围 | 交付判据 | | --- | --- | --- | | [M1 授权与血缘骨架](./project-memory/plans/【里程碑】游戏共创授权与血缘-2026-10-03.md) | game 追加授权列、新血缘表、创建 game 携带血缘的校验与写入、授权提升接口、公开 DTO 增量、详情页徽章+溯源卡、`/games/mine` 授权设置、后台展示、署名修复 | 可在平台内看到并管理「能否被改编 / 改编自谁 / 第几代」;改造动作本身在 M2a 接线 | -| [M2a 参考式改编(只试玩 + 素材 + 血缘,不含重新发布)](./project-memory/plans/【里程碑】成品包改造闭环-2026-10-03.md) | 复用已存在的成品包做受鉴权内容下发、AGC 下载/本地副本/素材提取、血缘写入与溯源展示、发布面板授权选择 | 真实 AGC 上完成「看别人的作品 → 本地试玩并参考 → 在**自建合规工程**里重做 → 发布 → 溯源正确」;**不承诺**成品包可直接重新发布(§3.5.1)。**成立前提**:产品接受对外口径从「复刻工程」改为「参考改编 / 素材复用」(§3.5.4 路线 A) | +| [M2a 参考式改编(可玩参考 + 素材 + 血缘)](./project-memory/plans/【里程碑】成品包改造闭环-2026-10-03.md) | 复用已存在的成品包做受鉴权内容下发、AGC 下载/建项(成品解到**预览根**)、素材提取、血缘写入与溯源展示、发布面板授权选择 | 真实 AGC 上完成「看别人的作品 → fork 后**直接运行** → 参考改编 → 改后在自建合规工程里重做 → 发布 → 溯源正确」。**2026-10-07 更正**:M2b 建项形态落地后,成品包 fork **也能直接发布**(发布当前预览根),原「不承诺可直接重新发布」的口径作废(§3.5.1 / §5.8) | | [M2b 工程源包与源码级复刻](./project-memory/plans/【里程碑】作品工程源包与一键改造-2026-10-03.md) | version 追加工程包字段、AGC 打包/上传、`fork-source` 优先返回工程包、源码形态建项、**新增工程包下载/授权接口**(当前不存在,§3.5.1) | 真实 AGC 上完成「拿到源码 → 改核心逻辑 → 发布 → 溯源正确」;这是「一键复刻完整工程」的**唯一正解路径**(§3.5.4 路线 B) | | [M3 族谱与衍生列表](./project-memory/plans/【里程碑】创作族谱与衍生列表-2026-10-03.md) | `/games/lineage` 树页、`/games/{id}/lineage` 与 `/derived` 接口、`/games/mine` 被改编列表 | 族谱树可浏览、可跳转、父作品下架有降级展示 | | [M4 共创主题与作品树](./project-memory/plans/【里程碑】共创主题与作品树-2026-10-06.md) | 两张主题表(主题 + 成员,成员只允许根)、公开主题列表 / 详情接口、作品详情 `themes` 增量、后台主题写接口(后台 UI 不在本轮) | 共创 Tab 能按运营命名列出主题并进入多棵作品树;作品详情能看到并跳到所属主题;方案见 §3.10。**已拍板采纳「平台命名主题」方案**(§7 第 3 条),设计已定、实现排在下一轮 |