@@ -0,0 +1,464 @@
# 【技术方案】游戏共创与作品 Fork
更新时间:`2026-10-03`
> 状态:`draft`(待评审)。评审通过前不写业务代码。
> 本文件同时作为「游戏共创」的主规范与技术方案;行为合同部分不得绑定类名、文件名和实现算法。
## 0. 结论摘要
1. **Fork 关系挂在 `game`,不挂在 `version`。 ** `game` 是稳定作品身份,`version` 是不可变内容快照;血缘是「作品 ↔ 作品」关系。唯一留在版本级的是**被复刻的内容本身**(已存在的成品包与新增的工程源包都属于版本资产),因为内容随版本演进。
2. **现状不存在任何 fork/remix/来源/父作品字段或表。 ** 旧的「作品改造 / Remix」在网页端已整体退役(`vite.config.ts:16-18,20-59,126-140,170-173` 把退役固化成构建门禁),后端只剩 `#[cfg(any())]` 死码(`module-runtime/src/domain.rs:137` 、`api-server/src/state.rs:1317` 、`shared-contracts/src/runtime.rs:879-880` )与 CSS 死类名。本功能是**从零建**,没有历史包袱,也不复活 Remix 命名。
3. **必须区分两种 Fork,它们是两层能力而不是一个开关 ** :
- **成品包 Fork(零新增资产,可立即做)**:每个已发布版本本来就存着构建产物 ZIP(`game_distribution_version.package_object_key` , `:797` ),直接拿它建项目就能跑通「下载 → 建项 → 改造 → 发布带溯源」。
- **工程源包 Fork(真·复刻工程)**:成品包是 Vite 构建产物——模板工程 `vite.config.js` 只配了 `build: { outDir: 'dist' }` , **未关压缩也未开 sourcemap**,且平台发行校验明确拒收 `*.map` ( `server-rs/crates/module-game-distribution/src/package.rs:171-175` )。所以 AI 在成品包上改核心逻辑不可靠,要让文档里「用户改造完成后可发布」成立,仍需版本级可选资产**工程源包**。
- 两者共用同一套血缘模型;成品包路径同时是工程源包缺失时(网页端发布、作者不愿公开源码)的天然降级路径。
4. **工程源包有现成规范与现成客户端链路可复用 ** :排除规则见 `docs/【模板规范】AGC模板包组织指南-2026-09-21.md` ;下载→校验 SHA-256→解压→建项全链路已存在于 `apps/ai-game-creator-shell/src-tauri/src/template_library.rs` ( `create_project_from_installed_template_at` `:816` )。
5. **本期不做 ** :创作者收益分成(仓库无收益/分成/结算表,`docs/【技术方案】外部产品支付服务接入-2026-10-03.md:200` 明确人工结算)、相似度反洗稿校验、上链。
6. **顺带必修的相关缺口(已在 M1 修复) ** :公开 `author.name` 长期落到兜底文案(创建游戏时 `author_name` 写死 `None` , `api-server/src/modules/game_distribution.rs:1001-1003` ;公开 payload 兜底「创作者」)。修法:公开快照改为读时联 `user_account` ,账号改名/换头像立即跟随,与后台游戏管理页同口径。
---
## 1. 现状事实(设计依据)
### 1.1 现役「作品」只有一套:游戏分发
| 形态 | 表 | 状态 |
| --- | --- | --- |
| 游戏分发(游戏广场) | `game_distribution_game` / `_version` / `_review` / `_review_moderation_log` / `_idempotency_receipt` | 现役 |
| 编辑器精选素材 | `editor_showcase_asset` / `_like` | 现役,与作品 Fork 无关 |
| 公开玩法作品(custom-world / puzzle / 大鱼 …) | `public_work_like` / `public_work_play_daily_stat` / `profile_played_world` + 各玩法源表 | **写入路径全部 `#[cfg(any())]` 死码 ** ,前端路由与组件已删除 |
现役路由唯一真相 `src/routing/activeAppPageRoutes.ts:7-18` ,作品相关只有:
```
/games 游戏广场 src/components/game-distribution/GameGalleryPage.tsx
/games/detail 作品详情 GameDetailPage.tsx
/games/play 游玩(iframe) GamePlayPage.tsx
/games/mine 我的作品 MyGamesPage.tsx
/games/publish 发布 / 发布新版本 GamePublishPage.tsx
```
导航:桌面 rail 创作 / 项目 / 游戏 / 我的(`PlatformEntryActiveFlowShell.tsx:653-682` ),移动 dock 游戏 / 我的(`:198-207` )。
### 1.2 游戏 - 版本模型
- `game_distribution_game` ( `server-rs/crates/spacetime-module/src/game_distribution.rs:718-764` ):`game_id` PK、`owner_user_id` 、资料(标题/简介/分类/标签/封面/截图/设备/输入/朝向)、`publication_revision` (公开修订号 CAS)、`active_version_id` (当前公开版本指针)、`visibility` ( `unpublished` / `published` / `suspended` )、`play_count` 、`local_project_id` (同作者同本地项目复用身份,**不构成所有权证明**)、`cover_object_key` 、`screenshots_json` 。
- `game_distribution_version` ( `:766-821` ):`version_id` PK、`game_id` 索引、`owner_user_id` 索引、`version_number` 、包摘要(`package_sha256` / `package_bytes` / `package_file_count` / `package_entry_path` )、`status` 、`package_object_key` 、`package_manifest_json` 、`entry_url` 、审核人/阶段时间、`metadata_json` (该版本冻结的作者资料快照,审核通过时整体生效到 game)。
- 版本状态:`awaiting_upload → uploaded → pending_review → published` ,失败终态 `upload_failed / validation_failed / rejected / cancelled / revoked` (常量 `:868-877` ,枚举 `module-game-distribution/src/domain.rs:42-53` )。
- 公开性唯一口径:`visibility == published` 且 `active_version_id` 指向 `status == published` 的版本(`:3335-3346` )。
- 公开读只有 3 条无鉴权路径:列表、详情、发行网关 `/api/game-distribution/releases/{gameId}[/{*asset}]` ( `api-server/src/modules/game_distribution.rs:344-363` )。
- 写操作全部经受信服务身份 procedure;幂等收据 30 天;所有公开切换走 `publication_revision` CAS。
### 1.3 与 Fork 相关的既有能力与缺口
| 项 | 现状 | 证据 |
| --- | --- | --- |
| 「能否被 fork」 | **不存在 ** (旧的 `remixEnabled` 是玩法级死配置) | `module-runtime/src/domain.rs:137` ( `#[cfg(any())]` ) |
| 「fork 自谁」 | **不存在 ** 。最近似的只有 `local_project_id` 身份复用,语义是同一作者自己的本地项目 | `game_distribution.rs:1700-1727` |
| fork 计数 | **不存在 ** ( `remixCount` 是 `#[cfg(any())]` 退役契约里的死字段,零消费方) | `shared-contracts/src/runtime.rs:922,950` |
| 作者署名 | 字段存在但创建时写死 `None` ,公开页兜底「创作者」 | `api-server/.../game_distribution.rs:1001-1003` 、`:2390` |
| 作者主页 / 关注 / 粉丝 | **不存在 ** (无任何社交关系表) | `migration.rs:149-200` 无相关表 |
| 收藏 / 收录 | **不存在 ** ,且文档明确禁止虚构收藏状态 | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md:74` |
| 播放次数 | 本分支上 `play_count` 仍**从无递增路径**却被前端展示;PR #565 已新增游玩计数上报 procedure,合并后以 master 结果为准 | `game_distribution.rs:1759` (仅初值);`GameDetailPage.tsx:292` |
| 收益 / 分成 | **不存在 ** ; `PuzzleAuthorIncentiveClaim` 有枚举无写入方 | `module-runtime/src/domain.rs:1147` |
| 从包建项(客户端) | **存在且完整 ** :下载→SHA-256 校验→解压(拒绝绝对路径/`..` /盘符/反斜杠/符号链接,条目 ≤4096、单文件 ≤256 MiB)→建项→失败删半成品 | `template_library.rs` 、`:815-850` 、`:240-342` |
| 工程包排除规则 | **存在 ** :禁止 `.agent` / `.git` / `.svn` / `node_modules` 段与根 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode` | `docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md` (后台模板上传章节)、`docs/【模板规范】AGC模板包组织指南-2026-09-21.md` |
---
## 2. 产品设计
### 2.1 目标
让平台上的游戏可以被他人**合法地继续改造**,并让改造形成的世代链路可展示、可追溯、原作者可署名。
### 2.2 非目标
- 不做收益分成、版税结算、算力成本核算(无账本与口径,属独立议题)。
- 不做相似度反洗稿校验(无判定标准;本方案只保证**来源声明不可伪造**)。
- 不做上链;「永久溯源」实现为**不可变的父子链 + 平台持久化事实**。
- 不做跨作品类型 Fork(现役只有一种作品:游戏分发)。
- 不做收藏 / 关注 / 粉丝。
### 2.3 授权模式(作者侧,作品级)
作者在上架时可选择,之后**只能单向提升开放度**:
| 值 | 用户可见文案 | 含义 |
| --- | --- | --- |
| `forbidden` (默认) | 禁止共创 | 仅可游玩,不可复刻改编 |
| `nonCommercial` | 允许非商用共创 | 可二次开发,禁止盈利,仅可公开分享 |
| `full` | 允许全开放共创 | 可改编、可商用、可引流、可发布新版本 |
状态机(唯一合法迁移):
``` mermaid
stateDiagram-v2
[*] --> forbidden : 创建作品(默认)
forbidden --> nonCommercial : 作者提升
forbidden --> full : 作者跳级提升
nonCommercial --> full : 作者提升
full --> [*] : 终态(不可降级)
```
约束:
- 只有 `owner_user_id` 可以变更;请求必须带 `expectedForkAuthorization` ( CAS),不匹配返回 `409` 。
- 任何降级请求一律 `409 FORK_AUTHORIZATION_DOWNGRADE_NOT_ALLOWED` ,不写库。
- 已按旧授权完成的 Fork **不受后续提升影响 ** (授权在建立血缘时已兑现)。
- **提升与源码的时序**:作品公开后提升授权时,已发布的版本里没有工程源包,因此允许对**当前公开版本补传一次**工程源包(规则见 §3.2.3)。提升界面对此给出提示,但不作为提升的前置条件。
### 2.4 用户视角入口矩阵
**玩家 / 未登录 **
| 位置 | 内容 |
| --- | --- |
| `/games` 广场卡片 | `forkedFrom` 存在时显示「改编」角标;不做父子聚合,卡片仍独立展示 |
| `/games/detail` 详情页 | ① 授权徽章(禁止共创 / 允许非商用共创 / 允许全开放共创);② 有父作品时显示溯源卡「改编自《X》· 由 Y 制作」+ 可点进父作品;③ 显示「第 N 代作品」与「N 个衍生作品」;④ `forkAuthorization != forbidden` 时显示主行动作「改造这个作品」(未登录点击走既有登录门禁) |
| `/games/detail` 族谱入口 | 「查看创作族谱」→ `/games/lineage?id=<rootGameId>` |
| `/games/lineage` (新页面) | 以根作品为顶的树:根节点、各代分支、每代作品卡(封面 / 标题 / 作者 / 第 N 代 / 游玩数),点击进入详情;父作品已下架时该节点显示「原作品已下架」但仍可点;空态与加载失败按现有平台错误组件 |
**作者 / 已登录 **
| 位置 | 内容 |
| --- | --- |
| `/games/mine` 每张作品卡 | 新增「共创授权」三态设置(仅允许提升,终态 `full` 时只读);新增「被改编 N」入口,弹层列出直接子代(标题 / 作者 / 代际 / 状态);提升授权后若当前公开版本没有工程源包,行内提示「上传工程源码以支持源码级改造」(上传在桌面端客户端完成,网页端只做引导) |
| `/games/publish` 、AGC 发布面板 | 新增「授权共创」三态单选(默认「禁止共创」,页面提示:开启后可被他人复刻改编,开启后不可撤销);从父作品 Fork 而来时显示只读的「改编自《X》」 |
| AGC 客户端 | ① 详情页「改造这个作品」唤起 AGC(deep link `genarrative://fork?gameId=<id>` );② AGC 首页/项目入口提供「从平台作品开始创作」(输入 gameId 或从平台跳转) |
**后台 **
| 位置 | 内容 |
| --- | --- |
| `#game-management` 游戏管理 | 列表新增「授权」「代际」列;版本历史弹层不变;不做强制改授权(避免平台替作者背授权责任) |
| `#game-distribution` 审核队列 | 允许共创的作品审核通过时,若作者未提供工程源包,标注「仅产物级改造」(不影响发布与审核) |
### 2.5 两个层级的改造能力(必须让作者和用户都看懂)
已公开的作品**总是**有成品包,所以「改造这个作品」按钮对所有开放授权的作品都可用;区别只在用户拿到的东西能改到什么程度。
| 情形 | 用户拿到什么 | 用户可见文案 |
| --- | --- | --- |
| 父作品有**工程源包**(AGC 发布且作者选择公开源码) | **源码级复刻 ** :AGC 下载工程 → 解压为新项目 → 可改源码、可重跑构建、可发布 | 「改造这个作品」 |
| 父作品只有**成品包**(网页端发布,或作者未上传工程源包) | **产物级改造 ** :AGC 以该发行版本的 ZIP 作为项目内容(静态可玩入口、无构建步骤),可改素材、数值、HUD、样式与局部逻辑 | 「改造这个作品」,并在面板注明「基于已构建成品,改核心逻辑建议作者开放工程」 |
两条路径在发布时写入**完全相同**的血缘与溯源,差别只在可编辑程度;不设「作者必须开源才能被改造」的前置条件。
### 2.6 关键流程
**A. 作者发起共创 **
``` mermaid
sequenceDiagram
participant A as 作者(AGC/网页)
participant API as api-server
A->>API: 发布游戏/版本,携带 forkAuthorization
API->>API: 校验三态合法;写入 game.fork_authorization
A->>API: ( AGC 且授权非 forbidden)上传工程源包
API->>API: 校验 zip(复用模板包门禁)→ OSS → version.project_bundle_*
Note over A,API: 审核通过后作品公开,且可被改造
A->>API: 后续提升授权(POST fork-authorization,仅升)
```
**B. 用户一键改造 **
``` mermaid
sequenceDiagram
participant U as 用户(浏览器)
participant AGC as AGC 客户端
participant API as api-server
U->>U: 详情页点「改造这个作品」
U->>AGC: deep link genarrative://fork?gameId=X
AGC->>API: GET /api/game-distribution/games/X/fork-source (Bearer)
API-->>AGC: {versionId, bundleSha256, bundleBytes, downloadPath}
AGC->>AGC: 下载 → 校验 SHA-256 → 解压(同模板安装门禁)→ 新建项目
AGC->>AGC: manifest 写入 forkedFrom{gameId, versionId, rootGameId, generation}
Note over AGC: 用户改造 → 发布
AGC->>API: POST /games(携带 forkedFromGameId/VersionId)
API->>API: 校验授权/来源版本/计算 generation 与 root → 写 lineage
```
**C. 溯源与命名 **
- 溯源卡出现在详情页与 AGC 项目内(项目信息区显示「改编自《X》」)。
- 代际:根作品 = 第 0 代;直接改编根作品 = 第 1 代;`generation = parent.generation + 1` 。
- 「衍生作品数」= lineage 表按 `parent_game_id` 的计数(实时算,与现有评分实时聚合同口径;不新增物化计数)。
---
## 3. 技术方案
### 3.1 分层与边界
``` text
src/(网页) 详情页/我的作品/族谱页 ←→ /api/game-distribution/**
apps/ai-game-creator-shell( AGC)
React 展示层 发布面板、改造入口
Rust facade 工程包打包/上传/下载/解压/建项(复用 template_library 基座)
api-server 鉴权、灰度、OSS 编排、ZIP 校验、payload 组装
spacetime-client typed facade / mapper
spacetime-module 表、procedure、事务
module-game-distribution 领域规则(授权状态机、代际计算、血缘校验)
shared-contracts Rust DTO ←→ packages/shared TS DTO
```
不新增数据库访问通道;不新增第二套作品系统。
### 3.2 数据模型
#### 3.2.1 `game_distribution_game` 追加 1 列(表尾,带默认)
``` rust
/// 共创授权等级:forbidden(默认)/ nonCommercial / full。只允许单向提升;不改变不写库。
#[ default( " forbidden " .to_string()) ]
pub ( crate ) fork_authorization : String ,
```
旧行反序列化自动补 `"forbidden"` ,与「默认禁止共创」语义一致。
#### 3.2.2 新增 `game_distribution_lineage`(父子血缘,1:1)
``` rust
#[ spacetimedb::table(
accessor = game_distribution_lineage,
index(accessor = by_game_distribution_lineage_parent, btree(columns = [ parent_game_id ] )),
index(accessor = by_game_distribution_lineage_root, btree(columns = [ root_game_id ] )),
index(accessor = by_game_distribution_lineage_owner, btree(columns = [ owner_user_id ] )),
) ]
pub struct GameDistributionLineage {
/// 子作品;主键即 1:1 约束,一个作品只能有一个父。
#[ primary_key ]
pub ( crate ) game_id : String ,
/// 子作品作者,冗余用于「我改编过的作品」列表,不参与授权判定。
pub ( crate ) owner_user_id : String ,
pub ( crate ) parent_game_id : String ,
/// 建立血缘时父作品的当前公开版本(不可变溯源事实)。
pub ( crate ) parent_version_id : String ,
/// 0 代母版;根作品自身不写行,读侧以「无行」判定为根。
pub ( crate ) root_game_id : String ,
/// 代际;直接改编根作品为 1。
pub ( crate ) generation : u32 ,
pub ( crate ) created_at : Timestamp ,
}
```
为什么用独立表而不是在 game 上再加 3 列:
- 血缘查询必须按 `parent` / `root` 建索引,而这两列在 game 上只能是 `Option<String>` ;本仓从未验证过对 `Option` 列建 btree 索引的行为,不拿 schema 赌。
- 血缘是 1:1 关系实体,主键承载「一个作品只有一个父」这条不变量。
- 不污染 game 主行读路径(详情页绝大多数请求不需要血缘行)。
- 事务一致性:创建 game 与插入 lineage 必须同事务,插入失败整笔回滚。
#### 3.2.3 `game_distribution_version` 追加 3 列(表尾,带默认)
``` rust
/// 该版本随包上传的工程源包对象键(不含 node_modules/.git/dist 等,规则见模板包组织指南)。
#[ default(None::<String>) ]
pub ( crate ) project_bundle_object_key : Option < String > ,
#[ default(0u64) ]
pub ( crate ) project_bundle_bytes : u64 ,
#[ default(None::<String>) ]
pub ( crate ) project_bundle_sha256 : Option < String > ,
```
工程包跟随版本,但保留**一次补齐机会**:
- **发布时**:仅当该版本所属作品的 `fork_authorization != forbidden` 才上传,与版本创建在同一次确认里完成。
- **补齐**:作品公开后,作者可以把工程源包补传给它**当前公开版本**,每个版本至多一次,内容摘要写入后不可再改。这是「版本不可变」的唯一例外——理由是工程源包不是发行内容:它不参与试玩、不参与审核、不改包摘要,补传只增加「能否被源码级改造」这一个能力。
- **不做版本回溯**:作者 v1 传了、v2 没传,那么 v2 只能走产物级改造,历史版本不给补。
- **补齐前置**:目标版本必须是该作品**当前公开版本**,且该作品 `fork_authorization` 已不是 `forbidden` 。
对象键:`agc/project-snapshots/v1/game-fork/{game_id}/{version_id}.zip` (与发行包同前缀族,便于生命周期统一)。
### 3.3 状态与流转
| 对象 | 字段 | 取值 | 合法迁移 | 触发方 | 并发控制 |
| --- | --- | --- | --- | --- | --- |
| game | `fork_authorization` | `forbidden` / `nonCommercial` / `full` | 只升不降(可跳级) | owner | `expectedForkAuthorization` 值 CAS |
| lineage | 全字段 | 创建即不可变 | 无 | 创建 game 时 | 主键冲突 → 409 |
| version | `project_bundle_*` | 有 / 无 | ① 随版本创建时确认;② 仍为空时,可对**当前公开版本**补传一次。写入后内容不可再改 | owner | 与 `confirm_package` 同一幂等键族 |
**血缘建立的服务端校验(全部失败关闭) ** :
1. `forkedFromGameId` 必须存在 → 否则 `409 FORK_SOURCE_NOT_FOUND` 。
2. 父游戏 `visibility == published` 、`deleted_at` 为空,且存在 `status == published` 的当前版本 → 否则 `409 FORK_SOURCE_NOT_AVAILABLE` (下架 / 封禁 / 已软删除作品不可被新 fork,但不影响既有子作品)。
3. 父游戏 `fork_authorization != forbidden` → 否则 `403 FORK_NOT_AUTHORIZED` 。
4. `forkedFromVersionId` 必须等于父游戏当前公开版本 id → 否则 `409 FORK_SOURCE_VERSION_MISMATCH` (禁止指向历史版本或伪造)。
5. 同一 `localProjectId` 复用既有 game 的场景**不允许**携带血缘(避免把既有作品改判成衍生作品)→ `409 FORK_DECLARATION_ON_EXISTING_GAME` 。
6. `generation = parent.generation + 1` ; `root_game_id = parent 有血缘行 ? parent.root_game_id : parent.game_id` 。服务端计算,不接受客户端传入。
### 3.4 接口契约
所有新增路径沿用 `/api/game-distribution` 命名空间与平台 envelope;作者写路由叠加 Bearer + 发布灰度;读路径 `Cache-Control: no-store` 。
#### 公开(匿名可读)
| 方法 / 路径 | 说明 |
| --- | --- |
| `GET /games/{gameId}` ( **既有,响应增量**) | 追加 `forkAuthorization` 、`forkCount` 、`lineage` (可选)、`forkSourceAvailable` (布尔,仅表达"能否真复刻",不泄露对象键) |
| `GET /games/{gameId}/lineage` (新) | 以该 game 的根为顶返回树:`{ root: GameSummary, nodes: [{ gameId, title, author, generation, parentGameId, status, playCount }] }` ,按代际与创建时间稳定排序;节点上限 200,超出返回 `truncated: true` |
| `GET /games/{gameId}/derived` (新,可选分页) | 直接子代列表 |
#### 作者(Bearer + 发布灰度)
| 方法 / 路径 | 说明 |
| --- | --- |
| `POST /games/{gameId}/fork-authorization` (新) | body `{ expectedForkAuthorization, forkAuthorization }` + `Idempotency-Key` ;只允许提升;返回最新 `forkAuthorization` 与 `replayed` |
| `PUT /versions/{versionId}/project-bundle` (新) | `application/octet-stream` ,整包或复用现役分片族(`upload-state` / `chunk` / `complete` );服务端校验 zip 门禁后写 OSS 并确认。前置:调用者是该版本作者,且该版本尚**没有**工程包;**发布阶段**上传只要求版本归属,**补齐**场景额外要求该版本是作品当前公开版本且 `fork_authorization != forbidden` |
| `GET /games/{gameId}/fork-source` (新) | 校验授权与来源可用性,返回 `{ gameId, versionId, bundleSha256, bundleBytes, downloadPath }` ; `downloadPath` 为受鉴权网关路径,不是可匿名访问的对象键 |
| `POST /games` (**既有,请求增量**) | 追加可选 `forkedFromGameId` / `forkedFromVersionId` |
#### 后台(admin)
| 方法 / 路径 | 说明 |
| --- | --- |
| `GET /admin/api/game-distribution/games` ( **既有,响应增量**) | 追加 `forkAuthorization` / `generation` / `forkedFromGameId` / `derivedCount` (只读展示) |
#### 契约同步(强制)
- Rust DTO: `server-rs/crates/shared-contracts/src/game_distribution.rs`
- TS DTO: `packages/shared/src/contracts/gameDistribution.ts`
- 版本状态枚举如需新增取值,同步 `GameDistributionVersionStatus` 与 `scripts/check-game-distribution-dto-parity.mjs` 的公开构建器约束。
- 本期不动 `/api/external/v1` ,因此不改 External OpenAPI;若后续对外开放 Fork 查询,再按现有门禁同步 OpenAPI。
### 3.5 两条改造路径:成品包(先行)与工程源包(补强)
#### 3.5.1 成品包路径(零新增上传资产)
- **内容来源**:父作品当前公开版本的 `package_object_key` (已存在,无需作者做任何额外动作)。服务端按现有发行网关同一套对象读取与校验复用该 ZIP,不新造存储。
- **下载授权**:与工程源包同一条受鉴权入口(`GET /games/{gameId}/fork-source` ),返回的是网关路径而非裸对象键;未登录、无授权、来源不可用时一律拒绝。
- **AGC 建项**:把 ZIP 内容铺进新项目的可玩入口目录(`game/` ),项目无需构建步骤即可试玩与发布——这条形态 AGC 本来就支持(`export_local_project_package_for_publish_at` 在已有可玩入口时直接打包,`src-tauri/src/project/export.rs:259-262` )。
- **能力边界**:可改素材、数值、样式、HUD 与局部逻辑;**不可**重建压缩后的核心逻辑。面板必须如实说明这一点,不能让用户以为拿到了源码。
- **价值**:零新增资产即可跑通完整闭环(血缘、溯源、代际、族谱),也是工程源包缺失时的降级路径。
#### 3.5.2 工程源包路径(源码级复刻)
- **打包(AGC) **:新增 Rust 侧 `project_bundle` 模块,复用模板包门禁:拒绝 `.agent` / `.git` / `.svn` / `node_modules` 段(任意层级)与根 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode` ;条目数 ≤4096;单文件 ≤256 MiB;解压总量 ≤512 MiB;条目排序 + 固定时间戳保证同内容同摘要。
- **上传(AGC)**:仅当作者选择的授权不是 `forbidden` 时上传(默认不上传,省流量且避免无谓的源码外发)。上传失败不影响发布,但要在发布面板明确提示「未上传工程包,你的作品只能被产物级改造」。
- **下载与建项(AGC,复用现役基座)**:走同一条 `GET /games/{gameId}/fork-source` → 校验 `bundleSha256` 与 `bundleBytes` → 解压到 `<app_data>/forks/<gameId>/<versionId>/` (与模板安装同一套路径门禁、同一套私有 DACL 写入)→ `create_project_from_installed_template_at` 建到用户工作区 → `manifest` 写入 `forkedFrom` 。
- **优先级**:同一版本同时存在工程源包与成品包时,`fork-source` 优先返回工程源包,并在响应里标明 `source: 'project' | 'package'` ,由客户端决定建项形态。
#### 3.5.3 发布时声明(两条路径共用)
AGC 发布链路(`game_distribution_publish.rs` )读取 `manifest.forkedFrom` 并写入创建 game 的请求体。网页端**不提供**手填来源,声明只能来自真实下载过的内容,避免伪造血缘;网页端发布的作品因此只能作为父作品,不能作为子作品。
### 3.6 前端链路
| 文件 | 改动 |
| --- | --- |
| `src/routing/activeAppPageRoutes.ts` | 新增 `['game-lineage', '/games/lineage']` ;同步 `SelectionStage` 类型与 `PlatformEntryActiveFlowShell` 的 stage 分支 |
| `src/components/game-distribution/GameDetailPage.tsx` | 授权徽章、溯源卡、「改造这个作品」主行动作、族谱入口 |
| `src/components/game-distribution/GameLineagePage.tsx` (新) | 族谱树页面 |
| `src/components/game-distribution/MyGamesPage.tsx` | 授权三态设置(只升)、「被改编 N」弹层 |
| `src/components/game-distribution/GamePublishPage.tsx` | 「授权共创」三态单选 + 不可撤销提示 |
| `src/services/gameDistributionClient.ts` | 新增 `setForkAuthorization` / `getGameLineage` ; `getGame` 类型增量 |
| `apps/ai-game-creator-shell` | 发布面板授权选择;`genarrative://fork` deep link 注册;改造入口与进度/失败提示 |
| `apps/admin-web/src/pages/AdminGameManagementPage.tsx` | 列表新增授权 / 代际列 |
移动端:移动 dock 只有游戏 / 我的,族谱页与详情页可读;「改造这个作品」在移动端提示「请在桌面端改造」,与现有创作/项目入口的桌面端提示同口径。
### 3.7 灰度与可观测
- **M1 实现口径**:共创写入复用现役 `game-distribution:publish` 开关(`ensure_publish_enabled` ),不新增独立灰度键——本功能与发布能力同批上线,先少一个开关减少漂移面。独立键 `game-distribution:fork` (含 `/api/runtime/frontend-config` 的 `gameDistributionForkEnabled` 与前端入口门禁)作为后续增强保留,接入前需要同时改 `module-runtime` 、`api-server` 与前端配置读取。
- 事件:`game_fork_source_downloaded` 、`game_fork_declared` 、`game_fork_authorization_updated` ,含成功/失败分类;不发对象键与用户隐私字段。
- **M1 未接埋点**:上述事件在 M1 未实现(当前只有既有 `tracing` 日志),随 M2 的内容下发一起补,避免在无下载能力时先埋无意义事件。
### 3.8 兼容与迁移
1. `game_distribution_game` / `game_distribution_version` 均为**表尾追加 + 明确默认值**,符合 SpacetimeDB 兼容追加规则;旧行语义 = 禁止共创 / 无工程包。
2. `game_distribution_lineage` 为新表,初始为空;根作品的「0 代」由「无血缘行」表达,不需要回填。
3. `migration.rs` 的 `migration_tables!` 白名单登记新表,并在注释中说明「血缘是业务事实,随迁移导出/导入;工程包正文在对象存储,不进入表」。
4. 必须运行 `npm run spacetime:generate` 、`npm run check:spacetime-schema` 、`npm run check:game-distribution-dto-parity` 、`npm run check:generated-bindings` 。
5. 不改删除 / 改名 / 重排 / 类型;若实施中发现必须如此,先停下来确认迁移计划。
### 3.9 与「作品管理」改动的关系(PR #565)
`feat/game-works-management` ( PR #565 ,作品管理与 Phaser4 客户端发布)与本功能改同一批文件,且引入了三处**语义**影响,落地时必须按其口径收敛:
| PR #565 带来的变化 | 对本功能的影响 |
| --- | --- |
| `game_distribution_game` 追加 `deleted_at` ,软删除后对该作品一律按「不存在」处理(作者视图、公开目录/详情、发行网关、后台默认列表全部下线,版本行与冻结资料保留) | ① 血缘来源校验增加「父作品未软删除」;②「衍生作品数」只统计**未删除**的子作品;③ 已被软删除的父作品是既有子作品的可见来源,但溯源只展示「原作品已不可用」,**不展示被删作品标题**,避免软删除语义被血缘绕过 |
| 游戏行同时被两条分支在表尾追加列(`deleted_at` 与 `fork_authorization` ) | 合并后两列并存即可;SpacetimeDB 自动迁移按列补齐,旧行各自取默认值 |
| `play_count` 由新增的游玩计数上报变成**活字段** | 本功能不依赖它;主规范 §1.3 里「死字段」的结论在 #565 合并后失效,以合并结果为准 |
同一批重叠文件(24 个)里多数是**机械冲突**(同一函数/同一 `json!` 块/同一枚举块各加一段),唯一需要重新生成的是 `spacetime-client/src/module_bindings/**` :合并后必须重跑 `npm run spacetime:generate` ,不得手工合并生成物。
合并顺序:**先合 #565 到 master,再把本分支 rebase 到新 master**,然后重跑 §5.1 的全部门禁与本地发布 smoke。
---
## 4. 为什么是 `game` 而不是 `version`
| 判断 | 结论 | 理由 |
| --- | --- | --- |
| 血缘(forkedFrom / root / generation) | **game ** | 血缘是作品间关系;父作品后续发 v4 不应改写子作品的父指针。放 version 会导致同一作品的每个版本都要重复声明来源,且任一新版本可能丢掉来源 |
| 授权(能否被 fork) | **game ** | 是作者对「这部作品」的策略,不是对某个构建产物;且授权可后续提升,必须有稳定载体 |
| 衍生计数 | **game ** (由 lineage 表按 parent 计) | 与 game 维度一致 |
| 被复刻的内容(工程源包) | **version ** | 内容随版本演进;复刻必须取到确定的内容快照,才能让溯源精确到「改编自 v3」 |
| 复刻时锁定的父版本 | **game 侧血缘行里的 `parent_version_id` ** | 是建立血缘那一刻的不可变事实,之后父作品发新版不影响它 |
一句话:**关系与策略在 game,内容在 version,血缘行把两者钉在一起。**
---
## 5. 验收标准与证据
| 条款 | 验收方式 | 证据 |
| --- | --- | --- |
| 授权默认 `forbidden` ,旧作品迁移后一律禁止共创 | 迁移后读旧 game 的公开详情 | 待补 |
| 授权只能提升:`forbidden→nonCommercial→full` 合法,任何降级 `409` | 定向 API/领域测试 + 真实栈 smoke | 待补 |
| 非 owner 改授权 `403` ; CAS 不匹配 `409` ;同 key 重放 `replayed=true` | 定向测试 + 真实栈 | 待补 |
| 未授权 / 已下架 / 非当前公开版本的 fork 声明被拒(`403` / `409` ) | 定向测试 | 待补 |
| 血缘不可变:同一作品第二次声明血缘 `409` ; `local_project_id` 复用场景拒绝携带血缘 | 定向测试 | 待补 |
| 代际与根计算正确:A→B→C 得到 generation 1/2、root 均为 A | 领域单测 + 真实栈三层链路 smoke | 待补 |
| 父作品下架后:既有子作品保持公开,新 fork 被拒 | 真实栈 smoke | 待补 |
| 工程包门禁:含 `node_modules` / 符号链接 / 绝对路径 / 超限的包被拒且不产生任何对象 | 客户端 Rust 定向测试 + 服务端校验测试 | 待补 |
| 补齐:提升授权后可对当前公开版本补传工程包一次,补齐后改造升级为源码级;二次补传或换内容重传被拒;历史版本不被补齐 | 定向测试 + 真实栈 | 待补 |
| 一键改造闭环:详情页 → AGC → 下载 → 解压 → 建项 → 改造 → 发布 → 详情页出现溯源 | 真实 AGC + 真实 api-server smoke(含截图/录屏) | 待补 |
| 无工程源包时仍有可用的产物级改造路径,且面板如实说明能力边界;不伪造血缘、不出现空白或死链 | 真实栈 + 浏览器验证 | 待补 |
| 公开 DTO 不泄露对象键、不泄露未公开作品信息 | DTO parity + 逐键核对 | 待补 |
| 署名:公开详情/广场展示真实作者名与陶泥号,不再落到「创作者」兜底 | 真实栈 + 浏览器验证 | 待补 |
| 移动端与桌面端布局可用;移动端改造入口给出桌面端提示 | 320px / 1280px 浏览器验证 | 待补 |
### 5.1 M1(授权与血缘)实施证据 — 2026-10-04
分支 `/` 工作树:`feat/game-fork` / `.worktrees/feat/game-fork` 。
| 项 | 证据 | 状态 |
| --- | --- | --- |
| schema 迁移(持久表加列 + 新表 + 新索引) | 本地 SpacetimeDB 2.8.3 上发布模块后,线上 schema 含 `fork_authorization` 列、`game_distribution_lineage` 表(`_game_id_key` + parent / root / owner 三个 btree 索引)与 `set_game_distribution_fork_authorization_and_return` procedure | ✅ 已验证 |
| 领域规则(授权阶梯、代际、错误码前缀) | `cargo test -p module-game-distribution` 全部通过 | ✅ 已验证 |
| BFF / 路由 / payload / 错误映射 | `cargo test -p api-server game_distribution` 全部通过(含新增的「共创授权路由未带 Bearer 必须 401」用例) | ✅ 已验证 |
| 前端类型与页面 | `npx tsc` (仓库 `npm run typecheck` )通过;`npx vitest run src/components/game-distribution src/services/gameDistributionClient.test.ts` 全部通过 | ✅ 已验证 |
| 契约一致性 | `npm run check:game-distribution-dto-parity` 、`npm run check:spacetime-schema` 、`npm run check:encoding` 、`git diff --check` 通过 | ✅ 已验证 |
| 生成绑定 | `npm run spacetime:generate` 后 `check:generated-bindings` 的 shared-contracts 段通过;AGC 段因工作树缺少随包 Codex CLI 资源(`resources/codex/win-x64/manifest.json` ,非本次改动)无法运行 | ⚠️ 环境限制 |
| HTTP 端到端(BFF 路由与真实数据库连接) | `npm run dev` 整套拉起后:`/healthz` 200; `GET /api/game-distribution/games` 200(空列表,走通真实数据库查询与 envelope);`PUT …/fork-authorization` 未带 Bearer 返回 401 `UNAUTHORIZED` | ✅ 已验证 |
| HTTP 端到端(真实登录态下的提升 / 声明血缘 / 详情页字段) | 未跑:需要真实登录态与一个已发布作品;本地开发库当前为空 | ⏳ 待补 |
| 浏览器视觉与交互验证(详情页共创卡、我的作品授权入口、移动端布局) | 未跑:本地库无作品数据,登录态不可用,页面无法进入有效状态 | ⏳ 待补 |
| 权限与边界(非作者 403、降级 409、父作品下架后新声明被拒) | 领域层与 procedure 校验已实现并覆盖单测;真实栈边界用例待补 | ⏳ 待补 |
---
## 6. 里程碑拆分(评审后逐个开实施计划)
| 里程碑 | 范围 | 交付判据 |
| --- | --- | --- |
| [M1 授权与血缘骨架 ](./project-memory/plans/【里程碑】游戏共创授权与血缘-2026-10-03.md ) | game 追加授权列、新血缘表、创建 game 携带血缘的校验与写入、授权提升接口、公开 DTO 增量、详情页徽章+溯源卡、`/games/mine` 授权设置、后台展示、署名修复 | 可在平台内看到并管理「能否被改编 / 改编自谁 / 第几代」;改造动作本身在 M2a 接线 |
| [M2a 成品包改造闭环 ](./project-memory/plans/【里程碑】成品包改造闭环-2026-10-03.md ) | 复用已存在的成品包做受鉴权 `fork-source` 、AGC 下载/建项/改造入口、发布自动声明血缘、发布面板授权选择 | 真实 AGC 上完成「改别人的作品 → 发布 → 溯源正确」,零新增上传资产 |
| [M2b 工程源包与源码级复刻 ](./project-memory/plans/【里程碑】作品工程源包与一键改造-2026-10-03.md ) | version 追加工程包字段、AGC 打包/上传、`fork-source` 优先返回工程包、源码形态建项 | 真实 AGC 上完成「拿到源码 → 改核心逻辑 → 发布 → 溯源正确」 |
| [M3 族谱与衍生列表 ](./project-memory/plans/【里程碑】创作族谱与衍生列表-2026-10-03.md ) | `/games/lineage` 树页、`/games/{id}/lineage` 与 `/derived` 接口、`/games/mine` 被改编列表 | 族谱树可浏览、可跳转、父作品下架有降级展示 |
| M4(可选,需产品决策) | 共创主题(平台命名的归组实体)与广场共创 Tab | 仅当采纳「平台命名主题」方案时才做 |
依赖:M2 依赖 M1 的数据模型;M3 依赖 M1;M4 依赖 M2 的实际使用数据。
---
## 7. 未决问题(需要拍板)
1. **成品包路径默认要做 ** (零新增上传资产,且是工程源包缺失时的降级路径);需拍板的是**是否追加工程源包(M2b)**:不做,则用户只能产物级改造,AI 改核心逻辑不可靠,需求文档里「一键复刻完整工程」这半句无法兑现。建议:做。
2. **授权默认值 ** :本文档按需求描述取「默认禁止」;飞书文档评论中包仲航建议改为「默认允许 + 发布前合同勾选」。两者会改变默认曝光面与合规口径,需产品拍板(默认允许对生态更友好,但要处理存量作品的合法性回溯)。
3. **是否引入「共创主题」实体 ** :包仲航建议「平台命名主题 → 作品树」,并明确作品之间不存在曝光挂靠。本文档的 M1–M3 按「作品间独立展示 + 详情页溯源 + 族谱按根归组」实现,不引入新实体;若采纳主题方案,需要额外的运营命名流程与表。
4. **收益分成 ** :需求文档要求「每一代均享有权益(署名 / 流量回馈 / 版权分成)」。署名本期做,流量回馈与分成本期不做(无账本、无算力成本口径,`docs/【技术方案】外部产品支付服务接入-2026-10-03.md:200` 明确人工结算)。
5. **相似度反洗稿校验 ** :需求文档要求「低改动度复刻判定」。本期不做;本方案只保证来源声明真实、不可伪造。若要做,只能基于工程源包做结构化比对,属于独立议题。
6. * * 「永久链上溯源」表述**:实现为平台持久化的不可变父子链,不上链。需确认该措辞是否可以调整。
7. * * `play_count` 死字段**:它被前端展示为「N 次游玩」但永不自增。本方案不依赖它;是否顺带修复(新增游玩上报)需单独立项。