Merge origin/master into codex/wechat
This commit is contained in:
@@ -11,12 +11,26 @@
|
||||
- [规划与优先级](./planning/README.md):当前阶段的迭代排序与落地优先级。
|
||||
- [参考目录](./reference/README.md):脚本/Function 速查入口。
|
||||
重点补充:RPG 创作与运行时脚本职责地图见 [RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md](./reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md)。
|
||||
- [PRD](./prd):产品需求与阶段计划;后台管理独立前端工程见 [ADMIN_WEB_CONSOLE_PRD_2026-04-30.md](./prd/ADMIN_WEB_CONSOLE_PRD_2026-04-30.md),新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md),新增抓大鹅 Match3D 玩法方案见 [AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md](./prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md)。
|
||||
- [埋点查询](./tracking/README.md):埋点原始事件与聚合投影的本地 SQL 查询。
|
||||
- [运营查询](./operations/README.md):任务、领奖、钱包对账等后台核查查询。
|
||||
- [PRD](./prd/README.md):产品需求与阶段计划;参考 MOKU / 幕间类 AI 文游的百梦 `text-game` 模板口径见 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md](./prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md),视觉小说模板 TXT 玩法平台化接入口径见 [AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md](./prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md),创意互动内容 Agent Phase 1 的 LangChain-Rust PoC、拼图闭环和并行任务拆分见 [CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md](./prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md),幸存者类模板闭环见 [AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md](./prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md),后台管理独立前端工程见 [ADMIN_WEB_CONSOLE_PRD_2026-04-30.md](./prd/ADMIN_WEB_CONSOLE_PRD_2026-04-30.md),新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md),新增抓大鹅 Match3D 玩法方案见 [AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md](./prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md),方洞挑战创作、发布与试玩闭环见 [AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md](./prd/AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md)。
|
||||
|
||||
生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md);private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md)。
|
||||
|
||||
SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。
|
||||
|
||||
创作 Agent 问答流式失败时保留已显示回复、并透出更具体上游错误的契约见 [CREATION_AGENT_STREAM_FAILURE_RETENTION_FIX_2026-05-05.md](./technical/CREATION_AGENT_STREAM_FAILURE_RETENTION_FIX_2026-05-05.md)。
|
||||
|
||||
`maincloud` 相关脚本、环境变量、测试名和文档要求已统一判定为历史残留,后续禁止新增、运行或引用;当前后端 smoke 使用 `npm run api-server` 与 `/healthz`,详细规则见 [MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md](./technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md)。
|
||||
|
||||
基于 LangChain-Rust,以“感知—思考—记忆—行动—反思—协作”闭环完成图文创意理解、拼图模板选择、积分范围确认、拼图草稿字段填充、立即试玩和自然语言修订草稿字段的 Agent 方案见 [CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md](./technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md)。
|
||||
|
||||
创意互动内容 Agent Phase 1 的产品边界、实现细节、SpacetimeDB 落点、前端接入和可并行任务拆分见 [CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md](./prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md)。
|
||||
|
||||
视觉小说模板接入以 [AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md](./prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md) 为最新口径:只吸收外部 TXT 玩法的模板创作与运行经验,禁止迁入外部平台功能,并删除回放。
|
||||
|
||||
AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md](./prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md) 为最新口径:只吸收 MOKU / 幕间类 AI 文游的剧本游乐场、自由行动、AI GM、记忆和模拟器强反馈经验,禁止迁入外部社区、支付、榜单、私有存档或回放。
|
||||
|
||||
## 推荐阅读顺序
|
||||
|
||||
1. 先看 [经验沉淀](./experience/README.md),快速建立这个项目的开发共识。
|
||||
@@ -33,3 +47,5 @@ SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段
|
||||
- `technical/`:偏技术选型、实现路线、竞品/产品形态拆解。
|
||||
- `planning/`:偏阶段优先级与推进顺序。
|
||||
- `reference/`:偏目录、速查、检索辅助。
|
||||
- `tracking/`:偏埋点原始事实和聚合投影查询,不放任务进度或钱包对账。
|
||||
- `operations/`:偏后台运营核查、对账和排障查询。
|
||||
|
||||
36
docs/agents/domain.md
Normal file
36
docs/agents/domain.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Domain Docs
|
||||
|
||||
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
||||
|
||||
## Layout
|
||||
|
||||
This repo uses a **single-context** layout for Matt Pocock engineering skills:
|
||||
|
||||
- `CONTEXT.md` at the repo root, when present, is the primary domain glossary/context file.
|
||||
- `docs/adr/`, when present, contains architecture decision records.
|
||||
- If either path does not exist, proceed silently; do not block the task just to create it.
|
||||
|
||||
## Before exploring, read these
|
||||
|
||||
1. Root `CONTEXT.md`, if present.
|
||||
2. Relevant ADRs under `docs/adr/`, if present.
|
||||
3. Existing project context that predates this setup:
|
||||
- `.hermes/README.md`
|
||||
- `.hermes/shared-memory/project-overview.md`
|
||||
- `.hermes/shared-memory/team-conventions.md`
|
||||
- `.hermes/shared-memory/development-workflow.md`
|
||||
- `.hermes/shared-memory/decision-log.md`
|
||||
- `.hermes/shared-memory/pitfalls.md`
|
||||
- Relevant files under `docs/technical/`, `docs/prd/`, `docs/design/`, and `docs/experience/`
|
||||
|
||||
Follow `AGENTS.md` when it is more specific than this file. If older docs conflict with current code or newer technical docs, treat current code and newer docs as authoritative and update stale docs when the task requires it.
|
||||
|
||||
## Use the glossary's vocabulary
|
||||
|
||||
When your output names a domain concept in an issue title, refactor proposal, diagnosis, test name, or implementation plan, use the term as defined in `CONTEXT.md` when available. Do not drift to synonyms the glossary explicitly avoids.
|
||||
|
||||
If the concept you need is not in the glossary yet, either use the established vocabulary from `.hermes/shared-memory/` and `docs/`, or note the gap for a future documentation pass.
|
||||
|
||||
## Flag ADR conflicts
|
||||
|
||||
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding it.
|
||||
35
docs/agents/issue-tracker.md
Normal file
35
docs/agents/issue-tracker.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Issue tracker: Gitea
|
||||
|
||||
Issues and PRDs for this repo live as issues in the self-hosted Gitea remote:
|
||||
|
||||
- Remote: `http://82.157.175.59:3000/GenarrativeAI/Genarrative.git`
|
||||
- Tracker type: Gitea Issues
|
||||
|
||||
## Conventions
|
||||
|
||||
- Prefer the Gitea `tea` CLI when it is installed and configured for this host.
|
||||
- Do not use GitHub `gh` or GitLab `glab` for this repo unless the repository is explicitly migrated to those platforms.
|
||||
- If `tea` is unavailable, use the Gitea Web UI or Gitea REST API for the same operations.
|
||||
|
||||
## Common operations with `tea`
|
||||
|
||||
Exact flags can vary by `tea` version. Run `tea issues --help` or `tea issue --help` before using a command in a new environment.
|
||||
|
||||
- Create an issue: `tea issues create --title "..." --body "..."`
|
||||
- Read an issue: `tea issues view <number>`
|
||||
- List issues: `tea issues list`
|
||||
- Comment on an issue: use the installed `tea` issue comment command shown by `tea issues --help`; if unavailable, use the Gitea Web UI or REST API.
|
||||
- Apply labels: use the installed `tea` issue update/edit command shown by `tea issues --help`; if unavailable, use the Gitea Web UI or REST API.
|
||||
- Close an issue: use the installed `tea` issue close/update command shown by `tea issues --help`; if unavailable, use the Gitea Web UI or REST API.
|
||||
|
||||
## When a skill says "publish to the issue tracker"
|
||||
|
||||
Create a Gitea issue in `GenarrativeAI/Genarrative` with the requested title, body, labels, and links back to any relevant docs or branch.
|
||||
|
||||
## When a skill says "fetch the relevant ticket"
|
||||
|
||||
Read the Gitea issue body and comments/notes for the referenced issue number. Include labels and current open/closed state in the working context.
|
||||
|
||||
## Authentication
|
||||
|
||||
Use the locally configured Gitea credentials for the current developer. Do not commit tokens, cookies, `.env`, or local credential files.
|
||||
15
docs/agents/triage-labels.md
Normal file
15
docs/agents/triage-labels.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Triage Labels
|
||||
|
||||
The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's Gitea issue tracker.
|
||||
|
||||
| Label in mattpocock/skills | Label in our tracker | Meaning |
|
||||
| -------------------------- | -------------------- | ---------------------------------------- |
|
||||
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
|
||||
| `needs-info` | `needs-info` | Waiting on reporter for more information |
|
||||
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
|
||||
| `ready-for-human` | `ready-for-human` | Requires human implementation |
|
||||
| `wontfix` | `wontfix` | Will not be actioned |
|
||||
|
||||
When a skill mentions a role, use the corresponding Gitea label string from this table.
|
||||
|
||||
If the Gitea repository later adopts Chinese labels or a different naming scheme, edit the right-hand column here rather than letting skills create duplicate labels.
|
||||
@@ -19,6 +19,8 @@
|
||||
- [CHARACTER_ASSET_PROMPT_CHAIN_AUDIT_2026-04-20.md](./CHARACTER_ASSET_PROMPT_CHAIN_AUDIT_2026-04-20.md):角色资产默认描述文本、正式图像/动作 prompt、共享模板与保留接口的分层与冗余审计。
|
||||
- [RPG_RUNTIME_DIRECT_DRAFT_PROFILE_AUDIT_2026-04-25.md](./RPG_RUNTIME_DIRECT_DRAFT_PROFILE_AUDIT_2026-04-25.md):RPG 运行时进入世界时改为直读 Agent session 草稿 profile 的链路检查。
|
||||
- [RPG_WORLD_DRAFT_EDIT_AUTOSAVE_OVERRIDE_AUDIT_2026-04-28.md](./RPG_WORLD_DRAFT_EDIT_AUTOSAVE_OVERRIDE_AUDIT_2026-04-28.md):RPG 世界草稿结果页编辑后被旧设定覆盖的前端本地态、session 真相源与自动保存链路审计。
|
||||
- [VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md](./VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md):视觉小说 VN-11 回放删除与外部平台功能误入负向扫描报告。
|
||||
- [VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md](./VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md):视觉小说 VN-12 全链路联调与自动化验收报告。
|
||||
- [engineering/RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_AUDIT_2026-04-28.md](./engineering/RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_AUDIT_2026-04-28.md):RPG 前端脚本中仍应迁到 `server-rs` / SpacetimeDB 的开局、快照、story engine、战斗、NPC/背包规则与创作残留后门审计。
|
||||
- [engineering/RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_COMPLETION_CHECK_2026-04-28.md](./engineering/RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_COMPLETION_CHECK_2026-04-28.md):RPG 前端脚本后端迁移完成度复核,标明开局、快照、story engine / prompt context、`camp_travel_home_scene`、战斗、NPC、背包/锻造、结果页保存 normalize 与角色资产 prompt 主链均已收口。
|
||||
- [engineering/ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-20.md](./engineering/ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-20.md):对 `2026-04-19` 工程清理审计的当前仓库复核,区分已完成项、仍存边界问题和新的热点迁移。
|
||||
|
||||
377
docs/audits/SECURITY_VULNERABILITY_SCAN_2026-05-11.md
Normal file
377
docs/audits/SECURITY_VULNERABILITY_SCAN_2026-05-11.md
Normal 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-1:Vite 依赖存在高危 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 dependency,severity 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-1:Rust 依赖漏洞未完成扫描
|
||||
|
||||
**原因:**
|
||||
|
||||
本机没有 `cargo-audit`,本次没有擅自安装用户级 Cargo 工具。
|
||||
|
||||
**建议:**
|
||||
|
||||
如确认允许安装:
|
||||
|
||||
```bash
|
||||
cargo install cargo-audit --locked
|
||||
cargo audit --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
或在 CI/具备工具的环境执行并回填结果。
|
||||
|
||||
## Informational / 源码热点
|
||||
|
||||
### I-1:JS/TS XSS / 动态执行热点
|
||||
|
||||
扫描命中 1 行:
|
||||
|
||||
- `src/routing/RouteImageReadyGate.test.ts` 中测试代码使用 `root.innerHTML`。
|
||||
|
||||
初步判断为测试环境构造 DOM,不是生产漏洞。若后续发现生产代码使用 `dangerouslySetInnerHTML` 或直接 `innerHTML = userInput`,应升级为 High。
|
||||
|
||||
### I-2:Node 脚本命令执行热点
|
||||
|
||||
扫描命中 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-3:Rust 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。
|
||||
32
docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md
Normal file
32
docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# VN-11 负向扫描报告
|
||||
|
||||
生成日期:2026-05-07
|
||||
|
||||
## 扫描范围
|
||||
|
||||
- 工程代码:`src/`、`packages/shared/src/`、`server-rs/crates/`
|
||||
- 文档与共享记忆:`docs/`、`.hermes/shared-memory/`
|
||||
- 外部平台误入复核:视觉小说前端、service、shared contracts、Rust contracts、module、api-server、SpacetimeDB schema 与 facade 路径
|
||||
|
||||
## 扫描结论
|
||||
|
||||
- 工程代码回放类直出命中:0
|
||||
- 文档 / 共享记忆回放类命中:222
|
||||
- 视觉小说实现路径外部平台能力疑似误入命中:0
|
||||
|
||||
## 处理记录
|
||||
|
||||
- 已将 `storyEngine` 回归工具的命名从 replay 语义收口为 rerun / 复测语义。
|
||||
- 已将技能效果预览按钮的内部状态与文案从重播语义收口为重新预览语义。
|
||||
- 已确认视觉小说工程路径未新增回放路由、DTO、表、按钮、文案、外部平台账号 / 订单 / 会员 / 促销 / 后台 / 公开市场或私有存档能力。
|
||||
|
||||
## 文档命中说明
|
||||
|
||||
- 文档命中来自历史旧文档、设计复盘、禁止语境、负向验收或本报告记录。VN-11 工程门禁只阻断代码路径新增能力。
|
||||
|
||||
## 门禁命令
|
||||
|
||||
```bash
|
||||
npm run check:visual-novel-vn11
|
||||
```
|
||||
|
||||
92
docs/audits/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md
Normal file
92
docs/audits/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# VN-12 全链路联调与自动化验收报告
|
||||
|
||||
生成日期:2026-05-07
|
||||
|
||||
## 结论
|
||||
|
||||
- 状态:通过
|
||||
- 失败项:0
|
||||
- 收口说明:VN-12 本次只补验收门禁、关键路径测试和报告记录,未扩展新玩法功能。
|
||||
|
||||
## 自动化验收清单
|
||||
|
||||
- docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md
|
||||
- docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md
|
||||
- src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx
|
||||
- src/components/visual-novel-result/VisualNovelResultView.test.tsx
|
||||
- src/components/visual-novel-runtime/VisualNovelRuntimeShell.test.tsx
|
||||
- src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts
|
||||
- src/services/visual-novel-runtime/visualNovelRuntimeSse.test.ts
|
||||
- server-rs/crates/api-server/src/visual_novel.rs
|
||||
- server-rs/crates/module-visual-novel/src/application.rs
|
||||
- server-rs/crates/shared-contracts/src/visual_novel.rs
|
||||
- package.json
|
||||
- server-rs/crates/api-server/src/app.rs
|
||||
- src/services/visual-novel-runtime/visualNovelRuntimeClient.ts
|
||||
- src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts
|
||||
- src/services/visual-novel-runtime/visualNovelRuntimeSse.test.ts
|
||||
- src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx
|
||||
- src/components/visual-novel-result/VisualNovelResultView.test.tsx
|
||||
- src/components/visual-novel-runtime/VisualNovelRuntimeShell.test.tsx
|
||||
|
||||
## API smoke
|
||||
|
||||
- `/api/creation/visual-novel/sessions`
|
||||
- `/api/creation/visual-novel/works`
|
||||
- `/api/runtime/visual-novel/gallery`
|
||||
- `/api/runtime/visual-novel/works/{profile_id}/runs`
|
||||
- `/api/runtime/visual-novel/runs/{run_id}/actions/stream`
|
||||
- `/api/runtime/visual-novel/runs/{run_id}/history`
|
||||
- `/api/runtime/visual-novel/runs/{run_id}/regenerate`
|
||||
- `/api/profile/save-archives`
|
||||
- `/api/profile/save-archives/{world_key}`
|
||||
- `/api/runtime/save/snapshot`
|
||||
|
||||
本次实测:
|
||||
|
||||
- `npm run api-server` 可启动 Rust `api-server`。
|
||||
- `GET http://127.0.0.1:3100/healthz` 返回 `200`,响应为 `{"ok":true,"service":"genarrative-api-server"}`。
|
||||
- `GET /api/runtime/visual-novel/gallery` 在当前本地环境返回超时 / `502`,日志显示 `api-server` 连接 `127.0.0.1:3101` SpacetimeDB 数据库 `xushi-p4wfr` 被拒绝;该项按本地 SpacetimeDB 未完整就绪记录为环境阻塞,不新增工程实现。
|
||||
|
||||
## 前端关键路径
|
||||
|
||||
- 创作工作台:`VisualNovelAgentWorkspace`
|
||||
- 结果页:`VisualNovelResultView`
|
||||
- 运行时:`VisualNovelRuntimeShell`
|
||||
- 运行时 SSE:`visualNovelRuntimeSse` / `visualNovelRuntimeClient`
|
||||
|
||||
## 桌面 / 移动端检查
|
||||
|
||||
- 桌面端:已用 Edge headless 截取 `/creation/visual-novel/agent`,文件为 `docs/audits/VN12_VISUAL_NOVEL_DESKTOP_2026-05-07.png`。
|
||||
- 移动端:已用 Edge headless 截取 `/creation/visual-novel/agent`,文件为 `docs/audits/VN12_VISUAL_NOVEL_MOBILE_2026-05-07.png`。
|
||||
- in-app browser 插件本次未发现可用 IAB backend,截图使用本机 Edge headless 兜底完成。
|
||||
|
||||
## 校验摘要
|
||||
|
||||
- package.json scripts: 通过
|
||||
- api-server visual novel routes: 通过
|
||||
- visual novel runtime client routes: 通过
|
||||
- visual novel runtime client tests: 通过
|
||||
- visual novel SSE tests: 通过
|
||||
- visual novel creation tests: 通过
|
||||
- visual novel result tests: 通过
|
||||
- visual novel runtime tests: 通过
|
||||
|
||||
## 执行命令
|
||||
|
||||
```bash
|
||||
npm run check:visual-novel-vn12 -- --write-report
|
||||
npm run test -- src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/visual-novel-runtime/VisualNovelRuntimeShell.test.tsx src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts src/services/visual-novel-runtime/visualNovelRuntimeSse.test.ts
|
||||
npm run check:encoding
|
||||
npm run typecheck
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts
|
||||
cargo test -p module-visual-novel
|
||||
cargo check -p api-server
|
||||
```
|
||||
|
||||
## 未覆盖风险
|
||||
|
||||
- 当前本地 SpacetimeDB 连接未完整就绪,公开 gallery API 的真实数据返回未在本次环境完成;`/healthz` 与编译 / 单测已通过。
|
||||
- 若接口路由或测试名称后续调整,需要同步更新本门禁脚本与报告模板。
|
||||
|
||||
BIN
docs/audits/VN12_VISUAL_NOVEL_DESKTOP_2026-05-07.png
Normal file
BIN
docs/audits/VN12_VISUAL_NOVEL_DESKTOP_2026-05-07.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
BIN
docs/audits/VN12_VISUAL_NOVEL_MOBILE_2026-05-07.png
Normal file
BIN
docs/audits/VN12_VISUAL_NOVEL_MOBILE_2026-05-07.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
149
docs/design/BAIMENG_EXPO_ROLLUP_BANNER_DESIGN_2026-05-07.md
Normal file
149
docs/design/BAIMENG_EXPO_ROLLUP_BANNER_DESIGN_2026-05-07.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# 百梦线下展会易拉宝设计记录 2026-05-07
|
||||
|
||||
## 1. 目标
|
||||
|
||||
为百梦线下展会制作一张纵向易拉宝广告展板,用于在展位现场快速传达:
|
||||
|
||||
1. 产品名称:百梦。
|
||||
2. 产品愿景:百梦AI团队致力于打造AI互动内容UGC平台。
|
||||
3. 产品slogan:不用代码,不用美术,10分钟把脑洞变成有趣的体验。
|
||||
4. 产品特点:低门槛创作、高完成度作品、玩过后可改造并发布。
|
||||
5. 关键技术:Harness Engineering、多Agent调度、AI创作工具、AI原生游戏框架。
|
||||
6. 产品心智:想玩但找不到、玩到不满意、平台外体验不满意时,都可以来百梦做成自己满意的。
|
||||
|
||||
## 2. 视觉方向
|
||||
|
||||
本次延续 `BAIMENG_LOGO_GPT_IMAGE_2_CONCEPTS_2026-05-05.md` 中最新收敛的气泡共创方向:
|
||||
|
||||
- 参考 logo:`output/imagegen/baimeng-logo-bubble-04-07-refine-batch13/baimeng-bubble-04-07-refine-01-04-flat-rainbow-band-core.png`
|
||||
- 主视觉语义:轻盈气泡、很多创意、UGC共创、作品改造与分享。
|
||||
- 色彩:暖白、珊瑚粉、薰衣草紫、天蓝、薄荷青、少量金色光感。
|
||||
- 展会阅读策略:远看先读产品名与slogan,近看再读产品心智与关键技术。
|
||||
|
||||
## 3. 文案层级
|
||||
|
||||
最终展板文案压缩为四层。
|
||||
|
||||
第一层:品牌识别
|
||||
|
||||
```text
|
||||
百梦
|
||||
10分钟做自己的互动内容
|
||||
```
|
||||
|
||||
第二层:远读slogan
|
||||
|
||||
```text
|
||||
不用代码,不用美术,
|
||||
10分钟把脑洞变成有趣的体验
|
||||
```
|
||||
|
||||
第三层:产品特点
|
||||
|
||||
```text
|
||||
10分钟成品级创作
|
||||
玩过就能改造发布
|
||||
创意到作品闭环
|
||||
```
|
||||
|
||||
并拆成三张近读卡:
|
||||
|
||||
```text
|
||||
创作:从一句灵感开始,AI帮助完成剧本、角色、场景、系统与视觉草稿。
|
||||
游玩:作品不是静态文本,而是可进入、可推进、可演出的互动体验。
|
||||
改造:玩到不满意的作品,可以快速改成自己喜欢的版本并再次发布。
|
||||
```
|
||||
|
||||
第四层:产品心智与关键技术
|
||||
|
||||
```text
|
||||
当用户找不到想玩的游戏 -> 来百梦做给自己玩
|
||||
当用户玩到了不好玩的游戏 -> 快速改成自己喜欢玩的
|
||||
当用户在平台外玩到了不满意的游戏 -> 来百梦做成自己满意的
|
||||
什么游戏最好玩? -> 来百梦玩自己做的游戏最好玩
|
||||
```
|
||||
|
||||
关键技术压缩为:
|
||||
|
||||
```text
|
||||
基于 Harness Engineering 理论,将专家知识融入 AI 创作工具与 AI 原生游戏框架。
|
||||
|
||||
AI创作工具:
|
||||
通过多Agent调度算法,把策划、美术SOP与专家知识融入模板框架,提升剧本类、数值类、系统类、角色设计、场景设计、CG设计等垂类任务的完成率和效率。
|
||||
|
||||
AI原生游戏框架:
|
||||
通过系统化约束模型输入输出,把实时剧本创作和游戏设计专家知识内嵌在规则中,提升实时剧情、数值对齐、画面对齐、任务与物品生成质量。
|
||||
```
|
||||
|
||||
## 4. 生成方式
|
||||
|
||||
主视觉底图使用仓库内 `gpt-image-2` 工作流生成;2026-05-09 起同类工作流走 VectorEngine:
|
||||
|
||||
```text
|
||||
model: gpt-image-2
|
||||
size: 1536x3840
|
||||
reference image: 百梦气泡共创logo方向图
|
||||
output: output/imagegen/baimeng-expo-rollup/baimeng-rollup-background-gpt-image-2.png
|
||||
```
|
||||
|
||||
2026-05-08 根据新文案重新调用 `gpt-image-2` 生成新版底图。新版底图在中上部保留更干净的两行 slogan 留白,并在下半部增加轻量内容卡、创作路径和 AI 辅助创作氛围,最终再叠加精确中文排版。
|
||||
|
||||
因为图片模型直接生成中文长文案存在错字风险,最终稿采用“gpt-image-2 底图 + 本地精确中文排版”的方式生成:
|
||||
|
||||
```text
|
||||
tmp/imagegen/generate-baimeng-rollup-background.mjs
|
||||
tmp/imagegen/render-baimeng-rollup.py
|
||||
```
|
||||
|
||||
最终输出:
|
||||
|
||||
```text
|
||||
output/imagegen/baimeng-expo-rollup/baimeng-rollup-final-cn.png
|
||||
output/imagegen/baimeng-expo-rollup/baimeng-rollup-final-cn-preview.png
|
||||
```
|
||||
|
||||
## 5. 后续改版建议
|
||||
|
||||
1. 若印刷厂提供具体尺寸和出血要求,优先在 `render-baimeng-rollup.py` 中调整画布比例、边距和安全区。
|
||||
2. 若需要放二维码,应放在底部独立留白区,不遮挡产品心智和关键技术段。
|
||||
3. 若展会现场观众偏投资人或B端合作方,可以把“产品心智”段压缩,放大“关键技术”与平台愿景。
|
||||
4. 若观众偏玩家或普通创作者,可以把“关键技术”段压缩,放大“10分钟创作、玩过就改、发布分享”的闭环。
|
||||
|
||||
## 6. 公司招聘版 2026-05-11
|
||||
|
||||
2026-05-11 根据线下招聘场景,将海报方向从“纯产品宣传”调整为“公司 + 产品 + 岗位”的整体宣传。
|
||||
|
||||
新版定位:
|
||||
|
||||
```text
|
||||
北京亓盒网络科技有限公司
|
||||
岗位名称:AI 原生游戏产品/内容实习生
|
||||
行业方向:AI 原生游戏 × UGC 内容创作 × 互动叙事
|
||||
产品:百梦 AI互动内容创作平台
|
||||
```
|
||||
|
||||
新版保留百梦气泡色彩、轻盈白底和创作流动感,但新增校园实验室、AI 游戏创作、作品卡、产品测试与内容设计氛围。版面结构调整为:
|
||||
|
||||
1. 顶部:公司名、岗位名、行业方向与招聘主标题。
|
||||
2. 中上:百梦产品主张与三枚产品能力标签。
|
||||
3. 中部:按 `游玩 -> 改造 -> 创作` 顺序展示产品体验闭环。
|
||||
4. 中下:介绍“我们正在做的事「百梦」”。
|
||||
5. 下部:实习生参与内容、加分项、团队背景和联系方式。
|
||||
6. 底部:预留两个方形二维码占位,收尾文案为 `百梦 | 让每个人都能做自己的游戏`。
|
||||
|
||||
新版使用当前仓库 `VectorEngine gpt-image-2-all` 路径生成底图:
|
||||
|
||||
```text
|
||||
model: gpt-image-2-all
|
||||
size: 1536x3840
|
||||
reference image 1: 用户提供的上一版海报截图
|
||||
reference image 2: 百梦气泡共创logo方向图
|
||||
output: output/imagegen/baimeng-recruitment-rollup/baimeng-recruitment-rollup-background-gpt-image-2-all.png
|
||||
```
|
||||
|
||||
最终输出:
|
||||
|
||||
```text
|
||||
output/imagegen/baimeng-recruitment-rollup/baimeng-recruitment-rollup-final-cn.png
|
||||
output/imagegen/baimeng-recruitment-rollup/baimeng-recruitment-rollup-final-cn-preview.png
|
||||
```
|
||||
287
docs/design/BAIMENG_LOGO_GPT_IMAGE_2_CONCEPTS_2026-05-05.md
Normal file
287
docs/design/BAIMENG_LOGO_GPT_IMAGE_2_CONCEPTS_2026-05-05.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# 百梦 logo gpt-image-2 概念方案 2026-05-05
|
||||
|
||||
## 1. 产品气质提炼
|
||||
|
||||
当前产品对外名为“百梦”,核心不是单一 RPG 玩法,而是面向互动叙事、世界生成和运行时演出的 AI 原生创作平台。
|
||||
|
||||
本次 logo 概念围绕以下关键词设计:
|
||||
|
||||
1. `百`:多世界、多题材、多角色关系,不是单条故事线。
|
||||
2. `梦`:创作者的世界锚点、想象入口和叙事氛围。
|
||||
3. `AI 原生`:AI 负责叙事表达、关系生成和世界扩展。
|
||||
4. `规则裁决`:本地系统负责状态、任务、背包、招募、存档等可信边界。
|
||||
5. `视觉 RPG`:保留游戏感、角色感和舞台感,但平台层需要比纯像素 UI 更清爽。
|
||||
|
||||
## 2. 方案 A:梦门星轨
|
||||
|
||||
视觉方向:
|
||||
|
||||
- 用一个打开的门 / 星门作为主体,门内有多条细线向外延展,代表百梦主从一个灵感入口进入多个世界。
|
||||
- 负形中弱化出“百”的结构,不强行写字,方便后续做 App icon、favicon 和小尺寸导航标。
|
||||
- 色彩以暖白、珊瑚粉、少量紫蓝和深墨色组成,贴近平台亮色主题,同时保留梦境和 AI 的科技感。
|
||||
|
||||
适合场景:
|
||||
|
||||
- 平台主 logo
|
||||
- App icon
|
||||
- 创作入口主视觉
|
||||
|
||||
设计含义:
|
||||
|
||||
“梦门”代表创作入口,“星轨”代表剧情线程、角色关系和世界分支。它强调百梦是让创作者开启世界的工具,而不是只播放固定剧情的游戏。
|
||||
|
||||
## 3. 方案 B:叙事图谱
|
||||
|
||||
视觉方向:
|
||||
|
||||
- 用节点、弧线和轻微书页轮廓组成一个稳定的圆形标志。
|
||||
- 图谱整体形成近似“百”的秩序感,避免复杂到小尺寸失真。
|
||||
- 色彩以深墨、湖青、光点金和珊瑚色组合,体现规则边界与创作活力并存。
|
||||
|
||||
适合场景:
|
||||
|
||||
- 技术品牌页
|
||||
- 开发者文档
|
||||
- AI 剧情引擎介绍
|
||||
|
||||
设计含义:
|
||||
|
||||
这个方向更强调“世界不是一段文本,而是一张可控的叙事图谱”。节点是角色、地点、物件和任务,弧线是关系、暗线与回响。
|
||||
|
||||
## 4. 方案 C:光点梦织
|
||||
|
||||
视觉方向:
|
||||
|
||||
- 多个光点汇聚成柔和的“B / 百”抽象符号,中心像一枚被点亮的种子。
|
||||
- 线条更轻,图形更偏平台化和消费级,不做重游戏徽章。
|
||||
- 色彩采用珊瑚粉、暖金、浅青和深色细线,表达“光点”消费单位与灵感生长。
|
||||
|
||||
适合场景:
|
||||
|
||||
- 移动端启动页
|
||||
- 用户增长、邀请、充值与创作激励相关页面
|
||||
- 更轻量的品牌应用
|
||||
|
||||
设计含义:
|
||||
|
||||
每个光点都是一次生成、一次灵感、一次世界推进。多点汇聚成梦,呼应“百梦”里从许多小创意生长出完整作品的路径。
|
||||
|
||||
## 5. 方案 D:像素剧场
|
||||
|
||||
视觉方向:
|
||||
|
||||
- 以轻度像素化的舞台窗、卷轴和星形光标构成标志,但不使用重像素边框。
|
||||
- 保留视觉 RPG 的游戏感,适合作为游戏内或活动页的品牌变体。
|
||||
- 色彩更高对比,适配暗色游戏 UI,也能在亮色平台层中作为强调图标。
|
||||
|
||||
适合场景:
|
||||
|
||||
- 游戏内 HUD 品牌露出
|
||||
- 活动页、社区头像
|
||||
- 复古 RPG 氛围更强的物料
|
||||
|
||||
设计含义:
|
||||
|
||||
舞台代表演出,卷轴代表叙事,星形光标代表 AI 与玩家选择。它让百梦看起来仍然属于游戏和互动内容,而不是泛 AI 工具。
|
||||
|
||||
## 6. 生成说明
|
||||
|
||||
本次推荐先用 `gpt-image-2` 生成 4 张 `1024x1024` 方形草案。由于图片模型对中文文字的稳定性有限,首轮应优先验证“图形标识”而不是直接把“百梦”两个字烘焙进图片。最终产品落地时,建议使用真实前端字体或 SVG 字形承载“百梦”字标,把生成图作为图形标志参考。
|
||||
|
||||
## 7. 女性友好与全年龄潮流版补充
|
||||
|
||||
在用户明确希望吸引女性用户或全年龄段用户后,logo 方向从“玄感书法 / 高级符印”调整为“可爱、圆润、潮流、轻社交平台感”。
|
||||
|
||||
视觉方向:
|
||||
|
||||
- 字形更圆润,减少尖锐笔锋、黑底压迫感和玄幻气质。
|
||||
- 色彩从黑白、金色、深墨切换到奶油白、莓果粉、薰衣草紫、薄荷青。
|
||||
- 允许轻量梦泡、云朵、柔软光点等符号,但不堆叠插画。
|
||||
- 保留“百梦”中文字标作为主识别,图标可作为 App icon 或社交头像补充。
|
||||
|
||||
本轮生成 prompt:
|
||||
|
||||
`tmp/imagegen/baimeng_logo_cute_trendy_batch6_prompts.jsonl`
|
||||
|
||||
本轮输出目录:
|
||||
|
||||
`output/imagegen/baimeng-logo-cute-trendy-batch6/`
|
||||
|
||||
当前更适合作为产品主方向的是 `01-rounded-wordmark` 与 `04-icon-wordmark-lockup`。`03-dream-bubble-icon` 和 `05-soft-blob-mark` 更适合作为 App 图标或营销贴纸,而不是完整品牌字标。
|
||||
|
||||
## 8. 生活物件原型抽象版补充
|
||||
|
||||
在用户进一步要求“保持扁平和抽象,可以意象一些生活中较常见的事物作为原型”后,本轮将图形从糖果质感的梦泡收敛为更扁平的日常物件抽象。
|
||||
|
||||
候选原型:
|
||||
|
||||
- 枕头
|
||||
- 小夜灯
|
||||
- 便签 / 书签
|
||||
- 杯垫 / 泡泡饮料
|
||||
- 香薰 / 扩香石
|
||||
- 糖纸 / 包装折角
|
||||
- 叠放贴纸
|
||||
- 睡眠眼罩 / 软窗帘
|
||||
|
||||
本轮生成 prompt:
|
||||
|
||||
`tmp/imagegen/baimeng_logo_flat_daily_object_batch8_prompts.jsonl`
|
||||
|
||||
本轮输出目录:
|
||||
|
||||
`output/imagegen/baimeng-logo-flat-daily-object-batch8/`
|
||||
|
||||
当前最值得继续推进的是 `03-bookmark-notes`:它保留了扁平、抽象、生活物件原型和创作平台语义,也较少落入儿童化或睡眠 App 的既有联想。`06-candy-wrapper` 可以作为更潮流的备选方向,但品牌语义比书签/便签弱。
|
||||
|
||||
## 9. 简化产品主标版补充
|
||||
|
||||
在用户反馈“整体还不错,但元素太碎,有些适合作为 icon 但不适合作为产品 logo”后,本轮将生活物件原型继续压缩为:
|
||||
|
||||
- `1` 个主体轮廓
|
||||
- `1` 个负形
|
||||
- 不使用文字、星点、叠片、贴纸装饰和多层方案板
|
||||
|
||||
本轮生成 prompt:
|
||||
|
||||
`tmp/imagegen/baimeng_logo_simplified_product_batch9_prompts.jsonl`
|
||||
|
||||
本轮输出目录:
|
||||
|
||||
`output/imagegen/baimeng-logo-simplified-product-batch9/`
|
||||
|
||||
当前最值得继续推进的是 `03-single-bookmark` 与 `06-rounded-square-notch`:
|
||||
|
||||
- `03-single-bookmark` 更像独立品牌符号,保留了书签 / 作品卡 / 世界入口语义。
|
||||
- `06-rounded-square-notch` 更像 App icon 主体,亲和、简洁,但需要降低通用 App 图标感。
|
||||
|
||||
不建议继续推进 `01/02/07/08`,它们被模型渲染成了方案板或 icon set;`05` 的版式参考价值高,但中文文字仍不能直接作为最终资产。
|
||||
|
||||
## 10. 气泡共创主标版补充
|
||||
|
||||
在用户明确提出“用轻盈的气泡展现梦,用多个气泡展现很多(百),气泡交错表示 UGC、共创、分享”后,本轮将主方向收敛到多气泡共创符号。
|
||||
|
||||
设计原则:
|
||||
|
||||
- 用 `3-5` 个大气泡表达“很多 / 百”,避免散乱小点。
|
||||
- 气泡之间必须交错或重叠,表达用户创作互相连接、分享和共创。
|
||||
- 整体必须收束成一个可识别主轮廓,而不是一组装饰元素。
|
||||
- 保持扁平、抽象、轻盈、女性友好和全年龄亲和。
|
||||
|
||||
本轮生成 prompt:
|
||||
|
||||
`tmp/imagegen/baimeng_logo_bubble_cocreation_batch10_prompts.jsonl`
|
||||
|
||||
本轮输出目录:
|
||||
|
||||
`output/imagegen/baimeng-logo-bubble-cocreation-batch10/`
|
||||
|
||||
当前最值得继续推进的是:
|
||||
|
||||
- `08-minimal-three-bubbles`:最像产品 logo,结构克制,气泡交错和共创语义清楚。
|
||||
- `01-overlap-cluster`:更适合品牌主视觉,轻盈梦感更明显,但作为小尺寸 logo 稍弱。
|
||||
- `06-sharing-knot`:共创感强,但中间负形需要继续简化。
|
||||
|
||||
## 11. 吹泡泡行为亲和版补充
|
||||
|
||||
在用户进一步提出“希望落在现实中常见的事物或行为上,比如吹泡泡行为,让用户觉得接地气或使用起来很简单,有亲和力”后,本轮将气泡主标落到“泡泡棒 / 泡泡水 / 轻轻吹出泡泡”的日常原型。
|
||||
|
||||
设计原则:
|
||||
|
||||
- 泡泡棒或泡泡圈表达“很简单、人人会用、低门槛”。
|
||||
- `3-4` 个气泡表达梦、很多、分享和共创。
|
||||
- 不画人物、嘴巴、小孩或玩具包装,避免儿童用品感。
|
||||
- 保持扁平抽象和品牌主标感,避免成为功能 icon。
|
||||
|
||||
本轮生成 prompt:
|
||||
|
||||
`tmp/imagegen/baimeng_logo_bubble_wand_batch11_prompts.jsonl`
|
||||
|
||||
本轮输出目录:
|
||||
|
||||
`output/imagegen/baimeng-logo-bubble-wand-batch11/`
|
||||
|
||||
当前最值得继续推进的是:
|
||||
|
||||
- `08-minimal-ring-bubbles`:最干净,保留泡泡棒原型,同时不太幼稚。
|
||||
- `01-ring-three-bubbles`:识别直接,但工具 icon 感略强。
|
||||
- `04-breath-origin`:梦感更强,但现实吹泡泡行为弱。
|
||||
|
||||
## 12. 吹泡泡主标再收敛版补充
|
||||
|
||||
在“方向正确,再来一些”的基础上,本轮继续压缩泡泡棒方向,目标是减少玩具感,让它更像成熟产品主标。
|
||||
|
||||
本轮生成 prompt:
|
||||
|
||||
`tmp/imagegen/baimeng_logo_bubble_refine_batch12_prompts.jsonl`
|
||||
|
||||
本轮输出目录:
|
||||
|
||||
`output/imagegen/baimeng-logo-bubble-refine-batch12/`
|
||||
|
||||
当前最值得继续推进的是:
|
||||
|
||||
- `02-simple-action-mark`:泡泡棒行为明确,结构干净,比上一批更接地气,也不太幼稚。
|
||||
- `06-one-plus-two-bubbles`:更抽象、更高级,但现实吹泡泡行为感弱一些。
|
||||
- `08-final-simple-bubbles`:适合 App icon,但聊天气泡联想较强,需弱化社交聊天框尾巴。
|
||||
|
||||
不建议推进 `03/04/07`,它们出现了不稳定文字或方案式字标;`01` 容易被误认为放大镜。
|
||||
|
||||
批量生成 prompt 已放在:
|
||||
|
||||
`tmp/imagegen/baimeng_logo_gpt_image_2_prompts.jsonl`
|
||||
|
||||
建议输出目录:
|
||||
|
||||
`output/imagegen/baimeng-logo/`
|
||||
|
||||
生成命令:
|
||||
|
||||
```powershell
|
||||
$env:OPENAI_API_KEY="本机已配置的 OpenAI API Key"
|
||||
python "C:\Users\wuxiangwanzi\.codex\skills\.system\imagegen\scripts\image_gen.py" generate-batch `
|
||||
--input tmp\imagegen\baimeng_logo_gpt_image_2_prompts.jsonl `
|
||||
--out-dir output\imagegen\baimeng-logo `
|
||||
--concurrency 2 `
|
||||
--quality high `
|
||||
--size 1024x1024
|
||||
```
|
||||
|
||||
若继续使用仓库现有 VectorEngine GPT-image-2 路由,则需要配置:
|
||||
|
||||
```text
|
||||
VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai
|
||||
VECTOR_ENGINE_API_KEY=...
|
||||
```
|
||||
|
||||
## 13. 04/07 气泡方向单独优化补充
|
||||
|
||||
在用户反馈“更喜欢 04 和 07”后,本轮不再横向发散其它生活物件,而是只围绕两条已被接受的视觉方向做收敛:
|
||||
|
||||
- 继承 `04` 的中心大泡泡:保留多条虹彩色带组成的饱满主视觉,但压低小泡泡的高光、阴影和玻璃拟物感。
|
||||
- 继承 `07` 的色彩和轻轻吹泡泡行为:减少元素数量,确保整体居中,避免一串气泡散向右上角。
|
||||
- 明确排除 `03/05/08` 中容易出现的聊天软件联想,不使用聊天尾巴、对话框轮廓、碎小装饰点和星形元素。
|
||||
|
||||
本轮生成 prompt:
|
||||
|
||||
`tmp/imagegen/baimeng_logo_bubble_04_07_refine_batch13_prompts.jsonl`
|
||||
|
||||
本轮输出目录:
|
||||
|
||||
`output/imagegen/baimeng-logo-bubble-04-07-refine-batch13/`
|
||||
|
||||
联系表:
|
||||
|
||||
`output/imagegen/baimeng-logo-bubble-04-07-refine-batch13/baimeng-bubble-04-07-refine-batch13-contact-sheet.png`
|
||||
|
||||
当前最值得继续推进的是:
|
||||
|
||||
- `01-04-flat-rainbow-band-core`:保留了 04 的彩虹色带饱满度,同时只留下一个小辅助泡泡,聊天气泡联想弱,适合作为“梦 / 很多 / 共创”的品牌主标继续精修。
|
||||
- `02-04-single-full-bubble-ring`:最克制、最像可注册主符号,完全去掉碎元素;缺点是“吹泡泡行为”和 UGC 共创感比其它方案弱,可作为极简品牌基线。
|
||||
- `08-07-rainbow-breath-symbol`:较好融合了 04 的虹彩大环与 07 的吹泡泡动作,亲和度高;后续需要继续压缩右上两个泡泡的体量,避免重心偏右。
|
||||
|
||||
不建议优先推进:
|
||||
|
||||
- `03-04-overlap-two-bubbles-brand`:结构清楚,但小环容易变成独立图标,整体略硬。
|
||||
- `04-04-rainbow-bubble-wand-minimal`:泡泡棒语义明确,但下方手柄过于具象,容易像工具图标。
|
||||
- `05/06/07`:色彩和亲和力可参考,但泡泡簇仍比 01/02/08 更接近装饰插画,产品主标凝聚力不足。
|
||||
529
docs/design/CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md
Normal file
529
docs/design/CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md
Normal file
@@ -0,0 +1,529 @@
|
||||
# 儿童动作识别互动玩法 Demo 热身关开发文档
|
||||
|
||||
> 日期:2026-05-09
|
||||
> 适用范围:儿童动作识别互动玩法 Demo 的固定启动热身关
|
||||
> 文档性质:玩法 Demo 开发设计文档
|
||||
> 说明:本文整理当前已确认的热身关内容、体验、流程和热身数据记录要求。
|
||||
|
||||
## 1. 热身关定位
|
||||
|
||||
热身关是 Demo 启动后的固定流程,用于在正式进入后续趣味学习关前完成以下事项:
|
||||
|
||||
- 调用摄像头;
|
||||
- 识别用户和环境;
|
||||
- 引导用户来到建议互动位置;
|
||||
- 教学基础交互方式;
|
||||
- 确认用户可在互动空间内完成左右移动、挥手和跳跃;
|
||||
- 记录用户左右移动距离、挥动手臂空间和跳跃空间,作为后续关卡的空间边界与行为坐标;
|
||||
- 完成后进入关卡选择。
|
||||
|
||||
热身关不接入创作模块,不作为可配置玩法模板提供给创作者。
|
||||
|
||||
## 2. 屏幕与设备适配
|
||||
|
||||
本产品适用于电视屏幕、电脑屏幕等环境。
|
||||
|
||||
热身关制作表达使用横屏比例。
|
||||
|
||||
## 3. 画面基础表现
|
||||
|
||||
用户进入热身关后,摄像头被调用,并开始识别用户和环境。
|
||||
|
||||
画面基础表现如下:
|
||||
|
||||
1. 在屏幕中央位置的地面生成预设的绿色圆环,作为建议位置的指引。
|
||||
2. 将用户的实际位置生成角色剪影,作为用户在画面中的标识。
|
||||
3. 只对摄像头背景做虚化处理,用于表达对用户隐私的保护、屏蔽周围环境干扰,并营造空间感。
|
||||
|
||||
## 4. 通用检测与引导规则
|
||||
|
||||
### 4.1 不允许跳过
|
||||
|
||||
热身关每个步骤都必须由用户完成,不允许跳过,也不允许系统自动进入下一步。
|
||||
|
||||
### 4.2 引导动画播放规则
|
||||
|
||||
每个动作等待 3 秒后可以播放引导动画。
|
||||
|
||||
当前不设置最长等待时间。
|
||||
|
||||
### 4.3 绿色圆环完成规则
|
||||
|
||||
用户到达绿色圆环后,绿色圆环进入 2 秒选中状态。
|
||||
|
||||
用户需要在绿色圆环内保持停留 2 秒,才算完成该圆环位置检测。
|
||||
|
||||
### 4.4 左右距离映射规则
|
||||
|
||||
“约半米”的左右移动距离,技术上以角色剪影移动距离为准。
|
||||
|
||||
该距离后续会根据实际体验继续调校。
|
||||
|
||||
### 4.5 手势区分规则
|
||||
|
||||
招手 / 摆手、挥动左手、挥动右手三类动作需要有动作区分。
|
||||
|
||||
手势检测仅对肢体进行区分,不对手部细节进行区分。
|
||||
|
||||
### 4.6 手势引导规则
|
||||
|
||||
挥动哪只手,就使用对应手的引导。
|
||||
|
||||
## 5. 热身关完整流程
|
||||
|
||||
### 5.1 进入热身关
|
||||
|
||||
#### 画面表现
|
||||
|
||||
- 摄像头被调用。
|
||||
- 系统识别用户和环境。
|
||||
- 屏幕中央位置的地面出现预设绿色圆环。
|
||||
- 用户实际位置以角色剪影形式显示。
|
||||
- 只对摄像头背景做虚化处理,保留空间感。
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
屏幕中上方浮现文字,同时语音播报:
|
||||
|
||||
```text
|
||||
欢迎你,小朋友,见到你真开心
|
||||
```
|
||||
|
||||
随后继续播报:
|
||||
|
||||
```text
|
||||
请你来到圆圈这里和我打个招呼吧
|
||||
```
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否到达屏幕中央绿色圆环位置。
|
||||
|
||||
用户到达圆环后,绿色圆环进入 2 秒选中状态。用户保持停留 2 秒后,该步骤完成。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成中央圆环位置检测后:
|
||||
|
||||
- 播放圆圈消失特效;
|
||||
- 进入招手手势教学步骤。
|
||||
|
||||
---
|
||||
|
||||
### 5.2 招手教学
|
||||
|
||||
#### 画面表现
|
||||
|
||||
播放招手的手势引导。
|
||||
|
||||
若用户进入该步骤后 3 秒仍未完成动作,可以播放引导动画。
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否完成招手 / 摆手手势。
|
||||
|
||||
该动作与后续挥动左手、挥动右手需要有动作区分,但仅对肢体进行区分,不对手部细节进行区分。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成招手 / 摆手手势后,进入下一步。
|
||||
|
||||
---
|
||||
|
||||
### 5.3 热身说明
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
```text
|
||||
你好呀小朋友,为了你玩的安全和开心,先来和我一起热个身吧
|
||||
```
|
||||
|
||||
播放完成后进入左右移动热身步骤。
|
||||
|
||||
---
|
||||
|
||||
### 5.4 向左一步
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
```text
|
||||
向左一步
|
||||
```
|
||||
|
||||
#### 画面表现
|
||||
|
||||
屏幕中心向左一个身位,约半米的地面位置,出现新的绿色圆圈。
|
||||
|
||||
“约半米”技术上以角色剪影移动距离为准,后续根据体验调校。
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否到达该绿色圆圈位置。
|
||||
|
||||
用户到达圆环后,绿色圆环进入 2 秒选中状态。用户保持停留 2 秒后,该步骤完成。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成后播放鼓励语:
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
同时记录本次向左移动距离,作为后续关卡中的左侧空间边界参考。
|
||||
|
||||
完成后进入“回到中间来”。
|
||||
|
||||
---
|
||||
|
||||
### 5.5 回到中间来(一)
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
```text
|
||||
回到中间来
|
||||
```
|
||||
|
||||
#### 画面表现
|
||||
|
||||
场地中心位置出现绿色圆圈。
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否到达场地中心绿色圆圈位置。
|
||||
|
||||
用户到达圆环后,绿色圆环进入 2 秒选中状态。用户保持停留 2 秒后,该步骤完成。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成后播放鼓励语:
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
完成后进入“向右一步”。
|
||||
|
||||
---
|
||||
|
||||
### 5.6 向右一步
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
```text
|
||||
向右一步
|
||||
```
|
||||
|
||||
#### 画面表现
|
||||
|
||||
屏幕中心向右一个身位,约半米的地面位置,出现新的绿色圆圈。
|
||||
|
||||
“约半米”技术上以角色剪影移动距离为准,后续根据体验调校。
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否到达该绿色圆圈位置。
|
||||
|
||||
用户到达圆环后,绿色圆环进入 2 秒选中状态。用户保持停留 2 秒后,该步骤完成。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成后播放鼓励语:
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
同时记录本次向右移动距离,作为后续关卡中的右侧空间边界参考。
|
||||
|
||||
完成后进入“回到中间来”。
|
||||
|
||||
---
|
||||
|
||||
### 5.7 回到中间来(二)
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
```text
|
||||
回到中间来
|
||||
```
|
||||
|
||||
#### 画面表现
|
||||
|
||||
场地中心位置出现绿色圆圈。
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否到达场地中心绿色圆圈位置。
|
||||
|
||||
用户到达圆环后,绿色圆环进入 2 秒选中状态。用户保持停留 2 秒后,该步骤完成。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成后播放鼓励语:
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
完成后进入左手挥动教学。
|
||||
|
||||
---
|
||||
|
||||
### 5.8 挥动左手
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
```text
|
||||
挥动左手
|
||||
```
|
||||
|
||||
#### 画面表现
|
||||
|
||||
播放伸展手臂挥动左手的手势引导。
|
||||
|
||||
若用户进入该步骤后 3 秒仍未完成动作,可以播放引导动画。
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否完成挥动左手手势。
|
||||
|
||||
该手势检测仅对肢体进行区分,不对手部细节进行区分。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成后播放鼓励语:
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
同时记录用户挥动左手的空间,保存为该用户对应的行为坐标。
|
||||
|
||||
完成后进入右手挥动教学。
|
||||
|
||||
---
|
||||
|
||||
### 5.9 挥动右手
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
```text
|
||||
挥动右手
|
||||
```
|
||||
|
||||
#### 画面表现
|
||||
|
||||
播放伸展手臂挥动右手的手势引导。
|
||||
|
||||
若用户进入该步骤后 3 秒仍未完成动作,可以播放引导动画。
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否完成挥动右手手势。
|
||||
|
||||
该手势检测仅对肢体进行区分,不对手部细节进行区分。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成后播放鼓励语:
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
同时记录用户挥动右手的空间,保存为该用户对应的行为坐标。
|
||||
|
||||
完成后进入跳跃教学。
|
||||
|
||||
---
|
||||
|
||||
### 5.10 原地跳一下
|
||||
|
||||
#### 屏幕文字与语音
|
||||
|
||||
```text
|
||||
原地跳一下
|
||||
```
|
||||
|
||||
#### 画面表现
|
||||
|
||||
播放跳跃姿势引导。
|
||||
|
||||
若用户进入该步骤后 3 秒仍未完成动作,可以播放引导动画。
|
||||
|
||||
#### 检测逻辑
|
||||
|
||||
系统检测用户是否完成跳跃姿势。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
用户完成后:
|
||||
|
||||
- 记录用户跳跃空间,保存为该用户对应的行为坐标;
|
||||
- 播放热身结束特效、上浮字幕和语音:
|
||||
|
||||
```text
|
||||
真厉害,你是我见过最聪明的小朋友
|
||||
```
|
||||
|
||||
随后继续播放:
|
||||
|
||||
```text
|
||||
别走开,现在开始我们的游戏吧
|
||||
```
|
||||
|
||||
热身关结束,进入关卡选择。
|
||||
|
||||
## 6. 流程状态表
|
||||
|
||||
| 顺序 | 步骤 | 屏幕文字 / 语音 | 画面表现 | 检测目标 | 完成后反馈 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 进入热身关 | 欢迎你,小朋友,见到你真开心;请你来到圆圈这里和我打个招呼吧 | 中央地面绿色圆环;用户角色剪影;摄像头背景虚化 | 用户到达中央圆环并保持 2 秒 | 圆圈消失特效 |
|
||||
| 2 | 招手教学 | 同上流程延续 | 招手手势引导;等待 3 秒可播放引导动画 | 招手 / 摆手 | 进入下一步 |
|
||||
| 3 | 热身说明 | 你好呀小朋友,为了你玩的安全和开心,先来和我一起热个身吧 | 保持热身引导状态 | 无新增动作检测 | 进入移动热身 |
|
||||
| 4 | 向左一步 | 向左一步 | 左侧约半米处绿色圆圈 | 用户到达左侧圆环并保持 2 秒 | 真棒;记录左侧空间边界 |
|
||||
| 5 | 回到中间来 | 回到中间来 | 中心位置绿色圆圈 | 用户到达中心圆环并保持 2 秒 | 真棒 |
|
||||
| 6 | 向右一步 | 向右一步 | 右侧约半米处绿色圆圈 | 用户到达右侧圆环并保持 2 秒 | 真棒;记录右侧空间边界 |
|
||||
| 7 | 回到中间来 | 回到中间来 | 中心位置绿色圆圈 | 用户到达中心圆环并保持 2 秒 | 真棒 |
|
||||
| 8 | 挥动左手 | 挥动左手 | 伸展手臂挥动左手手势引导;等待 3 秒可播放引导动画 | 挥动左手 | 真棒;记录左手挥动空间 |
|
||||
| 9 | 挥动右手 | 挥动右手 | 伸展手臂挥动右手手势引导;等待 3 秒可播放引导动画 | 挥动右手 | 真棒;记录右手挥动空间 |
|
||||
| 10 | 原地跳一下 | 原地跳一下 | 跳跃姿势引导;等待 3 秒可播放引导动画 | 跳跃姿势 | 记录跳跃空间;真厉害,你是我见过最聪明的小朋友;别走开,现在开始我们的游戏吧;进入关卡选择 |
|
||||
|
||||
## 7. 固定文案与语音清单
|
||||
|
||||
以下文案需要作为屏幕中上方浮现文字,并同步语音播报。
|
||||
|
||||
```text
|
||||
欢迎你,小朋友,见到你真开心
|
||||
请你来到圆圈这里和我打个招呼吧
|
||||
你好呀小朋友,为了你玩的安全和开心,先来和我一起热个身吧
|
||||
向左一步
|
||||
真棒
|
||||
回到中间来
|
||||
真棒
|
||||
向右一步
|
||||
真棒
|
||||
回到中间来
|
||||
真棒
|
||||
挥动左手
|
||||
真棒
|
||||
挥动右手
|
||||
真棒
|
||||
原地跳一下
|
||||
真厉害,你是我见过最聪明的小朋友
|
||||
别走开,现在开始我们的游戏吧
|
||||
```
|
||||
|
||||
## 8. 需要开发支持的识别能力
|
||||
|
||||
热身关当前流程需要支持以下识别能力:
|
||||
|
||||
1. 摄像头调用;
|
||||
2. 用户识别;
|
||||
3. 环境识别;
|
||||
4. 用户实际位置识别;
|
||||
5. 用户是否到达中央绿色圆环位置;
|
||||
6. 用户是否在绿色圆环内持续保持 2 秒;
|
||||
7. 用户是否到达左侧约半米绿色圆环位置;
|
||||
8. 用户是否到达右侧约半米绿色圆环位置;
|
||||
9. 招手 / 摆手手势识别;
|
||||
10. 挥动左手识别;
|
||||
11. 挥动右手识别;
|
||||
12. 原地跳跃姿势识别;
|
||||
13. 用户左右移动距离记录;
|
||||
14. 用户挥动手臂空间记录;
|
||||
15. 用户跳跃空间记录。
|
||||
|
||||
## 9. 需要开发支持的表现能力
|
||||
|
||||
热身关当前流程需要支持以下表现能力:
|
||||
|
||||
1. 横屏比例显示;
|
||||
2. 摄像头背景虚化;
|
||||
3. 用户位置生成角色剪影;
|
||||
4. 屏幕中央地面绿色圆环;
|
||||
5. 左侧约半米地面绿色圆环;
|
||||
6. 右侧约半米地面绿色圆环;
|
||||
7. 绿色圆环 2 秒选中状态;
|
||||
8. 圆圈消失特效;
|
||||
9. 招手手势引导;
|
||||
10. 伸展手臂挥动左手手势引导;
|
||||
11. 伸展手臂挥动右手手势引导;
|
||||
12. 跳跃姿势引导;
|
||||
13. 热身结束特效;
|
||||
14. 上浮字幕;
|
||||
15. 语音播报。
|
||||
|
||||
## 10. 热身数据记录要求
|
||||
|
||||
热身关需要记录以下数据,用于后续关卡的空间边界和行为坐标判断。
|
||||
|
||||
### 10.1 左右空间边界
|
||||
|
||||
用户完成向左一步后,记录该移动距离,作为后续关卡中的左侧空间边界。
|
||||
|
||||
用户完成向右一步后,记录该移动距离,作为后续关卡中的右侧空间边界。
|
||||
|
||||
后续关卡中,当用户身体主体覆盖安全边界线时,对应侧屏幕边缘出现虚影提醒。
|
||||
|
||||
后续关卡中,当用户身体主体超出安全边界线时:
|
||||
|
||||
1. 关卡内容暂停;
|
||||
2. 屏幕虚化;
|
||||
3. 屏幕中央地面出现绿色圆圈;
|
||||
4. 屏幕提示文案:
|
||||
|
||||
```text
|
||||
小朋友,要注意安全哦
|
||||
```
|
||||
|
||||
5. 用户需要回到中心绿色圆圈并保持 2 秒后,才能继续游戏内容。
|
||||
|
||||
### 10.2 手臂挥动空间
|
||||
|
||||
用户完成挥动左手后,记录用户挥动左手的空间,保存为该用户对应的行为坐标。
|
||||
|
||||
用户完成挥动右手后,记录用户挥动右手的空间,保存为该用户对应的行为坐标。
|
||||
|
||||
### 10.3 跳跃空间
|
||||
|
||||
用户完成原地跳一下后,记录用户跳跃空间,保存为该用户对应的行为坐标。
|
||||
|
||||
## 11. 热身关完成条件
|
||||
|
||||
热身关完成条件为用户按顺序完成以下流程:
|
||||
|
||||
1. 到达中央圆环位置并保持 2 秒;
|
||||
2. 完成招手 / 摆手手势;
|
||||
3. 到达左侧约半米圆环位置并保持 2 秒;
|
||||
4. 记录左侧空间边界;
|
||||
5. 回到中央圆环位置并保持 2 秒;
|
||||
6. 到达右侧约半米圆环位置并保持 2 秒;
|
||||
7. 记录右侧空间边界;
|
||||
8. 回到中央圆环位置并保持 2 秒;
|
||||
9. 完成挥动左手;
|
||||
10. 记录左手挥动空间;
|
||||
11. 完成挥动右手;
|
||||
12. 记录右手挥动空间;
|
||||
13. 完成原地跳一下;
|
||||
14. 记录跳跃空间;
|
||||
15. 播放热身结束特效和结束语音;
|
||||
16. 进入关卡选择。
|
||||
|
||||
## 12. 数据保存方式
|
||||
|
||||
左右空间边界、手臂挥动空间、跳跃空间仅在当前 Demo 体验会话内保存。
|
||||
|
||||
这里的“当前 Demo 体验会话”指用户本次打开并体验 Demo 的过程。当用户关闭 Demo、刷新页面、退出当前体验流程、重新进入 Demo,或更换设备后,系统不再沿用上一次热身记录的数据,需要重新完成热身关并重新记录。
|
||||
|
||||
采用仅当前 Demo 体验会话内保存的原因:
|
||||
|
||||
1. 每名用户的身高、体型、动作幅度不同,安全边界和行为坐标会发生变化。
|
||||
2. 当前 Demo 不做特定用户识别,无法确认下一次体验的是否仍是同一名用户。
|
||||
3. 用户所处的体验环境可能变化,包括房间大小、摄像头位置、屏幕位置和站立距离。
|
||||
4. 为保证安全,每次体验都需要重新对环境和距离进行安全检查。
|
||||
|
||||
## 13. 后续待确认事项
|
||||
|
||||
当前暂无待确认事项。
|
||||
@@ -0,0 +1,136 @@
|
||||
# 寓教于乐发现页临时入口设计
|
||||
|
||||
> 日期:2026-05-09
|
||||
> 适用范围:平台入口发现页、儿童动作识别娱乐教育内容线临时入口
|
||||
> 文档性质:产品与前端落地边界
|
||||
|
||||
## 1. 目标
|
||||
|
||||
为儿童动作识别娱乐教育内容线提供一个临时入口。
|
||||
|
||||
入口放置在平台“发现”页面内,作为独立标签展示,标签名称为:
|
||||
|
||||
```text
|
||||
寓教于乐
|
||||
```
|
||||
|
||||
后续生产的该内容线模板和游戏关卡,都放置在“寓教于乐”独立标签下。
|
||||
|
||||
该内容线当前只覆盖儿童动作识别 Demo 内容。后续创作环节需要继续对该板块内容做区分和独立管理,不把普通公开作品仅凭近似教育题材自动归入本板块。
|
||||
|
||||
## 2. 展示边界
|
||||
|
||||
寓教于乐内容不直接展示在以下位置:
|
||||
|
||||
1. 推荐页;
|
||||
2. 发现页的推荐标签;
|
||||
3. 发现页的今日标签;
|
||||
4. 发现页的分类标签;
|
||||
5. 发现页的排行标签;
|
||||
6. 发现页搜索结果。
|
||||
|
||||
寓教于乐内容只在“发现 / 寓教于乐”标签下展示。
|
||||
|
||||
“寓教于乐”标签在发现页频道列表中放在最后,桌面端和移动端都显示。移动端访问该内容线的动作识别 Demo 时,需要提示横屏体验。
|
||||
|
||||
## 3. 开关规则
|
||||
|
||||
该入口需要支持灵活开关。
|
||||
|
||||
开关打开时:
|
||||
|
||||
1. 发现页显示“寓教于乐”标签;
|
||||
2. “寓教于乐”标签下展示该内容线内容;
|
||||
3. 该内容线内容仍不进入推荐、今日、分类、排行和搜索结果。
|
||||
|
||||
开关关闭时:
|
||||
|
||||
1. 发现页隐藏“寓教于乐”标签;
|
||||
2. 隐藏“寓教于乐”标签下内容;
|
||||
3. 该内容线内容不进入推荐、今日、分类、排行和搜索结果;
|
||||
4. 该内容线内容完全不可见,公开作品搜索、作品号搜索直达、公开详情深链、浏览历史入口、创作入口和创作页作品架等平台入口都不能打开或展示该内容。
|
||||
|
||||
## 4. 内容识别规则
|
||||
|
||||
临时阶段使用作品标签识别寓教于乐内容。
|
||||
|
||||
当公开作品标签中存在一个精确等于以下文本的标签:
|
||||
|
||||
```text
|
||||
寓教于乐
|
||||
```
|
||||
|
||||
则该作品归入寓教于乐内容线。
|
||||
|
||||
识别规则为精确匹配,不做包含匹配,不兼容空格、大小写变体或同义标签,例如“教育”“儿童教育”“动作教育”都不视为寓教于乐内容。
|
||||
|
||||
关闭开关时,即使作品具备精确的“寓教于乐”标签,也不允许通过任何平台公开展示入口或搜索入口访问。
|
||||
|
||||
## 5. 技术落地边界
|
||||
|
||||
本次只做前端入口和前端展示过滤,不新增后端接口。
|
||||
|
||||
前端通过功能开关控制入口显隐。
|
||||
|
||||
开关环境变量:
|
||||
|
||||
```text
|
||||
VITE_ENABLE_EDUTAINMENT_ENTRY
|
||||
```
|
||||
|
||||
默认开启。
|
||||
|
||||
当该变量显式配置为以下值时,入口关闭:
|
||||
|
||||
```text
|
||||
false
|
||||
0
|
||||
off
|
||||
no
|
||||
```
|
||||
|
||||
## 6. 验收点
|
||||
|
||||
1. 开关打开时,发现页显示“寓教于乐”标签。
|
||||
2. 开关关闭时,发现页不显示“寓教于乐”标签。
|
||||
3. 带有“寓教于乐”标签的公开作品不进入推荐页。
|
||||
4. 带有“寓教于乐”标签的公开作品不进入发现页推荐、今日、分类、排行和搜索结果。
|
||||
5. 带有“寓教于乐”标签的公开作品只在“发现 / 寓教于乐”标签下展示。
|
||||
6. “寓教于乐”标签位于发现页频道列表最后,桌面端和移动端均可见。
|
||||
7. 开关关闭后,带有“寓教于乐”标签的公开作品不可通过作品号搜索、公开详情深链或浏览历史入口打开。
|
||||
8. 标签识别只接受精确等于“寓教于乐”的作品标签,近似标签不归入该内容线。
|
||||
|
||||
## 7. 待补充事项
|
||||
|
||||
“寓教于乐”标签下暂无内容时的空状态文案待定。落地时可先复用平台现有空状态组件,但不新增功能说明类长文案。
|
||||
|
||||
## 8. 第 1-2 项工程落地状态
|
||||
|
||||
第 1 项“发现页入口与过滤”和第 2 项“搜索 / 深链 / 历史入口拦截”已进入前端落地阶段,当前实现口径如下:
|
||||
|
||||
1. 入口开关由 `VITE_ENABLE_EDUTAINMENT_ENTRY` 控制,默认开启,显式配置 `false`、`0`、`off`、`no` 时关闭。
|
||||
2. 内容识别集中在 `src/components/platform-entry/platformEdutainmentVisibility.ts`,只读取公开作品原始 `themeTags`,且只接受精确等于“寓教于乐”的标签。
|
||||
3. `src/components/rpg-entry/RpgEntryHomeView.tsx` 已在发现页频道末尾追加“寓教于乐”频道,并将该类作品从推荐、今日、分类、排行、搜索、本地搜索兜底和桌面推荐模块中过滤。
|
||||
4. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx` 已复用同一过滤 helper,避免推荐运行态自动启动寓教于乐作品,并在公开详情、作品号直达和公开详情深链等公开入口保留不可见保护。
|
||||
5. 浏览历史入口会优先按当前公开作品集合匹配作品标签;匹配到“寓教于乐”作品且开关关闭时不再展示历史入口。
|
||||
6. `/child-motion-demo` 本地动作 Demo 直达路由也复用同一开关;开关关闭时不匹配独立 Demo 应用,回落到主站入口。
|
||||
7. `宝贝识物` 创作入口和创作页作品架也复用同一开关;开关关闭时不展示模板入口,也不展示本地宝贝识物草稿或已发布卡片。
|
||||
8. 定向回归覆盖在 `src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`src/components/platform-entry/platformEdutainmentVisibility.test.ts`、`src/components/platform-entry/platformEntryCreationTypes.test.ts` 和 `src/routing/appRoutes.test.ts`,包含频道顺序、开关关闭、普通列表过滤、搜索过滤、作品号直达拦截、Demo 直达路由拦截、创作入口隐藏和精确标签识别。
|
||||
|
||||
## 9. 第 4 项作品架 / 广场接入边界
|
||||
|
||||
`宝贝识物` 首关的公开作品展示接入按以下口径收口:
|
||||
|
||||
1. 平台公共作品模型新增 `sourceType = edutainment`,当前只承接 `templateId = baby-object-match`、`templateName = 宝贝识物`。
|
||||
2. `宝贝识物` 作品仍必须携带精确等于“寓教于乐”的公开标签,才会进入“发现 / 寓教于乐”频道。
|
||||
3. `宝贝识物` 不因为模板名自动归入寓教于乐,也不因为近似标签归入寓教于乐。
|
||||
4. 第 4 项只负责公开作品卡片、发现页专属频道、公开详情、分享作品号和开关隐藏保护。
|
||||
5. 创作模板、image-2 资产生成、发布接口、运行时开始游戏和关卡状态由对应线程接入;当前公共作品卡直接透传后续数据源提供的 `publicWorkCode`,不在前端新增最终作品号前缀规则。
|
||||
6. 在创作和运行时链路真正接入前,公开详情内的启动、改造、编辑和点赞只做保护性占位,不新增玩法规则。
|
||||
|
||||
当前工程落点:
|
||||
|
||||
1. `src/components/rpg-entry/rpgEntryWorldPresentation.ts` 定义 `PlatformEdutainmentGalleryCard` 与 `isEdutainmentGalleryEntry`。
|
||||
2. `src/components/rpg-entry/RpgEntryHomeView.tsx` 将 `宝贝识物` 卡片识别为寓教于乐公开作品,并继续从推荐、今日、分类、排行和搜索结果中过滤。
|
||||
3. `src/components/platform-entry/PlatformWorkDetailView.tsx` 在公开详情中显示 `宝贝识物` 类型标签,并继续复用作品号复制和分享链路。
|
||||
4. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx` 已识别 `edutainment` 公共作品,避免落入 RPG 默认详情、推荐运行态或错误的改造链路。
|
||||
@@ -28,3 +28,13 @@
|
||||
- 锁定的模板卡统一以“敬请期待”作为状态标注,不再显示“锁定”。
|
||||
- RPG 入口展示为“角色扮演 / 剧情演绎,冒险成长”,拼图入口展示为“拼图 / 创意礼物,生活分享”。
|
||||
- 忙碌状态仅保留在模块标题行的轻量状态中,避免占用每张可用卡片的首要视觉层级。
|
||||
|
||||
## 2026-05-07 玩法参考图
|
||||
|
||||
1. 每个玩法入口都必须配置一张 `public/creation-type-references/` 下的参考图。
|
||||
2. 当前创作 Tab 顶部玩法卡带、旧创作中心卡带和玩法类型弹层都消费同一份 `PLATFORM_CREATION_TYPES.imageSrc`,避免多入口视觉漂移。
|
||||
3. 图片只承担玩法识别和氛围锚定,不在卡片上叠加规则说明文案。
|
||||
4. 移动端卡片仍以紧凑横滑为主,参考图使用暗色遮罩承接标题,文本不得溢出卡片。
|
||||
5. 当前创作 Tab 的可见玩法卡必须真实渲染 `img`,不能只在隐藏弹窗或旧入口中配置图片。
|
||||
6. 参考图卡片上的标题和副标题必须显式使用白色文字,并配合底部加深渐变与文字阴影;禁止依赖 `text-inherit`,避免黑字叠在暗蒙版上。
|
||||
7. 当前创作 Tab 顶部不再保留“10分钟创作一个精品互动玩法”标题,玩法参考图卡带直接作为首屏入口;移动端卡带必须支持横向拖动滑动。
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
- 新增“分类” Tab,用作品标签聚合所有公开发布作品。
|
||||
- 强化“创作” Tab 的导航视觉权重,让它在底部导航中居中并更醒目。
|
||||
- 登录态底部导航顺序为:首页、分类、创作、存档、我的。
|
||||
- 未登录态底部导航只保留:首页、创作、分类,其中创作保持居中。
|
||||
- 登录态底部导航顺序为:推荐、发现、创作、草稿、我的。
|
||||
- 未登录态底部导航只保留:推荐、创作、发现,其中创作保持居中。
|
||||
|
||||
## 2. 数据边界
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
|
||||
底部导航展示 5 个入口:
|
||||
|
||||
1. 首页
|
||||
2. 分类
|
||||
1. 推荐
|
||||
2. 发现
|
||||
3. 创作
|
||||
4. 存档
|
||||
4. 草稿
|
||||
5. 我的
|
||||
|
||||
创作入口位于第三位,视觉上使用更大的图标壳、轻微上浮、渐变高亮和阴影,保证它是主行动入口。
|
||||
@@ -36,11 +36,11 @@
|
||||
|
||||
底部导航展示 3 个入口:
|
||||
|
||||
1. 首页
|
||||
1. 推荐
|
||||
2. 创作
|
||||
3. 分类
|
||||
3. 发现
|
||||
|
||||
不展示“存档”和“我的”,避免未登录用户在底部导航看到必须登录后才有价值的入口。创作入口位于第二位,保持几何居中。
|
||||
不展示“草稿”和“我的”,避免未登录用户在底部导航看到必须登录后才有价值的入口。创作入口位于第二位,保持几何居中,并沿用原推荐 Tab 的星光图标;推荐 Tab 改用游戏手柄图标。
|
||||
|
||||
### 3.3 桌面端
|
||||
|
||||
@@ -59,6 +59,6 @@
|
||||
|
||||
- 登录态移动端底部导航顺序准确,创作在 5 个 Tab 中居中。
|
||||
- 未登录态移动端底部导航只显示 3 个 Tab,创作在中间。
|
||||
- 分类 Tab 能按标签切换并展示公开作品。
|
||||
- 发现 Tab 能按标签切换并展示公开作品。
|
||||
- 创作 Tab 在移动端和桌面端都比普通 Tab 更醒目。
|
||||
- 不修改 server-node,不新增后端逻辑。
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# 平台创作 Tab 模板入口设计
|
||||
|
||||
更新时间:`2026-05-07`
|
||||
|
||||
## 1. 目标
|
||||
|
||||
创作 Tab 恢复为模板选择入口,但不回到旧的大卡片选择面板:
|
||||
|
||||
1. 首屏保留现有创作页布局骨架,顶部标题固定为“10分钟创作一个精品互动玩法”。
|
||||
2. 选择模板入口改为横向 Tab,数据来自 `GET /api/creation-entry/config` 返回的可见玩法配置。
|
||||
3. 默认选中“拼图”模板,并在创作 Tab 内直接展示拼图创作表单。
|
||||
4. 智能创作入口从可见模板中隐藏,保留既有 `creative-agent` 运行链路用于后续内部恢复或草稿目标打开。
|
||||
5. 草稿、发现、我的等一级 Tab 职责不变,作品管理仍在草稿 Tab。
|
||||
|
||||
## 2. 页面结构
|
||||
|
||||
移动端和桌面端共用同一信息结构:
|
||||
|
||||
```text
|
||||
标题:10分钟创作一个精品互动玩法
|
||||
模板 Tab:拼图 / 抓大鹅 / 视觉小说(敬请期待)/ AIRP
|
||||
默认内容:拼图创作表单
|
||||
```
|
||||
|
||||
拼图表单嵌入创作 Tab 时:
|
||||
|
||||
- 不展示工作台返回按钮。
|
||||
- 不重复展示“创建拼图”标题。
|
||||
- 保留表单内的拼图模板、参考图上传、画面描述和生图模型选择。
|
||||
- 生成草稿仍走登录保护,未登录时先触发登录流程。
|
||||
|
||||
## 3. 交互
|
||||
|
||||
1. 打开“创作”一级 Tab 时默认停留在拼图 Tab,不主动创建拼图 session。
|
||||
2. 点击拼图表单“生成草稿”后,才创建拼图 session 并执行 `compile_puzzle_draft`。
|
||||
3. 拼图表单内的模板按钮使用 `tablist / 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 卡片中。
|
||||
5. 草稿页返回创作页后仍回到同一模板入口,并可保留拼图表单草稿内容。
|
||||
|
||||
## 5. 嵌入式表单 UI 细节
|
||||
|
||||
2026-05-10 补充:抓大鹅与视觉小说作为创作 Tab 内嵌表单时,风格类横滑选择器应统一使用浅底卡片、柔和玫瑰色选中态和小圆点确认标记。不要使用大面积黑色渐变、黑底胶囊标签或高饱和红色外框,以免在输入框下方误读为错误提示。
|
||||
|
||||
2026-05-10 追加:视觉小说画风选项已改为使用 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2-all` 生成的参考图,作为横向卡片主视觉。
|
||||
|
||||
嵌入式表单控件保持以下口径:
|
||||
|
||||
1. 大文本输入框使用白底、低饱和边框和轻量 focus ring。
|
||||
2. 风格选择区作为独立浅色分组承载横滑卡片,移动端只横向滚动,不挤压生成按钮。
|
||||
3. 风格卡标签使用浅底胶囊,保证图片仍是主体。
|
||||
4. 难度等分段选项可以使用主品牌色,但选中态需要降低阴影和饱和度。
|
||||
5. UI 中不补充玩法规则说明文案,保持创作入口清爽。
|
||||
@@ -0,0 +1,126 @@
|
||||
# 平台移动端推荐、发现与草稿 Tab 改版设计
|
||||
|
||||
更新时间:`2026-05-05`
|
||||
|
||||
## 1. 目标
|
||||
|
||||
本次只调整平台入口的信息架构与移动端视觉,不新增后端接口:
|
||||
|
||||
1. 原“首页”一级 Tab 对用户改名为“推荐”,进入后直接展示原首页推荐榜单启动后的公开游戏内容流。
|
||||
2. 原“首页”中的搜索、今日游戏、游戏分类等探索内容移动到第二个一级 Tab;第二个 Tab 对用户命名为“发现”。
|
||||
3. 原“排行”页内容并入“发现”页的子 Tab 中,不再作为一级主 Tab 独立展示。
|
||||
4. 创作页只保留新建创作入口;原创作页作品列表拆到一级“草稿” Tab,替换原“存档” Tab。
|
||||
5. 原“存档”列表结构并入“我的”页面的“玩过”列表弹层,作为每个已玩作品的可继续存档入口。
|
||||
6. 移动端推荐页与底部 Tab 栏参考用户给定样式,使用大画面推荐流、顶部品牌/通知、悬浮胶囊底部导航;保留当前平台已有明暗两套主题色 token。
|
||||
|
||||
## 2. 状态映射
|
||||
|
||||
为降低迁移风险,前端内部 `PlatformHomeTab` 仍复用既有状态值:
|
||||
|
||||
- `home`:用户看到“推荐”。
|
||||
- `category`:用户看到“发现”,内容包含搜索、今日游戏、分类和排行子 Tab。
|
||||
- `create`:用户看到“创作”,只承担新建入口。
|
||||
- `saves`:用户看到“草稿”,承载原创作页作品列表。
|
||||
- `profile`:用户看到“我的”,其中“玩过”弹层合并存档入口。
|
||||
|
||||
旧 `category` 状态此前承载“排行”,本次不改状态名,只改用户文案和页面内容,避免详情页返回目标、测试辅助和历史路由状态大范围迁移。
|
||||
|
||||
## 3. 推荐页
|
||||
|
||||
移动端推荐页默认不展示搜索和频道横滑条,进入一级“推荐”后直接渲染公开作品启动后的内容:
|
||||
|
||||
- 数据来源沿用 `featuredEntries + latestEntries` 去重后的公开作品列表。
|
||||
- 首个可运行作品自动进入推荐页内嵌运行态,主视口不再展示作品封面卡。
|
||||
- 主视口占据顶部栏与作品信息区之间的主要空间,使用平台主题 token 控制运行容器背景、边框、阴影、加载态文字和错误态按钮;亮色主题不得残留纯黑底白字加载块。
|
||||
- 主视口下方展示当前作品操作区与基础信息:作者头像、作者名、作品名、点赞按钮、点赞数、分享按钮、改造按钮;不展示游玩次数、评论入口或额外心形收藏入口,不写规则说明类文案。
|
||||
- 作品信息区不再提供详情箭头或点击详情入口;点击该区域无效,上滑切换下一个推荐作品,下滑切换上一个推荐作品。
|
||||
- 推荐页切换参考短视频上下滑交互:当前运行画布位于中间,上一个/下一个作品的封面预览提前挂载在画布上下屏幕外;拖动作品信息区时三屏轨道跟随位移,松手后完成切换或回弹。
|
||||
- 用户停留在推荐页时,底部当前 Tab 从“推荐”切换为“下一个”,图标使用向下的倒三角 / 双下箭头语义,点击后切换下一个推荐作品。
|
||||
- 推荐页不再展示额外的底部作品切换块;当前作品的点赞、分享与改造在推荐页底部操作区直接完成,其它作品深层操作继续由详情页和作品自身运行态承接。
|
||||
- 推荐页嵌入运行只调整平台外壳容器、主题注入和玩法壳层配色,不改写作品数据、关卡设定、道具设定或图片资产。
|
||||
- 屏幕外预览只允许使用公开封面、作品名和类型,不提前启动其它作品 run,不触发道具、计时、存档或作品数据变更。
|
||||
- 推荐页嵌入拼图玩法时隐藏拼图左上返回按钮,并在设置弹层中隐藏退出入口;作品切换前对当前拼图 run 执行既有“保存并退出”收口,正式 run 的交互状态以已写回后端的快照为准。
|
||||
- 底部操作区移除游玩次数、评论功能和额外心形收藏入口;点赞按钮可直接点击并刷新公开读模型返回的点赞数,分享按钮复制作品号与公开作品链接,改造按钮只保留改造 icon 并复用既有改造入口。操作按钮只响应自身点击,按钮外的信息区点击无效,拖动时仍跟随整张推荐卡上下切换作品。
|
||||
- 无数据、加载中、启动失败和暂不支持内嵌运行的作品沿用短状态文案。
|
||||
|
||||
桌面端仍保持现有首页布局,只把一级导航文案从“首页”改为“推荐”。
|
||||
|
||||
## 4. 发现页
|
||||
|
||||
发现页承接原首页探索能力和原排行能力,子 Tab 为:
|
||||
|
||||
1. 推荐:原首页推荐内容流,可作为发现页内的快速回看。
|
||||
2. 今日:原首页“今日游戏”。
|
||||
3. 分类:原首页“游戏分类”。
|
||||
4. 排行:原一级“排行”页四榜切换。
|
||||
|
||||
移动端发现页顶部保留搜索框和子 Tab;分类内容继续使用纵向应用商店式列表;排行内容继续使用榜单行。
|
||||
|
||||
## 5. 创作与草稿
|
||||
|
||||
创作页只保留新建创作入口:
|
||||
|
||||
- 继续复用 `CustomWorldCreationStartCard` 和现有创作类型弹窗。
|
||||
- 不在创作页下方展示作品列表,避免“创作入口”和“作品管理”挤在同一首屏。
|
||||
|
||||
草稿页复用创作中心作品架:
|
||||
|
||||
- 默认显示全部作品列表,保留草稿/已发布筛选。
|
||||
- 入口、删除、分享、领取拼图激励等行为全部复用现有 `CustomWorldCreationHub` 的作品卡逻辑。
|
||||
- 一级底部 Tab 文案为“草稿”,内部仍可按草稿与已发布筛选。
|
||||
|
||||
## 6. 我的页玩过列表
|
||||
|
||||
“我的”页面的“玩过”列表弹层合并存档结构:
|
||||
|
||||
- 顶部仍展示总游戏时长。
|
||||
- 列表先展示可恢复存档,使用原 `SaveArchiveCard` 的字段结构和恢复行为。
|
||||
- 再展示已玩作品统计列表,保持作品号、最近时间和时长。
|
||||
- 若某个存档被点击,必须继续走既有后端恢复接口,不在前端拼接运行态。
|
||||
|
||||
## 7. 验收
|
||||
|
||||
1. 移动端底部导航非推荐页显示“推荐 / 发现 / 创作 / 草稿 / 我的”,未登录时显示“推荐 / 创作 / 发现”;停留在推荐页时当前 Tab 显示“下一个”。
|
||||
2. 点击“推荐”直接看到公开作品启动后的内容,不再先看到搜索框、频道 Tab 或封面卡流。
|
||||
3. 点击“发现”可看到搜索、推荐、今日、分类、排行子 Tab。
|
||||
4. 点击“草稿”看到原创作页作品列表。
|
||||
5. 点击“创作”只看到新建创作入口。
|
||||
6. “我的”里的“玩过”弹层包含原存档列表入口,点击存档能继续恢复。
|
||||
7. 移动端底部导航为悬浮胶囊样式,保留当前明暗主题色变量,不新增第三套主题。
|
||||
8. 推荐页不出现额外底部作品卡或横滑切换块,运行视口、加载态和错误态跟随当前明暗主题。
|
||||
9. 拼图玩法背景、HUD、按钮、弹窗、排行榜和相似作品卡跟随平台主题色;暗色主题仍保留深色游戏感,亮色主题不得出现大面积固定黑底。
|
||||
10. 推荐页作品信息区点击无效,上滑切下一个、下滑切上一个;点击底部“下一个”也切下一个作品。
|
||||
11. 仅推荐页嵌入拼图态隐藏返回与设置内退出入口;详情页、新手引导和普通拼图运行态继续保留原有退出能力。
|
||||
12. 推荐页切换作品前,如果当前作品是拼图,必须先执行当前拼图 run 的退出收口,再启动下一作品。
|
||||
13. 已登录推荐页的上/下滑切换必须展示相邻作品的屏幕外预览,并在拖动不足阈值时回弹;相邻预览不得提前启动玩法运行态。
|
||||
14. 推荐页底部区域不展示游玩次数、评论入口和额外心形收藏入口;点赞、分享和改造都使用 icon 按钮,点赞数紧邻点赞按钮展示。
|
||||
|
||||
## 8. 2026-05-07 未登录三栏补充
|
||||
|
||||
未登录状态下底部导航只显示 3 个入口,顺序调整为:
|
||||
|
||||
1. 推荐
|
||||
2. 创作
|
||||
3. 发现
|
||||
|
||||
创作 Tab 必须位于中间,并使用原推荐 Tab 的星光图标,保持几何和视觉上的主行动入口。推荐 Tab 改用游戏手柄图标,避免与创作图标重复。
|
||||
|
||||
## 9. 2026-05-08 新用户默认发现与推荐门禁补充
|
||||
|
||||
未登录新用户首次进入平台时默认落在“发现”Tab,不再直接进入“推荐”Tab 的内嵌运行态。
|
||||
|
||||
- 未登录用户点击底部或侧边栏“推荐”Tab 时,页面可切到推荐封面预览态,同时打开登录弹窗。
|
||||
- 未登录状态下推荐页只展示当前推荐作品封面,不启动作品运行态,不展示推荐作品信息区。
|
||||
- 未登录用户点击推荐页封面时,再次打开同一个登录弹窗;登录成功后由既有受保护动作继续进入作品详情或玩法入口。
|
||||
- 未登录状态下点击“下一个”只切换下一张推荐封面,不触发登录弹窗,也不启动玩法。
|
||||
- 已登录用户继续沿用推荐页内嵌运行态、上下滑切换和底部“下一个”行为。
|
||||
|
||||
## 10. 2026-05-11 草稿生成中与新完成标记
|
||||
|
||||
草稿生成过程页允许用户直接返回创作中心并自由使用平台其它功能:
|
||||
|
||||
- 点击生成过程页的返回按钮时,当前生成任务继续在后台执行,页面回到创作中心,不清空生成状态。
|
||||
- 用户再进入草稿 Tab 并点击同一草稿时,若生成仍未完成,进入对应生成过程页查看最新进度;若已完成,直接进入对应结果页。
|
||||
- 草稿作品卡在生成中展示“生成中”状态标记;新生成完成且用户尚未查看的草稿在卡片右上角展示红点。
|
||||
- 底部一级“草稿”Tab 在存在未查看新完成草稿时展示红点;用户点击查看带红点的作品后,该作品红点消失。若草稿页已无任何带红点作品,底部“草稿”Tab 红点同步消失。
|
||||
- 生成完成时如果用户仍停留在对应生成过程页,可自动进入结果页;如果用户已经回到创作中心或其它功能页,不打断当前操作。
|
||||
@@ -17,9 +17,8 @@
|
||||
|
||||
1. 左侧保留返回按钮。
|
||||
2. 中间居中展示关卡主信息:
|
||||
- 第一行:拼图关卡名
|
||||
- 第二行:作者昵称
|
||||
- 第三行:`第 N 关`
|
||||
- 第一行:`第 N 关` 与拼图关卡名,二者保持同一行。
|
||||
- 第二行:紧凑倒计时。
|
||||
3. 右侧新增设置按钮。
|
||||
|
||||
同时移除以下冗余标识:
|
||||
@@ -31,12 +30,33 @@
|
||||
|
||||
### 1.1 2026-04-30 顶栏与底部工具补充
|
||||
|
||||
1. 顶栏作者信息不再只显示一行作者名,必须展示为作者头像与昵称组合;当前运行态只提供昵称时,用昵称首字生成圆形占位头像。
|
||||
2. 倒计时组件提升为顶栏中的强信息,字号、内边距和图标尺寸都需要明显大于作者昵称与关卡序号。
|
||||
1. 历史口径曾要求顶栏展示作者头像与昵称;该要求已被 2026-05-08 精简口径替代,当前拼图棋盘 HUD 不再展示作者信息。
|
||||
2. 倒计时组件保持为顶栏中的强信息,但需要采用紧凑尺寸,不得遮挡棋盘内容。
|
||||
3. 底部只保留 `提示 / 原图 / 冻结` 三个功能按钮,并整体居中展示;三个按钮触控面积和图标字号都需要放大。
|
||||
4. 底部不再展示“等待下一关候选”这类状态占位。通关后在三个道具按钮上方固定展示“下一关”按钮,展示条件只依赖当前关卡已通关,不依赖 `recommendedNextProfileId` 是否已有值。
|
||||
5. 点击底部“下一关”按钮继续调用运行时壳层已有 `onAdvanceNextLevel` 事件;正式 run 由后端 `next-level` 选择候选,本地 run 由 `local-next-level` 生成或接续下一关,前端不在按钮层自行决定下一关来源。
|
||||
|
||||
### 1.2 2026-05-08 推荐页嵌入态 HUD 精简
|
||||
|
||||
1. 拼图运行时顶栏不再展示作者头像、作者昵称或作者首字占位,作者信息只在推荐页作品信息、详情页和排行榜等非棋盘 HUD 区域展示。
|
||||
2. 顶部主信息压缩为两行:第一行 `第 N 关 + 关卡名`,第二行小号倒计时;倒计时不能使用此前的大号胶囊尺寸。
|
||||
3. 返回按钮和设置按钮上移并缩小移动端基础尺寸,减少对棋盘顶部空间的占用。
|
||||
4. 棋盘容器必须保留固定顶部安全区,确保关卡名和倒计时不会遮挡拼图内容。
|
||||
5. 推荐页嵌入时只调整外壳间距和 HUD 布局,不改写作品关卡、作者、道具、时间限制或图片资产等作品设定。
|
||||
|
||||
### 1.3 2026-05-08 主题色联动
|
||||
|
||||
1. 拼图运行态根容器、背景、棋盘底色、顶部 HUD、底部工具栏、加载态、失败弹窗、通关弹窗、道具确认弹窗、设置弹窗和相似作品卡必须通过 `src/index.css` 中的 `--puzzle-runtime-*` 主题变量控制。
|
||||
2. `platform-theme--light` 下拼图玩法背景应使用浅色平台底色与粉橙主色点缀,文字使用平台正文 token;不得继续使用固定 `bg-slate-950 text-white` 作为大面积底色。
|
||||
3. `platform-theme--dark` 下可保留深色棋盘氛围,但按钮、选中态、倒计时、弹窗边框和推荐页加载态仍要从主题 token 取色,避免局部色板漂移。
|
||||
4. 推荐页内嵌拼图时,父级必须保持 `platform-theme` 类可传递到 `PuzzleRuntimeShell`,不能让 runtime 脱离平台主题变量。
|
||||
|
||||
### 1.4 2026-05-08 推荐页嵌入态退出控制
|
||||
|
||||
1. 推荐页嵌入拼图时需要单独隐藏左上返回按钮和设置面板里的退出入口,避免把推荐流里的作品切换与普通退出混在一起。
|
||||
2. 推荐页切换到下一作品前,平台外壳会先沿用现有“保存并退出”语义收口当前拼图 run,再进入新作品。
|
||||
3. 该隐藏规则只作用于推荐页嵌入态,不影响详情页、新手引导或普通拼图入口。
|
||||
|
||||
### 2. 拼图块显示规则
|
||||
|
||||
运行时单块右下角编号全部移除。
|
||||
@@ -45,6 +65,7 @@
|
||||
|
||||
1. 玩家需要优先依赖画面主体、构图和色块识别位置。
|
||||
2. 编号覆盖会削弱“完整图片被逐步复原”的视觉奖励。
|
||||
3. 单块和合成后的拼图块只保留原图切片、外轮廓描边和必要的拖拽层级,不叠加额外的色块、暗层或蒙版,避免破坏原图识别。
|
||||
|
||||
### 3. 设置能力
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md](./CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md):4-8 岁儿童动作识别互动玩法 Demo 固定热身关的横屏体验流程、识别目标、表现需求与待确认事项。
|
||||
- [CUSTOM_WORLD_CREATOR_INPUT_AND_AI_BOUNDARY_DESIGN_2026-04-06.md](./CUSTOM_WORLD_CREATOR_INPUT_AND_AI_BOUNDARY_DESIGN_2026-04-06.md):自定义世界里百梦主输入与 AI 分工边界设计。
|
||||
- [CUSTOM_WORLD_CREATOR_MANUAL_AI_SYSTEM_BALANCE_DESIGN_2026-04-12.md](./CUSTOM_WORLD_CREATOR_MANUAL_AI_SYSTEM_BALANCE_DESIGN_2026-04-12.md):自定义世界创作里“手填锚点 / AI 可改初稿 / 系统托管层”的平衡设计。
|
||||
- [CUSTOM_WORLD_CREATOR_PURE_AGENT_COMPARISON_AND_CONVERSION_DESIGN_2026-04-12.md](./CUSTOM_WORLD_CREATOR_PURE_AGENT_COMPARISON_AND_CONVERSION_DESIGN_2026-04-12.md):纯 Agent 式创作工具与结构化工作台方案的优缺点对比,以及转型设计。
|
||||
@@ -12,9 +13,12 @@
|
||||
- [MOBILE_CREATION_NEW_WORK_COMPACT_LAYOUT_2026-04-24.md](./MOBILE_CREATION_NEW_WORK_COMPACT_LAYOUT_2026-04-24.md):移动端创作页新建作品模块最多占用首屏约 1/3 高度的紧凑布局设计。
|
||||
- [MOBILE_CREATION_WORK_LIST_TWO_COLUMN_LAYOUT_2026-04-29.md](./MOBILE_CREATION_WORK_LIST_TWO_COLUMN_LAYOUT_2026-04-29.md):移动端创作页作品列表至少 2 列的紧凑布局设计。
|
||||
- [PLATFORM_HOME_MOBILE_FEED_CARD_REDESIGN_2026-04-28.md](./PLATFORM_HOME_MOBILE_FEED_CARD_REDESIGN_2026-04-28.md):平台首页移动端参考图式信息流、双端公开作品卡 16:9 封面结构与点赞数读模型设计。
|
||||
- [PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md](./PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md):平台移动端推荐、发现、创作、草稿、我的 Tab 重新分工与推荐页/底部导航改版设计。
|
||||
- [PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md](./PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md):平台创作 Tab 恢复模板 Tab 入口、默认选中拼图并内嵌拼图创作表单的布局设计。
|
||||
- [PLATFORM_CATEGORY_AND_CREATE_TAB_DESIGN_2026-04-24.md](./PLATFORM_CATEGORY_AND_CREATE_TAB_DESIGN_2026-04-24.md):平台入口新增分类 Tab、登录态导航裁剪与创作 Tab 视觉强化设计。
|
||||
- [PLATFORM_BIG_FISH_ENTRY_HIDE_2026-04-28.md](./PLATFORM_BIG_FISH_ENTRY_HIDE_2026-04-28.md):平台入口暂时隐藏大鱼吃小鱼创作卡片,但保留现有玩法链路。
|
||||
- [UNIFIED_MODAL_WINDOW_DESIGN_2026-04-25.md](./UNIFIED_MODAL_WINDOW_DESIGN_2026-04-25.md):统一平台风与 RPG 像素风模态窗口外壳、交互边界和迁移顺序。
|
||||
- [BAIMENG_EXPO_ROLLUP_BANNER_DESIGN_2026-05-07.md](./BAIMENG_EXPO_ROLLUP_BANNER_DESIGN_2026-05-07.md):百梦线下展会易拉宝广告展板的文案层级、视觉方向与 gpt-image-2 生成记录。
|
||||
- [AI_NATIVE_RUNTIME_ITEM_SYSTEM_REDESIGN_2026-04-02.md](./AI_NATIVE_RUNTIME_ITEM_SYSTEM_REDESIGN_2026-04-02.md):运行时物品生成系统重设计。
|
||||
- [LEVEL_PROGRESS_AND_CHAPTER_NPC_AUTO_SCALING_DESIGN_2026-04-20.md](./LEVEL_PROGRESS_AND_CHAPTER_NPC_AUTO_SCALING_DESIGN_2026-04-20.md):等级成长、章节经验节奏与 NPC 自动定级设计。
|
||||
- [RPG_NARRATIVE_PLANNING_FULL_PIPELINE_WORKFLOW_2026-04-12.md](./RPG_NARRATIVE_PLANNING_FULL_PIPELINE_WORKFLOW_2026-04-12.md):专业剧情策划构建 RPG 游戏全剧情的工作流程与交付模板。
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 943 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 999 KiB |
20
docs/experience/ADMIN_TASK_CONFIG_SCOPE_LOCK_2026-05-04.md
Normal file
20
docs/experience/ADMIN_TASK_CONFIG_SCOPE_LOCK_2026-05-04.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# AdminTaskConfigPage 埋点范围收口记录(2026-05-04)
|
||||
|
||||
## 变更目标
|
||||
- 前端隐藏「埋点范围」选择项。
|
||||
- 保存任务配置时固定传递 `scopeKind=user`,避免运营在后台选择其他范围。
|
||||
|
||||
## 落地结果
|
||||
- `apps/admin-web/src/pages/AdminTaskConfigPage.tsx`
|
||||
- 移除了「埋点范围」下拉选择 UI。
|
||||
- 保存时改为固定提交 `scopeKind: 'user'`。
|
||||
- 回填表单时不再读取或维护 `scopeKind` 的可编辑状态。
|
||||
|
||||
## 验证结果
|
||||
- 已检查页面源码,确认不再存在「埋点范围」字段与可编辑逻辑。
|
||||
- 已执行 `npm run admin-web:typecheck`。
|
||||
- 当前验证失败原因为环境缺少 `node_modules/typescript/bin/tsc`,属于依赖安装状态问题,不是本次前端改动引入的代码错误。
|
||||
|
||||
## 遗留说明
|
||||
- `apps/admin-web/src/api/adminApiTypes.ts` 中仍保留 `TrackingScopeKind` 类型,供接口契约与其他前端模块兼容使用。
|
||||
- `apps/admin-web/src/config/trackingEventDefinitions.ts` 仍保留事件定义中的 `scopeKind` 元数据,但页面不再允许运营修改。
|
||||
@@ -118,6 +118,11 @@
|
||||
- 早期方案曾在 `AuthGate` 层提供右上角全局账号信息条,并在 `GameShellRuntime` 中临时隐藏。
|
||||
- 2026-04-20 起,这个全局悬浮入口已整体下线,不再区分“平台显示 / 冒险隐藏”。
|
||||
- 原因是右上角高频观察区不适合承载账号入口,且平台内已经有更明确的页面内入口。
|
||||
|
||||
## 9. 2026-05-08 创作首页通知入口下线
|
||||
|
||||
- `CreativeAgentHome` 顶栏右上角不再展示“通知与账户”按钮,避免创作首页把通知入口放在首屏高频区域。
|
||||
- 账号入口仍保留在侧边栏底部,创作首页顶栏维持左侧菜单、居中品牌的轻量结构。
|
||||
- 当前账号相关入口统一保留在平台首页受保护动作、个人页、存档页与账号弹窗,不再占用全局悬浮层。
|
||||
|
||||
---
|
||||
|
||||
@@ -85,6 +85,11 @@
|
||||
2. 用户返回成本更低
|
||||
3. 操作像手游副面板,更符合预期
|
||||
|
||||
### 4.3.1 弹出确认面板不能透明
|
||||
- 删除作品、发布后分享、确认离开等关键弹窗必须有实体面板底色,不能只靠透明背景、毛玻璃或遮罩承载内容。
|
||||
- 通过 portal 挂到 `body` 的平台弹窗必须在遮罩层补齐平台主题类,否则主题变量会脱离页面容器,轻则颜色漂移,重则面板背景看起来透明。
|
||||
- 移动端关键确认弹窗优先居中显示,并保留 `max-height + 内部滚动`,避免被底部导航、安全区或底部抽屉布局遮住。
|
||||
|
||||
### 4.4 图标优于文字按钮
|
||||
- 在底部工具区,队伍/背包改成 icon 后更紧凑。
|
||||
- 但必须保留 `aria-label`,保证语义清晰、后续也方便测试。
|
||||
@@ -216,3 +221,8 @@
|
||||
- 后续新增怪物资源时,先检查红圈标注的实际落点,再调整锚点分档或单怪物偏移,避免出现“悬在地面上方”的状态。
|
||||
- 自定义世界里敌对角色已经先作为场景 NPC 存在,即使它同时携带 `characterId` 和 `monsterPresetId`,画布也不能直接沿用模板角色的 `groundOffsetY`;只要 encounter 自身有 `imageSrc` 或 `visual`,就按场景 NPC 自定义形象锚点处理。
|
||||
- 幕预览运行时还会构造“无 `characterId`、但有 `visual` 的场景 NPC”,这类和平相遇分支同样必须套用场景 NPC 自定义形象锚点,否则会停在画面中上部。
|
||||
|
||||
### 10.3 移动端固定整页画布缩放
|
||||
- 主站移动端以固定游戏画布体验为准,入口 `viewport` 需要锁定 `minimum-scale=1.0`、`maximum-scale=1.0` 和 `user-scalable=no`,同时保留 `viewport-fit=cover` 适配安全区。
|
||||
- 浏览器仍可能通过 iOS `gesture*` 或多指 `touchmove` 触发整页缩放,因此主站启动入口应统一调用 `lockMobileViewportZoom()` 拦截页面级捏合与快速双击缩放。
|
||||
- 不要在每个画布组件里重复注册缩放拦截;单指滚动、点击、拖拽应继续留给具体页面和玩法处理。
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
- [RPG_DRAFT_IMAGE_PARALLEL_GENERATION_2026-04-24.md](./RPG_DRAFT_IMAGE_PARALLEL_GENERATION_2026-04-24.md):记录 RPG 底稿阶段角色主形象与场景背景图并行生成约束。
|
||||
- [PLATFORM_HOME_BANNER_IMAGE_SIZE_FIX_2026-04-25.md](./PLATFORM_HOME_BANNER_IMAGE_SIZE_FIX_2026-04-25.md):记录首页 banner 背景图不能进入普通布局流的修复经验。
|
||||
- [RPG_PUBLISH_GALLERY_REFRESH_FIX_2026-04-25.md](./RPG_PUBLISH_GALLERY_REFRESH_FIX_2026-04-25.md):记录 RPG 发布后首页 / 分类页公开作品列表刷新链路。
|
||||
- [VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md](./VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md):记录视觉小说模板交接时的阅读顺序、常见坑和维护检查口径。
|
||||
- [AGENT_EMPTY_SESSION_DRAFT_VISIBILITY_2026-04-26.md](./AGENT_EMPTY_SESSION_DRAFT_VISIBILITY_2026-04-26.md):记录 Agent 空会话不应进入作品草稿列表的后端判定规则。
|
||||
- [BIG_FISH_PUBLISH_FEEDBACK_FIX_2026-04-26.md](./BIG_FISH_PUBLISH_FEEDBACK_FIX_2026-04-26.md):记录大鱼吃小鱼发布成功后结果页反馈与作品列表刷新的修复口径。
|
||||
- [BIG_FISH_WORKS_JSON_COMPAT_FIX_2026-04-28.md](./BIG_FISH_WORKS_JSON_COMPAT_FIX_2026-04-28.md):记录大鱼作品列表 `items_json` 字段升级后的向后兼容修复口径,避免旧 JSON 直接打崩 works 接口。
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# 视觉小说模板交接与维护经验 2026-05-07
|
||||
|
||||
## 1. 先读什么
|
||||
|
||||
新开发者接手视觉小说时,建议按这个顺序看:
|
||||
|
||||
1. [AI 原生视觉小说模板 PRD](../prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md)
|
||||
2. [视觉小说模板实现收口与交接说明](../technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md)
|
||||
3. [SpacetimeDB 表说明与查询目录](../technical/SPACETIMEDB_TABLE_CATALOG.md)
|
||||
4. [视觉小说 VN-03 Prompt 与 LLM 工具实现说明](../technical/VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md)
|
||||
5. [视觉小说 VN-11 负向扫描报告](../audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md)
|
||||
|
||||
## 2. 最容易踩的坑
|
||||
|
||||
1. 不要把 `visual_novel_runtime_history_entry` 当回放表来扩。
|
||||
2. 不要把 `visual_novel_runtime_event` 当业务回放数据源来用。
|
||||
3. 不要绕过平台资产对象去保存图片、音乐或文档。
|
||||
4. 不要让旧 TXT 迁移文档重新变成实现口径。
|
||||
5. 不要忘记发布后刷新作品架和公开聚合。
|
||||
6. 不要忘记退出登录时清理视觉小说私有状态。
|
||||
|
||||
## 3. 维护时的判断顺序
|
||||
|
||||
1. 先看是不是共享契约变化。
|
||||
2. 再看是不是 SpacetimeDB 表或 facade 变化。
|
||||
3. 然后看是不是作品架、广场或 runtime 的前端分流变化。
|
||||
4. 最后才看文档措辞和历史说明。
|
||||
|
||||
## 4. 常用检查
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
npm run check:visual-novel-vn11
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
如果改了后端,再补:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts
|
||||
cargo test -p module-visual-novel
|
||||
cargo check -p api-server
|
||||
```
|
||||
|
||||
## 5. 一句话结论
|
||||
|
||||
视觉小说模板已经是平台内的正式模板玩法,不是外部平台迁移;后续维护只需要沿着 PRD、表目录、prompt 文档和这份交接说明往下走。
|
||||
33
docs/operations/PROFILE_TASK_QUERY_PLAYBOOK_2026-05-03.md
Normal file
33
docs/operations/PROFILE_TASK_QUERY_PLAYBOOK_2026-05-03.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 个人任务运营查询手册
|
||||
|
||||
更新时间:`2026-05-03`
|
||||
|
||||
## 任务配置
|
||||
|
||||
```powershell
|
||||
spacetime sql <db> "SELECT * FROM profile_task_config ORDER BY updated_at DESC"
|
||||
spacetime sql <db> "SELECT * FROM profile_task_config WHERE task_id = 'daily_login'"
|
||||
```
|
||||
|
||||
## 任务进度
|
||||
|
||||
```powershell
|
||||
spacetime sql <db> "SELECT * FROM profile_task_progress WHERE user_id = '<user_id>' ORDER BY updated_at DESC"
|
||||
spacetime sql <db> "SELECT * FROM profile_task_progress WHERE user_id = '<user_id>' AND task_id = 'daily_login' ORDER BY day_key DESC"
|
||||
```
|
||||
|
||||
## 领奖记录
|
||||
|
||||
```powershell
|
||||
spacetime sql <db> "SELECT * FROM profile_task_reward_claim WHERE user_id = '<user_id>' ORDER BY claimed_at DESC"
|
||||
spacetime sql <db> "SELECT * FROM profile_task_reward_claim WHERE claim_id = '<user_id>:daily_login:<day_key>'"
|
||||
```
|
||||
|
||||
## 钱包流水对账
|
||||
|
||||
```powershell
|
||||
spacetime sql <db> "SELECT * FROM profile_wallet_ledger WHERE user_id = '<user_id>' AND source_type = 'DailyTaskReward' ORDER BY created_at DESC"
|
||||
spacetime sql <db> "SELECT * FROM profile_dashboard_state WHERE user_id = '<user_id>'"
|
||||
```
|
||||
|
||||
每日登录奖励流水 ID 为 `task-reward:<user_id>:daily_login:<day_key>`,领奖记录中的 `wallet_ledger_id` 必须能在 `profile_wallet_ledger` 中查到。
|
||||
5
docs/operations/README.md
Normal file
5
docs/operations/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 运营查询
|
||||
|
||||
本目录存放后台运营核查、对账和排障查询,不承载埋点系统本身的查询手册。
|
||||
|
||||
- [PROFILE_TASK_QUERY_PLAYBOOK_2026-05-03.md](./PROFILE_TASK_QUERY_PLAYBOOK_2026-05-03.md):个人任务配置、进度、领奖记录与钱包流水对账查询。
|
||||
863
docs/prd/AI_NATIVE_2048_GAMEPLAY_TEMPLATE_PRD_2026-05-05.md
Normal file
863
docs/prd/AI_NATIVE_2048_GAMEPLAY_TEMPLATE_PRD_2026-05-05.md
Normal file
@@ -0,0 +1,863 @@
|
||||
# AI 原生 2048 游戏玩法模板 PRD
|
||||
|
||||
更新时间:`2026-05-05`
|
||||
|
||||
## 0. 文档目的
|
||||
|
||||
这份 PRD 用于在当前平台内新增一条 `2048` 游戏玩法模板,并冻结它从创作入口、Agent 生成、结果页编辑、试玩、发布、公开运行到作品架展示的完整产品边界。
|
||||
|
||||
本次不是只做一个浏览器本地 2048 小游戏,也不是把经典 2048 规则随手写进某个前端组件。正式落地时,`2048` 必须作为平台内独立玩法类型接入现有创作中心、作品、广场、运行态和后端 DDD 分层。
|
||||
|
||||
---
|
||||
|
||||
## 1. 一句话定义
|
||||
|
||||
`2048` 是一个主题化数字合成玩法模板:百梦主通过 Agent 设定棋盘主题、合成链、视觉皮肤、目标格和难度参数,系统生成可试玩、可发布的 2048 作品;玩家通过滑动方向移动棋盘格,相同等级方块合并升级,直到达成目标格或棋盘无可行动作。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前接入级别
|
||||
|
||||
根据 `genarrative-play-type-integration` 的接入分级,本玩法按 **完整玩法闭环** 设计:
|
||||
|
||||
1. 新增玩法 ID:`twenty-forty-eight`。
|
||||
2. 展示名称:`2048`。
|
||||
3. 子标题:`主题合成棋盘`。
|
||||
4. 新建作品入口可展示,开放节奏由 `src/config/newWorkEntryConfig.ts` 控制。
|
||||
5. 支持 Agent 创作、草稿生成、结果页编辑、试玩、发布、公开运行、作品架和广场。
|
||||
6. 后端以 `server-rs + Axum + SpacetimeDB` 为服务侧真相源。
|
||||
7. 前端负责棋盘渲染、输入采集和动画表现,不绕过后端保存正式运行结果、榜单、扣费和发布状态。
|
||||
|
||||
工程命名采用 `twenty-forty-eight` 而不是裸 `2048`,避免 TypeScript、Rust 模块、文件名和路由中出现数字开头命名;面向用户的标题始终显示 `2048`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 产品定位
|
||||
|
||||
## 3.1 模板名称
|
||||
|
||||
1. 对外模板名称:`2048`。
|
||||
2. 对外子标题:`主题合成棋盘`。
|
||||
3. 开发代号:`TwentyFortyEight`。
|
||||
4. 工程玩法域:`twenty-forty-eight`。
|
||||
5. 后端模块命名:`twenty_forty_eight`。
|
||||
6. 公开作品号前缀:`TF-`。
|
||||
|
||||
## 3.2 核心乐趣
|
||||
|
||||
1. 玩家通过一次滑动改变整盘局势。
|
||||
2. 相同方块合并升级,形成清晰的成长反馈。
|
||||
3. 主题化合成链让数字升级变成可感知的叙事或收藏序列。
|
||||
4. 棋盘越来越拥挤,玩家在空间管理和短期收益之间做取舍。
|
||||
5. 达成目标格后可以继续冲击更高分,也可以结算分享成绩。
|
||||
|
||||
## 3.3 与现有玩法的区别
|
||||
|
||||
1. 不等同于拼图:不切图、不交换、不合并图片碎片。
|
||||
2. 不等同于抓大鹅:不做三消备选栏,不做物体堆叠点击。
|
||||
3. 不等同于方洞挑战:不做单次投放裁决,不靠反直觉规则制造误导。
|
||||
4. 不等同于大鱼吃小鱼:不做实时移动、吞噬和碰撞成长。
|
||||
5. 不复用 RPG 的世界、角色、章节或剧情推进结构。
|
||||
|
||||
---
|
||||
|
||||
## 4. 完整闭环目标
|
||||
|
||||
本玩法完整闭环必须补齐:
|
||||
|
||||
1. 平台创作中心选择 `2048`。
|
||||
2. Agent 对话收集主题、合成链、视觉风格、目标格和难度。
|
||||
3. 生成 2048 草稿。
|
||||
4. 进入结果页编辑作品名、简介、标签、封面、棋盘配置和方块皮肤。
|
||||
5. 支持发布前试玩。
|
||||
6. 发布作品。
|
||||
7. 玩家从作品详情、广场或作品架进入运行态。
|
||||
8. 后端初始化 run,保存种子、当前棋盘、分数、目标、状态和动作序列摘要。
|
||||
9. 玩家滑动方向后,前端提交动作,后端裁决新棋盘;前端按返回快照播放移动与合并动画。
|
||||
10. 达成目标、继续挑战、失败结算和排行榜写入都由后端正式裁决。
|
||||
11. 前端不得自行提交伪造分数、目标达成或榜单记录。
|
||||
|
||||
---
|
||||
|
||||
## 5. 明确不做
|
||||
|
||||
首版不做:
|
||||
|
||||
1. 不做多人实时对战。
|
||||
2. 不做复杂技能树或 RPG 数值养成。
|
||||
3. 不做可破坏障碍、棋盘机关和随机事件。
|
||||
4. 不支持玩家自定义任意 JavaScript 规则。
|
||||
5. 不在 UI 中默认展示长篇玩法说明、规则描述或内部字段解释。
|
||||
6. 不新增独立于平台之外的 2048 站点。
|
||||
7. 不复用 `customWorld`、`rpgWorld` 或旧 `server-node` 命名承载 2048 业务。
|
||||
8. 不把 LLM、生图、OSS 上传放进 SpacetimeDB reducer。
|
||||
|
||||
---
|
||||
|
||||
## 6. 创作锚点设计
|
||||
|
||||
Agent 型创作至少收集下面 5 个锚点:
|
||||
|
||||
| 锚点 | 字段建议 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| 合成主题 | `themePrompt` | 决定棋盘整体题材,例如修仙境界、猫咪成长、城市建设、料理升级。 |
|
||||
| 合成链 | `tileLadder` | 决定从 `2` 到目标格的每一级显示名、图标提示和视觉差异。 |
|
||||
| 视觉皮肤 | `visualStyle` | 决定方块材质、色彩、背景、动效气质和封面方向。 |
|
||||
| 难度参数 | `difficultyConfig` | 决定棋盘尺寸、目标格、初始方块、生成概率和是否允许撤销。 |
|
||||
| 反馈节奏 | `feedbackRhythm` | 决定移动、合并、升级、目标达成、失败结算的反馈强度。 |
|
||||
|
||||
Agent 行为要求:
|
||||
|
||||
1. 优先接住百梦主的一句话灵感,不把创作变成问卷。
|
||||
2. 每轮最多追问 1 个最影响成品质量的问题。
|
||||
3. 当主题和合成链已经足够明确时,优先生成草稿。
|
||||
4. 合成链必须围绕同一主题递进,不允许出现互不相关的方块名。
|
||||
5. 进入结果页前至少生成 `2` 到目标格之间的完整等级定义。
|
||||
|
||||
---
|
||||
|
||||
## 7. 玩法规则
|
||||
|
||||
## 7.1 棋盘
|
||||
|
||||
首版默认支持:
|
||||
|
||||
1. `4x4` 标准棋盘。
|
||||
2. 可选 `5x5` 放松棋盘。
|
||||
3. 可选 `3x3` 高压棋盘。
|
||||
|
||||
发布默认值:
|
||||
|
||||
1. `boardSize = 4`。
|
||||
2. `targetTileValue = 2048`。
|
||||
3. 初始方块数量为 `2`。
|
||||
4. 新方块生成值为 `2` 或 `4`。
|
||||
5. `2` 的生成概率为 `90%`,`4` 的生成概率为 `10%`。
|
||||
|
||||
## 7.2 输入
|
||||
|
||||
玩家每次只能提交一个方向:
|
||||
|
||||
```ts
|
||||
type TwentyFortyEightMoveDirection = 'up' | 'down' | 'left' | 'right';
|
||||
```
|
||||
|
||||
交互支持:
|
||||
|
||||
1. 移动端滑动。
|
||||
2. 桌面端方向键。
|
||||
3. 桌面端按钮或触控手势兜底。
|
||||
|
||||
无效输入规则:
|
||||
|
||||
1. 如果某方向不会改变棋盘,该动作不消耗步数。
|
||||
2. 无效动作返回当前快照和 `moveAccepted = false`。
|
||||
3. 前端只展示轻量反馈,不弹长说明。
|
||||
|
||||
## 7.3 合并
|
||||
|
||||
合并规则遵循经典 2048:
|
||||
|
||||
1. 同一行或列按移动方向压缩。
|
||||
2. 相邻且同值的两个方块合并成一个更高值方块。
|
||||
3. 每个方块在一次移动中最多合并一次。
|
||||
4. 合并后的方块值为原值的 `2` 倍。
|
||||
5. 本次得分增加合并后方块值。
|
||||
6. 移动完成后,如果棋盘有变化,随机空格生成一个新方块。
|
||||
|
||||
规则核心应封装为可测试的领域引擎。实现时优先评估成熟 2048 规则库;如 Rust / TypeScript 生态无合适库,必须把自研规则收敛在 `module-twenty-forty-eight` 的纯函数内,并用黄金用例、属性测试和前后端 fixture 保证一致。
|
||||
|
||||
## 7.4 胜负
|
||||
|
||||
状态取值:
|
||||
|
||||
```ts
|
||||
type TwentyFortyEightRunStatus =
|
||||
| 'playing'
|
||||
| 'target_reached'
|
||||
| 'continued_after_target'
|
||||
| 'game_over'
|
||||
| 'abandoned';
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
1. 首次出现 `targetTileValue` 时进入 `target_reached`。
|
||||
2. 玩家可选择结算,或继续挑战。
|
||||
3. 继续挑战后状态变为 `continued_after_target`。
|
||||
4. 棋盘无空格且四个方向都无法移动时进入 `game_over`。
|
||||
5. 玩家主动退出未结算时为 `abandoned`。
|
||||
|
||||
---
|
||||
|
||||
## 8. 草稿与结果页
|
||||
|
||||
## 8.1 结果页定位
|
||||
|
||||
2048 结果页是发布前的最小工作台,承担:
|
||||
|
||||
1. 编辑作品基本信息。
|
||||
2. 编辑棋盘参数。
|
||||
3. 编辑合成链显示名和视觉提示。
|
||||
4. 生成或上传封面。
|
||||
5. 进入试玩。
|
||||
6. 发布作品。
|
||||
|
||||
## 8.2 必备字段
|
||||
|
||||
```ts
|
||||
interface TwentyFortyEightResultDraft {
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
workTags: string[];
|
||||
coverImageSrc: string | null;
|
||||
coverAssetId: string | null;
|
||||
themePrompt: string;
|
||||
visualStyle: string;
|
||||
boardConfig: TwentyFortyEightBoardConfig;
|
||||
tileLadder: TwentyFortyEightTileDefinition[];
|
||||
scoringConfig: TwentyFortyEightScoringConfig;
|
||||
}
|
||||
|
||||
interface TwentyFortyEightBoardConfig {
|
||||
boardSize: 3 | 4 | 5;
|
||||
targetTileValue: 512 | 1024 | 2048 | 4096 | 8192;
|
||||
initialTileCount: 2 | 3 | 4;
|
||||
spawnValueWeights: Array<{ value: 2 | 4; weight: number }>;
|
||||
allowUndo: boolean;
|
||||
maxUndoCount: number;
|
||||
}
|
||||
|
||||
interface TwentyFortyEightTileDefinition {
|
||||
value: number;
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
colorToken: string;
|
||||
iconPrompt: string;
|
||||
imageSrc: string | null;
|
||||
assetId: string | null;
|
||||
}
|
||||
|
||||
interface TwentyFortyEightScoringConfig {
|
||||
scoreMode: 'classic';
|
||||
leaderboardMetric: 'score_then_steps' | 'score_then_time';
|
||||
}
|
||||
```
|
||||
|
||||
## 8.3 字段约束
|
||||
|
||||
1. `workTitle` 必填,建议 `4~16` 个中文字符。
|
||||
2. `workDescription` 必填,建议 `12~80` 个中文字符。
|
||||
3. `workTags` 必须为 `3~6` 个中文短标签。
|
||||
4. `boardSize` 首版只能是 `3`、`4`、`5`。
|
||||
5. `targetTileValue` 必须存在于 `tileLadder`。
|
||||
6. `tileLadder` 必须从 `2` 开始按倍增连续覆盖到目标格。
|
||||
7. `shortLabel` 移动端格子内最多建议 `4` 个中文字符。
|
||||
8. `spawnValueWeights` 权重和必须大于 `0`,归一化后用于后端生成。
|
||||
9. `allowUndo = true` 时 `maxUndoCount` 必须在 `1~3`。
|
||||
|
||||
## 8.4 结果页 UI
|
||||
|
||||
结果页采用移动端优先的页签结构:
|
||||
|
||||
1. `基本信息`:标题、简介、标签。
|
||||
2. `棋盘`:棋盘尺寸、目标格、撤销次数、生成概率。
|
||||
3. `合成链`:每级方块的显示名、短标签、颜色和图标预览。
|
||||
4. `封面`:封面预览、生成、上传或历史素材选择。
|
||||
|
||||
交互要求:
|
||||
|
||||
1. 发布按钮放在结果页右下操作区,移动端固定在底部安全区。
|
||||
2. 试玩按钮与发布按钮并列,但试玩不触发发布阻断。
|
||||
3. 发布校验只在点击发布后进入独立发布面板展示。
|
||||
4. 合成链编辑使用独立面板或抽屉,不在当前列表下方展开大表单。
|
||||
5. 页面不默认展示玩法规则说明。
|
||||
|
||||
---
|
||||
|
||||
## 9. 作品发布与广场
|
||||
|
||||
## 9.1 发布阻断
|
||||
|
||||
发布前必须校验:
|
||||
|
||||
1. 作品标题非空。
|
||||
2. 简介非空。
|
||||
3. 标签数量为 `3~6`。
|
||||
4. 棋盘配置合法。
|
||||
5. 合成链完整覆盖到目标格。
|
||||
6. 目标格存在。
|
||||
7. 封面存在。
|
||||
8. 作者信息可读。
|
||||
|
||||
## 9.2 作品摘要
|
||||
|
||||
```ts
|
||||
interface TwentyFortyEightWorkSummary {
|
||||
workId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
authorDisplayName: string;
|
||||
gameName: string;
|
||||
summary: string;
|
||||
tags: string[];
|
||||
coverImageSrc: string;
|
||||
boardSize: 3 | 4 | 5;
|
||||
targetTileValue: number;
|
||||
bestScore: number | null;
|
||||
playCount: number;
|
||||
likeCount: number;
|
||||
sourceSessionId: string;
|
||||
publicationStatus: 'draft' | 'published';
|
||||
updatedAt: string;
|
||||
publishedAt: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
## 9.3 广场卡片
|
||||
|
||||
广场卡片至少展示:
|
||||
|
||||
1. 封面。
|
||||
2. 作品名。
|
||||
3. 作者名。
|
||||
4. 标签。
|
||||
5. 棋盘规格和目标格。
|
||||
6. 进入游戏按钮。
|
||||
|
||||
不在卡片内展示完整规则说明。
|
||||
|
||||
---
|
||||
|
||||
## 10. 运行态设计
|
||||
|
||||
## 10.1 首屏
|
||||
|
||||
运行态首屏必须直接展示棋盘:
|
||||
|
||||
1. 顶部 HUD:分数、最高格、目标格、步数。
|
||||
2. 中部正方形棋盘。
|
||||
3. 底部轻量操作区:撤销、重新开始、退出。
|
||||
4. 移动端棋盘尽量贴近屏幕两侧安全边界。
|
||||
5. 桌面端棋盘居中,不使用营销式大卡片布局。
|
||||
|
||||
## 10.2 运行快照
|
||||
|
||||
```ts
|
||||
interface TwentyFortyEightRunSnapshot {
|
||||
runId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
status: TwentyFortyEightRunStatus;
|
||||
seed: string;
|
||||
board: TwentyFortyEightBoardSnapshot;
|
||||
score: number;
|
||||
bestTileValue: number;
|
||||
moveCount: number;
|
||||
undoRemaining: number;
|
||||
targetTileValue: number;
|
||||
reachedTargetAtMove: number | null;
|
||||
startedAtMs: number;
|
||||
updatedAtMs: number;
|
||||
endedAtMs: number | null;
|
||||
lastMove: TwentyFortyEightMoveResult | null;
|
||||
work: TwentyFortyEightWorkSummary;
|
||||
}
|
||||
|
||||
interface TwentyFortyEightBoardSnapshot {
|
||||
size: 3 | 4 | 5;
|
||||
cells: TwentyFortyEightCellSnapshot[];
|
||||
}
|
||||
|
||||
interface TwentyFortyEightCellSnapshot {
|
||||
row: number;
|
||||
col: number;
|
||||
tile: TwentyFortyEightTileSnapshot | null;
|
||||
}
|
||||
|
||||
interface TwentyFortyEightTileSnapshot {
|
||||
tileId: string;
|
||||
value: number;
|
||||
mergedFromTileIds: string[];
|
||||
spawnedAtMove: number;
|
||||
}
|
||||
|
||||
interface TwentyFortyEightMoveResult {
|
||||
direction: TwentyFortyEightMoveDirection;
|
||||
moveAccepted: boolean;
|
||||
scoreDelta: number;
|
||||
spawnedTile: TwentyFortyEightTileSnapshot | null;
|
||||
mergedTileIds: string[];
|
||||
}
|
||||
```
|
||||
|
||||
## 10.3 前后端职责
|
||||
|
||||
前端负责:
|
||||
|
||||
1. 渲染棋盘、HUD、结算面板。
|
||||
2. 采集滑动、键盘和按钮输入。
|
||||
3. 根据后端返回的 `lastMove` 播放移动、合并和生成动画。
|
||||
4. 做乐观动画可以,但必须以服务端快照回正。
|
||||
|
||||
前端不负责:
|
||||
|
||||
1. 保存正式分数。
|
||||
2. 写入榜单。
|
||||
3. 伪造目标达成。
|
||||
4. 绕过后端生成新方块。
|
||||
5. 自行发布作品状态。
|
||||
|
||||
后端负责:
|
||||
|
||||
1. 创建 run。
|
||||
2. 按 seed 初始化棋盘。
|
||||
3. 裁决每次移动。
|
||||
4. 生成新方块。
|
||||
5. 保存分数、步数、最高格和状态。
|
||||
6. 裁决目标达成、继续挑战、失败和放弃。
|
||||
7. 写入排行榜和埋点事件。
|
||||
|
||||
---
|
||||
|
||||
## 11. 后端分层边界
|
||||
|
||||
正式实现必须遵循当前 `server-rs + Axum + SpacetimeDB` 路线:
|
||||
|
||||
1. `server-rs/crates/module-twenty-forty-eight`
|
||||
- 纯领域规则、棋盘移动、合并裁决、随机种子输入、分数计算、发布校验。
|
||||
- 不依赖 Axum、SpacetimeDB、OSS 或 LLM。
|
||||
2. `server-rs/crates/shared-contracts`
|
||||
- 暴露 Agent、作品、运行态、广场 DTO。
|
||||
3. `server-rs/crates/spacetime-module`
|
||||
- 存储 session、message、work profile、runtime run、leaderboard、event。
|
||||
- 表结构变化必须同步 `migration.rs` 与表目录。
|
||||
4. `server-rs/crates/spacetime-client`
|
||||
- 提供 api-server 调用 SpacetimeDB 的 typed facade。
|
||||
5. `server-rs/crates/api-server`
|
||||
- 暴露 `/api/creation/twenty-forty-eight/*` 与 `/api/runtime/twenty-forty-eight/*`。
|
||||
- 处理鉴权、错误 envelope、LLM turn、生图编排、OSS 资产和 HTTP facade。
|
||||
6. `platform-llm` / `platform-oss`
|
||||
- 分别承载外部模型和资产副作用。
|
||||
|
||||
涉及 SpacetimeDB 的表、reducer、procedure、绑定生成、前端 SDK 接入时,必须按 `spacetimedb-cli`、`spacetimedb-rust`、`spacetimedb-concepts`、`spacetimedb-typescript` 约束执行。
|
||||
|
||||
---
|
||||
|
||||
## 12. SpacetimeDB 表建议
|
||||
|
||||
首版建议新增:
|
||||
|
||||
1. `twenty_forty_eight_agent_session`
|
||||
2. `twenty_forty_eight_agent_message`
|
||||
3. `twenty_forty_eight_work_profile`
|
||||
4. `twenty_forty_eight_runtime_run`
|
||||
5. `twenty_forty_eight_leaderboard_entry`
|
||||
6. `twenty_forty_eight_event`
|
||||
|
||||
表职责:
|
||||
|
||||
| 表 | 职责 |
|
||||
| --- | --- |
|
||||
| `twenty_forty_eight_agent_session` | 创作会话、阶段、草稿、已发布 profile 绑定。 |
|
||||
| `twenty_forty_eight_agent_message` | Agent 对话消息和流式 turn 结果。 |
|
||||
| `twenty_forty_eight_work_profile` | 作品草稿、发布状态、封面、棋盘配置、合成链和统计投影。 |
|
||||
| `twenty_forty_eight_runtime_run` | 单次运行快照、动作摘要、分数、状态和结算时间。 |
|
||||
| `twenty_forty_eight_leaderboard_entry` | 按作品、用户、棋盘规格和目标格记录最好成绩。 |
|
||||
| `twenty_forty_eight_event` | 发布、试玩、开始、移动、达成目标、失败、结算等审计事件。 |
|
||||
|
||||
reducer / procedure 不允许调用 LLM、OSS、生图、HTTP 或非确定性外部服务。
|
||||
|
||||
---
|
||||
|
||||
## 13. API 设计
|
||||
|
||||
## 13.1 创作接口
|
||||
|
||||
统一前缀:
|
||||
|
||||
```text
|
||||
/api/creation/twenty-forty-eight
|
||||
```
|
||||
|
||||
建议接口:
|
||||
|
||||
1. `POST /api/creation/twenty-forty-eight/sessions`
|
||||
2. `GET /api/creation/twenty-forty-eight/sessions/{sessionId}`
|
||||
3. `POST /api/creation/twenty-forty-eight/sessions/{sessionId}/messages`
|
||||
4. `POST /api/creation/twenty-forty-eight/sessions/{sessionId}/messages/stream`
|
||||
5. `POST /api/creation/twenty-forty-eight/sessions/{sessionId}/actions`
|
||||
6. `POST /api/creation/twenty-forty-eight/sessions/{sessionId}/compile`
|
||||
7. `GET /api/creation/twenty-forty-eight/works`
|
||||
8. `GET /api/creation/twenty-forty-eight/works/{profileId}`
|
||||
9. `PUT /api/creation/twenty-forty-eight/works/{profileId}`
|
||||
10. `POST /api/creation/twenty-forty-eight/works/{profileId}/publish`
|
||||
11. `DELETE /api/creation/twenty-forty-eight/works/{profileId}`
|
||||
|
||||
## 13.2 运行接口
|
||||
|
||||
统一前缀:
|
||||
|
||||
```text
|
||||
/api/runtime/twenty-forty-eight
|
||||
```
|
||||
|
||||
建议接口:
|
||||
|
||||
1. `GET /api/runtime/twenty-forty-eight/gallery`
|
||||
2. `GET /api/runtime/twenty-forty-eight/gallery/{profileId}`
|
||||
3. `POST /api/runtime/twenty-forty-eight/works/{profileId}/runs`
|
||||
4. `GET /api/runtime/twenty-forty-eight/runs/{runId}`
|
||||
5. `POST /api/runtime/twenty-forty-eight/runs/{runId}/moves`
|
||||
6. `POST /api/runtime/twenty-forty-eight/runs/{runId}/undo`
|
||||
7. `POST /api/runtime/twenty-forty-eight/runs/{runId}/continue`
|
||||
8. `POST /api/runtime/twenty-forty-eight/runs/{runId}/restart`
|
||||
9. `POST /api/runtime/twenty-forty-eight/runs/{runId}/abandon`
|
||||
10. `POST /api/runtime/twenty-forty-eight/runs/{runId}/leaderboard`
|
||||
|
||||
移动请求:
|
||||
|
||||
```ts
|
||||
interface TwentyFortyEightMoveRequest {
|
||||
clientActionId: string;
|
||||
direction: TwentyFortyEightMoveDirection;
|
||||
baseSnapshotVersion: number;
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```ts
|
||||
interface TwentyFortyEightMoveResponse {
|
||||
snapshot: TwentyFortyEightRunSnapshot;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. 前端落点
|
||||
|
||||
建议新增:
|
||||
|
||||
```text
|
||||
src/components/twenty-forty-eight-creation/TwentyFortyEightAgentWorkspace.tsx
|
||||
src/components/twenty-forty-eight-result/TwentyFortyEightResultView.tsx
|
||||
src/components/twenty-forty-eight-runtime/TwentyFortyEightRuntimeShell.tsx
|
||||
src/components/twenty-forty-eight-runtime/TwentyFortyEightBoard.tsx
|
||||
src/components/twenty-forty-eight-runtime/TwentyFortyEightHud.tsx
|
||||
src/services/twenty-forty-eight-creation/twentyFortyEightCreationClient.ts
|
||||
src/services/twenty-forty-eight-works/twentyFortyEightWorksClient.ts
|
||||
src/services/twenty-forty-eight-runtime/twentyFortyEightRuntimeClient.ts
|
||||
src/services/twenty-forty-eight-gallery/twentyFortyEightGalleryClient.ts
|
||||
```
|
||||
|
||||
平台入口接入时需要扩展:
|
||||
|
||||
1. `src/config/newWorkEntryConfig.ts`
|
||||
2. `src/components/platform-entry/platformEntryTypes.ts`
|
||||
3. `src/components/platform-entry/platformEntryCreationTypes.ts`
|
||||
4. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
5. `src/components/custom-world-home/creationWorkShelf.ts`
|
||||
6. `src/services/publicWorkCode.ts`
|
||||
|
||||
新增 selection stage:
|
||||
|
||||
```ts
|
||||
| 'twenty-forty-eight-agent-workspace'
|
||||
| 'twenty-forty-eight-result'
|
||||
| 'twenty-forty-eight-runtime'
|
||||
| 'twenty-forty-eight-gallery-detail'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. UI 要求
|
||||
|
||||
## 15.1 创作入口
|
||||
|
||||
入口卡片只表达:
|
||||
|
||||
1. `2048`
|
||||
2. `主题合成棋盘`
|
||||
3. 开放状态
|
||||
|
||||
不在入口卡片里堆规则说明。
|
||||
|
||||
## 15.2 Agent 工作台
|
||||
|
||||
工作台结构:
|
||||
|
||||
1. 对话流。
|
||||
2. 当前锚点摘要。
|
||||
3. 生成草稿动作。
|
||||
4. 进入结果页动作。
|
||||
|
||||
不展示世界观、角色、地点等 RPG 重结构。
|
||||
|
||||
## 15.3 运行态
|
||||
|
||||
运行态设计原则:
|
||||
|
||||
1. 棋盘是绝对主角。
|
||||
2. 移动端优先,单手可滑动。
|
||||
3. HUD 信息克制,只显示分数、目标、步数和最高格。
|
||||
4. 撤销、重新开始、退出使用 icon button 或短按钮。
|
||||
5. 目标达成、失败和排行榜使用独立弹窗或底部面板。
|
||||
6. 不把弹出面板实现成当前面板下方追加内容。
|
||||
|
||||
---
|
||||
|
||||
## 16. 测试与验收
|
||||
|
||||
## 16.1 领域测试
|
||||
|
||||
必须覆盖:
|
||||
|
||||
1. 向左移动压缩。
|
||||
2. 向右移动压缩。
|
||||
3. 单次移动中每个方块最多合并一次。
|
||||
4. 合并得分计算。
|
||||
5. 无效移动不生成新方块。
|
||||
6. 有效移动按 seed 生成新方块。
|
||||
7. 目标格达成。
|
||||
8. 无可行动作进入失败。
|
||||
9. 撤销次数消耗和快照恢复。
|
||||
|
||||
## 16.2 API 测试
|
||||
|
||||
必须覆盖:
|
||||
|
||||
1. 未登录不能创建作品和 run。
|
||||
2. 创建 session。
|
||||
3. 编译草稿。
|
||||
4. 发布校验阻断。
|
||||
5. 创建 run。
|
||||
6. 提交 move 后返回新快照。
|
||||
7. 无效 move 不增加步数。
|
||||
8. 达成目标后可继续挑战。
|
||||
9. 失败后不能继续提交 move。
|
||||
10. 排行榜只接受后端已结算 run。
|
||||
|
||||
## 16.3 前端测试
|
||||
|
||||
必须覆盖:
|
||||
|
||||
1. 入口展示与点击分流。
|
||||
2. Agent 工作台打开。
|
||||
3. 结果页编辑棋盘参数和合成链。
|
||||
4. 试玩进入运行态。
|
||||
5. 移动端滑动提交方向。
|
||||
6. 键盘方向键提交方向。
|
||||
7. 无效移动反馈。
|
||||
8. 目标达成弹窗。
|
||||
9. 失败结算弹窗。
|
||||
|
||||
## 16.4 建议验证命令
|
||||
|
||||
按改动范围执行:
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
npm run typecheck
|
||||
npm run test
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts twenty_forty_eight
|
||||
cargo test -p module-twenty-forty-eight
|
||||
cargo check -p api-server
|
||||
```
|
||||
|
||||
涉及 SpacetimeDB 表变化后:
|
||||
|
||||
```bash
|
||||
npm run spacetime:generate -- --rust-only
|
||||
npm run check:server-rs-ddd
|
||||
```
|
||||
|
||||
涉及 API smoke 时:
|
||||
|
||||
```bash
|
||||
npm run api-server
|
||||
```
|
||||
|
||||
启动后确认:
|
||||
|
||||
```text
|
||||
GET /healthz
|
||||
POST /api/creation/twenty-forty-eight/sessions
|
||||
POST /api/runtime/twenty-forty-eight/works/{profileId}/runs
|
||||
POST /api/runtime/twenty-forty-eight/runs/{runId}/moves
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. 并行任务拆分
|
||||
|
||||
## 任务 A:契约与共享类型
|
||||
|
||||
写入范围:
|
||||
|
||||
```text
|
||||
packages/shared/src/contracts/twentyFortyEightAgent.ts
|
||||
packages/shared/src/contracts/twentyFortyEightWorks.ts
|
||||
packages/shared/src/contracts/twentyFortyEightRuntime.ts
|
||||
packages/shared/src/contracts/twentyFortyEightGallery.ts
|
||||
server-rs/crates/shared-contracts/src/twenty_forty_eight_agent.rs
|
||||
server-rs/crates/shared-contracts/src/twenty_forty_eight_works.rs
|
||||
server-rs/crates/shared-contracts/src/twenty_forty_eight_runtime.rs
|
||||
server-rs/crates/shared-contracts/src/twenty_forty_eight_gallery.rs
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts twenty_forty_eight
|
||||
```
|
||||
|
||||
## 任务 B:领域规则模块
|
||||
|
||||
写入范围:
|
||||
|
||||
```text
|
||||
server-rs/crates/module-twenty-forty-eight/src/domain.rs
|
||||
server-rs/crates/module-twenty-forty-eight/src/application.rs
|
||||
server-rs/crates/module-twenty-forty-eight/src/rule_engine.rs
|
||||
server-rs/crates/module-twenty-forty-eight/src/random.rs
|
||||
server-rs/crates/module-twenty-forty-eight/src/lib.rs
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo test -p module-twenty-forty-eight
|
||||
```
|
||||
|
||||
## 任务 C:SpacetimeDB 表与 facade
|
||||
|
||||
写入范围:
|
||||
|
||||
```text
|
||||
server-rs/crates/spacetime-module/src/twenty_forty_eight.rs
|
||||
server-rs/crates/spacetime-module/src/lib.rs
|
||||
server-rs/crates/spacetime-module/src/migration.rs
|
||||
server-rs/crates/spacetime-client/src/twenty_forty_eight.rs
|
||||
docs/technical/SPACETIMEDB_TABLE_CATALOG.md
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
```bash
|
||||
npm run spacetime:generate -- --rust-only
|
||||
npm run check:server-rs-ddd
|
||||
cd server-rs
|
||||
cargo check -p spacetime-module
|
||||
cargo check -p spacetime-client
|
||||
```
|
||||
|
||||
## 任务 D:API / SSE facade
|
||||
|
||||
写入范围:
|
||||
|
||||
```text
|
||||
server-rs/crates/api-server/src/twenty_forty_eight.rs
|
||||
server-rs/crates/api-server/src/twenty_forty_eight_sse.rs
|
||||
server-rs/crates/api-server/src/app.rs
|
||||
server-rs/crates/api-server/src/state.rs
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo check -p api-server
|
||||
npm run api-server
|
||||
```
|
||||
|
||||
## 任务 E:前端创作、结果页与运行态
|
||||
|
||||
写入范围:
|
||||
|
||||
```text
|
||||
src/config/newWorkEntryConfig.ts
|
||||
src/components/platform-entry/*
|
||||
src/components/twenty-forty-eight-creation/*
|
||||
src/components/twenty-forty-eight-result/*
|
||||
src/components/twenty-forty-eight-runtime/*
|
||||
src/services/twenty-forty-eight-*
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
npm run test -- twenty
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
## 任务 F:作品架、广场、分享与回归
|
||||
|
||||
写入范围:
|
||||
|
||||
```text
|
||||
src/components/custom-world-home/creationWorkShelf.ts
|
||||
src/services/publicWorkCode.ts
|
||||
src/components/common/PublishShareModal.tsx
|
||||
src/components/twenty-forty-eight-gallery/*
|
||||
docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md
|
||||
docs/technical/SPACETIMEDB_TABLE_CATALOG.md
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
npm run test
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 18. 最小上线清单
|
||||
|
||||
1. `twenty-forty-eight` 入口可展示并进入工作台。
|
||||
2. Agent 可生成主题化 2048 草稿。
|
||||
3. 结果页可编辑基本信息、棋盘配置和合成链。
|
||||
4. 结果页可试玩。
|
||||
5. 运行态可完成标准 2048 移动、合并、生成新方块、目标达成和失败。
|
||||
6. 发布后作品进入作品架和广场。
|
||||
7. 玩家可从广场进入公开 run。
|
||||
8. 榜单只记录后端裁决的正式成绩。
|
||||
9. 前后端契约字段 camelCase / snake_case 映射明确。
|
||||
10. SpacetimeDB 表、migration、表目录和 bindings 同步。
|
||||
|
||||
---
|
||||
|
||||
## 19. 验收标准
|
||||
|
||||
当下面结果都成立时,视为 `2048` 玩法模板落地完成:
|
||||
|
||||
1. 平台有独立 `2048` 创作入口。
|
||||
2. 玩法 ID 使用 `twenty-forty-eight`。
|
||||
3. 能进入 2048 Agent 工作台。
|
||||
4. 能通过 Agent 生成草稿。
|
||||
5. 结果页可编辑作品名、简介、标签、棋盘参数、合成链和封面。
|
||||
6. 发布校验能阻断非法草稿。
|
||||
7. 试玩能进入 2048 运行态。
|
||||
8. 运行态支持移动端滑动和桌面方向键。
|
||||
9. 后端能裁决移动、合并、得分、新方块生成和胜负。
|
||||
10. 无效移动不增加步数,不生成新方块。
|
||||
11. 达成目标后可结算或继续挑战。
|
||||
12. 失败后不能继续提交移动。
|
||||
13. 发布作品能进入作品架、广场和分享链路。
|
||||
14. 排行榜只接受正式 run 的后端结算成绩。
|
||||
15. 新增表结构同步 `migration.rs`、表目录和 bindings。
|
||||
16. UI 不默认展示长篇规则说明,不把独立弹窗做成面板下方展开。
|
||||
17. 移动端和桌面端都能正常显示和操作。
|
||||
|
||||
---
|
||||
|
||||
## 20. 一句话结论
|
||||
|
||||
`2048` 在 Genarrative 中应被做成一个可创作、可换皮、可发布、可排行的主题合成棋盘模板:创作端让百梦主定义合成链和视觉承诺,运行端保持经典 2048 的滑动合并手感,服务端负责正式棋盘裁决、作品状态和成绩真相。
|
||||
@@ -468,8 +468,9 @@ interface CustomWorldCoverProfile {
|
||||
|
||||
1. 上传后先进入独立裁剪面板
|
||||
2. 裁剪框比例固定为 `16:9`
|
||||
3. 作者只能平移和缩放,不允许自由改比例
|
||||
4. 裁剪完成后,再提交给后端保存
|
||||
3. 作者直接在图片上拖拽裁剪框内部移动区域,拖拽四边或四角调整裁剪范围,不再通过参数滑杆调整
|
||||
4. 裁剪框调整过程中必须持续锁定 `16:9`,不允许自由改比例
|
||||
5. 裁剪完成后,再提交给后端保存
|
||||
|
||||
### 上传大小与格式限制
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# AI 原生抓大鹅 Match3D 玩法创作工具与玩法系统 PRD
|
||||
|
||||
更新时间:`2026-04-30`
|
||||
更新时间:`2026-05-10`
|
||||
|
||||
## 0. 文档目的
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
## 1. 一句话定义
|
||||
|
||||
让百梦主通过 Agent 对话确认题材、需要消除次数和难度,系统编译出一个可试玩、可发布的单局抓大鹅玩法作品;玩家在 `10` 分钟倒计时内点击圆形空间中可见物品,把物品放入下方 `7` 格备选栏,每凑齐 `3` 个同物品 id 自动消除,最终清空圆形空间内全部物品即胜利。
|
||||
让百梦主在创作页通过表单确认题材主题和难度选项,系统根据难度选项派生需要消除次数与难度数值,并编译出一个可试玩、可发布的单局抓大鹅玩法作品;玩家在 `10` 分钟倒计时内点击圆形空间中可见物品,把物品放入下方 `7` 格备选栏,每凑齐 `3` 个同物品 id 自动消除,最终清空圆形空间内全部物品即胜利。
|
||||
|
||||
---
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
```text
|
||||
平台创作入口
|
||||
-> 选择“抓大鹅”
|
||||
-> Agent 对话确认题材、需要消除次数、难度
|
||||
-> 入口表单确认题材主题、难度选项
|
||||
-> 生成待发布结果页
|
||||
-> 编辑作品基础信息
|
||||
-> 发布前试玩
|
||||
@@ -62,7 +62,7 @@
|
||||
## 3.1 复用什么
|
||||
|
||||
1. 复用平台创作中心入口。
|
||||
2. 复用 Agent-first 创作体验。
|
||||
2. 复用拼图式创作页入口表单体验。
|
||||
3. 复用“创作会话 -> 结果页 -> 发布 -> 运行态”的平台主链。
|
||||
4. 复用作品基础信息、标签、封面、发布接口和作品管理体验。
|
||||
5. 复用现有 Rust / SpacetimeDB 后端基线。
|
||||
@@ -93,13 +93,13 @@ Match3D 必须形成独立玩法域,后续技术方案至少需要覆盖:
|
||||
首版 demo 必须满足:
|
||||
|
||||
1. 平台新增“抓大鹅”玩法创作入口。
|
||||
2. 创建流程采用 Agent 对话收集关键配置。
|
||||
3. Agent 必须在进入结果页前确认:
|
||||
2. 创建流程采用入口表单收集关键配置。
|
||||
3. 表单必须在进入结果页前确认:
|
||||
- 题材主题
|
||||
- 需要消除次数
|
||||
- 难度 `1~10`
|
||||
4. 支持系统自动补全配置,用户确认后开始创造。
|
||||
5. 题材阶段允许上传参考图片。
|
||||
- 3D 素材风格
|
||||
- 难度选项
|
||||
4. `需要消除次数` 与难度 `1~10` 数值不再作为独立输入框展示,由难度选项派生。
|
||||
5. 生成抓大鹅草稿消耗 `20` 光点,生成按钮必须显式展示。
|
||||
6. 结果页支持编辑游戏名称、标签、封面图等基础发布信息。
|
||||
7. 发布前支持试玩,并允许随时停止和修改配置。
|
||||
8. 发布不要求试玩通关。
|
||||
@@ -110,7 +110,8 @@ Match3D 必须形成独立玩法域,后续技术方案至少需要覆盖:
|
||||
13. 清空圆形空间中全部物品即胜利。
|
||||
14. 倒计时结束或备选栏满即失败。
|
||||
15. 胜利 / 失败后展示结算界面。
|
||||
16. 点击、入槽、消除、失败、胜利的即时反馈效果由前端先行呈现,后端负责权威确认、状态落库和成绩可信性。
|
||||
16. 入口页的 3D 素材风格选择会进入素材图提示词,并作为结果页手动 Rodin 3D 模型生成的默认提示词依据。
|
||||
17. 点击、入槽、消除、失败、胜利的即时反馈效果由前端先行呈现,后端负责权威确认、状态落库和成绩可信性。
|
||||
|
||||
---
|
||||
|
||||
@@ -123,7 +124,7 @@ Match3D 必须形成独立玩法域,后续技术方案至少需要覆盖:
|
||||
3. 不做排行榜正式展示。
|
||||
4. 不做道具,但需要预留功能口。
|
||||
5. 不做洗牌、重置、旋转、放大等局内操作。
|
||||
6. 不做真实 3D 模型。
|
||||
6. 不做多批次真实 3D 模型生成;当前草稿生成只固定产出 `3` 个 GLB 模型并写入 OSS。
|
||||
7. 不做真实 3D 物理遮挡。
|
||||
8. 不做真实物理碰撞结算。
|
||||
9. 不做必须试玩通关才能发布的门槛。
|
||||
@@ -137,13 +138,22 @@ Match3D 必须形成独立玩法域,后续技术方案至少需要覆盖:
|
||||
|
||||
## 6.1 创作方式
|
||||
|
||||
Match3D 首版参考拼图早期的 Agent 对话收集锚点,而不是拼图后期的纯表单入口。
|
||||
Match3D 首版参考拼图后期的入口表单收集方式,而不是早期的 Agent 对话锚点收集。
|
||||
|
||||
Agent 的职责是帮助用户确认可以直接编译 demo 的最小配置:
|
||||
表单的职责是帮助用户确认可以直接编译 demo 的最小配置:
|
||||
|
||||
1. 题材主题。
|
||||
2. 需要消除次数。
|
||||
3. 游戏难度。
|
||||
2. 3D 素材风格。
|
||||
3. 游戏难度选项。
|
||||
|
||||
`需要消除次数` 与游戏难度数值仍属于后端会话配置,但不再要求用户手填。当前入口页固定采用以下映射:
|
||||
|
||||
```text
|
||||
轻松 -> 需要消除 8 次,难度 2
|
||||
标准 -> 需要消除 12 次,难度 4
|
||||
进阶 -> 需要消除 16 次,难度 6
|
||||
硬核 -> 需要消除 20 次,难度 8
|
||||
```
|
||||
|
||||
## 6.2 必填配置
|
||||
|
||||
@@ -151,13 +161,13 @@ Agent 的职责是帮助用户确认可以直接编译 demo 的最小配置:
|
||||
|
||||
题材决定后续生成或选择物品素材的方向。用户可以自定义主题,例如水果、玩具、食物、符号等。
|
||||
|
||||
首版 demo 不接入真实图片生成。运行态可消除物统一使用纯色几何体表现,不使用透明气泡,也不在图案上放文字标识。题材仍决定后端生成的 `visualKey` 和尺寸比例,但前端首版用差异化颜色与几何造型表现可消除物,例如圆形、三角形、菱形、五角星、梯形、平行四边形等,避免玩家在堆叠状态下难以辨认。
|
||||
当前抓大鹅草稿生成会固定生成 `3` 个题材物品:素材图切割出的独立图片会作为 Rodin 图生 3D 参考图,生成出的 GLB 模型必须转存 OSS,并随作品 profile 的 `generatedItemAssets` 持久化。运行态优先使用这些生成模型;只有模型缺失、加载失败或未进入 3D 渲染模式时,才回退到 25 个默认积木件视觉键。默认素材不使用透明气泡,也不在图案上放文字标识。
|
||||
|
||||
水果图形资产需要具备常识可感知的相对大小关系,但不要求真实比例绝对精准。首版固定规则为:西瓜明显大于苹果;苹果、橙子、梨、桃子为中等尺寸;葡萄、李子、青柠等小型水果略小。该尺寸由后端运行态物品 `radius` 下发,前端只按快照表现。
|
||||
可消除物尺寸使用五档相对体积规则: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` 下发权威尺寸,前端只按快照表现。
|
||||
|
||||
### 需要消除次数
|
||||
|
||||
用户输入任意正整数。
|
||||
该字段由难度选项派生,不作为入口页独立输入框展示。
|
||||
|
||||
该字段不是胜利条件,而是本局总物品规模配置:
|
||||
|
||||
@@ -169,21 +179,23 @@ Agent 的职责是帮助用户确认可以直接编译 demo 的最小配置:
|
||||
|
||||
### 难度
|
||||
|
||||
用户输入 `1~10` 的数字,代表从低到高的难度感受。
|
||||
用户选择 `轻松 / 标准 / 进阶 / 硬核` 之一,系统派生 `1~10` 范围内的难度数值。
|
||||
|
||||
首版 demo 中,用户只需凭感觉选择难度;具体难度规则由系统内部解释。后续优化阶段再细化难度曲线、生成算法和遮挡策略。
|
||||
|
||||
## 6.3 自动配置
|
||||
### 3D 素材风格
|
||||
|
||||
如果用户不想逐项填写,系统可以自动补全题材、需要消除次数和难度。
|
||||
入口页在题材主题与难度之间展示 `3D素材风格` 横向滑动选择。首批固定选项为:
|
||||
|
||||
自动补全后的配置必须展示给用户确认,用户确认后才能开始创造。
|
||||
```text
|
||||
黏土手作 / 低多边形 / 玩具塑料 / 木质雕刻 / 体素积木 / 金属机甲 / 自定义
|
||||
```
|
||||
|
||||
## 6.4 参考图片
|
||||
每个内置选项使用 VectorEngine `gpt-image-2-all` 生成的画风参考图展示;参考图保存在 `public/match3d-style-references/`,只作为入口选择的视觉提示,不作为用户上传参考图。选择内置风格时,前端提交 `assetStyleId`、`assetStyleLabel` 与对应 `assetStylePrompt`。选择 `自定义` 时必须弹出独立面板,用户填写描述后才允许应用;自定义描述作为 `assetStylePrompt` 进入后端生成链路。
|
||||
|
||||
题材阶段允许用户上传参考图片。
|
||||
## 6.3 参考图片
|
||||
|
||||
首版只支持图片参考,不支持视频参考。参考图片用于影响题材表现,不作为运行时规则裁决依据。
|
||||
抓大鹅入口页不展示参考图片上传。题材表现由题材文本和草稿切割图片链路承接;草稿生成阶段会直接以切割图片作为 Rodin 图生模型参考图生成首批 GLB。结果页 `3D素材` Tab 仍可对单个素材触发重新生成。
|
||||
|
||||
---
|
||||
|
||||
@@ -210,9 +222,9 @@ Agent 的职责是帮助用户确认可以直接编译 demo 的最小配置:
|
||||
|
||||
## 7.3 素材生成边界
|
||||
|
||||
首版结果页暂时不生成额外素材。
|
||||
抓大鹅草稿生成链路会生成首批 `3` 个题材物品素材:文本模型生成物品名,VectorEngine 生成 `2*2` 素材图并切割独立图片,再以每张独立图片调用 Rodin 图生 3D,下载 `.glb` 并转存 OSS。入口页选择的 `assetStylePrompt` 必须写入素材图提示词;结果页手动 Rodin 图生模型时,继续以该物品图片和默认提示词作为起点。
|
||||
|
||||
后续如果需要生成题材物品、伪 3D 物品、场景背景或封面图,需要先补充本文档或新增技术方案,再进入编码。
|
||||
生成出的独立图片与 GLB 模型都必须作为草稿页 `3D素材` Tab 的预览资产返回。模型生成成功时 `generatedItemAssets[].status = model_ready`,并携带 `modelSrc`、`modelObjectKey`、`modelFileName`、`taskUuid` 和 `subscriptionKey`;正式平台资产绑定和更完整的二次编辑流程以后续技术方案为准。
|
||||
|
||||
## 7.4 发布前试玩
|
||||
|
||||
@@ -265,6 +277,16 @@ totalItemCount = clearCount * 3
|
||||
|
||||
每种物品数量必须是 `3` 的倍数,避免生成无法通关的局。
|
||||
|
||||
生成的消除物类型数由用户填写的需要消除次数决定:
|
||||
|
||||
```text
|
||||
itemTypeCount = clearCount <= 25 ? clearCount : 25
|
||||
```
|
||||
|
||||
当 `clearCount <= 25` 时,本局生成的 `itemTypeId` 数量等于 `clearCount`,每种类型默认生成 `3` 件;当 `clearCount > 25` 时,本局最多生成 `25` 种 `itemTypeId`,后续消除组按这 `25` 种类型轮转补齐,且每种类型最终数量仍必须保持 `3` 的倍数。
|
||||
|
||||
同一局内这些类型必须分别使用不同的形状和颜色组合,不能出现两个组看起来像同一种物体的情况。
|
||||
|
||||
## 8.4 阶段陆续生成
|
||||
|
||||
每局物品允许阶段陆续生成。
|
||||
@@ -275,12 +297,14 @@ totalItemCount = clearCount * 3
|
||||
|
||||
## 8.5 物品资产
|
||||
|
||||
首版 demo 使用 2D 图案素材。
|
||||
当前 demo 使用生成 GLB 优先、默认积木兜底的物品资产策略。
|
||||
|
||||
1. demo 至少提供 `10` 种颜色与几何造型组合素材。
|
||||
2. 当题材为水果时,后端仍可切换到 `10` 种水果视觉键和尺寸比例,但前端首版必须把这些视觉键映射为无文字的纯色几何体,不能显示为水果图、透明气泡或文字标记。
|
||||
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. 用户题材主题后续会映射为符合常识预期的物品集合。
|
||||
|
||||
示例:水果题材可以对应红色苹果、黄色香蕉、紫色葡萄等。
|
||||
|
||||
@@ -310,6 +334,8 @@ totalItemCount = clearCount * 3
|
||||
|
||||
飞行动画过程中,物品不再与其他物品产生碰撞。
|
||||
|
||||
当前 3D 实验模式下,物品进入备选栏后必须从圆形空间的物理世界移除;备选栏只展示该物品同款 3D 模型的独立预览,固定为斜 `45` 度便于识别,不再参与场内碰撞、重力或堆叠。
|
||||
|
||||
前端播放即时反馈的同时,必须向后端提交点击意图。后端确认后固化入槽结果;后端拒绝时,前端恢复物品位置和备选栏表现。
|
||||
|
||||
## 8.9 备选栏
|
||||
@@ -318,8 +344,9 @@ totalItemCount = clearCount * 3
|
||||
|
||||
1. 每次点击进入即时反馈流程后,前端先把物品表现为进入备选栏。
|
||||
2. 备选栏中每出现 `3` 个相同物品 id,前端立即播放自动消除效果并腾出格子。
|
||||
3. 后端确认后固化真实备选栏和消除结果;若后端返回状态不一致,前端必须以最新后端快照校正。
|
||||
4. 如果备选栏满且无法消除,前端可以立即展示失败过渡,最终失败状态以后端确认为准。
|
||||
3. 3D 模式下,备选栏格子展示从场内取出的同款 3D 模型预览,视角固定斜 `45` 度,不使用另一套不一致的 UI 图标;托盘预览必须共享一个 WebGL renderer,并按 `7` 格容器实际宽高把模型居中摆放到对应格子,不能因多个预览上下文导致中心场地模型不可见;WebGL 回退或 `2D` 模式下才使用保留的 2D 图标。
|
||||
4. 后端确认后固化真实备选栏和消除结果;若后端返回状态不一致,前端必须以最新后端快照校正。
|
||||
5. 如果备选栏满且无法消除,前端可以立即展示失败过渡,最终失败状态以后端确认为准。
|
||||
|
||||
## 8.10 胜利
|
||||
|
||||
@@ -402,15 +429,14 @@ totalItemCount = clearCount * 3
|
||||
|
||||
前端负责所有游戏过程中需要即时呈现的反馈效果:
|
||||
|
||||
1. 展示 Agent 创作界面。
|
||||
1. 展示表单式创作界面。
|
||||
2. 展示结果页和基础编辑表单。
|
||||
3. 上传参考图片。
|
||||
4. 展示运行态场景、物品、倒计时和备选栏。
|
||||
5. 基于最新后端快照执行 2D 命中检测、悬停、按压和选中反馈。
|
||||
6. 发送玩家点击意图。
|
||||
7. 在等待后端确认期间,先行播放飞入、入槽、三消、腾格、胜利和失败过渡效果。
|
||||
8. 收到后端确认后,把本地表现校正到权威快照。
|
||||
9. 展示结算界面。
|
||||
3. 展示运行态场景、物品、倒计时和备选栏。
|
||||
4. 基于最新后端快照执行 2D 命中检测、悬停、按压和选中反馈。
|
||||
5. 发送玩家点击意图。
|
||||
6. 在等待后端确认期间,先行播放飞入、入槽、三消、腾格、胜利和失败过渡效果。
|
||||
7. 收到后端确认后,把本地表现校正到权威快照。
|
||||
8. 展示结算界面。
|
||||
|
||||
前端可以做即时表现预判,但不得把预判结果作为最终规则真相或成绩来源。
|
||||
|
||||
@@ -431,7 +457,7 @@ totalItemCount = clearCount * 3
|
||||
```ts
|
||||
interface Match3DCreatorConfig {
|
||||
themeText: string;
|
||||
referenceImageSrc?: string;
|
||||
referenceImageSrc?: string | null;
|
||||
clearCount: number;
|
||||
difficulty: number;
|
||||
}
|
||||
@@ -440,9 +466,9 @@ interface Match3DCreatorConfig {
|
||||
字段说明:
|
||||
|
||||
1. `themeText`:题材主题。
|
||||
2. `referenceImageSrc`:可选参考图片。
|
||||
3. `clearCount`:需要消除次数,必须为正整数。
|
||||
4. `difficulty`:难度,范围为 `1~10`。
|
||||
2. `referenceImageSrc`:历史兼容字段,入口页固定提交为 `null`。
|
||||
3. `clearCount`:由难度选项派生的需要消除次数,必须为正整数。
|
||||
4. `difficulty`:由难度选项派生的难度数值,范围为 `1~10`。
|
||||
|
||||
## 11.2 作品结构
|
||||
|
||||
@@ -643,15 +669,17 @@ GET /api/runtime/match3d/runs/:runId
|
||||
|
||||
创作入口只展示必要信息,不默认堆叠玩法规则说明。
|
||||
|
||||
## 14.2 Agent 工作区
|
||||
## 14.2 入口表单
|
||||
|
||||
Agent 每轮优先追问最影响 demo 生成的一个问题。
|
||||
入口表单只展示三个输入块:
|
||||
|
||||
已确认的信息应清晰展示给用户确认:
|
||||
1. `想做一个什么题材的抓大鹅?` 大文本输入框。
|
||||
2. `3D素材风格` 横向滑动风格卡,最后一个为 `自定义`。
|
||||
3. `难度` 选项按钮。
|
||||
|
||||
1. 题材主题。
|
||||
2. 需要消除次数。
|
||||
3. 难度。
|
||||
入口页不展示参考图、`需要消除次数` 数值输入、`难度数值` 滑杆,也不展示 `题材 / 物品 / 难度` 三个摘要框。`需要消除次数` 和 `difficulty` 由难度选项派生后提交给后端。
|
||||
|
||||
抓大鹅入口页必须适配移动端创作页一屏内展示,页面自身不产生纵向滚动。题材输入框、风格横滑区、难度按钮和生成按钮根据可视高度自适应压缩,风格区只允许横向滑动。
|
||||
|
||||
## 14.3 结果页
|
||||
|
||||
@@ -676,22 +704,24 @@ Agent 每轮优先追问最影响 demo 生成的一个问题。
|
||||
首版 PRD 对应 demo 验收标准:
|
||||
|
||||
1. 用户可从平台创作入口进入“抓大鹅”模板。
|
||||
2. Agent 能确认题材、需要消除次数和难度。
|
||||
3. 用户可上传参考图片。
|
||||
4. 系统可生成待发布结果页。
|
||||
5. 用户可编辑游戏名称、标签、封面图等基础信息。
|
||||
6. 用户可发布前试玩,且试玩失败不阻断发布。
|
||||
7. 运行态能展示圆形空间、倒计时、物品和 `7` 格备选栏。
|
||||
8. 物品可重叠、遮挡、堆叠。
|
||||
9. 被完全遮挡物品不可点击,露出可点击区域的物品可点击。
|
||||
10. 点击通过后物品飞入备选栏。
|
||||
11. 备选栏中 `3` 个相同物品 id 自动消除。
|
||||
12. 清空空间中全部物品后胜利。
|
||||
13. 倒计时结束或备选栏满后失败。
|
||||
14. 胜利结算展示使用时间。
|
||||
15. 失败结算展示完成进度和重新开始按钮。
|
||||
16. 局内即时反馈由前端先行呈现,关键状态以后端确认快照校正。
|
||||
17. 相关中文文档通过编码检查。
|
||||
2. 入口表单能确认题材主题、3D 素材风格和难度选项,并提交派生后的消除次数与难度数值。
|
||||
3. 入口页不展示参考图上传。
|
||||
4. 内置风格显示画风参考图,自定义风格通过独立面板填写并进入提交 payload。
|
||||
5. 移动端入口页所有内容一屏展示,不产生纵向滚动。
|
||||
6. 系统可生成待发布结果页,并在草稿中返回首批切割图片与 OSS GLB 模型素材预览。
|
||||
7. 用户可编辑游戏名称、标签、封面图等基础信息。
|
||||
8. 用户可发布前试玩,且试玩失败不阻断发布。
|
||||
9. 运行态能展示圆形空间、倒计时、物品和 `7` 格备选栏;存在 `generatedItemAssets` 模型时必须优先展示生成 GLB,而不是默认积木素材。
|
||||
10. 物品可重叠、遮挡、堆叠。
|
||||
11. 被完全遮挡物品不可点击,露出可点击区域的物品可点击。
|
||||
12. 点击通过后物品飞入备选栏。
|
||||
13. 备选栏中 `3` 个相同物品 id 自动消除。
|
||||
14. 清空空间中全部物品后胜利。
|
||||
15. 倒计时结束或备选栏满后失败。
|
||||
16. 胜利结算展示使用时间。
|
||||
17. 失败结算展示完成进度和重新开始按钮。
|
||||
18. 局内即时反馈由前端先行呈现,关键状态以后端确认快照校正。
|
||||
19. 相关中文文档通过编码检查。
|
||||
|
||||
---
|
||||
|
||||
@@ -706,7 +736,7 @@ Agent 每轮优先追问最影响 demo 生成的一个问题。
|
||||
## 阶段 B:创作与结果页
|
||||
|
||||
1. 新增平台创作入口。
|
||||
2. 接入 Agent 会话。
|
||||
2. 接入创作会话。
|
||||
3. 编译草稿并进入结果页。
|
||||
4. 复用基础信息编辑和发布链。
|
||||
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
# AI 原生方洞挑战玩法创作工具与玩法系统 PRD
|
||||
|
||||
更新时间:`2026-05-04`
|
||||
|
||||
## 0. 文档目的
|
||||
|
||||
这份 PRD 用于在当前平台内新增一条“方洞挑战”玩法类型,并先冻结它从入口占位到完整可创建闭环的产品边界。
|
||||
|
||||
本玩法来自参考视频中的核心反差:玩家看到不同形状的积木与多个洞口,会本能判断“形状应放入对应洞口”,但演示不断把不同形状都放进同一个方洞,形成“规则预期被打破”的喜剧张力。
|
||||
|
||||
本次不是简单复制视频内容,也不是只新增一个前端小游戏。正式版本需要把这种反直觉机制抽象成平台内可创作、可试玩、可发布的玩法类型。
|
||||
|
||||
---
|
||||
|
||||
## 1. 一句话定义
|
||||
|
||||
“方洞挑战”是一个反直觉形状分拣玩法:百梦主通过 Agent 设定形状主题、洞口规则、误导节奏和反差演出,系统生成一个移动端优先的单局挑战;玩家在限时内把连续出现的形状投入正确洞口,后端根据当前关卡的真实兼容规则裁决成功、失败和连击。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前接入级别
|
||||
|
||||
根据 `genarrative-play-type-integration` 的接入分级,本次落地到 **完整玩法闭环**:
|
||||
|
||||
1. 新增玩法 ID:`square-hole`。
|
||||
2. 新增展示名称:`方洞挑战`。
|
||||
3. 新增子标题:`反直觉形状分拣`。
|
||||
4. 在新建作品入口、创作类型弹层、结果页、运行态和作品架展示。
|
||||
5. 支持 Agent 创作、草稿生成、结果页编辑、试玩、发布和公开运行。
|
||||
6. 后端以 `server-rs + Axum + SpacetimeDB` 为真相源,前端只渲染快照与交互。
|
||||
|
||||
不允许把运行规则临时写成前端本地真相,也不复用 `server-node`、Express 或 PostgreSQL。
|
||||
|
||||
---
|
||||
|
||||
## 3. 产品定位
|
||||
|
||||
## 3.1 模板名称
|
||||
|
||||
1. 对外模板名称:`方洞挑战`。
|
||||
2. 对外子标题:`反直觉形状分拣`。
|
||||
3. 开发代号:`SquareHole`。
|
||||
4. 工程玩法域:`square-hole`。
|
||||
5. 后端模块命名预期:`square_hole`。
|
||||
|
||||
## 3.2 核心乐趣
|
||||
|
||||
1. 玩家先根据形状轮廓做直觉判断。
|
||||
2. 关卡通过洞口、提示、视觉遮挡和演出制造误导。
|
||||
3. 真实规则可以是“方洞万能”“指定洞口万能”“颜色优先于形状”“本轮只看尺寸”等反直觉规则。
|
||||
4. 玩家通过连续试探和反馈理解规则,形成短局重复挑战。
|
||||
|
||||
## 3.3 与现有玩法的区别
|
||||
|
||||
1. 不等同于拼图:不切图、不交换、不合并拼块。
|
||||
2. 不等同于抓大鹅:不做三消备选栏,不做堆叠遮挡点击。
|
||||
3. 不等同于大鱼吃小鱼:不做摇杆移动、吞噬、成长等级。
|
||||
4. 不复用 RPG 的世界、角色、章节或剧情推进结构。
|
||||
5. 运行态只保留“当前形状 + 洞口选择 + 后端裁决”,不在前端写正式规则真相。
|
||||
|
||||
---
|
||||
|
||||
## 4. 完整闭环目标
|
||||
|
||||
本次完整闭环必须补齐:
|
||||
|
||||
1. 平台创作入口选择“方洞挑战”。
|
||||
2. Agent 对话收集玩法锚点。
|
||||
3. 生成方洞挑战草稿。
|
||||
4. 进入结果页编辑作品名、标签、封面、形状数量、反差规则和视觉主题。
|
||||
5. 支持发布前试玩。
|
||||
6. 发布作品。
|
||||
7. 玩家从作品详情或广场进入运行态。
|
||||
8. 后端初始化单局形状队列、洞口兼容规则和计分状态。
|
||||
9. 玩家拖拽或点击形状投入洞口。
|
||||
10. 后端裁决投入结果、连击、扣时、失败、胜利和成绩。
|
||||
11. 形状贴图、封面图和背景图必须由后端图片生成接口生成或由创作者上传,前端只保存和展示 URL / data URL。
|
||||
12. 前端只渲染后端快照与即时反馈,不承接正式规则真相。
|
||||
|
||||
---
|
||||
|
||||
## 5. 创作锚点设计
|
||||
|
||||
Agent 型创作版本至少收集下面 5 个锚点:
|
||||
|
||||
| 锚点 | 字段建议 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| 主题外观 | `themePrompt` | 决定玩具、洞板、背景、形状材质和色彩风格。 |
|
||||
| 反差规则 | `twistRule` | 决定“为什么不是按形状匹配”的真实判定规则。 |
|
||||
| 洞口组 | `holeSet` | 决定本局出现的洞口种类、数量、位置和视觉误导强度。 |
|
||||
| 形状队列 | `shapeSequence` | 决定连续出现的形状、颜色、大小和难度递增。 |
|
||||
| 反馈节奏 | `feedbackRhythm` | 决定成功、错误、连击、惊讶和结算演出风格。 |
|
||||
|
||||
Agent 需要把玩家一句灵感收束为上述锚点,不允许逐项盘问低价值字段。
|
||||
|
||||
## 5.1 Agent AI 生成契约
|
||||
|
||||
方洞挑战的创作对话必须接入 `api-server` 的现有 LLM 能力,不能把用户输入解析成固定模板后直接写回会话。工程实现以 `state.llm_client()` 为唯一模型入口,通过方洞专属 Agent turn 生成回复与下一轮配置。
|
||||
|
||||
单轮模型输出必须是严格 JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"replyText": "",
|
||||
"progressPercent": 0,
|
||||
"nextConfig": {
|
||||
"themeText": "",
|
||||
"twistRule": "",
|
||||
"shapeCount": 12,
|
||||
"difficulty": 4,
|
||||
"shapeOptions": [
|
||||
{
|
||||
"shapeKind": "circle",
|
||||
"label": "圆形",
|
||||
"imagePrompt": "一个圆形办公室印章贴纸"
|
||||
}
|
||||
],
|
||||
"holeOptions": [
|
||||
{
|
||||
"holeId": "square-hole",
|
||||
"holeKind": "square",
|
||||
"label": "方洞",
|
||||
"bonus": true
|
||||
}
|
||||
],
|
||||
"backgroundPrompt": "办公室纸箱玩具桌面背景"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
落地约束:
|
||||
|
||||
1. `replyText` 是直接展示给百梦主的中文回复,不得出现 JSON、字段名、内部结构等说明。
|
||||
2. `nextConfig` 必须是完整配置,不允许只输出 patch;缺失字段只能由后端用当前会话配置兜底。
|
||||
3. `shapeCount` 由后端限制在 `6` 到 `24`,`difficulty` 限制在 `1` 到 `10`。
|
||||
4. `quickFillRequested=true` 时,模型应直接补齐剩余配置,后端把 `progressPercent` 固定为 `100`。
|
||||
5. 模型不可用或结果无法解析时,接口返回明确错误,不允许用确定性模板伪装成 AI 回复。
|
||||
6. 非流式消息接口和 SSE 流式消息接口都必须走同一套方洞 Agent turn,SSE 只额外负责把 `replyText` 增量回传。
|
||||
7. `shapeOptions` 至少包含 `6` 个候选项;缺失时后端用当前题材生成默认候选项。
|
||||
8. `holeOptions` 至少包含 `3` 个选项,最多 `6` 个选项;创作者可以自定义 label、洞口类型与是否为加分选项。
|
||||
9. `bonus=true` 只表示“该选项被后端判定为正确时额外加 50 分”,不是公开提示;运行态 UI 不允许直接显示哪个选项是加分选项。
|
||||
10. `backgroundPrompt` 用于生成运行态背景图;为空时后端用题材和反差规则拼出默认提示词。
|
||||
|
||||
---
|
||||
|
||||
## 6. 运行规则设计
|
||||
|
||||
## 6.1 单局结构
|
||||
|
||||
1. 单局默认 `60` 秒。
|
||||
2. 每局默认 `12` 个形状。
|
||||
3. 洞口数量默认 `4` 到 `6` 个。
|
||||
4. 玩家每次只能操作当前形状。
|
||||
5. 正确投入后进入下一个形状。
|
||||
6. 错误投入扣除时间或清空连击。
|
||||
7. 全部形状完成即胜利。
|
||||
8. 时间归零即失败。
|
||||
|
||||
## 6.2 真实兼容规则
|
||||
|
||||
首版可支持下面几类规则:
|
||||
|
||||
1. `shape_match`:形状轮廓匹配。
|
||||
2. `square_hole_priority`:方洞兼容所有形状,其他洞口只作为误导。
|
||||
3. `color_match`:颜色优先于形状。
|
||||
4. `size_match`:尺寸优先于形状。
|
||||
5. `round_prompt`:本轮按后端给出的短提示规则判定。
|
||||
|
||||
其中 `square_hole_priority` 是参考视频核心反差的首选默认规则。
|
||||
|
||||
## 6.3 计分规则
|
||||
|
||||
首版计分由 `module-square-hole` 统一裁决:
|
||||
|
||||
1. 正确投入基础得分:`100 + 当前连击数 * 10`。
|
||||
2. 如果本次投入的洞口是创作者配置的 `bonus=true` 选项,并且本次投入被判定为正确,额外加 `50` 分。
|
||||
3. 错误投入不扣分,但连击清零。
|
||||
4. 时间到、主动退出或本局结束不追加结算奖励。
|
||||
5. 前端只展示后端返回的 `score / combo / bestCombo`,不自行计算分数。
|
||||
|
||||
## 6.4 图片资产规则
|
||||
|
||||
1. 草稿生成后必须进入生成进度页,后端按草稿配置生成:
|
||||
- 封面图 `coverImageSrc`
|
||||
- 背景图 `backgroundImageSrc`
|
||||
- 每个 `shapeOptions[].imageSrc`
|
||||
2. 创作者上传入口保留;上传图片可以覆盖生成图片。
|
||||
3. 图片生成失败时保留草稿和可编辑配置,结果页展示缺失槽位,允许创作者重试生成或上传替代图。
|
||||
4. 结果页必须展示每个形状选项及其图片、背景图、封面图和洞口选项配置。
|
||||
5. 每个图片槽位点击后进入查看模式;查看模式内同时提供历史图片、上传图片和 AI 生成图片入口。
|
||||
6. 查看模式里的 AI 生成只能重生成当前槽位,不触发整份草稿重新编译,也不切换到草稿生成进度页。
|
||||
7. 洞口图使用独立历史素材类型 `square_hole_hole_image`,需要和封面、背景、形状图一样支持历史图片选择。
|
||||
8. 运行态当前形状优先显示 `imageSrc`,没有图片时才回退到 CSS 形状。
|
||||
9. 运行态背景优先显示 `backgroundImageSrc`,没有图片时才回退到默认渐变。
|
||||
10. 运行态顶部不显示“方洞是唯一解”或等价真实规则提示;只保留时间、进度、分数和连击。
|
||||
|
||||
## 6.5 前端表现
|
||||
|
||||
1. 竖屏优先,桌面端居中显示游戏台。
|
||||
2. 当前形状位于屏幕下半区域,洞板位于上半区域。
|
||||
3. 只显示必要状态:剩余时间、连击、当前进度。
|
||||
4. 不默认展示长篇规则说明。
|
||||
5. 错误反馈用短促动画、颜色闪烁和轻量文字状态,不堆解释。
|
||||
6. 点击按钮弹出的配置或结算必须使用独立面板,不在当前面板下方展开。
|
||||
7. 运行态必须同时支持拖拽和点击投放:拖动当前选项到洞口松开即提交该洞口,点击当前选项后点击洞口也提交该洞口;直接点击洞口时若当前局可操作,也提交该洞口。
|
||||
8. `difficulty` 当前不参与运行态裁决、计时、计分或队列生成;前端不应把它作为显性玩法调参展示,后端仅保留兼容字段。
|
||||
|
||||
---
|
||||
|
||||
## 7. 后端分层边界
|
||||
|
||||
完整实现时必须遵循当前 `server-rs + Axum + SpacetimeDB` 路线:
|
||||
|
||||
1. `server-rs/crates/module-square-hole`
|
||||
- 纯领域规则、形状队列生成、兼容性裁决、分数计算。
|
||||
- 不依赖 Axum、SpacetimeDB、OSS 或 LLM。
|
||||
2. `server-rs/crates/shared-contracts`
|
||||
- 暴露 Agent、作品、运行态 DTO。
|
||||
3. `server-rs/crates/spacetime-module`
|
||||
- 存储 session、message、work profile、runtime run。
|
||||
- 表结构变化必须同步 `migration.rs` 与表目录。
|
||||
4. `server-rs/crates/spacetime-client`
|
||||
- 提供 api-server 调用 SpacetimeDB 的 typed facade。
|
||||
5. `server-rs/crates/api-server`
|
||||
- 暴露 `/api/creation/square-hole/*` 与 `/api/runtime/square-hole/*`。
|
||||
- 处理鉴权、错误 envelope、LLM turn 和 HTTP facade。
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据模型
|
||||
|
||||
## 8.1 创作 session
|
||||
|
||||
1. `sessionId`
|
||||
2. `ownerUserId`
|
||||
3. `currentTurn`
|
||||
4. `progressPercent`
|
||||
5. `stage`
|
||||
6. `config`
|
||||
7. `draft`
|
||||
8. `messages`
|
||||
9. `lastAssistantReply`
|
||||
10. `publishedProfileId`
|
||||
|
||||
## 8.2 结果页 work profile
|
||||
|
||||
1. `workId`
|
||||
2. `profileId`
|
||||
3. `ownerUserId`
|
||||
4. `sourceSessionId`
|
||||
5. `gameName`
|
||||
6. `themeText`
|
||||
7. `twistRule`
|
||||
8. `summary`
|
||||
9. `tags`
|
||||
10. `coverImageSrc`
|
||||
11. `shapeCount`
|
||||
12. `difficulty`
|
||||
13. `shapeOptions`
|
||||
14. `holeOptions`
|
||||
15. `backgroundImageSrc`
|
||||
16. `publicationStatus`
|
||||
17. `playCount`
|
||||
18. `updatedAt`
|
||||
19. `publishedAt`
|
||||
|
||||
## 8.3 运行态 run snapshot
|
||||
|
||||
1. `runId`
|
||||
2. `profileId`
|
||||
3. `ownerUserId`
|
||||
4. `status`
|
||||
5. `snapshotVersion`
|
||||
6. `startedAtMs`
|
||||
7. `durationLimitMs`
|
||||
8. `remainingMs`
|
||||
9. `totalShapeCount`
|
||||
10. `completedShapeCount`
|
||||
11. `combo`
|
||||
12. `bestCombo`
|
||||
13. `score`
|
||||
14. `ruleLabel`
|
||||
15. `backgroundImageSrc`
|
||||
16. `currentShape`
|
||||
17. `holes`
|
||||
18. `lastFeedback`
|
||||
|
||||
运行态 `ruleLabel` 仅保留后端调试兼容字段,前端默认不展示。
|
||||
|
||||
---
|
||||
|
||||
## 9. API 设计
|
||||
|
||||
## 9.1 创作接口
|
||||
|
||||
1. `POST /api/creation/square-hole/sessions`
|
||||
2. `GET /api/creation/square-hole/sessions/{sessionId}`
|
||||
3. `POST /api/creation/square-hole/sessions/{sessionId}/messages`
|
||||
4. `POST /api/creation/square-hole/sessions/{sessionId}/messages/stream`
|
||||
5. `POST /api/creation/square-hole/sessions/{sessionId}/actions`
|
||||
6. `POST /api/creation/square-hole/sessions/{sessionId}/compile`
|
||||
7. `GET /api/creation/square-hole/works`
|
||||
8. `GET /api/creation/square-hole/works/{profileId}`
|
||||
9. `PUT /api/creation/square-hole/works/{profileId}`
|
||||
10. `POST /api/creation/square-hole/works/{profileId}/publish`
|
||||
11. `DELETE /api/creation/square-hole/works/{profileId}`
|
||||
|
||||
## 9.2 运行接口
|
||||
|
||||
1. `GET /api/runtime/square-hole/gallery`
|
||||
2. `GET /api/runtime/square-hole/gallery/{profileId}`
|
||||
3. `POST /api/runtime/square-hole/works/{profileId}/runs`
|
||||
4. `GET /api/runtime/square-hole/runs/{runId}`
|
||||
5. `POST /api/runtime/square-hole/runs/{runId}/drop`
|
||||
6. `POST /api/runtime/square-hole/runs/{runId}/stop`
|
||||
7. `POST /api/runtime/square-hole/runs/{runId}/restart`
|
||||
8. `POST /api/runtime/square-hole/runs/{runId}/time-up`
|
||||
|
||||
---
|
||||
|
||||
## 10. 验收标准
|
||||
|
||||
1. `src/config/newWorkEntryConfig.ts` 中存在 `square-hole` 类型且开放创建。
|
||||
2. 新建作品入口和创作类型弹层能展示“方洞挑战”。
|
||||
3. 能进入 `square-hole` Agent 工作台。
|
||||
4. 能生成草稿并进入结果页。
|
||||
5. 能编辑结果页并保存、发布。
|
||||
6. 能从作品详情或广场进入运行态。
|
||||
7. 能点击或拖拽当前形状投入洞口。
|
||||
8. 后端裁决命中规则、连击、失败和胜利。
|
||||
9. 刷新后可恢复作品与运行态快照。
|
||||
10. `docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md` 记录该入口开放状态。
|
||||
11. 后端改动完成后必须执行 `npm run api-server`,以 `GET /healthz` 返回 `200` 作为主云配置启动 smoke 通过标准,并在 smoke 后清理本次启动进程。
|
||||
@@ -0,0 +1,826 @@
|
||||
# AI 原生幸存者类游戏模板 PRD
|
||||
|
||||
更新时间:`2026-05-05`
|
||||
|
||||
## 0. 文档目的
|
||||
|
||||
这份 PRD 用于在当前平台内新增一条“幸存者类游戏模板”产品链路,冻结它从创作入口、Agent 共创、结果页、试玩、发布到运行态结算的完整边界。
|
||||
|
||||
本模板参考的是幸存者 / 割草 / 轻度 Roguelite 类玩法的通用结构:玩家在俯视角战场中移动、生存、自动攻击、拾取经验、选择升级,并在持续敌潮中坚持到胜利时间或击败终局首领。
|
||||
|
||||
本次不是复制某个具体商业游戏,也不是新增一个孤立前端小游戏。正式实现必须接入 Genarrative 现有平台创作中心、作品架、广场、资产、钱包、埋点和 `server-rs + Axum + SpacetimeDB` 后端基线。
|
||||
|
||||
---
|
||||
|
||||
## 1. 一句话定义
|
||||
|
||||
`survivor` 是一个 AI 原生幸存者类游戏模板:百梦主通过 Agent 设定主角幻想、敌潮母题、武器技能、成长流派和战场节奏,系统编译出一个移动端优先的俯视角割草生存作品;玩家通过移动躲避敌潮,武器自动攻击,拾取经验升级,在若干分钟内完成生存挑战、击败首领或倒下结算。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前接入级别
|
||||
|
||||
根据新增玩法类型接入分级,本模板目标为 **完整玩法闭环**:
|
||||
|
||||
1. 新增玩法 ID:`survivor`。
|
||||
2. 对外模板名称:`幸存者挑战`。
|
||||
3. 对外子标题:`割草成长玩法`。
|
||||
4. 支持 Agent 创作、草稿生成、结果页编辑、试玩、发布和公开运行。
|
||||
5. 支持作品架恢复草稿和已发布作品二次编辑。
|
||||
6. 支持玩家从作品详情或广场进入运行态。
|
||||
7. 支持后端权威生成局面、升级候选、波次配置、结算和成绩基础数据。
|
||||
8. 前端负责高频模拟与表现,但不得自行发明正式规则、波次、掉落、升级池或结算真相。
|
||||
|
||||
---
|
||||
|
||||
## 3. 产品定位
|
||||
|
||||
## 3.1 命名
|
||||
|
||||
1. 对外模板名称:`幸存者挑战`。
|
||||
2. 对外子标题:`割草成长玩法`。
|
||||
3. 开发代号:`Survivor`。
|
||||
4. 工程玩法域:`survivor`。
|
||||
5. 后端模块命名预期:`survivor`。
|
||||
|
||||
## 3.2 核心乐趣
|
||||
|
||||
1. 玩家被敌潮包围,通过走位制造空间。
|
||||
2. 武器自动攻击,玩家专注移动、拾取、选择升级。
|
||||
3. 每次升级出现少量高价值选择,形成局内构筑。
|
||||
4. 敌人数量、速度、血量和特殊能力逐步加压。
|
||||
5. 后期屏幕内形成高密度清场反馈,玩家获得成长压倒敌潮的爽感。
|
||||
|
||||
## 3.3 与现有玩法的区别
|
||||
|
||||
1. 不等同于 RPG:不依赖世界章节、NPC 对话或剧情推进。
|
||||
2. 不等同于大鱼吃小鱼:不做吞噬收编和三合一实体升级。
|
||||
3. 不等同于抓大鹅:不做备选栏三消和堆叠点击。
|
||||
4. 不等同于拼图:不切图、不交换、不合并拼块。
|
||||
5. 不新增通用动作游戏引擎,本期只服务幸存者类模板。
|
||||
|
||||
---
|
||||
|
||||
## 4. 完整闭环目标
|
||||
|
||||
本模板首版必须补齐:
|
||||
|
||||
1. 平台创作入口展示“幸存者挑战”。
|
||||
2. Agent 对话收集玩法锚点。
|
||||
3. 后端编译幸存者玩法草稿。
|
||||
4. 结果页编辑作品名、封面、标签、主角、敌人、武器、成长和战场配置。
|
||||
5. 结果页支持生成或替换关键视觉资产。
|
||||
6. 结果页支持发布前试玩。
|
||||
7. 作品可保存、发布、删除和二次编辑。
|
||||
8. 玩家从作品详情或广场创建 run。
|
||||
9. 后端下发权威 run seed、波次、敌人、武器、升级候选和结算规则。
|
||||
10. 前端按权威配置执行高频运行表现,定期提交 checkpoint。
|
||||
11. 玩家升级时从后端确认的候选卡中选择一张。
|
||||
12. 玩家死亡、主动退出、倒计时胜利或终局首领胜利后进入结算。
|
||||
13. 结算成绩以后端确认的 run snapshot 为准。
|
||||
|
||||
---
|
||||
|
||||
## 5. 明确不做
|
||||
|
||||
首版明确不做:
|
||||
|
||||
1. 不做联机、PvP、组队或排行榜赛季。
|
||||
2. 不做横屏专属设计,移动端竖屏优先。
|
||||
3. 不做复杂地形寻路、真实物理碰撞和高精度弹幕编辑器。
|
||||
4. 不做多地图章节战役,只做单局挑战。
|
||||
5. 不做局外装备养成、抽卡、永久技能树或付费强化。
|
||||
6. 不要求百梦主逐帧编辑怪物 AI、弹道曲线或数值公式。
|
||||
7. 不把玩法规则说明长文默认写进 UI 面板。
|
||||
8. 不把按钮弹出的资产、升级或参数编辑做成当前卡片下方展开内容,统一使用独立面板。
|
||||
9. 不让前端本地结算直接写入正式成绩、钱包、任务或排行榜。
|
||||
10. 不复用 `server-node`、Express、PostgreSQL 或旧 RPG runtime 作为新增实现目标。
|
||||
|
||||
---
|
||||
|
||||
## 6. 创作锚点设计
|
||||
|
||||
Agent 型创作版本至少收集下面 5 个高杠杆锚点:
|
||||
|
||||
| 锚点 | 字段建议 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| 生存幻想 | `survivalFantasy` | 决定主角身份、战斗气质和作品一句话承诺。 |
|
||||
| 敌潮母题 | `hordeTheme` | 决定敌人类型、数量感、压迫方式和视觉风格。 |
|
||||
| 武器技能 | `weaponFantasy` | 决定自动攻击、范围技能、投射物和特效方向。 |
|
||||
| 成长流派 | `buildArchetype` | 决定升级卡池、进化组合和玩家构筑路线。 |
|
||||
| 战场节奏 | `stageRhythm` | 决定单局时长、波次曲线、首领节点和胜利条件。 |
|
||||
|
||||
Agent 必须围绕用户灵感收束这些锚点,不允许把创作入口做成一屏参数问卷。
|
||||
|
||||
## 6.1 锚点状态
|
||||
|
||||
每个锚点都支持:
|
||||
|
||||
1. `待补充`
|
||||
2. `Agent 推断`
|
||||
3. `已确认`
|
||||
4. `已锁定`
|
||||
|
||||
## 6.2 收束条件
|
||||
|
||||
满足下面条件后,系统允许编译第一版草稿:
|
||||
|
||||
1. `生存幻想` 已确认或已锁定。
|
||||
2. `敌潮母题` 已确认或已锁定。
|
||||
3. `武器技能` 至少有 `3` 个可用武器草稿。
|
||||
4. `成长流派` 至少有 `6` 张升级卡草稿。
|
||||
5. `战场节奏` 已确认,或使用系统默认 `8` 分钟短局配置。
|
||||
|
||||
## 6.3 快捷补全
|
||||
|
||||
当会话至少完成 `2` 轮后,工作区提供 `补充剩余关键字` 快捷动作:
|
||||
|
||||
1. 前端发送消息接口,并携带 `quickFillRequested: true`。
|
||||
2. 用户消息固定为“请补充剩余关键字。”。
|
||||
3. 后端 Agent 根据当前锚点补齐缺失内容。
|
||||
4. 前端不得自行推断武器、波次、敌人或升级卡。
|
||||
|
||||
## 6.4 Agent AI 生成契约
|
||||
|
||||
单轮模型输出必须是严格 JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"replyText": "",
|
||||
"progressPercent": 0,
|
||||
"anchors": {
|
||||
"survivalFantasy": { "status": "pending", "summary": "" },
|
||||
"hordeTheme": { "status": "pending", "summary": "" },
|
||||
"weaponFantasy": { "status": "pending", "summary": "" },
|
||||
"buildArchetype": { "status": "pending", "summary": "" },
|
||||
"stageRhythm": { "status": "pending", "summary": "" }
|
||||
},
|
||||
"nextDraftPatch": {}
|
||||
}
|
||||
```
|
||||
|
||||
落地约束:
|
||||
|
||||
1. `replyText` 是直接展示给百梦主的中文回复,不得出现 JSON、字段名或内部协议说明。
|
||||
2. `progressPercent` 只能由后端校验后采纳,范围 `0~100`。
|
||||
3. `nextDraftPatch` 只允许写入幸存者草稿字段,不允许修改平台账号、钱包或作品归属。
|
||||
4. 模型不可用或结果无法解析时,接口返回明确错误,不用固定模板伪装 AI 回复。
|
||||
|
||||
---
|
||||
|
||||
## 7. 草稿契约
|
||||
|
||||
## 7.1 `SurvivorResultDraft`
|
||||
|
||||
```ts
|
||||
export interface SurvivorResultDraft {
|
||||
profileId: string | null;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
workTags: string[];
|
||||
coverImageSrc: string | null;
|
||||
anchors: SurvivorAnchorPack;
|
||||
hero: SurvivorHeroDraft;
|
||||
enemies: SurvivorEnemyDraft[];
|
||||
weapons: SurvivorWeaponDraft[];
|
||||
upgrades: SurvivorUpgradeDraft[];
|
||||
evolutions: SurvivorEvolutionDraft[];
|
||||
stage: SurvivorStageDraft;
|
||||
wavePlan: SurvivorWavePlanDraft;
|
||||
runtimeConfig: SurvivorRuntimeConfigDraft;
|
||||
publishReady: boolean;
|
||||
validationIssues: SurvivorValidationIssue[];
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
## 7.2 主角草稿
|
||||
|
||||
```ts
|
||||
export interface SurvivorHeroDraft {
|
||||
heroId: string;
|
||||
name: string;
|
||||
fantasy: string;
|
||||
moveSpeed: number;
|
||||
maxHp: number;
|
||||
defense: number;
|
||||
pickupRadius: number;
|
||||
spriteAssetId: string | null;
|
||||
portraitAssetId: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
默认范围:
|
||||
|
||||
1. `moveSpeed`:`80~180`,默认 `120`。
|
||||
2. `maxHp`:`50~300`,默认 `100`。
|
||||
3. `defense`:`0~50`,默认 `0`。
|
||||
4. `pickupRadius`:`30~160`,默认 `60`。
|
||||
|
||||
## 7.3 敌人草稿
|
||||
|
||||
```ts
|
||||
export interface SurvivorEnemyDraft {
|
||||
enemyId: string;
|
||||
name: string;
|
||||
role: 'fodder' | 'runner' | 'tank' | 'ranged' | 'elite' | 'boss';
|
||||
visualPrompt: string;
|
||||
maxHp: number;
|
||||
damage: number;
|
||||
moveSpeed: number;
|
||||
xpValue: number;
|
||||
spawnWeight: number;
|
||||
spriteAssetId: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
首版发布至少需要:
|
||||
|
||||
1. `2` 个 `fodder` 或 `runner` 敌人。
|
||||
2. `1` 个 `elite` 敌人。
|
||||
3. `1` 个 `boss` 敌人。
|
||||
|
||||
## 7.4 武器草稿
|
||||
|
||||
```ts
|
||||
export interface SurvivorWeaponDraft {
|
||||
weaponId: string;
|
||||
name: string;
|
||||
attackKind: 'projectile' | 'orbit' | 'area' | 'beam' | 'melee_aura';
|
||||
targeting: 'nearest' | 'random_enemy' | 'forward' | 'around_hero';
|
||||
baseDamage: number;
|
||||
cooldownMs: number;
|
||||
projectileCount: number;
|
||||
range: number;
|
||||
effectAssetId: string | null;
|
||||
upgradeTags: string[];
|
||||
}
|
||||
```
|
||||
|
||||
首版必须至少有 `3` 个武器。玩家开局默认持有第一个武器。
|
||||
|
||||
## 7.5 升级卡草稿
|
||||
|
||||
```ts
|
||||
export interface SurvivorUpgradeDraft {
|
||||
upgradeId: string;
|
||||
name: string;
|
||||
rarity: 'common' | 'rare' | 'epic';
|
||||
targetKind: 'hero' | 'weapon' | 'global';
|
||||
targetId: string | null;
|
||||
effectKind:
|
||||
| 'damage_percent'
|
||||
| 'cooldown_percent'
|
||||
| 'projectile_count'
|
||||
| 'move_speed_percent'
|
||||
| 'max_hp_flat'
|
||||
| 'pickup_radius_percent'
|
||||
| 'regen_flat';
|
||||
value: number;
|
||||
maxStack: number;
|
||||
iconAssetId: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
首版至少需要 `6` 张升级卡,并保证任意升级节点能从可用池中抽出 `3` 张候选。
|
||||
|
||||
## 7.6 进化组合
|
||||
|
||||
```ts
|
||||
export interface SurvivorEvolutionDraft {
|
||||
evolutionId: string;
|
||||
name: string;
|
||||
baseWeaponId: string;
|
||||
requiredUpgradeIds: string[];
|
||||
resultWeapon: SurvivorWeaponDraft;
|
||||
}
|
||||
```
|
||||
|
||||
进化组合首版可选,但字段必须预留。若启用进化,结果页必须展示组合条件,不在运行态长文解释。
|
||||
|
||||
## 7.7 战场草稿
|
||||
|
||||
```ts
|
||||
export interface SurvivorStageDraft {
|
||||
stageId: string;
|
||||
name: string;
|
||||
theme: string;
|
||||
backgroundAssetId: string | null;
|
||||
groundTextureAssetId: string | null;
|
||||
safeAreaShape: 'rectangle' | 'circle';
|
||||
width: number;
|
||||
height: number;
|
||||
obstacleDensity: number;
|
||||
}
|
||||
```
|
||||
|
||||
首版默认不生成实心障碍,`obstacleDensity` 固定为 `0`。后续要开放障碍,必须先补充寻路与碰撞方案。
|
||||
|
||||
## 7.8 波次草稿
|
||||
|
||||
```ts
|
||||
export interface SurvivorWavePlanDraft {
|
||||
durationSeconds: number;
|
||||
waves: SurvivorWaveDraft[];
|
||||
bossWave: SurvivorBossWaveDraft;
|
||||
}
|
||||
|
||||
export interface SurvivorWaveDraft {
|
||||
startSecond: number;
|
||||
endSecond: number;
|
||||
enemyPool: string[];
|
||||
spawnRatePerSecond: number;
|
||||
maxAlive: number;
|
||||
statMultiplier: number;
|
||||
}
|
||||
```
|
||||
|
||||
默认短局:
|
||||
|
||||
1. `durationSeconds`:`480`。
|
||||
2. 每 `60` 秒一个波次。
|
||||
3. `240` 秒出现精英压力波。
|
||||
4. `480` 秒出现终局首领或进入胜利结算。
|
||||
|
||||
---
|
||||
|
||||
## 8. 运行规则设计
|
||||
|
||||
## 8.1 单局结构
|
||||
|
||||
首版标准局为 `8` 分钟短局:
|
||||
|
||||
1. 玩家开局拥有 `1` 个基础武器。
|
||||
2. 玩家通过虚拟摇杆或键盘移动。
|
||||
3. 武器按冷却自动攻击。
|
||||
4. 敌人从屏幕外安全环刷出并向玩家移动。
|
||||
5. 敌人碰撞玩家造成伤害。
|
||||
6. 敌人死亡掉落经验晶体。
|
||||
7. 玩家拾取经验后升级。
|
||||
8. 升级时暂停运行态,展示 `3` 张升级卡。
|
||||
9. 玩家选择一张升级卡后继续运行。
|
||||
10. 玩家生命归零失败。
|
||||
11. 生存到终局时间或击败终局首领胜利。
|
||||
|
||||
## 8.2 移动与输入
|
||||
|
||||
1. 移动端使用左下角虚拟摇杆。
|
||||
2. 桌面端支持 `WASD` / 方向键移动。
|
||||
3. 首版不要求手动瞄准和手动开火。
|
||||
4. 前端必须保证摇杆、血量、经验条、升级面板和结算面板在竖屏下不遮挡核心战场。
|
||||
|
||||
## 8.3 自动攻击
|
||||
|
||||
1. 每个武器独立维护冷却。
|
||||
2. 目标选择策略由后端配置下发。
|
||||
3. 前端可本地执行攻击表现与命中模拟,但必须使用后端下发的武器参数和 RNG seed。
|
||||
4. 武器伤害、冷却、投射数量和范围变化来自后端确认的升级结果。
|
||||
|
||||
## 8.4 敌潮生成
|
||||
|
||||
1. 敌人只能从玩家当前视野外的刷怪环生成。
|
||||
2. 刷怪位置不得直接贴脸。
|
||||
3. 每个波次有 `spawnRatePerSecond` 和 `maxAlive` 上限。
|
||||
4. 前端不得自行增加敌人类型、刷新率或精英节点。
|
||||
5. 后端 checkpoint 校验时应能根据 seed、波次和输入摘要重放关键统计。
|
||||
|
||||
## 8.5 经验与升级
|
||||
|
||||
1. 敌人死亡生成经验晶体。
|
||||
2. 玩家碰到晶体或晶体进入拾取半径时获得经验。
|
||||
3. 经验达到当前等级阈值时触发升级。
|
||||
4. 升级候选由后端按当前 build、权重、稀有度和 seed 生成。
|
||||
5. 前端只能展示后端返回的候选卡。
|
||||
6. 玩家选择后,后端更新 run build snapshot。
|
||||
|
||||
## 8.6 失败与胜利
|
||||
|
||||
失败条件:
|
||||
|
||||
1. `heroHp <= 0`。
|
||||
2. 玩家主动退出并确认放弃。
|
||||
3. 后端判定 run 校验失败。
|
||||
|
||||
胜利条件:
|
||||
|
||||
1. 生存到 `durationSeconds` 并完成终局结算。
|
||||
2. 或在终局波次击败 `boss`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 权威状态与高频模拟边界
|
||||
|
||||
幸存者类玩法需要高频碰撞和大量实体,不能把每一帧都交给 HTTP 往返。首版采用“后端权威配置 + 前端确定性运行表现 + 后端 checkpoint / 结算确认”的边界。
|
||||
|
||||
## 9.1 后端职责
|
||||
|
||||
后端负责:
|
||||
|
||||
1. 创建创作会话和玩法草稿。
|
||||
2. 校验和保存作品 profile。
|
||||
3. 编译 run seed、波次、敌人、武器、升级池和结算规则。
|
||||
4. 生成升级候选。
|
||||
5. 确认升级选择。
|
||||
6. 接收运行 checkpoint。
|
||||
7. 校验关键统计是否在合理范围内。
|
||||
8. 确认死亡、胜利、退出和最终成绩。
|
||||
9. 写入作品游玩次数、成绩、埋点和任务事件。
|
||||
|
||||
## 9.2 前端职责
|
||||
|
||||
前端负责:
|
||||
|
||||
1. 展示 Agent 工作台和结果页。
|
||||
2. 渲染战场、主角、敌人、投射物、特效、经验晶体和 HUD。
|
||||
3. 基于后端 run config 执行确定性高频模拟。
|
||||
4. 采集玩家移动输入。
|
||||
5. 展示攻击、受击、拾取、升级、胜利和失败反馈。
|
||||
6. 定期提交 checkpoint。
|
||||
7. 在后端拒绝 checkpoint 或结算时,展示同步失败并结束本局。
|
||||
|
||||
## 9.3 Checkpoint 规则
|
||||
|
||||
前端每 `5` 秒或关键节点提交 checkpoint:
|
||||
|
||||
```ts
|
||||
export interface SurvivorRunCheckpointRequest {
|
||||
runId: string;
|
||||
snapshotVersion: number;
|
||||
elapsedMs: number;
|
||||
inputDigest: string;
|
||||
rngStep: number;
|
||||
heroHp: number;
|
||||
heroLevel: number;
|
||||
killCount: number;
|
||||
eliteKillCount: number;
|
||||
bossKillCount: number;
|
||||
xpCollected: number;
|
||||
selectedUpgradeIds: string[];
|
||||
checksum: string;
|
||||
}
|
||||
```
|
||||
|
||||
关键节点包括:
|
||||
|
||||
1. 升级触发前。
|
||||
2. 选择升级后。
|
||||
3. 精英死亡。
|
||||
4. Boss 出现。
|
||||
5. Boss 死亡。
|
||||
6. 玩家死亡。
|
||||
7. 胜利结算。
|
||||
|
||||
---
|
||||
|
||||
## 10. 结果页设计
|
||||
|
||||
## 10.1 结果页职责
|
||||
|
||||
幸存者结果页是最小可编辑工作台,不是只读总结页。
|
||||
|
||||
结果页至少包含:
|
||||
|
||||
1. 作品基础信息。
|
||||
2. 主角配置。
|
||||
3. 敌人图鉴。
|
||||
4. 武器与升级卡池。
|
||||
5. 战场与波次。
|
||||
6. 资产状态。
|
||||
7. 试玩入口。
|
||||
8. 发布校验。
|
||||
|
||||
## 10.2 移动端优先布局
|
||||
|
||||
1. 首屏显示作品标题、封面、发布状态和试玩按钮。
|
||||
2. 主角、敌人、武器、战场使用分组入口。
|
||||
3. 点击分组入口打开独立编辑面板。
|
||||
4. 不在卡片下方展开长规则说明。
|
||||
5. 发布校验只展示短状态和可处理项,不展示底层协议。
|
||||
|
||||
## 10.3 资产要求
|
||||
|
||||
正式发布前至少需要:
|
||||
|
||||
1. 作品封面。
|
||||
2. 主角头像或主角小人资产。
|
||||
3. `3` 类普通敌人资产。
|
||||
4. `1` 类精英或首领资产。
|
||||
5. `3` 个武器或技能图标。
|
||||
6. `1` 张战场背景或地面纹理。
|
||||
|
||||
程序化色块或几何占位只允许用于开发调试和未发布草稿,不允许作为正式发布作品的默认资产。
|
||||
|
||||
## 10.4 结果页核心操作
|
||||
|
||||
结果页支持:
|
||||
|
||||
1. `生成主角资产`
|
||||
2. `生成敌人资产`
|
||||
3. `生成武器图标`
|
||||
4. `生成战场背景`
|
||||
5. `重新编译波次`
|
||||
6. `试玩`
|
||||
7. `保存草稿`
|
||||
8. `发布`
|
||||
9. `返回创作对话`
|
||||
|
||||
---
|
||||
|
||||
## 11. API 设计
|
||||
|
||||
## 11.1 创作接口
|
||||
|
||||
1. `POST /api/creation/survivor/sessions`
|
||||
2. `GET /api/creation/survivor/sessions/{sessionId}`
|
||||
3. `POST /api/creation/survivor/sessions/{sessionId}/messages`
|
||||
4. `POST /api/creation/survivor/sessions/{sessionId}/messages/stream`
|
||||
5. `POST /api/creation/survivor/sessions/{sessionId}/actions`
|
||||
6. `POST /api/creation/survivor/sessions/{sessionId}/compile`
|
||||
7. `GET /api/creation/survivor/works`
|
||||
8. `GET /api/creation/survivor/works/{profileId}`
|
||||
9. `PUT /api/creation/survivor/works/{profileId}`
|
||||
10. `POST /api/creation/survivor/works/{profileId}/publish`
|
||||
11. `DELETE /api/creation/survivor/works/{profileId}`
|
||||
|
||||
## 11.2 运行接口
|
||||
|
||||
1. `GET /api/runtime/survivor/gallery`
|
||||
2. `GET /api/runtime/survivor/gallery/{profileId}`
|
||||
3. `POST /api/runtime/survivor/works/{profileId}/runs`
|
||||
4. `GET /api/runtime/survivor/runs/{runId}`
|
||||
5. `POST /api/runtime/survivor/runs/{runId}/checkpoint`
|
||||
6. `POST /api/runtime/survivor/runs/{runId}/upgrade-options`
|
||||
7. `POST /api/runtime/survivor/runs/{runId}/choose-upgrade`
|
||||
8. `POST /api/runtime/survivor/runs/{runId}/settle`
|
||||
9. `POST /api/runtime/survivor/runs/{runId}/stop`
|
||||
10. `POST /api/runtime/survivor/runs/{runId}/restart`
|
||||
|
||||
## 11.3 SSE 事件
|
||||
|
||||
创作对话流式事件复用平台 Agent envelope。运行态首版不要求全程 SSE;如后续加入服务端实时推送,只允许推送权威 milestone,不传每帧实体状态。
|
||||
|
||||
---
|
||||
|
||||
## 12. 后端分层边界
|
||||
|
||||
完整实现时必须遵循当前后端路线:
|
||||
|
||||
1. `server-rs/crates/module-survivor`
|
||||
- 纯领域规则、草稿校验、波次编译、升级池、评分和 checkpoint 校验。
|
||||
- 不依赖 Axum、SpacetimeDB、OSS 或 LLM。
|
||||
2. `server-rs/crates/shared-contracts`
|
||||
- 暴露 Survivor Agent、作品、运行态 DTO。
|
||||
3. `server-rs/crates/spacetime-module`
|
||||
- 存储 session、message、work profile、runtime run 和 runtime event。
|
||||
- 表结构变化必须同步 `migration.rs` 与表目录。
|
||||
4. `server-rs/crates/spacetime-client`
|
||||
- 提供 api-server 调用 SpacetimeDB 的 typed facade。
|
||||
5. `server-rs/crates/api-server`
|
||||
- 暴露 `/api/creation/survivor/*` 与 `/api/runtime/survivor/*`。
|
||||
- 处理鉴权、错误 envelope、LLM turn、资产生成和 HTTP facade。
|
||||
6. `platform-agent` / `platform-llm`
|
||||
- 承接 Agent 对话与草稿生成。
|
||||
7. `platform-oss`
|
||||
- 承接封面、角色、敌人、武器图标和战场背景资产。
|
||||
|
||||
---
|
||||
|
||||
## 13. SpacetimeDB 表建议
|
||||
|
||||
首版建议新增:
|
||||
|
||||
1. `survivor_agent_session`
|
||||
- 创作会话主表,保存 owner、stage、anchors、draft、progress、published_profile_id。
|
||||
2. `survivor_agent_message`
|
||||
- 创作消息流水。
|
||||
3. `survivor_work_profile`
|
||||
- 作品 profile,保存发布态草稿、封面、标签、作者和公开状态。
|
||||
4. `survivor_runtime_run`
|
||||
- 运行态 run,保存 run config、seed、当前 snapshot、checkpoint 摘要和结算状态。
|
||||
5. `survivor_runtime_event`
|
||||
- 运行审计事件,记录 start、checkpoint、upgrade、death、victory、settle 等关键事件。
|
||||
|
||||
首版不新增正式排行榜表。若后续加入排行榜,再新增 `survivor_leaderboard_entry` 并补充反作弊和赛季边界。
|
||||
|
||||
---
|
||||
|
||||
## 14. 作品结构建议
|
||||
|
||||
```ts
|
||||
export interface SurvivorWorkProfile {
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
sourceSessionId: string | null;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
workTags: string[];
|
||||
coverImageSrc: string | null;
|
||||
publicationStatus: 'draft' | 'published' | 'archived';
|
||||
draft: SurvivorResultDraft;
|
||||
playCount: number;
|
||||
updatedAt: string;
|
||||
publishedAt: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
## 14.1 发布校验
|
||||
|
||||
发布必须满足:
|
||||
|
||||
1. `workTitle` 非空。
|
||||
2. 至少 `1` 个标签。
|
||||
3. 有封面图。
|
||||
4. 主角配置完整。
|
||||
5. 至少 `4` 个敌人,其中包含普通、精英或首领。
|
||||
6. 至少 `3` 个武器。
|
||||
7. 至少 `6` 张升级卡。
|
||||
8. 波次覆盖完整 `durationSeconds`。
|
||||
9. 资产覆盖满足正式发布要求。
|
||||
10. `runtimeConfig` 在后端数值上限内。
|
||||
|
||||
---
|
||||
|
||||
## 15. 运行态快照建议
|
||||
|
||||
```ts
|
||||
export interface SurvivorRunSnapshot {
|
||||
runId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string | null;
|
||||
status: 'running' | 'level_up_pending' | 'victory' | 'defeat' | 'stopped' | 'settlement_failed';
|
||||
snapshotVersion: number;
|
||||
seed: string;
|
||||
elapsedMs: number;
|
||||
durationLimitMs: number;
|
||||
hero: SurvivorRunHeroSnapshot;
|
||||
build: SurvivorRunBuildSnapshot;
|
||||
wave: SurvivorRunWaveSnapshot;
|
||||
stats: SurvivorRunStatsSnapshot;
|
||||
pendingUpgradeOptions: SurvivorUpgradeOption[];
|
||||
settlement: SurvivorRunSettlement | null;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
前端渲染可维护更细的局内实体列表,但正式持久化只保存 run snapshot、checkpoint 摘要和结算结果。
|
||||
|
||||
---
|
||||
|
||||
## 16. 计分与结算
|
||||
|
||||
首版结算至少展示:
|
||||
|
||||
1. 胜利 / 失败状态。
|
||||
2. 生存时间。
|
||||
3. 击杀数。
|
||||
4. 等级。
|
||||
5. 精英击杀数。
|
||||
6. 首领击杀数。
|
||||
7. 选择过的核心升级。
|
||||
8. 再来一局。
|
||||
9. 返回作品详情。
|
||||
|
||||
基础分数公式:
|
||||
|
||||
```text
|
||||
score = survivedSeconds * 10
|
||||
+ killCount * 5
|
||||
+ eliteKillCount * 120
|
||||
+ bossKillCount * 500
|
||||
+ heroLevel * 80
|
||||
```
|
||||
|
||||
结算入库以后端确认结果为准。前端展示的即时结算在后端确认前必须标记为待确认状态。
|
||||
|
||||
---
|
||||
|
||||
## 17. 平台接入
|
||||
|
||||
## 17.1 创作入口
|
||||
|
||||
需要接入:
|
||||
|
||||
1. `src/config/newWorkEntryConfig.ts`
|
||||
2. `src/components/platform-entry/platformEntryCreationTypes.ts`
|
||||
3. `src/components/platform-entry/platformEntryTypes.ts`
|
||||
4. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
|
||||
新增阶段建议:
|
||||
|
||||
1. `survivor-agent-workspace`
|
||||
2. `survivor-generating`
|
||||
3. `survivor-result`
|
||||
4. `survivor-runtime`
|
||||
5. `survivor-gallery-detail`
|
||||
|
||||
## 17.2 前端组件建议
|
||||
|
||||
1. `src/components/survivor-creation/SurvivorAgentWorkspace.tsx`
|
||||
2. `src/components/survivor-result/SurvivorResultView.tsx`
|
||||
3. `src/components/survivor-runtime/SurvivorRuntimeShell.tsx`
|
||||
4. `src/services/survivorCreationClient.ts`
|
||||
5. `src/services/survivorRuntimeClient.ts`
|
||||
6. `src/services/survivorWorksClient.ts`
|
||||
|
||||
## 17.3 作品架与广场
|
||||
|
||||
发布后必须接入:
|
||||
|
||||
1. 创作页作品架。
|
||||
2. 平台作品详情。
|
||||
3. 首页或发现页公开作品流。
|
||||
4. 公开作品试玩入口。
|
||||
5. 删除、二次编辑和重新发布。
|
||||
|
||||
---
|
||||
|
||||
## 18. UI 设计约束
|
||||
|
||||
1. 移动端竖屏优先,桌面端居中或宽屏增强。
|
||||
2. HUD 只保留血量、经验、时间、等级、击杀数和暂停入口。
|
||||
3. 升级卡使用独立面板,最多显示 `3` 张候选。
|
||||
4. 暂停、结算、资产生成、参数编辑都使用独立面板。
|
||||
5. 不默认展示规则说明长文。
|
||||
6. 运行态主画面不能被功能介绍、键位说明或装饰卡片遮挡。
|
||||
7. 触控区、摇杆、升级卡和按钮必须满足移动端可点击尺寸。
|
||||
8. 图标按钮优先使用现有图标库;无图标时再使用简短文本。
|
||||
|
||||
---
|
||||
|
||||
## 19. 里程碑拆分
|
||||
|
||||
## 19.1 Phase 1:入口与 PRD 骨架
|
||||
|
||||
1. 冻结 `survivor` 玩法 ID。
|
||||
2. 新增入口配置。
|
||||
3. 新增前端阶段枚举。
|
||||
4. 新增 shared contracts 草案。
|
||||
5. 入口可以进入占位工作台。
|
||||
|
||||
## 19.2 Phase 2:创作会话与结果页
|
||||
|
||||
1. 新增 Agent session。
|
||||
2. 新增消息与流式回复。
|
||||
3. 编译第一版 `SurvivorResultDraft`。
|
||||
4. 结果页可编辑主角、敌人、武器、升级和波次。
|
||||
5. 支持保存草稿。
|
||||
|
||||
## 19.3 Phase 3:后端运行态
|
||||
|
||||
1. `module-survivor` 实现波次、升级池、结算和 checkpoint 校验。
|
||||
2. SpacetimeDB 表、migration 和 facade 收口。
|
||||
3. api-server 暴露 runtime API。
|
||||
4. 支持创建 run、选择升级、提交 checkpoint 和结算。
|
||||
|
||||
## 19.4 Phase 4:前端运行态
|
||||
|
||||
1. 实现竖屏战场。
|
||||
2. 实现摇杆移动。
|
||||
3. 实现自动攻击、敌潮、经验和升级面板。
|
||||
4. 实现胜利、失败、暂停和结算。
|
||||
5. 接入后端 checkpoint 与升级确认。
|
||||
|
||||
## 19.5 Phase 5:发布与平台闭环
|
||||
|
||||
1. 发布作品。
|
||||
2. 接入作品架。
|
||||
3. 接入公开详情和广场。
|
||||
4. 支持二次编辑。
|
||||
5. 接入埋点、任务和基础成绩记录。
|
||||
|
||||
---
|
||||
|
||||
## 20. 验收标准
|
||||
|
||||
1. `src/config/newWorkEntryConfig.ts` 中存在 `survivor` 类型。
|
||||
2. 新建作品入口和创作类型弹层能展示“幸存者挑战”。
|
||||
3. 能进入 `survivor-agent-workspace`。
|
||||
4. Agent 能生成锚点和草稿。
|
||||
5. 能进入 `survivor-result` 并编辑基础字段。
|
||||
6. 发布校验能阻止缺少主角、敌人、武器、升级卡、波次或资产的作品发布。
|
||||
7. 能从结果页进入试玩运行态。
|
||||
8. 运行态能移动、自动攻击、刷怪、拾取经验、升级和结算。
|
||||
9. 升级候选必须来自后端返回,不由前端临时生成。
|
||||
10. 前端提交 checkpoint 后,后端能接受或拒绝并返回明确结果。
|
||||
11. 作品发布后能在作品架和公开入口恢复。
|
||||
12. 移动端竖屏下主战场、摇杆、HUD、升级卡和结算不互相遮挡。
|
||||
13. 后端实现后执行对应 DDD 验收命令,并用 `npm run api-server` 启动后端检查 `/healthz`。
|
||||
14. 修改包含中文的文档后执行 `npm run check:encoding`。
|
||||
|
||||
---
|
||||
|
||||
## 21. 后续可选增强
|
||||
|
||||
以下能力不进入首版,但需要保留扩展空间:
|
||||
|
||||
1. 多地图主题。
|
||||
2. 局外角色解锁。
|
||||
3. 进化武器动画。
|
||||
4. 精英词缀。
|
||||
5. Boss 特殊弹幕。
|
||||
6. 道具掉落。
|
||||
7. 排行榜。
|
||||
8. 赛季挑战。
|
||||
9. 更多输入模式。
|
||||
10. 服务端更严格的确定性重放校验。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1810
docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md
Normal file
1810
docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
# 宝贝识物寓教于乐模板 PRD 2026-05-11
|
||||
|
||||
## 1. 目标
|
||||
|
||||
新增寓教于乐内容线的创作模板:
|
||||
|
||||
```text
|
||||
宝贝识物
|
||||
```
|
||||
|
||||
创作者必须通过该模板创作并发布作品后,用户才能在寓教于乐板块体验对应关卡。
|
||||
|
||||
本模板只服务儿童动作 Demo 内容线,不把普通教育题材作品自动归入寓教于乐。
|
||||
|
||||
## 2. 创作输入
|
||||
|
||||
创作者必须填写两个物品名称:
|
||||
|
||||
1. 物品 A 名称;
|
||||
2. 物品 B 名称。
|
||||
|
||||
两个名称都必须去除首尾空白后非空。当前阶段不新增题材、难度、计时、失败次数、分数、体力或递增规则。
|
||||
|
||||
## 3. 生成规则
|
||||
|
||||
提交后生成一份宝贝识物草稿,草稿包含:
|
||||
|
||||
1. 模板 ID:`baby-object-match`;
|
||||
2. 模板名称:`宝贝识物`;
|
||||
3. 两个物品;
|
||||
4. 两个物品图;
|
||||
5. 作品标签。
|
||||
|
||||
物品图使用 VectorEngine `gpt-image-2-all` / image-2 生成。图片生成只能走后端或后续后端预留接口,前端不得泄露 `VECTOR_ENGINE_API_KEY`。
|
||||
|
||||
本地 Demo 阶段若真实生图接口未接入完成,允许前端 service 返回明确标记为 `placeholder` 的占位图形,用于打通创作到结果页的交互链路;该占位结果不得伪装成正式 image-2 资产。
|
||||
|
||||
## 4. 标签规则
|
||||
|
||||
发布作品必须携带精确标签:
|
||||
|
||||
```text
|
||||
寓教于乐
|
||||
```
|
||||
|
||||
标签识别只接受精确等于 `寓教于乐`。不接受 `儿童教育`、`动作教育`、`寓教于乐 ` 等近似标签。
|
||||
|
||||
宝贝识物草稿与发布 payload 中都必须保留该标签。发布后的公开展示、搜索、深链和入口开关继续遵循 `CHILD_MOTION_EDUTAINMENT_DISCOVER_ENTRY_2026-05-09.md`。
|
||||
|
||||
## 5. 结果页能力
|
||||
|
||||
结果页展示:
|
||||
|
||||
1. 作品名称;
|
||||
2. 两个物品名称;
|
||||
3. 两个物品图;
|
||||
4. 标签;
|
||||
5. 保存草稿;
|
||||
6. 发布;
|
||||
7. 试玩。
|
||||
|
||||
结果页不展示长规则说明文案。试玩按钮直接进入宝贝识物首关本地运行态。
|
||||
|
||||
试玩按钮进入宝贝识物首关运行态,运行态消费当前草稿中的两个物品名称和两张物品图,不重新生成或改写物品内容。
|
||||
|
||||
## 6. 发布后体验
|
||||
|
||||
发布完成后作品应进入寓教于乐内容线,并在寓教于乐入口开启时可被板块消费。
|
||||
|
||||
入口关闭时,发布作品完全不可见,不能通过推荐、发现普通频道、搜索、作品号、公开详情深链或浏览历史访问。
|
||||
|
||||
## 7. 与运行时线程的边界
|
||||
|
||||
本 PRD 同步约束首关运行态,已确认规则包括:
|
||||
|
||||
1. 礼物盒打开在本地调试绑定 `F` 键;
|
||||
2. 每轮仅中间礼物盒跳出的物品随机;左右两侧篮子固定为当前草稿两个物品的顺序;
|
||||
3. 下一关按钮当前占位;
|
||||
4. 不新增用户未确认的计时、失败次数、分数、体力或难度递增。
|
||||
5. 屏幕中上方字幕固定为“将物品放入对应的篮子里”。
|
||||
6. 礼物盒位于屏幕中下方,任意手抬起后打开并跳出下一个随机物品。
|
||||
7. 屏幕下方左侧和右侧分别展示两个固定篮子,左侧固定使用草稿第一个物品图,右侧固定使用草稿第二个物品图。
|
||||
8. 明确左手连续横向移动达到阈值时将当前物品送入左侧篮子,明确右手连续横向移动达到阈值时将当前物品送入右侧篮子;选篮不使用动作名判定,侧别未知的手部轨迹不参与选篮。
|
||||
9. 正确时展示“真棒”字幕和正确特效;错误时展示“再想一想吧”字幕和错误特效,物品回到中央。
|
||||
10. 成功 20 次后展示“恭喜你!小朋友!”字幕和特效,并展示“再来一次”和“下一关”按钮。
|
||||
11. 当前本地 Demo 阶段音效与语音播报接口只预留调用点,不在前端写死外部硬件或服务接口。
|
||||
|
||||
## 8. 验收
|
||||
|
||||
1. 创作入口显示 `宝贝识物` 并可进入模板表单。
|
||||
2. 未填写任一物品名称时不能生成草稿。
|
||||
3. 生成草稿后进入结果页,展示两个物品名称和物品图。
|
||||
4. 草稿标签中始终包含精确 `寓教于乐`。
|
||||
5. 发布 payload 始终包含精确 `寓教于乐`。
|
||||
6. 发布完成后出现分享弹窗或发布完成状态。
|
||||
7. 前端不读取或暴露 VectorEngine 密钥。
|
||||
8. 结果页试玩进入宝贝识物运行态,不再显示“试玩关卡正在接入中”。
|
||||
9. 运行态可通过 `F` 打开礼物盒,通过鼠标左键拖动映射左手横向移动,通过鼠标右键拖动映射右手横向移动。
|
||||
10. 成功 20 次后出现“再来一次”和“下一关”按钮。
|
||||
412
docs/prd/BARK_BATTLE_BDD_2026-05-11.md
Normal file
412
docs/prd/BARK_BATTLE_BDD_2026-05-11.md
Normal file
@@ -0,0 +1,412 @@
|
||||
# 汪汪声浪大作战 / bark-battle BDD 验收场景
|
||||
|
||||
## 背景
|
||||
|
||||
- 需求来源:用户提供的视频 `C:\Users\DSK\Videos\一款双方比狗叫的游戏 - 1.一款双方比狗叫的游戏(Av116504192360177,P1).mp4`,并已在 `.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md` 中完成抽帧分析和玩法方案整理。
|
||||
- 玩法定位:浏览器 2D 声控狗叫对战小游戏,暂定中文名 `汪汪声浪大作战`,英文代号与 play type ID 建议为 `bark-battle`。
|
||||
- 核心玩法:双方狗狗在 30 秒限时内通过麦克风输入“狗叫声”进行声浪拔河;系统依据声音强度、有效叫声次数和叫声节奏计算推动力,实时推动顶部红蓝能量条;倒计时结束后按能量条位置判定胜负或平局。
|
||||
- 文档目的:为产品、测试、前端、后端在编码前统一可验证验收口径;本文只定义 PRD/BDD 级行为与测试映射,不实现工程代码。
|
||||
|
||||
## 角色与目标
|
||||
|
||||
### 主要角色
|
||||
|
||||
- 浏览器玩家:进入 `bark-battle` 玩法,用麦克风发声参与对战。
|
||||
- 移动端玩家:在手机浏览器中进入玩法,需要看到核心游戏信息并完成授权、对战、结算。
|
||||
- 无麦克风或无 API 支持玩家:设备或浏览器不支持声控输入时,需要得到明确降级反馈并可返回。
|
||||
- 测试人员:依据本文场景验证权限、校准、叫声识别、能量条、胜负结算和布局兼容。
|
||||
- 前端实现人员:依据本文拆分 Web Audio 输入、领域规则、Phaser/DOM HUD 表现和自动化测试。
|
||||
|
||||
### 用户目标
|
||||
|
||||
- 玩家可以在开局前完成麦克风授权和环境噪音校准。
|
||||
- 玩家发出有效狗叫时,能看到叫声计数、狗狗动画、拟声词/冲击波以及能量条变化。
|
||||
- 低于阈值的背景噪音不会被误计为有效叫声。
|
||||
- 单局在 30 秒后给出明确胜负、平局和关键数据。
|
||||
- 移动端和不支持麦克风的环境不会进入不可操作状态。
|
||||
|
||||
### 非目标
|
||||
|
||||
- MVP 不要求识别“是否真实狗叫”,不引入机器学习声纹/物种分类;有效输入以音量阈值、峰值间隔、持续时间和校准结果为准。
|
||||
- MVP 不要求实时联机对战;可先按“玩家 vs AI 对手”完成单机浏览器 runtime。
|
||||
- MVP 不要求成绩持久化、作品发布、作品架、广场和排行榜;若后续接入 Genarrative 作品闭环,需要另补玩法类型集成 PRD/技术文档。
|
||||
- MVP 不要求在 UI 中长期展示大段规则说明;游戏界面应保持倒计时、能量条、狗狗、麦克风状态和结算信息为主。
|
||||
- MVP 不处理竞技级反作弊;播放录音、拍桌、喊叫等非狗叫输入只作为后续公平性风险记录。
|
||||
|
||||
## 业务规则口径
|
||||
|
||||
- 单局时长:默认 30 秒,从正式进入 `playing` 阶段开始计时。
|
||||
- 能量条:使用 `-100` 到 `100` 的连续值表示,负数偏对手侧,正数偏玩家侧,`0` 为中线。
|
||||
- 平局阈值:倒计时结束时,若能量条绝对值小于或等于 `drawThreshold`,判定平局;具体数值由实现配置,但测试需可注入固定阈值。
|
||||
- 有效叫声:一次有效叫声至少满足:音量超过校准后的有效阈值、与上一次有效峰值间隔不小于 `minBarkGapMs`、持续时长在 `minBarkDurationMs` 到 `maxBarkDurationMs` 之间。
|
||||
- 背景噪音:校准阶段采集到的环境声用于计算动态阈值;低于阈值的输入不得增加叫声次数,也不得让能量条出现可见推进。
|
||||
- 推动力:玩家推动力由音量分数、有效叫声频率和连击加成组成;能量条按玩家推动力与对手推动力差值移动,并被限制在 `-100` 到 `100`。
|
||||
- UI 反馈:有效叫声应触发可观察反馈,包括玩家侧狗狗张嘴/吠叫动画、拟声词或冲击波;反馈不应遮挡倒计时和顶部能量条。
|
||||
|
||||
## 中文 Gherkin 场景
|
||||
|
||||
### 功能: 麦克风授权与开局准备
|
||||
|
||||
```gherkin
|
||||
功能: 狗叫对战麦克风准备
|
||||
为了让玩家能用声音参与对战
|
||||
作为浏览器玩家
|
||||
我希望游戏在正式开局前请求麦克风权限并完成环境校准
|
||||
|
||||
背景:
|
||||
假如玩家进入 bark-battle 玩法页面
|
||||
而且浏览器支持 navigator.mediaDevices.getUserMedia
|
||||
|
||||
场景: 玩家允许麦克风权限后进入环境噪音校准
|
||||
当玩家同意浏览器麦克风授权
|
||||
那么系统应进入环境噪音校准阶段
|
||||
而且游戏不应在校准完成前进入 playing 阶段
|
||||
而且界面应显示麦克风已授权的可观察状态
|
||||
|
||||
场景: 校准完成后进入开局倒计时
|
||||
假如玩家已允许麦克风权限
|
||||
而且系统已采集足够的环境噪音样本
|
||||
当校准计算出有效叫声阈值
|
||||
那么系统应进入开局倒计时阶段
|
||||
而且倒计时结束后应进入 30 秒对战阶段
|
||||
而且初始能量条应位于中线
|
||||
|
||||
场景: 玩家拒绝麦克风权限后不能开始声控对战
|
||||
当玩家拒绝浏览器麦克风授权
|
||||
那么系统应停留在无法声控游玩的状态
|
||||
而且应提供重新授权或返回入口
|
||||
而且不应进入校准、倒计时或 playing 阶段
|
||||
```
|
||||
|
||||
### 功能: 环境噪音校准
|
||||
|
||||
```gherkin
|
||||
功能: 环境噪音校准
|
||||
为了减少背景噪音误触发
|
||||
作为浏览器玩家
|
||||
我希望游戏在开局前根据当前环境设置有效叫声阈值
|
||||
|
||||
场景: 安静环境生成低但非零的有效阈值
|
||||
假如校准阶段采集到的环境噪音 RMS 稳定低于默认噪音基线
|
||||
当系统完成校准
|
||||
那么有效叫声阈值应高于环境噪音平均值
|
||||
而且阈值不应低于系统配置的最小阈值
|
||||
|
||||
场景: 嘈杂环境生成更高的有效阈值
|
||||
假如校准阶段采集到的环境噪音 RMS 高于默认噪音基线
|
||||
当系统完成校准
|
||||
那么有效叫声阈值应随环境噪音上调
|
||||
而且低于该阈值的后续输入不应计为有效叫声
|
||||
|
||||
场景: 校准期间无法获得有效音频样本
|
||||
假如麦克风授权成功但音频样本持续为空或不可读
|
||||
当校准超过系统配置的最长等待时间
|
||||
那么系统应展示麦克风输入不可用状态
|
||||
而且应提供重试校准入口
|
||||
而且不应直接开始对战
|
||||
```
|
||||
|
||||
### 功能: 有效叫声计数
|
||||
|
||||
```gherkin
|
||||
功能: 有效叫声计数
|
||||
为了把玩家的狗叫行为转换为可计分输入
|
||||
作为玩家
|
||||
我希望每次符合规则的短促叫声只被计数一次
|
||||
|
||||
背景:
|
||||
假如游戏处于 30 秒 playing 阶段
|
||||
而且系统已完成环境噪音校准
|
||||
|
||||
场景: 单次超过阈值且间隔足够的叫声计数加一
|
||||
假如玩家当前叫声次数为 0
|
||||
而且上一次有效叫声时间早于 minBarkGapMs
|
||||
当麦克风输入出现一次超过有效阈值且持续时长合规的峰值
|
||||
那么玩家叫声次数应变为 1
|
||||
而且玩家侧应出现一次吠叫动画反馈
|
||||
而且画面应出现一次拟声词或冲击波反馈
|
||||
|
||||
场景: 持续噪音不会被无限计数
|
||||
假如玩家当前叫声次数为 1
|
||||
当麦克风输入持续超过阈值但没有新的峰值间隔
|
||||
那么玩家叫声次数不应在每个 tick 中持续增加
|
||||
而且系统最多只应记录当前连续声音段内的一次有效叫声
|
||||
|
||||
场景: 间隔过短的连续峰值不重复计数
|
||||
假如玩家刚刚产生一次有效叫声
|
||||
当麦克风输入在 minBarkGapMs 内再次出现峰值
|
||||
那么玩家叫声次数不应增加
|
||||
而且连击或推动力不应因该峰值重复加成
|
||||
```
|
||||
|
||||
### 功能: 声音大小和连续叫声推动能量条
|
||||
|
||||
```gherkin
|
||||
功能: 声浪推动能量条
|
||||
为了复刻双方比狗叫的核心体验
|
||||
作为玩家
|
||||
我希望更响、更连续的有效叫声能把顶部能量条推向自己一侧
|
||||
|
||||
背景:
|
||||
假如游戏处于 30 秒 playing 阶段
|
||||
而且能量条当前位于中线
|
||||
|
||||
场景: 玩家推动力高于对手时能量条向玩家侧移动
|
||||
假如玩家在短时间窗口内产生多次有效叫声
|
||||
而且玩家推动力高于对手推动力
|
||||
当系统推进一个 simulation tick
|
||||
那么能量条数值应向玩家侧增加
|
||||
而且顶部红蓝能量条的玩家侧占比应变大
|
||||
|
||||
场景: 连续大声叫声触发更强反馈
|
||||
假如玩家连续产生多次高于强叫声阈值的有效叫声
|
||||
当系统计算玩家连击加成
|
||||
那么玩家侧推动力应高于单次普通叫声推动力
|
||||
而且玩家侧声浪或冲击波反馈应比普通叫声更明显
|
||||
但是反馈不应遮挡倒计时和能量条
|
||||
|
||||
场景: 能量条到达边界后不会越界
|
||||
假如能量条已经接近玩家侧最大值
|
||||
而且玩家推动力仍高于对手推动力
|
||||
当系统推进多个 simulation tick
|
||||
那么能量条数值不应超过 100
|
||||
而且界面不应显示超出容器范围的能量条
|
||||
|
||||
场景: 对手推动力高于玩家时能量条向对手侧移动
|
||||
假如对手推动力高于玩家推动力
|
||||
当系统推进一个 simulation tick
|
||||
那么能量条数值应向对手侧减少
|
||||
而且顶部红蓝能量条的对手侧占比应变大
|
||||
```
|
||||
|
||||
### 功能: 低噪音和无效输入过滤
|
||||
|
||||
```gherkin
|
||||
功能: 背景噪音过滤
|
||||
为了避免环境声替玩家自动得分
|
||||
作为玩家
|
||||
我希望低于阈值或不合规的声音不会被当作有效狗叫
|
||||
|
||||
背景:
|
||||
假如游戏处于 30 秒 playing 阶段
|
||||
而且系统已完成环境噪音校准
|
||||
|
||||
场景: 低于阈值的背景噪音不计数
|
||||
当麦克风只接收到低于有效叫声阈值的背景噪音
|
||||
那么玩家叫声次数不应增加
|
||||
而且玩家侧不应播放吠叫动画
|
||||
而且能量条不应因为该背景噪音出现可见推进
|
||||
|
||||
场景: 过短脉冲不计为有效叫声
|
||||
假如麦克风输入峰值超过有效阈值
|
||||
但是持续时长短于 minBarkDurationMs
|
||||
当系统完成该声音段判定
|
||||
那么玩家叫声次数不应增加
|
||||
而且不应触发连击加成
|
||||
|
||||
场景: 过长持续声被削弱为单段输入
|
||||
假如麦克风输入持续超过有效阈值
|
||||
但是持续时长长于 maxBarkDurationMs
|
||||
当系统完成该声音段判定
|
||||
那么系统不应按多个叫声重复计数
|
||||
而且该声音段的推动力应按持续噪音削弱规则处理
|
||||
```
|
||||
|
||||
### 功能: 倒计时与胜负结算
|
||||
|
||||
```gherkin
|
||||
功能: 狗叫对战胜负结算
|
||||
为了让单局对抗有明确目标
|
||||
作为玩家
|
||||
我希望 30 秒倒计时结束后根据能量条位置得到胜负或平局
|
||||
|
||||
背景:
|
||||
假如游戏已经进入 30 秒 playing 阶段
|
||||
|
||||
场景: 倒计时每秒递减并在归零时停止对战输入
|
||||
当系统时间从 30 秒推进到 0 秒
|
||||
那么界面应显示倒计时归零
|
||||
而且系统应进入 finished 结算阶段
|
||||
而且归零后的麦克风输入不应再改变本局能量条和叫声次数
|
||||
|
||||
场景: 玩家侧占优时判定玩家胜利
|
||||
假如倒计时归零时能量条数值大于 drawThreshold
|
||||
当系统进入结算阶段
|
||||
那么系统应判定玩家胜利
|
||||
而且结算面板应展示玩家叫声次数、最大音量和声浪评分
|
||||
而且应提供再来一局入口
|
||||
|
||||
场景: 对手侧占优时判定玩家失败
|
||||
假如倒计时归零时能量条数值小于 -drawThreshold
|
||||
当系统进入结算阶段
|
||||
那么系统应判定对手胜利
|
||||
而且结算面板应展示玩家叫声次数、最大音量和声浪评分
|
||||
而且应提供再来一局入口
|
||||
|
||||
场景: 能量条接近平衡时判定平局
|
||||
假如倒计时归零时能量条数值位于 -drawThreshold 到 drawThreshold 之间
|
||||
当系统进入结算阶段
|
||||
那么系统应判定为平局
|
||||
而且结算面板应展示双方接近平衡的结果
|
||||
而且应提供再来一局入口
|
||||
```
|
||||
|
||||
### 功能: 再来一局与状态重置
|
||||
|
||||
```gherkin
|
||||
功能: 对战重开
|
||||
为了让玩家快速再次挑战
|
||||
作为玩家
|
||||
我希望结算后可以开始新的一局且旧状态不会污染新局
|
||||
|
||||
场景: 结算后点击再来一局重置本局状态
|
||||
假如系统处于 finished 结算阶段
|
||||
而且结算面板展示上一局结果
|
||||
当玩家选择再来一局
|
||||
那么系统应重置剩余时间为 30 秒
|
||||
而且能量条应回到中线
|
||||
而且玩家叫声次数、最大音量、连击和胜负结果应清零
|
||||
而且系统应重新进入校准或开局倒计时流程
|
||||
|
||||
场景: 结算后返回玩法入口
|
||||
假如系统处于 finished 结算阶段
|
||||
当玩家选择返回入口
|
||||
那么系统应离开当前对战运行态
|
||||
而且不应继续采集麦克风输入
|
||||
```
|
||||
|
||||
### 功能: 移动端布局
|
||||
|
||||
```gherkin
|
||||
功能: 移动端 bark-battle 布局
|
||||
为了让手机浏览器玩家可以完成声控对战
|
||||
作为移动端玩家
|
||||
我希望核心信息在窄屏中保持可见且可操作
|
||||
|
||||
场景: 移动端进入对战页面时核心元素可见
|
||||
假如玩家使用宽度不超过 430px 的移动端视口
|
||||
当玩家进入 bark-battle 页面
|
||||
那么顶部能量条应完整显示在首屏可见区域内
|
||||
而且倒计时应可见
|
||||
而且双方狗狗主体不应被权限、设置或说明面板长期遮挡
|
||||
而且非关键设置应收起到菜单或次级入口中
|
||||
|
||||
场景: 移动端授权和开始必须由用户手势触发
|
||||
假如玩家使用移动端浏览器
|
||||
当玩家点击开始或授权入口
|
||||
那么系统才应请求麦克风权限并激活音频上下文
|
||||
而且不应在页面自动加载时直接启动 AudioContext
|
||||
|
||||
场景: 移动端结算面板不遮挡主要操作
|
||||
假如玩家在移动端完成一局对战
|
||||
当系统展示结算面板
|
||||
那么胜负结果、再来一局和返回入口应在不横向滚动的情况下可见
|
||||
而且结算面板不应要求玩家阅读大段规则说明才能继续
|
||||
```
|
||||
|
||||
### 功能: 无 getUserMedia 或不可用环境降级
|
||||
|
||||
```gherkin
|
||||
功能: 无麦克风 API 降级
|
||||
为了避免不支持设备进入卡死状态
|
||||
作为无麦克风或无 API 支持玩家
|
||||
我希望系统明确告知无法声控游玩并提供退出路径
|
||||
|
||||
场景: 当前浏览器不支持 getUserMedia
|
||||
假如玩家设备不支持 navigator.mediaDevices.getUserMedia
|
||||
当玩家进入 bark-battle 页面
|
||||
那么系统应显示设备或浏览器不支持麦克风输入的状态
|
||||
而且应提供返回入口
|
||||
而且不应展示可开始声控对战的按钮
|
||||
|
||||
场景: getUserMedia 调用失败但浏览器 API 存在
|
||||
假如浏览器支持 getUserMedia
|
||||
但是请求麦克风时返回 NotFoundError 或 NotReadableError
|
||||
当系统接收到失败结果
|
||||
那么系统应展示麦克风不可用状态
|
||||
而且应提供重试授权或返回入口
|
||||
而且不应进入 playing 阶段
|
||||
|
||||
场景: 非安全上下文导致麦克风不可用
|
||||
假如页面运行在浏览器不允许麦克风的非安全上下文
|
||||
当玩家进入 bark-battle 页面
|
||||
那么系统应展示当前环境无法使用麦克风的状态
|
||||
而且应提示使用受支持的安全环境或返回
|
||||
而且不应开始对战倒计时
|
||||
```
|
||||
|
||||
### 功能: 刷新与退出时释放麦克风资源
|
||||
|
||||
```gherkin
|
||||
功能: 麦克风资源释放
|
||||
为了保护用户隐私并避免浏览器资源泄漏
|
||||
作为浏览器玩家
|
||||
我希望离开对战时麦克风采集被停止
|
||||
|
||||
场景: 对战中离开页面停止采集
|
||||
假如玩家已经授权麦克风并处于 playing 阶段
|
||||
当玩家离开 bark-battle 页面或运行态卸载
|
||||
那么系统应停止当前 MediaStream 的所有音轨
|
||||
而且不应继续推进本局 simulation tick
|
||||
|
||||
场景: 刷新页面后不沿用旧局临时状态
|
||||
假如玩家在 playing 阶段刷新页面
|
||||
当页面重新加载 bark-battle
|
||||
那么系统应重新进入权限检查或授权准备状态
|
||||
而且不应沿用刷新前的剩余时间、能量条和叫声次数作为新局结果
|
||||
```
|
||||
|
||||
## 测试映射
|
||||
|
||||
| 场景 | 测试层级 | 建议目标文件 | 自动化状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 玩家允许麦克风权限后进入环境噪音校准 | application/component | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned |
|
||||
| 校准完成后进入开局倒计时 | application/unit | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/domain/BarkBattleSession.test.ts` | planned |
|
||||
| 玩家拒绝麦克风权限后不能开始声控对战 | application/component | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned |
|
||||
| 安静环境生成低但非零的有效阈值 | unit | `src/games/bark-battle/domain/BarkNoiseCalibration.test.ts` | planned |
|
||||
| 嘈杂环境生成更高的有效阈值 | unit | `src/games/bark-battle/domain/BarkNoiseCalibration.test.ts` | planned |
|
||||
| 校准期间无法获得有效音频样本 | application/component | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned |
|
||||
| 单次超过阈值且间隔足够的叫声计数加一 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
|
||||
| 持续噪音不会被无限计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
|
||||
| 间隔过短的连续峰值不重复计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
|
||||
| 玩家推动力高于对手时能量条向玩家侧移动 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
|
||||
| 连续大声叫声触发更强反馈 | unit/integration/component | `src/games/bark-battle/domain/BarkBattleScoring.test.ts`, `src/games/bark-battle/ui/BarkBattleHud.test.tsx` | planned |
|
||||
| 能量条到达边界后不会越界 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
|
||||
| 对手推动力高于玩家时能量条向对手侧移动 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
|
||||
| 低于阈值的背景噪音不计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
|
||||
| 过短脉冲不计为有效叫声 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
|
||||
| 过长持续声被削弱为单段输入 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
|
||||
| 倒计时每秒递减并在归零时停止对战输入 | unit/application | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/application/BarkBattleController.test.ts` | planned |
|
||||
| 玩家侧占优时判定玩家胜利 | unit/component | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned |
|
||||
| 对手侧占优时判定玩家失败 | unit/component | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned |
|
||||
| 能量条接近平衡时判定平局 | unit/component | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned |
|
||||
| 结算后点击再来一局重置本局状态 | application/component | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned |
|
||||
| 结算后返回玩法入口 | integration/smoke | `src/games/bark-battle/application/BarkBattleController.test.ts`, Playwright 或人工 smoke 清单 | planned |
|
||||
| 移动端进入对战页面时核心元素可见 | component/visual/smoke | `src/games/bark-battle/ui/BarkBattleHud.test.tsx`, Playwright 移动端视口 smoke | planned |
|
||||
| 移动端授权和开始必须由用户手势触发 | infrastructure/application/e2e-smoke | `src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/application/__tests__/BarkBattleController.test.ts`, Playwright 移动端 smoke | planned |
|
||||
| 移动端结算面板不遮挡主要操作 | component/visual/smoke | `src/games/bark-battle/ui/__tests__/BarkBattleResultPanel.test.tsx`, Playwright 移动端视口 smoke | planned |
|
||||
| 当前浏览器不支持 getUserMedia | infrastructure/component | `src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/ui/__tests__/BarkBattlePermissionPanel.test.tsx` | planned |
|
||||
| getUserMedia 调用失败但浏览器 API 存在 | infrastructure/application/component | `src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/application/__tests__/BarkBattleController.test.ts`, `src/games/bark-battle/ui/__tests__/BarkBattlePermissionPanel.test.tsx` | planned |
|
||||
| 非安全上下文导致麦克风不可用 | infrastructure/application/component | `src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/application/__tests__/BarkBattleController.test.ts`, `src/games/bark-battle/ui/__tests__/BarkBattlePermissionPanel.test.tsx` | planned |
|
||||
| 对战中离开页面停止采集 | infrastructure/application/integration | `src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/application/__tests__/BarkBattleController.test.ts` | planned |
|
||||
| 刷新页面后不沿用旧局临时状态 | integration/smoke | Playwright 或人工 smoke 清单 | planned |
|
||||
|
||||
## 验收清单
|
||||
|
||||
- [ ] 权限允许、拒绝、非安全上下文、API 不支持、麦克风未找到/不可读、AudioContext 被拦截、校准超时或样本不可读均有明确状态,且不会误进入 playing。
|
||||
- [ ] 校准阶段会影响有效叫声阈值,低噪音不会增加叫声计数。
|
||||
- [ ] 有效叫声计数具备阈值、峰值间隔、持续时长约束。
|
||||
- [ ] 能量条根据双方推动力差值双向移动,并限制在 `-100` 到 `100`。
|
||||
- [ ] 30 秒归零后停止本局输入影响,并按玩家胜利、对手胜利、平局三类结果结算。
|
||||
- [ ] 移动端核心元素可见,非关键设置收起,不在主画面堆叠长规则说明。
|
||||
- [ ] 离开页面或返回入口时停止麦克风采集。
|
||||
- [ ] 每个编码依据场景已在测试映射中标注测试层级和建议文件。
|
||||
|
||||
## 开放问题
|
||||
|
||||
1. MVP 是否确认只做“玩家 vs AI”,还是第一版需要双人同屏或联机对战?
|
||||
2. `drawThreshold`、`minBarkGapMs`、`minBarkDurationMs`、`maxBarkDurationMs` 的首版默认值由产品/调参阶段确认,还是先采用开发可配置默认值?
|
||||
3. 是否允许无麦克风设备提供键盘/点击备用输入?若允许,需要另补非声控模式场景;若不允许,当前降级只提供返回入口。
|
||||
4. 是否需要在结算中记录或上报成绩、最高音量、叫声次数和声浪评分?若需要,需补埋点/后端持久化场景。
|
||||
5. bark-battle 是否作为 Genarrative 正式 play type 接入创作入口、作品发布和广场,还是先作为独立 runtime 原型验证?
|
||||
6. 狗狗、背景、拟声词和冲击波素材来源是临时占位、AI 生成,还是复用项目现有素材管线?
|
||||
File diff suppressed because it is too large
Load Diff
64
docs/prd/FIRST_LAUNCH_PUZZLE_ONBOARDING_PRD_2026-05-05.md
Normal file
64
docs/prd/FIRST_LAUNCH_PUZZLE_ONBOARDING_PRD_2026-05-05.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# 新手引导流程 PRD
|
||||
|
||||
## 1. 目标
|
||||
|
||||
引导未登录且首次访问产品的用户,快速体验从输入想法、AI 生成拼图游戏、完成拼图、注册或登录保留作品,到进入产品首页的创作闭环。
|
||||
|
||||
## 2. 触发条件
|
||||
|
||||
1. 用户处于未登录状态。
|
||||
2. 用户首次访问产品。
|
||||
|
||||
## 3. 流程
|
||||
|
||||
1. 用户首次打开产品。
|
||||
2. 页面浮现文字:`待定待定待定`。
|
||||
3. 文字下方展示文字输入框。
|
||||
4. 输入框提示文字:`把你的梦讲给我听吧`。
|
||||
5. 用户输入内容后,点击生成按钮。
|
||||
6. 系统拉起拼图游戏创作机制。
|
||||
7. 系统根据用户输入内容生成一个仅包含 1 关的拼图游戏。
|
||||
8. 生成完成后,页面浮现文字:`待定待定待定`。
|
||||
9. 用户进入当前生成的拼图游戏。
|
||||
10. 用户完成该拼图游戏的第 1 关。
|
||||
11. 页面浮现文字:`只差一步,就可以永久保留你的梦`。
|
||||
12. 文字下方展示注册账号/登录模块。
|
||||
13. 用户完成注册或登录。
|
||||
14. 系统进入产品首页。
|
||||
|
||||
## 4. 文案
|
||||
|
||||
| 位置 | 文案 |
|
||||
| --- | --- |
|
||||
| 首次启动浮现文案 | `待定待定待定` |
|
||||
| 输入框提示文字 | `把你的梦讲给我听吧` |
|
||||
| 生成完成浮现文案 | `待定待定待定` |
|
||||
| 完成拼图后的注册/登录引导文案 | `只差一步,就可以永久保留你的梦` |
|
||||
|
||||
## 5. 范围边界
|
||||
|
||||
1. 首屏右上角提供 `跳过` 入口,点击后写入首次访问标记并回到产品首页。
|
||||
2. 不定义额外功能说明文案。
|
||||
3. 不扩展拼图为多关。
|
||||
4. 不调整注册/登录后的去向,当前进入产品首页。
|
||||
5. 不新增未确认的 UI 动画、样式、奖励、埋点或保存策略。
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
1. 未登录首次访问产品时,进入新手引导首屏。
|
||||
2. 首屏展示确认文案、输入框和生成按钮。
|
||||
3. 首屏右上角展示跳过按钮;点击后本次和后续访问不再自动展示新手引导。
|
||||
4. 用户输入内容并点击生成后,系统生成 1 关拼图。
|
||||
5. 若临时生成接口返回 `404 / 资源不存在`,前端使用本地临时拼图兜底继续进入试玩,不把错误直接展示给用户。
|
||||
6. 生成完成后,用户可以进入该拼图并完成第 1 关。
|
||||
7. 第 1 关完成后,页面展示注册/登录引导文案和登录模块。
|
||||
8. 用户完成注册或登录后,进入产品首页。
|
||||
|
||||
## 7. 落地接口与状态
|
||||
|
||||
1. 首次访问判定由前端本地状态承载,未登录用户首次访问平台首页时展示;标记键为 `genarrative.puzzle-onboarding.first-visit.v1`。
|
||||
2. 临时生成入口为 `POST /api/runtime/puzzle/onboarding/generate`,不要求登录,只返回本次新手引导使用的 1 关拼图作品摘要与关卡数据。
|
||||
3. 登录后保存入口为 `POST /api/runtime/puzzle/onboarding/save`,要求登录;服务端为当前用户创建拼图 agent session,并把临时 1 关拼图保存为当前用户作品草稿。
|
||||
4. 新手引导游玩阶段复用现有本地拼图运行时,不新增 SpacetimeDB 表、reducer 或运行时真相。
|
||||
5. 保存完成后清空新手引导临时态,刷新拼图作品架,并回到产品首页。
|
||||
6. 跳过新手引导只更新本地首次访问标记和界面状态,不创建临时作品、不调用保存接口。
|
||||
@@ -22,15 +22,16 @@
|
||||
- 自 `2026-04-19` 起,“最近游玩 / 历史浏览”已从“我的”页迁出,改为平台一级主 Tab“存档”。
|
||||
- 对应母文档见 [PLATFORM_SAVE_TAB_PRD_2026-04-19.md](/E:/Repos/Genarrative/docs/prd/PLATFORM_SAVE_TAB_PRD_2026-04-19.md)。
|
||||
|
||||
当前“我的”页保留以下 `7` 个独立功能:
|
||||
当前“我的”页保留以下 `8` 个独立功能:
|
||||
|
||||
1. 账号资料与身份卡
|
||||
2. 会员中心与充值
|
||||
3. 我的数据看板
|
||||
4. 邀请好友
|
||||
5. 填邀请码
|
||||
6. 玩家社区
|
||||
7. 设置与账号安全
|
||||
6. 每日任务
|
||||
7. 玩家社区
|
||||
8. 设置与账号安全
|
||||
|
||||
---
|
||||
|
||||
@@ -42,8 +43,9 @@
|
||||
4. [PLATFORM_SAVE_TAB_PRD_2026-04-19.md](/E:/Repos/Genarrative/docs/prd/PLATFORM_SAVE_TAB_PRD_2026-04-19.md)
|
||||
5. [MY_TAB_INVITE_FRIENDS_PRD_2026-04-16.md](/E:/Repos/Genarrative/docs/prd/MY_TAB_INVITE_FRIENDS_PRD_2026-04-16.md)
|
||||
6. [MY_TAB_INVITE_CODE_REDEMPTION_PRD_2026-04-16.md](/E:/Repos/Genarrative/docs/prd/MY_TAB_INVITE_CODE_REDEMPTION_PRD_2026-04-16.md)
|
||||
7. [MY_TAB_PLAYER_COMMUNITY_PRD_2026-04-16.md](/E:/Repos/Genarrative/docs/prd/MY_TAB_PLAYER_COMMUNITY_PRD_2026-04-16.md)
|
||||
8. [MY_TAB_SETTINGS_AND_SECURITY_PRD_2026-04-16.md](/E:/Repos/Genarrative/docs/prd/MY_TAB_SETTINGS_AND_SECURITY_PRD_2026-04-16.md)
|
||||
7. [PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md](../technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md)
|
||||
8. [MY_TAB_PLAYER_COMMUNITY_PRD_2026-04-16.md](/E:/Repos/Genarrative/docs/prd/MY_TAB_PLAYER_COMMUNITY_PRD_2026-04-16.md)
|
||||
9. [MY_TAB_SETTINGS_AND_SECURITY_PRD_2026-04-16.md](/E:/Repos/Genarrative/docs/prd/MY_TAB_SETTINGS_AND_SECURITY_PRD_2026-04-16.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -58,7 +60,8 @@
|
||||
5. 会员中心与充值
|
||||
6. 邀请好友
|
||||
7. 填邀请码
|
||||
8. 玩家社区
|
||||
8. 每日任务
|
||||
9. 玩家社区
|
||||
|
||||
原因:
|
||||
|
||||
@@ -66,6 +69,7 @@
|
||||
- `3 + 4` 直接增强账号资产与回流体验,短期收益高
|
||||
- `5 + 6` 涉及商业化和关系绑定,依赖结算与奖励台账
|
||||
- `7` 最适合放在平台内容层能力稳定后再做
|
||||
- `8` 依赖埋点聚合、任务配置和钱包流水,首版只接每日登录
|
||||
|
||||
---
|
||||
|
||||
@@ -76,6 +80,7 @@
|
||||
- `PlatformHomeView` 继续作为“我的”Tab 首屏承载层
|
||||
- 优先采用现有面板、抽屉、弹窗,不新建独立大系统
|
||||
- 页面只展示后端返回的状态,不自行计算结论型业务状态
|
||||
- 每日任务入口放在“常用功能”,点击后弹出独立任务面板
|
||||
|
||||
### 4.2 后端边界
|
||||
|
||||
|
||||
182
docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md
Normal file
182
docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# “我的”页签帮助与反馈入口 PRD
|
||||
|
||||
更新时间:`2026-05-08`
|
||||
|
||||
## 0. 目标
|
||||
|
||||
在平台“我的”页签新增“反馈”入口。用户点击后进入独立路由 `/profile/feedback`,看到移动端优先的“帮助与反馈”表单页面,用于提交问题描述、上传问题截图凭证并选填联系电话。
|
||||
|
||||
本次目标是补齐用户反馈入口、图片预览、后端提交接口和 SpacetimeDB 持久化闭环,不重新发明新的个人中心系统,不在“我的”页签当前面板下方展开表单。
|
||||
|
||||
## 1. 参考图
|
||||
|
||||
计划参考图保存在:
|
||||
|
||||
`../.hermes/plans/assets/profile-feedback-reference-2026-05-08.png`
|
||||
|
||||
页面结构以该参考图为准:
|
||||
|
||||
1. 页面顶部保留项目风格返回按钮和标题“帮助与反馈”,不保留无实际功能的仿客户端顶部栏。
|
||||
2. 内容背景、卡片、按钮和状态色使用现有平台主题变量,避免硬编码成与主站不一致的蓝灰色页面。
|
||||
3. 分区标题为“反馈问题”。
|
||||
4. 第一张白色圆角卡片为“问题描述”。
|
||||
5. 第二张白色圆角卡片为“上传凭证(提供问题截图)”。
|
||||
6. 第三张白色圆角卡片为“联系电话”。
|
||||
7. 底部为蓝色主按钮“提交”。
|
||||
8. 提交按钮下方为蓝色文本入口“查看反馈与投诉记录”。
|
||||
|
||||
## 2. 首版范围
|
||||
|
||||
### 2.1 包含
|
||||
|
||||
- “我的”页签常用功能区新增“反馈”入口。
|
||||
- 点击入口进入 `/profile/feedback` 独立路由。
|
||||
- 反馈页标题显示“帮助与反馈”。
|
||||
- 问题描述输入:
|
||||
- 最少 10 个字。
|
||||
- 最多 200 个字。
|
||||
- 实时显示 `当前字数/200`。
|
||||
- placeholder:`请填写10个字以上的问题描述以便我们提供更好的帮助,温馨提醒您请勿填写身份证号等个人隐私信息。`
|
||||
- 上传凭证:
|
||||
- 展示虚线上传方块。
|
||||
- 支持选择图片时,最多 4 张。
|
||||
- 前端可预览已选图片。
|
||||
- 已选图片使用 Data URL 预览,并随提交请求作为首版反馈凭证入库。
|
||||
- 联系电话:
|
||||
- 选填。
|
||||
- placeholder:`选填,如您填写则将会同步开发者与您联系`。
|
||||
- 提交后通过 `POST /api/profile/feedback` 写入 SpacetimeDB,接口成功后显示成功态。
|
||||
- 返回后回到平台首页并定位“我的”页签。
|
||||
|
||||
### 2.2 不包含
|
||||
|
||||
- 不新增后台反馈记录管理页。
|
||||
- 不实现真实“反馈与投诉记录”列表。
|
||||
|
||||
后端接入细节以 [`docs/technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md`](../technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md) 为准。
|
||||
|
||||
## 3. 入口设计
|
||||
|
||||
### 3.1 入口位置
|
||||
|
||||
入口放在“我的”页签常用功能区,和“每日任务 / 邀请好友 / 填邀请码 / 玩家社区”同级。
|
||||
|
||||
入口展示:
|
||||
|
||||
- 主标题:`反馈`
|
||||
- 副标题:`问题与建议`
|
||||
- 图标:可复用 `MessageCircle` 或类似消息图标。
|
||||
|
||||
### 3.2 未登录状态
|
||||
|
||||
首版默认未登录用户点击入口时触发登录弹窗,不进入反馈页。
|
||||
|
||||
原因:当前“我的”页签的数据与账号绑定,反馈如果未来接入后端,也应能关联提交账号。
|
||||
|
||||
## 4. 反馈页 UI
|
||||
|
||||
### 4.1 页面整体
|
||||
|
||||
- 移动端优先。
|
||||
- 背景、表单容器、输入框、提示和按钮复用 `platform-*` 主题变量。
|
||||
- 桌面端居中展示,最大宽度不超过移动表单阅读范围,避免横向拉满。
|
||||
|
||||
### 4.2 返回区
|
||||
|
||||
- 标题:`帮助与反馈`。
|
||||
- 左侧返回按钮:点击返回平台首页“我的”页签。
|
||||
- 不展示右侧胶囊控制区、假系统按钮或无实际功能顶部栏。
|
||||
|
||||
### 4.3 问题描述卡片
|
||||
|
||||
- 标题:`问题描述`。
|
||||
- 输入框类型:textarea。
|
||||
- 最小高度接近参考图的大文本区域。
|
||||
- 右下角字数:`0/200`。
|
||||
- 校验失败提示靠近卡片或提交按钮上方展示,不弹浏览器 alert。
|
||||
|
||||
### 4.4 上传凭证卡片
|
||||
|
||||
- 标题:`上传凭证(提供问题截图)`。
|
||||
- 上传入口为虚线边框方块。
|
||||
- 文案:
|
||||
- `上传凭证`
|
||||
- `(最多四张)`
|
||||
- 支持图片选择时,只允许 `image/*`。
|
||||
- 超过 4 张时提示:`最多上传四张凭证`。
|
||||
- 单张图片最大 1MB,总大小最大 4MB。
|
||||
- 选择图片后必须立即展示缩略图预览,删除后从待提交凭证中移除。
|
||||
|
||||
### 4.5 联系电话卡片
|
||||
|
||||
- 标题:`联系电话`。
|
||||
- 输入框类型:text 或 tel。
|
||||
- 联系电话选填,不阻塞提交。
|
||||
- 最长 40 字符。
|
||||
|
||||
### 4.6 底部操作
|
||||
|
||||
- 主按钮:`提交`。
|
||||
- 按钮为蓝色圆角,宽度接近容器宽度。
|
||||
- 二级链接:`查看反馈与投诉记录`。
|
||||
- 首版无记录页时,该链接可以:
|
||||
- 隐藏;或
|
||||
- 保留并点击后显示轻量提示“反馈记录暂未开放”。
|
||||
|
||||
## 5. 路由与状态
|
||||
|
||||
- 新增页面阶段:`profile-feedback`。
|
||||
- 新增路由:`/profile/feedback`。
|
||||
- 浏览器直接访问 `/profile/feedback` 时应显示反馈页。
|
||||
- 点击页面返回时:
|
||||
- 设置平台 tab 为 `profile`。
|
||||
- 回到 `platform` 阶段。
|
||||
|
||||
## 6. 表单校验
|
||||
|
||||
提交时按以下顺序校验:
|
||||
|
||||
1. 问题描述去除首尾空白后少于 10 个字:提示 `请填写10个字以上的问题描述`。
|
||||
2. 问题描述超过 200 个字:提示 `问题描述不能超过 200 字`。
|
||||
3. 联系电话超过 40 字符:提示 `联系电话不能超过 40 字`。
|
||||
4. 上传凭证超过 4 张:提示 `最多上传四张凭证`。
|
||||
|
||||
校验通过后调用 `POST /api/profile/feedback`,请求成功后进入成功态。
|
||||
|
||||
## 7. 验收标准
|
||||
|
||||
- “我的”页签可看到“反馈”入口。
|
||||
- 已登录用户点击后进入 `/profile/feedback`。
|
||||
- 未登录用户点击后弹登录。
|
||||
- `/profile/feedback` 页面显示“帮助与反馈”。
|
||||
- 问题描述字数统计实时变化。
|
||||
- 空内容或少于 10 个字提交时显示校验错误。
|
||||
- 有效内容提交后显示成功态。
|
||||
- 上传凭证最多 4 张。
|
||||
- 上传凭证选择后显示图片预览。
|
||||
- 有效反馈提交后写入 `profile_feedback_submission` 表。
|
||||
- 提交接口返回 `feedbackId`、状态、提交时间和凭证元数据。
|
||||
- 联系电话为空时可以提交。
|
||||
- 返回后回到“我的”页签。
|
||||
- 页面在 390×844 移动端视口不横向溢出。
|
||||
- `npm run check:encoding`、`npm run typecheck`、定向测试通过。
|
||||
|
||||
## 8. 文件落点
|
||||
|
||||
- PRD:`docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`
|
||||
- 路由:`src/routing/appPageRoutes.ts`
|
||||
- 阶段类型:`src/components/platform-entry/platformEntryTypes.ts`
|
||||
- 反馈页:`src/components/platform-entry/PlatformFeedbackView.tsx`
|
||||
- 前端 profile client:`src/services/rpg-entry/rpgProfileClient.ts`
|
||||
- 我的页签入口:`src/components/rpg-entry/RpgEntryHomeView.tsx`
|
||||
- 页面接入:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
- 共享契约:`packages/shared/src/contracts/runtime.ts`、`server-rs/crates/shared-contracts/src/runtime.rs`
|
||||
- 后端 API:`server-rs/crates/api-server/src/runtime_profile.rs`
|
||||
- SpacetimeDB:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
||||
- 领域规则:`server-rs/crates/module-runtime/src/*`
|
||||
- 测试:
|
||||
- `src/routing/appPageRoutes.test.ts`
|
||||
- `src/components/platform-entry/PlatformFeedbackView.test.tsx`
|
||||
- `src/services/rpg-entry/rpgProfileClient.test.ts`
|
||||
- `server-rs/crates/module-runtime`
|
||||
- `server-rs/crates/api-server/src/runtime_profile.rs`
|
||||
21
docs/prd/README.md
Normal file
21
docs/prd/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# PRD 总览
|
||||
|
||||
本目录用于存放产品需求、玩法闭环、阶段计划和可直接指导编码的需求拆分文档。
|
||||
|
||||
## 重点入口
|
||||
|
||||
- [宝贝识物寓教于乐模板 PRD](./BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md):定义寓教于乐内容线的 `宝贝识物` 创作模板,覆盖两个物品名称输入、image-2 物品图生成、精确 `寓教于乐` 标签、结果页和发布边界。
|
||||
- [AI 原生幕间文字游戏模板 PRD:参考 MOKU 的剧本模拟器闭环](./AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md):参考 MOKU / 幕间类 AI 文游的剧本游乐场、自由行动、AI GM、记忆和模拟器强反馈经验,但只落为百梦 `text-game` 模板,复用平台接口,不迁入外部社区、支付、私有存档或回放。
|
||||
- [AI 原生视觉小说模板 PRD:TXT 玩法平台化接入](./AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md):参考 `Interactive-fiction-backend` / `Interactive-fiction-frontend` 的 TXT 玩法经验,但只保留视觉小说模板创作与运行闭环,完全使用 Genarrative 平台接口,并明确删除回放和外部平台功能。
|
||||
- [AI 原生幸存者类游戏模板 PRD](./AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md):定义 `survivor` 幸存者挑战模板,从 Agent 创作、结果页、资产、试玩、发布到后端权威配置与前端高频运行表现的完整闭环。
|
||||
- [创意互动内容生成 Agent Phase 1 PRD:LangChain-Rust PoC + 拼图闭环](./CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md):首版只支持拼图模板,Agent 使用 APIMart Responses `gpt-5` 支持文本和图像多模态输入,明确模板选择、积分范围、草稿字段填充、单关卡/多关卡图片生成、立即试玩、自然语言修改和可并行任务拆分。
|
||||
- [AI 原生 2048 游戏玩法模板 PRD](./AI_NATIVE_2048_GAMEPLAY_TEMPLATE_PRD_2026-05-05.md):新增 `twenty-forty-eight` 玩法模板,定义主题化合成链创作、结果页、发布、公开运行、后端棋盘裁决、排行榜和并行落地任务。
|
||||
- [AI 原生拼图玩法创作工具与玩法系统 PRD](./AI_NATIVE_PUZZLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-22.md):拼图玩法创作、结果页、发布、广场和运行时主链路。
|
||||
- [AI 原生方洞挑战玩法创作工具与玩法系统 PRD](./AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md):方洞挑战创作、发布与试玩闭环。
|
||||
- [后台管理独立前端工程 PRD](./ADMIN_WEB_CONSOLE_PRD_2026-04-30.md):后台管理端产品边界。
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 新玩法、新 Agent 阶段、新创作闭环或较大功能落地前,优先补 PRD。
|
||||
- PRD 必须写到可以编码的程度,包含字段、接口、状态、验收和并行任务拆分。
|
||||
- 若 PRD 与最新代码或技术方案冲突,以代码和最新技术方案为准,并同步修正 PRD。
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
更新时间:`2026-04-20`
|
||||
|
||||
> 2026-05-05 更新口径:本文保留为历史参考。视觉小说模板后续落地以 [`AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`](./AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md) 为准;冲突时不再按“外部 TXT 模式原样迁入”执行,必须只保留模板玩法能力、完全使用 Genarrative 平台接口,并删除回放功能。
|
||||
|
||||
## 0. 文档目的
|
||||
|
||||
这份 PRD 只定义 `Interactive-fiction-frontend` + `Interactive-fiction-backend` 中 TXT 模式在 `Genarrative` 落地时的**核心玩法闭环**。
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# 后台创作入口开关操作入口
|
||||
|
||||
日期:2026-05-11
|
||||
|
||||
## 背景
|
||||
|
||||
创作中心入口配置已经迁移到 SpacetimeDB,前端创作中心与 api-server 路由熔断共用同一份入口配置。为了让运营/管理员可以调整这张开关表,需要在后台提供显式操作入口,而不是只通过数据库表查询或手工 reducer 操作。
|
||||
|
||||
## 后台入口
|
||||
|
||||
后台新增导航:
|
||||
|
||||
- 名称:入口开关
|
||||
- hash:`#creation-entry`
|
||||
- 页面:`apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx`
|
||||
|
||||
页面能力:
|
||||
|
||||
- 读取当前入口开关表。
|
||||
- 编辑单个入口:
|
||||
- `id`
|
||||
- `title`
|
||||
- `subtitle`
|
||||
- `badge`
|
||||
- `imageSrc`
|
||||
- `visible`
|
||||
- `open`
|
||||
- `sortOrder`
|
||||
- 保存前复用后台写操作确认弹窗。
|
||||
- 保存后重新用后端返回的配置刷新列表。
|
||||
|
||||
## API
|
||||
|
||||
后台新增受管理员鉴权保护的接口:
|
||||
|
||||
```text
|
||||
GET /admin/api/creation-entry/config
|
||||
POST /admin/api/creation-entry/config
|
||||
```
|
||||
|
||||
POST body:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "puzzle",
|
||||
"title": "拼图",
|
||||
"subtitle": "拼图关卡创作",
|
||||
"badge": "可创建",
|
||||
"imageSrc": "/creation-type-references/puzzle.webp",
|
||||
"visible": true,
|
||||
"open": true,
|
||||
"sortOrder": 30
|
||||
}
|
||||
```
|
||||
|
||||
响应统一返回当前全量入口列表,方便后台页面直接刷新本地状态。
|
||||
|
||||
## 入库链路
|
||||
|
||||
```text
|
||||
Admin Web
|
||||
-> api-server /admin/api/creation-entry/config
|
||||
-> AppState::upsert_creation_entry_type_config
|
||||
-> spacetime-client procedure upsert_creation_entry_type_config
|
||||
-> spacetime-module creation_entry_type_config 表
|
||||
```
|
||||
|
||||
`visible=false` 会让创作中心不展示对应入口;`open=false` 会让前端展示锁定态,并让 api-server 熔断对应玩法创作 / 运行态 API。隐藏入口但仍保留既有作品号、广场详情或试玩链路时,应只关闭 `visible`,不要关闭 `open`。
|
||||
|
||||
## 注意
|
||||
|
||||
- 前端后台页面只做管理表单,不成为配置事实源。
|
||||
- `src/config/newWorkEntryConfig.ts` 不应恢复。
|
||||
- SpacetimeDB client bindings 当前新增了对应临时 binding 文件;后续执行标准 bindings regenerate 时应覆盖并保持同名 procedure/type。
|
||||
98
docs/technical/ADMIN_DATABASE_TABLE_QUERY_2026-05-08.md
Normal file
98
docs/technical/ADMIN_DATABASE_TABLE_QUERY_2026-05-08.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 后台数据库表查询技术方案(2026-05-08)
|
||||
|
||||
## 背景
|
||||
|
||||
后台“总览”页已经通过 `/admin/api/overview` 展示 SpacetimeDB 表统计,但只能看到表名、行数和统计状态。运营和排障时需要从统计行直接进入单表查询页,按基础条件快速查看真实行数据。
|
||||
|
||||
## 目标
|
||||
|
||||
- 在后台新增“表查询”页,支持所有 schema 表的只读查询。
|
||||
- “总览 / 表统计”中的每一行可点击跳转到对应表的查询页。
|
||||
- 提供基础查询能力:表选择、关键词搜索、JSON 条件过滤、条数限制、刷新、查看行详情。
|
||||
- 不修改 SpacetimeDB 表结构,不新增 reducer,不引入写操作。
|
||||
|
||||
## 后续增强
|
||||
|
||||
- 查询页增加“重置条件”快捷操作,便于运营快速回到默认筛选状态。
|
||||
- 行详情支持一键复制完整 JSON,减少人工选中复制的操作成本。
|
||||
- 查询页顶部增加轻量摘要,显示当前选表和可见列数,方便移动端快速确认上下文。
|
||||
|
||||
## 后端接口
|
||||
|
||||
### `GET /admin/api/database/tables`
|
||||
|
||||
鉴权:沿用 `require_admin_auth`。
|
||||
|
||||
数据来源:SpacetimeDB schema HTTP API。
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"tables": ["tracking_event", "user_account"],
|
||||
"fetchErrors": []
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /admin/api/database/tables/{tableName}/rows`
|
||||
|
||||
鉴权:沿用 `require_admin_auth`。
|
||||
|
||||
Query:
|
||||
|
||||
- `limit`:默认 100,范围 1-500。
|
||||
- `search`:可选,前端关键词;后端返回行后在 JSON 文本中大小写不敏感过滤。
|
||||
- `filters`:可选 JSON object 字符串,例如 `{"user_id":"u1","enabled":true}`;后端返回行后按字段等值过滤。
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"tableName": "tracking_event",
|
||||
"columns": ["event_id", "event_key"],
|
||||
"rows": [
|
||||
{
|
||||
"cells": {
|
||||
"event_id": "event-1",
|
||||
"event_key": "daily_login"
|
||||
},
|
||||
"raw": ["event-1", "daily_login"]
|
||||
}
|
||||
],
|
||||
"totalReturned": 1,
|
||||
"limit": 100
|
||||
}
|
||||
```
|
||||
|
||||
实现约束:
|
||||
|
||||
- 表名必须来自 schema 且通过标识符安全校验,避免任意 SQL 注入。
|
||||
- SQL 固定为 `SELECT * FROM {tableName} LIMIT {limit}`;SpacetimeDB 2.2 HTTP SQL 不拼 `ORDER BY`。
|
||||
- 用户输入不直接拼入 SQL;关键词和条件在 API Server 内存中过滤。
|
||||
- private 表或 token 不可见时返回后台可读错误信息。
|
||||
- SpacetimeDB SQL 行和 SATS 值统一转成人可读 JSON:Option None 为 null,Some 展开为内部值,Timestamp 单元素数组展开为内部值,enum 可保留 tag/name 或原始数组文本。
|
||||
|
||||
## 前端页面
|
||||
|
||||
路由:`#tables`,导航名“表查询”。
|
||||
|
||||
页面能力:
|
||||
|
||||
- 表选择下拉展示中文表名并保留原始表名,支持 URL hash `#tables?table=xxx` 直达指定表。
|
||||
- 查询表单:表名、关键词、JSON 条件、条数。
|
||||
- 查询结果表格横向滚动,移动端不撑坏布局。
|
||||
- 查询结果标题和已选表摘要展示中文表名,鼠标悬浮显示原始表名和表说明,方便运营识别真实数据域。
|
||||
- 表头支持点击排序,排序只作用于当前已拉取的行数据,不改变后端 SQL。
|
||||
- 表头展示中文字段名,鼠标悬浮显示原始字段名、字段说明和排序提示,方便运营阅读且保留排障所需的真实列名。
|
||||
- 单元格内容过长时在表格内单行省略,完整内容可通过悬浮标题或行详情弹层查看。
|
||||
- 每行提供“详情”按钮,以独立弹层展示完整 JSON。
|
||||
- 总览表统计行点击后跳转到 `#tables?table={tableName}`。
|
||||
|
||||
## 验收
|
||||
|
||||
- `cd server-rs && cargo fmt -p api-server -p shared-contracts --check`
|
||||
- `cd server-rs && cargo test -p api-server admin_database -- --nocapture`
|
||||
- `npm run admin-web:typecheck`
|
||||
- `npm run admin-web:build`
|
||||
- `npm run check:encoding`
|
||||
- `git diff --check`
|
||||
170
docs/technical/ADMIN_TRACKING_EVENT_DETAIL_EXPORT_2026-05-07.md
Normal file
170
docs/technical/ADMIN_TRACKING_EVENT_DETAIL_EXPORT_2026-05-07.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# 后台埋点数据明细与 Excel 导出方案
|
||||
|
||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 在百梦后台新增“埋点数据”页,展示每条埋点原始事件的详细字段,并支持导出为 Excel 可直接打开的表格文件。
|
||||
|
||||
**Architecture:** 后端继续由 `api-server` 作为后台 BFF,经 SpacetimeDB HTTP SQL 只读查询 `tracking_event`,不改变表结构和 reducer。前端在 `apps/admin-web` 中新增独立路由与页面,页面渲染后端返回的原始明细,并在浏览器侧导出 Excel 兼容的 `.xls` HTML 表格,避免新增依赖。
|
||||
|
||||
**Tech Stack:** Rust Axum、SpacetimeDB HTTP SQL、shared-contracts、React 19、TypeScript、Vite。
|
||||
|
||||
---
|
||||
|
||||
## 范围
|
||||
|
||||
本次只做后台只读能力:
|
||||
|
||||
- 展示 `tracking_event` 原始事件明细。
|
||||
- 每条埋点展示:事件 ID、Event Key、事件名称、Scope、Scope ID、Day Key、用户 ID、作品拥有者、Profile ID、模块、metadata、发生时间。
|
||||
- 支持按 Event Key、用户 ID、Scope Kind、Scope ID 筛选。
|
||||
- 支持导出当前筛选结果为 Excel 可打开文件。
|
||||
|
||||
不做:
|
||||
|
||||
- 不新增或修改 SpacetimeDB 表结构。
|
||||
- 不在后台写入或删除埋点。
|
||||
- 不把埋点聚合口径下沉到前端计算。
|
||||
|
||||
## 后端契约
|
||||
|
||||
新增接口:
|
||||
|
||||
```text
|
||||
GET /admin/api/tracking/events?eventKey=&userId=&scopeKind=&scopeId=&limit=
|
||||
```
|
||||
|
||||
鉴权:复用后台 `require_admin_auth`。
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"eventId": "daily-login:user:xxx:123",
|
||||
"eventKey": "daily_login",
|
||||
"eventTitle": "每日登录",
|
||||
"scopeKind": "user",
|
||||
"scopeId": "xxx",
|
||||
"dayKey": 20580,
|
||||
"userId": "xxx",
|
||||
"ownerUserId": null,
|
||||
"profileId": null,
|
||||
"moduleKey": "profile",
|
||||
"metadataJson": "{}",
|
||||
"occurredAt": "2026-05-07T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
后端实现要点:
|
||||
|
||||
1. DTO 放在 `shared-contracts/src/admin.rs`,避免 Rust 与前端口径分叉。
|
||||
2. Handler 放在 `api-server/src/admin.rs`,使用当前已有 SpacetimeDB HTTP SQL helper 思路。
|
||||
3. SQL 只读 `tracking_event`,固定白名单列;由于 SpacetimeDB 2.2 HTTP SQL 不支持 `ORDER BY`,后端取回默认 200 / 最大 1000 条后在 API 层按 `occurred_at` 倒序排序。
|
||||
4. 查询条件只通过字符串转义函数拼接,禁止直接拼接未转义用户输入。
|
||||
5. `eventTitle` 由后端根据已知事件 key 映射,未知事件返回 `eventKey`。
|
||||
|
||||
## 前端页面
|
||||
|
||||
新增路由:`#tracking`,导航标题为“埋点数据”。
|
||||
|
||||
页面能力:
|
||||
|
||||
1. 顶部筛选区:Event Key、用户 ID、Scope Kind、Scope ID、刷新、导出 Excel。Event Key 候选来自后台前端的埋点定义注册表,需覆盖 `BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md` 中已接入的通用埋点事件,不能只保留 `daily_login`。
|
||||
2. 列表区:移动端可横向滚动,桌面端表格展示。
|
||||
3. 详情区:每行有“详情”按钮,弹出独立面板展示完整字段与格式化后的 metadata JSON。
|
||||
4. 导出:导出当前页面已加载结果,文件名形如 `tracking-events-2026-05-07.xls`。
|
||||
|
||||
导出实现:
|
||||
|
||||
- 使用 HTML table + Excel MIME:`application/vnd.ms-excel;charset=utf-8`。
|
||||
- 文件扩展名使用 `.xls`,Excel/WPS 可直接打开。
|
||||
- 所有单元格做 HTML 转义。
|
||||
- metadata 保留原始 JSON 文本,便于运营继续筛选。
|
||||
|
||||
## 验收命令
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
npm run admin-web:typecheck
|
||||
cargo test -p shared-contracts -p api-server admin_tracking -- --nocapture
|
||||
```
|
||||
|
||||
如后端接口改动较大,再补充:
|
||||
|
||||
```bash
|
||||
npm run api-server
|
||||
curl http://127.0.0.1:<port>/healthz
|
||||
```
|
||||
|
||||
## 实施任务
|
||||
|
||||
### Task 1: 补充 shared-contracts 后台埋点 DTO
|
||||
|
||||
**Files:**
|
||||
- Modify: `server-rs/crates/shared-contracts/src/admin.rs`
|
||||
|
||||
**Steps:**
|
||||
1. 新增 `AdminTrackingEventListQuery`。
|
||||
2. 新增 `AdminTrackingEventEntryPayload`。
|
||||
3. 新增 `AdminTrackingEventListResponse`。
|
||||
4. 为 DTO 添加中文注释。
|
||||
|
||||
### Task 2: 增加后端后台埋点查询接口
|
||||
|
||||
**Files:**
|
||||
- Modify: `server-rs/crates/api-server/src/admin.rs`
|
||||
- Modify: `server-rs/crates/api-server/src/app.rs`
|
||||
|
||||
**Steps:**
|
||||
1. 在 `admin.rs` 新增 query 解析与 SQL 构造。
|
||||
2. 复用 SpacetimeDB HTTP SQL 调用风格读取 rows。
|
||||
3. 新增 `admin_list_tracking_events` handler。
|
||||
4. 在 `app.rs` 挂载 `/admin/api/tracking/events`。
|
||||
5. 添加单元测试覆盖 SQL 字符串转义、limit clamp、SQL 响应解析。
|
||||
|
||||
### Task 3: 增加前端 API 类型与客户端方法
|
||||
|
||||
**Files:**
|
||||
- Modify: `apps/admin-web/src/api/adminApiTypes.ts`
|
||||
- Modify: `apps/admin-web/src/api/adminApiClient.ts`
|
||||
|
||||
**Steps:**
|
||||
1. 新增埋点 entry/list/query 类型。
|
||||
2. 新增 `listAdminTrackingEvents(token, query)`。
|
||||
3. 使用 `URLSearchParams` 拼接非空查询字段。
|
||||
|
||||
### Task 4: 新增后台埋点数据页面
|
||||
|
||||
**Files:**
|
||||
- Create: `apps/admin-web/src/pages/AdminTrackingEventsPage.tsx`
|
||||
- Modify: `apps/admin-web/src/styles/admin.css`
|
||||
|
||||
**Steps:**
|
||||
1. 实现筛选、刷新、错误状态。
|
||||
2. 实现明细表格。
|
||||
3. 实现独立详情面板。
|
||||
4. 实现 Excel `.xls` 导出。
|
||||
5. 保持 UI 简洁,不添加说明类大段文案。
|
||||
|
||||
### Task 5: 接入后台路由与导航
|
||||
|
||||
**Files:**
|
||||
- Modify: `apps/admin-web/src/app/adminRoutes.ts`
|
||||
- Modify: `apps/admin-web/src/app/AdminShell.tsx`
|
||||
- Modify: `apps/admin-web/src/app/AdminApp.tsx`
|
||||
|
||||
**Steps:**
|
||||
1. 增加 `tracking` 路由。
|
||||
2. 导航增加图标。
|
||||
3. `AdminApp` 渲染新页面。
|
||||
|
||||
### Task 6: 验证并提交
|
||||
|
||||
**Steps:**
|
||||
1. 运行 `npm run check:encoding`。
|
||||
2. 运行 `npm run admin-web:typecheck`。
|
||||
3. 运行后端相关 cargo test。
|
||||
4. 修复问题后提交并推送当前分支。
|
||||
@@ -96,8 +96,10 @@ export interface ApiErrorEnvelope {
|
||||
| 当前管理员 | `GET /admin/api/me` | 管理员 Bearer |
|
||||
| 服务与数据库概览 | `GET /admin/api/overview` | 管理员 Bearer |
|
||||
| 受控 HTTP 调试 | `POST /admin/api/debug/http` | 管理员 Bearer |
|
||||
| 读取兑换码列表 | `GET /admin/api/profile/redeem-codes` | 管理员 Bearer |
|
||||
| 创建/更新兑换码 | `POST /admin/api/profile/redeem-codes` | 管理员 Bearer |
|
||||
| 停用兑换码 | `POST /admin/api/profile/redeem-codes/disable` | 管理员 Bearer |
|
||||
| 读取后台邀请码列表 | `GET /admin/api/profile/invite-codes` | 管理员 Bearer |
|
||||
| 创建/更新注册邀请码 | `POST /admin/api/profile/invite-codes` | 管理员 Bearer |
|
||||
|
||||
### 4.3 前端类型命名
|
||||
@@ -190,6 +192,8 @@ export interface AdminDisableProfileRedeemCodeRequest {
|
||||
|
||||
export interface AdminUpsertProfileInviteCodeRequest {
|
||||
inviteCode: string;
|
||||
startsAt?: string | null;
|
||||
expiresAt?: string | null;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -206,13 +210,24 @@ export interface ProfileRedeemCodeAdminResponse {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ProfileRedeemCodeAdminListResponse {
|
||||
entries: ProfileRedeemCodeAdminResponse[];
|
||||
}
|
||||
|
||||
export interface ProfileInviteCodeAdminResponse {
|
||||
userId: string;
|
||||
inviteCode: string;
|
||||
startsAt: string | null;
|
||||
expiresAt: string | null;
|
||||
status: 'pending' | 'active' | 'expired';
|
||||
metadata: Record<string, unknown>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ProfileInviteCodeAdminListResponse {
|
||||
entries: ProfileInviteCodeAdminResponse[];
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 登录 contract
|
||||
@@ -284,6 +299,31 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
|
||||
### 4.7 兑换码管理 contract
|
||||
|
||||
列表请求:
|
||||
|
||||
`GET /admin/api/profile/redeem-codes`
|
||||
|
||||
成功返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"code": "WELCOME2026",
|
||||
"mode": "public",
|
||||
"rewardPoints": 100,
|
||||
"maxUses": 1,
|
||||
"globalUsedCount": 0,
|
||||
"enabled": true,
|
||||
"allowedUserIds": [],
|
||||
"createdBy": "admin:root",
|
||||
"createdAt": "2026-04-30T00:00:00Z",
|
||||
"updatedAt": "2026-04-30T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
创建/更新请求:
|
||||
|
||||
```json
|
||||
@@ -300,8 +340,6 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
|
||||
停用请求:
|
||||
|
||||
兑换码管理页的最近一次接口返回记录由 `AdminApp` 维护为管理端会话态,并传入 `AdminRedeemCodePage` 渲染。页面页签通过 hash 切换时子页面会卸载,不能把最近记录只放在兑换码页面内部 `useState` 中,否则切换到其他页签再返回会展示“暂无记录”。该会话态只用于保留当前操作结果,不作为兑换码历史列表;退出登录或重新登录时清空。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "WELCOME2026"
|
||||
@@ -325,15 +363,46 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
}
|
||||
```
|
||||
|
||||
兑换码管理页进入时必须通过 `GET /admin/api/profile/redeem-codes` 加载数据库已有记录。最近一次接口返回记录仍由 `AdminApp` 维护为管理端会话态,用于展示当前操作结果;历史列表不得依赖该会话态,刷新页面后必须从后端列表接口恢复。列表项点击后回填表单,继续通过同一个 `POST /admin/api/profile/redeem-codes` 修改原记录。
|
||||
|
||||
前端只做基础输入约束,最终标准化、私有码用户解析、次数和奖励合法性以 `server-rs` 为准。
|
||||
|
||||
### 4.8 邀请码管理 contract
|
||||
|
||||
列表请求:
|
||||
|
||||
`GET /admin/api/profile/invite-codes`
|
||||
|
||||
成功返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"userId": "admin:root:SPRING2026",
|
||||
"inviteCode": "SPRING2026",
|
||||
"startsAt": "2026-05-01T00:00:00Z",
|
||||
"expiresAt": "2026-06-01T00:00:00Z",
|
||||
"status": "active",
|
||||
"metadata": {
|
||||
"batch": "spring"
|
||||
},
|
||||
"createdAt": "2026-04-30T00:00:00Z",
|
||||
"updatedAt": "2026-04-30T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
后台邀请码列表只返回后台运营预置码。后端按 `profile_invite_code.user_id` 的 `admin:` 前缀过滤,普通用户在邀请中心生成的个人邀请码不得展示在后台列表中。
|
||||
|
||||
创建/更新请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"inviteCode": "SPRING2026",
|
||||
"startsAt": "2026-05-01T00:00:00Z",
|
||||
"expiresAt": "2026-06-01T00:00:00Z",
|
||||
"metadata": {
|
||||
"batch": "spring"
|
||||
}
|
||||
@@ -346,6 +415,9 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
{
|
||||
"userId": "admin",
|
||||
"inviteCode": "SPRING2026",
|
||||
"startsAt": "2026-05-01T00:00:00Z",
|
||||
"expiresAt": "2026-06-01T00:00:00Z",
|
||||
"status": "active",
|
||||
"metadata": {
|
||||
"batch": "spring"
|
||||
},
|
||||
@@ -356,6 +428,118 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
|
||||
邀请码页的 metadata 输入必须先在前端解析为 JSON 对象;空字符串按 `{}` 处理,数组、字符串、数字等非对象值直接提示错误。最终标准化、长度限制和邀请码合法性以 `server-rs` 为准。
|
||||
|
||||
#### 4.8.1 邀请码有效期语义
|
||||
|
||||
邀请码仍然是“用户稳定邀请身份码”,不做删除或软删除。本轮只增加时间窗字段,用于控制**新填写邀请码**是否允许绑定:
|
||||
|
||||
1. `startsAt` / 后端 `starts_at`:邀请码开始生效时间;为空表示立即生效。
|
||||
2. `expiresAt` / 后端 `expires_at`:邀请码截止时间;为空表示长期有效。
|
||||
3. 两个字段都为空时,邀请码视为长期有效。
|
||||
4. `expiresAt` 采用左闭右开语义:当前时间 `>= expiresAt` 时视为已过期。
|
||||
5. 时间字段在管理 API JSON 中统一使用 ISO 8601 UTC 字符串或 `null`;SpacetimeDB 内部仍按 `Timestamp` 存储,契约层负责转换,前端不得自行假设微秒/毫秒整数。
|
||||
6. 有效期只影响用户之后调用填写邀请码接口建立新邀请关系;已绑定的邀请关系、历史奖励、统计和审计记录不回溯修改。
|
||||
|
||||
字段合法性要求:
|
||||
|
||||
1. `startsAt` 和 `expiresAt` 均允许为空。
|
||||
2. 若两者都存在,必须满足 `startsAt < expiresAt`;相等或开始晚于截止应由后端拒绝,前端可提前提示但不能替代后端校验。
|
||||
3. 后台编辑已有邀请码时,空值代表清空该边界;不要用空字符串写入契约。
|
||||
|
||||
#### 4.8.2 用户填写邀请码的错误优先级与校验逻辑
|
||||
|
||||
填写邀请码时,后端是唯一业务真相。前端只展示后端错误,不复制完整业务规则。推荐校验优先级如下:
|
||||
|
||||
1. **请求身份与输入基础校验**:未登录、空邀请码、格式不合法等请求级错误优先返回。
|
||||
2. **用户自身状态校验**:用户不存在、用户资料不可用、已绑定过邀请关系等与当前用户直接相关的错误优先于邀请码时间窗。
|
||||
3. **邀请码查找**:按标准化后的邀请码查找记录;不存在时返回“邀请码不存在或不可用”。
|
||||
4. **自邀请校验**:邀请码归属用户等于当前用户时,返回“不能填写自己的邀请码”。
|
||||
5. **时间窗校验**:
|
||||
- `starts_at` 存在且当前时间 `< starts_at`,返回“邀请码未生效”。
|
||||
- `expires_at` 存在且当前时间 `>= expires_at`,返回“邀请码已过期”。
|
||||
6. **绑定写入与奖励发放**:只有以上校验全部通过,才写入邀请绑定、奖励或相关流水。
|
||||
|
||||
该顺序的目标是避免用“未生效/已过期”泄露不该暴露的用户状态,同时保证用户看到的错误与实际阻断原因一致。若后续新增风控、封禁、黑名单等规则,应在写入前补入,并在本节同步明确优先级。
|
||||
|
||||
#### 4.8.3 后台邀请码列表状态展示规则
|
||||
|
||||
后台列表状态可由后端返回 `status`,也可在前端用同一规则从 `startsAt` / `expiresAt` 派生;如果两者同时存在,列表展示以后端 `status` 为准,并仅把前端派生结果用于兜底。
|
||||
|
||||
| 条件 | 状态值 | 中文标签 | 展示建议 |
|
||||
| --- | --- | --- | --- |
|
||||
| `startsAt` 存在且当前时间 `< startsAt` | `pending` | 未生效 | 展示开始时间,提示尚不能被新用户填写 |
|
||||
| `expiresAt` 存在且当前时间 `>= expiresAt` | `expired` | 已过期 | 展示截止时间,提示不再允许新绑定 |
|
||||
| 其他情况 | `active` | 有效 | 正常高亮展示 |
|
||||
|
||||
补充展示规则:
|
||||
|
||||
1. 两个字段都为空时状态为 `active`,中文可展示为“长期有效”。
|
||||
2. `startsAt` 为空、`expiresAt` 未来存在时状态为 `active`,中文可展示为“有效至 YYYY-MM-DD HH:mm”。
|
||||
3. `startsAt` 未来、`expiresAt` 为空时状态为 `pending`,中文可展示为“YYYY-MM-DD HH:mm 生效”。
|
||||
4. 列表至少展示邀请码、状态、开始时间、截止时间、更新时间;metadata 可保留折叠/摘要展示,避免挤占移动端宽度。
|
||||
5. 列表状态只用于运营理解,不作为安全边界;真正是否可填写仍以后端 redeem 校验为准。
|
||||
|
||||
### 4.9 后台写操作二次确认规范
|
||||
|
||||
后台所有会修改线上数据的操作,在真正调用 API 前必须二次确认;取消确认时不得发送任何请求。该规范覆盖当前和未来新增的管理写入口,不限于 profile 模块。
|
||||
|
||||
必须二次确认的操作包括但不限于:
|
||||
|
||||
1. 创建/更新兑换码:`POST /admin/api/profile/redeem-codes`。
|
||||
2. 停用兑换码:`POST /admin/api/profile/redeem-codes/disable`。
|
||||
3. 创建/更新邀请码:`POST /admin/api/profile/invite-codes`。
|
||||
4. 创建/更新个人任务配置:`POST /admin/api/profile/tasks`。
|
||||
5. 停用个人任务配置:`POST /admin/api/profile/tasks/disable`。
|
||||
6. 后续任何 `POST` / `PATCH` / `PUT` / `DELETE` 管理接口,只要会修改数据、触发任务、写审计或影响线上配置,均默认纳入确认。
|
||||
|
||||
交互要求:
|
||||
|
||||
1. 确认弹窗必须在 API 调用前出现,确认后才进入 loading 和提交状态。
|
||||
2. 弹窗必须展示操作类型(新增、更新、停用、删除、发布等)、对象标识(如 `code`、`inviteCode`、`taskId`)和影响说明。
|
||||
3. 默认按钮顺序为“取消 / 确认”,取消不应有危险色;危险操作(停用、删除、覆盖线上配置)确认按钮使用警示样式。
|
||||
4. 弹窗文案统一提示“该操作会立即影响线上数据”,但不要在页面常驻展示大段规则说明。
|
||||
5. 支持键盘和移动端:Esc 或取消按钮关闭;移动端弹窗宽度自适应,不遮挡关键对象信息。
|
||||
6. loading 期间锁定确认按钮和原页面提交按钮,避免重复写入。
|
||||
7. 成功后按现有页面规则刷新列表或合并返回记录;失败时展示后端错误,不能静默关闭为成功。
|
||||
|
||||
建议抽象通用确认能力,例如 `confirmAdminWriteAction({ actionLabel, targetLabel, riskLevel, onConfirm })` 或通用 `AdminConfirmDialog`,页面只传入对象与回调,避免每个页面重复实现不同交互。
|
||||
|
||||
#### 4.9.1 二次确认文案模板
|
||||
|
||||
```text
|
||||
标题:确认{操作类型}{对象类型}
|
||||
正文:即将{操作类型}「{对象标识}」。该操作会立即影响线上数据。
|
||||
取消按钮:取消
|
||||
确认按钮:确认{操作类型}
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
1. `确认更新邀请码`:即将更新「SPRING2026」的有效期与 metadata。该操作会立即影响线上数据。
|
||||
2. `确认停用兑换码`:即将停用「WELCOME2026」。该操作会立即影响线上数据。
|
||||
3. `确认更新任务配置`:即将更新「daily_login」。该操作会立即影响线上数据。
|
||||
|
||||
### 4.10 邀请码有效期与二次确认改动范围
|
||||
|
||||
实现本设计时预期改动范围如下,未列出的层级不要擅自承接业务规则:
|
||||
|
||||
1. `server-rs/crates/spacetime-module/src/runtime/profile.rs`:邀请码表结构、upsert、redeem 时间窗校验与后台列表投影。
|
||||
2. `server-rs/crates/spacetime-module/src/migration.rs`:旧邀请码记录迁移,默认 `starts_at = None`、`expires_at = None`。
|
||||
3. `server-rs/crates/shared-contracts/src/**`:管理请求/响应 DTO 增加 `startsAt`、`expiresAt`、`status` 等字段。
|
||||
4. `server-rs/crates/spacetime-client/src/module_bindings/**` 与 mapper:按表结构变更重新生成/补齐绑定字段。
|
||||
5. `server-rs/crates/api-server/src/runtime_profile.rs`:接收、校验、转发并返回邀请码时间窗字段;保持错误 envelope 兼容后台读取逻辑。
|
||||
6. `apps/admin-web/src/api/adminApiTypes.ts` 与 `adminApiClient.ts`:同步契约字段,不在 client 层写业务判断。
|
||||
7. `apps/admin-web/src/pages/AdminInviteCodePage.tsx`:有效期表单、列表状态展示、保存前确认。
|
||||
8. `apps/admin-web/src/pages/AdminRedeemCodePage.tsx`、`AdminTaskConfigPage.tsx` 及后续写页面:统一接入写操作二次确认。
|
||||
9. `apps/admin-web/src/styles/admin.css`:状态标签、确认弹窗与移动端样式。
|
||||
|
||||
验证建议:
|
||||
|
||||
1. 服务端单测覆盖:未生效邀请码拒绝、已过期邀请码拒绝、有效时间窗可绑定、空时间窗长期有效、已绑定关系不受后续过期影响。
|
||||
2. 管理 API 覆盖:upsert 能写入/清空 `startsAt`、`expiresAt`;列表返回状态正确;`startsAt >= expiresAt` 被拒绝。
|
||||
3. 前端交互覆盖:点击保存/停用不会直接请求,取消确认不请求,确认后只请求一次,失败展示后端错误。
|
||||
4. 回归兑换码与任务配置页面,确认所有写操作均有统一二次确认。
|
||||
5. 修改后端时按项目约束运行对应 Rust 测试、`npm run api-server` 联调和 `/healthz`;修改前端时运行 `npm run admin-web:typecheck`、`npm run admin-web:build`;文档或中文改动后运行 `npm run check:encoding`。
|
||||
|
||||
## 5. 鉴权与会话
|
||||
|
||||
1. token key 固定为 `genarrative_admin_token`。
|
||||
@@ -372,19 +556,22 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
3. 总览页加载失败时展示后端错误,不吞掉 `fetchErrors`。
|
||||
4. API 调试页的 headers 使用键值行编辑,提交前转为 `[{ name, value }]`。
|
||||
5. 兑换码页的 `mode=private` 时展示允许用户输入区;其他模式提交空数组。
|
||||
6. 邀请码页只提交 `inviteCode` 与 JSON 对象 metadata,不在前端复制后端邀请码规则。
|
||||
7. 所有按钮的 loading 状态必须锁定重复提交。
|
||||
8. 移动端优先:表单单列,导航紧凑,结果面板可横向/纵向滚动。
|
||||
6. 兑换码页和邀请码页进入时加载数据库列表,保存后合并返回记录,点击列表项回填表单进入编辑态。
|
||||
7. 邀请码页只提交 `inviteCode` 与 JSON 对象 metadata,不在前端复制后端邀请码规则。
|
||||
8. 所有按钮的 loading 状态必须锁定重复提交。
|
||||
9. 移动端优先:表单单列,导航紧凑,结果面板可横向/纵向滚动。
|
||||
|
||||
## 7. 部署与联调
|
||||
|
||||
### 7.1 本地联调
|
||||
|
||||
1. 启动后端:`npm run api-server`。
|
||||
2. 启动后台前端:在 `apps/admin-web` 执行 `npm run dev`。
|
||||
3. 后台 dev server 通过 Vite proxy 转发 `/admin/api` 到 `ADMIN_API_TARGET`;未配置时默认 `http://127.0.0.1:3100`。
|
||||
4. 若使用非 3100 端口,在仓库根目录 `.env.local` 设置 `ADMIN_API_TARGET=http://127.0.0.1:<api-server-port>`,并重启后台前端 dev server。
|
||||
5. `GENARRATIVE_API_PORT` 控制 Rust `api-server` 监听端口;`ADMIN_API_TARGET` 只控制后台前端 dev proxy 目标,二者需要指向同一个端口。
|
||||
1. 完整本地栈直接在仓库根目录执行 `npm run dev`。
|
||||
2. `npm run dev` 默认启动 SpacetimeDB standalone、Rust `api-server`、主站 Vite 和后台 Vite。
|
||||
3. 主站默认地址为 `http://127.0.0.1:3000`,后台可从主站 `http://127.0.0.1:3000/admin/` 进入,也可直连 `http://127.0.0.1:3102`。
|
||||
4. 主站 Vite 会把 `/admin/` 转发到后台 dev server,贴近生产同域 `/admin/` 入口。
|
||||
5. 后台 dev server 通过 Vite proxy 转发 `/admin/api` 到当前 Rust API 地址;`--api-port` 改动时脚本会同步注入 `ADMIN_API_TARGET`。
|
||||
6. 如需单独启动后台前端,可继续执行根脚本 `npm run admin-web:dev`,或在 `apps/admin-web` 执行 `npm run dev`;单独启动时未配置 `ADMIN_API_TARGET` 会默认代理到 `http://127.0.0.1:3100`。
|
||||
7. 后台 dev 端口可用 `npm run dev -- --admin-web-port <port>` 覆盖。
|
||||
|
||||
### 7.2 构建部署
|
||||
|
||||
@@ -430,8 +617,8 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
- token 恢复、过期清理、退出登录。
|
||||
- 总览页正常数据、部分表统计失败、整体请求失败。
|
||||
- API 调试成功访问 `/healthz`,绝对 URL 被后端拒绝。
|
||||
- 兑换码 public/unique/private 表单提交和停用。
|
||||
- 邀请码表单提交、metadata JSON 对象校验和结果展示。
|
||||
- 兑换码数据库列表加载、列表点击回填、public/unique/private 表单提交和停用。
|
||||
- 邀请码数据库列表加载、普通用户邀请码不展示、列表点击回填、metadata JSON 对象校验和结果展示。
|
||||
2. 根工程:
|
||||
- `npm run check:encoding`。
|
||||
- 后续接入根 workspace 后,补充后台工程 build/typecheck 脚本。
|
||||
|
||||
69
docs/technical/ALIYUN_SMS_TIMESTAMP_FORMAT_FIX_2026-05-07.md
Normal file
69
docs/technical/ALIYUN_SMS_TIMESTAMP_FORMAT_FIX_2026-05-07.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 短信验证码阿里云时间戳格式修复(2026-05-07)
|
||||
|
||||
## 背景
|
||||
|
||||
使用阿里云短信验证码真实 provider 发送验证码时,接口返回:
|
||||
|
||||
```text
|
||||
短信验证码发送失败:Specified time stamp or date value is not well formatted.
|
||||
```
|
||||
|
||||
该错误来自阿里云 OpenAPI 网关对签名请求头 `x-acs-date` 的格式校验。
|
||||
|
||||
## 根因
|
||||
|
||||
`server-rs/crates/platform-auth/src/lib.rs` 中阿里云 ACS3 签名逻辑会构造 `x-acs-date` 请求头。
|
||||
|
||||
原实现使用 `time::format_description::well_known::Rfc3339`,当 `OffsetDateTime::now_utc()` 带纳秒时会生成形如:
|
||||
|
||||
```text
|
||||
2026-05-07T14:23:59.364767Z
|
||||
```
|
||||
|
||||
阿里云 ACS3 签名要求 `x-acs-date` 使用不带小数秒的 UTC ISO 8601 格式:
|
||||
|
||||
```text
|
||||
yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||
```
|
||||
|
||||
即:
|
||||
|
||||
```text
|
||||
2026-05-07T14:23:59Z
|
||||
```
|
||||
|
||||
带小数秒的时间戳会被阿里云网关判定为格式非法,从而返回 `Specified time stamp or date value is not well formatted.`。
|
||||
|
||||
## 修复方案
|
||||
|
||||
将 `current_aliyun_timestamp()` 改为手动输出不带小数秒的 UTC ISO 8601 格式:
|
||||
|
||||
```text
|
||||
yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||
```
|
||||
|
||||
并新增单元测试,确保:
|
||||
|
||||
- 长度等于 `2026-05-07T12:34:56Z`;
|
||||
- 固定位置包含 `-`、`T`、`:`、`Z`;
|
||||
- 不包含小数点;
|
||||
- 除固定分隔符外均为数字。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅影响阿里云短信验证码 provider 的请求签名头 `x-acs-date`。
|
||||
- 不改动短信模板、签名、验证码业务参数。
|
||||
- 不改动 mock 短信 provider。
|
||||
- 不涉及前端接口契约变化。
|
||||
|
||||
## 验收
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo test -p platform-auth aliyun -- --nocapture
|
||||
cargo fmt -p platform-auth --check
|
||||
```
|
||||
|
||||
预期:相关测试通过,格式检查通过。
|
||||
@@ -0,0 +1,367 @@
|
||||
# Analytics Date Dimension 与个人任务埋点范围收口记录(2026-05-04)
|
||||
|
||||
## 背景
|
||||
|
||||
本记录用于收口 `.hermes/plans/2026-05-04_022223-analytics-time-dimension-mapping.md` 中当前阶段已经落地的内容,并明确尚未执行的后续范围。
|
||||
|
||||
本阶段目标不是完整上线运营统计查询,而是先完成两件基础工作:
|
||||
|
||||
1. 收紧个人任务系统的埋点范围,避免运营或接口把个人任务错误配置为 `site`、`module`、`work` 等非用户维度。
|
||||
2. 新增统一日期维表 `analytics_date_dimension`,为后续按周、月、季、年聚合埋点数据提供稳定的日期 bucket 映射。
|
||||
|
||||
## 当前已完成范围
|
||||
|
||||
### 1. 个人任务埋点范围锁定为 User
|
||||
|
||||
当前个人任务系统首版只支持用户维度埋点。
|
||||
|
||||
已完成:
|
||||
|
||||
- Admin 任务配置页不再展示“埋点范围”选择。
|
||||
- Admin 保存任务配置时固定传 `scopeKind: 'user'`。
|
||||
- API 层拒绝非 `user` 的个人任务配置。
|
||||
- 领域输入构造层拒绝非 `User` 的个人任务配置。
|
||||
- `Work => user_id` 的错误映射已移除。
|
||||
- 任务进度刷新、任务中心快照、领奖链路遇到非 `User` 的异常个人任务配置时显式报错,不再静默按 0 进度处理。
|
||||
|
||||
相关文件:
|
||||
|
||||
```text
|
||||
apps/admin-web/src/pages/AdminTaskConfigPage.tsx
|
||||
apps/admin-web/src/api/adminApiTypes.ts
|
||||
server-rs/crates/api-server/src/runtime_profile.rs
|
||||
server-rs/crates/module-runtime/src/commands.rs
|
||||
server-rs/crates/module-runtime/src/errors.rs
|
||||
server-rs/crates/spacetime-module/src/runtime/profile.rs
|
||||
```
|
||||
|
||||
### 2. 日期维表领域模型与纯函数
|
||||
|
||||
已在 `module-runtime` 中补充日期维表快照和纯函数。
|
||||
|
||||
日期维表使用现有北京时间业务日 `day_key` 语义:
|
||||
|
||||
```text
|
||||
date_key = floor((occurred_at_micros + 8h) / 1d)
|
||||
```
|
||||
|
||||
已完成能力:
|
||||
|
||||
- 从 `YYYY-MM-DD` 解析业务日 `date_key`。
|
||||
- 从 `date_key` 构造日期维表快照。
|
||||
- 生成 ISO weekday:周一=1,周日=7。
|
||||
- 生成 ISO week key:`YYYYWW`,跨年周按 ISO week-year。
|
||||
- 生成 week/month/quarter/year 的 key 和起止 `date_key`。
|
||||
- 限制日期维表支持范围为:
|
||||
- `2000-01-01`
|
||||
- 到 `2100-12-31`
|
||||
|
||||
相关文件:
|
||||
|
||||
```text
|
||||
server-rs/crates/module-runtime/src/domain.rs
|
||||
server-rs/crates/module-runtime/src/application.rs
|
||||
server-rs/crates/module-runtime/src/lib.rs
|
||||
```
|
||||
|
||||
### 3. SpacetimeDB 日期维表与 reducer
|
||||
|
||||
已新增 SpacetimeDB 表:
|
||||
|
||||
```text
|
||||
analytics_date_dimension
|
||||
```
|
||||
|
||||
表字段包括:
|
||||
|
||||
```text
|
||||
date_key
|
||||
calendar_date
|
||||
weekday
|
||||
iso_week_key
|
||||
week_start_date_key
|
||||
week_end_date_key
|
||||
month_key
|
||||
month_start_date_key
|
||||
month_end_date_key
|
||||
quarter_key
|
||||
quarter_start_date_key
|
||||
quarter_end_date_key
|
||||
year_key
|
||||
year_start_date_key
|
||||
year_end_date_key
|
||||
created_at
|
||||
updated_at
|
||||
```
|
||||
|
||||
已新增索引:
|
||||
|
||||
```text
|
||||
iso_week_key
|
||||
month_key
|
||||
quarter_key
|
||||
year_key
|
||||
```
|
||||
|
||||
已新增 reducer:
|
||||
|
||||
```text
|
||||
ensure_analytics_date_dimension_for_date
|
||||
seed_analytics_date_dimensions
|
||||
```
|
||||
|
||||
当前 reducer 行为:
|
||||
|
||||
- `ensure` 单日幂等补齐。
|
||||
- `seed` 按日期范围幂等补齐。
|
||||
- `seed` 拒绝 `start_date > end_date`。
|
||||
- `seed` 单次最多允许 `ANALYTICS_DATE_DIMENSION_MAX_SEED_DAYS = 3660` 天。
|
||||
- 裸 `date_key` 进入 ensure 前先做支持范围校验,避免极端整数进入日历算法。
|
||||
|
||||
相关文件:
|
||||
|
||||
```text
|
||||
server-rs/crates/spacetime-module/src/runtime/analytics_date_dimension.rs
|
||||
server-rs/crates/spacetime-module/src/runtime/mod.rs
|
||||
server-rs/crates/spacetime-module/src/migration.rs
|
||||
docs/technical/SPACETIMEDB_TABLE_CATALOG.md
|
||||
```
|
||||
|
||||
### 4. SpacetimeDB Rust client bindings
|
||||
|
||||
已按项目脚本生成 Rust bindings,并在生成参数中显式包含 private tables/functions:
|
||||
|
||||
```bash
|
||||
PATH="/tmp/spacetime-bin:$PATH" npm run spacetime:generate -- --rust-only
|
||||
```
|
||||
|
||||
本次已修改生成脚本:
|
||||
|
||||
```text
|
||||
scripts/generate-spacetime-bindings.mjs
|
||||
```
|
||||
|
||||
在 `spacetime generate` 参数中加入:
|
||||
|
||||
```text
|
||||
--include-private
|
||||
```
|
||||
|
||||
说明:SpacetimeDB CLI 2.1.0 的参数名是 `--include-private`,不是 `--non-private`。该参数含义是将 private tables/functions 也包含进生成代码,满足 api-server 通过 Rust bindings 访问 module private table/reducer 的需求。
|
||||
|
||||
```text
|
||||
spacetimedb tool version 2.1.0; spacetimedb-lib version 2.1.0
|
||||
```
|
||||
|
||||
生成脚本:
|
||||
|
||||
```text
|
||||
scripts/generate-spacetime-bindings.mjs
|
||||
```
|
||||
|
||||
已新增 analytics date dimension 相关 bindings:
|
||||
|
||||
```text
|
||||
server-rs/crates/spacetime-client/src/module_bindings/analytics_date_dimension_ensure_input_type.rs
|
||||
server-rs/crates/spacetime-client/src/module_bindings/analytics_date_dimension_seed_input_type.rs
|
||||
server-rs/crates/spacetime-client/src/module_bindings/analytics_date_dimension_type.rs
|
||||
server-rs/crates/spacetime-client/src/module_bindings/analytics_date_dimension_table.rs
|
||||
server-rs/crates/spacetime-client/src/module_bindings/ensure_analytics_date_dimension_for_date_reducer.rs
|
||||
server-rs/crates/spacetime-client/src/module_bindings/seed_analytics_date_dimensions_reducer.rs
|
||||
```
|
||||
|
||||
并更新了:
|
||||
|
||||
```text
|
||||
server-rs/crates/spacetime-client/src/module_bindings/mod.rs
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- `analytics_date_dimension` 表当前是 private table;由于生成脚本已加 `--include-private`,本次 codegen 已生成 `analytics_date_dimension_table.rs`,可通过 `ctx.db.analytics_date_dimension()` 访问 client cache / query builder。
|
||||
- bindings 目录是自动生成产物,本次以项目脚本整体刷新,除新增 analytics 文件外,也带来了大量已存在 table/reducer/procedure 文件的格式化/生成器输出差异。
|
||||
|
||||
### 5. 测试覆盖
|
||||
|
||||
已新增测试:
|
||||
|
||||
```text
|
||||
server-rs/crates/module-runtime/tests/analytics_date_dimension.rs
|
||||
server-rs/crates/module-runtime/tests/profile_task_scope.rs
|
||||
server-rs/crates/shared-contracts/tests/profile_task_contract.rs
|
||||
```
|
||||
|
||||
覆盖重点:
|
||||
|
||||
- `2024-02-29` 闰年。
|
||||
- `2025-12-29` ISO week 跨年。
|
||||
- `2026-01-01` 跨年周。
|
||||
- `2026-03-31` Q1 结束。
|
||||
- `2026-04-01` Q2 开始。
|
||||
- `2026-12-31` 年末。
|
||||
- 非法日期解析失败。
|
||||
- 超出日期维表支持范围失败。
|
||||
- 个人任务 `scopeKind=user` 成功。
|
||||
- 个人任务 `scopeKind=site/module/work` 失败。
|
||||
- `work` scope 不会静默映射到 `user_id`。
|
||||
- Admin 个人任务配置 contract 保持 `scopeKind: user`。
|
||||
|
||||
## 已验证命令
|
||||
|
||||
从 `server-rs/` 执行:
|
||||
|
||||
```bash
|
||||
cargo fmt -p module-runtime -p spacetime-module -p spacetime-client
|
||||
cargo test -p spacetime-client --no-run
|
||||
cargo test -p spacetime-module --no-run
|
||||
cargo test -p module-runtime --test analytics_date_dimension
|
||||
cargo test -p module-runtime --test profile_task_scope
|
||||
cargo test -p shared-contracts --test profile_task_contract
|
||||
```
|
||||
|
||||
从项目根目录执行:
|
||||
|
||||
```bash
|
||||
npm run admin-web:typecheck
|
||||
```
|
||||
|
||||
当前结果:
|
||||
|
||||
- `spacetime-client --no-run` 编译通过。
|
||||
- `spacetime-module --no-run` 编译通过。
|
||||
- `analytics_date_dimension` 测试通过:8 passed。
|
||||
- `profile_task_scope` 测试通过:3 passed。
|
||||
- `profile_task_contract` 测试通过:2 passed。
|
||||
- `admin-web:typecheck` 通过。
|
||||
|
||||
已知非本阶段阻塞:
|
||||
|
||||
- 完整运行 `cargo test -p spacetime-module` 时,曾出现既有 puzzle 测试失败:
|
||||
|
||||
```text
|
||||
puzzle::tests::puzzle_preview_is_publishable_with_complete_draft FAILED
|
||||
assertion failed: preview.publish_ready
|
||||
```
|
||||
|
||||
该失败与当前埋点范围和日期维表改动无直接关系,本阶段以 `cargo test -p spacetime-module --no-run` 作为编译门禁。
|
||||
|
||||
## 当前未完成 / 暂缓项
|
||||
|
||||
### 1. 暂未新增 spacetime-client facade
|
||||
|
||||
当前没有新增:
|
||||
|
||||
```text
|
||||
SpacetimeClient::ensure_analytics_date_dimension_for_date
|
||||
SpacetimeClient::seed_analytics_date_dimensions
|
||||
```
|
||||
|
||||
原因:
|
||||
|
||||
- 生成脚本已加入 `--include-private`,private reducer/type/table bindings 已可用于后续 facade 实现。
|
||||
- 但 Step 7/8/9 暂缓,尚未由 `api-server` 或统计查询链路调用该能力。
|
||||
- 如后续只是 SpacetimeDB module 内部写入统计时 ensure,可以直接复用 module 内部 helper,不一定需要远程 client facade。
|
||||
- 若后续需要由 API 或运维接口触发 seed/ensure,可基于本次已生成的 reducer bindings 再补 facade。
|
||||
|
||||
### 2. Step 7/8/9 暂缓
|
||||
|
||||
本阶段未接入:
|
||||
|
||||
- 事件写入链路自动 ensure 日期维表。
|
||||
- 聚合查询 API 的 `granularity = day | week | month | quarter | year`。
|
||||
- shared contracts / 前端 analytics contracts。
|
||||
- 历史事件回填。
|
||||
|
||||
这些应作为后续阶段单独设计和落地。
|
||||
|
||||
## 后续建议顺序
|
||||
|
||||
1. 如需提交本阶段改动,确认是否接受 `module_bindings` 整体刷新带来的大量生成文件 diff。
|
||||
2. 如希望 diff 更小,可评估仅提交 analytics date dimension 相关生成文件与 `mod.rs`;但需要非常谨慎,因为 `module_bindings` 是自动生成产物。
|
||||
3. 如需要由 `api-server` 触发 seed/ensure,再补 `spacetime-client` facade。
|
||||
4. 进入 Step 7/8/9:事件写入链路、聚合查询 API、前端 contracts。
|
||||
|
||||
## Step 7/8/9 后续接入记录(2026-05-04)
|
||||
|
||||
本次继续推进此前暂缓的 Step 7/8/9 中“按日期维度聚合查询 API / contracts / client facade”部分。
|
||||
|
||||
### 已新增能力
|
||||
|
||||
1. `module-runtime` 新增 analytics metric 聚合领域类型与纯函数:
|
||||
- `AnalyticsGranularity = day | week | month | quarter | year`
|
||||
- `AnalyticsMetricQueryInput`
|
||||
- `AnalyticsBucketMetric`
|
||||
- `AnalyticsMetricQueryResponse`
|
||||
- `aggregate_runtime_tracking_daily_stats(...)`
|
||||
|
||||
2. `spacetime-module` 新增 `query_analytics_metric` procedure,直接聚合 tracking daily stat,输出按 bucket 排序的统计结果。
|
||||
|
||||
3. `spacetime-client` 新增 facade:
|
||||
|
||||
```rust
|
||||
SpacetimeClient::query_analytics_metric(event_key, scope_kind, scope_id, granularity)
|
||||
```
|
||||
|
||||
4. `api-server` 新增登录态接口:
|
||||
|
||||
```http
|
||||
GET /api/profile/analytics/metric?eventKey=...&scopeKind=user&scopeId=...&granularity=day
|
||||
```
|
||||
|
||||
请求参数:
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `eventKey` | 埋点事件 key,必填 |
|
||||
| `scopeKind` | `site | work | module | user` |
|
||||
| `scopeId` | 对应范围 ID,必填 |
|
||||
| `granularity` | `day | week | month | quarter | year` |
|
||||
|
||||
响应 data:
|
||||
|
||||
```ts
|
||||
type AnalyticsMetricQueryResponse = {
|
||||
buckets: Array<{
|
||||
bucketKey: string;
|
||||
bucketStartDateKey: number;
|
||||
bucketEndDateKey: number;
|
||||
value: number;
|
||||
}>;
|
||||
};
|
||||
```
|
||||
|
||||
5. shared contracts / 前端 shared contracts 已新增 analytics query 类型:
|
||||
- `AnalyticsMetricQueryRequest`
|
||||
- `AnalyticsMetricQueryResponse`
|
||||
- `AnalyticsBucketMetricResponse` / `AnalyticsBucketMetric`
|
||||
- `AnalyticsGranularity`
|
||||
|
||||
### 本次验证
|
||||
|
||||
从 `server-rs/` 执行通过:
|
||||
|
||||
```bash
|
||||
cargo test -p module-runtime --test analytics_granularity
|
||||
cargo check -p spacetime-module
|
||||
cargo check -p spacetime-client
|
||||
cargo check -p api-server
|
||||
```
|
||||
|
||||
验证结果:
|
||||
|
||||
- `analytics_granularity` 测试通过:3 passed。
|
||||
- `spacetime-module` 编译通过,仅存在既有 dead_code warnings。
|
||||
- `spacetime-client` 编译通过。
|
||||
- `api-server` 编译通过,仅存在既有 prompt dead_code warnings。
|
||||
|
||||
### 注意事项
|
||||
|
||||
当前环境未检测到 `spacetime` / `spacetimedb` CLI,因此 analytics metric 相关 `module_bindings` 是按现有生成物结构手动补齐的临时生成物。后续有 CLI 的开发机应优先通过项目脚本重新生成 bindings,并复核手写生成物是否可被正式生成输出覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 阶段结论
|
||||
|
||||
当前阶段已经完成“个人任务埋点范围收紧”和“日期维表 module 侧能力”的核心落地,并已生成 SpacetimeDB Rust client bindings。
|
||||
|
||||
剩余工作不再是 bindings 环境阻塞,而是后续业务接入范围:是否增加 `spacetime-client` facade,以及是否继续推进事件写入链路、聚合查询 API 和前端 analytics contracts。
|
||||
@@ -0,0 +1,118 @@
|
||||
# api-server 外部服务环境变量配置 2026-05-07
|
||||
|
||||
## 背景
|
||||
|
||||
`server-rs/crates/api-server/src/config.rs` 统一收口 api-server 启动配置。外部服务分为两类:
|
||||
|
||||
1. 公共服务:阿里云、腾讯云、微信等对外公开且接口域名稳定的服务。
|
||||
2. 非公共服务:团队自选模型网关、图片网关、视频模型、内部兼容服务等,URL 与模型名可能随部署、供应商或账号策略变化。
|
||||
|
||||
本次约定:公共服务 URL 可以保留代码默认值;非公共服务的 URL 与模型名必须通过环境变量提供,不再在 `config.rs` 写死具体模型名称或私有网关地址。
|
||||
|
||||
## 公共服务默认值
|
||||
|
||||
以下默认值属于公共服务稳定接口,可继续保留在代码或示例环境中:
|
||||
|
||||
```text
|
||||
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/api/v1
|
||||
ALIYUN_SMS_ENDPOINT=dypnsapi.aliyuncs.com
|
||||
WECHAT_AUTHORIZE_ENDPOINT=https://open.weixin.qq.com/connect/qrconnect
|
||||
WECHAT_ACCESS_TOKEN_ENDPOINT=https://api.weixin.qq.com/sns/oauth2/access_token
|
||||
WECHAT_USER_INFO_ENDPOINT=https://api.weixin.qq.com/sns/userinfo
|
||||
```
|
||||
|
||||
说明:DashScope 属于阿里云公开服务,基础 URL 可保留;具体图片模型名不属于稳定公共接口,必须由环境变量配置。
|
||||
|
||||
## 非公共服务必配项
|
||||
|
||||
生产环境或真实联调使用到对应能力时,应显式配置以下变量:
|
||||
|
||||
```text
|
||||
# 文本 LLM 网关
|
||||
GENARRATIVE_LLM_PROVIDER=openai-compatible
|
||||
GENARRATIVE_LLM_BASE_URL=
|
||||
GENARRATIVE_LLM_API_KEY=
|
||||
GENARRATIVE_LLM_MODEL=
|
||||
|
||||
# APIMart / OpenAI 兼容 Responses 文本网关
|
||||
APIMART_BASE_URL=
|
||||
APIMART_API_KEY=
|
||||
|
||||
# VectorEngine / GPT-image-2 / Suno / Vidu 生成网关
|
||||
VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai
|
||||
VECTOR_ENGINE_API_KEY=
|
||||
VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=180000
|
||||
VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS=180000
|
||||
|
||||
# Hyper3D Rodin Gen-2 3D 模型生成
|
||||
HYPER3D_BASE_URL=https://api.hyper3d.com/api/v2
|
||||
HYPER3D_API_KEY=
|
||||
HYPER3D_MODEL_REQUEST_TIMEOUT_MS=180000
|
||||
|
||||
# 火山引擎豆包语音 ASR / TTS
|
||||
VOLCENGINE_SPEECH_API_KEY=
|
||||
VOLCENGINE_SPEECH_APP_ID=
|
||||
VOLCENGINE_SPEECH_ACCESS_KEY=
|
||||
VOLCENGINE_SPEECH_ASR_RESOURCE_ID=volc.seedasr.sauc.concurrent
|
||||
VOLCENGINE_SPEECH_TTS_RESOURCE_ID=seed-tts-2.0
|
||||
VOLCENGINE_SPEECH_REQUEST_TIMEOUT_MS=180000
|
||||
VOLCENGINE_SPEECH_ASR_WS_URL=wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async
|
||||
VOLCENGINE_SPEECH_TTS_BIDIRECTION_WS_URL=wss://openspeech.bytedance.com/api/v3/tts/bidirection
|
||||
VOLCENGINE_SPEECH_TTS_SSE_URL=https://openspeech.bytedance.com/api/v3/tts/unidirectional/sse
|
||||
|
||||
# DashScope 图片模型名
|
||||
DASHSCOPE_SCENE_IMAGE_MODEL=
|
||||
DASHSCOPE_REFERENCE_IMAGE_MODEL=
|
||||
DASHSCOPE_COVER_IMAGE_MODEL=
|
||||
|
||||
# Ark / 角色视频模型网关
|
||||
ARK_CHARACTER_VIDEO_BASE_URL=
|
||||
ARK_CHARACTER_VIDEO_API_KEY=
|
||||
ARK_CHARACTER_VIDEO_MODEL=
|
||||
ARK_CHARACTER_VIDEO_REQUEST_TIMEOUT_MS=420000
|
||||
```
|
||||
|
||||
## 兼容变量
|
||||
|
||||
为降低部署切换成本,当前代码仍兼容部分历史变量:
|
||||
|
||||
```text
|
||||
GENARRATIVE_LLM_BASE_URL / LLM_BASE_URL
|
||||
GENARRATIVE_LLM_MODEL / LLM_MODEL / VITE_LLM_MODEL
|
||||
GENARRATIVE_LLM_API_KEY / LLM_API_KEY / ARK_API_KEY
|
||||
DASHSCOPE_SCENE_IMAGE_MODEL / DASHSCOPE_IMAGE_MODEL
|
||||
DASHSCOPE_REFERENCE_IMAGE_MODEL / DASHSCOPE_IMAGE_EDIT_MODEL
|
||||
DASHSCOPE_COVER_IMAGE_MODEL / DASHSCOPE_IMAGE_MODEL
|
||||
ARK_CHARACTER_VIDEO_BASE_URL / ARK_BASE_URL / GENARRATIVE_LLM_BASE_URL / LLM_BASE_URL
|
||||
ARK_CHARACTER_VIDEO_API_KEY / ARK_API_KEY / GENARRATIVE_LLM_API_KEY / LLM_API_KEY
|
||||
ARK_CHARACTER_VIDEO_MODEL / DASHSCOPE_CHARACTER_VIDEO_MODEL
|
||||
VOLCENGINE_SPEECH_API_KEY / VOLCENGINE_API_KEY
|
||||
VOLCENGINE_SPEECH_APP_ID / VOLCENGINE_ACCESS_KEY_ID
|
||||
VOLCENGINE_SPEECH_ACCESS_KEY / VOLCENGINE_SECRET_ACCESS_KEY
|
||||
HYPER3D_BASE_URL / RODIN_BASE_URL
|
||||
HYPER3D_API_KEY / RODIN_API_KEY
|
||||
HYPER3D_MODEL_REQUEST_TIMEOUT_MS / RODIN_MODEL_REQUEST_TIMEOUT_MS
|
||||
```
|
||||
|
||||
## 运行时行为
|
||||
|
||||
1. `AppConfig::default()` 不再包含具体非公共模型名或私有网关 URL。
|
||||
2. `AppConfig::from_env()` 会从环境变量读取非公共模型名和 URL。
|
||||
3. 文本 LLM provider 为 `ark` 且未配置 `GENARRATIVE_LLM_BASE_URL` 时,仍回退到 Ark 公开基础 URL。
|
||||
4. 角色视频 provider 复用 Ark 且未配置 `ARK_CHARACTER_VIDEO_BASE_URL` 时,仍回退到 Ark 公开基础 URL。
|
||||
5. 具体模型名缺失时不在配置层伪造默认模型,调用到对应能力时由下游配置校验返回缺配置错误。
|
||||
6. VectorEngine 图片与音频生成只读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY`,其中 GPT-image-2 图片生成额外读取 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;不复用 `APIMART_*`、`GENARRATIVE_LLM_*` 或前端变量。
|
||||
7. 火山引擎语音能力由 `platform-speech` 收口协议帧与上游鉴权,`api-server` 只暴露平台鉴权后的代理路由,不向前端返回任何密钥字段。
|
||||
8. Hyper3D Rodin Gen-2 使用公开默认 `https://api.hyper3d.com/api/v2`,API Key 只读取 `HYPER3D_API_KEY` / `RODIN_API_KEY`,不复用文本 LLM、图片或音频网关密钥。
|
||||
9. APIMart 当前只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态理解链路;GPT-image-2 图片生成不得再读取 APIMart 配置。
|
||||
10. 本地 `npm run api-server`、`npm run dev:rust` 与 `npm run dev` 的环境文件优先级固定为外层 shell 变量最高,其后 `.env`、`.env.local`、`.env.secrets.local` 逐层覆盖;真实密钥建议放在 `.env.secrets.local`,防止 `.env` 中的空示例值覆盖私密配置。
|
||||
|
||||
## 示例文件
|
||||
|
||||
生产示例环境变量维护在:
|
||||
|
||||
```text
|
||||
deploy/env/api-server.env.example
|
||||
```
|
||||
|
||||
真实密钥、内部网关 URL 和具体模型名只应写入服务器 `/etc/genarrative/api-server.env` 或本地未提交的 `.env.local` / `.env.secrets.local`,不得提交到仓库。
|
||||
@@ -49,7 +49,7 @@ npm run build
|
||||
npm run check:content
|
||||
```
|
||||
|
||||
后端代码变更后,按项目约束还需要用 `npm run api-server:maincloud` 做一次启动验证。
|
||||
后端代码变更后,按项目约束还需要用 `npm run api-server` 做一次启动验证。
|
||||
|
||||
本轮最终结果:
|
||||
|
||||
@@ -58,7 +58,7 @@ npm run check:content
|
||||
- `cargo test --manifest-path server-rs\Cargo.toml` 已通过,结果同 `api-server` 默认测试。
|
||||
- `npm test` 已通过,结果为 `160 passed` 个测试文件、`704 passed` 个用例。
|
||||
- `npm run typecheck`、`npm run build`、`npm run check:content`、`npm run check:encoding`、`git diff --check` 已通过。
|
||||
- `npm run api-server:maincloud` 已完成启动烟测,`/healthz` 返回 `200`;期间 Maincloud 订阅恢复出现 `503` warning,但未阻止服务启动。
|
||||
- `npm run api-server` 已完成启动烟测,`/healthz` 返回 `200`;期间 Maincloud 订阅恢复出现 `503` warning,但未阻止服务启动。
|
||||
|
||||
仍需单独处理的非本轮阻塞:
|
||||
|
||||
|
||||
37
docs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md
Normal file
37
docs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# `AuthGate` 登录后又回到未登录状态修复
|
||||
|
||||
日期:`2026-05-09`
|
||||
|
||||
## 背景
|
||||
|
||||
本地联调中,手机号验证码登录有时会先显示登录成功,随后又瞬间回到未登录态。
|
||||
|
||||
## 根因
|
||||
|
||||
`AuthGate` 首次挂载时会异步 hydrate:
|
||||
|
||||
1. 先轮换 refresh cookie
|
||||
2. 再请求 `/api/auth/me`
|
||||
3. 再根据结果写入 `user` 和 `status`
|
||||
|
||||
如果用户在这轮 hydrate 尚未完成时已经完成了登录,后到达的旧 hydrate 结果仍可能把刚写入的 `user` 覆盖回 `null`,导致登录态闪回未登录。
|
||||
|
||||
## 修复
|
||||
|
||||
`AuthGate` 增加 hydrate 版本号保护:
|
||||
|
||||
1. 每次启动 hydrate 都分配独立版本号。
|
||||
2. 登录成功、退出登录、收到全局 auth state 事件时递增版本号。
|
||||
3. 旧版本 hydrate 的结果到达后直接丢弃,不再覆盖当前 `user` / `status`。
|
||||
|
||||
## 验证
|
||||
|
||||
1. `npm run test -- src/components/auth/AuthGate.test.tsx`
|
||||
2. `npm run test -- src/services/apiClient.test.ts src/services/authService.test.ts`
|
||||
3. `npm run check:encoding`
|
||||
|
||||
## 关联
|
||||
|
||||
- `src/components/auth/AuthGate.tsx`
|
||||
- `src/components/auth/AuthGate.test.tsx`
|
||||
- `.hermes/shared-memory/pitfalls.md`
|
||||
@@ -115,3 +115,41 @@
|
||||
2. 该失败只代表登录方式配置探测失败,不代表登录功能不可用,因此不把 `读取登录方式失败` 写入登录弹窗错误条。
|
||||
3. 登录弹窗仍展示密码登录表单,玩家可继续登录后进入创作链路。
|
||||
4. 本地仍需要启动 `api-server`,否则后续 `POST /api/auth/entry` 等真实登录请求无法完成。
|
||||
|
||||
## 9. 2026-05-07 本地短信入口恢复记录
|
||||
|
||||
如果登录弹窗里短信登录页签“像是被删了”,先不要改前端表单,优先检查本地登录方式探测结果:
|
||||
|
||||
1. 仓库根目录 `.env.local` 里必须显式保留 `SMS_AUTH_ENABLED=true`。
|
||||
2. 本地启动请优先使用 `npm run api-server`、`npm run dev:rust` 或 `npm run dev`,这些脚本会按“shell 环境优先、`.env.local` 覆盖 `.env`”合并配置。
|
||||
3. 若 `GET /api/auth/login-options` 只返回 `["password"]`,说明短信入口没有被服务端配置打开,前端只是按 contract 正常降级。
|
||||
4. 当 `SMS_AUTH_ENABLED=true` 生效时,`GET /api/auth/login-options` 至少应返回 `["phone", "password"]`,短信登录页签才会重新出现。
|
||||
|
||||
## 10. 2026-05-07 前端代理端口错配修复记录
|
||||
|
||||
如果 Rust API 直连返回 `["phone", "password"]`,但从前端域名请求 `GET /api/auth/login-options` 返回 `500`,短信页签同样会消失。此时不是登录 UI 被删除,而是 `AuthGate` 按第 5.3 节降级成 `["password"]`。
|
||||
|
||||
本地排查顺序固定为:
|
||||
|
||||
1. 先请求 `http://127.0.0.1:3000/api/auth/login-options`,确认前端代理是否成功返回 JSON。
|
||||
2. 再请求当前 Rust API 目标,例如 `http://127.0.0.1:3100/api/auth/login-options` 或 `http://127.0.0.1:8082/api/auth/login-options`。
|
||||
3. 若直连 API 成功而 3000 返回 `500`,检查 `RUST_SERVER_TARGET`、`GENARRATIVE_API_TARGET`、`GENARRATIVE_RUNTIME_SERVER_TARGET` 是否指向仍在监听的 API 端口。
|
||||
4. `npm run dev` / `npm run dev:rust` 完整栈默认由脚本计算 API 端口;加载 `.env.local` 给后端使用后,脚本必须重新固定 `RUST_SERVER_TARGET`,避免 `.env.local` 中的旧代理目标覆盖本次启动的实际 API 端口。
|
||||
5. `npm run dev:web` 只启动前端,不会自动拉起 Rust API;如果 `.env.local` / 当前环境已经显式声明 `GENARRATIVE_RUNTIME_SERVER_TARGET`、`RUST_SERVER_TARGET`、`GENARRATIVE_API_TARGET` 或 `GENARRATIVE_API_PORT`,脚本必须固定使用该目标。目标当下不可用时只打印警告,不自动切到另一个端口,避免前端进程长时间绑定到随后会停掉的临时 API。
|
||||
6. 如果 `3000` 仍然返回 `500`,先确认浏览器是不是还开着旧的前端进程。当前脚本如果因为端口占用漂移到 `3001` / `3002`,应直接关掉旧进程后重启,而不是继续用旧的 3000 页面判断登录入口状态。
|
||||
|
||||
## 11. 2026-05-10 `npm run api-server` 环境加载与短信 provider 排查记录
|
||||
|
||||
本地单独启动 `api-server` 时,环境变量合并顺序固定为:
|
||||
|
||||
```text
|
||||
外层 shell > .env > .env.local > .env.secrets.local
|
||||
```
|
||||
|
||||
这保证 `.env.local` 能覆盖 `.env.example` 派生出的默认值,`.env.secrets.local` 能继续覆盖本地私密密钥配置。`scripts/api-server-dev.mjs` 不得让 `.env` 后加载并覆盖 `.env.local`,否则 `SMS_AUTH_ENABLED` 或 `SMS_AUTH_PROVIDER` 可能被压回错误值。
|
||||
|
||||
排查“点击获取验证码但手机收不到短信”时,除了确认 `availableLoginMethods` 包含 `phone`,还必须确认当前进程实际使用的 provider:
|
||||
|
||||
1. `SMS_AUTH_PROVIDER="mock"` 只用于本地 UI / 账号链路联调,不会向手机发送真实短信;此时应使用 `SMS_AUTH_MOCK_VERIFY_CODE`,默认 `123456`。
|
||||
2. 真实短信链路必须使用 `SMS_AUTH_PROVIDER="aliyun"`,并在修改 `.env.local` 后重启 `api-server`,运行中的进程不会自动切换 provider。
|
||||
3. 真实 provider 是否被使用,以 `api-server` 日志中的 `provider=aliyun`、`provider_request_id` 和 `provider_out_id` 为准。
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# 登录恢复与推荐页加载态收口修复
|
||||
|
||||
日期:`2026-05-09`
|
||||
|
||||
## 背景
|
||||
|
||||
推荐页作品卡偶发一直停留在“加载中...”,同时刷新网页后可能从已登录态回到未登录态。两类问题都发生在平台入口首屏和登录态恢复链路中,表现上像推荐页问题,实际涉及 `AuthGate`、请求层 token 处理和推荐页嵌入运行态的启动状态机。
|
||||
|
||||
## 根因
|
||||
|
||||
刷新网页掉线的关键根因是 `AuthGate` hydrate 先强制调用 `refreshStoredAccessToken()`。如果 refresh cookie 临时不可用、代理错配或后端短暂返回 `401`,该方法会清掉本地 access token,随后 `/api/auth/me` 只能恢复成未登录。
|
||||
|
||||
推荐页作品卡卡住加载中的根因是推荐页自动启动运行态时,`activeRecommendEntryKey` 和 `activeRecommendRuntimeKind` 先被设置,失败时只把 kind 置空或由玩法内部写错误;外层没有稳定的 `activeRecommendRuntimeError` 收口。并发切换作品时,旧启动请求也可能晚到覆盖新启动状态。
|
||||
|
||||
## 修复
|
||||
|
||||
1. `refreshStoredAccessToken()` 增加 `clearOnFailure` 选项,默认保持原全局恢复语义;显式传 `false` 时 refresh 失败不会清空现有 access token。
|
||||
2. `AuthGate` 已有本地 access token 时,先用 `/api/auth/me` 确认当前用户;确认成功后再后台调用 refresh 续期与写每日登录埋点。后台 refresh 失败只静默忽略,不再把已确认账号改成未登录。
|
||||
3. 本地没有 access token 时,`AuthGate` 仍通过 refresh cookie 补票;该路径失败会清 token 并落到未登录,保持原有安全语义。
|
||||
4. 推荐页 `selectRecommendRuntimeEntry` 增加启动请求版本号。旧请求晚到后直接丢弃,不能覆盖当前作品。
|
||||
5. 推荐页运行态启动失败时统一写入 `activeRecommendRuntimeError = "作品暂时无法进入,请稍后再试。"`,并关闭 `isStartingRecommendEntry`,避免作品卡永久显示加载态。
|
||||
6. 推荐页入口继续保持登录门禁。未登录用户点击推荐 Tab 只切到推荐封面并弹出登录弹窗;未登录状态下点击推荐封面再次弹出登录弹窗,不打开详情、不启动运行态。
|
||||
7. `RpgEntryHomeView` 将公开作品详情入口与推荐页入口拆成 `onOpenGalleryDetail` 和 `onOpenRecommendGalleryDetail`:发现页、搜索结果和排行榜仍可按公开浏览能力打开详情;推荐页封面、推荐运行态错误重试、桌面推荐模块统一走推荐门禁入口。
|
||||
|
||||
## 验证
|
||||
|
||||
1. `npm run test -- src/services/apiClient.test.ts src/components/auth/AuthGate.test.tsx src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "explicit refresh opts out|auth gate keeps a valid local token login|home recommendation"`
|
||||
2. 后续完整收口仍建议执行:
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx src/components/auth/AuthGate.test.tsx src/services/apiClient.test.ts`
|
||||
- `npm run test -- src/services/apiClient.test.ts src/components/auth/AuthGate.test.tsx src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
|
||||
## 关联文件
|
||||
|
||||
1. `src/services/apiClient.ts`
|
||||
2. `src/components/auth/AuthGate.tsx`
|
||||
3. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
4. `src/components/rpg-entry/RpgEntryHomeView.tsx`
|
||||
5. `src/components/auth/AuthGate.test.tsx`
|
||||
6. `src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`
|
||||
7. `src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`
|
||||
@@ -67,9 +67,11 @@ HTTP status server error (503 Service Unavailable)
|
||||
远端 `xushi-p4wfr` 挂起期间,抓大鹅本地体验应使用本地 SpacetimeDB:
|
||||
|
||||
```powershell
|
||||
spacetime --root-dir=server-rs/.spacetimedb/local start --edition standalone --listen-addr 127.0.0.1:3101
|
||||
npm run dev:rust
|
||||
$env:GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET="codex-local-bootstrap-secret-20260501"
|
||||
spacetime --root-dir=server-rs/.spacetimedb/local publish xushi-p4wfr --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module -c=on-conflict --yes
|
||||
Push-Location server-rs
|
||||
spacetime publish xushi-p4wfr --server http://127.0.0.1:3101 --module-path crates/spacetime-module -c=on-conflict --yes
|
||||
Pop-Location
|
||||
```
|
||||
|
||||
再让 Rust API 指向本地库:
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
# 宝贝识物创作发布实现方案 2026-05-11
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本方案对应第 2 线程:创作发布线程。
|
||||
|
||||
本线程落地:
|
||||
|
||||
1. 创作入口配置;
|
||||
2. 模板表单;
|
||||
3. 本地草稿生成 service;
|
||||
4. 结果页;
|
||||
5. 发布 payload 约束;
|
||||
6. 本地 Demo 运行态;
|
||||
7. 后端 image-2 / 作品持久化 / 运行态接口预留形状。
|
||||
|
||||
本阶段运行态先做浏览器本地 Demo,并消费现有本地 mocap 动作数据源;正式硬件接口和摄像头调教在后续接口稳定后继续接入。
|
||||
|
||||
## 2. 前端接入点
|
||||
|
||||
新增玩法 ID:
|
||||
|
||||
```text
|
||||
baby-object-match
|
||||
```
|
||||
|
||||
用户展示名:
|
||||
|
||||
```text
|
||||
宝贝识物
|
||||
```
|
||||
|
||||
入口文件:
|
||||
|
||||
1. `src/config/newWorkEntryConfig.ts`
|
||||
2. `src/components/platform-entry/platformEntryCreationTypes.ts`
|
||||
3. `src/components/platform-entry/PlatformEntryCreationTypeModal.tsx`
|
||||
4. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
|
||||
`baby-object-match` 必须复用 `VITE_ENABLE_EDUTAINMENT_ENTRY` 开关;开关关闭时,创作类型弹层不展示 `宝贝识物`,创作页作品架不展示本地宝贝识物草稿或已发布作品卡,公开发现、搜索、详情、作品号和浏览历史也继续完全不可见。
|
||||
|
||||
新增阶段:
|
||||
|
||||
```text
|
||||
baby-object-match-workspace
|
||||
baby-object-match-generating
|
||||
baby-object-match-result
|
||||
baby-object-match-runtime
|
||||
```
|
||||
|
||||
## 3. 契约
|
||||
|
||||
前端共享契约放在:
|
||||
|
||||
```text
|
||||
packages/shared/src/contracts/edutainmentBabyObject.ts
|
||||
```
|
||||
|
||||
核心字段:
|
||||
|
||||
1. `BabyObjectMatchDraft.templateId = "baby-object-match"`;
|
||||
2. `BabyObjectMatchDraft.templateName = "宝贝识物"`;
|
||||
3. `BabyObjectMatchDraft.themeTags` 必须包含精确 `寓教于乐`;
|
||||
4. `BabyObjectMatchItemAsset.generationProvider` 首版允许为 `vector-engine-gpt-image-2` 或 `placeholder`;
|
||||
5. `BabyObjectMatchPublishRequest.draft.themeTags` 发布前必须归一化补齐 `寓教于乐`。
|
||||
|
||||
## 4. Service 边界
|
||||
|
||||
前端 service 放在:
|
||||
|
||||
```text
|
||||
src/services/edutainment-baby-object/babyObjectMatchClient.ts
|
||||
```
|
||||
|
||||
首版提供:
|
||||
|
||||
1. `createBabyObjectMatchDraft(payload)`;
|
||||
2. `saveBabyObjectMatchDraft(draft)`;
|
||||
3. `publishBabyObjectMatchWork(payload)`。
|
||||
|
||||
当前后端正式接口未在本线程扩表落地,因此 service 先走本地 Demo 存储,并把 asset 结果标记为 `placeholder`。后续后端接入时,应替换为:
|
||||
|
||||
```text
|
||||
POST /api/creation/edutainment/baby-object-match/drafts
|
||||
PUT /api/creation/edutainment/baby-object-match/drafts/{draftId}
|
||||
POST /api/creation/edutainment/baby-object-match/drafts/{draftId}/publish
|
||||
```
|
||||
|
||||
图片生成必须在后端调用 VectorEngine `gpt-image-2-all`,不得从前端直接调用外部图片接口。
|
||||
|
||||
## 5. UI 边界
|
||||
|
||||
工作台只展示两个必填输入和生成按钮。
|
||||
|
||||
结果页只展示草稿核心信息、两个物品、保存草稿、发布、试玩。不在 UI 内写玩法说明长文案。
|
||||
|
||||
移动端优先:表单和结果页使用单列布局,桌面端自然扩展为双列。
|
||||
|
||||
## 6. 运行态边界
|
||||
|
||||
前端运行态放在:
|
||||
|
||||
```text
|
||||
src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx
|
||||
```
|
||||
|
||||
运行态直接消费 `BabyObjectMatchDraft`,必须使用草稿中的两个物品名称和物品图。
|
||||
每轮只随机当前从礼物盒跳出的物品;左右篮子不随机交换,左侧固定为草稿 `itemAssets[0]`,右侧固定为草稿 `itemAssets[1]`。
|
||||
|
||||
首关状态机:
|
||||
|
||||
1. `waiting`:礼物盒关闭,等待任意手抬起;
|
||||
2. `active`:当前物品停留在屏幕中央;
|
||||
3. `correct`:展示“真棒”反馈,成功次数加 1;
|
||||
4. `wrong`:展示“再想一想吧”反馈,当前物品回到中央;
|
||||
5. `complete`:成功次数达到 20,展示“恭喜你!小朋友!”和按钮。
|
||||
|
||||
动作输入:
|
||||
|
||||
1. 任意手完成一次 `open_palm -> grab` 抓握序列:打开礼物盒并生成当前物品;
|
||||
2. 左手连续横向移动达到阈值:将当前物品送入左侧篮子;
|
||||
3. 右手连续横向移动达到阈值:将当前物品送入右侧篮子。
|
||||
|
||||
运行态直接通过 `useMocapInput` 消费本地 mocap WebSocket `/stream`。选篮只使用明确 `leftHand` 或 `rightHand` 的连续横向轨迹阈值,不再通过 `wave_left_hand`、`wave_right_hand`、`wave` 等动作名触发;侧别为 `unknown` 的手部轨迹也不参与选篮,以避免多套判定误命中和连续误触发。当前本地 mocap 输出的 handedness 按摄像头视角标记,宝贝识物运行态必须先换算为用户身体视角:`rightHand` 轨迹映射玩家左手并进入左侧篮子,`leftHand` 轨迹映射玩家右手并进入右侧篮子。草稿试玩、发布后正式体验和热身关后的本地 Demo 都复用同一个运行态,因此三条入口都必须具备同一套动作控制能力。
|
||||
|
||||
开发者调试输入:
|
||||
|
||||
1. `F`:映射任意手抬起,打开礼物盒并生成当前物品;
|
||||
2. 鼠标左键按下并拖动:映射左手轨迹,抬起后将当前物品送入左侧篮子;
|
||||
3. 鼠标右键按下并拖动:映射右手轨迹,抬起后将当前物品送入右侧篮子。
|
||||
|
||||
运行态不得新增计时、失败次数、分数、体力或难度递增规则。
|
||||
|
||||
音效和语音播报当前只保留接口预留边界,正式语音接口后续接入。
|
||||
|
||||
## 7. 发布约束
|
||||
|
||||
发布前必须执行:
|
||||
|
||||
1. 两个物品名非空;
|
||||
2. 两个物品名对应的 asset 存在;
|
||||
3. 标签补齐精确 `寓教于乐`;
|
||||
4. `publicationStatus` 从 `draft` 变为 `published`。
|
||||
|
||||
发布后首版本地响应返回 `publicWorkCode`,用于分享弹窗;正式后端接入时 public code 生成规则需要纳入统一作品号服务。
|
||||
|
||||
## 8. 热身关衔接
|
||||
|
||||
`/child-motion-demo` 热身完成后的“开始游戏”按钮进入同一个 `BabyObjectMatchRuntimeShell`。
|
||||
|
||||
热身关独立 Demo 没有创作者草稿上下文,因此使用固定本地 Demo 草稿承载两个物品,仅用于热身关后验证首关体验;正式平台体验仍必须从 `宝贝识物` 模板创作发布后进入寓教于乐板块。
|
||||
|
||||
## 9. 验收命令
|
||||
|
||||
```bash
|
||||
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/services/edutainment-baby-object/babyObjectMatchClient.test.ts
|
||||
npx vitest run src/components/platform-entry/platformEdutainmentVisibility.test.ts src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/custom-world-home/creationWorkShelf.test.ts src/services/useMocapInput.test.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts
|
||||
npx eslint src/components/platform-entry/platformEntryCreationTypes.ts src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --ext .ts,.tsx --max-warnings 0
|
||||
npm run check:encoding
|
||||
npm run typecheck
|
||||
npm run build:raw
|
||||
```
|
||||
|
||||
若后续接入真实 Rust API 和 SpacetimeDB 表,再补充 `npm run api-server`、`/healthz`、Rust contract / api-server / spacetime-client 定向测试和 migration 表目录更新。
|
||||
245
docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md
Normal file
245
docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# 后端用户行为埋点覆盖方案
|
||||
|
||||
更新时间:`2026-05-09`
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本方案用于补齐后端可直接观测的用户行为埋点入口,统一写入 SpacetimeDB 的 `tracking_event` 与 `tracking_daily_stat`,为任务系统、运营看板与后续漏斗分析提供事实数据。
|
||||
|
||||
本轮明确不纳入以下范围:
|
||||
|
||||
- 后台管理入口:`/admin/...`
|
||||
- RPG 相关入口
|
||||
- 大鱼吃小鱼相关入口
|
||||
- Visual Novel 相关入口
|
||||
- Story 相关入口
|
||||
- Combat 相关入口
|
||||
|
||||
上述范围后续若需要埋点,应单独定义事件口径,避免把后台运营审计或特定玩法内行为混入本轮通用用户行为埋点。
|
||||
|
||||
## 2. 写入链路
|
||||
|
||||
### 2.1 SpacetimeDB 通用 procedure
|
||||
|
||||
新增通用 procedure:
|
||||
|
||||
- `record_tracking_event_and_return(input: RuntimeTrackingEventInput)`
|
||||
|
||||
该入口复用既有运行态埋点写入能力:
|
||||
|
||||
1. 按 `occurred_at_micros` 计算北京时间业务日 `day_key`。
|
||||
2. 按同一 `day_key` 幂等补齐 `analytics_date_dimension`,保证周/月/季/年聚合查询有日期 bucket 映射。
|
||||
3. 写入原始事实 `tracking_event`。
|
||||
4. 更新聚合投影 `tracking_daily_stat`。
|
||||
5. 触发依赖事件进度的个人任务刷新。
|
||||
|
||||
每日登录 `daily_login` 也必须走该通用 procedure:认证链路仍保留 `record_daily_login_tracking_event_after_auth_success(...)` 作为业务语义 helper,但 helper 内部构造 `TrackingEventDraft` 后调用 `record_tracking_event_after_success(...)`,不再绕到每日登录专用 SpacetimeDB procedure。
|
||||
|
||||
### 2.2 spacetime-client 封装
|
||||
|
||||
`spacetime-client` 提供薄封装:
|
||||
|
||||
- `SpacetimeRuntimeClient::record_tracking_event(...)`
|
||||
|
||||
API Server 只依赖该 facade,不在 handler 中直接拼接 SpacetimeDB procedure 调用。
|
||||
|
||||
### 2.3 api-server helper 与中间件
|
||||
|
||||
API Server 新增统一 helper:
|
||||
|
||||
- `tracking::TrackingEventDraft`
|
||||
- `tracking::record_tracking_event_after_success(...)`
|
||||
- `tracking::record_route_tracking_event_after_success(...)`
|
||||
|
||||
路由级中间件 `record_api_tracking_after_success` 挂在最终响应链路上,只在最终 HTTP status 为 2xx 时写入埋点。埋点失败只写 `warn` 日志,不阻断认证、充值、发布、任务领取等主业务流程。
|
||||
|
||||
## 3. metadata 口径
|
||||
|
||||
当前通用路由埋点仅记录低敏字段:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `route` | 请求路径,不包含 query string |
|
||||
| `method` | HTTP Method |
|
||||
| `status` | 最终成功响应状态码 |
|
||||
| `operation` | `RequestContext` 中的操作名 |
|
||||
| `asset` | 仅资产类事件写入的低敏资产/操作信息,包含 `operation`、`operationFamily`、`assetObjectId`、`assetKind`、`objectKey`、`bucket`、`contentType`、`contentLength`、`version`、`bindingId`、`entityKind`、`entityId`、`slot`、`ownerUserId`、`profileId` 等可用于定位资产事实的字段;不写签名 URL、表单签名、OSS policy、token 或完整请求体。 |
|
||||
| `assetOperation` | 资产类路由兜底事件的操作 key,用于不读取请求体时仍能按操作族聚合。 |
|
||||
|
||||
禁止在通用埋点 metadata 中写入手机号、token、cookie、邀请码、请求体、密钥、连接串、外部凭证、OSS 签名 URL、PostObject policy 或签名表单字段。
|
||||
|
||||
### 3.1 作品级游玩埋点
|
||||
|
||||
所有已接入后端正式试玩/播放入口的作品类型统一写 `work_play_start`:
|
||||
|
||||
- `scope_kind = work`。
|
||||
- `scope_id = 稳定作品 ID`,优先使用 `profile_id`;大鱼吃小鱼沿用 `session_id` 作为作品 ID。
|
||||
- `user_id = 当前认证用户`。
|
||||
- `owner_user_id = 作品作者/拥有者`,无法从入口直接确认作者时可为空,但 `metadata.userId` 仍保留当前玩家。
|
||||
- `profile_id = 作品 profile_id`,大鱼吃小鱼这类 session 型作品可为空。
|
||||
- `module_key = play_type`,例如 `puzzle`、`match3d`、`square-hole`、`custom-world`、`big-fish`、`visual-novel`。
|
||||
- `metadata` 固定包含 `operation = work_play_start`、`playType`、`workId`、`sourceRoute`,并按入口补充 `runId`、`ownerUserId`、`profileId`、`levelId`、`mode` 等低敏字段。
|
||||
|
||||
该事件用于“某个作品被多少不同用户玩过”等作品级分析;权威去重统计仍建议优先使用业务投影(如 `profile_played_world`),埋点侧用于分析与漏斗联动。
|
||||
|
||||
## 4. 事件清单
|
||||
|
||||
### 4.1 认证与会话
|
||||
|
||||
| 事件 | 入口 |
|
||||
| --- | --- |
|
||||
| `auth_login_options_view` | `GET /api/auth/login-options` |
|
||||
| `auth_phone_code_send` | `POST /api/auth/phone/send-code` |
|
||||
| `daily_login` | 认证成功与 refresh 续期后由 `record_daily_login_tracking_event_after_auth_success(...)` 主动写入,事件 ID 按 `daily-login:{user_id}:{day_key}` 幂等 |
|
||||
| `auth_phone_login_success` | `POST /api/auth/phone/login` |
|
||||
| `auth_me_view` | `GET /api/auth/me` |
|
||||
| `auth_sessions_view` | `GET /api/auth/sessions` |
|
||||
| `auth_refresh_success` | `POST /api/auth/refresh` |
|
||||
| `auth_logout` | `POST /api/auth/logout` |
|
||||
| `auth_logout_all` | `POST /api/auth/logout-all` |
|
||||
| `auth_wechat_bind_phone_success` | `POST /api/auth/wechat/bind-phone` |
|
||||
|
||||
### 4.2 个人中心、账户运营与任务
|
||||
|
||||
| 事件 | 入口 |
|
||||
| --- | --- |
|
||||
| `profile_identity_update` | `PATCH /api/profile/me` |
|
||||
| `profile_dashboard_view` | `GET /api/profile/dashboard` |
|
||||
| `wallet_ledger_view` | `GET /api/profile/wallet-ledger` |
|
||||
| `recharge_center_view` | `GET /api/profile/recharge-center` |
|
||||
| `recharge_order_create` | `POST /api/profile/recharge/orders` |
|
||||
| `feedback_submit` | `POST /api/profile/feedback` |
|
||||
| `invite_center_view` | `GET /api/profile/referrals/invite-center` |
|
||||
| `referral_invite_code_redeem` | `POST /api/profile/referrals/redeem-code` |
|
||||
| `redeem_code_submit` | `POST /api/profile/redeem-codes/redeem` |
|
||||
| `task_center_view` | `GET /api/profile/tasks` |
|
||||
| `task_reward_claim` | `POST /api/profile/tasks/{task_id}/claim` |
|
||||
| `save_archive_list_view` | `GET /api/profile/save-archives` |
|
||||
| `save_archive_detail_view` | `GET /api/profile/save-archives/{archive_id}` |
|
||||
| `browse_history_view` | `GET /api/profile/browse-history` |
|
||||
| `browse_history_record` | `POST /api/profile/browse-history` |
|
||||
| `browse_history_clear` | `DELETE /api/profile/browse-history` |
|
||||
| `play_stats_view` | `GET /api/profile/play-stats` |
|
||||
| `profile_analytics_metric_view` | `GET /api/profile/analytics/metric` |
|
||||
|
||||
### 4.3 AI、资产、LLM 与语音
|
||||
|
||||
资产操作统一按用户级事件写入:`scope_kind = user`、`scope_id = 当前认证 user_id`、`user_id/owner_user_id = 当前认证 user_id`。其中 `asset_upload_ticket_create`、`asset_upload_confirm`、`asset_bind` 在 handler 成功后主动记录资产 metadata,避免只依赖路由兜底;其余资产工坊入口通过路由级兜底保留用户级操作事实。
|
||||
|
||||
| 事件 | 入口 |
|
||||
| --- | --- |
|
||||
| `ai_task_create` | `POST /api/ai/tasks` |
|
||||
| `ai_task_start` | `POST /api/ai/tasks/{task_id}/start` |
|
||||
| `ai_task_stage_start` | `POST /api/ai/tasks/{task_id}/stages/{stage_id}/start` |
|
||||
| `ai_task_chunk_append` | `POST /api/ai/tasks/{task_id}/chunks` |
|
||||
| `ai_task_stage_complete` | `POST /api/ai/tasks/{task_id}/stages/{stage_id}/complete` |
|
||||
| `ai_task_reference_attach` | `POST /api/ai/tasks/{task_id}/references` |
|
||||
| `ai_task_complete` | `POST /api/ai/tasks/{task_id}/complete` |
|
||||
| `ai_task_fail` | `POST /api/ai/tasks/{task_id}/fail` |
|
||||
| `ai_task_cancel` | `POST /api/ai/tasks/{task_id}/cancel` |
|
||||
| `asset_upload_ticket_create` | `POST /api/assets/direct-upload-tickets` |
|
||||
| `asset_sts_credentials_create` | `POST /api/assets/sts-upload-credentials` |
|
||||
| `asset_upload_confirm` | `POST /api/assets/objects/confirm` |
|
||||
| `asset_bind` | `POST /api/assets/objects/bind` |
|
||||
| `asset_character_visual_generate` | `POST /api/assets/character-visual/generate` |
|
||||
| `asset_character_visual_publish` | `POST /api/assets/character-visual/publish` |
|
||||
| `asset_character_animation_generate` | `POST /api/assets/character-animation/generate` |
|
||||
| `asset_character_animation_publish` | `POST /api/assets/character-animation/publish` |
|
||||
| `asset_character_animation_import` | `POST /api/assets/character-animation/import-video` |
|
||||
| `asset_character_workflow_cache_save` | `POST /api/assets/character-workflow-cache` |
|
||||
| `asset_history_view` | `GET /api/assets/history` |
|
||||
| `llm_request` | `POST /api/llm/chat/completions` |
|
||||
| `speech_config_view` | `GET /api/speech/volcengine/config` |
|
||||
| `asr_stream_start` | `GET /api/speech/volcengine/asr/stream` |
|
||||
| `tts_bidirection_start` | `GET /api/speech/volcengine/tts/bidirection` |
|
||||
| `tts_sse_start` | `POST /api/speech/volcengine/tts/sse` |
|
||||
|
||||
### 4.4 运行态与创作入口
|
||||
|
||||
| 事件 | 入口 |
|
||||
| --- | --- |
|
||||
| `runtime_settings_view` | `GET /api/runtime/settings` |
|
||||
| `runtime_settings_update` | `PUT /api/runtime/settings` |
|
||||
| `runtime_snapshot_view` | `GET /api/runtime/save/snapshot` |
|
||||
| `runtime_snapshot_save` | `PUT /api/runtime/save/snapshot` |
|
||||
| `runtime_snapshot_delete` | `DELETE /api/runtime/save/snapshot` |
|
||||
| `puzzle_route_success` | `/api/runtime/puzzle/...` 成功响应兜底 |
|
||||
| `match3d_route_success` | `/api/creation/match3d/...` 与 `/api/runtime/match3d/...` 成功响应兜底 |
|
||||
| `square_hole_route_success` | `/api/creation/square-hole/...` 与 `/api/runtime/square-hole/...` 成功响应兜底 |
|
||||
| `custom_world_route_success` | `/api/runtime/custom-world...` 成功响应兜底 |
|
||||
| `creative_agent_route_success` | `/api/runtime/creative-agent...` 成功响应兜底 |
|
||||
| `work_play_start` | 拼图、抓大鹅、方洞挑战、自定义世界、大鱼吃小鱼、Visual Novel 的正式开始游玩/播放入口;写 `scope_kind = work`、`scope_id = 作品 ID` |
|
||||
|
||||
2048、Survivor、Moku 等未被排除的模板/玩法,如果经由上述 runtime、creative、custom-world、puzzle、match3d 或 square-hole 后端入口,会被路由级兜底事件覆盖。
|
||||
|
||||
## 5. 查询与验收建议
|
||||
|
||||
按每日登录核查原始事实:
|
||||
|
||||
```sql
|
||||
SELECT event_id, event_key, scope_kind, scope_id, user_id, module_key, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'daily_login'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
按作品级游玩核查原始事实:
|
||||
|
||||
```sql
|
||||
SELECT event_key, scope_kind, scope_id, user_id, owner_user_id, profile_id, module_key, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'work_play_start'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
按某个作品统计不同游玩用户:
|
||||
|
||||
```sql
|
||||
SELECT scope_id, COUNT(DISTINCT user_id) AS player_count
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'work_play_start'
|
||||
AND scope_kind = 'work'
|
||||
AND scope_id = '<profile_id_or_work_id>'
|
||||
GROUP BY scope_id;
|
||||
```
|
||||
|
||||
按资产操作核查原始事实:
|
||||
|
||||
```sql
|
||||
SELECT event_key, scope_kind, scope_id, user_id, owner_user_id, module_key, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE module_key = 'asset'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
按事件核查原始事实:
|
||||
|
||||
```sql
|
||||
SELECT event_key, scope_kind, scope_id, user_id, module_key, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'task_center_view'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
按日聚合核查:
|
||||
|
||||
```sql
|
||||
SELECT day_key, event_key, scope_kind, scope_id, count
|
||||
FROM tracking_daily_stat
|
||||
WHERE event_key = 'task_center_view'
|
||||
ORDER BY day_key DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
验收重点:
|
||||
|
||||
1. 成功请求写入 `tracking_event` 并刷新 `tracking_daily_stat`。
|
||||
2. `daily_login` 由认证成功/refresh 续期链路主动写入,且走 `record_tracking_event_and_return` 通用 procedure。
|
||||
3. 非 2xx 响应不记录通用成功事件。
|
||||
4. 后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 路由不写入本轮通用埋点。
|
||||
5. 埋点写入失败时主接口仍返回原业务结果,只记录后端 warning。
|
||||
6. metadata 不包含凭证、请求体或敏感业务字段。
|
||||
@@ -0,0 +1,729 @@
|
||||
# bark-battle 2D Runtime 前端技术方案(2026-05-11)
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
本方案基于 `.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md`,为“汪汪声浪大作战 / bark-battle”细化前端与浏览器游戏 runtime 的技术实现路线。
|
||||
|
||||
本任务只产出技术方案,不直接实现代码。后续编码应以本文作为 runtime 层设计约束,再按 TDD 小步落地。
|
||||
|
||||
### 1.1 玩法定位
|
||||
|
||||
`bark-battle` 是一个声控拔河式 2D 浏览器小游戏:玩家对麦克风发出狗叫声,系统根据音量峰值、有效叫声次数和节奏计算本方声浪推动力,在限时 30 秒内推动顶部红蓝能量条,时间结束后按能量条偏向判定胜负。
|
||||
|
||||
### 1.2 本文范围
|
||||
|
||||
本文覆盖:
|
||||
|
||||
- Phaser + TypeScript + Vite 栈选择
|
||||
- simulation / render / HUD 边界
|
||||
- 建议目录结构
|
||||
- 核心 domain 类型
|
||||
- Web Audio 输入适配
|
||||
- Phaser Scene 切分
|
||||
- DOM HUD 设计
|
||||
- 移动端与权限降级
|
||||
- 测试与验证命令
|
||||
|
||||
本文不覆盖:
|
||||
|
||||
- 后端表结构、持久化成绩、作品发布、广场接入
|
||||
- 实时多人对战协议
|
||||
- 复杂 AI 狗叫识别模型
|
||||
- 美术素材正式生产流程
|
||||
|
||||
## 2. 技术栈选择
|
||||
|
||||
### 2.1 推荐栈
|
||||
|
||||
```text
|
||||
Runtime Renderer: Phaser 3
|
||||
Language: TypeScript
|
||||
Build: Vite
|
||||
Host UI: React / DOM overlay
|
||||
Audio Input: Web Audio API + MediaDevices.getUserMedia
|
||||
Test: Vitest + Testing Library + 浏览器 smoke / Playwright 可选
|
||||
```
|
||||
|
||||
### 2.2 选择理由
|
||||
|
||||
1. 玩法是横版 2D 舞台,核心表现是狗狗 sprite、声浪、拟声词、粒子、屏幕震动与能量条反馈,Phaser 对 2D 渲染、时间循环、sprite animation、camera、粒子和 Scene 生命周期支持成熟。
|
||||
2. 当前项目主前端已使用 TypeScript + Vite,继续复用现有构建和测试体系,避免引入独立构建链。
|
||||
3. 文字密集、权限提示、结算、设置和移动端响应式布局适合 DOM HUD;Canvas 保持负责 playfield 和动态特效。
|
||||
4. Web Audio API 可以在浏览器端完成 MVP 所需音量采样、RMS/peak 计算、环境噪音校准和输入归一化,不需要首版接入后端音频处理。
|
||||
|
||||
### 2.3 不选择其它路线的原因
|
||||
|
||||
- 不使用 Three.js / 3D:当前玩法画面是 2D 横版舞台,不需要 3D 相机、模型和材质管线。
|
||||
- 不把 HUD 全部塞入 Phaser Canvas:权限说明、重试、结算、移动端布局和可访问性更适合 DOM。
|
||||
- 不在前端实现正式业务真相:浏览器 runtime 可承载单局即时 simulation,但若后续涉及成绩、作品、排行榜、发布和奖励,必须交给后端投影/API 裁决。
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
### 3.1 分层总览
|
||||
|
||||
```text
|
||||
React Runtime Shell
|
||||
├─ DOM HUD / Panels
|
||||
│ ├─ PermissionPanel
|
||||
│ ├─ TopEnergyBar
|
||||
│ ├─ TimerChip
|
||||
│ └─ ResultPanel
|
||||
│
|
||||
├─ Application Controller
|
||||
│ ├─ permission / calibration orchestration
|
||||
│ ├─ simulation tick
|
||||
│ ├─ audio sample submission
|
||||
│ └─ snapshot publish
|
||||
│
|
||||
├─ Pure Domain / Simulation
|
||||
│ ├─ BarkBattleSession
|
||||
│ ├─ BarkDetector
|
||||
│ ├─ EnergyTugOfWar
|
||||
│ ├─ BarkBattleScoring
|
||||
│ └─ OpponentStrategy
|
||||
│
|
||||
├─ Infrastructure Adapters
|
||||
│ ├─ BrowserMicrophoneInput
|
||||
│ ├─ AudioAnalyserSampler
|
||||
│ └─ PhaserGameHost
|
||||
│
|
||||
└─ Phaser Renderer
|
||||
├─ BootScene
|
||||
├─ PreloadScene
|
||||
├─ BattleScene
|
||||
├─ FxScene / DebugScene(可选)
|
||||
└─ Asset manifest
|
||||
```
|
||||
|
||||
### 3.2 强制边界
|
||||
|
||||
1. `domain/` 不依赖 Phaser、Web Audio、DOM、React、浏览器全局对象或后端 API。
|
||||
2. `domain/` 只接收 plain data,例如时间增量、归一化音量样本、对手 power、配置参数。
|
||||
3. `application/` 负责编排权限、校准、音频输入、AI 对手、tick 和 snapshot 分发。
|
||||
4. Phaser Scene 只消费 `BarkBattleSnapshot`,把 snapshot 映射成 sprite、动画、粒子、camera 和 sound effect;不得持有核心胜负、计数和能量条规则。
|
||||
5. DOM HUD 只消费 snapshot 和少量 runtime UI 状态,负责展示、按钮和弹层;不得重复实现核心胜负规则。
|
||||
6. 若后续接入平台作品/成绩/排行榜,前端只调用后端 API 和展示投影,不在本地绕过后端生成正式结论。
|
||||
|
||||
## 4. 建议目录结构
|
||||
|
||||
首版建议以独立 runtime 原型落在 `src/games/bark-battle/`,避免提前侵入平台创作链路。
|
||||
|
||||
```text
|
||||
src/games/bark-battle/
|
||||
domain/
|
||||
BarkBattleTypes.ts
|
||||
BarkBattleSession.ts
|
||||
BarkDetector.ts
|
||||
EnergyTugOfWar.ts
|
||||
BarkBattleScoring.ts
|
||||
OpponentStrategy.ts
|
||||
__tests__/
|
||||
BarkDetector.test.ts
|
||||
EnergyTugOfWar.test.ts
|
||||
BarkBattleSession.test.ts
|
||||
BarkBattleScoring.test.ts
|
||||
|
||||
application/
|
||||
BarkBattleController.ts
|
||||
BarkBattleConfig.ts
|
||||
BarkBattleSnapshotStore.ts
|
||||
__tests__/
|
||||
BarkBattleController.test.ts
|
||||
|
||||
infrastructure/
|
||||
BrowserMicrophoneInput.ts
|
||||
AudioAnalyserSampler.ts
|
||||
MicrophonePermission.ts
|
||||
__tests__/
|
||||
BrowserMicrophoneInput.test.ts
|
||||
AudioAnalyserSampler.test.ts
|
||||
|
||||
phaser/
|
||||
BarkBattleGameHost.ts
|
||||
scenes/
|
||||
BarkBattleBootScene.ts
|
||||
BarkBattlePreloadScene.ts
|
||||
BarkBattleScene.ts
|
||||
BarkBattleFxScene.ts
|
||||
assets/
|
||||
barkBattleAssetManifest.ts
|
||||
|
||||
ui/
|
||||
BarkBattleRuntimeShell.tsx
|
||||
BarkBattleHud.tsx
|
||||
BarkBattlePermissionPanel.tsx
|
||||
BarkBattleResultPanel.tsx
|
||||
BarkBattleMobileControls.tsx
|
||||
BarkBattleHud.css
|
||||
__tests__/
|
||||
BarkBattleHud.test.tsx
|
||||
BarkBattlePermissionPanel.test.tsx
|
||||
BarkBattleResultPanel.test.tsx
|
||||
```
|
||||
|
||||
若后续进入 Genarrative 正式玩法类型闭环,再按 `genarrative-play-type-integration` 扩展到:
|
||||
|
||||
```text
|
||||
src/components/bark-battle-runtime/BarkBattleRuntimeShell.tsx
|
||||
src/components/bark-battle-result/BarkBattleResultView.tsx
|
||||
src/services/barkBattleRuntimeClient.ts
|
||||
packages/shared/src/contracts/barkBattle.ts
|
||||
server-rs/crates/shared-contracts/src/bark_battle.rs
|
||||
```
|
||||
|
||||
首版不建议直接新增后端表或正式作品链路,除非产品明确要求成绩、发布和广场能力。
|
||||
|
||||
## 5. 核心 Domain 类型
|
||||
|
||||
### 5.1 基础枚举与数值约定
|
||||
|
||||
```ts
|
||||
export type BarkBattlePhase =
|
||||
| 'permission'
|
||||
| 'calibration'
|
||||
| 'countdown'
|
||||
| 'playing'
|
||||
| 'finished'
|
||||
| 'unavailable'
|
||||
|
||||
export type BarkBattleSide = 'player' | 'opponent'
|
||||
|
||||
export type BarkBattleWinner = BarkBattleSide | 'draw' | null
|
||||
|
||||
export type BarkBattleDifficulty = 'easy' | 'normal' | 'hard'
|
||||
|
||||
export type BarkBattleUiState =
|
||||
| 'idle'
|
||||
| 'permission-ready'
|
||||
| 'microphone-authorized'
|
||||
| 'calibrating'
|
||||
| 'ready-countdown'
|
||||
| 'playing'
|
||||
| 'finished'
|
||||
| 'microphone-unavailable'
|
||||
|
||||
export type MicrophoneFailureReason =
|
||||
| 'unsupported'
|
||||
| 'permission-denied'
|
||||
| 'non-secure-context'
|
||||
| 'not-found'
|
||||
| 'not-readable'
|
||||
| 'audio-context-blocked'
|
||||
| 'calibration-timeout'
|
||||
| 'calibration-sample-unreadable'
|
||||
| 'unknown'
|
||||
```
|
||||
|
||||
关键数值:
|
||||
|
||||
- `energy`: `-100..100`,正数偏玩家侧,负数偏对手侧。
|
||||
- `currentVolume`: `0..1`,音频采样归一化后的瞬时音量。
|
||||
- `recentPeak`: `0..1`,短窗口内峰值。
|
||||
- `power`: `0..1` 或 `0..100` 二选一,建议 domain 内统一 `0..1`,HUD 显示再转百分比。
|
||||
- `remainingMs`: 单局剩余毫秒。
|
||||
|
||||
### 5.2 Snapshot
|
||||
|
||||
```ts
|
||||
export type BarkBattleSnapshot = {
|
||||
phase: BarkBattlePhase
|
||||
uiState: BarkBattleUiState
|
||||
errorReason: MicrophoneFailureReason | null
|
||||
statusMessageKey: BarkBattleStatusMessageKey | null
|
||||
elapsedMs: number
|
||||
remainingMs: number
|
||||
countdownMs: number
|
||||
energy: number
|
||||
player: BarkSideState
|
||||
opponent: BarkSideState
|
||||
winner: BarkBattleWinner
|
||||
result: BarkBattleResult | null
|
||||
lastEvents: BarkBattleVisualEvent[]
|
||||
}
|
||||
|
||||
export type BarkSideState = {
|
||||
side: BarkBattleSide
|
||||
barkCount: number
|
||||
currentVolume: number
|
||||
recentPeak: number
|
||||
combo: number
|
||||
power: number
|
||||
isBarking: boolean
|
||||
lastBarkAtMs: number | null
|
||||
maxVolume: number
|
||||
}
|
||||
|
||||
export type BarkBattleResult = {
|
||||
winner: BarkBattleWinner
|
||||
finalEnergy: number
|
||||
playerBarkCount: number
|
||||
playerMaxVolume: number
|
||||
playerAveragePower: number
|
||||
score: number
|
||||
}
|
||||
|
||||
export type BarkBattleStatusMessageKey =
|
||||
| 'microphone-unsupported'
|
||||
| 'microphone-permission-denied'
|
||||
| 'microphone-non-secure-context'
|
||||
| 'microphone-not-found'
|
||||
| 'microphone-not-readable'
|
||||
| 'microphone-audio-context-blocked'
|
||||
| 'microphone-calibration-timeout'
|
||||
| 'microphone-calibration-sample-unreadable'
|
||||
| 'microphone-unknown-error'
|
||||
```
|
||||
|
||||
### 5.3 输入样本与叫声事件
|
||||
|
||||
```ts
|
||||
export type BarkAudioSample = {
|
||||
atMs: number
|
||||
volume: number
|
||||
peak: number
|
||||
rms: number
|
||||
}
|
||||
|
||||
export type BarkDetectedEvent = {
|
||||
id: string
|
||||
atMs: number
|
||||
side: BarkBattleSide
|
||||
volume: number
|
||||
strength: number
|
||||
combo: number
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 视觉事件
|
||||
|
||||
视觉事件由 domain 或 application 生成,但只包含 plain data,不包含 Phaser 对象:
|
||||
|
||||
```ts
|
||||
export type BarkBattleVisualEvent =
|
||||
| {
|
||||
type: 'bark-word'
|
||||
id: string
|
||||
side: BarkBattleSide
|
||||
atMs: number
|
||||
strength: number
|
||||
text: 'BARK' | 'WOOF' | 'WAN' | 'WANGOOF'
|
||||
}
|
||||
| {
|
||||
type: 'shockwave'
|
||||
id: string
|
||||
side: BarkBattleSide
|
||||
atMs: number
|
||||
strength: number
|
||||
}
|
||||
| {
|
||||
type: 'combo-burst'
|
||||
id: string
|
||||
side: BarkBattleSide
|
||||
atMs: number
|
||||
combo: number
|
||||
}
|
||||
```
|
||||
|
||||
Phaser 只根据这些事件播放一次性特效,并维护已消费事件 ID,避免重复播放。
|
||||
|
||||
## 6. Domain 模块职责
|
||||
|
||||
### 6.1 BarkDetector
|
||||
|
||||
职责:把连续音频样本转换为有效叫声事件。
|
||||
|
||||
输入:
|
||||
|
||||
- `BarkAudioSample`
|
||||
- 校准后的 `ambientNoiseFloor`
|
||||
- `barkThreshold`
|
||||
- `minBarkGapMs`
|
||||
- `minBarkDurationMs`
|
||||
- `maxBarkDurationMs`
|
||||
|
||||
规则建议:
|
||||
|
||||
1. 音量超过动态阈值进入 candidate 状态。
|
||||
2. 峰值回落到阈值以下或持续时长达到上限时结束 candidate。
|
||||
3. candidate 持续时间在 `80ms..1200ms` 且与上一次有效叫声间隔足够时,记为一次叫声。
|
||||
4. 长时间持续噪音不应无限计数,只能按冷却和峰值回落形成新事件。
|
||||
5. MVP 不要求识别“是否真狗叫”,先基于音量峰值、时长和间隔判断。
|
||||
|
||||
### 6.2 EnergyTugOfWar
|
||||
|
||||
职责:更新红蓝拉锯条。
|
||||
|
||||
建议公式:
|
||||
|
||||
```text
|
||||
playerPower = volumeScore * 0.65 + barkRateScore * 0.35 + comboBonus
|
||||
opponentPower = opponentStrategy.tick(...)
|
||||
energyDelta = (playerPower - opponentPower) * deltaSeconds * balanceFactor
|
||||
energy = clamp(energy + energyDelta, -100, 100)
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- `EnergyTugOfWar` 不知道玩家来自麦克风还是 mock input。
|
||||
- `EnergyTugOfWar` 不知道 Phaser 能量条宽度。
|
||||
- 平衡参数集中在 `BarkBattleConfig`,不要散落在 Scene 或 HUD 中。
|
||||
|
||||
### 6.3 BarkBattleSession
|
||||
|
||||
职责:管理局内 phase、计时、胜负和 snapshot。
|
||||
|
||||
状态机建议:
|
||||
|
||||
```text
|
||||
permission → calibration → countdown → playing → finished
|
||||
↘ unavailable
|
||||
```
|
||||
|
||||
`phase` 只表达 runtime 是否可继续参与局内流程;所有麦克风不可用、权限失败、非安全上下文和校准失败都统一收敛到 `phase: 'unavailable'`,再通过 `uiState: 'microphone-unavailable'` 与 `errorReason` 区分 HUD 展示和重试策略,避免把基础设施错误枚举直接扩散成 domain 阶段。
|
||||
|
||||
关键规则:
|
||||
|
||||
- `countdown` 结束才进入 `playing`。
|
||||
- `playing` 时 `remainingMs` 随 tick 递减。
|
||||
- `remainingMs <= 0` 后进入 `finished`。
|
||||
- `energy > drawThreshold` 判定玩家胜利。
|
||||
- `energy < -drawThreshold` 判定对手胜利。
|
||||
- `abs(energy) <= drawThreshold` 判定平局。
|
||||
|
||||
### 6.4 OpponentStrategy
|
||||
|
||||
职责:为单机 MVP 提供对手推动力。
|
||||
|
||||
```ts
|
||||
export interface OpponentStrategy {
|
||||
tick(input: OpponentTickInput): OpponentTickOutput
|
||||
}
|
||||
```
|
||||
|
||||
普通难度建议:
|
||||
|
||||
- 周期性小叫声提供基础压力。
|
||||
- 每 3~6 秒一次短爆发。
|
||||
- 玩家大幅领先时可轻微增强,但不能追到不可赢。
|
||||
|
||||
## 7. Web Audio 输入适配
|
||||
|
||||
### 7.1 BrowserMicrophoneInput
|
||||
|
||||
职责:封装浏览器麦克风权限与音频流生命周期。
|
||||
|
||||
建议 API:
|
||||
|
||||
```ts
|
||||
export interface MicrophoneInputPort {
|
||||
isSupported(): boolean
|
||||
requestPermission(): Promise<MicrophoneSession>
|
||||
stop(): void
|
||||
}
|
||||
|
||||
export interface MicrophoneSession {
|
||||
sample(atMs: number): BarkAudioSample
|
||||
stop(): void
|
||||
}
|
||||
```
|
||||
|
||||
实现要点:
|
||||
|
||||
1. 使用 `navigator.mediaDevices?.getUserMedia({ audio: true })`。
|
||||
2. 在用户点击“开始”后创建或 resume `AudioContext`,避免移动端自动播放策略拦截。
|
||||
3. 使用 `AnalyserNode` 读取时域数据,计算 RMS 与 peak。
|
||||
4. 输出归一化样本,不把 `MediaStream`、`AudioContext`、`AnalyserNode` 泄漏到 domain。
|
||||
5. 退出、重开、页面卸载时停止 track,避免麦克风占用残留。
|
||||
|
||||
### 7.2 校准流程
|
||||
|
||||
`calibration` 阶段建议持续 `800ms..1500ms`:
|
||||
|
||||
1. 收集静默环境样本。
|
||||
2. 计算 `ambientNoiseFloor`,例如 `p75` 或均值 + 标准差。
|
||||
3. 设置动态阈值:
|
||||
|
||||
```text
|
||||
barkThreshold = clamp(ambientNoiseFloor + 0.12, 0.18, 0.55)
|
||||
```
|
||||
|
||||
4. 若环境噪音过高,HUD 给出简短提示和“继续 / 重新校准”入口,但不要把长说明常驻在画面上。
|
||||
|
||||
### 7.3 权限与错误分类
|
||||
|
||||
```ts
|
||||
export type MicrophoneFailureReason =
|
||||
| 'unsupported'
|
||||
| 'permission-denied'
|
||||
| 'non-secure-context'
|
||||
| 'not-found'
|
||||
| 'not-readable'
|
||||
| 'audio-context-blocked'
|
||||
| 'calibration-timeout'
|
||||
| 'calibration-sample-unreadable'
|
||||
| 'unknown'
|
||||
```
|
||||
|
||||
错误来源与分层归属:
|
||||
|
||||
| 失败原因 | 主要检测位置 | controller snapshot 表达 | HUD 可区分状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 浏览器无 `mediaDevices.getUserMedia` | `BrowserMicrophoneInput.isSupported()` | `phase: 'unavailable'`, `uiState: 'microphone-unavailable'`, `errorReason: 'unsupported'` | 设备或浏览器不支持麦克风输入,只提供返回入口,不展示可开始声控按钮 |
|
||||
| 非安全上下文 | `BrowserMicrophoneInput.isSupported()` 或 `MicrophonePermission` 预检 `window.isSecureContext` | `phase: 'unavailable'`, `errorReason: 'non-secure-context'` | 当前环境无法使用麦克风,提示使用受支持的安全环境或返回 |
|
||||
| 用户拒绝授权 | `BrowserMicrophoneInput.requestPermission()` 捕获 `NotAllowedError` / `SecurityError` | `phase: 'unavailable'`, `errorReason: 'permission-denied'` | 提供重新授权或返回入口,不进入 calibration/countdown/playing |
|
||||
| 未检测到设备 | `getUserMedia` 捕获 `NotFoundError` / `DevicesNotFoundError` | `phase: 'unavailable'`, `errorReason: 'not-found'` | 展示麦克风不可用,可重试授权或返回 |
|
||||
| 设备被占用或不可读 | `getUserMedia` 捕获 `NotReadableError` / `TrackStartError` | `phase: 'unavailable'`, `errorReason: 'not-readable'` | 展示麦克风不可用,可重试授权或返回 |
|
||||
| AudioContext 被移动端策略拦截 | 用户手势后创建 / resume `AudioContext` 失败 | `phase: 'unavailable'`, `errorReason: 'audio-context-blocked'` | 提示点击重试,不自动循环请求 |
|
||||
| 校准超时 | `BarkBattleController` 在 calibration 阶段等待样本超出 `calibrationMaxWaitMs` | `phase: 'unavailable'`, `errorReason: 'calibration-timeout'` | 展示麦克风输入不可用,提供重试校准入口 |
|
||||
| 校准样本不可读 | `AudioAnalyserSampler.sample()` 持续返回空样本、NaN 或无法读取 buffer | `phase: 'unavailable'`, `errorReason: 'calibration-sample-unreadable'` | 展示麦克风输入不可用,提供重试校准入口 |
|
||||
|
||||
前端只根据错误分类展示可操作状态:重试授权、重试校准、返回、或使用调试备用输入。不要把浏览器原始错误堆栈展示给玩家。
|
||||
|
||||
## 8. Phaser Scene 切分
|
||||
|
||||
### 8.1 BarkBattleBootScene
|
||||
|
||||
职责:
|
||||
|
||||
- 初始化 Phaser 全局配置。
|
||||
- 注册 scale、background color、全局事件桥。
|
||||
- 不加载重资源,不处理玩法规则。
|
||||
|
||||
### 8.2 BarkBattlePreloadScene
|
||||
|
||||
职责:
|
||||
|
||||
- 根据 `barkBattleAssetManifest` 加载背景、狗狗 sprite、声浪 FX、拟声词 bitmap / atlas、轻量音效。
|
||||
- 使用稳定 manifest key,不在 gameplay 代码中散写文件路径。
|
||||
- 加载完成后进入 `BarkBattleScene`。
|
||||
|
||||
### 8.3 BarkBattleScene
|
||||
|
||||
职责:
|
||||
|
||||
- 创建横版舞台、左右狗狗、背景层、声浪层、拟声词层。
|
||||
- 每帧读取最新 `BarkBattleSnapshot`。
|
||||
- 根据 snapshot 更新:
|
||||
- 狗狗 idle / bark / win / lose 动画
|
||||
- 声浪强度
|
||||
- camera shake
|
||||
- transient bark words
|
||||
- shockwave
|
||||
- 把可选的调试输入 action 传给 controller,但不处理麦克风和规则。
|
||||
|
||||
不得在 Scene 中实现:
|
||||
|
||||
- 叫声计数
|
||||
- 胜负判定
|
||||
- 能量条规则
|
||||
- 权限流程
|
||||
- 结算数据计算
|
||||
|
||||
### 8.4 BarkBattleFxScene(可选)
|
||||
|
||||
如果特效复杂,可拆出叠加 Scene:
|
||||
|
||||
- 专门处理拟声词、粒子、冲击波和 camera shake。
|
||||
- 通过视觉事件 ID 去重。
|
||||
- 对 `prefers-reduced-motion` 或低端设备降级。
|
||||
|
||||
首版也可以先把 FX 保持在 `BarkBattleScene` 内,但必须仍然只消费 snapshot / visual events。
|
||||
|
||||
## 9. DOM HUD 设计
|
||||
|
||||
### 9.1 HUD 层级
|
||||
|
||||
DOM HUD 建议覆盖在 Phaser Canvas 上方:
|
||||
|
||||
```text
|
||||
BarkBattleRuntimeShell
|
||||
├─ <div className="bark-battle-canvas-host" />
|
||||
└─ <BarkBattleHud snapshot={snapshot} uiState={uiState} />
|
||||
```
|
||||
|
||||
HUD 分区:
|
||||
|
||||
- 顶部:红蓝声浪能量条 + 小型剩余时间。
|
||||
- 中央:仅在倒计时、关键提示或结算时短暂展示大号状态。
|
||||
- 左右边缘:双方简洁状态,例如叫声次数 / combo chip。
|
||||
- 底部角落:麦克风状态、重试、小菜单。
|
||||
- 结算:独立居中面板,显示胜负、叫声次数、最大音量、评分、再来一局、返回。
|
||||
|
||||
### 9.2 Playfield 保护
|
||||
|
||||
遵循 game UI 约束:
|
||||
|
||||
1. 正常 playing 阶段保持中心和下中部 playfield 清爽,不常驻长文案。
|
||||
2. 不把规则说明、长控制说明、多段提示默认铺在画面上。
|
||||
3. 权限、设置、结算使用独立面板或弹层,不在当前面板下面展开一大块内容。
|
||||
4. 移动端优先保证顶部能量条、倒计时、狗狗和重试入口可见可点。
|
||||
5. 大动效不能遮挡顶部能量条和倒计时。
|
||||
|
||||
### 9.3 CSS 设计建议
|
||||
|
||||
- 使用局部 CSS class 或 CSS module,避免污染全站。
|
||||
- 使用 CSS 变量定义主题:
|
||||
- `--bark-player-color`
|
||||
- `--bark-opponent-color`
|
||||
- `--bark-panel-bg`
|
||||
- `--bark-safe-bottom`
|
||||
- 使用 `dvh` / `svh` 和 safe-area inset 处理移动端地址栏与刘海。
|
||||
- `pointer-events` 分层:HUD 容器默认 `pointer-events: none`,按钮和面板恢复 `pointer-events: auto`。
|
||||
|
||||
## 10. 移动端与权限降级
|
||||
|
||||
### 10.1 移动端输入约束
|
||||
|
||||
移动端浏览器通常要求用户手势才能启动 AudioContext。开局流程必须是:
|
||||
|
||||
```text
|
||||
玩家点击“开始” → requestPermission → 创建/恢复 AudioContext → calibration → countdown → playing
|
||||
```
|
||||
|
||||
不要在页面加载时自动请求或自动启动 AudioContext。
|
||||
|
||||
### 10.2 响应式布局
|
||||
|
||||
移动端建议:
|
||||
|
||||
- 横屏优先呈现完整舞台;竖屏可保持舞台居中并缩小 HUD。
|
||||
- 顶部能量条高度保持可读,但不要占满大面积。
|
||||
- 结算面板宽度使用 `min(92vw, 420px)`。
|
||||
- 底部按钮避开 `env(safe-area-inset-bottom)`。
|
||||
- 非关键设置折叠进小菜单。
|
||||
|
||||
### 10.3 权限失败降级
|
||||
|
||||
权限失败时:
|
||||
|
||||
- `unsupported`:展示“当前浏览器不支持麦克风输入”,提供返回入口,不展示开始声控按钮。
|
||||
- `non-secure-context`:展示“当前环境无法使用麦克风”,提示切换到受支持的安全环境或返回。
|
||||
- `permission-denied`:展示简短说明和“重新授权”入口。
|
||||
- `not-found`:提示未检测到麦克风,提供重试授权或返回入口。
|
||||
- `not-readable`:提示麦克风被占用或暂时不可读,提供重试授权或返回入口。
|
||||
- `audio-context-blocked`:提示点击重试。
|
||||
- `calibration-timeout` / `calibration-sample-unreadable`:提示麦克风输入不可用,提供“重新校准”和返回入口。
|
||||
|
||||
可选开发调试降级:
|
||||
|
||||
- 本地 dev 可启用键盘 mock input,例如按住空格模拟音量峰值。
|
||||
- mock input 必须标记为开发/调试能力,不作为正式竞技能力。
|
||||
|
||||
## 11. 测试策略
|
||||
|
||||
### 11.1 Domain 单元测试(优先)
|
||||
|
||||
目标:不接 Phaser、不接 DOM、不接 Web Audio。
|
||||
|
||||
建议测试:
|
||||
|
||||
- `BarkDetector`:超过阈值且间隔足够时计为一次有效叫声。
|
||||
- `BarkDetector`:持续噪音不会无限计数。
|
||||
- `BarkDetector`:低于环境噪音阈值不计入叫声。
|
||||
- `EnergyTugOfWar`:玩家 power 高于对手时 energy 向玩家侧移动。
|
||||
- `EnergyTugOfWar`:energy clamp 在 `-100..100`。
|
||||
- `BarkBattleSession`:倒计时结束进入 playing。
|
||||
- `BarkBattleSession`:剩余时间归零进入 finished。
|
||||
- `BarkBattleSession`:按 energy 和 drawThreshold 判定胜 / 负 / 平。
|
||||
|
||||
### 11.2 Application 测试
|
||||
|
||||
目标:验证输入样本、AI、tick 和 snapshot 编排。
|
||||
|
||||
建议测试:
|
||||
|
||||
- 权限允许后进入校准,再进入倒计时。
|
||||
- 权限拒绝后 `phase` 为 `unavailable`、`errorReason` 为 `permission-denied`,不进入 playing。
|
||||
- 非安全上下文、设备未找到、设备不可读、AudioContext 被拦截时,controller snapshot 都进入 `phase: 'unavailable'`,并保留可供 HUD 区分的 `errorReason`。
|
||||
- 校准超时或样本持续不可读时,controller snapshot 使用 `errorReason: 'calibration-timeout'` 或 `calibration-sample-unreadable`,并提供重试校准动作。
|
||||
- 提交 mock audio sample 后 snapshot 中玩家状态更新。
|
||||
- AI 对手 power 参与能量条拉锯。
|
||||
- `lastEvents` 只发布新增视觉事件。
|
||||
|
||||
### 11.3 HUD 组件测试
|
||||
|
||||
目标:验证 snapshot 到 DOM 的展示映射。
|
||||
|
||||
建议测试:
|
||||
|
||||
- playing 阶段展示倒计时和能量条。
|
||||
- energy 正负值映射到玩家 / 对手侧比例。
|
||||
- `errorReason: 'permission-denied'` 展示重试授权入口。
|
||||
- `errorReason: 'unsupported'` 展示返回入口且不展示开始声控按钮。
|
||||
- `not-found`、`not-readable`、`non-secure-context`、`audio-context-blocked`、`calibration-timeout`、`calibration-sample-unreadable` 分别映射到可区分的简短状态文案和对应操作。
|
||||
- finished 展示胜负、叫声次数和再来一局。
|
||||
- 移动端 class / 结构不依赖 Phaser Canvas 才能渲染。
|
||||
|
||||
### 11.4 Phaser 集成与 smoke
|
||||
|
||||
自动化层面不强行在 Vitest 中完整启动 Phaser。建议:
|
||||
|
||||
- 用 adapter mock 测试 Scene 消费 snapshot 的纯映射函数。
|
||||
- 浏览器 smoke 验证真实 Canvas、Web Audio 和动画。
|
||||
- 若后续引入 Playwright,再做最小视觉和交互 smoke。
|
||||
|
||||
## 12. 验证命令建议
|
||||
|
||||
文档阶段只需要编码检查和 diff 检查:
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
git diff -- docs/prd/BARK_BATTLE_BDD_2026-05-11.md docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md
|
||||
```
|
||||
|
||||
后续实现 domain 后建议:
|
||||
|
||||
```bash
|
||||
npm run test -- --run src/games/bark-battle/domain/**/*.test.ts
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
后续实现 infrastructure/application 错误状态后建议:
|
||||
|
||||
```bash
|
||||
npm run test -- --run src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts src/games/bark-battle/application/__tests__/BarkBattleController.test.ts
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
后续实现 HUD 后建议:
|
||||
|
||||
```bash
|
||||
npm run test -- --run src/games/bark-battle/ui/**/*.test.tsx
|
||||
npm run lint:eslint
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
后续接入浏览器 runtime 后建议:
|
||||
|
||||
```bash
|
||||
npm run dev:web
|
||||
# 人工 smoke:授权麦克风 → 校准 → 发声 → 能量条变化 → 结算 → 再来一局
|
||||
```
|
||||
|
||||
若未来接入 Genarrative 正式玩法类型、后端持久化或发布链路,再追加对应契约、api-server 和 SpacetimeDB 验证;首版 runtime 原型不应提前新增这些命令作为门槛。
|
||||
|
||||
## 13. 后续落地顺序
|
||||
|
||||
建议后续实现按以下顺序推进:
|
||||
|
||||
1. 先建 `domain/` 和纯单元测试。
|
||||
2. 实现 `BarkDetector`、`EnergyTugOfWar`、`BarkBattleSession` 的最小规则。
|
||||
3. 建 `application/` controller,用 mock audio sample 跑通 snapshot。
|
||||
4. 实现 DOM HUD 的 permission / energy / timer / result 展示。
|
||||
5. 接入 `BrowserMicrophoneInput` 和校准流程。
|
||||
6. 接入 Phaser host、Scene 和 asset manifest,占位素材先跑通视觉反馈。
|
||||
7. 做移动端视口和权限失败 smoke。
|
||||
8. 产品确认后再决定是否进入正式玩法类型、作品发布和后端真相链。
|
||||
|
||||
## 14. 关键技术决策
|
||||
|
||||
1. 默认采用 Phaser 3 + TypeScript + Vite,符合 2D 浏览器游戏默认路线。
|
||||
2. 核心 simulation 放在纯 TypeScript domain,严格不依赖 Phaser / Web Audio / DOM。
|
||||
3. Web Audio 只作为输入 adapter,输出归一化 `BarkAudioSample`。
|
||||
4. Phaser Scene 是 renderer,只消费 snapshot 和 visual events,不承载规则真相。
|
||||
5. HUD 使用 DOM overlay,承载权限、能量条、倒计时、结算和移动端响应式布局。
|
||||
6. MVP 不做复杂狗叫语义识别,先用音量峰值、持续时长、冷却和环境噪音校准。
|
||||
7. MVP 建议玩家 vs AI 单机 runtime,正式成绩、排行榜、发布和奖励后续再交给后端链路。
|
||||
1055
docs/technical/BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md
Normal file
1055
docs/technical/BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,700 @@
|
||||
# 儿童动作识别互动玩法 Demo 热身关开发规格文档
|
||||
|
||||
> 日期:2026-05-09
|
||||
> 关联设计文档:[CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md](../design/CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md)
|
||||
> 适用范围:儿童动作识别互动玩法 Demo 固定启动热身关
|
||||
> 文档性质:开发落地规格
|
||||
> 说明:本文只将已确认的热身关设计内容拆解为工程可执行规格,不新增未确认的玩法、文案或视觉设计。
|
||||
|
||||
## 1. 开发目标
|
||||
|
||||
热身关作为 Demo 启动后的固定流程,需要完成以下开发目标:
|
||||
|
||||
1. 调用摄像头并识别用户和环境。
|
||||
2. 使用横屏比例展示热身关。
|
||||
3. 在屏幕中央地面生成绿色圆环,引导用户到达建议位置。
|
||||
4. 将用户实际位置生成角色剪影。
|
||||
5. 只对摄像头背景做虚化处理,表达隐私保护、屏蔽环境干扰,并营造空间感。
|
||||
6. 按固定步骤完成站位、招手、左右移动、挥动左右手、原地跳跃检测。
|
||||
7. 记录用户左右移动距离、挥动手臂空间和跳跃空间。
|
||||
8. 将记录结果仅保存在当前 Demo 体验会话内。
|
||||
9. 后续关卡使用热身记录的边界进行安全提醒和暂停恢复。
|
||||
10. 热身结束后进入关卡选择。
|
||||
|
||||
当前阶段先落浏览器本地 Demo。浏览器摄像头视频流仅作为舞台背景;热身动作检测以本地 mocap 动作数据源为准,通过 `useMocapInput` 连接 `http://127.0.0.1:8876/stream` 对应的 WebSocket 流,消费 `general.body.center_norm` 身体中心、手势、左右手坐标和跳跃事件推进站位、招手、左右手挥动与原地跳跃步骤。正式语音播报接口继续预留适配层,不阻塞前端热身流程、调试输入和页面表现骨架落地。
|
||||
|
||||
## 2. 非目标范围
|
||||
|
||||
热身关当前不包含以下内容:
|
||||
|
||||
1. 不接入创作模块。
|
||||
2. 不作为可配置玩法模板提供给创作者。
|
||||
3. 不允许跳过步骤。
|
||||
4. 不允许系统自动进入下一步。
|
||||
5. 不设置动作检测最长等待时间。
|
||||
6. 不做特定用户识别。
|
||||
7. 不跨会话保存左右空间边界、手臂挥动空间和跳跃空间。
|
||||
8. 不对手部细节进行识别,只对肢体进行区分。
|
||||
9. 本阶段不处理无硬件、拒绝摄像头、多人入镜、识别丢失等异常流程;这些问题记录为待决策事项,后续硬件与摄像头方案稳定后再重新设计。
|
||||
|
||||
## 3. 运行入口与流向
|
||||
|
||||
### 3.1 入口
|
||||
|
||||
用户进入 Demo 后,先进入热身关。
|
||||
|
||||
### 3.2 出口
|
||||
|
||||
用户完成热身关所有步骤后,进入关卡选择。
|
||||
|
||||
热身结束后展示“开始游戏”按钮,用户点击后进入宝贝识物首关本地 Demo。该入口只用于热身关后的本地体验验证;正式平台体验仍必须通过“宝贝识物”创作模板发布后,在寓教于乐板块进入。
|
||||
|
||||
### 3.3 固定流程顺序
|
||||
|
||||
热身关必须按照以下顺序执行:
|
||||
|
||||
```text
|
||||
进入热身关
|
||||
↓
|
||||
到达中央绿色圆环并保持 2 秒
|
||||
↓
|
||||
招手 / 摆手
|
||||
↓
|
||||
热身说明
|
||||
↓
|
||||
向左一步,到达左侧绿色圆环并保持 2 秒
|
||||
↓
|
||||
回到中间,到达中央绿色圆环并保持 2 秒
|
||||
↓
|
||||
向右一步,到达右侧绿色圆环并保持 2 秒
|
||||
↓
|
||||
回到中间,到达中央绿色圆环并保持 2 秒
|
||||
↓
|
||||
挥动左手
|
||||
↓
|
||||
挥动右手
|
||||
↓
|
||||
原地跳一下
|
||||
↓
|
||||
播放热身结束特效和结束语音
|
||||
↓
|
||||
进入关卡选择
|
||||
```
|
||||
|
||||
## 4. 页面基础表现规格
|
||||
|
||||
### 4.1 横屏比例
|
||||
|
||||
热身关需要使用横屏比例制作和展示,适用于电视屏幕、电脑屏幕等环境。
|
||||
|
||||
### 4.2 摄像头画面处理
|
||||
|
||||
用户进入热身关时调用摄像头。
|
||||
|
||||
摄像头画面处理要求:
|
||||
|
||||
1. 识别用户和环境。
|
||||
2. 将用户实际位置生成角色剪影。
|
||||
3. 只对摄像头背景做虚化处理。
|
||||
4. 用户角色剪影用于表达用户在画面中的实际位置。
|
||||
5. 背景虚化用于表达对用户隐私的保护、屏蔽周围环境干扰,并营造空间感。
|
||||
|
||||
### 4.3 绿色圆环
|
||||
|
||||
绿色圆环用于指引用户到达指定位置。
|
||||
|
||||
绿色圆环出现位置包括:
|
||||
|
||||
1. 屏幕中央位置的地面。
|
||||
2. 屏幕中心向左一个身位,约半米的地面位置。
|
||||
3. 屏幕中心向右一个身位,约半米的地面位置。
|
||||
|
||||
“约半米”技术上以角色剪影移动距离为准,后续根据体验调校。
|
||||
|
||||
### 4.4 绿色圆环选中状态
|
||||
|
||||
用户到达绿色圆环后,绿色圆环进入 2 秒选中状态。
|
||||
|
||||
用户需要在绿色圆环内保持 2 秒,才算完成该位置检测。
|
||||
|
||||
## 5. 通用交互规则
|
||||
|
||||
### 5.1 不允许跳过
|
||||
|
||||
每个步骤都必须由用户完成。
|
||||
|
||||
系统不提供跳过,也不自动进入下一步。
|
||||
|
||||
### 5.2 引导动画规则
|
||||
|
||||
每个动作等待 3 秒后可以播放对应引导动画。
|
||||
|
||||
当前不设置最长等待时间。
|
||||
|
||||
### 5.3 手势检测规则
|
||||
|
||||
招手 / 摆手、挥动左手、挥动右手三类动作需要有动作区分。
|
||||
|
||||
检测只区分肢体,不识别手部细节。
|
||||
|
||||
### 5.4 手势引导规则
|
||||
|
||||
挥动哪只手,就使用对应手的引导。
|
||||
|
||||
## 6. 状态机规格
|
||||
|
||||
### 6.1 状态列表
|
||||
|
||||
热身关至少需要支持以下流程状态:
|
||||
|
||||
| 状态 ID | 状态名称 | 进入条件 | 完成条件 | 下一状态 |
|
||||
|---|---|---|---|---|
|
||||
| warmup_enter | 进入热身关 | 用户进入 Demo | 摄像头调用并展示中央绿色圆环 | center_arrive |
|
||||
| center_arrive | 到达中央圆环 | 中央绿色圆环出现 | 用户到达中央圆环并保持 2 秒 | wave_greeting |
|
||||
| wave_greeting | 招手教学 | 中央圆环完成并播放圆圈消失特效 | 用户完成招手 / 摆手 | warmup_intro |
|
||||
| warmup_intro | 热身说明 | 招手 / 摆手完成 | 播放热身说明文案与语音 | move_left |
|
||||
| move_left | 向左一步 | 热身说明完成 | 用户到达左侧圆环并保持 2 秒 | return_center_1 |
|
||||
| return_center_1 | 回到中间(一) | 向左一步完成 | 用户到达中央圆环并保持 2 秒 | move_right |
|
||||
| move_right | 向右一步 | 回到中间(一)完成 | 用户到达右侧圆环并保持 2 秒 | return_center_2 |
|
||||
| return_center_2 | 回到中间(二) | 向右一步完成 | 用户到达中央圆环并保持 2 秒 | wave_left_hand |
|
||||
| wave_left_hand | 挥动左手 | 回到中间(二)完成 | 用户完成挥动左手 | wave_right_hand |
|
||||
| wave_right_hand | 挥动右手 | 挥动左手完成 | 用户完成挥动右手 | jump_once |
|
||||
| jump_once | 原地跳一下 | 挥动右手完成 | 用户完成原地跳一下 | warmup_finish |
|
||||
| warmup_finish | 热身结束 | 原地跳一下完成 | 播放热身结束特效和结束语音 | level_select |
|
||||
| level_select | 关卡选择 | 热身结束 | 进入关卡选择 | - |
|
||||
|
||||
### 6.2 状态推进约束
|
||||
|
||||
1. 状态必须按顺序推进。
|
||||
2. 用户未完成当前状态检测目标时,不进入下一状态。
|
||||
3. 位置类状态必须满足“到达绿色圆环并保持 2 秒”。
|
||||
4. 动作类状态没有最长等待时间。
|
||||
5. 动作类状态等待 3 秒后可以播放对应引导动画。
|
||||
|
||||
### 6.3 开发者调试输入
|
||||
|
||||
本地 Demo 需要支持开发者调试模式,用于无摄像头和自动化验证场景。
|
||||
|
||||
调试映射如下:
|
||||
|
||||
1. `A` 键映射用户向左移动。
|
||||
2. `D` 键映射用户向右移动。
|
||||
3. 鼠标左键按下并拖动映射左手轨迹。
|
||||
4. 鼠标右键按下并拖动映射右手轨迹。
|
||||
5. 空格键映射原地跳跃。
|
||||
|
||||
调试输入只作为本地 Demo 与测试辅助,不代表正式动作识别硬件口径。正式摄像头接入后,位置、手势和跳跃判断需要按摄像头硬件调教结果重新校准。
|
||||
|
||||
## 7. 分步骤开发规格
|
||||
|
||||
### 7.1 进入热身关
|
||||
|
||||
#### 展示内容
|
||||
|
||||
- 调用摄像头。
|
||||
- 识别用户和环境。
|
||||
- 屏幕中央地面显示绿色圆环。
|
||||
- 用户实际位置显示为角色剪影。
|
||||
- 只对摄像头背景做虚化。
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
欢迎你,小朋友,见到你真开心
|
||||
请你来到圆圈这里和我打个招呼吧
|
||||
```
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户到达中央绿色圆环并保持 2 秒。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
播放圆圈消失特效。
|
||||
|
||||
---
|
||||
|
||||
### 7.2 招手教学
|
||||
|
||||
#### 展示内容
|
||||
|
||||
播放招手的手势引导。
|
||||
|
||||
用户进入该步骤 3 秒仍未完成动作时,可以播放引导动画。
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户完成招手 / 摆手手势。
|
||||
|
||||
#### 完成后
|
||||
|
||||
进入热身说明。
|
||||
|
||||
---
|
||||
|
||||
### 7.3 热身说明
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
你好呀小朋友,为了你玩的安全和开心,先来和我一起热个身吧
|
||||
```
|
||||
|
||||
#### 完成后
|
||||
|
||||
进入“向左一步”。
|
||||
|
||||
---
|
||||
|
||||
### 7.4 向左一步
|
||||
|
||||
#### 展示内容
|
||||
|
||||
屏幕中心向左一个身位,约半米的地面位置出现新的绿色圆圈。
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
向左一步
|
||||
```
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户到达左侧绿色圆环并保持 2 秒。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
#### 数据记录
|
||||
|
||||
记录本次向左移动距离,作为后续关卡中的左侧空间边界参考。
|
||||
|
||||
---
|
||||
|
||||
### 7.5 回到中间来(一)
|
||||
|
||||
#### 展示内容
|
||||
|
||||
场地中心位置出现绿色圆圈。
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
回到中间来
|
||||
```
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户到达中央绿色圆环并保持 2 秒。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.6 向右一步
|
||||
|
||||
#### 展示内容
|
||||
|
||||
屏幕中心向右一个身位,约半米的地面位置出现新的绿色圆圈。
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
向右一步
|
||||
```
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户到达右侧绿色圆环并保持 2 秒。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
#### 数据记录
|
||||
|
||||
记录本次向右移动距离,作为后续关卡中的右侧空间边界参考。
|
||||
|
||||
---
|
||||
|
||||
### 7.7 回到中间来(二)
|
||||
|
||||
#### 展示内容
|
||||
|
||||
场地中心位置出现绿色圆圈。
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
回到中间来
|
||||
```
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户到达中央绿色圆环并保持 2 秒。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.8 挥动左手
|
||||
|
||||
#### 展示内容
|
||||
|
||||
播放伸展手臂挥动左手的手势引导。
|
||||
|
||||
用户进入该步骤 3 秒仍未完成动作时,可以播放引导动画。
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
挥动左手
|
||||
```
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户完成挥动左手。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
#### 数据记录
|
||||
|
||||
记录用户挥动左手的空间,保存为该用户对应的行为坐标。
|
||||
|
||||
---
|
||||
|
||||
### 7.9 挥动右手
|
||||
|
||||
#### 展示内容
|
||||
|
||||
播放伸展手臂挥动右手的手势引导。
|
||||
|
||||
用户进入该步骤 3 秒仍未完成动作时,可以播放引导动画。
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
挥动右手
|
||||
```
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户完成挥动右手。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
```text
|
||||
真棒
|
||||
```
|
||||
|
||||
#### 数据记录
|
||||
|
||||
记录用户挥动右手的空间,保存为该用户对应的行为坐标。
|
||||
|
||||
---
|
||||
|
||||
### 7.10 原地跳一下
|
||||
|
||||
#### 展示内容
|
||||
|
||||
播放跳跃姿势引导。
|
||||
|
||||
用户进入该步骤 3 秒仍未完成动作时,可以播放引导动画。
|
||||
|
||||
#### 文案与语音
|
||||
|
||||
```text
|
||||
原地跳一下
|
||||
```
|
||||
|
||||
#### 检测目标
|
||||
|
||||
用户完成原地跳一下。
|
||||
|
||||
#### 数据记录
|
||||
|
||||
记录用户跳跃空间,保存为该用户对应的行为坐标。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
播放热身结束特效、上浮字幕和语音:
|
||||
|
||||
```text
|
||||
真厉害,你是我见过最聪明的小朋友
|
||||
别走开,现在开始我们的游戏吧
|
||||
```
|
||||
|
||||
#### 完成后
|
||||
|
||||
进入关卡选择。
|
||||
|
||||
## 8. 当前 Demo 体验会话数据
|
||||
|
||||
### 8.1 保存范围
|
||||
|
||||
以下数据仅在当前 Demo 体验会话内保存:
|
||||
|
||||
1. 左侧空间边界。
|
||||
2. 右侧空间边界。
|
||||
3. 左手挥动空间。
|
||||
4. 右手挥动空间。
|
||||
5. 跳跃空间。
|
||||
|
||||
当前 Demo 体验会话数据需要满足:
|
||||
|
||||
1. 用户刷新产品或退出产品后失效。
|
||||
2. 用户只关闭当前游戏关卡并重新进入时,可以直接来到开始游戏界面,不强制重复热身。
|
||||
3. 首版可使用前端运行时内存或同等生命周期容器保存;不得跨产品刷新持久化保存。
|
||||
|
||||
### 8.2 当前 Demo 体验会话定义
|
||||
|
||||
“当前 Demo 体验会话”指用户本次打开并体验 Demo 的过程。
|
||||
|
||||
当用户关闭 Demo、刷新页面、退出当前体验流程、重新进入 Demo,或更换设备后,系统不再沿用上一次热身记录的数据,需要重新完成热身关并重新记录。
|
||||
|
||||
### 8.3 仅会话内保存原因
|
||||
|
||||
采用仅当前 Demo 体验会话内保存的原因:
|
||||
|
||||
1. 每名用户的身高、体型、动作幅度不同,安全边界和行为坐标会发生变化。
|
||||
2. 当前 Demo 不做特定用户识别,无法确认下一次体验的是否仍是同一名用户。
|
||||
3. 用户所处的体验环境可能变化,包括房间大小、摄像头位置、屏幕位置和站立距离。
|
||||
4. 为保证安全,每次体验都需要重新对环境和距离进行安全检查。
|
||||
|
||||
## 9. 后续关卡安全边界使用规则
|
||||
|
||||
后续关卡需要使用热身关记录的左右空间边界进行安全判断。
|
||||
|
||||
### 9.1 覆盖安全边界线
|
||||
|
||||
当用户身体主体覆盖安全边界线时,对应侧屏幕边缘出现虚影提醒。
|
||||
|
||||
### 9.2 超出安全边界线
|
||||
|
||||
当用户身体主体超出安全边界线时:
|
||||
|
||||
1. 关卡内容暂停。
|
||||
2. 屏幕虚化。
|
||||
3. 屏幕中央地面出现绿色圆圈。
|
||||
4. 屏幕提示文案:
|
||||
|
||||
```text
|
||||
小朋友,要注意安全哦
|
||||
```
|
||||
|
||||
5. 用户需要回到中心绿色圆圈并保持 2 秒后,才能继续游戏内容。
|
||||
|
||||
## 10. 识别能力清单
|
||||
|
||||
热身关需要接入或实现以下识别能力:
|
||||
|
||||
1. 摄像头调用。
|
||||
2. 用户识别。
|
||||
3. 环境识别。
|
||||
4. 用户实际位置识别。
|
||||
5. 用户是否到达中央绿色圆环位置。
|
||||
6. 用户是否在绿色圆环内持续保持 2 秒。
|
||||
7. 用户是否到达左侧约半米绿色圆环位置。
|
||||
8. 用户是否到达右侧约半米绿色圆环位置。
|
||||
9. 招手 / 摆手手势识别。
|
||||
10. 挥动左手识别。
|
||||
11. 挥动右手识别。
|
||||
12. 原地跳跃姿势识别。
|
||||
13. 用户左右移动距离记录。
|
||||
14. 用户挥动手臂空间记录。
|
||||
15. 用户跳跃空间记录。
|
||||
16. 用户身体主体覆盖安全边界线判断。
|
||||
17. 用户身体主体超出安全边界线判断。
|
||||
18. 用户回到中心绿色圆环并保持 2 秒判断。
|
||||
|
||||
## 11. 表现能力清单
|
||||
|
||||
热身关需要实现以下表现能力:
|
||||
|
||||
1. 横屏比例显示。
|
||||
2. 摄像头背景虚化。
|
||||
3. 用户位置生成角色剪影。
|
||||
4. 屏幕中央地面绿色圆环。
|
||||
5. 左侧约半米地面绿色圆环。
|
||||
6. 右侧约半米地面绿色圆环。
|
||||
7. 绿色圆环 2 秒选中状态。
|
||||
8. 圆圈消失特效。
|
||||
9. 招手手势引导。
|
||||
10. 伸展手臂挥动左手手势引导。
|
||||
11. 伸展手臂挥动右手手势引导。
|
||||
12. 跳跃姿势引导。
|
||||
13. 热身结束特效。
|
||||
14. 上浮字幕。
|
||||
15. 语音播报。
|
||||
16. 安全边界虚影提醒。
|
||||
17. 关卡暂停时屏幕虚化。
|
||||
18. 关卡暂停时屏幕中央地面绿色圆圈。
|
||||
19. 关卡暂停提示文案。
|
||||
|
||||
角色剪影、绿色圆环、虚影提醒、圆圈消失特效、手势引导动画和热身结束特效的正式视觉资源将通过 gpt-image-2 设计和生成。本地 Demo 阶段可以先使用 CSS、Canvas 或临时占位资源实现相同交互位置与状态,不把占位资源写死为正式资产。
|
||||
|
||||
## 12. 固定文案与语音清单
|
||||
|
||||
以下文案需要作为屏幕中上方浮现文字,并同步语音播报。
|
||||
|
||||
正式语音播报后续接入语音播报功能接口。本地 Demo 阶段保留播报适配层与调用点,可先只展示文字,不强制生成或播放正式语音资产。
|
||||
|
||||
```text
|
||||
欢迎你,小朋友,见到你真开心
|
||||
请你来到圆圈这里和我打个招呼吧
|
||||
你好呀小朋友,为了你玩的安全和开心,先来和我一起热个身吧
|
||||
向左一步
|
||||
真棒
|
||||
回到中间来
|
||||
真棒
|
||||
向右一步
|
||||
真棒
|
||||
回到中间来
|
||||
真棒
|
||||
挥动左手
|
||||
真棒
|
||||
挥动右手
|
||||
真棒
|
||||
原地跳一下
|
||||
真厉害,你是我见过最聪明的小朋友
|
||||
别走开,现在开始我们的游戏吧
|
||||
小朋友,要注意安全哦
|
||||
```
|
||||
|
||||
## 13. 开发验收标准
|
||||
|
||||
### 13.1 热身流程验收
|
||||
|
||||
1. 用户进入 Demo 后先进入热身关。
|
||||
2. 热身关使用横屏比例展示。
|
||||
3. 摄像头被调用。
|
||||
4. 用户位置显示为角色剪影。
|
||||
5. 摄像头背景被虚化。
|
||||
6. 中央、左侧、右侧绿色圆环可以按流程出现。
|
||||
7. 用户到达每个绿色圆环后,需要保持 2 秒才算完成。
|
||||
8. 每个步骤未完成时不能跳过,也不能自动进入下一步。
|
||||
9. 动作等待 3 秒后可以播放对应引导动画。
|
||||
10. 所有固定文案可以展示并语音播报。
|
||||
11. 完成全部热身步骤后进入关卡选择。
|
||||
|
||||
### 13.2 数据记录验收
|
||||
|
||||
1. 完成向左一步后,可以记录左侧空间边界。
|
||||
2. 完成向右一步后,可以记录右侧空间边界。
|
||||
3. 完成挥动左手后,可以记录左手挥动空间。
|
||||
4. 完成挥动右手后,可以记录右手挥动空间。
|
||||
5. 完成原地跳一下后,可以记录跳跃空间。
|
||||
6. 以上数据仅在当前 Demo 体验会话内保存。
|
||||
7. 重新进入 Demo 后,不沿用上一次热身记录,需要重新完成热身关。
|
||||
|
||||
### 13.3 后续关卡安全边界验收
|
||||
|
||||
1. 用户身体主体覆盖安全边界线时,对应侧屏幕边缘出现虚影提醒。
|
||||
2. 用户身体主体超出安全边界线时,关卡内容暂停。
|
||||
3. 关卡暂停时,屏幕虚化。
|
||||
4. 关卡暂停时,屏幕中央地面出现绿色圆圈。
|
||||
5. 关卡暂停时,展示提示文案:
|
||||
|
||||
```text
|
||||
小朋友,要注意安全哦
|
||||
```
|
||||
|
||||
6. 用户回到中心绿色圆圈并保持 2 秒后,游戏内容继续。
|
||||
|
||||
## 14. 不确定项与补充确认
|
||||
|
||||
当前需求已明确本文所需的热身关开发规格。
|
||||
|
||||
以下内容作为待决策事项保留,后续硬件、摄像头和正式关卡设计稳定后再补充:
|
||||
|
||||
1. 具体接入的动作识别 SDK、硬件接口和摄像头接口。
|
||||
2. 无硬件、摄像头拒绝授权、多人入镜、识别不到用户、跟踪丢失等异常流程。
|
||||
3. 角色剪影、圆环、虚影提醒、特效、手势引导动画的正式资源文件命名。
|
||||
4. 绿色圆环、角色剪影、安全边界在线性空间或屏幕坐标中的正式计算公式。
|
||||
5. 正式关卡选择页与后续游戏关卡的具体页面结构。
|
||||
|
||||
## 15. 第 3 项本地 Demo 落地记录
|
||||
|
||||
本地浏览器 Demo 入口已落在:
|
||||
|
||||
```text
|
||||
/child-motion-demo
|
||||
```
|
||||
|
||||
当前实现范围:
|
||||
|
||||
1. `src/ChildMotionDemoApp.tsx` 挂载独立 Demo 应用壳。
|
||||
2. `src/components/child-motion-demo/childMotionWarmupModel.ts` 维护热身步骤、圆环目标、2 秒保持判定、热身校准记录和当前运行时会话完成标记。
|
||||
3. `src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 实现横屏舞台、背景虚化占位层、角色剪影、绿色圆环、手势引导、热身记录面板、热身完成后的“开始游戏”按钮,并复用宝贝识物运行态进入首关本地 Demo。
|
||||
4. `src/services/child-motion-demo/childMotionDebugInput.ts` 保留开发者调试输入适配层,后续可被正式动作识别 SDK 适配层替换或并行接入。
|
||||
5. `src/routing/appRoutes.tsx` 新增 `/child-motion-demo` 独立路由,并复用 `VITE_ENABLE_EDUTAINMENT_ENTRY` 开关;开关关闭时不允许通过该直达路径进入 Demo。
|
||||
|
||||
当前调试输入:
|
||||
|
||||
1. `A` 键映射用户向左移动,松开后回到中心。
|
||||
2. `D` 键映射用户向右移动,松开后回到中心。
|
||||
3. 鼠标左键按下并拖动映射左手轨迹。
|
||||
4. 鼠标右键按下并拖动映射右手轨迹。
|
||||
5. 空格键映射原地跳跃。
|
||||
|
||||
当前硬件和动作检测接口接入:
|
||||
|
||||
1. 浏览器摄像头视频流已接入舞台背景。
|
||||
2. 热身关全流程已通过 `src/services/useMocapInput.ts` 接入本地 mocap WebSocket `/stream`;动作数据源状态优先于浏览器背景摄像头状态展示。
|
||||
3. mocap 包支持从 `general.body.center_norm` 读取身体中心,位置类步骤使用该身体中心更新角色剪影横向位置并完成圆环保持检测。
|
||||
4. mocap 包支持从 `actions/action/gesture/gestures/event/name/type` 读取动作名,并支持 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 读取左右手坐标。
|
||||
5. `hands[].landmarks` 存在时优先用手腕和 MCP 点计算掌心中心;掌心点不足时退回 wrist landmark,再退回 hand 直出坐标。
|
||||
6. `wave_greeting` 可由 `wave/wave_greeting/hand_wave/open_palm` 等动作或 open palm 手势完成。
|
||||
7. `wave_left_hand` 和 `wave_right_hand` 优先消费对应左右手动作名;当硬件只持续输出手部坐标时,也可以根据连续手部横向轨迹完成挥手检测。
|
||||
8. `jump_once` 消费 `jump/jump_once/hop` 等跳跃动作事件完成。
|
||||
9. 键盘 `A/D/Space` 与鼠标左右键拖拽仍保留为本地 Demo 调试兜底,不代表正式硬件口径。
|
||||
|
||||
当前未接入但已保留边界:
|
||||
|
||||
1. 正式语音播报接口暂不接入,当前先展示热身文案。
|
||||
2. 后续关卡安全边界暂停逻辑暂未落地,当前只完成热身记录和宝贝识物首关本地 Demo 衔接。
|
||||
|
||||
## 16. 当前视觉资产与生图口径补充
|
||||
|
||||
儿童动作 Demo 的视觉口径已经统一收敛到绘本风格草地舞台:
|
||||
|
||||
1. 舞台主环境采用卡通绘本风格、明亮草地、天空、小山坡和树木的组合,默认背景环境需要保证中心与下方前景留空,便于角色轮廓和地面指示环叠加。
|
||||
2. 该卡通绘本草地风格是儿童动作 Demo 后续场景、物品、UI 资源的全局风格要求;新增资源不得切回暗色科技风、真实照片风或后台面板风。
|
||||
3. `src/index.css` 中的热身舞台、摄像头背景层、地面、角色轮廓、地面圆环、开始按钮和横屏提示均按绘本草地风格接入真实资源;资源加载失败时保留 CSS 兜底。
|
||||
4. 生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 触发;脚本使用 `gpt-image-2-all` 调用 VectorEngine `POST /v1/images/generations`,透明资源先生成品红底源图,再在本地移除色键,源图写入 `tmp/child-motion-demo-assets/`。
|
||||
5. 当前已生成并接入以下正式 Demo 资源:
|
||||
- `public/child-motion-demo/picture-book-grass-stage.png`:默认草地舞台背景。
|
||||
- `public/child-motion-demo/picture-book-foreground-grass-v2.png`:底部前景草坪条,只覆盖舞台下沿,不作为整块地板拉伸。
|
||||
- `public/child-motion-demo/picture-book-ground-ring-v2.png`:已按透视绘制的地面椭圆指示环,CSS 只等比缩放。
|
||||
- `public/child-motion-demo/picture-book-character-outline-v2.png`:半透明用户角色轮廓,使用独立去背后处理避免内部填充被误删。
|
||||
- `public/child-motion-demo/picture-book-hud-strip-v2.png`:顶部 HUD 细长软纸条。
|
||||
- `public/child-motion-demo/picture-book-calibration-strip-v2.png`:右下角五格热身状态条。
|
||||
- `public/child-motion-demo/picture-book-start-panel-v2.png`:开始按钮背后的轻盈托盘。
|
||||
- `public/child-motion-demo/picture-book-ui-button-v2.png`:开始按钮绘本风按钮底图。
|
||||
6. v2 资源按最终用途拆分,CSS 必须按资源原始比例、`aspect-ratio` 或 `background-size: contain / auto` 等方式等比使用;禁止把方形面板强行拉伸为 HUD、状态条或地板,也禁止把底部草坪扩展成覆盖角色脚下的大色块。
|
||||
7. 若后续补充或重绘资源,应先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt 和输出路径,再使用 `--live --only <asset-id>` 小批量生成;仅调整透明去背、裁切、画布归一或品红边缘时,可用 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用 `tmp/child-motion-demo-assets/` 中的源图,不额外请求 image-2;不得把 `VECTOR_ENGINE_API_KEY`、源图或中间预览图提交到仓库。
|
||||
|
||||
已执行的定向验证命令:
|
||||
|
||||
```bash
|
||||
npx eslint src/components/child-motion-demo/ChildMotionWarmupDemo.tsx src/components/child-motion-demo/childMotionWarmupModel.ts src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/components/child-motion-demo/childMotionWarmupModel.test.ts src/services/child-motion-demo/childMotionDebugInput.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/services/child-motion-demo/index.ts src/ChildMotionDemoApp.tsx src/routing/appRoutes.tsx src/routing/appRoutes.test.ts --ext .ts,.tsx --max-warnings 0
|
||||
npx vitest run src/components/child-motion-demo/childMotionWarmupModel.test.ts src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts
|
||||
npm run check:encoding
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
# 创作 Agent 流式失败保留可见回复修复 2026-05-05
|
||||
|
||||
## 1. 问题
|
||||
|
||||
方洞挑战等轻量玩法复用 `usePlatformCreationAgentFlowController` 与 `creationAgentSse.ts` 消费 `reply_delta / session / error`。当上游 LLM 已经返回部分 `replyText`,但后续因为超时、上游断流、SSE 解析或最终 JSON 解析失败而发送 `error` 事件时,前端会在 `finally` 里退出流式态。
|
||||
|
||||
旧 UI 只在 `isStreamingReply=true` 时展示临时 assistant 气泡,因此用户会先看到一段回答,然后回答突然消失,只剩错误提示。
|
||||
|
||||
## 2. 目标
|
||||
|
||||
1. 已经展示给用户的流式回复不能因为最终失败从聊天区消失。
|
||||
2. SSE `error` 仍然终止本轮提交,并保留错误提示。
|
||||
3. 后端错误不能只压成 `上游服务请求失败`,应优先把 LLM 流错误原因放到业务 `message`。
|
||||
4. 不修改 SpacetimeDB schema、消息表结构或玩法运行规则。
|
||||
|
||||
## 3. 前端契约
|
||||
|
||||
`readCreationAgentSessionFromSse()` 在收到 `reply_delta` 后再收到 `error` 时,必须先触发 `onUpdate(text)`,再抛出错误。调用方可以从最近一次可见文本中恢复 UI。
|
||||
|
||||
`usePlatformCreationAgentFlowController.submitMessage()` 的失败收尾规则:
|
||||
|
||||
1. 提交时仍先追加 optimistic user message。
|
||||
2. 每次 `onUpdate` 同步更新 `streamingReplyText` 与最近可见回复引用。
|
||||
3. 如果 `streamMessage()` 抛错且最近可见回复非空,把该文本追加为本地 assistant `warning` 消息。
|
||||
4. 再设置 `error`,最后关闭 `isStreamingReply`。
|
||||
5. 成功拿到最终 session 时,以后端 session snapshot 为准,并清空最近可见回复。
|
||||
|
||||
这条本地 `warning` 消息只用于失败态 UI 保留,不代表该 assistant 消息已经写入 SpacetimeDB。
|
||||
|
||||
## 4. 后端契约
|
||||
|
||||
`creation_agent_llm_turn` 在 `LlmClient::stream_text()` 失败时,返回:
|
||||
|
||||
```text
|
||||
<玩法 generation_failed 文案>:<LlmError Display>
|
||||
```
|
||||
|
||||
同时写 `warn` 日志,便于结合 `logs/llm-raw` 定位上游原始输入输出。
|
||||
|
||||
方洞挑战 SSE 错误提取优先级:
|
||||
|
||||
1. `error.details.message`
|
||||
2. `error.message`
|
||||
3. 其它嵌套 JSON message
|
||||
4. 原始 body 文本
|
||||
5. 状态码兜底
|
||||
|
||||
## 5. 验收
|
||||
|
||||
1. `reply_delta` 后收到 `error` 时,测试应断言 `onUpdate` 已经收到可见文本。
|
||||
2. 控制器测试应断言失败后本地消息列表包含 user 消息和 assistant warning 消息。
|
||||
3. `cargo check -p api-server` 通过。
|
||||
4. `npm run typecheck` 与编码检查通过。
|
||||
@@ -0,0 +1,113 @@
|
||||
# 创作入口配置数据库化与 Runtime 缺失作品返回首页
|
||||
|
||||
日期:2026-05-10
|
||||
|
||||
## 背景
|
||||
|
||||
前端创作中心原本把新建作品入口配置保存在前端代码中,导致入口是否展示、是否开放、卡片文案和 api-server 路由可用性无法使用同一份事实源控制。
|
||||
|
||||
同时,用户直接访问 `/runtime/<玩法>?work=<作品号>` 时,如果作品不存在,运行态会先弹出错误提示;但弹窗关闭后仍停留在无运行数据的页面,容易出现空白页。
|
||||
|
||||
## 设计结论
|
||||
|
||||
1. 创作入口配置事实源迁入 SpacetimeDB。
|
||||
2. 前端只通过 `GET /api/creation-entry/config` 读取配置并派生展示卡片。
|
||||
3. api-server 使用同一份配置对相关运行时路由做熔断。
|
||||
4. 直接 runtime 深链找不到作品时,弹窗确认后回到首页 `/`,避免停留在空白运行态。
|
||||
|
||||
## 数据模型
|
||||
|
||||
SpacetimeDB 新增两张表:
|
||||
|
||||
- `creation_entry_config`
|
||||
- 全局配置头,保存创作入口主卡片和类型选择弹窗文案。
|
||||
- `creation_entry_type_config`
|
||||
- 每个玩法入口的展示与开关配置。
|
||||
- 关键字段:
|
||||
- `id`
|
||||
- `title`
|
||||
- `subtitle`
|
||||
- `badge`
|
||||
- `image_src`
|
||||
- `visible`
|
||||
- `open`
|
||||
- `sort_order`
|
||||
|
||||
其中:
|
||||
|
||||
- `visible=false`:前端隐藏入口。
|
||||
- `open=false`:前端展示为锁定/暂不可创建,api-server 据此熔断对应玩法 API;只隐藏创作页入口但保留既有作品链路时不要关闭 `open`。
|
||||
- `sort_order`:数据库排序字段,前端只做可见/锁定分组派生。
|
||||
|
||||
## API
|
||||
|
||||
新增:
|
||||
|
||||
```text
|
||||
GET /api/creation-entry/config
|
||||
```
|
||||
|
||||
返回 shared-contracts 中的 `CreationEntryConfigResponse`:
|
||||
|
||||
```json
|
||||
{
|
||||
"startCard": {
|
||||
"title": "...",
|
||||
"description": "...",
|
||||
"idleBadge": "...",
|
||||
"busyBadge": "..."
|
||||
},
|
||||
"typeModal": {
|
||||
"title": "...",
|
||||
"description": "..."
|
||||
},
|
||||
"creationTypes": [
|
||||
{
|
||||
"id": "puzzle",
|
||||
"title": "拼图",
|
||||
"subtitle": "拼图关卡创作",
|
||||
"badge": "可创建",
|
||||
"imageSrc": "/creation-type-references/puzzle.webp",
|
||||
"visible": true,
|
||||
"open": true,
|
||||
"sortOrder": 30,
|
||||
"updatedAtMicros": 0
|
||||
}
|
||||
],
|
||||
"updatedAtMicros": 0
|
||||
}
|
||||
```
|
||||
|
||||
## 前端约束
|
||||
|
||||
- 禁止再新增 `src/config/newWorkEntryConfig.ts` 这类入口事实源。
|
||||
- 创作入口 UI 使用 `src/services/creationEntryConfigService.ts` 拉取后端配置。
|
||||
- `src/components/platform-entry/platformEntryCreationTypes.ts` 只保留展示派生:
|
||||
- `visible -> hidden`
|
||||
- `open -> locked`
|
||||
- `sortOrder -> 初始顺序`
|
||||
- 缺失作品的 runtime 深链恢复策略放在 `src/routing/runtimeNotFoundRecovery.ts`。
|
||||
|
||||
## Runtime 缺失作品恢复
|
||||
|
||||
当路径是以下任意 runtime 深链,并且作品读取/启动返回 404 或 NOT_FOUND:
|
||||
|
||||
- `/runtime/puzzle`
|
||||
- `/runtime/match3d`
|
||||
- `/runtime/big-fish`
|
||||
- `/runtime/square-hole`
|
||||
- `/runtime/visual-novel`
|
||||
|
||||
前端执行:
|
||||
|
||||
1. 清理当前运行态选择和错误状态。
|
||||
2. 弹出“作品不存在或已下架,将返回首页。”。
|
||||
3. 跳转首页 `/`。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts src/routing/runtimeNotFoundRecovery.test.ts
|
||||
npm run typecheck
|
||||
cd server-rs && cargo check -p api-server -p spacetime-module --no-default-features
|
||||
```
|
||||
@@ -0,0 +1,950 @@
|
||||
# 创意互动内容生成 Agent 技术方案 2026-05-05
|
||||
|
||||
## 1. 目标
|
||||
|
||||
构建一个基于 LangChain-Rust 的创意互动内容生成 Agent。用户输入文字、图片、文档或混合素材后,Agent 不通过规则分类硬选玩法,而是以模型为核心完成理解、判断、规划和执行:先理解用户真正想表达的创作意图,再选择当前可用的互动内容模板,最后调用拼图模块工具把内容填入草稿契约中。
|
||||
|
||||
当前版本只支持拼图模板。RPG 世界、大鱼吃小鱼、抓大鹅 Match3D、方洞挑战等模板必须在 Agent 可见能力中标记为 `unsupported`,不能创建这些玩法的目标 session。即便只有拼图可用,Agent 仍必须先展示多个拼图模板候选,用户选择某个模板后,再确认该模板下的关卡模式、关卡数和预计积分范围,确认后才进入草稿生成。
|
||||
|
||||
本方案不再把 Agent 设计成“规则路由 workflow”。规则只作为安全护栏、契约校验和成本控制。真正的模板选择、素材理解、草稿构思、行动顺序和补问判断由模型通过工具调用和反思循环完成。
|
||||
|
||||
## 2. LangChain-Rust 选型依据
|
||||
|
||||
当前可参考 `langchainrust` crate 作为 Rust 侧 Agent 编排底座。官方 crate 页面显示 `0.2.18` 支持 Agents、Tools、Memory、Chains、RAG、BM25、Hybrid Retrieval、LangGraph、Typed/JSON output parser、Function Calling、Callbacks 等能力;其中 Agent 层包括 ReActAgent、FunctionCallingAgent、AgentExecutor 和 LangGraph,Memory 层包括 Buffer、Window、Summary、SummaryBuffer、Persistent。
|
||||
|
||||
落地时建议先以 `langchainrust = "0.2.18"` 做隔离性 PoC,不直接替换现有 `platform-llm`。若 crate API 与 docs.rs 最新构建存在差异,以源码和本地编译结果为准,先封装一层 `platform-agent` adapter,再接入 `api-server`。
|
||||
|
||||
参考:
|
||||
|
||||
- `langchainrust` crates.io/docs.rs 页面:https://docs.rs/crate/langchainrust/latest
|
||||
- 仓库主页:https://github.com/atliliw/langchainrust
|
||||
|
||||
## 2.1 Agent 模型与多模态输入
|
||||
|
||||
创意互动内容 Agent 的感知、思考、反思和自然语言草稿修订统一使用 APIMart OpenAI 兼容 Responses API 的 `gpt-5`。这里的 `gpt-5` 负责理解用户文字和图片、选择拼图模板、规划草稿字段和生成结构化工具调用参数;拼图图片生成仍由拼图模块图片工具使用 `gpt-image-2`,不要把“理解/规划模型”和“生图模型”混在同一个配置里。
|
||||
|
||||
请求协议以 APIMart 文档 `OpenAI 多模态响应接口` 为准:
|
||||
|
||||
```text
|
||||
POST https://api.apimart.ai/v1/responses
|
||||
model: gpt-5
|
||||
official_fallback: true
|
||||
input[].content[].type: input_text | input_image
|
||||
```
|
||||
|
||||
Phase 1 的 `platform-agent` / `platform-llm` 必须支持下面的项目内请求结构:
|
||||
|
||||
```ts
|
||||
export interface CreativeAgentMultimodalInputPart {
|
||||
type: 'input_text' | 'input_image';
|
||||
text?: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export interface CreativeAgentGpt5Request {
|
||||
model: 'gpt-5';
|
||||
official_fallback: true;
|
||||
input: Array<{
|
||||
role: 'system' | 'user' | 'assistant';
|
||||
content: CreativeAgentMultimodalInputPart[];
|
||||
}>;
|
||||
stream: boolean;
|
||||
tools?: CreativeAgentToolSchema[];
|
||||
}
|
||||
```
|
||||
|
||||
落地约束:
|
||||
|
||||
1. Agent 入口支持文本 + 图片多模态输入,首版至少支持 1 张图片,协议层预留多图。
|
||||
2. 图片必须先进入资产系统,Agent 请求使用可访问的 `readUrl` 或受控 Data URI;SpacetimeDB 不保存大图 base64。
|
||||
3. `platform-llm` 当前已有 Responses 协议骨架,但 Phase 1 需要把 content part 从纯文本扩展成 `input_text` / `input_image` 两类;APIMart GPT-5 client 必须显式开启 `official_fallback = true`,该供应商字段不默认扩散到 Ark 等非 APIMart provider。
|
||||
4. `CREATION_TEMPLATE_LLM_MODEL` 等旧文本创作模型不能作为创意互动内容 Agent 的默认模型;本 Agent 必须显式使用 `gpt-5`。
|
||||
5. 如果 LangChain-Rust adapter 暂时无法直接表达多模态 Responses 请求,应在 `platform-agent` 内桥接到 `platform-llm` 的多模态 Responses client,不能退回纯文本摘要替代图片理解。
|
||||
6. 模型工具调用可用 APIMart Responses 的 `tools` 能力承载;工具真正执行仍由 `platform-agent` 注册表和后端 typed Tool 控制。
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
Agent 由六大核心模块组成,形成“感知 -> 思考 -> 记忆 -> 行动 -> 反思 -> 协作”的闭环。
|
||||
|
||||
```text
|
||||
用户图文输入
|
||||
-> 感知 Perception
|
||||
-> 思考 Reasoning
|
||||
-> 记忆 Memory
|
||||
-> 行动 Action
|
||||
-> 反思 Reflection
|
||||
-> 协作 Collaboration
|
||||
-> 目标玩法草稿 / 追问 / 人工确认
|
||||
```
|
||||
|
||||
Rust 分层建议:
|
||||
|
||||
```text
|
||||
server-rs/crates/platform-agent
|
||||
langchain_adapter.rs LangChain-Rust 封装,屏蔽第三方 API 变化
|
||||
agent_graph.rs 六模块 LangGraph / AgentExecutor 编排
|
||||
tools.rs 工具注册与权限边界
|
||||
output_parsers.rs Typed / JSON 输出解析
|
||||
callbacks.rs Trace、SSE、成本与错误事件
|
||||
|
||||
server-rs/crates/module-creative-agent
|
||||
domain.rs Agent 会话、目标、模板语义、计划、反思记录
|
||||
commands.rs 创建会话、写入输入、确认计划、保存结果
|
||||
application.rs 纯领域校验、阶段迁移、契约门槛
|
||||
errors.rs 字段错误与领域错误
|
||||
|
||||
server-rs/crates/api-server/src/creative_agent.rs
|
||||
HTTP / SSE facade,调用 platform-agent 和 spacetime-client
|
||||
```
|
||||
|
||||
DDD 边界保持不变:
|
||||
|
||||
- `platform-agent` 负责 Agent 编排和工具调用抽象,不保存业务真相。
|
||||
- `module-creative-agent` 只放纯领域类型、阶段、校验和决策记录结构。
|
||||
- `api-server` 负责鉴权、SSE、LLM/视觉/工具编排。
|
||||
- `spacetime-module` 保存会话、输入、记忆索引、目标玩法绑定和审计事件。
|
||||
- 当前目标玩法只允许拼图。拼图模板协议、积分范围、单关卡/多关卡图片生成计划、草稿校验和工具实现全部封装在 `module-puzzle` / 拼图相关 facade 中;通用 Agent 不复制拼图字段推导逻辑。
|
||||
|
||||
## 4. 六大核心模块
|
||||
|
||||
### 4.1 感知模块 Perception
|
||||
|
||||
职责:把用户输入的字、图、文档和上下文变成模型可推理的多模态语义状态。
|
||||
|
||||
它不是关键词分类器。它要做的是“看懂素材”和“看懂用户想要什么”。
|
||||
|
||||
输入:
|
||||
|
||||
```ts
|
||||
export interface CreativePerceptionInput {
|
||||
text: string;
|
||||
documents: CreativeTextAttachment[];
|
||||
images: CreativeImageAttachment[];
|
||||
currentUserProfile?: CreativeUserPreferenceSnapshot | null;
|
||||
entryContext: 'creation_home' | 'puzzle_workspace' | 'gallery_remix' | 'draft_restore';
|
||||
}
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```ts
|
||||
export interface CreativePerceptionState {
|
||||
userIntent: string;
|
||||
emotionalTone: string;
|
||||
targetAudience: string | null;
|
||||
sourceMaterials: CreativeMaterialSummary[];
|
||||
visualUnderstanding: CreativeImageUnderstanding[];
|
||||
constraints: CreativeConstraint[];
|
||||
uncertainties: CreativeUncertainty[];
|
||||
}
|
||||
```
|
||||
|
||||
实现方式:
|
||||
|
||||
1. 文档输入复用 `creationAgentDocumentInput`,再交给 LangChain-Rust 的 text splitter / document chain 做摘要。
|
||||
2. 图片输入必须先上传为资产,Agent 只拿 `readUrl`、缩略图、尺寸和视觉摘要,不把大 data URL 存入 SpacetimeDB。
|
||||
3. 图像理解首版直接通过 APIMart Responses API 的 `gpt-5` 多模态输入完成,返回主体、场景、风格、OCR、构图线索和安全风险;后续如独立 `platform-vision`,也必须保持相同的文本/图像内容块契约。
|
||||
4. 模板和玩法说明通过 RAG 检索注入,而不是写死规则。检索源包括玩法模板注册表、拼图草稿契约、已有优秀作品摘要和玩法适配说明。
|
||||
|
||||
LangChain-Rust 对应能力:
|
||||
|
||||
- Document loader / splitter / summarization chain
|
||||
- RAG、BM25、Hybrid retrieval
|
||||
- Typed / JSON output parser
|
||||
- Callback 将感知进度推给前端 SSE
|
||||
|
||||
### 4.2 思考模块 Reasoning
|
||||
|
||||
职责:让模型在可用工具、用户意图和玩法知识之间主动做计划。
|
||||
|
||||
思考模块不是 `if playType == puzzle` 的路由函数,而是一个 FunctionCallingAgent 或 LangGraph 中的 planner 节点。模型可以自主选择:
|
||||
|
||||
- 先调用拼图模板知识检索
|
||||
- 先返回拼图模板目录
|
||||
- 请求用户选择模板
|
||||
- 用户选中模板后,再确认该模板的关卡模式、关卡数和积分范围
|
||||
- 确认后生成单关卡或多关卡草稿计划
|
||||
- 先调用图片理解
|
||||
- 先问用户一个关键问题
|
||||
- 委托拼图专家 Agent
|
||||
|
||||
当前版本的思考模块不能选择非拼图玩法作为行动目标。如果模型认为输入更适合 RPG、Match3D、大鱼或方洞挑战,应输出“当前仅支持拼图模板”的说明,并尝试给出可转化为拼图的创意方案;若无法转化,应进入 `waiting_user`,不能创建非拼图 session。
|
||||
|
||||
核心状态:
|
||||
|
||||
```ts
|
||||
export interface CreativeReasoningState {
|
||||
goal: string;
|
||||
candidatePlayTypes: CreativePlayCandidate[];
|
||||
selectedPlayType: CreativePlayType | null;
|
||||
selectedTemplateId: string | null;
|
||||
selectedPuzzleTemplate: PuzzleCreativeTemplateSelection | null;
|
||||
selectedImageGenerationPlan: PuzzleImageGenerationPlan | null;
|
||||
plan: CreativeAgentPlanStep[];
|
||||
confidence: number;
|
||||
needUserClarification: boolean;
|
||||
rationale: string;
|
||||
}
|
||||
```
|
||||
|
||||
建议使用 LangChain-Rust:
|
||||
|
||||
- `FunctionCallingAgent` 作为主决策 Agent,所有玩法能力以工具形式暴露。
|
||||
- `AgentExecutor` 控制最大迭代次数、超时、工具调用错误回传。
|
||||
- `LangGraph` 表达长链路:`perceive -> plan -> act -> reflect -> finalize`,允许循环和人工确认。
|
||||
- `JsonOutputParser` / `TypedOutputParser` 保证模型最终输出能落到 Rust/TS shared contract。
|
||||
|
||||
系统提示词要明确:
|
||||
|
||||
1. 你是创意互动内容生成 Agent,不是分类器。
|
||||
2. 你可以相信自己的多模态理解能力。
|
||||
3. 你应选择能最大化互动体验的玩法,而不是机械匹配关键词。
|
||||
4. 当前产品只开放拼图模板,非拼图模板只能解释为暂不支持,不能调用非拼图工具。
|
||||
5. 即便只有拼图玩法可用,也必须先显式展示多个拼图子模板;用户选中模板后,才展示选择理由、关卡配置和预计积分范围。
|
||||
6. 当输入足够明确时不要过度追问。
|
||||
7. 当合规、素材权属、人物肖像或儿童内容存在风险时进入确认或降级。
|
||||
|
||||
### 4.3 记忆模块 Memory
|
||||
|
||||
职责:让 Agent 能利用当前会话、用户偏好、历史作品和反思经验,而不是每次从零判断。
|
||||
|
||||
记忆分四层:
|
||||
|
||||
```text
|
||||
短期记忆:当前会话消息、工具调用、草稿状态
|
||||
工作记忆:本次任务的目标、计划、候选、未解决问题
|
||||
长期记忆:用户偏好、常用玩法、作品风格、发布反馈
|
||||
反思记忆:过去失败原因、模板误选案例、修正策略
|
||||
```
|
||||
|
||||
推荐结构:
|
||||
|
||||
```ts
|
||||
export interface CreativeAgentMemorySnapshot {
|
||||
shortTermSummary: string;
|
||||
workingPlan: CreativeAgentPlanStep[];
|
||||
retrievedUserPreferences: CreativeUserPreference[];
|
||||
retrievedTemplateMemories: CreativeTemplateMemory[];
|
||||
retrievedReflections: CreativeReflectionMemory[];
|
||||
}
|
||||
```
|
||||
|
||||
落地方式:
|
||||
|
||||
1. LangChain-Rust Memory 用于单次 AgentExecutor 内的短期上下文,可用 Buffer / Window / SummaryBuffer。
|
||||
2. SpacetimeDB 保存长期真相:`creative_agent_session`、`creative_agent_message`、`creative_agent_reflection`、`creative_agent_target_binding`。
|
||||
3. 向量/混合检索保存可召回记忆:用户偏好、模板选择结果、发布后反馈、失败反思。首版可用 SQLite 或 Redis feature,生产再评估 Qdrant/Redis。
|
||||
4. 每次生成结束后写一条反思记忆:选择了什么模板、为什么、哪些字段由模型推断、用户是否接受、是否返回编辑。
|
||||
|
||||
记忆使用原则:
|
||||
|
||||
- 记忆给模型参考,不替代用户本轮输入。
|
||||
- 用户本轮明确要求优先级最高。
|
||||
- 任何长期记忆都要带来源、时间和置信度。
|
||||
- 涉及个人隐私、图片内容和未发布作品时只在用户私有 namespace 检索。
|
||||
|
||||
### 4.4 行动模块 Action
|
||||
|
||||
职责:把 Agent 的计划变成可审计、可回滚、可测试的工具调用。
|
||||
|
||||
所有对系统产生影响的操作都必须以 LangChain-Rust Tool 的形式注册。模型通过 function calling 选择工具,工具内部再调用现有服务或 SpacetimeDB facade。
|
||||
|
||||
首批工具:
|
||||
|
||||
```text
|
||||
perceive_image(imageAssetId)
|
||||
retrieve_puzzle_template_catalog(query)
|
||||
retrieve_user_creation_memory(userId, query)
|
||||
create_puzzle_agent_session(payload)
|
||||
compile_puzzle_draft(sessionId, payload)
|
||||
plan_puzzle_level_images(payload)
|
||||
generate_puzzle_level_images(sessionId, payload)
|
||||
select_puzzle_template(payload)
|
||||
confirm_puzzle_template(payload)
|
||||
apply_puzzle_draft_natural_language_edit(sessionId, payload)
|
||||
start_puzzle_draft_test_run(sessionId, payload)
|
||||
ask_user_clarification(question, options?)
|
||||
request_user_confirmation(summary, candidates)
|
||||
validate_target_draft(playType, draft)
|
||||
save_creative_reflection(payload)
|
||||
```
|
||||
|
||||
工具设计要求:
|
||||
|
||||
1. 工具描述要清楚告诉模型何时使用,而不是由外层规则决定。
|
||||
2. 工具输入必须是 JSON Schema / Rust typed struct,禁止自由字符串拼接。
|
||||
3. 工具只做一件事。比如“创建拼图 session”和“编译拼图草稿”分开。
|
||||
4. 工具返回结构化结果,包含 `ok`、`summary`、`nextSuggestedTools`、`warnings`。
|
||||
5. 所有写操作必须鉴权,不能信任模型传入的 `ownerUserId`。
|
||||
6. 工具调用审计写入 SpacetimeDB,便于排障和反思。
|
||||
7. 当前工具注册表只能暴露拼图工具。非拼图工具即使已有实现,也不能注册给当前 Agent。
|
||||
|
||||
拼图草稿不是路由器手填,而是 Action 模块通过工具让 Agent 产出 typed payload。这里的“草稿字段”只指用户表单和 Agent 自然语言都共同编辑的那一组字段:
|
||||
|
||||
```ts
|
||||
export interface CreativePuzzleDraftToolInput {
|
||||
templateId: string;
|
||||
templateCostRange: PuzzleTemplateCostRange;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
workTags: string[];
|
||||
levels: CreativePuzzleLevelDraftInput[];
|
||||
}
|
||||
|
||||
export interface CreativePuzzleLevelDraftInput {
|
||||
levelName: string;
|
||||
pictureDescription: string;
|
||||
pictureReference?: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
工具内部负责映射到现有 `PuzzleAgentActionRequest` 和 `PuzzleResultDraft`,并调用拼图领域校验。`workTitle`、`workDescription`、`workTags`、`levels[].levelName`、`levels[].pictureDescription`、`levels[].pictureReference` 是 Agent 直接写入的草稿真相;`summary`、`anchorPack`、`forbiddenDirectives`、`imagePrompt`、候选图等都属于拼图模块内部派生或生成结果,不作为 Agent 的直接填表目标。
|
||||
|
||||
拼图模块必须额外暴露模板和多关卡图片协议:
|
||||
|
||||
```ts
|
||||
export interface PuzzleCreativeTemplateProtocol {
|
||||
templateId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
supportedLevelMode: 'single' | 'multi' | 'single_or_multi';
|
||||
defaultLevelCount: number;
|
||||
minLevelCount: number;
|
||||
maxLevelCount: number;
|
||||
costRange: PuzzleTemplateCostRange;
|
||||
requiredDraftFields: string[];
|
||||
imageGenerationPolicy: PuzzleTemplateImageGenerationPolicy;
|
||||
}
|
||||
|
||||
export interface PuzzleTemplateCostRange {
|
||||
minPoints: number;
|
||||
maxPoints: number;
|
||||
pricingUnit: 'point';
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface PuzzleTemplateImageGenerationPolicy {
|
||||
allowUploadedImageDirectly: boolean;
|
||||
allowGeneratedImages: boolean;
|
||||
allowPerLevelReferenceImage: boolean;
|
||||
defaultCandidateCountPerLevel: number;
|
||||
}
|
||||
|
||||
export interface PuzzleImageGenerationPlan {
|
||||
mode: 'single_level' | 'multi_level';
|
||||
levels: CreativePuzzleLevelDraftInput[];
|
||||
estimatedCostRange: PuzzleTemplateCostRange;
|
||||
}
|
||||
```
|
||||
|
||||
积分范围由拼图模板协议提供,Agent 只能解释和选择,不能自行发明价格。真实扣费仍以后端钱包/任务系统最终结算为准。
|
||||
|
||||
### 4.5 反思模块 Reflection
|
||||
|
||||
职责:让 Agent 在交付前检查自己的选择是否真的适合用户,而不是一次模型输出就结束。
|
||||
|
||||
反思节点运行在每次关键行动后:
|
||||
|
||||
1. 模板选择后:检查是否已经向用户显式展示拼图模板和积分范围。
|
||||
2. 用户确认前:检查是否误承诺了非拼图模板或非真实价格。
|
||||
3. 草稿填充后:检查字段是否完整、玩法体验是否成立。
|
||||
4. 图片使用前:检查单关卡/多关卡图片计划是否与模板协议一致。
|
||||
5. 最终交付前:检查是否需要用户确认。
|
||||
|
||||
反思输出:
|
||||
|
||||
```ts
|
||||
export interface CreativeReflectionReport {
|
||||
pass: boolean;
|
||||
score: number;
|
||||
issues: CreativeReflectionIssue[];
|
||||
revisionInstruction?: string | null;
|
||||
shouldAskUser: boolean;
|
||||
shouldTryAlternativePlayType: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
实现方式:
|
||||
|
||||
- 用 LangGraph 增加 `reflect` 节点。
|
||||
- 反思模型拿到感知状态、计划、工具调用结果和目标草稿。
|
||||
- 如果 `pass=false` 且迭代次数未超限,回到 `plan` 或 `act`。
|
||||
- 如果问题是用户偏好缺失,调用 `ask_user_clarification`。
|
||||
- 如果问题是契约字段缺失,调用目标玩法 draft 修复工具。
|
||||
- 如果问题是未展示模板选择或积分范围,回到模板确认节点。
|
||||
- 如果问题是多关卡计划超出模板 `maxLevelCount`,调用拼图计划修复工具。
|
||||
|
||||
硬性终止条件:
|
||||
|
||||
- 最大反思循环 2 次。
|
||||
- 同一工具同一参数失败 2 次后停止并返回可读错误。
|
||||
- 预算超限时返回当前可用草稿和补救建议。
|
||||
|
||||
反思记忆要沉淀:
|
||||
|
||||
- 模板误选原因。
|
||||
- 用户是否接受模板积分范围。
|
||||
- 单关卡或多关卡图片生成计划是否被用户调整。
|
||||
- 用户手动改选的玩法。
|
||||
- 结果页返回编辑最多的字段。
|
||||
- 发布失败 blockers。
|
||||
|
||||
### 4.6 协作模块 Collaboration
|
||||
|
||||
职责:把复杂创意任务拆给多个专长 Agent,而不是让单个提示词吞掉所有任务。
|
||||
|
||||
当前首版只开放拼图协作,不开放其它玩法子 Agent。建议四个子 Agent:
|
||||
|
||||
```text
|
||||
创意导演 Agent:理解用户目标,决定整体方向和互动体验。
|
||||
视觉解读 Agent:理解图片、构图、主体、风格和可交互线索。
|
||||
拼图模板策展 Agent:基于拼图模板协议和历史作品选择候选拼图模板,并读取积分范围。
|
||||
拼图专家 Agent:生成单关卡或多关卡拼图草稿 payload 和图片生成计划。
|
||||
契约审校 Agent:检查字段、发布门槛、积分展示、安全边界和可恢复性。
|
||||
```
|
||||
|
||||
LangChain-Rust 落地:
|
||||
|
||||
- 用 LangGraph 的 subgraph 表达子 Agent。
|
||||
- 子 Agent 共享 `CreativeAgentState`,但只能写自己负责的字段。
|
||||
- 主 Agent 通过 handoff 工具委托子任务。
|
||||
- 必要时并行执行视觉解读和拼图模板知识检索,再由创意导演合并。
|
||||
- 协作结果由契约审校 Agent 最终检查。
|
||||
|
||||
协作不是增加 UI 复杂度。前端仍只看到一个 Agent,但后端内部有多个可观测步骤,SSE 推送简短阶段即可。
|
||||
|
||||
## 5. Agent 状态机
|
||||
|
||||
LangGraph 状态建议:
|
||||
|
||||
```rust
|
||||
pub struct CreativeAgentGraphState {
|
||||
pub session_id: String,
|
||||
pub owner_user_id: String,
|
||||
pub perception: Option<CreativePerceptionState>,
|
||||
pub memory: Option<CreativeAgentMemorySnapshot>,
|
||||
pub reasoning: Option<CreativeReasoningState>,
|
||||
pub tool_results: Vec<CreativeToolResult>,
|
||||
pub reflection: Option<CreativeReflectionReport>,
|
||||
pub target_binding: Option<CreativeTargetSessionBinding>,
|
||||
pub final_response: Option<CreativeAgentFinalResponse>,
|
||||
pub iteration_count: u32,
|
||||
}
|
||||
```
|
||||
|
||||
Graph 节点:
|
||||
|
||||
```text
|
||||
load_memory
|
||||
perceive_input
|
||||
plan_with_agent
|
||||
act_with_tools
|
||||
reflect_result
|
||||
collaborate_if_needed
|
||||
finalize_or_ask_user
|
||||
persist_memory
|
||||
```
|
||||
|
||||
边:
|
||||
|
||||
```text
|
||||
load_memory -> perceive_input
|
||||
perceive_input -> plan_with_agent
|
||||
plan_with_agent -> act_with_tools
|
||||
act_with_tools -> reflect_result
|
||||
reflect_result(pass) -> finalize_or_ask_user
|
||||
reflect_result(revise) -> plan_with_agent
|
||||
reflect_result(collaborate) -> collaborate_if_needed
|
||||
collaborate_if_needed -> act_with_tools
|
||||
finalize_or_ask_user -> persist_memory
|
||||
```
|
||||
|
||||
## 6. 数据契约
|
||||
|
||||
新增 shared contracts:
|
||||
|
||||
```text
|
||||
packages/shared/src/contracts/creativeAgent.ts
|
||||
server-rs/crates/shared-contracts/src/creative_agent.rs
|
||||
```
|
||||
|
||||
核心 DTO:
|
||||
|
||||
```ts
|
||||
export type CreativeAgentStage =
|
||||
| 'perceiving'
|
||||
| 'thinking'
|
||||
| 'remembering'
|
||||
| 'selecting_puzzle_template'
|
||||
| 'waiting_template_confirmation'
|
||||
| 'planning_puzzle_levels'
|
||||
| 'acting'
|
||||
| 'reflecting'
|
||||
| 'collaborating'
|
||||
| 'waiting_user'
|
||||
| 'target_ready'
|
||||
| 'failed';
|
||||
|
||||
export interface CreativeAgentSessionSnapshot {
|
||||
sessionId: string;
|
||||
stage: CreativeAgentStage;
|
||||
perception: CreativePerceptionState | null;
|
||||
reasoning: CreativeReasoningState | null;
|
||||
puzzleTemplateCatalog: PuzzleCreativeTemplateProtocol[];
|
||||
puzzleTemplateSelection: PuzzleCreativeTemplateSelection | null;
|
||||
puzzleImageGenerationPlan: PuzzleImageGenerationPlan | null;
|
||||
reflection: CreativeReflectionReport | null;
|
||||
targetBinding: CreativeTargetSessionBinding | null;
|
||||
messages: CreativeAgentMessage[];
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface PuzzleCreativeTemplateSelection {
|
||||
templateId: string;
|
||||
title: string;
|
||||
reason: string;
|
||||
costRange: PuzzleTemplateCostRange;
|
||||
supportedLevelMode: 'single' | 'multi' | 'single_or_multi';
|
||||
selectedLevelMode: 'single_level' | 'multi_level';
|
||||
plannedLevelCount: number;
|
||||
requiresUserConfirmation: true;
|
||||
}
|
||||
```
|
||||
|
||||
HTTP facade:
|
||||
|
||||
```text
|
||||
POST /api/runtime/creative-agent/sessions
|
||||
GET /api/runtime/creative-agent/sessions/{sessionId}
|
||||
POST /api/runtime/creative-agent/sessions/{sessionId}/messages/stream
|
||||
POST /api/runtime/creative-agent/sessions/{sessionId}/confirm
|
||||
POST /api/runtime/creative-agent/sessions/{sessionId}/cancel
|
||||
```
|
||||
|
||||
SSE 事件:
|
||||
|
||||
```text
|
||||
stage
|
||||
agent_message_delta
|
||||
puzzle_template_catalog
|
||||
puzzle_template_selection
|
||||
puzzle_cost_range
|
||||
puzzle_level_plan
|
||||
tool_started
|
||||
tool_completed
|
||||
reflection
|
||||
target_session
|
||||
need_user_input
|
||||
session
|
||||
error
|
||||
```
|
||||
|
||||
## 7. SpacetimeDB 持久化
|
||||
|
||||
新增表:
|
||||
|
||||
```text
|
||||
creative_agent_session
|
||||
creative_agent_message
|
||||
creative_agent_input_asset
|
||||
creative_agent_tool_call
|
||||
creative_agent_reflection
|
||||
creative_agent_target_binding
|
||||
creative_agent_memory_index
|
||||
puzzle_creative_template_snapshot
|
||||
puzzle_creative_level_generation_plan
|
||||
```
|
||||
|
||||
关键约束:
|
||||
|
||||
1. SpacetimeDB 保存结构化真相和 JSON 快照,不保存大图片 data URL。
|
||||
2. 工具调用和反思必须可追溯,便于排查模型为什么选择某个模板。
|
||||
3. `creative_agent_memory_index` 只保存记忆元数据和向量索引引用,不直接承担向量数据库职责。
|
||||
4. 表结构变更必须同步 `migration.rs`、`SPACETIMEDB_TABLE_CATALOG.md` 和 bindings。
|
||||
5. `creative_agent_session` 必须保存当前拼图模板目录、已确认的模板选择快照和积分范围,保证刷新后仍能恢复“先选模板、再确认配置”的两段式状态。
|
||||
6. `puzzle_creative_level_generation_plan` 保存单关卡/多关卡图片生成计划,包括每关 `level_id`、`level_name`、`picture_description`、`image_prompt`、`generation_status`、`candidate_count` 和 `estimated_cost_points`。
|
||||
7. 非拼图玩法不新增 target binding,避免后续误恢复到暂不支持的玩法。
|
||||
8. 自然语言修订草稿字段要记录工具调用、原始用户指令、结构化 patch 和修改后的 draft 版本,便于撤销、审计和反思。
|
||||
9. 试玩 run 与草稿 session 需要有绑定关系,确保“立即玩”后返回结果页能恢复同一草稿。
|
||||
|
||||
## 8. 拼图首版落地
|
||||
|
||||
拼图首版不是“规则判断为拼图”,而是模型通过工具和反思得出选择。但产品边界明确:当前只支持拼图模板。Agent 必须把非拼图创意转化为拼图可承接的方案,或告诉用户当前不支持该模板。
|
||||
|
||||
### 8.1 强制模板选择与积分展示
|
||||
|
||||
任何草稿生成前都必须先进入 `selecting_puzzle_template` 和 `waiting_template_confirmation`。
|
||||
|
||||
前端至少展示:
|
||||
|
||||
- 拼图模板标题
|
||||
- 模板缩略图或示意图
|
||||
- Agent 选择理由
|
||||
- 支持单关卡、多关卡或二者皆可
|
||||
- 计划关卡数
|
||||
- 预计积分范围,例如 `预计消耗 8 到 18 光点`
|
||||
|
||||
积分范围来自拼图模板协议:
|
||||
|
||||
```ts
|
||||
export interface PuzzleTemplateCostRange {
|
||||
minPoints: number;
|
||||
maxPoints: number;
|
||||
pricingUnit: 'point';
|
||||
reason: string;
|
||||
}
|
||||
```
|
||||
|
||||
Agent 可以解释 `reason`,但不能修改 `minPoints` / `maxPoints`。如果模板协议没有积分范围,该模板不能展示为可选项。
|
||||
|
||||
Agent 可调用工具:
|
||||
|
||||
```text
|
||||
retrieve_puzzle_template_catalog
|
||||
inspect_puzzle_draft_contract
|
||||
select_puzzle_template
|
||||
confirm_puzzle_template
|
||||
plan_puzzle_level_images
|
||||
create_puzzle_agent_session
|
||||
compile_puzzle_draft
|
||||
apply_puzzle_draft_natural_language_edit
|
||||
validate_puzzle_result_preview
|
||||
select_uploaded_image_as_puzzle_cover
|
||||
generate_puzzle_images
|
||||
start_puzzle_draft_test_run
|
||||
return_to_puzzle_result
|
||||
```
|
||||
|
||||
### 8.2 拼图模板协议
|
||||
|
||||
拼图模板协议应封装在拼图模块,不放在通用 Agent:
|
||||
|
||||
```text
|
||||
server-rs/crates/module-puzzle/src/creative_templates.rs
|
||||
server-rs/crates/module-puzzle/src/creative_tools.rs
|
||||
server-rs/crates/shared-contracts/src/puzzle_creative_template.rs
|
||||
packages/shared/src/contracts/puzzleCreativeTemplate.ts
|
||||
```
|
||||
|
||||
协议至少包含:
|
||||
|
||||
```ts
|
||||
export interface PuzzleCreativeTemplateProtocol {
|
||||
templateId: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
previewImageSrc: string | null;
|
||||
supportedLevelMode: 'single' | 'multi' | 'single_or_multi';
|
||||
minLevelCount: number;
|
||||
maxLevelCount: number;
|
||||
defaultLevelCount: number;
|
||||
costRange: PuzzleTemplateCostRange;
|
||||
imagePolicy: PuzzleTemplateImageGenerationPolicy;
|
||||
draftFieldHints: PuzzleTemplateDraftFieldHints;
|
||||
}
|
||||
```
|
||||
|
||||
模板示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"templateId": "puzzle.family-keepsake",
|
||||
"title": "家庭纪念拼图",
|
||||
"supportedLevelMode": "single_or_multi",
|
||||
"minLevelCount": 1,
|
||||
"maxLevelCount": 6,
|
||||
"defaultLevelCount": 3,
|
||||
"costRange": {
|
||||
"minPoints": 8,
|
||||
"maxPoints": 24,
|
||||
"pricingUnit": "point",
|
||||
"reason": "按关卡数、每关候选图数量和是否使用上传图估算"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 单关卡与多关卡图片生成
|
||||
|
||||
拼图草稿必须支持两种计划:
|
||||
|
||||
```ts
|
||||
export type PuzzleLevelGenerationMode = 'single_level' | 'multi_level';
|
||||
|
||||
export interface PuzzleImageGenerationPlan {
|
||||
mode: PuzzleLevelGenerationMode;
|
||||
templateId: string;
|
||||
estimatedCostRange: PuzzleTemplateCostRange;
|
||||
levels: PuzzleImageGenerationPlanLevel[];
|
||||
}
|
||||
|
||||
export interface PuzzleImageGenerationPlanLevel {
|
||||
levelId: string;
|
||||
levelName: string;
|
||||
pictureDescription: string;
|
||||
imagePrompt: string;
|
||||
pictureReference?: string | null;
|
||||
candidateCount: number;
|
||||
}
|
||||
```
|
||||
|
||||
单关卡:
|
||||
|
||||
- `levels.length = 1`
|
||||
- 可使用上传图直出,也可生成一张候选图。
|
||||
- 适合纪念照、商品图、单张海报、单主题知识图。
|
||||
|
||||
多关卡:
|
||||
|
||||
- `levels.length` 必须在模板 `minLevelCount` 到 `maxLevelCount` 之间。
|
||||
- 每关有独立 `levelName`、`pictureDescription` 和 `imagePrompt`。
|
||||
- 可选择每关生成一张图,也可第一关使用上传图、后续关卡生图。
|
||||
- 适合故事型照片组、知识步骤、活动流程、系列商品、节日卡片组。
|
||||
|
||||
生成工具建议:
|
||||
|
||||
```ts
|
||||
export interface GeneratePuzzleLevelImagesToolInput {
|
||||
sessionId: string;
|
||||
plan: PuzzleImageGenerationPlan;
|
||||
imageModel: 'gpt-image-2';
|
||||
}
|
||||
|
||||
export interface GeneratePuzzleLevelImagesToolOutput {
|
||||
draft: PuzzleResultDraft;
|
||||
levels: PuzzleDraftLevel[];
|
||||
costEstimate: PuzzleTemplateCostRange;
|
||||
generatedCount: number;
|
||||
uploadedCount: number;
|
||||
}
|
||||
```
|
||||
|
||||
工具内部可以复用现有 `generate_puzzle_images` action,但必须以 `levelId` 为粒度逐关执行,或新增拼图后端 action `generate_puzzle_level_images` 批量处理。批量 action 仍归属拼图模块,通用 Agent 只负责调用。
|
||||
|
||||
### 8.5 模板草稿字段填充与自然语言修订
|
||||
|
||||
Agent 创作互动内容的本质就是向模板中的草稿字段填充内容。拼图模板草稿字段是唯一创作真相:表单化创作页是这些字段的可视化编辑器,Agent 对话是这些字段的自然语言编辑器,二者属于同一条创作流程。
|
||||
|
||||
通用 Agent 只负责把用户自然语言转成“草稿字段填充 / 修订意图”,真正的字段写入仍通过拼图模块 Tool 完成:
|
||||
|
||||
```ts
|
||||
export interface PuzzleDraftFieldEditInstruction {
|
||||
scope: 'work' | 'level' | 'tags' | 'cover' | 'images' | 'all';
|
||||
operation: 'set' | 'append' | 'replace' | 'remove' | 'reorder';
|
||||
fieldPath: string;
|
||||
value: string | string[] | boolean | number | null;
|
||||
rationale: string;
|
||||
}
|
||||
|
||||
export interface ApplyPuzzleDraftNaturalLanguageEditToolInput {
|
||||
sessionId: string;
|
||||
userInstruction: string;
|
||||
currentDraftSnapshot: PuzzleResultDraft;
|
||||
}
|
||||
|
||||
export interface ApplyPuzzleDraftNaturalLanguageEditToolOutput {
|
||||
updatedDraft: PuzzleResultDraft;
|
||||
editInstructions: PuzzleDraftFieldEditInstruction[];
|
||||
needsUserConfirmation: boolean;
|
||||
confirmationSummary: string;
|
||||
}
|
||||
```
|
||||
|
||||
这个工具由拼图模块封装,内部要做三步:
|
||||
|
||||
1. 用模型把自然语言改写成结构化草稿字段 patch。
|
||||
2. 用拼图领域规则校验 patch 是否会破坏草稿约束。
|
||||
3. 通过现有草稿保存接口写回同一份 `PuzzleResultDraft` 或 `formDraft`。
|
||||
|
||||
典型自然语言字段修订场景:
|
||||
|
||||
- “把这张图改成更适合家庭纪念。”
|
||||
- “第二关再多加一张风景图。”
|
||||
- “标题别太正式,轻松一点。”
|
||||
- “主题标签里去掉商品感,增加温暖和节日感。”
|
||||
|
||||
Agent 不能直接篡改结果页 DOM,也不能绕过拼图草稿字段写最终发布数据。用户在表单里手动编辑、用户用自然语言让 Agent 编辑,本质上都必须落到同一份草稿字段 patch。
|
||||
|
||||
### 8.6 立即玩与试玩闭环
|
||||
|
||||
生成好的互动内容必须能立即玩到。对拼图来说,Agent 完成草稿后必须直接导向现有 `puzzle-result`,并提供明确的“立即玩”入口。
|
||||
|
||||
闭环要求:
|
||||
|
||||
1. 草稿创建成功后,结果页首屏就能看到 `Play` 或“立即玩”按钮。
|
||||
2. 点击后直接启动 `puzzle-runtime`,不需要用户再手动跳过别的中间页。
|
||||
3. 如果当前草稿还在生成更多关卡图片,已完成关卡可先试玩,后续图片再逐关补齐。
|
||||
4. 试玩入口必须复用现有 `PuzzleRuntimeShell` 和 `startPuzzleRun` 链路。
|
||||
5. 试玩后返回结果页时,仍然停留在同一草稿上下文,不丢失表单草稿状态。
|
||||
|
||||
结果页到运行态之间的切换不由通用 Agent 再次判断,而是由拼图模块暴露的 `start_run` / `resume_run` / `return_to_result` 工具完成。
|
||||
|
||||
### 8.4 拼图专家输出
|
||||
|
||||
拼图专家 Agent 需要产出:
|
||||
|
||||
```ts
|
||||
export interface PuzzleCreativeDraftIntent {
|
||||
templateSelection: PuzzleCreativeTemplateSelection;
|
||||
imageGenerationPlan: PuzzleImageGenerationPlan;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
workTags: string[];
|
||||
levels: CreativePuzzleLevelDraftInput[];
|
||||
}
|
||||
```
|
||||
|
||||
字段映射仍必须对齐现有契约:
|
||||
|
||||
- `PuzzleResultDraft.workTitle`
|
||||
- `PuzzleResultDraft.workDescription`
|
||||
- `PuzzleResultDraft.workTags`
|
||||
- `PuzzleResultDraft.levels[].levelName`
|
||||
- `PuzzleResultDraft.levels[].pictureDescription`
|
||||
- `PuzzleResultDraft.levels[].pictureReference`
|
||||
|
||||
领域校验仍由 `module-puzzle` 负责。Agent 可以创造内容,但不能绕过发布 blockers。表单化创作页与 Agent 自然语言修订都只是这些字段的不同编辑界面。
|
||||
|
||||
## 9. 前端体验
|
||||
|
||||
前端只呈现一个“智能创作 Agent”入口:
|
||||
|
||||
1. 用户输入文字、上传图片或文档。
|
||||
2. 前端显示 Agent 正在理解素材、构思玩法、生成草稿。
|
||||
3. Agent 必须先展示拼图模板目录。
|
||||
4. 用户选择模板并确认关卡模式、关卡数和预计积分范围后,Agent 才能生成草稿。
|
||||
5. 生成完成后进入拼图结果页,并提供“立即玩”入口,点击后直接进入拼图运行态。
|
||||
6. 结果页保留原来的表单化创作能力,包括作品标题、作品描述、作品标签、关卡名称、关卡图面描述、关卡图面参考、关卡图片生成和选图;这些控件编辑的是同一份模板草稿字段。
|
||||
7. Agent 对话区继续可用,用户可以用自然语言补填或修订模板草稿字段,后端通过拼图模块 Tool 生成结构化 patch 并回写同一份草稿;可写字段仅限 `workTitle`、`workDescription`、`workTags`、`levels[].levelName`、`levels[].pictureDescription`、`levels[].pictureReference`。
|
||||
8. 若 Agent 有关键不确定点,弹出独立确认面板。
|
||||
9. 用户确认或回答后,Agent 继续执行并进入拼图结果页或保持在当前草稿上下文。
|
||||
|
||||
UI 不展示大段规则说明,只展示:
|
||||
|
||||
- 当前阶段
|
||||
- 简短 Agent 回复
|
||||
- 拼图模板选择和积分范围
|
||||
- 单关卡 / 多关卡计划
|
||||
- 需要确认的问题
|
||||
- 最终草稿入口
|
||||
- 立即玩入口
|
||||
- 表单化草稿字段编辑入口
|
||||
- Agent 自然语言补填 / 修订入口
|
||||
|
||||
移动端优先,上传图片预览使用横向缩略图条,确认面板用底部抽屉。
|
||||
|
||||
## 10. 安全与治理
|
||||
|
||||
模型能力是核心,但不能没有边界:
|
||||
|
||||
1. 工具权限边界:模型只能调用已注册工具,不能直接写库。
|
||||
2. 契约边界:Typed parser 和目标玩法 validator 必须通过。
|
||||
3. 成本边界:AgentExecutor 设置最大迭代、最大工具调用、超时和预算。
|
||||
4. 内容安全:人物肖像、儿童内容、版权图、隐私图进入确认或拒绝。
|
||||
5. 记忆安全:长期记忆按用户 namespace 隔离。
|
||||
6. 可观测性:Callbacks 记录每个节点、工具、token、耗时和错误。
|
||||
|
||||
## 11. 分阶段落地
|
||||
|
||||
### Phase 1:LangChain-Rust PoC + 拼图闭环
|
||||
|
||||
- 新增 `platform-agent` PoC。
|
||||
- 封装 LangChain-Rust FunctionCallingAgent / AgentExecutor。
|
||||
- 注册拼图相关工具。
|
||||
- 只支持拼图模板;非拼图模板在 Agent 能力中标记为暂不支持。
|
||||
- 强制展示拼图模板选择、选择理由和预计积分范围。
|
||||
- 支持文字 + 单图输入,模型自主选择拼图模板并填入草稿字段。
|
||||
- 支持单关卡图片生成计划和多关卡图片生成计划。
|
||||
- 生成后的拼图内容点击“立即玩”可直接进入 `puzzle-runtime`。
|
||||
- 保留结果页原有表单化草稿字段编辑入口。
|
||||
- 支持 Agent 自然语言补填 / 修订模板草稿字段,并通过拼图模块 Tool 回写草稿。
|
||||
- SSE 推送六模块阶段。
|
||||
- 结果进入现有 `puzzle-result`。
|
||||
|
||||
### Phase 2:记忆与反思闭环
|
||||
|
||||
- 增加 `creative_agent_tool_call`、`creative_agent_reflection`、`creative_agent_memory_index`。
|
||||
- 引入短期 Memory 和长期检索。
|
||||
- 记录用户改选、发布失败、返回编辑等反馈。
|
||||
- 反思循环支持自动修正草稿。
|
||||
|
||||
### Phase 3:多玩法协作
|
||||
|
||||
- 在产品开放后再增加 Match3D、大鱼吃小鱼、方洞挑战、RPG 世界专家 Agent。
|
||||
- 使用 LangGraph subgraph 做多 Agent 协作。
|
||||
- 支持多候选玩法对比和人工确认。
|
||||
|
||||
## 12. 验收标准
|
||||
|
||||
功能验收:
|
||||
|
||||
1. 用户输入含图文材料时,Agent 能说出它理解到的创作意图。
|
||||
2. Agent 能调用拼图模板知识检索,而不是靠硬编码规则选模板。
|
||||
3. Agent 必须显式展示拼图模板、选择理由和预计积分范围,用户确认后才生成草稿。
|
||||
4. 非拼图输入不会创建其它玩法 session,只能转成拼图方案或提示暂不支持。
|
||||
5. Agent 能自主选择拼图模板并生成可通过契约校验的 `PuzzleResultDraft`。
|
||||
6. 当图片更适合直接作为拼图图面时,Agent 能选择 uploaded candidate,而不是强制生图。
|
||||
7. Agent 能生成单关卡图片计划,也能生成多关卡图片计划。
|
||||
8. 多关卡计划中每关都有独立 `levelName`、`pictureDescription` 和 `imagePrompt`。
|
||||
9. 多关卡图片生成逻辑通过拼图模块工具执行,通用 Agent 不直接拼接 `levelsJson`。
|
||||
10. 生成好的拼图内容点击“立即玩”后能直接进入 `puzzle-runtime`。
|
||||
11. 从 `puzzle-runtime` 返回时仍恢复同一 `puzzle-result` 草稿上下文。
|
||||
12. 结果页保留表单化草稿字段编辑能力,用户可以修改作品标题、作品描述、作品标签、关卡名称、关卡图面描述和关卡图面参考。
|
||||
13. 用户用自然语言提出“把标题改轻松一点”“第二关加一张风景图”等请求时,Agent 能生成结构化草稿字段 patch 并通过拼图模块 Tool 回写同一份草稿。
|
||||
14. 自然语言修订草稿字段后必须重新通过拼图草稿校验和结果预览校验。
|
||||
15. 当不确定时,Agent 只问一个关键问题,而不是把所有字段丢给用户填写。
|
||||
16. 反思节点能发现未展示积分范围、关卡数越界、草稿字段缺失或自然语言字段 patch 风险,并自动修正一次。
|
||||
17. 工具调用、反思报告、模板选择、草稿字段 patch 和目标拼图 session 绑定能恢复和审计。
|
||||
|
||||
建议命令:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts creative_agent
|
||||
cargo test -p module-creative-agent
|
||||
cargo check -p platform-agent
|
||||
cargo check -p api-server
|
||||
```
|
||||
|
||||
涉及 SpacetimeDB schema 后:
|
||||
|
||||
```bash
|
||||
npm run spacetime:generate -- --rust-only
|
||||
npm run check:server-rs-ddd
|
||||
```
|
||||
|
||||
前端:
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
## 13. 当前实现状态
|
||||
|
||||
截至 `2026-05-05`,任务 C 已完成首版 PoC 落地:
|
||||
|
||||
1. `server-rs/crates/platform-agent` 已新增为 workspace member。
|
||||
2. `platform-agent` 已提供项目自有的 `CreativeAgentExecutor`、工具注册表、回调事件、`gpt-5` 多模态请求适配器和 mock executor。
|
||||
3. `platform-llm` 已支持 Responses 多模态输入块,`input_text` 与 `input_image` 会按 content part 序列化到请求体。
|
||||
4. 任务 C 的验收命令已通过:`cargo check -p platform-agent`、`cargo test -p platform-agent`、`cargo test -p platform-llm responses_multimodal`。
|
||||
|
||||
截至 `2026-05-05`,任务 E 的 API / SSE facade 已补充 Windows debug 稳定性修复:
|
||||
|
||||
1. `/api/runtime/creative-agent/sessions/{sessionId}/messages/stream` 不再把 Agent 执行、模型请求、会话更新和所有 SSE 事件内联到单个大型 `async_stream` 中。
|
||||
2. 当前实现使用后台 `tokio::spawn` 执行业务流程,并通过 `mpsc` / `UnboundedReceiverStream` 向 Axum 返回轻量 SSE stream;执行失败会更新会话为 `failed` 并发送 SSE `error`。
|
||||
3. 已补实际消费 SSE body 的回归测试,覆盖 `stage`、`puzzle_template_catalog` 与 `done` 事件;`puzzle_template_selection`、`puzzle_cost_range`、`puzzle_level_plan` 只在用户确认后进入后续快照或流程。
|
||||
|
||||
## 14. 本方案相对旧方案的变化
|
||||
|
||||
旧方案偏“规则预筛 + workflow + Adapter”。新方案调整为:
|
||||
|
||||
1. 模型负责理解素材、整理候选和草稿构思;最终模板由用户从多个候选中主动选择。
|
||||
2. LangChain-Rust AgentExecutor / FunctionCallingAgent 承担工具调用决策。
|
||||
3. LangGraph 承担六模块闭环和反思循环。
|
||||
4. Memory/RAG 让 Agent 学习用户偏好和模板经验。
|
||||
5. 当前只开放拼图玩法,但模板选择仍是显式 Agent 步骤,不因只有一个玩法而跳过。
|
||||
6. 拼图模板协议必须携带积分范围,用户选择模板并确认配置后才进入草稿生成。
|
||||
7. 单关卡/多关卡图片生成通过拼图模块 Tool 和模板协议实现,不写进通用 Agent。
|
||||
8. Agent 创作方式就是填充和修订模板草稿字段;表单化创作页和 Agent 自然语言修订都操作同一份 `PuzzleResultDraft`,并围绕 `workTitle`、`workDescription`、`workTags`、`levels[].levelName`、`levels[].pictureDescription`、`levels[].pictureReference` 这一组字段协同。
|
||||
9. Adapter 从“路由实现”降级为 Agent action tool。
|
||||
10. 规则只保留为安全、契约、成本和权限边界。
|
||||
@@ -0,0 +1,75 @@
|
||||
# 登录成功每日登录埋点闭环方案(2026-05-08)
|
||||
|
||||
## 背景
|
||||
|
||||
后台“埋点数据”需要能看到真实登录触发的 `daily_login` 埋点。此前方案 A 已把“读取任务中心时顺手写每日登录埋点”拆成独立 SpacetimeDB procedure:
|
||||
|
||||
- `record_daily_login_tracking_event_and_return`
|
||||
- `spacetime-client` 方法:`record_daily_login_tracking_event(user_id)`
|
||||
|
||||
但认证成功链路当时还没有调用该方法,因此当时只完成了“任务中心读取不污染登录埋点”,没有完成“用户真实登录写入每日登录埋点”。后续后端通用埋点能力落地后,`daily_login` 已进一步改为通过统一 `record_tracking_event_and_return(RuntimeTrackingEventInput)` procedure 写入,旧 `record_daily_login_tracking_event_and_return` 不再作为认证链路的目标入口。
|
||||
|
||||
## 现象
|
||||
|
||||
用户已经登录、cookie 未过期时,直接打开网页并不会触发每日登录埋点。原因是前端恢复登录态只读取 `/api/auth/me`,这条链路不会主动走 refresh cookie 续期,因此后端新的埋点写入点不会被触发。
|
||||
|
||||
## 修复思路
|
||||
|
||||
在 `AuthGate` 恢复已登录会话时,先主动调用一次 refresh 接口轮换 refresh cookie,再调用 `/api/auth/me` 读取当前会话。这样无论本地 access token 是否仍然有效,打开页面都会进入 refresh 续期链路,从而触发后端的 `daily_login` 埋点写入。
|
||||
|
||||
## 目标
|
||||
|
||||
在用户认证成功并创建 refresh session / access token 后,异步尝试写入每日登录埋点。
|
||||
|
||||
覆盖入口:
|
||||
|
||||
- 手机验证码登录:`POST /api/auth/phone/login`
|
||||
- 密码入口登录:`POST /api/auth/entry`
|
||||
- 重置密码后自动登录:`POST /api/auth/password/reset`
|
||||
- 微信 OAuth callback 登录:`GET /api/auth/wechat/callback`
|
||||
- 微信绑定手机号后激活/登录态刷新:`POST /api/auth/wechat/bind-phone`
|
||||
- refresh cookie 续期:`POST /api/auth/session/refresh`
|
||||
|
||||
## 设计约束
|
||||
|
||||
1. 埋点写入不能阻断登录成功响应。
|
||||
2. 只有认证成功并已创建会话后,或 refresh session rotate 成功并签发新 access token 后才记录。
|
||||
3. 失败只记 warning,继续返回 token / cookie。
|
||||
4. 写入统一收口,避免多个登录 handler 各自拼 procedure 调用。
|
||||
5. 不修改 SpacetimeDB 表结构,不需要更新 `migration.rs`。
|
||||
|
||||
## 实现方案
|
||||
|
||||
新增 `api-server` 内部 helper:
|
||||
|
||||
```rust
|
||||
record_daily_login_tracking_event_after_auth_success(
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
user_id: &str,
|
||||
login_method: AuthLoginMethod,
|
||||
).await;
|
||||
```
|
||||
|
||||
该 helper:
|
||||
|
||||
- 构造 `TrackingEventDraft::user("daily_login", "profile", user_id)`
|
||||
- 使用 `daily-login:{user_id}:{day_key}` 作为事件 ID,保持北京时间自然日幂等
|
||||
- 调用统一 `record_tracking_event_after_success(...)`,最终进入 `record_tracking_event_and_return(RuntimeTrackingEventInput)`
|
||||
- 成功时记录 info
|
||||
- 失败时记录 warn,并明确“登录流程继续”
|
||||
|
||||
在各登录入口 `create_auth_session` 成功后调用该 helper。refresh cookie 续期在 `rotate_session` 和 `sign_access_token_for_user` 成功后调用同一个 helper,`login_method` 使用 refresh session 上保存的 `issued_by_provider`,避免把续期统一误标成 password。
|
||||
|
||||
## 验收
|
||||
|
||||
- `cargo test -p api-server auth_session -- --nocapture`
|
||||
- `cargo check -p api-server`
|
||||
- `cargo check -p spacetime-client`
|
||||
- `npm run check:encoding`
|
||||
- `git diff --check`
|
||||
- `npm run test -- AuthGate.test.tsx`
|
||||
|
||||
## 注意
|
||||
|
||||
`npm run dev` 是长期运行进程;如需本地 smoke,应启动后用 `/healthz` 和后台页面验证,不要等待该命令退出。
|
||||
@@ -0,0 +1,74 @@
|
||||
# 本地 Rust 栈端口冲突预检
|
||||
|
||||
日期:`2026-05-09`
|
||||
|
||||
## 问题
|
||||
|
||||
执行完整本地栈启动命令时:
|
||||
|
||||
```bash
|
||||
node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh --spacetime-port 3101 --skip-spacetime
|
||||
```
|
||||
|
||||
如果本机已有旧的 `api-server`、主站 Vite 或后台 Vite 进程仍在监听默认端口,脚本可能出现混合日志:
|
||||
|
||||
```text
|
||||
Port 3000 is in use, trying another one...
|
||||
Error: Os { code: 10048, kind: AddrInUse }
|
||||
process didn't exit successfully: `server-rs\target\debug\api-server.exe`
|
||||
```
|
||||
|
||||
其中 `wait_for_api_server` 只探测 `http://127.0.0.1:<api-port>/healthz`。当旧 `api-server` 仍监听 `8082` 时,健康检查会命中旧进程并误判新服务已就绪;随后新 `cargo run` 真正绑定 `8082` 时失败。与此同时,Vite 默认会在 `3000` 被占用时漂移到下一个端口,导致浏览器仍可能打开旧前端。
|
||||
|
||||
## 处理
|
||||
|
||||
`scripts/dev-rust-stack.sh` 在进入 SpacetimeDB publish 和 Rust 编译前,先检查三类端口是否可绑定:
|
||||
|
||||
1. Rust `api-server`:默认 `127.0.0.1:8082`。
|
||||
2. 主站 Vite:默认 `0.0.0.0:3000`。
|
||||
3. 后台 Vite:默认 `127.0.0.1:3102`。
|
||||
|
||||
端口被占用时,脚本会直接失败并打印监听进程。Windows 本地会通过 `Get-NetTCPConnection` 与 `Win32_Process` 输出 `pid / name / address / command`,方便精确停止旧进程。
|
||||
|
||||
主站和后台 Vite 也追加 `--strictPort`,避免默认漂移到 `3001`、`3103` 等端口后让浏览器继续访问旧页面。
|
||||
|
||||
## 排障步骤
|
||||
|
||||
PowerShell 查看默认端口占用:
|
||||
|
||||
```powershell
|
||||
Get-NetTCPConnection -State Listen -LocalPort 3000,3102,8082,3101 -ErrorAction SilentlyContinue |
|
||||
Select-Object LocalAddress,LocalPort,OwningProcess |
|
||||
Sort-Object LocalPort
|
||||
```
|
||||
|
||||
查看进程命令行:
|
||||
|
||||
```powershell
|
||||
Get-CimInstance Win32_Process |
|
||||
Where-Object { $_.ProcessId -in @(3000端口PID, 8082端口PID) } |
|
||||
Select-Object ProcessId,Name,CommandLine
|
||||
```
|
||||
|
||||
停止确认可丢弃的旧本地开发进程:
|
||||
|
||||
```powershell
|
||||
Stop-Process -Id <pid> -Force
|
||||
```
|
||||
|
||||
如果确实需要保留旧栈,可显式换端口启动新栈:
|
||||
|
||||
```bash
|
||||
node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh \
|
||||
--skip-spacetime \
|
||||
--spacetime-port 3101 \
|
||||
--api-port 8090 \
|
||||
--web-port 3001 \
|
||||
--admin-web-port 3103
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
1. `bash -n scripts/dev-rust-stack.sh` 通过。
|
||||
2. 默认端口被占用时重新运行完整栈,脚本应在 publish 前失败并打印占用进程。
|
||||
3. 清理占用进程或换端口后,重新启动时不再出现 Vite 端口漂移或 `api-server` `AddrInUse`。
|
||||
137
docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md
Normal file
137
docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Hyper3D Rodin Gen-2 3D 模型生成接入方案 2026-05-08
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本方案用于接入 Hyper3D Rodin Gen-2 的文生 3D 模型与图生 3D 模型能力。
|
||||
|
||||
本次只做后端安全代理与前端可复用 client,不新增 SpacetimeDB 表,不落正式资产对象,不把 Hyper3D API Key 下发到前端。生成结果仍由调用方在拿到下载链接后决定是否进入 OSS / `asset_object` 的正式资产链。
|
||||
|
||||
## 2. 参考接口
|
||||
|
||||
参考文档:
|
||||
|
||||
- `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`
|
||||
|
||||
上游接口:
|
||||
|
||||
```text
|
||||
POST https://api.hyper3d.com/api/v2/rodin
|
||||
POST https://api.hyper3d.com/api/v2/status
|
||||
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
|
||||
HYPER3D_BASE_URL=https://api.hyper3d.com/api/v2
|
||||
HYPER3D_API_KEY=
|
||||
HYPER3D_MODEL_REQUEST_TIMEOUT_MS=180000
|
||||
```
|
||||
|
||||
兼容变量:
|
||||
|
||||
```text
|
||||
RODIN_BASE_URL
|
||||
RODIN_API_KEY
|
||||
RODIN_MODEL_REQUEST_TIMEOUT_MS
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
1. `HYPER3D_API_KEY` / `RODIN_API_KEY` 只允许写入本地或生产私密环境,不提交到 Git。
|
||||
2. 缺少 API Key 时,后端返回 `503 SERVICE_UNAVAILABLE`。
|
||||
3. 本地真实联调推荐把 key 放在 `.env.secrets.local`;`npm run api-server`、`npm run dev:rust` 与 `npm run dev` 均应保持“外层 shell 变量优先,随后 `.env`、`.env.local`、`.env.secrets.local` 逐层覆盖”的口径,避免 `.env` 里的空示例值压掉私密 key。
|
||||
4. `HYPER3D_BASE_URL` 默认使用公开 API 基础地址;如果团队后续改用代理网关,可通过环境变量覆盖。
|
||||
|
||||
## 4. 后端路由
|
||||
|
||||
新增 4 个鉴权路由:
|
||||
|
||||
| 方法 | 路由 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| `POST` | `/api/assets/hyper3d/text-to-model` | 提交 Rodin Gen-2 文生模型任务 |
|
||||
| `POST` | `/api/assets/hyper3d/image-to-model` | 提交 Rodin Gen-2 图生模型任务 |
|
||||
| `POST` | `/api/assets/hyper3d/status` | 使用 `subscriptionKey` 查询任务状态 |
|
||||
| `POST` | `/api/assets/hyper3d/download` | 使用 `taskUuid` 获取模型下载列表 |
|
||||
|
||||
文生模型请求最小体:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompt": "一只低多边形宝箱,适合 RPG 游戏资产",
|
||||
"geometryFileFormat": "glb",
|
||||
"material": "PBR",
|
||||
"quality": "medium",
|
||||
"meshMode": "Quad",
|
||||
"previewRender": true
|
||||
}
|
||||
```
|
||||
|
||||
图生模型请求最小体:
|
||||
|
||||
```json
|
||||
{
|
||||
"imageDataUrls": ["data:image/png;base64,..."],
|
||||
"prompt": "保留主体轮廓,生成游戏可用 3D 模型",
|
||||
"conditionMode": "concat",
|
||||
"geometryFileFormat": "glb"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 约束
|
||||
|
||||
1. 图片只接受 `data:image/png|jpeg|webp;base64,...`,最多 5 张。
|
||||
2. 单张图片解码后不超过 10MB。
|
||||
3. `geometryFileFormat` 限定为 `glb/usdz/fbx/obj/stl`,默认 `glb`。
|
||||
4. `material` 限定为 `PBR/Shaded/All`,默认 `PBR`。
|
||||
5. `quality` 限定为 `high/medium/low/extra-low`,默认 `medium`。
|
||||
6. `meshMode` 限定为 `Quad/Raw`,默认 `Quad`。
|
||||
7. `addons` 首版只允许 `HighPack`。
|
||||
8. `bboxCondition` 必须为 3 个正数,按上游要求序列化为 JSON 字符串。
|
||||
9. `subscriptionKey` 是 Hyper3D 返回的 opaque token,状态查询只校验非空,不在本地做 256 字符等固定长度限制,避免长 token 阻断抓大鹅草稿内联模型生成。
|
||||
|
||||
## 6. 返回语义
|
||||
|
||||
提交任务成功后返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"provider": "hyper3d-rodin",
|
||||
"mode": "text-to-model",
|
||||
"taskUuid": "task-uuid",
|
||||
"subscriptionKey": "subscription-key",
|
||||
"jobUuids": ["job-uuid"],
|
||||
"message": "Submitted.",
|
||||
"tier": "Gen-2"
|
||||
}
|
||||
```
|
||||
|
||||
状态查询会把上游 `Waiting / Generating / Done / Failed` 归一化为 `waiting / generating / done / failed / unknown`,整体状态必须以 `jobs[]` 聚合结果为准。下载接口只返回上游 `list.name` 与 `list.url`,不在 Hyper3D 代理路由中转存文件;具体玩法若需要持久化模型,应在玩法编排层等待 `Done` 后再下载并转存。
|
||||
|
||||
## 7. 验收
|
||||
|
||||
建议执行:
|
||||
|
||||
```bash
|
||||
npm run check:encoding
|
||||
npm run typecheck
|
||||
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts hyper3d
|
||||
cargo test -p api-server hyper3d
|
||||
cargo check -p api-server
|
||||
```
|
||||
|
||||
真实 API smoke 只在本地私密环境设置 `HYPER3D_API_KEY` 后执行。提交生成任务会消耗 Hyper3D Credit,默认验证不自动调用真实生成接口。
|
||||
@@ -108,6 +108,12 @@ Node 旧链路对上传封面有明确处理:
|
||||
|
||||
Rust 本批必须保持这组兼容约束。
|
||||
|
||||
2026-05-08 交互补充:
|
||||
|
||||
1. 前端裁剪面板不再展示 `缩放 / 左右位置 / 上下位置` 参数滑杆。
|
||||
2. 作者直接在图片上拖拽裁剪框内部移动区域,或拖拽四边、四角调整裁剪范围。
|
||||
3. 调整过程中前端持续锁定 `16:9`,确认时仍只提交后端兼容的 `cropRect`。
|
||||
|
||||
## 4. 请求与响应 contract
|
||||
|
||||
### 4.1 `POST /api/custom-world/scene-image`
|
||||
|
||||
@@ -122,6 +122,12 @@
|
||||
2. AI worker 绕过确认链路写出不完整记录
|
||||
3. 把 OSS 响应中的派生 URL 当成对象真相
|
||||
|
||||
### 5.1 OSS V4 签名时间格式
|
||||
|
||||
`platform-oss` 的 OSS V4 签名时间必须显式格式化为 `YYYYMMDDTHHMMSSZ`,签名 scope 日期必须显式格式化为 `YYYYMMDD`。实现中不要依赖 `OffsetDateTime::time().to_string()` 或 `date().to_string()` 再替换字符,因为 UTC 小时、分钟、秒为个位数时可能不会保留前导零,导致 AI 生图已完成但上传 OSS 阶段报 `OSS V4 签名时间格式化失败`。
|
||||
|
||||
拼图、视觉小说、自定义世界等所有服务端生成图片上传链路都复用同一套 `platform-oss` 签名 helper;新增签名逻辑时必须覆盖个位时间分量的测试样例,例如 `05:03:09` 应输出 `T050309Z`。
|
||||
|
||||
## 6. 与 Web 端的边界
|
||||
|
||||
Web 端当前只允许:
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Maincloud 残留引用移除策略
|
||||
|
||||
## 背景
|
||||
|
||||
项目后端、发布和本地联调已经切到 `server-rs + Axum + SpacetimeDB` 当前基线,并以本地或显式配置的 SpacetimeDB 服务为运行目标。历史上用于连接 SpacetimeDB Maincloud 的脚本、环境变量、测试名和文档口径已经不再代表当前工程约束。
|
||||
|
||||
## 决策
|
||||
|
||||
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关命名、脚本、测试、环境变量、文档要求和启动命令全部视为历史残留。
|
||||
- 后续禁止新增、运行或引用 `maincloud` 相关代码、测试、脚本、文档要求、环境变量和命令。
|
||||
- 旧文档若要求执行 `npm run api-server:maincloud`、`npm.cmd run api-server:maincloud` 或读取 `GENARRATIVE_SPACETIME_MAINCLOUD_*`,一律以本策略和 `AGENTS.md` 最新约束为准,并在触碰该文档或代码时同步修正。
|
||||
- 后端 API smoke 统一使用当前非 Maincloud 启动入口:`npm run api-server`。服务就绪以 `GET /healthz` 返回成功为准。
|
||||
- SpacetimeDB 运行目标必须来自本地开发服务、生产自托管服务,或显式 `SERVER_URL` 配置;不得再回退到 Maincloud 默认值。
|
||||
|
||||
## 落地规则
|
||||
|
||||
1. 修改后端代码后,按对应 DDD 文档执行定向测试;涉及 API smoke 时执行 `npm run api-server` 并探测 `/healthz`。
|
||||
2. 触碰历史脚本、测试支撑或文档时,优先删除或改名其中的 `maincloud` 口径,改为当前本地或显式服务配置口径。
|
||||
3. 新增文档不得把 `api-server:maincloud` 写成验收命令,也不得要求配置 `GENARRATIVE_SPACETIME_MAINCLOUD_*`。
|
||||
4. 新增测试不得使用 `DEFAULT_MAINCLOUD_*` 这类历史命名;测试辅助应使用通用 `api-server`、`healthz` 或明确的本地 SpacetimeDB 命名。
|
||||
5. 如需保留历史文档中的旧执行记录,只能作为归档事实存在,不得作为当前执行清单、验收命令或开发约束继续引用。
|
||||
|
||||
## 验证方式
|
||||
|
||||
常规检查:
|
||||
|
||||
```bash
|
||||
rg -n "maincloud|Maincloud|MAINCLOUD|api-server:maincloud|GENARRATIVE_SPACETIME_MAINCLOUD" AGENTS.md docs .hermes package.json scripts server-rs -S
|
||||
```
|
||||
|
||||
验收口径:
|
||||
|
||||
- `AGENTS.md`、`.hermes/shared-memory/` 和当前任务相关文档不得要求使用 Maincloud。
|
||||
- 活跃脚本、测试和配置不得依赖 `GENARRATIVE_SPACETIME_MAINCLOUD_*`。
|
||||
- 后端启动和 smoke 以 `npm run api-server` 与 `/healthz` 为准。
|
||||
|
||||
## 关联文档
|
||||
|
||||
- [SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md](./SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md)
|
||||
- [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md)
|
||||
- [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)
|
||||
@@ -518,15 +518,25 @@ totalItemCount = clearCount * 3
|
||||
|
||||
每种 `itemTypeId` 的数量必须是 `3` 的倍数。
|
||||
|
||||
消除物类型数按创作输入的 `clearCount` 计算:
|
||||
|
||||
```text
|
||||
itemTypeCount = clearCount <= 25 ? clearCount : 25
|
||||
```
|
||||
|
||||
当 `clearCount <= 25` 时,运行态快照中的不同 `itemTypeId` 数量必须等于 `clearCount`;当 `clearCount > 25` 时,不同 `itemTypeId` 数量必须等于 `25`。超过 `25` 组的消除目标按这 `25` 种类型轮转生成,确保每种类型的最终数量仍是 `3` 的倍数。
|
||||
|
||||
这 `25` 组在同一局内还必须对应 25 套不同的形状和颜色签名,不能有两组视觉上撞型。
|
||||
|
||||
## 9.3 demo 视觉素材
|
||||
|
||||
首版使用内置视觉键和前端内置几何图形资产,不接真实图片生成。
|
||||
首版使用 25 个内置积木件视觉键和前端内置几何图形资产,不接真实图片生成。
|
||||
|
||||
1. 水果题材必须使用 `watermelon-green / apple-red / banana-yellow / grape-purple / melon-green / berry-blue / peach-pink / plum-indigo / lime-lime / orange-orange` 这组内置水果视觉键;前端首版将其映射为纯色几何体,不渲染水果写实图,也不能显示为带文字或透明气泡的小球。
|
||||
2. 非水果题材暂使用 `red_circle / yellow_triangle / purple_diamond / green_square / blue_star / orange_hexagon / cyan_capsule / pink_heart / lime_leaf / white_moon` 这组兜底颜色形状视觉键。
|
||||
3. `visualKey` 不允许在前端统一兜底为同一个素材;未知 key 至少要有稳定的颜色差异,避免多个不同 `itemTypeId` 被玩家误认为同一种物品。
|
||||
4. 运行态图案必须使用实心、高饱和、无文字的几何 SVG,至少覆盖圆形、三角形、菱形、方形、五角星、六边形、胶囊、心形、梯形、平行四边形等多种轮廓;外层命中按钮不得再显示半透明气泡底。
|
||||
5. 水果题材的相对尺寸由后端权威半径决定,首版要求西瓜明显大于苹果,苹果、橙子、桃子等中型水果大于葡萄、李子、青柠等小型水果;前端不得自行改写规则半径,只负责按快照表现。
|
||||
1. 当前 demo 使用 25 个积木件视觉键作为默认素材池;前端首版将其映射为无文字的 2D 图标和程序化 3D 积木模型,不渲染写实图,也不能显示为带文字或透明气泡的小球。
|
||||
2. `visualKey` 不允许在前端统一兜底为同一个素材;未知 key 至少要有稳定的颜色差异,避免多个不同 `itemTypeId` 被玩家误认为同一种物品。
|
||||
3. 运行态图案必须使用实心、高饱和、无文字的几何 SVG,并保持与 3D 模型同一批 `visualKey` 对应关系;外层命中按钮不得再显示半透明气泡底。
|
||||
4. 每局按使用类型数量分配五档相对体积:XL 型 `1.60~2.30` 占 `20%`,L 型 `1.25~1.60` 占 `30%`,M 型固定 `1.00` 占 `30%`,XS 型 `0.65~0.85` 占 `15%`,S 型 `0.35~0.50` 占 `5%`。非整数配额按最大余数补齐,总数必须等于本局使用类型数量。
|
||||
5. 同一局内同一个颜色和造型的 `visualKey` 只能对应一个尺寸档位和一个半径,不能出现同一物品类型三件副本大小不同,也不能出现同一视觉键在复用时被分配到两种大小。前端不得自行改写规则半径,只负责按快照表现。
|
||||
6. 后续接入真实题材图片素材前,必须另补资产生成方案。
|
||||
|
||||
## 9.4 难度
|
||||
@@ -639,6 +649,8 @@ src/components/match3d-runtime/
|
||||
|
||||
1. 名称:`抓大鹅`
|
||||
2. 子标题:`经典消除玩法`
|
||||
3. `src/config/newWorkEntryConfig.ts` 中 `match3d` 必须保持 `visible: true` 与 `open: true`,平台首屏卡带和创作类型弹层都从该配置派生,不允许只保留路由能力却隐藏创作入口。
|
||||
4. 入口点击后进入 `match3d-agent-workspace`,对应前端路径为 `/creation/match3d/agent`,并通过 `/api/creation/match3d/sessions` 创建正式 Agent 会话;如果公开广场读取失败,只降级广场列表,不能阻断或隐藏抓大鹅创作入口。
|
||||
|
||||
## 11.4 运行态 UI
|
||||
|
||||
@@ -646,9 +658,10 @@ src/components/match3d-runtime/
|
||||
|
||||
1. 圆形空间占据主要区域。
|
||||
2. 备选栏固定 `7` 格。
|
||||
3. 倒计时清晰但不遮挡物品。
|
||||
4. 物品点击区域稳定,不因动画造成布局跳动。
|
||||
5. 胜利/失败结算使用独立面板,不在当前面板下方展开。
|
||||
3. 3D 模式下,备选栏格子使用与圆形空间内一致的程序化 3D 模型预览,固定斜 `45` 度视角,且不接入场内物理碰撞;托盘预览必须共享一个 WebGL renderer,按实际容器宽高更新正交相机,并以独立 pivot 居中每个模型后定位到对应格子;不能每格创建独立 renderer;仅 WebGL 回退或 `2D` 模式使用 2D 图标。
|
||||
4. 倒计时清晰但不遮挡物品。
|
||||
5. 物品点击区域稳定,不因动画造成布局跳动。
|
||||
6. 胜利/失败结算使用独立面板,不在当前面板下方展开。
|
||||
|
||||
## 11.5 本地 mock 口径
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 抓大鹅创作入口开放与错误隔离 2026-05-01
|
||||
|
||||
> 2026-05-08 更新:抓大鹅创作端入口已重新开放,`match3d.visible` 调整为 `true`。当前入口状态以 `NEW_WORK_ENTRY_CONFIG_2026-05-01.md` 和 `src/config/newWorkEntryConfig.ts` 为准。
|
||||
|
||||
## 1. 背景
|
||||
|
||||
抓大鹅 Match3D 玩法域已完成当前 demo 主链接入,本轮恢复创作页入口,使玩家可以从创作中心直接进入抓大鹅共创工作台。同时,平台首页会并行读取 RPG、拼图、抓大鹅等公开广场数据,公开广场接口未就绪、空表或临时失败不应污染创作入口错误态,也不应表现成登录异常。
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
# 抓大鹅草稿素材生成流水线 2026-05-10
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本方案用于改造 `生成抓大鹅草稿` 的首版生成链路:点击按钮后先进入独立生成过程页,生成结束后自动进入抓大鹅草稿页,并在草稿页 `3D素材` Tab 预览本次生成的 3D 模型。
|
||||
|
||||
本次只把任意难度都收敛为 `3` 件物品。后续难度曲线恢复时,再把物品数、网格数和手动 3D 任务数量从配置中放开。
|
||||
|
||||
## 2. 前端流程
|
||||
|
||||
入口仍复用 `Match3DAgentWorkspace` 表单。点击 `生成抓大鹅草稿` 后:
|
||||
|
||||
1. 创建 Match3D session。
|
||||
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 的确定性写入。
|
||||
|
||||
`match3d_compile_draft` action 的后端顺序为:
|
||||
|
||||
1. 读取 session config。
|
||||
2. 将本次 MVP 的 `clearCount` 固定为 `3`,并同步用于草稿编译。
|
||||
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 并等待 GLB 下载完成;前端 `match3d_compile_draft` action 请求超时必须覆盖该长耗时链路,当前 Match3D client 使用 20 分钟超时。Rodin 单模型状态轮询预算为 10 分钟,下载列表发布轮询预算为 5 分钟;GLB 下载和 OSS PutObject 各自设置 3 分钟 HTTP 超时,避免上游下载或转存连接长期悬挂。由于 3 个模型并行生成,总耗时按最慢模型计算,不能按模型数量线性叠加。结果页 `3D素材` Tab 直接加载已生成模型;用户点击 `重新生成` 时再复用 Rodin 安全代理,首版重新生成只更新当前页面内预览状态,后续正式资产绑定以独立技术方案为准。
|
||||
|
||||
## 4. 图片提示词
|
||||
|
||||
素材图提示词必须显式包含:
|
||||
|
||||
```text
|
||||
生成一张1:1图片
|
||||
生成2*2网格素材图
|
||||
整体画风遵循:...
|
||||
只绘制这些物品:...
|
||||
不要出现文字、水印、UI、边框
|
||||
```
|
||||
|
||||
`包含若干个物品名称` 在落地中解释为“按生成出的物品名称绘制对应主体”,不要求图片上写出物品名称。这样可以避免文字渲染污染切图和后续手动 3D 模型参考。
|
||||
|
||||
入口页内置风格参考图通过同一 VectorEngine `gpt-image-2-all` 能力生成,保存路径固定为:
|
||||
|
||||
```text
|
||||
public/match3d-style-references/clay-toy.png
|
||||
public/match3d-style-references/low-poly.png
|
||||
public/match3d-style-references/toy-plastic.png
|
||||
public/match3d-style-references/wood-carved.png
|
||||
public/match3d-style-references/voxel-block.png
|
||||
public/match3d-style-references/metal-mecha.png
|
||||
```
|
||||
|
||||
这些图片只作为入口页风格选择的视觉参考,不进入用户草稿资产,不替代生成时的物品素材图。
|
||||
|
||||
## 5. OSS 路径
|
||||
|
||||
新增 generated legacy prefix:
|
||||
|
||||
```text
|
||||
generated-match3d-assets
|
||||
```
|
||||
|
||||
建议对象分组:
|
||||
|
||||
```text
|
||||
generated-match3d-assets/{sessionId}/{profileId}/material-sheet/{taskId}/sheet.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`、`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. 自动保存与草稿恢复
|
||||
|
||||
点击 `生成抓大鹅草稿` 后,草稿存档创建与素材生成解耦:
|
||||
|
||||
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。
|
||||
|
||||
草稿架重进路径为:
|
||||
|
||||
```text
|
||||
草稿 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 保留本次生成顺序和图片;同 `itemId` 在 `profile.generatedItemAssets` 中已有模型字段时,用 profile 模型字段补齐 draft;从草稿架重进没有 draft 时,用 `profile.generatedItemAssets`;两者都没有才回退到默认 3D 素材占位。
|
||||
|
||||
结果页 `作品信息` Tab 字段命名对齐拼图草稿:
|
||||
|
||||
1. `作品名称` 对应 Match3D `gameName`。
|
||||
2. `作品描述` 对应 Match3D `summary`,草稿生成默认空。
|
||||
3. `作品标签` 对应 Match3D `tags`,可由 AI 首次生成并允许用户继续编辑。
|
||||
4. 封面图与作品名称不再拆成左右两个大模块;封面只作为同一 Tab 内的可选上传入口,避免和作品基础信息割裂。
|
||||
|
||||
`3D素材` 详情页只保留:
|
||||
|
||||
1. 模型预览区:优先加载 `modelSrc` 对应 GLB,缺失时加载 `modelObjectKey`,支持拖动旋转;没有模型时展示空预览。
|
||||
2. 素材名称输入。
|
||||
3. `重新生成` 按钮。
|
||||
|
||||
详情页不再展示参考图、用途、提示词、文生/图生切换、状态查询、下载列表、taskUuid 或 subscriptionKey。
|
||||
|
||||
## 7. 验收
|
||||
|
||||
建议执行:
|
||||
|
||||
```powershell
|
||||
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
|
||||
cargo test -p platform-oss --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
|
||||
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`、`HYPER3D_API_KEY` 和 OSS 访问变量。后端改动后使用 `npm run api-server` 启动,并检查 `/healthz`。
|
||||
@@ -1,5 +1,11 @@
|
||||
# 抓大鹅 Match3D F1 创作入口与 Agent UI 落地记录 2026-04-30
|
||||
|
||||
> 2026-05-08 更新:抓大鹅创作端入口已重新开放,当前 `match3d.visible` 为 `true`。本文件记录 F1 接入能力,入口是否展示以 `NEW_WORK_ENTRY_CONFIG_2026-05-01.md` 和 `src/config/newWorkEntryConfig.ts` 为准。
|
||||
>
|
||||
> 2026-05-10 更新:抓大鹅入口页对齐拼图入口页,直接嵌入创作页模板 Tab。入口表单不再展示参考图、消除次数输入、难度数值滑杆和题材/物品/难度摘要框,仅保留题材主题大输入框和难度选项。难度选项负责派生 `clearCount` 与 `difficulty`,生成按钮必须展示 `消耗20光点`。
|
||||
>
|
||||
> 2026-05-10 补充:入口页新增 `3D素材风格` 横向滑动选择,首批风格参考图通过 VectorEngine `gpt-image-2-all` 生成并保存到 `public/match3d-style-references/`。最后一个选项为 `自定义`,点击后弹出独立面板填写画风描述。
|
||||
|
||||
## 1. 阶段边界
|
||||
|
||||
本文件承接《MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md》的 F1 包。
|
||||
@@ -28,19 +34,38 @@ badge: 可创建
|
||||
|
||||
入口来源统一走 `getVisiblePlatformCreationTypes()`,因此创作首页首屏卡带与“选择创作类型”弹层会同时出现抓大鹅。
|
||||
|
||||
## 4. Agent 工作区
|
||||
## 4. 入口表单工作区
|
||||
|
||||
新增 `Match3DAgentWorkspace`,复用通用 `CreationAgentWorkspace`。
|
||||
新增 `Match3DAgentWorkspace`,组件名继续兼容既有路由、草稿恢复和父层分流;当前主入口已从固定 Agent 追问收口为拼图式表单。
|
||||
|
||||
Agent 只收集三类锚点:
|
||||
创作页 `选择模板` Tab 中切换到 `抓大鹅` 时,直接渲染该表单,不创建会话,也不跳到独立工作台。点击生成后才创建 Match3D 会话并执行 `match3d_compile_draft`。
|
||||
|
||||
1. 题材主题。
|
||||
2. 需要消除次数。
|
||||
3. 难度。
|
||||
表单只展示三个输入块:
|
||||
|
||||
工作区支持参考图片上传入口。图片在 F1 中先以 Data URL 形式随消息 payload 带给 mock client;B5 接入后由后端 facade 替换为正式资产上传与引用。
|
||||
1. `想做一个什么题材的抓大鹅?`:大文本输入框,收集 `themeText`。
|
||||
2. `3D素材风格`:横向滑动风格卡,选择会写入 `assetStyleId`、`assetStyleLabel` 与 `assetStylePrompt`。
|
||||
3. `难度`:四个选项按钮,选项内部派生消除次数和难度数值。
|
||||
|
||||
UI 中不默认展示玩法规则长文,只展示进度、锚点、聊天内容和必要按钮。
|
||||
当前难度映射固定为:
|
||||
|
||||
```text
|
||||
轻松 -> clearCount 8, difficulty 2
|
||||
标准 -> clearCount 12, difficulty 4
|
||||
进阶 -> clearCount 16, difficulty 6
|
||||
硬核 -> clearCount 20, difficulty 8
|
||||
```
|
||||
|
||||
入口页不再上传参考图,提交 payload 中 `referenceImageSrc` 固定为 `null`。如果从旧会话或旧草稿恢复,前端只根据已有 `difficulty` 选择最接近的难度选项,并按当前选项重新派生 `clearCount` 与 `difficulty`。
|
||||
|
||||
内置风格选项为:
|
||||
|
||||
```text
|
||||
黏土手作 / 低多边形 / 玩具塑料 / 木质雕刻 / 体素积木 / 金属机甲 / 自定义
|
||||
```
|
||||
|
||||
自定义风格必须在弹出面板中填写描述后才能应用。入口表单必须在移动端创作页可视区内完成题材、风格、难度和生成按钮的展示,页面自身不产生纵向滚动;风格卡只允许横向滑动。
|
||||
|
||||
生成按钮文案为 `生成抓大鹅草稿`,按钮内必须同时展示 `消耗20光点`。UI 中不默认展示玩法规则长文,也不展示隐藏派生数值的摘要框。
|
||||
|
||||
## 5. mock client
|
||||
|
||||
@@ -82,10 +107,13 @@ POST /api/creation/match3d/sessions/:sessionId/compile
|
||||
|
||||
## 8. 验收口径
|
||||
|
||||
1. 创作首页能看到“抓大鹅 / 经典消除玩法”。
|
||||
2. 弹层选择“抓大鹅”能进入 Agent 工作区。
|
||||
3. 输入题材、消除次数、难度后进度到 `100%`。
|
||||
4. 点击“生成结果页”进入草稿承接页。
|
||||
5. 可从草稿承接页返回 Agent 修改。
|
||||
6. `npm run check:encoding` 通过。
|
||||
7. `npm run typecheck` 通过。
|
||||
1. 创作页 `选择模板` Tab 能看到 `抓大鹅 / 经典消除玩法`。
|
||||
2. 切换到 `抓大鹅` Tab 后,页面内直接显示抓大鹅入口表单,不提前创建会话。
|
||||
3. 表单不展示参考图、`需要消除次数`、`难度数值`、`题材`、`物品`、`难度`摘要框。
|
||||
4. 输入题材、选择风格和难度后,提交 payload 包含派生后的 `clearCount` 与 `difficulty`,`referenceImageSrc` 为 `null`,并包含 `assetStyleId`、`assetStyleLabel` 与 `assetStylePrompt`。
|
||||
5. 生成按钮展示 `消耗20光点`。
|
||||
6. 点击 `自定义` 风格弹出独立面板,填写后应用到提交 payload;未填写时不能应用空自定义风格。
|
||||
7. 移动端创作页内抓大鹅入口内容不产生纵向滚动,风格卡横向滑动。
|
||||
8. 点击生成后创建会话并进入草稿生成/结果页链路。
|
||||
9. `npm run check:encoding` 通过。
|
||||
10. `npm run typecheck` 通过。
|
||||
|
||||
75
docs/technical/MATCH3D_RODIN_ASSET_TAB_2026-05-10.md
Normal file
75
docs/technical/MATCH3D_RODIN_ASSET_TAB_2026-05-10.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# 抓大鹅 Rodin 3D 素材 Tab 接入方案 2026-05-10
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本方案用于把抓大鹅结果页调整为多 Tab 工作台,并新增 `3D素材` Tab。该 Tab 专门服务抓大鹅玩法内物件、奖励、障碍等 3D 素材的 Rodin 生成试验。
|
||||
|
||||
本次复用已有 Hyper3D Rodin Gen-2 后端安全代理,不新增 SpacetimeDB 表;草稿生成链路会把 Rodin 模型转存到 OSS,并通过 Match3D 作品 profile 的 `generatedItemAssets` 恢复。不得把 Hyper3D API Key、上游下载 URL 或本地 Data URL 写成正式资产真相。
|
||||
|
||||
## 2. 页面结构
|
||||
|
||||
抓大鹅结果页拆为三个 Tab:
|
||||
|
||||
1. `作品信息`:承接封面、游戏名称、标签、简介。
|
||||
2. `玩法配置`:承接题材、消除次数、难度、参考图与局内数量摘要。
|
||||
3. `3D素材`:展示 Rodin 素材列表,点击列表项进入详情生成页。
|
||||
|
||||
`3D素材` Tab 的列表布局参考 RPG 结果页角色列表:移动端纵向卡片,桌面端多列紧凑卡片;卡片展示素材名和状态。详情页只保留模型预览、素材名称和 `重新生成` 按钮。
|
||||
|
||||
## 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. 查询到的状态、进度与下载文件列表仅作为内部状态,不在详情页展示。
|
||||
|
||||
正式资产链后续再接:
|
||||
|
||||
1. 下载文件转存 OSS。
|
||||
2. `asset_object` 确认。
|
||||
3. `asset_entity_binding` 绑定到 Match3D 作品或物件槽位。
|
||||
|
||||
首版不得把上游下载 URL 直接写入 Match3D profile,也不得把 Data URL 持久化到 SpacetimeDB。
|
||||
|
||||
## 4. 接口复用
|
||||
|
||||
继续使用既有前端 client:
|
||||
|
||||
```text
|
||||
src/services/hyper3dModelGenerationService.ts
|
||||
```
|
||||
|
||||
对应后端路由:
|
||||
|
||||
```text
|
||||
POST /api/assets/hyper3d/text-to-model
|
||||
POST /api/assets/hyper3d/image-to-model
|
||||
POST /api/assets/hyper3d/status
|
||||
POST /api/assets/hyper3d/download
|
||||
```
|
||||
|
||||
请求参数默认使用:
|
||||
|
||||
```text
|
||||
geometryFileFormat = glb
|
||||
material = PBR
|
||||
quality = medium
|
||||
meshMode = Quad
|
||||
previewRender = true
|
||||
conditionMode = concat
|
||||
```
|
||||
|
||||
## 5. 验收
|
||||
|
||||
建议执行:
|
||||
|
||||
```powershell
|
||||
npm run check:encoding
|
||||
npm run test -- src\components\match3d-result\Match3DResultView.test.tsx
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
真实 Rodin 生成会消耗 Hyper3D Credit,只在本地私密环境配置 `HYPER3D_API_KEY` 或 `RODIN_API_KEY` 后手动验证。
|
||||
@@ -19,7 +19,7 @@
|
||||
1. 现有 `Match3DVisualIcon`、`Match3DToken` 和托盘 2D 图案渲染代码必须保留。
|
||||
2. 新增 3D 表现层只作为运行态棋盘的可选渲染分支。
|
||||
3. 当浏览器不支持 WebGL、3D 依赖加载失败或实验开关关闭时,运行态必须自动回到现有 2D 图案表现。
|
||||
4. 托盘继续使用当前 2D 图标,便于玩家识别已选物品,也便于实验失败时快速回滚。
|
||||
4. 3D 模式下,托盘直接复用场内同一套程序化 3D 模型,以固定斜 `45` 度识别视角展示已选物品;托盘内物品不进入物理世界,不参与碰撞。WebGL 不可用或实验回退时,托盘继续使用当前 2D 图标。
|
||||
|
||||
## 3. 工程落点
|
||||
|
||||
@@ -50,7 +50,7 @@ cannon-es
|
||||
|
||||
3D 分支只读取后端快照中的物品坐标、层级、可点击状态和视觉键。物理碰撞、轻微堆叠和几何体姿态只作为前端表现层,不改变消除规则、备选栏规则、胜负判定或最终权威快照。
|
||||
|
||||
`match3dVisualAssets.tsx` 保留 2D 纯色几何图案映射,运行态托盘继续使用该 2D 图标;`match3dRuntimePresentation.ts` 收口显示层坐标和状态兼容,避免异常旧坐标把 2D 或 3D 物体推到圆形边界外。
|
||||
`match3dVisualAssets.tsx` 保留 2D 纯色几何图案映射,运行态托盘在 3D 模式下通过 `Match3DTrayPreviewBoard` 使用单个共享 WebGL 预览层复用 `createMatch3DItemMesh` 生成同款 3D 模型,不能为每个托盘格单独创建 `WebGLRenderer`。托盘预览层必须按实际容器宽高更新正交相机,并把每个模型放入独立 pivot 后再沿相机屏幕横轴定位到对应格子中心;托盘预览不能把所有模型统一缩放到同一外观尺寸,必须保留场内相对尺寸差异,否则会让点击后入槽的模型和场内物件对应关系失真。WebGL 不可用或 2D 回退时继续使用该 2D 图标;`match3dRuntimePresentation.ts` 收口显示层坐标和状态兼容,避免异常旧坐标把 2D 或 3D 物体推到圆形边界外。
|
||||
|
||||
## 4. 验收口径
|
||||
|
||||
@@ -58,8 +58,10 @@ cannon-es
|
||||
2. 3D 几何体保持在圆形区域内,不被圆形边界裁切到不可点。
|
||||
3. 物体进入场景后有轻微物理碰撞和堆叠稳定过程。
|
||||
4. 点击 3D 物体后仍执行原有乐观入槽、后端确认、三消反馈和结算。
|
||||
5. 单元测试仍覆盖 2D 回退图案,确保回退路径没有被删除。
|
||||
6. 390px 移动端与桌面端均不能出现横向溢出,顶部状态、圆形棋盘和 7 格备选栏都要完整可见。
|
||||
5. 被取出的 3D 物体必须立即从棋盘物理世界移除;备选栏展示的是无碰撞、固定角度的独立预览模型,不允许继续受场内碰撞、重力或堆叠影响。
|
||||
6. 托盘 3D 预览必须共享一个 renderer,避免多个 WebGL 上下文导致中心棋盘上下文被浏览器回收;中心棋盘监听 `webglcontextlost`,丢失时自动回退 2D 表现,禁止出现模型不可见但仍可点击的状态。
|
||||
7. 单元测试仍覆盖 2D 回退图案,确保回退路径没有被删除。
|
||||
8. 390px 移动端与桌面端均不能出现横向溢出,顶部状态、圆形棋盘和 7 格备选栏都要完整可见。
|
||||
|
||||
## 5. 锅型容器优化
|
||||
|
||||
@@ -72,3 +74,202 @@ cannon-es
|
||||
3. 物理世界使用同一个锅内半径作为水平活动边界,所有可消除物体的初始位置和运行中位置都必须被约束在圆形锅内。
|
||||
4. 物体受到重力后只允许在锅内碰撞、滑动、翻滚和向上堆叠,不能因为碰撞或初始坐标散落到圆形区域外。
|
||||
5. 该优化仍只属于前端 3D 表现层,不改变后端运行态坐标、点击权威判定、备选栏、消除和胜负规则。
|
||||
|
||||
## 6. 中心引力优化
|
||||
|
||||
2026-05-02 追加中心引力,用来解决高消除次数下 3D 物体过于松散、贴边后被圆形场地裁切的问题。体验后发现默认向心力会让模型过度挤压成团,因此当前先关闭默认引力,只保留代码开关,后续如需再尝试可重新调参。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 中心引力默认系数为 `0`,默认不对物理 body 施加水平向心力。
|
||||
2. 引力只作用在 X/Z 平面,不改变垂直重力,物体仍会自然落到锅底或堆叠在其他物体上。
|
||||
3. 引力在越靠近锅边时越明显,避免大量物体碰撞后形成稀疏外环;靠近中心时力度收敛,避免所有物体被吸成单点。
|
||||
4. 锅内活动边界继续作为硬约束;高数量物体应被锅边挡住并向上堆叠,不允许散落到圆形场地外。
|
||||
5. `/match3d?clearCount=100` 可作为本地直达压力测试入口,用于验证 300 个物体时仍在锅内聚拢。
|
||||
|
||||
## 7. 正交俯视与真实场地边界
|
||||
|
||||
2026-05-02 针对高堆叠时 3D 物体被 DOM 圆形裁切的问题,明确中心圆形区域不是裁切蒙版,而是游戏实际游玩场地。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 3D 棋盘使用正交俯视相机,避免高处物体因为透视放大而投影到圆形场地外。
|
||||
2. 圆形场地的内圈圆环对应 3D 世界里的锅内空气墙,物体范围由物理约束控制,不再依赖 DOM `overflow-hidden` 裁切。
|
||||
3. 外层圆形 UI 只负责显示锅沿和场地外观,不能把物体裁成半截;如果物体看起来越界,优先修正相机、物理半径和空气墙。
|
||||
4. 高数量压力测试以 `/match3d?clearCount=100` 为基准,物体可以在场地内向上堆叠,但不能被圆形边缘压住或切掉。
|
||||
|
||||
## 8. 类型数量与样式池历史口径
|
||||
|
||||
2026-05-03 曾调整消除物类型生成规则,解决 3D 关卡中可消除物类型和样式过少的问题。该节为历史口径,后续实际实现以第 11 节 25 个积木件资源池为准。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 历史版本曾使用 20 类形状颜色组合。
|
||||
2. 当前版本已替换为 25 个积木件,旧 20 类上限不再作为编码依据。
|
||||
3. 3D 与 2D 回退仍共用视觉键映射,新增样式不能破坏 `?match3dRender=2d` 回退路径。
|
||||
|
||||
## 9. 特殊形状 3D 可读性修正
|
||||
|
||||
2026-05-03 针对 20 组关卡中看不到十字、圆环、盾形、闪电、月牙、箭头等新形状的问题,补充 3D 几何体渲染口径。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 数据层仍使用 `visualKey` 决定类型,不新增贴图素材或文本标识。
|
||||
2. 十字、心形、星形、圆环、盾形、闪电、月牙、箭头、V 形等特殊形状不能继续使用普通盒子、球体或锥体代理,必须生成俯视角可辨认的 3D 轮廓。
|
||||
3. 特殊形状使用 Three.js 程序化轮廓挤出生成,保持当前 3D 实验可快速回退,不影响现有 2D 图案分支。
|
||||
4. 特殊形状的物理碰撞可以继续使用近似碰撞体,但显示网格需要固定为俯视可读姿态,避免落地翻滚后又变成长方块或普通三角体。
|
||||
5. 当前特殊形状已被 25 个积木件资源池替换;不能为了让玩家开局肉眼看到全部类型而改动初始层级、物理堆叠、遮挡、边界或可点击规则。
|
||||
|
||||
## 10. 15 组中档局面的类型唯一性修正
|
||||
|
||||
2026-05-03 针对 `clearCount=15` 时可消除物类型不足 15 种的问题,补充中档局面的规则验收口径。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. `clearCount=15` 时,运行态数据中必须生成 `15` 种不同 `itemTypeId`,且首个 `15` 个 `visualKey` 必须分别对应不同几何形状。
|
||||
2. 每种 `itemTypeId` 在 `clearCount=15` 时只对应 1 次消除目标,即恰好生成 `3` 件物体;同一种视觉模型在同局中不应出现超过 3 件。
|
||||
3. 不为了展示 15 种而修改初始层级、物理堆叠、遮挡、边界或可点击规则;被盖住、堆叠和局部不可见是正常玩法效果。
|
||||
4. 当前版本已改为第 11 节的 `itemTypeCount = clearCount <= 25 ? clearCount : 25` 规则。
|
||||
|
||||
## 11. 25 个积木件资源池替换
|
||||
|
||||
2026-05-03 根据新的参考图,把可消除物体替换为 25 个积木件类型,并调整本局类型抽取规则。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 默认 `visualKey` 资源池改为 25 个积木件,覆盖长条、短条、2x2、2x3、2x4、1x1、光板、斜坡、圆柱、透明圆环、拱门和锥形件等差异化模型。
|
||||
2. 前端 3D 表现继续使用 Three.js 程序化几何体生成,不引入外部贴图或 GLB;托盘和 2D 回退继续使用同一批 `visualKey` 的简化图标。
|
||||
3. `clearCount <= 25` 时,本局从 25 个类型中按确定性随机顺序抽取 `clearCount` 种类型,不允许同局刷新重复类型。
|
||||
4. `clearCount > 25` 时,本局最多使用 25 种类型,额外消除组在这 25 种中轮转复用;每种类型最终数量仍必须是 3 的倍数。
|
||||
5. 该随机抽取只决定本局使用哪些类型和使用顺序,不改变物理堆叠、遮挡、边界、可点击判定、备选栏和胜负规则。
|
||||
6. 前端本地试玩、创作后试玩和后端权威运行态必须使用同一套 `itemTypeCount = clearCount <= 25 ? clearCount : 25` 口径。
|
||||
|
||||
## 12. 五档体积规则
|
||||
|
||||
2026-05-03 追加可消除物模型大小规则,把每局可消除物按五档相对体积分配。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. M 型作为标准体积 `1.00`。
|
||||
2. XL 型相对体积范围为 `1.60~2.30`,占本局可消除类型数的 `20%`。
|
||||
3. L 型相对体积范围为 `1.25~1.60`,占本局可消除类型数的 `30%`。
|
||||
4. M 型相对体积固定为 `1.00`,占本局可消除类型数的 `30%`。
|
||||
5. XS 型相对体积范围为 `0.65~0.85`,占本局可消除类型数的 `15%`。
|
||||
6. S 型相对体积范围为 `0.35~0.50`,占本局可消除类型数的 `5%`。
|
||||
7. 本局使用类型数仍按第 11 节计算,即 `clearCount <= 25 ? clearCount : 25`。比例遇到非整数时按最大余数补齐,确保五档数量之和等于本局使用类型数。
|
||||
8. 体积档位分配绑定到本局选中的 `visualKey`,同一局内同一个颜色和造型只能有一个尺寸档位和一个半径;当 `clearCount > 25` 轮转复用类型时,复用的同一 `visualKey` 继续沿用同一尺寸。
|
||||
9. 前端本地试玩、创作后试玩和后端权威运行态必须使用同一套五档体积分配口径。
|
||||
|
||||
## 13. 可点击物整体显示倍率
|
||||
|
||||
2026-05-04 追加一轮点击手感优化,解决当前玩家点击可消除物偏困难的问题。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 运行态表现层使用 `MATCH3D_RENDER_ITEM_SCALE = 2`,把后端快照中的 `item.radius` 统一乘 `2` 后再进入显示层坐标收束。
|
||||
2. 该倍率只影响前端 2D 回退图标、3D 场内模型、碰撞体、射线点击命中区域和托盘 3D 预览测量。
|
||||
3. 五档体积规则、每局类型数量、每种物品的唯一尺寸关系、后端权威快照和消除判定不做变化;所有物体之间的相对大小比例保持不变。
|
||||
4. 放大后的物体仍必须通过圆形场地显示层收束和 3D 锅内空气墙约束,不允许重新依赖 DOM 圆形裁切。
|
||||
5. 2026-05-04 追加修正:碰撞体必须和当前视觉模型使用同一套尺寸公式。长条、光板、斜坡、圆柱、圆环、拱门和锥形件不能再只按 `shape + radius` 粗略生成统一碰撞体;不得借此调整整体显示倍率、点击倍率、锅体尺寸或物理步进。
|
||||
|
||||
## 14. 两位数消除局的点击命中与旧模型复用修正
|
||||
|
||||
2026-05-05 针对 `clearCount >= 10` 时出现“点击到的 3D 模型和下方备选栏模型不一致”的问题,补充运行态复用口径。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 运行态 3D 棋盘的物理条目不能只按 `itemInstanceId` 复用,还必须结合 `runId`、`itemTypeId`、`visualKey`、`radius` 和 `layer` 生成当前渲染签名。
|
||||
2. 当同一个 `itemInstanceId` 出现在新的 run 快照里,但渲染签名已经变化时,旧 mesh 和 body 必须先销毁,再按当前快照重建。
|
||||
3. 这条修正只针对前端 3D 表现层,不改变后端权威快照、点击判定、备选栏规则和三消规则。
|
||||
4. 底部备选栏预览继续沿用按当前 run 快照重建的视觉键,不允许把上一局的旧 3D 资源误复用到新一局。
|
||||
|
||||
## 15. 备选栏 3D 模型可读性优化
|
||||
|
||||
2026-05-05 针对备选栏中的 3D 模型偏小、部分积木件难以辨认的问题,补充 UI 预览层展示口径。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 备选栏 3D 预览可以使用比场内更紧凑的显示尺度,让模型在单格 UI 中占用更大可读面积。
|
||||
2. 托盘相机和模型姿态只服务 UI 识别;当前采用偏强的俯视 `3/4` 立体角,并通过更明显的光照对比突出顶面与侧面差异,避免退化成纯平面旋转。
|
||||
3. 该调整不能改变中心场地 3D 模型的物理姿态、碰撞体、点击判定和后端权威快照。
|
||||
4. 托盘仍使用共享 `WebGLRenderer`,继续按当前 `visualKey` 和尺寸关系生成同款模型;不得新增每格独立 renderer。
|
||||
5. 托盘缩放不能继续只按本局最大模型统一压缩所有物体;小尺寸模型需要保留最低可读显示尺寸,但仍不能改动场内真实尺寸、碰撞尺寸和后端权威尺寸。
|
||||
6. 备选栏单格高度可大于宽度,优先保证局内 3D 预览的识别面积;不得为了适配旧正方形格子把模型再次压小。
|
||||
|
||||
## 16. 中心场地隐藏纵深与动态上顶
|
||||
|
||||
2026-05-05 针对中心场地高数量局面穿模严重、消除后中下层物体长期陷在深处的问题,追加隐藏纵深与动态上顶表现修正。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 该纵深只存在于 3D 物理表现层,不修改锅体图案、锅壁模型、托盘表现、后端快照、点击权威判定、消除和胜负规则。
|
||||
2. 物体生成高度不再使用固定极小层级步长,而是按本局总物体数计算一个隐藏初始纵深;物体总量越大,初始逻辑纵深越深,用来减少大量放大后模型被挤进同一高度区间导致的穿模。
|
||||
3. 当前剩余场内物体数会动态缩短可用纵深;随着玩家持续消除,下层物体的目标高度逐步上移,表现为中下层物体陆续向上顶到表面层。
|
||||
4. 动态上顶只通过向上托举力和目标高度调整完成,不增加中心引力,不修改水平约束半径,不改变碰撞体尺寸倍率。
|
||||
5. 表面层高度保持稳定,避免越消除越显得物体掉进深处或视觉尺寸异常变小。
|
||||
|
||||
## 17. 高数量局面物理稳定与动态锅容量
|
||||
|
||||
2026-05-06 继续按方案 C 和方案 D 优化 `clearCount=100` 等高数量局面的稳定性。
|
||||
|
||||
方案 C 编码口径:
|
||||
|
||||
1. 只调整 3D 表现层的物理稳定参数,包括求解迭代次数、接触摩擦、接触弹性、线性阻尼、角阻尼、睡眠阈值和速度上限。
|
||||
2. 物体数量越大,物理世界越偏向高摩擦、低弹性和更强阻尼,减少大量物体同时生成后的持续弹跳、穿插和边界挤压。
|
||||
3. 速度保护只限制极端水平速度和垂直速度,不改物体位置生成规则、点击判定、备选栏、消除和胜负规则。
|
||||
|
||||
方案 D 编码口径:
|
||||
|
||||
1. 隐藏锅容量的纵深按本局总物体数,也就是用户配置的消除次数乘 `3` 后动态计算;消除次数越大,锅内容量纵深越深。
|
||||
2. 动态纵深只影响 3D 物理层的生成高度、目标层高度和消除后的上顶回补;锅底、锅壁、锅沿和 DOM 场地外观不随纵深变化。
|
||||
3. 高数量局面需要降低单层容量,让更多物体分散到纵向层级中,避免 `300` 个物体被压进少量高度层。
|
||||
4. 随着消除进度推进,当前可用纵深继续按剩余物体数收缩,确保下层物体逐步向表面回补,保持中心场地表层稳定可见。
|
||||
5. 本节不改变中心引力默认值,不改水平活动半径,不改碰撞体与视觉模型的尺寸一致性规则。
|
||||
|
||||
## 18. 原型入场节奏与创建限流
|
||||
|
||||
2026-05-07 根据原型视频补充创建过程优化。原型不是在同一帧把全部物体摆进容器,而是先短暂空场,再用连续小批量把物体投放到容器中,批与批之间留出自然沉降时间,最后再进入可操作局面。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 该优化只作用于前端 3D 表现层的物理 body 创建节奏,不改变后端快照、消除目标数量、点击权威判定、备选栏、三消和胜负规则。
|
||||
2. `totalItemCount < 30` 时保留较快创建节奏;`30 <= totalItemCount <= 50` 进入中速波次投放,降低每波数量并增加波次沉降窗口,避免最后一层物体压进尚未稳定的表层堆叠。
|
||||
3. `totalItemCount > 50` 后进入更强限流投放,单帧创建数量下降,避免同一帧把过多碰撞体塞入物理世界。
|
||||
4. 随着总物体数增加,投放初始等待、层级间隔和同层错峰间隔都要逐步变长,模拟原型中“持续落入、短暂沉降、继续补入”的节奏。
|
||||
5. `clearCount=100` 对应 `300` 个物体时,投放节奏应接近连续数秒完成,而不是在一秒左右完成全量创建。
|
||||
6. 该节不允许通过缩小碰撞体、扩大锅半径、开启中心引力或修改模型尺寸来掩盖穿模;如果后续仍需调整,只继续围绕创建节拍和物理沉降窗口处理。
|
||||
|
||||
## 19. 生成高度避让已有堆叠
|
||||
|
||||
2026-05-07 继续按方案 2 优化 `30` 件左右局面最后一层或最后一波物体仍会穿进已有堆叠的问题。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 该优化只作用于前端 3D 表现层的新物体创建高度,不改变后端快照、物品数量、模型尺寸、碰撞体尺寸、锅半径、点击判定、备选栏、三消和胜负规则。
|
||||
2. 新物体进入物理世界前,先根据当前同一水平区域附近已有物体的碰撞体顶部高度,计算一个不低于原计划高度的生成高度。
|
||||
3. 只有水平外接半径发生重叠的已有物体会影响本次生成高度;远处物体不能把新物体整体抬高,避免破坏原有随机洒落和分层节奏。
|
||||
4. 该避让只解决“直接创建在已有模型内部”的初始穿插,后续沉降、翻滚、堆叠仍交给 cannon-es 物理模拟。
|
||||
5. 本节不允许额外引入中心引力、扩大锅容量或修改模型生成规则;若后续仍需优化,只继续围绕生成高度、入场节拍和沉降窗口做局部迭代。
|
||||
|
||||
## 20. 从小到大的生成动画
|
||||
|
||||
2026-05-08 追加生成动画优化,参考原型中物体逐个出现、从小到大补入容器的观感。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 该优化只作用于前端 3D 表现层的可见 mesh 缩放,不改变后端快照、碰撞体尺寸、物品数量、锅半径、点击判定、备选栏、三消和胜负规则。
|
||||
2. 物理 body 在创建时仍使用最终尺寸碰撞体,并立即加入 cannon-es 物理世界,确保生成动画过程中碰撞已经按完整体积稳定占位。
|
||||
3. 可见 mesh 初始以较小比例显示,再用缓动动画放大到完整尺寸;视觉缩放不得反向修改 body shape、质量、边界半径或生成高度避让计算。
|
||||
4. 入场动画继续服从第 18 节的创建限流和第 19 节的生成高度避让;不能为了动画效果把物体直接放进已有堆叠内部。
|
||||
5. 动画结束后 mesh 缩放必须回到 `1`,避免影响后续点击可读性和托盘对应关系。
|
||||
|
||||
## 21. 高 DPR 移动端 WebGL 画布尺寸锁定
|
||||
|
||||
2026-05-10 针对移动端试玩入口中 3D 锅体和物体从中心区域向右下溢出的问题,补充 WebGL canvas 布局口径。
|
||||
|
||||
编码口径:
|
||||
|
||||
1. 中心 3D 棋盘和托盘 3D 预览都允许通过 `renderer.setPixelRatio(...)` 提升绘制清晰度,但 `WebGLRenderer.domElement` 的 CSS 显示尺寸必须单独锁定为 `position: absolute; inset: 0; width: 100%; height: 100%; display: block`。
|
||||
2. `renderer.setSize(width, height, false)` 只负责绘图缓冲区与当前容器尺寸同步,不能依赖 canvas 默认属性尺寸承载 CSS 布局;否则高 DPR 设备会把绘图缓冲区尺寸当成页面尺寸显示,导致棋盘内容放大并从容器右下溢出。
|
||||
3. 移动端验收仍以 `390px` 竖屏为基准:顶部状态、圆形棋盘和 `7` 格备选栏都必须完整可见,锅体内容应落在屏幕中间的圆形区域内。
|
||||
4. 该修复只影响 WebGL 画布 CSS 布局,不改变相机、物理世界、碰撞体、点击命中、后端权威快照或托盘规则。
|
||||
|
||||
@@ -2,32 +2,39 @@
|
||||
|
||||
## 背景
|
||||
|
||||
创作中心顶部“新建作品”入口和平台创作类型弹层都依赖同一组玩法模板。此前入口开放状态、隐藏状态和中文文案集中写在 `src/components/platform-entry/platformEntryCreationTypes.ts` 与入口组件中,后续切换玩法开放节奏时容易出现多个入口不一致。
|
||||
创作中心的模板 Tab、平台创作类型弹层和旧“新建作品”卡片配置都依赖同一组玩法模板。此前入口开放状态、隐藏状态和中文文案集中写在前端配置与入口组件中,后续切换玩法开放节奏时容易出现多个入口不一致。当前入口配置事实源已经迁移到 SpacetimeDB,由 `api-server` 通过 `GET /api/creation-entry/config` 下发。
|
||||
|
||||
## 落地规则
|
||||
|
||||
1. 新建作品入口配置统一放在 `src/config/newWorkEntryConfig.ts`。
|
||||
2. `visible` 控制玩法是否展示在新建作品入口和创作类型弹层中。
|
||||
3. `open` 控制玩法是否允许点击创建;`open: false` 时入口保持展示但禁用。
|
||||
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` 控制玩法是否允许点击创建以及对应创作 / runtime API 路由是否放行;`open: false` 时入口保持展示但禁用,并由 `api-server` 熔断对应玩法 API。
|
||||
4. `title`、`subtitle`、`badge` 控制玩法卡片文案。
|
||||
5. `startCard` 控制创作中心顶部新建作品模块的标题、辅助文案和移动端角标文案。
|
||||
5. `startCard` 控制旧创作中心顶部新建作品模块的标题、辅助文案和移动端角标文案;当前创作 Tab 首屏标题固定在 `PlatformEntryFlowShellImpl.tsx`,不再由 `startCard` 控制。
|
||||
6. `typeModal` 控制平台创作类型弹层标题和描述。
|
||||
7. 入口排序仍遵循“可创建玩法在前,未开放玩法在后”;同组内部沿用配置顺序。
|
||||
8. `creative-agent` 可以继续保留运行链路,但默认 `visible: false`,不出现在创作 Tab 模板入口。
|
||||
9. 前端 `src/components/platform-entry/platformEntryCreationTypes.ts` 只做展示派生,不再承载默认入口配置。
|
||||
|
||||
## 当前状态
|
||||
|
||||
| 玩法 | 展示 | 开放 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 角色扮演 | 是 | 是 | 点击后进入 RPG Agent 共创工作台 |
|
||||
| 角色扮演 | 否 | 是 | 暂时从创作端入口下线,既有链路与作品能力保留 |
|
||||
| 大鱼吃小鱼 | 否 | 是 | 功能仍保留,不在新建作品入口展示 |
|
||||
| 拼图 | 是 | 是 | 点击后进入拼图 Agent 共创工作台 |
|
||||
| 抓大鹅 | 是 | 是 | 点击后进入抓大鹅 Agent 共创工作台 |
|
||||
| 拼图 | 是 | 是 | 创作 Tab 默认选中并内嵌展示拼图创作表单,提交后进入拼图草稿生成 |
|
||||
| 抓大鹅 | 是 | 是 | 点击后进入抓大鹅 Match3D Agent 共创工作台 |
|
||||
| 方洞挑战 | 否 | 是 | 创作页入口暂时完全隐藏,既有草稿、结果页、发布、试玩、作品架与广场链路保留 |
|
||||
| AIRP | 是 | 否 | 保留入口,显示敬请期待 |
|
||||
| 视觉小说 | 是 | 否 | 保留入口,显示敬请期待 |
|
||||
| 视觉小说 | 是 | 否 | 保留入口,显示敬请期待,暂不允许创建视觉小说草稿 |
|
||||
| 智能创作 | 否 | 是 | 入口隐藏,既有 `creative-agent` 链路保留 |
|
||||
|
||||
## 验收
|
||||
|
||||
1. 修改 `src/config/newWorkEntryConfig.ts` 后,创作中心顶部卡带和平台创作类型弹层应同步变化。
|
||||
1. 修改 SpacetimeDB 入口配置后,创作 Tab 模板入口、创作中心顶部卡带和平台创作类型弹层应同步变化。
|
||||
2. 隐藏玩法不触发入口预加载,也不出现在新建作品入口中。
|
||||
3. 未开放玩法点击态保持禁用,不应进入鉴权或创建会话链路。
|
||||
4. 已开放玩法点击后必须进入对应创建链路;若用户未登录,先走登录保护。
|
||||
5. 创作 Tab 首屏应显示“10分钟创作一个精品互动玩法”,并默认展示拼图创作表单。
|
||||
6. 智能创作入口隐藏后,不应出现“Hi, 朋友”“问一问百梦”或“一句话生成闪应用”等旧首页入口。
|
||||
7. 方洞挑战入口隐藏后,不应出现在创作 Tab 模板入口、创作中心顶部卡带、平台创作类型弹层和创作页作品架中;既有 `SH-` 作品号、广场详情和试玩 runtime 链路不因此删除。
|
||||
|
||||
@@ -76,3 +76,16 @@
|
||||
3. 只有用户显式修改或重置密码后,才允许密码登录。
|
||||
|
||||
后续迁移到 SpacetimeDB 表时,保持同一语义:密码哈希字段允许为空,密码登录 reducer 不承担注册能力,验证码登录 reducer 承担“无账号则自动注册”的唯一注册入口。
|
||||
|
||||
## 5. 2026-05-12 快照同步修复
|
||||
|
||||
重置密码和修改密码都会改变认证真相:`password_hash`、`password_login_enabled`、`token_version`,重置密码还会立即创建新的 refresh session。因此 API 层在 `POST /api/auth/password/change` 与 `POST /api/auth/password/reset` 成功后,必须和密码登录、手机号登录、刷新、退出一样调用 `sync_auth_store_snapshot_to_spacetime()`。
|
||||
|
||||
若只更新本地 `InMemoryAuthStore` 而不同步 SpacetimeDB 认证快照,`api-server` 重启时可能从旧的 SpacetimeDB 表或旧快照恢复账号状态,表现为用户已通过忘记密码重设成功,但再次密码登录仍返回“手机号或密码错误”。启动恢复时应从 SpacetimeDB 表、SpacetimeDB 快照记录和本地 `GENARRATIVE_AUTH_STORE_PATH` 文件中选择可判断的最新快照;当本地文件更新且远端表无更新时间戳时,优先使用本地文件并尝试回写 SpacetimeDB,避免旧远端状态覆盖刚重设的密码。
|
||||
|
||||
验证命令:
|
||||
|
||||
```bash
|
||||
cargo test -p module-auth password --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p api-server password --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
@@ -104,9 +104,11 @@
|
||||
|
||||
1. 发送验证码调用 `SendSmsVerifyCode`。
|
||||
2. 校验验证码调用 `CheckSmsVerifyCode`。
|
||||
3. 使用阿里云 RPC 签名口径:
|
||||
- `SignatureMethod=HMAC-SHA1`
|
||||
- `SignatureVersion=1.0`
|
||||
3. 使用阿里云 OpenAPI V3 请求头签名口径:
|
||||
- `Authorization: ACS3-HMAC-SHA256 ...`
|
||||
- `x-acs-action`
|
||||
- `x-acs-version`
|
||||
- `x-acs-content-sha256`
|
||||
4. 当前仍只支持中国大陆手机号。
|
||||
|
||||
## 7. 状态与快照
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# 手机验证码短信 Provider 错误 HTTP 映射修复
|
||||
|
||||
日期:`2026-05-08`
|
||||
|
||||
## 背景
|
||||
|
||||
本地登录弹窗点击手机号验证码登录时,浏览器报:
|
||||
|
||||
```text
|
||||
POST /api/auth/phone/login 500
|
||||
```
|
||||
|
||||
排查发现当前 `.env.local` 使用:
|
||||
|
||||
```text
|
||||
SMS_AUTH_PROVIDER=aliyun
|
||||
```
|
||||
|
||||
因此 `send-code` 会走真实阿里云短信 provider。真实 provider 返回 `UNKNOWN` 或 `biz.FREQUENCY / check frequency failed` 时,`module-auth` 曾把 provider 失败统一折叠成 `PhoneAuthError::Store`,`api-server` 再映射为 `500 Internal Server Error`,前端只能看到登录失败。
|
||||
|
||||
## 根因
|
||||
|
||||
短信 provider 失败不是认证仓储内部错误:
|
||||
|
||||
1. 阿里云配置缺失或配置非法属于服务配置问题。
|
||||
2. 阿里云返回频控、网关失败或业务失败属于上游短信 provider 问题。
|
||||
3. 这些错误不应被映射成 `Store`,否则 HTTP 层无法区分真实内部错误与外部 provider 失败。
|
||||
|
||||
## 修复
|
||||
|
||||
`module-auth` 新增短信 provider 错误分类:
|
||||
|
||||
1. `PhoneAuthError::SmsProviderInvalidConfig`
|
||||
2. `PhoneAuthError::SmsProviderUpstream`
|
||||
|
||||
`api-server` 映射规则调整为:
|
||||
|
||||
1. provider 配置错误返回 `503 Service Unavailable`
|
||||
2. provider 上游失败返回 `502 Bad Gateway`
|
||||
3. 验证码不存在、错误、过期仍返回 `400`
|
||||
4. 本地仓储或签发错误仍返回 `500`
|
||||
|
||||
## 本地排查
|
||||
|
||||
如果本地只想验证登录 UI 和账号链路,可以临时用 shell 环境覆盖真实短信 provider:
|
||||
|
||||
```powershell
|
||||
$env:SMS_AUTH_PROVIDER="mock"
|
||||
npm run api-server
|
||||
```
|
||||
|
||||
若要验证真实短信链路,保持 `SMS_AUTH_PROVIDER=aliyun`,并查看 `api-server` 日志中的:
|
||||
|
||||
1. `阿里云短信发送接口返回响应`
|
||||
2. `阿里云短信发送接口返回业务失败`
|
||||
3. `手机号验证码发送失败`
|
||||
|
||||
看到 `biz.FREQUENCY` / `check frequency failed` 时,说明请求已到达短信 provider,但被 provider 频控或业务规则拒绝。
|
||||
|
||||
## 验收
|
||||
|
||||
1. `cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`
|
||||
2. `cargo test -p api-server send_phone --manifest-path server-rs/Cargo.toml`
|
||||
3. `cargo test -p api-server phone_login_creates_user_and_sets_refresh_cookie --manifest-path server-rs/Cargo.toml`
|
||||
4. `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||
5. `npm run check:encoding`
|
||||
@@ -17,6 +17,7 @@
|
||||
- `scripts/deploy/maintenance-status.sh`
|
||||
- `scripts/build-production-release.sh`
|
||||
- `scripts/jenkins-checkout-source.sh`
|
||||
- `scripts/jenkins-server-provision.sh`
|
||||
- `scripts/deploy/production-web-deploy.sh`
|
||||
- `scripts/deploy/production-api-deploy.sh`
|
||||
- `scripts/deploy/production-stdb-publish.sh`
|
||||
@@ -93,6 +94,21 @@
|
||||
|
||||
`api-server` 不放入 Docker,也不直接暴露公网端口。发布时替换版本目录并重启 `genarrative-api.service`。
|
||||
|
||||
全量发布流水线的 `DATABASE` 参数必须同时传给 Stdb 发布和 API 发布:Stdb 发布负责把 wasm 发布到目标数据库,API 发布必须在重启 `genarrative-api.service` 前把同一个库名写入 `/etc/genarrative/api-server.env` 的 `GENARRATIVE_SPACETIME_DATABASE`,并同步 `GENARRATIVE_SPACETIME_SERVER_URL`。否则 api-server 会继续读取环境文件中的旧库名,出现 wasm 已发布到新库但 HTTP facade 仍访问旧库的错位。
|
||||
|
||||
API 发布阶段只使用上游 API 构建产物,不应回退到上游源码 commit 执行部署脚本;部署脚本应始终取 `SOURCE_BRANCH` 最新提交。否则全量流水线在修复部署脚本后仍可能按旧 `COMMIT_HASH` checkout,继续执行不认识新参数的旧版 `production-api-deploy.sh`。
|
||||
|
||||
### 服务器配置流水线
|
||||
|
||||
`Genarrative-Server-Provision` 的 Jenkinsfile 只负责参数、节点路由与调用脚本;服务器配置主体逻辑放在 `scripts/jenkins-server-provision.sh`。不要再把数百行 Bash 内联进 Jenkins `sh ''' ... '''` 或 `bash -lc '...'`,否则 Jenkins/Groovy/sh/bash 多层转义会把 `\"`、`${...}`、sed 表达式等内容二次改写,容易在运行时出现 `syntax error near unexpected token '}'` 这类难定位错误。
|
||||
|
||||
该脚本负责安装构建依赖、同步 SpacetimeDB current 目录、安装 systemd/Nginx 配置、创建或保留 `/etc/genarrative/api-server.env`、维护模式配置以及首次服务启动前的 SpacetimeDB client token 初始化。修改后应至少执行:
|
||||
|
||||
```bash
|
||||
bash -n scripts/jenkins-server-provision.sh
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## Nginx 规则
|
||||
|
||||
生产正式入口只保留必要路由:
|
||||
@@ -132,6 +148,7 @@ Nginx 配置文件分为两类:
|
||||
- `api-server` 发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更必须进入维护模式。
|
||||
- 普通页面在维护模式下展示 `/maintenance.html`。
|
||||
- `/admin/api/*` 在维护模式下返回 503。
|
||||
- `/v1/database/<database>/subscribe` 与 `/v1/identity` 在维护模式下返回 503,阻断已打开前端继续通过 SpacetimeDB SDK 访问运行时数据。
|
||||
- 静态资源仍允许访问,避免维护页样式和资源加载失败。
|
||||
- 发布成功后自动解除维护模式。
|
||||
- 发布失败时保持维护模式,并通过邮件通知人工处理。
|
||||
@@ -195,7 +212,8 @@ Jenkins 可运行在 Windows 或其他机器上,本机 Windows 只作为人工
|
||||
|
||||
- Jenkins Job 参数不暴露真实节点名、IP 或带 IP 的标签。
|
||||
- 构建 Job 固定使用 label expression:`linux && genarrative-build`。
|
||||
- 当前开发/构建/开发部署 agent 必须同时配置 `linux` 与 `genarrative-build` 两个标签;非 Linux 节点不能承担构建或部署。
|
||||
- 当前开发/构建/开发部署 agent 使用脱敏节点名 `genarrative-build-01`,必须同时配置 `linux` 与 `genarrative-build` 两个标签;非 Linux 节点不能承担构建或部署。
|
||||
- 构建机 agent 启动方式统一改为 inbound agent + systemd 自守护,不再依赖 Jenkins controller 通过 SSH launcher 长期拉起。SSH 只作为首次登录和安装 systemd 服务的运维通道。
|
||||
- 用途:拉代码、安装依赖、构建主站、构建后台、构建 `api-server`、构建 SpacetimeDB wasm、归档产物,并执行 `DEPLOY_TARGET=development` 的开发环境部署。
|
||||
|
||||
### 生产/发布实例
|
||||
@@ -209,25 +227,34 @@ Jenkins 可运行在 Windows 或其他机器上,本机 Windows 只作为人工
|
||||
|
||||
### Jenkins inbound agent 自恢复
|
||||
|
||||
发布 agent 必须由目标 Linux 机器主动连接 Jenkins controller,并由 systemd 托管:
|
||||
构建 agent 与发布 agent 都必须由目标 Linux 机器主动连接 Jenkins controller,并由 systemd 托管:
|
||||
|
||||
- Jenkins 节点 Launch method 使用 inbound agent,优先启用 WebSocket。这样目标机只需要能访问 Jenkins Web 地址,不依赖 controller 每次 SSH 拉起 agent。
|
||||
- 目标机安装 `deploy/systemd/jenkins-agent@.service`、`scripts/deploy/jenkins-inbound-agent-start.sh` 与 `scripts/deploy/install-jenkins-inbound-agent.sh`。
|
||||
- systemd 服务名采用 `jenkins-agent@<node-name>.service`,例如 `jenkins-agent@genarrative-release-deploy-01.service`。
|
||||
- systemd 自身 `WorkingDirectory` 保持 `/var/lib/jenkins/agent/<node-name>`;Jenkins remoting `-workDir` 可继续使用旧 SSH agent 的 `/root/jenkins-agent`,避免迁移时 workspace 和缓存路径漂移。
|
||||
- systemd 服务名采用 `jenkins-agent@<node-name>.service`,例如 `jenkins-agent@genarrative-build-01.service`、`jenkins-agent@genarrative-release-deploy-01.service`。
|
||||
- systemd 自身 `WorkingDirectory` 保持 `/var/lib/jenkins/agent/<node-name>`;Jenkins remoting `-workDir` 可按节点拆分,例如构建机使用 `/root/jenkins-agent-build`、发布机继续使用旧 SSH agent 的 `/root/jenkins-agent`,避免多 agent 共用 remoting 根目录,同时减少发布机迁移时 workspace 和缓存路径漂移。
|
||||
- inbound secret 只能放在目标机 `/etc/jenkins-agent/<node-name>.secret` 或等价 Secret Text 注入位置,不能提交到 Git,也不能写入 Jenkinsfile 默认参数。
|
||||
- systemd unit 使用 `Restart=always` 和 `RestartSec=10`;agent Java 进程退出、网络短断或机器重启后由 systemd 自动恢复,不需要人工盯着 Jenkins 页面手动重启。
|
||||
- 当前 `Genarrative-Server-Provision` 仍负责 systemd、Nginx、`/opt/genarrative`、`/etc/genarrative` 等特权写入,因此 inbound agent 默认仍按现有 root 执行口径迁移。若后续改为 `jenkins` 用户运行 agent,必须先把生产流水线需要的特权命令收敛为精确 `NOPASSWD` sudoers 白名单。
|
||||
|
||||
如果 Jenkins controller 只运行在本地 Windows,不直接对目标机暴露公网地址,需要在本地控制机启动 `scripts/deploy/jenkins-agent-reverse-tunnel.ps1`。该脚本通过同一条 SSH 会话把远端 `127.0.0.1:18080` 转到本地 Jenkins Web `127.0.0.1:8080`,把远端 `127.0.0.1:50000` 转到本地 Jenkins inbound TCP agent port `127.0.0.1:50000`,并在隧道断开后自动重试。此时远端 agent 的 `JENKINS_URL` 固定写 `http://127.0.0.1:18080/`,不写本地 Windows 的 `127.0.0.1:8080`。
|
||||
|
||||
本地反向隧道脚本不内置目标机地址;注册 Windows 计划任务时必须显式传入 `-RemoteHost <release-agent-host>`,真实 IP 或主机名只保存在本地计划任务配置中,不提交到 Git。
|
||||
本地反向隧道脚本不内置目标机地址;注册 Windows 计划任务时必须显式传入 `-RemoteHost <agent-host>`,真实 IP 或主机名只保存在本地计划任务配置中,不提交到 Git。同一台 Linux 机器上同时运行构建与发布 agent 时,两者共用这一条反向隧道,不为每个 Jenkins 节点重复注册本地隧道任务。
|
||||
|
||||
当 Jenkins controller 以本地 Windows `java -jar jenkins.war` 方式运行时,使用 `scripts/deploy/jenkins-local-controller-watchdog.ps1` 作为本地守护脚本。该脚本只保存本机 Java、`jenkins.war`、`JENKINS_HOME` 和端口路径,不保存 Jenkins 账号、密码、token 或 agent secret;注册 Windows 计划任务后,脚本会在登录后检查 `8080` 是否已有 Jenkins 监听,若已有则监控现有 PID,若进程退出或端口空闲则重新启动 Jenkins,并固定 `--agentPort=50000` 供远端 inbound agent 连接。
|
||||
|
||||
首次迁移示例:
|
||||
|
||||
```bash
|
||||
sudo install -m 0600 /tmp/genarrative-build-01.secret /etc/jenkins-agent/genarrative-build-01.secret
|
||||
sudo scripts/deploy/install-jenkins-inbound-agent.sh \
|
||||
--agent-name genarrative-build-01 \
|
||||
--jenkins-url http://127.0.0.1:18080/ \
|
||||
--secret-file /etc/jenkins-agent/genarrative-build-01.secret \
|
||||
--workdir /root/jenkins-agent-build \
|
||||
--java-bin /usr/bin/java
|
||||
sudo systemctl status jenkins-agent@genarrative-build-01.service --no-pager -l
|
||||
|
||||
sudo install -m 0600 /tmp/genarrative-release-deploy-01.secret /etc/jenkins-agent/genarrative-release-deploy-01.secret
|
||||
sudo scripts/deploy/install-jenkins-inbound-agent.sh \
|
||||
--agent-name genarrative-release-deploy-01 \
|
||||
@@ -236,7 +263,7 @@ sudo scripts/deploy/install-jenkins-inbound-agent.sh \
|
||||
--workdir /root/jenkins-agent \
|
||||
--java-bin /usr/bin/java
|
||||
sudo systemctl status jenkins-agent@genarrative-release-deploy-01.service --no-pager -l
|
||||
journalctl -u jenkins-agent@genarrative-release-deploy-01.service -f
|
||||
journalctl -u 'jenkins-agent@*.service' -f
|
||||
```
|
||||
|
||||
如果 Jenkins controller 暂时仍配置为 SSH launcher,只能作为过渡方案使用:需要把 SSH launch timeout 拉长、增加 retry 和 retry wait、固定 Java 路径,并确认 `ssh user@host 'java -version'` 稳定返回。最终仍要切到 inbound + systemd,避免 SSH 连接卡住时阻塞发布队列。
|
||||
@@ -381,7 +408,7 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
||||
并发与清理规则:
|
||||
|
||||
- 同一个 Rust 构建 Job 建议使用 `disableConcurrentBuilds()`,避免同一组件的多个 release 构建同时写入同一最终产物路径。
|
||||
- 如果 Linux agent 未安装 `sccache`,应先运行 `Genarrative-Server-Provision` 补齐缓存工具;Rust 构建流水线仍必须自动取消 `RUSTC_WRAPPER`,回退到直接使用 `rustc`,不能因为缺少可选缓存工具阻断真实构建。
|
||||
- 如果 Linux/Windows agent 未安装 `sccache`,或 `sccache --version` 无法实际执行,应先补齐缓存工具;Rust 构建流水线仍必须自动取消 `RUSTC_WRAPPER`,回退到直接使用 `rustc`,不能因为缺少可选缓存工具阻断真实构建。
|
||||
- 生产发布流水线只能消费 `build/<version>/` 或 Jenkins 归档产物,不允许从共享 `cargo-target` 目录直接发布。
|
||||
- `SCCACHE_CACHE_SIZE` 必须设置上限,避免编译缓存无限增长。
|
||||
- 对 `/var/cache/genarrative-build/*/cargo-target` 或数据盘对应目录配置定期清理,建议保留最近 14 到 30 天。
|
||||
@@ -403,6 +430,7 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
||||
- `COMMIT_HASH` 非空时,先解析到完整 commit,再用 `git merge-base --is-ancestor <commit> refs/remotes/origin/<SOURCE_BRANCH>` 校验该提交属于指定分支,校验通过后 detached checkout。
|
||||
- 流水线日志必须输出最终 `SOURCE_BRANCH` 与实际 `SOURCE_COMMIT`。
|
||||
- 构建产物必须写入 `release-manifest.json`,至少包含 `version`、`source_branch`、`source_commit`、`built_at` 和组件类型,供发布、回滚和审计使用。
|
||||
- Windows 构建 Job 写入 `.jenkins-source-commit` 时必须使用 UTF-8 无 BOM;部署脚本在校验 `COMMIT_HASH` 前也会剥离 UTF-8 BOM 和 CRLF,避免上游 PowerShell 5.1 `Set-Content -Encoding UTF8` 产生的不可见 BOM 让下游发布误判 commit hash 非法。
|
||||
|
||||
构建流水线使用上述参数决定实际构建源码。发布流水线也暴露同名参数,但只用于选择本次发布使用的部署脚本、配置模板和 smoke test 逻辑;被发布的应用文件仍必须来自 Jenkins 归档产物或指定 release 包,不允许在发布流水线中重新构建。
|
||||
|
||||
@@ -482,8 +510,10 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
||||
构建:
|
||||
|
||||
- 先按 `SOURCE_BRANCH` / `COMMIT_HASH` 解析并 checkout 目标源码,默认构建 `origin/master` 最新 commit。
|
||||
- 使用 `spacetime build` 构建 `spacetime_module.wasm`。
|
||||
- 归档 wasm、发布脚本和 `release-manifest.json`。
|
||||
- 构建 `spacetime_module.wasm` 前默认生成 32 字节随机 hex 迁移引导密钥,注入 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET`,并把同一份密钥写入 `build/<version>/migration-bootstrap-secret.txt`。构建日志只输出密钥来源和长度,不打印明文。
|
||||
- `Genarrative-Stdb-Module-Build` 提供 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 参数:留空时自动生成新密钥;填写 Jenkins Secret Text 凭据 ID 时,构建环境注入 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET` 并复用该值。仅在明确传 `--no-migration-bootstrap-secret` 时才构建不带引导密钥的 wasm。
|
||||
- 使用 Rust wasm target 构建 `spacetime_module.wasm`。
|
||||
- 归档 wasm、`migration-bootstrap-secret.txt` 和 `release-manifest.json`。`migration-bootstrap-secret.txt` 属于敏感产物,只用于创建首个迁移操作员或录入数据库导入/导出流水线的 `BOOTSTRAP_SECRET_CREDENTIAL_ID` 指向的 Jenkins Secret Text;授权完成后不要把明文留在公开归档或聊天记录中。
|
||||
|
||||
发布:
|
||||
|
||||
@@ -493,6 +523,7 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
||||
- 在生产实例本机执行 `spacetime --root-dir=/stdb publish <database-name> --server http://127.0.0.1:3101 --bin-path spacetime_module.wasm --yes --no-config`。
|
||||
- 发布动作默认以 `spacetimedb` 服务用户执行,避免 root 默认 CLI 身份对自托管数据库验签失败,也避免 root 写入 `/stdb/config` 造成后续服务用户启动权限错误。
|
||||
- `Stdb publish` 固定追加 `--no-config`,只依赖显式传入的 `--root-dir`、`--server`、`--bin-path` 与数据库名,避免 agent 工作区、本机用户目录或仓库内 `spacetime` 配置干扰发布目标。
|
||||
- 首次迁移操作员授权时,使用本次 Stdb module 构建归档的 `migration-bootstrap-secret.txt` 创建 Jenkins Secret Text,然后在 `Genarrative-Database-Export` / `Genarrative-Database-Import` 的 `BOOTSTRAP_SECRET_CREDENTIAL_ID` 中填写该凭据 ID。后续已有迁移操作员时优先改用 `TOKEN_CREDENTIAL_ID`。
|
||||
- 成功后执行必要 smoke test。
|
||||
- 成功后解除维护模式。
|
||||
- 失败时保留维护模式并发邮件。
|
||||
@@ -520,6 +551,7 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module
|
||||
- 从目标机器本机 SpacetimeDB 导出指定数据库数据,默认连接 `SPACETIME_SERVER_URL=http://127.0.0.1:3101`,自托管 `root-dir` 默认 `/stdb`。
|
||||
- 产物归档到 Jenkins,并可额外保存到 `SERVER_BACKUP_DIRECTORY`。
|
||||
- 敏感 token 与 bootstrap secret 只通过 Jenkins Secret Text 凭据 ID 注入,不作为明文 Job 参数。
|
||||
- 导出和导入流水线的 Bash 执行块启用 `set -u`;所有可选 Jenkins 参数必须先通过 `${VAR:-}` 收敛成本地默认值,再传给 Node 迁移脚本,避免空参数没有导出时触发 `unbound variable`。
|
||||
- 成功后解除维护模式。
|
||||
- 失败时保留维护模式并邮件通知。
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# 个人反馈后端接入方案
|
||||
|
||||
更新时间:`2026-05-08`
|
||||
|
||||
## 目标
|
||||
|
||||
`/profile/feedback` 不再停留在前端成功态,提交时必须经过 `api-server` 鉴权、`spacetime-client` facade、`spacetime-module` procedure,并持久化到 SpacetimeDB。
|
||||
|
||||
## 接口
|
||||
|
||||
- 路由:`POST /api/profile/feedback`
|
||||
- 鉴权:必须登录,用户 ID 取 `AuthenticatedAccessToken`,前端不得上传或伪造 `userId`。
|
||||
- 请求体:
|
||||
- `description`:必填,trim 后 10 至 200 字符。
|
||||
- `contactPhone`:选填,trim 后最多 40 字符。
|
||||
- `evidenceItems`:选填,最多 4 张图片。
|
||||
- 每张凭证包含 `fileName`、`contentType`、`sizeBytes`、`dataUrl`。
|
||||
- 响应体:
|
||||
- `feedback.feedbackId`
|
||||
- `feedback.status`
|
||||
- `feedback.createdAt`
|
||||
- `feedback.evidenceItems` 只回传凭证元数据,不回显 Data URL。
|
||||
|
||||
## 表结构
|
||||
|
||||
新增私有表 `profile_feedback_submission`:
|
||||
|
||||
- `feedback_id PK: String`
|
||||
- `user_id: String`
|
||||
- `description: String`
|
||||
- `contact_phone: Option<String>`
|
||||
- `evidence_json: String`
|
||||
- `status: RuntimeProfileFeedbackStatus`
|
||||
- `created_at: Timestamp`
|
||||
- `updated_at: Timestamp`
|
||||
- 索引:`user_id`、`(user_id, created_at)`
|
||||
|
||||
`evidence_json` 保存首版图片凭证快照,后续如果迁移 OSS,应保持 HTTP 契约不变,仅替换内部 evidence 存储字段。
|
||||
|
||||
## 分层落点
|
||||
|
||||
- `shared-contracts`:冻结 HTTP DTO。
|
||||
- `module-runtime`:负责输入归一化、长度限制、图片数量/大小/Data URL 前缀校验、反馈 ID 和 evidence ID 生成、响应记录组装。
|
||||
- `spacetime-module`:新增 table 与 `submit_profile_feedback_and_return` procedure;只做事务写入和表到快照映射。
|
||||
- `spacetime-client`:新增 `submit_profile_feedback` facade,不让 `api-server` 直接依赖生成绑定。
|
||||
- `api-server`:新增鉴权 POST route,并对该 route 单独放宽 JSON body 上限。
|
||||
- 前端:`PlatformFeedbackView` 只负责临时表单状态、图片预览和调用 profile client;正式提交结果以后端返回为准。
|
||||
- 绑定生成:Windows 本地如遇 `sccache` 远端缓存被网络沙箱拦截,可临时使用仓库内短路径 `GENARRATIVE_BINDGEN_TEMP_ROOT` 并设置 `CARGO_BUILD_RUSTC_WRAPPER` 到本地 rustc passthrough wrapper 后重跑生成,不修改 `server-rs/.cargo/config.toml`。
|
||||
|
||||
## 验收
|
||||
|
||||
- 图片选择后能在页面看到缩略图。
|
||||
- 有效表单调用 `POST /api/profile/feedback` 并写入 `profile_feedback_submission`。
|
||||
- 未登录提交返回 `401`。
|
||||
- 超过图片数量、单张大小、总大小或非法 Data URL 时返回清晰校验错误。
|
||||
- `migration.rs`、SpacetimeDB 表目录、生成绑定同步更新。
|
||||
- 定向前端测试、Rust 领域测试和 API 认证测试通过。
|
||||
@@ -0,0 +1,83 @@
|
||||
# 个人任务与埋点系统技术方案
|
||||
|
||||
更新时间:`2026-05-03`
|
||||
|
||||
## 1. 目标
|
||||
|
||||
本轮新增一套可配置的个人任务系统,并补齐任务依赖的埋点统计能力。首个任务为“每日登录”,奖励 `10` 光点,入口放在“我的”页签;后台可修改任务配置。
|
||||
|
||||
## 2. 核心边界
|
||||
|
||||
- 埋点原始事实写入 `tracking_event`,这是实际存在的 SpacetimeDB 表。
|
||||
- 聚合投影写入 `tracking_daily_stat`,这也是后端维护的真实表,不是 view。
|
||||
- 任务配置写入 `profile_task_config`,默认配置包含 `daily_login`,后台修改后不得被默认初始化覆盖。
|
||||
- 任务进度写入 `profile_task_progress`,用于任务中心快速读取状态。
|
||||
- 领奖记录写入 `profile_task_reward_claim`,与钱包流水 `profile_wallet_ledger` 同事务写入。
|
||||
- “星光”奖励复用现有“光点”钱包,不新增第二种货币。
|
||||
|
||||
## 3. 埋点分层
|
||||
|
||||
| 层级 | scope_kind | scope_id 口径 |
|
||||
| --- | --- | --- |
|
||||
| 整站 | `site` | 固定为 `site` 或站点分区 key |
|
||||
| 作品 | `work` | 作品 profile_id / work_id |
|
||||
| 模块 | `module` | 模块 key,例如 `profile`、`puzzle` |
|
||||
| 用户 | `user` | 用户 id |
|
||||
|
||||
每条埋点可同时记录 `user_id`、`owner_user_id`、`profile_id`、`module_key` 与 `metadata_json`。任务首版只依赖用户层 `daily_login`,表结构先保留四层统计能力。
|
||||
|
||||
## 4. 日期桶
|
||||
|
||||
任务统计使用北京时间自然日:`day_key = floor((occurred_at_micros + 8h) / 1d)`。
|
||||
|
||||
这样存储仍是 UTC 时间戳,日切规则固定为业务口径,不依赖服务器本地时区。`tracking_event.occurred_at` 保存精确发生时间,`tracking_daily_stat.day_key` 只承担聚合桶职责。
|
||||
|
||||
## 5. 首版任务
|
||||
|
||||
| 字段 | 默认值 |
|
||||
| --- | --- |
|
||||
| task_id | `daily_login` |
|
||||
| title | `每日登录` |
|
||||
| event_key | `daily_login` |
|
||||
| cycle | `daily` |
|
||||
| threshold | `1` |
|
||||
| reward_points | `10` |
|
||||
| enabled | `true` |
|
||||
|
||||
用户成功登录时,认证链路会通过统一后端埋点 helper 幂等记录当日 `daily_login` 并刷新任务进度;用户打开任务中心只记录 `task_center_view` 浏览事件,不再承担每日登录事实写入。用户点击领取时,后端校验当日进度、领奖记录和配置状态,然后同事务写入领奖记录与钱包流水。
|
||||
|
||||
后台任务配置页的 `Event Key` 使用可搜索下拉控件,选项来自前端后台的埋点定义注册表。后台全量埋点筛选候选应对齐 `BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md` 的事件清单;任务配置页只展示标记为个人任务可用的事件,当前仅开放 `daily_login`,展示中文名称和备注。后续新增任务依赖的埋点时,应先补充注册表并显式标记任务可用,再开放运营配置。
|
||||
|
||||
## 6. 接口
|
||||
|
||||
### 用户侧
|
||||
|
||||
- `GET /api/profile/tasks`:读取任务中心,并记录 `task_center_view` 浏览事件;不在此入口写入 `daily_login`。
|
||||
- `POST /api/profile/tasks/{task_id}/claim`:领取任务奖励,并记录 `task_reward_claim` 成功事件。
|
||||
|
||||
### 后台侧
|
||||
|
||||
- `GET /admin/api/profile/tasks`:读取任务配置列表。
|
||||
- `POST /admin/api/profile/tasks`:新增或更新任务配置。
|
||||
- `POST /admin/api/profile/tasks/disable`:停用任务配置。
|
||||
|
||||
后台任务配置页进入时从 `profile_task_config` 对应的列表接口读取已有配置,点击列表项回填表单后仍通过同一个 upsert 接口修改原配置。最近一次保存结果可以保留为会话态提示,但不得作为任务配置列表的唯一来源。
|
||||
|
||||
## 7. 查询文档边界
|
||||
|
||||
- `docs/tracking/` 只存放具体埋点与埋点聚合查询,例如 `tracking_event`、`tracking_daily_stat` 的站点/作品/模块/用户查询。
|
||||
- `docs/operations/` 存放运营核查查询,例如任务进度、领奖记录、钱包流水对账。
|
||||
|
||||
不要把任务进度、领奖记录或钱包对账查询塞进 `docs/tracking/`,它们不是埋点系统本身。
|
||||
|
||||
## 8. 通用后端埋点覆盖
|
||||
|
||||
后端用户行为埋点统一按 `docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md` 执行。该文档维护通用 procedure、api-server 中间件、事件清单、排除范围与查询验收口径;每日登录也走该统一路径,仅保留认证 helper 作为业务语义入口。
|
||||
|
||||
## 9. 验收
|
||||
|
||||
1. `profile_task_config` 默认存在 `daily_login`,后台可修改奖励、阈值、标题和启用状态。
|
||||
2. “我的”页可以打开每日任务面板,登录后任务可领取 `10` 光点。
|
||||
3. 登录成功会幂等记录 `daily_login`;重复打开任务中心只记录 `task_center_view`,不会重复增加领取资格。
|
||||
4. 重复领奖不会重复发放。
|
||||
5. 表目录、迁移白名单、Rust/TypeScript 契约和前端入口同步更新。
|
||||
@@ -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`
|
||||
@@ -1,15 +1,16 @@
|
||||
# 拼图 APIMart 图片模型路由接入 2026-05-01
|
||||
# 拼图图片模型路由接入 2026-05-01
|
||||
|
||||
> 2026-05-09 更新:GPT-image-2 图片生成已从 APIMart 迁移到 VectorEngine。本文保留前端模型选择和拼图扣费/持久化历史设计,图片上游接口、环境变量和请求体以 `VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md` 为准。
|
||||
|
||||
## 背景
|
||||
|
||||
拼图创作已收口为填表式流程,首图生成和结果页关卡重新生成都由 `server-rs/crates/api-server/src/puzzle.rs` 执行外部图片 I/O,再把正式图写入 OSS 与 SpacetimeDB。新的模型选择只影响图片生成上游,不改变 SpacetimeDB 表结构、拼图草稿结构或前端运行时规则。
|
||||
|
||||
本轮参考 APIMart 文档:
|
||||
历史版本曾参考 APIMart 文档;当前 GPT-image-2 图片生成参考 VectorEngine Apifox 文档:
|
||||
|
||||
1. `https://docs.apimart.ai/cn/api-reference/images/gpt-image-2/generation`
|
||||
2. `https://docs.apimart.ai/cn/api-reference/images/gemini-3.1-flash/generation`
|
||||
1. `https://vectorengine.apifox.cn/api-448710071`
|
||||
|
||||
两条文档均指向 OpenAI 兼容风格的图片生成入口:`POST https://api.apimart.ai/v1/images/generations`,头部使用 `Authorization: Bearer {APIMART_API_KEY}`。请求体至少包含 `model`、`prompt`、`n`、`size`。返回体按 OpenAI images 兼容格式优先读取 `data[].url`,若供应商返回异步任务结构,则继续按 `task_id` / `tasks/{task_id}` 轮询并提取图片 URL。
|
||||
当前图片生成入口:`POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations`,头部使用 `Authorization: Bearer {VECTOR_ENGINE_API_KEY}`。请求体至少包含 `model = gpt-image-2-all`、`prompt`、`n`、`size`,参考图使用 `image` 数组。返回体按同步 OpenAI images 结构读取 `data[].url` 或 `data[].b64_json`,不再轮询 APIMart `tasks/{task_id}`。
|
||||
|
||||
## 模型选项
|
||||
|
||||
@@ -17,8 +18,8 @@
|
||||
|
||||
| 前端显示 | 请求值 | 上游 |
|
||||
| --- | --- | --- |
|
||||
| `gpt-image-2` | `gpt-image-2` | APIMart `/v1/images/generations` |
|
||||
| `nanobanana2` | `gemini-3.1-flash-image-preview` | APIMart `/v1/images/generations` |
|
||||
| `gpt-image-2` | `gpt-image-2` | VectorEngine `/v1/images/generations`,上游模型 `gpt-image-2-all` |
|
||||
| `nanobanana2` | `gemini-3.1-flash-image-preview` | 历史兼容选项,后端回落到 VectorEngine `gpt-image-2-all` |
|
||||
|
||||
默认值为 `gpt-image-2`。前端只负责展示和传递所选模型,不能把模型路由逻辑、上游请求体拼装或 API Key 暴露到浏览器。历史草稿或旧请求中的空值、`original`、未知值统一按 `gpt-image-2` 处理,不再把拼图生图路由回 DashScope 原模型。
|
||||
|
||||
@@ -40,36 +41,44 @@
|
||||
2. `compile_puzzle_draft_with_initial_cover` 与 `generate_puzzle_image_candidates` 增加图片模型参数。
|
||||
3. `imageModel` 归一化规则:
|
||||
- 空值、`original` 或未知值统一回落为 `gpt-image-2`;
|
||||
- `gpt-image-2` 走 APIMart;
|
||||
- `gemini-3.1-flash-image-preview` 走 APIMart,前端显示名为 `nanobanana2`。
|
||||
4. APIMart 文生图和图生图共用 `POST /v1/images/generations`。有参考图时,后端将参考图 Data URL 作为 `image_urls` 数组传入;若上游不接受该字段,错误按上游失败返回,不在前端降级伪造结果。
|
||||
5. APIMart 尺寸使用文档要求的比例写法 `1:1`。`gemini-3.1-flash-image-preview` 额外带 `resolution = "1K"`,对齐约 1024px 的拼图正方形素材。
|
||||
6. APIMart 生成成功后仍下载远程图片,沿用现有 OSS 私有对象、`asset_object` 和 `asset_entity_binding` 写入流程。若图片已成功上传 OSS,但 Maincloud / SpacetimeDB 短暂返回 `503 Service Unavailable`,资产索引写入允许降级跳过,并返回本次生成图片;日志必须记录 `拼图图片资产索引写入因 SpacetimeDB 连接不可用而降级跳过`。
|
||||
7. `save_puzzle_generated_images` 写回草稿时若遇到 Maincloud 连接级 `503` 或断线,API 层基于本次生成结果合成 session 快照返回给前端,避免 APIMart 已成功出图却被后置持久化误报成服务不可用。余额不足、参数错误、上游生图失败仍按原错误返回,不做伪成功。
|
||||
8. 结果页 `generate_puzzle_images` 会携带当前作品信息和 `levelsJson`。当 Maincloud / SpacetimeDB 在读取 session 阶段就返回连接级 `503` 或断线时,后端必须先用这份结果页快照构造最小内存 session,再继续调用 APIMart;外部图片已经生成后仍按第 6、7 条处理持久化降级。余额不足、参数错误、缺少草稿快照、关卡不存在等业务错误不走此降级。
|
||||
9. APIMart 异步任务轮询按文档口径在提交后先等待 `10s`,再调用 `GET /v1/tasks/{task_id}`;图片地址提取同时支持 `url: "..."` 与 `url: ["..."]` 两种结构。
|
||||
10. APIMart 错误统一映射为 `502 UPSTREAM_ERROR`,`details.provider = "apimart"`,保留上游状态码、业务 message 和截断后的 raw excerpt。
|
||||
11. 拼图首图生成 `compile_puzzle_draft` 与关卡图片生成 `generate_puzzle_images` 每次预扣 `2` 光点;余额不足仍返回 `409 CONFLICT`,Maincloud 连接级 503 仍按既有降级策略处理。
|
||||
- `gpt-image-2` 走 VectorEngine;
|
||||
- `gemini-3.1-flash-image-preview` 不再走 APIMart,前端显示名仍为 `nanobanana2`,后端统一回落到 VectorEngine `gpt-image-2-all`。
|
||||
4. VectorEngine 文生图和图生图共用 `POST /v1/images/generations`。有参考图时,后端将参考图 Data URL 作为 `image` 数组传入;若上游不接受该字段,错误按上游失败返回,不在前端降级伪造结果。
|
||||
5. VectorEngine 拼图尺寸使用 `1024x1024`,请求体不携带 `official_fallback`。
|
||||
6. VectorEngine 生成成功后仍下载远程图片,沿用现有 OSS 私有对象、`asset_object` 和 `asset_entity_binding` 写入流程。若图片已成功上传 OSS,但 SpacetimeDB 短暂返回 `503 Service Unavailable`,资产索引写入允许降级跳过,并返回本次生成图片;日志必须记录 `拼图图片资产索引写入因 SpacetimeDB 连接不可用而降级跳过`。
|
||||
7. `save_puzzle_generated_images` 写回草稿时若遇到 SpacetimeDB 连接级 `503` 或断线,API 层基于本次生成结果合成 session 快照返回给前端,避免 VectorEngine 已成功出图却被后置持久化误报成服务不可用。余额不足、参数错误、上游生图失败仍按原错误返回,不做伪成功。
|
||||
8. 结果页 `generate_puzzle_images` 会携带当前作品信息和 `levelsJson`。当 SpacetimeDB 在读取 session 阶段就返回连接级 `503` 或断线时,后端必须先用这份结果页快照构造最小内存 session,再继续调用 VectorEngine;外部图片已经生成后仍按第 6、7 条处理持久化降级。余额不足、参数错误、缺少草稿快照、关卡不存在等业务错误不走此降级。
|
||||
9. VectorEngine 错误统一映射为 `502 UPSTREAM_ERROR`,`details.provider = "vector-engine"`,保留上游状态码、业务 message 和截断后的 raw excerpt。
|
||||
10. 拼图首图生成 `compile_puzzle_draft` 与关卡图片生成 `generate_puzzle_images` 每次预扣 `2` 光点;余额不足仍返回 `409 CONFLICT`,SpacetimeDB 连接级 503 仍按既有降级策略处理。
|
||||
|
||||
## 关卡名多模态生成
|
||||
|
||||
1. 第一关和结果页关卡重新生图的最终关卡名统一由 APIMart Chat Completions `gpt-4o-mini` 生成。
|
||||
2. 输入必须同时包含生成完成后的正式图片和当前关卡 `pictureDescription`;图片由 `api-server` 从生成结果字节压缩为最多 768 边长的 PNG Data URL 后,以 OpenAI 兼容 `messages[].content[]` 的 `image_url` 形式传入。
|
||||
3. 文本模型仍只输出 `{"levelName":"..."}`,并继续复用现有 2 到 8 个中文字符、禁用“画面 / 拼图 / 作品”等泛词的解析与归一化规则。
|
||||
4. `gpt-4o-mini` 调用失败、返回非法或 APIMart 文本配置缺失时,不阻断图片生成;后端保留图片生成前的文本关卡名或确定性兜底名。
|
||||
5. 关卡名与候选图在同一次 `save_puzzle_generated_images` 中写回 `levels_json` 和正式候选图,避免图片与关卡名不同步。
|
||||
|
||||
## 环境变量
|
||||
|
||||
新增服务端环境变量:
|
||||
|
||||
```text
|
||||
APIMART_BASE_URL="https://api.apimart.ai/v1"
|
||||
APIMART_API_KEY="YOUR_APIMART_API_KEY"
|
||||
APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000
|
||||
VECTOR_ENGINE_BASE_URL="https://api.vectorengine.ai"
|
||||
VECTOR_ENGINE_API_KEY="YOUR_VECTOR_ENGINE_API_KEY"
|
||||
VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=180000
|
||||
```
|
||||
|
||||
`APIMART_API_KEY` 只能存在于本地或部署环境,不写入 Git 跟踪文件。若选择 APIMart 模型但缺少 key,后端返回服务不可用错误,前端展示现有错误面板。
|
||||
`VECTOR_ENGINE_API_KEY` 只能存在于本地或部署环境,不写入 Git 跟踪文件。若缺少 key,后端返回服务不可用错误,前端展示现有错误面板。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 创作表单和关卡详情的画面描述框左下角能切换 `gpt-image-2`、`nanobanana2`,默认显示 `gpt-image-2`。
|
||||
2. 点击“生成草稿”时,后端首图生成使用当前表单选择的模型。
|
||||
3. 点击“生成画面 / 重新生成画面”时,后端当前关卡图片生成使用关卡详情选择的模型。
|
||||
4. 历史 `original` 或空模型值不会再触发 DashScope,统一按 `gpt-image-2` 请求 APIMart。
|
||||
5. 选择 APIMart 模型时,请求 `POST {APIMART_BASE_URL}/images/generations`,使用 `Authorization: Bearer {APIMART_API_KEY}`,`model` 等于请求值,`size = 1:1`。
|
||||
6. “生成草稿”和关卡详情生图按钮展示 `消耗2光点`;关卡详情确认后展示 30 秒预计剩余进度条。
|
||||
7. 不改 SpacetimeDB 表结构,因此无需更新 `migration.rs` 或重新生成 bindings。
|
||||
8. 后端改动后运行对应 Rust 测试,并按项目约束用 `npm run api-server:maincloud` 重启验证。
|
||||
4. 历史 `original` 或空模型值不会再触发 DashScope,统一按 `gpt-image-2` 请求 VectorEngine。
|
||||
5. 选择图片模型时,请求 `POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations`,使用 `Authorization: Bearer {VECTOR_ENGINE_API_KEY}`,上游 `model = gpt-image-2-all`,不携带 `official_fallback`,`size = 1024x1024`。
|
||||
6. 首图和结果页关卡重新生图成功后,Network 中应先完成 VectorEngine 图片生成,再调用 APIMart `POST {APIMART_BASE_URL}/chat/completions`,请求模型为 `gpt-4o-mini`,消息同时包含画面描述文本和正式图 `image_url` Data URL。
|
||||
7. “生成草稿”和关卡详情生图按钮展示 `消耗2光点`;关卡详情确认后展示 30 秒预计剩余进度条。
|
||||
8. 不改 SpacetimeDB 表结构,因此无需更新 `migration.rs` 或重新生成 bindings。
|
||||
9. 后端改动后运行对应 Rust 测试,并按项目约束用 `npm run api-server` 重启验证。
|
||||
|
||||
@@ -20,7 +20,7 @@ RPG 在点击生成草稿后会离开聊天工作区,进入独立的生成进
|
||||
|
||||
- 前端只负责展示生成进度与触发已有后端动作,不新增 server-node 或 PostgreSQL 链路。
|
||||
- 后端继续沿用 `server-rs` + `SpacetimeDB` 的会话、草稿与资产写入能力。
|
||||
- 拼图生成草稿链路仍包含:结果页草稿、候选图生成、正式图确认。
|
||||
- 拼图生成草稿链路仍包含:首关草稿编译、首关画面生成、正式草稿写入。
|
||||
- 大鱼吃小鱼生成草稿链路只包含:玩法草稿、等级蓝图、背景蓝图与运行参数编译。
|
||||
- 大鱼吃小鱼的主图、动作、背景都在结果页工坊单独触发,不再属于草稿编译阶段。
|
||||
- 生成过程中展示的“角色描述、角色图片、动作”等,统一映射为锚点、草稿蓝图与资产步骤,不把规则说明类文本写成默认 UI 文案。
|
||||
@@ -38,9 +38,9 @@ RPG 在点击生成草稿后会离开聊天工作区,进入独立的生成进
|
||||
|
||||
### 拼图
|
||||
|
||||
- `compile_puzzle_draft`:在 `server-rs` 内整理主题、主体、构图与标签,写入结果页草稿。
|
||||
- `compile_puzzle_draft`:同一次后端 action 内根据草稿摘要生成候选图。
|
||||
- `compile_puzzle_draft`:同一次后端 action 内自动选择第一张候选图作为正式图。
|
||||
- `compile_puzzle_draft`:在 `server-rs` 内根据入口画面描述生成首关名称和结果页草稿。
|
||||
- `compile_puzzle_draft`:同一次后端 action 内根据画面描述、参考图和当前图片模型生成首关画面。
|
||||
- `compile_puzzle_draft`:同一次后端 action 内自动把首图设为正式图,并同步到结果页草稿。
|
||||
- `ready`:进入拼图结果页。
|
||||
|
||||
### 大鱼吃小鱼
|
||||
|
||||
@@ -4,13 +4,23 @@
|
||||
|
||||
拼图创作入口不再使用 Agent 对话收集题材锚点。新流程让玩家填写作品名称、作品描述、画面描述三类信息,其中画面描述只服务首关画面生成与关卡画面语义,不再作为作品详情页的作品描述。画面描述支持上传参考图。玩家确认后直接进入草稿生成进度页,后续草稿生成、首图生成、正式图选择、结果页编辑和发布沿用现有后端编排。
|
||||
|
||||
2026-05-03 后入口进一步收口为画面描述直创:入口表单只保留画面描述、参考图和图片模型选择;作品名称、作品描述、作品标签全部进入结果页补全。若本文件早期段落仍提到入口必填作品名称或作品描述,以 `PUZZLE_PICTURE_ONLY_CREATION_AND_AI_TAGS_2026-05-03.md` 为准。
|
||||
|
||||
## 入口表单
|
||||
|
||||
### 2026-05-03 画面描述直创补充
|
||||
|
||||
1. 入口表单只展示 `画面描述`、参考图和图片模型选择;`画面描述` 是唯一必填字段。
|
||||
2. 表单自动保存只保存 `pictureDescription`,不再保存入口作品名称、作品描述或推断标签。
|
||||
3. 点击“生成草稿”后进入生成进度页,步骤固定为“编译首关草稿 -> 生成首关画面 -> 写入正式草稿”。
|
||||
4. 生成进度页“当前拼图信息”只展示画面描述;不得展示空作品名称、空作品描述或旧五锚点结构。
|
||||
5. 结果页打开后,作品名称默认使用首关名称,作品描述与作品标签保持为空,等待用户在作品信息 Tab 补全或触发 AI 标签生成。
|
||||
|
||||
### 2026-04-30 初始表单草稿保存补充
|
||||
|
||||
1. 玩家在创作页点击“拼图”入口时,前端必须立即创建一个新的拼图 Agent session,并同步生成一条 `publicationStatus = draft` 的拼图作品卡;此时不触发 `compile_puzzle_draft`,不生成图片,不进入生成进度页。
|
||||
2. 新 session 的 `seedText` 允许为空;SpacetimeDB 侧用空锚点和空表单草稿初始化,不得把默认题材文案写入玩家草稿字段。
|
||||
3. 初始表单输入自动保存到 session 的 `draft_json` 与 `puzzle_work_profile` 投影。保存字段只包含 `workTitle`、`workDescription`、`pictureDescription`、可推断标签和一个 `generationStatus = idle` 的默认关卡;草稿设置阶段默认关卡名称必须为空,不得写入“第一关”“第1关”或作品名称作为默认值。参考图只保存在当前前端会话内,不落入 SpacetimeDB。
|
||||
3. 初始表单输入自动保存到 session 的 `draft_json` 与 `puzzle_work_profile` 投影。保存字段只包含 `workTitle`、`workDescription`、`pictureDescription`、可推断标签和一个 `generationStatus = idle` 的默认关卡;草稿设置阶段默认关卡名称必须为空,不得写入“第一关”“第1关”或作品名称作为默认值。生成前的参考图只保存在当前前端会话内;一旦用于首图生成并成功返回,后端必须把该参考图写入首关 `pictureReference`,供结果页后续重新生成继续复用。
|
||||
4. 玩家在生成草稿前退出,再次从创作中心点击这条拼图草稿时,必须恢复到填表页,并回填之前自动保存的作品名称、作品描述和画面描述;只有执行 `compile_puzzle_draft` 且生成结果页草稿后,草稿入口才进入结果页。
|
||||
5. 表单自动保存走 `save_puzzle_form_draft` action,不消耗光点,不生成图片,不改变 `stage = collecting_anchors`;生成草稿按钮仍单独触发 `compile_puzzle_draft` 并进入进度页。
|
||||
6. 点击拼图入口始终创建新草稿,不复用上一次未完成 session;恢复旧草稿只通过“我的创作”中的草稿卡进入。
|
||||
@@ -66,13 +76,14 @@
|
||||
4. 首图文生图 prompt 由 api-server 拼接固定拼图约束后统一压缩到 `500` 字符以内,避免玩家长画面描述触发 DashScope 参数非法;进度页和结果页仍展示玩家原始画面描述,不展示压缩后的内部 prompt。
|
||||
5. 图片生成仍在 api-server 内完成,遵守 SpacetimeDB reducer 不做网络 I/O 的约束。
|
||||
6. 参考图以 Data URL 进入 `POST /api/runtime/puzzle/agent/sessions` 和 `POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions`,这两条路由必须单独放宽 JSON body 上限;不要放大全局默认 body limit。
|
||||
7. 前端仍应优先压缩参考图;后端 body 上限只用于容纳合理尺寸的单张参考图,超大原图不应直接落入 SpacetimeDB 或作为作品字段持久化。
|
||||
8. 作品更新接口 `PUT /api/runtime/puzzle/works/{profileId}` 必须支持作品信息和关卡列表一起写入,前端自动保存不得只写旧单关字段。
|
||||
9. `StartPuzzleRunRequest` 新增可选 `levelId`。详情页或草稿结果页单独体验某关时传入目标关卡,后端从作品/草稿的 `levels` 中选取该关卡生成运行态。
|
||||
10. `ExecutePuzzleAgentActionRequest` 必须保留 `pictureDescription` 字段。表单直达生成时,`compile_puzzle_draft` 优先用 `pictureDescription` 作为首图 prompt,再回退到旧 `promptText`;避免生成页展示的是玩家画面描述,但后端实际用作品名称或旧摘要出图。
|
||||
11. `compile_puzzle_draft` 中的图片上游失败不得映射成 `400 BAD_REQUEST`。DashScope 返回 `InvalidParameter` 或任务失败时,api-server 统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留“拼图图片生成失败:...”的业务原因,避免生成页只显示“请求参数不合法”。
|
||||
12. `compile_puzzle_draft` 前置光点预扣失败不得映射成 `400 BAD_REQUEST`。余额不足返回 `409 CONFLICT`,SpacetimeDB procedure 不可用、绑定不匹配、钱包服务异常等统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留真实钱包错误。
|
||||
13. 生成拼图作品草稿动作涉及的表单 seed prompt 与首图 prompt 来源选择统一收口在 `server-rs/crates/api-server/src/prompt/puzzle/draft.rs`;`puzzle.rs` 只负责调用 SpacetimeDB、计费、图片服务和持久化,不再直接拼草稿 prompt 文本。
|
||||
7. 前端仍应优先压缩参考图,入口上传图和裁剪图统一压到单边 1024 以内;后端 body 上限只用于容纳合理尺寸的单张参考图,超大原图不应直接落入 SpacetimeDB 或作为作品字段持久化,后端解析后会拒绝超过 8MB 字节的参考图。
|
||||
8. `aiRedraw = true` 且存在 `referenceImageSrc` 时,api-server 必须走 VectorEngine `POST /v1/images/edits` multipart 图生图接口;无参考图或入口页 `aiRedraw = false` 时不走图生图,关闭 AI 重绘会直接应用上传图为首关正式图。
|
||||
9. 作品更新接口 `PUT /api/runtime/puzzle/works/{profileId}` 必须支持作品信息和关卡列表一起写入,前端自动保存不得只写旧单关字段。
|
||||
10. `StartPuzzleRunRequest` 新增可选 `levelId`。详情页或草稿结果页单独体验某关时传入目标关卡,后端从作品/草稿的 `levels` 中选取该关卡生成运行态。
|
||||
11. `ExecutePuzzleAgentActionRequest` 必须保留 `pictureDescription` 字段。表单直达生成时,`compile_puzzle_draft` 优先用 `pictureDescription` 作为首图 prompt,再回退到旧 `promptText`;避免生成页展示的是玩家画面描述,但后端实际用作品名称或旧摘要出图。
|
||||
12. `compile_puzzle_draft` 中的图片上游失败不得映射成 `400 BAD_REQUEST`。DashScope 返回 `InvalidParameter` 或任务失败时,api-server 统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留“拼图图片生成失败:...”的业务原因,避免生成页只显示“请求参数不合法”。
|
||||
13. `compile_puzzle_draft` 前置光点预扣失败不得映射成 `400 BAD_REQUEST`。余额不足返回 `409 CONFLICT`,SpacetimeDB procedure 不可用、绑定不匹配、钱包服务异常等统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留真实钱包错误。
|
||||
14. 生成拼图作品草稿动作涉及的表单 seed prompt 与首图 prompt 来源选择统一收口在 `server-rs/crates/api-server/src/prompt/puzzle/draft.rs`;`puzzle.rs` 只负责调用 SpacetimeDB、计费、图片服务和持久化,不再直接拼草稿 prompt 文本。
|
||||
|
||||
## 结果页
|
||||
|
||||
@@ -94,10 +105,23 @@
|
||||
4. 底部吸底操作区只承载动作按钮,不默认写玩法说明或规则解释,避免压缩移动端编辑空间。
|
||||
5. 关卡详情面板内触发生成画面时,前端必须把当前编辑态完整 `levelsJson` 随 `generate_puzzle_images` action 一起提交。这样新建关卡在自动保存完成前立即生成,也能由后端写回目标关卡。
|
||||
6. api-server 处理 `generate_puzzle_images` 时,若 action 带有 `levelsJson`,必须用这份关卡快照覆盖本次生成的草稿关卡视图后再定位 `levelId`。若请求明确传入 `levelId` 但关卡列表中不存在该关卡,必须返回错误,不得静默回退第一关。
|
||||
7. 历史拼图素材入口只在已有正式图的 `画面图` 区域右下角展示,不再放在 `画面描述` 输入区;本地上传参考图入口仍保留在画面描述输入区右下角。
|
||||
7. 历史拼图素材入口和本地上传参考图入口统一收口到 `画面图` 图卡右下角,避免 `画面描述` 输入区同时承载文本编辑和素材入口;无正式图时也展示空图态图卡。
|
||||
8. 历史拼图素材列表必须由服务端按当前登录账号过滤,只返回 `asset_kind = puzzle_cover_image` 且 `owner_user_id = 当前账号` 的资产;不得依赖前端过滤,也不得展示其他账号素材。
|
||||
9. `画面图` 图卡本身就是上传热区,详情页不再保留右下角独立“上传参考图”按钮;历史入口统一使用带 `History` 图标和 `历史` 小字的按钮。入口页空图态的“点击上传拼图图片”只作为图卡内轻量提示,不使用胶囊按钮、边框或背景样式。
|
||||
|
||||
画面描述区域不再展示候选图实际 prompt 或“请生成一张适合……”之类内部提示词模块。参考图入口保留在画面描述编辑区域内,便于重新生成时继续带入。结果页编辑关卡画面描述时只同步该关卡 `pictureDescription`;作品描述只在作品信息 Tab 编辑,作品详情页不得再回退使用画面描述。
|
||||
### 2026-05-10 关卡生图交互补充
|
||||
|
||||
1. 关卡详情页的 `画面图` 与 `画面描述` 模块对齐入口页拼图表单:画面图使用稳定正方形图卡,画面描述使用固定高度输入区并保留图片模型选择。
|
||||
2. 新建关卡或无正式图关卡也展示 `画面图` 图卡;空图态只保留图标化占位和生成中状态,不追加规则说明文案。
|
||||
3. 关卡详情页删除手填 `参考图链接或资产ID` 输入框。参考图只能通过本地上传或历史拼图素材选择进入本次生成请求;字段 `levels[].pictureReference` 继续作为后端生成后的复用字段透传,不作为用户可手填表单项。
|
||||
4. 单关生成等待估算从 `30` 秒调整为 `90` 秒;生成按钮内展示小字 `等待时间可以制作更多关卡哦~`,不得另起说明面板。
|
||||
5. 触发某一关生成时,前端必须立即把该关 `generationStatus` 标为 `generating` 并随当前 `levelsJson` 写入草稿自动保存链路;后端生成完成后再写回 `ready`。
|
||||
6. `generationStatus = generating` 的关卡在详情弹窗关闭后仍保留进度展示,再次打开同一关详情能继续看到生成进度;关卡列表卡片也必须展示生成中的轻量状态。
|
||||
7. 单关图片生成必须作为后台 action 执行,不占用拼图结果页全局 busy 状态;生成期间仍允许编辑作品信息、编辑关卡、新增关卡、删除其他关卡、关卡测试和继续触发其他可并行动作。
|
||||
8. 发布是唯一必须等待全部图片生成完成的草稿结果页动作;发布检查需要把 `generationStatus = generating` 的关卡列为 blocker,避免未完成资源进入广场。
|
||||
9. 后台生图回包只合并对应关卡的图片候选、正式图、资产 ID 与生成状态,不得覆盖用户等待期间对关卡名、画面描述、作品信息或新增关卡所做的本地编辑。
|
||||
|
||||
画面描述区域不再展示候选图实际 prompt 或“请生成一张适合……”之类内部提示词模块。参考图入口统一放在画面图图卡内,便于重新生成时继续带入,同时保持画面描述输入区只负责文本编辑。结果页编辑关卡画面描述时只同步该关卡 `pictureDescription`;作品描述只在作品信息 Tab 编辑,作品详情页不得再回退使用画面描述。
|
||||
|
||||
## 验收
|
||||
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
### 1. 图片生成
|
||||
|
||||
1. 拼图默认使用 APIMart `gpt-image-2` 生成图,外部请求尺寸固定为 `1:1`;`nanobanana2` 仍映射为 `gemini-3.1-flash-image-preview`。
|
||||
1. 拼图默认使用 VectorEngine `gpt-image-2-all` 生成图,外部请求尺寸固定为 `1024x1024`;前端历史 `nanobanana2` 选项只保留兼容展示,后端同样回落到 VectorEngine GPT-image-2-all,不再调用 APIMart 图片网关。
|
||||
2. 历史 `original` 或空模型值只做兼容输入,不再进入 DashScope 原模型链路,统一按 `gpt-image-2` 路由。
|
||||
3. 文生图和参考图生图共用同一个正方形尺寸口径,禁止一条链路仍生成竖屏或横版图。
|
||||
4. 拼图图片提示词明确写入 `1:1 正方形画布`,继续保留适配 `3x3 / 4x4 / 5x5 / 6x6 / 7x7` 拼图切块、主体清晰、层次明确、无文字水印等约束。
|
||||
5. 文生图正向 prompt 必须由后端压缩到 `500` 字符以内,优先保留玩家画面描述开头与固定拼图约束,避免上游把超长 prompt 判为“请求参数不合法”。
|
||||
6. APIMart 上游失败时,api-server 必须在错误 details 中保留业务 message、`upstreamStatus` 和截断后的 `rawExcerpt`,日志也要记录同样的摘要,避免生成进度页只能看到通用 HTTP 文案。
|
||||
6. VectorEngine 上游失败时,api-server 必须在错误 details 中保留业务 message、`upstreamStatus` 和截断后的 `rawExcerpt`,日志也要记录同样的摘要,避免生成进度页只能看到通用 HTTP 文案。
|
||||
7. 图片生成仍由 `api-server` 执行。SpacetimeDB reducer 不做网络 I/O。
|
||||
8. 光点预扣失败属于钱包或 SpacetimeDB 服务链路错误,不得映射成 `400 BAD_REQUEST`。除余额不足返回 `409 CONFLICT` 外,其余预扣异常统一按上游/服务错误暴露,避免生成页误提示“请求参数不合法”。
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
|
||||
## 验收
|
||||
|
||||
1. 点击拼图草稿生成或重新生成画面时,后端请求 APIMart 的 `size` 为 `1:1`,默认模型为 `gpt-image-2`。
|
||||
1. 点击拼图草稿生成或重新生成画面时,后端请求 VectorEngine 的 `size` 为 `1024x1024`,上游模型为 `gpt-image-2-all`。
|
||||
2. 图片提示词包含 `1:1 正方形拼图关卡`。
|
||||
3. 图片提示词长度不超过 `500` 字符,超长画面描述会被截断,但适配 `3x3 / 4x4 / 5x5 / 6x6 / 7x7` 拼图切块、`避免文字、水印、边框和 UI 元素` 等玩法约束不能丢。
|
||||
4. APIMart 返回参数错误、任务失败或非 2xx 时,前端错误优先展示后端 details.message,后端日志能看到 `upstreamStatus` 和 `rawExcerpt`。
|
||||
4. VectorEngine 返回参数错误、任务失败或非 2xx 时,前端错误优先展示后端 details.message,后端日志能看到 `upstreamStatus` 和 `rawExcerpt`。
|
||||
5. 正式拼图 run 中拖动拼块后,前端立即更新棋盘、合并块和通关状态,不再等待 `/drag`。
|
||||
6. 移动端运行时棋盘为正方形,并尽量贴近屏幕两侧边缘。
|
||||
7. 基础单块和合并块都能看到圆角,合并块的外凸角与内凹角都不是直角,且图片不会溢出圆角裁剪。
|
||||
|
||||
@@ -1,31 +1,40 @@
|
||||
# 拼图生成图片资源代理修复
|
||||
# 拼图生成图片读取链路修复
|
||||
|
||||
日期:`2026-04-27`
|
||||
|
||||
更新:`2026-05-08`
|
||||
|
||||
## 背景
|
||||
|
||||
拼图结果页的“生成或更换图片”会在 `api-server` 中调用 DashScope 生成图片,再把候选图上传到 OSS,最终以 `/generated-puzzle-assets/...` 旧兼容路径写回 `PuzzleGeneratedImageCandidate.image_src` 与草稿封面字段。
|
||||
|
||||
本次排查发现拼图图片写入路径已经进入 `platform-oss::LegacyAssetPrefix::PuzzleAssets`,但后端 Axum 旧资源代理和 Vite 本地代理没有挂载 `/generated-puzzle-assets`。这会导致候选图或正式图无法读取;后续如果把已有候选图作为参考图继续更换图片,也会让参考图读取链路失效。
|
||||
历史排查曾发现拼图图片写入路径已经进入 `platform-oss::LegacyAssetPrefix::PuzzleAssets`,但后端 Axum 旧资源代理和 Vite 本地代理没有挂载 `/generated-puzzle-assets`。当时的处理口径是补旧资源代理。
|
||||
|
||||
当前 `WP-DEL` 后,旧 `/generated-*` 直读代理已经物理删除;`/generated-puzzle-assets/...` 只允许作为 `legacyPublicPath` / OSS object key 标识。浏览器不能再直接请求该路径,必须通过 `/api/assets/read-url?legacyPublicPath=...` 换取短期签名 URL 后预览。
|
||||
|
||||
## 修复口径
|
||||
|
||||
1. `server-rs/crates/api-server/src/legacy_generated_assets.rs` 增加 `proxy_generated_puzzle_assets(...)`,复用统一的 OSS 签名读取逻辑。
|
||||
2. `server-rs/crates/api-server/src/app.rs` 挂载 `/generated-puzzle-assets/{*path}`,与角色、大鱼、自定义世界图片资源前缀保持一致。
|
||||
3. `vite.config.ts` 增加 `/generated-puzzle-assets` dev proxy,保证本地网页端不会因为 Vite 代理缺口读不到后端资源。
|
||||
1. `platform-oss::LEGACY_PUBLIC_PREFIXES` 必须包含 `generated-puzzle-assets`,保持直传票据、服务端上传、read-url 支持列表和错误提示同一口径。
|
||||
2. `src/services/assetReadUrlService.ts` 的 `isGeneratedLegacyPath(...)` 需要同时识别 `/generated-puzzle-assets/...` 和 `generated-puzzle-assets/...`。后者可能来自 object key 形态的历史字段。
|
||||
3. `ResolvedAssetImage` / `useResolvedAssetReadUrl` 在签名 URL 返回前不能把裸 `/generated-*` 或 `generated-*` 写进 `<img src>`。
|
||||
4. `refreshKey` 只能用于跳过前端签名 URL 缓存并重新请求 `/api/assets/read-url`,不能再给 OSS V4 签名 URL 追加 `_v` 等额外 query;OSS 会把 query 纳入签名,额外参数会让新生成图变成 403/破图。
|
||||
5. 历史素材被选为参考图后,参考图小预览也必须走 `ResolvedAssetImage`,不能使用裸 `<img src="/generated-*">`。
|
||||
6. 禁止恢复 `/generated-puzzle-assets/{*path}` Axum 路由或 Vite 直读代理;正式读取统一走 `/api/assets/read-url`。
|
||||
|
||||
## 后续约束
|
||||
|
||||
1. 任何新增 `LegacyAssetPrefix` 都必须同时检查:
|
||||
- `platform-oss` 前缀枚举
|
||||
- `api-server` 旧资源代理路由
|
||||
- Vite dev proxy
|
||||
- `platform-oss::LEGACY_PUBLIC_PREFIXES`
|
||||
- `/api/assets/read-url` 入参解析
|
||||
- 前端 `isGeneratedLegacyPath(...)` 是否能识别
|
||||
2. 拼图候选图 JSON 仍保持 SpacetimeDB 持久化结构 `PuzzleGeneratedImageCandidate` 的 snake_case 字段,不把 HTTP camelCase 响应结构写入 `draft_json`。
|
||||
3. 图片生成、OSS 读写和外部参考图解析继续留在 `api-server`,不能下沉到 SpacetimeDB reducer。
|
||||
4. 如果图片组件需要刷新 generated 私有资源,优先让 `refreshKey` 触发重新换签;不要修改已返回的 `signedUrl`。
|
||||
|
||||
## 验收
|
||||
|
||||
1. `npm run check:encoding`
|
||||
2. `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||
3. `npm run api-server` 重启后,点击拼图结果页“生成或更换图片”,候选图应能写回并正常展示。
|
||||
1. `npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx`
|
||||
2. `cargo test -p platform-oss --manifest-path server-rs\Cargo.toml`
|
||||
3. `npm run check:encoding`
|
||||
4. `npm run api-server` 重启后检查 `/healthz`,再点击拼图结果页“生成或更换图片”,候选图应能写回并正常展示。
|
||||
|
||||
89
docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md
Normal file
89
docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md
Normal 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`。
|
||||
@@ -0,0 +1,75 @@
|
||||
# 拼图画面描述直创与 AI 标签生成调整 2026-05-03
|
||||
|
||||
## 背景
|
||||
|
||||
拼图创作入口继续保留填表式体验,但入口表单不再要求百梦主提前填写作品名称和作品描述。入口只收集“拼图画面描述”,后端用该描述完成首图生成和第一关关卡名生成;进入结果页后再补作品信息。
|
||||
|
||||
## 入口表单
|
||||
|
||||
1. 点击“开始创作”后的拼图表单只展示 `画面描述`、参考图和图片模型选择。
|
||||
2. `画面描述` 是唯一必填字段,提交时写入 `pictureDescription`,并作为 `promptText` 传给 `compile_puzzle_draft`。
|
||||
3. `workTitle`、`workDescription` 不再从入口表单传入;`seedText` 只由画面描述组成,格式为 `画面描述:...`。
|
||||
4. 表单自动保存只保存画面描述,不生成图片,不消耗光点。
|
||||
5. 生成进度页“当前拼图信息”只展示画面描述,不再展示空作品名称或空作品描述。
|
||||
|
||||
## 生成进度步骤
|
||||
|
||||
1. `compile` 展示为“编译首关草稿”:根据画面描述生成首关名称和结果页草稿,不在本步骤生成作品标签。
|
||||
2. `puzzle-images` 展示为“生成首关画面”:按画面描述、参考图和当前图片模型生成第一张拼图图。
|
||||
3. `puzzle-select-image` 展示为“写入正式草稿”:把首图设为第一关正式图,并同步到结果页草稿。
|
||||
4. `ready` 文案提示进入结果页补作品信息;不得暗示作品名称、作品描述或作品标签已经完整生成。
|
||||
|
||||
### 2026-05-08 进度页预计等待与步骤动效补充
|
||||
|
||||
1. 拼图草稿生成进度页预计等待时间固定按 `60` 秒展示和倒计时,后端真实完成后立即进入结果页,不强制等满 60 秒。
|
||||
2. 60 秒进度拆成三段:`compile` 约 12 秒,`puzzle-images` 约 42 秒,`puzzle-select-image` 约 6 秒。
|
||||
3. 生成中即使后端 `compile_puzzle_draft` 仍是一次同步 action,前端也必须按本地计时推进总进度和当前步骤进度,避免页面停在静态等待态。
|
||||
4. 每个步骤卡片都展示独立进度条;已完成步骤显示 100%,当前步骤按该段预计时长推进,后续步骤保留 0% 待处理状态。
|
||||
5. 后端未返回前总进度最多推进到 98%,防止 UI 提前宣称生成完成;只有 action 成功并写回 `ready` 后才显示 100%。
|
||||
|
||||
## 草稿默认值
|
||||
|
||||
1. 后端先由 `module-puzzle` 生成可回滚的确定性草稿,再由 `api-server` 生成第一关关卡名。图片生成前可先基于画面描述生成临时关卡名;正式图片生成完成后,必须使用 APIMart Chat Completions 的 `gpt-4o-mini`,把正式图片 data URL 与画面描述一起传入模型,生成最终关卡名。
|
||||
2. 最终关卡名生成后,必须写回首关 `levelName`,并在入口直创默认场景下作为 `workTitle` 同步写入草稿和作品草稿卡;模型不可用、图片压缩失败或返回非法时,才保留前一步文本名或确定性兜底名。
|
||||
3. `workDescription` 默认保持空字符串,不再回退为画面描述。
|
||||
4. `themeTags` 默认保持空数组,不再由入口画面描述自动推断为正式作品标签。
|
||||
5. `formDraft` 只保留 `pictureDescription`,`workTitle` 与 `workDescription` 为空。
|
||||
|
||||
## 作品标签
|
||||
|
||||
1. 作品信息 Tab 继续支持手动新增、删除标签。
|
||||
2. 作品标签合法数量仍为 `3~6` 个,发布前和后端发布逻辑都要检查。
|
||||
3. 新增 `generate_puzzle_tags` action:
|
||||
- 前端点击 AI 生成标签时先检查作品名称和作品描述。
|
||||
- 若任一为空,前端直接提示先填写,不请求后端。
|
||||
- 两者都不为空时,后端基于作品名称和作品描述调用文本模型,生成 6 个中文短标签。
|
||||
- 生成结果回写 session draft 与 puzzle work profile,前端直接使用返回 session 更新界面。
|
||||
4. AI 标签生成失败时可以降级为确定性关键词标签,但仍必须返回去重后的 6 个标签,保证用户能继续编辑。
|
||||
|
||||
## 保存与发布
|
||||
|
||||
1. 用户在结果页修改作品名称、作品描述、作品标签、关卡名称或画面描述时,继续通过 `PUT /api/runtime/puzzle/works/{profileId}` 自动保存。
|
||||
2. 自动保存允许标签为空,用于支持初始草稿和用户清空标签后的继续编辑。
|
||||
3. 发布前必须检查:
|
||||
- 每个关卡名称非空。
|
||||
- 作品名称非空。
|
||||
- 作品描述非空。
|
||||
- 作品标签数量为 `3~6`。
|
||||
- 每关正式图存在。
|
||||
4. `publish_puzzle_work` 仍由 SpacetimeDB procedure 执行最终校验和发布,前端不能绕过后端门禁。
|
||||
|
||||
## 结果页返回
|
||||
|
||||
1. 从拼图草稿结果页点击左上角返回时,直接回到平台创作页。
|
||||
2. 结果页返回不回到上一页填表工作区;表单页只作为发起新草稿或恢复纯表单草稿的入口。
|
||||
3. 返回创作页时清理拼图生成态、运行态和临时操作态,保留后端已保存的草稿,用户后续从作品卡继续完善。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 拼图入口表单不再出现作品名称和作品描述输入框。
|
||||
2. 只填写画面描述即可生成草稿、图片和第一关关卡名。
|
||||
3. 进入结果页后作品名称默认为模型生成的第一关关卡名,作品描述为空,作品标签为空。
|
||||
4. 点击 AI 生成标签时,作品名称或作品描述为空会先提示补齐。
|
||||
5. 作品名称和作品描述都不为空时,AI 生成 6 个作品标签,并自动保存到后端。
|
||||
6. 手动增删标签仍可用,发布前标签必须至少 3 个且最多 6 个。
|
||||
7. 拼图草稿结果页左上角返回直接回到创作页,不再显示上一页表单。
|
||||
@@ -56,3 +56,9 @@
|
||||
3. 标签少于 `3` 个时,发布弹窗明确提示“正式标签数量必须在 3 到 6 之间”。
|
||||
4. 标签补到 `3~6` 个后,无需刷新页面即可通过前端发布校验。
|
||||
5. 结果页顶部能看到轻量自动保存状态,不额外堆叠说明文案。
|
||||
|
||||
## 2026-05-09 发布失败提示补充
|
||||
|
||||
`publish_puzzle_work` 属于资产操作发布入口,按 `ASSET_GENERATION_POINTS_CONSUMPTION_2026-04-27.md` 会在发布 mutation 前预扣 `1` 枚光点。余额不足时后端返回 `409 CONFLICT`,响应 `details.message` 为 `光点余额不足`,这属于业务拒绝,不是拼图发布接口不可用。
|
||||
|
||||
结果页发布弹窗必须在用户点击发布后继续展示后端错误原因,不能只把错误写到弹窗背后的页面 banner。这样余额不足、SpacetimeDB 发布门禁或其他后端业务错误都会在当前独立发布面板中直接可见。
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
7. 当前作品没有下一关时,通关弹窗展示后端 handoff 返回的相似作品;用户点击具体候选作品时直接 `startPuzzleRun(profileId, null)`,从目标作品第 `1` 关重新开始。
|
||||
8. 失败状态点击“重新开始”时,正式 run 使用当前关 `levelId` 重新 `startPuzzleRun`,草稿/本地 run 使用本地重建,二者都保留当前失败关卡。
|
||||
9. 结果页草稿试玩没有正式后端 run 时,继续使用本地 run、local leaderboard 和本地下一关兜底。
|
||||
10. 运行态输入采用全项目通用的 `src/services/input-devices/` 抽象层承接,指针、触控、mocap 等设备都先归一为 `press / move / release / tap / drop` 拖拽语义,再由拼图运行态解析具体拼块和落点。
|
||||
11. mocap `grab` 不是点击选中语义,而是持续拖拽语义;松手时按当前棋盘归一坐标提交 drop。合并大块只需要提交其中任一成员拼块 `pieceId`,本地拼图运行时会按 `mergedGroupId` 解析整组平移。
|
||||
12. 拼图作品详情或开局遇到后端 `404 / NOT_FOUND / 资源不存在` 时,平台入口不再停留在空详情或运行态错误页,而是清理当前拼图详情/run 状态并返回首页。
|
||||
|
||||
## 工程落点
|
||||
|
||||
@@ -38,6 +41,15 @@
|
||||
3. `src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`
|
||||
- 公开拼图玩法交互测试断言前端本地交换函数被调用。
|
||||
- 同时断言后端 `swap / drag` 不参与棋盘交互,后端 `leaderboard / next-level` 继续参与非即时链路。
|
||||
4. `src/services/input-devices/`
|
||||
- `runtimeDragInputController` 提供设备无关的拖拽会话状态机。
|
||||
- `runtimeInputGeometry` 提供屏幕坐标、归一坐标和网格命中的通用转换能力。
|
||||
- 玩法组件只传入“这个点对应哪个目标”和“drop 到哪个目标”的玩法解释,不在输入层写拼图专用规则。
|
||||
5. `src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`
|
||||
- 鼠标/触控与 mocap 共用同一个 runtime drag controller。
|
||||
- 合并块成员不再被 mocap 路径过滤;mocap 可从合并块任一占用格抓取,并复用本地运行时的大块拖拽规则。
|
||||
6. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
- `openPuzzleDetail`、`openPuzzlePublicWorkDetail`、`startPuzzleRunFromProfile` 对拼图作品缺失统一回首页。
|
||||
|
||||
## 边界
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
不能继续写到仓库本地 `public/generated-puzzle-covers/*`。
|
||||
|
||||
这些路径只是前后端 DTO 里的兼容标识,不是浏览器可以直接裸读的公开资源地址。实际图片对象存放在私有 OSS 中,前端渲染前必须先通过 `/api/assets/read-url?legacyPublicPath=...` 换取签名读 URL;签名 URL 未返回或换签失败时,图片组件不能把 `/generated-puzzle-assets/*` 直接写入 `<img src>`,避免浏览器发起无签名、无鉴权请求。
|
||||
这些路径只是前后端 DTO 里的兼容标识,不是浏览器可以直接裸读的公开资源地址。实际图片对象存放在私有 OSS 中,前端渲染前必须先通过 `/api/assets/read-url?legacyPublicPath=...` 换取签名读 URL;签名 URL 未返回或换签失败时,图片组件不能把 `/generated-puzzle-assets/*` 或无前导斜杠的 `generated-puzzle-assets/*` 直接写入 `<img src>`,避免浏览器发起无签名、无鉴权请求。
|
||||
|
||||
### 4.2 运行态边界
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# 拼图创作模板表单与 gpt-image-2 Skill 封装 2026-05-03
|
||||
|
||||
## 背景
|
||||
|
||||
拼图创作入口已经从对话式 Agent 收口为填表式表单。本次改版目标是让“点击拼图创作”后的表单更接近图像创作工具的单屏体验:优先上传参考图或填写画面描述,然后直接生成首关草稿与首张拼图图。2026-05-07 起,入口表单删除 Template 模块,模板参考图只保留为历史资产,不再作为表单首屏交互。
|
||||
|
||||
## 落地范围
|
||||
|
||||
1. `src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`
|
||||
- 改为顶部标题、超大参考图上传区、大输入框、底部操作区的布局。
|
||||
- 保留参考图上传、模型切换和生成草稿。
|
||||
- 删除 Template 模块与模板卡切换入口。
|
||||
- 不再提供输入框底部的 `try` 示例入口。
|
||||
2. `src/components/puzzle-agent/puzzleCreationTemplates.ts`
|
||||
- 保留历史拼图创作模板数据,当前表单不再消费。
|
||||
- 后续若重新开放模板入口,必须先更新本文档和移动端首屏验收。
|
||||
3. `public/puzzle-creation-templates/`
|
||||
- 存放历史模板样例图。
|
||||
- 样例图不作为正式拼图作品资产,当前也不再出现在拼图入口表单。
|
||||
4. `public/creation-type-references/`
|
||||
- 存放平台创作入口和玩法类型弹层的参考图。
|
||||
- 每个玩法一个参考图,首版用于视觉识别,不承载规则说明。
|
||||
- 当前创作 Tab 顶部玩法卡带必须直接渲染这些图片,避免参考图只出现在隐藏弹层里。
|
||||
5. `.codex/skills/gpt-image-2-apimart/`
|
||||
- 历史目录名保留,实际封装仓库内 `gpt-image-2` 的 VectorEngine 调用流程。
|
||||
- Skill 默认读取本地环境变量,不把密钥写入代码、文档或前端。
|
||||
|
||||
## UI 规则
|
||||
|
||||
1. 顶部主标题展示“想做个什么玩法?”和轻量状态标识,不写玩法规则说明。
|
||||
2. 上传拼图图片区优先占据首屏左侧/上方的大块区域,移动端也必须完整露出。
|
||||
- 上传区自身就是图片卡片,不再额外封装为 `platform-subpanel` 模块壳。
|
||||
- 亮色主题下上传卡片必须使用白色或暖浅色卡面,不得显示整块黑色底。
|
||||
- 上传卡片固定为 1:1 正方形,避免拼图主画面在首屏出现非正方形预期。
|
||||
- 移动端表单主体不可依赖纵向拖动查看核心控件;玩法卡带、描述输入框和底部生成按钮占位固定后,上传卡片必须按剩余高度等比例缩放,仍保持 1:1。
|
||||
- 上传卡片底部不再叠加文件名 bar;`点击上传拼图图片` 入口必须显示在拼图画面卡片内部。
|
||||
- 上传卡片上方固定展示 `拼图画面` 标题。
|
||||
- 无图状态下,上传卡片内部、`点击上传拼图图片` 按钮上方展示 11px 级辅助提示 `若没有合适的图片可以通过填写画面描述生成画面`,提示用户可不上传图片、直接填写画面描述生成画面。
|
||||
- 上传成功后,`AI重绘` 开关显示在卡片左下角,右上角显示移除拼图图片图标按钮;移除必须先弹出二次确认。
|
||||
- 叠在上传卡片上的 `AI重绘`、移除图标和上传入口必须和卡面保持足够对比,避免浅色主题重映射后不可读。
|
||||
3. 画面描述输入框高度固定,移动端保持约 `6rem`,不随剩余屏幕高度变大或变小,避免把上传参考图和提交区挤出首屏。
|
||||
4. 创作 Tab 顶部玩法卡带的选中态只使用卡内暗色蒙版、细描边或内描边,不使用粉色外发光、外扩阴影或会从卡片边缘突出的高饱和边。
|
||||
5. 输入区保留:
|
||||
- 上传拼图图片按钮。
|
||||
- 图片模型切换按钮。
|
||||
6. 输入区不保留:
|
||||
- `try` 文本。
|
||||
- 示例 prompt chip。
|
||||
- 画面描述输入框默认提示词或占位示例。
|
||||
- 玩法规则说明。
|
||||
- Template 模块和模板卡切换。
|
||||
|
||||
## 历史模板抽样
|
||||
|
||||
以下模板曾用于表单 Template 模块。2026-05-07 后入口表单不再展示这些模板,列表仅作为历史资产索引:
|
||||
|
||||
1. 情侣合照拼图
|
||||
2. 家庭纪念拼图
|
||||
3. 朋友聚会拼图
|
||||
4. 节日贺卡拼图
|
||||
5. 知识点总结拼图
|
||||
6. 商品细节拼图
|
||||
7. 治愈风景拼图
|
||||
8. 宠物可爱拼图
|
||||
9. 热点海报拼图
|
||||
10. 活动邀请拼图
|
||||
11. 每日挑战拼图
|
||||
12. 儿童认知拼图
|
||||
|
||||
模板提示词必须是可直接送入拼图生图链路的画面描述,不写 UI、按钮、教程、规则或营销解释。
|
||||
|
||||
## gpt-image-2 Skill 规则
|
||||
|
||||
Skill 封装仓库现有后端口径:
|
||||
|
||||
```text
|
||||
POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations
|
||||
Authorization: Bearer {VECTOR_ENGINE_API_KEY}
|
||||
model = gpt-image-2-all
|
||||
n = 1
|
||||
size = 1024x1024
|
||||
```
|
||||
|
||||
响应兼容:
|
||||
|
||||
1. `data[].url`
|
||||
2. `data[].b64_json`
|
||||
|
||||
本次 Skill 只封装生成样例图和研发复用流程,不改变正式后端接口、扣费、OSS、SpacetimeDB 写入和发布链路。
|
||||
|
||||
## 2026-05-07 AI 重绘与上传直用
|
||||
|
||||
拼图入口上传区左下角展示 `AI重绘` 开关,默认打开;未上传拼图图片前不显示开关,上传成功后才显示。上传成功后右上角展示移除图标按钮,点击后必须二次确认。
|
||||
|
||||
1. `AI重绘=true`
|
||||
- 上传区文案为 `点击上传拼图图片`,上传图作为生图参考图。
|
||||
- 未上传图片时,输入框标题为 `画面描述`。
|
||||
- 已上传图片时,输入框标题为 `画面AI重绘要求(提示词)`。
|
||||
- 展示图片模型切换。
|
||||
- `compile_puzzle_draft` 携带 `aiRedraw: true`,继续走 VectorEngine 生图与 `PUZZLE_IMAGE_GENERATION_POINTS_COST = 2` 扣费链路。
|
||||
- 生成按钮展示 `消耗2光点`。
|
||||
2. `AI重绘=false`
|
||||
- 隐藏画面描述输入框和模型切换。
|
||||
- 必须上传拼图图片,按钮不展示 `消耗2光点`。
|
||||
- `compile_puzzle_draft` 携带 `aiRedraw: false`,后端只编译草稿和生成首关名,不调用 VectorEngine,不进入光点扣费 wrapper。
|
||||
- 后端把上传图片 Data URL 按拼图资产路径持久化,构造 `sourceType=uploaded` 的候选图并直接选为第一关正式图。
|
||||
3. 上传裁剪
|
||||
- 前端读取上传图原始宽高。
|
||||
- 非 1:1 图片必须先弹出正方形裁剪工具,裁剪完成后再进入表单状态和提交 payload。
|
||||
- 裁剪工具必须在完整原图上展示正方形裁剪框,支持拖拽框内区域移动,以及拖拽四边或四角调整裁剪边界,不再展示 `缩放 / 横向 / 纵向` 参数滑杆。
|
||||
- 裁剪输出仍按参考图体积预算压缩,避免上传图撑爆 JSON body。
|
||||
|
||||
契约字段同步:
|
||||
|
||||
```ts
|
||||
CreatePuzzleAgentSessionRequest.aiRedraw?: boolean
|
||||
PuzzleAgentActionRequest.compile_puzzle_draft.aiRedraw?: boolean
|
||||
```
|
||||
|
||||
Rust 共享契约使用 `ai_redraw: Option<bool>` 并按 camelCase 序列化为 `aiRedraw`。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 点击拼图创作后,移动端表单无需纵向拖动即可看到大参考图区、固定高度文本输入框和 `生成拼图游戏草稿` 按钮。
|
||||
2. 输入框里没有 `try` 示例功能。
|
||||
3. 图片模型切换仍可打开并选择 `gpt-image-2` / `nanobanana2`。
|
||||
4. 历史模板样例图文件可保留,但不出现在拼图入口表单。
|
||||
5. 当前创作 Tab 顶部的拼图、方洞挑战、视觉小说和 AIRP 卡片能看到对应 `creation-type-references` 图片。
|
||||
6. 默认 `AI重绘` 打开时,无图状态展示 `画面描述` 与 `消耗2光点`;上传图片后输入框标题改为 `画面AI重绘要求(提示词)`。
|
||||
7. 关闭 `AI重绘` 后隐藏画面描述输入框,生成按钮不展示 `消耗2光点`,后端直接应用上传图片为第一关图片。
|
||||
8. 上传非 1:1 图片时必须先通过拖拽裁剪框完成正方形裁剪。
|
||||
9. gpt-image-2 Skill 校验通过,且脚本 dry-run 能输出计划请求而不泄露密钥。
|
||||
10. `npm run check:encoding` 通过。
|
||||
@@ -5,10 +5,37 @@
|
||||
## 文档列表
|
||||
|
||||
- [WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md](./WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md):记录微信小程序 `web-view` 壳的最小接入范围、需要填写的 H5 业务域名、微信后台配置和后续原生化边界。
|
||||
- [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` 空状态白屏。
|
||||
- [BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md](./BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md):冻结寓教于乐 `宝贝识物` 模板创作发布线程的前端入口、契约、service、结果页、发布标签和后端 image-2 接口预留边界。
|
||||
- [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 差异;同时冻结 `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 构建参数口径和手动排障命令。
|
||||
- [AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md](./AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md):记录刷新网页后登录态失效和推荐页作品卡卡在加载中的联合修复,覆盖 `AuthGate` 本地 token 优先恢复、refresh 失败不清 token、推荐页启动请求版本保护和错误态收口。
|
||||
- [USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md](./USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md):冻结用户标签字段、后台邀请码授予标签、用户填写邀请码后合并账号标签,以及拼图排行榜只展示白名单标签 `北科` 的落地口径。
|
||||
- [RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md](./RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md):记录平台推荐页自动加载作品、公开拼图作品完整运行态、平台 bootstrap 私有投影刷新和展示层图片换签的局部请求 `401` 不应扩散成全局登出的修复,覆盖 `authImpact: local` 请求策略、推荐页 embedded 运行态启动、拼图开局/排行榜/下一关和回归测试。
|
||||
- [AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md](./AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md):记录 `AuthGate` 登录成功后又被旧 hydrate 覆盖回未登录态的竞态根因、版本号保护修复与回归测试。
|
||||
- [HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md](./HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md):记录 Hyper3D Rodin Gen-2 文生 3D 模型、图生 3D 模型、状态查询和下载列表的后端代理、环境变量、请求约束与验收边界。
|
||||
- [MATCH3D_RODIN_ASSET_TAB_2026-05-10.md](./MATCH3D_RODIN_ASSET_TAB_2026-05-10.md):记录抓大鹅结果页多 Tab 改造与 Rodin 3D 素材列表/详情页的前端接入边界,明确首版只复用 Hyper3D 后端代理,不新增表或正式资产写入。
|
||||
- [MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md](./MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md):冻结抓大鹅草稿生成过程页、3 件物品名称生成、VectorEngine 1:1 素材图、n*n 切图、并行 Rodin 图生 3D 与 OSS 回填草稿页的端到端边界。
|
||||
- [VOLCENGINE_SPEECH_STREAMING_INTEGRATION_2026-05-08.md](./VOLCENGINE_SPEECH_STREAMING_INTEGRATION_2026-05-08.md):记录火山引擎大模型 ASR 双向流式、TTS WebSocket 双向流式和 TTS HTTP SSE 单向流式的后端代理、环境变量、协议帧和验收边界。
|
||||
- [VECTOR_ENGINE_AUDIO_GENERATION_SUNO_VIDU_2026-05-08.md](./VECTOR_ENGINE_AUDIO_GENERATION_SUNO_VIDU_2026-05-08.md):记录视觉小说结果页接入 VectorEngine Suno 文生背景音乐与 Vidu 文生音效的接口、环境变量、后端路由、OSS 资产回写和前端弹层交互边界。
|
||||
- [PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md](./PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md):冻结“我的”页签帮助与反馈入口的后端接入方案,覆盖 `POST /api/profile/feedback`、`profile_feedback_submission`、凭证图片 Data URL 校验和前端预览/提交边界。
|
||||
- [API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md](./API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md):冻结 api-server 外部服务配置边界,公共服务 URL 可保留代码默认值,非公共模型名和私有网关 URL 统一通过环境变量注入。
|
||||
- [CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md](./CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md):冻结基于 LangChain-Rust 的创意互动内容生成 Agent 技术方案,明确首版只支持拼图模板、必须显式展示模板选择和积分范围,通过拼图模块 Tool/模板协议填充同一份草稿字段,支持单关卡与多关卡图片生成、立即试玩、表单化编辑和 Agent 自然语言修订草稿字段。
|
||||
- [VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md](./VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md):记录视觉小说模板 `VN-03` Prompt / LLM 工具落地,包含创作底稿 Prompt、运行时 GM Prompt、repair Prompt、工具参数 schema、Responses 请求口径和定向验证结果。
|
||||
- [VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md](./VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md):记录视觉小说模板 `VN-13` 实现收口、当前正式入口、表目录、路由、作品 / 运行 / 资产和负向扫描口径。
|
||||
- [PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md](./PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md):冻结个人任务与埋点系统首版方案,明确 `tracking_event`、`tracking_daily_stat`、`profile_task_config`、任务进度、领奖记录和光点钱包流水的边界。
|
||||
- [SQUARE_HOLE_IMAGE_SLOT_AND_RUNTIME_INTERACTION_FIX_2026-05-06.md](./SQUARE_HOLE_IMAGE_SLOT_AND_RUNTIME_INTERACTION_FIX_2026-05-06.md):记录方洞挑战结果页图片槽位局部生成、洞口图历史素材、运行态拖拽与点击投放交互的修正口径。
|
||||
- [MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md](./MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md):冻结 Maincloud 历史残留引用禁用策略,明确后续不得新增、运行或引用 `api-server:maincloud`、`GENARRATIVE_SPACETIME_MAINCLOUD_*` 和相关测试/文档口径。
|
||||
- [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md):冻结单机生产部署目标,从旧一体化启动脚本切到 Nginx、systemd 托管 SpacetimeDB 与 Rust `api-server`,并记录生产 Jenkins 流水线拆分计划和首批部署骨架。
|
||||
- [PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md](./PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md):记录拼图正式平台入口移动、交换、合并、拆分和通关裁决收回前端即时运行态,排行榜、下一关和游玩记录继续由后端持久化处理。
|
||||
- [RPG_FOUNDATION_DRAFT_ROLE_DOSSIER_TIMEOUT_FALLBACK_2026-05-02.md](./RPG_FOUNDATION_DRAFT_ROLE_DOSSIER_TIMEOUT_FALLBACK_2026-05-02.md):记录 `agent-foundation-*-dossier-batch-*` 无搜索 Responses 请求超时后的本地养成档案兜底,避免底稿主链被尾部角色润色阶段阻断。
|
||||
- [RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md](./RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md):记录 RPG 角色主图与场景幕背景图统一迁移到 APIMart OpenAI 兼容 `gpt-image-2` 生图入口的边界、配置和验收口径。
|
||||
- [RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md](./RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md):记录 RPG 角色主图与场景幕背景图统一迁移到 `gpt-image-2` 生图入口的边界、配置和验收口径;2026-05-09 起实际上游以 VectorEngine 迁移文档为准。
|
||||
- [RPG_FOUNDATION_DRAFT_LANDMARK_SEED_BATCH_TIMEOUT_FIX_2026-05-02.md](./RPG_FOUNDATION_DRAFT_LANDMARK_SEED_BATCH_TIMEOUT_FIX_2026-05-02.md):记录 `agent-foundation-landmark-seed-batch-1` 无搜索 Responses 请求超时的根因,并将场景骨架批次收敛为单场景生成。
|
||||
- [PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md](./PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md):记录“我的”和“存档”页面在本地把 `/api/profile/*` 请求落到 Vite SPA fallback、导致 HTML 被当 JSON 解析的根因,以及 `/api/profile` 代理补齐与回归测试。
|
||||
- [SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md](./SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md):记录 `WP-DEL 删除旧层与命名收口`,物理删除旧 runtime story HTTP DTO、前端 `Rpg*` alias、旧 `/api/custom-world/*` 非 runtime 前缀、Puzzle `local-next-level` 入口和 `/generated-*` 资产直读代理;生成资产读取统一走 OSS read-url 链路。
|
||||
@@ -21,7 +48,7 @@
|
||||
- [SERVER_RS_DDD_WP_RS_RUNTIME_STORY_CLOSURE_2026-05-01.md](./SERVER_RS_DDD_WP_RS_RUNTIME_STORY_CLOSURE_2026-05-01.md):记录 `WP-RS Runtime Story` 写链路收尾,补齐 `/api/story/sessions/runtime` 与 `/api/story/sessions/{storySessionId}/actions/resolve`,统一返回 `StoryRuntimeMutationResponse.projection`,并保持旧 `/api/runtime/story/*` 未挂载。
|
||||
- [SERVER_RS_DDD_WP_CW_ACTION_AND_DOMAIN_SPLIT_2026-04-30.md](./SERVER_RS_DDD_WP_CW_ACTION_AND_DOMAIN_SPLIT_2026-04-30.md):记录 `WP-CW Custom World` 的领域拆分与 Agent action 收口,将 `module-custom-world` 大 `lib.rs` 拆入 DDD 骨架,并移除 Custom World 运行代码中的最小兼容占位动作。
|
||||
- [SERVER_RS_DDD_WP_BF_AND_G2_DRIFT_CLEANUP_2026-04-30.md](./SERVER_RS_DDD_WP_BF_AND_G2_DRIFT_CLEANUP_2026-04-30.md):记录 `WP-BF Big Fish` 物理拆分漂移和 G2 迁移期口径清理,将 Big Fish 创作域类型、命令、应用规则和错误层拆入 DDD 文件,并清理剩余 `过渡落位` 注释。
|
||||
- [SERVER_RS_DDD_TESTS_SUPPORT_CRATE_CLOSURE_2026-04-30.md](./SERVER_RS_DDD_TESTS_SUPPORT_CRATE_CLOSURE_2026-04-30.md):记录 `tests-support` 从目录占位收口为 `server-rs` workspace 共享测试支撑 crate,首版提供 Maincloud healthz 与 HTTP smoke 通用断言。
|
||||
- [SERVER_RS_DDD_TESTS_SUPPORT_CRATE_CLOSURE_2026-04-30.md](./SERVER_RS_DDD_TESTS_SUPPORT_CRATE_CLOSURE_2026-04-30.md):记录 `tests-support` 从目录占位收口为 `server-rs` workspace 共享测试支撑 crate;该历史文档中的旧 Maincloud 口径不再作为当前执行依据,当前 smoke 以通用 `/healthz` 为准。
|
||||
- [SERVER_RS_DDD_WP_BF_RUNTIME_BACKEND_TRUTH_2026-04-29.md](./SERVER_RS_DDD_WP_BF_RUNTIME_BACKEND_TRUTH_2026-04-29.md):记录 `WP-BF Big Fish` 运行态从前端本地规则切到 Rust 领域真相源、SpacetimeDB run 表、API facade 和前端新接口接入的关闭口径。
|
||||
- [SERVER_RS_DDD_WP_PF_PLATFORM_ERROR_CLASSIFICATION_2026-04-29.md](./SERVER_RS_DDD_WP_PF_PLATFORM_ERROR_CLASSIFICATION_2026-04-29.md):记录 `WP-PF platform side effects` 平台副作用收口,统一 LLM、OSS、SMS、微信平台错误分类与 API 映射,并将微信 OAuth provider 下沉到 `platform-auth`。
|
||||
- [SERVER_RS_DDD_WP_RT_ADAPTER_API_CLOSURE_2026-04-29.md](./SERVER_RS_DDD_WP_RT_ADAPTER_API_CLOSURE_2026-04-29.md):记录 `WP-RT Runtime/Profile/Save` Adapter/API 收口,将 checkpoint、profile/save archive meta、充值/邀请/兑换/钱包等剩余纯规则迁入 `module-runtime`,移除 `/api/runtime/profile/*` 旧兼容挂载并对齐前端 `/api/profile/*` 请求路径。
|
||||
@@ -57,13 +84,13 @@
|
||||
- [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md):冻结 SpacetimeDB 表结构变更约束、自动迁移可接受范围、冲突后的系统行为,以及保留旧数据的增量迁移流程;凡涉及 `spacetime publish`、表字段调整或 `migration.rs` 对齐时优先参考。
|
||||
- [PRODUCT_NAMING_BAIMENG_RENAME_2026-05-01.md](./PRODUCT_NAMING_BAIMENG_RENAME_2026-05-01.md):冻结当前对外中文命名,产品展示名统一为“百梦”,消费单位为“光点”,公开账号标识为“百梦号”,创作侧称谓为“百梦主”。
|
||||
- [SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md](./SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md):记录旧云端 SpacetimeDB 配置、发布脚本和默认文档口径的移除结果,冻结后续仅使用本地或显式 `SERVER_URL` 的运维规则。
|
||||
- [SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md](./SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md):记录本地 standalone 启动时报 `mismatched database identity` 的 root-dir/replica 数据残留根因、备份重建步骤和脚本诊断口径。
|
||||
- [SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md](./SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md):记录本地 standalone 启动时报 `mismatched database identity` 的数据目录/replica 数据残留根因、备份重建步骤和脚本诊断口径。
|
||||
- [AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md](./AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md):记录远端库挂起导致认证快照同步和抓大鹅创作失败的根因、认证同步非阻断修复、`/api/creation` Vite 代理补齐和本地 SpacetimeDB 可跑链路。
|
||||
- [LLM_MODEL_ROUTING_RPG_AND_CREATION_2026-04-30.md](./LLM_MODEL_ROUTING_RPG_AND_CREATION_2026-04-30.md):冻结 RPG 运行时剧情推理使用 `doubao-seed-character-251128` 的 `/chat/completions`,以及所有模板创作大模型推理使用 `deepseek-v3-2-251201` 的 `/responses`。
|
||||
- [PROFILE_INVITE_CODE_REGISTRATION_AND_ADMIN_2026-04-30.md](./PROFILE_INVITE_CODE_REGISTRATION_AND_ADMIN_2026-04-30.md):冻结邀请码从“我的 Tab 填写”迁到注册环节的前后端边界、`profile_invite_code.metadata_json` 表结构扩展、管理员邀请码虚拟主体和奖励规则。
|
||||
- [MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md](./MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md):冻结抓大鹅 Match3D 首版 demo 的独立玩法域、表与 procedure、HTTP facade、前端即时反馈/后端权威确认协议,以及可并行开发包。
|
||||
- [MATCH3D_DOMAIN_AND_CONTRACTS_STAGE1_2026-04-30.md](./MATCH3D_DOMAIN_AND_CONTRACTS_STAGE1_2026-04-30.md):冻结抓大鹅 Match3D B1+B2 的纯领域规则 crate、Rust/TypeScript shared contracts,以及 Stage1 不触碰 SpacetimeDB 表和 api-server 的边界。
|
||||
- [MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md](./MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md):记录抓大鹅 F1 创作入口、Agent 工作区、参考图入口、本地 mock client 与后续 B5 HTTP facade 替换点。
|
||||
- [MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md](./MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md):记录抓大鹅 F1 创作入口与当前拼图式内嵌 Tab 表单,明确入口页仅保留题材大输入框和难度选项,由难度选项派生消除次数与难度数值。
|
||||
- [MATCH3D_F2_RESULT_AND_PUBLISH_2026-04-30.md](./MATCH3D_F2_RESULT_AND_PUBLISH_2026-04-30.md):冻结抓大鹅 F2 结果页、基础信息编辑、发布前试玩入口、发布门槛、自动保存和已发布作品二次编辑恢复口径。
|
||||
- [MATCH3D_SPACETIME_CLIENT_AND_API_FACADE_2026-04-30.md](./MATCH3D_SPACETIME_CLIENT_AND_API_FACADE_2026-04-30.md):记录抓大鹅 B4+B5 已落地的 SpacetimeDB bindings、`spacetime-client` facade、`api-server` HTTP 路由、shared contract 对齐和验收命令。
|
||||
- [MATCH3D_CREATION_ENTRY_COMING_SOON_2026-05-01.md](./MATCH3D_CREATION_ENTRY_COMING_SOON_2026-05-01.md):记录抓大鹅创作页入口重新开放、首屏与弹层分流一致,以及公开广场失败不污染创作错误态的边界。
|
||||
@@ -82,6 +109,8 @@
|
||||
- [BIG_FISH_MAIN_IMAGE_TRANSPARENT_BACKGROUND_ALIGNMENT_2026-04-28.md](./BIG_FISH_MAIN_IMAGE_TRANSPARENT_BACKGROUND_ALIGNMENT_2026-04-28.md):记录大鱼吃小鱼等级主图与动作关键帧正式图在 Rust 后端复用 RPG 角色主图透明背景 alpha 后处理的对齐口径,并明确场地背景不走该处理。
|
||||
- [PUZZLE_IMAGE_AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md](./PUZZLE_IMAGE_AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md):记录拼图生成图片回到 1:1,运行时拖动、交换、合并与拆分由前端即时裁决,以及移动端棋盘贴近屏幕边缘的落地边界。
|
||||
- [PUZZLE_FORM_CREATION_FLOW_2026-04-29.md](./PUZZLE_FORM_CREATION_FLOW_2026-04-29.md):冻结拼图填表式创作入口、初始表单自动保存草稿、生成前退出后的表单恢复,以及草稿编译/首图生成的前后端边界。
|
||||
- [PUZZLE_PICTURE_ONLY_CREATION_AND_AI_TAGS_2026-05-03.md](./PUZZLE_PICTURE_ONLY_CREATION_AND_AI_TAGS_2026-05-03.md):记录拼图入口只填写画面描述、首关名默认作品名、作品描述和标签初始为空、AI 生成 6 个作品标签以及发布前校验的落地规则。
|
||||
- [PUZZLE_TEMPLATE_FORM_AND_GPT_IMAGE_SKILL_2026-05-03.md](./PUZZLE_TEMPLATE_FORM_AND_GPT_IMAGE_SKILL_2026-05-03.md):记录拼图入口模板样例图与 gpt-image-2 Skill 约定,2026-05-07 起表单不再展示 Template 模块,改为大参考图区 + 大输入框的单屏布局。
|
||||
- [PUZZLE_LEADERBOARD_FRONTEND_LEVEL_AND_RPG_COMING_SOON_2026-04-30.md](./PUZZLE_LEADERBOARD_FRONTEND_LEVEL_AND_RPG_COMING_SOON_2026-04-30.md):记录拼图第二关排行榜提交以前端当前关卡为准、不被 SpacetimeDB 旧 run 快照误杀,以及 RPG 创作入口改为敬请期待的落地边界。
|
||||
- [PUZZLE_NEXT_LEVEL_AND_SIMILAR_WORK_HANDOFF_2026-04-30.md](./PUZZLE_NEXT_LEVEL_AND_SIMILAR_WORK_HANDOFF_2026-04-30.md):记录拼图通关后优先同作品下一关、无下一关时按 RPG/build 标签语义相似度返回三个候选作品,并在跨作品时只切换到候选作品第 1 张图、运行时关卡序号继续累进的落地规则。
|
||||
- [PUZZLE_FAILURE_EXTENSION_AND_SAVE_ARCHIVE_2026-05-01.md](./PUZZLE_FAILURE_EXTENSION_AND_SAVE_ARCHIVE_2026-05-01.md):记录拼图失败后重新开始/付费续时,以及进入作品与过关后同步存档页投影的落地规则。
|
||||
@@ -89,11 +118,11 @@
|
||||
- [RPG_SCENE_ACT_PREVIEW_BOOTSTRAP_FIX_2026-04-30.md](./RPG_SCENE_ACT_PREVIEW_BOOTSTRAP_FIX_2026-04-30.md):记录编辑器幕预览卡在“正在载入这一幕”时的启动态根因,收口预览本地运行态装配与禁持久化首段 story 注入。
|
||||
- [PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md](./PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md):记录拼图结果页名称与标签编辑自动保存、发布门槛统一到 `3~6` 标签,以及前端发布校验不再被旧 session blocker 卡死的修复口径。
|
||||
- [WORK_AUTHOR_ID_RESOLUTION_2026-04-30.md](./WORK_AUTHOR_ID_RESOLUTION_2026-04-30.md):记录作品作者以 `owner_user_id` 为真相源,API 按用户 ID 解析最新昵称与公开用户码,历史 `author_display_name` 仅作为兼容回退。
|
||||
- [SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md](./SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md):记录发布包 `start.sh` 只输出“SpacetimeDB 进程在就绪前退出”时的诊断补强,启动失败或超时时自动回显 `logs/spacetimedb.log`、`server ping`、端口监听和 root-dir 相关进程。
|
||||
- [SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md](./SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md):历史事故记录,保留旧发布包 `start.sh` 只输出“SpacetimeDB 进程在就绪前退出”时的诊断补强;该文档不再作为当前发布或人工排障依据。
|
||||
- [RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md](./RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md):记录 RPG 运行时 NPC 聊天、RPG/自定义世界 Agent 与大鱼 Agent 从“拼完整 SSE 字符串后一次性返回”改为 `mpsc + Sse<Event>` 真流式输出的后端落地口径。
|
||||
- [SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md](./SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md):记录发布包 `start.sh` root-dir 占用检测把 `grep -F .../.spacetimedb` 误判为 SpacetimeDB 实例的根因、脚本修复和现场处理方式。
|
||||
- [SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md](./SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md):历史事故记录,保留旧发布包 `start.sh` 占用检测把 `grep -F .../.spacetimedb` 误判为 SpacetimeDB 实例的根因;该文档不再作为当前发布或人工排障依据。
|
||||
- [RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md](./RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md):记录 RPG 战斗血条安全锚点、服务端战斗回包前端短表现,以及 `battle_use_skill` 指定技能兜底结算的修复口径。
|
||||
- [SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md](./SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md):记录发布包 `start.sh` 执行 `spacetime publish` 遇到 `403 Forbidden` 的身份根因、`.spacetimedb/` root-dir 隔离修复和排查步骤。
|
||||
- [SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md](./SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md):历史事故记录,保留旧发布包执行 `spacetime publish` 遇到 `403 Forbidden` 的身份根因;当前人工命令禁止使用 `spacetime --root-dir`,CI/CD 脚本内部受控用法除外。
|
||||
- [SPACETIMEDB_TABLE_CATALOG.md](./SPACETIMEDB_TABLE_CATALOG.md):持续维护当前 SpacetimeDB 表目录,按领域说明每张表的作用、字段结构、索引和常用 `spacetime sql` 查询模板。
|
||||
- [RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md](./RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md):记录开局场景与普通场景复用同一场景展示解析服务,修复列表幕缩略图和详情幕背景预览图片不一致的问题。
|
||||
- [FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md](./FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md):记录网站启动后首次加载约三分钟的前端根因,收口 `RouteImageReadyGate` 首屏图片门控和 Vite dev server 无关文件监听范围。
|
||||
@@ -112,7 +141,7 @@
|
||||
- [CREATION_WORK_SHELF_UNIFICATION_2026-04-25.md](./CREATION_WORK_SHELF_UNIFICATION_2026-04-25.md):冻结创作中心作品货架统一视图模型,先在前端归一 RPG、大鱼、拼图 works 的展示字段、筛选状态和卡片动作语义,不新增后端聚合接口。
|
||||
- [PUZZLE_BIG_FISH_DRAFT_PROGRESS_AND_ASSET_CHAIN_2026-04-25.md](./PUZZLE_BIG_FISH_DRAFT_PROGRESS_AND_ASSET_CHAIN_2026-04-25.md):冻结拼图与大鱼吃小鱼点击生成草稿后进入独立进度页,并一次性生成草稿、图片与动作资产的前端编排边界。
|
||||
- [BIG_FISH_DIRECTION_TOUCH_CONTROL_2026-04-24.md](./BIG_FISH_DIRECTION_TOUCH_CONTROL_2026-04-24.md):记录大鱼吃小鱼从固定摇杆改为屏幕首触点方向控制,并要求本地直达局在未操作时保持对象运动。
|
||||
- [RUST_WORKSPACE_DEFAULT_BUILD_SCOPE_FIX_2026-04-25.md](./RUST_WORKSPACE_DEFAULT_BUILD_SCOPE_FIX_2026-04-25.md):记录 `server-rs` 无参数 `cargo build` 链接 `spacetime-module` 失败的根因,并冻结默认只构建原生 `api-server`、模块产物继续走 `spacetime build` 的命令边界。
|
||||
- [RUST_WORKSPACE_DEFAULT_BUILD_SCOPE_FIX_2026-04-25.md](./RUST_WORKSPACE_DEFAULT_BUILD_SCOPE_FIX_2026-04-25.md):记录 `server-rs` 无参数 `cargo build` 链接 `spacetime-module` 失败的根因,并冻结默认只构建原生 `api-server`、模块产物继续走 `spacetime publish --build-options="--debug"` 或发布脚本的命令边界。
|
||||
- [BIG_FISH_DIRECT_ROUTE_PLAYGROUND_2026-04-24.md](./BIG_FISH_DIRECT_ROUTE_PLAYGROUND_2026-04-24.md):记录 `/big-fish` 大鱼吃小鱼玩法直达入口,明确复用现有 `BigFishRuntimeShell` 和本地占位运行态的调试边界。
|
||||
- [PUZZLE_DIRECT_ROUTE_PLAYGROUND_2026-04-24.md](./PUZZLE_DIRECT_ROUTE_PLAYGROUND_2026-04-24.md):记录 `/puzzle` 拼图玩法直达入口,明确复用现有 `PuzzleRuntimeShell` 和本地占位图运行态的调试边界。
|
||||
- [FRONTEND_INDEPENDENT_PAGE_ROUTES_2026-04-25.md](./FRONTEND_INDEPENDENT_PAGE_ROUTES_2026-04-25.md):记录平台入口、RPG 创作、拼图创作和大鱼吃小鱼创作各页面的独立前端路径,以及与 `/puzzle`、`/big-fish` 调试直达入口的边界。
|
||||
@@ -151,6 +180,7 @@
|
||||
- [PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](./PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md):冻结 Rust `api-server + module-auth + platform-auth` 接入真实阿里云短信 provider 的 crate 边界、发送与校验职责、配置项和错误语义。
|
||||
- [PHONE_SMS_ALIYUN_RESPONSE_FIELD_MAPPING_FIX_2026-04-23.md](./PHONE_SMS_ALIYUN_RESPONSE_FIELD_MAPPING_FIX_2026-04-23.md):记录 Rust `platform-auth` 把阿里云 PascalCase 响应字段误判成空值的问题根因,并冻结字段映射修复与回归标准。
|
||||
- [PHONE_SMS_SEND_CODE_OBSERVABILITY_FIX_2026-04-23.md](./PHONE_SMS_SEND_CODE_OBSERVABILITY_FIX_2026-04-23.md):冻结手机号验证码发送链路的日志补强口径,确保 `api-server`、`module-auth`、`platform-auth` 能直接暴露发送前后与错误分类关键字段。
|
||||
- [PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md](./PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md):记录真实短信 provider 返回 `UNKNOWN` / `biz.FREQUENCY` 时被误映射成登录 `500` 的根因,冻结 provider 配置错误 `503`、上游失败 `502` 的 HTTP 映射。
|
||||
- [PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md](./PHONE_SMS_DELIVERY_OBSERVABILITY_AND_RECEIPT_DESIGN_2026-04-22.md):冻结短信平台受理成功与最终送达状态的区分方式、追踪字段、送达回执接口和前端提示文案边界。
|
||||
- [PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md):冻结验证清单第一项“真实短信验证码链路”的本地启动、前端操作、日志观察点、通过标准与失败排查步骤。
|
||||
- [ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md](./ASSET_EXTERNAL_GENERATION_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md):冻结验证清单第四项“图片、视频、动作真实外部生成”的人工联调口径,明确哪些入口已接真实外部图片服务、哪些入口仍是 Stage 1 占位链,以及前端点击路径、日志观察点和通过标准。
|
||||
@@ -256,7 +286,7 @@
|
||||
- [CREATION_FLOW_CHAIN_REFACTOR_EXECUTION_PLAN_2026-04-21.md#93-工作包-c前端结果页与编辑器拆分](./CREATION_FLOW_CHAIN_REFACTOR_EXECUTION_PLAN_2026-04-21.md#93-%E5%B7%A5%E4%BD%9C%E5%8C%85-c%E5%89%8D%E7%AB%AF%E7%BB%93%E6%9E%9C%E9%A1%B5%E4%B8%8E%E7%BC%96%E8%BE%91%E5%99%A8%E6%8B%86%E5%88%86):记录工作包 C 已完成的结果页壳层拆分、编辑器目标分发与 mapper 收口、角色资产工坊 section/workflow 拆分,以及仍保留的阶段性 shared 实现边界。
|
||||
- [CREATION_PAGE_MOBILE_UI_FIX_2026-04-21.md](./CREATION_PAGE_MOBILE_UI_FIX_2026-04-21.md):创作页移动端底部 Tab、亮色主题 token 与滚动权责修复记录。
|
||||
- [RPG_FOUNDATION_DRAFT_EIGHT_ANCHOR_SEED_FIX_2026-04-25.md](./RPG_FOUNDATION_DRAFT_EIGHT_ANCHOR_SEED_FIX_2026-04-25.md):记录 RPG 创作 Agent session 八锚点进入 foundation draft seed 时被旧字段压缩的根因、修复和后续约束。
|
||||
- [TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md](./TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md):把外部仓库 TXT 模式完整迁入当前项目的冻结边界、模块映射、分阶段计划与验收清单。
|
||||
- [TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md](./TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md):旧 TXT 模式迁移方案的历史参考;视觉小说模板最新落地口径以 PRD [`AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`](../prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md) 为准,不再按外部平台工程完整迁入执行。
|
||||
- [AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md](./AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md):AI 生成角色形象与角色动画的技术路线。
|
||||
- [ALIYUN_NPC_IMAGE_ANIMATION_EXPERIMENT_2026-04-07.md](./ALIYUN_NPC_IMAGE_ANIMATION_EXPERIMENT_2026-04-07.md):面向编辑器的阿里云 NPC 形象与动作实验方案,按 4 条生成链路对比。
|
||||
- [PIXELMOTION_TECHNICAL_BREAKDOWN_2026-04-04.md](./PIXELMOTION_TECHNICAL_BREAKDOWN_2026-04-04.md):PixelMotion 产品形态与能力拆解。
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# 推荐页运行态鉴权失败隔离修复
|
||||
|
||||
日期:`2026-05-09`
|
||||
|
||||
## 背景
|
||||
|
||||
登录成功进入平台推荐页后,推荐页会自动加载一个公开作品并启动嵌入式运行态。实际联调中出现过:作品刚加载出来,前端又瞬间回到未登录状态;停留在其他页面,或推荐页没有成功加载出作品时不会复现。
|
||||
|
||||
后续复测又发现:登录成功后,从推荐页点进拼图公开作品详情并启动完整拼图运行态,也可能在开局或通关后瞬间退回未登录。两类现象的底层问题一致,都是玩法/展示层局部请求把 `401` 扩散成全局鉴权事件。
|
||||
|
||||
## 根因
|
||||
|
||||
推荐页首屏的作品运行态启动是后台自动副作用,不是用户主动点击的账号操作。它会触发多条受保护请求,例如:
|
||||
|
||||
1. 拼图、抓大鹅、方洞挑战、视觉小说的 `start run`。
|
||||
2. 大鱼吃小鱼的 `start run` 与游玩记录上报。
|
||||
3. 视觉小说运行前的作品详情读取。
|
||||
|
||||
这些请求一旦遇到本地代理错配、后端短暂不可用或 token 刷新失败,原请求层会按普通受保护请求处理 `401`,清空 access token 并广播全局鉴权变更。`AuthGate` 收到事件后重新 hydrate,于是当前用户界面被切回未登录态。
|
||||
|
||||
再次复测确认还有更深一层根因:即使单个业务请求显式传了 `clearAuthOnUnauthorized: false`,`refreshAccessToken()` 自身在 refresh 失败时也会先静默清空本地 access token。这样局部请求可能没有广播事件,却已经把本地凭证掏空;后续任意一次默认鉴权探测或 `AuthGate` hydrate 都会变成未登录。
|
||||
|
||||
推荐页进入公开拼图作品后还会伴随平台侧私有投影刷新,例如存档列表、浏览历史、个人看板和作品架列表。这些请求用于页面展示与局部缓存同步,不是账号会话权威;其中任意一个 401 都不应把整站登录态改写为未登录。
|
||||
|
||||
推荐页里还有一类更隐蔽的触发点:`ResolvedAssetImage` / `useResolvedAssetReadUrl` 在挂载时会请求 `/api/assets/read-url` 给 generated 私有图片换签。它本质上也是展示层后台请求,若按普通受保护请求处理 `401`,同样会把一次图片换签失败放大成全局掉线。
|
||||
|
||||
公开拼图作品的完整运行态还会在用户进入作品后自动发起 `startPuzzleRun`,通关后自动 `submitPuzzleLeaderboard`,点击下一关时 `advancePuzzleNextLevel`。这些请求属于当前玩法的运行态同步,失败时应该落到当前拼图错误态;它们不能清空全局 access token,也不能触发 `AuthGate` 重新 hydrate。
|
||||
|
||||
## 修复
|
||||
|
||||
本次把推荐页自动运行态请求定义为“卡片级后台请求”:
|
||||
|
||||
1. `apiClient` 增加 `authImpact: 'global' | 'local'` 策略,并导出 `BACKGROUND_AUTH_REQUEST_OPTIONS`。`local` 请求统一跳过 refresh,不清空 token,不广播 `AUTH_STATE_EVENT`。
|
||||
2. `refreshAccessToken()` 不再自行清空 token;只有 `refreshStoredAccessToken()` 这类全局会话恢复入口和默认全局请求策略能决定清 token。
|
||||
3. 推荐页嵌入式运行态请求统一使用 `BACKGROUND_AUTH_REQUEST_OPTIONS`。
|
||||
3. 推荐页自动启动作品前必须满足 `canReadProtectedData`,避免 `AuthGate` 仍在恢复阶段就提前发起受保护写请求。
|
||||
4. generated 图片换签请求同样使用局部后台鉴权选项并跳过 refresh,失败只让当前图片为空,不触发全局登录态清理。
|
||||
5. 公开拼图作品进入完整运行态后,把本次 run 标记为 `isolated` 鉴权模式;开局、重开、排行榜提交和下一关推进都沿用局部鉴权选项。
|
||||
6. 平台 bootstrap 的私有投影读写,包括个人看板、私有作品架、创作作品列表、浏览历史写入和存档列表刷新,也统一作为局部后台请求处理。
|
||||
7. Remix、发布、点赞、账号设置、退出登录等真正账号动作继续保留默认全局鉴权处理。
|
||||
|
||||
## 验证
|
||||
|
||||
1. `npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.ts`
|
||||
2. `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"`
|
||||
3. `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"`
|
||||
4. `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"`
|
||||
5. `npm run typecheck`
|
||||
6. `npm run check:encoding`
|
||||
|
||||
## 关联文件
|
||||
|
||||
1. `src/services/apiClient.ts`
|
||||
2. `src/services/rpg-runtime/rpgRuntimeRequest.ts`
|
||||
3. `src/services/rpg-creation/rpgCreationRuntimeClient.ts`
|
||||
4. `src/components/rpg-entry/useRpgEntryBootstrap.ts`
|
||||
5. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
6. `src/services/*-runtime/*RuntimeClient.ts`
|
||||
7. `src/services/visual-novel-works/visualNovelWorksClient.ts`
|
||||
@@ -42,4 +42,4 @@ Your account has not activated web search.
|
||||
2. 降级重试请求体不再包含 `tools` / `web_search`。
|
||||
3. `GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=false` 时,foundation draft 全流程直接不带搜索工具。
|
||||
4. `cargo test -p api-server custom_world_foundation_draft --manifest-path server-rs/Cargo.toml` 通过。
|
||||
5. 修改后按项目约束使用 `npm run api-server:maincloud` 重启后端。
|
||||
5. 修改后按项目约束使用 `npm run api-server` 重启后端。
|
||||
|
||||
@@ -7,7 +7,7 @@ RPG 创作链路里有两类正式图片资产需要统一模型:
|
||||
1. 角色主图候选生成。
|
||||
2. 场景幕背景图生成。
|
||||
|
||||
旧实现中角色主图默认使用 `wan2.7-image-pro`,场景图根据是否有参考图分别使用 DashScope 文生图与图生图模型。拼图链路已经接入 APIMart 的 OpenAI 兼容 `/images/generations`,并以 `gpt-image-2` 作为默认图片模型,因此本次 RPG 图片迁移复用同一类服务端配置与请求口径。
|
||||
旧实现中角色主图默认使用 `wan2.7-image-pro`,场景图根据是否有参考图分别使用 DashScope 文生图与图生图模型。拼图链路已经接入 GPT-image-2 图片生成,因此本次 RPG 图片迁移复用同一类服务端配置与请求口径。2026-05-09 起,GPT-image-2 图片生成上游统一迁移到 VectorEngine,具体接口以 `VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md` 为准。
|
||||
|
||||
## 落地范围
|
||||
|
||||
@@ -26,9 +26,9 @@ RPG 创作链路里有两类正式图片资产需要统一模型:
|
||||
服务端使用:
|
||||
|
||||
```text
|
||||
POST {APIMART_BASE_URL}/images/generations
|
||||
Authorization: Bearer {APIMART_API_KEY}
|
||||
model = gpt-image-2
|
||||
POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations
|
||||
Authorization: Bearer {VECTOR_ENGINE_API_KEY}
|
||||
model = gpt-image-2-all
|
||||
```
|
||||
|
||||
请求体统一包含:
|
||||
@@ -37,14 +37,14 @@ model = gpt-image-2
|
||||
2. `prompt`
|
||||
3. `n`
|
||||
4. `size`
|
||||
5. 有参考图时增加 `image_urls`
|
||||
5. 有参考图时增加 `image`
|
||||
|
||||
尺寸归一规则:
|
||||
|
||||
1. `1024*1024`、`1024x1024`、`1:1` -> `1:1`
|
||||
2. `1280*720`、`1600*900`、`16:9` -> `16:9`
|
||||
1. `1024*1024`、`1024x1024`、`1:1` -> `1024x1024`
|
||||
2. `1280*720`、`1600*900`、`16:9` -> `1536x1024`
|
||||
|
||||
响应解析兼容同步 `data[].url`、`data[].b64_json` 与异步 `task_id` / `GET /tasks/{task_id}` 结构。
|
||||
响应解析同步 `data[].url` 与 `data[].b64_json`;VectorEngine GPT-image-2-all 当前不再使用 APIMart 异步 `task_id` / `GET /tasks/{task_id}` 结构。
|
||||
|
||||
## 非范围
|
||||
|
||||
@@ -55,22 +55,22 @@ model = gpt-image-2
|
||||
|
||||
## 配置
|
||||
|
||||
本次复用已有 APIMart 配置:
|
||||
本次复用 VectorEngine 图片配置:
|
||||
|
||||
```text
|
||||
APIMART_BASE_URL=https://api.apimart.ai/v1
|
||||
APIMART_API_KEY=...
|
||||
APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000
|
||||
VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai
|
||||
VECTOR_ENGINE_API_KEY=...
|
||||
VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=180000
|
||||
```
|
||||
|
||||
`APIMART_API_KEY` 缺失时,角色主图与场景图返回 `SERVICE_UNAVAILABLE`,`details.provider = "apimart"`。
|
||||
`VECTOR_ENGINE_API_KEY` 缺失时,角色主图与场景图返回 `SERVICE_UNAVAILABLE`,`details.provider = "vector-engine"`。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 角色主图生成请求上游 `model` 为 `gpt-image-2`。
|
||||
2. 场景图生成请求上游 `model` 为 `gpt-image-2`。
|
||||
1. 角色主图生成请求上游 `model` 为 `gpt-image-2-all`,且不携带 `official_fallback`。
|
||||
2. 场景图生成请求上游 `model` 为 `gpt-image-2-all`,且不携带 `official_fallback`。
|
||||
3. 旧前端或历史草稿传 `wan2.7-image-pro` 时不会回退旧模型。
|
||||
4. 场景参考图生成仍能把参考图 Data URL 放入 `image_urls`。
|
||||
4. 场景参考图生成仍能把参考图 Data URL 放入 `image`。
|
||||
5. 角色主图生成后仍执行原有 PNG 透明背景处理与 OSS 写入。
|
||||
6. `cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml` 通过。
|
||||
7. `cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml` 通过。
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# RPG 聊天退出后继续冒险过场方案(2026-05-03)
|
||||
|
||||
## 1. 目标
|
||||
|
||||
玩家退出 NPC 聊天后点击“继续冒险”,不能直接瞬间切到下一幕或下一场景。继续冒险必须先完成一段清晰的角色退场与入场演出,再让新对面角色主动开启对话。
|
||||
|
||||
## 2. 时序约束
|
||||
|
||||
点击“继续冒险”后的顺序固定为:
|
||||
|
||||
1. 保持旧场景画面,隐藏当前场景对面的所有角色。
|
||||
2. 主角色与同行角色播放行走动画,向右走出屏幕。
|
||||
3. 点击后可以先更新真实 `gameState/currentStory`,但画布继续使用过场模型缓存的旧可见态;退场完成前不得把新幕画面展示出来。
|
||||
4. 新场景或新幕画面展示后,主角色从左侧走到默认站位。
|
||||
5. 新场景对面角色从屏幕左侧走入到指定对面站位。
|
||||
6. 入场完成后,如果后续选项里存在 `npc_preview_talk` 或 `npc_chat`,自动执行该选项,直接开启主角色与对面角色的对话。
|
||||
|
||||
## 3. 代码落点
|
||||
|
||||
1. `src/hooks/rpg-runtime-story/choiceActions.ts`
|
||||
- 点击 `story_continue_adventure` 时只提交延迟状态与选项,不直接进入对话。
|
||||
- 若延迟故事标记了自动执行,则把目标 option 放到新的 `deferredAutoChoice`。
|
||||
|
||||
2. `src/components/rpg-runtime-shell/useRpgSceneTransitionModel.ts`
|
||||
- `story_continue_adventure` 也纳入 `content-change` 过场。
|
||||
- 入场动画结束后触发 `deferredAutoChoice`,避免在角色尚未走到位前开聊。
|
||||
- 自动触发时通过最新回调读取当前运行态,避免计时器拿到点击“继续冒险”前的旧状态。
|
||||
|
||||
3. `src/components/game-canvas/GameCanvasEntityLayer.tsx`
|
||||
- 退场期隐藏旧对面角色。
|
||||
- 入场期让新对面角色从左侧走入到右侧指定站位。
|
||||
- 对面角色入场期使用移动动画,完成后恢复 idle 与对话气泡。
|
||||
|
||||
4. `src/components/rpg-runtime-shell/useRpgRuntimeShellViewModel.ts`
|
||||
- `story_continue_adventure` 只要携带 `deferredRuntimeState` 或 `deferredAutoChoice`,就先进入过场,再交给 story choice 处理真实状态。
|
||||
|
||||
## 4. 验收标准
|
||||
|
||||
1. 退出 NPC 聊天后点击“继续冒险”,不会在同一帧瞬间切换到下一幕对话。
|
||||
2. 退场时旧对面角色不可见,主角色向右走出画面。
|
||||
3. 入场时新对面角色从左侧进入右侧站位。
|
||||
4. 入场完成后自动进入新对面角色对话。
|
||||
5. 移动端与桌面端都不新增说明类 UI 文案,只保留游戏内演出。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user