Merge remote-tracking branch 'origin/master' into hermes/hermes-1e775b03
Some checks failed
CI / verify (pull_request) Has been cancelled

# Conflicts:
#	docs/technical/README.md
#	src/components/custom-world-home/CustomWorldCreationHub.tsx
#	src/components/custom-world-home/creationWorkShelf.ts
#	src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
#	src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
This commit is contained in:
2026-05-12 15:02:47 +08:00
141 changed files with 13407 additions and 2277 deletions

View File

@@ -0,0 +1,377 @@
# 安全漏洞扫描报告 2026-05-11
## 扫描范围
- 工作区:`C:/proj/Genarrative/.worktrees/hermes-3337436a`
- 分支:`hermes/hermes-3337436a`
- Git 基线:扫描时存在一个未跟踪计划文件 `.hermes/plans/2026-05-11_205658-security-vulnerability-scan.md`
- 扫描对象:根 Node/Vite/React 依赖、`server-rs` Rust workspace 依赖入口、仓库已跟踪文件中的敏感配置、JS/TS/Rust 源码安全热点。
## 扫描命令与工具状态
已执行:
```bash
pwd
git branch --show-current
git rev-parse --show-toplevel
git status --short
node --version
npm --version
cargo --version
rustc --version
rg --version
npm audit --json
npm audit --audit-level=moderate
git ls-files -z | xargs -0 grep -nIE "(api[_-]?key|secret|password|passwd|token|private[_-]?key|BEGIN (RSA|OPENSSH|EC|DSA)? ?PRIVATE KEY|AKIA[0-9A-Z]{16}|xox[baprs]-|sk-[A-Za-z0-9_-]{20,})"
rg -n "\beval\(|new Function\(|dangerouslySetInnerHTML|innerHTML\s*=|document\.write\(" src apps scripts packages
rg -n "exec\(|execSync\(|spawn\(|spawnSync\(|shell:\s*true|child_process" scripts src apps packages
rg -n "Command::new|std::process|\.unwrap\(|\.expect\(|fs::|File::open|PathBuf|set_header|cors|CorsLayer" server-rs/crates
rg -n "allow_origin|Any|cookie|Authorization|Bearer|refresh|access_token|set_cookie|SameSite|Secure|HttpOnly" server-rs/crates src apps scripts
```
工具版本:
- Node`v22.22.2`
- npm`10.9.7`
- Cargo`cargo 1.95.0 (f2d3ce0bd 2026-03-21)`
- Rustc`rustc 1.95.0 (59807616e 2026-04-14)`
- ripgrep`15.1.0`
- `gitleaks`:未安装,本次未执行。
- `cargo-audit`:未安装,本次未执行;未擅自安装到用户环境。
原始扫描输出保存于:
- `.hermes/plans/assets/security-scan-2026-05-11/npm-audit.json`
- `.hermes/plans/assets/security-scan-2026-05-11/npm-audit.txt`
- `.hermes/plans/assets/security-scan-2026-05-11/secret-grep.txt`
- `.hermes/plans/assets/security-scan-2026-05-11/js-xss-dynamic.txt`
- `.hermes/plans/assets/security-scan-2026-05-11/node-command-exec.txt`
- `.hermes/plans/assets/security-scan-2026-05-11/rust-hotspots.txt`
- `.hermes/plans/assets/security-scan-2026-05-11/auth-cors-hotspots.txt`
注意:`secret-grep.txt` 可能包含敏感片段,提交前应删除或改为脱敏摘要,不建议直接进入 Git。
## 摘要
| 等级 | 数量 | 说明 |
| --- | ---: | --- |
| Critical | 1 | `.env.local` 被 Git 跟踪且含多项非空真实密钥/凭据形态配置。 |
| High | 2 | npm 依赖存在 8 个 high advisory 聚合项Vite dev server 任意文件读取类漏洞需要优先升级。另有 TypeScript ESLint 链路 ReDoS 风险。 |
| Medium | 2 | esbuild dev server 请求读取、PostCSS CSS stringify XSS。 |
| Low | 3 | jsdom/http-proxy-agent/@tootallnate/once 低危链路。 |
| Unknown | 1 | Rust 依赖漏洞未完成扫描,因为本机未安装 `cargo-audit`。 |
| Informational | 多项 | 源码热点扫描命中大量测试/脚本/unwrap/expect需要按入口人工复核。 |
## Critical
### C-1仓库跟踪了 `.env.local`,且包含多项非空真实密钥形态配置
**证据:**
`git ls-files --error-unmatch .env.local` 显示 `.env.local` 已被 Git 跟踪。扫描确认该文件包含多项非空密钥/凭据形态变量,包括但不限于:
- `LLM_API_KEY`
- `ARK_API_KEY`
- `ARK_CHARACTER_VIDEO_API_KEY`
- `DASHSCOPE_API_KEY`
- `VOLCENGINE_ACCESS_KEY_ID`
- `VOLCENGINE_SECRET_ACCESS_KEY`
- `ALIYUN_SMS_ACCESS_KEY_ID`
- `ALIYUN_SMS_ACCESS_KEY_SECRET`
- `GENARRATIVE_LLM_API_KEY`
- `ALIYUN_OSS_ACCESS_KEY_ID`
- `ALIYUN_OSS_ACCESS_KEY_SECRET`
- `GENARRATIVE_ADMIN_PASSWORD`
报告中不记录具体值。
**影响:**
如果该文件已进入远端仓库或被团队成员拉取相关外部服务密钥、OSS/SMS/LLM/后台密码均应视为已泄露。即使后续从当前工作树删除,也不能撤销历史泄露风险。
**建议修复:**
1. 立即轮换 `.env.local` 中出现过的所有真实密钥、访问密钥、后台密码和 token。
2. 从 Git 跟踪中移除 `.env.local`,但不要删除本地私有文件:
```bash
git rm --cached .env.local
```
3. 按项目约束,不要在 `.gitignore` 中新增 `.env.local`;如果仓库已有其他机制管理本地私密 env应遵循既有约定。若没有应先补一份安全说明文档而不是提交真实 `.env.local`。
4. 将必要的占位示例保留在 `.env.example` 或 `deploy/env/api-server.env.example`,确保示例值不是可用密钥。
5. 如该文件已推送到远端历史,评估是否需要历史清理;无论是否清历史,密钥轮换都是必须步骤。
**验证:**
```bash
git ls-files --error-unmatch .env.local
# 预期:返回非 0表示不再跟踪
git diff --cached -- .env.local
# 预期:只显示从索引移除,不输出真实值到公开报告
```
## High
### H-1Vite 依赖存在高危 dev server 任意文件读取/路径遍历类 advisory
**证据:**
`npm audit` 显示:
- package`vite`
- direct dependency
- installed vulnerable range`<=6.4.1`
- severity`high`
- 相关 advisory 包括:
- `GHSA-p9ff-h696-f583`Vite dev server WebSocket 任意文件读取,高危。
- `GHSA-4w7w-66w2-5vf9`optimized deps `.map` 处理路径遍历,中危。
- 多个 `server.fs` / public directory 相关低中危问题。
- `fixAvailable=true`。
**影响:**
主要影响开发服务器和预览环境。如果开发机、测试机或内网联调环境将 Vite dev server 暴露给不可信网络,攻击者可能读取工作区文件或旁路 `server.fs` 限制。
**建议修复:**
1. 优先将 `vite` 升级到 npm audit 推荐的安全版本范围。
2. 升级后执行:
```bash
npm run check:encoding
npm run lint:eslint
npm run typecheck
npm run test
npm run build
```
3. 检查 `scripts/vite-cli.mjs`、`scripts/dev-web-rust.mjs`、Vite 配置中的 dev server host 暴露范围,开发环境避免绑定 `0.0.0.0` 或暴露到公网。
### H-2`@typescript-eslint/*` 链路经 `minimatch` 存在 ReDoS 高危 advisory
**证据:**
`npm audit` 显示:
- direct packages
- `@typescript-eslint/eslint-plugin`,当前范围 `6.16.0 - 7.5.0`high。
- `@typescript-eslint/parser`,当前范围 `6.16.0 - 7.5.0`high。
- transitive packages
- `@typescript-eslint/type-utils`
- `@typescript-eslint/typescript-estree`
- `@typescript-eslint/utils`
- `minimatch`
- `minimatch` advisory
- `GHSA-3ppc-4f35-3m26`
- `GHSA-7r86-cg39-jmmj`
- `GHSA-23c5-xmqv-rm74`
- npm 建议升级到 `@typescript-eslint/* 8.59.3`,属于 SemVer major。
**影响:**
主要影响 lint/构建工具链。如果 CI 或开发命令处理不可信 glob pattern可能造成 ReDoS。生产运行时直接影响较低但 CI 可用性和供应链安全仍应修复。
**建议修复:**
1. 单独开依赖升级分支,将 `@typescript-eslint/eslint-plugin` 与 `@typescript-eslint/parser` 升级到兼容 ESLint 8/TypeScript 5.8 的安全版本。
2. 因为是 major 升级,先阅读迁移说明并运行 ESLint 全量检查。
3. 验证:
```bash
npm run lint:eslint
npm run typecheck
npm run test
```
### H-3`picomatch` ReDoS / glob matching 高危 advisory
**证据:**
`npm audit` 显示:
- package`picomatch`
- severity`high`
- vulnerable range`4.0.0 - 4.0.3`
- advisory
- `GHSA-c2c7-rcm5-vvqj`extglob quantifiers ReDoS高危。
- `GHSA-3v7f-55p6-f55p`POSIX character classes method injection中危。
- `fixAvailable=true`。
**影响:**
主要影响依赖 picomatch 的构建、测试、文件匹配工具链。生产直接影响取决于是否在服务端运行时用它处理用户输入 glob当前未在扫描摘要中发现明显业务入口直接使用。
**建议修复:**
通过升级引入它的 direct dependency 来消除,不建议手工改 lockfile。
## Medium
### M-1`esbuild <=0.24.2` dev server 允许任意网站请求并读取响应
**证据:**
`npm audit` 显示:
- package`esbuild`
- severity`moderate`
- advisory`GHSA-67mh-4wv8-2f99`
- vulnerable range`<=0.24.2`
- `fixAvailable=true`。
**影响:**
主要影响开发服务器场景。若本地开发服务暴露到不可信网络,风险上升。
**建议修复:**
随 Vite / 构建链路升级一并修复,升级后跑前端检查与构建。
### M-2`postcss <8.5.10` CSS stringify XSS advisory
**证据:**
`npm audit` 显示:
- package`postcss`
- severity`moderate`
- advisory`GHSA-qx2v-qp2m-jg93`
- vulnerable range`<8.5.10`
- `fixAvailable=true`。
**影响:**
如果系统把不可信 CSS 内容 stringify 后注入页面,可能触发 XSS。当前项目是否存在这类业务入口需人工复核从依赖角度建议升级。
**建议修复:**
升级 Tailwind/Vite/PostCSS 链路带出的安全版本,并执行前端构建验证。
## Low
### L-1`jsdom` 链路低危 advisory
**证据:**
`npm audit` 显示:
- `jsdom` direct dependencyseverity low。
- transitive`http-proxy-agent`、`@tootallnate/once`。
- npm 建议升级到 `jsdom 29.1.1`SemVer major。
**影响:**
通常影响测试环境。若测试工具处理不可信 URL/代理输入,风险上升。
**建议修复:**
不要和 Vite/TypeScript ESLint 大升级混在一个提交里。单独升级 jsdom 后运行:
```bash
npm run test
```
## Unknown / 未完成项
### U-1Rust 依赖漏洞未完成扫描
**原因:**
本机没有 `cargo-audit`,本次没有擅自安装用户级 Cargo 工具。
**建议:**
如确认允许安装:
```bash
cargo install cargo-audit --locked
cargo audit --manifest-path server-rs/Cargo.toml
```
或在 CI/具备工具的环境执行并回填结果。
## Informational / 源码热点
### I-1JS/TS XSS / 动态执行热点
扫描命中 1 行:
- `src/routing/RouteImageReadyGate.test.ts` 中测试代码使用 `root.innerHTML`。
初步判断为测试环境构造 DOM不是生产漏洞。若后续发现生产代码使用 `dangerouslySetInnerHTML` 或直接 `innerHTML = userInput`,应升级为 High。
### I-2Node 脚本命令执行热点
扫描命中 21 行,主要集中在:
- `scripts/spacetime-migration-common.mjs`
- `scripts/run-bash-script.mjs`
- `scripts/generate-spacetime-bindings.mjs`
- `scripts/dev-web-rust.mjs`
初步判断为项目脚本启动 `spacetime`、bash、Vite 等工具的正常行为。后续人工复核重点:
- 是否使用固定命令和参数数组,而不是拼接 shell 字符串。
- 是否把用户输入直接作为命令或 shell 参数。
- 是否设置 `shell: true`。
### I-3Rust unwrap/expect、文件路径、CORS/Auth 热点较多
扫描命中:
- `rust-hotspots.txt`1348 行。
- `auth-cors-hotspots.txt`1157 行。
这些是热点,不等于漏洞。建议后续按模块分批人工复核:
1. `server-rs/crates/api-server/src/admin.rs`
2. `server-rs/crates/api-server/src/app.rs`
3. `server-rs/crates/platform-auth/src/**`
4. `server-rs/crates/platform-oss/src/**`
5. `server-rs/crates/platform-llm/src/**`
6. `server-rs/crates/spacetime-client/src/**`
重点看:生产 CORS、Cookie 安全属性、token 日志、路径拼接、外部 URL 下载、Data URL 大小限制、OSS 签名边界。
## 推荐修复顺序
1. 立即处理 C-1轮换 `.env.local` 里所有真实密钥,并从 Git 索引移除 `.env.local`。
2. 升级 `vite` 相关依赖,优先消除 dev server 任意文件读取/路径遍历 advisory。
3. 升级 `@typescript-eslint/*`,消除 minimatch 链路 ReDoS因 major 升级,单独提交。
4. 升级 `postcss` / `esbuild` / `picomatch` 的来源依赖。
5. 单独评估 `jsdom` major 升级。
6. 用户确认后安装或使用 CI 执行 `cargo audit`,补齐 Rust 依赖漏洞结论。
7. 对 `auth-cors-hotspots.txt` 和 `rust-hotspots.txt` 做模块级人工审计。
## 修复后的验证命令
```bash
npm run check:encoding
npm run lint:eslint
npm run typecheck
npm run test
npm run build
```
如修改后端安全、Auth、Cookie、CORS 或 API
```bash
cd server-rs && cargo test --workspace
npm run api-server
# 检查 /healthz并执行相关 API/auth smoke
```
如补齐 Rust audit
```bash
cargo audit --manifest-path server-rs/Cargo.toml
```
## 备注
- 本报告没有输出任何真实密钥值。
- `.hermes/plans/assets/security-scan-2026-05-11/secret-grep.txt` 可能包含敏感内容,仅用于本地排查;提交前应删除或替换为脱敏报告。
- 由于 `gitleaks` 未安装,本次密钥扫描只是 grep 兜底,不等价于完整 secrets audit。

View File

@@ -7,7 +7,7 @@
创作 Tab 恢复为模板选择入口,但不回到旧的大卡片选择面板:
1. 首屏保留现有创作页布局骨架顶部标题固定为“10分钟创作一个精品互动玩法”。
2. 选择模板入口改为横向 Tab数据来自 `src/config/newWorkEntryConfig.ts` 的可见玩法配置。
2. 选择模板入口改为横向 Tab数据来自 `GET /api/creation-entry/config` 返回的可见玩法配置。
3. 默认选中“拼图”模板,并在创作 Tab 内直接展示拼图创作表单。
4. 智能创作入口从可见模板中隐藏,保留既有 `creative-agent` 运行链路用于后续内部恢复或草稿目标打开。
5. 草稿、发现、我的等一级 Tab 职责不变,作品管理仍在草稿 Tab。
@@ -18,7 +18,7 @@
```text
标题10分钟创作一个精品互动玩法
模板 Tab拼图 / 方洞挑战 / 视觉小说 / AIRP
模板 Tab拼图 / 抓大鹅 / 视觉小说(敬请期待)/ AIRP
默认内容:拼图创作表单
```
@@ -34,21 +34,24 @@
1. 打开“创作”一级 Tab 时默认停留在拼图 Tab不主动创建拼图 session。
2. 点击拼图表单“生成草稿”后,才创建拼图 session 并执行 `compile_puzzle_draft`
3. 拼图表单内的模板按钮使用 `tablist / tab` 语义,点击后只填充画面描述。
4. 点击非拼图且已开放的模板 Tab 时,进入该玩法既有工作台;未开放模板保持禁用。
4. 点击非拼图且已开放的模板 Tab 时,进入该玩法既有工作台;视觉小说与 AIRP 当前保持敬请期待禁用
5. `creative-agent` 不出现在模板 Tab 和选择弹层中,不再作为创作 Tab 首屏入口。
6. 方洞挑战暂时从创作页完全隐藏,不出现在模板 Tab、旧选择弹层和创作 Hub 卡片中;既有作品链路继续保留。
## 4. 验收
1. 点击“创作”后首屏出现“10分钟创作一个精品互动玩法”。
2. 顶部选择模板入口为 Tab拼图 Tab 默认 `aria-selected=true`
3. 创作 Tab 默认显示拼图创作表单内容且不显示旧“Hi, 朋友”、输入框或智能创作快捷按钮。
4. 隐藏的智能创作类型不出现在模板 Tab、旧选择弹层和创作 Hub 卡片中。
4. 隐藏的智能创作类型与方洞挑战不出现在模板 Tab、旧选择弹层和创作 Hub 卡片中。
5. 草稿页返回创作页后仍回到同一模板入口,并可保留拼图表单草稿内容。
## 5. 嵌入式表单 UI 细节
2026-05-10 补充:抓大鹅与视觉小说作为创作 Tab 内嵌表单时,风格类横滑选择器应统一使用浅底卡片、柔和玫瑰色选中态和小圆点确认标记。不要使用大面积黑色渐变、黑底胶囊标签或高饱和红色外框,以免在输入框下方误读为错误提示。
2026-05-10 追加:视觉小说画风选项已改为使用 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2-all` 生成的参考图,作为横向卡片主视觉。
嵌入式表单控件保持以下口径:
1. 大文本输入框使用白底、低饱和边框和轻量 focus ring。

View File

@@ -114,3 +114,13 @@
- 未登录用户点击推荐页封面时,再次打开同一个登录弹窗;登录成功后由既有受保护动作继续进入作品详情或玩法入口。
- 未登录状态下点击“下一个”只切换下一张推荐封面,不触发登录弹窗,也不启动玩法。
- 已登录用户继续沿用推荐页内嵌运行态、上下滑切换和底部“下一个”行为。
## 10. 2026-05-11 草稿生成中与新完成标记
草稿生成过程页允许用户直接返回创作中心并自由使用平台其它功能:
- 点击生成过程页的返回按钮时,当前生成任务继续在后台执行,页面回到创作中心,不清空生成状态。
- 用户再进入草稿 Tab 并点击同一草稿时,若生成仍未完成,进入对应生成过程页查看最新进度;若已完成,直接进入对应结果页。
- 草稿作品卡在生成中展示“生成中”状态标记;新生成完成且用户尚未查看的草稿在卡片右上角展示红点。
- 底部一级“草稿”Tab 在存在未查看新完成草稿时展示红点用户点击查看带红点的作品后该作品红点消失。若草稿页已无任何带红点作品底部“草稿”Tab 红点同步消失。
- 生成完成时如果用户仍停留在对应生成过程页,可自动进入结果页;如果用户已经回到创作中心或其它功能页,不打断当前操作。

View File

@@ -124,7 +124,7 @@ Match3D 必须形成独立玩法域,后续技术方案至少需要覆盖:
3. 不做排行榜正式展示。
4. 不做道具,但需要预留功能口。
5. 不做洗牌、重置、旋转、放大等局内操作。
6. 不做真实 3D 模型。
6. 不做多批次真实 3D 模型生成;当前草稿生成只固定产出 `3` 个 GLB 模型并写入 OSS
7. 不做真实 3D 物理遮挡。
8. 不做真实物理碰撞结算。
9. 不做必须试玩通关才能发布的门槛。
@@ -161,7 +161,7 @@ Match3D 首版参考拼图后期的入口表单收集方式,而不是早期的
题材决定后续生成或选择物品素材的方向。用户可以自定义主题,例如水果、玩具、食物、符号等。
首版 demo 不接入真实图片生成。当前运行态可消除物统一使用题材方向的 25 个积木件类型表现,不使用透明气泡,也不在图案上放文字标识。前端首版用差异化颜色、积木造型和 3D 程序化模型表现可消除物,避免玩家在堆叠状态下难以辨认。
当前抓大鹅草稿生成会固定生成 `3` 个题材物品:素材图切割出的独立图片会作为 Rodin 图生 3D 参考图,生成出的 GLB 模型必须转存 OSS并随作品 profile 的 `generatedItemAssets` 持久化。运行态优先使用这些生成模型;只有模型缺失、加载失败或未进入 3D 渲染模式时,才回退到 25 个默认积木件视觉键。默认素材不使用透明气泡,也不在图案上放文字标识。
可消除物尺寸使用五档相对体积规则XL 型相对体积为 `1.60~2.30`L 型为 `1.25~1.60`M 型为 `1.00`XS 型为 `0.65~0.85`S 型为 `0.35~0.50`。单局中 XL / L / M / XS / S 按本局使用的消除物类型数的 `20% / 30% / 30% / 15% / 5%` 分配;非整数配额按最大余数补齐,确保总数等于本局使用类型数量。同一关卡内同一个颜色和造型的物品只能对应一个尺寸档位;可存在同尺寸但不同颜色和造型的物品。后端运行态通过 `radius` 下发权威尺寸,前端只按快照表现。
@@ -195,7 +195,7 @@ Match3D 首版参考拼图后期的入口表单收集方式,而不是早期的
## 6.3 参考图片
抓大鹅入口页不展示参考图片上传。题材表现由题材文本和草稿切割图片链路承接;后续需要 3D 模型时,在结果页 `3D素材` Tab 以切割图片作为图生模型参考图手动触发
抓大鹅入口页不展示参考图片上传。题材表现由题材文本和草稿切割图片链路承接;草稿生成阶段会直接以切割图片作为 Rodin 图生模型参考图生成首批 GLB。结果页 `3D素材` Tab 仍可对单个素材触发重新生成
---
@@ -222,9 +222,9 @@ Match3D 首版参考拼图后期的入口表单收集方式,而不是早期的
## 7.3 素材生成边界
抓大鹅草稿生成链路会生成首批 `3` 个题材物品素材文本模型生成物品名VectorEngine 生成 `2*2` 素材图并切割独立图片。入口页选择的 `assetStylePrompt` 必须写入素材图提示词;结果页手动 Rodin 图生模型时,继续以该物品图片和默认提示词作为起点。
抓大鹅草稿生成链路会生成首批 `3` 个题材物品素材文本模型生成物品名VectorEngine 生成 `2*2` 素材图并切割独立图片,再以每张独立图片调用 Rodin 图生 3D下载 `.glb` 并转存 OSS。入口页选择的 `assetStylePrompt` 必须写入素材图提示词;结果页手动 Rodin 图生模型时,继续以该物品图片和默认提示词作为起点。
生成出的独立图片作为草稿页 `3D素材` Tab 的预览资产返回,状态为 `image_ready`模型文件为空。正式平台资产绑定、Rodin 生成模型转存和二次编辑流程以后续技术方案为准。
生成出的独立图片与 GLB 模型都必须作为草稿页 `3D素材` Tab 的预览资产返回。模型生成成功时 `generatedItemAssets[].status = model_ready`,并携带 `modelSrc``modelObjectKey``modelFileName``taskUuid``subscriptionKey`;正式平台资产绑定和更完整的二次编辑流程以后续技术方案为准。
## 7.4 发布前试玩
@@ -297,12 +297,14 @@ itemTypeCount = clearCount <= 25 ? clearCount : 25
## 8.5 物品资产
首版 demo 使用 2D 图案素材
当前 demo 使用生成 GLB 优先、默认积木兜底的物品资产策略
1. demo 至少提供 `25` 种彼此不同的颜色与几何造型组合素材,支撑 `clearCount > 25` 时的类型上限。
2. 当前 demo 使用 25 个积木件视觉键作为默认素材池;前端首版必须把这些视觉键映射为无文字的纯色 2D 图标和程序化 3D 积木模型,不能显示为透明气泡或文字标记
3. 后续可以尝试替换为伪 3D 或 3D 模型
4. 用户题材主题后续会映射为符合常识预期的物品集合
1. demo 至少提供 `25` 种彼此不同的颜色与几何造型组合默认素材,支撑 `clearCount > 25` 时的类型上限和 GLB 缺失兜底
2. `generatedItemAssets[].modelSrc``modelObjectKey` 时,运行态与备选栏必须优先读取该 GLB默认积木件只作为加载失败、模型缺失或 2D 回退时的兜底素材池
3. 前端读取生成模型必须通过 `/api/assets/read-bytes` 获取私有 OSS 字节,再交给 Three.js `GLTFLoader` 解析;不得直接把 `/generated-match3d-assets/...` 当裸 URL 请求
4. 当前固定 `clearCount = 3` 的生成草稿中,运行态 `match3d-type-01/02/03` 按类型编号顺序映射到生成出的 `3` 个模型;后续恢复更大生成数量时,模型列表顺序必须继续与类型编号稳定对应
5. 默认积木视觉键仍需映射为无文字的纯色 2D 图标和程序化 3D 积木模型,不能显示为透明气泡或文字标记。
6. 用户题材主题后续会映射为符合常识预期的物品集合。
示例:水果题材可以对应红色苹果、黄色香蕉、紫色葡萄等。
@@ -706,10 +708,10 @@ GET /api/runtime/match3d/runs/:runId
3. 入口页不展示参考图上传。
4. 内置风格显示画风参考图,自定义风格通过独立面板填写并进入提交 payload。
5. 移动端入口页所有内容一屏展示,不产生纵向滚动。
6. 系统可生成待发布结果页,并在草稿中返回首批切割图片素材预览。
6. 系统可生成待发布结果页,并在草稿中返回首批切割图片与 OSS GLB 模型素材预览。
7. 用户可编辑游戏名称、标签、封面图等基础信息。
8. 用户可发布前试玩,且试玩失败不阻断发布。
9. 运行态能展示圆形空间、倒计时、物品和 `7` 格备选栏。
9. 运行态能展示圆形空间、倒计时、物品和 `7` 格备选栏;存在 `generatedItemAssets` 模型时必须优先展示生成 GLB而不是默认积木素材
10. 物品可重叠、遮挡、堆叠。
11. 被完全遮挡物品不可点击,露出可点击区域的物品可点击。
12. 点击通过后物品飞入备选栏。

View File

@@ -50,18 +50,18 @@
外部仓库文件只作为玩法语义参考,不能作为目录结构迁入依据:
| 外部参考 | 可参考内容 | Genarrative 落点 |
| --- | --- | --- |
| `interface/routes/visual.js` | 视觉小说创作和运行时路由语义 | `server-rs/crates/api-server/src/visual_novel.rs` |
| `interface/handlers/visual/sendActionStream.js` | 流式动作推进事件 | Axum SSE handler + `shared-contracts` typed envelope |
| `interface/handlers/visual/getHistory.js` | 历史记录读取 | 平台 runtime history API |
| `interface/handlers/visual/saves.js` | 存档语义 | 平台统一 `profile/save-archives` |
| `services/visual/gameLogic.js` | step 解析、会话状态推进 | `server-rs/crates/module-visual-novel` |
| `services/visual/storyGeneration.js` | 创作底稿生成语义 | `platform-agent` / `platform-llm` + visual novel Tool |
| `prompts/visual.gm.system/1.0.1/body.js` | 视觉小说 GM 输出结构经验 | 新 prompt 必须适配 Genarrative 契约,不原样照搬平台规则 |
| `src/page/Galgame.tsx` | 运行时界面结构 | `src/components/visual-novel-runtime/VisualNovelRuntimeShell.tsx` |
| `src/hooks/galgame/useGalgameController.ts` | 前端运行时状态组织 | visual novel runtime hooks |
| `src/page/GameSettingsEditor.tsx` | 创作编辑器模块划分 | visual novel result / workspace 组件 |
| 外部参考 | 可参考内容 | Genarrative 落点 |
| ----------------------------------------------- | ---------------------------- | ----------------------------------------------------------------- |
| `interface/routes/visual.js` | 视觉小说创作和运行时路由语义 | `server-rs/crates/api-server/src/visual_novel.rs` |
| `interface/handlers/visual/sendActionStream.js` | 流式动作推进事件 | Axum SSE handler + `shared-contracts` typed envelope |
| `interface/handlers/visual/getHistory.js` | 历史记录读取 | 平台 runtime history API |
| `interface/handlers/visual/saves.js` | 存档语义 | 平台统一 `profile/save-archives` |
| `services/visual/gameLogic.js` | step 解析、会话状态推进 | `server-rs/crates/module-visual-novel` |
| `services/visual/storyGeneration.js` | 创作底稿生成语义 | `platform-agent` / `platform-llm` + visual novel Tool |
| `prompts/visual.gm.system/1.0.1/body.js` | 视觉小说 GM 输出结构经验 | 新 prompt 必须适配 Genarrative 契约,不原样照搬平台规则 |
| `src/page/Galgame.tsx` | 运行时界面结构 | `src/components/visual-novel-runtime/VisualNovelRuntimeShell.tsx` |
| `src/hooks/galgame/useGalgameController.ts` | 前端运行时状态组织 | visual novel runtime hooks |
| `src/page/GameSettingsEditor.tsx` | 创作编辑器模块划分 | visual novel result / workspace 组件 |
---
@@ -69,7 +69,7 @@
### 3.1 必须完成的模板闭环
1. 平台创作中心展示 `视觉小说` 入口,并在实现完成后从 `open: false` 改为 `open: true`
1. 平台创作中心展示 `视觉小说` 入口2026-05-11 起当前运营状态回退为 `open: false`,入口显示敬请期待,不允许创建新视觉小说草稿
2. 创作者可选择 `idea``document``blank` 三种起点创建视觉小说底稿。
3. Agent 或表单工作台生成 / 编辑同一份 `VisualNovelResultDraft`
4. 结果页可编辑世界观、角色、场景、剧情阶段、资产和运行时配置。
@@ -160,8 +160,8 @@
5. 玩家身份。
6. 3 到 6 个主要角色。
7. 3 到 8 个可用场景。
7. 3 到 6 个剧情阶段。
8. 初始场景、初始旁白和第一轮可选行动。
8. 3 到 6 个剧情阶段。
9. 初始场景、初始旁白和第一轮可选行动。
### 5.2 文档创建 `document`
@@ -399,7 +399,15 @@ export type VisualNovelAgentActionKind =
```ts
export type VisualNovelAgentStreamEvent =
| { type: 'start'; sessionId: string }
| { type: 'phase'; phase: 'perception' | 'reasoning' | 'drafting' | 'reflection' | 'finalizing' }
| {
type: 'phase';
phase:
| 'perception'
| 'reasoning'
| 'drafting'
| 'reflection'
| 'finalizing';
}
| { type: 'text_delta'; text: string }
| { type: 'draft_patch'; patch: VisualNovelDraftPatch }
| { type: 'action_required'; action: VisualNovelAgentPendingAction }
@@ -558,35 +566,35 @@ export type VisualNovelRuntimeStreamEvent =
### 9.1 创作 session
| 方法 | 路由 | 用途 |
| --- | --- | --- |
| `POST` | `/api/creation/visual-novel/sessions` | 创建视觉小说创作 session |
| `GET` | `/api/creation/visual-novel/sessions/{session_id}` | 读取 session snapshot |
| `POST` | `/api/creation/visual-novel/sessions/{session_id}/messages` | 非流式发送创作消息 |
| `POST` | `/api/creation/visual-novel/sessions/{session_id}/messages/stream` | 流式发送创作消息 |
| `POST` | `/api/creation/visual-novel/sessions/{session_id}/actions` | 执行结构化创作 action |
| `POST` | `/api/creation/visual-novel/sessions/{session_id}/compile` | 编译为 work profile 草稿 |
| 方法 | 路由 | 用途 |
| ------ | ------------------------------------------------------------------ | ------------------------ |
| `POST` | `/api/creation/visual-novel/sessions` | 创建视觉小说创作 session |
| `GET` | `/api/creation/visual-novel/sessions/{session_id}` | 读取 session snapshot |
| `POST` | `/api/creation/visual-novel/sessions/{session_id}/messages` | 非流式发送创作消息 |
| `POST` | `/api/creation/visual-novel/sessions/{session_id}/messages/stream` | 流式发送创作消息 |
| `POST` | `/api/creation/visual-novel/sessions/{session_id}/actions` | 执行结构化创作 action |
| `POST` | `/api/creation/visual-novel/sessions/{session_id}/compile` | 编译为 work profile 草稿 |
### 9.2 作品草稿与发布
| 方法 | 路由 | 用途 |
| --- | --- | --- |
| `GET` | `/api/creation/visual-novel/works` | 读取当前用户视觉小说作品草稿列表 |
| `GET` | `/api/creation/visual-novel/works/{profile_id}` | 读取作品详情 |
| `PUT/PATCH` | `/api/creation/visual-novel/works/{profile_id}` | 更新作品草稿 |
| `DELETE` | `/api/creation/visual-novel/works/{profile_id}` | 删除未发布草稿或用户自有作品 |
| `POST` | `/api/creation/visual-novel/works/{profile_id}/publish` | 发布到平台作品体系 |
| 方法 | 路由 | 用途 |
| ----------- | ------------------------------------------------------- | -------------------------------- |
| `GET` | `/api/creation/visual-novel/works` | 读取当前用户视觉小说作品草稿列表 |
| `GET` | `/api/creation/visual-novel/works/{profile_id}` | 读取作品详情 |
| `PUT/PATCH` | `/api/creation/visual-novel/works/{profile_id}` | 更新作品草稿 |
| `DELETE` | `/api/creation/visual-novel/works/{profile_id}` | 删除未发布草稿或用户自有作品 |
| `POST` | `/api/creation/visual-novel/works/{profile_id}/publish` | 发布到平台作品体系 |
### 9.3 运行时
| 方法 | 路由 | 用途 |
| --- | --- | --- |
| `GET` | `/api/runtime/visual-novel/gallery` | 读取平台聚合后的视觉小说公开作品列表 |
| `POST` | `/api/runtime/visual-novel/works/{profile_id}/runs` | 创建测试或正式 run |
| `GET` | `/api/runtime/visual-novel/runs/{run_id}` | 读取 run snapshot |
| `POST` | `/api/runtime/visual-novel/runs/{run_id}/actions/stream` | 提交选择 / 自由行动并流式推进 |
| `GET` | `/api/runtime/visual-novel/runs/{run_id}/history` | 读取当前 run 历史 |
| `POST` | `/api/runtime/visual-novel/runs/{run_id}/regenerate` | 从历史节点重生成 |
| 方法 | 路由 | 用途 |
| ------ | -------------------------------------------------------- | ------------------------------------ |
| `GET` | `/api/runtime/visual-novel/gallery` | 读取平台聚合后的视觉小说公开作品列表 |
| `POST` | `/api/runtime/visual-novel/works/{profile_id}/runs` | 创建测试或正式 run |
| `GET` | `/api/runtime/visual-novel/runs/{run_id}` | 读取 run snapshot |
| `POST` | `/api/runtime/visual-novel/runs/{run_id}/actions/stream` | 提交选择 / 自由行动并流式推进 |
| `GET` | `/api/runtime/visual-novel/runs/{run_id}/history` | 读取当前 run 历史 |
| `POST` | `/api/runtime/visual-novel/runs/{run_id}/regenerate` | 从历史节点重生成 |
### 9.4 存档
@@ -627,16 +635,16 @@ GET /api/runtime/profile/save-archives
### 10.1 crate 职责
| crate / 层 | 职责 |
| --- | --- |
| crate / 层 | 职责 |
| -------------------------------------- | ------------------------------------------------------------- |
| `server-rs/crates/module-visual-novel` | 纯领域规则草稿校验、step 解析、run 状态推进、历史重生成边界 |
| `server-rs/crates/shared-contracts` | DTO、请求响应、SSE envelope、草稿和运行时契约 |
| `server-rs/crates/spacetime-module` | 表、reducer、procedure、migration 和事务编排 |
| `server-rs/crates/spacetime-client` | api-server 到 SpacetimeDB 的 typed facade |
| `server-rs/crates/api-server` | Axum 路由、鉴权、SSE、LLM 编排、资产和钱包 facade 调用 |
| `server-rs/crates/platform-llm` | 视觉小说创作和运行时 GM 的模型调用 |
| `server-rs/crates/platform-oss` | 文档、图片、音乐等资产对象读写 |
| `server-rs/crates/platform-agent` | 如复用创意 Agent负责 Agent 编排和工具注册 |
| `server-rs/crates/shared-contracts` | DTO、请求响应、SSE envelope、草稿和运行时契约 |
| `server-rs/crates/spacetime-module` | 表、reducer、procedure、migration 和事务编排 |
| `server-rs/crates/spacetime-client` | api-server 到 SpacetimeDB 的 typed facade |
| `server-rs/crates/api-server` | Axum 路由、鉴权、SSE、LLM 编排、资产和钱包 facade 调用 |
| `server-rs/crates/platform-llm` | 视觉小说创作和运行时 GM 的模型调用 |
| `server-rs/crates/platform-oss` | 文档、图片、音乐等资产对象读写 |
| `server-rs/crates/platform-agent` | 如复用创意 Agent负责 Agent 编排和工具注册 |
### 10.2 领域规则
@@ -654,14 +662,14 @@ GET /api/runtime/profile/save-archives
新增表必须同步 `migration.rs`、表目录和 bindings
| 表 | 用途 |
| --- | --- |
| `visual_novel_agent_session` | 创作 session 主表 |
| `visual_novel_agent_message` | 创作消息和模型回复 |
| `visual_novel_work_profile` | 视觉小说作品草稿 / 发布 profile |
| `visual_novel_runtime_run` | 玩家或测试 run |
| `visual_novel_runtime_history_entry` | 运行时历史 |
| `visual_novel_runtime_event` | 可审计事件,不用于回放 |
| 表 | 用途 |
| ------------------------------------ | ------------------------------- |
| `visual_novel_agent_session` | 创作 session 主表 |
| `visual_novel_agent_message` | 创作消息和模型回复 |
| `visual_novel_work_profile` | 视觉小说作品草稿 / 发布 profile |
| `visual_novel_runtime_run` | 玩家或测试 run |
| `visual_novel_runtime_history_entry` | 运行时历史 |
| `visual_novel_runtime_event` | 可审计事件,不用于回放 |
不得新增:
@@ -684,27 +692,27 @@ GET /api/runtime/profile/save-archives
### 11.1 入口配置
当前 `src/config/newWorkEntryConfig.ts` 已存在
入口配置事实源已经迁移到 SpacetimeDB 的 `creation_entry_type_config` 表,默认种子位于 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`。2026-05-11 起视觉小说当前默认状态为
```ts
{
id: 'visual-novel',
title: '视觉小说',
subtitle: '敬请期待',
subtitle: '分支叙事体验',
badge: '敬请期待',
visible: true,
open: false,
}
```
实现完成后更新为:
重新开放创建时再通过后台入口开关或默认种子更新为:
```ts
{
id: 'visual-novel',
title: '视觉小说',
subtitle: 'AI 生成可玩的视觉小说',
badge: '新玩法',
subtitle: '分支叙事体验',
badge: '可创建',
visible: true,
open: true,
}
@@ -781,6 +789,8 @@ service client 要复用现有请求封装、鉴权和错误提示风格,不
3. 生成草稿按钮。
4. 错误、忙碌与禁用态。
视觉画风卡片使用 `public/visual-novel-style-references/` 下的 `gpt-image-2-all` 参考图,分别对应映画动画、水彩绘本、像素霓虹、水墨幻想、柔彩校园和暗色哥特。卡片只承载图像和标签,不额外展示说明文案。
点击生成草稿后进入 `visual-novel-generating` 过程页,过程页复用平台已有生成进度面板,展示一句话输入、画风和草稿生成阶段;完成后自动进入 `visual-novel-result` 草稿页。
不展示:
@@ -975,13 +985,13 @@ V1 默认提供平台统一存档能力;如果平台存档 UI 当前按槽位
### 16.1 并行批次总览
| 批次 | 可并行任务 | 进入条件 | 汇合点 |
| --- | --- | --- | --- |
| Batch 0 | `VN-00` | PRD 已冻结 | 所有人以本文为唯一口径 |
| Batch 1 | `VN-01``VN-02``VN-03``VN-04` | `VN-00` 完成 | 契约、领域、UI 骨架、Prompt 口径可对齐 |
| Batch 2 | `VN-05``VN-06``VN-07``VN-08` | `VN-01` 产出契约初稿;`VN-02` 产出表草案 | 后端 API、前端创作、前端运行时可联调 |
| Batch 3 | `VN-09``VN-10``VN-11` | `VN-05``VN-06``VN-07``VN-08` 主链路可跑 | 发布、存档、广场、负向扫描收口 |
| Batch 4 | `VN-12``VN-13` | 主链路完成 | 全链路验收和文档同步 |
| 批次 | 可并行任务 | 进入条件 | 汇合点 |
| ------- | ---------------------------------- | --------------------------------------------- | -------------------------------------- |
| Batch 0 | `VN-00` | PRD 已冻结 | 所有人以本文为唯一口径 |
| Batch 1 | `VN-01``VN-02``VN-03``VN-04` | `VN-00` 完成 | 契约、领域、UI 骨架、Prompt 口径可对齐 |
| Batch 2 | `VN-05``VN-06``VN-07``VN-08` | `VN-01` 产出契约初稿;`VN-02` 产出表草案 | 后端 API、前端创作、前端运行时可联调 |
| Batch 3 | `VN-09``VN-10``VN-11` | `VN-05``VN-06``VN-07``VN-08` 主链路可跑 | 发布、存档、广场、负向扫描收口 |
| Batch 4 | `VN-12``VN-13` | 主链路完成 | 全链路验收和文档同步 |
并行规则:
@@ -992,22 +1002,22 @@ V1 默认提供平台统一存档能力;如果平台存档 UI 当前按槽位
### 16.2 并行任务具体要求速查表
| 任务 | 可并行窗口 | 输入依赖 | 必须完成 | 禁止事项 | 验收口径 |
| --- | --- | --- | --- | --- | --- |
| `VN-00` 口径冻结 | Batch 0 | 本 PRD、旧 TXT 文档、Hermes 决策记录 | 冻结 `visual-novel` 只做模板玩法、平台接口、删除回放;标注旧文档冲突口径 | 不再使用“原样迁入外部平台工程”作为实现目标 | 文档和 Hermes 记录明确三条硬边界;`npm run check:encoding` 通过 |
| `VN-01` 契约与领域 | Batch 1 | PRD 第 6 到 8 章契约 | TS contracts、Rust shared-contracts、`module-visual-novel`、领域单测 | 不写 HTTP、DB reducer、LLM、UI不出现 replay 类型 | TS/Rust 契约一致草稿校验、step 解析、状态推进、重生成测试通过 |
| `VN-02` SpacetimeDB 与 facade | Batch 1 | `VN-01` 契约草案、SpacetimeDB 约束文档 | 表、reducer/procedure、migration、表目录、bindings、`spacetime-client` facade | 不手改 bindings 绕过 schema不建 replay 表或私有 save 表 | schema 可生成;表目录写明 event 不是回放;`cargo check -p spacetime-client` 通过 |
| `VN-03` Prompt / LLM 工具 | Batch 1 | `VN-01` draft 与 step 契约 | 创作 prompt、运行 GM prompt、repair prompt、工具参数 | 不照搬外部平台规则、扣费规则、回放规则;不让前端猜业务 step | fixture 输出可解析;坏格式进入 repair 或可重试错误 |
| `VN-04` 前端 UI 骨架 | Batch 1 | PRD 第 12 章 UI 要求 | workspace、result、runtime mock 骨架;移动端优先布局;独立面板模式 | 不接真实 API不写规则长文不出现回放入口 | 桌面 / 移动基础布局可检查;长文本不撑破按钮 |
| `VN-05` API Server | Batch 2 | `VN-01``VN-02`,真实 LLM 依赖 `VN-03` | creation、works、runtime、history、regenerate 路由SSE平台 save archive 接入 | 不把领域规则塞 handler不新增 replay 路由;不新增私有 save API | `/api/creation/visual-novel/*``/api/runtime/visual-novel/*` 可 smoke`cargo check -p api-server` 通过 |
| `VN-06` 前端 service 与入口 | Batch 2 | `VN-01` TS 契约草案mock 可先行 | service client、selection stage、入口分流、壳层挂载、登录态清理 | 不设计表单细节;不写 runtime UI 细节;不绕过平台入口 | 创作中心可进入 workspaceservice 路由与 PRD 一致;`npm run typecheck` 通过 |
| `VN-07` 创作工作台与结果页 | Batch 2 | `VN-04``VN-06`,真实生成依赖 `VN-05` | `idea` / `document` / `blank`、Agent 进度、结果页表单、保存草稿、测试 run 入口 | 不做正式玩家 runtime不新增说明页复杂内容不在卡片下展开 | 三种创建起点可用;草稿写入 `VisualNovelResultDraft`;发布校验 issue 可见 |
| `VN-08` 前端运行时 | Batch 2 | `VN-04``VN-06`,真实运行依赖 `VN-05` | runtime shell、SSE 消费、step 渲染、历史、设置、存档、重生成 | 不做回放 UI历史不生成分享播放页`raw_text` 不作为业务真相 | typed step 可渲染历史与重生成可用save archive 可继续体验 |
| `VN-09` 作品架 / 广场 / 发布 | Batch 3 | `VN-05` works/gallery API、`VN-06` 壳层 | work summary 聚合、作品架、公开聚合、public work code、详情跳转 | 不新增独立视觉小说市场;不迁入外部平台详情页;不做分享回放 | 发布后作品架可见;公开作品可启动 run聚合 key 不冲突 |
| `VN-10` 资产与文档输入 | Batch 3 | `VN-05` action/API、平台资产接口 | 文档资产读取、封面/场景/角色/音乐资产引用、可选图片生成 action | 不迁入外部 R2不存大 Data URL不绕过资产鉴权 | 文档创建使用资产引用SpacetimeDB 不保存二进制或大 base64 |
| `VN-11` 负向扫描 | Batch 1 到发布前 | 所有任务增量改动 | 扫描 replay 和外部平台功能;补负向测试或脚本清单 | 不把历史误判为回放;不做大范围重构 | 新工程代码无 replay外部平台账号/订单/会员/后台等未误入 |
| `VN-12` 全链路验收 | Batch 4 | `VN-05``VN-10` 主链路完成 | 创作、结果页、测试 run、发布、正式 run、历史、重生成、存档联调 | 不跳过移动端;不只测 mock不忽略负向验收 | 全链路可跑;前端 typecheck、后端相关 cargo 检查、编码检查通过 |
| `VN-13` 文档与交接 | Batch 4 | 实际工程落地结果 | PRD、技术方案、表目录、Hermes 决策 / 踩坑同步 | 不让旧 TXT 文档重新成为实现口径;不留下过期接口描述 | 新开发者可按最新文档维护;文档与代码一致 |
| 任务 | 可并行窗口 | 输入依赖 | 必须完成 | 禁止事项 | 验收口径 |
| ----------------------------- | ---------------- | --------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `VN-00` 口径冻结 | Batch 0 | 本 PRD、旧 TXT 文档、Hermes 决策记录 | 冻结 `visual-novel` 只做模板玩法、平台接口、删除回放;标注旧文档冲突口径 | 不再使用“原样迁入外部平台工程”作为实现目标 | 文档和 Hermes 记录明确三条硬边界;`npm run check:encoding` 通过 |
| `VN-01` 契约与领域 | Batch 1 | PRD 第 6 到 8 章契约 | TS contracts、Rust shared-contracts、`module-visual-novel`、领域单测 | 不写 HTTP、DB reducer、LLM、UI不出现 replay 类型 | TS/Rust 契约一致草稿校验、step 解析、状态推进、重生成测试通过 |
| `VN-02` SpacetimeDB 与 facade | Batch 1 | `VN-01` 契约草案、SpacetimeDB 约束文档 | 表、reducer/procedure、migration、表目录、bindings、`spacetime-client` facade | 不手改 bindings 绕过 schema不建 replay 表或私有 save 表 | schema 可生成;表目录写明 event 不是回放;`cargo check -p spacetime-client` 通过 |
| `VN-03` Prompt / LLM 工具 | Batch 1 | `VN-01` draft 与 step 契约 | 创作 prompt、运行 GM prompt、repair prompt、工具参数 | 不照搬外部平台规则、扣费规则、回放规则;不让前端猜业务 step | fixture 输出可解析;坏格式进入 repair 或可重试错误 |
| `VN-04` 前端 UI 骨架 | Batch 1 | PRD 第 12 章 UI 要求 | workspace、result、runtime mock 骨架;移动端优先布局;独立面板模式 | 不接真实 API不写规则长文不出现回放入口 | 桌面 / 移动基础布局可检查;长文本不撑破按钮 |
| `VN-05` API Server | Batch 2 | `VN-01``VN-02`,真实 LLM 依赖 `VN-03` | creation、works、runtime、history、regenerate 路由SSE平台 save archive 接入 | 不把领域规则塞 handler不新增 replay 路由;不新增私有 save API | `/api/creation/visual-novel/*``/api/runtime/visual-novel/*` 可 smoke`cargo check -p api-server` 通过 |
| `VN-06` 前端 service 与入口 | Batch 2 | `VN-01` TS 契约草案mock 可先行 | service client、selection stage、入口分流、壳层挂载、登录态清理 | 不设计表单细节;不写 runtime UI 细节;不绕过平台入口 | 创作中心可进入 workspaceservice 路由与 PRD 一致;`npm run typecheck` 通过 |
| `VN-07` 创作工作台与结果页 | Batch 2 | `VN-04``VN-06`,真实生成依赖 `VN-05` | `idea` / `document` / `blank`、Agent 进度、结果页表单、保存草稿、测试 run 入口 | 不做正式玩家 runtime不新增说明页复杂内容不在卡片下展开 | 三种创建起点可用;草稿写入 `VisualNovelResultDraft`;发布校验 issue 可见 |
| `VN-08` 前端运行时 | Batch 2 | `VN-04``VN-06`,真实运行依赖 `VN-05` | runtime shell、SSE 消费、step 渲染、历史、设置、存档、重生成 | 不做回放 UI历史不生成分享播放页`raw_text` 不作为业务真相 | typed step 可渲染历史与重生成可用save archive 可继续体验 |
| `VN-09` 作品架 / 广场 / 发布 | Batch 3 | `VN-05` works/gallery API、`VN-06` 壳层 | work summary 聚合、作品架、公开聚合、public work code、详情跳转 | 不新增独立视觉小说市场;不迁入外部平台详情页;不做分享回放 | 发布后作品架可见;公开作品可启动 run聚合 key 不冲突 |
| `VN-10` 资产与文档输入 | Batch 3 | `VN-05` action/API、平台资产接口 | 文档资产读取、封面/场景/角色/音乐资产引用、可选图片生成 action | 不迁入外部 R2不存大 Data URL不绕过资产鉴权 | 文档创建使用资产引用SpacetimeDB 不保存二进制或大 base64 |
| `VN-11` 负向扫描 | Batch 1 到发布前 | 所有任务增量改动 | 扫描 replay 和外部平台功能;补负向测试或脚本清单 | 不把历史误判为回放;不做大范围重构 | 新工程代码无 replay外部平台账号/订单/会员/后台等未误入 |
| `VN-12` 全链路验收 | Batch 4 | `VN-05``VN-10` 主链路完成 | 创作、结果页、测试 run、发布、正式 run、历史、重生成、存档联调 | 不跳过移动端;不只测 mock不忽略负向验收 | 全链路可跑;前端 typecheck、后端相关 cargo 检查、编码检查通过 |
| `VN-13` 文档与交接 | Batch 4 | 实际工程落地结果 | PRD、技术方案、表目录、Hermes 决策 / 踩坑同步 | 不让旧 TXT 文档重新成为实现口径;不留下过期接口描述 | 新开发者可按最新文档维护;文档与代码一致 |
每个任务的交付回复必须包含:
@@ -1143,20 +1153,20 @@ cd server-rs
cargo check -p spacetime-client
```
阻塞关系:
1. 依赖 `VN-01` 的 Rust 契约。
2. 阻塞 `VN-05` 的真实数据库联调。
阻塞关系:
VN-02 实施收口记录2026-05-05
1. 依赖 `VN-01` 的 Rust 契约。
2. 阻塞 `VN-05` 的真实数据库联调。
1. 已新增 `server-rs/crates/spacetime-module/src/visual_novel.rs`,落地 `visual_novel_agent_session``visual_novel_agent_message``visual_novel_work_profile``visual_novel_runtime_run``visual_novel_runtime_history_entry``visual_novel_runtime_event` 六张表及对应 procedure。
2. 已同步 `server-rs/crates/spacetime-module/src/lib.rs``server-rs/crates/spacetime-module/src/migration.rs``docs/technical/SPACETIMEDB_TABLE_CATALOG.md` 和 Rust bindings。
3. 已新增 `server-rs/crates/spacetime-client/src/visual_novel.rs` typed facade并从 `server-rs/crates/spacetime-client/src/lib.rs` 导出视觉小说 record / input 类型
4. `visual_novel_runtime_event` 只作为 `public event` 审计事件表使用;`visual_novel_runtime_history_entry` 只保存继续体验与历史重生成所需 step 和快照哈希,二者均不是 replay 数据源
5. 本阶段未新增 Axum 路由、LLM prompt、前端 UI、replay 表、replay 路由或私有 save 表;后续 VN-05 只应通过本阶段 facade 接入真实数据库
### VN-03创作与运行 Prompt / LLM 工具
VN-02 实施收口记录2026-05-05
1. 已新增 `server-rs/crates/spacetime-module/src/visual_novel.rs`,落地 `visual_novel_agent_session``visual_novel_agent_message``visual_novel_work_profile``visual_novel_runtime_run``visual_novel_runtime_history_entry``visual_novel_runtime_event` 六张表及对应 procedure
2. 已同步 `server-rs/crates/spacetime-module/src/lib.rs``server-rs/crates/spacetime-module/src/migration.rs``docs/technical/SPACETIMEDB_TABLE_CATALOG.md` 和 Rust bindings
3. 已新增 `server-rs/crates/spacetime-client/src/visual_novel.rs` typed facade并从 `server-rs/crates/spacetime-client/src/lib.rs` 导出视觉小说 record / input 类型
4. `visual_novel_runtime_event` 只作为 `public event` 审计事件表使用;`visual_novel_runtime_history_entry` 只保存继续体验与历史重生成所需 step 和快照哈希,二者均不是 replay 数据源。
5. 本阶段未新增 Axum 路由、LLM prompt、前端 UI、replay 表、replay 路由或私有 save 表;后续 VN-05 只应通过本阶段 facade 接入真实数据库。
### VN-03创作与运行 Prompt / LLM 工具
负责范围:
@@ -1744,7 +1754,7 @@ VN-11 从 Batch 1 开始持续运行,最终阻塞发布。
### 17.1 正向验收
1. `visual-novel` 入口可见并可点击创建。
1. `visual-novel` 入口当前可见但处于敬请期待禁用态;重新开放 `open=true` 后,再验收点击创建闭环
2. 一句话创建能生成可编辑底稿。
3. 文档创建能读取平台文档资产并生成底稿。
4. 空白创建能进入结果页。

View File

@@ -65,7 +65,7 @@ Admin Web
-> spacetime-module creation_entry_type_config 表
```
`visible=false` 会让创作中心不展示对应入口;`open=false` 会让前端展示锁定态api-server 的运行态熔断继续以 `visible && open` 判断路由是否可用
`visible=false` 会让创作中心不展示对应入口;`open=false` 会让前端展示锁定态,并让 api-server 熔断对应玩法创作 / 运行态 API。隐藏入口但仍保留既有作品号、广场详情或试玩链路时应只关闭 `visible`,不要关闭 `open`
## 注意

View File

@@ -36,7 +36,7 @@ SpacetimeDB 新增两张表:
其中:
- `visible=false`:前端隐藏入口。
- `open=false`:前端展示为锁定/暂不可创建api-server 也可据此熔断运行时入口
- `open=false`:前端展示为锁定/暂不可创建api-server 据此熔断对应玩法 API只隐藏创作页入口但保留既有作品链路时不要关闭 `open`
- `sort_order`:数据库排序字段,前端只做可见/锁定分组派生。
## API

View File

@@ -13,6 +13,8 @@
- `https://developer.hyper3d.ai/api-specification/rodin-generation-gen2`
- `https://developer.hyper3d.ai/api-specification/check-status`
- `https://developer.hyper3d.ai/api-specification/download-results`
- `https://developer.hyper3d.ai/api-specification/check-status_reset_v`
- `https://developer.hyper3d.ai/api-specification/download-results_reset_v`
上游接口:
@@ -24,6 +26,11 @@ POST https://api.hyper3d.com/api/v2/download
Rodin Gen-2 提交接口必须使用 `multipart/form-data`。文本生成时提交 `prompt`;图片生成时提交一个或多个 `images` 文件,可选 `prompt` 作为辅助描述。两种模式均固定提交 `tier=Gen-2`
官方 `*_reset_v` 文档对状态和下载有两个关键约束:
1. 生成接口返回的顶层 `uuid` 是后续下载接口的 `task_uuid`,不要使用 `jobs.uuids` 中的子任务 uuid 作为下载参数。
2. 状态接口使用 `subscription_key` 查询,并返回 `jobs[]`;只有所有 job 的 `status` 都为 `Done` 才能进入下载,任一 job `Failed` 都应视为任务失败。
## 3. 环境变量
```text
@@ -111,7 +118,7 @@ RODIN_MODEL_REQUEST_TIMEOUT_MS
}
```
状态查询会把上游 `Waiting / Generating / Done / Failed` 归一化为 `waiting / generating / done / failed / unknown`。下载接口只返回上游 `list.name``list.url`,不在后端转存文件
状态查询会把上游 `Waiting / Generating / Done / Failed` 归一化为 `waiting / generating / done / failed / unknown`,整体状态必须以 `jobs[]` 聚合结果为准。下载接口只返回上游 `list.name``list.url`,不在 Hyper3D 代理路由中转存文件;具体玩法若需要持久化模型,应在玩法编排层等待 `Done` 后再下载并转存
## 7. 验收

View File

@@ -11,19 +11,22 @@
入口仍复用 `Match3DAgentWorkspace` 表单。点击 `生成抓大鹅草稿` 后:
1. 创建 Match3D session。
2. 进入 `match3d-generating` 生成过程页
3. 过程页复用拼图生成页的 `CustomWorldGenerationView` 结构
4. 生成成功后自动进入 `match3d-result`
5. 生成失败时停留在生成过程页,允许重新生成或返回创作中心
2. 后端先用当前题材和本地兜底元信息创建同一个 Match3D 草稿 profile草稿 Tab 必须立即能看到这份存档
3. 进入 `match3d-generating` 生成过程页
4. 过程页复用拼图生成页的 `CustomWorldGenerationView` 结构
5. 生成成功后自动进入 `match3d-result`
6. 生成失败时停留在生成过程页,允许重新生成或返回创作中心;重新生成必须复用同一个 session / profile并从缺失的素材阶段继续不新建第二份草稿。
生成页步骤固定为:
```text
生成游戏名称 -> 生成物品名称 -> 生成素材图 -> 切割独立图片 -> 上传图片资产 -> 写入草稿页
生成游戏名称 -> 生成物品名称 -> 生成素材图 -> 切割独立图片 -> 上传图片资产 -> 生成3D模型 -> 写入草稿页
```
生成页只展示题材和物品数量,不展示玩法规则说明。
当前 `match3d-generating` 进度页不是后端 task 状态订阅页,而是一个覆盖 `match3d_compile_draft` 长 action 的本地时间进度页:前端每 500ms 以本地时间刷新阶段展示,真正的生成完成仍以 action 返回为准。为避免长 action 未返回时页面完全无感,生成页在 `match3d_compile_draft` 执行期间每 3 秒旁路读取一次 session 和 work detail并用 profile 中已写回的 `generatedItemAssets` 更新 `生成3D模型` 的完成数量。Hyper3D 控制台中看到 3 个 Rodin 任务已经 `Done` 后,页面仍可能继续停留在 `生成3D模型`,此时通常表示后端还在等待下载列表、下载 GLB、转存 OSS 或写回 `generated_item_assets_json`;若 `generatedItemAssets` 已出现 `model_ready`,前端应逐步显示完成数量。排查时应看 api-server 日志中的 `抓大鹅 Rodin 状态轮询返回``抓大鹅 Rodin 下载列表轮询返回``抓大鹅 Rodin GLB 下载完成``抓大鹅 Rodin GLB 转存 OSS 完成`
## 3. 后端编排边界
外部模型和 OSS 上传全部由 `api-server` 编排,不进入 SpacetimeDB reducer。SpacetimeDB 继续只负责 Match3D 会话、草稿和作品 profile 的确定性写入。
@@ -32,17 +35,19 @@
1. 读取 session config。
2. 将本次 MVP 的 `clearCount` 固定为 `3`,并同步用于草稿编译。
3. 基于入口页题材设定文本调用文本模型生成作品元信息。模型固定请求 `gpt-4o`,只返回 JSON其中 `gameName` 为 4 到 12 个中文字符的游戏名称,`tags` 为 3 到 6 个中文短标签;`summary` 首版必须保持空字符串,结果页 `作品描述` 默认留给用户填写
4. 调用文本模型生成 `3` 个题材下的短物品名称
5. 调用项目当前图片链路 VectorEngine `gpt-image-2-all` 生成一张 `1:1` 素材图,提示词必须合入入口页选择的 `assetStylePrompt`。历史 `nanobanana2` 图片选项当前按项目统一决策回落到 VectorEngine不重新接入 APIMart 图片网关
6. 将素材图按 `n*n` 网格切割成独立图片。当前 `3` 件物品使用 `2*2` 网格,取前 `3`
7. 将素材图和每张独立图片上传到 OSS其中独立图片作为草稿页素材预览和后续 Rodin 图生模型参考图
8. 调用现有 SpacetimeDB compile procedure 写入草稿,并把本次生成的独立物品图片列表序列化写入 `match3d_work_profile.generated_item_assets_json`。这一步对标拼图的 `save_puzzle_generated_images`:生成资产不能只挂在本次 HTTP response 上,否则退出结果页后从草稿架读取 `getMatch3DWorkDetail` 会丢失素材列表
9. 在 HTTP 返回的 draft/profile DTO 中附带本次生成的素材资产预览信息,独立图片状态为 `image_ready`,模型字段保持为空;后续重进草稿页时从 work profile 的持久化 `generatedItemAssets` 恢复同一批素材
3. 先调用 SpacetimeDB compile procedure 写入草稿。首次执行使用新 `profileId`;重试时复用 session draft / work profile 中已有 `profileId`。这一步不能等待 LLM、图片、OSS 或 Rodin 成功后才执行
4. 基于入口页题材设定文本调用文本模型生成作品元信息。模型固定请求 `gpt-4o`,只返回 JSON其中 `gameName` 为 4 到 12 个中文字符的游戏名称,`tags` 为 3 到 6 个中文短标签;`summary` 首版必须保持空字符串,结果页 `作品描述` 默认留给用户填写。文本模型不可用时保留第 3 步的本地兜底,不阻断草稿
5. 调用文本模型生成 `3` 个题材下的短物品名称
6. 调用项目当前图片链路 VectorEngine `gpt-image-2-all` 生成一张 `1:1` 素材图,提示词必须合入入口页选择的 `assetStylePrompt`。历史 `nanobanana2` 图片选项当前按项目统一决策回落到 VectorEngine不重新接入 APIMart 图片网关
7. 将素材图`n*n` 网格切割成独立图片。当前 `3` 件物品使用 `2*2` 网格,取前 `3`
8. 将素材图和每张独立图片上传到 OSS其中独立图片作为草稿页素材预览和 Rodin 图生模型参考图;每次获得可恢复的图片资产后,都要回写 `match3d_work_profile.generated_item_assets_json`
9. 使用每张独立图片作为参考图,并行调用 Hyper3D Rodin 图生模型;所有 3D 模型任务必须在同一阶段同时提交、同时轮询状态、同时下载并转存 OSS禁止逐个物品串行等待模型完成。每个任务按官方 `check-status_reset_v` / `download-results_reset_v` 文档轮询状态和下载:状态查询使用 `subscription_key`,整体完成态以 `jobs[]` 聚合为准;下载查询使用生成响应顶层 `uuid` 作为 `task_uuid`,不能使用 `jobs.uuids` 子任务 uuid。只有 `jobs` 全部进入 `Done` 才能视为任务完成,任一 job `Failed` 则失败。完成后选择 `.glb` 下载文件,并把 GLB 转存到 OSS。Rodin 的 `subscriptionKey` 是上游 opaque token不做 256 字符这类短文本长度限制。Rodin 任务状态进入完成态后,下载列表仍可能延迟发布;后端必须对下载列表继续轮询,并兼容 `url``downloadUrl``fileUrl``signedUrl` 等下载字段别名,只有预览图而没有模型文件时不能伪装成 GLB 成功
10. Rodin 每批完成后继续回写 `generated_item_assets_json`。成功素材状态为 `model_ready`;失败素材保留图片引用并记录 `error`,下次 `match3d_compile_draft` 只继续缺失模型的素材,不重复生成已完成的 GLB。
11. 在 HTTP 返回的 draft/profile DTO 中附带本次生成的素材资产预览信息;后续重进草稿页时从 work profile 的持久化 `generatedItemAssets` 恢复同一批素材。
若文本模型不可用或返回无法解析,后端必须降级为 `{themeText}抓大鹅` 与本地标签兜底,不阻断素材生成;但描述仍保持空字符串。
草稿生成阶段调用 Hyper3D Rodin,不等待 `subscriptionKey`也不下载模型文件Rodin 生成只在结果页 `3D素材` Tab 由用户手动触发。手动生成得到的上游下载 URL 仍不得直接写入 Match3D profile,后续正式资产绑定以独立技术方案为准。
草稿生成阶段调用 Hyper3D Rodin等待 GLB 下载完成;前端 `match3d_compile_draft` action 请求超时必须覆盖该长耗时链路,当前 Match3D client 使用 20 分钟超时。Rodin 单模型状态轮询预算为 10 分钟,下载列表发布轮询预算为 5 分钟GLB 下载和 OSS PutObject 各自设置 3 分钟 HTTP 超时,避免上游下载或转存连接长期悬挂。由于 3 个模型并行生成,总耗时按最慢模型计算,不能按模型数量线性叠加。结果页 `3D素材` Tab 直接加载已生成模型;用户点击 `重新生成` 时再复用 Rodin 安全代理,首版重新生成只更新当前页面内预览状态,后续正式资产绑定以独立技术方案为准。
## 4. 图片提示词
@@ -83,16 +88,47 @@ generated-match3d-assets
```text
generated-match3d-assets/{sessionId}/{profileId}/material-sheet/{taskId}/sheet.png
generated-match3d-assets/{sessionId}/{profileId}/items/{itemSlug}/image.png
generated-match3d-assets/{sessionId}/{profileId}/items/{itemSlug}/image/image.png
generated-match3d-assets/{sessionId}/{profileId}/items/{itemSlug}/model/{taskUuid}/model.glb
```
`itemSlug` 必须带 `itemId` 前缀,例如 `match3d-item-1-item`。中文物品名清洗后可能都退回 `item`,不能只用物品名做路径,否则多张切割图会写到同一个 object key导致草稿页预览图全部一致。
HTTP DTO 同时返回 `imageSrc``imageObjectKey`空的 `modelSrc`空的 `modelObjectKey``status = image_ready`。前端预览图片继续走 `ResolvedAssetImage` 换签;后续手动生成的模型文件也必须通过 `useResolvedAssetReadUrl` / `/api/assets/read-url` 换签后打开,不直接请求裸 `/generated-match3d-assets/...` 路径。
HTTP DTO 同时返回 `imageSrc``imageObjectKey``modelSrc``modelObjectKey``modelFileName``taskUuid``subscriptionKey``status`。模型生成成功后 `status = model_ready`;若后续允许部分模型失败降级,失败素材必须带 `error`,且不能伪装成可预览模型。前端模型预览必须通过 `/api/assets/read-bytes` 读取私有 GLB 字节并转成 Blob URL 后交给 Three.js,不直接请求裸 `/generated-match3d-assets/...` 路径。
## 5.1 运行态模型消费
生成模型不仅用于结果页预览,也必须进入游戏运行态。运行态入口的传递链路为:
```text
Match3DWorkProfile / PlatformMatch3DGalleryCard
-> Match3DRuntimeShell(generatedItemAssets)
-> Match3DPhysicsBoard / Match3DTrayPreviewBoard
```
`Match3DPhysicsBoard``Match3DTrayPreviewBoard` 按运行快照中的 `itemTypeId` 稳定排序后,把生成出的模型顺序映射到对应类型。当前 MVP 固定 `clearCount = 3`,因此 `match3d-type-01/02/03` 分别对应生成列表的第 `1/2/3` 个模型;后续恢复更多物品生成时,后端必须继续保证 `generatedItemAssets` 顺序与类型编号一致。
前端加载规则:
1. 优先读取 `modelSrc`;为空时使用 `modelObjectKey`
2. 通过 `readAssetBytes` 调用 `/api/assets/read-bytes`,由同源后端读取 OSS 私有对象字节。
3. 使用 Three.js `GLTFLoader.parseAsync` 解析 GLB 字节,并按物品类型缓存模板。
4. 场内每个物品和备选栏预览都从模板 clone 独立对象,点击命中继续写入 `itemInstanceId`
5. 物理碰撞和边界仍沿用现有 `visualKey` 的程序化几何,生成 GLB 只替换视觉模型,不承接规则真相。
6. 模型缺失、读取失败或 WebGL 回退时,继续使用默认积木素材,不能阻断开局、点击、入槽或结算;调试模式下需要输出加载失败的 `itemTypeId`、模型来源和错误信息便于区分“资产没有传入”和“GLB 字节读取或解析失败”。
结果页点击 `试玩` 时,前端必须把当前结果页可见的 `generatedItemAssets` 带入运行态启动入参。`PUT /api/runtime/match3d/works/{profileId}` 若因为并发或旧快照返回了缺少素材的 profile`Match3DResultView` 需要把当前 draft / profile 的素材重新合并到运行态 profile并在启动试玩前调用生成素材保存接口把当前可见的 `generatedItemAssets` 写回作品 profile不能只在内存里把素材补到 `onStartTestRun(profile)`。发布同理必须先落库当前素材,再调用 `publish_match3d_work`,否则公开推荐流和正式运行态只能读到旧 profile 快照,历史草稿尤其容易表现为结果页有 3D 模型、正式游戏仍是默认积木。若历史草稿同时存在旧 `draft.generatedItemAssets` 和较新的 `profile.generatedItemAssets`,同 `itemId` 下以 profile 中已有的 `modelSrc` / `modelObjectKey` 补齐 draft不能让旧 draft 把模型状态覆盖回 `image_ready``PlatformEntryFlowShellImpl` 在渲染 `match3d-runtime` 时按 `run.profileId` 优先使用当前 `match3dProfile.generatedItemAssets`,只有 profileId 不匹配时才读取 `selectedPublicWorkDetail.generatedItemAssets`。推荐流内嵌正式运行态也必须走同一解析器;当推荐卡片摘要缺少素材时,启动前补读 `getMatch3DWorkDetail(profileId)`,把详情里的生成模型写入 `match3dProfile` 后再传给运行态。这样可以避免从公开详情页残留状态或推荐卡片旧摘要进入试玩 / 正式游戏时,把已生成草稿的 3D 模型覆盖成空列表。
## 6. 自动保存与草稿恢复
抓大鹅结果页的基础信息自动保存继续调用 `PUT /api/runtime/match3d/works/{profileId}` 更新名称、题材、描述、标签、封面、消除数和难度;该保存不得清空 `generated_item_assets_json`。SpacetimeDB `update_match3d_work` / `publish_match3d_work` 必须保留当前行的生成素材 JSON。
点击 `生成抓大鹅草稿` 后,草稿存档创建与素材生成解耦:
1. 首次 compile 必须先写 `match3d_work_profile` 草稿行即使后续卡在文本模型、图片生成、OSS 上传、Rodin 生成或下载转存任意阶段。
2. 失败态前端要重新读取 session / work detail并刷新草稿作品架保证用户离开生成页后仍能在草稿 Tab 找到这份作品。
3. 重新生成时优先使用当前 session 的 `draft.profileId``publishedProfileId`,不得重新创建 session后端读取同一 profile 的 `generated_item_assets_json` 后,只补齐缺失图片或缺失模型的阶段。
4. 已有 `status = model_ready` 且带 `modelSrc` / `modelObjectKey` 的素材视为完成,不再重复调用 Rodin。
抓大鹅结果页的基础信息自动保存继续调用 `PUT /api/runtime/match3d/works/{profileId}` 更新名称、题材、描述、标签、封面、消除数和难度;该保存不得清空 `generated_item_assets_json`。结果页 `3D素材` Tab 手动点击 `重新生成` 并拿到 GLB 下载文件后,必须把当前素材草稿重新序列化成 `generatedItemAssets` 并写回作品 profile否则页面内预览会显示新模型但试玩、发布和重进草稿仍会读取旧的空模型快照。SpacetimeDB `update_match3d_work` / `publish_match3d_work` 必须保留当前行的生成素材 JSON。
草稿架重进路径为:
@@ -100,7 +136,7 @@ HTTP DTO 同时返回 `imageSrc`、`imageObjectKey`、空的 `modelSrc`、空的
草稿 Tab -> getMatch3DWorkDetail(profileId) -> Match3DResultView(profile.generatedItemAssets)
```
因此 `map_match3d_work_summary_response` / `map_match3d_work_profile_response` 需要从 work profile snapshot 反序列化 `generated_item_assets_json` 并输出 `generatedItemAssets`。前端 `Match3DResultView` 仍保持现有优先级:本次生成流程内有 `draft.generatedItemAssets` 时用 draft从草稿架重进没有 draft 时,用 `profile.generatedItemAssets`;两者都没有才回退到默认 3D 素材占位。
因此 `map_match3d_work_summary_response` / `map_match3d_work_profile_response` 需要从 work profile snapshot 反序列化 `generated_item_assets_json` 并输出 `generatedItemAssets`。前端 `Match3DResultView` 的读取顺序为:有 `draft.generatedItemAssets` 时先用 draft 保留本次生成顺序和图片;同 `itemId``profile.generatedItemAssets` 中已有模型字段时,用 profile 模型字段补齐 draft从草稿架重进没有 draft 时,用 `profile.generatedItemAssets`;两者都没有才回退到默认 3D 素材占位。
结果页 `作品信息` Tab 字段命名对齐拼图草稿:
@@ -111,7 +147,7 @@ HTTP DTO 同时返回 `imageSrc`、`imageObjectKey`、空的 `modelSrc`、空的
`3D素材` 详情页只保留:
1. 模型预览区:优先加载 `modelSrc` 对应 GLB支持拖动旋转没有模型时展示空预览。
1. 模型预览区:优先加载 `modelSrc` 对应 GLB缺失时加载 `modelObjectKey`支持拖动旋转;没有模型时展示空预览。
2. 素材名称输入。
3. `重新生成` 按钮。
@@ -125,6 +161,8 @@ HTTP DTO 同时返回 `imageSrc`、`imageObjectKey`、空的 `modelSrc`、空的
npm run check:encoding
npm run test -- src\services\miniGameDraftGenerationProgress.test.ts
npm run test -- src\components\match3d-result\Match3DResultView.test.tsx
npm run test -- src\components\match3d-runtime\Match3DRuntimeShell.test.tsx
npm run test -- src\components\rpg-entry\RpgEntryFlowShell.agent.interaction.test.tsx
npm run typecheck
cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml
cargo test -p spacetime-client match3d --manifest-path server-rs\Cargo.toml
@@ -135,4 +173,4 @@ cargo check -p spacetime-client --manifest-path server-rs\Cargo.toml
cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml
```
真实草稿生成需要本地私密环境配置 `VECTOR_ENGINE_API_KEY` 和 OSS 访问变量;`HYPER3D_API_KEY` 只在结果页手动生成 3D 模型时需要。后端改动后使用 `npm run api-server` 启动,并检查 `/healthz`
真实草稿生成需要本地私密环境配置 `VECTOR_ENGINE_API_KEY``HYPER3D_API_KEY` 和 OSS 访问变量。后端改动后使用 `npm run api-server` 启动,并检查 `/healthz`

View File

@@ -18,13 +18,13 @@
## 3. Rodin 任务边界
前端只维护当前页面内的临时重新生成任务状态:
前端只维护当前页面内的临时重新生成任务状态;草稿生成得到的正式模型资产从 `generatedItemAssets.modelSrc` 恢复
1. 素材槽位名称。
2. 模型预览。草稿生成的 `/generated-match3d-assets/...` GLB 必须通过同源 `/api/assets/read-bytes` 由后端换签并读取字节,前端再转成 Blob URL 后交给 Three.js GLTFLoader避免浏览器直接 `fetch` OSS 签名 URL 时被 CORS 拦截。
3. 图生模型参考图只作为重新生成的隐藏输入来源,不在详情页展示。上传图片在前端直接读成 Data URL草稿生成的 `/generated-match3d-assets/...` 图片必须通过 `/api/assets/read-bytes` 转成 Data URL 后提交给 Hyper3D。
4. Hyper3D `taskUuid``subscriptionKey`
5. 查询到的状态、进度与下载文件列表。
4. Hyper3D `taskUuid``subscriptionKey` 仅用于重新生成过程,不在详情页展示
5. 查询到的状态、进度与下载文件列表仅作为内部状态,不在详情页展示
正式资产链后续再接:

View File

@@ -2,18 +2,19 @@
## 背景
创作中心的模板 Tab、平台创作类型弹层和旧“新建作品”卡片配置都依赖同一组玩法模板。此前入口开放状态、隐藏状态和中文文案集中写在 `src/components/platform-entry/platformEntryCreationTypes.ts` 与入口组件中,后续切换玩法开放节奏时容易出现多个入口不一致
创作中心的模板 Tab、平台创作类型弹层和旧“新建作品”卡片配置都依赖同一组玩法模板。此前入口开放状态、隐藏状态和中文文案集中写在前端配置与入口组件中,后续切换玩法开放节奏时容易出现多个入口不一致。当前入口配置事实源已经迁移到 SpacetimeDB`api-server` 通过 `GET /api/creation-entry/config` 下发
## 落地规则
1. 新建作品入口配置统一放在 `src/config/newWorkEntryConfig.ts`
1. 新建作品入口配置统一放在 SpacetimeDB 的 `creation_entry_config` / `creation_entry_type_config` 表;默认种子位于 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
2. `visible` 控制玩法是否展示在创作 Tab 模板入口、新建作品入口和创作类型弹层中。
3. `open` 控制玩法是否允许点击创建`open: false` 时入口保持展示但禁用
3. `open` 控制玩法是否允许点击创建以及对应创作 / runtime API 路由是否放行;`open: false` 时入口保持展示但禁用,并由 `api-server` 熔断对应玩法 API
4. `title``subtitle``badge` 控制玩法卡片文案。
5. `startCard` 控制旧创作中心顶部新建作品模块的标题、辅助文案和移动端角标文案;当前创作 Tab 首屏标题固定在 `PlatformEntryFlowShellImpl.tsx`,不再由 `startCard` 控制。
6. `typeModal` 控制平台创作类型弹层标题和描述。
7. 入口排序仍遵循“可创建玩法在前,未开放玩法在后”;同组内部沿用配置顺序。
8. `creative-agent` 可以继续保留运行链路,但默认 `visible: false`,不出现在创作 Tab 模板入口。
9. 前端 `src/components/platform-entry/platformEntryCreationTypes.ts` 只做展示派生,不再承载默认入口配置。
## 当前状态
@@ -23,17 +24,17 @@
| 大鱼吃小鱼 | 否 | 是 | 功能仍保留,不在新建作品入口展示 |
| 拼图 | 是 | 是 | 创作 Tab 默认选中并内嵌展示拼图创作表单,提交后进入拼图草稿生成 |
| 抓大鹅 | 是 | 是 | 点击后进入抓大鹅 Match3D Agent 共创工作台 |
| 方洞挑战 | | 是 | 点击后进入方洞挑战 Agent 共创工作台,支持草稿、结果页、发布、试玩、作品架与广场 |
| 方洞挑战 | | 是 | 创作页入口暂时完全隐藏,既有草稿、结果页、发布、试玩、作品架与广场链路保留 |
| AIRP | 是 | 否 | 保留入口,显示敬请期待 |
| 视觉小说 | 是 | | 点击后进入视觉小说创作工作台 |
| 视觉小说 | 是 | | 保留入口,显示敬请期待,暂不允许创建视觉小说草稿 |
| 智能创作 | 否 | 是 | 入口隐藏,既有 `creative-agent` 链路保留 |
## 验收
1. 修改 `src/config/newWorkEntryConfig.ts` 后,创作 Tab 模板入口、创作中心顶部卡带和平台创作类型弹层应同步变化。
1. 修改 SpacetimeDB 入口配置后,创作 Tab 模板入口、创作中心顶部卡带和平台创作类型弹层应同步变化。
2. 隐藏玩法不触发入口预加载,也不出现在新建作品入口中。
3. 未开放玩法点击态保持禁用,不应进入鉴权或创建会话链路。
4. 已开放玩法点击后必须进入对应创建链路;若用户未登录,先走登录保护。
5. 创作 Tab 首屏应显示“10分钟创作一个精品互动玩法”并默认展示拼图创作表单。
6. 智能创作入口隐藏后不应出现“Hi, 朋友”“问一问百梦”或“一句话生成闪应用”等旧首页入口。
7. 方洞挑战作品发布后应生成 `SH-` 作品号,并能从作品架、广场详情和试玩 runtime 回到同一作品详情
7. 方洞挑战入口隐藏后,不应出现在创作 Tab 模板入口、创作中心顶部卡带、平台创作类型弹层和创作页作品架中;既有 `SH-` 作品号、广场详情和试玩 runtime 链路不因此删除

View File

@@ -0,0 +1,27 @@
# 公开作品详情失效回首页修复
日期:`2026-05-11`
## 背景
直接访问 `/works/detail?work=<公开作品号>` 时,如果作品已经删除、下架或当前公开列表无法命中该作品,统一作品详情会先进入 `work-detail` 阶段。此前该阶段在没有 `selectedPublicWorkDetail` 时不会渲染任何内容;用户关闭“作品不存在或已下架”的提示后,页面可能只剩空白区域。
## 修复
1. `resolveWorkNotFoundRecoveryAction(...)` 覆盖 `/works/detail`、拼图公开详情和视觉小说公开详情,并复用运行态深链失效的回首页策略。
2. 拼图公开详情、拼图运行态启动和拼图详情页读取的 `404/NOT_FOUND` 分支改为统一走公开作品失效恢复逻辑。
3. 直接打开 `/works/detail?work=...` 的搜索失败分支会清理详情态、运行态临时数据,切回首页并清掉 URL query。
4. `work-detail` 阶段在详情数据为空时渲染轻量读取态,避免异步间隙或异常分支出现纯白屏。
## 验证
- `npm run test -- src/routing/runtimeNotFoundRecovery.test.ts`
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct missing public work detail alert returns to platform home"`
- `npm run typecheck`
- `npm run check:encoding -- src/routing/runtimeNotFoundRecovery.ts src/routing/runtimeNotFoundRecovery.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx docs/technical/PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md`
## 关联文件
1. `src/routing/runtimeNotFoundRecovery.ts`
2. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
3. `src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`

View File

@@ -0,0 +1,89 @@
# 拼图与抓大鹅结果页音乐 Tab 2026-05-11
## 1. 范围
本方案把 VectorEngine 音频生成能力从视觉小说结果页扩展到拼图与抓大鹅结果页:
1. 拼图结果页新增 `音乐` Tab支持通过 Suno 生成作品背景音乐。
2. 抓大鹅结果页新增 `音乐` Tab支持通过 Suno 生成作品背景音乐。
3. 抓大鹅 `3D素材` Tab 支持为每个生成物体通过 Vidu 生成点击音效。
本轮不新增 SpacetimeDB 表,不修改表字段,不把供应商密钥下发到前端。
## 2. 通用音频接口
后端在既有视觉小说音频路由外新增通用创作音频路由:
| 方法 | 路由 | 用途 |
| --- | --- | --- |
| `POST` | `/api/creation/audio/background-music` | 提交 Suno 背景音乐任务 |
| `POST` | `/api/creation/audio/background-music/{task_id}/asset` | 查询并转存 Suno 音频资产 |
| `POST` | `/api/creation/audio/sound-effect` | 提交 Vidu 音效任务 |
| `POST` | `/api/creation/audio/sound-effect/{task_id}/asset` | 查询并转存 Vidu 音效资产 |
通用转存请求由前端传入 `entityKind``entityId``slot``assetKind``profileId`。后端仍负责:
1. 校验 VectorEngine 与 OSS 环境变量。
2. 轮询供应商任务结果。
3. 下载音频字节。
4. 写入 OSS 私有对象。
5. 确认 `asset_object` 并绑定 `asset_entity_binding`
视觉小说原路由保持兼容,内部继续复用同一套提交、轮询、转存逻辑。
## 3. 数据落点
### 3.1 拼图
拼图作品没有独立作品级 metadata 字段。背景音乐随 `levels_json` 保存到首个 `PuzzleDraftLevel.backgroundMusic`
```json
{
"levelId": "puzzle-level-1",
"backgroundMusic": {
"taskId": "suno-task",
"provider": "vector-engine-suno",
"assetObjectId": "assetobj_1",
"assetKind": "puzzle_background_music",
"audioSrc": "/generated-puzzle-assets/..."
}
}
```
运行态后续可从当前关卡快照或作品详情读取该字段作为背景音乐源;若字段为空,继续使用现有程序化背景音乐兜底。
### 3.2 抓大鹅
抓大鹅作品级音频与物体点击音效复用 `generated_item_assets_json` 数组保存,不新增表字段:
1. 作品背景音乐暂存到第一个 `Match3DGeneratedItemAsset.backgroundMusic`,表示当前 work profile 的作品级背景音乐。
2. 单个物体点击音效保存到对应 `Match3DGeneratedItemAsset.clickSound`
这是一个兼容性折中:当前 Match3D work profile 没有 work-level metadata 字段,而 `generated_item_assets_json` 已经随作品详情、草稿架、运行态入口稳定传递。后续若新增正式作品 metadata 表达,应迁移 `backgroundMusic` 到作品级字段。
## 4. 前端交互
结果页 UI 保持轻量:
1. `音乐` Tab 只展示必要输入、生成按钮、状态与音频预览,不展示供应商规则说明。
2. 生成完成后立即写回本地草稿状态,并触发既有保存链路或专用保存接口。
3. 抓大鹅每个物体音效生成入口放在对应素材详情面板内,不在列表下方展开大段配置。
## 5. 验收
建议执行:
```powershell
npm run check:encoding
npm run test -- src\components\puzzle-result\PuzzleResultView.test.tsx
npm run test -- src\components\match3d-result\Match3DResultView.test.tsx
npm run typecheck
cargo test -p shared-contracts creation_audio --manifest-path server-rs\Cargo.toml
cargo test -p shared-contracts puzzle --manifest-path server-rs\Cargo.toml
cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml
cargo test -p api-server vector_engine_audio_generation --manifest-path server-rs\Cargo.toml
cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml
cargo check -p api-server --manifest-path server-rs\Cargo.toml
```
真实生成 smoke 需要本地私密环境配置 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 与 OSS 变量。后端改动后使用 `npm run api-server` 启动,并确认 `/healthz`

View File

@@ -6,9 +6,10 @@
- [BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md](./BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md):冻结“汪汪声浪大作战 / bark-battle”后端 DDD 技术方案,明确 `server-rs + Axum + SpacetimeDB` 分层边界、shared contracts、作品配置、runtime run、派生成绩、排行榜、`work_play_start` 埋点、migration/绑定生成策略,以及不保存原始麦克风音频的隐私与反作弊约束。
- [BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md](./BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md):冻结“汪汪声浪大作战 / bark-battle”2D 浏览器 runtime 技术方案,明确 Phaser + TypeScript + Vite 选型、纯 TS simulation 与 Phaser renderer/DOM HUD 边界、Web Audio 输入适配、移动端权限降级和后续测试验证命令。
- [PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md](./PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md):记录直接访问公开作品详情深链时作品不存在或已下架的回首页修复,避免关闭提示后停在 `work-detail` 空状态白屏。
- [CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md](./CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md):冻结儿童动作识别互动玩法 Demo 固定热身关的开发落地规格,覆盖横屏展示、摄像头背景虚化、角色剪影、绿色圆环 2 秒保持、动作教学、当前会话内空间边界记录和后续关卡安全暂停规则。
- [RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md](./RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md)记录运行态输入设备抽象层明确鼠标、触控、mocap 等设备统一归一为通用拖拽语义,玩法组件只负责解释目标和落点。
- [RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](./RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md):记录 `server-rs` Cargo 依赖集中配置口径,第三方版本和 workspace 内部 crate path 统一维护在根 `server-rs/Cargo.toml`,成员 crate 只保留 feature/optional 差异。
- [RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](./RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md):记录 `server-rs` Cargo 依赖集中配置口径,第三方版本和 workspace 内部 crate path 统一维护在根 `server-rs/Cargo.toml`,成员 crate 只保留 feature/optional 差异;同时冻结 `shared-contracts` 不得反向依赖 `platform-*`,避免 SpacetimeDB 模块发布时拉入 `wasm-bindgen`
- [DEV_RUST_STACK_PORT_CONFLICT_PRECHECK_2026-05-09.md](./DEV_RUST_STACK_PORT_CONFLICT_PRECHECK_2026-05-09.md):记录本地完整 Rust 栈启动时 `api-server`、主站 Vite 和后台 Vite 端口占用的误判根因、脚本预检策略和 Windows 排障命令。
- [VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md](./VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md):记录 GPT-image-2 图片生成从 APIMart 迁移到 VectorEngine `gpt-image-2-all` 的接口、环境变量、尺寸映射、错误口径和验收命令。
- [SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md](./SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md):记录本地 `spacetime publish` 被 sccache wrapper 通信异常阻断时的根因、debug 构建参数口径和手动排障命令。

View File

@@ -26,6 +26,12 @@
- mocap 光标按 60Hz 插值更新 UI 位置,并在拖拽中用插值后的当前点持续驱动输入层,避免输入包帧率低或抖动时出现明显跳变。
- 合并大块由拼图运行态把手部坐标命中到任一成员拼块;本地拼图运行时再按 `mergedGroupId` 执行整组平移。
## 调试模式
前端全局调试模式统一通过 `src/config/debugMode.ts` 判断。默认跟随 Vite 开发态:`import.meta.env.DEV` 为真时开启,生产构建默认关闭;如需显式覆盖,可设置 `VITE_DEBUG_MODE=true``VITE_DEBUG_MODE=false`
拼图运行态的 mocap 调试面板只在全局调试模式下渲染。面板默认折叠,只保留一行连接状态,展开后才显示动作、手势、解析告警和原始包预览,避免开发诊断信息遮挡拼图棋盘和底部操作。
## 接入规则
新玩法或新设备接入时遵循以下边界:

View File

@@ -16,7 +16,9 @@
4. 成员 crate 只保留自身需要表达的差异,例如 `features``optional = true` 或 target-specific dependency。
5. 需要关闭 default features 的依赖,应优先在 workspace 根依赖中声明;成员 crate 不再重复覆盖同一项。
6. `module-assets` 这类有默认服务端 feature 的领域 crate在 workspace 根内按 `default-features = false` 维护;需要服务端 OSS/HTTP 能力的 adapter crate 显式启用 `features = ["server-service"]`
7. 面向 SpacetimeDB WASM 的依赖链不得隐式启用原生 HTTP / OSS / Web 平台依赖;例如 `shared-contracts``assets` 模块通过 `oss-contracts` feature 暴露给 `api-server``spacetime-module` 路径只消费关闭默认 feature 后的纯 DTO 子集
7. `shared-contracts` 只能承载前后端公开 DTO 和轻量枚举,禁止直接依赖 `platform-*` 服务实现 crate需要把平台实现响应转换为公开 DTO 时,转换函数放在 `api-server` 等 adapter 层
8. 面向 SpacetimeDB WASM 的依赖链不得隐式启用原生 HTTP / OSS / Web 平台依赖;例如 `shared-contracts``assets` 模块通过不依赖 `platform-oss``oss-contracts` feature 暴露给 `api-server``spacetime-module` 路径只消费关闭默认 feature 后的纯 DTO 子集。
9. `spacetime-module` 的传递依赖不能包含 `reqwest``web-sys``js-sys``wasm-bindgen` 等 Web/HTTP 客户端链路;发布前可用 `cargo tree -i wasm-bindgen --manifest-path server-rs/Cargo.toml -p spacetime-module --target wasm32-unknown-unknown` 排查。
## 3. 本次收敛范围
@@ -57,14 +59,33 @@ npm.cmd run check:encoding -- docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDA
## 6. SpacetimeDB WASM 依赖边界
2026-05-11 本地重置 SpacetimeDB 并重新发布 `xushi-p4wfr` 时,`spacetime publish` 在 Rust 编译成功后报 `wasm-bindgen detected`。排查命令显示链路为:
```text
spacetime-module -> module-runtime -> shared-contracts -> platform-oss -> reqwest -> wasm-bindgen
```
根因是 `shared-contracts` 为了复用 OSS 直传/读签名返回类型,直接依赖了 `platform-oss`。这违反 DDD 分层边界:契约 crate 不能依赖平台副作用实现,否则所有引用契约的纯领域和 SpacetimeDB 模块都会被迫拉入 HTTP client。
`spacetime publish` 会构建 `spacetime-module``wasm32-unknown-unknown` 目标。这个目标不能包含 `wasm-bindgen`,也不应通过 DTO crate 间接拉入 `reqwest``web-sys` 或浏览器 WebAssembly 平台依赖。
修正口径:
1. `shared-contracts::assets` 定义独立的公开 DTO 和 `DirectUploadObjectAccess` 轻量枚举。
2. `platform-oss` 保持 OSS 签名、读写请求和错误分类实现,不被契约层引用。
3. `api-server::assets` 负责把 `platform_oss::OssPostObjectResponse` / `OssSignedGetObjectUrlResponse` 转成 `shared-contracts` DTO。
4. 后续新增外部平台能力时,重复使用这个边界:平台 crate 不得被 `shared-contracts``module-*``spacetime-module` 反向依赖。
已验证的排查命令:
```powershell
cargo tree -i wasm-bindgen --manifest-path server-rs\Cargo.toml -p spacetime-module --target wasm32-unknown-unknown
cargo tree -i wasm-bindgen --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown
cargo tree --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown | Select-String -Pattern 'wasm-bindgen|platform-oss|reqwest'
cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml --target wasm32-unknown-unknown
cargo check -p shared-contracts --manifest-path server-rs\Cargo.toml
cargo check -p api-server --manifest-path server-rs\Cargo.toml
spacetime publish xushi-p4wfr --server local --module-path server-rs\crates\spacetime-module --build-options="--debug" -c=on-conflict --yes
```
若反向树显示 `reqwest -> platform-oss -> shared-contracts -> module-* -> spacetime-module`,优先检查新增的 `shared-contracts` 或领域 crate 依赖是否忘记关闭默认 feature。原生 `api-server` 需要资产上传契约时,应在自身 `Cargo.toml` 显式启用 `shared-contracts``oss-contracts` feature而不是让 workspace 根依赖默认启用。
若反向树显示 `reqwest -> platform-oss -> shared-contracts -> module-* -> spacetime-module`,优先检查新增的 `shared-contracts` 或领域 crate 依赖是否忘记关闭默认 feature,或 `shared-contracts` feature 是否错误依赖了平台实现 crate。原生 `api-server` 需要资产上传契约时,应在自身 `Cargo.toml` 显式启用 `shared-contracts``oss-contracts` feature而不是让 workspace 根依赖默认启用。

View File

@@ -7,6 +7,7 @@ Windows 本地执行 `npm run dev:rust` 或 `spacetime publish` 时,`spacetime
当本机 sccache server 状态损坏、client/server 通信异常或版本残留不一致时,可能出现:
```text
sccache: error: Timed out waiting for server startup. Maybe the remote service is unreachable?
sccache: error: failed to execute compile
sccache: caused by: Failed to send data to or receive data from server
sccache: caused by: Failed to read response header
@@ -15,9 +16,24 @@ sccache: caused by: failed to fill whole buffer
这类错误发生在 rustc wrapper 层,不能说明 SpacetimeDB module 代码本身编译失败。
## 2026-05-11 本机根因定位
本机 `cargo check -p api-server` 失败时Cargo 还没有进入业务 crate 编译,而是在读取 `server-rs/.cargo/config.toml` 后执行 `sccache rustc -vV` 探测编译器版本。失败的 stderr 会被写入 `server-rs/target/.rustc_info.json`,内容为 `Timed out waiting for server startup`
当前 PowerShell 环境设置了 `SCCACHE_OSS_BUCKET=genarrative-sccache``SCCACHE_OSS_ENDPOINT=https://oss-rg-china-mainland.aliyuncs.com``SCCACHE_OSS_KEY_PREFIX=genarrative`,且没有设置本地 `SCCACHE_DIR`。因此 sccache daemon 冷启动时会先初始化 OSS 远端缓存,并执行 `.sccache_check` 的读写检查;日志中可见 `Init oss cache ...``proxy(http://127.0.0.1:7897/) intercepts ...`,随后才出现 `server started, listening on 127.0.0.1:4226`
本次排查的结论是:冷启动失败主要发生在 sccache client 等待 daemon 启动的握手窗口内,而 daemon 启动又依赖 OSS/本机代理链路先完成缓存可读写检查。代理或 OSS 链路稍慢时Cargo 调用的 `sccache rustc -vV` 会先超时daemon 预热后直接执行同一条 `sccache rustc -vV` 又可能成功,所以这是冷启动/通道状态问题,不是 `api-server` 或 Rust 代码错误。
辅助证据:
1. `rustc -vV` 可直接输出版本,说明 Rust 工具链本身可用。
2. `tasklist` 曾只看到 `sccache --show-stats` 客户端进程,`netstat` 只出现到 `127.0.0.1:4226``SYN_SENT`,没有真正的 `LISTEN`,说明当时 client 正在等一个尚未成功监听的 daemon。
3. 在子进程中临时清掉 `SCCACHE_OSS_*` 并设置本地 `SCCACHE_DIR`sccache 退回本地磁盘缓存,日志显示 `Init disk cache ...``rustc -vV``sccache --show-stats` 均能完成。
4. `C:\Users\DSK\AppData\Roaming\Mozilla\sccache\config\config` 缺失只是非致命 warning本机实际配置来自环境变量。
## 本地开发处理
`scripts/dev-rust-stack.sh` 的 publish 阶段继续由 SpacetimeDB CLI 内部调用 Cargo并通过 `--build-options="--debug"` 使用 debug 构建参数。遇到 sccache 通信或 wrapper 失败时,本地排障仍优先绕过 wrapper 验证 rustc 本身可用。
`scripts/dev-rust-stack.sh` 的 publish 阶段继续由 SpacetimeDB CLI 内部调用 Cargo并通过 `--build-options="--debug"` 使用 debug 构建参数。遇到 sccache 冷启动超时时,优先保留 `sccache` wrapper并修复 sccache daemon 的启动等待时间;只有在排除 sccache 本身问题时,才临时绕过 wrapper 验证 rustc 本身可用。
该处理不修改 `server-rs/.cargo/config.toml`,也不删除本地 target 缓存。
@@ -29,13 +45,56 @@ sccache: caused by: failed to fill whole buffer
rustc -vV
```
如果只想绕过本次 Cargo 构建的 sccache wrapper可在 Git Bash 中执行
如果要保留 sccache 并修复冷启动等待时间,在 PowerShell 中创建或更新 sccache 默认配置
```powershell
$configDir = Join-Path $env:APPDATA "Mozilla\sccache\config"
New-Item -ItemType Directory -Force -Path $configDir | Out-Null
@(
"# Windows 本机 sccache 冷启动需要先完成 OSS 缓存读写检查。"
"# 拉长 client 等待 daemon 启动的时间,避免 Cargo 在 rustc -vV 阶段误判超时。"
"server_startup_timeout_ms = 60000"
) | Set-Content -Encoding UTF8 -Path (Join-Path $configDir "config")
```
随后清掉 Cargo 曾缓存的失败探测结果,并从冷启动验证:
```powershell
cd C:\proj\Genarrative\server-rs
sccache --stop-server
Remove-Item -Force target\.rustc_info.json -ErrorAction SilentlyContinue
cargo check -p api-server
```
注意:不要在另一个 `cargo` / `rustc` 仍在编译时执行 `taskkill /F /IM sccache.exe /T`。sccache 对 proc-macro crate 会显示 `Server sent UnhandledCompile` 并把请求转交给真实 rustc如果此时强杀 sccache client/server可能让 `serde_derive``spacetimedb-bindings-macro` 等 proc-macro 编译直接以 `sccache ... exit code: 1` 失败,而 stderr 里看不到真正的 Rust 诊断。这是排障动作打断编译,不是 `spacetime-module` 源码错误。
如果只想临时绕过本次 Cargo 构建的 sccache wrapper可在 Git Bash 中执行:
```bash
cd server-rs/crates/spacetime-module
RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo check --target=wasm32-unknown-unknown
```
PowerShell 原生 Cargo 的一次性 wrapper 绕过命令是:
```powershell
cd C:\proj\Genarrative\server-rs
cargo check -p api-server --config "build.rustc-wrapper=''"
```
如果需要验证是否为 OSS/代理冷启动问题,可只在当前 PowerShell 进程中切到本地缓存做对照:
```powershell
$env:SCCACHE_LOG = "debug"
$env:SCCACHE_ERROR_LOG = "C:\proj\Genarrative\logs\sccache-local-start-error.log"
$env:SCCACHE_DIR = Join-Path $env:TEMP "genarrative-sccache-local-test"
Remove-Item Env:SCCACHE_OSS_BUCKET -ErrorAction SilentlyContinue
Remove-Item Env:SCCACHE_OSS_ENDPOINT -ErrorAction SilentlyContinue
Remove-Item Env:SCCACHE_OSS_KEY_PREFIX -ErrorAction SilentlyContinue
sccache "C:\Users\DSK\.rustup\toolchains\stable-x86_64-pc-windows-msvc\bin\rustc.exe" -vV
sccache --show-stats
```
如果需要排查 sccache server 状态:
```bash
@@ -44,10 +103,12 @@ sccache --stop-server
sccache --start-server
```
`sccache --stop-server` 本身也可能因为 server 通道已损坏而失败;此时不应阻断本地开发 publish先使用 wrapper 降级完成验证。
`sccache --stop-server` 本身也可能因为 server 通道已损坏而失败;只有确认当前没有 `cargo``rustc``link` 进程后,才用 `taskkill /F /IM sccache.exe /T` 清理残留进程。此时不应阻断本地开发 publish先使用 wrapper 降级完成验证。
## 验证
1. `bash -n scripts/dev-rust-stack.sh`
2. `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo check --target=wasm32-unknown-unknown`
3. 重新运`npm run dev:rust`,确认 publish 命令带有 `--build-options="--debug"`
2. 冷启动后直接执行 `cargo check -p api-server`,确认不再出现 `Timed out waiting for server startup`
3. `cargo check -p spacetime-module`,确认 proc-macro 依赖和 SpacetimeDB module 都能在 sccache wrapper 下通过。
4. `sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,确认仍在使用 sccache/OSS 缓存。
5. 重新运行 `npm run dev:rust`,确认 publish 命令带有 `--build-options="--debug"`

View File

@@ -72,8 +72,8 @@ SELECT * FROM auth_store_snapshot WHERE snapshot_id = 'default';
### `user_account`
- 作用用户账号主表保存用户名、公开百梦号、手机号掩码、登录方式、密码登录开关、token 版本和默认不前端展示的运营标签。
- 结构:`user_id PK: String`, `public_user_code: String`, `username: String`, `display_name: String`, `avatar_url: Option<String>`, `phone_number_masked: Option<String>`, `phone_number_e164: Option<String>`, `login_method: String`, `binding_status: String`, `wechat_bound: bool`, `password_hash: String`, `password_login_enabled: bool`, `token_version: u64`, `user_tags: Vec<String>`
- 说明:`user_tags` 默认空数组,只允许后端白名单投影到特定业务接口不得在登录态、个人资料等通用前端响应中直接暴露。
- 结构:`user_id PK: String`, `public_user_code: String`, `username: String`, `display_name: String`, `avatar_url: Option<String>`, `phone_number_masked: Option<String>`, `phone_number_e164: Option<String>`, `login_method: String`, `binding_status: String`, `wechat_bound: bool`, `password_hash: String`, `password_login_enabled: bool`, `token_version: u64`, `user_tags: Option<Vec<String>>`
- 说明:`user_tags` 数据库默认 `None`,业务读取时按空数组归一化;只允许后端白名单投影到特定业务接口不得在登录态、个人资料等通用前端响应中直接暴露。
- 索引:`username`, `public_user_code`
```sql
@@ -256,11 +256,11 @@ SELECT * FROM profile_redeem_code_usage WHERE user_id = '<user_id>';
### `profile_invite_code`
- 作用:用户邀请中心的邀请码主表,也承载后台运营预置邀请码和使用后授予账号的运营标签。
- 结构:`user_id PK: String`, `invite_code: String`, `metadata_json: String`, `created_at: Timestamp`, `updated_at: Timestamp`, `starts_at: Option<Timestamp>`, `expires_at: Option<Timestamp>`, `granted_user_tags: Vec<String>`
- 作用:用户邀请中心的邀请码主表,也承载后台运营预置邀请码和使用后授予账号的运营标签配置
- 结构:`user_id PK: String`, `invite_code: String`, `metadata_json: String`, `created_at: Timestamp`, `updated_at: Timestamp`, `starts_at: Option<Timestamp>`, `expires_at: Option<Timestamp>`
- 索引:主键 `user_id`,唯一索引 `invite_code`
- 后台读取:`GET /admin/api/profile/invite-codes` 只返回 `user_id``admin:` 开头的后台预置码;普通用户自己的邀请码不得进入后台运营列表。
- 说明:`granted_user_tags` 默认空数组;用户注册填写该邀请码后合并进 `user_account.user_tags`,不回改历史用户。
- 说明:使用该邀请码后授予的标签存放在 `metadata_json.userTags`,服务端兼容读取 `metadata_json.user_tags`;用户注册填写该邀请码后合并进 `user_account.user_tags`,不回改历史用户。
```sql
SELECT * FROM profile_invite_code WHERE user_id = '<user_id>';
@@ -665,7 +665,7 @@ SELECT * FROM match3d_agent_message WHERE session_id = '<session_id>' ORDER BY c
- 作用:抓大鹅 Match3D 作品主表,保存作品基础信息、配置、发布状态、游玩次数和草稿生成出的独立物品素材引用。
- 结构:`profile_id PK: String`, `owner_user_id: String`, `source_session_id: String`, `author_display_name: String`, `game_name: String`, `theme_text: String`, `summary_text: String`, `tags_json: String`, `cover_image_src: String`, `cover_asset_id: String`, `clear_count: u32`, `difficulty: u32`, `config_json: String`, `publication_status: String`, `play_count: u32`, `updated_at: Timestamp`, `published_at: Option<Timestamp>`, `generated_item_assets_json: Option<String>`
- 说明:`generated_item_assets_json` 保存 `Match3DGeneratedItemAsset` 数组 JSON用于草稿页退出后从作品架重进时恢复 `3D素材` Tab 中的切割图片预览;基础信息自动保存和发布必须保留该字段。
- 说明:`generated_item_assets_json` 保存 `Match3DGeneratedItemAsset` 数组 JSON用于草稿页退出后从作品架重进时恢复 `3D素材` Tab 中的切割图片和 GLB 模型预览;运行态也通过该字段拿到 `modelSrc` / `modelObjectKey` 并优先渲染生成模型。基础信息自动保存和发布必须保留该字段。
- 索引:`owner_user_id`, `publication_status`
```sql

View File

@@ -1,13 +1,13 @@
# 用户标签、邀请码授予与拼图榜单展示方案
更新时间:`2026-05-10`
更新时间:`2026-05-11`
## 1. 目标
本次新增用户标签系统的最小闭环:
1. `user_account` 增加账号标签字段,默认空
2. 后台预置邀请码可配置授予标签。
1. `user_account` 增加账号标签字段,数据库默认空,业务读取时按空数组处理
2. 后台预置邀请码可通过原有 `metadata` 字段配置授予标签。
3. 用户填写带标签的邀请码后,把标签合并到自己的账号。
4. 标签默认不在前端资料页、邀请中心或通用接口展示。
5. 拼图排行榜仅对白名单标签做展示,首版只展示 `北科`
@@ -16,19 +16,21 @@
### `user_account.user_tags`
- 类型:`Vec<String>`
- 默认:空数组。
- 类型:`Option<Vec<String>>`
- 默认:`None`,业务层读取时统一按空数组处理
- 语义:账号级运营标签,属于后台与服务端投影数据,不作为普通前端个人资料字段。
- 写入:首版只由邀请码兑换链路合并写入。
- 迁移:旧迁移包和旧数据库按空数组兼容
- 迁移:旧迁移包和旧数据库按 `null` 兼容,再由业务层归一化为空数组。
### `profile_invite_code.granted_user_tags`
### `profile_invite_code.metadata_json.userTags`
- 类型:`Vec<String>`
- 默认:空数组
- 类型:`metadata_json` 对象里的 `userTags: string[]`
- 默认:字段缺失或空数组时不授予标签
- 语义:使用该邀请码后授予被邀请账号的标签列表。
- 范围:后台运营预置码和普通用户个人邀请码都可存字段,但后台表单首版只允许管理员配置预置码。
- 迁移:旧邀请码按空数组兼容
- 存储:不再新增或使用独立的邀请码标签列;后台保存时把用户标签写回 `metadata.userTags`
- 解析:服务端优先读取 `metadata_json.userTags`,并兼容解析 `metadata_json.user_tags`
- 迁移:旧邀请码缺少 `metadata_json` 时按 `{}` 兼容;旧迁移包里已废弃的独立字段会在导入时丢弃。
## 3. 标签归一化
@@ -45,35 +47,38 @@
1. 写入 `profile_referral_relation`
2. 发放原有双方奖励。
3. 读取 `profile_invite_code.granted_user_tags`
3. `profile_invite_code.metadata_json` 解析 `userTags` / `user_tags`
4. 将这些标签合并进 `user_account.user_tags`
管理员更新邀请码时,`grantedUserTags` 代表覆盖该邀请码之后授予的标签集合;空数组代表不授予标签。更新邀请码不会回溯修改已经使用过该邀请码的账号。
管理员更新邀请码时,后台表单里的用户标签会覆盖写入 `metadata.userTags`;空数组代表不授予标签。更新邀请码不会回溯修改已经使用过该邀请码的账号。
## 5. API 契约
后台邀请码 upsert 请求增加
后台邀请码 upsert 请求继续只提交 `metadata`,标签写在 `metadata.userTags`
```json
{
"inviteCode": "BEIKE2026",
"grantedUserTags": ["北科"],
"metadata": {},
"metadata": {
"userTags": ["北科"]
},
"startsAt": null,
"expiresAt": null
}
```
后台邀请码列表和 upsert 返回增加同名字段
后台邀请码列表和 upsert 返回继续回传 `metadata`
```json
{
"inviteCode": "BEIKE2026",
"grantedUserTags": ["北科"]
"metadata": {
"userTags": ["北科"]
}
}
```
用户侧邀请中心、账号资料、登录返回和普通 profile 接口不返回 `userTags`
后台表单展示时从 `metadata.userTags` 回显用户标签;用户侧邀请中心、账号资料、登录返回和普通 profile 接口不返回 `userTags`
## 6. 拼图排行榜展示
@@ -94,9 +99,10 @@
## 7. 验收
1. 新账号 `user_account.user_tags` 默认为空
2. 后台创建邀请码时可填写 `北科`列表和结果面板可回显。
1. 新账号 `user_account.user_tags` 数据库默认为 `None`,业务读取为空数组
2. 后台创建邀请码时可填写 `北科`请求和返回的 `metadata.userTags` 可回显。
3. 用户填写该邀请码后,账号表 `user_tags` 包含 `北科`
4. 不带标签的邀请码不改变账号标签。
5. 拼图排行榜中带 `北科` 的用户昵称下方显示 `北科`,其它标签不显示。
6. 执行 `npm run check:encoding``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`,并按影响范围执行后台 typecheck / 拼图前端测试。
6. 生成 SpacetimeDB bindings 后,不再出现独立的邀请码标签字段。
7. 执行 `npm run check:encoding``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`,并按影响范围执行后台 typecheck / 拼图前端测试。