Prune stale docs and update .hermes content
Delete a large set of outdated documentation (many files under docs/ and .hermes/plans/, including audits, design, prd, technical, planning, assets, and todos). Update and consolidate .hermes content: refresh shared-memory pages (decision-log, development-workflow, document-map, pitfalls, project-overview, team-conventions) and several skills/references under .hermes/skills. Also modify AGENTS.md, README.md, UI_CODING_STANDARD.md, docs/README.md and .encoding-check-ignore. Purpose: clean up stale planning/audit material and keep current hermes documentation and related top-level docs in sync.
@@ -1,11 +1,6 @@
|
|||||||
# Temporary baseline for legacy files that already contain broken text.
|
# Temporary baseline for legacy files that already contain broken text.
|
||||||
# Remove a path from this list as soon as the file is repaired.
|
# Remove a path from this list as soon as the file is repaired.
|
||||||
|
|
||||||
docs/audits/text/GAME_EDITOR_PRESET_TEXT_AUDIT_2026-03-29.md
|
|
||||||
docs/audits/text/GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-03-30_CONTINUED.md
|
|
||||||
docs/audits/text/GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-03-31.md
|
|
||||||
docs/audits/text/GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-04-01.md
|
|
||||||
docs/audits/text/GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-04-02.md
|
|
||||||
src/components/AdventurePanel.tsx
|
src/components/AdventurePanel.tsx
|
||||||
src/data/customWorldCharacterLoadout.ts
|
src/data/customWorldCharacterLoadout.ts
|
||||||
dist_check_monster_position/**
|
dist_check_monster_position/**
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
- 开发前先阅读本目录下与任务相关的记忆文件;开发后如产生稳定知识,更新对应文档。
|
- 开发前先阅读本目录下与任务相关的记忆文件;开发后如产生稳定知识,更新对应文档。
|
||||||
- 后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`,便于团队跨目录检索。
|
- 后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`,便于团队跨目录检索。
|
||||||
- 若本目录内容与 `docs/` 或代码事实冲突,以当前代码和最新 `docs/` 为准,并同步修正过期记忆。
|
- 若本目录内容与 `docs/` 或代码事实冲突,以当前代码和最新 `docs/` 为准,并同步修正过期记忆。
|
||||||
|
- 阶段性计划、一次性 TODO 和已关闭实验不再长期沉淀为仓库文档;仍有效内容应合并进 `docs/` 当前融合文档或 `.hermes/shared-memory/`。
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
@@ -24,8 +25,6 @@
|
|||||||
│ ├─ decision-log.md # 长期决策记录
|
│ ├─ decision-log.md # 长期决策记录
|
||||||
│ ├─ pitfalls.md # 踩坑与排障记录
|
│ ├─ pitfalls.md # 踩坑与排障记录
|
||||||
│ └─ handoff-template.md # 任务交接模板
|
│ └─ handoff-template.md # 任务交接模板
|
||||||
├─ plans/ # 阶段性计划与实施方案
|
|
||||||
├─ todos/ # 已定稿但尚未执行的共享 TODO 计划
|
|
||||||
├─ skills/ # 仓库级 Hermes skills
|
├─ skills/ # 仓库级 Hermes skills
|
||||||
└─ plugins/ # 仓库级 Hermes plugins(需显式启用项目 plugin)
|
└─ plugins/ # 仓库级 Hermes plugins(需显式启用项目 plugin)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,724 +0,0 @@
|
|||||||
# 埋点系统新增周、月、季、年维度映射表计划
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
在 Genarrative 的埋点/统计系统中新增“周、月、季、年”维度映射表,让后续统计查询可以按不同时间粒度稳定聚合,而不是只依赖运行时临时计算日期范围。
|
|
||||||
|
|
||||||
本计划只做设计与落地步骤,不直接修改业务代码。
|
|
||||||
|
|
||||||
## 当前上下文与初步发现
|
|
||||||
|
|
||||||
1. 当前仓库根目录为 `/home/dsk/workspace/Genarrative`。
|
|
||||||
2. 远端更新后已定位到当前真实埋点/任务系统:
|
|
||||||
- 原始埋点表:`tracking_event`
|
|
||||||
- 日聚合投影表:`tracking_daily_stat`
|
|
||||||
- 任务配置表:`profile_task_config`
|
|
||||||
- 任务进度表:`profile_task_progress`
|
|
||||||
- 领奖记录表:`profile_task_reward_claim`
|
|
||||||
3. 相关文件:
|
|
||||||
- `server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
|
||||||
- `server-rs/crates/module-runtime/src/domain.rs`
|
|
||||||
- `server-rs/crates/module-runtime/src/application.rs`
|
|
||||||
- `server-rs/crates/api-server/src/runtime_profile.rs`
|
|
||||||
- `apps/admin-web/src/pages/AdminTaskConfigPage.tsx`
|
|
||||||
- `apps/admin-web/src/api/adminApiTypes.ts`
|
|
||||||
- `apps/admin-web/src/config/trackingEventDefinitions.ts`
|
|
||||||
- `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
|
|
||||||
- `docs/tracking/TRACKING_QUERY_PLAYBOOK_2026-05-03.md`
|
|
||||||
4. 当前 `tracking_event` 是明细表,`tracking_daily_stat` 是统一日汇总表,不按范围拆表;它通过 `event_key + scope_kind + scope_id + day_key` 区分不同聚合桶。
|
|
||||||
5. 当前任务系统是“个人任务系统”,首版任务配置均面向用户维度;后台暴露“埋点范围”选择会导致运营误配。
|
|
||||||
6. 已决定采用任务配置方案 B:
|
|
||||||
- 后台任务配置不再让运营手动选择埋点范围。
|
|
||||||
- 后端个人任务配置统一限制为 `RuntimeTrackingScopeKind::User`。
|
|
||||||
- 若 API 收到非 `user` 的 `scopeKind`,应拒绝或兼容忽略但最终落库必须为 `User`;推荐直接拒绝并返回清晰错误。
|
|
||||||
7. 已发现一个需要纳入本计划修复的语义问题:`profile_task_tracking_scope_id` 里 `RuntimeTrackingScopeKind::Work` 当前返回 `user_id`,这不符合 work 维度语义。即使个人任务暂不支持 work,也应避免错误映射继续存在。
|
|
||||||
8. 当前日期桶使用北京时间自然日:`day_key = floor((occurred_at_micros + 8h) / 1d)`;周/月/季/年映射表应优先沿用这一业务日口径,除非产品另行确认。
|
|
||||||
9. 如果新增正式后端表,需要同步:
|
|
||||||
- 表定义
|
|
||||||
- reducer/procedure
|
|
||||||
- migration.rs
|
|
||||||
- 生成绑定
|
|
||||||
- spacetime-client facade
|
|
||||||
- shared-contracts / API DTO,如有接口暴露
|
|
||||||
|
|
||||||
## 关键假设
|
|
||||||
|
|
||||||
在未定位现有埋点模块前,先按以下假设规划:
|
|
||||||
|
|
||||||
1. 当前已有某种事件明细表或统计事实表,例如:
|
|
||||||
- `telemetry_event`
|
|
||||||
- `analytics_event`
|
|
||||||
- `metric_event`
|
|
||||||
- `narrative_telemetry`
|
|
||||||
- 或类似命名
|
|
||||||
2. 新增的“映射表”用于把具体日期或事件时间映射到时间维度 bucket。
|
|
||||||
3. 映射维度包括:
|
|
||||||
- 周:week
|
|
||||||
- 月:month
|
|
||||||
- 季:quarter
|
|
||||||
- 年:year
|
|
||||||
4. 已明确选择“一张通用日期维度映射表”方案:`analytics_date_dimension`。
|
|
||||||
5. 统计口径需要明确:
|
|
||||||
- 周从周一还是周日开始
|
|
||||||
- 是否使用 ISO week
|
|
||||||
- 季度是自然季度还是财务季度
|
|
||||||
- 时区使用 UTC 还是业务本地时区
|
|
||||||
|
|
||||||
## 推荐设计方向
|
|
||||||
|
|
||||||
### 方案 A:单一时间维度映射表,推荐
|
|
||||||
|
|
||||||
新增一张日历维度表,每一行对应一个自然日,并包含它归属的周、月、季、年。
|
|
||||||
|
|
||||||
表概念:
|
|
||||||
|
|
||||||
```text
|
|
||||||
analytics_date_dimension
|
|
||||||
```
|
|
||||||
|
|
||||||
建议字段:
|
|
||||||
|
|
||||||
```text
|
|
||||||
date_key string 例如 2026-05-04
|
|
||||||
calendar_date string 真实日期,按 YYYY-MM-DD 存储
|
|
||||||
weekday u8 1-7 或 0-6,需要统一约定
|
|
||||||
iso_week_key string 例如 2026-W19
|
|
||||||
week_start_date_key string 例如 2026-05-04
|
|
||||||
week_end_date_key string 例如 2026-05-10
|
|
||||||
month_key string 例如 2026-05
|
|
||||||
month_start_date_key string
|
|
||||||
month_end_date_key string
|
|
||||||
quarter_key string 例如 2026-Q2
|
|
||||||
year_key string 例如 2026
|
|
||||||
created_at timestamp/string
|
|
||||||
updated_at timestamp/string
|
|
||||||
```
|
|
||||||
|
|
||||||
优点:
|
|
||||||
|
|
||||||
- 一张表即可支持日、周、月、季、年映射。
|
|
||||||
- 便于后续新增半月、财年、节假日、自然周等维度。
|
|
||||||
- 查询逻辑简单:事件日期 join/date_key 映射到目标粒度。
|
|
||||||
- 数据量很小,按 20 年也只有约 7300 行。
|
|
||||||
|
|
||||||
缺点:
|
|
||||||
|
|
||||||
- 需要在事件时间写入或统计查询时把 timestamp 归一为 date_key。
|
|
||||||
- 如果要支持多时区,可能需要增加 timezone 字段或多套 calendar。
|
|
||||||
|
|
||||||
### 方案 B:四张独立映射表
|
|
||||||
|
|
||||||
分别新增:
|
|
||||||
|
|
||||||
```text
|
|
||||||
analytics_week_dimension
|
|
||||||
analytics_month_dimension
|
|
||||||
analytics_quarter_dimension
|
|
||||||
analytics_year_dimension
|
|
||||||
```
|
|
||||||
|
|
||||||
优点:
|
|
||||||
|
|
||||||
- 每个粒度表结构更纯粹。
|
|
||||||
- 查询时可以直接针对目标粒度表。
|
|
||||||
|
|
||||||
缺点:
|
|
||||||
|
|
||||||
- 表更多,维护复杂。
|
|
||||||
- 日期归属关系仍然需要额外处理。
|
|
||||||
- 容易出现周/月/季/年口径漂移。
|
|
||||||
|
|
||||||
### 最终选择
|
|
||||||
|
|
||||||
本计划采用方案 A:单一 `analytics_date_dimension` 日期维表,而不是四张独立映射表。
|
|
||||||
|
|
||||||
如业务未来明确要求“周、月、季、年各自有独立映射表”,也应优先在日期维表基础上派生视图或物化派生表,而不是一开始拆成四张重复表。
|
|
||||||
|
|
||||||
## 后端设计建议
|
|
||||||
|
|
||||||
### 1. 明确埋点领域归属
|
|
||||||
|
|
||||||
先定位现有埋点模块。如果没有独立模块,建议新增或归入:
|
|
||||||
|
|
||||||
```text
|
|
||||||
server-rs/crates/module-analytics/
|
|
||||||
```
|
|
||||||
|
|
||||||
或如果当前项目已有 telemetry 命名,则保持已有命名,例如:
|
|
||||||
|
|
||||||
```text
|
|
||||||
server-rs/crates/module-telemetry/
|
|
||||||
```
|
|
||||||
|
|
||||||
领域层职责:
|
|
||||||
|
|
||||||
- 时间粒度定义
|
|
||||||
- date_key/week_key/month_key/quarter_key/year_key 生成规则
|
|
||||||
- 时间维度校验
|
|
||||||
- 事件聚合查询输入的纯规则
|
|
||||||
|
|
||||||
不应包含:
|
|
||||||
|
|
||||||
- SpacetimeDB 表读写
|
|
||||||
- Axum handler
|
|
||||||
- HTTP response
|
|
||||||
|
|
||||||
### 2. SpacetimeDB 表设计
|
|
||||||
|
|
||||||
在 `spacetime-module` 中新增时间维度表。
|
|
||||||
|
|
||||||
建议表名:
|
|
||||||
|
|
||||||
```text
|
|
||||||
analytics_date_dimension
|
|
||||||
```
|
|
||||||
|
|
||||||
建议主键:
|
|
||||||
|
|
||||||
```text
|
|
||||||
date_key
|
|
||||||
```
|
|
||||||
|
|
||||||
建议索引:
|
|
||||||
|
|
||||||
```text
|
|
||||||
iso_week_key
|
|
||||||
month_key
|
|
||||||
quarter_key
|
|
||||||
year_key
|
|
||||||
```
|
|
||||||
|
|
||||||
如果 SpacetimeDB 表定义已有统一命名规范,应按现有规范命名。
|
|
||||||
|
|
||||||
### 3. 初始化/补全 reducer
|
|
||||||
|
|
||||||
新增 reducer 或内部 procedure,用于生成指定日期范围内的维度数据。
|
|
||||||
|
|
||||||
建议能力:
|
|
||||||
|
|
||||||
```text
|
|
||||||
seed_analytics_date_dimensions(start_date, end_date)
|
|
||||||
ensure_analytics_date_dimension_for_date(date_key)
|
|
||||||
ensure_analytics_date_dimensions_for_range(start_date, end_date)
|
|
||||||
```
|
|
||||||
|
|
||||||
设计原则:
|
|
||||||
|
|
||||||
- 可幂等执行。
|
|
||||||
- 已存在 date_key 时不重复插入。
|
|
||||||
- 支持一次补一段日期。
|
|
||||||
- 避免一次补太大范围导致事务过重。
|
|
||||||
- 生产环境建议按年份或月份分批。
|
|
||||||
|
|
||||||
### 4. 事件表和映射表关系
|
|
||||||
|
|
||||||
如果事件表目前只有 timestamp,建议新增或计算出:
|
|
||||||
|
|
||||||
```text
|
|
||||||
event_date_key
|
|
||||||
```
|
|
||||||
|
|
||||||
可选策略:
|
|
||||||
|
|
||||||
1. 写入事件时同步写 `event_date_key`。
|
|
||||||
2. 查询统计时从 timestamp 临时计算 date_key。
|
|
||||||
3. 后台迁移为历史事件补 `event_date_key`。
|
|
||||||
|
|
||||||
推荐:
|
|
||||||
|
|
||||||
- 新事件写入时保存 `event_date_key`。
|
|
||||||
- 历史事件通过批量迁移 reducer 分批补齐。
|
|
||||||
|
|
||||||
### 5. 聚合查询设计
|
|
||||||
|
|
||||||
支持按粒度查询时,API 或 facade 可以接收:
|
|
||||||
|
|
||||||
```text
|
|
||||||
granularity = day | week | month | quarter | year
|
|
||||||
start_date
|
|
||||||
end_date
|
|
||||||
metric/event_name
|
|
||||||
filters
|
|
||||||
```
|
|
||||||
|
|
||||||
内部根据粒度选择 bucket key:
|
|
||||||
|
|
||||||
```text
|
|
||||||
day -> date_key
|
|
||||||
week -> iso_week_key 或 week_key
|
|
||||||
month -> month_key
|
|
||||||
quarter -> quarter_key
|
|
||||||
year -> year_key
|
|
||||||
```
|
|
||||||
|
|
||||||
返回结构建议统一:
|
|
||||||
|
|
||||||
```text
|
|
||||||
bucket_key
|
|
||||||
bucket_label
|
|
||||||
bucket_start_date
|
|
||||||
bucket_end_date
|
|
||||||
value
|
|
||||||
```
|
|
||||||
|
|
||||||
## 可能涉及的文件
|
|
||||||
|
|
||||||
由于当前尚未定位明确埋点模块,以下是预计文件范围。
|
|
||||||
|
|
||||||
### 必查文件/目录
|
|
||||||
|
|
||||||
```text
|
|
||||||
./Genarrative/server-rs/crates/
|
|
||||||
./Genarrative/server-rs/crates/spacetime-module/src/
|
|
||||||
./Genarrative/server-rs/crates/spacetime-client/src/
|
|
||||||
./Genarrative/server-rs/crates/shared-contracts/src/
|
|
||||||
./Genarrative/server-rs/crates/api-server/src/
|
|
||||||
./Genarrative/packages/shared/src/contracts/
|
|
||||||
./Genarrative/src/services/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 可能新增文件
|
|
||||||
|
|
||||||
如果采用 analytics 命名:
|
|
||||||
|
|
||||||
```text
|
|
||||||
server-rs/crates/module-analytics/src/domain.rs
|
|
||||||
server-rs/crates/module-analytics/src/commands.rs
|
|
||||||
server-rs/crates/module-analytics/src/application.rs
|
|
||||||
server-rs/crates/module-analytics/src/errors.rs
|
|
||||||
server-rs/crates/module-analytics/src/events.rs
|
|
||||||
server-rs/crates/shared-contracts/src/analytics.rs
|
|
||||||
server-rs/crates/spacetime-client/src/analytics.rs
|
|
||||||
server-rs/crates/api-server/src/analytics.rs
|
|
||||||
packages/shared/src/contracts/analytics.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
如果只是新增 SpacetimeDB 映射表且暂不暴露 API,则可能只需:
|
|
||||||
|
|
||||||
```text
|
|
||||||
server-rs/crates/spacetime-module/src/**
|
|
||||||
server-rs/crates/spacetime-module/src/migration.rs
|
|
||||||
server-rs/crates/spacetime-client/src/** # 如果查询会被 api-server 使用
|
|
||||||
```
|
|
||||||
|
|
||||||
## 详细实施步骤
|
|
||||||
|
|
||||||
### Step 1:复核现有埋点系统与任务配置链路
|
|
||||||
|
|
||||||
当前已定位真实链路,实施前再做一次只读复核,确认远端最新代码没有继续变化。
|
|
||||||
|
|
||||||
已知核心表:
|
|
||||||
|
|
||||||
```text
|
|
||||||
tracking_event # 原始埋点明细
|
|
||||||
tracking_daily_stat # 日聚合投影
|
|
||||||
profile_task_config # 个人任务配置
|
|
||||||
profile_task_progress # 个人任务进度
|
|
||||||
profile_task_reward_claim # 领奖记录
|
|
||||||
```
|
|
||||||
|
|
||||||
已知核心文件:
|
|
||||||
|
|
||||||
```text
|
|
||||||
server-rs/crates/spacetime-module/src/runtime/profile.rs
|
|
||||||
server-rs/crates/module-runtime/src/domain.rs
|
|
||||||
server-rs/crates/module-runtime/src/application.rs
|
|
||||||
server-rs/crates/api-server/src/runtime_profile.rs
|
|
||||||
apps/admin-web/src/pages/AdminTaskConfigPage.tsx
|
|
||||||
apps/admin-web/src/api/adminApiTypes.ts
|
|
||||||
apps/admin-web/src/config/trackingEventDefinitions.ts
|
|
||||||
docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md
|
|
||||||
docs/tracking/TRACKING_QUERY_PLAYBOOK_2026-05-03.md
|
|
||||||
```
|
|
||||||
|
|
||||||
重点确认:
|
|
||||||
|
|
||||||
1. `tracking_event` 是否仍包含 `event_key/scope_kind/scope_id/day_key/user_id/occurred_at`。
|
|
||||||
2. `tracking_daily_stat` 是否仍按 `event_key + scope_kind + scope_id + day_key` 生成 `stat_id`。
|
|
||||||
3. `profile_task_config` 是否仍包含 `scope_kind` 和 `sort_order`。
|
|
||||||
4. 后台 `AdminTaskConfigPage` 是否仍暴露“埋点范围”下拉。
|
|
||||||
5. `profile_task_tracking_scope_id` 中 `Work => user_id` 的错误映射是否仍存在。
|
|
||||||
|
|
||||||
### Step 1.5:先收紧个人任务配置的埋点范围,采用方案 B
|
|
||||||
|
|
||||||
在做周/月/季/年维度映射前,先修正个人任务配置边界,避免后续在错误配置模型上继续扩展。
|
|
||||||
|
|
||||||
目标行为:
|
|
||||||
|
|
||||||
```text
|
|
||||||
个人任务配置只支持用户维度埋点。
|
|
||||||
后台页面不再展示“埋点范围”。
|
|
||||||
后端不允许 profile_task_config 被写入 site/work/module 维度。
|
|
||||||
```
|
|
||||||
|
|
||||||
建议实现:
|
|
||||||
|
|
||||||
1. 前端隐藏 `AdminTaskConfigPage` 的“埋点范围”选择。
|
|
||||||
- 文件:`apps/admin-web/src/pages/AdminTaskConfigPage.tsx`
|
|
||||||
- 移除或隐藏:`scopeKinds` 下拉 UI。
|
|
||||||
- 保存请求仍可兼容传 `scopeKind: 'user'`,避免一次性改动 API contract。
|
|
||||||
2. 后端 upsert 校验 `scopeKind` 必须为 `RuntimeTrackingScopeKind::User`。
|
|
||||||
- 文件:`server-rs/crates/api-server/src/runtime_profile.rs`
|
|
||||||
- 或更底层:`server-rs/crates/module-runtime/src/domain.rs` / `server-rs/crates/spacetime-module/src/runtime/profile.rs` 的输入构造函数。
|
|
||||||
- 推荐在领域输入构造处兜底校验,API 层返回清晰错误。
|
|
||||||
3. 若暂不改 API DTO,则保持字段存在但限定值只能是 `user`。
|
|
||||||
- 文件:`apps/admin-web/src/api/adminApiTypes.ts`
|
|
||||||
- `AdminUpsertProfileTaskConfigRequest.scopeKind` 可保留,前端固定传 `user`。
|
|
||||||
4. 更新后台埋点定义注册表的语义:
|
|
||||||
- 文件:`apps/admin-web/src/config/trackingEventDefinitions.ts`
|
|
||||||
- 当前每个 event definition 包含 `scopeKind`,如果个人任务统一 `user`,可以保留为只读内部默认值;但不要让运营在页面改。
|
|
||||||
5. 更新技术文档:
|
|
||||||
- `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
|
|
||||||
- 明确“个人任务首版只支持用户维度埋点,后台不开放 scope_kind 配置”。
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
```text
|
|
||||||
后台任务配置页不再出现“埋点范围”选择。
|
|
||||||
保存 daily_login 后落库 scope_kind 仍为 User。
|
|
||||||
直接调用后台 upsert 接口传 site/work/module 时被拒绝,或最终不会落库为非 User;推荐拒绝。
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.6:修复 Work 范围错误返回 user_id 的语义问题
|
|
||||||
|
|
||||||
当前函数:
|
|
||||||
|
|
||||||
```text
|
|
||||||
server-rs/crates/spacetime-module/src/runtime/profile.rs
|
|
||||||
profile_task_tracking_scope_id(user_id, config)
|
|
||||||
```
|
|
||||||
|
|
||||||
当前问题:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
RuntimeTrackingScopeKind::Work => user_id.to_string()
|
|
||||||
```
|
|
||||||
|
|
||||||
这会把 work 维度错误映射为用户 ID。虽然个人任务将限制为 User,但保留这个分支会误导后续扩展。
|
|
||||||
|
|
||||||
推荐修复策略:
|
|
||||||
|
|
||||||
1. 对个人任务进度计算来说,`Work` 不应进入该函数。
|
|
||||||
2. 将 `profile_task_tracking_scope_id` 改为返回 `Result<String, String>` 或 `Option<String>`。
|
|
||||||
3. 对不支持的范围返回错误,而不是伪造 scope_id:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Site -> "site",如果个人任务仍不允许 site,则上游先拒绝
|
|
||||||
Module -> "profile",如果个人任务仍不允许 module,则上游先拒绝
|
|
||||||
User -> user_id
|
|
||||||
Work -> error: personal task progress does not support work scope without work_id
|
|
||||||
```
|
|
||||||
|
|
||||||
更严格的推荐:
|
|
||||||
|
|
||||||
```text
|
|
||||||
个人任务链路只接受 User。
|
|
||||||
Work/Site/Module 在 profile_task_progress_count 前就被拒绝。
|
|
||||||
profile_task_tracking_scope_id 只保留 User 分支,或者非 User 返回错误。
|
|
||||||
```
|
|
||||||
|
|
||||||
需要同步调整调用点:
|
|
||||||
|
|
||||||
```text
|
|
||||||
profile_task_progress_count
|
|
||||||
refresh_profile_task_progress
|
|
||||||
build_profile_task_center_snapshot
|
|
||||||
claim_profile_task_reward
|
|
||||||
```
|
|
||||||
|
|
||||||
避免因为函数返回 `Result` 后调用链未处理错误。
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
```text
|
|
||||||
不存在 Work => user_id 的映射。
|
|
||||||
个人任务配置非 User 时不会静默算出错误进度。
|
|
||||||
相关测试覆盖:User 正常;Work/Site/Module 被拒绝。
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2:确定时间口径
|
|
||||||
|
|
||||||
必须先确认:
|
|
||||||
|
|
||||||
1. 周维度是否使用 ISO week。
|
|
||||||
2. 周开始日是周一还是周日。
|
|
||||||
3. 月/季/年是否按自然日历。
|
|
||||||
4. 统计时区是 UTC、服务器时区,还是用户本地时区。
|
|
||||||
5. 跨年周如何命名,例如 `2025-W01` 可能开始于 2024 年末。
|
|
||||||
|
|
||||||
推荐默认:
|
|
||||||
|
|
||||||
```text
|
|
||||||
时区:UTC,除非产品明确要求中国时区
|
|
||||||
周:ISO week,周一开始
|
|
||||||
月:自然月
|
|
||||||
季:自然季度
|
|
||||||
年:自然年
|
|
||||||
```
|
|
||||||
|
|
||||||
如果业务面向国内用户,建议考虑:
|
|
||||||
|
|
||||||
```text
|
|
||||||
时区:Asia/Shanghai
|
|
||||||
周:周一开始
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3:设计 date dimension 表
|
|
||||||
|
|
||||||
设计字段和 key 格式,写入技术文档。
|
|
||||||
|
|
||||||
建议 key 格式:
|
|
||||||
|
|
||||||
```text
|
|
||||||
date_key: 2026-05-04
|
|
||||||
week_key: 2026-W19
|
|
||||||
month_key: 2026-05
|
|
||||||
quarter_key: 2026-Q2
|
|
||||||
year_key: 2026
|
|
||||||
```
|
|
||||||
|
|
||||||
注意:
|
|
||||||
|
|
||||||
- `week_key` 建议使用 ISO week-year,不一定等于 calendar year。
|
|
||||||
- `quarter_key` 使用 calendar year。
|
|
||||||
|
|
||||||
### Step 4:新增领域纯函数
|
|
||||||
|
|
||||||
在领域层或 shared-kernel 中实现纯函数:
|
|
||||||
|
|
||||||
```text
|
|
||||||
resolve_date_dimension(date, timezone) -> AnalyticsDateDimension
|
|
||||||
resolve_bucket_key(date_dimension, granularity) -> String
|
|
||||||
resolve_bucket_range(bucket_key, granularity) -> start/end date
|
|
||||||
```
|
|
||||||
|
|
||||||
要求:
|
|
||||||
|
|
||||||
- 有单元测试。
|
|
||||||
- 覆盖跨年周。
|
|
||||||
- 覆盖闰年 2 月。
|
|
||||||
- 覆盖季度边界。
|
|
||||||
|
|
||||||
### Step 5:新增 SpacetimeDB 表
|
|
||||||
|
|
||||||
在 `spacetime-module` 中新增表。
|
|
||||||
|
|
||||||
遵守约束:
|
|
||||||
|
|
||||||
- 新增表通常安全。
|
|
||||||
- 不修改已有表字段。
|
|
||||||
- 如果必须给已有事件表加 `event_date_key`,必须加在表定义末尾并提供 default。
|
|
||||||
- 若要补历史数据,使用 reducer 分批迁移。
|
|
||||||
|
|
||||||
### Step 6:新增 seed/ensure reducer
|
|
||||||
|
|
||||||
新增幂等 reducer:
|
|
||||||
|
|
||||||
```text
|
|
||||||
seed_analytics_date_dimensions(start_date, end_date)
|
|
||||||
ensure_analytics_date_dimension_for_date(date_key)
|
|
||||||
```
|
|
||||||
|
|
||||||
验证点:
|
|
||||||
|
|
||||||
- 重复执行不会重复插入。
|
|
||||||
- 日期范围非法时返回稳定错误。
|
|
||||||
- 单次范围过大时拒绝或分页。
|
|
||||||
|
|
||||||
### Step 7:接入事件写入链路
|
|
||||||
|
|
||||||
如果现有事件写入链路存在,新增:
|
|
||||||
|
|
||||||
```text
|
|
||||||
event_date_key
|
|
||||||
```
|
|
||||||
|
|
||||||
策略:
|
|
||||||
|
|
||||||
- 新事件写入时同步计算并保存。
|
|
||||||
- 写入前确保对应 date dimension 存在。
|
|
||||||
- 历史事件通过迁移 reducer 补齐。
|
|
||||||
|
|
||||||
如果暂不改事件表,也可以在查询阶段临时映射,但性能和一致性较差。
|
|
||||||
|
|
||||||
### Step 8:接入聚合查询
|
|
||||||
|
|
||||||
如已有统计接口,扩展请求参数:
|
|
||||||
|
|
||||||
```text
|
|
||||||
granularity: day | week | month | quarter | year
|
|
||||||
```
|
|
||||||
|
|
||||||
查询逻辑改为:
|
|
||||||
|
|
||||||
```text
|
|
||||||
事件/事实表
|
|
||||||
→ event_date_key
|
|
||||||
→ analytics_date_dimension
|
|
||||||
→ 取对应 bucket key
|
|
||||||
→ group by bucket key
|
|
||||||
```
|
|
||||||
|
|
||||||
返回 bucket 时包含:
|
|
||||||
|
|
||||||
```text
|
|
||||||
bucket_key
|
|
||||||
bucket_start_date
|
|
||||||
bucket_end_date
|
|
||||||
value
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 9:补 shared contracts 和前端 contracts
|
|
||||||
|
|
||||||
如果有 API 暴露,需要补:
|
|
||||||
|
|
||||||
```text
|
|
||||||
server-rs/crates/shared-contracts/src/analytics.rs
|
|
||||||
packages/shared/src/contracts/analytics.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
建议 DTO:
|
|
||||||
|
|
||||||
```text
|
|
||||||
AnalyticsGranularity = day | week | month | quarter | year
|
|
||||||
AnalyticsBucketMetric
|
|
||||||
AnalyticsMetricQueryRequest
|
|
||||||
AnalyticsMetricQueryResponse
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 10:补测试
|
|
||||||
|
|
||||||
测试范围:
|
|
||||||
|
|
||||||
1. 领域日期映射测试
|
|
||||||
2. SpacetimeDB reducer 幂等测试
|
|
||||||
3. API 查询维度测试
|
|
||||||
4. 历史事件迁移测试,如涉及
|
|
||||||
5. 跨边界日期测试
|
|
||||||
6. 个人任务配置 scope 限制测试
|
|
||||||
7. `Work => user_id` 错误映射回归测试
|
|
||||||
|
|
||||||
重点用例:
|
|
||||||
|
|
||||||
```text
|
|
||||||
2024-02-29 闰年
|
|
||||||
2025-12-29 ISO week 可能属于 2026-W01
|
|
||||||
2026-01-01 跨年周
|
|
||||||
2026-03-31 Q1 结束
|
|
||||||
2026-04-01 Q2 开始
|
|
||||||
2026-12-31 年末
|
|
||||||
```
|
|
||||||
|
|
||||||
任务配置重点用例:
|
|
||||||
|
|
||||||
```text
|
|
||||||
admin upsert daily_login + scopeKind=user -> 成功
|
|
||||||
admin upsert daily_login + scopeKind=site -> 失败,错误信息说明个人任务仅支持 user
|
|
||||||
admin upsert daily_login + scopeKind=module -> 失败
|
|
||||||
admin upsert daily_login + scopeKind=work -> 失败
|
|
||||||
任务中心读取 daily_login -> 按 User + 当前 user_id 查询进度
|
|
||||||
代码中不存在 Work => user_id 的静默映射
|
|
||||||
```
|
|
||||||
|
|
||||||
## 测试与验证命令
|
|
||||||
|
|
||||||
具体命令需在定位模块后确认。初步建议:
|
|
||||||
|
|
||||||
```text
|
|
||||||
npm run typecheck
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
后端如涉及 Rust:
|
|
||||||
|
|
||||||
```text
|
|
||||||
cargo test -p module-analytics
|
|
||||||
cargo test -p spacetime-module
|
|
||||||
cargo test -p api-server
|
|
||||||
```
|
|
||||||
|
|
||||||
涉及 API smoke:
|
|
||||||
|
|
||||||
```text
|
|
||||||
npm run api-server
|
|
||||||
```
|
|
||||||
|
|
||||||
然后验证:
|
|
||||||
|
|
||||||
```text
|
|
||||||
GET /healthz
|
|
||||||
```
|
|
||||||
|
|
||||||
涉及 SpacetimeDB schema:
|
|
||||||
|
|
||||||
- 需要生成绑定。
|
|
||||||
- 需要确认 migration.rs 对齐。
|
|
||||||
- 需要确认 publish 不触发不安全 schema 变更。
|
|
||||||
|
|
||||||
## 风险与权衡
|
|
||||||
|
|
||||||
### 风险 1:个人任务 scope_kind 被误配置导致进度异常
|
|
||||||
|
|
||||||
当前个人任务系统本质上按用户维度计算进度。如果允许运营配置 `site/work/module`,可能导致任务进度查错 `tracking_daily_stat` 聚合桶,出现任务永远不可领取或错误可领取。
|
|
||||||
|
|
||||||
缓解:
|
|
||||||
|
|
||||||
```text
|
|
||||||
采用方案 B:后台隐藏埋点范围,后端限制个人任务配置只能写入 User。
|
|
||||||
```
|
|
||||||
|
|
||||||
### 风险 2:Work 维度缺少 work_id,上游却静默用 user_id 代替
|
|
||||||
|
|
||||||
当前 `profile_task_tracking_scope_id` 中 `Work => user_id` 是错误语义。若后续扩展作品任务,会把作品维度统计错误映射到用户维度。
|
|
||||||
|
|
||||||
缓解:
|
|
||||||
|
|
||||||
```text
|
|
||||||
移除 Work => user_id 映射;非 User 的个人任务配置应被拒绝。未来做作品任务时新增明确 work_id 来源和任务类型。
|
|
||||||
```
|
|
||||||
|
|
||||||
### 风险 3:时区口径影响统计结果
|
|
||||||
|
|
||||||
周/月/季/年映射对时区敏感。当前日桶使用北京时间自然日:`floor((occurred_at_micros + 8h) / 1d)`。新增映射表应明确沿用北京时间业务日,还是切换为 UTC/用户本地时区。
|
|
||||||
|
|
||||||
### 风险 4:ISO week 跨年
|
|
||||||
|
|
||||||
ISO week-year 与自然年不同。若前端展示按自然年理解,可能产生认知差异。
|
|
||||||
|
|
||||||
### 风险 5:修改已有事件表可能触发 SpacetimeDB 迁移限制
|
|
||||||
|
|
||||||
如果已有事件表需要新增字段:
|
|
||||||
|
|
||||||
- 字段必须加末尾。
|
|
||||||
- 必须提供 default。
|
|
||||||
- 历史数据要分批迁移。
|
|
||||||
|
|
||||||
### 风险 5:表设计过早绑定单一业务
|
|
||||||
|
|
||||||
建议用通用 date dimension,而不是为某个单一埋点写死周/月/季/年表,避免后续复用困难。
|
|
||||||
|
|
||||||
## 待确认问题
|
|
||||||
|
|
||||||
1. 周维度使用 ISO week 还是自然周?周一开始还是周日开始?
|
|
||||||
2. 周/月/季/年映射是否沿用当前北京时间业务日口径?
|
|
||||||
3. 这个映射表服务的是所有埋点,还是只服务个人任务/运营后台统计?
|
|
||||||
4. 是否需要 API 暴露这些映射关系,还是只用于后端聚合?
|
|
||||||
5. 是否需要回填历史事件?历史数据规模多大?
|
|
||||||
6. 未来是否会存在非个人任务,例如整站任务、模块任务、作品任务?如果会,应另行设计任务类型和 `scope_id` 来源,不应复用当前个人任务配置页直接开放 scope。
|
|
||||||
|
|
||||||
## 建议结论
|
|
||||||
|
|
||||||
优先采用“一张通用日期维度映射表”的设计:
|
|
||||||
|
|
||||||
```text
|
|
||||||
analytics_date_dimension
|
|
||||||
```
|
|
||||||
|
|
||||||
通过字段同时提供:
|
|
||||||
|
|
||||||
```text
|
|
||||||
day / week / month / quarter / year
|
|
||||||
```
|
|
||||||
|
|
||||||
后续统计按 `granularity` 选择 bucket key 聚合。这样比直接新增四张独立映射表更稳定、更容易复用,也更容易处理跨年周、季度边界和历史回填。
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
# 邀请码有效期与后台二次确认实施计划
|
|
||||||
|
|
||||||
> **For Hermes:** 按 plan 模式,仅输出并保存实施计划,不直接改业务代码。
|
|
||||||
|
|
||||||
**Goal:** 为邀请码新增开始日期与截止日期,并让后台所有会修改数据的操作在提交前增加二次确认,降低误操作风险。
|
|
||||||
|
|
||||||
**Architecture:**
|
|
||||||
邀请码仍作为“用户稳定邀请身份码”保留,不做停用删除;在数据层增加 `starts_at` / `expires_at`,前台填写邀请码时按时间窗校验,后台列表与编辑页展示状态。后台所有写操作统一先弹二次确认,再真正调用 API,避免对兑换码、邀请码、任务配置等管理动作误触发。
|
|
||||||
|
|
||||||
**Tech Stack:**
|
|
||||||
Rust / SpacetimeDB / Axum / shared-contracts / TS + React 的 admin-web。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 当前上下文
|
|
||||||
|
|
||||||
- 邀请码当前只有 `user_id`、`invite_code`、`metadata_json`、`created_at`、`updated_at`,没有状态字段。
|
|
||||||
- 目前后台存在邀请码管理入口,但没有停用能力,也没有有效期概念。
|
|
||||||
- 邀请码用于 `redeem_profile_referral_invite_code` 时的实时校验,适合增加“时间窗”而不是“禁用删除”。
|
|
||||||
- 后台已存在兑换码、任务配置等可写操作;本次要求把所有后台操作统一加二次确认,包括新增、编辑、禁用、删除等写入口。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 设计原则
|
|
||||||
|
|
||||||
1. **邀请码不做软删除**:保留历史记录和邀请链路。
|
|
||||||
2. **有效期由时间窗推导**:
|
|
||||||
- `starts_at` 为空表示立即生效。
|
|
||||||
- `expires_at` 为空表示长期有效。
|
|
||||||
3. **前台只拒绝新绑定**:已绑定关系不回溯修改。
|
|
||||||
4. **后台写操作统一确认**:所有会触发 POST / PATCH / DELETE 的管理动作,在真正提交前必须弹出二次确认。
|
|
||||||
5. **尽量少改接口语义**:优先在现有 admin upsert/list 体系内扩展字段,而不是新增一套并行 API。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 方案概要
|
|
||||||
|
|
||||||
### 邀请码时间窗
|
|
||||||
|
|
||||||
新增字段:
|
|
||||||
- `starts_at: Option<Timestamp>`
|
|
||||||
- `expires_at: Option<Timestamp>`
|
|
||||||
|
|
||||||
校验规则:
|
|
||||||
- 当前时间 `< starts_at`:返回“邀请码未生效”
|
|
||||||
- 当前时间 `>= expires_at`:返回“邀请码已过期”
|
|
||||||
- 其他情况允许填写
|
|
||||||
|
|
||||||
建议把状态展示为:
|
|
||||||
- 未生效
|
|
||||||
- 有效
|
|
||||||
- 已过期
|
|
||||||
- 长期有效(两个字段都为空或仅无截止)
|
|
||||||
|
|
||||||
### 后台二次确认
|
|
||||||
|
|
||||||
对 admin-web 所有管理动作统一加确认弹窗/对话框,至少覆盖:
|
|
||||||
- 兑换码新增/更新
|
|
||||||
- 兑换码停用
|
|
||||||
- 邀请码新增/更新
|
|
||||||
- 任务配置新增/更新
|
|
||||||
- 任务配置停用
|
|
||||||
- 其他后续新增的后台写操作
|
|
||||||
|
|
||||||
确认文案要求:
|
|
||||||
- 显示对象标识(如 code / inviteCode / taskId)
|
|
||||||
- 显示操作类型(新增 / 更新 / 停用)
|
|
||||||
- 明确提醒“该操作会立即影响线上数据”
|
|
||||||
- 允许取消返回,不调用 API
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 预期修改文件
|
|
||||||
|
|
||||||
### 1. 服务端领域与契约
|
|
||||||
- `server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
|
||||||
- `ProfileInviteCode` 表结构新增开始/截止字段
|
|
||||||
- 邀请码 upsert 逻辑写入时间窗
|
|
||||||
- 邀请码 redeem 逻辑增加时间窗校验
|
|
||||||
- 邀请中心快照补充时间窗/状态
|
|
||||||
- `server-rs/crates/spacetime-module/src/migration.rs`
|
|
||||||
- 兼容旧表数据,给旧邀请码补默认空值
|
|
||||||
- `server-rs/crates/shared-contracts/src/runtime*.rs` 或对应生成/手写契约文件
|
|
||||||
- `AdminUpsertProfileInviteCodeRequest` 扩展字段
|
|
||||||
- `ProfileInviteCodeAdminResponse` 扩展字段
|
|
||||||
- 如需要,增加时间窗相关状态枚举或派生字段
|
|
||||||
- `server-rs/crates/spacetime-client/src/module_bindings/*`
|
|
||||||
- 重新生成 bindings
|
|
||||||
- mapper 补齐新字段
|
|
||||||
|
|
||||||
### 2. API Server
|
|
||||||
- `server-rs/crates/api-server/src/runtime_profile.rs`
|
|
||||||
- 接收/转发邀请码时间窗参数
|
|
||||||
- 返回新增字段给后台
|
|
||||||
- 如需要,调整校验错误文案
|
|
||||||
- `server-rs/crates/api-server/src/app.rs`
|
|
||||||
- 若有新路由或错误码需挂接,在此统一登记
|
|
||||||
|
|
||||||
### 3. Admin Web
|
|
||||||
- `apps/admin-web/src/api/adminApiTypes.ts`
|
|
||||||
- 增加邀请码时间窗字段
|
|
||||||
- 如有需要,增加后台操作请求结构字段
|
|
||||||
- `apps/admin-web/src/api/adminApiClient.ts`
|
|
||||||
- 透传新的请求/响应字段
|
|
||||||
- `apps/admin-web/src/app/adminRoutes.ts`
|
|
||||||
- 不一定需要改,但如果新增独立页面/子面板,需要在此登记
|
|
||||||
- `apps/admin-web/src/styles/admin.css`
|
|
||||||
- 确认弹窗与时间窗展示样式
|
|
||||||
- `apps/admin-web/src/**` 实际管理页面组件
|
|
||||||
- 邀请码编辑表单
|
|
||||||
- 邀请码列表状态展示
|
|
||||||
- 所有写操作前的二次确认弹窗
|
|
||||||
|
|
||||||
### 4. 文档
|
|
||||||
- `docs/technical/` 或 `docs/design/`
|
|
||||||
- 补一份邀请码时间窗与后台确认交互说明
|
|
||||||
- 如现有文档已经覆盖后台管理规范,则优先补充现有文档,不重复造新说明页
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 分步实施计划
|
|
||||||
|
|
||||||
### Task 1: 明确数据模型与契约扩展
|
|
||||||
**Objective:** 定义邀请码开始/截止日期字段及其在响应中的展示方式。
|
|
||||||
|
|
||||||
**要点:**
|
|
||||||
- 确认字段名采用 `starts_at` / `expires_at`,避免与现有字段语义冲突。
|
|
||||||
- 确认时间类型统一用 `Timestamp` / 毫秒微秒整数转换策略。
|
|
||||||
- 明确返回给后台的字段是否需要附带派生状态(如 `status`)。
|
|
||||||
|
|
||||||
**产出:**
|
|
||||||
- 契约字段定义
|
|
||||||
- 状态枚举/派生规则
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 2: 更新 SpacetimeDB 表与迁移
|
|
||||||
**Objective:** 让邀请码表可保存有效期,并兼容旧数据。
|
|
||||||
|
|
||||||
**要点:**
|
|
||||||
- 修改 `ProfileInviteCode` 表结构。
|
|
||||||
- 更新迁移逻辑,旧记录默认无开始/截止。
|
|
||||||
- 检查是否需要补充索引或查询辅助字段。
|
|
||||||
|
|
||||||
**验证:**
|
|
||||||
- 旧数据能正常读取。
|
|
||||||
- 新数据能写入开始/截止。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 3: 实现邀请填写时的时间窗校验
|
|
||||||
**Objective:** 在邀请码被填写时正确拒绝未生效或已过期的邀请码。
|
|
||||||
|
|
||||||
**要点:**
|
|
||||||
- 在 `redeem_profile_referral_invite_code_record` 内增加开始/截止校验。
|
|
||||||
- 保持“自己的邀请码不能填”“邀请码不存在”等原有错误优先级清晰。
|
|
||||||
- 保留历史绑定关系不受影响。
|
|
||||||
|
|
||||||
**验证:**
|
|
||||||
- 未到开始时间时返回明确错误。
|
|
||||||
- 超过截止时间时返回明确错误。
|
|
||||||
- 正常区间可绑定成功。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 4: 扩展后台邀请码管理接口
|
|
||||||
**Objective:** 让后台可以创建/修改邀请码时间窗,并在列表中查看状态。
|
|
||||||
|
|
||||||
**要点:**
|
|
||||||
- 扩展 `AdminUpsertProfileInviteCodeRequest`。
|
|
||||||
- 扩展 `ProfileInviteCodeAdminResponse`。
|
|
||||||
- `api-server` 接口负责接收新字段并转发。
|
|
||||||
- 列表接口返回可读时间字段与状态。
|
|
||||||
|
|
||||||
**验证:**
|
|
||||||
- 后台表单提交后,返回结果包含时间窗信息。
|
|
||||||
- 列表页能看到状态与时间。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 5: 给后台所有写操作加二次确认
|
|
||||||
**Objective:** 统一拦截所有后台写动作,避免误点直接生效。
|
|
||||||
|
|
||||||
**覆盖范围建议:**
|
|
||||||
- 邀请码新增/更新
|
|
||||||
- 兑换码新增/更新/停用
|
|
||||||
- 任务配置新增/更新/停用
|
|
||||||
- 后续新增的管理写操作
|
|
||||||
|
|
||||||
**实现要求:**
|
|
||||||
- 在真正调用 API 之前弹出确认框。
|
|
||||||
- 确认框需要展示对象名、操作类型、影响范围。
|
|
||||||
- 取消后不发送请求。
|
|
||||||
- 尽量抽象出通用确认组件/通用 action 包装函数,避免每个页面重复写。
|
|
||||||
|
|
||||||
**验证:**
|
|
||||||
- 点击“保存”不会直接提交,需先确认。
|
|
||||||
- 点击“取消”不会发请求。
|
|
||||||
- 所有后台写入口行为一致。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 6: 补充文档与交互说明
|
|
||||||
**Objective:** 把新规则写进项目文档,避免后续实现偏差。
|
|
||||||
|
|
||||||
**要点:**
|
|
||||||
- 记录邀请码时间窗语义。
|
|
||||||
- 记录后台二次确认规范。
|
|
||||||
- 说明哪些动作属于“必须确认”的写操作。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 测试与验证
|
|
||||||
|
|
||||||
### 服务端
|
|
||||||
- 邀请码时间窗单测 / 集成测试
|
|
||||||
- 邀请码 redeem 流程回归测试
|
|
||||||
- 旧数据兼容测试
|
|
||||||
|
|
||||||
### API / 前端
|
|
||||||
- 管理后台列表展示正确
|
|
||||||
- 表单提交能回传新字段
|
|
||||||
- 二次确认取消后不请求接口
|
|
||||||
- 二次确认确认后正常提交
|
|
||||||
|
|
||||||
### 推荐验证命令
|
|
||||||
- 视项目现有脚本执行对应后端测试
|
|
||||||
- 前端按 admin-web 构建/测试脚本验证
|
|
||||||
- 如涉及生成绑定,先确认生成产物无漏字段
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 风险与权衡
|
|
||||||
|
|
||||||
1. **时间字段格式不统一**
|
|
||||||
- 风险:前后端对时间单位理解不一致。
|
|
||||||
- 处理:在契约层明确是 ISO 字符串还是微秒整数,并全链路统一。
|
|
||||||
|
|
||||||
2. **后台“所有操作”范围过大**
|
|
||||||
- 风险:遗漏某些写入口。
|
|
||||||
- 处理:先枚举现有写 API,再做统一确认封装。
|
|
||||||
|
|
||||||
3. **邀请码过期后历史链接解释成本**
|
|
||||||
- 风险:用户误以为历史邀请码失效影响已绑定关系。
|
|
||||||
- 处理:文案明确“仅影响新填写,不影响已绑定记录”。
|
|
||||||
|
|
||||||
4. **契约与生成绑定联动较多**
|
|
||||||
- 风险:字段变更后生成文件数量较多。
|
|
||||||
- 处理:先改源契约与服务端,再统一重生成 bindings。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 待确认问题
|
|
||||||
|
|
||||||
1. `starts_at` / `expires_at` 在接口里要返回 **ISO 字符串** 还是 **微秒整数**?
|
|
||||||
2. 后台二次确认是否统一用一个全局弹窗组件,还是页面级本地实现?
|
|
||||||
3. 邀请码列表是否需要直接展示“状态标签”还是只展示时间字段由前端推导?
|
|
||||||
4. 现有后台所有写操作里,是否还要覆盖调试类接口,还是仅覆盖业务管理接口?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 建议执行顺序
|
|
||||||
|
|
||||||
1. 先确认时间字段格式与确认弹窗范围。
|
|
||||||
2. 再改服务端契约与迁移。
|
|
||||||
3. 再改 redeem 校验与后台接口。
|
|
||||||
4. 最后统一改 admin-web 的二次确认与表单展示。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**结论:** 这是一个适合分阶段落地的改动,建议先做“邀请码时间窗 + 后台统一二次确认”的基础能力,再补交互细节。
|
|
||||||
@@ -1,584 +0,0 @@
|
|||||||
# 我的页签反馈入口与反馈页 Implementation Plan
|
|
||||||
|
|
||||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
|
||||||
|
|
||||||
**Goal:** 在平台“我的”页签中新增“反馈”入口,点击后进入独立反馈路由,并按用户提供的参考图落地反馈页面 UI。
|
|
||||||
|
|
||||||
**Architecture:** 复用现有前端单页路由体系:`SelectionStage` 负责页面阶段,`appPageRoutes.ts` 负责 URL 映射,`PlatformEntryFlowShellImpl` 负责按阶段渲染视图。“我的”页签只增加一个入口回调,不在当前面板下方展开内容;反馈页作为独立页面组件挂到新阶段。首版先做前端静态表单与本地提交成功态,不新增后端表结构或 SpacetimeDB 写入,除非产品补充明确要求持久化反馈。
|
|
||||||
|
|
||||||
**Tech Stack:** React 19、TypeScript、Tailwind utility class、lucide-react、现有 Genarrative 平台入口组件体系。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current context / assumptions
|
|
||||||
|
|
||||||
## Reference image
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
参考图是一张移动端“帮助与反馈”页面,视觉和信息结构如下:
|
|
||||||
|
|
||||||
- 页面整体:浅灰背景,白色圆角卡片,黑/深灰标题文字,浅灰 placeholder,蓝色主按钮与蓝色文本链接。
|
|
||||||
- 顶部栏:白色导航/header,左侧为小 home 图标,中间标题为“帮助与反馈”,右侧为胶囊形更多/控制区。项目实现时可按现有平台导航规范简化为返回按钮 + 居中标题;若需要完全贴近图片,可使用 home 图标作为返回到“我的”页签的按钮。
|
|
||||||
- 内容区 section label:左上灰色文字“反馈问题”。
|
|
||||||
- 第一张表单卡:标题“问题描述”,大文本输入区域,placeholder 为“请填写10个字以上的问题描述以便我们提供更好的帮助,温馨提醒您请勿填写身份证号等个人隐私信息。”,右下角字数统计“0/200”。
|
|
||||||
- 第二张表单卡:标题“上传凭证(提供问题截图)”,左侧虚线边框上传方块,内含图片/上传 + 加号图标,文字“上传凭证”“(最多四张)”。
|
|
||||||
- 第三张表单卡:标题“联系电话”,placeholder 为“选填,如您填写则将会同步开发者与您联系”。
|
|
||||||
- 底部操作:大号蓝色圆角按钮“提交”,下方居中蓝色链接“查看反馈与投诉记录”。
|
|
||||||
|
|
||||||
实现约束:
|
|
||||||
|
|
||||||
- 反馈页面应命名为“帮助与反馈”,但“我的”页签入口可显示为“反馈”或“帮助与反馈”,优先以清爽短入口为准。
|
|
||||||
- 问题描述最少 10 个字、最多 200 个字,并实时显示 `当前字数/200`。
|
|
||||||
- 上传凭证首版如不接后端,可先支持前端选择/预览最多 4 张图片,提交时仅进入成功态;如无法快速安全实现预览,可先保留上传占位并在文档中标注待接入。
|
|
||||||
- 联系电话为选填。
|
|
||||||
- “查看反馈与投诉记录”首版无后端记录时可以先禁用、隐藏,或点击后给出轻量提示;若保留可见,应在计划/PRD 标明记录页不在首版范围。
|
|
||||||
|
|
||||||
1. 当前工作区是 `/home/dsk/workspace/Genarrative/.worktrees/hermes-19e77eb0`,不要额外拼接 `Genarrative/`。
|
|
||||||
2. 平台首页复用 `src/components/rpg-entry/RpgEntryHomeView.tsx`;`src/components/platform-entry/PlatformEntryHomeView.tsx` 只是 re-export。
|
|
||||||
3. “我的”页签的常用功能区域位于 `src/components/rpg-entry/RpgEntryHomeView.tsx:3958-4000`,现有入口包括“每日任务 / 邀请好友 / 填邀请码 / 玩家社区”。
|
|
||||||
4. 当前页面阶段类型位于 `src/components/platform-entry/platformEntryTypes.ts:16-38`;路由映射位于 `src/routing/appPageRoutes.ts:7-27`。
|
|
||||||
5. `src/App.tsx:60-63` 调用 `pushAppHistoryPath(resolvePathForSelectionStage(stage))`,所以新增阶段必须同步 `APP_STAGE_ROUTES`。
|
|
||||||
6. `PlatformEntryFlowShellImpl` 在 `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx:5081+` 根据 `selectionStage` 渲染不同页面,平台首页在 `selectionStage === 'platform'` 分支。
|
|
||||||
7. 参考图片已保存到 `.hermes/plans/assets/profile-feedback-reference-2026-05-08.png`,计划与实现均以该图片内容为主要 UI 依据。
|
|
||||||
8. 按项目约束,工程修改需同步文档;若没有更具体 PRD,需要先补一份简洁落地文档到 `docs/`。
|
|
||||||
|
|
||||||
## Proposed approach
|
|
||||||
|
|
||||||
新增一个轻量前端反馈页面阶段:
|
|
||||||
|
|
||||||
- 路由:`/profile/feedback`
|
|
||||||
- 阶段:`profile-feedback`
|
|
||||||
- 组件:`src/components/platform-entry/PlatformFeedbackView.tsx`
|
|
||||||
- “我的”页签入口:在常用功能区增加“反馈”按钮,点击调用新 prop `onOpenFeedback`。
|
|
||||||
- 页面行为:
|
|
||||||
- 顶部返回按钮返回 `platform` 阶段,并切回 `profile` 页签。
|
|
||||||
- 未登录用户点击入口时,优先弹登录;如果产品允许匿名反馈,可改为允许进入。
|
|
||||||
- 表单字段首版只在前端维护:问题描述、上传凭证图片、联系电话。
|
|
||||||
- 提交后显示成功态,不做 API 请求;后续如要持久化,再补 `shared-contracts + api-server + SpacetimeDB` 方案。
|
|
||||||
|
|
||||||
## Step-by-step plan
|
|
||||||
|
|
||||||
### Task 1: 补充反馈页落地文档
|
|
||||||
|
|
||||||
**Objective:** 先把反馈入口和页面边界写清楚,避免编码时需求漂移。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`
|
|
||||||
|
|
||||||
**Step 1: 新建 PRD 文档**
|
|
||||||
|
|
||||||
写入内容建议包含:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# 我的页签反馈入口 PRD
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
- 在“我的”页签提供反馈入口。
|
|
||||||
- 点击入口进入独立反馈路由 `/profile/feedback`。
|
|
||||||
- 反馈页移动端优先,桌面端居中卡片展示。
|
|
||||||
|
|
||||||
## 首版范围
|
|
||||||
- 前端表单:问题描述、上传凭证占位/前端图片预览、联系电话。
|
|
||||||
- 问题描述 10-200 字,显示实时字数统计。
|
|
||||||
- 提交后显示成功态。
|
|
||||||
- 不新增后端存储,不修改 SpacetimeDB 表结构。
|
|
||||||
|
|
||||||
## 交互
|
|
||||||
- 已登录用户:点击“反馈”进入反馈页。
|
|
||||||
- 未登录用户:点击入口触发登录弹窗。
|
|
||||||
- 返回:回到平台首页并定位“我的”页签。
|
|
||||||
|
|
||||||
## UI
|
|
||||||
- 以 `.hermes/plans/assets/profile-feedback-reference-2026-05-08.png` 为准,落地“帮助与反馈”移动端表单。
|
|
||||||
- 不在 UI 中堆叠说明性长文案。
|
|
||||||
- 入口是独立页面导航,不在“我的”面板下方展开。
|
|
||||||
|
|
||||||
## 验收
|
|
||||||
- `/profile/feedback` 可被浏览器前进/后退访问。
|
|
||||||
- “我的”页签反馈入口可进入该路由。
|
|
||||||
- 移动端和桌面端均不溢出。
|
|
||||||
- `npm run check:encoding`、`npm run typecheck` 通过。
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 验证文档编码**
|
|
||||||
|
|
||||||
Run: `npm run check:encoding`
|
|
||||||
|
|
||||||
Expected: PASS,无中文编码错误。
|
|
||||||
|
|
||||||
**Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md
|
|
||||||
git commit -m "docs: add profile feedback entry prd"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2: 扩展页面阶段与路由映射
|
|
||||||
|
|
||||||
**Objective:** 让 `/profile/feedback` 成为主应用可识别的独立路由。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/components/platform-entry/platformEntryTypes.ts`
|
|
||||||
- Modify: `src/routing/appPageRoutes.ts`
|
|
||||||
|
|
||||||
**Step 1: 修改 SelectionStage 类型**
|
|
||||||
|
|
||||||
在 `SelectionStage` union 中追加:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
| 'profile-feedback'
|
|
||||||
```
|
|
||||||
|
|
||||||
推荐放在 `'platform'` 附近或末尾,保持字面量清晰。
|
|
||||||
|
|
||||||
**Step 2: 修改 STAGE_ROUTE_ENTRIES**
|
|
||||||
|
|
||||||
在 `src/routing/appPageRoutes.ts` 的 `STAGE_ROUTE_ENTRIES` 中追加:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
['profile-feedback', '/profile/feedback'],
|
|
||||||
```
|
|
||||||
|
|
||||||
建议放在 `['platform', '/']` 后面,表示平台个人页子路由。
|
|
||||||
|
|
||||||
**Step 3: 验证类型推导**
|
|
||||||
|
|
||||||
Run: `npm run typecheck`
|
|
||||||
|
|
||||||
Expected: 若还未创建渲染组件,可能只通过路由类型;若出现 exhaustive 相关错误,留到后续任务处理。
|
|
||||||
|
|
||||||
**Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/components/platform-entry/platformEntryTypes.ts src/routing/appPageRoutes.ts
|
|
||||||
git commit -m "feat: add profile feedback route stage"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3: 新建反馈页面组件
|
|
||||||
|
|
||||||
**Objective:** 创建移动端优先的独立反馈页面。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/components/platform-entry/PlatformFeedbackView.tsx`
|
|
||||||
|
|
||||||
**Step 1: 创建组件 props**
|
|
||||||
|
|
||||||
组件接口建议:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export type PlatformFeedbackViewProps = {
|
|
||||||
onBack: () => void;
|
|
||||||
onSubmit?: (payload: PlatformFeedbackPayload) => void | Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PlatformFeedbackPayload = {
|
|
||||||
description: string;
|
|
||||||
contactPhone: string;
|
|
||||||
evidenceFiles: File[];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 实现 UI 状态**
|
|
||||||
|
|
||||||
使用 `useState` 管理:
|
|
||||||
|
|
||||||
- `description`
|
|
||||||
- `contactPhone`
|
|
||||||
- `evidenceFiles`
|
|
||||||
- `evidencePreviewUrls`
|
|
||||||
- `error`
|
|
||||||
- `isSubmitting`
|
|
||||||
- `submitted`
|
|
||||||
|
|
||||||
**Step 3: 实现页面结构**
|
|
||||||
|
|
||||||
建议结构:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { ArrowLeft, CheckCircle2, Home, ImagePlus, Send } from 'lucide-react';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
const MAX_FEEDBACK_DESCRIPTION_LENGTH = 200;
|
|
||||||
const MIN_FEEDBACK_DESCRIPTION_LENGTH = 10;
|
|
||||||
const MAX_FEEDBACK_EVIDENCE_COUNT = 4;
|
|
||||||
```
|
|
||||||
|
|
||||||
页面外壳建议复用现有视觉变量:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<div className="platform-page-stage platform-remap-surface min-h-0 min-w-0 overflow-y-auto px-4 py-4 sm:px-6 lg:px-8">
|
|
||||||
<div className="mx-auto flex w-full max-w-2xl flex-col gap-4">
|
|
||||||
<header className="platform-surface platform-surface--soft rounded-[1.6rem] px-4 py-4">
|
|
||||||
<button type="button" onClick={onBack} ...>
|
|
||||||
<ArrowLeft ... /> 返回
|
|
||||||
</button>
|
|
||||||
<h1>反馈</h1>
|
|
||||||
</header>
|
|
||||||
...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
注意:不要写大段“功能说明类文案”;字段 label 简短即可。
|
|
||||||
|
|
||||||
**Step 4: 表单校验**
|
|
||||||
|
|
||||||
提交时:
|
|
||||||
|
|
||||||
- `description.trim().length < 10`:提示“请填写10个字以上的问题描述”
|
|
||||||
- `description.trim().length > 200`:提示“问题描述不能超过 200 字”
|
|
||||||
- `contactPhone.trim().length > 40`:提示“联系电话不能超过 40 字”
|
|
||||||
- 上传凭证最多 4 张;超出时提示“最多上传四张凭证”
|
|
||||||
|
|
||||||
**Step 5: 提交行为**
|
|
||||||
|
|
||||||
首版无后端时:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
await onSubmit?.({
|
|
||||||
description: description.trim(),
|
|
||||||
contactPhone: contactPhone.trim(),
|
|
||||||
evidenceFiles,
|
|
||||||
});
|
|
||||||
setSubmitted(true);
|
|
||||||
```
|
|
||||||
|
|
||||||
如果没有传 `onSubmit`,也显示成功态。代码注释说明:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// 中文注释:首版反馈页只完成前端收集与成功态;接入后端时在 onSubmit 中替换为 API 调用。
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 6: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/components/platform-entry/PlatformFeedbackView.tsx
|
|
||||||
git commit -m "feat: add platform feedback view"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 4: 在“我的”页签增加反馈入口 prop
|
|
||||||
|
|
||||||
**Objective:** 让 Profile 页面能触发反馈路由,同时保持组件职责清晰。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/components/rpg-entry/RpgEntryHomeView.tsx`
|
|
||||||
- Modify: `src/components/platform-entry/PlatformEntryHomeView.tsx`(通常无需改,re-export 类型会自动带出)
|
|
||||||
|
|
||||||
**Step 1: 扩展 Props**
|
|
||||||
|
|
||||||
在 `RpgEntryHomeViewProps` 中新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
onOpenFeedback?: () => void;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 从 props 解构**
|
|
||||||
|
|
||||||
在 `RpgEntryHomeView` 函数参数解构区新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
onOpenFeedback,
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 增加入口按钮**
|
|
||||||
|
|
||||||
在 `profileContent` 的常用功能 grid 中,建议在“玩家社区”后追加:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<ProfileShortcutButton
|
|
||||||
label="反馈"
|
|
||||||
subLabel="问题与建议"
|
|
||||||
icon={MessageCircle}
|
|
||||||
onClick={onOpenFeedback}
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
如果参考图中入口位置不同,按参考图调整;但仍必须进入独立路由。
|
|
||||||
|
|
||||||
**Step 4: 未提供回调时行为**
|
|
||||||
|
|
||||||
`ProfileShortcutButton` 已允许 `onClick` 为空;此处传 `onOpenFeedback` 即可。若希望按钮始终可点,应在父组件必传。
|
|
||||||
|
|
||||||
**Step 5: 验证类型**
|
|
||||||
|
|
||||||
Run: `npm run typecheck`
|
|
||||||
|
|
||||||
Expected: PASS 或只剩父组件未传 prop 的问题。
|
|
||||||
|
|
||||||
**Step 6: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/components/rpg-entry/RpgEntryHomeView.tsx src/components/platform-entry/PlatformEntryHomeView.tsx
|
|
||||||
git commit -m "feat: add feedback shortcut to profile tab"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 5: 接入 PlatformEntryFlowShellImpl 渲染与导航
|
|
||||||
|
|
||||||
**Objective:** 点击“反馈”进入 `/profile/feedback`,返回后回到“我的”页签。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
|
|
||||||
**Step 1: 导入组件**
|
|
||||||
|
|
||||||
在 imports 中新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 创建打开反馈页函数**
|
|
||||||
|
|
||||||
在 `const { setPlatformTab } = platformBootstrap;` 附近新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const openProfileFeedback = useCallback(() => {
|
|
||||||
if (!authUi?.user) {
|
|
||||||
authUi?.openLoginModal();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlatformTab('profile');
|
|
||||||
setSelectionStage('profile-feedback');
|
|
||||||
}, [authUi, setPlatformTab, setSelectionStage]);
|
|
||||||
```
|
|
||||||
|
|
||||||
如产品允许匿名反馈,则移除登录判断。
|
|
||||||
|
|
||||||
**Step 3: 给首页传入入口回调**
|
|
||||||
|
|
||||||
在 `PlatformEntryHomeView` props 中加入:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
onOpenFeedback={openProfileFeedback}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 4: 增加渲染分支**
|
|
||||||
|
|
||||||
在 `selectionStage === 'platform'` 分支后、详情页分支前新增:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
{selectionStage === 'profile-feedback' && (
|
|
||||||
<motion.div
|
|
||||||
key="platform-profile-feedback"
|
|
||||||
initial={{ opacity: 0, y: 12 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, y: -12 }}
|
|
||||||
className="flex h-full min-h-0 flex-col"
|
|
||||||
>
|
|
||||||
<PlatformFeedbackView
|
|
||||||
onBack={() => {
|
|
||||||
setPlatformTab('profile');
|
|
||||||
setSelectionStage('platform');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 5: 直接访问路由的 tab 同步**
|
|
||||||
|
|
||||||
为处理用户直接访问 `/profile/feedback` 后返回,返回逻辑已 `setPlatformTab('profile')`。如需要进入反馈页时也设置 tab,可加 effect:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectionStage === 'profile-feedback') {
|
|
||||||
setPlatformTab('profile');
|
|
||||||
}
|
|
||||||
}, [selectionStage, setPlatformTab]);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 6: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
|
|
||||||
git commit -m "feat: wire profile feedback navigation"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 6: 增加路由与反馈页基础测试
|
|
||||||
|
|
||||||
**Objective:** 用自动化测试覆盖新路由映射和反馈页核心交互。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create or Modify: `src/routing/appPageRoutes.test.ts`
|
|
||||||
- Create: `src/components/platform-entry/PlatformFeedbackView.test.tsx`
|
|
||||||
|
|
||||||
**Step 1: 路由测试**
|
|
||||||
|
|
||||||
如果已有 `appPageRoutes.test.ts`,追加;否则创建:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
import {
|
|
||||||
resolvePathForSelectionStage,
|
|
||||||
resolveSelectionStageFromPath,
|
|
||||||
} from './appPageRoutes';
|
|
||||||
|
|
||||||
describe('appPageRoutes', () => {
|
|
||||||
it('resolves profile feedback route', () => {
|
|
||||||
expect(resolveSelectionStageFromPath('/profile/feedback')).toBe('profile-feedback');
|
|
||||||
expect(resolvePathForSelectionStage('profile-feedback')).toBe('/profile/feedback');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 反馈页测试**
|
|
||||||
|
|
||||||
测试重点:
|
|
||||||
|
|
||||||
- 渲染“帮助与反馈”标题。
|
|
||||||
- 问题描述过短时提交显示错误。
|
|
||||||
- 输入有效问题描述后提交显示成功态。
|
|
||||||
- 字数统计随输入更新。
|
|
||||||
- 上传凭证入口最多接受 4 张图片。
|
|
||||||
- 点击返回调用 `onBack`。
|
|
||||||
|
|
||||||
示例:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
|
||||||
import { describe, expect, it, vi } from 'vitest';
|
|
||||||
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
|
||||||
|
|
||||||
describe('PlatformFeedbackView', () => {
|
|
||||||
it('validates content before submit', () => {
|
|
||||||
render(<PlatformFeedbackView onBack={vi.fn()} />);
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: '提交' }));
|
|
||||||
expect(screen.getByText('请填写10个字以上的问题描述')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
注意检查项目当前 test setup 是否已引入 jest-dom matcher;若没有,使用 truthy DOM 节点断言:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
expect(screen.getByText('请补充反馈内容')).toBeTruthy();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 运行定向测试**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
**Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx
|
|
||||||
git commit -m "test: cover profile feedback route and form"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 7: 全量前端验证与移动端 smoke
|
|
||||||
|
|
||||||
**Objective:** 确认新增页面不破坏编码、类型和基础交互。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- No code changes unless validation finds issues.
|
|
||||||
|
|
||||||
**Step 1: 编码检查**
|
|
||||||
|
|
||||||
Run: `npm run check:encoding`
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
**Step 2: ESLint**
|
|
||||||
|
|
||||||
Run: `npm run lint:eslint`
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
**Step 3: TypeScript**
|
|
||||||
|
|
||||||
Run: `npm run typecheck`
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
**Step 4: 测试**
|
|
||||||
|
|
||||||
Run: `npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx`
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
**Step 5: 本地页面 smoke**
|
|
||||||
|
|
||||||
Run: `npm run dev:web`
|
|
||||||
|
|
||||||
手动验证:
|
|
||||||
|
|
||||||
1. 打开 `http://127.0.0.1:3000/`。
|
|
||||||
2. 登录后进入“我的”页签。
|
|
||||||
3. 点击“反馈”。
|
|
||||||
4. 地址变为 `/profile/feedback`。
|
|
||||||
5. 页面显示反馈表单。
|
|
||||||
6. 提交空内容出现错误。
|
|
||||||
7. 输入有效内容后显示成功态。
|
|
||||||
8. 点击返回后回到首页“我的”页签。
|
|
||||||
9. 直接打开 `http://127.0.0.1:3000/profile/feedback` 能显示反馈页。
|
|
||||||
10. 使用移动端视口(如 390×844)确认按钮和表单不溢出。
|
|
||||||
|
|
||||||
**Step 6: Commit validation fixes if any**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add <fixed-files>
|
|
||||||
git commit -m "fix: polish profile feedback validation"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files likely to change
|
|
||||||
|
|
||||||
- `docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`:新增反馈入口落地文档。
|
|
||||||
- `src/components/platform-entry/platformEntryTypes.ts`:新增 `profile-feedback` 阶段。
|
|
||||||
- `src/routing/appPageRoutes.ts`:新增 `/profile/feedback` 路由映射。
|
|
||||||
- `.hermes/plans/assets/profile-feedback-reference-2026-05-08.png`:反馈页参考图。
|
|
||||||
- `src/components/platform-entry/PlatformFeedbackView.tsx`:新增反馈页面。
|
|
||||||
- `src/components/rpg-entry/RpgEntryHomeView.tsx`:新增“我的”页签反馈入口和 `onOpenFeedback` prop。
|
|
||||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`:接入反馈页打开与返回导航。
|
|
||||||
- `src/routing/appPageRoutes.test.ts`:新增路由映射测试。
|
|
||||||
- `src/components/platform-entry/PlatformFeedbackView.test.tsx`:新增反馈页交互测试。
|
|
||||||
|
|
||||||
## Tests / validation
|
|
||||||
|
|
||||||
Minimum required:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run check:encoding
|
|
||||||
npm run typecheck
|
|
||||||
npm run test -- src/routing/appPageRoutes.test.ts src/components/platform-entry/PlatformFeedbackView.test.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
Recommended before merge:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run lint:eslint
|
|
||||||
npm run test
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Manual smoke:
|
|
||||||
|
|
||||||
- 登录后“我的”页签显示“反馈”入口。
|
|
||||||
- 点击入口进入 `/profile/feedback`。
|
|
||||||
- 浏览器后退和页面返回按钮行为符合预期。
|
|
||||||
- 移动端视口无横向溢出。
|
|
||||||
- 页面没有把反馈表单展开在“我的”页签下方。
|
|
||||||
|
|
||||||
## Risks, tradeoffs, and open questions
|
|
||||||
|
|
||||||
1. **参考图落地风险:** 参考图是浅色移动端表单,而项目现有平台 UI 可能偏游戏化/深色变量;实现时需要优先复刻信息结构与交互,不要为了完全一致而破坏现有主题适配。
|
|
||||||
2. **反馈是否需要后端存储:** 本计划首版不新增后端,只做前端收集和成功态。若产品要求真实提交,需要新增后端方案:`shared-contracts` DTO、`api-server` 路由、SpacetimeDB 表/迁移、后台查看入口,并按 SpacetimeDB skills 执行。
|
|
||||||
3. **登录要求:** 计划默认未登录用户点击入口弹登录。若希望匿名反馈,应取消该限制,并在 payload 中允许无用户身份。
|
|
||||||
4. **入口位置:** 当前建议放在“我的”页签常用功能 grid 中。若参考图明确是列表项或设置区入口,应按图调整,但仍进入独立路由。
|
|
||||||
5. **图标复用:** 可先用 `MessageCircle` 或 `MessageSquareText`,避免引入新依赖。
|
|
||||||
6. **现有大文件风险:** `RpgEntryHomeView.tsx` 很大,实施时必须局部补丁,避免整文件重写导致中文编码或格式大范围变化。
|
|
||||||
|
|
||||||
## Implementation notes
|
|
||||||
|
|
||||||
- 所有中文注释和文案保持 UTF-8。
|
|
||||||
- 不要新增 `.env.local` 到 `.gitignore`。
|
|
||||||
- 不要把反馈页做成“我的”页签内部展开面板。
|
|
||||||
- 不要新增后端或数据库,除非用户确认反馈必须持久化。
|
|
||||||
- 若后续接入后端,必须先补技术文档,再按 DDD 与 SpacetimeDB 约束落地。
|
|
||||||
@@ -1,561 +0,0 @@
|
|||||||
# 声控狗叫对战 2D 浏览器游戏设计与实现计划
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
基于用户提供的视频:
|
|
||||||
|
|
||||||
`C:\Users\DSK\Videos\一款双方比狗叫的游戏 - 1.一款双方比狗叫的游戏(Av116504192360177,P1).mp4`
|
|
||||||
|
|
||||||
提取其中“双方比狗叫”的核心玩法,并按照 BDD / TDD / DDD 的方法,为 Genarrative 中可运行于浏览器的 2D 游戏方案生成一份可落地设计与实现思路。实现方向遵循仓库内 `game-studio` 插件工作流,默认采用 2D Phaser + TypeScript + Vite + DOM HUD 的浏览器游戏架构。
|
|
||||||
|
|
||||||
本计划仅做方案设计,不直接编码。
|
|
||||||
|
|
||||||
## 当前上下文与输入分析
|
|
||||||
|
|
||||||
### 已识别视频核心画面
|
|
||||||
|
|
||||||
通过抽帧观察,视频中的游戏呈现出以下稳定特征:
|
|
||||||
|
|
||||||
- 画面是横版 2D 手绘舞台,场景包括公园、海边等固定关卡背景。
|
|
||||||
- 双方各有一只狗作为对战角色,站在左右两侧。
|
|
||||||
- 中央有明显倒计时,例如 `30`、`28`。
|
|
||||||
- 顶部有红蓝双方拉锯式能量条 / 进度条。
|
|
||||||
- 中央提示出现:`对着麦克风汪一声`、`用声音大小 + 叫声次数推动能量条!`
|
|
||||||
- 玩家输入不是传统键鼠,而是麦克风声音。
|
|
||||||
- 玩家需要模仿狗叫,系统根据声音大小与叫声次数推动能量条。
|
|
||||||
- 屏幕会根据叫声出现 `BARK`、`WOOF`、`WAN`、`WANGOOF` 等拟声词与冲击波视觉反馈。
|
|
||||||
- 回合结束时,根据能量条偏向或推进结果判定胜负。
|
|
||||||
|
|
||||||
### 提炼出的核心玩法
|
|
||||||
|
|
||||||
这是一个“声控拔河式狗叫对战”小游戏:
|
|
||||||
|
|
||||||
- 两名玩家 / 一名玩家对 AI 分别代表左右两只狗。
|
|
||||||
- 每局限时 30 秒。
|
|
||||||
- 玩家通过麦克风持续发出狗叫声。
|
|
||||||
- 游戏实时分析音量峰值、叫声次数、叫声节奏。
|
|
||||||
- 声音越大、叫声越密集,己方推动力越强。
|
|
||||||
- 顶部能量条在双方推动力差值下左右移动。
|
|
||||||
- 时间结束后,能量条偏向哪一方,哪一方获胜。
|
|
||||||
|
|
||||||
### 需要合理抽象的地方
|
|
||||||
|
|
||||||
视频中存在直播弹幕、贴图、表情包、遮挡层,这些不是游戏本体机制。本方案只吸收游戏本体核心:
|
|
||||||
|
|
||||||
- 双方狗狗对叫
|
|
||||||
- 麦克风输入
|
|
||||||
- 声音强度 + 次数判定
|
|
||||||
- 红蓝拉锯能量条
|
|
||||||
- 限时回合
|
|
||||||
- 夸张拟声词与冲击波反馈
|
|
||||||
|
|
||||||
## game-studio 插件路线
|
|
||||||
|
|
||||||
根据仓库内 `.hermes/plugins/game-studio` 技能:
|
|
||||||
|
|
||||||
- 早期游戏工作先走 `game-studio` 总入口。
|
|
||||||
- 2D 浏览器游戏默认选择 Phaser。
|
|
||||||
- 架构上需要分离 simulation 与 renderer。
|
|
||||||
- HUD / 菜单 / 设置优先使用 DOM overlay,不把密集文字塞进 canvas。
|
|
||||||
- 玩法状态不应由 Phaser Scene 直接持有,Scene 只负责渲染、动画、相机、输入适配。
|
|
||||||
|
|
||||||
因此本方案采用:
|
|
||||||
|
|
||||||
- Runtime:Phaser 3
|
|
||||||
- Language:TypeScript
|
|
||||||
- Build:Vite
|
|
||||||
- UI:React/DOM HUD overlay 或项目现有 DOM UI 层
|
|
||||||
- Audio input:Web Audio API + MediaDevices.getUserMedia
|
|
||||||
- Simulation:纯 TS domain/service 层
|
|
||||||
- Renderer:Phaser Scene 读取 simulation snapshot 并播放动画/特效
|
|
||||||
|
|
||||||
## 游戏概念设计
|
|
||||||
|
|
||||||
### 游戏名建议
|
|
||||||
|
|
||||||
- 中文:`汪汪声浪大作战`
|
|
||||||
- 英文代号:`bark-battle`
|
|
||||||
- Play type ID 建议:`bark-battle`
|
|
||||||
|
|
||||||
### 玩家幻想
|
|
||||||
|
|
||||||
玩家不是通过按键战斗,而是真的对着麦克风“汪汪叫”,把自己的狗狗声浪推向对手。游戏目标是在倒计时结束前用更响、更密集、更有节奏的叫声赢得声浪拔河。
|
|
||||||
|
|
||||||
### 核心动词
|
|
||||||
|
|
||||||
- 叫:对麦克风发出狗叫声。
|
|
||||||
- 推:通过叫声推动能量条。
|
|
||||||
- 压制:让能量条持续向对手方向倾斜。
|
|
||||||
- 爆发:短时间内连续高质量叫声触发冲击波。
|
|
||||||
- 防守:对手强势时通过持续叫声把能量条拉回。
|
|
||||||
|
|
||||||
### 单局流程
|
|
||||||
|
|
||||||
1. 准备阶段
|
|
||||||
- 展示双方狗狗、地图、麦克风权限提示。
|
|
||||||
- 用户授权麦克风。
|
|
||||||
- 系统检测环境噪音并校准阈值。
|
|
||||||
|
|
||||||
2. 倒计时阶段
|
|
||||||
- 3、2、1 或中央 `30` 倒计时开始。
|
|
||||||
- 玩家看到提示:`对着麦克风汪一声`。
|
|
||||||
|
|
||||||
3. 对战阶段
|
|
||||||
- 每帧或固定 tick 采集麦克风音量。
|
|
||||||
- 根据音量峰值与短促叫声次数计算本方 barkPower。
|
|
||||||
- AI 或远端对手产生 opponentPower。
|
|
||||||
- 能量条根据 `playerPower - opponentPower` 拉锯。
|
|
||||||
- 狗狗张嘴动画、拟声词、冲击波按声音强度生成。
|
|
||||||
|
|
||||||
4. 结算阶段
|
|
||||||
- 30 秒结束。
|
|
||||||
- 能量条偏玩家侧则胜利,偏对手侧则失败,接近中线则平局。
|
|
||||||
- 展示叫声次数、最大音量、平均节奏、声浪评分。
|
|
||||||
|
|
||||||
5. 重开 / 返回
|
|
||||||
- 支持再来一局。
|
|
||||||
- 支持返回玩法入口或结果页。
|
|
||||||
|
|
||||||
## 规则设计
|
|
||||||
|
|
||||||
### 关键状态
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type BarkBattlePhase = 'permission' | 'calibration' | 'countdown' | 'playing' | 'finished'
|
|
||||||
|
|
||||||
type BarkBattleSnapshot = {
|
|
||||||
phase: BarkBattlePhase
|
|
||||||
remainingMs: number
|
|
||||||
energy: number // -100 到 100,负数偏对手,正数偏玩家
|
|
||||||
player: BarkSideState
|
|
||||||
opponent: BarkSideState
|
|
||||||
winner: 'player' | 'opponent' | 'draw' | null
|
|
||||||
}
|
|
||||||
|
|
||||||
type BarkSideState = {
|
|
||||||
barkCount: number
|
|
||||||
currentVolume: number
|
|
||||||
recentPeak: number
|
|
||||||
combo: number
|
|
||||||
power: number
|
|
||||||
isBarking: boolean
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 输入判定
|
|
||||||
|
|
||||||
#### 音量采样
|
|
||||||
|
|
||||||
- 使用 Web Audio API 创建 `AnalyserNode`。
|
|
||||||
- 每个 simulation tick 读取频域或时域数据。
|
|
||||||
- 计算 RMS 或 peak volume。
|
|
||||||
- 根据校准后的环境噪音设置动态阈值。
|
|
||||||
|
|
||||||
#### 一次“叫声”的判定
|
|
||||||
|
|
||||||
一次有效叫声建议满足:
|
|
||||||
|
|
||||||
- 音量超过 `barkThreshold`。
|
|
||||||
- 与上一次叫声峰值至少间隔 `minBarkGapMs`,避免持续噪音被无限计数。
|
|
||||||
- 持续时长在合理范围,例如 80ms 到 1200ms。
|
|
||||||
- 可选:频谱能量集中在中高频,不强制做复杂语音识别,MVP 先用音量 + 峰值节奏。
|
|
||||||
|
|
||||||
#### 推动力计算
|
|
||||||
|
|
||||||
```text
|
|
||||||
playerPower = volumeScore * 0.65 + barkRateScore * 0.35 + comboBonus
|
|
||||||
opponentPower = aiPower 或远端玩家 power
|
|
||||||
energyDelta = (playerPower - opponentPower) * deltaTime * balanceFactor
|
|
||||||
energy = clamp(energy + energyDelta, -100, 100)
|
|
||||||
```
|
|
||||||
|
|
||||||
### AI 对手 MVP
|
|
||||||
|
|
||||||
若先做单机浏览器版,右侧对手可由 AI 模拟:
|
|
||||||
|
|
||||||
- 简单难度:周期性小叫,power 低。
|
|
||||||
- 普通难度:有节奏地爆发,power 中等。
|
|
||||||
- 困难难度:根据玩家领先程度自适应追赶,但不得作弊到不可赢。
|
|
||||||
|
|
||||||
后续可扩展为多人实时对战。
|
|
||||||
|
|
||||||
## BDD 行为场景
|
|
||||||
|
|
||||||
### 功能: 麦克风授权与准备
|
|
||||||
|
|
||||||
```gherkin
|
|
||||||
功能: 狗叫对战麦克风准备
|
|
||||||
为了让玩家能用声音参与对战
|
|
||||||
作为浏览器玩家
|
|
||||||
我希望游戏在开局前明确请求麦克风权限并完成环境校准
|
|
||||||
|
|
||||||
场景: 玩家允许麦克风权限后进入准备倒计时
|
|
||||||
假如玩家打开狗叫对战页面
|
|
||||||
当玩家同意浏览器麦克风授权
|
|
||||||
那么系统应进入环境噪音校准阶段
|
|
||||||
而且校准完成后应显示开局倒计时
|
|
||||||
|
|
||||||
场景: 玩家拒绝麦克风权限
|
|
||||||
假如玩家打开狗叫对战页面
|
|
||||||
当玩家拒绝浏览器麦克风授权
|
|
||||||
那么系统应显示无法声控游玩的提示
|
|
||||||
而且应提供重试授权入口
|
|
||||||
而且不应直接开始对战
|
|
||||||
```
|
|
||||||
|
|
||||||
### 功能: 声音推动能量条
|
|
||||||
|
|
||||||
```gherkin
|
|
||||||
功能: 声音大小和叫声次数推动能量条
|
|
||||||
为了复刻双方比狗叫的核心体验
|
|
||||||
作为玩家
|
|
||||||
我希望自己的叫声能实时推动顶部能量条
|
|
||||||
|
|
||||||
场景: 玩家发出一次有效狗叫
|
|
||||||
假如游戏处于 playing 阶段
|
|
||||||
而且麦克风输入音量超过有效叫声阈值
|
|
||||||
当系统检测到一次新的叫声峰值
|
|
||||||
那么玩家叫声次数应增加 1
|
|
||||||
而且玩家狗狗应播放张嘴吠叫动画
|
|
||||||
而且画面应出现拟声词反馈
|
|
||||||
|
|
||||||
场景: 玩家连续大声狗叫压制对手
|
|
||||||
假如游戏处于 playing 阶段
|
|
||||||
而且玩家在短时间内产生多次有效叫声
|
|
||||||
当玩家推动力高于对手推动力
|
|
||||||
那么顶部能量条应向玩家侧移动
|
|
||||||
而且玩家侧声浪特效应增强
|
|
||||||
|
|
||||||
场景: 环境噪音低于阈值不计入叫声
|
|
||||||
假如游戏处于 playing 阶段
|
|
||||||
当麦克风只有低于阈值的背景噪音
|
|
||||||
那么玩家叫声次数不应增加
|
|
||||||
而且能量条不应因为背景噪音明显移动
|
|
||||||
```
|
|
||||||
|
|
||||||
### 功能: 限时胜负结算
|
|
||||||
|
|
||||||
```gherkin
|
|
||||||
功能: 狗叫对战胜负结算
|
|
||||||
为了让单局对抗有明确目标
|
|
||||||
作为玩家
|
|
||||||
我希望倒计时结束后根据能量条位置判定胜负
|
|
||||||
|
|
||||||
场景: 倒计时结束时玩家侧占优
|
|
||||||
假如游戏剩余时间归零
|
|
||||||
而且能量条位于玩家侧
|
|
||||||
当系统进入结算阶段
|
|
||||||
那么系统应判定玩家胜利
|
|
||||||
而且展示玩家叫声次数、最大音量和声浪评分
|
|
||||||
|
|
||||||
场景: 倒计时结束时双方接近平衡
|
|
||||||
假如游戏剩余时间归零
|
|
||||||
而且能量条处于平局阈值范围内
|
|
||||||
当系统进入结算阶段
|
|
||||||
那么系统应判定为平局
|
|
||||||
而且展示再来一局入口
|
|
||||||
```
|
|
||||||
|
|
||||||
### 功能: 移动端与无麦克风降级
|
|
||||||
|
|
||||||
```gherkin
|
|
||||||
功能: 声控游戏移动端与无麦克风降级
|
|
||||||
为了让不同设备玩家都能理解当前状态
|
|
||||||
作为移动端或无麦克风环境玩家
|
|
||||||
我希望系统给出清晰、可操作的降级路径
|
|
||||||
|
|
||||||
场景: 当前浏览器不支持麦克风 API
|
|
||||||
假如玩家设备不支持 getUserMedia
|
|
||||||
当玩家进入狗叫对战页面
|
|
||||||
那么系统应显示设备不支持麦克风输入
|
|
||||||
而且提供返回入口
|
|
||||||
|
|
||||||
场景: 移动端进入对战页面
|
|
||||||
假如玩家使用移动端浏览器
|
|
||||||
当玩家进入狗叫对战页面
|
|
||||||
那么主要能量条、倒计时和狗狗角色应保持可见
|
|
||||||
而且非关键设置应收起到菜单中
|
|
||||||
```
|
|
||||||
|
|
||||||
## DDD 领域划分
|
|
||||||
|
|
||||||
### 领域层:bark-battle domain
|
|
||||||
|
|
||||||
职责:只处理玩法规则,不依赖 Phaser、DOM、Web Audio、后端。
|
|
||||||
|
|
||||||
建议模块:
|
|
||||||
|
|
||||||
- `BarkBattleSession`
|
|
||||||
- 管理 phase、remainingMs、energy、winner。
|
|
||||||
- `BarkDetector`
|
|
||||||
- 根据音量样本判断是否形成一次有效叫声。
|
|
||||||
- `EnergyTugOfWar`
|
|
||||||
- 根据双方 power 更新能量条。
|
|
||||||
- `BarkBattleScoring`
|
|
||||||
- 计算最大音量、叫声次数、combo、评分。
|
|
||||||
- `OpponentStrategy`
|
|
||||||
- 单机 AI 对手策略接口。
|
|
||||||
|
|
||||||
领域规则必须可用纯单元测试验证。
|
|
||||||
|
|
||||||
### 应用层:use case / controller
|
|
||||||
|
|
||||||
职责:编排麦克风输入、simulation tick、AI 对手、结果输出。
|
|
||||||
|
|
||||||
建议用例:
|
|
||||||
|
|
||||||
- `requestMicrophonePermission()`
|
|
||||||
- `calibrateAmbientNoise()`
|
|
||||||
- `startBarkBattleSession()`
|
|
||||||
- `submitAudioSample(sample)`
|
|
||||||
- `tickBarkBattle(deltaMs)`
|
|
||||||
- `finishBarkBattle()`
|
|
||||||
|
|
||||||
### 基础设施层
|
|
||||||
|
|
||||||
职责:浏览器 API 与引擎适配。
|
|
||||||
|
|
||||||
- `BrowserMicrophoneInput`
|
|
||||||
- 封装 `navigator.mediaDevices.getUserMedia`。
|
|
||||||
- 输出 normalized volume samples。
|
|
||||||
- `PhaserBarkBattleScene`
|
|
||||||
- 渲染狗狗、背景、拟声词、冲击波。
|
|
||||||
- 不持有核心玩法规则。
|
|
||||||
- `DomBarkBattleHud`
|
|
||||||
- 展示倒计时、能量条、权限提示、结算面板。
|
|
||||||
|
|
||||||
### 表现层
|
|
||||||
|
|
||||||
- Phaser Canvas:地图、狗狗、声浪、粒子、拟声词。
|
|
||||||
- DOM HUD:顶部能量条、倒计时、权限/结算/设置面板。
|
|
||||||
|
|
||||||
## TDD 落地顺序
|
|
||||||
|
|
||||||
### 第一轮:领域规则 RED-GREEN-REFACTOR
|
|
||||||
|
|
||||||
先写纯 TS 单元测试,不接 Phaser,不接麦克风。
|
|
||||||
|
|
||||||
目标测试:
|
|
||||||
|
|
||||||
- `BarkDetector`:超过阈值且间隔足够时计为一次叫声。
|
|
||||||
- `BarkDetector`:持续噪音不会无限增加叫声次数。
|
|
||||||
- `EnergyTugOfWar`:玩家 power 高于对手时 energy 向玩家侧移动。
|
|
||||||
- `EnergyTugOfWar`:energy 被 clamp 在 -100 到 100。
|
|
||||||
- `BarkBattleSession`:倒计时归零后进入 finished。
|
|
||||||
- `BarkBattleSession`:根据 energy 判定 player/opponent/draw。
|
|
||||||
|
|
||||||
### 第二轮:应用层测试
|
|
||||||
|
|
||||||
- 模拟音频 sample 输入,验证 session snapshot 更新。
|
|
||||||
- 模拟 AI 对手 power,验证能量条拉锯。
|
|
||||||
- 模拟权限失败,验证 phase 不进入 playing。
|
|
||||||
|
|
||||||
### 第三轮:组件 / 集成测试
|
|
||||||
|
|
||||||
- HUD 根据 snapshot 显示倒计时。
|
|
||||||
- HUD 根据 energy 渲染红蓝能量条比例。
|
|
||||||
- 权限拒绝时显示重试入口。
|
|
||||||
- 结算阶段显示胜负与再来一局。
|
|
||||||
|
|
||||||
### 第四轮:浏览器 smoke / playtest
|
|
||||||
|
|
||||||
- 本地启动页面。
|
|
||||||
- 授权麦克风。
|
|
||||||
- 对麦克风发声后看到拟声词与能量条变化。
|
|
||||||
- 移动端宽度下主游戏画面不被 HUD 遮挡。
|
|
||||||
|
|
||||||
## 建议文件结构
|
|
||||||
|
|
||||||
如果作为独立前端玩法原型,可采用:
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/games/bark-battle/
|
|
||||||
domain/
|
|
||||||
BarkBattleSession.ts
|
|
||||||
BarkDetector.ts
|
|
||||||
EnergyTugOfWar.ts
|
|
||||||
BarkBattleScoring.ts
|
|
||||||
OpponentStrategy.ts
|
|
||||||
application/
|
|
||||||
BarkBattleController.ts
|
|
||||||
BrowserMicrophoneInput.ts
|
|
||||||
phaser/
|
|
||||||
BarkBattleScene.ts
|
|
||||||
BarkBattlePreloadScene.ts
|
|
||||||
barkBattleAssets.ts
|
|
||||||
ui/
|
|
||||||
BarkBattleHud.tsx
|
|
||||||
BarkBattleResultPanel.tsx
|
|
||||||
BarkBattlePermissionPanel.tsx
|
|
||||||
tests/
|
|
||||||
BarkDetector.test.ts
|
|
||||||
EnergyTugOfWar.test.ts
|
|
||||||
BarkBattleSession.test.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
如果接入 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
|
|
||||||
```
|
|
||||||
|
|
||||||
MVP 阶段建议先做浏览器单机 runtime 原型,再决定是否进入创作入口、作品发布、广场和后端持久化。
|
|
||||||
|
|
||||||
## UI / 视觉方向
|
|
||||||
|
|
||||||
### 画面
|
|
||||||
|
|
||||||
- 横版固定舞台。
|
|
||||||
- 左右两只狗对峙。
|
|
||||||
- 背景可先做公园一张图,后续扩展海边、街区等地图。
|
|
||||||
- 狗狗用 2D sprite 或简单骨架帧动画。
|
|
||||||
|
|
||||||
### HUD
|
|
||||||
|
|
||||||
- 顶部:红蓝声浪能量条。
|
|
||||||
- 中央:大号倒计时,只在开局和关键时间突出显示。
|
|
||||||
- 左右:双方狗狗状态,不堆叠复杂面板。
|
|
||||||
- 底部或角落:麦克风状态、小型重试按钮。
|
|
||||||
- 结算:居中弹出简洁面板,显示胜负和关键数据。
|
|
||||||
|
|
||||||
### 动效
|
|
||||||
|
|
||||||
- 叫声触发狗狗张嘴。
|
|
||||||
- 声音越大,拟声词越大,冲击波越宽。
|
|
||||||
- combo 时触发短暂屏幕震动,但不能遮挡能量条。
|
|
||||||
- 尊重 reduced motion,非必要动画可降级。
|
|
||||||
|
|
||||||
## 测试映射
|
|
||||||
|
|
||||||
| BDD 场景 | 测试层级 | 目标文件 | 状态 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| 玩家允许麦克风权限后进入准备倒计时 | application/component | `BarkBattleController.test.ts`, `BarkBattlePermissionPanel.test.tsx` | planned |
|
|
||||||
| 玩家拒绝麦克风权限 | application/component | `BarkBattleController.test.ts`, `BarkBattlePermissionPanel.test.tsx` | planned |
|
|
||||||
| 玩家发出一次有效狗叫 | unit | `BarkDetector.test.ts` | planned |
|
|
||||||
| 玩家连续大声狗叫压制对手 | unit/integration | `EnergyTugOfWar.test.ts`, `BarkBattleController.test.ts` | planned |
|
|
||||||
| 环境噪音低于阈值不计入叫声 | unit | `BarkDetector.test.ts` | planned |
|
|
||||||
| 倒计时结束时玩家侧占优 | unit | `BarkBattleSession.test.ts` | planned |
|
|
||||||
| 倒计时结束时双方接近平衡 | unit | `BarkBattleSession.test.ts` | planned |
|
|
||||||
| 当前浏览器不支持麦克风 API | component | `BarkBattlePermissionPanel.test.tsx` | planned |
|
|
||||||
| 移动端进入对战页面 | visual/smoke | Playwright 或人工 playtest 清单 | planned |
|
|
||||||
|
|
||||||
## 验证命令建议
|
|
||||||
|
|
||||||
具体命令以后续实际落地位置为准,建议包括:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/**/*.test.ts
|
|
||||||
npm run test -- --run src/games/bark-battle/**/*.test.tsx
|
|
||||||
npm run typecheck
|
|
||||||
npm run check:encoding
|
|
||||||
```
|
|
||||||
|
|
||||||
若接入 Genarrative 后端或玩法配置,还需要追加:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd server-rs && cargo check -p api-server -p shared-contracts --no-default-features
|
|
||||||
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实施阶段拆分
|
|
||||||
|
|
||||||
### Phase 0:产品与技术定稿
|
|
||||||
|
|
||||||
- 确认玩法 ID:`bark-battle`。
|
|
||||||
- 确认 MVP 只做单机玩家 vs AI,不做实时多人。
|
|
||||||
- 确认是否只做 runtime 原型,还是接入 Genarrative 创作入口。
|
|
||||||
- 确认是否允许浏览器麦克风权限作为核心输入。
|
|
||||||
|
|
||||||
### Phase 1:纯领域模型
|
|
||||||
|
|
||||||
- 建立 bark-battle domain。
|
|
||||||
- 按 TDD 写 `BarkDetector`、`EnergyTugOfWar`、`BarkBattleSession` 测试。
|
|
||||||
- 实现最小规则让测试通过。
|
|
||||||
|
|
||||||
### Phase 2:麦克风输入适配
|
|
||||||
|
|
||||||
- 封装 Web Audio API。
|
|
||||||
- 支持权限请求、权限失败、环境噪音校准。
|
|
||||||
- 使用 mock input 完成自动化测试,真实麦克风做 smoke。
|
|
||||||
|
|
||||||
### Phase 3:Phaser 2D runtime
|
|
||||||
|
|
||||||
- 新建 Phaser Scene。
|
|
||||||
- 绘制或占位加载公园背景、左右狗狗、声浪特效。
|
|
||||||
- Scene 只消费 snapshot,不写规则。
|
|
||||||
- 接入 DOM HUD。
|
|
||||||
|
|
||||||
### Phase 4:反馈与结算
|
|
||||||
|
|
||||||
- 加入拟声词、冲击波、狗狗张嘴动画。
|
|
||||||
- 加入结算面板。
|
|
||||||
- 加入再来一局与返回入口。
|
|
||||||
|
|
||||||
### Phase 5:Genarrative 集成可选项
|
|
||||||
|
|
||||||
若要正式接入玩法类型:
|
|
||||||
|
|
||||||
- 补 `shared-contracts` 中 bark-battle runtime/result DTO。
|
|
||||||
- 补前端 service 与 runtime shell。
|
|
||||||
- 补入口配置数据库 seed。
|
|
||||||
- 补作品架 / 发布 / 广场链路,若需要持久化成绩或作品。
|
|
||||||
- 按 `genarrative-play-type-integration` 执行完整闭环验证。
|
|
||||||
|
|
||||||
## 风险与权衡
|
|
||||||
|
|
||||||
### 麦克风权限风险
|
|
||||||
|
|
||||||
浏览器麦克风权限受 HTTPS、浏览器策略、用户设置影响。MVP 需要明确:
|
|
||||||
|
|
||||||
- 本地开发可在 localhost 使用。
|
|
||||||
- 线上必须 HTTPS。
|
|
||||||
- 权限拒绝需要可恢复。
|
|
||||||
|
|
||||||
### 声音识别准确性风险
|
|
||||||
|
|
||||||
MVP 不建议做复杂“是否真的是狗叫”的 AI 识别,否则实现成本高、误判多。建议先用:
|
|
||||||
|
|
||||||
- 音量阈值
|
|
||||||
- 峰值次数
|
|
||||||
- 节奏间隔
|
|
||||||
- 环境噪音校准
|
|
||||||
|
|
||||||
后续再考虑加入频谱特征或 ML 分类。
|
|
||||||
|
|
||||||
### 噪音作弊风险
|
|
||||||
|
|
||||||
玩家可以喊叫、拍桌子或播放音频。若是娱乐派对玩法可以接受;若要竞技公平,需要后续加入:
|
|
||||||
|
|
||||||
- 频谱特征
|
|
||||||
- 输入冷却
|
|
||||||
- 异常持续噪音削弱
|
|
||||||
- 本地/服务端反作弊策略
|
|
||||||
|
|
||||||
### 移动端兼容风险
|
|
||||||
|
|
||||||
移动端 Web Audio 可能需要用户手势激活 AudioContext。计划中需把“开始”按钮作为显式用户手势,避免自动启动失败。
|
|
||||||
|
|
||||||
### UI 遮挡风险
|
|
||||||
|
|
||||||
视频原型中的核心可读信息非常少:倒计时、能量条、狗狗、拟声词。实现时应避免把说明文案、复杂面板长期铺在画面上。
|
|
||||||
|
|
||||||
## 开放问题
|
|
||||||
|
|
||||||
1. MVP 是“玩家 vs AI”,还是需要从第一版开始支持双人同屏 / 联机?
|
|
||||||
2. 是否要作为 Genarrative 新玩法入口完整接入,还是先做独立 runtime 原型?
|
|
||||||
3. 是否需要记录成绩、发布作品、进入作品架和广场?
|
|
||||||
4. 狗狗与背景素材是使用临时占位、AI 生成,还是需要复用项目既有素材系统?
|
|
||||||
5. 是否允许游戏强依赖麦克风权限,还是必须提供键盘备用输入?
|
|
||||||
|
|
||||||
## 推荐下一步
|
|
||||||
|
|
||||||
建议下一步先执行 Phase 0 + Phase 1:
|
|
||||||
|
|
||||||
1. 明确 MVP 边界:单机玩家 vs AI。
|
|
||||||
2. 写 `BarkDetector` / `EnergyTugOfWar` / `BarkBattleSession` 的 BDD 对应单元测试。
|
|
||||||
3. 不接 Phaser、不接麦克风,先把核心规则用 TDD 跑通。
|
|
||||||
4. 规则稳定后再接 Web Audio 与 Phaser runtime。
|
|
||||||
@@ -1,709 +0,0 @@
|
|||||||
# bark-battle 三阶段实施计划:浏览器原型 → AI 创作入口 → 数据库落地
|
|
||||||
|
|
||||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
|
||||||
|
|
||||||
**Goal:** 按“三阶段”推进 `bark-battle / 汪汪声浪大作战`:第一阶段先做纯浏览器可运行游戏原型并验证玩法跑通;第二阶段接入 Genarrative 创作入口,用 AI 生成可试玩内容;第三阶段再打通后端数据库、发布、成绩和作品闭环。
|
|
||||||
|
|
||||||
**Architecture:** 第一阶段只在前端 runtime 内闭环,优先落 `src/games/bark-battle/` 与直达路由,不依赖后端和 SpacetimeDB。第二阶段在已有创作入口、Agent flow controller、结果页和 runtime shell 上接入 `bark-battle`,AI 只生成配置化草稿,不承接正式业务真相。第三阶段按 `server-rs + Axum + SpacetimeDB` DDD 分层落库,前端只展示后端投影和调用后端 API。
|
|
||||||
|
|
||||||
**Tech Stack:** React 19、TypeScript、Vite、Vitest、Testing Library;第一阶段优先 DOM/Canvas 原型,可在验证玩法后再引入 Phaser 3;后端阶段使用 `server-rs`、Axum、SpacetimeDB、shared-contracts。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 当前上下文 / 假设
|
|
||||||
|
|
||||||
- 现有需求与技术文档:
|
|
||||||
- `docs/prd/BARK_BATTLE_BDD_2026-05-11.md`
|
|
||||||
- `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md`
|
|
||||||
- `docs/technical/BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md`
|
|
||||||
- 用户明确要求阶段顺序:
|
|
||||||
1. 第一阶段:先制作纯浏览器运行的游戏原型,需要测试游戏功能是否能跑通。
|
|
||||||
2. 第二阶段:打通创作入口,使用 AI 赋能游戏内容创作。
|
|
||||||
3. 第三阶段:最后打通数据库落地。
|
|
||||||
- 因此本计划调整原技术方案中的落地优先级:
|
|
||||||
- 第一阶段不新增后端表、不接发布、不接作品架。
|
|
||||||
- 第一阶段可以用 mock / local draft 配置与直达路由 `/bark-battle` 完成 playable prototype。
|
|
||||||
- 第一阶段若 Phaser 依赖尚未安装,优先用 React DOM + Canvas/CSS 2D 原型跑通功能;待核心规则验证后再决定是否引入 Phaser,避免第一阶段被依赖安装和素材管线阻塞。
|
|
||||||
- 当前仓库 `package.json` 还没有 `phaser` 依赖;如实现者选择 Phaser,需要单独评估依赖引入、包体和测试影响。
|
|
||||||
- 本计划只写计划,不直接实现代码。
|
|
||||||
|
|
||||||
## 1. 总体分阶段验收口径
|
|
||||||
|
|
||||||
### Phase 1:纯浏览器游戏原型
|
|
||||||
|
|
||||||
目标:打开本地前端路由即可玩到一局 `bark-battle`,并通过自动测试确认核心规则跑通。
|
|
||||||
|
|
||||||
必须满足:
|
|
||||||
- 可从 `/bark-battle` 进入独立原型页面。
|
|
||||||
- 不登录、不请求后端、不依赖数据库。
|
|
||||||
- 支持开发 mock input:点击/按键/按钮可模拟音量峰值;有真实麦克风时可走 Web Audio。
|
|
||||||
- 能完成:权限/开始 → 校准或 mock 准备 → 倒计时 → 30 秒 playing → 结算 → 再来一局。
|
|
||||||
- 低于阈值输入不计数;有效叫声计数;能量条向玩家或对手移动;结算胜/负/平。
|
|
||||||
- 移动端至少能看到能量条、倒计时、双方狗狗、主要按钮和结算。
|
|
||||||
|
|
||||||
### Phase 2:AI 创作入口
|
|
||||||
|
|
||||||
目标:创作者能从创作中心选择 `bark-battle`,用 AI 生成玩法配置草稿,并进入结果页试玩。
|
|
||||||
|
|
||||||
必须满足:
|
|
||||||
- 后端入口配置中出现 `bark-battle`,按开关展示/可点击。
|
|
||||||
- 前端类型分流、SelectionStage、工作台、结果页、runtime 入口齐全。
|
|
||||||
- AI 生成内容仅限配置化草稿:标题、主题、狗狗外观描述、背景风格、难度、局长、AI 对手参数、提示文案 key 等。
|
|
||||||
- 生成结果可在本地 runtime 中试玩。
|
|
||||||
- 未落库前可先用 session/local state 保存草稿,但要清楚标识为“未发布草稿”。
|
|
||||||
|
|
||||||
### Phase 3:数据库落地与正式作品闭环
|
|
||||||
|
|
||||||
目标:`bark-battle` 草稿、发布态配置、runtime start/finish、成绩和作品级游玩埋点都进入后端 DDD / SpacetimeDB 链路。
|
|
||||||
|
|
||||||
必须满足:
|
|
||||||
- `shared-contracts`、`module-bark-battle`、`spacetime-module`、`spacetime-client`、`api-server` 分层清晰。
|
|
||||||
- 发布为稳定作品 ID,runtime 从后端读取发布态 config。
|
|
||||||
- start 成功写 `work_play_start`:`scope_kind=work`、`scope_id=稳定作品 ID`、metadata 包含 `playType/workId/sourceRoute/userId`。
|
|
||||||
- finish 只上传派生指标,不保存原始麦克风音频、波形或可还原语音内容。
|
|
||||||
- 作品架/广场/分享/排行榜如启用,均来自后端投影。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Phase 1:纯浏览器运行游戏原型
|
|
||||||
|
|
||||||
### Task 1.1:补齐阶段边界文档
|
|
||||||
|
|
||||||
**Objective:** 在现有技术方案中明确“先浏览器原型,后 AI 创作,最后数据库”的落地顺序,避免实现时过早接后端。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md`
|
|
||||||
- Modify: `docs/prd/BARK_BATTLE_BDD_2026-05-11.md`
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
1. 在 runtime 技术方案中新增“三阶段落地顺序”小节。
|
|
||||||
2. 明确 Phase 1 不接后端、不接数据库、不接创作入口事实源。
|
|
||||||
3. 在 BDD 中补充“浏览器原型 smoke”验收场景。
|
|
||||||
4. 运行:
|
|
||||||
```bash
|
|
||||||
npm run check:encoding -- docs/prd/BARK_BATTLE_BDD_2026-05-11.md docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected:** 编码检查和 diff 空白检查通过。
|
|
||||||
|
|
||||||
### Task 1.2:建立 Phase 1 目录骨架和类型
|
|
||||||
|
|
||||||
**Objective:** 建立不依赖 React/DOM/Web Audio 的核心类型,后续所有测试和 UI 都围绕这些类型。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/games/bark-battle/domain/BarkBattleTypes.ts`
|
|
||||||
- Create: `src/games/bark-battle/application/BarkBattleConfig.ts`
|
|
||||||
|
|
||||||
**Key design:**
|
|
||||||
- `BarkBattlePhase = 'permission' | 'calibration' | 'countdown' | 'playing' | 'finished' | 'unavailable'`
|
|
||||||
- `MicrophoneFailureReason` 覆盖已有文档中的 9 类失败原因。
|
|
||||||
- `BarkBattleSnapshot` 包含 `phase/uiState/errorReason/statusMessageKey/remainingMs/energy/player/opponent/winner/result/lastEvents`。
|
|
||||||
- `BarkBattleConfig` 包含 `roundDurationMs/drawThreshold/minBarkGapMs/minBarkDurationMs/maxBarkDurationMs/balanceFactor/calibrationMaxWaitMs`。
|
|
||||||
|
|
||||||
**Tests:**
|
|
||||||
- 本任务可先不写运行时逻辑,但需要让 typecheck 能引用这些类型。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run typecheck
|
|
||||||
npm run check:encoding -- src/games/bark-battle/domain/BarkBattleTypes.ts src/games/bark-battle/application/BarkBattleConfig.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.3:TDD 实现叫声检测 BarkDetector
|
|
||||||
|
|
||||||
**Objective:** 用纯函数/纯类把音频样本转换为有效叫声事件。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/games/bark-battle/domain/BarkDetector.ts`
|
|
||||||
- Create: `src/games/bark-battle/domain/__tests__/BarkDetector.test.ts`
|
|
||||||
|
|
||||||
**Test cases:**
|
|
||||||
1. 超过阈值、持续时长合规、间隔足够时计为一次有效叫声。
|
|
||||||
2. 持续噪音不在每个 tick 无限计数。
|
|
||||||
3. 低于阈值的背景噪音不计数。
|
|
||||||
4. `minBarkGapMs` 内连续峰值不重复计数。
|
|
||||||
5. 过短脉冲不计数;过长持续声削弱为单段输入。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/domain/__tests__/BarkDetector.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.4:TDD 实现能量条 EnergyTugOfWar
|
|
||||||
|
|
||||||
**Objective:** 验证玩家/对手推动力能稳定改变 `energy`,并 clamp 到 `-100..100`。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/games/bark-battle/domain/EnergyTugOfWar.ts`
|
|
||||||
- Create: `src/games/bark-battle/domain/__tests__/EnergyTugOfWar.test.ts`
|
|
||||||
|
|
||||||
**Test cases:**
|
|
||||||
1. 玩家 power 高于对手时 `energy` 增加。
|
|
||||||
2. 对手 power 高于玩家时 `energy` 减少。
|
|
||||||
3. energy 不超过 `100`。
|
|
||||||
4. energy 不低于 `-100`。
|
|
||||||
5. power 相等时变化不超过浮点误差。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/domain/__tests__/EnergyTugOfWar.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.5:TDD 实现单局状态机 BarkBattleSession
|
|
||||||
|
|
||||||
**Objective:** 跑通 permission/calibration/countdown/playing/finished/unavailable 状态流转和结算。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/games/bark-battle/domain/BarkBattleSession.ts`
|
|
||||||
- Create: `src/games/bark-battle/domain/BarkBattleScoring.ts`
|
|
||||||
- Create: `src/games/bark-battle/domain/OpponentStrategy.ts`
|
|
||||||
- Create: `src/games/bark-battle/domain/__tests__/BarkBattleSession.test.ts`
|
|
||||||
- Create: `src/games/bark-battle/domain/__tests__/BarkBattleScoring.test.ts`
|
|
||||||
|
|
||||||
**Test cases:**
|
|
||||||
1. 校准完成后进入 countdown。
|
|
||||||
2. countdown 结束后进入 playing。
|
|
||||||
3. playing 中 `remainingMs` 随 tick 递减。
|
|
||||||
4. `remainingMs <= 0` 后进入 finished。
|
|
||||||
5. `energy > drawThreshold` 判定玩家胜。
|
|
||||||
6. `energy < -drawThreshold` 判定对手胜。
|
|
||||||
7. `abs(energy) <= drawThreshold` 判定平局。
|
|
||||||
8. finished 后新输入不再改变本局计数和能量。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/domain/__tests__/BarkBattleSession.test.ts src/games/bark-battle/domain/__tests__/BarkBattleScoring.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.6:实现 mock-first Application Controller
|
|
||||||
|
|
||||||
**Objective:** 不依赖真实麦克风,先用 mock audio sample 驱动完整 snapshot。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/games/bark-battle/application/BarkBattleController.ts`
|
|
||||||
- Create: `src/games/bark-battle/application/BarkBattleSnapshotStore.ts`
|
|
||||||
- Create: `src/games/bark-battle/application/__tests__/BarkBattleController.test.ts`
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- `startWithMockInput()` 进入校准完成或直接 countdown。
|
|
||||||
- `submitMockSample(sample)` 更新玩家输入。
|
|
||||||
- `tick(deltaMs)` 推进对手、能量条、倒计时。
|
|
||||||
- `restart()` 重置状态。
|
|
||||||
- `failMicrophone(reason)` 进入 `phase: 'unavailable'`,并设置 `errorReason/statusMessageKey`。
|
|
||||||
|
|
||||||
**Test cases:**
|
|
||||||
1. mock start 后能进入 countdown/playing。
|
|
||||||
2. 提交 mock 峰值后 bark count 增加。
|
|
||||||
3. tick 后 energy 可变化。
|
|
||||||
4. finish 后生成 result。
|
|
||||||
5. `failMicrophone('permission-denied')` 不进入 playing。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/application/__tests__/BarkBattleController.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.7:实现浏览器原型 UI Shell(不接平台)
|
|
||||||
|
|
||||||
**Objective:** 提供 `/bark-battle` 可访问的 playable prototype。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/BarkBattlePlaygroundApp.tsx`
|
|
||||||
- Create: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
|
|
||||||
- Create: `src/games/bark-battle/ui/BarkBattleHud.tsx`
|
|
||||||
- Create: `src/games/bark-battle/ui/BarkBattleResultPanel.tsx`
|
|
||||||
- Create: `src/games/bark-battle/ui/BarkBattleHud.css`
|
|
||||||
- Modify: `src/routing/appRoutes.tsx`
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- 新增路由匹配:`/bark-battle`。
|
|
||||||
- 首屏只有清爽开始面板,不常驻大段规则。
|
|
||||||
- 提供开发原型按钮:开始、模拟叫声、模拟对手增强、再来一局。
|
|
||||||
- playing 画面展示:顶部能量条、倒计时、玩家/对手狗狗、叫声次数、麦克风/mock 状态。
|
|
||||||
- 结算面板独立居中,不追加在当前面板下方。
|
|
||||||
|
|
||||||
**UI constraints:**
|
|
||||||
- 移动端优先。
|
|
||||||
- 正常 playing 阶段不在 playfield 常驻规则说明。
|
|
||||||
- 大动效不遮挡顶部能量条和倒计时。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/ui/**/*.test.tsx
|
|
||||||
npm run typecheck
|
|
||||||
npm run dev:web
|
|
||||||
# 手动 smoke: 访问 /bark-battle → 开始 → 模拟叫声 → energy 变化 → 结算 → 再来一局
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.8:实现 HUD 组件测试
|
|
||||||
|
|
||||||
**Objective:** 自动验证核心 UI 状态,不只依赖人工试玩。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/games/bark-battle/ui/__tests__/BarkBattleHud.test.tsx`
|
|
||||||
- Create: `src/games/bark-battle/ui/__tests__/BarkBattleResultPanel.test.tsx`
|
|
||||||
|
|
||||||
**Test cases:**
|
|
||||||
1. playing 阶段展示倒计时和能量条。
|
|
||||||
2. energy 正值时玩家侧占比更大。
|
|
||||||
3. energy 负值时对手侧占比更大。
|
|
||||||
4. permission-denied 展示重试授权入口。
|
|
||||||
5. unsupported 不展示开始声控按钮。
|
|
||||||
6. finished 展示胜负、叫声次数、再来一局。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/ui/__tests__/BarkBattleHud.test.tsx src/games/bark-battle/ui/__tests__/BarkBattleResultPanel.test.tsx
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.9:接入真实 Web Audio(可晚于 mock 原型)
|
|
||||||
|
|
||||||
**Objective:** 在支持麦克风的浏览器中真实采样并驱动 controller,同时保留 mock fallback 便于测试。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/games/bark-battle/infrastructure/BrowserMicrophoneInput.ts`
|
|
||||||
- Create: `src/games/bark-battle/infrastructure/AudioAnalyserSampler.ts`
|
|
||||||
- Create: `src/games/bark-battle/infrastructure/MicrophonePermission.ts`
|
|
||||||
- Create: `src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts`
|
|
||||||
- Create: `src/games/bark-battle/infrastructure/__tests__/AudioAnalyserSampler.test.ts`
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- 用户点击开始后才请求麦克风。
|
|
||||||
- 用户手势后创建/resume `AudioContext`。
|
|
||||||
- 输出归一化 `BarkAudioSample`。
|
|
||||||
- 捕获并映射:unsupported、permission-denied、non-secure-context、not-found、not-readable、audio-context-blocked、unknown。
|
|
||||||
- stop/restart/page unload 时停止 tracks。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/infrastructure/__tests__/BrowserMicrophoneInput.test.ts src/games/bark-battle/infrastructure/__tests__/AudioAnalyserSampler.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
npm run dev:web
|
|
||||||
# 手动 smoke: 真实麦克风授权 → 校准 → 发声 → 结算
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.10:Phase 1 收口验证
|
|
||||||
|
|
||||||
**Objective:** 确认“纯浏览器原型”已经可以交给产品/测试试玩。
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/games/bark-battle/domain/**/*.test.ts src/games/bark-battle/application/**/*.test.ts src/games/bark-battle/infrastructure/**/*.test.ts src/games/bark-battle/ui/**/*.test.tsx
|
|
||||||
npm run typecheck
|
|
||||||
npm run lint:eslint
|
|
||||||
npm run check:encoding
|
|
||||||
npm run dev:web
|
|
||||||
```
|
|
||||||
|
|
||||||
**Manual smoke checklist:**
|
|
||||||
- [ ] `/bark-battle` 能打开。
|
|
||||||
- [ ] mock 模式可完整完成一局。
|
|
||||||
- [ ] 真实麦克风模式可授权、校准、发声、结算。
|
|
||||||
- [ ] 拒绝权限后不会进入 playing。
|
|
||||||
- [ ] 移动端窄屏能看到核心信息并能点击主要按钮。
|
|
||||||
- [ ] 再来一局不会继承上一局 energy/barkCount/result。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Phase 2:打通创作入口,用 AI 赋能内容创作
|
|
||||||
|
|
||||||
### Task 2.1:定义 `bark-battle` 草稿契约(前端本地版)
|
|
||||||
|
|
||||||
**Objective:** 在接后端前,先定义 AI 可生成的 runtime draft shape。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `packages/shared/src/contracts/barkBattle.ts`(临时前端共享类型,后端阶段再对齐 Rust shared-contracts)
|
|
||||||
- Create: `src/services/bark-battle-creation/barkBattleDraftDefaults.ts`
|
|
||||||
- Create: `src/services/bark-battle-creation/barkBattleDraftValidation.ts`
|
|
||||||
|
|
||||||
**Draft fields:**
|
|
||||||
- `title`
|
|
||||||
- `description`
|
|
||||||
- `themePrompt`
|
|
||||||
- `playerDogName`
|
|
||||||
- `opponentDogName`
|
|
||||||
- `backgroundStyle`
|
|
||||||
- `difficulty`
|
|
||||||
- `roundDurationMs`
|
|
||||||
- `drawThreshold`
|
|
||||||
- `opponentConfig`
|
|
||||||
- `audioSensitivityPreset`
|
|
||||||
- `visualStyle`
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/services/bark-battle-creation/**/*.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.2:新增创作入口配置
|
|
||||||
|
|
||||||
**Objective:** 让 `bark-battle` 出现在创作中心入口中,但可通过后端入口配置开关控制。
|
|
||||||
|
|
||||||
**Files likely to change:**
|
|
||||||
- `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
|
|
||||||
- `server-rs/crates/module-runtime/src/domain.rs`
|
|
||||||
- `server-rs/crates/module-runtime/src/application.rs`
|
|
||||||
- `server-rs/crates/shared-contracts/src/creation_entry_config.rs`
|
|
||||||
- `src/components/platform-entry/platformEntryCreationTypes.ts`
|
|
||||||
- `src/components/platform-entry/platformEntryCreationTypes.test.ts`
|
|
||||||
|
|
||||||
**Plan:**
|
|
||||||
1. 在入口 seed 中新增 `bark-battle`,首轮可设:`visible: true`、`open: true`(若需要灰度则 `open: false`)。
|
|
||||||
2. 前端展示派生只消费 API 返回,不恢复旧静态入口事实源。
|
|
||||||
3. 更新排序和锁定态测试。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
cd server-rs && cargo check -p api-server -p spacetime-module --no-default-features
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.3:扩展 SelectionStage 与流程分流
|
|
||||||
|
|
||||||
**Objective:** 点击 `bark-battle` 入口后进入对应创作工作台。
|
|
||||||
|
|
||||||
**Files likely to change:**
|
|
||||||
- `src/components/platform-entry/platformEntryTypes.ts`
|
|
||||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
- `src/components/platform-entry/usePlatformCreationAgentFlowController.ts`(如复用通用 agent flow)
|
|
||||||
|
|
||||||
**Stages:**
|
|
||||||
- `bark-battle-agent-workspace`
|
|
||||||
- `bark-battle-generating`(可选)
|
|
||||||
- `bark-battle-result`
|
|
||||||
- `bark-battle-runtime`
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- src/components/platform-entry/**/*.test.tsx src/components/platform-entry/**/*.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.4:实现 AI 创作工作台
|
|
||||||
|
|
||||||
**Objective:** 用对话式或表单式输入生成 `BarkBattleDraft`。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/components/bark-battle-creation/BarkBattleAgentWorkspace.tsx`
|
|
||||||
- Create: `src/services/bark-battle-creation/barkBattleCreationClient.ts`
|
|
||||||
- Create: `src/services/bark-battle-creation/barkBattlePromptBuilder.ts`
|
|
||||||
- Create: `src/services/bark-battle-creation/__tests__/barkBattlePromptBuilder.test.ts`
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- 用户输入一句主题,例如“柴犬在赛博公园比谁叫得响”。
|
|
||||||
- AI 返回结构化草稿。
|
|
||||||
- 前端校验并填默认值,不让非法 roundDuration/difficulty 进入 runtime。
|
|
||||||
- 错误时保留用户输入和已生成草稿。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/services/bark-battle-creation/**/*.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.5:实现结果页与试玩入口
|
|
||||||
|
|
||||||
**Objective:** AI 草稿生成后可查看、返回编辑、进入 Phase 1 runtime 试玩。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/components/bark-battle-result/BarkBattleResultView.tsx`
|
|
||||||
- Create: `src/components/bark-battle-result/BarkBattleResultView.test.tsx`
|
|
||||||
- Modify: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`(允许传入 draft config)
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- 展示标题、主题、狗狗名、背景风格、难度、局长。
|
|
||||||
- 提供“返回编辑”“试玩”按钮。
|
|
||||||
- 暂不展示发布按钮,或发布按钮显示为后端阶段能力。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- --run src/components/bark-battle-result/BarkBattleResultView.test.tsx
|
|
||||||
npm run typecheck
|
|
||||||
npm run dev:web
|
|
||||||
# 手动 smoke: 创作入口 → AI 草稿 → 结果页 → 试玩 → 返回编辑
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.6:Phase 2 收口验证
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
```bash
|
|
||||||
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/bark-battle-result/BarkBattleResultView.test.tsx src/services/bark-battle-creation/**/*.test.ts src/games/bark-battle/**/*.test.ts src/games/bark-battle/**/*.test.tsx
|
|
||||||
npm run typecheck
|
|
||||||
npm run lint:eslint
|
|
||||||
npm run check:encoding
|
|
||||||
```
|
|
||||||
|
|
||||||
**Manual smoke checklist:**
|
|
||||||
- [ ] 创作中心展示 `bark-battle`。
|
|
||||||
- [ ] 点击入口进入工作台。
|
|
||||||
- [ ] AI 可生成草稿。
|
|
||||||
- [ ] 草稿结果页可展示并返回编辑。
|
|
||||||
- [ ] 试玩使用草稿配置影响 runtime 表现。
|
|
||||||
- [ ] 未接数据库前不会假装发布成功。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Phase 3:数据库落地与正式作品闭环
|
|
||||||
|
|
||||||
### Task 3.1:补齐 shared contracts
|
|
||||||
|
|
||||||
**Objective:** 前后端共享 bark-battle DTO,避免前端手写正式契约漂移。
|
|
||||||
|
|
||||||
**Files likely to change:**
|
|
||||||
- `server-rs/crates/shared-contracts/src/bark_battle.rs`
|
|
||||||
- `server-rs/crates/shared-contracts/src/lib.rs`
|
|
||||||
- `packages/shared/src/contracts/barkBattle.ts`
|
|
||||||
|
|
||||||
**DTO:**
|
|
||||||
- `BarkBattleDraft`
|
|
||||||
- `BarkBattlePublishedConfig`
|
|
||||||
- `CreateBarkBattleSessionRequest/Response`
|
|
||||||
- `BarkBattleRuntimeStartRequest/Response`
|
|
||||||
- `BarkBattleRuntimeFinishRequest/Response`
|
|
||||||
- `BarkBattleRunResult`
|
|
||||||
- `BarkBattleScoreSummary`
|
|
||||||
- `BarkBattleLeaderboardEntry`
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run typecheck
|
|
||||||
cd server-rs && cargo check -p shared-contracts --no-default-features
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3.2:新增 `module-bark-battle` 纯领域模块
|
|
||||||
|
|
||||||
**Objective:** 后端正式分数、配置校验、提交合法性不写在 api-server handler 里。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `server-rs/crates/module-bark-battle/`
|
|
||||||
- Modify: `server-rs/Cargo.toml`
|
|
||||||
|
|
||||||
**Responsibilities:**
|
|
||||||
- 配置合法性校验。
|
|
||||||
- run start/finish 状态约束。
|
|
||||||
- 派生指标范围校验。
|
|
||||||
- 分数与排行榜排序分计算。
|
|
||||||
- 不接 Axum、不接 SpacetimeDB、不接 HTTP。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
cd server-rs && cargo test -p module-bark-battle --no-default-features
|
|
||||||
cd server-rs && cargo check -p module-bark-battle --no-default-features
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3.3:SpacetimeDB 表、reducer、migration
|
|
||||||
|
|
||||||
**Objective:** 保存草稿、发布态配置、run、result、leaderboard 投影。
|
|
||||||
|
|
||||||
**Files likely to change:**
|
|
||||||
- `server-rs/crates/spacetime-module/src/runtime/bark_battle.rs`(或按现有模块目录命名)
|
|
||||||
- `server-rs/crates/spacetime-module/src/migration.rs`
|
|
||||||
- `server-rs/crates/spacetime-module/src/lib.rs`
|
|
||||||
- 生成绑定目录(通过命令生成,不手改生成物)
|
|
||||||
|
|
||||||
**Tables draft:**
|
|
||||||
- `bark_battle_draft`
|
|
||||||
- `bark_battle_published_config`
|
|
||||||
- `bark_battle_run`
|
|
||||||
- `bark_battle_run_result`
|
|
||||||
- `bark_battle_leaderboard_entry`
|
|
||||||
|
|
||||||
**Reducers/procedures:**
|
|
||||||
- `create_bark_battle_draft`
|
|
||||||
- `publish_bark_battle_config`
|
|
||||||
- `start_bark_battle_run`
|
|
||||||
- `finish_bark_battle_run`
|
|
||||||
- `list_bark_battle_leaderboard`
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run spacetime:generate
|
|
||||||
cd server-rs && cargo check -p spacetime-module --no-default-features
|
|
||||||
npm run check:server-rs-ddd
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3.4:spacetime-client facade
|
|
||||||
|
|
||||||
**Objective:** api-server 通过 facade 调用 SpacetimeDB,不直接散落 reducer 细节。
|
|
||||||
|
|
||||||
**Files likely to change:**
|
|
||||||
- `server-rs/crates/spacetime-client/src/runtime.rs`
|
|
||||||
- `server-rs/crates/spacetime-client/src/mapper.rs`
|
|
||||||
- `server-rs/crates/spacetime-client/src/lib.rs`
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
cd server-rs && cargo check -p spacetime-client --no-default-features
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3.5:api-server BFF 路由
|
|
||||||
|
|
||||||
**Objective:** 提供创作、发布态 runtime start/finish、leaderboard API。
|
|
||||||
|
|
||||||
**Files likely to change:**
|
|
||||||
- `server-rs/crates/api-server/src/bark_battle.rs`
|
|
||||||
- `server-rs/crates/api-server/src/main.rs` 或路由注册文件
|
|
||||||
|
|
||||||
**Routes draft:**
|
|
||||||
- `POST /api/bark-battle/sessions`
|
|
||||||
- `GET /api/bark-battle/sessions/:sessionId`
|
|
||||||
- `POST /api/bark-battle/runtime/start`
|
|
||||||
- `POST /api/bark-battle/runtime/finish`
|
|
||||||
- `GET /api/bark-battle/works/:workId/runtime-config`
|
|
||||||
- `GET /api/bark-battle/works/:workId/leaderboard`
|
|
||||||
|
|
||||||
**Tracking:**
|
|
||||||
- runtime start 成功后主动写 `work_play_start`。
|
|
||||||
- `scope_kind=work`。
|
|
||||||
- `scope_id=稳定作品 ID`。
|
|
||||||
- metadata 包含 `playType=bark-battle`、`workId`、`sourceRoute`、`userId`。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run api-server
|
|
||||||
# 另一个终端检查 /healthz,并执行对应 API smoke
|
|
||||||
cd server-rs && cargo check -p api-server --no-default-features
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3.6:前端正式 client 与 runtime 切换
|
|
||||||
|
|
||||||
**Objective:** runtime 从本地草稿模式升级为可读取后端发布态 config,并提交正式派生结果。
|
|
||||||
|
|
||||||
**Files likely to change:**
|
|
||||||
- Create: `src/services/bark-battle-runtime/barkBattleRuntimeClient.ts`
|
|
||||||
- Create: `src/services/bark-battle-works/barkBattleWorksClient.ts`
|
|
||||||
- Modify: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
|
|
||||||
- Modify: `src/components/bark-battle-result/BarkBattleResultView.tsx`
|
|
||||||
- Modify: `src/components/custom-world-home/creationWorkShelf.ts`
|
|
||||||
- Modify: `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
- Modify: `src/services/publicWorkCode.ts`
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- 本地 preview 仍可使用 draft config。
|
|
||||||
- 正式作品 runtime 必须先调用 start API,拿 run token/session。
|
|
||||||
- finish 只提交派生 metrics。
|
|
||||||
- 发布后刷新作品架/广场。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
```bash
|
|
||||||
npm run test -- src/services/bark-battle-runtime/**/*.test.ts src/games/bark-battle/**/*.test.ts src/games/bark-battle/**/*.test.tsx
|
|
||||||
npm run typecheck
|
|
||||||
npm run check:encoding
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3.7:Phase 3 收口验证
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
```bash
|
|
||||||
npm run test -- src/games/bark-battle/**/*.test.ts src/games/bark-battle/**/*.test.tsx src/services/bark-battle-runtime/**/*.test.ts src/services/bark-battle-creation/**/*.test.ts
|
|
||||||
npm run typecheck
|
|
||||||
npm run lint:eslint
|
|
||||||
npm run check:encoding
|
|
||||||
npm run check:server-rs-ddd
|
|
||||||
cd server-rs && cargo test -p module-bark-battle --no-default-features
|
|
||||||
cd server-rs && cargo check -p api-server -p spacetime-module -p spacetime-client -p shared-contracts --no-default-features
|
|
||||||
npm run api-server
|
|
||||||
```
|
|
||||||
|
|
||||||
**Manual smoke checklist:**
|
|
||||||
- [ ] 创作者生成并发布 bark-battle 作品。
|
|
||||||
- [ ] 玩家从作品页进入 runtime。
|
|
||||||
- [ ] start API 成功并写 `work_play_start`。
|
|
||||||
- [ ] 浏览器本地完成一局。
|
|
||||||
- [ ] finish API 只上传派生指标。
|
|
||||||
- [ ] 成绩/排行榜/作品架刷新来自后端投影。
|
|
||||||
- [ ] 拒绝麦克风权限时不会创建非法 finished result。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 文件清单总览
|
|
||||||
|
|
||||||
### Phase 1 likely files
|
|
||||||
|
|
||||||
- `src/routing/appRoutes.tsx`
|
|
||||||
- `src/BarkBattlePlaygroundApp.tsx`
|
|
||||||
- `src/games/bark-battle/domain/BarkBattleTypes.ts`
|
|
||||||
- `src/games/bark-battle/domain/BarkDetector.ts`
|
|
||||||
- `src/games/bark-battle/domain/EnergyTugOfWar.ts`
|
|
||||||
- `src/games/bark-battle/domain/BarkBattleSession.ts`
|
|
||||||
- `src/games/bark-battle/domain/BarkBattleScoring.ts`
|
|
||||||
- `src/games/bark-battle/domain/OpponentStrategy.ts`
|
|
||||||
- `src/games/bark-battle/application/BarkBattleConfig.ts`
|
|
||||||
- `src/games/bark-battle/application/BarkBattleController.ts`
|
|
||||||
- `src/games/bark-battle/application/BarkBattleSnapshotStore.ts`
|
|
||||||
- `src/games/bark-battle/infrastructure/BrowserMicrophoneInput.ts`
|
|
||||||
- `src/games/bark-battle/infrastructure/AudioAnalyserSampler.ts`
|
|
||||||
- `src/games/bark-battle/infrastructure/MicrophonePermission.ts`
|
|
||||||
- `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
|
|
||||||
- `src/games/bark-battle/ui/BarkBattleHud.tsx`
|
|
||||||
- `src/games/bark-battle/ui/BarkBattleResultPanel.tsx`
|
|
||||||
- `src/games/bark-battle/ui/BarkBattleHud.css`
|
|
||||||
|
|
||||||
### Phase 2 likely files
|
|
||||||
|
|
||||||
- `packages/shared/src/contracts/barkBattle.ts`
|
|
||||||
- `src/components/platform-entry/platformEntryTypes.ts`
|
|
||||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
- `src/components/platform-entry/platformEntryCreationTypes.ts`
|
|
||||||
- `src/components/bark-battle-creation/BarkBattleAgentWorkspace.tsx`
|
|
||||||
- `src/components/bark-battle-result/BarkBattleResultView.tsx`
|
|
||||||
- `src/services/bark-battle-creation/*`
|
|
||||||
|
|
||||||
### Phase 3 likely files
|
|
||||||
|
|
||||||
- `server-rs/crates/shared-contracts/src/bark_battle.rs`
|
|
||||||
- `server-rs/crates/module-bark-battle/*`
|
|
||||||
- `server-rs/crates/spacetime-module/src/runtime/bark_battle.rs`
|
|
||||||
- `server-rs/crates/spacetime-module/src/migration.rs`
|
|
||||||
- `server-rs/crates/spacetime-client/src/runtime.rs`
|
|
||||||
- `server-rs/crates/spacetime-client/src/mapper.rs`
|
|
||||||
- `server-rs/crates/api-server/src/bark_battle.rs`
|
|
||||||
- `src/services/bark-battle-runtime/*`
|
|
||||||
- `src/services/bark-battle-works/*`
|
|
||||||
- `src/components/custom-world-home/creationWorkShelf.ts`
|
|
||||||
- `src/services/publicWorkCode.ts`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 风险、取舍与开放问题
|
|
||||||
|
|
||||||
### 风险
|
|
||||||
|
|
||||||
1. **麦克风权限和移动端 AudioContext 差异大。** 需要 mock input 保底,否则自动化和本地开发会被真实设备阻塞。
|
|
||||||
2. **第一阶段过早引入 Phaser 可能拖慢验证。** 当前仓库没有 `phaser` 依赖;建议先用 DOM/Canvas 跑通玩法,再决定是否引入 Phaser。
|
|
||||||
3. **AI 草稿和正式发布配置容易漂移。** Phase 2 临时 TS 类型必须在 Phase 3 与 Rust shared-contracts 对齐。
|
|
||||||
4. **不能保存原始音频。** 后端阶段只能保存派生指标,任何音频片段、波形、频谱明细都不应落库。
|
|
||||||
5. **入口配置事实源在后端/SpacetimeDB。** Phase 2 接入口时不要恢复旧前端静态入口配置。
|
|
||||||
|
|
||||||
### 取舍
|
|
||||||
|
|
||||||
- Phase 1 先把“游戏是否好玩、功能是否跑通”作为第一目标,不追求正式作品闭环。
|
|
||||||
- Phase 2 让 AI 生成内容配置,而不是让 AI 直接生成任意代码或不受控规则。
|
|
||||||
- Phase 3 再把正式业务真相交给后端,避免前端 runtime 先背上发布、成绩、排行榜的复杂度。
|
|
||||||
|
|
||||||
### 开放问题
|
|
||||||
|
|
||||||
1. Phase 1 是否必须使用 Phaser?如果只是验证玩法,可先使用 DOM/CSS/Canvas 原型,后续再替换 renderer。
|
|
||||||
2. `bark-battle` 的正式中文名是否固定为“汪汪声浪大作战”?如果名称要改,需先统一文档、入口配置和分享标题。
|
|
||||||
3. AI 创作阶段是否需要生成图片/狗狗视觉资产,还是只生成风格描述和使用占位素材?
|
|
||||||
4. 是否需要排行榜作为 Phase 3 必选,还是作为数据库落地后的增强项?
|
|
||||||
5. 真实麦克风 smoke 需要哪些目标设备:Chrome 桌面、Android Chrome、iOS Safari 是否都纳入首批验收?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 建议执行方式
|
|
||||||
|
|
||||||
1. 先按 Phase 1 执行,且每个 domain/application task 坚持 TDD:先失败测试,再实现。
|
|
||||||
2. Phase 1 合并前不要接数据库,不要新增后端表,不要把入口配置切到 open。
|
|
||||||
3. Phase 1 验证通过后,让产品/团队试玩 `/bark-battle`,确认玩法数值和 UI 方向。
|
|
||||||
4. 再进入 Phase 2,把 AI 创作工作台接到同一个 runtime draft config。
|
|
||||||
5. 最后进入 Phase 3,按后端 DDD 文档做数据库、发布、成绩和追踪闭环。
|
|
||||||
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
# K6 作品列表压测计划(使用 spacetime-migration-7.json 作为数据源)
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
使用 K6 对 Genarrative 的“作品列表”相关接口进行压测,并将用户提供的 `spacetime-migration-7.json` 作为压测数据源;数据处理时**只导入作品列表相关数据**,不导入用户、会话、钱包、埋点、运行存档等非作品表,避免把敏感或无关数据带入压测环境。
|
|
||||||
|
|
||||||
## 当前上下文
|
|
||||||
|
|
||||||
- 工作区:`/c/proj/Genarrative`
|
|
||||||
- 原始迁移文件:`C:\Users\DSK\AppData\Local\hermes\cache\documents\doc_150e84029b2d_spacetime-migration-7.json`
|
|
||||||
- 已确认原始迁移文件结构:
|
|
||||||
- `schema_version = 1`
|
|
||||||
- `tables = 53`
|
|
||||||
- 作品相关表中当前有数据的重点表:
|
|
||||||
- `puzzle_work_profile`:80 行
|
|
||||||
- `custom_world_profile`:1 行
|
|
||||||
- `match3d_work_profile`:0 行
|
|
||||||
- `big_fish_*`:当前样本中相关表为 0 行
|
|
||||||
- 原始文件还包含 `user_account`、`auth_identity`、`refresh_session`、`profile_wallet_ledger`、`asset_object`、运行记录等数据,压测导入时必须过滤。
|
|
||||||
- 当前仓库未发现现成 K6 脚本或 `k6` 相关文件,需要新增压测脚本与数据提取脚本。
|
|
||||||
- `package.json` 当前有 `dev/dev:rust/test/check` 等脚本,未发现 K6 npm script。
|
|
||||||
|
|
||||||
## 范围约束
|
|
||||||
|
|
||||||
### 本次只导入/使用
|
|
||||||
|
|
||||||
1. 作品列表表:
|
|
||||||
- `puzzle_work_profile`
|
|
||||||
- `custom_world_profile`
|
|
||||||
- 后续若接口覆盖其他玩法,可扩展:
|
|
||||||
- `match3d_work_profile`
|
|
||||||
- `square_hole_work_profile`(以实际 SpacetimeDB 表名为准)
|
|
||||||
- `big_fish_work_profile`(以实际 SpacetimeDB 表名为准)
|
|
||||||
- `visual_novel_work_profile`(以实际 SpacetimeDB 表名为准)
|
|
||||||
2. 为作品列表卡片展示所需的最小字段:
|
|
||||||
- 稳定 ID:`profile_id`、`work_id` 或 `public_work_code`
|
|
||||||
- 标题:`work_title` / `level_name` / `world_name`
|
|
||||||
- 描述:`work_description` / `summary` / `summary_text` / `subtitle`
|
|
||||||
- 作者:`owner_user_id`、`author_display_name`、`author_public_user_code`
|
|
||||||
- 封面:`cover_image_src`、`cover_asset_id`(如果接口只返回 asset id,则压测阶段不额外导入二进制 asset)
|
|
||||||
- 状态与计数:`publication_status`、`published_at`、`play_count`、`like_count`、`remix_count`
|
|
||||||
- 作品内容摘要:`levels_json`、`profile_payload_json`、`theme_tags_json` 等列表渲染或进入作品详情可能需要的 JSON 字段
|
|
||||||
|
|
||||||
### 本次不导入/不使用
|
|
||||||
|
|
||||||
- 认证与账号:`user_account`、`auth_identity`、`refresh_session`、`auth_store_snapshot`
|
|
||||||
- 用户资产与钱包:`profile_wallet_ledger`、`profile_dashboard_state`、`profile_redeem_*`、`profile_invite_*`
|
|
||||||
- 游玩历史/存档/运行态:`profile_played_world`、`public_work_play_daily_stat`、`puzzle_runtime_run`、`profile_save_archive`、`runtime_snapshot` 等
|
|
||||||
- AI 任务过程:`ai_task`、`ai_task_stage`、`ai_text_chunk`
|
|
||||||
- asset 二进制与绑定:`asset_object`、`asset_entity_binding`,除非后续确认作品列表接口强依赖它们;即便需要,也只导入作品列表封面所需的最小 metadata,不导入原始大对象。
|
|
||||||
|
|
||||||
## 推荐目录与文件
|
|
||||||
|
|
||||||
建议新增:
|
|
||||||
|
|
||||||
```text
|
|
||||||
.hermes/plans/2026-05-11_195214-k6-works-list-load-test-plan.md # 本计划
|
|
||||||
scripts/loadtest/extract-works-list-data.mjs # 从迁移文件提取作品列表数据
|
|
||||||
scripts/loadtest/k6-works-list.js # K6 压测脚本
|
|
||||||
scripts/loadtest/data/works-list.sample.json # 过滤后的样例数据(不要提交敏感原始迁移全量)
|
|
||||||
scripts/loadtest/README.md # 执行说明与指标阈值
|
|
||||||
```
|
|
||||||
|
|
||||||
可选新增 npm scripts:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"loadtest:extract-works": "node scripts/loadtest/extract-works-list-data.mjs",
|
|
||||||
"loadtest:k6:works": "k6 run scripts/loadtest/k6-works-list.js"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据提取方案
|
|
||||||
|
|
||||||
### 输入
|
|
||||||
|
|
||||||
默认读取:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node scripts/loadtest/extract-works-list-data.mjs \
|
|
||||||
--input "C:/Users/DSK/AppData/Local/hermes/cache/documents/doc_150e84029b2d_spacetime-migration-7.json" \
|
|
||||||
--output scripts/loadtest/data/works-list.local.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### 输出结构
|
|
||||||
|
|
||||||
建议输出为 K6 直接可读的 JSON:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"source": "spacetime-migration-7.json",
|
|
||||||
"generatedAt": "<iso datetime>",
|
|
||||||
"tables": {
|
|
||||||
"puzzle_work_profile": [
|
|
||||||
{
|
|
||||||
"profile_id": "...",
|
|
||||||
"work_id": "...",
|
|
||||||
"owner_user_id": "...",
|
|
||||||
"work_title": "...",
|
|
||||||
"work_description": "...",
|
|
||||||
"publication_status": "Published",
|
|
||||||
"published_at": { "__timestamp_micros_since_unix_epoch__": 0 },
|
|
||||||
"play_count": 0,
|
|
||||||
"like_count": 0,
|
|
||||||
"levels_json": "..."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"custom_world_profile": []
|
|
||||||
},
|
|
||||||
"workIds": {
|
|
||||||
"puzzle": ["<profile_id>"],
|
|
||||||
"customWorld": ["<profile_id>"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 过滤原则
|
|
||||||
|
|
||||||
1. 按 `tables[].name` 白名单过滤,只保留作品 profile 表。
|
|
||||||
2. 对每个 row 再按字段白名单过滤,避免误带账号、手机号、token、钱包流水等字段。
|
|
||||||
3. 对特别大的字段进行处理:
|
|
||||||
- `cover_image_src` 如果是 `data:image/...base64`,默认替换为占位符或截断,避免压测数据文件过大。
|
|
||||||
- `levels_json`、`profile_payload_json` 保留原文,但可以记录大小;如果过大,再提供 `--compact` 选项只保留摘要。
|
|
||||||
4. 输出 `.local.json` 默认加入 `.gitignore`;如果要提交样例数据,只提交脱敏/裁剪后的 `works-list.sample.json`。
|
|
||||||
|
|
||||||
## K6 压测接口矩阵
|
|
||||||
|
|
||||||
需要先确认本地 api-server 实际端口。默认以 `http://127.0.0.1:8787` 为例,实际运行时通过环境变量覆盖:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BASE_URL=http://127.0.0.1:<actual-api-port> k6 run scripts/loadtest/k6-works-list.js
|
|
||||||
```
|
|
||||||
|
|
||||||
初版建议覆盖以下“作品列表”读接口,具体路径以仓库服务端路由为准,实施时需要通过搜索 api-server 路由确认:
|
|
||||||
|
|
||||||
| 场景 | 目的 | 候选路径 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 拼图作品列表 | 作品列表主场景之一,当前数据量最多 | `/api/creation/puzzle/works` 或实际 puzzle works list route |
|
|
||||||
| RPG/自定义世界作品列表 | 使用 `custom_world_profile` 数据 | `/api/creation/custom-world/works` 或实际 custom world works route |
|
|
||||||
| 作品详情/启动前读取 | 模拟用户从列表点进作品 | `/api/creation/*/works/:profileId` 或 `/api/runtime/*/works/:profileId` |
|
|
||||||
| 公开作品库 | 如果首页/发现页依赖 | `/api/runtime/*/works` 或 gallery/list route |
|
|
||||||
|
|
||||||
> 注意:不要凭空固定 endpoint。实施阶段先用 `search_files` / 路由源码确认真实路径,再写入 K6 脚本。
|
|
||||||
|
|
||||||
## K6 场景设计
|
|
||||||
|
|
||||||
### 阶段 1:基线 smoke
|
|
||||||
|
|
||||||
目的:确认脚本、数据和目标服务可用。
|
|
||||||
|
|
||||||
```js
|
|
||||||
export const options = {
|
|
||||||
scenarios: {
|
|
||||||
smoke: {
|
|
||||||
executor: 'constant-vus',
|
|
||||||
vus: 1,
|
|
||||||
duration: '30s'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
thresholds: {
|
|
||||||
http_req_failed: ['rate<0.01'],
|
|
||||||
http_req_duration: ['p(95)<800']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 阶段 2:常规读压
|
|
||||||
|
|
||||||
目的:模拟日常列表浏览。
|
|
||||||
|
|
||||||
- `constant-vus`: 10/25/50 三档
|
|
||||||
- 每个 VU 随机选择作品类型和列表分页参数
|
|
||||||
- `sleep(0.5~2s)` 模拟用户停留
|
|
||||||
- 阈值建议:
|
|
||||||
- `http_req_failed < 1%`
|
|
||||||
- `p95 < 800ms`
|
|
||||||
- `p99 < 1500ms`
|
|
||||||
|
|
||||||
### 阶段 3:峰值/突刺
|
|
||||||
|
|
||||||
目的:模拟首页入口或活动导致的作品列表突增。
|
|
||||||
|
|
||||||
- `ramping-arrival-rate`
|
|
||||||
- 从 5 RPS 增长到 100 RPS,维持 2~5 分钟,再降回
|
|
||||||
- 单独输出 `checks`:列表接口状态码、响应 JSON shape、items 数量
|
|
||||||
|
|
||||||
### 阶段 4:容量探索
|
|
||||||
|
|
||||||
目的:找瓶颈,不作为每次回归必跑。
|
|
||||||
|
|
||||||
- 每轮提升 RPS 或 VU
|
|
||||||
- 观察:api-server CPU/内存、SpacetimeDB 日志、错误率、p95/p99
|
|
||||||
- 一旦 `http_req_failed >= 5%` 或 p95 持续超过 2s,停止继续加压并记录容量点。
|
|
||||||
|
|
||||||
## K6 脚本设计要点
|
|
||||||
|
|
||||||
1. 使用 `SharedArray` 加载 `works-list.local.json`,避免每个 VU 重复解析大 JSON。
|
|
||||||
2. 基于数据源里的 `profile_id` / `work_id` 随机抽样,保证请求覆盖真实作品 ID。
|
|
||||||
3. 对列表接口添加分页/排序 query,例如:
|
|
||||||
- `?limit=20&offset=0`
|
|
||||||
- `?pageSize=20&cursor=...`(以真实 API 为准)
|
|
||||||
4. 使用 `check()` 验证:
|
|
||||||
- HTTP 200
|
|
||||||
- 响应体是 JSON
|
|
||||||
- `items` 或 `works` 是数组
|
|
||||||
- 列表项包含 `profileId/profile_id`、标题字段、状态字段
|
|
||||||
5. 使用 `Trend` / `Rate` 细分指标:
|
|
||||||
- `works_list_duration`
|
|
||||||
- `works_detail_duration`
|
|
||||||
- `works_list_shape_error_rate`
|
|
||||||
6. 支持环境变量:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BASE_URL=http://127.0.0.1:8787 \
|
|
||||||
WORKS_DATA=scripts/loadtest/data/works-list.local.json \
|
|
||||||
SCENARIO=baseline \
|
|
||||||
k6 run scripts/loadtest/k6-works-list.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实施步骤
|
|
||||||
|
|
||||||
1. **确认路由**
|
|
||||||
- 搜索 api-server / BFF 的作品列表路由。
|
|
||||||
- 明确各玩法对应 endpoint、鉴权要求、分页参数、返回字段。
|
|
||||||
2. **实现数据提取脚本**
|
|
||||||
- 新增 `scripts/loadtest/extract-works-list-data.mjs`。
|
|
||||||
- 只按表白名单读取作品列表 profile 表。
|
|
||||||
- 对字段做白名单与脱敏/截断。
|
|
||||||
- 输出 `works-list.local.json`。
|
|
||||||
3. **生成本地压测数据**
|
|
||||||
- 用用户提供的迁移文件生成 `scripts/loadtest/data/works-list.local.json`。
|
|
||||||
- 验证输出只包含作品表和作品字段。
|
|
||||||
4. **实现 K6 脚本**
|
|
||||||
- 新增 `scripts/loadtest/k6-works-list.js`。
|
|
||||||
- 支持 `BASE_URL`、`WORKS_DATA`、`SCENARIO`。
|
|
||||||
- 覆盖列表接口,必要时增加详情/启动前读取接口。
|
|
||||||
5. **新增执行说明**
|
|
||||||
- 在 `scripts/loadtest/README.md` 写明:安装 K6、启动本地 dev 栈、提取数据、运行 smoke/baseline/spike、查看结果。
|
|
||||||
6. **本地验证**
|
|
||||||
- 启动 Genarrative dev 栈;注意端口可能漂移,使用实际 api-server 端口。
|
|
||||||
- 跑 smoke:`SCENARIO=smoke`。
|
|
||||||
- 确认失败率、p95、响应 shape。
|
|
||||||
7. **可选集成 npm scripts**
|
|
||||||
- 如果团队希望标准化入口,再加入 `package.json` scripts。
|
|
||||||
8. **记录结果**
|
|
||||||
- 将 smoke/baseline/spike 的结果摘要追加到 `scripts/loadtest/README.md` 或单独保存到 `.hermes/plans/` 的结果文档中。
|
|
||||||
|
|
||||||
## 启动与运行建议
|
|
||||||
|
|
||||||
本地服务启动按当前 Genarrative dev 栈约定:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
如果 SpacetimeDB/API/Vite 端口被占用,项目脚本会寻找可用端口;压测时必须从启动日志中读取实际 api-server 地址,并传给 K6:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BASE_URL=http://127.0.0.1:<actual-api-port> \
|
|
||||||
WORKS_DATA=scripts/loadtest/data/works-list.local.json \
|
|
||||||
SCENARIO=smoke \
|
|
||||||
k6 run scripts/loadtest/k6-works-list.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## 验证标准
|
|
||||||
|
|
||||||
### 数据源验证
|
|
||||||
|
|
||||||
- `works-list.local.json` 中只出现作品 profile 表。
|
|
||||||
- 不出现以下字段或内容:
|
|
||||||
- `password_hash`
|
|
||||||
- `refresh_token_hash`
|
|
||||||
- `phone_number_e164`
|
|
||||||
- `phone_number_masked`
|
|
||||||
- `wallet_ledger_id`
|
|
||||||
- `auth_identity`
|
|
||||||
- `user_account`
|
|
||||||
- `puzzle_work_profile` 行数应接近原始文件中的 80 行。
|
|
||||||
- `custom_world_profile` 行数应接近原始文件中的 1 行。
|
|
||||||
|
|
||||||
### K6 smoke 验证
|
|
||||||
|
|
||||||
- 所有目标接口返回 2xx。
|
|
||||||
- `http_req_failed < 1%`。
|
|
||||||
- 响应 JSON shape 与 shared contracts 对齐:`items` 或 `works` 数组。
|
|
||||||
- K6 输出中能区分不同 endpoint 的耗时。
|
|
||||||
|
|
||||||
### 性能阈值初稿
|
|
||||||
|
|
||||||
- Smoke:`p95 < 800ms`,失败率 `< 1%`
|
|
||||||
- Baseline:`p95 < 1000ms`,`p99 < 2000ms`,失败率 `< 1%`
|
|
||||||
- Spike:允许短暂 p95 抖动,但 1 分钟内应恢复;失败率 `< 5%`
|
|
||||||
|
|
||||||
阈值后续需要结合本地机器性能、SpacetimeDB 本地模式和正式部署规格调整。
|
|
||||||
|
|
||||||
## 风险与注意事项
|
|
||||||
|
|
||||||
1. **原始迁移文件包含敏感数据。** 必须只提取作品列表白名单字段,禁止把原始 JSON 全量提交到仓库。
|
|
||||||
2. **base64 封面可能导致压测数据膨胀。** 默认截断或替换为占位符,除非本次明确要测封面 payload 对响应体积的影响。
|
|
||||||
3. **本地 SpacetimeDB 与 api-server 端口会漂移。** 不要硬编码端口,运行时通过 `BASE_URL` 注入。
|
|
||||||
4. **列表接口可能需要鉴权。** 若实际接口要求登录,不要导入真实 refresh session;应使用本地测试账号或专门的压测 token 生成流程。
|
|
||||||
5. **作品表名/接口路径可能与候选名称不完全一致。** 实施前必须以源码路由为准。
|
|
||||||
6. **本计划仅保存压测方案,不执行实际压测。** 后续执行时再创建/修改脚本、导出过滤数据、跑 K6 并记录结果。
|
|
||||||
|
|
||||||
## 开放问题
|
|
||||||
|
|
||||||
1. 压测目标是本地 dev 栈、测试环境,还是预发/生产只读接口?不同环境阈值和安全边界不同。
|
|
||||||
2. “作品列表”是否只包含拼图和自定义世界,还是要覆盖 match3d、square-hole、big-fish、visual-novel 的统一列表入口?
|
|
||||||
3. 是否允许使用专门压测账号/token?如果接口无鉴权则无需处理。
|
|
||||||
4. 是否需要测封面/asset 加载,还是只测作品列表 JSON API?
|
|
||||||
@@ -1,447 +0,0 @@
|
|||||||
# Genarrative 容灾方案设计计划
|
|
||||||
|
|
||||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
|
||||||
|
|
||||||
**Goal:** 基于当前 Genarrative 单机生产部署、Jenkins 流水线、SpacetimeDB 与 Rust `api-server` 架构,补齐一套可落地、可演练、可审计的容灾方案。
|
|
||||||
|
|
||||||
**Architecture:** 首版容灾不引入复杂多活系统,优先围绕现有 `systemd + Nginx + SpacetimeDB + api-server + Jenkins` 单机生产推荐方案做“备份可恢复、版本可回滚、故障可切换、演练可复盘”。方案采用分层容灾:入口层、静态资源层、API 服务层、SpacetimeDB 数据层、外部服务与密钥层、Jenkins/发布链路层。
|
|
||||||
|
|
||||||
**Tech Stack:** Nginx、systemd、SpacetimeDB self-hosting、Rust `api-server` / Axum、Jenkins Pipeline、Shell/Node.js 运维脚本、仓库 `deploy/` 与 `docs/technical/` 文档体系。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 当前上下文与已确认事实
|
|
||||||
|
|
||||||
### 1.1 当前生产部署口径
|
|
||||||
|
|
||||||
来自 `docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md` 的现状:
|
|
||||||
|
|
||||||
- 生产为单机推荐方案,不使用 Docker。
|
|
||||||
- 公网入口为 Nginx,负责 HTTPS、静态站点、后台静态页面、维护页、`/admin/api/` 与临时 `/api/*` 反向代理。
|
|
||||||
- SpacetimeDB 作为 systemd 服务运行:
|
|
||||||
- `spacetimedb.service`
|
|
||||||
- 监听:`127.0.0.1:3101`
|
|
||||||
- 数据根目录:`/stdb`
|
|
||||||
- Rust `api-server` 作为 systemd 服务运行:
|
|
||||||
- `genarrative-api.service`
|
|
||||||
- 监听:`127.0.0.1:8082`
|
|
||||||
- 环境文件:`/etc/genarrative/api-server.env`
|
|
||||||
- 静态站点发布到 release/current 目录:
|
|
||||||
- `/opt/genarrative/releases/<version>/`
|
|
||||||
- `/opt/genarrative/current`
|
|
||||||
- `/srv/genarrative/web`
|
|
||||||
- 已有维护模式:
|
|
||||||
- 开关文件:`/var/lib/genarrative/maintenance/enabled`
|
|
||||||
- API 发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更必须进入维护模式。
|
|
||||||
- 已有数据库导入导出 Jenkins Job:
|
|
||||||
- `Genarrative-Database-Export`
|
|
||||||
- `Genarrative-Database-Import`
|
|
||||||
- 对应文件:`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`
|
|
||||||
- 已有回滚基本口径:
|
|
||||||
- Web 回滚:切 `/srv/genarrative/web` 或 `/opt/genarrative/current` 到上一版本并 reload Nginx。
|
|
||||||
- API 回滚:切 `/opt/genarrative/current` 到上一版本并重启 `genarrative-api.service`。
|
|
||||||
- SpacetimeDB 模块回滚:发布上一版本 `spacetime_module.wasm`。
|
|
||||||
- 数据回滚:使用导入流水线恢复指定备份,必须进入维护模式。
|
|
||||||
|
|
||||||
### 1.2 关键风险
|
|
||||||
|
|
||||||
- 当前是单机生产拓扑,单机磁盘、系统盘、`/stdb`、Nginx 或公网 IP 故障会造成整体不可用。
|
|
||||||
- SpacetimeDB 是核心业务真相,容灾重点必须围绕 `/stdb`、数据库导出产物、schema 迁移与导入验证。
|
|
||||||
- `/etc/genarrative/api-server.env` 持有生产密钥,不能进入 Git,也不能写进普通备份明文归档。
|
|
||||||
- Jenkins controller/agent 同时承担构建、发布、备份、导入导出编排;Jenkins 不可用时仍需要有最小人工恢复路径。
|
|
||||||
- 外部 LLM、图片、语音、3D 网关不是本仓库可控系统,容灾只能做到配置降级、超时隔离、能力熔断与可观测告警。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 容灾目标
|
|
||||||
|
|
||||||
### 2.1 恢复目标建议
|
|
||||||
|
|
||||||
| 灾难类型 | 目标 RTO | 目标 RPO | 首版策略 |
|
|
||||||
| --- | ---: | ---: | --- |
|
|
||||||
| Web 静态资源发布失败 | 5 分钟 | 0 | release/current 原子切换回滚 |
|
|
||||||
| API 发布失败 | 10 分钟 | 0 | 维护模式 + 上一版二进制回滚 |
|
|
||||||
| SpacetimeDB wasm 发布失败 | 15 分钟 | 0 或按迁移前备份 | 发布前导出 + 上一版 wasm 回滚 |
|
|
||||||
| 数据误写 / 迁移失败 | 30-60 分钟 | 最近一次导出点 | 导入流水线从备份恢复 |
|
|
||||||
| 生产机磁盘损坏 | 2-4 小时 | 最近一次异地备份 | 新机器 provision + 拉取 release 包 + 恢复数据库 |
|
|
||||||
| Jenkins controller 不可用 | 1-2 小时 | 不影响线上数据 | 手工脚本恢复 + Jenkins 备份恢复 |
|
|
||||||
| 第三方模型网关不可用 | 5-15 分钟内降级 | 不丢核心数据 | 配置切换 / 功能熔断 / 队列失败可重试 |
|
|
||||||
|
|
||||||
### 2.2 首版不做
|
|
||||||
|
|
||||||
- 不做跨地域双活写入。
|
|
||||||
- 不做 SpacetimeDB 在线主从复制,除非后续官方能力与项目压测验证支持。
|
|
||||||
- 不让前端绕过 `api-server` 直接承担正式业务真相。
|
|
||||||
- 不把生产密钥、Token、数据库 dump、Jenkins secret 写入 Git。
|
|
||||||
- 不恢复旧 `server-node`、Express、PostgreSQL 或 Docker 一体化部署方案。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 总体容灾设计
|
|
||||||
|
|
||||||
### 3.1 分层策略
|
|
||||||
|
|
||||||
1. **入口层:Nginx / DNS / HTTPS**
|
|
||||||
- 保留 Nginx 配置模板在 Git:`deploy/nginx/genarrative.conf`、`deploy/nginx/genarrative-dev-http.conf`。
|
|
||||||
- 为 release 环境建立 Nginx 配置备份与证书恢复流程。
|
|
||||||
- 明确 DNS 切换预案:生产机不可恢复时,将域名指向灾备机公网 IP。
|
|
||||||
|
|
||||||
2. **静态资源层:Web / Admin Web**
|
|
||||||
- 依赖 `web.tar.gz`、`web.tar.gz.sha256`、`release-manifest.json`。
|
|
||||||
- 保留最近 N 个 release 目录与构建产物指针。
|
|
||||||
- 回滚只切软链,不重新构建。
|
|
||||||
|
|
||||||
3. **API 服务层:Rust `api-server`**
|
|
||||||
- 依赖归档的 `api-server` 二进制、checksum、`release-manifest.json`。
|
|
||||||
- `/etc/genarrative/api-server.env` 通过加密备份或密钥管理恢复,不进入 release 包。
|
|
||||||
- systemd unit 由 `deploy/systemd/genarrative-api.service` 重新安装。
|
|
||||||
|
|
||||||
4. **数据层:SpacetimeDB**
|
|
||||||
- 每次高风险发布前强制导出数据库。
|
|
||||||
- 定时导出:建议每天至少 1 次;高活跃期可每 4 小时 1 次。
|
|
||||||
- 导出产物同时保存在:Jenkins 归档 + 生产机 `SERVER_BACKUP_DIRECTORY` + 异地对象存储/备份机。
|
|
||||||
- 导入前自动生成安全备份,保留当前实现口径。
|
|
||||||
|
|
||||||
5. **发布编排层:Jenkins**
|
|
||||||
- Jenkins Job、Jenkinsfile 在 Git 中可恢复。
|
|
||||||
- Jenkins controller 配置、凭据、插件清单需要额外备份。
|
|
||||||
- 发布 agent 使用 inbound + systemd 自恢复,agent secret 仅存在目标机或 Jenkins 凭据。
|
|
||||||
|
|
||||||
6. **密钥与外部服务层**
|
|
||||||
- `/etc/genarrative/api-server.env`、Jenkins Secret Text、SSH PEM、agent secret 不进 Git。
|
|
||||||
- 制定密钥清单和恢复责任人,但不在仓库记录明文。
|
|
||||||
- 外部服务配置按 `docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md` 维护必配项。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 建议新增/更新的文档
|
|
||||||
|
|
||||||
### Task 1: 新增生产容灾技术方案文档
|
|
||||||
|
|
||||||
**Objective:** 形成团队可共享、可执行的容灾总纲。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
|
|
||||||
- Modify: `docs/technical/README.md`(若已有技术索引,应加入该文档入口)
|
|
||||||
- Optional Modify: `.hermes/shared-memory/project-overview.md`(只加稳定索引,不写敏感信息)
|
|
||||||
|
|
||||||
**文档必须覆盖:**
|
|
||||||
|
|
||||||
1. 容灾目标:RTO/RPO 表。
|
|
||||||
2. 生产资产清单:Nginx、systemd、release/current、`/stdb`、`/etc/genarrative/api-server.env`、Jenkins、构建产物。
|
|
||||||
3. 备份策略:
|
|
||||||
- 数据库导出。
|
|
||||||
- release 产物保留。
|
|
||||||
- Nginx/systemd/env 配置备份。
|
|
||||||
- Jenkins 配置备份。
|
|
||||||
4. 恢复流程:
|
|
||||||
- Web 回滚。
|
|
||||||
- API 回滚。
|
|
||||||
- Stdb module 回滚。
|
|
||||||
- 数据恢复。
|
|
||||||
- 整机重建。
|
|
||||||
5. 演练计划:每月一次数据库恢复演练,每季度一次整机重建演练。
|
|
||||||
6. 安全边界:密钥不进 Git,备份加密,最小权限。
|
|
||||||
7. 验收命令与人工检查清单。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run check:encoding
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS,无中文乱码、无 BOM/CRLF 问题。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 建议新增/更新的脚本与流水线
|
|
||||||
|
|
||||||
### Task 2: 增强数据库定时备份流水线
|
|
||||||
|
|
||||||
**Objective:** 把现有人工导出扩展为可定时执行、可异地保存、可审计的备份流程。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `jenkins/Jenkinsfile.production-database-export`
|
|
||||||
- Modify: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
|
|
||||||
- Optional Create: `scripts/deploy/production-backup-sync.sh`
|
|
||||||
|
|
||||||
**Implementation notes:**
|
|
||||||
|
|
||||||
- 在 Jenkins Job 中保留人工触发能力,同时建议配置 cron:
|
|
||||||
- development:每天凌晨。
|
|
||||||
- release:每天凌晨或业务低峰。
|
|
||||||
- 增加备份命名规范:
|
|
||||||
- `spacetime-migration-<database>-<yyyyMMdd-HHmmss>-<source_commit>.json`
|
|
||||||
- 增加 `SERVER_BACKUP_DIRECTORY` 默认建议:
|
|
||||||
- `/var/backups/genarrative/spacetimedb/<database>/`
|
|
||||||
- 增加备份保留策略:
|
|
||||||
- 本机保留 7-14 天。
|
|
||||||
- 异地保留 30-90 天。
|
|
||||||
- 如实现 `production-backup-sync.sh`,只做同步框架,不硬编码真实 bucket、账号、endpoint 或密钥。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash -n scripts/deploy/production-backup-sync.sh
|
|
||||||
npm run check:encoding
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: shell 语法通过;文档编码检查通过。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 3: 增加灾备恢复 Runbook
|
|
||||||
|
|
||||||
**Objective:** 在真正故障时不依赖临场推理,按清单执行恢复。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `docs/operations/PRODUCTION_DR_RUNBOOK_2026-05-11.md`
|
|
||||||
- Modify: `docs/operations/README.md`(如果存在)
|
|
||||||
|
|
||||||
**Runbook sections:**
|
|
||||||
|
|
||||||
1. 故障分级:P0/P1/P2。
|
|
||||||
2. 第一响应:
|
|
||||||
- 判断 Nginx 是否在线。
|
|
||||||
- 判断 `genarrative-api.service` 是否在线。
|
|
||||||
- 判断 `spacetimedb.service` 是否在线。
|
|
||||||
- 判断磁盘是否满。
|
|
||||||
- 判断 Jenkins agent 是否在线。
|
|
||||||
3. 快速止血:
|
|
||||||
- 开维护模式。
|
|
||||||
- 禁止继续发布。
|
|
||||||
- 保留现场日志。
|
|
||||||
4. 回滚流程:
|
|
||||||
- Web 回滚命令。
|
|
||||||
- API 回滚命令。
|
|
||||||
- Stdb wasm 回滚命令。
|
|
||||||
5. 数据恢复流程:
|
|
||||||
- 选择备份。
|
|
||||||
- dry-run 导入。
|
|
||||||
- 确认导入。
|
|
||||||
- smoke test。
|
|
||||||
6. 整机重建流程:
|
|
||||||
- 新机器 provision。
|
|
||||||
- 恢复 `/etc/genarrative/api-server.env`。
|
|
||||||
- 恢复 SpacetimeDB 数据。
|
|
||||||
- 发布最近稳定 release。
|
|
||||||
- DNS 切换。
|
|
||||||
7. 复盘模板。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run check:encoding
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 4: 增加备份健康检查与恢复演练记录模板
|
|
||||||
|
|
||||||
**Objective:** 防止“有备份但不可恢复”。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `docs/operations/DR_DRILL_REPORT_TEMPLATE.md`
|
|
||||||
- Optional Create: `scripts/deploy/verify-database-backup.sh`
|
|
||||||
- Modify: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
|
|
||||||
|
|
||||||
**建议检查项:**
|
|
||||||
|
|
||||||
- 备份文件存在且大小非 0。
|
|
||||||
- 备份文件 checksum 可验证。
|
|
||||||
- 备份文件可被 `Genarrative-Database-Import` dry-run 解析。
|
|
||||||
- 最近一次备份时间未超过 RPO 阈值。
|
|
||||||
- 导入后 `/healthz` 可用。
|
|
||||||
- 首页、后台登录页、关键 API smoke 可用。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash -n scripts/deploy/verify-database-backup.sh
|
|
||||||
npm run check:encoding
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 具体恢复流程草案
|
|
||||||
|
|
||||||
### 6.1 Web 静态资源回滚
|
|
||||||
|
|
||||||
1. 进入目标机。
|
|
||||||
2. 查看 release 目录:`/opt/genarrative/releases/`。
|
|
||||||
3. 选择上一个稳定版本。
|
|
||||||
4. 切换 `/srv/genarrative/web` 或 `/opt/genarrative/current` 软链。
|
|
||||||
5. 执行 Nginx 配置检查与 reload。
|
|
||||||
6. 访问首页与后台静态入口。
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
- `/` 返回最新稳定页面。
|
|
||||||
- `/admin/` 返回后台页面。
|
|
||||||
- 静态资源无 404。
|
|
||||||
|
|
||||||
### 6.2 API 回滚
|
|
||||||
|
|
||||||
1. 开维护模式。
|
|
||||||
2. 切 `/opt/genarrative/current` 到上一版包含稳定 `api-server` 的 release。
|
|
||||||
3. 重启 `genarrative-api.service`。
|
|
||||||
4. 本机检查 `http://127.0.0.1:8082/healthz`。
|
|
||||||
5. 检查 Nginx 反代路径。
|
|
||||||
6. 解除维护模式。
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
- `systemctl status genarrative-api.service` 正常。
|
|
||||||
- `/healthz` 正常。
|
|
||||||
- 后台 `/admin/api/*` 基础接口正常。
|
|
||||||
|
|
||||||
### 6.3 SpacetimeDB 模块回滚
|
|
||||||
|
|
||||||
1. 开维护模式。
|
|
||||||
2. 确认目标数据库名与当前 API 环境一致:`GENARRATIVE_SPACETIME_DATABASE`。
|
|
||||||
3. 选择上一版 `spacetime_module.wasm`。
|
|
||||||
4. 使用 `spacetimedb` 服务用户发布上一版 wasm。
|
|
||||||
5. 重启或检查 `spacetimedb.service`。
|
|
||||||
6. 检查 `api-server` 对目标数据库访问。
|
|
||||||
7. 解除维护模式。
|
|
||||||
|
|
||||||
注意:如果 schema 已迁移且旧 wasm 不兼容当前数据,需要走数据恢复,不应直接盲目发布旧 wasm。
|
|
||||||
|
|
||||||
### 6.4 数据恢复
|
|
||||||
|
|
||||||
1. 开维护模式。
|
|
||||||
2. 从 Jenkins 归档或 `SERVER_BACKUP_DIRECTORY` 选择备份。
|
|
||||||
3. 先执行导入 dry-run。
|
|
||||||
4. 真正导入前生成当前数据库安全备份。
|
|
||||||
5. 执行导入。
|
|
||||||
6. 执行 smoke test。
|
|
||||||
7. 解除维护模式。
|
|
||||||
|
|
||||||
必须记录:
|
|
||||||
|
|
||||||
- 备份文件名。
|
|
||||||
- 来源 Job/build number。
|
|
||||||
- 恢复目标 database。
|
|
||||||
- 恢复开始/结束时间。
|
|
||||||
- 恢复后验证结果。
|
|
||||||
|
|
||||||
### 6.5 整机重建
|
|
||||||
|
|
||||||
1. 准备新 Linux 机器。
|
|
||||||
2. 接入 Jenkins release deploy agent,或准备人工 SSH 运维路径。
|
|
||||||
3. 运行 `Genarrative-Server-Provision`:
|
|
||||||
- 创建用户和目录。
|
|
||||||
- 安装 SpacetimeDB。
|
|
||||||
- 安装 systemd unit。
|
|
||||||
- 安装 Nginx 配置。
|
|
||||||
4. 恢复 `/etc/genarrative/api-server.env`。
|
|
||||||
5. 发布最近稳定 Web/API/Stdb 产物。
|
|
||||||
6. 导入最近一次有效数据库备份。
|
|
||||||
7. smoke test。
|
|
||||||
8. 切 DNS。
|
|
||||||
9. 观察 30-60 分钟。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 文件可能变更清单
|
|
||||||
|
|
||||||
首版落地建议按以下文件收口:
|
|
||||||
|
|
||||||
- Create: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md`
|
|
||||||
- Create: `docs/operations/PRODUCTION_DR_RUNBOOK_2026-05-11.md`
|
|
||||||
- Create: `docs/operations/DR_DRILL_REPORT_TEMPLATE.md`
|
|
||||||
- Modify: `docs/technical/README.md`
|
|
||||||
- Modify: `docs/operations/README.md`(若存在)
|
|
||||||
- Modify: `.hermes/shared-memory/project-overview.md`(仅增加文档索引)
|
|
||||||
- Optional Modify: `jenkins/Jenkinsfile.production-database-export`
|
|
||||||
- Optional Modify: `jenkins/Jenkinsfile.production-database-import`
|
|
||||||
- Optional Create: `scripts/deploy/production-backup-sync.sh`
|
|
||||||
- Optional Create: `scripts/deploy/verify-database-backup.sh`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 测试与验收
|
|
||||||
|
|
||||||
### 8.1 文档与编码
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run check:encoding
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
### 8.2 Shell 脚本语法
|
|
||||||
|
|
||||||
如新增 shell 脚本:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash -n scripts/deploy/production-backup-sync.sh
|
|
||||||
bash -n scripts/deploy/verify-database-backup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS。
|
|
||||||
|
|
||||||
### 8.3 Jenkinsfile 静态检查
|
|
||||||
|
|
||||||
建议在 Jenkins UI 或本地 Jenkins Pipeline Linter 中检查:
|
|
||||||
|
|
||||||
- `jenkins/Jenkinsfile.production-database-export`
|
|
||||||
- `jenkins/Jenkinsfile.production-database-import`
|
|
||||||
|
|
||||||
Expected: Pipeline syntax valid。
|
|
||||||
|
|
||||||
### 8.4 演练验收
|
|
||||||
|
|
||||||
至少完成一次 development 目标演练:
|
|
||||||
|
|
||||||
1. 触发 `Genarrative-Database-Export`。
|
|
||||||
2. 确认备份产物存在并归档。
|
|
||||||
3. 使用 `Genarrative-Database-Import` dry-run 验证备份可解析。
|
|
||||||
4. 不覆盖生产数据的前提下,记录演练报告。
|
|
||||||
|
|
||||||
release 目标演练应在业务低峰进行,并先确认通知渠道可用。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 风险、取舍与开放问题
|
|
||||||
|
|
||||||
### 9.1 风险
|
|
||||||
|
|
||||||
- 单机生产仍存在物理机级单点故障,首版只能通过“快速重建 + 异地备份”降低恢复时间。
|
|
||||||
- SpacetimeDB schema 回滚不一定可逆,必须把发布前备份作为强约束。
|
|
||||||
- Jenkins controller 若在本地 Windows,controller 自身备份和恢复需要单独制定,不应只依赖 agent 自恢复。
|
|
||||||
- 外部模型网关失败可能影响创作能力,但不应影响已发布作品浏览和后台基础能力。
|
|
||||||
|
|
||||||
### 9.2 取舍
|
|
||||||
|
|
||||||
- 选择先做可执行 runbook 和备份恢复演练,而不是直接引入复杂多活。
|
|
||||||
- 选择继续复用现有 Jenkins 导入导出流水线,降低工程改造风险。
|
|
||||||
- 选择不把密钥恢复细节写死到 Git 文档,避免泄露。
|
|
||||||
|
|
||||||
### 9.3 开放问题
|
|
||||||
|
|
||||||
1. release 环境是否已经有独立备份机或对象存储?如果有,需要补充备份同步目标,但不能提交密钥。
|
|
||||||
2. Jenkins controller 的 `JENKINS_HOME` 当前实际部署在哪里?是否已有周期备份?
|
|
||||||
3. 生产域名 DNS TTL 当前是多少?是否可降低到适合故障切换的值?
|
|
||||||
4. `/stdb` 所在磁盘是否独立于系统盘?是否已有磁盘水位告警?
|
|
||||||
5. release 环境的通知渠道除邮件外是否需要接入企业微信/飞书/Telegram?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 推荐实施顺序
|
|
||||||
|
|
||||||
1. 先只落文档:技术方案 + runbook + 演练模板。
|
|
||||||
2. 在 development 目标做一次数据库导出 + dry-run 导入演练。
|
|
||||||
3. 根据演练结果补脚本:备份同步、备份健康检查。
|
|
||||||
4. 再把 release 备份设置为定时任务。
|
|
||||||
5. 最后规划整机重建演练与 DNS 切换演练。
|
|
||||||
|
|
||||||
首版完成标准:
|
|
||||||
|
|
||||||
- 团队任一成员打开 runbook,即可在 30 分钟内完成 Web/API 回滚或数据库备份 dry-run 恢复。
|
|
||||||
- 最近一次数据库备份时间、备份位置、checksum、恢复演练结果可追溯。
|
|
||||||
- 生产密钥仍只存在于服务器/Jenkins 凭据/加密备份中,不进入 Git。
|
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
# 当前项目安全漏洞检查计划
|
|
||||||
|
|
||||||
> **For Hermes:** Use subagent-driven-development skill only if the user later asks to execute this plan. 本计划当前仅用于规划,不实施代码修改。
|
|
||||||
|
|
||||||
**Goal:** 对 Genarrative 当前工作区做一次可复现的安全漏洞基线检查,覆盖依赖漏洞、密钥泄露、常见高风险代码模式、后端 Rust crate 风险和前端/Node 供应链风险,并输出可落地的整改清单。
|
|
||||||
|
|
||||||
**Architecture:** 采用“只读扫描 → 结果归档 → 人工分级 → 最小修复建议”的方式推进。先不直接升级依赖或改代码,避免安全扫描引入不可控 breaking change;执行阶段只在用户确认后运行扫描命令,并把报告保存到 `docs/audits/` 或 `.hermes/plans/` 附件中。
|
|
||||||
|
|
||||||
**Tech Stack:** Node/Vite/React/TypeScript、Rust workspace/Axum/SpacetimeDB、npm lockfile、Cargo.lock、Git worktree。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 当前上下文 / 假设
|
|
||||||
|
|
||||||
- 当前有效工作区:`C:/proj/Genarrative/.worktrees/hermes-3337436a`。
|
|
||||||
- 本次用户以 `/plan` 模式要求“检查一下当前项目的安全漏洞”,因此本轮只制定计划,不执行会产生报告、安装工具、修改依赖、提交或推送的操作。
|
|
||||||
- 已确认项目包含:
|
|
||||||
- 根 `package.json`,脚本包括 `npm run lint`、`npm run test`、`npm run build`、`npm run check:encoding`。
|
|
||||||
- 根 `package-lock.json`。
|
|
||||||
- `server-rs/Cargo.toml` 和 `server-rs/Cargo.lock`。
|
|
||||||
- `apps/admin-web/package.json`、`packages/shared/package.json`。
|
|
||||||
- `.hermes/shared-memory/development-workflow.md` 要求开发前读取共享记忆,并以当前代码、`docs/`、`AGENTS.md` 为准。
|
|
||||||
- 安全扫描不应把真实密钥写入仓库;发现疑似密钥时只记录文件位置、变量名、脱敏片段和处置建议。
|
|
||||||
|
|
||||||
## 总体策略
|
|
||||||
|
|
||||||
1. 先做仓库状态和范围确认,避免扫描其他 worktree 或错误路径。
|
|
||||||
2. 优先运行不会修改文件的安全检查:`npm audit --json`、`cargo audit`、密钥扫描、危险代码模式扫描。
|
|
||||||
3. 分前端供应链、后端供应链、源码安全、配置/脚本安全四类归档。
|
|
||||||
4. 对结果按严重级别分层:Critical / High / Medium / Low / Informational。
|
|
||||||
5. 对每个真实问题给出:影响范围、证据、可行修复、验证命令、是否需要业务回归。
|
|
||||||
6. 只有在用户确认进入执行/修复阶段后,才做依赖升级、代码修复、文档更新、测试和提交。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step-by-step Plan
|
|
||||||
|
|
||||||
### Task 1: 确认扫描工作区和基线状态
|
|
||||||
|
|
||||||
**Objective:** 确保后续扫描针对当前 worktree,且不会误把既有未提交变更当成安全修复结果。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Read-only: `AGENTS.md`
|
|
||||||
- Read-only: `.hermes/README.md`
|
|
||||||
- Read-only: `.hermes/shared-memory/development-workflow.md`
|
|
||||||
- Read-only: `package.json`
|
|
||||||
- Read-only: `server-rs/Cargo.toml`
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pwd
|
|
||||||
git status --short
|
|
||||||
git branch --show-current
|
|
||||||
git rev-parse --show-toplevel
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected:**
|
|
||||||
- `pwd` / `git rev-parse --show-toplevel` 指向 `C:/proj/Genarrative/.worktrees/hermes-3337436a` 对应路径。
|
|
||||||
- 分支为当前隔离 worktree 分支。
|
|
||||||
- 记录是否已有未提交变更;如存在,扫描报告需标注“基于含未提交变更的工作区”。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- 不修改任何项目文件。
|
|
||||||
- 如发现路径不是当前 worktree,停止并重新确认路径。
|
|
||||||
|
|
||||||
### Task 2: 生成依赖清单和锁文件基线
|
|
||||||
|
|
||||||
**Objective:** 明确 Node 与 Rust 依赖入口,避免漏扫子包或 admin web。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Read-only: `package.json`
|
|
||||||
- Read-only: `package-lock.json`
|
|
||||||
- Read-only: `apps/admin-web/package.json`
|
|
||||||
- Read-only: `packages/shared/package.json`
|
|
||||||
- Read-only: `server-rs/Cargo.toml`
|
|
||||||
- Read-only: `server-rs/Cargo.lock`
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm --version
|
|
||||||
node --version
|
|
||||||
cargo --version
|
|
||||||
rustc --version
|
|
||||||
```
|
|
||||||
|
|
||||||
可选只读清单:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm ls --all --json > /tmp/genarrative-npm-ls.json
|
|
||||||
cargo metadata --manifest-path server-rs/Cargo.toml --format-version 1 > /tmp/genarrative-cargo-metadata.json
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected:**
|
|
||||||
- 明确 npm / Node / Rust / Cargo 版本。
|
|
||||||
- 若 `npm ls` 因 peer dependency 或历史依赖问题非 0,保留输出并继续 audit。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- `/tmp` 输出不进入 Git。
|
|
||||||
- 不运行 `npm install`、`npm update`、`cargo update`。
|
|
||||||
|
|
||||||
### Task 3: Node 供应链漏洞扫描
|
|
||||||
|
|
||||||
**Objective:** 检查根 lockfile 覆盖的前端、脚本和 admin web 依赖漏洞。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Read-only: `package-lock.json`
|
|
||||||
- Read-only: `package.json`
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm audit --json > /tmp/genarrative-npm-audit.json
|
|
||||||
npm audit --audit-level=moderate
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected:**
|
|
||||||
- `npm audit --json` 生成机器可读结果。
|
|
||||||
- 第二条命令给出人类可读摘要;如返回非 0,按漏洞严重度记录,不直接执行 `npm audit fix`。
|
|
||||||
|
|
||||||
**Result fields to extract:**
|
|
||||||
- package name
|
|
||||||
- vulnerable versions
|
|
||||||
- installed version
|
|
||||||
- severity
|
|
||||||
- CVE / GHSA
|
|
||||||
- via chain
|
|
||||||
- fixAvailable 是否为 major/breaking
|
|
||||||
- affected direct dependency or transitive dependency
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- 不执行 `npm audit fix`。
|
|
||||||
- 如 npm registry 网络不可用,记录阻塞原因和可重试命令。
|
|
||||||
|
|
||||||
### Task 4: Rust 供应链漏洞扫描
|
|
||||||
|
|
||||||
**Objective:** 检查 `server-rs` workspace 的 Cargo 依赖漏洞、弃用 crate 和 yanked crate。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Read-only: `server-rs/Cargo.toml`
|
|
||||||
- Read-only: `server-rs/Cargo.lock`
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
|
|
||||||
优先:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo audit --json --manifest-path server-rs/Cargo.toml > /tmp/genarrative-cargo-audit.json
|
|
||||||
cargo audit --manifest-path server-rs/Cargo.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
如果本机没有 `cargo audit`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install cargo-audit --locked
|
|
||||||
cargo audit --manifest-path server-rs/Cargo.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution note:**
|
|
||||||
- 安装 `cargo-audit` 会改变用户 Cargo 工具目录,不属于纯只读扫描;执行前需用户确认。
|
|
||||||
- 如果用户不希望安装工具,则记录“Rust 漏洞扫描未完成”,并给出本地安装或 CI 执行建议。
|
|
||||||
|
|
||||||
**Result fields to extract:**
|
|
||||||
- advisory id
|
|
||||||
- package
|
|
||||||
- version
|
|
||||||
- patched versions
|
|
||||||
- unaffected versions
|
|
||||||
- severity / CVSS if available
|
|
||||||
- dependency path
|
|
||||||
- whether it is runtime reachable in `api-server` / `spacetime-module`
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- 不运行 `cargo update`。
|
|
||||||
- 不改 `Cargo.lock`。
|
|
||||||
|
|
||||||
### Task 5: 密钥和敏感配置泄露扫描
|
|
||||||
|
|
||||||
**Objective:** 检查仓库中是否误提交 API key、token、私钥、cookie、`.env` 类文件或个人 Hermes 配置。
|
|
||||||
|
|
||||||
**Files / paths to scan:**
|
|
||||||
- Full repo excluding `.git/`, `node_modules/`, `target/`, `dist/`, build artifacts。
|
|
||||||
- 特别关注:`.hermes/`、`scripts/`、`server-rs/`、`apps/admin-web/`、`src/`、`docs/`。
|
|
||||||
|
|
||||||
**Preferred commands:**
|
|
||||||
|
|
||||||
如果有 gitleaks:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gitleaks detect --source . --no-git --redact --report-format json --report-path /tmp/genarrative-gitleaks.json
|
|
||||||
```
|
|
||||||
|
|
||||||
如果没有 gitleaks,先用只读 grep/ripgrep 兜底:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
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,})" > /tmp/genarrative-secret-grep.txt || true
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution note:**
|
|
||||||
- 安装 gitleaks 需要用户确认。
|
|
||||||
- grep 结果包含 false positive,必须人工分级,不得直接当作泄露结论。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- 报告中对值做脱敏,只保留前后 3-4 位或完全不记录值。
|
|
||||||
- 如果发现 `.env.local` 或真实 token 被跟踪,立即标为 Critical。
|
|
||||||
|
|
||||||
### Task 6: 常见源码安全模式扫描
|
|
||||||
|
|
||||||
**Objective:** 快速发现高风险代码模式:命令注入、动态执行、路径穿越、危险反序列化、XSS、日志泄密、宽松 CORS 等。
|
|
||||||
|
|
||||||
**Files / paths:**
|
|
||||||
- `src/**/*.{ts,tsx,js,mjs,cjs}`
|
|
||||||
- `apps/admin-web/**/*.{ts,tsx,js,mjs,cjs}`
|
|
||||||
- `scripts/**/*.{js,mjs,cjs,ts}`
|
|
||||||
- `server-rs/crates/**/*.rs`
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# JS/TS 动态执行与 HTML 注入
|
|
||||||
rg -n "\beval\(|new Function\(|dangerouslySetInnerHTML|innerHTML\s*=|document\.write\(" src apps scripts packages
|
|
||||||
|
|
||||||
# Node 命令执行风险
|
|
||||||
rg -n "exec\(|execSync\(|spawn\(|spawnSync\(|shell:\s*true|child_process" scripts src apps packages
|
|
||||||
|
|
||||||
# Rust 命令、文件路径、unwrap 风险热点
|
|
||||||
rg -n "Command::new|std::process|\.unwrap\(|\.expect\(|fs::|File::open|PathBuf|set_header|cors|CorsLayer" server-rs/crates
|
|
||||||
|
|
||||||
# 宽松 CORS / Cookie / Auth 相关热点
|
|
||||||
rg -n "allow_origin|Any|cookie|Authorization|Bearer|refresh|access_token|set_cookie|SameSite|Secure|HttpOnly" server-rs/crates src apps scripts
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected:**
|
|
||||||
- 输出作为“热点清单”,不等同于漏洞。
|
|
||||||
- 对 auth/session、文件上传、OSS 签名、外部 LLM/图片服务请求、SpacetimeDB 访问 facade 做人工复核。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- 每个疑似问题必须能说明可利用条件,无法说明则降级为 Informational。
|
|
||||||
|
|
||||||
### Task 7: Web/API 安全配置人工复核
|
|
||||||
|
|
||||||
**Objective:** 对项目特有的安全边界做代码审阅,补足工具扫描无法覆盖的业务风险。
|
|
||||||
|
|
||||||
**Likely files to review:**
|
|
||||||
- `server-rs/crates/api-server/src/**`
|
|
||||||
- `server-rs/crates/platform-auth/src/**`
|
|
||||||
- `server-rs/crates/platform-oss/src/**`
|
|
||||||
- `server-rs/crates/platform-llm/src/**`
|
|
||||||
- `server-rs/crates/spacetime-client/src/**`
|
|
||||||
- `src/services/**`
|
|
||||||
- `apps/admin-web/src/**`
|
|
||||||
- `scripts/*deploy*`
|
|
||||||
- `scripts/*api-server*`
|
|
||||||
- `.github/workflows/**` if present
|
|
||||||
|
|
||||||
**Checklist:**
|
|
||||||
- Auth / session:access token 与 refresh cookie 的生命周期、SameSite/Secure/HttpOnly、错误日志是否泄露 token。
|
|
||||||
- CORS:开发环境与生产环境是否区分,是否存在生产 `Any`。
|
|
||||||
- SSRF / outbound:LLM、图片生成、OSS、任意 URL 下载是否校验协议和大小。
|
|
||||||
- Upload / Data URL:大小限制、MIME 校验、base64 解析错误处理。
|
|
||||||
- Path traversal:脚本和后端是否拼接用户输入路径。
|
|
||||||
- Admin:后台接口是否有权限校验,是否复用普通用户 token。
|
|
||||||
- SpacetimeDB:private table / reducer 是否绕过 api-server facade 暴露敏感数据。
|
|
||||||
- Logging:日志是否打印 API key、token、cookie、用户私密内容。
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- 对每个命中的真实风险,记录具体文件路径和函数名。
|
|
||||||
- 对“需要运行环境才能验证”的风险,列出 smoke 或单测建议。
|
|
||||||
|
|
||||||
### Task 8: 汇总漏洞分级与整改建议
|
|
||||||
|
|
||||||
**Objective:** 把扫描结果转成团队可执行的安全整改报告。
|
|
||||||
|
|
||||||
**Deliverable candidates:**
|
|
||||||
- `docs/audits/SECURITY_VULNERABILITY_SCAN_YYYY-MM-DD.md`
|
|
||||||
- 或如果用户只要临时报告:`.hermes/plans/assets/security-scan-YYYY-MM-DD.md`
|
|
||||||
|
|
||||||
**Report structure:**
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# 安全漏洞扫描报告 YYYY-MM-DD
|
|
||||||
|
|
||||||
## 扫描范围
|
|
||||||
## 扫描命令与环境
|
|
||||||
## 摘要
|
|
||||||
## Critical
|
|
||||||
## High
|
|
||||||
## Medium
|
|
||||||
## Low
|
|
||||||
## Informational / False Positive
|
|
||||||
## 依赖升级建议
|
|
||||||
## 代码修复建议
|
|
||||||
## 需要人工确认的问题
|
|
||||||
## 验证命令
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- 报告不包含真实密钥。
|
|
||||||
- 每条问题都有“证据、影响、建议、验证”。
|
|
||||||
- 明确哪些是工具扫描结果,哪些是人工判断。
|
|
||||||
|
|
||||||
### Task 9: 如用户要求修复,再分批执行最小修复
|
|
||||||
|
|
||||||
**Objective:** 避免一次性大规模升级导致回归,把修复拆为可验证的小批次。
|
|
||||||
|
|
||||||
**Suggested order:**
|
|
||||||
1. Critical secrets:立即移除、轮换密钥、补 `.gitignore`/文档约束(注意项目约束:不要在 `.gitignore` 中添加 `.env.local`)。
|
|
||||||
2. Critical/High direct dependencies:优先升级 direct dependency,运行最小测试。
|
|
||||||
3. Critical/High transitive dependencies:评估是否由 direct dependency patch/minor 升级带出。
|
|
||||||
4. 源码漏洞:按入口编写回归测试,再修复。
|
|
||||||
5. Medium/Low:按风险和 breaking change 代价排期。
|
|
||||||
|
|
||||||
**Required verification after fixes:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run check:encoding
|
|
||||||
npm run lint:eslint
|
|
||||||
npm run typecheck
|
|
||||||
npm run test
|
|
||||||
npm run build
|
|
||||||
cd server-rs && cargo test --workspace
|
|
||||||
```
|
|
||||||
|
|
||||||
后端 API 或 auth 修复涉及运行态时,还需要:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run api-server
|
|
||||||
# 另一个终端检查 /healthz 并执行对应 smoke
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- 修复后重新跑对应 audit / secret scan。
|
|
||||||
- 走 `requesting-code-review` 的独立安全复核流程。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files likely to change(仅修复阶段)
|
|
||||||
|
|
||||||
本计划阶段不修改以下文件;只有用户确认执行修复时才可能变化:
|
|
||||||
|
|
||||||
- `package.json`
|
|
||||||
- `package-lock.json`
|
|
||||||
- `apps/admin-web/package.json`
|
|
||||||
- `server-rs/Cargo.toml`
|
|
||||||
- `server-rs/Cargo.lock`
|
|
||||||
- `server-rs/crates/api-server/src/**`
|
|
||||||
- `server-rs/crates/platform-auth/src/**`
|
|
||||||
- `server-rs/crates/platform-oss/src/**`
|
|
||||||
- `server-rs/crates/platform-llm/src/**`
|
|
||||||
- `src/services/**`
|
|
||||||
- `apps/admin-web/src/**`
|
|
||||||
- `scripts/**`
|
|
||||||
- `docs/audits/SECURITY_VULNERABILITY_SCAN_YYYY-MM-DD.md`
|
|
||||||
- `.hermes/shared-memory/pitfalls.md`(仅当发现长期有效、会反复踩的安全排障经验时更新)
|
|
||||||
|
|
||||||
## Tests / Validation
|
|
||||||
|
|
||||||
安全扫描执行阶段:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm audit --json > /tmp/genarrative-npm-audit.json
|
|
||||||
npm audit --audit-level=moderate
|
|
||||||
cargo audit --manifest-path server-rs/Cargo.toml
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
修复执行阶段:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run check:encoding
|
|
||||||
npm run lint:eslint
|
|
||||||
npm run typecheck
|
|
||||||
npm run test
|
|
||||||
npm run build
|
|
||||||
cd server-rs && cargo test --workspace
|
|
||||||
```
|
|
||||||
|
|
||||||
如变更后端运行态、安全中间件、auth/session:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run api-server
|
|
||||||
# 检查 /healthz
|
|
||||||
# 执行相关 auth / API smoke
|
|
||||||
```
|
|
||||||
|
|
||||||
## Risks, tradeoffs, and open questions
|
|
||||||
|
|
||||||
- `npm audit fix` 可能升级 major version,破坏 Vite/React/ESLint/Vitest 兼容性;必须先人工审查 `fixAvailable`。
|
|
||||||
- `cargo audit` 可能需要安装 `cargo-audit`;安装工具属于用户环境变更,应先确认。
|
|
||||||
- 密钥扫描极易产生 false positive;必须人工复核,报告中禁止输出真实密钥。
|
|
||||||
- Rust `unwrap/expect` 不是天然漏洞;只有对外部输入、网络、文件、数据库响应等不可信数据造成 panic/DoS 时才升级为真实风险。
|
|
||||||
- Web 安全检查需要区分开发环境和生产环境;开发 CORS 放宽不等于生产漏洞,但生产配置必须有明确边界。
|
|
||||||
- 如果扫描发现历史提交中曾泄露密钥,删除当前文件不够,必须轮换密钥并考虑历史清理策略。
|
|
||||||
- 当前计划未直接访问 CI/Jenkins/生产配置;若用户希望覆盖 CI/CD、镜像、部署主机和运行时端口,需要补充 Jenkins console、部署脚本和生产环境配置的只读访问方式。
|
|
||||||
|
|
||||||
## Missing artifacts / follow-up checkpoints
|
|
||||||
|
|
||||||
- 尚未获得用户确认是否允许安装 `cargo-audit` / `gitleaks` 等工具。
|
|
||||||
- 尚未执行真实扫描,因此当前没有漏洞结论;执行后需要生成正式报告。
|
|
||||||
- 如果用户希望“检查当前项目”包含远端仓库历史 secrets、Docker 镜像、Jenkins 凭据和生产运行时配置,需要另行确认访问范围和凭据边界。
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
# 远端作品列表压测排查报告
|
|
||||||
|
|
||||||
时间:2026-05-12 06:16 CST
|
|
||||||
目标:`http://82.157.175.59`
|
|
||||||
SSH:远端生产机 root 账号(具体私钥路径仅保留在本机环境,不写入仓库)
|
|
||||||
|
|
||||||
## 背景
|
|
||||||
|
|
||||||
远端 `k6-works-list.js` 压测中:
|
|
||||||
|
|
||||||
- smoke 通过。
|
|
||||||
- baseline 10 VU:无 HTTP 错误,但 p95/p99 超阈值。
|
|
||||||
- 50 RPS spike:`http_req_failed` / `works_list_shape_error_rate` 约 21.99%。
|
|
||||||
- 100 RPS spike:`http_req_failed` / `works_list_shape_error_rate` 约 25.47%。
|
|
||||||
- 从 k6 check 看,失败主要集中在 `puzzle_gallery_list`,`custom_world_gallery_list` 基本正常。
|
|
||||||
|
|
||||||
## 已完成排查
|
|
||||||
|
|
||||||
### 1. 服务器进程与资源
|
|
||||||
|
|
||||||
远端服务监听:
|
|
||||||
|
|
||||||
- Rust api-server:`127.0.0.1:8082`,systemd 服务 `genarrative-api.service`。
|
|
||||||
- SpacetimeDB:`127.0.0.1:3101`,systemd 服务 `spacetimedb.service`。
|
|
||||||
- Nginx:公网 80 反代 `/api/*` 到 `127.0.0.1:8082`。
|
|
||||||
|
|
||||||
服务器规格/状态:
|
|
||||||
|
|
||||||
- 2 vCPU。
|
|
||||||
- 内存约 1.9GiB。
|
|
||||||
- Swap 约 1.9GiB,已有约 600MiB 使用。
|
|
||||||
- `/` 磁盘约 69%。
|
|
||||||
- Rust api-server 当前 CPU 不高。
|
|
||||||
- SpacetimeDB 当前 CPU 不高。
|
|
||||||
|
|
||||||
发现一个独立异常:
|
|
||||||
|
|
||||||
- PM2 下旧 `server-node` 进程 `genarrative` 正在重启风暴。
|
|
||||||
- cwd:`/work/Genarrative/server-node`
|
|
||||||
- 错误:连接 `127.0.0.1:5432` PostgreSQL 被拒绝。
|
|
||||||
- PM2 restart 次数已超过 33 万。
|
|
||||||
- 该进程不是当前公网 `/api/*` 使用的 Rust api-server,但会制造额外 CPU/内存/日志抖动。
|
|
||||||
|
|
||||||
### 2. 压测窗口服务端日志
|
|
||||||
|
|
||||||
子任务聚合了 2026-05-12 04:50-05:05 的 nginx 与 api-server 日志。
|
|
||||||
|
|
||||||
nginx access:
|
|
||||||
|
|
||||||
- `/api/runtime/puzzle/gallery`:4661 次,全部 200。
|
|
||||||
- `/api/runtime/custom-world-gallery`:4659 次,全部 200。
|
|
||||||
|
|
||||||
api-server journal:
|
|
||||||
|
|
||||||
`/api/runtime/puzzle/gallery`:
|
|
||||||
|
|
||||||
- completed:4661
|
|
||||||
- status:200 全部
|
|
||||||
- slow_request:0
|
|
||||||
- latency_ms:min 13 / p50 30 / p90 43 / p95 50 / p99 62 / max 88
|
|
||||||
|
|
||||||
`/api/runtime/custom-world-gallery`:
|
|
||||||
|
|
||||||
- completed:4659
|
|
||||||
- status:200 全部
|
|
||||||
- slow_request:0
|
|
||||||
- latency_ms:min 0 / p50 1 / p90 5 / p95 7 / p99 13 / max 49
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
- 在服务端视角,两个接口在该窗口都没有 5xx,也没有慢请求。
|
|
||||||
- 这与 k6 客户端侧 30s timeout / failed check 存在明显不一致。
|
|
||||||
- 需要进一步区分:客户端侧网络/连接耗尽/本机 k6 执行环境问题,还是 k6 统计混合/响应解析问题。
|
|
||||||
|
|
||||||
### 3. k6 脚本行为
|
|
||||||
|
|
||||||
文件:`scripts/loadtest/k6-works-list.js`
|
|
||||||
|
|
||||||
无 `AUTH_TOKEN` 时,每轮 iteration 顺序请求两个接口:
|
|
||||||
|
|
||||||
1. `GET /api/runtime/puzzle/gallery`
|
|
||||||
2. `GET /api/runtime/custom-world-gallery`
|
|
||||||
|
|
||||||
`DETAIL_RATIO=0` 时不会请求详情。
|
|
||||||
|
|
||||||
`works_list_shape_error_rate` 不只代表字段结构错误,只要下面任意 check 失败都会计入:
|
|
||||||
|
|
||||||
- status is 200
|
|
||||||
- returns json object
|
|
||||||
- has collection
|
|
||||||
- list item shape
|
|
||||||
|
|
||||||
因此 timeout、非 JSON、非 200、响应结构不符合都会表现为 shape error。
|
|
||||||
|
|
||||||
数据文件实际路径:
|
|
||||||
|
|
||||||
- `scripts/loadtest/data/works-list.local.json`
|
|
||||||
|
|
||||||
脚本里 `data/works-list.local.json` 是相对 k6 脚本文件解析的,因此本身合理。
|
|
||||||
|
|
||||||
### 4. 代码层疑似瓶颈
|
|
||||||
|
|
||||||
虽然这次远端服务端日志没有复现慢请求,但代码层仍发现一个真实性能隐患。
|
|
||||||
|
|
||||||
`/api/runtime/puzzle/gallery` 调用链:
|
|
||||||
|
|
||||||
- `server-rs/crates/api-server/src/app.rs:1192`
|
|
||||||
- `server-rs/crates/api-server/src/puzzle.rs:1385-1409`
|
|
||||||
- `server-rs/crates/spacetime-client/src/puzzle.rs:367-381`
|
|
||||||
- `server-rs/crates/spacetime-module/src/puzzle.rs:430-443`
|
|
||||||
- `server-rs/crates/spacetime-module/src/puzzle.rs:1393-1404`
|
|
||||||
|
|
||||||
关键实现:
|
|
||||||
|
|
||||||
- `list_puzzle_gallery_tx` 对 `puzzle_work_profile().iter()` 全表扫描。
|
|
||||||
- 再过滤 `publication_status == Published`。
|
|
||||||
- 对每个公开作品调用 `build_puzzle_work_profile_from_row_with_recent_count`。
|
|
||||||
- 该函数调用 `count_recent_public_work_plays(ctx, "puzzle", &row.profile_id, now_micros)`。
|
|
||||||
|
|
||||||
`count_recent_public_work_plays`:
|
|
||||||
|
|
||||||
- 文件:`server-rs/crates/spacetime-module/src/runtime/profile.rs:1296-1321`
|
|
||||||
- 当前实现对 `public_work_play_daily_stat().iter()` 全表扫描过滤。
|
|
||||||
- 但表定义已有复合索引:
|
|
||||||
- `server-rs/crates/spacetime-module/src/runtime/profile.rs:242-248`
|
|
||||||
- `by_public_work_play_daily_stat_work_day(source_type, profile_id, played_day)`
|
|
||||||
- 当前统计函数未使用该索引。
|
|
||||||
|
|
||||||
复杂度风险:
|
|
||||||
|
|
||||||
```text
|
|
||||||
puzzle gallery ~= O(puzzle_work_profile 全表扫描 + Published作品数 * public_work_play_daily_stat 全表扫描)
|
|
||||||
```
|
|
||||||
|
|
||||||
`custom-world-gallery` 与 puzzle 的差异:
|
|
||||||
|
|
||||||
- custom-world 使用 `CustomWorldGalleryEntry` 公开读模型表。
|
|
||||||
- puzzle 直接从 `puzzle_work_profile` 即席拼装。
|
|
||||||
- 两者都调用 recent count,但 puzzle 更容易受作品表规模和统计表规模影响。
|
|
||||||
|
|
||||||
## 当前判断
|
|
||||||
|
|
||||||
本次排查有两个层面的结论:
|
|
||||||
|
|
||||||
1. 生产服务端日志没有证明 `puzzle/gallery` 在 04:50-05:05 窗口真的 30s 慢或 5xx。
|
|
||||||
- api-server 记录的 p95 只有 50ms。
|
|
||||||
- nginx 看到两个接口都是 200。
|
|
||||||
- 所以 k6 侧的 30s timeout 需要进一步从客户端网络、连接池、Windows/k6 执行环境、summary 混合统计角度验证。
|
|
||||||
|
|
||||||
2. 代码层确实存在可修的性能隐患。
|
|
||||||
- `count_recent_public_work_plays` 未使用已有索引。
|
|
||||||
- puzzle gallery 对每个作品重复做 recent count。
|
|
||||||
- puzzle gallery 未使用 `publication_status` 索引或读模型。
|
|
||||||
|
|
||||||
## 建议下一步
|
|
||||||
|
|
||||||
### A. 先处理服务器 PM2 重启风暴
|
|
||||||
|
|
||||||
建议确认旧 Node 服务是否仍需要。
|
|
||||||
|
|
||||||
如果不需要,应停止并禁用 PM2 中的旧 `server-node`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PM2_HOME=/home/ubuntu/.pm2 pm2 stop genarrative
|
|
||||||
PM2_HOME=/home/ubuntu/.pm2 pm2 delete genarrative
|
|
||||||
PM2_HOME=/home/ubuntu/.pm2 pm2 save
|
|
||||||
```
|
|
||||||
|
|
||||||
这是生产侧操作,执行前需要确认。
|
|
||||||
|
|
||||||
### B. 单接口短压验证客户端/服务端不一致
|
|
||||||
|
|
||||||
不要继续用混合脚本大压。
|
|
||||||
|
|
||||||
建议新增或临时使用单接口 k6 脚本,分别只测:
|
|
||||||
|
|
||||||
- `/api/runtime/puzzle/gallery`
|
|
||||||
- `/api/runtime/custom-world-gallery`
|
|
||||||
|
|
||||||
并在同一时间窗口并行采集:
|
|
||||||
|
|
||||||
- k6 客户端 summary
|
|
||||||
- nginx access 请求数/状态码
|
|
||||||
- api-server journal latency
|
|
||||||
- 本机到服务器网络错误/timeout
|
|
||||||
|
|
||||||
目标是确认 timeout 是不是发生在客户端侧连接/网络,而不是服务端处理慢。
|
|
||||||
|
|
||||||
### C. 修复代码性能隐患
|
|
||||||
|
|
||||||
优先级建议:
|
|
||||||
|
|
||||||
1. `count_recent_public_work_plays` 改为使用 `by_public_work_play_daily_stat_work_day` 复合索引,或至少改成批量统计,避免 N 次全表扫描。
|
|
||||||
2. `list_puzzle_gallery_tx` 使用 `by_puzzle_work_publication_status` 索引查询 Published,或参考 custom-world 建立 `puzzle_gallery_entry` 公开读模型。
|
|
||||||
3. gallery 列表页不要实时逐条扫描统计表,可维护读模型或批量聚合 `recent_play_count_7d`。
|
|
||||||
|
|
||||||
### D. 调整 k6 脚本输出
|
|
||||||
|
|
||||||
建议 k6 summary 按 endpoint tag 输出或新增单接口模式,否则 overall 指标会把 puzzle/custom-world 混在一起。
|
|
||||||
|
|
||||||
建议增加:
|
|
||||||
|
|
||||||
- `ENDPOINT=puzzle_gallery_list`
|
|
||||||
- `ENDPOINT=custom_world_gallery_list`
|
|
||||||
|
|
||||||
让脚本只跑一个 endpoint,避免诊断时混淆。
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
# Genarrative 视觉小说“一句话生成”最小闭环落地计划
|
|
||||||
|
|
||||||
生成时间:2026-05-13 11:22
|
|
||||||
工作区:`C:/proj/Genarrative/.worktrees/hermes-visual-novel`
|
|
||||||
参考文档:`C:/Users/DSK/Documents/Interactive-fiction/一句话生成视觉小说整体流程总结.md`
|
|
||||||
|
|
||||||
## 1. 目标
|
|
||||||
|
|
||||||
把 Interactive-fiction 总结文档中的“一句话生成视觉小说”流程,映射并落地到 Genarrative 现有视觉小说能力中,优先做成一个可端到端验证的最小闭环:
|
|
||||||
|
|
||||||
1. 用户在视觉小说入口输入一句话并选择画风。
|
|
||||||
2. 前端进入生成过程页,展示分阶段进度。
|
|
||||||
3. 后端创建视觉小说创作会话,并基于 seedText 生成 `VisualNovelResultDraft`。
|
|
||||||
4. 生成完成后进入草稿结果页,可看到世界观、角色、场景、剧情阶段、开场选择。
|
|
||||||
5. 草稿可编译/保存为作品 profile,并进入视觉小说运行态测试/正式游玩。
|
|
||||||
|
|
||||||
本计划只覆盖 Genarrative 内部最小闭环,不引入 Interactive-fiction 原项目的独立 TXT 播放记录、分享播放包、外部活动运营、独立账号/交易/资产系统。
|
|
||||||
|
|
||||||
## 2. 当前上下文与已发现实现
|
|
||||||
|
|
||||||
### 2.1 Interactive-fiction 总结文档提炼
|
|
||||||
|
|
||||||
参考文档将整体流程分为:
|
|
||||||
|
|
||||||
- 输入侧:一句话创意、主题/风格、可选文档或素材。
|
|
||||||
- 生成侧:理解意图、扩展世界观、角色、场景、剧情阶段、开场与选择。
|
|
||||||
- 编辑侧:草稿页可查看和调整生成结果。
|
|
||||||
- 运行侧:从草稿进入视觉小说游玩,支持剧情推进、玩家选择、历史与状态。
|
|
||||||
- 资产侧:角色立绘、背景、音乐/音效可作为后续增强,最小闭环可先使用文字描述与空资产占位。
|
|
||||||
|
|
||||||
### 2.2 Genarrative 已有实现基础
|
|
||||||
|
|
||||||
已确认项目中视觉小说相关能力并非从零开始:
|
|
||||||
|
|
||||||
- 前端入口表单:
|
|
||||||
- `src/components/visual-novel-creation/VisualNovelAgentWorkspace.tsx`
|
|
||||||
- 已有“一句话创作” textarea、6 个视觉画风选项、提交按钮“生成视觉小说草稿”。
|
|
||||||
- 前端入口 payload/progress:
|
|
||||||
- `src/components/visual-novel-creation/visualNovelEntryGeneration.ts`
|
|
||||||
- 已有 `VisualNovelEntryFormPayload`、锚点展示、一句话/画风生成进度步骤。
|
|
||||||
- 前端平台主流程:
|
|
||||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
- 已接入 `createVisualNovelDraftFromForm`,会创建 session、stream message、进入 `visual-novel-generating`,完成后进入 `visual-novel-result`。
|
|
||||||
- 前端 API client:
|
|
||||||
- `src/services/visual-novel-creation/visualNovelCreationClient.ts`
|
|
||||||
- 已封装 session/message/action/compile 接口。
|
|
||||||
- 共享契约:
|
|
||||||
- `packages/shared/src/contracts/visualNovel.ts`
|
|
||||||
- 已定义 `VisualNovelResultDraft`、world/characters/scenes/storyPhases/opening/runtimeConfig/work/run/history 等结构。
|
|
||||||
- 后端 API:
|
|
||||||
- `server-rs/crates/api-server/src/visual_novel.rs`
|
|
||||||
- 已有创建 session、发消息、流式消息、执行 action、compile、work、runtime run 等接口。
|
|
||||||
- 后端 prompt:
|
|
||||||
- `server-rs/crates/api-server/src/prompt/visual_novel.rs`
|
|
||||||
- 已有 `VISUAL_NOVEL_CREATION_SYSTEM_PROMPT`、结构化输出契约、runtime GM prompt、repair prompt。
|
|
||||||
- SpacetimeDB 模块:
|
|
||||||
- `server-rs/crates/spacetime-module/src/visual_novel.rs`
|
|
||||||
- 已有 session/message/work/run/history/event 表与 procedure。
|
|
||||||
- 文档参考:
|
|
||||||
- `docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`
|
|
||||||
- `docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`
|
|
||||||
- `docs/technical/VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md`
|
|
||||||
|
|
||||||
### 2.3 关键实现判断
|
|
||||||
|
|
||||||
当前项目已经实现了视觉小说的主要骨架,本次不应大规模重写。更合理的落地方式是补齐“一句话生成”闭环中最容易断裂的点:
|
|
||||||
|
|
||||||
- 入口输入与画风信息是否被稳定传给后端 prompt。
|
|
||||||
- 后端生成 draft 后是否自动保存/关联可编辑 work profile。
|
|
||||||
- 生成过程页是否能清晰展示 Interactive-fiction 文档中提到的阶段。
|
|
||||||
- 结果页是否有足够的字段展示与继续游玩入口。
|
|
||||||
- 运行态是否能基于 opening/choices 正常启动,而不依赖尚未生成的图片/音乐资产。
|
|
||||||
|
|
||||||
## 3. 拟采用方案
|
|
||||||
|
|
||||||
### 3.1 最小闭环范围
|
|
||||||
|
|
||||||
本次优先实现:
|
|
||||||
|
|
||||||
1. “一句话 + 视觉画风”作为 `sourceMode: 'idea'` 的 seedText。
|
|
||||||
2. 后端生成完整 `VisualNovelResultDraft`,包括:
|
|
||||||
- world
|
|
||||||
- 3-6 个角色
|
|
||||||
- 3-8 个场景
|
|
||||||
- 3-6 个剧情阶段
|
|
||||||
- opening narration/firstDialogue/2-4 个 choices
|
|
||||||
- runtimeConfig
|
|
||||||
3. 若 LLM 输出失败,使用 repair 或确定性 fallback,保证可回到草稿页并显示错误/警告。
|
|
||||||
4. 结果页支持保存/编译为 work profile。
|
|
||||||
5. work profile 支持启动 runtime run,opening 能展示初始场景、旁白、对话和选择。
|
|
||||||
|
|
||||||
暂不做或仅预留:
|
|
||||||
|
|
||||||
- 真实图片/音乐生成队列。
|
|
||||||
- 多文档解析导入的完整链路。
|
|
||||||
- 复杂分镜/节点图编辑器。
|
|
||||||
- 外部 Interactive-fiction 项目的播放器、TXT 记录包、分享活动、独立账号系统。
|
|
||||||
|
|
||||||
### 3.2 与 Genarrative 架构的映射
|
|
||||||
|
|
||||||
| Interactive-fiction 概念 | Genarrative 落点 |
|
|
||||||
| --- | --- |
|
|
||||||
| 一句话创意 | `VisualNovelEntryFormPayload.ideaText` / `seedText` |
|
|
||||||
| 画风/主题 | `seedText` 中的“视觉画风/画风要求”,后续可结构化为 metadata |
|
|
||||||
| 世界观设定 | `VisualNovelResultDraft.world` |
|
|
||||||
| 角色设定 | `VisualNovelResultDraft.characters` |
|
|
||||||
| 场景设定 | `VisualNovelResultDraft.scenes` |
|
|
||||||
| 剧情阶段/章节 | `VisualNovelResultDraft.storyPhases` |
|
|
||||||
| 开场文本与选项 | `VisualNovelResultDraft.opening` |
|
|
||||||
| 运行时剧情推进 | `VisualNovelRuntimeStep[]` + run snapshot/history |
|
|
||||||
| 发布/作品库 | `VisualNovelWorkProfileRecord` / works API |
|
|
||||||
|
|
||||||
## 4. 分步计划
|
|
||||||
|
|
||||||
### Step 1:补齐入口 payload 与生成过程语义
|
|
||||||
|
|
||||||
涉及文件:
|
|
||||||
|
|
||||||
- `src/components/visual-novel-creation/VisualNovelAgentWorkspace.tsx`
|
|
||||||
- `src/components/visual-novel-creation/visualNovelEntryGeneration.ts`
|
|
||||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
|
|
||||||
任务:
|
|
||||||
|
|
||||||
1. 保持现有 6 个画风选项,但确认每个 option 的 prompt 会进入 `seedText`。
|
|
||||||
2. 将生成过程阶段从当前 3 步细化为更贴合参考文档的 4-5 步,例如:
|
|
||||||
- 理解一句话创意
|
|
||||||
- 扩展世界观与玩家身份
|
|
||||||
- 设计角色/场景/剧情阶段
|
|
||||||
- 生成开场与选择
|
|
||||||
- 准备可编辑草稿
|
|
||||||
3. 生成过程页的 anchor 保留“一句话”和“视觉画风”,必要时增加“生成目标:视觉小说草稿”。
|
|
||||||
4. 确认 `createVisualNovelDraftFromForm` 对失败状态会保留返回入口/重试能力。
|
|
||||||
|
|
||||||
验收点:提交一句话后能进入 `visual-novel-generating`,看到阶段进度;完成后进入 `visual-novel-result`。
|
|
||||||
|
|
||||||
### Step 2:增强后端 creation prompt 与 fallback 约束
|
|
||||||
|
|
||||||
涉及文件:
|
|
||||||
|
|
||||||
- `server-rs/crates/api-server/src/prompt/visual_novel.rs`
|
|
||||||
- `server-rs/crates/api-server/src/visual_novel.rs`
|
|
||||||
- 如已有 domain crate:`server-rs/crates/module-visual-novel/**` 或相关 normalize/validate 文件
|
|
||||||
|
|
||||||
任务:
|
|
||||||
|
|
||||||
1. 在 creation prompt 中显式吸收 Interactive-fiction 的“一句话生成”目标:
|
|
||||||
- 从 seedText 提取核心创意、视觉风格、故事类型。
|
|
||||||
- 生成可直接运行的 opening 和 choices。
|
|
||||||
- 图片/音乐资产先置 null,但必须有可生成图像的描述。
|
|
||||||
2. 强化输出约束:
|
|
||||||
- `opening.sceneId` 必须指向存在且 availability 为 `opening` 的 scene。
|
|
||||||
- `opening.initialChoices` 必须 2-4 个。
|
|
||||||
- `storyPhases[0]` 必须包含 opening scene 和主要角色。
|
|
||||||
- `publishReady` 的判定与 validationIssues 一致。
|
|
||||||
3. 检查 `submit_visual_novel_message_turn` / `resolve_action_draft` / compile 相关代码:
|
|
||||||
- 如果 LLM 失败,是否已有 fallback;没有则补确定性 fallback draft。
|
|
||||||
- 如果 draft 不完整,是否会 normalize/repair 并写入 session。
|
|
||||||
4. 保留现有“不要输出旧 TXT 播放记录、分享播放包、外部商业字段”的约束,避免把参考项目的外部概念误并入 Genarrative。
|
|
||||||
|
|
||||||
验收点:后端给定 seedText 时,返回 session.draft 不为空且满足共享契约。
|
|
||||||
|
|
||||||
### Step 3:确认草稿结果页、保存/编译与作品库链路
|
|
||||||
|
|
||||||
涉及文件:
|
|
||||||
|
|
||||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
- `src/components/visual-novel-creation/**`
|
|
||||||
- `src/services/visual-novel-works*` 或相关 visual novel works client
|
|
||||||
- `server-rs/crates/api-server/src/visual_novel.rs`
|
|
||||||
- `packages/shared/src/contracts/visualNovel.ts`
|
|
||||||
|
|
||||||
任务:
|
|
||||||
|
|
||||||
1. 查找并确认 `visual-novel-result` 页面组件:
|
|
||||||
- 是否显示 workTitle/workDescription/world/characters/scenes/storyPhases/opening。
|
|
||||||
- 是否有保存/发布/开始试玩按钮。
|
|
||||||
2. 确认 `compileVisualNovelWorkProfile` 或 `executeVisualNovelAction({kind:'compile_work_profile'})` 会生成/更新 work profile。
|
|
||||||
3. 确认作品架上使用 `profileId` 而不是 sessionId 作为稳定作品 ID。
|
|
||||||
4. 如果结果页缺少“一句话来源/画风”的可视化提示,可在结果页或 summary 中补轻量展示,避免用户以为画风丢失。
|
|
||||||
|
|
||||||
验收点:生成完成后能保存为作品;作品出现在“我的作品/创作架”;再次打开能读取同一 draft。
|
|
||||||
|
|
||||||
### Step 4:确认运行态 opening 闭环
|
|
||||||
|
|
||||||
涉及文件:
|
|
||||||
|
|
||||||
- `src/components/visual-novel-runtime/**`
|
|
||||||
- `src/services/visual-novel-runtime*`
|
|
||||||
- `server-rs/crates/api-server/src/visual_novel.rs`
|
|
||||||
- `server-rs/crates/api-server/src/prompt/visual_novel.rs`
|
|
||||||
- `packages/shared/src/contracts/visualNovel.ts`
|
|
||||||
|
|
||||||
任务:
|
|
||||||
|
|
||||||
1. 启动 visual novel work run 时,优先使用 `draft.opening` 生成第一轮 runtime snapshot/history。
|
|
||||||
2. 如果没有图片/音乐,前端 runtime shell 必须可用文字 fallback,不应白屏或阻断游玩。
|
|
||||||
3. 玩家选择 `choice` 后,后端 runtime GM prompt 生成下一轮 `VisualNovelRuntimeStep[]`。
|
|
||||||
4. 确认正式游玩入口调用 `work_play_start`,并满足已有埋点约定:
|
|
||||||
- `scope_kind=work`
|
|
||||||
- `scope_id=稳定作品 ID`
|
|
||||||
- metadata 包含 `playType/workId/sourceRoute/userId` 等。
|
|
||||||
|
|
||||||
验收点:从生成出的作品进入运行态,能看到 opening 并点击至少一个选择推进一轮。
|
|
||||||
|
|
||||||
### Step 5:补测试与文档
|
|
||||||
|
|
||||||
涉及文件:
|
|
||||||
|
|
||||||
- 前端测试:按仓库现有测试布局查找 `*.test.ts` / `*.test.tsx`
|
|
||||||
- Rust 测试:`server-rs/crates/api-server/src/**` 或 domain crate tests
|
|
||||||
- 文档:可追加到 `docs/technical/` 或 `.hermes/shared-memory/decision-log.md`(如团队约定需要)
|
|
||||||
|
|
||||||
建议测试:
|
|
||||||
|
|
||||||
1. TypeScript 单元测试:
|
|
||||||
- `buildVisualNovelEntryGenerationProgress` 阶段输出。
|
|
||||||
- `buildVisualNovelEntryGenerationAnchorEntries` 能展示一句话和画风。
|
|
||||||
2. Rust 单元测试:
|
|
||||||
- creation prompt 包含 seedText、sourceMode、输出契约。
|
|
||||||
- draft normalize/fallback 能生成合法 opening/choices。
|
|
||||||
- runtime opening 或 first-step 构造不依赖图片/音乐。
|
|
||||||
3. 集成/手工测试文档:
|
|
||||||
- 访问平台视觉小说入口。
|
|
||||||
- 输入一句话。
|
|
||||||
- 选择画风。
|
|
||||||
- 点击生成。
|
|
||||||
- 查看结果页。
|
|
||||||
- 保存作品。
|
|
||||||
- 启动试玩并点击选择。
|
|
||||||
|
|
||||||
## 5. 可能改动文件清单
|
|
||||||
|
|
||||||
高概率改动:
|
|
||||||
|
|
||||||
- `src/components/visual-novel-creation/VisualNovelAgentWorkspace.tsx`
|
|
||||||
- `src/components/visual-novel-creation/visualNovelEntryGeneration.ts`
|
|
||||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
- `server-rs/crates/api-server/src/prompt/visual_novel.rs`
|
|
||||||
- `server-rs/crates/api-server/src/visual_novel.rs`
|
|
||||||
- `packages/shared/src/contracts/visualNovel.ts`
|
|
||||||
|
|
||||||
中概率改动:
|
|
||||||
|
|
||||||
- `src/components/visual-novel-runtime/**`
|
|
||||||
- `src/services/visual-novel-creation/**`
|
|
||||||
- `src/services/visual-novel-runtime/**`
|
|
||||||
- `src/services/visual-novel-works/**`
|
|
||||||
- `server-rs/crates/spacetime-module/src/visual_novel.rs`
|
|
||||||
- `server-rs/crates/spacetime-client/**` 生成/绑定文件,若 SpacetimeDB contract 需要更新
|
|
||||||
|
|
||||||
低概率/仅文档:
|
|
||||||
|
|
||||||
- `docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`
|
|
||||||
- `docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`
|
|
||||||
- `.hermes/shared-memory/decision-log.md`
|
|
||||||
|
|
||||||
## 6. 验证计划
|
|
||||||
|
|
||||||
### 6.1 静态检查
|
|
||||||
|
|
||||||
在 worktree 根目录执行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
如仓库无统一 typecheck,则按 package scripts 选择最接近的前端类型检查命令。
|
|
||||||
|
|
||||||
### 6.2 前端定向测试
|
|
||||||
|
|
||||||
优先运行与 visual novel / platform entry 相关测试,如存在:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm test -- visual-novel
|
|
||||||
npm test -- platform-entry
|
|
||||||
```
|
|
||||||
|
|
||||||
若仓库使用 vitest:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run test -- visual-novel
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 Rust 定向测试
|
|
||||||
|
|
||||||
在 `server-rs` 下运行 visual novel 相关测试:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p api-server visual_novel
|
|
||||||
cargo test -p shared-contracts visual_novel
|
|
||||||
```
|
|
||||||
|
|
||||||
如改动 SpacetimeDB module:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p spacetime-module visual_novel
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.4 人工验收步骤
|
|
||||||
|
|
||||||
1. 启动本地 dev 栈。
|
|
||||||
2. 访问 Genarrative 主站。
|
|
||||||
3. 进入创作/视觉小说入口。
|
|
||||||
4. 输入:`一个雨夜,失忆的高中生在旧图书馆发现一本会回应她心声的日记。`
|
|
||||||
5. 选择任一画风,例如“映画动画”。
|
|
||||||
6. 点击“生成视觉小说草稿”。
|
|
||||||
7. 预期:进入生成过程页,能看到分阶段进度。
|
|
||||||
8. 预期:完成后进入草稿结果页,包含标题、简介、世界观、角色、场景、剧情阶段和 opening choices。
|
|
||||||
9. 点击保存/编译作品。
|
|
||||||
10. 从作品入口进入试玩。
|
|
||||||
11. 预期:opening 文本出现,至少 2 个选择可点击;点击后剧情继续推进一轮。
|
|
||||||
|
|
||||||
## 7. 风险、权衡与开放问题
|
|
||||||
|
|
||||||
### 7.1 风险
|
|
||||||
|
|
||||||
- 现有视觉小说代码已较完整,贸然新增一套 parallel pipeline 会制造重复逻辑;应复用当前 `VisualNovelResultDraft` 与 creation agent flow。
|
|
||||||
- LLM 输出不稳定可能导致草稿结构不完整;需要 normalize/repair/fallback 确保最小闭环。
|
|
||||||
- 视觉/音乐资产生成未接入时,UI 必须接受 null asset,否则运行态可能白屏。
|
|
||||||
- `PlatformEntryFlowShellImpl.tsx` 文件很大,改动需局部、谨慎,避免影响其他玩法入口。
|
|
||||||
- 若改动 SpacetimeDB 表结构,可能牵涉 publish、client binding、清库/迁移;最小闭环阶段应尽量避免 schema 变更。
|
|
||||||
|
|
||||||
### 7.2 权衡
|
|
||||||
|
|
||||||
- 先让文字版视觉小说完整跑通,再补角色立绘/背景图生成。
|
|
||||||
- 先用 `seedText` 承载画风,再考虑把 `visualStyleId/Label/Prompt` 结构化进 draft metadata。
|
|
||||||
- 先用现有 result/work/runtime 页面闭环,不引入新编辑器。
|
|
||||||
|
|
||||||
### 7.3 开放问题
|
|
||||||
|
|
||||||
1. 用户是否要求把 Interactive-fiction 原项目中的具体 UI 样式/页面布局迁移到 Genarrative?当前计划只迁移流程语义,不迁移独立 UI。
|
|
||||||
2. 画风是否需要成为作品可编辑字段?当前以 seedText/prompt 影响生成内容,后续可在 draft 中增加 metadata。
|
|
||||||
3. 文档导入模式是否本期要做?当前计划聚焦一句话模式,document 模式只保留契约能力。
|
|
||||||
4. 是否需要真实图片/音乐生成?当前计划作为后续增强,不纳入最小闭环。
|
|
||||||
|
|
||||||
## 8. 建议实施顺序
|
|
||||||
|
|
||||||
1. 先做只改 prompt/progress/少量前端展示的轻量闭环修补。
|
|
||||||
2. 运行前后端定向测试,确认现有能力是否已足够。
|
|
||||||
3. 如果后端没有 fallback 或 normalize,再补 Rust 层确定性兜底。
|
|
||||||
4. 手工跑通“一句话 -> 生成 -> 结果页 -> 保存 -> 试玩”。
|
|
||||||
5. 最后再考虑是否需要资产生成、文档导入、结构化画风 metadata。
|
|
||||||
@@ -1,549 +0,0 @@
|
|||||||
# Bark Battle Phase 2 Platform Work Loop Implementation Plan
|
|
||||||
|
|
||||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
|
||||||
|
|
||||||
**Goal:** 将 `bark-battle` 从内部试玩 demo 升级为 Genarrative 正式 play type,打通轻创作配置、发布态作品、正式 runtime、run start / finish、后端裁决、个人历史、作品统计和最小排行榜闭环。
|
|
||||||
|
|
||||||
**Architecture:** 先冻结 shared contracts 与 `module-bark-battle` 纯领域规则,再落 SpacetimeDB 表/reducer、`spacetime-client` facade 和 `api-server` BFF,随后接前端最小纵切,最后补排行榜/个人历史/作品统计投影体验。前端只承接表现、交互和临时 UI 状态,正式业务真相由后端裁决。
|
|
||||||
|
|
||||||
**Tech Stack:** React + TypeScript + Vite, server-rs + Axum, SpacetimeDB Rust module, shared-contracts, Vitest, Cargo tests, npm scripts.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 已确认决策
|
|
||||||
|
|
||||||
1. “有效叫声”统一为 **有效声浪触发**:当前采样响度达到有效阈值且满足 `minBarkGapMs` 冷却即触发;不再要求 `minBarkDurationMs` / `maxBarkDurationMs`,也不等待响度回落。
|
|
||||||
2. Phase 2 范围是 **Bark Battle 平台作品闭环**,不是单纯玩法表现深化。
|
|
||||||
3. 作品形态是 **轻创作配置作品**:标题、描述、主题/背景预设、狗狗皮肤预设、难度预设、排行榜开关。
|
|
||||||
4. 难度预设只影响 AI 对手行为;不影响有效阈值、冷却、时长、分数公式或反作弊阈值。
|
|
||||||
5. 排行榜按 `workId + difficultyPreset + rulesetVersion` 分榜。
|
|
||||||
6. 后端裁决正式单局结果;前端只提交派生指标,`clientResult` 只用于 debug/对账。
|
|
||||||
7. 排行榜只收录 `serverResult = player_win` 且未被反作弊拒绝的单局结果,排序以 `finalEnergy` 优先。
|
|
||||||
8. 作品统计使用最小后端投影:start、finish、win/draw/loss、flagged、leaderboard、best/avg energy。
|
|
||||||
9. 个人历史成绩 = 最近记录列表 + 个人最佳摘要;仅本人可见。
|
|
||||||
10. 正式入口闭环覆盖创作入口、作品详情 CTA、广场/作品卡片、我的作品、稳定作品 ID runtime 路由和 `work_play_start`。
|
|
||||||
11. 创作编辑形态是单页轻配置表单 + 预览卡片。
|
|
||||||
12. 实施顺序固定为:契约与领域规则 → SpacetimeDB 表/reducer 与 api-server BFF → 最小前端纵切 → 投影与列表体验 → 收口验证。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 必读文档与约束
|
|
||||||
|
|
||||||
实施前先读:
|
|
||||||
|
|
||||||
- `AGENTS.md`
|
|
||||||
- `CONTEXT.md`
|
|
||||||
- `docs/prd/BARK_BATTLE_BDD_2026-05-11.md`
|
|
||||||
- `docs/technical/BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md`
|
|
||||||
- `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md`
|
|
||||||
- `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
|
|
||||||
- `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
|
|
||||||
- `.codex/skills/spacetimedb-cli/SKILL.md`
|
|
||||||
- `.codex/skills/spacetimedb-rust/SKILL.md`
|
|
||||||
- `.codex/skills/spacetimedb-concepts/SKILL.md`
|
|
||||||
- `.codex/skills/spacetimedb-typescript/SKILL.md`
|
|
||||||
|
|
||||||
关键约束:
|
|
||||||
|
|
||||||
- 后端路线固定 `server-rs + Axum + SpacetimeDB`。
|
|
||||||
- 领域规则进 `module-bark-battle`,SpacetimeDB 表和事务编排进 `spacetime-module`。
|
|
||||||
- HTTP/SSE/BFF 留在 `api-server`。
|
|
||||||
- 前后端 DTO 留在 `shared-contracts`。
|
|
||||||
- 数据库表结构更改必须同步 `migration.rs` 和生成绑定。
|
|
||||||
- 人工命令/文档示例禁止继续使用 `spacetime --root-dir`。
|
|
||||||
- 修改中文文件后必须跑 `npm run check:encoding`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 阶段一:契约与领域规则
|
|
||||||
|
|
||||||
### Task 1.1: 新增 Rust shared-contracts 模块
|
|
||||||
|
|
||||||
**Objective:** 定义 Bark Battle Phase 2 的 Rust DTO 边界。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `server-rs/crates/shared-contracts/src/bark_battle.rs`
|
|
||||||
- Modify: `server-rs/crates/shared-contracts/src/lib.rs`
|
|
||||||
- Test: `server-rs/crates/shared-contracts/src/bark_battle.rs`
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
1. 新增枚举:`BarkBattleDifficultyPreset { Easy, Normal, Hard }`、`BarkBattleServerResult { PlayerWin, OpponentWin, Draw }`、`BarkBattleFinishStatus { Accepted, AcceptedWithFlags, Rejected }`。
|
|
||||||
2. 新增配置 DTO:`BarkBattleDraftConfig`、`BarkBattlePublishedConfig`、`BarkBattleRuntimeConfig`。
|
|
||||||
3. 新增 run DTO:`BarkBattleRunStartRequest/Response`、`BarkBattleRunFinishRequest/Response`。
|
|
||||||
4. 新增派生指标 DTO:`BarkBattleDerivedMetrics`,字段包含 `trigger_count`、`max_volume`、`average_volume`、`final_energy`、`combo_max`。
|
|
||||||
5. 新增排行榜/历史/统计 DTO:`BarkBattleLeaderboardEntry`、`BarkBattlePersonalHistoryItem`、`BarkBattlePersonalBestSummary`、`BarkBattleWorkStats`。
|
|
||||||
6. 在 `lib.rs` 导出 `pub mod bark_battle;`。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p shared-contracts bark_battle
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: contracts tests pass.
|
|
||||||
|
|
||||||
### Task 1.2: 新增 TypeScript shared contracts mirror
|
|
||||||
|
|
||||||
**Objective:** 让前端获得与 Rust DTO 对齐的类型。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `packages/shared/src/contracts/barkBattle.ts`
|
|
||||||
- Modify: `packages/shared/src/contracts/index.ts`
|
|
||||||
- Test: `packages/shared/src/contracts/barkBattle.test.ts`
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
1. 定义 `BarkBattleDifficultyPreset = 'easy' | 'normal' | 'hard'`。
|
|
||||||
2. 定义 `BarkBattleServerResult = 'player_win' | 'opponent_win' | 'draw'`。
|
|
||||||
3. 定义 draft / published / runtime config 类型。
|
|
||||||
4. 定义 start / finish request response 类型。
|
|
||||||
5. 定义 leaderboard / personal history / work stats 类型。
|
|
||||||
6. 写最小序列化/fixture 测试,确保字段命名采用前端约定 camelCase,并在 API client 层做必要映射。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm test -- --run packages/shared/src/contracts/barkBattle.test.ts
|
|
||||||
npx tsc -p tsconfig.typecheck-guardrails.json --noEmit --pretty false
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.3: 新建 module-bark-battle crate
|
|
||||||
|
|
||||||
**Objective:** 将正式裁决规则放入纯领域 crate。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `server-rs/crates/module-bark-battle/Cargo.toml`
|
|
||||||
- Create: `server-rs/crates/module-bark-battle/src/lib.rs`
|
|
||||||
- Create: `server-rs/crates/module-bark-battle/src/domain.rs`
|
|
||||||
- Create: `server-rs/crates/module-bark-battle/src/scoring.rs`
|
|
||||||
- Modify: `server-rs/Cargo.toml`
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
1. 在 workspace 中注册 `module-bark-battle`。
|
|
||||||
2. 定义 `RulesetVersion`,首版固定如 `bark-battle-ruleset-v1`。
|
|
||||||
3. 定义 `BarkBattleRuleset`,包含标准局时长 30s、`min_bark_gap_ms`、合法音量/能量/连击范围、duration tolerance。
|
|
||||||
4. 实现 `validate_finish_metrics()`。
|
|
||||||
5. 实现 `adjudicate_result()`:以后端 `final_energy` 和 draw threshold 生成 `serverResult`。
|
|
||||||
6. 实现 `compute_leaderboard_score()`:只允许胜利局入榜,排序因子为 `finalEnergy`、`triggerCount`、`maxVolume`、duration 接近度、`finishedAt`。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p module-bark-battle
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 1.4: 领域规则单测覆盖作弊边界
|
|
||||||
|
|
||||||
**Objective:** 防止前端伪造 finish 直接刷榜。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `server-rs/crates/module-bark-battle/src/scoring.rs`
|
|
||||||
|
|
||||||
**Test cases:**
|
|
||||||
- 28s-35s 合法窗口内可接受。
|
|
||||||
- 1s / 300s 应 rejected 或 flagged。
|
|
||||||
- `triggerCount > durationMs / minBarkGapMs + tolerance` 应 flagged。
|
|
||||||
- `finalEnergy` 越界应 rejected。
|
|
||||||
- 平/负不生成 leaderboard entry。
|
|
||||||
- easy/normal/hard 不改变阈值、冷却、分数公式,只改变 AI preset key。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p module-bark-battle -- --nocapture
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 阶段二:SpacetimeDB 表/reducer 与 api-server BFF
|
|
||||||
|
|
||||||
### Task 2.1: 设计 SpacetimeDB 表目录
|
|
||||||
|
|
||||||
**Objective:** 新增 Bark Battle 表并与 migration 对齐。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `server-rs/crates/spacetime-module/src/bark_battle/mod.rs`
|
|
||||||
- Create: `server-rs/crates/spacetime-module/src/bark_battle/types.rs`
|
|
||||||
- Create: `server-rs/crates/spacetime-module/src/bark_battle/tables.rs`
|
|
||||||
- Modify: `server-rs/crates/spacetime-module/src/lib.rs`
|
|
||||||
- Modify: `server-rs/crates/spacetime-module/src/migration.rs`
|
|
||||||
|
|
||||||
**Tables:**
|
|
||||||
- `bark_battle_draft_config`
|
|
||||||
- `bark_battle_published_config`
|
|
||||||
- `bark_battle_runtime_run`
|
|
||||||
- `bark_battle_score_record`
|
|
||||||
- `bark_battle_leaderboard_entry`
|
|
||||||
- `bark_battle_work_stats_projection`
|
|
||||||
- `bark_battle_personal_best_projection`
|
|
||||||
|
|
||||||
**Pitfalls:**
|
|
||||||
- 表结构不要 derive `SpacetimeType`。
|
|
||||||
- reducer 使用 `&ReducerContext`。
|
|
||||||
- 授权身份来自 `ctx.sender()`。
|
|
||||||
- 需要公开订阅的表才加 `public`。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p spacetime-module
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.2: 实现草稿/发布 reducer
|
|
||||||
|
|
||||||
**Objective:** 支持轻配置草稿保存和发布态 config 固化。
|
|
||||||
|
|
||||||
**Reducers:**
|
|
||||||
- `create_bark_battle_draft`
|
|
||||||
- `update_bark_battle_draft_config`
|
|
||||||
- `publish_bark_battle_work`
|
|
||||||
- `get_bark_battle_runtime_config` 如仓库约定使用 reducer/procedure 查询则按现有 pattern 实现。
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- 草稿配置只允许标题、描述、主题/背景预设、狗狗皮肤预设、难度预设、排行榜开关。
|
|
||||||
- 发布生成稳定作品 ID / config version。
|
|
||||||
- 发布态 config 包含 `rulesetVersion`。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p spacetime-module bark_battle
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.3: 实现 run start / finish reducer
|
|
||||||
|
|
||||||
**Objective:** 打通正式运行态后端事务。
|
|
||||||
|
|
||||||
**Reducers:**
|
|
||||||
- `start_bark_battle_run`
|
|
||||||
- `finish_bark_battle_run`
|
|
||||||
- `get_bark_battle_run`
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- start 创建 `run_id` 和一次性 `run_token`。
|
|
||||||
- start 记录 work/config/ruleset/difficulty 快照。
|
|
||||||
- finish 必须校验 run token、未 finish、work/config/ruleset/difficulty 一致。
|
|
||||||
- finish 调用 `module-bark-battle` 裁决结果。
|
|
||||||
- accepted 写 score record。
|
|
||||||
- `serverResult = player_win` 且排行榜开启且未 rejected 时写 leaderboard entry。
|
|
||||||
- accepted / accepted_with_flags 更新 work stats 和 personal best projection。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p spacetime-module bark_battle_run
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.4: 更新 migration 与生成绑定
|
|
||||||
|
|
||||||
**Objective:** 让 SpacetimeDB 表结构变更可发布。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `server-rs/crates/spacetime-module/src/migration.rs`
|
|
||||||
- Generated: `server-rs/crates/spacetime-client/src/module_bindings/*bark*`
|
|
||||||
|
|
||||||
**Commands:**
|
|
||||||
按仓库现有脚本优先;不要手改 generated bindings。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run spacetime:build
|
|
||||||
npm run spacetime:generate
|
|
||||||
```
|
|
||||||
|
|
||||||
若脚本名不同,先查 `package.json` 和 `server-rs` README。
|
|
||||||
|
|
||||||
### Task 2.5: 实现 spacetime-client facade
|
|
||||||
|
|
||||||
**Objective:** api-server 不直接操作 generated bindings。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `server-rs/crates/spacetime-client/src/bark_battle.rs`
|
|
||||||
- Modify: `server-rs/crates/spacetime-client/src/lib.rs`
|
|
||||||
|
|
||||||
**Methods:**
|
|
||||||
- `create_bark_battle_draft`
|
|
||||||
- `save_bark_battle_draft_config`
|
|
||||||
- `publish_bark_battle_work`
|
|
||||||
- `get_bark_battle_runtime_config`
|
|
||||||
- `start_bark_battle_run`
|
|
||||||
- `finish_bark_battle_run`
|
|
||||||
- `list_bark_battle_leaderboard`
|
|
||||||
- `list_my_bark_battle_history`
|
|
||||||
- `get_my_bark_battle_best_summary`
|
|
||||||
- `get_bark_battle_work_stats`
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p spacetime-client bark_battle
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2.6: 实现 api-server BFF 路由
|
|
||||||
|
|
||||||
**Objective:** 暴露前端需要的 HTTP API。
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `server-rs/crates/api-server/src/bark_battle.rs`
|
|
||||||
- Modify: `server-rs/crates/api-server/src/app.rs`
|
|
||||||
|
|
||||||
**Routes:**
|
|
||||||
- `POST /api/bark-battle/drafts`
|
|
||||||
- `PATCH /api/bark-battle/drafts/:draftId`
|
|
||||||
- `POST /api/bark-battle/drafts/:draftId/publish`
|
|
||||||
- `GET /api/bark-battle/works/:workId/runtime-config`
|
|
||||||
- `POST /api/bark-battle/runs/start`
|
|
||||||
- `POST /api/bark-battle/runs/:runId/finish`
|
|
||||||
- `GET /api/bark-battle/works/:workId/leaderboard`
|
|
||||||
- `GET /api/bark-battle/me/history`
|
|
||||||
- `GET /api/bark-battle/me/best-summary`
|
|
||||||
- `GET /api/bark-battle/works/:workId/stats`
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p api-server bark_battle
|
|
||||||
npm run api-server
|
|
||||||
curl -f http://127.0.0.1:<api-port>/healthz
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 阶段三:最小前端纵切
|
|
||||||
|
|
||||||
### Task 3.1: 新增前端 service client
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/services/bark-battle/barkBattleClient.ts`
|
|
||||||
- Test: `src/services/bark-battle/barkBattleClient.test.ts`
|
|
||||||
|
|
||||||
**Methods:** 与 BFF routes 一一对应。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm test -- --run src/services/bark-battle/barkBattleClient.test.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3.2: 接入创作入口与 SelectionStage
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/config/newWorkEntryConfig.ts`
|
|
||||||
- Modify: `src/components/platform-entry/platformEntryCreationTypes.ts`
|
|
||||||
- Modify: `src/components/platform-entry/platformEntryTypes.ts`
|
|
||||||
- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- 新增 `bark-battle` play type。
|
|
||||||
- 入口打开单页轻配置表单,不走复杂 agent workspace。
|
|
||||||
- 移动端入口布局不能溢出。
|
|
||||||
|
|
||||||
### Task 3.3: 实现单页轻配置表单 + 预览卡片
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`
|
|
||||||
- Create: `src/components/bark-battle-creation/BarkBattlePreviewCard.tsx`
|
|
||||||
- Test: `src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx`
|
|
||||||
|
|
||||||
**UI fields:**
|
|
||||||
- 标题必填
|
|
||||||
- 简介选填
|
|
||||||
- 主题/背景预设
|
|
||||||
- 狗狗皮肤预设
|
|
||||||
- 难度预设,默认 `normal`
|
|
||||||
- 排行榜开关,默认开启
|
|
||||||
|
|
||||||
**UI constraints:**
|
|
||||||
- 不堆大段玩法说明。
|
|
||||||
- 按现有游戏 UI 风格设计。
|
|
||||||
- 移动端优先。
|
|
||||||
|
|
||||||
### Task 3.4: 发布后进入作品详情
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
|
||||||
- Modify: `src/components/platform-entry/PlatformWorkDetailView.tsx`
|
|
||||||
- Modify: `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
- Modify: `src/components/custom-world-home/creationWorkShelf.ts`
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- 发布成功刷新 works/gallery/shelf。
|
|
||||||
- 跳作品详情。
|
|
||||||
- 详情 CTA 可以进入正式 runtime。
|
|
||||||
|
|
||||||
### Task 3.5: runtime 拉发布态 config 并 start / finish
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/games/bark-battle/*`
|
|
||||||
- Modify: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
|
|
||||||
- Create/Modify: `src/components/bark-battle-runtime/BarkBattleRuntimeRoute.tsx` 如需要
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- runtime 通过稳定 `workId` 拉 `BarkBattleRuntimeConfig`。
|
|
||||||
- 开始正式局时调用 start run。
|
|
||||||
- 结束时提交 finish 派生指标。
|
|
||||||
- 结算展示 `serverResult`、`scoreSummary`、`antiCheatFlags`、leaderboard entry。
|
|
||||||
- 麦克风原始音频不上传。
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm test -- --run src/games/bark-battle/domain/__tests__/BarkDetector.test.ts src/games/bark-battle/application/__tests__/BarkBattleController.test.ts src/games/bark-battle/ui/__tests__/BarkBattleRuntimeShell.test.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 阶段四:投影与列表体验
|
|
||||||
|
|
||||||
### Task 4.1: 排行榜 UI
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/components/bark-battle-leaderboard/BarkBattleLeaderboardPanel.tsx`
|
|
||||||
- Test: `src/components/bark-battle-leaderboard/BarkBattleLeaderboardPanel.test.tsx`
|
|
||||||
- Modify: `src/components/platform-entry/PlatformWorkDetailView.tsx`
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- 查询维度 `workId + difficultyPreset + rulesetVersion`。
|
|
||||||
- 只展示胜利入榜成绩。
|
|
||||||
- 不展示平/负/flagged 历史。
|
|
||||||
|
|
||||||
### Task 4.2: 个人历史最近记录 + 最佳摘要 UI
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/components/bark-battle-history/BarkBattlePersonalHistoryPanel.tsx`
|
|
||||||
- Test: `src/components/bark-battle-history/BarkBattlePersonalHistoryPanel.test.tsx`
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- 默认最近 20 条。
|
|
||||||
- 仅本人可见。
|
|
||||||
- 可按 workId / difficultyPreset 过滤。
|
|
||||||
- flagged 只做轻提示,不展示详细反作弊原因。
|
|
||||||
|
|
||||||
### Task 4.3: 作品统计展示
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/components/bark-battle-stats/BarkBattleWorkStatsPanel.tsx`
|
|
||||||
- Test: `src/components/bark-battle-stats/BarkBattleWorkStatsPanel.test.tsx`
|
|
||||||
|
|
||||||
**Fields:**
|
|
||||||
- `playStartCount`
|
|
||||||
- `finishCount`
|
|
||||||
- `winCount`
|
|
||||||
- `drawCount`
|
|
||||||
- `lossCount`
|
|
||||||
- `flaggedCount`
|
|
||||||
- `leaderboardEntryCount`
|
|
||||||
- `bestLeaderboardScore`
|
|
||||||
- `bestFinalEnergy`
|
|
||||||
- `averageFinalEnergy`
|
|
||||||
- `updatedAt`
|
|
||||||
|
|
||||||
### Task 4.4: 广场卡片/我的作品适配
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/components/custom-world-home/creationWorkShelf.ts`
|
|
||||||
- Modify: `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
- Modify: `src/components/rpg-entry/rpgEntryWorldPresentation.ts`
|
|
||||||
- Modify: `src/services/publicWorkCode.ts` 如分享码需要支持
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- Bark Battle 作品能展示、打开详情、开始游玩。
|
|
||||||
- 不新增独立 Bark Battle 专区。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 阶段五:收口验证
|
|
||||||
|
|
||||||
### Task 5.1: 自动测试清单
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -p shared-contracts bark_battle
|
|
||||||
cargo test -p module-bark-battle
|
|
||||||
cargo test -p spacetime-module bark_battle
|
|
||||||
cargo test -p spacetime-client bark_battle
|
|
||||||
cargo test -p api-server bark_battle
|
|
||||||
npm test -- --run packages/shared/src/contracts/barkBattle.test.ts
|
|
||||||
npm test -- --run src/services/bark-battle/barkBattleClient.test.ts
|
|
||||||
npm test -- --run src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx
|
|
||||||
npm test -- --run src/games/bark-battle/domain/__tests__/BarkDetector.test.ts src/games/bark-battle/application/__tests__/BarkBattleController.test.ts src/games/bark-battle/ui/__tests__/BarkBattleRuntimeShell.test.tsx
|
|
||||||
npx tsc -p tsconfig.typecheck-guardrails.json --noEmit --pretty false
|
|
||||||
npm run check:encoding
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 5.2: 后端 smoke
|
|
||||||
|
|
||||||
1. 按项目脚本启动 SpacetimeDB + api-server,优先使用 `npm run api-server`,不要使用旧命令。
|
|
||||||
2. 确认 `/healthz`。
|
|
||||||
3. smoke 流程:创建草稿 → 保存配置 → 发布 → 拉 runtime config → start run → finish run → 查询 leaderboard/history/stats。
|
|
||||||
|
|
||||||
### Task 5.3: 人工验收路径
|
|
||||||
|
|
||||||
1. 进入创作入口/玩法选择,选择 Bark Battle。
|
|
||||||
2. 在单页轻配置表单中填写标题,选择主题、狗狗皮肤、难度,保持排行榜开启。
|
|
||||||
3. 保存草稿。
|
|
||||||
4. 发布作品。
|
|
||||||
5. 发布后自动进入作品详情。
|
|
||||||
6. 点击开始游玩进入正式 runtime。
|
|
||||||
7. 授权麦克风,完成 30 秒单局。
|
|
||||||
8. 结算页显示后端 `serverResult` 和 score summary。
|
|
||||||
9. 若胜利,排行榜出现本局成绩。
|
|
||||||
10. 我的记录显示最近记录和个人最佳摘要。
|
|
||||||
11. 作品详情/作者视角能看到作品统计。
|
|
||||||
12. 广场/作品卡片和我的作品入口都能再次进入详情和 runtime。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 不做范围
|
|
||||||
|
|
||||||
- 不做实时多人。
|
|
||||||
- 不做 ghost replay。
|
|
||||||
- 不做 AI 狗叫识别。
|
|
||||||
- 不保存原始音频、PCM、waveform 或可还原语音内容。
|
|
||||||
- 不做独立 Bark Battle 专区/活动页。
|
|
||||||
- 不做挑战分享、好友邀请、多人数房间。
|
|
||||||
- 不做复杂编辑器、多步骤向导、规则参数编辑、AI 生成配置。
|
|
||||||
- 不做 DAU/留存、按小时统计曲线、好友对比。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 三人并行建议
|
|
||||||
|
|
||||||
### 开发者 A:后端契约与领域规则
|
|
||||||
|
|
||||||
负责 Task 1.1、1.3、1.4。先提交 contracts 与 `module-bark-battle`,为后续后端/前端提供稳定类型和裁决规则。
|
|
||||||
|
|
||||||
### 开发者 B:SpacetimeDB + api-server
|
|
||||||
|
|
||||||
负责 Task 2.1 到 2.6。必须等开发者 A 的 DTO/领域规则基本稳定后开始,或先基于计划字段开分支实现表结构。
|
|
||||||
|
|
||||||
### 开发者 C:前端纵切与 UI
|
|
||||||
|
|
||||||
负责 Task 3.x 与 4.x。开始时可先做组件空态和 service client 类型,真正联调等 B 的 BFF ready。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 推荐提交节奏
|
|
||||||
|
|
||||||
1. `feat: add bark battle contracts and domain rules`
|
|
||||||
2. `feat: add bark battle spacetime tables and reducers`
|
|
||||||
3. `feat: add bark battle api server routes`
|
|
||||||
4. `feat: add bark battle creation editor`
|
|
||||||
5. `feat: connect bark battle runtime to server results`
|
|
||||||
6. `feat: add bark battle leaderboard history stats`
|
|
||||||
7. `docs: finalize bark battle phase2 verification guide`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 完成定义
|
|
||||||
|
|
||||||
Phase 2 完成必须同时满足:
|
|
||||||
|
|
||||||
- Bark Battle 可以从正式创作入口创建轻配置作品。
|
|
||||||
- 作品可以发布为稳定 workId。
|
|
||||||
- 作品详情/广场/我的作品可以发现并进入正式 runtime。
|
|
||||||
- runtime 从后端发布态 config 拉配置。
|
|
||||||
- start run 写 `work_play_start`。
|
|
||||||
- finish 只上传派生指标。
|
|
||||||
- 后端裁决 `serverResult` / `scoreSummary` / `leaderboardScore` / `antiCheatFlags`。
|
|
||||||
- 胜利局进入按 `workId + difficultyPreset + rulesetVersion` 分榜的排行榜。
|
|
||||||
- 个人历史和作品统计可查询。
|
|
||||||
- 自动测试、encoding、typecheck、diff check 和人工验收路径通过。
|
|
||||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 41 KiB |
@@ -11,18 +11,34 @@
|
|||||||
- 决策:最终决定是什么
|
- 决策:最终决定是什么
|
||||||
- 影响范围:涉及哪些模块/文档/流程
|
- 影响范围:涉及哪些模块/文档/流程
|
||||||
- 验证方式:如何确认决策仍有效
|
- 验证方式:如何确认决策仍有效
|
||||||
- 关联文档:相关 PRD、技术文档、提交或 Issue
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-05-15 汪汪声浪和宝贝识物入口设为敬请期待
|
||||||
|
|
||||||
|
- 背景:当前需要暂时关闭汪汪声浪和宝贝识物两个模板的创建链路,但仍保留创作 Tab 中的模板占位。
|
||||||
|
- 决策:`bark-battle` 与 `baby-object-match` 的默认创作入口配置调整为 `visible=true`、`open=false`、`badge=敬请期待`;SpacetimeDB 入口配置种子会把旧默认开放行纠偏为敬请期待,api-server 路由熔断覆盖 `/api/creation/bark-battle/*`、`/api/runtime/bark-battle/*` 和 `/api/creation/edutainment/baby-object-match/*`。
|
||||||
|
- 影响范围:`module-runtime` 默认入口配置、`spacetime-module` 入口配置种子纠偏、`api-server` 入口路由熔断、创作玩法文档和入口配置排障记忆。
|
||||||
|
- 验证方式:执行 `cargo test -p module-runtime default_creation_entry_types --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server creation_entry_config --manifest-path server-rs/Cargo.toml`、`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`、`npm run check:encoding`,并用 `npm run api-server` 后检查 `/healthz` 与 `/api/creation-entry/config`。
|
||||||
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
|
## 2026-05-15 抓大鹅生成素材记录物品相对尺寸
|
||||||
|
|
||||||
|
- 背景:抓大鹅 2D 五视角素材此前只保存物品名称和图片,运行态所有生成素材显示尺寸一致;用户要求生成物品名称时同时给出合理的 `大 / 中 / 小` 相对尺寸,并把当前默认尺寸视为 `大`。
|
||||||
|
- 决策:`match3d_compile_draft` 的生成计划 `items[]` 增加 `itemSize`,只允许 `大 / 中 / 小`;后端把该字段持久化到 `generatedItemAssets[].itemSize` 并通过 Agent / Works DTO 下发。历史缺失 `itemSize` 的素材按 `大` 兼容;模型缺失或返回非法值时按物品名称本地推断,仍无法判断时使用 `中`。运行态只用该字段缩放生成 2D 图片本体,不改变后端下发的布局半径、点击半径和规则真相。
|
||||||
|
- 影响范围:`api-server` Match3D 草稿生成计划、`shared-contracts` 与 TS Match3D 作品契约、结果页素材合并、`Match3DRuntimeShell` 场内/托盘/飞行动画图片显示、Match3D PRD 与素材流水线技术文档。
|
||||||
|
- 验证方式:执行 `cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml`、`cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,并定向跑 `Match3DRuntimeShell.test.tsx` 中尺寸和生成图片相关用例。
|
||||||
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 创作页图像输入统一封装为图像组件
|
## 2026-05-14 创作页图像输入统一封装为图像组件
|
||||||
|
|
||||||
- 背景:拼图创作页已经具备“画面描述生图 / 多参考图生图 / 上传主图后 AI 重绘 / 上传主图后不重绘”四条路径,抓大鹅封面和后续创作页也会复用同一套交互;继续在页面内复制会导致参考图、预览、删除确认和重绘开关漂移。
|
- 背景:拼图创作页已经具备“画面描述生图 / 多参考图生图 / 上传主图后 AI 重绘 / 上传主图后不重绘”四条路径,抓大鹅封面和后续创作页也会复用同一套交互;继续在页面内复制会导致参考图、预览、删除确认和重绘开关漂移。
|
||||||
- 决策:通用图像输入 UI 统一使用 `src/components/common/CreativeImageInputPanel.tsx`。组件采用受控模式,只负责主图上传卡、画面描述输入、参考图缩略图与预览、AI 重绘开关、错误展示和提交按钮;外层页面负责文件读取/裁剪、历史素材弹层、计费确认、自动保存和具体后端请求。
|
- 决策:通用图像输入 UI 统一使用 `src/components/common/CreativeImageInputPanel.tsx`。组件采用受控模式,只负责主图上传卡、画面描述输入、参考图缩略图与预览、AI 重绘开关、错误展示和提交按钮;外层页面负责文件读取/裁剪、历史素材弹层、计费确认、自动保存和具体后端请求。
|
||||||
- 影响范围:拼图创作入口、后续抓大鹅封面生成入口、其它需要复用图像输入链路的创作页。
|
- 影响范围:拼图创作入口、后续抓大鹅封面生成入口、其它需要复用图像输入链路的创作页。
|
||||||
- 验证方式:拼图入口交互测试继续覆盖四种路径;后续页面接入时只传入业务回调与文案,不复制上传卡和参考图缩略图实现。
|
- 验证方式:拼图入口交互测试继续覆盖四种路径;后续页面接入时只传入业务回调与文案,不复制上传卡和参考图缩略图实现。
|
||||||
- 关联文档:`docs/technical/【前端体验】图像组件统一封装与复用边界-2026-05-14.md`。
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 汪汪声浪创作入口改为创作 Tab 内嵌轻配置
|
## 2026-05-14 汪汪声浪创作入口改为创作 Tab 内嵌轻配置
|
||||||
|
|
||||||
@@ -30,7 +46,7 @@
|
|||||||
- 决策:`bark-battle` 的创作入口只在创作 Tab 内嵌渲染轻配置表单,入口点击只切到创作页并选中该模板,不再使用 `bark-battle-config` 独立阶段;runtime 退出时回到创作页并恢复汪汪声浪模板选中态。
|
- 决策:`bark-battle` 的创作入口只在创作 Tab 内嵌渲染轻配置表单,入口点击只切到创作页并选中该模板,不再使用 `bark-battle-config` 独立阶段;runtime 退出时回到创作页并恢复汪汪声浪模板选中态。
|
||||||
- 影响范围:`PlatformEntryFlowShellImpl`、`BarkBattleConfigEditor`、`BarkBattleRuntimeShell`、入口配置说明和相关交互测试。
|
- 影响范围:`PlatformEntryFlowShellImpl`、`BarkBattleConfigEditor`、`BarkBattleRuntimeShell`、入口配置说明和相关交互测试。
|
||||||
- 验证方式:创作 Tab 中点击汪汪声浪后直接看到内嵌表单,不应再出现单独配置页;发布进入 runtime 后退出应回到创作页的汪汪声浪模板。
|
- 验证方式:创作 Tab 中点击汪汪声浪后直接看到内嵌表单,不应再出现单独配置页;发布进入 runtime 后退出应回到创作页的汪汪声浪模板。
|
||||||
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 拼图与抓大鹅生成页移动端收口为等待与计时双栏
|
## 2026-05-14 拼图与抓大鹅生成页移动端收口为等待与计时双栏
|
||||||
|
|
||||||
@@ -38,7 +54,7 @@
|
|||||||
- 决策:这两类轻量玩法的生成页隐藏“当前批次”模块,只保留“预计等待”和“计时”并排展示;生成步骤进入页面时按顺序从左侧滑入,强化推进感。
|
- 决策:这两类轻量玩法的生成页隐藏“当前批次”模块,只保留“预计等待”和“计时”并排展示;生成步骤进入页面时按顺序从左侧滑入,强化推进感。
|
||||||
- 影响范围:`CustomWorldGenerationView`、拼图与抓大鹅创作入口调用处、移动端生成页体验文档。
|
- 影响范围:`CustomWorldGenerationView`、拼图与抓大鹅创作入口调用处、移动端生成页体验文档。
|
||||||
- 验证方式:拼图与抓大鹅生成页在手机竖屏下只显示等待与计时双栏,步骤卡按顺序滑入;其它未传入隐藏参数的生成页继续保留原批次模块。
|
- 验证方式:拼图与抓大鹅生成页在手机竖屏下只显示等待与计时双栏,步骤卡按顺序滑入;其它未传入隐藏参数的生成页继续保留原批次模块。
|
||||||
- 关联文档:`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 移动端输入法弹出时平台画布不压缩
|
## 2026-05-14 移动端输入法弹出时平台画布不压缩
|
||||||
|
|
||||||
@@ -46,7 +62,7 @@
|
|||||||
- 决策:主站入口统一注册移动端输入法聚焦适配;输入法未打开时记录稳定布局高度,输入法打开期间 `.platform-viewport-shell` 不跟随 `visualViewport.height` 缩小,只通过 `--platform-keyboard-focus-offset` 上移画面聚焦当前输入框,并临时隐藏移动端底部 dock。
|
- 决策:主站入口统一注册移动端输入法聚焦适配;输入法未打开时记录稳定布局高度,输入法打开期间 `.platform-viewport-shell` 不跟随 `visualViewport.height` 缩小,只通过 `--platform-keyboard-focus-offset` 上移画面聚焦当前输入框,并临时隐藏移动端底部 dock。
|
||||||
- 影响范围:主站平台壳、移动端创作首页底部输入框、后续所有复用 `.platform-viewport-shell` 的输入表单;业务组件不重复注册键盘适配。
|
- 影响范围:主站平台壳、移动端创作首页底部输入框、后续所有复用 `.platform-viewport-shell` 的输入表单;业务组件不重复注册键盘适配。
|
||||||
- 验证方式:手机竖屏点击输入框,画布不压缩,输入框移动到输入法上方;输入法关闭后画布回位,底部 dock 恢复。
|
- 验证方式:手机竖屏点击输入框,画布不压缩,输入框移动到输入法上方;输入法关闭后画布回位,底部 dock 恢复。
|
||||||
- 关联文档:`docs/technical/【前端体验】移动端输入法不压缩画布聚焦方案-2026-05-14.md`、`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 抓大鹅物品素材批量重新生成复用 item-assets 替换模式
|
## 2026-05-14 抓大鹅物品素材批量重新生成复用 item-assets 替换模式
|
||||||
|
|
||||||
@@ -54,7 +70,7 @@
|
|||||||
- 决策:继续复用 `POST /api/creation/match3d/works/{profileId}/item-assets`,请求体通过 `mode = "replace"` 表达替换模式;前端面板预填当前素材名称,只提交仍能匹配到已有素材的名称。后端只替换匹配素材的 `imageSrc/imageObjectKey/imageViews/status/error`,保留原 `itemId`、列表顺序、模型兼容字段、UI 背景、历史背景音乐和点击音效字段;未匹配名称不计费、不新增、不持久化。
|
- 决策:继续复用 `POST /api/creation/match3d/works/{profileId}/item-assets`,请求体通过 `mode = "replace"` 表达替换模式;前端面板预填当前素材名称,只提交仍能匹配到已有素材的名称。后端只替换匹配素材的 `imageSrc/imageObjectKey/imageViews/status/error`,保留原 `itemId`、列表顺序、模型兼容字段、UI 背景、历史背景音乐和点击音效字段;未匹配名称不计费、不新增、不持久化。
|
||||||
- 影响范围:Match3D 结果页素材配置、前端/后端 shared contracts、`api-server` Match3D item-assets 编排、运行态物品类型映射和素材生成技术文档。
|
- 影响范围:Match3D 结果页素材配置、前端/后端 shared contracts、`api-server` Match3D item-assets 编排、运行态物品类型映射和素材生成技术文档。
|
||||||
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`cargo test -p api-server match3d_item_asset --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_regenerated_asset --manifest-path server-rs\Cargo.toml`、`npm run check:encoding`。
|
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`cargo test -p api-server match3d_item_asset --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_regenerated_asset --manifest-path server-rs\Cargo.toml`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 拼图与抓大鹅音频生成入口临时关闭
|
## 2026-05-14 拼图与抓大鹅音频生成入口临时关闭
|
||||||
|
|
||||||
@@ -62,7 +78,7 @@
|
|||||||
- 决策:拼图 `compile_puzzle_draft` 不再自动生成背景音乐,结果页素材配置只保留 `UI`;抓大鹅 `match3d_compile_draft` 和批量新增只生成 2D 图片、背景和容器 UI,不再调用 Suno/Vidu,结果页隐藏 `背景音乐` 子 Tab 与点击音效生成控件;通用 `/api/creation/audio/*` 当前整体返回 `410 Gone`。历史已写入的 `backgroundMusic` / `clickSound` 字段保留,运行态继续兼容播放旧音频。
|
- 决策:拼图 `compile_puzzle_draft` 不再自动生成背景音乐,结果页素材配置只保留 `UI`;抓大鹅 `match3d_compile_draft` 和批量新增只生成 2D 图片、背景和容器 UI,不再调用 Suno/Vidu,结果页隐藏 `背景音乐` 子 Tab 与点击音效生成控件;通用 `/api/creation/audio/*` 当前整体返回 `410 Gone`。历史已写入的 `backgroundMusic` / `clickSound` 字段保留,运行态继续兼容播放旧音频。
|
||||||
- 影响范围:`api-server` 拼图/抓大鹅草稿编排、通用创作音频路由、拼图/抓大鹅结果页、生成进度模型、相关技术文档。
|
- 影响范围:`api-server` 拼图/抓大鹅草稿编排、通用创作音频路由、拼图/抓大鹅结果页、生成进度模型、相关技术文档。
|
||||||
- 验证方式:执行拼图/抓大鹅结果页定向测试、生成进度单测、`cargo check -p api-server --manifest-path server-rs/Cargo.toml` 和 `npm run check:encoding`。
|
- 验证方式:执行拼图/抓大鹅结果页定向测试、生成进度单测、`cargo check -p api-server --manifest-path server-rs/Cargo.toml` 和 `npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 抓大鹅物品素材 sheet 改用 VectorEngine Gemini
|
## 2026-05-14 抓大鹅物品素材 sheet 改用 VectorEngine Gemini
|
||||||
|
|
||||||
@@ -70,7 +86,7 @@
|
|||||||
- 决策:抓大鹅物品素材 sheet 生图固定走 VectorEngine `POST {VECTOR_ENGINE_BASE_URL}/v1beta/models/gemini-3-pro-image-preview:generateContent?key={VECTOR_ENGINE_API_KEY}`,请求体使用 `contents[].parts[].text` 与 `generationConfig.responseModalities = ["TEXT", "IMAGE"]`、`imageConfig.aspectRatio = "1:1"`;响应从 `candidates[].content.parts[].inlineData.data` / `inline_data.data` 读取 base64 图片。封面、9:16 纯背景图、1:1 容器 UI 图、切图、OSS、扣费和运行态消费链路保持不变;音频以后续“拼图与抓大鹅音频生成入口临时关闭”决策为准。
|
- 决策:抓大鹅物品素材 sheet 生图固定走 VectorEngine `POST {VECTOR_ENGINE_BASE_URL}/v1beta/models/gemini-3-pro-image-preview:generateContent?key={VECTOR_ENGINE_API_KEY}`,请求体使用 `contents[].parts[].text` 与 `generationConfig.responseModalities = ["TEXT", "IMAGE"]`、`imageConfig.aspectRatio = "1:1"`;响应从 `candidates[].content.parts[].inlineData.data` / `inline_data.data` 读取 base64 图片。封面、9:16 纯背景图、1:1 容器 UI 图、切图、OSS、扣费和运行态消费链路保持不变;音频以后续“拼图与抓大鹅音频生成入口临时关闭”决策为准。
|
||||||
- 影响范围:`server-rs/crates/api-server/src/match3d.rs`、`server-rs/crates/api-server/src/config.rs`、`deploy/env/api-server.env.example`、抓大鹅素材生成技术文档。
|
- 影响范围:`server-rs/crates/api-server/src/match3d.rs`、`server-rs/crates/api-server/src/config.rs`、`deploy/env/api-server.env.example`、抓大鹅素材生成技术文档。
|
||||||
- 验证方式:执行 `cargo test -p api-server match3d_material_sheet --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_vector_engine_gemini --manifest-path server-rs\Cargo.toml`、`cargo check -p api-server --manifest-path server-rs\Cargo.toml`、`npm run check:encoding`。
|
- 验证方式:执行 `cargo test -p api-server match3d_material_sheet --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_vector_engine_gemini --manifest-path server-rs\Cargo.toml`、`cargo check -p api-server --manifest-path server-rs\Cargo.toml`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 草稿页作品卡对齐分类页列表
|
## 2026-05-14 草稿页作品卡对齐分类页列表
|
||||||
|
|
||||||
@@ -78,7 +94,7 @@
|
|||||||
- 决策:草稿页作品卡统一收口为与分类页一致的横向列表卡结构,左侧承载标题/状态/类型/摘要与必要数据,右侧显示带透明度的封面图;移动端保持单列列表,网页端使用两到三列卡片式网格,避免宽屏长条列表。不再常驻“继续创作”“查看详情”“查看进度”等右侧动作按钮。原有删除、分享、积分激励、公开统计、未读红点全部保留,其中删除与分享进入左滑操作层,常态不显示删除按钮,也不得透出删除底层。生成中的作品在整卡上加半透明蒙版、旋转等待符号和“生成中...”标识,但不移除任何原有信息。
|
- 决策:草稿页作品卡统一收口为与分类页一致的横向列表卡结构,左侧承载标题/状态/类型/摘要与必要数据,右侧显示带透明度的封面图;移动端保持单列列表,网页端使用两到三列卡片式网格,避免宽屏长条列表。不再常驻“继续创作”“查看详情”“查看进度”等右侧动作按钮。原有删除、分享、积分激励、公开统计、未读红点全部保留,其中删除与分享进入左滑操作层,常态不显示删除按钮,也不得透出删除底层。生成中的作品在整卡上加半透明蒙版、旋转等待符号和“生成中...”标识,但不移除任何原有信息。
|
||||||
- 影响范围:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkCard.tsx`、相关样式与测试、草稿页 UI 文档。
|
- 影响范围:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkCard.tsx`、相关样式与测试、草稿页 UI 文档。
|
||||||
- 验证方式:草稿页作品卡与分类页列表视觉口径保持一致;`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run typecheck`、`npm run check:encoding`。
|
- 验证方式:草稿页作品卡与分类页列表视觉口径保持一致;`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run typecheck`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/design/MOBILE_CREATION_WORK_LIST_TWO_COLUMN_LAYOUT_2026-04-29.md`、`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
2026-05-14 补充:草稿页作品卡不再用“草稿 / 已发布”文字标识状态,改为图标化 UI 状态点;作品封面直接铺到卡片右半区并从右向左渐隐;已发布作品右上角常驻分享图标;草稿长按弹出删除面板,已发布长按弹出分享和删除面板。
|
2026-05-14 补充:草稿页作品卡不再用“草稿 / 已发布”文字标识状态,改为图标化 UI 状态点;作品封面直接铺到卡片右半区并从右向左渐隐;已发布作品右上角常驻分享图标;草稿长按弹出删除面板,已发布长按弹出分享和删除面板。
|
||||||
|
|
||||||
@@ -88,7 +104,7 @@
|
|||||||
- 决策:运行期认证变更继续由 `module-auth` 生成一致内存快照,但 `api-server` 改为调用 `import_auth_store_snapshot_json` 直接覆盖导入 `user_account/auth_identity/refresh_session`;`auth_store_projection_meta/default` 只记录正式认证表最近一次导入时间;`upsert_auth_store_snapshot` 与 `import_auth_store_snapshot` 仅保留为旧库迁移和兜底入口。
|
- 决策:运行期认证变更继续由 `module-auth` 生成一致内存快照,但 `api-server` 改为调用 `import_auth_store_snapshot_json` 直接覆盖导入 `user_account/auth_identity/refresh_session`;`auth_store_projection_meta/default` 只记录正式认证表最近一次导入时间;`upsert_auth_store_snapshot` 与 `import_auth_store_snapshot` 仅保留为旧库迁移和兜底入口。
|
||||||
- 影响范围:`spacetime-module` auth procedures/tables、`spacetime-client` auth facade/bindings、`api-server` 认证同步和启动恢复、SpacetimeDB 表目录与认证 Stage 3 文档。
|
- 影响范围:`spacetime-module` auth procedures/tables、`spacetime-client` auth facade/bindings、`api-server` 认证同步和启动恢复、SpacetimeDB 表目录与认证 Stage 3 文档。
|
||||||
- 验证方式:执行 `npm run spacetime:generate -- --rust-only`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、认证相关定向测试和 `npm run check:encoding`。
|
- 验证方式:执行 `npm run spacetime:generate -- --rust-only`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、认证相关定向测试和 `npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/AUTH_SPACETIMEDB_FORMAL_TABLE_RECOVERY_STAGE3_2026-04-24.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-13 微信小程序支付以后端通知为唯一入账事实
|
## 2026-05-13 微信小程序支付以后端通知为唯一入账事实
|
||||||
|
|
||||||
@@ -96,7 +112,7 @@
|
|||||||
- 决策:`paymentChannel = "mock"` 继续创建即 paid 订单并立即入账;`paymentChannel = "wechat_mp"` 先在 `profile_recharge_order` 写入 `pending` 订单,再由 `api-server` 调微信支付 JSAPI 下单并返回小程序 `wx.requestPayment` 参数。小程序或 H5 的支付成功回调只触发刷新,不直接发放泥点或会员;最终入账只由 `/api/profile/recharge/wechat/notify` 验签、解密并确认 `trade_state = SUCCESS` 后完成。`provider_transaction_id` 保存微信支付平台交易号,用于对账、查单、退款和客服排障。
|
- 决策:`paymentChannel = "mock"` 继续创建即 paid 订单并立即入账;`paymentChannel = "wechat_mp"` 先在 `profile_recharge_order` 写入 `pending` 订单,再由 `api-server` 调微信支付 JSAPI 下单并返回小程序 `wx.requestPayment` 参数。小程序或 H5 的支付成功回调只触发刷新,不直接发放泥点或会员;最终入账只由 `/api/profile/recharge/wechat/notify` 验签、解密并确认 `trade_state = SUCCESS` 后完成。`provider_transaction_id` 保存微信支付平台交易号,用于对账、查单、退款和客服排障。
|
||||||
- 影响范围:`profile_recharge_order` 表、SpacetimeDB 充值 procedure、`api-server` 微信支付客户端、小程序 native 支付页、H5 充值弹窗与共享 contract。
|
- 影响范围:`profile_recharge_order` 表、SpacetimeDB 充值 procedure、`api-server` 微信支付客户端、小程序 native 支付页、H5 充值弹窗与共享 contract。
|
||||||
- 验证方式:执行 `npm run typecheck`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`cargo test -p module-runtime recharge --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server wechat_pay --manifest-path server-rs/Cargo.toml`,后端联调仍用 `npm run api-server` 和 `/healthz`。
|
- 验证方式:执行 `npm run typecheck`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`cargo test -p module-runtime recharge --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server wechat_pay --manifest-path server-rs/Cargo.toml`,后端联调仍用 `npm run api-server` 和 `/healthz`。
|
||||||
- 关联文档:`docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-13 修改密码后全设备强制下线
|
## 2026-05-13 修改密码后全设备强制下线
|
||||||
|
|
||||||
@@ -104,7 +120,7 @@
|
|||||||
- 决策:`POST /api/auth/password/change` 成功后必须在同一认证真相更新中撤销该用户全部 active `refresh_session`,继续递增 `token_version`,响应清除当前 refresh cookie;前端 `changePassword` 成功后清空本地 access token 并回到未登录态。用户需要使用新密码重新登录。
|
- 决策:`POST /api/auth/password/change` 成功后必须在同一认证真相更新中撤销该用户全部 active `refresh_session`,继续递增 `token_version`,响应清除当前 refresh cookie;前端 `changePassword` 成功后清空本地 access token 并回到未登录态。用户需要使用新密码重新登录。
|
||||||
- 影响范围:`module-auth` 修改密码用例、`api-server` password management route、`AuthGate`、`authService`、密码登录/重置技术文档。
|
- 影响范围:`module-auth` 修改密码用例、`api-server` password management route、`AuthGate`、`authService`、密码登录/重置技术文档。
|
||||||
- 验证方式:执行 `cargo test -p api-server --manifest-path server-rs/Cargo.toml password_change_allows_login_with_new_password_only -- --nocapture`、`npm run test -- AuthGate.test.tsx authService.test.ts`、`npm run check:encoding`、`git diff --check`。
|
- 验证方式:执行 `cargo test -p api-server --manifest-path server-rs/Cargo.toml password_change_allows_login_with_new_password_only -- --nocapture`、`npm run test -- AuthGate.test.tsx authService.test.ts`、`npm run check:encoding`、`git diff --check`。
|
||||||
- 关联文档:`docs/technical/PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md`、`docs/technical/AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-13 refresh_session 会话组后端聚合与远端踢下线
|
## 2026-05-13 refresh_session 会话组后端聚合与远端踢下线
|
||||||
|
|
||||||
@@ -112,7 +128,7 @@
|
|||||||
- 决策:`GET /api/auth/sessions` 由后端按“同设备 + 同 IP”聚合 active refresh sessions,响应保留代表 `sessionId` 并新增 `sessionIds/sessionCount`;组内包含当前 refresh hash 或 Bearer `sid` 时整组视为当前设备组,前端不展示踢下线。新增 `POST /api/auth/sessions/{session_id}/revoke`,只允许撤销当前用户自己的非当前会话,不递增 `token_version`,但认证中间件会校验 access token `sid` 对应 active refresh session,使被踢设备立即失效。`/api/auth/logout` 在 refresh cookie 缺失时回退用 Bearer `sid` 撤销当前 session,并继续递增 `token_version`。
|
- 决策:`GET /api/auth/sessions` 由后端按“同设备 + 同 IP”聚合 active refresh sessions,响应保留代表 `sessionId` 并新增 `sessionIds/sessionCount`;组内包含当前 refresh hash 或 Bearer `sid` 时整组视为当前设备组,前端不展示踢下线。新增 `POST /api/auth/sessions/{session_id}/revoke`,只允许撤销当前用户自己的非当前会话,不递增 `token_version`,但认证中间件会校验 access token `sid` 对应 active refresh session,使被踢设备立即失效。`/api/auth/logout` 在 refresh cookie 缺失时回退用 Bearer `sid` 撤销当前 session,并继续递增 `token_version`。
|
||||||
- 影响范围:`module-auth` refresh session service、`api-server` auth middleware/logout/sessions route、`shared-contracts`/TS auth contract、`AuthGate`、`AccountModal`、认证会话技术文档和路由/埋点索引。
|
- 影响范围:`module-auth` refresh session service、`api-server` auth middleware/logout/sessions route、`shared-contracts`/TS auth contract、`AuthGate`、`AccountModal`、认证会话技术文档和路由/埋点索引。
|
||||||
- 验证方式:执行 `cargo test -p module-auth --manifest-path server-rs/Cargo.toml refresh_session`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml auth_sessions -- --nocapture`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml revoke_auth_session -- --nocapture`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml logout_succeeds_without_refresh_cookie_when_bearer_token_is_valid -- --nocapture`、`npm run test -- AuthGate.test.tsx AccountModal.test.tsx authService.test.ts`、`npm run check:encoding`、`git diff --check`,并用 `npm run api-server` 检查 `/healthz`。
|
- 验证方式:执行 `cargo test -p module-auth --manifest-path server-rs/Cargo.toml refresh_session`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml auth_sessions -- --nocapture`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml revoke_auth_session -- --nocapture`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml logout_succeeds_without_refresh_cookie_when_bearer_token_is_valid -- --nocapture`、`npm run test -- AuthGate.test.tsx AccountModal.test.tsx authService.test.ts`、`npm run check:encoding`、`git diff --check`,并用 `npm run api-server` 检查 `/healthz`。
|
||||||
- 关联文档:`docs/technical/AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md`、`docs/technical/SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md`、`docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-12 抓大鹅入口素材风格改为 2D 常见素材风格
|
## 2026-05-12 抓大鹅入口素材风格改为 2D 常见素材风格
|
||||||
|
|
||||||
@@ -120,7 +136,7 @@
|
|||||||
- 决策:抓大鹅创作入口 `2D素材风格` 固定为 `扁平图标 / 赛璐璐卡通 / 像素复古 / 手绘水彩 / 贴纸描边 / 厚涂图标 / 自定义`;默认风格为 `flat-icon`。入口参考图统一由 `npm run assets:match3d-style-references -- --live` 调用 VectorEngine `gpt-image-2-all` 生成,输出到 `public/match3d-style-references/`。旧 3D 风格参考图不再保留为入口资产。
|
- 决策:抓大鹅创作入口 `2D素材风格` 固定为 `扁平图标 / 赛璐璐卡通 / 像素复古 / 手绘水彩 / 贴纸描边 / 厚涂图标 / 自定义`;默认风格为 `flat-icon`。入口参考图统一由 `npm run assets:match3d-style-references -- --live` 调用 VectorEngine `gpt-image-2-all` 生成,输出到 `public/match3d-style-references/`。旧 3D 风格参考图不再保留为入口资产。
|
||||||
- 影响范围:`Match3DAgentWorkspace`、抓大鹅入口交互测试、Match3D PRD、素材生成流水线技术文档、F1 入口文档和 `public/match3d-style-references/` 静态资产。
|
- 影响范围:`Match3DAgentWorkspace`、抓大鹅入口交互测试、Match3D PRD、素材生成流水线技术文档、F1 入口文档和 `public/match3d-style-references/` 静态资产。
|
||||||
- 验证方式:执行 `npm run test -- src\components\match3d-creation\Match3DAgentWorkspace.interaction.test.tsx`、`cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,并人工抽查 `.tmp/match3d-style-preview.png`。
|
- 验证方式:执行 `npm run test -- src\components\match3d-creation\Match3DAgentWorkspace.interaction.test.tsx`、`cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,并人工抽查 `.tmp/match3d-style-preview.png`。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`、`docs/technical/MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-12 拼图与抓大鹅草稿背景音乐按纯音乐自动生成
|
## 2026-05-12 拼图与抓大鹅草稿背景音乐按纯音乐自动生成
|
||||||
|
|
||||||
@@ -128,7 +144,7 @@
|
|||||||
- 决策:复用通用 VectorEngine Suno 创作音频链路,不新增 SpacetimeDB 表;拼图音乐保存到首关 `PuzzleDraftLevel.backgroundMusic`,运行态通过 `PuzzleRuntimeLevelSnapshot.backgroundMusic` 下发;抓大鹅音乐保存到首个 `generatedItemAssets[].backgroundMusic`。两者草稿生成都使用 `title` 驱动、`prompt = ""`、`make_instrumental = true`;自动草稿阶段必须拿到可播放 `audioSrc` 才能返回成功,失败时停留在生成页并允许重试同一 session/profile。结果页内的手动重新生成继续作为已有草稿的补救入口。
|
- 决策:复用通用 VectorEngine Suno 创作音频链路,不新增 SpacetimeDB 表;拼图音乐保存到首关 `PuzzleDraftLevel.backgroundMusic`,运行态通过 `PuzzleRuntimeLevelSnapshot.backgroundMusic` 下发;抓大鹅音乐保存到首个 `generatedItemAssets[].backgroundMusic`。两者草稿生成都使用 `title` 驱动、`prompt = ""`、`make_instrumental = true`;自动草稿阶段必须拿到可播放 `audioSrc` 才能返回成功,失败时停留在生成页并允许重试同一 session/profile。结果页内的手动重新生成继续作为已有草稿的补救入口。
|
||||||
- 影响范围:`api-server` 音频生成、拼图草稿编译、抓大鹅草稿编译、Puzzle/Match3D 结果页和运行态音频播放。
|
- 影响范围:`api-server` 音频生成、拼图草稿编译、抓大鹅草稿编译、Puzzle/Match3D 结果页和运行态音频播放。
|
||||||
- 验证方式:检查草稿 response / work detail 中的 `backgroundMusic.audioSrc`,运行态开局后隐藏 audio 循环播放;执行音频相关后端 check、前端 typecheck 和编码检查。
|
- 验证方式:检查草稿 response / work detail 中的 `backgroundMusic.audioSrc`,运行态开局后隐藏 audio 循环播放;执行音频相关后端 check、前端 typecheck 和编码检查。
|
||||||
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-12 拼图 UI 背景图复用 levels_json 持久化
|
## 2026-05-12 拼图 UI 背景图复用 levels_json 持久化
|
||||||
|
|
||||||
@@ -136,7 +152,7 @@
|
|||||||
- 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt`、`uiBackgroundImageSrc`、`uiBackgroundImageObjectKey`;`compile_puzzle_draft` 草稿编译阶段在首图完成后自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc` 或 `uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab,可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2-all` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。
|
- 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt`、`uiBackgroundImageSrc`、`uiBackgroundImageObjectKey`;`compile_puzzle_draft` 草稿编译阶段在首图完成后自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc` 或 `uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab,可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2-all` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。
|
||||||
- 影响范围:拼图结果页、拼图运行态背景渲染、拼图 agent action、`module-puzzle` / `spacetime-module` / `spacetime-client` 的拼图关卡 JSON 映射、拼图流程技术文档。
|
- 影响范围:拼图结果页、拼图运行态背景渲染、拼图 agent action、`module-puzzle` / `spacetime-module` / `spacetime-client` 的拼图关卡 JSON 映射、拼图流程技术文档。
|
||||||
- 验证方式:执行 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`、`cargo test -p api-server puzzle_ui_background --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`。
|
- 验证方式:执行 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`、`cargo test -p api-server puzzle_ui_background --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-12 抓大鹅结果页素材编辑统一走作品级资产面板
|
## 2026-05-12 抓大鹅结果页素材编辑统一走作品级资产面板
|
||||||
|
|
||||||
@@ -144,7 +160,7 @@
|
|||||||
- 决策:结果页 `作品信息` 的封面图点击打开独立面板,封面图面板对齐拼图入口上传卡。已有上传主图时,请求体传 `uploadedImageSrc`,AI 重绘走 VectorEngine `/v1/images/edits`;关闭 AI 重绘时只写回上传图,不调用生图。没有上传主图时,请求体传 `referenceImageSrcs`,可混合本地上传、物品素材和 UI 素材,多参考图作为 `gpt-image-2-all` generations 的 `image` 数组传入。生成结果统一调用 `POST /api/creation/match3d/works/{profileId}/cover-image` 并转存到 `generated-match3d-assets`。`素材配置 > 物品` 列表项点击打开独立预览面板,不再提供单项重新生成按钮;单项删除和批量新增都写回同一份 `generated_item_assets_json`。批量新增调用 `POST /api/creation/match3d/works/{profileId}/item-assets`,复用草稿生成的 2D 素材图、5x5 切图、OSS 上传和可选点击音效链路,仅作用于新增物品,不新增 SpacetimeDB 表。
|
- 决策:结果页 `作品信息` 的封面图点击打开独立面板,封面图面板对齐拼图入口上传卡。已有上传主图时,请求体传 `uploadedImageSrc`,AI 重绘走 VectorEngine `/v1/images/edits`;关闭 AI 重绘时只写回上传图,不调用生图。没有上传主图时,请求体传 `referenceImageSrcs`,可混合本地上传、物品素材和 UI 素材,多参考图作为 `gpt-image-2-all` generations 的 `image` 数组传入。生成结果统一调用 `POST /api/creation/match3d/works/{profileId}/cover-image` 并转存到 `generated-match3d-assets`。`素材配置 > 物品` 列表项点击打开独立预览面板,不再提供单项重新生成按钮;单项删除和批量新增都写回同一份 `generated_item_assets_json`。批量新增调用 `POST /api/creation/match3d/works/{profileId}/item-assets`,复用草稿生成的 2D 素材图、5x5 切图、OSS 上传和可选点击音效链路,仅作用于新增物品,不新增 SpacetimeDB 表。
|
||||||
- 影响范围:Match3D 结果页、Match3D works shared contracts、`api-server` Match3D 作品路由、生成资产历史类型和草稿恢复路径。
|
- 影响范围:Match3D 结果页、Match3D works shared contracts、`api-server` Match3D 作品路由、生成资产历史类型和草稿恢复路径。
|
||||||
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run typecheck`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
|
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run typecheck`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-12 平台法律文档入口与登录协议确认
|
## 2026-05-12 平台法律文档入口与登录协议确认
|
||||||
|
|
||||||
@@ -152,14 +168,14 @@
|
|||||||
- 决策:法律文档内容读取 `media/files/*.md`,统一通过 `LegalDocumentModal` 独立弹窗展示;“我的”页常用功能区固定 3 列,设置入口下方展示法律信息和 `京ICP备2026025677号` 外链。登录弹窗用 `genarrative.auth.legal-consent.v1` 记录本机确认,首次未勾选时短信 / 密码登录按钮禁用,法律链接不自动勾选。
|
- 决策:法律文档内容读取 `media/files/*.md`,统一通过 `LegalDocumentModal` 独立弹窗展示;“我的”页常用功能区固定 3 列,设置入口下方展示法律信息和 `京ICP备2026025677号` 外链。登录弹窗用 `genarrative.auth.legal-consent.v1` 记录本机确认,首次未勾选时短信 / 密码登录按钮禁用,法律链接不自动勾选。
|
||||||
- 影响范围:平台个人页、登录弹窗、法律 Markdown 渲染和前端认证交互测试。
|
- 影响范围:平台个人页、登录弹窗、法律 Markdown 渲染和前端认证交互测试。
|
||||||
- 验证方式:执行 `npm run test -- src/components/auth/AuthGate.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、触碰文件 ESLint、`npm run check:encoding`。
|
- 验证方式:执行 `npm run test -- src/components/auth/AuthGate.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、触碰文件 ESLint、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/prd/PROFILE_LEGAL_INFO_AND_AUTH_AGREEMENT_PRD_2026-05-12.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
## 2026-05-12 微信小程序待绑定手机号优先走原生手机号授权
|
## 2026-05-12 微信小程序待绑定手机号优先走原生手机号授权
|
||||||
|
|
||||||
- 背景:微信小程序 `web-view` 壳登录后若返回 `pending_bind_phone`,H5 仍会展示手输手机号和短信验证码绑定页,体验上多了一步。
|
- 背景:微信小程序 `web-view` 壳登录后若返回 `pending_bind_phone`,H5 仍会展示手输手机号和短信验证码绑定页,体验上多了一步。
|
||||||
- 决策:小程序壳在 `pending_bind_phone` 时暂不打开 H5,先展示原生 `button open-type="getPhoneNumber"`;用户同意后把 `bindgetphonenumber` 返回的 `code` 作为 `wechatPhoneCode` 调用 `/api/auth/wechat/bind-phone`。后端通过微信 `stable_token` 与 `getuserphonenumber` 换取平台验证后的手机号,再复用现有微信待绑定账号合并逻辑并重新签发 active 系统 token。H5 旧短信验证码绑定流程继续作为非小程序环境兜底。
|
- 决策:小程序壳在 `pending_bind_phone` 时暂不打开 H5,先展示原生 `button open-type="getPhoneNumber"`;用户同意后把 `bindgetphonenumber` 返回的 `code` 作为 `wechatPhoneCode` 调用 `/api/auth/wechat/bind-phone`。后端通过微信 `stable_token` 与 `getuserphonenumber` 换取平台验证后的手机号,再复用现有微信待绑定账号合并逻辑并重新签发 active 系统 token。H5 旧短信验证码绑定流程继续作为非小程序环境兜底。
|
||||||
- 影响范围:`miniprogram/pages/web-view/index.*`、`server-rs/crates/platform-auth`、`server-rs/crates/api-server/src/wechat_auth.rs`、认证共享契约、微信小程序 web-view 壳技术文档。
|
- 影响范围:`miniprogram/pages/web-view/index.*`、`server-rs/crates/platform-auth`、`server-rs/crates/api-server/src/wechat_auth.rs`、认证共享契约、微信小程序 web-view 壳技术文档。
|
||||||
- 验证方式:执行 `npm run check:encoding`、`node scripts/check-wechat-miniprogram-auth-smoke.mjs`、`cargo test -p shared-contracts wechat_bind_phone_request_accepts_mini_program_phone_code --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server wechat_miniprogram_bind_phone_code_activates_pending_user --manifest-path server-rs/Cargo.toml -- --nocapture`。
|
- 验证方式:执行 `npm run check:encoding`、`node scripts/check-wechat-miniprogram-auth-smoke.mjs`、`cargo test -p shared-contracts wechat_bind_phone_request_accepts_mini_program_phone_code --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server wechat_miniprogram_bind_phone_code_activates_pending_user --manifest-path server-rs/Cargo.toml -- --nocapture`。
|
||||||
- 关联文档:`docs/technical/WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-13 宝贝爱画先作为寓教于乐独立本地 Demo 落地
|
## 2026-05-13 宝贝爱画先作为寓教于乐独立本地 Demo 落地
|
||||||
|
|
||||||
@@ -167,7 +183,7 @@
|
|||||||
- 决策:`baby-love-drawing / 宝贝爱画` 先作为独立运行态接入,入口由发现页寓教于乐默认卡片打开,并支持 `/runtime/baby-love-drawing` 直达;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时前端不展示频道/卡片且直达路由回落主应用。绘画魔法统一走 `POST /api/creation/edutainment/baby-love-drawing/magic` 后端安全代理,使用 VectorEngine `gpt-image-2-all` 与原始画布 Data URL 参考图生成绘本风图片;保存只写 localStorage,正式持久化后续再设计。
|
- 决策:`baby-love-drawing / 宝贝爱画` 先作为独立运行态接入,入口由发现页寓教于乐默认卡片打开,并支持 `/runtime/baby-love-drawing` 直达;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时前端不展示频道/卡片且直达路由回落主应用。绘画魔法统一走 `POST /api/creation/edutainment/baby-love-drawing/magic` 后端安全代理,使用 VectorEngine `gpt-image-2-all` 与原始画布 Data URL 参考图生成绘本风图片;保存只写 localStorage,正式持久化后续再设计。
|
||||||
- 影响范围:`packages/shared/src/contracts/edutainmentBabyDrawing.ts`、`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx`、`src/services/edutainment-baby-drawing/`、`src/routing/appRoutes.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`server-rs/crates/api-server/src/edutainment_baby_drawing.rs`、`src/index.css`、宝贝爱画 PRD 与技术方案。
|
- 影响范围:`packages/shared/src/contracts/edutainmentBabyDrawing.ts`、`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx`、`src/services/edutainment-baby-drawing/`、`src/routing/appRoutes.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`server-rs/crates/api-server/src/edutainment_baby_drawing.rs`、`src/index.css`、宝贝爱画 PRD 与技术方案。
|
||||||
- 验证方式:执行宝贝爱画 model/runtime/service/route 定向测试、`npm run typecheck`、定向 ESLint、`cargo test -p api-server edutainment_baby_drawing --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server resolves_runtime_paths_to_creation_type_ids --manifest-path server-rs/Cargo.toml` 和编码检查;真实魔法生成需配置 `VECTOR_ENGINE_BASE_URL` 与 `VECTOR_ENGINE_API_KEY`。
|
- 验证方式:执行宝贝爱画 model/runtime/service/route 定向测试、`npm run typecheck`、定向 ESLint、`cargo test -p api-server edutainment_baby_drawing --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server resolves_runtime_paths_to_creation_type_ids --manifest-path server-rs/Cargo.toml` 和编码检查;真实魔法生成需配置 `VECTOR_ENGINE_BASE_URL` 与 `VECTOR_ENGINE_API_KEY`。
|
||||||
- 关联文档:`docs/prd/BABY_LOVE_DRAWING_EDUTAINMENT_LEVEL_PRD_2026-05-13.md`、`docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-12 宝贝识物创作同时生成玩法视觉主题包
|
## 2026-05-12 宝贝识物创作同时生成玩法视觉主题包
|
||||||
|
|
||||||
@@ -175,7 +191,7 @@
|
|||||||
- 决策:`POST /api/creation/edutainment/baby-object-match/assets` 同一次 image-2 / VectorEngine 调用链返回两个物品图和 `visualPackage`。视觉包包含 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 五类资源;总风格保持寓教于乐明亮卡通绘本插画风,主题按两个物品关键词匹配,水果偏果园自然,动漫角色 / 玩具偏动漫玩具。物品图和礼物盒 / 篮子 / UI / 烟雾特效资源走透明 PNG 后处理,背景为清爽不遮挡玩法区的环境图;运行态中礼物盒按约 2 倍视觉尺寸展示、篮子按约 1.5 倍展示,礼物盒打开时使用 `smoke-puff` 弹出中央物品并移除礼盒。前端草稿保存该包,运行态消费该包;旧草稿以 `visualPackage = null` 继续使用 CSS 兜底。
|
- 决策:`POST /api/creation/edutainment/baby-object-match/assets` 同一次 image-2 / VectorEngine 调用链返回两个物品图和 `visualPackage`。视觉包包含 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 五类资源;总风格保持寓教于乐明亮卡通绘本插画风,主题按两个物品关键词匹配,水果偏果园自然,动漫角色 / 玩具偏动漫玩具。物品图和礼物盒 / 篮子 / UI / 烟雾特效资源走透明 PNG 后处理,背景为清爽不遮挡玩法区的环境图;运行态中礼物盒按约 2 倍视觉尺寸展示、篮子按约 1.5 倍展示,礼物盒打开时使用 `smoke-puff` 弹出中央物品并移除礼盒。前端草稿保存该包,运行态消费该包;旧草稿以 `visualPackage = null` 继续使用 CSS 兜底。
|
||||||
- 影响范围:`packages/shared/src/contracts/edutainmentBabyObject.ts`、`server-rs/crates/api-server/src/edutainment_baby_object.rs`、`src/services/edutainment-baby-object/babyObjectMatchClient.ts`、`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx`、`src/index.css`、宝贝识物 PRD 与技术方案。
|
- 影响范围:`packages/shared/src/contracts/edutainmentBabyObject.ts`、`server-rs/crates/api-server/src/edutainment_baby_object.rs`、`src/services/edutainment-baby-object/babyObjectMatchClient.ts`、`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx`、`src/index.css`、宝贝识物 PRD 与技术方案。
|
||||||
- 验证方式:执行宝贝识物 service / runtime 定向测试、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml`、相关 ESLint 与编码检查;真实生图需配置 `VECTOR_ENGINE_BASE_URL` 与 `VECTOR_ENGINE_API_KEY`。
|
- 验证方式:执行宝贝识物 service / runtime 定向测试、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml`、相关 ESLint 与编码检查;真实生图需配置 `VECTOR_ENGINE_BASE_URL` 与 `VECTOR_ENGINE_API_KEY`。
|
||||||
- 关联文档:`docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-11 拼图与抓大鹅结果页音频资产复用通用创作音频链路
|
## 2026-05-11 拼图与抓大鹅结果页音频资产复用通用创作音频链路
|
||||||
|
|
||||||
@@ -184,7 +200,7 @@
|
|||||||
- 2026-05-12 补充:抓大鹅入口页新增 `generateClickSound` 开关,默认关闭;开启时 `match3d_compile_draft` 在生成首批 2D 物品素材后并行生成各物品点击音效,并继续复用通用创作音频路由的 OSS、资产绑定和扣费口径。
|
- 2026-05-12 补充:抓大鹅入口页新增 `generateClickSound` 开关,默认关闭;开启时 `match3d_compile_draft` 在生成首批 2D 物品素材后并行生成各物品点击音效,并继续复用通用创作音频路由的 OSS、资产绑定和扣费口径。
|
||||||
- 影响范围:拼图结果页、抓大鹅结果页、抓大鹅运行态音频播放、通用创作音频 shared contracts、`api-server` 音频路由和资产绑定。
|
- 影响范围:拼图结果页、抓大鹅结果页、抓大鹅运行态音频播放、通用创作音频 shared contracts、`api-server` 音频路由和资产绑定。
|
||||||
- 验证方式:执行拼图/抓大鹅结果页定向测试、`npm run typecheck`、`cargo test -p api-server vector_engine_audio_generation`、`cargo test -p shared-contracts creation_audio`、`cargo check -p api-server`,真实生成需配置 VectorEngine 与 OSS 私密环境。
|
- 验证方式:执行拼图/抓大鹅结果页定向测试、`npm run typecheck`、`cargo test -p api-server vector_engine_audio_generation`、`cargo test -p shared-contracts creation_audio`、`cargo check -p api-server`,真实生成需配置 VectorEngine 与 OSS 私密环境。
|
||||||
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-11 寓教于乐公开作品使用独立 `edutainment` 来源接入
|
## 2026-05-11 寓教于乐公开作品使用独立 `edutainment` 来源接入
|
||||||
|
|
||||||
@@ -192,7 +208,7 @@
|
|||||||
- 决策:寓教于乐公开作品在前端公共作品模型中使用 `sourceType = edutainment`,当前只承接 `templateId = baby-object-match`、`templateName = 宝贝识物`;进入“发现 / 寓教于乐”频道仍必须携带精确等于 `寓教于乐` 的公开标签,不因模板名或近似标签自动归类。公开详情、推荐运行态、改造、编辑、点赞和分享链路都必须显式识别 `edutainment`,不得回落到 RPG 默认处理。
|
- 决策:寓教于乐公开作品在前端公共作品模型中使用 `sourceType = edutainment`,当前只承接 `templateId = baby-object-match`、`templateName = 宝贝识物`;进入“发现 / 寓教于乐”频道仍必须携带精确等于 `寓教于乐` 的公开标签,不因模板名或近似标签自动归类。公开详情、推荐运行态、改造、编辑、点赞和分享链路都必须显式识别 `edutainment`,不得回落到 RPG 默认处理。
|
||||||
- 影响范围:公开作品卡、发现页频道、作品号搜索、公开详情深链、分享、作品架聚合、后续儿童动作 Demo 模板的发布结果展示。
|
- 影响范围:公开作品卡、发现页频道、作品号搜索、公开详情深链、分享、作品架聚合、后续儿童动作 Demo 模板的发布结果展示。
|
||||||
- 验证方式:执行第4线程定向单测、前端类型检查、ESLint 与编码检查;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时确认精确 `寓教于乐` 作品不可通过任何公开入口访问。
|
- 验证方式:执行第4线程定向单测、前端类型检查、ESLint 与编码检查;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时确认精确 `寓教于乐` 作品不可通过任何公开入口访问。
|
||||||
- 关联文档:`docs/design/CHILD_MOTION_EDUTAINMENT_DISCOVER_ENTRY_2026-05-09.md`、`docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-10 儿童动作 Demo 视觉资产统一为绘本草地舞台
|
## 2026-05-10 儿童动作 Demo 视觉资产统一为绘本草地舞台
|
||||||
|
|
||||||
@@ -200,7 +216,7 @@
|
|||||||
- 决策:热身舞台及后续儿童动作 Demo 场景、物品、UI 资源统一采用明亮卡通绘本草地视觉语言。真实资源默认输出到 `public/child-motion-demo/`。背景沿用 `picture-book-grass-stage.png`;地面、指示环、角色轮廓和 UI 已拆分为 v2 用途专属资源:`picture-book-foreground-grass-v2.png`、`picture-book-ground-ring-v2.png`、`picture-book-character-outline-v2.png`、`picture-book-hud-strip-v2.png`、`picture-book-calibration-strip-v2.png`、`picture-book-start-panel-v2.png` 和 `picture-book-ui-button-v2.png`。生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 调用 VectorEngine `gpt-image-2-all`;透明资源使用品红底生成后本地去背,中间源图仅保存在 `tmp/child-motion-demo-assets/`。在缺少 `VECTOR_ENGINE_BASE_URL` 或 `VECTOR_ENGINE_API_KEY` 时,只允许 dry-run 和 CSS 兜底,不伪造 live 生图结果。
|
- 决策:热身舞台及后续儿童动作 Demo 场景、物品、UI 资源统一采用明亮卡通绘本草地视觉语言。真实资源默认输出到 `public/child-motion-demo/`。背景沿用 `picture-book-grass-stage.png`;地面、指示环、角色轮廓和 UI 已拆分为 v2 用途专属资源:`picture-book-foreground-grass-v2.png`、`picture-book-ground-ring-v2.png`、`picture-book-character-outline-v2.png`、`picture-book-hud-strip-v2.png`、`picture-book-calibration-strip-v2.png`、`picture-book-start-panel-v2.png` 和 `picture-book-ui-button-v2.png`。生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 调用 VectorEngine `gpt-image-2-all`;透明资源使用品红底生成后本地去背,中间源图仅保存在 `tmp/child-motion-demo-assets/`。在缺少 `VECTOR_ENGINE_BASE_URL` 或 `VECTOR_ENGINE_API_KEY` 时,只允许 dry-run 和 CSS 兜底,不伪造 live 生图结果。
|
||||||
- 影响范围:`src/index.css`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 的舞台视觉层、儿童动作 Demo 技术文档、后续 image-2 资产生成流程。
|
- 影响范围:`src/index.css`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 的舞台视觉层、儿童动作 Demo 技术文档、后续 image-2 资产生成流程。
|
||||||
- 验证方式:检查 `/child-motion-demo` 舞台是否在未生成资产时仍有可用草地绘本兜底;补齐 VectorEngine 私密配置后运行 `npm run assets:child-motion-demo -- --live` 或 `--live --only <asset-id>` 应能写出对应 PNG,并确认页面静态资源返回 `image/png`。若只调整透明去背、裁切或品红边缘,可运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用源图后处理。页面接入时必须按资源原始比例等比使用,不得把方形软纸面板拉伸成 HUD、状态条或底部草坪。
|
- 验证方式:检查 `/child-motion-demo` 舞台是否在未生成资产时仍有可用草地绘本兜底;补齐 VectorEngine 私密配置后运行 `npm run assets:child-motion-demo -- --live` 或 `--live --only <asset-id>` 应能写出对应 PNG,并确认页面静态资源返回 `image/png`。若只调整透明去背、裁切或品红边缘,可运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用源图后处理。页面接入时必须按资源原始比例等比使用,不得把方形软纸面板拉伸成 HUD、状态条或底部草坪。
|
||||||
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`、`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-10 方洞挑战从创作页入口和作品架隐藏
|
## 2026-05-10 方洞挑战从创作页入口和作品架隐藏
|
||||||
|
|
||||||
@@ -208,7 +224,7 @@
|
|||||||
- 决策:SpacetimeDB `creation_entry_type_config` 中 `square-hole.visible=false` 作为创作页统一开关;创作 Tab 模板入口、旧选择弹层、创作 Hub 卡带和创作页作品架都基于该开关隐藏方洞挑战。既有方洞详情、作品号、广场和运行态链路暂不删除,api-server 路由熔断只按 `open=false` 禁用玩法 API。
|
- 决策:SpacetimeDB `creation_entry_type_config` 中 `square-hole.visible=false` 作为创作页统一开关;创作 Tab 模板入口、旧选择弹层、创作 Hub 卡带和创作页作品架都基于该开关隐藏方洞挑战。既有方洞详情、作品号、广场和运行态链路暂不删除,api-server 路由熔断只按 `open=false` 禁用玩法 API。
|
||||||
- 影响范围:SpacetimeDB 入口配置默认种子、`platformEntryCreationTypes`、`CustomWorldCreationHub`、`PlatformEntryFlowShellImpl` 以及创作入口相关文档和回归测试。
|
- 影响范围:SpacetimeDB 入口配置默认种子、`platformEntryCreationTypes`、`CustomWorldCreationHub`、`PlatformEntryFlowShellImpl` 以及创作入口相关文档和回归测试。
|
||||||
- 验证方式:执行入口配置、创作 Hub 和平台入口交互定向测试,确认看不到“方洞挑战” Tab、按钮和作品架条目。
|
- 验证方式:执行入口配置、创作 Hub 和平台入口交互定向测试,确认看不到“方洞挑战” Tab、按钮和作品架条目。
|
||||||
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`、`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 视觉小说从创作页入口隐藏
|
## 2026-05-14 视觉小说从创作页入口隐藏
|
||||||
|
|
||||||
@@ -216,7 +232,7 @@
|
|||||||
- 决策:SpacetimeDB `creation_entry_type_config` 默认种子中 `visual-novel.visible=false` 且 `open=false`;旧默认可见配置会被迁移为隐藏和关闭。前端继续只消费 `GET /api/creation-entry/config`,不得用硬编码恢复视觉小说模板入口。
|
- 决策:SpacetimeDB `creation_entry_type_config` 默认种子中 `visual-novel.visible=false` 且 `open=false`;旧默认可见配置会被迁移为隐藏和关闭。前端继续只消费 `GET /api/creation-entry/config`,不得用硬编码恢复视觉小说模板入口。
|
||||||
- 影响范围:SpacetimeDB 入口配置默认种子、api-server 测试配置、创作页模板 Tab、创作 Hub 测试和创作入口文档。
|
- 影响范围:SpacetimeDB 入口配置默认种子、api-server 测试配置、创作页模板 Tab、创作 Hub 测试和创作入口文档。
|
||||||
- 验证方式:执行入口配置、创作 Hub、平台入口交互和 api-server 路由熔断定向测试,确认“视觉小说”不出现在创作页且 `/api/creation/visual-novel/*` 默认被熔断。
|
- 验证方式:执行入口配置、创作 Hub、平台入口交互和 api-server 路由熔断定向测试,确认“视觉小说”不出现在创作页且 `/api/creation/visual-novel/*` 默认被熔断。
|
||||||
- 关联文档:`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`、`docs/technical/ADMIN_CREATION_ENTRY_SWITCH_CONFIG_2026-05-11.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-10 运行态输入设备抽象层全项目通用化
|
## 2026-05-10 运行态输入设备抽象层全项目通用化
|
||||||
|
|
||||||
@@ -224,7 +240,7 @@
|
|||||||
- 决策:前端运行态输入统一通过 `src/services/input-devices/` 承接,设备适配层只输出 `press / move / release / tap / drop` 等通用语义和通用坐标;玩法组件自己解释目标对象、落点和业务动作,输入层不得写拼图等玩法专用规则。
|
- 决策:前端运行态输入统一通过 `src/services/input-devices/` 承接,设备适配层只输出 `press / move / release / tap / drop` 等通用语义和通用坐标;玩法组件自己解释目标对象、落点和业务动作,输入层不得写拼图等玩法专用规则。
|
||||||
- 影响范围:拼图运行态鼠标/触控/mocap 输入、后续运行态设备接入、运行态输入技术文档与相关前端回归测试。
|
- 影响范围:拼图运行态鼠标/触控/mocap 输入、后续运行态设备接入、运行态输入技术文档与相关前端回归测试。
|
||||||
- 验证方式:执行 `npm run test -- src\services\input-devices\runtimeDragInputController.test.ts`、`npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。
|
- 验证方式:执行 `npm run test -- src\services\input-devices\runtimeDragInputController.test.ts`、`npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。
|
||||||
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`、`docs/technical/PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-11 前端调试模式统一判断
|
## 2026-05-11 前端调试模式统一判断
|
||||||
|
|
||||||
@@ -232,7 +248,7 @@
|
|||||||
- 决策:前端新增 `src/config/debugMode.ts` 作为全局调试模式判断,默认跟随 Vite 开发态,允许 `VITE_DEBUG_MODE=true/false` 显式覆盖。2026-05-14 起,拼图运行态已临时移除 mocap 调用、体感光标和 mocap 调试面板;调试模式仍供其它局部诊断 UI 使用。
|
- 决策:前端新增 `src/config/debugMode.ts` 作为全局调试模式判断,默认跟随 Vite 开发态,允许 `VITE_DEBUG_MODE=true/false` 显式覆盖。2026-05-14 起,拼图运行态已临时移除 mocap 调用、体感光标和 mocap 调试面板;调试模式仍供其它局部诊断 UI 使用。
|
||||||
- 影响范围:前端局部调试 UI、拼图运行态 mocap 诊断面板、`.env.example` 和运行态输入技术文档。
|
- 影响范围:前端局部调试 UI、拼图运行态 mocap 诊断面板、`.env.example` 和运行态输入技术文档。
|
||||||
- 验证方式:执行 `npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。
|
- 验证方式:执行 `npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。
|
||||||
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-10 儿童动作热身关直接消费 mocap 数据源
|
## 2026-05-10 儿童动作热身关直接消费 mocap 数据源
|
||||||
|
|
||||||
@@ -240,7 +256,7 @@
|
|||||||
- 决策:热身关全流程直接接入 `useMocapInput`,通过本地 mocap WebSocket `/stream` 消费 `general.body.center_norm` 身体中心、`actions/action/gesture/gestures/event/name/type` 动作名,以及 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 手部坐标;位置步骤由身体中心推进,`wave_greeting`、`wave_left_hand`、`wave_right_hand` 和 `jump_once` 由 mocap 手势/轨迹推进。浏览器摄像头只作为背景层,动作数据源状态优先展示,键鼠仍作为本地调试兜底。
|
- 决策:热身关全流程直接接入 `useMocapInput`,通过本地 mocap WebSocket `/stream` 消费 `general.body.center_norm` 身体中心、`actions/action/gesture/gestures/event/name/type` 动作名,以及 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 手部坐标;位置步骤由身体中心推进,`wave_greeting`、`wave_left_hand`、`wave_right_hand` 和 `jump_once` 由 mocap 手势/轨迹推进。浏览器摄像头只作为背景层,动作数据源状态优先展示,键鼠仍作为本地调试兜底。
|
||||||
- 影响范围:`src/services/useMocapInput.ts`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、对应单测与热身关技术文档。
|
- 影响范围:`src/services/useMocapInput.ts`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、对应单测与热身关技术文档。
|
||||||
- 验证方式:执行 `npx vitest run src/services/useMocapInput.test.ts src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/components/child-motion-demo/childMotionWarmupModel.test.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts`、`npx eslint ...`、`npm run typecheck`、`npm run check:encoding`,并确认 `http://127.0.0.1:8876/stream` WebSocket 可握手、`http://127.0.0.1:3000/child-motion-demo` 可访问。
|
- 验证方式:执行 `npx vitest run src/services/useMocapInput.test.ts src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/components/child-motion-demo/childMotionWarmupModel.test.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts`、`npx eslint ...`、`npm run typecheck`、`npm run check:encoding`,并确认 `http://127.0.0.1:8876/stream` WebSocket 可握手、`http://127.0.0.1:3000/child-motion-demo` 可访问。
|
||||||
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-09 GPT-image-2 图片生成统一迁移到 VectorEngine
|
## 2026-05-09 GPT-image-2 图片生成统一迁移到 VectorEngine
|
||||||
|
|
||||||
@@ -248,7 +264,7 @@
|
|||||||
- 决策:所有 GPT-image-2 生图请求统一走 VectorEngine `POST /v1/images/generations`,基础配置读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` / `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,上游模型使用 `gpt-image-2-all`,请求体不再携带 `official_fallback`,参考图字段改为 `image`。APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
|
- 决策:所有 GPT-image-2 生图请求统一走 VectorEngine `POST /v1/images/generations`,基础配置读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` / `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,上游模型使用 `gpt-image-2-all`,请求体不再携带 `official_fallback`,参考图字段改为 `image`。APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
|
||||||
- 影响范围:`api-server` 共享图片 helper、拼图图片生成、角色主图、RPG 场景图、开局 CG 故事板、方洞视觉资产、生产环境示例、gpt-image-2 本地 skill 和相关技术文档。
|
- 影响范围:`api-server` 共享图片 helper、拼图图片生成、角色主图、RPG 场景图、开局 CG 故事板、方洞视觉资产、生产环境示例、gpt-image-2 本地 skill 和相关技术文档。
|
||||||
- 验证方式:执行 `npm run check:encoding`、`cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`,并用 `npm run api-server` + `/healthz` 做后端 smoke。
|
- 验证方式:执行 `npm run check:encoding`、`cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`,并用 `npm run api-server` + `/healthz` 做后端 smoke。
|
||||||
- 关联文档:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-08 Hyper3D Rodin Gen-2 只通过后端安全代理接入
|
## 2026-05-08 Hyper3D Rodin Gen-2 只通过后端安全代理接入
|
||||||
|
|
||||||
@@ -256,7 +272,7 @@
|
|||||||
- 决策:Hyper3D 统一走 `api-server` 的 `/api/assets/hyper3d/*` 鉴权路由,配置只读取 `HYPER3D_BASE_URL` / `HYPER3D_API_KEY` / `HYPER3D_MODEL_REQUEST_TIMEOUT_MS` 及兼容 `RODIN_*` 变量;生成提交、状态查询和下载列表都由后端代理。首版不写 SpacetimeDB、不确认 `asset_object`,下载链接后续由调用方决定是否进入 OSS 资产链。
|
- 决策:Hyper3D 统一走 `api-server` 的 `/api/assets/hyper3d/*` 鉴权路由,配置只读取 `HYPER3D_BASE_URL` / `HYPER3D_API_KEY` / `HYPER3D_MODEL_REQUEST_TIMEOUT_MS` 及兼容 `RODIN_*` 变量;生成提交、状态查询和下载列表都由后端代理。首版不写 SpacetimeDB、不确认 `asset_object`,下载链接后续由调用方决定是否进入 OSS 资产链。
|
||||||
- 影响范围:`api-server` 外部服务配置、Hyper3D route、`shared-contracts` / TS contract、前端 service、生产环境示例和外部服务环境变量文档。
|
- 影响范围:`api-server` 外部服务配置、Hyper3D route、`shared-contracts` / TS contract、前端 service、生产环境示例和外部服务环境变量文档。
|
||||||
- 验证方式:执行 `cargo test -p api-server hyper3d --manifest-path server-rs/Cargo.toml`、`cargo test -p shared-contracts hyper3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck` 和编码检查;真实 API smoke 只在本地私密环境配置 key 后手动执行。
|
- 验证方式:执行 `cargo test -p api-server hyper3d --manifest-path server-rs/Cargo.toml`、`cargo test -p shared-contracts hyper3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck` 和编码检查;真实 API smoke 只在本地私密环境配置 key 后手动执行。
|
||||||
- 关联文档:`docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-08 APIMart 接口统一携带 `official_fallback`
|
## 2026-05-08 APIMart 接口统一携带 `official_fallback`
|
||||||
|
|
||||||
@@ -266,7 +282,7 @@
|
|||||||
- 决策:凡是仓库内调用 APIMart 的 OpenAI 兼容接口,请求体统一携带 `official_fallback: true`;其中图片生成请求直接固定写入,`platform-llm` 的 APIMart GPT-5 client 通过显式开关开启,不默认扩散到 Ark 等其它 provider。
|
- 决策:凡是仓库内调用 APIMart 的 OpenAI 兼容接口,请求体统一携带 `official_fallback: true`;其中图片生成请求直接固定写入,`platform-llm` 的 APIMart GPT-5 client 通过显式开关开启,不默认扩散到 Ark 等其它 provider。
|
||||||
- 影响范围:`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/puzzle.rs`、`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/platform-llm/src/lib.rs`、`.codex/skills/gpt-image-2-apimart/` 和相关技术文档。
|
- 影响范围:`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/puzzle.rs`、`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/platform-llm/src/lib.rs`、`.codex/skills/gpt-image-2-apimart/` 和相关技术文档。
|
||||||
- 验证方式:图片生成与 creative-agent APIMart 路径的单测都应断言 `official_fallback` 已写入请求 JSON;编码检查和相关 Rust 测试应持续通过。
|
- 验证方式:图片生成与 creative-agent APIMart 路径的单测都应断言 `official_fallback` 已写入请求 JSON;编码检查和相关 Rust 测试应持续通过。
|
||||||
- 关联文档:`docs/technical/PUZZLE_APIMART_IMAGE_MODEL_ROUTING_2026-05-01.md`、`docs/technical/RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md`、`docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-07 server-rs Cargo 依赖集中到 workspace
|
## 2026-05-07 server-rs Cargo 依赖集中到 workspace
|
||||||
|
|
||||||
@@ -274,23 +290,23 @@
|
|||||||
- 决策:`server-rs/Cargo.toml` 的 `[workspace.dependencies]` 统一维护第三方依赖版本和 workspace 内部 crate path;成员 crate 默认使用 `{ workspace = true }`,只保留自身 feature、optional 或 target-specific 差异;不再新增 `sha1`,OSS 与阿里云 OpenAPI 签名统一走 `sha2::Sha256` 对应的 V4/V3 口径。
|
- 决策:`server-rs/Cargo.toml` 的 `[workspace.dependencies]` 统一维护第三方依赖版本和 workspace 内部 crate path;成员 crate 默认使用 `{ workspace = true }`,只保留自身 feature、optional 或 target-specific 差异;不再新增 `sha1`,OSS 与阿里云 OpenAPI 签名统一走 `sha2::Sha256` 对应的 V4/V3 口径。
|
||||||
- 影响范围:`server-rs/Cargo.toml`、所有 `server-rs/crates/*/Cargo.toml`、`platform-oss`、`platform-auth`、后续新增 Rust crate 或新增 Rust 依赖的开发流程。
|
- 影响范围:`server-rs/Cargo.toml`、所有 `server-rs/crates/*/Cargo.toml`、`platform-oss`、`platform-auth`、后续新增 Rust crate 或新增 Rust 依赖的开发流程。
|
||||||
- 验证方式:修改 Cargo 配置后先执行 `cargo metadata --manifest-path server-rs\Cargo.toml --format-version 1 --no-deps`,再按影响范围执行 `cargo check`、DDD 边界检查和编码检查。
|
- 验证方式:修改 Cargo 配置后先执行 `cargo metadata --manifest-path server-rs\Cargo.toml --format-version 1 --no-deps`,再按影响范围执行 `cargo check`、DDD 边界检查和编码检查。
|
||||||
- 关联文档:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-08 资料页反馈提交必须走 Rust 后端与 SpacetimeDB
|
## 2026-05-08 资料页反馈提交必须走 Rust 后端与 SpacetimeDB
|
||||||
|
|
||||||
- 背景:`/profile/feedback` 首版页面曾只做前端成功态,无法沉淀到用户账号和数据库,也容易与主站平台主题脱节。
|
- 背景:`/profile/feedback` 首版页面曾只做前端成功态,无法沉淀到用户账号和数据库,也容易与主站平台主题脱节。
|
||||||
- 决策:反馈提交统一走鉴权 HTTP 路由 `POST /api/profile/feedback`,由 `api-server` 取当前 access token 用户,调用 `spacetime-client` facade,再通过 `spacetime-module` procedure 写入私有表 `profile_feedback_submission`;前端只负责输入采集、Data URL 预览和提交元数据,不再保存 `File[]` 作为外部契约。
|
- 决策:反馈提交统一走鉴权 HTTP 路由 `POST /api/profile/feedback`,由 `api-server` 取当前 access token 用户,调用 `spacetime-client` facade,再通过 `spacetime-module` procedure 写入私有表 `profile_feedback_submission`;前端只负责输入采集、Data URL 预览和提交元数据,不再保存 `File[]` 作为外部契约。
|
||||||
- 影响范围:`src/components/platform-entry/PlatformFeedbackView.tsx`、`src/services/rpg-entry/rpgProfileClient.ts`、`packages/shared/src/contracts/runtime.ts`、`server-rs/crates/shared-contracts`、`api-server`、`module-runtime`、`spacetime-client`、`spacetime-module`、表目录与 bindings。
|
- 影响范围:`src/components/platform-entry/PlatformFeedbackView.tsx`、`src/services/rpg-entry/rpgProfileClient.ts`、`packages/shared/src/contracts/runtime.ts`、`server-rs/crates/shared-contracts`、`api-server`、`module-runtime`、`spacetime-client`、`spacetime-module`、表目录与 bindings。
|
||||||
- 验证方式:前端定向测试应覆盖 Data URL 预览与 `/api/profile/feedback` 请求体;后端变更需同步 `migration.rs`、`SPACETIMEDB_TABLE_CATALOG.md` 和生成绑定;API smoke 使用 `npm run api-server` 和 `/healthz`。
|
- 验证方式:前端定向测试应覆盖 Data URL 预览与 `/api/profile/feedback` 请求体;后端变更需同步 `migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和生成绑定;API smoke 使用 `npm run api-server` 和 `/healthz`。
|
||||||
- 关联文档:`docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`、`docs/technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-06 Maincloud 历史残留引用禁止再使用
|
## 2026-05-06 Maincloud 历史残留引用禁止再使用
|
||||||
|
|
||||||
- 背景:项目已经全面移除 Maincloud 运行口径,但历史脚本、测试名和文档仍可能让后续开发误用 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`。
|
- 背景:项目已经全面移除 Maincloud 运行口径,但历史脚本、测试名和文档仍可能让后续开发误用 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`。
|
||||||
- 决策:`maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求全部视为历史残留,后续禁止新增、运行或引用;后端 API smoke 统一使用 `npm run api-server` 并检查 `/healthz`。
|
- 决策:`maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求全部视为历史残留,后续禁止新增、运行或引用;后端 API smoke 统一使用 `npm run api-server` 并检查 `/healthz`。
|
||||||
- 影响范围:`AGENTS.md`、`docs/technical/`、`.hermes/shared-memory/`、后端启动脚本、测试支撑和所有后续工程文档。
|
- 影响范围:`AGENTS.md`、当前 `docs/` 融合文档、`.hermes/shared-memory/`、后端启动脚本、测试支撑和所有后续工程文档。
|
||||||
- 验证方式:新增或修改后端相关文档时,检查不得要求 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`;触碰历史残留时同步删除或改名。
|
- 验证方式:新增或修改后端相关文档时,检查不得要求 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`;触碰历史残留时同步删除或改名。
|
||||||
- 关联文档:`docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`、`docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 新手引导首版复用拼图本地运行时
|
## 2026-05-05 新手引导首版复用拼图本地运行时
|
||||||
|
|
||||||
@@ -298,7 +314,7 @@
|
|||||||
- 决策:未登录首次访问由前端 localStorage 标记触发;生成入口走公开 BFF `POST /api/runtime/puzzle/onboarding/generate` 生成 1 关临时拼图;登录后保存走鉴权 BFF `POST /api/runtime/puzzle/onboarding/save`,由服务端创建当前用户拼图 agent session 并更新其草稿作品 profile;游玩阶段复用现有本地拼图运行时。
|
- 决策:未登录首次访问由前端 localStorage 标记触发;生成入口走公开 BFF `POST /api/runtime/puzzle/onboarding/generate` 生成 1 关临时拼图;登录后保存走鉴权 BFF `POST /api/runtime/puzzle/onboarding/save`,由服务端创建当前用户拼图 agent session 并更新其草稿作品 profile;游玩阶段复用现有本地拼图运行时。
|
||||||
- 影响范围:平台入口首屏、新手引导 PRD、拼图 BFF、拼图作品契约与前端 puzzle runtime。
|
- 影响范围:平台入口首屏、新手引导 PRD、拼图 BFF、拼图作品契约与前端 puzzle runtime。
|
||||||
- 验证方式:未登录首次访问应展示新手引导;生成后只进入 1 关本地拼图;通关后登录保存应在当前用户拼图作品架出现草稿作品;不应产生 SpacetimeDB schema 变更。
|
- 验证方式:未登录首次访问应展示新手引导;生成后只进入 1 关本地拼图;通关后登录保存应在当前用户拼图作品架出现草稿作品;不应产生 SpacetimeDB schema 变更。
|
||||||
- 关联文档:`docs/prd/FIRST_LAUNCH_PUZZLE_ONBOARDING_PRD_2026-05-05.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 text-game 作为陶泥儿幕间文字游戏模板接入
|
## 2026-05-05 text-game 作为陶泥儿幕间文字游戏模板接入
|
||||||
|
|
||||||
@@ -306,7 +322,7 @@
|
|||||||
- 决策:新增 `text-game` 作为陶泥儿 AI 原生文字游戏模板口径,展示名可用“幕间”或“幕间文字”;它与 `visual-novel` 分离,重点是 AI GM、自由行动、状态后果、长期记忆、章节目标和轻量剧本模拟器;入口、作品、发布、资产、钱包、埋点、存档和广场全部复用陶泥儿平台接口;禁止新增 replay、外部社区、外部支付、外部榜单和私有存档系统。
|
- 决策:新增 `text-game` 作为陶泥儿 AI 原生文字游戏模板口径,展示名可用“幕间”或“幕间文字”;它与 `visual-novel` 分离,重点是 AI GM、自由行动、状态后果、长期记忆、章节目标和轻量剧本模拟器;入口、作品、发布、资产、钱包、埋点、存档和广场全部复用陶泥儿平台接口;禁止新增 replay、外部社区、外部支付、外部榜单和私有存档系统。
|
||||||
- 影响范围:后续 `text-game` shared contracts、`module-text-game`、SpacetimeDB 表、`api-server` 路由、前端入口 / workspace / result / runtime、平台作品架和发现聚合。
|
- 影响范围:后续 `text-game` shared contracts、`module-text-game`、SpacetimeDB 表、`api-server` 路由、前端入口 / workspace / result / runtime、平台作品架和发现聚合。
|
||||||
- 验证方式:后续落地时确认路由使用 `/api/creation/text-game/*` 与 `/api/runtime/text-game/*`;确认正式业务真相在 Rust / SpacetimeDB 后端;确认没有 `replay` 能力和外部平台功能误入;确认 `text-game` 不复用 `visual-novel` step 契约作为运行态真相。
|
- 验证方式:后续落地时确认路由使用 `/api/creation/text-game/*` 与 `/api/runtime/text-game/*`;确认正式业务真相在 Rust / SpacetimeDB 后端;确认没有 `replay` 能力和外部平台功能误入;确认 `text-game` 不复用 `visual-novel` step 契约作为运行态真相。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md`。
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 2048 玩法模板采用 `twenty-forty-eight` 工程域
|
## 2026-05-05 2048 玩法模板采用 `twenty-forty-eight` 工程域
|
||||||
|
|
||||||
@@ -314,15 +330,15 @@
|
|||||||
- 决策:面向用户展示名保持 `2048`,工程玩法 ID 固定为 `twenty-forty-eight`,Rust 模块与表前缀使用 `twenty_forty_eight`,公开作品号前缀使用 `TF-`;玩法按完整闭环设计,包含 Agent 创作、结果页、试玩、发布、公开运行、后端棋盘裁决、排行榜和作品架 / 广场接入。
|
- 决策:面向用户展示名保持 `2048`,工程玩法 ID 固定为 `twenty-forty-eight`,Rust 模块与表前缀使用 `twenty_forty_eight`,公开作品号前缀使用 `TF-`;玩法按完整闭环设计,包含 Agent 创作、结果页、试玩、发布、公开运行、后端棋盘裁决、排行榜和作品架 / 广场接入。
|
||||||
- 影响范围:后续 SpacetimeDB 创作入口配置、平台 `SelectionStage`、前端 `twenty-forty-eight-*` 组件与 service、`module-twenty-forty-eight`、`shared-contracts`、`spacetime-module` 表、`spacetime-client` facade、`api-server` 路由、作品号和 PRD 索引。
|
- 影响范围:后续 SpacetimeDB 创作入口配置、平台 `SelectionStage`、前端 `twenty-forty-eight-*` 组件与 service、`module-twenty-forty-eight`、`shared-contracts`、`spacetime-module` 表、`spacetime-client` facade、`api-server` 路由、作品号和 PRD 索引。
|
||||||
- 验证方式:后续落地时确认用户可见标题为 `2048`,代码、路由和表统一使用 `twenty-forty-eight` / `twenty_forty_eight`;移动、合并、生成新方块、目标达成、失败和榜单成绩由后端正式裁决,前端不伪造分数或目标达成。
|
- 验证方式:后续落地时确认用户可见标题为 `2048`,代码、路由和表统一使用 `twenty-forty-eight` / `twenty_forty_eight`;移动、合并、生成新方块、目标达成、失败和榜单成绩由后端正式裁决,前端不伪造分数或目标达成。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_2048_GAMEPLAY_TEMPLATE_PRD_2026-05-05.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 幸存者类玩法作为平台模板接入
|
## 2026-05-05 幸存者类玩法作为平台模板接入
|
||||||
|
|
||||||
- 背景:平台继续扩展新玩法模板,需要把幸存者 / 割草 / 轻度 Roguelite 类玩法纳入统一创作中心、作品架、广场和运行态体系,避免再起一套独立小游戏工程。
|
- 背景:平台继续扩展新玩法模板,需要把幸存者 / 割草 / 轻度 Roguelite 类玩法纳入统一创作中心、作品架、广场和运行态体系,避免再起一套独立小游戏工程。
|
||||||
- 决策:新增 `survivor` 作为 Genarrative 平台玩法模板,统一使用 `server-rs + Axum + SpacetimeDB`,创作端、结果页、试玩、发布和运行态都复用平台接口;前端只负责表现和高频模拟,不承接正式规则真相。
|
- 决策:新增 `survivor` 作为 Genarrative 平台玩法模板,统一使用 `server-rs + Axum + SpacetimeDB`,创作端、结果页、试玩、发布和运行态都复用平台接口;前端只负责表现和高频模拟,不承接正式规则真相。
|
||||||
- 影响范围:`docs/prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md`、后续 `survivor` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台作品闭环。
|
- 影响范围:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、后续 `survivor` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台作品闭环。
|
||||||
- 验证方式:后续落地时检查 `survivor` 入口、session、work profile、runtime run、checkpoint、升级候选和结算接口是否都落在平台统一链路内,并确认没有新增独立小游戏壳层。
|
- 验证方式:后续落地时检查 `survivor` 入口、session、work profile、runtime run、checkpoint、升级候选和结算接口是否都落在平台统一链路内,并确认没有新增独立小游戏壳层。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 视觉小说 TXT 玩法只作为平台模板接入且删除回放
|
## 2026-05-05 视觉小说 TXT 玩法只作为平台模板接入且删除回放
|
||||||
|
|
||||||
@@ -330,15 +346,15 @@
|
|||||||
- 决策:`visual-novel` 只作为 Genarrative 视觉小说模板接入,保留想法 / 文档 / 空白创建、世界观 / 角色 / 场景 / 剧情阶段编辑、视觉小说 step 运行时、历史和重生成等模板能力;入口、作品、发布、资产、钱包、存档和广场全部使用 Genarrative 平台接口;彻底删除回放、分享回放、回放编译、回放路由、回放表和回放 UI。
|
- 决策:`visual-novel` 只作为 Genarrative 视觉小说模板接入,保留想法 / 文档 / 空白创建、世界观 / 角色 / 场景 / 剧情阶段编辑、视觉小说 step 运行时、历史和重生成等模板能力;入口、作品、发布、资产、钱包、存档和广场全部使用 Genarrative 平台接口;彻底删除回放、分享回放、回放编译、回放路由、回放表和回放 UI。
|
||||||
- 影响范围:视觉小说 PRD、旧 TXT 文档口径、后续 `visual-novel` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台存档接入。
|
- 影响范围:视觉小说 PRD、旧 TXT 文档口径、后续 `visual-novel` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台存档接入。
|
||||||
- 验证方式:后续落地时扫描前端、后端、契约、表和文档,确认不存在 `replay` 能力;确认视觉小说没有迁入外部平台账号、订单、会员、促销、后台、公开市场或私有存档系统;确认后端落在 `server-rs + Axum + SpacetimeDB`。
|
- 验证方式:后续落地时扫描前端、后端、契约、表和文档,确认不存在 `replay` 能力;确认视觉小说没有迁入外部平台账号、订单、会员、促销、后台、公开市场或私有存档系统;确认后端落在 `server-rs + Axum + SpacetimeDB`。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/prd/TXT_MODE_CORE_GAMEPLAY_PRD_2026-04-20.md`、`docs/technical/TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 视觉小说 VN-02 表与 spacetime-client facade 收口
|
## 2026-05-05 视觉小说 VN-02 表与 spacetime-client facade 收口
|
||||||
|
|
||||||
- 背景:`visual-novel` 后续 API、创作工作台和运行时需要稳定的 SpacetimeDB schema 与 Rust facade,且必须延续“无回放、无私有存档”的产品边界。
|
- 背景:`visual-novel` 后续 API、创作工作台和运行时需要稳定的 SpacetimeDB schema 与 Rust facade,且必须延续“无回放、无私有存档”的产品边界。
|
||||||
- 决策:视觉小说首批数据库只落六张表:`visual_novel_agent_session`、`visual_novel_agent_message`、`visual_novel_work_profile`、`visual_novel_runtime_run`、`visual_novel_runtime_history_entry`、`visual_novel_runtime_event`;`visual_novel_runtime_event` 是 `public event` 审计事件表,不是 replay 数据源;运行历史只保存继续体验与历史重生成需要的 typed step 和快照哈希。`api-server` 后续接入必须经 `spacetime-client/src/visual_novel.rs` typed facade,不直接依赖生成 bindings。
|
- 决策:视觉小说首批数据库只落六张表:`visual_novel_agent_session`、`visual_novel_agent_message`、`visual_novel_work_profile`、`visual_novel_runtime_run`、`visual_novel_runtime_history_entry`、`visual_novel_runtime_event`;`visual_novel_runtime_event` 是 `public event` 审计事件表,不是 replay 数据源;运行历史只保存继续体验与历史重生成需要的 typed step 和快照哈希。`api-server` 后续接入必须经 `spacetime-client/src/visual_novel.rs` typed facade,不直接依赖生成 bindings。
|
||||||
- 影响范围:`server-rs/crates/spacetime-module/src/visual_novel.rs`、`migration.rs`、`server-rs/crates/spacetime-client/src/visual_novel.rs`、`module_bindings/`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`、VN-05 API 联调。
|
- 影响范围:`server-rs/crates/spacetime-module/src/visual_novel.rs`、`migration.rs`、`server-rs/crates/spacetime-client/src/visual_novel.rs`、`module_bindings/`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、VN-05 API 联调。
|
||||||
- 验证方式:执行 `npm run spacetime:generate -- --rust-only`、`cargo check -p spacetime-module`、`cargo check -p spacetime-client`、`npm run check:encoding`;扫描视觉小说 schema / facade / 表目录确认没有 `replay` 表、路由或私有 save 表。
|
- 验证方式:执行 `npm run spacetime:generate -- --rust-only`、`cargo check -p spacetime-module`、`cargo check -p spacetime-client`、`npm run check:encoding`;扫描视觉小说 schema / facade / 表目录确认没有 `replay` 表、路由或私有 save 表。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 视觉小说 VN-07 前端创作闭环按阶段边界落地
|
## 2026-05-05 视觉小说 VN-07 前端创作闭环按阶段边界落地
|
||||||
|
|
||||||
@@ -346,31 +362,31 @@
|
|||||||
- 决策:VN-07 前端只接入口、Agent 工作台、可编辑 `VisualNovelResultDraft` 结果页和测试 run;`blank` 起点直接生成本地空白草稿进入结果页,`idea` / `document` 继续调用 `/api/creation/visual-novel/sessions`;结果页保存先更新当前 session 草稿,显式“编译草稿”才调用 `/compile`,测试 run 在真实 runtime 不可用时降级为本地 test run。
|
- 决策:VN-07 前端只接入口、Agent 工作台、可编辑 `VisualNovelResultDraft` 结果页和测试 run;`blank` 起点直接生成本地空白草稿进入结果页,`idea` / `document` 继续调用 `/api/creation/visual-novel/sessions`;结果页保存先更新当前 session 草稿,显式“编译草稿”才调用 `/compile`,测试 run 在真实 runtime 不可用时降级为本地 test run。
|
||||||
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/visual-novel-creation/`、`src/components/visual-novel-result/`、`packages/shared/src/contracts/visualNovel.ts`、视觉小说 PRD。
|
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/visual-novel-creation/`、`src/components/visual-novel-result/`、`packages/shared/src/contracts/visualNovel.ts`、视觉小说 PRD。
|
||||||
- 验证方式:执行前端 typecheck、视觉小说工作台 / 结果页定向测试和编码检查;确认未新增 replay、作品聚合或正式 runtime 能力。
|
- 验证方式:执行前端 typecheck、视觉小说工作台 / 结果页定向测试和编码检查;确认未新增 replay、作品聚合或正式 runtime 能力。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-07 视觉小说 VN-11 负向扫描门禁
|
## 2026-05-07 视觉小说 VN-11 负向扫描门禁
|
||||||
|
|
||||||
- 背景:视觉小说 TXT 模板进入收口后,需要一个可重复执行的守门方式,避免工程代码误入回放能力或外部平台功能。
|
- 背景:视觉小说 TXT 模板进入收口后,需要一个可重复执行的守门方式,避免工程代码误入回放能力或外部平台功能。
|
||||||
- 决策:新增 `npm run check:visual-novel-vn11`,由 `scripts/check-visual-novel-vn11-negative-scan.mjs` 扫描 `src/`、`packages/shared/src/`、`server-rs/crates/`、`docs/` 与 `.hermes/shared-memory/`;工程代码中不允许出现 replay / 回放 / 录制 / 复盘类直出命中;外部平台能力误入只在视觉小说实现路径内检查,避免把平台已有账号、会员、后台等能力误判为视觉小说迁入。
|
- 决策:新增 `npm run check:visual-novel-vn11`,由 `scripts/check-visual-novel-vn11-negative-scan.mjs` 扫描 `src/`、`packages/shared/src/`、`server-rs/crates/`、`docs/` 与 `.hermes/shared-memory/`;工程代码中不允许出现 replay / 回放 / 录制 / 复盘类直出命中;外部平台能力误入只在视觉小说实现路径内检查,避免把平台已有账号、会员、后台等能力误判为视觉小说迁入。
|
||||||
- 影响范围:视觉小说 VN-11 验收、后续 `visual-novel` 增量改动、同类新玩法负向扫描脚本。
|
- 影响范围:视觉小说 VN-11 验收、后续 `visual-novel` 增量改动、同类新玩法负向扫描脚本。
|
||||||
- 验证方式:执行 `npm run check:visual-novel-vn11`,报告写入 `docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;当前扫描结论为工程代码无回放类直出命中,视觉小说实现路径无外部平台能力误入。
|
- 验证方式:执行 `npm run check:visual-novel-vn11`,报告写入 `.tmp/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;当前扫描结论为工程代码无回放类直出命中,视觉小说实现路径无外部平台能力误入。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-07 视觉小说 VN-12 采用单独验收门禁脚本
|
## 2026-05-07 视觉小说 VN-12 采用单独验收门禁脚本
|
||||||
|
|
||||||
- 背景:VN-12 是视觉小说模板的全链路联调与自动化验收收口任务,需要把关键路径、API smoke、前端测试和报告输出固化成可复跑门禁,避免后续改动只靠手工口述结论。
|
- 背景:VN-12 是视觉小说模板的全链路联调与自动化验收收口任务,需要把关键路径、API smoke、前端测试和报告输出固化成可复跑门禁,避免后续改动只靠手工口述结论。
|
||||||
- 决策:新增 `npm run check:visual-novel-vn12`,由 `scripts/check-visual-novel-vn12-acceptance.mjs` 校验 PRD、VN-11 报告、关键前端测试、视觉小说 service client、`api-server` / `module-visual-novel` / `shared-contracts` 相关文件和路由命中,并生成 `docs/audits/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md`。
|
- 决策:新增 `npm run check:visual-novel-vn12`,由 `scripts/check-visual-novel-vn12-acceptance.mjs` 校验 PRD、VN-11 报告、关键前端测试、视觉小说 service client、`api-server` / `module-visual-novel` / `shared-contracts` 相关文件和路由命中,并生成 `.tmp/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md`。
|
||||||
- 影响范围:VN-12 验收、视觉小说后续回归、同类玩法的收口门禁模式。
|
- 影响范围:VN-12 验收、视觉小说后续回归、同类玩法的收口门禁模式。
|
||||||
- 验证方式:执行 `npm run check:visual-novel-vn12 -- --write-report`,报告应覆盖自动化验收清单、API smoke、前端关键路径、桌面/移动端检查说明和已执行命令;若脚本失败,直接回流到对应 owner 修复。
|
- 验证方式:执行 `npm run check:visual-novel-vn12 -- --write-report`,报告应覆盖自动化验收清单、API smoke、前端关键路径、桌面/移动端检查说明和已执行命令;若脚本失败,直接回流到对应 owner 修复。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/audits/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-07 视觉小说 VN-13 文档与交接收口
|
## 2026-05-07 视觉小说 VN-13 文档与交接收口
|
||||||
|
|
||||||
- 背景:视觉小说模板主链已经落地完成,需要把 PRD、表目录、prompt 工具说明、负向扫描报告和维护经验收成新开发者可直接接手的一组文档,避免后续仍回头查旧 TXT 迁移方案。
|
- 背景:视觉小说模板主链已经落地完成,需要把 PRD、表目录、prompt 工具说明、负向扫描报告和维护经验收成新开发者可直接接手的一组文档,避免后续仍回头查旧 TXT 迁移方案。
|
||||||
- 决策:视觉小说后续维护的正式入口固定为 `AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`SPACETIMEDB_TABLE_CATALOG.md`、`VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md`、`VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`、`VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md` 和 `VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;旧 TXT 迁移文档仅保留历史参考地位。
|
- 决策:视觉小说后续维护的正式入口固定为当前玩法链路文档、当前后端架构文档和 `npm run check:visual-novel-vn11` / `npm run check:visual-novel-vn12` 两个门禁;旧 TXT 迁移文档和旧视觉小说阶段文档不再作为实现目标。
|
||||||
- 影响范围:视觉小说 PRD 收口、技术文档索引、经验文档索引、Hermes 共享记忆和后续维护阅读顺序。
|
- 影响范围:视觉小说 PRD 收口、技术文档索引、经验文档索引、Hermes 共享记忆和后续维护阅读顺序。
|
||||||
- 验证方式:打开上述文档即可获得当前实现边界、表目录、Prompt 口径、负向扫描和维护经验;后续维护不需要把旧 TXT 平台工程文档重新当作实现目标。
|
- 验证方式:打开当前融合文档并运行 VN 门禁即可获得当前实现边界、表目录、Prompt 口径、负向扫描和维护经验;后续维护不需要把旧 TXT 平台工程文档重新当作实现目标。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`、`docs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 平台移动端一级 Tab 改为推荐/发现/创作/草稿/我的
|
## 2026-05-05 平台移动端一级 Tab 改为推荐/发现/创作/草稿/我的
|
||||||
|
|
||||||
@@ -378,7 +394,7 @@
|
|||||||
- 决策:前端内部继续复用 `PlatformHomeTab` 的 `home/category/create/saves/profile` 状态值,但用户看到的一级 Tab 分别为“推荐/发现/创作/草稿/我的”;`home` 直接展示公开推荐流,`category` 承载发现页及排行子 Tab,`saves` 承载草稿作品架,原存档结构并入“我的-玩过”弹层。
|
- 决策:前端内部继续复用 `PlatformHomeTab` 的 `home/category/create/saves/profile` 状态值,但用户看到的一级 Tab 分别为“推荐/发现/创作/草稿/我的”;`home` 直接展示公开推荐流,`category` 承载发现页及排行子 Tab,`saves` 承载草稿作品架,原存档结构并入“我的-玩过”弹层。
|
||||||
- 影响范围:平台入口导航、移动端推荐页、发现页子 Tab、创作中心作品架、个人页玩过弹层、相关设计文档。
|
- 影响范围:平台入口导航、移动端推荐页、发现页子 Tab、创作中心作品架、个人页玩过弹层、相关设计文档。
|
||||||
- 验证方式:检查移动端底部导航文案和顺序,确认登录态为“推荐/发现/创作/草稿/我的”,未登录态为“推荐/创作/发现”且创作居中;“推荐”无搜索/频道栏直出作品流,“发现”包含搜索/推荐/今日/分类/排行,“创作”只显示新建入口,“草稿”显示作品架,“我的-玩过”可恢复存档。
|
- 验证方式:检查移动端底部导航文案和顺序,确认登录态为“推荐/发现/创作/草稿/我的”,未登录态为“推荐/创作/发现”且创作居中;“推荐”无搜索/频道栏直出作品流,“发现”包含搜索/推荐/今日/分类/排行,“创作”只显示新建入口,“草稿”显示作品架,“我的-玩过”可恢复存档。
|
||||||
- 关联文档:`docs/design/PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-14 推荐页卡片主视觉优先于底部作者热区
|
## 2026-05-14 推荐页卡片主视觉优先于底部作者热区
|
||||||
|
|
||||||
@@ -386,7 +402,7 @@
|
|||||||
- 决策:推荐页卡片底部信息区保持紧凑固定高度,切换手势仍只绑定在该区域;视觉主体高度优先扩展,不再让作者信息区占用过多首屏空间。
|
- 决策:推荐页卡片底部信息区保持紧凑固定高度,切换手势仍只绑定在该区域;视觉主体高度优先扩展,不再让作者信息区占用过多首屏空间。
|
||||||
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx` 的推荐页卡片布局,以及 `src/index.css` 中的推荐页卡片热区样式。
|
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx` 的推荐页卡片布局,以及 `src/index.css` 中的推荐页卡片热区样式。
|
||||||
- 验证方式:移动端推荐页首屏应明显看到更大的作品内容区,底部作者信息区只保留紧凑一条,不再明显挤压运行态。
|
- 验证方式:移动端推荐页首屏应明显看到更大的作品内容区,底部作者信息区只保留紧凑一条,不再明显挤压运行态。
|
||||||
- 关联文档:`docs/technical/PLATFORM_MOBILE_RECOMMEND_CARD_SAFE_SWIPE_LAYOUT_2026-05-12.md`。
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 创作 Tab 固定为智能创作首页,草稿 Tab 承接旧作品架
|
## 2026-05-05 创作 Tab 固定为智能创作首页,草稿 Tab 承接旧作品架
|
||||||
|
|
||||||
@@ -394,7 +410,7 @@
|
|||||||
- 决策:`create` 只承载 `CreativeAgentHome` 智能创作首页与会话流,顶部品牌栏、问候、快捷胶囊、底部输入框和左侧抽屉是主结构;旧的新建作品类型卡不再在 `create` 里展示。原本的 RPG / 拼图 / 大鱼 / Match3D / 方洞 / 视觉小说作品架统一归到 `saves` 草稿 Tab。
|
- 决策:`create` 只承载 `CreativeAgentHome` 智能创作首页与会话流,顶部品牌栏、问候、快捷胶囊、底部输入框和左侧抽屉是主结构;旧的新建作品类型卡不再在 `create` 里展示。原本的 RPG / 拼图 / 大鱼 / Match3D / 方洞 / 视觉小说作品架统一归到 `saves` 草稿 Tab。
|
||||||
- 影响范围:平台创作页布局、创作首页抽屉、草稿页作品架、相关交互测试、旧创作入口 helper。
|
- 影响范围:平台创作页布局、创作首页抽屉、草稿页作品架、相关交互测试、旧创作入口 helper。
|
||||||
- 验证方式:移动端点击“创作”直接看到智能创作首页;点击“草稿”看到旧作品架;旧模板入口不再从创作页出现。
|
- 验证方式:移动端点击“创作”直接看到智能创作首页;点击“草稿”看到旧作品架;旧模板入口不再从创作页出现。
|
||||||
- 关联文档:`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 创意互动内容生成 Agent 采用 LangChain-Rust 六模块闭环
|
## 2026-05-05 创意互动内容生成 Agent 采用 LangChain-Rust 六模块闭环
|
||||||
|
|
||||||
@@ -402,7 +418,7 @@
|
|||||||
- 决策:新增方案改为基于 LangChain-Rust 的六模块 Agent 架构,核心模块是感知、思考、记忆、行动、反思、协作;首版只支持拼图玩法,但必须先展示多个拼图子模板候选,用户选择某个模板后,再确认该模板下的关卡模式、关卡数和预计积分范围,确认后才生成草稿;Agent 理解、规划和修订统一使用 APIMart Responses `gpt-5` 并支持文本/图像多模态输入;Agent 创作方式就是填充和修订模板草稿字段,表单化创作页与 Agent 自然语言修订都操作同一份 `PuzzleResultDraft`,且草稿可编辑字段只收敛为 `workTitle`、`workDescription`、`workTags`、`levels[].levelName`、`levels[].pictureDescription`、`levels[].pictureReference`;其中 `pictureReference` 已采用 `PuzzleDraftLevel.pictureReference` / Rust `picture_reference` 正式字段方案,不再走 metadata 过渡;单关卡/多关卡图片生成通过拼图模块 Tool 与模板协议实现;生成好的内容必须可立即试玩。
|
- 决策:新增方案改为基于 LangChain-Rust 的六模块 Agent 架构,核心模块是感知、思考、记忆、行动、反思、协作;首版只支持拼图玩法,但必须先展示多个拼图子模板候选,用户选择某个模板后,再确认该模板下的关卡模式、关卡数和预计积分范围,确认后才生成草稿;Agent 理解、规划和修订统一使用 APIMart Responses `gpt-5` 并支持文本/图像多模态输入;Agent 创作方式就是填充和修订模板草稿字段,表单化创作页与 Agent 自然语言修订都操作同一份 `PuzzleResultDraft`,且草稿可编辑字段只收敛为 `workTitle`、`workDescription`、`workTags`、`levels[].levelName`、`levels[].pictureDescription`、`levels[].pictureReference`;其中 `pictureReference` 已采用 `PuzzleDraftLevel.pictureReference` / Rust `picture_reference` 正式字段方案,不再走 metadata 过渡;单关卡/多关卡图片生成通过拼图模块 Tool 与模板协议实现;生成好的内容必须可立即试玩。
|
||||||
- 影响范围:创作中心入口、`platform-agent`、`module-creative-agent`、`module-puzzle` 拼图模板协议和工具、`shared-contracts`、`api-server` creative facade、SpacetimeDB creative agent 表、拼图玩法工具。
|
- 影响范围:创作中心入口、`platform-agent`、`module-creative-agent`、`module-puzzle` 拼图模板协议和工具、`shared-contracts`、`api-server` creative facade、SpacetimeDB creative agent 表、拼图玩法工具。
|
||||||
- 验证方式:后续落地时以创意互动内容生成 Agent 技术方案和 Phase 1 PRD 为编码依据,优先完成拼图 Phase 1,并执行 shared contracts、module、platform-agent、api-server、前端 typecheck 与编码检查。
|
- 验证方式:后续落地时以创意互动内容生成 Agent 技术方案和 Phase 1 PRD 为编码依据,优先完成拼图 Phase 1,并执行 shared contracts、module、platform-agent、api-server、前端 typecheck 与编码检查。
|
||||||
- 关联文档:`docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`、`docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 creative-agent Task C 首版平台 PoC 已落地
|
## 2026-05-05 creative-agent Task C 首版平台 PoC 已落地
|
||||||
|
|
||||||
@@ -410,7 +426,7 @@
|
|||||||
- 决策:新增 `server-rs/crates/platform-agent` 作为独立 workspace crate,保留项目自有 `CreativeAgentExecutor`、工具注册表、回调事件和 mock executor;`platform-llm` 的 Responses 请求体扩展为可序列化 `input_text` / `input_image` content part。
|
- 决策:新增 `server-rs/crates/platform-agent` 作为独立 workspace crate,保留项目自有 `CreativeAgentExecutor`、工具注册表、回调事件和 mock executor;`platform-llm` 的 Responses 请求体扩展为可序列化 `input_text` / `input_image` content part。
|
||||||
- 影响范围:`server-rs/Cargo.toml`、`server-rs/crates/platform-agent`、`server-rs/crates/platform-llm`、任务 C 的后续 API / SSE 接入。
|
- 影响范围:`server-rs/Cargo.toml`、`server-rs/crates/platform-agent`、`server-rs/crates/platform-llm`、任务 C 的后续 API / SSE 接入。
|
||||||
- 验证方式:`cargo check -p platform-agent`、`cargo test -p platform-agent`、`cargo test -p platform-llm responses_multimodal` 已通过。
|
- 验证方式:`cargo check -p platform-agent`、`cargo test -p platform-agent`、`cargo test -p platform-llm responses_multimodal` 已通过。
|
||||||
- 关联文档:`docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`。
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-05 creative-agent Task E API / SSE facade 已落地
|
## 2026-05-05 creative-agent Task E API / SSE facade 已落地
|
||||||
|
|
||||||
@@ -418,7 +434,7 @@
|
|||||||
- 决策:`api-server` 挂载 `/api/runtime/creative-agent/*` 六个鉴权路由;creative session 在 Task D 表未收口前暂存在 `api-server` 运行态并按 authenticated user 校验 owner;未确认模板前不创建拼图 session,`confirm-template` 后才通过既有 `spacetime-client` 创建/编译 `puzzle_agent_session`;`gpt-5` 请求只从 `APIMART_BASE_URL` / `APIMART_API_KEY` 构造专用 Responses client,不复用通用 `GENARRATIVE_LLM_API_KEY`。
|
- 决策:`api-server` 挂载 `/api/runtime/creative-agent/*` 六个鉴权路由;creative session 在 Task D 表未收口前暂存在 `api-server` 运行态并按 authenticated user 校验 owner;未确认模板前不创建拼图 session,`confirm-template` 后才通过既有 `spacetime-client` 创建/编译 `puzzle_agent_session`;`gpt-5` 请求只从 `APIMART_BASE_URL` / `APIMART_API_KEY` 构造专用 Responses client,不复用通用 `GENARRATIVE_LLM_API_KEY`。
|
||||||
- 影响范围:`server-rs/crates/api-server/src/creative_agent.rs`、`creative_agent_sse.rs`、`app.rs`、`state.rs`、`module-puzzle` creative template/tool、Phase 1 PRD。
|
- 影响范围:`server-rs/crates/api-server/src/creative_agent.rs`、`creative_agent_sse.rs`、`app.rs`、`state.rs`、`module-puzzle` creative template/tool、Phase 1 PRD。
|
||||||
- 验证方式:`cargo check -p api-server`、`cargo test -p module-puzzle creative`、`cargo test -p api-server creative_agent`、`npm run api-server` 后检查 `/healthz`、`POST /api/runtime/creative-agent/sessions`、`POST /api/runtime/creative-agent/sessions/{sessionId}/messages/stream`。
|
- 验证方式:`cargo check -p api-server`、`cargo test -p module-puzzle creative`、`cargo test -p api-server creative_agent`、`npm run api-server` 后检查 `/healthz`、`POST /api/runtime/creative-agent/sessions`、`POST /api/runtime/creative-agent/sessions/{sessionId}/messages/stream`。
|
||||||
- 关联文档:`docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-10 视觉小说入口收敛为单句创作 + 画风选择
|
## 2026-05-10 视觉小说入口收敛为单句创作 + 画风选择
|
||||||
|
|
||||||
@@ -426,15 +442,15 @@
|
|||||||
- 决策:入口页只展示一句话创作输入框和横向视觉画风卡片;画风通过 `seedText` 追加 `视觉画风` 和 `画风要求` 两行透传给既有创作链路;点击生成后先进入 `visual-novel-generating` 过程页,再自动进入 `visual-novel-result`。画风卡片主视觉固定消费 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2-all` 生成的静态参考图,不在前端运行时现场调用生图接口。
|
- 决策:入口页只展示一句话创作输入框和横向视觉画风卡片;画风通过 `seedText` 追加 `视觉画风` 和 `画风要求` 两行透传给既有创作链路;点击生成后先进入 `visual-novel-generating` 过程页,再自动进入 `visual-novel-result`。画风卡片主视觉固定消费 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2-all` 生成的静态参考图,不在前端运行时现场调用生图接口。
|
||||||
- 影响范围:`VisualNovelAgentWorkspace`、`visualNovelEntryGeneration`、`PlatformEntryFlowShellImpl`、视觉小说 PRD 和创作 Tab 设计文档;不新增后端字段或数据库结构。
|
- 影响范围:`VisualNovelAgentWorkspace`、`visualNovelEntryGeneration`、`PlatformEntryFlowShellImpl`、视觉小说 PRD 和创作 Tab 设计文档;不新增后端字段或数据库结构。
|
||||||
- 验证方式:执行 `npm run test -- VisualNovelAgentWorkspace`、视觉小说工作台相关 ESLint、`npx prettier --check` 和 `npm run check:encoding`;`npm run typecheck` 若失败需先区分是否来自无关 Match3D / RPG 既有改动。
|
- 验证方式:执行 `npm run test -- VisualNovelAgentWorkspace`、视觉小说工作台相关 ESLint、`npx prettier --check` 和 `npm run check:encoding`;`npm run typecheck` 若失败需先区分是否来自无关 Match3D / RPG 既有改动。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-10 用户标签只做后端白名单投影
|
## 2026-05-10 用户标签只做后端白名单投影
|
||||||
|
|
||||||
- 背景:运营邀请码需要给账号打标签,但标签默认不能暴露到前端通用用户资料;拼图排行榜仅需展示特定标签。
|
- 背景:运营邀请码需要给账号打标签,但标签默认不能暴露到前端通用用户资料;拼图排行榜仅需展示特定标签。
|
||||||
- 决策:`user_account.user_tags` 保存账号标签,数据库默认 `None`,业务按空数组读取;后台预置邀请码使用后授予的标签不再使用独立列,统一存放并解析自 `profile_invite_code.metadata_json.userTags`,兼容读取 `user_tags`。通用登录态和个人资料不返回原始标签。首版只在拼图排行榜 `visibleTags` 中白名单投影 `北科`。
|
- 决策:`user_account.user_tags` 保存账号标签,数据库默认 `None`,业务按空数组读取;后台预置邀请码使用后授予的标签不再使用独立列,统一存放并解析自 `profile_invite_code.metadata_json.userTags`,兼容读取 `user_tags`。通用登录态和个人资料不返回原始标签。首版只在拼图排行榜 `visibleTags` 中白名单投影 `北科`。
|
||||||
- 影响范围:用户认证表、邀请码后台、邀请兑换事务、拼图排行榜响应和 UI。
|
- 影响范围:用户认证表、邀请码后台、邀请兑换事务、拼图排行榜响应和 UI。
|
||||||
- 验证方式:表结构变更需同步 `migration.rs`、`SPACETIMEDB_TABLE_CATALOG.md` 和 SpacetimeDB bindings;后端运行 `cargo check -p api-server`,后台运行 `npm run admin-web:typecheck`。
|
- 验证方式:表结构变更需同步 `migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和 SpacetimeDB bindings;后端运行 `cargo check -p api-server`,后台运行 `npm run admin-web:typecheck`。
|
||||||
- 关联文档:`docs/technical/USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-10 抓大鹅草稿元信息由 gpt-4o 生成
|
## 2026-05-10 抓大鹅草稿元信息由 gpt-4o 生成
|
||||||
|
|
||||||
@@ -442,7 +458,7 @@
|
|||||||
- 决策:`match3d_compile_draft` 使用 `gpt-4o` 生成 `gameName` 与 3 到 6 个标签;`summary` 默认保持空字符串;标签可由结果页 `作品信息` Tab 手动编辑或再次 AI 生成。草稿生成会按难度产出多视角 2D 物品图片并写入 `generated_item_assets_json`,运行态必须优先消费 `generatedItemAssets[].imageViews[]`,默认积木只做兜底。
|
- 决策:`match3d_compile_draft` 使用 `gpt-4o` 生成 `gameName` 与 3 到 6 个标签;`summary` 默认保持空字符串;标签可由结果页 `作品信息` Tab 手动编辑或再次 AI 生成。草稿生成会按难度产出多视角 2D 物品图片并写入 `generated_item_assets_json`,运行态必须优先消费 `generatedItemAssets[].imageViews[]`,默认积木只做兜底。
|
||||||
- 影响范围:`api-server` Match3D 编译、Match3D works 标签接口、结果页 `作品信息` 与 `素材配置` Tab、运行态 `Match3DRuntimeShell` / `Match3DPhysicsBoard`、生成进度和 Match3D 技术文档。
|
- 影响范围:`api-server` Match3D 编译、Match3D works 标签接口、结果页 `作品信息` 与 `素材配置` Tab、运行态 `Match3DRuntimeShell` / `Match3DPhysicsBoard`、生成进度和 Match3D 技术文档。
|
||||||
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`,并用 `npm run api-server` 检查 `/healthz`。
|
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`,并用 `npm run api-server` 检查 `/healthz`。
|
||||||
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`;`docs/technical/MATCH3D_RODIN_ASSET_TAB_2026-05-10.md` 仅作历史参考。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-12 抓大鹅物品种类从消除次数中拆出并改为 2D 五视角素材
|
## 2026-05-12 抓大鹅物品种类从消除次数中拆出并改为 2D 五视角素材
|
||||||
|
|
||||||
@@ -450,7 +466,7 @@
|
|||||||
- 决策:难度配置统一使用 `物品种类`:轻松 3、标准 9、进阶 15、硬核 21;历史硬核 `clearCount=20` 在运行态升为 21 组三消。新草稿和批量新增不再调用 Rodin、不再生成 GLB。每个物品生成 5 个不同 2D 视角,单张 1K 素材图固定按 5x5 切割,最多承载 5 个物品;超过 5 个物品时由 `api-server` 自动分批并行生图。发布必须校验已生成 `image_ready` 且有 `imageViews[]` 或首图引用的素材数量满足当前难度;试玩通过 `itemTypeCountOverride` 自动降到可用 2D 素材数量。历史模型字段只作为旧数据兼容,不再进入新生产链路。
|
- 决策:难度配置统一使用 `物品种类`:轻松 3、标准 9、进阶 15、硬核 21;历史硬核 `clearCount=20` 在运行态升为 21 组三消。新草稿和批量新增不再调用 Rodin、不再生成 GLB。每个物品生成 5 个不同 2D 视角,单张 1K 素材图固定按 5x5 切割,最多承载 5 个物品;超过 5 个物品时由 `api-server` 自动分批并行生图。发布必须校验已生成 `image_ready` 且有 `imageViews[]` 或首图引用的素材数量满足当前难度;试玩通过 `itemTypeCountOverride` 自动降到可用 2D 素材数量。历史模型字段只作为旧数据兼容,不再进入新生产链路。
|
||||||
- 影响范围:Match3D 结果页、运行态启动契约、`module-match3d` 初始 run 生成、SpacetimeDB start input / restart、发布校验和 Match3D 技术文档。
|
- 影响范围:Match3D 结果页、运行态启动契约、`module-match3d` 初始 run 生成、SpacetimeDB start input / restart、发布校验和 Match3D 技术文档。
|
||||||
- 验证方式:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`、相关后端 check / tests。
|
- 验证方式:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`、相关后端 check / tests。
|
||||||
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-07 移动端整页缩放由入口统一锁定
|
## 2026-05-07 移动端整页缩放由入口统一锁定
|
||||||
|
|
||||||
@@ -458,7 +474,7 @@
|
|||||||
- 决策:主站入口统一使用 `viewport` 锁定 `minimum-scale=1.0`、`maximum-scale=1.0`、`user-scalable=no` 和 `viewport-fit=cover`,并在应用启动时调用 `lockMobileViewportZoom()` 拦截 iOS `gesture*` 与多指 `touchmove` 触发的页面级缩放。
|
- 决策:主站入口统一使用 `viewport` 锁定 `minimum-scale=1.0`、`maximum-scale=1.0`、`user-scalable=no` 和 `viewport-fit=cover`,并在应用启动时调用 `lockMobileViewportZoom()` 拦截 iOS `gesture*` 与多指 `touchmove` 触发的页面级缩放。
|
||||||
- 影响范围:主站 `index.html`、`src/main.tsx`、后续所有依赖主入口的移动端游戏/画布页面;不要求每个画布组件重复实现缩放锁定。
|
- 影响范围:主站 `index.html`、`src/main.tsx`、后续所有依赖主入口的移动端游戏/画布页面;不要求每个画布组件重复实现缩放锁定。
|
||||||
- 验证方式:移动端打开主站后,双指捏合和快速双击不应再缩放整页;单指滚动、点击和组件内交互保持正常。
|
- 验证方式:移动端打开主站后,双指捏合和快速双击不应再缩放整页;单指滚动、点击和组件内交互保持正常。
|
||||||
- 关联文档:`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-07 视觉小说 VN-10 资产引用统一走平台资产对象
|
## 2026-05-07 视觉小说 VN-10 资产引用统一走平台资产对象
|
||||||
|
|
||||||
@@ -466,7 +482,7 @@
|
|||||||
- 决策:VN 上传统一复用 `/api/assets/direct-upload-tickets`、OSS 直传、`/api/assets/objects/confirm`、`/api/assets/read-url`。文档上传后只把 `assetObjectId` 放入 `sourceAssetIds`,`seedText` 仅放截断摘要;封面、场景、角色、音乐只写 `/generated-*` 引用和平台 asset id。角色立绘写入 `imageAssets[].source = platform_asset`。运行时图片渲染统一使用 `ResolvedAssetImage` 换签。
|
- 决策:VN 上传统一复用 `/api/assets/direct-upload-tickets`、OSS 直传、`/api/assets/objects/confirm`、`/api/assets/read-url`。文档上传后只把 `assetObjectId` 放入 `sourceAssetIds`,`seedText` 仅放截断摘要;封面、场景、角色、音乐只写 `/generated-*` 引用和平台 asset id。角色立绘写入 `imageAssets[].source = platform_asset`。运行时图片渲染统一使用 `ResolvedAssetImage` 换签。
|
||||||
- 影响范围:`src/services/visual-novel-creation/visualNovelAssetClient.ts`、`VisualNovelAgentWorkspace`、`VisualNovelResultView`、`VisualNovelRuntimeShell`、`server-rs/crates/api-server/src/visual_novel.rs`。
|
- 影响范围:`src/services/visual-novel-creation/visualNovelAssetClient.ts`、`VisualNovelAgentWorkspace`、`VisualNovelResultView`、`VisualNovelRuntimeShell`、`server-rs/crates/api-server/src/visual_novel.rs`。
|
||||||
- 验证方式:VN 定向前端测试、`npm run typecheck`、`npm run check:encoding`、`cargo test -p api-server visual_novel`、`cargo test -p api-server creation_agent_document_input`。
|
- 验证方式:VN 定向前端测试、`npm run typecheck`、`npm run check:encoding`、`cargo test -p api-server visual_novel`、`cargo test -p api-server creation_agent_document_input`。
|
||||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`。
|
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-05-04 在仓库 `.hermes/` 中建立团队共享记忆
|
## 2026-05-04 在仓库 `.hermes/` 中建立团队共享记忆
|
||||||
|
|
||||||
@@ -474,15 +490,15 @@
|
|||||||
- 决策:不共享个人 `~/.hermes`,先在 Genarrative 仓库内使用 `.hermes/` 保存可 Git 同步的团队共享记忆、计划和未来 skills。
|
- 决策:不共享个人 `~/.hermes`,先在 Genarrative 仓库内使用 `.hermes/` 保存可 Git 同步的团队共享记忆、计划和未来 skills。
|
||||||
- 影响范围:`AGENTS.md`、`.hermes/README.md`、`.hermes/shared-memory/`。
|
- 影响范围:`AGENTS.md`、`.hermes/README.md`、`.hermes/shared-memory/`。
|
||||||
- 验证方式:任一开发者拉取仓库后,在项目根目录启动 Hermes,均可读取同一套 `.hermes/shared-memory/` 文件。
|
- 验证方式:任一开发者拉取仓库后,在项目根目录启动 Hermes,均可读取同一套 `.hermes/shared-memory/` 文件。
|
||||||
- 关联文档:`.hermes/README.md`、`.hermes/shared-memory/team-conventions.md`。
|
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-04-25 后端唯一落地口径固定为 Rust / SpacetimeDB
|
## 2026-04-25 后端唯一落地口径固定为 Rust / SpacetimeDB
|
||||||
|
|
||||||
- 背景:项目经历过 Node/Express/PostgreSQL、Go 试验、Rust/SpacetimeDB 等多条后端路线,旧路线文档容易造成开发歧义。
|
- 背景:项目经历过 Node/Express/PostgreSQL、Go 试验、Rust/SpacetimeDB 等多条后端路线,旧路线文档容易造成开发歧义。
|
||||||
- 决策:新功能以后端当前基线为准:HTTP 门面使用 Rust `api-server` / Axum,业务真相使用 SpacetimeDB,领域和契约在 `server-rs` 多 crate 分层维护。
|
- 决策:新功能以后端当前基线为准:HTTP 门面使用 Rust `api-server` / Axum,业务真相使用 SpacetimeDB,领域和契约在 `server-rs` 多 crate 分层维护。
|
||||||
- 影响范围:所有后端、数据真相、运行时状态、创作结果、用户系统、资产、任务、埋点、后台 API 等相关开发。
|
- 影响范围:所有后端、数据真相、运行时状态、创作结果、用户系统、资产、任务、埋点、后台 API 等相关开发。
|
||||||
- 验证方式:开发前优先阅读 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`;旧 `server-node`、Express、PostgreSQL、Go 方向只允许作为迁移参考。
|
- 验证方式:开发前优先阅读当前后端架构文档;旧 `server-node`、Express、PostgreSQL、Go 方向只允许作为迁移参考。
|
||||||
- 关联文档:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`、`AGENTS.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 2026-04-28/29 server-rs DDD 分层与契约矩阵冻结
|
## 2026-04-28/29 server-rs DDD 分层与契约矩阵冻结
|
||||||
|
|
||||||
@@ -490,28 +506,28 @@
|
|||||||
- 决策:按 DDD 总纲和 G1 契约/路由矩阵开发:`module-*` 承载领域,`spacetime-module` 承载表和事务,`spacetime-client` 承载 facade,`api-server` 承载 HTTP/SSE/BFF,`platform-*` 承载外部副作用,`shared-contracts` 承载 DTO。
|
- 决策:按 DDD 总纲和 G1 契约/路由矩阵开发:`module-*` 承载领域,`spacetime-module` 承载表和事务,`spacetime-client` 承载 facade,`api-server` 承载 HTTP/SSE/BFF,`platform-*` 承载外部副作用,`shared-contracts` 承载 DTO。
|
||||||
- 影响范围:server-rs 全部 crate、前端 API client、SpacetimeDB schema、旧接口清理。
|
- 影响范围:server-rs 全部 crate、前端 API client、SpacetimeDB schema、旧接口清理。
|
||||||
- 验证方式:执行任务前对照 DDD 总纲、并行任务清单、G1 矩阵;提交前运行相关 DDD 边界检查和定向测试。
|
- 验证方式:执行任务前对照 DDD 总纲、并行任务清单、G1 矩阵;提交前运行相关 DDD 边界检查和定向测试。
|
||||||
- 关联文档:`SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`、`SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`、`SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## SpacetimeDB 表结构变更必须显式维护迁移与表目录
|
## SpacetimeDB 表结构变更必须显式维护迁移与表目录
|
||||||
|
|
||||||
- 背景:SpacetimeDB 的 schema 迁移模型不同于 PostgreSQL,部分变更会触发冲突或拒绝自动迁移。
|
- 背景:SpacetimeDB 的 schema 迁移模型不同于 PostgreSQL,部分变更会触发冲突或拒绝自动迁移。
|
||||||
- 决策:凡涉及 table、reducer、procedure、row shape 或 binding 变化,必须同步 `migration.rs`、表目录和生成绑定;涉及 private 表迁移时按 JSON 导入导出和分片导入流程处理。
|
- 决策:凡涉及 table、reducer、procedure、row shape 或 binding 变化,必须同步 `migration.rs`、表目录和生成绑定;涉及 private 表迁移时按 JSON 导入导出和分片导入流程处理。
|
||||||
- 影响范围:`server-rs/crates/spacetime-module`、`spacetime-client` bindings、`SPACETIMEDB_TABLE_CATALOG.md`、部署/发布脚本。
|
- 影响范围:`server-rs/crates/spacetime-module`、`spacetime-client` bindings、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、部署/发布脚本。
|
||||||
- 验证方式:发布前检查 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md` 清单,更新 `SPACETIMEDB_TABLE_CATALOG.md`,执行生成绑定和相关测试。
|
- 验证方式:发布前检查 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 清单,更新 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`,执行生成绑定和相关测试。
|
||||||
- 关联文档:`SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`、`SPACETIMEDB_TABLE_CATALOG.md`、`SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 生产部署切换到 systemd + Nginx + 自托管 SpacetimeDB
|
## 生产部署切换到 systemd + Nginx + 自托管 SpacetimeDB
|
||||||
|
|
||||||
- 背景:旧一体化启动脚本和历史 Jenkinsfile 已不再是生产发布唯一入口。
|
- 背景:旧一体化启动脚本和历史 Jenkinsfile 已不再是生产发布唯一入口。
|
||||||
- 决策:生产部署以 systemd 托管 SpacetimeDB 与 Rust `api-server`,Nginx 负责站点和代理,生产 Jenkinsfile 按 web/api/stdB module/build/deploy/publish 拆分。
|
- 决策:生产部署以 systemd 托管 SpacetimeDB 与 Rust `api-server`,Nginx 负责站点和代理,生产 Jenkinsfile 按 web/api/stdB module/build/deploy/publish 拆分。
|
||||||
- 影响范围:部署脚本、服务器目录、维护模式、Jenkins、Nginx、systemd 服务。
|
- 影响范围:部署脚本、服务器目录、维护模式、Jenkins、Nginx、systemd 服务。
|
||||||
- 验证方式:生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
- 验证方式:生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
- 关联文档:`PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 个人任务与埋点首版边界冻结
|
## 个人任务与埋点首版边界冻结
|
||||||
|
|
||||||
- 背景:“我的”Tab、任务、奖励、钱包和埋点涉及用户、运营、分析多条链路,需要避免范围泛化。
|
- 背景:“我的”Tab、任务、奖励、钱包和埋点涉及用户、运营、分析多条链路,需要避免范围泛化。
|
||||||
- 决策:埋点原始事实进入 `tracking_event`,聚合投影进入 `tracking_daily_stat`;个人任务配置/进度/领奖/钱包分别进入 `profile_task_config`、`profile_task_progress`、`profile_task_reward_claim`、`profile_wallet_ledger`;首版个人任务 scope 仅支持 `user`。
|
- 决策:埋点原始事实进入 `tracking_event`,聚合投影进入 `tracking_daily_stat`;个人任务配置/进度/领奖/钱包分别进入 `profile_task_config`、`profile_task_progress`、`profile_task_reward_claim`、`profile_wallet_ledger`;首版个人任务 scope 仅支持 `user`。
|
||||||
- 影响范围:用户侧任务中心、后台任务配置、运营查询、埋点查询、钱包流水。
|
- 影响范围:用户侧任务中心、后台任务配置、运营查询、埋点查询、钱包流水。
|
||||||
- 验证方式:非 `user` scope 的个人任务配置应被 API 和领域构造层拒绝;任务查询与埋点查询分别放在 `docs/operations/` 和 `docs/tracking/`。
|
- 验证方式:非 `user` scope 的个人任务配置应被 API 和领域构造层拒绝;任务查询与埋点查询口径统一维护在当前开发运维文档。
|
||||||
- 关联文档:`PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`、`RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md`、`ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`。
|
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|||||||
@@ -1,40 +1,20 @@
|
|||||||
# 开发工作流
|
# 开发工作流
|
||||||
|
|
||||||
> 用途:给本地 Hermes 和开发人员提供统一的开发、测试、提交流程。具体命令以 `package.json`、`server-rs/Cargo.toml`、`AGENTS.md` 和相关 `docs/` 最新文档为准。
|
更新时间:`2026-05-15`
|
||||||
|
|
||||||
## 标准任务流程
|
## 标准流程
|
||||||
|
|
||||||
```text
|
```text
|
||||||
同步代码 → 读取 AGENTS.md → 读取 .hermes/shared-memory → 查找/完善 docs → 制定计划 → 小步实现 → 本地验证 → 更新文档/记忆 → 提交
|
同步代码 -> 读取 AGENTS.md -> 读取 .hermes/shared-memory -> 查当前 docs -> 小步实现 -> 本地验证 -> 更新 docs / .hermes -> 提交
|
||||||
```
|
```
|
||||||
|
|
||||||
## 建议启动方式
|
当前 `docs/` 已压缩为少量融合文档。复杂任务优先读:
|
||||||
|
|
||||||
在项目根目录启动 Hermes:
|
1. `docs/README.md`
|
||||||
|
2. `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
|
||||||
```bash
|
3. `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
|
||||||
cd /path/to/Genarrative
|
4. `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||||
hermes
|
5. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||||
```
|
|
||||||
|
|
||||||
在本机当前常见路径为:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/home/dsk/workspace/Genarrative
|
|
||||||
```
|
|
||||||
|
|
||||||
其他开发者以自己本地实际路径为准,不要把个人绝对路径写入共享文档作为通用规则。
|
|
||||||
|
|
||||||
## 开发前检查清单
|
|
||||||
|
|
||||||
- [ ] 当前分支是否正确
|
|
||||||
- [ ] 是否已拉取最新代码
|
|
||||||
- [ ] 是否阅读 `AGENTS.md`
|
|
||||||
- [ ] 是否阅读 `.hermes/shared-memory/` 相关文件
|
|
||||||
- [ ] 是否阅读 `README.md` 中的运行和检查命令
|
|
||||||
- [ ] 是否阅读 `docs/README.md` 及任务相关分类 README
|
|
||||||
- [ ] 是否存在足够具体的 PRD / 设计 / 技术文档
|
|
||||||
- [ ] 是否明确测试、验收和文档更新方式
|
|
||||||
|
|
||||||
## 本地运行命令
|
## 本地运行命令
|
||||||
|
|
||||||
@@ -44,19 +24,12 @@ hermes
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
完整联调开发环境:
|
完整联调:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
该命令会启动:
|
|
||||||
|
|
||||||
- SpacetimeDB standalone
|
|
||||||
- Rust `api-server`
|
|
||||||
- 主站 Vite
|
|
||||||
- 后台 Vite
|
|
||||||
|
|
||||||
单独启动前端:
|
单独启动前端:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -69,15 +42,7 @@ npm run dev:web
|
|||||||
npm run api-server
|
npm run api-server
|
||||||
```
|
```
|
||||||
|
|
||||||
该命令会保留终端实时输出,并把同一份输出持久化到 `logs/api-server/api-server-<timestamp>.log`。完整联调入口 `npm run dev` / `npm run dev:rust` 启动的 Rust `api-server` 也会写入 `logs/api-server/api-server-dev-rust-<timestamp>.log`。如需改写路径,可设置 `GENARRATIVE_API_SERVER_LOG_FILE`;如只改目录,可设置 `GENARRATIVE_API_SERVER_LOG_DIR`。
|
后台前端:
|
||||||
|
|
||||||
查看本地 Rust/SpacetimeDB 日志:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev:rust:logs
|
|
||||||
```
|
|
||||||
|
|
||||||
后台管理前端:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run admin-web:dev
|
npm run admin-web:dev
|
||||||
@@ -85,128 +50,82 @@ npm run admin-web:build
|
|||||||
npm run admin-web:typecheck
|
npm run admin-web:typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
SpacetimeDB bindings 生成:
|
SpacetimeDB bindings:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run spacetime:generate
|
npm run spacetime:generate
|
||||||
```
|
```
|
||||||
|
|
||||||
## 常用检查命令
|
## 常用检查
|
||||||
|
|
||||||
- 后端通用用户行为埋点统一通过 `record_tracking_event_and_return` procedure、`SpacetimeRuntimeClient::record_tracking_event(...)` 与 api-server `tracking` 中间件写入 `tracking_event` / `tracking_daily_stat`;后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 默认排除;作品级游玩埋点统一使用 `work_play_start`,详细事件清单见 `docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md`。
|
|
||||||
|
|
||||||
编码检查:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run check:encoding
|
npm run check:encoding
|
||||||
```
|
npm run check:spacetime-schema
|
||||||
|
npm run check:server-rs-ddd
|
||||||
ESLint:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run lint:eslint
|
npm run lint:eslint
|
||||||
```
|
|
||||||
|
|
||||||
类型检查:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run typecheck
|
npm run typecheck
|
||||||
```
|
|
||||||
|
|
||||||
综合 lint:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
测试:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run test
|
npm run test
|
||||||
```
|
|
||||||
|
|
||||||
生产构建:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
npm run build
|
||||||
```
|
|
||||||
|
|
||||||
内容检查:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run check:data
|
|
||||||
npm run check:overrides
|
|
||||||
npm run check:smoke
|
|
||||||
npm run check:content
|
npm run check:content
|
||||||
```
|
```
|
||||||
|
|
||||||
全量检查:
|
综合检查:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
npm run lint
|
||||||
npm run check
|
npm run check
|
||||||
```
|
```
|
||||||
|
|
||||||
DDD 边界检查:
|
视觉小说门禁:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run check:server-rs-ddd
|
npm run check:visual-novel-vn11
|
||||||
|
npm run check:visual-novel-vn12
|
||||||
```
|
```
|
||||||
|
|
||||||
## 后端相关默认验证
|
## 后端默认验证
|
||||||
|
|
||||||
后端修改后,按 DDD 文档中的验收命令执行。涉及 API smoke 时:
|
后端代码修改后按范围选择:
|
||||||
|
|
||||||
- 使用 `npm run api-server` 重新拉起后端。
|
- `cargo test -p <crate> --manifest-path server-rs/Cargo.toml`
|
||||||
- 禁止使用 `npm run api-server:maincloud`、`npm.cmd run api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径;这些只属于历史残留。
|
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||||
- 检查 `/healthz`。
|
- `cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`
|
||||||
- 执行对应自动测试。
|
- `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`
|
||||||
- 涉及 SpacetimeDB 表、reducer、procedure、row shape 或绑定变化时,同步更新 `migration.rs`、表目录和生成绑定。
|
- `npm run check:server-rs-ddd`
|
||||||
- SpacetimeDB 已有表新增字段必须放在 Rust 表结构体最后,并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划,再同步更新 `server-rs/crates/spacetime-module/src/migration.rs`、表目录和生成绑定。
|
- `npm run api-server` 后请求 `/healthz`
|
||||||
- 修改 SpacetimeDB schema 后运行 `npm run check:spacetime-schema`,用自动检查拦截缺 default、插入中间、字段删除/改名/重排/改类型,以及漏改迁移、表目录或绑定。
|
|
||||||
|
|
||||||
关键文档:
|
涉及 SpacetimeDB table、reducer、procedure、row shape 或 bindings 时,还必须运行:
|
||||||
|
|
||||||
- `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
```bash
|
||||||
- `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
|
npm run spacetime:generate
|
||||||
- `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
|
npm run check:spacetime-schema
|
||||||
- `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
|
```
|
||||||
- `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
|
|
||||||
- `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
|
|
||||||
- `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`
|
|
||||||
|
|
||||||
## 前端相关默认验证
|
禁止使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。
|
||||||
|
|
||||||
前端修改后,应根据修改范围选择:
|
## 前端默认验证
|
||||||
|
|
||||||
|
前端修改后按范围选择:
|
||||||
|
|
||||||
- `npm run check:encoding`
|
- `npm run check:encoding`
|
||||||
- `npm run lint:eslint`
|
- `npm run lint:eslint`
|
||||||
- `npm run typecheck`
|
- `npm run typecheck`
|
||||||
- `npm run test`
|
- `npm run test -- <具体测试文件>`
|
||||||
- 页面交互 smoke
|
- 页面交互 smoke
|
||||||
- 移动端视口检查
|
- 移动端视口检查
|
||||||
|
|
||||||
前端原则:
|
UI 相关修改重点检查:
|
||||||
|
|
||||||
- 移动端优先,再兼容网页端。
|
- 390px 左右移动端宽度不横向溢出。
|
||||||
- 页面只展示后端返回的状态,不自行计算结论型业务状态。
|
- 输入法弹出时平台画布不被压缩。
|
||||||
- 创作中心入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;前端只在 `platformEntryCreationTypes.ts` 做展示派生,api-server 路由熔断也使用同一份配置,禁止恢复前端硬编码入口配置文件。
|
- 弹窗、抽屉和独立面板没有实现成当前面板下方展开。
|
||||||
- 优先复用现有面板、抽屉、弹窗,不新建独立大系统。
|
- UI 不包含默认规则说明长文。
|
||||||
- 不在 UI 中默认写功能说明类文本。
|
- 私有图片和音频不裸请求 `/generated-*`。
|
||||||
- 弹出独立面板的交互不要实现成在当前面板下方追加内容。
|
|
||||||
|
|
||||||
## 文档更新规则
|
## 文档更新
|
||||||
|
|
||||||
- 工程修改要同步更新对应文档。
|
- 工程修改要同步更新当前 `docs/` 文档。
|
||||||
- 如果没有现成文档,新文档统一放入 `docs/` 下合适分类。
|
- 新增稳定知识优先合并进现有 4 份文档;只有现有文档无法容纳时才新增带 `【标签名】` 的 Markdown。
|
||||||
- `.hermes/shared-memory/` 只记录高频、长期、团队共享的摘要和索引,不替代完整 PRD/技术文档。
|
- `.hermes/shared-memory/` 只记录高频、长期、团队共享的摘要和索引。
|
||||||
|
- 阶段性流水账、一次性计划和已关闭 TODO 不再作为长期仓库文档依据。
|
||||||
- 如果 `.hermes/shared-memory/` 与代码或 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正共享记忆。
|
- 如果 `.hermes/shared-memory/` 与代码或 `docs/` 冲突,以代码和最新 `docs/` 为准,并同步修正共享记忆。
|
||||||
|
|
||||||
## 提交前建议让 Hermes 执行
|
|
||||||
|
|
||||||
```text
|
|
||||||
请检查当前 git diff,指出:
|
|
||||||
1. 是否违反 AGENTS.md 或 .hermes/shared-memory 约定;
|
|
||||||
2. 是否需要补充 docs;
|
|
||||||
3. 是否有长期知识需要写入 .hermes/shared-memory;
|
|
||||||
4. 建议的测试命令和提交信息。
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,103 +1,51 @@
|
|||||||
# 文档地图与阅读索引
|
# 文档地图与阅读索引
|
||||||
|
|
||||||
> 用途:根据 `README.md`、`AGENTS.md` 和 `docs/` 下文档索引整理团队记忆入口,帮助本地 Hermes 快速选择应该读哪些资料。
|
更新时间:`2026-05-15`
|
||||||
|
|
||||||
## 全局入口
|
## 当前文档入口
|
||||||
|
|
||||||
| 场景 | 优先阅读 |
|
| 场景 | 优先阅读 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 建立项目背景 | `README.md`、`AGENTS.md`、`.hermes/shared-memory/project-overview.md` |
|
| 建立项目背景 | `README.md`、`AGENTS.md`、`.hermes/shared-memory/project-overview.md` |
|
||||||
| 找文档分类 | `docs/README.md` |
|
| 找当前文档 | `docs/README.md` |
|
||||||
| 开发方法论 | `docs/experience/README.md` |
|
| 产品、命名、UI、协作和废弃路线 | `docs/【项目基线】当前产品与工程约束-2026-05-15.md` |
|
||||||
| 查风险与历史问题 | `docs/audits/README.md` |
|
| 后端、DDD、API、SpacetimeDB schema 和表目录 | `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` |
|
||||||
| 做玩法/交互/系统设计 | `docs/design/README.md` |
|
| 创作入口、草稿架和玩法链路 | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` |
|
||||||
| 做技术实现/后端/部署 | `docs/technical/README.md` |
|
| 本地启动、验证、部署、埋点和运营查询 | `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` |
|
||||||
| 排期与拆阶段 | `docs/planning/README.md` |
|
| UI 像素资产与 9-slice 规范 | `UI_CODING_STANDARD.md` |
|
||||||
| 查脚本/Function/prompt/职责地图 | `docs/reference/README.md` |
|
|
||||||
| 查埋点 SQL | `docs/tracking/README.md` |
|
|
||||||
| 查运营/任务/钱包对账 SQL | `docs/operations/README.md` |
|
|
||||||
| 查 PRD | `docs/prd/` |
|
|
||||||
|
|
||||||
## docs 分类规则
|
## 阅读顺序
|
||||||
|
|
||||||
- `experience/`:方法论、交接经验、长期有效的开发结论。
|
|
||||||
- `audits/`:现状扫描、问题定位、是否达标的审查类文档。
|
|
||||||
- `design/`:玩法机制、叙事关系、系统结构设计。
|
|
||||||
- `technical/`:技术选型、实现路线、竞品/产品形态拆解。
|
|
||||||
- `planning/`:阶段优先级与推进顺序。
|
|
||||||
- `reference/`:目录、速查、检索辅助。
|
|
||||||
- `tracking/`:埋点原始事实和聚合投影查询。
|
|
||||||
- `operations/`:后台运营核查、对账和排障查询。
|
|
||||||
- `prd/`:产品需求与阶段计划。
|
|
||||||
|
|
||||||
新增 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`。标签用于跨目录检索,不替代 `docs/` 的目录分类;历史文档不要求批量重命名。
|
|
||||||
|
|
||||||
## 推荐阅读顺序
|
|
||||||
|
|
||||||
通用复杂任务:
|
通用复杂任务:
|
||||||
|
|
||||||
1. `AGENTS.md`
|
1. `AGENTS.md`
|
||||||
2. `.hermes/shared-memory/`
|
2. `.hermes/shared-memory/`
|
||||||
3. `docs/README.md`
|
3. `docs/README.md`
|
||||||
4. `docs/experience/README.md`
|
4. 与任务匹配的当前融合文档
|
||||||
5. `docs/audits/README.md`
|
|
||||||
6. 任务对应分类下的 README 和具体文档
|
|
||||||
|
|
||||||
后端 / 数据真相 / SpacetimeDB:
|
后端 / 数据真相 / SpacetimeDB:
|
||||||
|
|
||||||
1. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
1. `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
|
||||||
2. `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
|
2. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||||
3. `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
|
3. 对应 crate README 或源码
|
||||||
4. `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
|
|
||||||
5. `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
|
玩法 / 创作入口 / 运行态:
|
||||||
6. `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
|
|
||||||
7. 具体模块方案文档
|
1. `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||||
|
2. `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
|
||||||
|
3. 相关前端组件、service、shared contract 和后端 module
|
||||||
|
|
||||||
生产部署 / 服务器 / Jenkins:
|
生产部署 / 服务器 / Jenkins:
|
||||||
|
|
||||||
1. `docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
|
1. `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||||
2. 需要迁移时再看 `SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md`
|
2. `deploy/env/api-server.env.example`
|
||||||
3. 历史 Jenkins / CORS / 本地远端脚本文档只作追溯,不作为当前入口
|
3. `deploy/nginx/README.md`
|
||||||
|
|
||||||
RPG 创作与运行时链路:
|
## 维护规则
|
||||||
|
|
||||||
1. `docs/reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md`
|
- 当前 `docs/` 只保留少量融合文档。
|
||||||
2. `docs/technical/CREATION_FLOW_CHAIN_REFACTOR_EXECUTION_PLAN_2026-04-21.md`
|
- 新增工程实现时,如果已有对应当前文档,必须同步更新。
|
||||||
3. `docs/technical/RPG_ENTRY_RUNTIME_CHAIN_REFACTOR_EXECUTION_PLAN_2026-04-21.md`
|
- 如果没有合适位置,新文档文件名必须使用 `【标签名】中文标题-YYYY-MM-DD.md`。
|
||||||
4. 相关工作包 progress / closure 文档
|
- 阶段性流水账、一次性修复记录和已关闭实验不要再新增为长期文档。
|
||||||
|
- 阶段性计划和一次性 TODO 不再作为长期文档目录;需要保留的决策、流程和坑点应进入 `docs/` 当前文档或 `.hermes/shared-memory/`。
|
||||||
移动端 UI / 游戏 UI:
|
|
||||||
|
|
||||||
1. `docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`
|
|
||||||
2. `UI_CODING_STANDARD.md`
|
|
||||||
3. 相关 `docs/design/` 文档
|
|
||||||
|
|
||||||
创作 Agent / 自定义世界:
|
|
||||||
|
|
||||||
1. `docs/design/CUSTOM_WORLD_CREATOR_INPUT_AND_AI_BOUNDARY_DESIGN_2026-04-06.md`
|
|
||||||
2. `docs/design/CUSTOM_WORLD_CREATOR_MANUAL_AI_SYSTEM_BALANCE_DESIGN_2026-04-12.md`
|
|
||||||
3. `docs/design/CUSTOM_WORLD_CREATOR_PURE_AGENT_COMPARISON_AND_CONVERSION_DESIGN_2026-04-12.md`
|
|
||||||
4. `docs/technical/UNIFIED_CREATION_AGENT_CHAT_FRAMEWORK_2026-04-22.md`
|
|
||||||
5. 相关 `SPACETIMEDB_CUSTOM_WORLD_*` 技术方案
|
|
||||||
|
|
||||||
拼图 / 大鱼 / Match3D:
|
|
||||||
|
|
||||||
- 拼图:优先看 `PUZZLE_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md`、`PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md` 和相关 Puzzle 技术文档。
|
|
||||||
- 大鱼吃小鱼:优先看 `BIG_FISH_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md` 和相关 Big Fish 技术/经验文档。
|
|
||||||
- 抓大鹅 Match3D:优先看 `docs/prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md`、`MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md` 和相关 Match3D 技术文档。
|
|
||||||
|
|
||||||
个人任务 / 埋点 / 运营查询:
|
|
||||||
|
|
||||||
1. `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
|
|
||||||
2. `docs/technical/RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md`
|
|
||||||
3. `docs/technical/ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`
|
|
||||||
4. `docs/tracking/TRACKING_QUERY_PLAYBOOK_2026-05-03.md`
|
|
||||||
5. `docs/operations/PROFILE_TASK_QUERY_PLAYBOOK_2026-05-03.md`
|
|
||||||
|
|
||||||
## 文档维护规则
|
|
||||||
|
|
||||||
- 新增工程实现时,如果已有对应文档,必须同步更新。
|
|
||||||
- 如果没有对应文档,新文档放入 `docs/` 下合适分类。
|
|
||||||
- 新文档文件名必须使用 `【标签名】` 前缀,标题尽量保留中文语义,日期使用 `YYYY-MM-DD`。
|
|
||||||
- `.hermes/shared-memory/` 只保留跨任务、跨成员、高频使用的摘要和索引。
|
|
||||||
- 如果文档与代码冲突,先确认代码事实,再更新过期文档和共享记忆。
|
- 如果文档与代码冲突,先确认代码事实,再更新过期文档和共享记忆。
|
||||||
|
|||||||
@@ -22,12 +22,12 @@
|
|||||||
- 验证:拼图入口测试仍可通过,且新组件可通过不同页面复用而不需要复制上传卡实现。
|
- 验证:拼图入口测试仍可通过,且新组件可通过不同页面复用而不需要复制上传卡实现。
|
||||||
- 关联:`src/components/common/CreativeImageInputPanel.tsx`、`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`。
|
- 关联:`src/components/common/CreativeImageInputPanel.tsx`、`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`。
|
||||||
|
|
||||||
## 汪汪声浪入口不要再回到独立配置阶段
|
## 汪汪声浪重新开放时不要再回到独立配置阶段
|
||||||
|
|
||||||
- 现象:汪汪声浪入口如果继续切换到独立配置阶段,会和拼图、抓大鹅的创作页内嵌结构不一致,用户会感觉入口跳页。
|
- 现象:汪汪声浪入口如果继续切换到独立配置阶段,会和拼图、抓大鹅的创作页内嵌结构不一致,用户会感觉入口跳页。
|
||||||
- 原因:旧实现把 `bark-battle` 单独挂到 `bark-battle-config` selectionStage,而不是复用创作 Tab 里的模板区。
|
- 原因:旧实现把 `bark-battle` 单独挂到 `bark-battle-config` selectionStage,而不是复用创作 Tab 里的模板区。
|
||||||
- 处理:入口点击只设置 `activeCreationFormType = 'bark-battle'` 并回到创作 Tab;`BarkBattleConfigEditor` 作为内嵌表单使用,默认隐藏返回按钮和页面标题;runtime `onExit` 重新回到创作 Tab 的汪汪声浪模板。
|
- 处理:当前 `bark-battle` 入口为 `visible=true`、`open=false`,展示为“敬请期待”,api-server 会熔断 `/api/creation/bark-battle/*` 与 `/api/runtime/bark-battle/*`。后续重新开放时,入口点击只设置 `activeCreationFormType = 'bark-battle'` 并回到创作 Tab;`BarkBattleConfigEditor` 作为内嵌表单使用,默认隐藏返回按钮和页面标题;runtime `onExit` 重新回到创作 Tab 的汪汪声浪模板。
|
||||||
- 验证:点击汪汪声浪后直接看到创作页内嵌表单,不再出现独立配置页;测试应覆盖内嵌表单与 runtime 返回路径。
|
- 验证:当前点击汪汪声浪不进入创作表单,直连创作 / 运行态 API 返回 `creation_entry_disabled`;重新开放时再覆盖内嵌表单与 runtime 返回路径。
|
||||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`。
|
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`。
|
||||||
|
|
||||||
## 抓大鹅批量重新生成物品不要新增 itemId
|
## 抓大鹅批量重新生成物品不要新增 itemId
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
- 原因:重新生成和批量新增共用 `item-assets` 接口,如果前端不传 `mode = "replace"`,或后端替换时重新分配 `itemId` / 追加未匹配名称,就会破坏 `generatedItemAssets` 顺序和运行态类型映射。
|
- 原因:重新生成和批量新增共用 `item-assets` 接口,如果前端不传 `mode = "replace"`,或后端替换时重新分配 `itemId` / 追加未匹配名称,就会破坏 `generatedItemAssets` 顺序和运行态类型映射。
|
||||||
- 处理:批量重新生成只提交当前素材列表中能匹配到的名称,并传 `mode = "replace"`;后端只对同名已有素材生成新图片,合并时保留原 `itemId`、`itemName`、模型兼容字段、UI 背景和历史音频字段,未匹配名称直接忽略且不计费。
|
- 处理:批量重新生成只提交当前素材列表中能匹配到的名称,并传 `mode = "replace"`;后端只对同名已有素材生成新图片,合并时保留原 `itemId`、`itemName`、模型兼容字段、UI 背景和历史音频字段,未匹配名称直接忽略且不计费。
|
||||||
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖前端提交口径,`cargo test -p api-server match3d_item_asset --manifest-path server-rs\Cargo.toml` 和 `cargo test -p api-server match3d_regenerated_asset --manifest-path server-rs\Cargo.toml` 覆盖后端替换计划与身份保留。
|
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖前端提交口径,`cargo test -p api-server match3d_item_asset --manifest-path server-rs\Cargo.toml` 和 `cargo test -p api-server match3d_regenerated_asset --manifest-path server-rs\Cargo.toml` 覆盖后端替换计划与身份保留。
|
||||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`server-rs/crates/api-server/src/match3d.rs`、`packages/shared/src/contracts/match3dWorks.ts`、`server-rs/crates/shared-contracts/src/match3d_works.rs`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅生成封面图不要覆盖物品素材或配置
|
## 抓大鹅生成封面图不要覆盖物品素材或配置
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
- 原因:封面生成属于定向图片槽位更新;若后端复用草稿编译写回,可能按 session config 重算作品行。即使后端已修正,前端若直接把封面接口返回的整份 `item` 当成最新 profile,也可能用旧回包里的空 `generatedItemAssets` 覆盖当前页面素材。
|
- 原因:封面生成属于定向图片槽位更新;若后端复用草稿编译写回,可能按 session config 重算作品行。即使后端已修正,前端若直接把封面接口返回的整份 `item` 当成最新 profile,也可能用旧回包里的空 `generatedItemAssets` 覆盖当前页面素材。
|
||||||
- 处理:`POST /api/creation/match3d/works/{profileId}/cover-image` 只保存 `coverImageSrc` / `coverAssetId` 等封面字段,保留当前 `generated_item_assets_json`、难度、消除次数、题材和描述;前端收到回包后只合并 `coverImageSrc`,继续保留当前可见 `generatedItemAssets`、`clearCount` 和 `difficulty`。
|
- 处理:`POST /api/creation/match3d/works/{profileId}/cover-image` 只保存 `coverImageSrc` / `coverAssetId` 等封面字段,保留当前 `generated_item_assets_json`、难度、消除次数、题材和描述;前端收到回包后只合并 `coverImageSrc`,继续保留当前可见 `generatedItemAssets`、`clearCount` 和 `difficulty`。
|
||||||
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖旧回包不覆盖物品素材和配置;`cargo test -p api-server match3d_cover --manifest-path server-rs\Cargo.toml` 覆盖封面提示词与参考图链路。
|
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖旧回包不覆盖物品素材和配置;`cargo test -p api-server match3d_cover --manifest-path server-rs\Cargo.toml` 覆盖封面提示词与参考图链路。
|
||||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`server-rs/crates/api-server/src/match3d.rs`、`server-rs/crates/spacetime-module/src/match3d/mod.rs`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## OSS V4 签名时间和 bucket/object_key 兼容
|
## OSS V4 签名时间和 bucket/object_key 兼容
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
- 原因:生成音乐转存到 OSS 私有对象后,`audioSrc` 是 generated legacy path;浏览器 `<audio>` 不能像公开静态资源一样直接请求裸路径。另一个常见误判是浏览器拒绝自动播放,资源已经进入运行态但开局第一次 `audio.play()` 被拦截。
|
- 原因:生成音乐转存到 OSS 私有对象后,`audioSrc` 是 generated legacy path;浏览器 `<audio>` 不能像公开静态资源一样直接请求裸路径。另一个常见误判是浏览器拒绝自动播放,资源已经进入运行态但开局第一次 `audio.play()` 被拦截。
|
||||||
- 处理:结果页试听控件和运行态隐藏 `<audio>` 设置 `src` 前,都先通过 `useResolvedAssetReadUrl` 或 `resolveAssetReadUrl` 换签;签名未就绪时不要回退请求裸 generated 路径。运行态自动播放失败只静默兜底,但玩家首次按下拼图块或点击抓大鹅物品时要重试同一个背景音乐播放函数。拼图读取 `currentLevel.backgroundMusic.audioSrc`,抓大鹅读取 `generatedItemAssets[].backgroundMusic.audioSrc`。
|
- 处理:结果页试听控件和运行态隐藏 `<audio>` 设置 `src` 前,都先通过 `useResolvedAssetReadUrl` 或 `resolveAssetReadUrl` 换签;签名未就绪时不要回退请求裸 generated 路径。运行态自动播放失败只静默兜底,但玩家首次按下拼图块或点击抓大鹅物品时要重试同一个背景音乐播放函数。拼图读取 `currentLevel.backgroundMusic.audioSrc`,抓大鹅读取 `generatedItemAssets[].backgroundMusic.audioSrc`。
|
||||||
- 验证:结果页试听和运行态 `<audio loop>` 的 `src` 为签名 URL 或公开 URL;拼图/抓大鹅运行态首次局内交互后会再次尝试播放背景音乐;`npm run typecheck` 不报契约字段缺失,后端 run response 带 `backgroundMusic`。
|
- 验证:结果页试听和运行态 `<audio loop>` 的 `src` 为签名 URL 或公开 URL;拼图/抓大鹅运行态首次局内交互后会再次尝试播放背景音乐;`npm run typecheck` 不报契约字段缺失,后端 run response 带 `backgroundMusic`。
|
||||||
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅背景音乐是作品级字段但暂存在首个物品素材
|
## 抓大鹅背景音乐是作品级字段但暂存在首个物品素材
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
- 原因:当前表结构没有作品级音频字段,背景音乐暂存在 `generatedItemAssets[]`。如果 action response 的 draft assets 缺音乐,前端又优先用它覆盖 work detail,或音乐落在非首个素材而结果页只读 `assetDrafts[0].backgroundMusic`,就会丢掉已生成音乐。
|
- 原因:当前表结构没有作品级音频字段,背景音乐暂存在 `generatedItemAssets[]`。如果 action response 的 draft assets 缺音乐,前端又优先用它覆盖 work detail,或音乐落在非首个素材而结果页只读 `assetDrafts[0].backgroundMusic`,就会丢掉已生成音乐。
|
||||||
- 处理:前端统一使用 `normalizeMatch3DGeneratedItemAssetsForRuntime` / `mergeMatch3DGeneratedItemAssetsForRuntime`:把任意素材上的 `backgroundMusic` 与音乐元信息迁移到首个素材,清空其它素材上的作品级音乐字段;action draft assets 与 work detail assets 按 `itemId` 合并,保留详情里的音乐、UI 背景和点击音效。
|
- 处理:前端统一使用 `normalizeMatch3DGeneratedItemAssetsForRuntime` / `mergeMatch3DGeneratedItemAssetsForRuntime`:把任意素材上的 `backgroundMusic` 与音乐元信息迁移到首个素材,清空其它素材上的作品级音乐字段;action draft assets 与 work detail assets 按 `itemId` 合并,保留详情里的音乐、UI 背景和点击音效。
|
||||||
- 验证:`npm run test -- src\services\match3dGeneratedModelCache.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`;平台推荐流定向跑 `RpgEntryFlowShell.agent.interaction.test.tsx` 中的 Match3D runtime assets 用例;`npm run typecheck`。
|
- 验证:`npm run test -- src\services\match3dGeneratedModelCache.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`;平台推荐流定向跑 `RpgEntryFlowShell.agent.interaction.test.tsx` 中的 Match3D runtime assets 用例;`npm run typecheck`。
|
||||||
- 关联:`src/services/match3dGeneratedModelCache.ts`、`src/components/match3d-result/Match3DResultView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 中文乱码与编码风险
|
## 中文乱码与编码风险
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
- 原因:重置/修改密码会更新 `password_hash`、`password_login_enabled` 和 `token_version`,如果 API 层只更新本地 `InMemoryAuthStore`,没有调用 `sync_auth_store_snapshot_to_spacetime()`,`api-server` 重启时可能从旧的 SpacetimeDB 表或旧快照恢复账号状态。
|
- 原因:重置/修改密码会更新 `password_hash`、`password_login_enabled` 和 `token_version`,如果 API 层只更新本地 `InMemoryAuthStore`,没有调用 `sync_auth_store_snapshot_to_spacetime()`,`api-server` 重启时可能从旧的 SpacetimeDB 表或旧快照恢复账号状态。
|
||||||
- 处理:`POST /api/auth/password/change` 与 `POST /api/auth/password/reset` 成功后必须同步认证快照;启动恢复时从 SpacetimeDB 表、SpacetimeDB 快照记录和本地 `GENARRATIVE_AUTH_STORE_PATH` 文件中选择可判断的最新快照,本地文件更新时尝试回写 SpacetimeDB。
|
- 处理:`POST /api/auth/password/change` 与 `POST /api/auth/password/reset` 成功后必须同步认证快照;启动恢复时从 SpacetimeDB 表、SpacetimeDB 快照记录和本地 `GENARRATIVE_AUTH_STORE_PATH` 文件中选择可判断的最新快照,本地文件更新时尝试回写 SpacetimeDB。
|
||||||
- 验证:执行 `cargo test -p module-auth password --manifest-path server-rs/Cargo.toml` 与 `cargo test -p api-server password --manifest-path server-rs/Cargo.toml`;手测时重设密码后旧密码应失败,新密码应成功,重启后仍应保持。
|
- 验证:执行 `cargo test -p module-auth password --manifest-path server-rs/Cargo.toml` 与 `cargo test -p api-server password --manifest-path server-rs/Cargo.toml`;手测时重设密码后旧密码应失败,新密码应成功,重启后仍应保持。
|
||||||
- 关联:`server-rs/crates/api-server/src/password_management.rs`、`server-rs/crates/api-server/src/state.rs`、`docs/technical/PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅生成页只显示服务暂不可用先查 reason 和外部服务配置
|
## 抓大鹅生成页只显示服务暂不可用先查 reason 和外部服务配置
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
- 原因:配置缺失类错误通常在后端 `error.details.reason` 中给出具体缺项,前端如果只读 `details.message` 会吞掉原因;本地只配置 `ALIYUN_OSS_BUCKET` / `ALIYUN_OSS_ENDPOINT` 时,旧逻辑还会在启动期构造空 AccessKey 的 OSS 客户端并失败。抓大鹅新链路仍是 2D 生图切割,不需要也不应回退 Rodin/GLB。
|
- 原因:配置缺失类错误通常在后端 `error.details.reason` 中给出具体缺项,前端如果只读 `details.message` 会吞掉原因;本地只配置 `ALIYUN_OSS_BUCKET` / `ALIYUN_OSS_ENDPOINT` 时,旧逻辑还会在启动期构造空 AccessKey 的 OSS 客户端并失败。抓大鹅新链路仍是 2D 生图切割,不需要也不应回退 Rodin/GLB。
|
||||||
- 处理:前端 API 错误展示优先读取 `details.reason`,再读取 `details.message`,避免底层 `error sending request` 覆盖真正可操作的配置或网络原因;`api-server` 只有在 OSS 四件套齐全时初始化 OSS 客户端,部分缺失只记 warning 并让具体 generated 上传/换签接口返回 `OSS 未完成环境变量配置`。抓大鹅素材、封面和背景生成在调用 VectorEngine 前先预检 OSS,并通过 `details.missingEnv` 列出缺项;真实生成需补齐 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和完整 `ALIYUN_OSS_*` 四件套。抓大鹅 `5*5` 素材图提示词还必须要求相邻物体主体至少保留 `1/4` 单格宽度空白间距,避免切割后相邻格内容污染。
|
- 处理:前端 API 错误展示优先读取 `details.reason`,再读取 `details.message`,避免底层 `error sending request` 覆盖真正可操作的配置或网络原因;`api-server` 只有在 OSS 四件套齐全时初始化 OSS 客户端,部分缺失只记 warning 并让具体 generated 上传/换签接口返回 `OSS 未完成环境变量配置`。抓大鹅素材、封面和背景生成在调用 VectorEngine 前先预检 OSS,并通过 `details.missingEnv` 列出缺项;真实生成需补齐 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和完整 `ALIYUN_OSS_*` 四件套。抓大鹅 `5*5` 素材图提示词还必须要求相邻物体主体至少保留 `1/4` 单格宽度空白间距,避免切割后相邻格内容污染。
|
||||||
- 验证:`npm run test -- src/services/apiClient.test.ts` 覆盖 `details.reason`;`cargo test -p api-server state --manifest-path server-rs/Cargo.toml` 覆盖半配置 OSS 不阻断启动;`npm run api-server` 后按实际 `GENARRATIVE_API_PORT` 请求 `/healthz`,不要默认打 `3100`。
|
- 验证:`npm run test -- src/services/apiClient.test.ts` 覆盖 `details.reason`;`cargo test -p api-server state --manifest-path server-rs/Cargo.toml` 覆盖半配置 OSS 不阻断启动;`npm run api-server` 后按实际 `GENARRATIVE_API_PORT` 请求 `/healthz`,不要默认打 `3100`。
|
||||||
- 关联:`packages/shared/src/http.ts`、`server-rs/crates/api-server/src/state.rs`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`、`docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
2026-05-14 补充:抓大鹅“物品素材 sheet”已改用 VectorEngine Gemini `gemini-3-pro-image-preview` 原生 `generateContent`,真实生成读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;封面和 `9:16` 背景图走 VectorEngine `/v1/images/generations`,`1:1` 容器 UI 走 VectorEngine `/v1/images/edits` multipart 参考图链路。排查素材 sheet 时看请求路径是否为 `/v1beta/models/gemini-3-pro-image-preview:generateContent?key=...`,响应图片在 `candidates[].content.parts[].inlineData.data` / `inline_data.data`,不要再按 APIMart `/images/generations` 或 `/tasks/{task_id}` 排查。
|
2026-05-14 补充:抓大鹅“物品素材 sheet”已改用 VectorEngine Gemini `gemini-3-pro-image-preview` 原生 `generateContent`,真实生成读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;封面和 `9:16` 背景图走 VectorEngine `/v1/images/generations`,`1:1` 容器 UI 走 VectorEngine `/v1/images/edits` multipart 参考图链路。排查素材 sheet 时看请求路径是否为 `/v1beta/models/gemini-3-pro-image-preview:generateContent?key=...`,响应图片在 `candidates[].content.parts[].inlineData.data` / `inline_data.data`,不要再按 APIMart `/images/generations` 或 `/tasks/{task_id}` 排查。
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
- 原因:发布按钮被 `publishReady` 直接禁用,导致未满足门槛时无法进入发布检查面板;封面编辑仍挂在作品信息 Tab,不能和发布检查一起收口。
|
- 原因:发布按钮被 `publishReady` 直接禁用,导致未满足门槛时无法进入发布检查面板;封面编辑仍挂在作品信息 Tab,不能和发布检查一起收口。
|
||||||
- 处理:发布按钮只受忙碌态控制,点击后始终打开独立发布面板;发布面板内先展示阻断项,再承载封面图上传 / AI 重绘 / 参考图编辑,满足条件后再点击 `发布到广场`。
|
- 处理:发布按钮只受忙碌态控制,点击后始终打开独立发布面板;发布面板内先展示阻断项,再承载封面图上传 / AI 重绘 / 参考图编辑,满足条件后再点击 `发布到广场`。
|
||||||
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`;`npm run typecheck`。
|
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`;`npm run typecheck`。
|
||||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`src/components/match3d-result/Match3DResultView.test.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## `.hermes` 只放共享内容,不放个人 Hermes 配置
|
## `.hermes` 只放共享内容,不放个人 Hermes 配置
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
- 原因:浏览器摄像头视频流只是舞台背景;如果热身关把 `getUserMedia` 状态当成主动作数据源,或只在 gesture 阶段消费 `useMocapInput`,就会错过 mocap 的身体中心、动作名和手部坐标。
|
- 原因:浏览器摄像头视频流只是舞台背景;如果热身关把 `getUserMedia` 状态当成主动作数据源,或只在 gesture 阶段消费 `useMocapInput`,就会错过 mocap 的身体中心、动作名和手部坐标。
|
||||||
- 处理:确认 `src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 全热身流程启用 `useMocapInput`,页面主提示展示 mocap 动作数据源状态而不是浏览器摄像头状态;确认 `src/services/useMocapInput.ts` 能解析 `/stream` 包里的 `general.body.center_norm`、`actions/action/gesture/gestures/event/name/type`、`hands[]`、`leftHand/rightHand`、`left_hand/right_hand`、左右手标记和 `open_palm/grab` 状态。`/stream` 是 WebSocket,普通 HTTP 访问返回 404 不能当成服务不可用。
|
- 处理:确认 `src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 全热身流程启用 `useMocapInput`,页面主提示展示 mocap 动作数据源状态而不是浏览器摄像头状态;确认 `src/services/useMocapInput.ts` 能解析 `/stream` 包里的 `general.body.center_norm`、`actions/action/gesture/gestures/event/name/type`、`hands[]`、`leftHand/rightHand`、`left_hand/right_hand`、左右手标记和 `open_palm/grab` 状态。`/stream` 是 WebSocket,普通 HTTP 访问返回 404 不能当成服务不可用。
|
||||||
- 验证:运行 `npx vitest run src\services\useMocapInput.test.ts src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx`,并在本地硬件服务启动后进入 `/child-motion-demo` 实测站位、招手、左右手挥动和跳跃阶段。
|
- 验证:运行 `npx vitest run src\services\useMocapInput.test.ts src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx`,并在本地硬件服务启动后进入 `/child-motion-demo` 实测站位、招手、左右手挥动和跳跃阶段。
|
||||||
- 关联:`src/services/useMocapInput.ts`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 儿童动作 Demo 左右手阶段误通过先查身体侧映射和手臂展开阈值
|
## 儿童动作 Demo 左右手阶段误通过先查身体侧映射和手臂展开阈值
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当作用户身体左/右;同时左右手阶段的目标是确认现实空间安全,需要验证手臂向外打开和上下摆动角度,不能只看手部 `x` 轨迹范围。
|
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当作用户身体左/右;同时左右手阶段的目标是确认现实空间安全,需要验证手臂向外打开和上下摆动角度,不能只看手部 `x` 轨迹范围。
|
||||||
- 处理:热身关中用户左手应消费 camera-right,用户右手应消费 camera-left;左右手阶段只在同侧肩肘腕外展、手腕非自然下垂、连续有效帧、横向范围、上下摆动范围、肩腕角度范围和上下方向变化全部达标时完成,并记录轨迹空间包络、角度范围和最大外展距离。
|
- 处理:热身关中用户左手应消费 camera-right,用户右手应消费 camera-left;左右手阶段只在同侧肩肘腕外展、手腕非自然下垂、连续有效帧、横向范围、上下摆动范围、肩腕角度范围和上下方向变化全部达标时完成,并记录轨迹空间包络、角度范围和最大外展距离。
|
||||||
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts`,确认相反侧手、自然下垂、单纯横向轨迹不会完成,真实展开上下摆动可以完成。
|
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts`,确认相反侧手、自然下垂、单纯横向轨迹不会完成,真实展开上下摆动可以完成。
|
||||||
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、`src/components/child-motion-demo/childMotionWarmupModel.ts`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 儿童动作 Demo 角色轮廓抽搐先查 mocap 坐标防抖和渲染分层
|
## 儿童动作 Demo 角色轮廓抽搐先查 mocap 坐标防抖和渲染分层
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
- 原因:`general.body.center_norm.x` 原始值逐包直接写入 `avatarX` 时,硬件坐标小噪声会直接驱动位置保持判定和 CSS 动画;如果角色外层同时承担横向定位和跳跃 `transform`,半透明 PNG 在移动时也更容易出现重采样抖动观感。
|
- 原因:`general.body.center_norm.x` 原始值逐包直接写入 `avatarX` 时,硬件坐标小噪声会直接驱动位置保持判定和 CSS 动画;如果角色外层同时承担横向定位和跳跃 `transform`,半透明 PNG 在移动时也更容易出现重采样抖动观感。
|
||||||
- 处理:mocap 身体中心进入角色位置前必须先 clamp,再经过小幅死区、低通阻尼和单包最大步长限制;键盘 A/D 调试输入仍保持即时。角色 DOM 外层只负责横向定位,内层 sprite 负责轮廓图和跳跃位移,避免同一层 `transform` 同时表达多种运动。
|
- 处理:mocap 身体中心进入角色位置前必须先 clamp,再经过小幅死区、低通阻尼和单包最大步长限制;键盘 A/D 调试输入仍保持即时。角色 DOM 外层只负责横向定位,内层 sprite 负责轮廓图和跳跃位移,避免同一层 `transform` 同时表达多种运动。
|
||||||
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts src\services\useMocapInput.test.ts src\services\child-motion-demo\childMotionDebugInput.test.ts`,并用真实硬件进入站位阶段观察小幅身体晃动不会导致角色频繁左右跳动。
|
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts src\services\useMocapInput.test.ts src\services\child-motion-demo\childMotionDebugInput.test.ts`,并用真实硬件进入站位阶段观察小幅身体晃动不会导致角色频繁左右跳动。
|
||||||
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、`src/index.css`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 宝贝识物选篮误触发先查多套判定和残余轨迹
|
## 宝贝识物选篮误触发先查多套判定和残余轨迹
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
- 处理:宝贝识物选篮只使用明确 `leftHand` / `rightHand` 的连续横向轨迹阈值;侧别为 `unknown` 的手部轨迹不参与选篮;反馈阶段清空轨迹,不在非 `active` 阶段累计路径。进入关卡和每次正确反馈结束后自动弹出物品,不再用 `open_palm -> grab` 抓握序列激活礼物盒。
|
- 处理:宝贝识物选篮只使用明确 `leftHand` / `rightHand` 的连续横向轨迹阈值;侧别为 `unknown` 的手部轨迹不参与选篮;反馈阶段清空轨迹,不在非 `active` 阶段累计路径。进入关卡和每次正确反馈结束后自动弹出物品,不再用 `open_palm -> grab` 抓握序列激活礼物盒。
|
||||||
- 补充:当前本地 mocap 的 handedness 是摄像头视角,宝贝识物选篮前需要换算为用户身体视角;`rightHand` 轨迹代表玩家左手并进入左篮,`leftHand` 轨迹代表玩家右手并进入右篮。键鼠调试不走该换算,仍保持鼠标左键=左篮、右键=右篮。
|
- 补充:当前本地 mocap 的 handedness 是摄像头视角,宝贝识物选篮前需要换算为用户身体视角;`rightHand` 轨迹代表玩家左手并进入左篮,`leftHand` 轨迹代表玩家右手并进入右篮。键鼠调试不走该换算,仍保持鼠标左键=左篮、右键=右篮。
|
||||||
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/services/useMocapInput.test.ts`,确认动作名负向测试、未知侧别负向测试和左右手横向轨迹测试通过。
|
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/services/useMocapInput.test.ts`,确认动作名负向测试、未知侧别负向测试和左右手横向轨迹测试通过。
|
||||||
- 关联:`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 宝贝爱画左右手反了先查 mocap 摄像头视角换算
|
## 宝贝爱画左右手反了先查 mocap 摄像头视角换算
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当成用户身体左 / 右;宝贝爱画初版直接消费 `latestCommand.leftHand/rightHand`,漏做摄像头视角到用户身体视角的换算。
|
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当成用户身体左 / 右;宝贝爱画初版直接消费 `latestCommand.leftHand/rightHand`,漏做摄像头视角到用户身体视角的换算。
|
||||||
- 处理:宝贝爱画运行态消费 mocap 前先换算:`rightHand` 作为用户左手,用于颜色悬停和左手指示器;`leftHand` 作为用户右手,用于画笔 / 橡皮光标、绘制、擦除和工具切换。键鼠调试输入不做该换算,继续保持鼠标左键为左手、右键为右手。
|
- 处理:宝贝爱画运行态消费 mocap 前先换算:`rightHand` 作为用户左手,用于颜色悬停和左手指示器;`leftHand` 作为用户右手,用于画笔 / 橡皮光标、绘制、擦除和工具切换。键鼠调试输入不做该换算,继续保持鼠标左键为左手、右键为右手。
|
||||||
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx src/components/edutainment-runtime/babyLoveDrawingModel.test.ts`,确认 camera-left 驱动用户右手画笔、camera-right 渲染用户左手选色指示器。
|
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx src/components/edutainment-runtime/babyLoveDrawingModel.test.ts`,确认 camera-left 驱动用户右手画笔、camera-right 渲染用户左手选色指示器。
|
||||||
- 关联:`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx`、`docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 宝贝识物创作卡在准备结果页先查长耗时 image-2 请求
|
## 宝贝识物创作卡在准备结果页先查长耗时 image-2 请求
|
||||||
|
|
||||||
@@ -164,15 +164,15 @@
|
|||||||
- 原因:宝贝识物一次创作会生成 2 张物品图和 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 5 张视觉包装图。旧前端只等待 180 秒并对长耗时 POST 自动重试,容易在 VectorEngine 仍在生成时先 abort,再重复发起第二次生成;上游某张图超过后端 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 或返回 5xx 时会表现为 502。
|
- 原因:宝贝识物一次创作会生成 2 张物品图和 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 5 张视觉包装图。旧前端只等待 180 秒并对长耗时 POST 自动重试,容易在 VectorEngine 仍在生成时先 abort,再重复发起第二次生成;上游某张图超过后端 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 或返回 5xx 时会表现为 502。
|
||||||
- 处理:`babyObjectMatchClient` 对 `/api/creation/edutainment/baby-object-match/assets` 使用 10 分钟超时并取消自动重试;后端并发启动物品图和视觉主题包生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,按资源类别输出开始、完成和耗时日志。
|
- 处理:`babyObjectMatchClient` 对 `/api/creation/edutainment/baby-object-match/assets` 使用 10 分钟超时并取消自动重试;后端并发启动物品图和视觉主题包生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,按资源类别输出开始、完成和耗时日志。
|
||||||
- 验证:运行 `npm run test -- src/services/edutainment-baby-object/babyObjectMatchClient.test.ts src/services/miniGameDraftGenerationProgress.test.ts`、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml` 和编码检查;真实联调时查看 `宝贝识物 image-2 资源生成完成` 耗时是否小于前端超时,若仍 502 再看 `VectorEngine 图片生成上游错误` 的 `upstreamStatus/raw_excerpt`。
|
- 验证:运行 `npm run test -- src/services/edutainment-baby-object/babyObjectMatchClient.test.ts src/services/miniGameDraftGenerationProgress.test.ts`、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml` 和编码检查;真实联调时查看 `宝贝识物 image-2 资源生成完成` 耗时是否小于前端超时,若仍 502 再看 `VectorEngine 图片生成上游错误` 的 `upstreamStatus/raw_excerpt`。
|
||||||
- 关联:`src/services/edutainment-baby-object/babyObjectMatchClient.ts`、`src/services/miniGameDraftGenerationProgress.ts`、`server-rs/crates/api-server/src/edutainment_baby_object.rs`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 寓教于乐作品和宝贝识物模板同时消失先查入口种子
|
## 寓教于乐作品和宝贝识物模板同时消失先查入口种子
|
||||||
|
|
||||||
- 现象:发现页“寓教于乐”分类下已发布的宝贝识物作品突然消失,同时创作界面模板选项中也看不到或无法正常展示 `宝贝识物`。
|
- 现象:发现页“寓教于乐”分类下已发布的宝贝识物作品突然消失,同时创作界面模板选项中也看不到或无法正常展示 `宝贝识物`。
|
||||||
- 原因:创作入口配置事实源已迁到 SpacetimeDB `creation_entry_type_config`;前端用 `baby-object-match` 入口可见性同时控制创作模板展示和发现页宝贝识物公开作品合入。若默认种子或后台配置缺少 `baby-object-match` 行,两条链路会一起被判定为不可见。
|
- 原因:创作入口配置事实源已迁到 SpacetimeDB `creation_entry_type_config`;前端用 `baby-object-match` 入口可见性同时控制创作模板展示和发现页宝贝识物公开作品合入。若默认种子或后台配置缺少 `baby-object-match` 行,两条链路会一起被判定为不可见。
|
||||||
- 处理:确认 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 默认种子包含 `id=baby-object-match`、`title=宝贝识物`、`visible=true`、`open=true`、`sort_order=90`;api-server 测试降级配置也要同步包含该类型。入口图片路径需指向真实存在资源,避免卡片图片 404。
|
- 处理:确认 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 默认种子包含 `id=baby-object-match`、`title=宝贝识物`、`visible=true`、`open=false`、`badge=敬请期待`、`sort_order=90`;api-server 测试降级配置也要同步包含该类型。入口图片路径需指向真实存在资源,避免卡片图片 404。若只是“敬请期待”,不要把 `visible` 关掉,否则发现页寓教于乐公开作品可见性也会受影响。
|
||||||
- 验证:运行 `cargo test -p module-runtime default_creation_entry_types_include_baby_object_match --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server test_creation_entry_config_response_keeps_baby_object_match_visible --manifest-path server-rs/Cargo.toml`、`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 和 `npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`。
|
- 验证:运行 `cargo test -p module-runtime default_creation_entry_types_include_baby_object_match --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server creation_entry_config --manifest-path server-rs/Cargo.toml`、`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 和 `npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`。
|
||||||
- 关联:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`、`server-rs/crates/api-server/src/creation_entry_config.rs`、`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 儿童动作 Demo 绘本风资源未生成先查 VectorEngine 配置
|
## 儿童动作 Demo 绘本风资源未生成先查 VectorEngine 配置
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
- 原因:儿童动作 Demo 的真实背景、地面、UI、地面指示环和角色轮廓资源都使用 VectorEngine `gpt-image-2-all` 生成,脚本只读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和可选 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;仓库内不能提交真实 key,缺配置时页面只能使用 CSS 草地绘本兜底。
|
- 原因:儿童动作 Demo 的真实背景、地面、UI、地面指示环和角色轮廓资源都使用 VectorEngine `gpt-image-2-all` 生成,脚本只读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和可选 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;仓库内不能提交真实 key,缺配置时页面只能使用 CSS 草地绘本兜底。
|
||||||
- 处理:在本地私密环境补齐 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai` 与 `VECTOR_ENGINE_API_KEY`,不要把 key 写入 Git;先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt,再运行 `npm run assets:child-motion-demo -- --live` 或 `npm run assets:child-motion-demo -- --live --only ui-panel` 等小批量命令生成资源。透明资源的品红底源图写入 `tmp/child-motion-demo-assets/`,不要把源图或预览图放入 `public/child-motion-demo/` 作为正式资产。
|
- 处理:在本地私密环境补齐 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai` 与 `VECTOR_ENGINE_API_KEY`,不要把 key 写入 Git;先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt,再运行 `npm run assets:child-motion-demo -- --live` 或 `npm run assets:child-motion-demo -- --live --only ui-panel` 等小批量命令生成资源。透明资源的品红底源图写入 `tmp/child-motion-demo-assets/`,不要把源图或预览图放入 `public/child-motion-demo/` 作为正式资产。
|
||||||
- 验证:生成后确认 `public/child-motion-demo/` 只保留页面引用的最终 PNG,重新打开 `/child-motion-demo` 可看到真实绘本草地背景、地面、圆环、角色轮廓和 UI 资源;`npm run check:encoding` 仍通过。
|
- 验证:生成后确认 `public/child-motion-demo/` 只保留页面引用的最终 PNG,重新打开 `/child-motion-demo` 可看到真实绘本草地背景、地面、圆环、角色轮廓和 UI 资源;`npm run check:encoding` 仍通过。
|
||||||
- 关联:`scripts/generate-child-motion-demo-assets.mjs`、`src/index.css`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 儿童动作 Demo 绘本资源变形先查用途拆分和透明后处理
|
## 儿童动作 Demo 绘本资源变形先查用途拆分和透明后处理
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
- 原因:早期资源中 `picture-book-ui-panel.png` 是接近方形画布,`picture-book-grass-floor.png` 也含大量透明边界;若 CSS 用 `background-size: 100% 100%` 把同一资源强行铺成 HUD、状态条、开始面板或底部地板,就会出现变形和层叠观感。
|
- 原因:早期资源中 `picture-book-ui-panel.png` 是接近方形画布,`picture-book-grass-floor.png` 也含大量透明边界;若 CSS 用 `background-size: 100% 100%` 把同一资源强行铺成 HUD、状态条、开始面板或底部地板,就会出现变形和层叠观感。
|
||||||
- 处理:使用 v2 用途专属资源:`picture-book-foreground-grass-v2.png`、`picture-book-ground-ring-v2.png`、`picture-book-character-outline-v2.png`、`picture-book-hud-strip-v2.png`、`picture-book-calibration-strip-v2.png`、`picture-book-start-panel-v2.png`、`picture-book-ui-button-v2.png`;CSS 按资源比例等比缩放,底部草坪只覆盖下沿,HUD / 状态条 / 开始托盘分别引用各自资源。若只需修透明裁切或品红边,运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>`,不重新请求 image-2。
|
- 处理:使用 v2 用途专属资源:`picture-book-foreground-grass-v2.png`、`picture-book-ground-ring-v2.png`、`picture-book-character-outline-v2.png`、`picture-book-hud-strip-v2.png`、`picture-book-calibration-strip-v2.png`、`picture-book-start-panel-v2.png`、`picture-book-ui-button-v2.png`;CSS 按资源比例等比缩放,底部草坪只覆盖下沿,HUD / 状态条 / 开始托盘分别引用各自资源。若只需修透明裁切或品红边,运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>`,不重新请求 image-2。
|
||||||
- 验证:用横屏截图检查没有新旧资源叠加、没有方形面板拉成长条、角色和地面指示环不被前景草坪埋住;同时运行 `npm run check:encoding`。
|
- 验证:用横屏截图检查没有新旧资源叠加、没有方形面板拉成长条、角色和地面指示环不被前景草坪埋住;同时运行 `npm run check:encoding`。
|
||||||
- 关联:`scripts/generate-child-motion-demo-assets.mjs`、`src/index.css`、`public/child-motion-demo/`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## GPT-image-2 不再读 APIMart 图片配置
|
## GPT-image-2 不再读 APIMart 图片配置
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
- 原因:2026-05-09 后 GPT-image-2 图片生成已切到 VectorEngine `gpt-image-2-all`,APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
|
- 原因:2026-05-09 后 GPT-image-2 图片生成已切到 VectorEngine `gpt-image-2-all`,APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
|
||||||
- 处理:为图片生成配置 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai`、`VECTOR_ENGINE_API_KEY`、`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;排查请求体时确认路径为 `/v1/images/generations`、模型为 `gpt-image-2-all`、参考图字段为 `image`。
|
- 处理:为图片生成配置 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai`、`VECTOR_ENGINE_API_KEY`、`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;排查请求体时确认路径为 `/v1/images/generations`、模型为 `gpt-image-2-all`、参考图字段为 `image`。
|
||||||
- 验证:运行 `cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml` 和相关玩法图片生成测试;真实联调只在本地私密环境放置 VectorEngine key。
|
- 验证:运行 `cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml` 和相关玩法图片生成测试;真实联调只在本地私密环境放置 VectorEngine key。
|
||||||
- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`server-rs/crates/api-server/src/openai_image_generation.rs`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图参考图没有影响生成时先查 action payload 和阶段日志
|
## 拼图参考图没有影响生成时先查 action payload 和阶段日志
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
- 原因:`gpt-image-2-all` 的 `/v1/images/generations` 更适合纯文生图;有参考图且需要重绘时应切到 `/v1/images/edits` 的 multipart 图生图接口。
|
- 原因:`gpt-image-2-all` 的 `/v1/images/generations` 更适合纯文生图;有参考图且需要重绘时应切到 `/v1/images/edits` 的 multipart 图生图接口。
|
||||||
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true` 时直接走 edits,prompt 仍保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
|
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true` 时直接走 edits,prompt 仍保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
|
||||||
- 验证:后端单测应覆盖 `images/edits` 路由、`b64_json` 响应解码和参考图强提示;真实联调先看日志里是否命中 `拼图 VectorEngine 图片编辑 HTTP 返回`。
|
- 验证:后端单测应覆盖 `images/edits` 路由、`b64_json` 响应解码和参考图强提示;真实联调先看日志里是否命中 `拼图 VectorEngine 图片编辑 HTTP 返回`。
|
||||||
- 关联:`server-rs/crates/api-server/src/puzzle.rs`、`src/services/puzzleReferenceImage.ts`、`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图 edits 报 error sending request 先看网络分类
|
## 拼图 edits 报 error sending request 先看网络分类
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
- 原因:这是 `reqwest` 在 `send()` 阶段失败,尚未收到 VectorEngine HTTP 响应;常见原因是服务器网络 / DNS / 防火墙 / 代理问题,或上游网关中断 multipart 连接。
|
- 原因:这是 `reqwest` 在 `send()` 阶段失败,尚未收到 VectorEngine HTTP 响应;常见原因是服务器网络 / DNS / 防火墙 / 代理问题,或上游网关中断 multipart 连接。
|
||||||
- 处理:查看错误响应 `details.reason/source/connect/body/timeout/endpoint` 和 `拼图 VectorEngine 请求发送失败` 日志。拼图图片客户端已强制 HTTP/1.1,降低 multipart HTTP/2 兼容风险;若 `connect=true` 先查网络出口,若 `body=true` 先查参考图大小和 multipart 发送。
|
- 处理:查看错误响应 `details.reason/source/connect/body/timeout/endpoint` 和 `拼图 VectorEngine 请求发送失败` 日志。拼图图片客户端已强制 HTTP/1.1,降低 multipart HTTP/2 兼容风险;若 `connect=true` 先查网络出口,若 `body=true` 先查参考图大小和 multipart 发送。
|
||||||
- 验证:`curl --http1.1 -i -X POST https://api.vectorengine.ai/v1/images/edits -H "Authorization: Bearer invalid" -F "model=gpt-image-2" -F "prompt=test" -F "n=1" -F "size=1024x1024" -F "image=@public/match3d-background-references/pot-fused-reference.png;type=image/png"` 至少应返回 HTTP `401`,说明域名、TLS、路径和 multipart 上传可达;执行 `cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`。
|
- 验证:`curl --http1.1 -i -X POST https://api.vectorengine.ai/v1/images/edits -H "Authorization: Bearer invalid" -F "model=gpt-image-2" -F "prompt=test" -F "n=1" -F "size=1024x1024" -F "image=@public/match3d-background-references/pot-fused-reference.png;type=image/png"` 至少应返回 HTTP `401`,说明域名、TLS、路径和 multipart 上传可达;执行 `cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`。
|
||||||
- 关联:`server-rs/crates/api-server/src/puzzle.rs`、`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图 UI 背景缺失先区分生成失败和消费链路丢字段
|
## 拼图 UI 背景缺失先区分生成失败和消费链路丢字段
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
- 原因:`compile_puzzle_draft` 设计上会在首图后生成 UI 背景,且缺 `uiBackgroundImageSrc/uiBackgroundImageObjectKey` 会让自动草稿失败;若草稿已成功,通常不是“没生成”,而是前端消费链路漏了 `levels[].uiBackgroundImageObjectKey` 回退,或本地 `startLocalPuzzleRun(...)` 只把 `coverImageSrc` 带入 `currentLevel`。
|
- 原因:`compile_puzzle_draft` 设计上会在首图后生成 UI 背景,且缺 `uiBackgroundImageSrc/uiBackgroundImageObjectKey` 会让自动草稿失败;若草稿已成功,通常不是“没生成”,而是前端消费链路漏了 `levels[].uiBackgroundImageObjectKey` 回退,或本地 `startLocalPuzzleRun(...)` 只把 `coverImageSrc` 带入 `currentLevel`。
|
||||||
- 处理:结果页预览、运行态和本地运行态统一用 `resolvePuzzleUiBackgroundSource`,优先 `uiBackgroundImageSrc`,为空时把 `uiBackgroundImageObjectKey` 规范成 `/generated-...` 路径并交给 `/api/assets/read-url` 换签;`startLocalPuzzleRun` 与本地下一关 handoff 都要从 `PuzzleWorkSummary.levels[]` 复制 `uiBackgroundImageSrc/uiBackgroundImageObjectKey/backgroundMusic` 到 `currentLevel`。结果页 `UI背景提示词` 输入框不得把本地兜底 prompt 直接显示成已保存提示词,避免误判为后端已生成。
|
- 处理:结果页预览、运行态和本地运行态统一用 `resolvePuzzleUiBackgroundSource`,优先 `uiBackgroundImageSrc`,为空时把 `uiBackgroundImageObjectKey` 规范成 `/generated-...` 路径并交给 `/api/assets/read-url` 换签;`startLocalPuzzleRun` 与本地下一关 handoff 都要从 `PuzzleWorkSummary.levels[]` 复制 `uiBackgroundImageSrc/uiBackgroundImageObjectKey/backgroundMusic` 到 `currentLevel`。结果页 `UI背景提示词` 输入框不得把本地兜底 prompt 直接显示成已保存提示词,避免误判为后端已生成。
|
||||||
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx src/services/puzzle-runtime/puzzleLocalRuntime.test.ts src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`,以及 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial"`;后端用 `cargo test -p api-server puzzle_ui_background --manifest-path server-rs\Cargo.toml` 确认生成 / 序列化链路。
|
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx src/services/puzzle-runtime/puzzleLocalRuntime.test.ts src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`,以及 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial"`;后端用 `cargo test -p api-server puzzle_ui_background --manifest-path server-rs\Cargo.toml` 确认生成 / 序列化链路。
|
||||||
- 关联:`src/services/puzzle-runtime/puzzleUiBackgroundSource.ts`、`src/services/puzzle-runtime/puzzleLocalRuntime.ts`、`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`、`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图草稿生成后音乐/UI 又变空先查结果页回包合并
|
## 拼图草稿生成后音乐/UI 又变空先查结果页回包合并
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
- 原因:结果页若已有本地 `generationStatus = generating` 编辑态,后端生成完成回包会走 `mergeDraftEditStateWithIncomingState(...)` 合并。该合并必须把生成候选图、正式图、`uiBackground*` 和 `backgroundMusic` 作为同一批生成资产处理;漏掉 `backgroundMusic` 时,随后自动保存会把空音乐写回 `levels_json`。
|
- 原因:结果页若已有本地 `generationStatus = generating` 编辑态,后端生成完成回包会走 `mergeDraftEditStateWithIncomingState(...)` 合并。该合并必须把生成候选图、正式图、`uiBackground*` 和 `backgroundMusic` 作为同一批生成资产处理;漏掉 `backgroundMusic` 时,随后自动保存会把空音乐写回 `levels_json`。
|
||||||
- 处理:`PuzzleResultView` 合并生成完成回包时同步保留 `backgroundMusic`,并用回归测试覆盖 UI 预览、音乐试听和试玩 payload 都读取最新 `levels[]` 资产。
|
- 处理:`PuzzleResultView` 合并生成完成回包时同步保留 `backgroundMusic`,并用回归测试覆盖 UI 预览、音乐试听和试玩 payload 都读取最新 `levels[]` 资产。
|
||||||
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`,以及自动试玩入口测试 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial"`。
|
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`,以及自动试玩入口测试 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial"`。
|
||||||
- 关联:`src/components/puzzle-result/PuzzleResultView.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 自动草稿成功但缺音乐或 UI 先查后端吞错
|
## 自动草稿成功但缺音乐或 UI 先查后端吞错
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
- 原因:自动草稿阶段如果把 VectorEngine / Suno / OSS / 资产绑定错误记录为 warning 后继续返回成功,前端只能拿到缺关键资产的成功 draft,随后保存和试玩都会消费这份空资产状态。
|
- 原因:自动草稿阶段如果把 VectorEngine / Suno / OSS / 资产绑定错误记录为 warning 后继续返回成功,前端只能拿到缺关键资产的成功 draft,随后保存和试玩都会消费这份空资产状态。
|
||||||
- 处理:自动草稿必须把必需生成资产当作后端完成条件:拼图首关需同时具备 `levels[0].backgroundMusic.audioSrc` 和 `levels[0].uiBackgroundImageSrc/uiBackgroundImageObjectKey`;抓大鹅需在 `generatedItemAssets[]` 中具备非空 `backgroundMusic.audioSrc`。缺失或上游失败时返回错误并停留在生成页,结果页手动重新生成只作为已有草稿补救入口。
|
- 处理:自动草稿必须把必需生成资产当作后端完成条件:拼图首关需同时具备 `levels[0].backgroundMusic.audioSrc` 和 `levels[0].uiBackgroundImageSrc/uiBackgroundImageObjectKey`;抓大鹅需在 `generatedItemAssets[]` 中具备非空 `backgroundMusic.audioSrc`。缺失或上游失败时返回错误并停留在生成页,结果页手动重新生成只作为已有草稿补救入口。
|
||||||
- 验证:`cargo test -p api-server puzzle_initial_draft_assets_must_include_music_and_ui_background match3d_background_music_ready_requires_audio_src match3d_background_music_title_is_required_for_auto_draft --manifest-path server-rs\Cargo.toml`,并重启 `npm run api-server` 后检查 `/healthz`。
|
- 验证:`cargo test -p api-server puzzle_initial_draft_assets_must_include_music_and_ui_background match3d_background_music_ready_requires_audio_src match3d_background_music_title_is_required_for_auto_draft --manifest-path server-rs\Cargo.toml`,并重启 `npm run api-server` 后检查 `/healthz`。
|
||||||
- 关联:`server-rs/crates/api-server/src/puzzle.rs`、`server-rs/crates/api-server/src/match3d.rs`、`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图草稿生成 180 秒后 502/504 先查 VectorEngine 超时与前端重试
|
## 拼图草稿生成 180 秒后 502/504 先查 VectorEngine 超时与前端重试
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
- 原因:首图生成走 VectorEngine `gpt-image-2-all`,默认 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000`;若上游在该窗口内未返回,后端退款并返回超时错误。旧前端 action 写请求会对 502/503/504 自动重试一次,导致同一次点击重复触发生图与扣退费。
|
- 原因:首图生成走 VectorEngine `gpt-image-2-all`,默认 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000`;若上游在该窗口内未返回,后端退款并返回超时错误。旧前端 action 写请求会对 502/503/504 自动重试一次,导致同一次点击重复触发生图与扣退费。
|
||||||
- 处理:拼图/创作 Agent 的 `executeAction` 默认不做前端自动重试;后端将 VectorEngine / 图片请求超时映射为 `504 Gateway Timeout`,`error.details.provider=vector-engine` 且 `timeout=true`。真实排障按日志同一 `session_id` 查 `拼图 VectorEngine 图片生成 HTTP 返回` 是否缺失,以及钱包流水扣费到退款的时间差是否接近 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`。
|
- 处理:拼图/创作 Agent 的 `executeAction` 默认不做前端自动重试;后端将 VectorEngine / 图片请求超时映射为 `504 Gateway Timeout`,`error.details.provider=vector-engine` 且 `timeout=true`。真实排障按日志同一 `session_id` 查 `拼图 VectorEngine 图片生成 HTTP 返回` 是否缺失,以及钱包流水扣费到退款的时间差是否接近 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`。
|
||||||
- 验证:运行 `npm run test -- src/services/creation-agent/creationAgentClientFactory.test.ts src/services/apiClient.test.ts`、`cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`,真实联调重启 `npm run api-server` 后检查 `/healthz`。
|
- 验证:运行 `npm run test -- src/services/creation-agent/creationAgentClientFactory.test.ts src/services/apiClient.test.ts`、`cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`,真实联调重启 `npm run api-server` 后检查 `/healthz`。
|
||||||
- 关联:`src/services/creation-agent/creationAgentClientFactory.ts`、`server-rs/crates/api-server/src/puzzle.rs`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 本地脚本调 VectorEngine 生图卡住先区分 fetch 首部超时
|
## 本地脚本调 VectorEngine 生图卡住先区分 fetch 首部超时
|
||||||
|
|
||||||
@@ -274,17 +274,17 @@
|
|||||||
|
|
||||||
- 现象:开发时参考到 Express、Node、PostgreSQL 或 Go 方向旧文档,导致接口、数据真相或部署路径与当前主线不一致。
|
- 现象:开发时参考到 Express、Node、PostgreSQL 或 Go 方向旧文档,导致接口、数据真相或部署路径与当前主线不一致。
|
||||||
- 原因:项目历史文档较多,部分旧方案仍保留作迁移参考。
|
- 原因:项目历史文档较多,部分旧方案仍保留作迁移参考。
|
||||||
- 处理:涉及服务端、数据真相、SpacetimeDB、运行时状态时,先看 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`,再看 DDD 总纲和具体技术方案。
|
- 处理:涉及服务端、数据真相、SpacetimeDB、运行时状态时,先看当前后端架构文档,再看相关 crate README 和源码。
|
||||||
- 验证:代码改动应落在 `server-rs + Axum + SpacetimeDB` 主线;旧路线只作为迁移参考,不作为兼容目标。
|
- 验证:代码改动应落在 `server-rs + Axum + SpacetimeDB` 主线;旧路线只作为迁移参考,不作为兼容目标。
|
||||||
- 关联:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`、`AGENTS.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## SpacetimeDB 表结构变更不能按 PostgreSQL 迁移直觉处理
|
## SpacetimeDB 表结构变更不能按 PostgreSQL 迁移直觉处理
|
||||||
|
|
||||||
- 现象:发布时 schema 冲突、自动迁移拒绝、旧客户端调用 reducer 失败、private 表数据迁移遗漏。
|
- 现象:发布时 schema 冲突、自动迁移拒绝、旧客户端调用 reducer 失败、private 表数据迁移遗漏。
|
||||||
- 原因:SpacetimeDB 对字段删除、类型变化、索引/主键/RLS/reducer 变化有不同自动迁移边界。
|
- 原因:SpacetimeDB 对字段删除、类型变化、索引/主键/RLS/reducer 变化有不同自动迁移边界。
|
||||||
- 处理:变更前阅读 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`;已有表新增字段必须放在 Rust 表结构体最后并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划;涉及表变化时同步 `migration.rs`、`SPACETIMEDB_TABLE_CATALOG.md` 和 bindings;必要时走 JSON 导入导出与分片导入迁移流程。
|
- 处理:变更前阅读 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`;已有表新增字段必须放在 Rust 表结构体最后并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划;涉及表变化时同步 `migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和 bindings;必要时走 JSON 导入导出与分片导入迁移流程。
|
||||||
- 验证:发布前运行 `npm run check:spacetime-schema`,完成 schema 检查、bindings 生成、表目录更新和相关 smoke。
|
- 验证:发布前运行 `npm run check:spacetime-schema`,完成 schema 检查、bindings 生成、表目录更新和相关 smoke。
|
||||||
- 关联:`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## SpacetimeDB publish 报 wasm-bindgen 时先查 shared-contracts feature
|
## SpacetimeDB publish 报 wasm-bindgen 时先查 shared-contracts feature
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
- 原因:SpacetimeDB module 的 wasm32 构建树被间接带入原生/网页依赖;已验证链路是 `reqwest -> platform-oss -> shared-contracts -> module-runtime -> spacetime-module`,由共享契约默认启用资产 OSS 契约触发。
|
- 原因:SpacetimeDB module 的 wasm32 构建树被间接带入原生/网页依赖;已验证链路是 `reqwest -> platform-oss -> shared-contracts -> module-runtime -> spacetime-module`,由共享契约默认启用资产 OSS 契约触发。
|
||||||
- 处理:让 `shared-contracts` 的 OSS 资产契约走 `oss-contracts` feature,workspace 根依赖保持 `default-features = false`;`api-server` 这类原生后端需要资产 DTO 时在自身 `Cargo.toml` 显式启用 `features = ["oss-contracts"]`。
|
- 处理:让 `shared-contracts` 的 OSS 资产契约走 `oss-contracts` feature,workspace 根依赖保持 `default-features = false`;`api-server` 这类原生后端需要资产 DTO 时在自身 `Cargo.toml` 显式启用 `features = ["oss-contracts"]`。
|
||||||
- 验证:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown` 应显示 nothing to print;再执行 `cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml --target wasm32-unknown-unknown`。
|
- 验证:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown` 应显示 nothing to print;再执行 `cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml --target wasm32-unknown-unknown`。
|
||||||
- 关联:`server-rs/crates/shared-contracts/Cargo.toml`、`server-rs/crates/api-server/Cargo.toml`、`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 本地 SpacetimeDB replica identity 不匹配
|
## 本地 SpacetimeDB replica identity 不匹配
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@
|
|||||||
- 原因:本地 SpacetimeDB 数据目录中的 replica 数据残留与当前数据库身份不一致。
|
- 原因:本地 SpacetimeDB 数据目录中的 replica 数据残留与当前数据库身份不一致。
|
||||||
- 处理:按本地 replica identity mismatch 文档进行备份、重建和脚本诊断。
|
- 处理:按本地 replica identity mismatch 文档进行备份、重建和脚本诊断。
|
||||||
- 验证:本地 SpacetimeDB 可正常启动并 publish / 访问。
|
- 验证:本地 SpacetimeDB 可正常启动并 publish / 访问。
|
||||||
- 关联:`docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 本地 SpacetimeDB publish 403 优先查 CLI 身份和目标库
|
## 本地 SpacetimeDB publish 403 优先查 CLI 身份和目标库
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@
|
|||||||
- 原因:当前 CLI 登录态不是目标数据库的创建者或授权身份,或 `.env.local` / publish 命令指向了另一个数据库或 SpacetimeDB 服务。
|
- 原因:当前 CLI 登录态不是目标数据库的创建者或授权身份,或 `.env.local` / publish 命令指向了另一个数据库或 SpacetimeDB 服务。
|
||||||
- 处理:除 CI/CD 脚本内部受控用法外,不再使用 `spacetime --root-dir` 排障或发布。先执行 `spacetime login show`、`spacetime server list`,再用 `spacetime list --server http://127.0.0.1:3101` 或实际 `--server-url` 确认当前身份是否能看到目标库;本地开发发布优先使用 `npm run dev:rust` 或从 `server-rs` 目录执行显式 `--server` 的 `spacetime publish`。如果身份不对,重新登录正确身份、使用项目脚本重新生成本地库,或在 SpacetimeDB 侧补授权。
|
- 处理:除 CI/CD 脚本内部受控用法外,不再使用 `spacetime --root-dir` 排障或发布。先执行 `spacetime login show`、`spacetime server list`,再用 `spacetime list --server http://127.0.0.1:3101` 或实际 `--server-url` 确认当前身份是否能看到目标库;本地开发发布优先使用 `npm run dev:rust` 或从 `server-rs` 目录执行显式 `--server` 的 `spacetime publish`。如果身份不对,重新登录正确身份、使用项目脚本重新生成本地库,或在 SpacetimeDB 侧补授权。
|
||||||
- 验证:`spacetime list --server http://127.0.0.1:3101` 能看到目标库;重新发布不再使用无权限 identity。
|
- 验证:`spacetime list --server http://127.0.0.1:3101` 能看到目标库;重新发布不再使用无权限 identity。
|
||||||
- 关联:`scripts/dev-rust-stack.sh`、`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## `npm run dev` 本地 SpacetimeDB 401 / 403 可重置默认 local 身份
|
## `npm run dev` 本地 SpacetimeDB 401 / 403 可重置默认 local 身份
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@
|
|||||||
- 原因:本机 `spacetime` CLI 保存的旧 token、默认 server、正在运行的 standalone 进程或默认 local 数据库与当前发布身份不一致。
|
- 原因:本机 `spacetime` CLI 保存的旧 token、默认 server、正在运行的 standalone 进程或默认 local 数据库与当前发布身份不一致。
|
||||||
- 处理:确认只是本地测试库且数据可丢弃后,先查看并停止本地 `spacetimedb-standalone`,执行 `spacetime logout`,确认并设置 `spacetime server set-default local`,停 server 后用 `spacetime server clear -y` 清空默认本地库,再 `spacetime start`,另开终端执行 `spacetime login --server-issued-login local`,最后用 `spacetime publish --server local A` 或项目脚本重新发布。
|
- 处理:确认只是本地测试库且数据可丢弃后,先查看并停止本地 `spacetimedb-standalone`,执行 `spacetime logout`,确认并设置 `spacetime server set-default local`,停 server 后用 `spacetime server clear -y` 清空默认本地库,再 `spacetime start`,另开终端执行 `spacetime login --server-issued-login local`,最后用 `spacetime publish --server local A` 或项目脚本重新发布。
|
||||||
- 验证:`spacetime server list` 默认目标为 local;重新登录后发布不再返回 `401` / `403`;`npm run dev` 可以完成 SpacetimeDB publish 并继续启动 `api-server`。
|
- 验证:`spacetime server list` 默认目标为 local;重新登录后发布不再返回 `401` / `403`;`npm run dev` 可以完成 SpacetimeDB publish 并继续启动 `api-server`。
|
||||||
- 关联:`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`、`scripts/dev-rust-stack.sh`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 本地 SpacetimeDB 联调可按阶段跳过宿主或发布
|
## 本地 SpacetimeDB 联调可按阶段跳过宿主或发布
|
||||||
|
|
||||||
@@ -324,7 +324,7 @@
|
|||||||
- 原因:`npm run dev` 默认同时启动 SpacetimeDB standalone、发布 `server-rs/crates/spacetime-module`、启动 Rust `api-server`、主站 Vite 与后台 Vite;并非每个阶段都需要完整重启和重新发布。
|
- 原因:`npm run dev` 默认同时启动 SpacetimeDB standalone、发布 `server-rs/crates/spacetime-module`、启动 Rust `api-server`、主站 Vite 与后台 Vite;并非每个阶段都需要完整重启和重新发布。
|
||||||
- 处理:`npm run dev` 启动后会把实际 SpacetimeDB URL 记录到 `server-rs/.spacetimedb/local/data/dev-rust-spacetime-url`。下次启动即使没有传 `--skip-spacetime`,脚本也会先检查 `spacetime.pid` 对应进程和该 URL 是否在线;在线则直接复用现有宿主。确认需要新启动 SpacetimeDB 时,脚本先检测 `3101`,被占用则输出占用进程并选择最近可用端口,保证 publish 与 `api-server` 都连接同一个实际 SpacetimeDB URL。显式传 `--skip-spacetime` 时表示复用既有宿主,脚本不再对 SpacetimeDB 端口做可用性漂移;`--spacetime-port 3101` 就是后端要连接的实际端口,避免被误改到空闲但未启动的 `3102`。`api-server` 启动前也会检测 `8082` 并选择最近可用端口。Windows / Git Bash 下不要用 `tr/head/xargs` 管道读取 `spacetime.pid` 或 URL 记录,脚本应使用 Node 读取并短重试,避免 `tr: read error: Device or resource busy`;未修改 `spacetime-module` 时使用 `npm run dev -- --skip-publish`;只查模块语法时执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。`npm run dev` 会在启动前检查 SpacetimeDB、api-server、主站 Vite、后台 Vite 端口,不可用时自动寻找后续可用端口,并把实际端口传给 publish、后端环境变量和前端代理目标。
|
- 处理:`npm run dev` 启动后会把实际 SpacetimeDB URL 记录到 `server-rs/.spacetimedb/local/data/dev-rust-spacetime-url`。下次启动即使没有传 `--skip-spacetime`,脚本也会先检查 `spacetime.pid` 对应进程和该 URL 是否在线;在线则直接复用现有宿主。确认需要新启动 SpacetimeDB 时,脚本先检测 `3101`,被占用则输出占用进程并选择最近可用端口,保证 publish 与 `api-server` 都连接同一个实际 SpacetimeDB URL。显式传 `--skip-spacetime` 时表示复用既有宿主,脚本不再对 SpacetimeDB 端口做可用性漂移;`--spacetime-port 3101` 就是后端要连接的实际端口,避免被误改到空闲但未启动的 `3102`。`api-server` 启动前也会检测 `8082` 并选择最近可用端口。Windows / Git Bash 下不要用 `tr/head/xargs` 管道读取 `spacetime.pid` 或 URL 记录,脚本应使用 Node 读取并短重试,避免 `tr: read error: Device or resource busy`;未修改 `spacetime-module` 时使用 `npm run dev -- --skip-publish`;只查模块语法时执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。`npm run dev` 会在启动前检查 SpacetimeDB、api-server、主站 Vite、后台 Vite 端口,不可用时自动寻找后续可用端口,并把实际端口传给 publish、后端环境变量和前端代理目标。
|
||||||
- 验证:`--skip-spacetime` 后脚本复用现有 `http://127.0.0.1:3101`;日志中的 `[dev:rust] spacetime:` 不应漂移到没有服务的 `3102`;`GET /api/creation-entry/config` 不应返回连接空端口导致的 `502`。`3101` 或 `8082` 被其他进程占用时,脚本输出占用进程并使用最近可用端口;`--skip-publish` 后不再进入 publish 阶段;`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能完成 Rust 语法和类型检查。端口漂移时控制台会打印 `[dev:ports] ... 不可用,改用 ...`,后续 `[dev:rust] web/admin web/rust api/spacetime` 地址应与实际端口一致。
|
- 验证:`--skip-spacetime` 后脚本复用现有 `http://127.0.0.1:3101`;日志中的 `[dev:rust] spacetime:` 不应漂移到没有服务的 `3102`;`GET /api/creation-entry/config` 不应返回连接空端口导致的 `502`。`3101` 或 `8082` 被其他进程占用时,脚本输出占用进程并使用最近可用端口;`--skip-publish` 后不再进入 publish 阶段;`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能完成 Rust 语法和类型检查。端口漂移时控制台会打印 `[dev:ports] ... 不可用,改用 ...`,后续 `[dev:rust] web/admin web/rust api/spacetime` 地址应与实际端口一致。
|
||||||
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`、`scripts/dev-rust-stack.sh`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 本地 SpacetimeDB publish 401 可清本地库重发
|
## 本地 SpacetimeDB publish 401 可清本地库重发
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@
|
|||||||
- 原因:本地开发数据目录中保留的数据库、控制库身份或发布身份与当前目标不一致。
|
- 原因:本地开发数据目录中保留的数据库、控制库身份或发布身份与当前目标不一致。
|
||||||
- 处理:确认本地开发数据可以丢弃后,停止本地 SpacetimeDB,备份或删除 `server-rs/.spacetimedb/local/data`,再重新运行 `npm run dev` 或本地 publish;不要用 `--root-dir` 手工清库。
|
- 处理:确认本地开发数据可以丢弃后,停止本地 SpacetimeDB,备份或删除 `server-rs/.spacetimedb/local/data`,再重新运行 `npm run dev` 或本地 publish;不要用 `--root-dir` 手工清库。
|
||||||
- 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 `401`,继续检查数据目录、库名和 CLI 身份。
|
- 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 `401`,继续检查数据目录、库名和 CLI 身份。
|
||||||
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`、`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## SpacetimeDB 模块 publish 报 `wasm-bindgen detected`
|
## SpacetimeDB 模块 publish 报 `wasm-bindgen detected`
|
||||||
|
|
||||||
@@ -340,7 +340,7 @@
|
|||||||
- 原因:SpacetimeDB 模块是数据库内 WASM,不允许拉入 Web/HTTP client 链路;常见误因是 `spacetime-module -> module-* -> shared-contracts -> platform-* -> reqwest -> wasm-bindgen` 这类反向依赖。
|
- 原因:SpacetimeDB 模块是数据库内 WASM,不允许拉入 Web/HTTP client 链路;常见误因是 `spacetime-module -> module-* -> shared-contracts -> platform-* -> reqwest -> wasm-bindgen` 这类反向依赖。
|
||||||
- 处理:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs/Cargo.toml -p spacetime-module --target wasm32-unknown-unknown` 找到链路;把平台实现类型从 `shared-contracts` 或 `module-*` 中移除,只保留公开 DTO,平台响应到 DTO 的转换放回 `api-server` 等 adapter 层。
|
- 处理:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs/Cargo.toml -p spacetime-module --target wasm32-unknown-unknown` 找到链路;把平台实现类型从 `shared-contracts` 或 `module-*` 中移除,只保留公开 DTO,平台响应到 DTO 的转换放回 `api-server` 等 adapter 层。
|
||||||
- 验证:上述 `cargo tree` 输出 `warning: nothing to print`;`cargo check -p shared-contracts`、`cargo check -p api-server` 通过;重新 `spacetime publish ... --module-path server-rs/crates/spacetime-module` 不再报 wasm-bindgen。
|
- 验证:上述 `cargo tree` 输出 `warning: nothing to print`;`cargo check -p shared-contracts`、`cargo check -p api-server` 通过;重新 `spacetime publish ... --module-path server-rs/crates/spacetime-module` 不再报 wasm-bindgen。
|
||||||
- 关联:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`、`server-rs/crates/shared-contracts/src/assets.rs`、`server-rs/crates/api-server/src/assets.rs`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## Vite SPA fallback 吞掉 API 请求
|
## Vite SPA fallback 吞掉 API 请求
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@
|
|||||||
- 原因:Vite 代理缺少对应 `/api/*` 前缀,API 请求落到 SPA fallback。
|
- 原因:Vite 代理缺少对应 `/api/*` 前缀,API 请求落到 SPA fallback。
|
||||||
- 处理:补齐 Vite 代理,让 API 请求转发到 Rust `api-server`。
|
- 处理:补齐 Vite 代理,让 API 请求转发到 Rust `api-server`。
|
||||||
- 验证:请求返回 JSON,相关页面不再出现 HTML parse 错误。
|
- 验证:请求返回 JSON,相关页面不再出现 HTML parse 错误。
|
||||||
- 关联:`docs/technical/PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 反馈页清空 file input 前必须先拷贝 FileList
|
## 反馈页清空 file input 前必须先拷贝 FileList
|
||||||
|
|
||||||
@@ -356,7 +356,7 @@
|
|||||||
- 原因:浏览器传入的 `FileList` 可能跟 `<input type="file">` 保持 live 绑定;如果先执行 `input.value = ''`,再从参数里的 `FileList` 读取文件,列表可能已经为空。
|
- 原因:浏览器传入的 `FileList` 可能跟 `<input type="file">` 保持 live 绑定;如果先执行 `input.value = ''`,再从参数里的 `FileList` 读取文件,列表可能已经为空。
|
||||||
- 处理:在清空 file input 前先执行 `const selectedFiles = files ? Array.from(files) : []`,后续图片类型、大小、Data URL 读取和预览都基于这个普通数组。
|
- 处理:在清空 file input 前先执行 `const selectedFiles = files ? Array.from(files) : []`,后续图片类型、大小、Data URL 读取和预览都基于这个普通数组。
|
||||||
- 验证:`PlatformFeedbackView.test.tsx` 用 mock `FileReader` 断言选择图片后出现 `反馈凭证预览`,且提交 payload 带 `evidenceItems[].dataUrl`。
|
- 验证:`PlatformFeedbackView.test.tsx` 用 mock `FileReader` 断言选择图片后出现 `反馈凭证预览`,且提交 payload 带 `evidenceItems[].dataUrl`。
|
||||||
- 关联:`src/components/platform-entry/PlatformFeedbackView.tsx`、`docs/technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图 VectorEngine 图片生成密钥不能复用 DashScope / ARK key
|
## 拼图 VectorEngine 图片生成密钥不能复用 DashScope / ARK key
|
||||||
|
|
||||||
@@ -364,7 +364,7 @@
|
|||||||
- 原因:拼图 `gpt-image-2` / 历史 `nanobanana2` 图片生成已统一走 VectorEngine;后端只读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY`、`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,不会用 `DASHSCOPE_API_KEY`、`LLM_API_KEY`、`ARK_API_KEY` 或 `APIMART_API_KEY` 兜底。
|
- 原因:拼图 `gpt-image-2` / 历史 `nanobanana2` 图片生成已统一走 VectorEngine;后端只读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY`、`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,不会用 `DASHSCOPE_API_KEY`、`LLM_API_KEY`、`ARK_API_KEY` 或 `APIMART_API_KEY` 兜底。
|
||||||
- 处理:在本机私密配置 `.env.secrets.local` 或进程环境中配置真实 `VECTOR_ENGINE_API_KEY`,不要提交到 Git;填入后必须重启 `api-server` / `npm run dev`,运行中的进程不会自动加载新 env。
|
- 处理:在本机私密配置 `.env.secrets.local` 或进程环境中配置真实 `VECTOR_ENGINE_API_KEY`,不要提交到 Git;填入后必须重启 `api-server` / `npm run dev`,运行中的进程不会自动加载新 env。
|
||||||
- 验证:不打印密钥内容,只检查 `VECTOR_ENGINE_API_KEY` 非空;重启后触发拼图生成不再返回本地配置缺失的 503。
|
- 验证:不打印密钥内容,只检查 `VECTOR_ENGINE_API_KEY` 非空;重启后触发拼图生成不再返回本地配置缺失的 503。
|
||||||
- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`.codex/skills/gpt-image-2-apimart/SKILL.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## `npm run api-server` 读取 env 的顺序必须让 `.env.secrets.local` 最后覆盖
|
## `npm run api-server` 读取 env 的顺序必须让 `.env.secrets.local` 最后覆盖
|
||||||
|
|
||||||
@@ -372,7 +372,7 @@
|
|||||||
- 原因:`scripts/api-server-dev.mjs` 之前按 `.env.secrets.local → .env.local → .env` 合并,结果仓库里的 `.env` 空示例值会把前面已经设置好的私密 key 覆盖掉。
|
- 原因:`scripts/api-server-dev.mjs` 之前按 `.env.secrets.local → .env.local → .env` 合并,结果仓库里的 `.env` 空示例值会把前面已经设置好的私密 key 覆盖掉。
|
||||||
- 处理:`npm run api-server` / `npm run dev:rust` / `npm run dev` 统一按“外层 shell 变量优先,其后 `.env`、`.env.local`、`.env.secrets.local` 逐层覆盖”的顺序加载;真实密钥优先放 `.env.secrets.local`。
|
- 处理:`npm run api-server` / `npm run dev:rust` / `npm run dev` 统一按“外层 shell 变量优先,其后 `.env`、`.env.local`、`.env.secrets.local` 逐层覆盖”的顺序加载;真实密钥优先放 `.env.secrets.local`。
|
||||||
- 验证:本地加入临时测试后,`HYPER3D_API_KEY` 应能被 `.env.secrets.local` 覆盖,且 shell 变量仍然最高优先级。
|
- 验证:本地加入临时测试后,`HYPER3D_API_KEY` 应能被 `.env.secrets.local` 覆盖,且 shell 变量仍然最高优先级。
|
||||||
- 关联:`scripts/api-server-dev.mjs`、`server-rs/crates/api-server/src/hyper3d_generation.rs`、`docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## OSS 密钥键名不要把字母 O 写成数字 0
|
## OSS 密钥键名不要把字母 O 写成数字 0
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@
|
|||||||
- 原因:`platform-oss` 曾用 `OffsetDateTime::time().to_string()` 拼接 `x-oss-date`,UTC 小时、分钟或秒为个位数时可能缺少前导零,导致 V4 签名时间不是固定 `YYYYMMDDTHHMMSSZ`。
|
- 原因:`platform-oss` 曾用 `OffsetDateTime::time().to_string()` 拼接 `x-oss-date`,UTC 小时、分钟或秒为个位数时可能缺少前导零,导致 V4 签名时间不是固定 `YYYYMMDDTHHMMSSZ`。
|
||||||
- 处理:OSS V4 签名日期统一显式补零格式化;签名 scope 用 `YYYYMMDD`,完整签名时间用 `YYYYMMDDTHHMMSSZ`,不要再依赖 `time().to_string()`。
|
- 处理:OSS V4 签名日期统一显式补零格式化;签名 scope 用 `YYYYMMDD`,完整签名时间用 `YYYYMMDDTHHMMSSZ`,不要再依赖 `time().to_string()`。
|
||||||
- 验证:运行 `cargo test -p platform-oss` 和 `cargo check -p api-server`;重启 `npm run api-server` 后检查 `/healthz`,再重新触发拼图生成。
|
- 验证:运行 `cargo test -p platform-oss` 和 `cargo check -p api-server`;重启 `npm run api-server` 后检查 `/healthz`,再重新触发拼图生成。
|
||||||
- 关联:`server-rs/crates/platform-oss/src/lib.rs`、`server-rs/crates/api-server/src/assets.rs`、`docs/technical/M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图生成完成后图片只显示破图或 alt 文案
|
## 拼图生成完成后图片只显示破图或 alt 文案
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@
|
|||||||
- 原因:拼图正式图保存为 `/generated-puzzle-assets/*` 兼容标识,旧 `/generated-*` 直读代理已删除;如果前端没有通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签,或收到无前导斜杠的 `generated-puzzle-assets/*` object key 后未识别为 generated 私有资源,浏览器会直接请求裸路径并失败。生成完成后的结果图还会传入 `refreshKey`,它只能用于重新请求 `/api/assets/read-url`,不能给 OSS V4 签名 URL 追加 `_v`;OSS 会把 query 纳入签名,额外参数会让签名失效,历史素材常因未传 `refreshKey` 而表现正常。
|
- 原因:拼图正式图保存为 `/generated-puzzle-assets/*` 兼容标识,旧 `/generated-*` 直读代理已删除;如果前端没有通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签,或收到无前导斜杠的 `generated-puzzle-assets/*` object key 后未识别为 generated 私有资源,浏览器会直接请求裸路径并失败。生成完成后的结果图还会传入 `refreshKey`,它只能用于重新请求 `/api/assets/read-url`,不能给 OSS V4 签名 URL 追加 `_v`;OSS 会把 query 纳入签名,额外参数会让签名失效,历史素材常因未传 `refreshKey` 而表现正常。
|
||||||
- 处理:拼图结果页、发布预览、运行态和历史素材预览都走 `ResolvedAssetImage` 或 `useResolvedAssetReadUrl`;`isGeneratedLegacyPath(...)` 必须同时识别 `/generated-*` 和 `generated-*`;`refreshKey` 只绕过前端签名缓存并重新换签,不修改已返回的 OSS 签名 URL;禁止恢复 `/generated-puzzle-assets` 直读代理。
|
- 处理:拼图结果页、发布预览、运行态和历史素材预览都走 `ResolvedAssetImage` 或 `useResolvedAssetReadUrl`;`isGeneratedLegacyPath(...)` 必须同时识别 `/generated-*` 和 `generated-*`;`refreshKey` 只绕过前端签名缓存并重新换签,不修改已返回的 OSS 签名 URL;禁止恢复 `/generated-puzzle-assets` 直读代理。
|
||||||
- 验证:运行 `npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx`,再触发一次真实生成确认 Network 中先请求 `/api/assets/read-url`,图片 `src` 为未追加 `_v` 的签名 URL。
|
- 验证:运行 `npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx`,再触发一次真实生成确认 Network 中先请求 `/api/assets/read-url`,图片 `src` 为未追加 `_v` 的签名 URL。
|
||||||
- 关联:`src/services/assetReadUrlService.ts`、`src/components/ResolvedAssetImage.tsx`、`docs/technical/PUZZLE_IMAGE_ASSET_PROXY_FIX_2026-04-27.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 本地短信登录页签突然消失
|
## 本地短信登录页签突然消失
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@
|
|||||||
- 单独 `npm run dev:web` 启动瞬间另一个临时 API 端口可用,脚本若自动切过去,之后临时 API 停掉也会让 3000 继续代理到空端口。
|
- 单独 `npm run dev:web` 启动瞬间另一个临时 API 端口可用,脚本若自动切过去,之后临时 API 停掉也会让 3000 继续代理到空端口。
|
||||||
- 处理:优先用 `npm run api-server`、`npm run dev:rust` 或 `npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local` 的 `SMS_AUTH_ENABLED` 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启。
|
- 处理:优先用 `npm run api-server`、`npm run dev:rust` 或 `npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local` 的 `SMS_AUTH_ENABLED` 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启。
|
||||||
- 验证:`http://127.0.0.1:3000/api/auth/login-options` 返回至少 `{"availableLoginMethods":["phone","password"]}` 后,登录弹窗会恢复短信登录页签和“获取验证码”按钮。
|
- 验证:`http://127.0.0.1:3000/api/auth/login-options` 返回至少 `{"availableLoginMethods":["phone","password"]}` 后,登录弹窗会恢复短信登录页签和“获取验证码”按钮。
|
||||||
- 关联:`scripts/api-server-dev.mjs`、`scripts/api-server-maincloud.mjs`、`scripts/dev-rust-stack.sh`、`scripts/dev-web-rust.mjs`、`docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 本地短信收不到验证码先查 provider
|
## 本地短信收不到验证码先查 provider
|
||||||
|
|
||||||
@@ -416,7 +416,7 @@
|
|||||||
- 原因:本地 `.env.local` 里如果是 `SMS_AUTH_PROVIDER="mock"`,后端不会发真实短信,只会返回固定 mock 验证码;另外 `npm run api-server` 过去曾让 `.env` 覆盖 `.env.local`,导致本地真实短信配置被错误压回默认值。
|
- 原因:本地 `.env.local` 里如果是 `SMS_AUTH_PROVIDER="mock"`,后端不会发真实短信,只会返回固定 mock 验证码;另外 `npm run api-server` 过去曾让 `.env` 覆盖 `.env.local`,导致本地真实短信配置被错误压回默认值。
|
||||||
- 处理:真实短信联调时把 `.env.local` 的 `SMS_AUTH_PROVIDER` 显式设为 `aliyun`,然后重启 `api-server`;如果只想验证 UI 和账号链路,则保留 `mock` 并使用 `SMS_AUTH_MOCK_VERIFY_CODE`。
|
- 处理:真实短信联调时把 `.env.local` 的 `SMS_AUTH_PROVIDER` 显式设为 `aliyun`,然后重启 `api-server`;如果只想验证 UI 和账号链路,则保留 `mock` 并使用 `SMS_AUTH_MOCK_VERIFY_CODE`。
|
||||||
- 验证:`GET /api/auth/login-options` 返回 `["phone","password"]`,`api-server` 日志里 `provider=aliyun` 才说明真实短信链路已生效。
|
- 验证:`GET /api/auth/login-options` 返回 `["phone","password"]`,`api-server` 日志里 `provider=aliyun` 才说明真实短信链路已生效。
|
||||||
- 关联:`scripts/api-server-dev.mjs`、`docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md`、`docs/technical/PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 手机验证码登录 500 先查短信 provider 语义
|
## 手机验证码登录 500 先查短信 provider 语义
|
||||||
|
|
||||||
@@ -424,7 +424,7 @@
|
|||||||
- 原因:真实短信 provider 的配置错误或上游失败曾被 `module-auth` 折叠成 `PhoneAuthError::Store`,HTTP 层只能按内部错误返回 `500`,掩盖了 provider 失败。
|
- 原因:真实短信 provider 的配置错误或上游失败曾被 `module-auth` 折叠成 `PhoneAuthError::Store`,HTTP 层只能按内部错误返回 `500`,掩盖了 provider 失败。
|
||||||
- 处理:保留 provider 错误语义,配置错误映射 `503 Service Unavailable`,上游短信失败映射 `502 Bad Gateway`;本地只验证 UI/账号链路时可用 shell 临时覆盖 `SMS_AUTH_PROVIDER=mock` 后启动 `npm run api-server`。
|
- 处理:保留 provider 错误语义,配置错误映射 `503 Service Unavailable`,上游短信失败映射 `502 Bad Gateway`;本地只验证 UI/账号链路时可用 shell 临时覆盖 `SMS_AUTH_PROVIDER=mock` 后启动 `npm run api-server`。
|
||||||
- 验证:`cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`,真实 provider 频控时接口不再返回 `500`。
|
- 验证:`cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`,真实 provider 频控时接口不再返回 `500`。
|
||||||
- 关联:`server-rs/crates/module-auth/src/errors.rs`、`server-rs/crates/api-server/src/phone_auth.rs`、`docs/technical/PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 手机验证码登录成功后又瞬间回到未登录
|
## 手机验证码登录成功后又瞬间回到未登录
|
||||||
|
|
||||||
@@ -432,7 +432,7 @@
|
|||||||
- 原因:`AuthGate` 首次 hydrate 会异步轮换 refresh cookie 并请求 `/api/auth/me`。如果用户在 hydrate 完成前已经登录,晚到的旧 hydrate 仍可能把刚写入的 `user` 覆盖成 `null`。
|
- 原因:`AuthGate` 首次 hydrate 会异步轮换 refresh cookie 并请求 `/api/auth/me`。如果用户在 hydrate 完成前已经登录,晚到的旧 hydrate 仍可能把刚写入的 `user` 覆盖成 `null`。
|
||||||
- 处理:给 `AuthGate` 的 hydrate 增加版本号保护;登录成功、退出登录和全局 auth 事件都会推进版本号,旧 hydrate 结果到达后直接丢弃。
|
- 处理:给 `AuthGate` 的 hydrate 增加版本号保护;登录成功、退出登录和全局 auth 事件都会推进版本号,旧 hydrate 结果到达后直接丢弃。
|
||||||
- 验证:`npm run test -- src/components/auth/AuthGate.test.tsx`,新增用例应覆盖“旧 guest hydrate 不覆盖新登录态”。
|
- 验证:`npm run test -- src/components/auth/AuthGate.test.tsx`,新增用例应覆盖“旧 guest hydrate 不覆盖新登录态”。
|
||||||
- 关联:`src/components/auth/AuthGate.tsx`、`src/components/auth/AuthGate.test.tsx`、`docs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 刷新网页后登录态失效
|
## 刷新网页后登录态失效
|
||||||
|
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
- 原因:`AuthGate` hydrate 曾先强制调用 `refreshStoredAccessToken()`;当 refresh cookie 临时失效、代理错配或后端返回 `401` 时,该方法会先清空本地 access token,随后 `/api/auth/me` 只能恢复成未登录。
|
- 原因:`AuthGate` hydrate 曾先强制调用 `refreshStoredAccessToken()`;当 refresh cookie 临时失效、代理错配或后端返回 `401` 时,该方法会先清空本地 access token,随后 `/api/auth/me` 只能恢复成未登录。
|
||||||
- 处理:`refreshStoredAccessToken()` 增加 `clearOnFailure` 选项;`AuthGate` 在已有本地 access token 时先用 `/api/auth/me` 确认用户,确认成功后再后台 refresh 续期与写每日登录埋点,后台 refresh 失败不清 token。
|
- 处理:`refreshStoredAccessToken()` 增加 `clearOnFailure` 选项;`AuthGate` 在已有本地 access token 时先用 `/api/auth/me` 确认用户,确认成功后再后台 refresh 续期与写每日登录埋点,后台 refresh 失败不清 token。
|
||||||
- 验证:`npm run test -- src/services/apiClient.test.ts src/components/auth/AuthGate.test.tsx -t "explicit refresh opts out|auth gate keeps a valid local token login"`。
|
- 验证:`npm run test -- src/services/apiClient.test.ts src/components/auth/AuthGate.test.tsx -t "explicit refresh opts out|auth gate keeps a valid local token login"`。
|
||||||
- 关联:`src/services/apiClient.ts`、`src/components/auth/AuthGate.tsx`、`docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 登录后推荐页加载出作品又回到未登录
|
## 登录后推荐页加载出作品又回到未登录
|
||||||
|
|
||||||
@@ -451,7 +451,7 @@
|
|||||||
- 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,`startPuzzleRun`、通关自动 `submitPuzzleLeaderboard`、下一关 `advancePuzzleNextLevel` 和重开同样属于当前玩法局部同步;这些请求失败时只应留在拼图错误态,不应清 token 或广播 auth 事件。
|
- 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,`startPuzzleRun`、通关自动 `submitPuzzleLeaderboard`、下一关 `advancePuzzleNextLevel` 和重开同样属于当前玩法局部同步;这些请求失败时只应留在拼图错误态,不应清 token 或广播 auth 事件。
|
||||||
- 追加处理:通关后 `refreshSaveArchives()`、首屏 bootstrap 的个人看板/作品架/浏览历史读写也只是平台投影刷新,失败应显示局部错误,不能充当全局登录态判定。
|
- 追加处理:通关后 `refreshSaveArchives()`、首屏 bootstrap 的个人看板/作品架/浏览历史读写也只是平台投影刷新,失败应显示局部错误,不能充当全局登录态判定。
|
||||||
- 验证:`npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"`。
|
- 验证:`npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"`。
|
||||||
- 关联:`src/services/apiClient.ts`、`src/services/assetReadUrlService.ts`、`src/services/puzzle-runtime/puzzleRuntimeClient.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 推荐页作品卡一直显示加载中
|
## 推荐页作品卡一直显示加载中
|
||||||
|
|
||||||
@@ -459,7 +459,7 @@
|
|||||||
- 原因:推荐页自动启动嵌入运行态时先设置 `activeRecommendEntryKey` / `activeRecommendRuntimeKind` / `isStartingRecommendEntry`,但失败或并发切换时外层缺少稳定错误态和请求版本保护,旧启动请求可能晚到覆盖新状态。
|
- 原因:推荐页自动启动嵌入运行态时先设置 `activeRecommendEntryKey` / `activeRecommendRuntimeKind` / `isStartingRecommendEntry`,但失败或并发切换时外层缺少稳定错误态和请求版本保护,旧启动请求可能晚到覆盖新状态。
|
||||||
- 处理:`selectRecommendRuntimeEntry` 使用启动请求版本号丢弃旧请求;启动失败统一设置 `activeRecommendRuntimeError = "作品暂时无法进入,请稍后再试。"` 并关闭 `isStartingRecommendEntry`。
|
- 处理:`selectRecommendRuntimeEntry` 使用启动请求版本号丢弃旧请求;启动失败统一设置 `activeRecommendRuntimeError = "作品暂时无法进入,请稍后再试。"` 并关闭 `isStartingRecommendEntry`。
|
||||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation surfaces start failure"`。
|
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation surfaces start failure"`。
|
||||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 推荐页未登录入口误打开公开详情
|
## 推荐页未登录入口误打开公开详情
|
||||||
|
|
||||||
@@ -467,7 +467,7 @@
|
|||||||
- 原因:`RpgEntryHomeView` 曾只有 `onOpenGalleryDetail` 一个回调,同时服务发现页公开详情和推荐页作品入口;一旦为发现页保留公开浏览能力,推荐页也会跟着打开详情。
|
- 原因:`RpgEntryHomeView` 曾只有 `onOpenGalleryDetail` 一个回调,同时服务发现页公开详情和推荐页作品入口;一旦为发现页保留公开浏览能力,推荐页也会跟着打开详情。
|
||||||
- 处理:公开详情与推荐页入口分离为 `onOpenGalleryDetail` 和 `onOpenRecommendGalleryDetail`。发现页、搜索和排行榜保留公开详情;推荐 Tab、推荐封面、推荐运行态错误重试和桌面推荐模块统一走登录门禁。未登录推荐页只显示封面,点击封面只弹登录窗,不携带登录后自动打开详情的回调。
|
- 处理:公开详情与推荐页入口分离为 `onOpenGalleryDetail` 和 `onOpenRecommendGalleryDetail`。发现页、搜索和排行榜保留公开详情;推荐 Tab、推荐封面、推荐运行态错误重试和桌面推荐模块统一走登录门禁。未登录推荐页只显示封面,点击封面只弹登录窗,不携带登录后自动打开详情的回调。
|
||||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "logged out recommend"`。
|
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "logged out recommend"`。
|
||||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## Rust 冷编译导致 api-server 健康检查误超时
|
## Rust 冷编译导致 api-server 健康检查误超时
|
||||||
|
|
||||||
@@ -475,7 +475,7 @@
|
|||||||
- 原因:脚本把 SpacetimeDB 与 api-server 等待窗口混在一起,未考虑 Rust 冷编译耗时。
|
- 原因:脚本把 SpacetimeDB 与 api-server 等待窗口混在一起,未考虑 Rust 冷编译耗时。
|
||||||
- 处理:按冷编译超时修复文档拆分等待窗口。
|
- 处理:按冷编译超时修复文档拆分等待窗口。
|
||||||
- 验证:冷启动时不再误杀仍在编译的 api-server。
|
- 验证:冷启动时不再误杀仍在编译的 api-server。
|
||||||
- 关联:`docs/technical/API_SERVER_DEV_STACK_COLD_BUILD_TIMEOUT_FIX_2026-04-25.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## Windows debug api-server 主线程栈溢出
|
## Windows debug api-server 主线程栈溢出
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@
|
|||||||
- 原因:旧 `api-server` 仍监听默认 `8082` 时,脚本的 `/healthz` 探测会命中旧进程并误判新服务已就绪;旧 Vite 占住 `3000` 时,Vite 默认漂移到新端口,浏览器仍可能打开旧页面。
|
- 原因:旧 `api-server` 仍监听默认 `8082` 时,脚本的 `/healthz` 探测会命中旧进程并误判新服务已就绪;旧 Vite 占住 `3000` 时,Vite 默认漂移到新端口,浏览器仍可能打开旧页面。
|
||||||
- 处理:`scripts/dev-rust-stack.sh` 已在 publish / 编译前预检 `api-server`、主站 Vite、后台 Vite 端口,并让 Vite 使用 `--strictPort`;遇到端口占用时按脚本打印的 PID 停止旧进程,或显式传入 `--api-port` / `--web-port` / `--admin-web-port`。
|
- 处理:`scripts/dev-rust-stack.sh` 已在 publish / 编译前预检 `api-server`、主站 Vite、后台 Vite 端口,并让 Vite 使用 `--strictPort`;遇到端口占用时按脚本打印的 PID 停止旧进程,或显式传入 `--api-port` / `--web-port` / `--admin-web-port`。
|
||||||
- 验证:默认端口被占用时,完整栈应在发布模块前直接失败并打印监听进程;清理端口后重新启动不再漂移端口或命中旧 `/healthz`。
|
- 验证:默认端口被占用时,完整栈应在发布模块前直接失败并打印监听进程;清理端口后重新启动不再漂移端口或命中旧 `/healthz`。
|
||||||
- 关联:`scripts/dev-rust-stack.sh`、`docs/technical/DEV_RUST_STACK_PORT_CONFLICT_PRECHECK_2026-05-09.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## Windows debug 长 SSE Future 触发 api-server 断连
|
## Windows debug 长 SSE Future 触发 api-server 断连
|
||||||
|
|
||||||
@@ -518,7 +518,7 @@
|
|||||||
- 工具开始事件必须等同一 `toolCallId` 的 `tool_completed` 收口;兼容旧流时可按后续同名完成事件兜底。
|
- 工具开始事件必须等同一 `toolCallId` 的 `tool_completed` 收口;兼容旧流时可按后续同名完成事件兜底。
|
||||||
- 思考摘要只展示用户可见摘要,且流结束或会话进入等待/完成/失败态后必须改成 done。
|
- 思考摘要只展示用户可见摘要,且流结束或会话进入等待/完成/失败态后必须改成 done。
|
||||||
- 验证:前端测试断言完成后 `CreativeAgentProcessItem` 不再存在 `tone === 'active'`;后端测试确认工具开始/完成事件使用相同 `toolCallId`。
|
- 验证:前端测试断言完成后 `CreativeAgentProcessItem` 不再存在 `tone === 'active'`;后端测试确认工具开始/完成事件使用相同 `toolCallId`。
|
||||||
- 关联:`src/components/creative-agent/creativeAgentViewModel.ts`、`server-rs/crates/api-server/src/creative_agent.rs`、`docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## creative-agent 会话切换要清理本地待确认模板
|
## creative-agent 会话切换要清理本地待确认模板
|
||||||
|
|
||||||
@@ -534,15 +534,15 @@
|
|||||||
- 原因:前端上传与预览容易混在一起,若不走平台资产对象,SpacetimeDB 和长期草稿会被大文本或大二进制污染。
|
- 原因:前端上传与预览容易混在一起,若不走平台资产对象,SpacetimeDB 和长期草稿会被大文本或大二进制污染。
|
||||||
- 处理:VN 资产统一用 `/api/assets/direct-upload-tickets`、OSS 直传、`/api/assets/objects/confirm`,长期状态只保存 `assetObjectId` 和 `/generated-*` 引用;运行时图片用 `ResolvedAssetImage` 换签。
|
- 处理:VN 资产统一用 `/api/assets/direct-upload-tickets`、OSS 直传、`/api/assets/objects/confirm`,长期状态只保存 `assetObjectId` 和 `/generated-*` 引用;运行时图片用 `ResolvedAssetImage` 换签。
|
||||||
- 验证:文档模式 `sourceAssetIds` 为平台资产 id;草稿中不出现 `data:`;图片和音乐字段为平台 generated 引用或 null。
|
- 验证:文档模式 `sourceAssetIds` 为平台资产 id;草稿中不出现 `data:`;图片和音乐字段为平台 generated 引用或 null。
|
||||||
- 关联:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`src/services/visual-novel-creation/visualNovelAssetClient.ts`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 视觉小说 VN-13 交接时不要再回头找旧迁移方案
|
## 视觉小说 VN-13 交接时不要再回头找旧迁移方案
|
||||||
|
|
||||||
- 现象:接手视觉小说的人容易重新打开旧 TXT 迁移文档,把“外部平台工程迁入”误当成当前实现目标。
|
- 现象:接手视觉小说的人容易重新打开旧 TXT 迁移文档,把“外部平台工程迁入”误当成当前实现目标。
|
||||||
- 原因:视觉小说历史资料里保留了很多迁移阶段的讨论,而当前真正的实现口径已经收口到 PRD、表目录、Prompt 工具说明、实现收口文档和负向扫描报告。
|
- 原因:视觉小说历史资料里保留了很多迁移阶段的讨论,而当前真正的实现口径已经收口到 PRD、表目录、Prompt 工具说明、实现收口文档和负向扫描报告。
|
||||||
- 处理:维护视觉小说时优先看 `AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`SPACETIMEDB_TABLE_CATALOG.md`、`VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md`、`VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`、`VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md` 和 `VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`。
|
- 处理:维护视觉小说时优先看当前玩法链路文档、当前后端架构文档,并运行 `npm run check:visual-novel-vn11` / `npm run check:visual-novel-vn12` 确认负向扫描和全链路验收。
|
||||||
- 验证:新开发者只读这组文档即可继续维护,不需要把旧 TXT 迁移方案重新当作编码依据。
|
- 验证:新开发者只读这组文档即可继续维护,不需要把旧 TXT 迁移方案重新当作编码依据。
|
||||||
- 关联:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`、`docs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 视觉小说公开广场不要触发登录刷新
|
## 视觉小说公开广场不要触发登录刷新
|
||||||
|
|
||||||
@@ -550,7 +550,7 @@
|
|||||||
- 原因:公开只读接口如果复用默认 `requestJson` 选项,缺少 access token 时会先走静默 refresh。
|
- 原因:公开只读接口如果复用默认 `requestJson` 选项,缺少 access token 时会先走静默 refresh。
|
||||||
- 处理:视觉小说公开广场列表使用 `skipAuth: true` 与 `skipRefresh: true`;鉴权 mutation 仍保持默认鉴权链路。
|
- 处理:视觉小说公开广场列表使用 `skipAuth: true` 与 `skipRefresh: true`;鉴权 mutation 仍保持默认鉴权链路。
|
||||||
- 验证:执行 `src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts`,确认 `/api/runtime/visual-novel/gallery` 请求携带 `skipAuth` / `skipRefresh`,而 run、重生成和存档 mutation 仍走受保护路由。
|
- 验证:执行 `src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts`,确认 `/api/runtime/visual-novel/gallery` 请求携带 `skipAuth` / `skipRefresh`,而 run、重生成和存档 mutation 仍走受保护路由。
|
||||||
- 关联:`src/services/visual-novel-runtime/visualNovelRuntimeClient.ts`、`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 创作 Tab 语义迁移后,旧“新建作品”测试要改看智能创作首页
|
## 创作 Tab 语义迁移后,旧“新建作品”测试要改看智能创作首页
|
||||||
|
|
||||||
@@ -566,7 +566,7 @@
|
|||||||
- 原因:workspace default-members 当前只包含 `crates/api-server`;SpacetimeDB module 有独立构建/发布方式。
|
- 原因:workspace default-members 当前只包含 `crates/api-server`;SpacetimeDB module 有独立构建/发布方式。
|
||||||
- 处理:默认 Rust 构建只覆盖原生 `api-server`;本地模块发布继续走 `spacetime publish --module-path ... --build-options="--debug"` / bindings 生成流程。
|
- 处理:默认 Rust 构建只覆盖原生 `api-server`;本地模块发布继续走 `spacetime publish --module-path ... --build-options="--debug"` / bindings 生成流程。
|
||||||
- 验证:查看 `server-rs/Cargo.toml` default-members,并按相关 SpacetimeDB 文档执行模块构建。
|
- 验证:查看 `server-rs/Cargo.toml` default-members,并按相关 SpacetimeDB 文档执行模块构建。
|
||||||
- 关联:`server-rs/Cargo.toml`、`docs/technical/RUST_WORKSPACE_DEFAULT_BUILD_SCOPE_FIX_2026-04-25.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## Windows 原生 `spacetime-module` 单测会链接缺失 SpacetimeDB 宿主符号
|
## Windows 原生 `spacetime-module` 单测会链接缺失 SpacetimeDB 宿主符号
|
||||||
|
|
||||||
@@ -574,7 +574,7 @@
|
|||||||
- 原因:`spacetime-module` 依赖的 SpacetimeDB runtime API 面向 wasm 宿主环境,原生 test exe 链接不到这些宿主导出。
|
- 原因:`spacetime-module` 依赖的 SpacetimeDB runtime API 面向 wasm 宿主环境,原生 test exe 链接不到这些宿主导出。
|
||||||
- 处理:日常语法和类型验证使用 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`;需要验证模块行为时走 SpacetimeDB publish/dev 或模块域纯 Rust crate 的单测,不把该原生链接错误当作业务测试失败。
|
- 处理:日常语法和类型验证使用 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`;需要验证模块行为时走 SpacetimeDB publish/dev 或模块域纯 Rust crate 的单测,不把该原生链接错误当作业务测试失败。
|
||||||
- 验证:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能通过;原生 `cargo test` 若仍报上述宿主符号缺失,按当前限制记录为未执行。
|
- 验证:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能通过;原生 `cargo test` 若仍报上述宿主符号缺失,按当前限制记录为未执行。
|
||||||
- 关联:`server-rs/crates/spacetime-module`、`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`。
|
- 关联:`server-rs/crates/spacetime-module`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## Rust 构建不要让不可用的 sccache 阻断 rustc
|
## Rust 构建不要让不可用的 sccache 阻断 rustc
|
||||||
|
|
||||||
@@ -582,15 +582,15 @@
|
|||||||
- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`,sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时,Cargo 的 `sccache rustc -vV` 可能先超时。
|
- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`,sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时,Cargo 的 `sccache rustc -vV` 可能先超时。
|
||||||
- 处理:保留 `server-rs/.cargo/config.toml` 的 `rustc-wrapper = "sccache"`;Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro` 的 `sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。
|
- 处理:保留 `server-rs/.cargo/config.toml` 的 `rustc-wrapper = "sccache"`;Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro` 的 `sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。
|
||||||
- 验证:`rustc -Vv` 能输出版本;冷启动后原始 `cargo check -p api-server` 和 `cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明仍在使用 sccache/OSS 缓存;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。
|
- 验证:`rustc -Vv` 能输出版本;冷启动后原始 `cargo check -p api-server` 和 `cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明仍在使用 sccache/OSS 缓存;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。
|
||||||
- 关联:`scripts/dev-rust-stack.sh`、`jenkins/Jenkinsfile.production-stdb-module-build`、`docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本
|
## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本
|
||||||
|
|
||||||
- 现象:部署、回滚或 Jenkins Job 重建时参考旧发布文档,导致 systemd、Nginx、SpacetimeDB 自托管和生产包拆分不一致。
|
- 现象:部署、回滚或 Jenkins Job 重建时参考旧发布文档,导致 systemd、Nginx、SpacetimeDB 自托管和生产包拆分不一致。
|
||||||
- 原因:旧 Jenkins / 旧本地远端部署脚本文档仍作为历史经验保留。
|
- 原因:旧 Jenkins / 旧本地远端部署脚本文档仍作为历史经验保留。
|
||||||
- 处理:生产相关操作先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`,再按需追溯旧文档。
|
- 处理:生产相关操作先看 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`,再按需追溯旧文档。
|
||||||
- 验证:发布链路使用当前 `deploy/systemd`、`deploy/nginx`、`scripts/deploy` 和 `jenkins/Jenkinsfile.production-*`。
|
- 验证:发布链路使用当前 `deploy/systemd`、`deploy/nginx`、`scripts/deploy` 和 `jenkins/Jenkinsfile.production-*`。
|
||||||
- 关联:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
- 关联:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## Release Web 产物通过内网 rsync 拉取
|
## Release Web 产物通过内网 rsync 拉取
|
||||||
|
|
||||||
@@ -598,7 +598,7 @@
|
|||||||
- 原因:Web 大包为了避免从 Linux 构建机拉回 Jenkins controller,默认留在构建机稳定缓存目录;development 目标与构建机同机可直接读取,release 目标是独立机器,需要内网同步。
|
- 原因:Web 大包为了避免从 Linux 构建机拉回 Jenkins controller,默认留在构建机稳定缓存目录;development 目标与构建机同机可直接读取,release 目标是独立机器,需要内网同步。
|
||||||
- 处理:release 服务器的 Jenkins 运行用户配置 SSH Host `genarrative-build-internal` 指向构建机内网地址,`Genarrative-Web-Deploy` 在 `DEPLOY_TARGET=release` 且本地缺少大包时默认执行 `rsync` 拉取同一路径内容;真实内网 IP、用户和私钥路径只放在服务器本机 SSH config,不写入 Jenkinsfile。
|
- 处理:release 服务器的 Jenkins 运行用户配置 SSH Host `genarrative-build-internal` 指向构建机内网地址,`Genarrative-Web-Deploy` 在 `DEPLOY_TARGET=release` 且本地缺少大包时默认执行 `rsync` 拉取同一路径内容;真实内网 IP、用户和私钥路径只放在服务器本机 SSH config,不写入 Jenkinsfile。
|
||||||
- 验证:在 release 服务器上先手工跑通 `rsync -av --progress "genarrative-build-internal:${SRC}/" "${DST}/"`,再运行 Web Deploy;流水线会继续执行 `web.tar.gz.sha256` 校验。
|
- 验证:在 release 服务器上先手工跑通 `rsync -av --progress "genarrative-build-internal:${SRC}/" "${DST}/"`,再运行 Web Deploy;流水线会继续执行 `web.tar.gz.sha256` 校验。
|
||||||
- 关联:`jenkins/Jenkinsfile.production-web-deploy`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
- 关联:`jenkins/Jenkinsfile.production-web-deploy`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## Jenkins 生产流水线拉 Git 先本机再域名备用
|
## Jenkins 生产流水线拉 Git 先本机再域名备用
|
||||||
|
|
||||||
@@ -606,7 +606,7 @@
|
|||||||
- 原因:`127.0.0.1` 只代表当前执行阶段的 agent 自身;当 release agent 与 Git 服务不在同一台机器,或本机 Git/Web 服务临时不可用时,固定写死 localhost 会阻断 Jenkinsfile 内部源码/脚本 checkout。即使只使用域名 Git,如果 `GitSCM` 没有显式 refspec 并开启 `CloneOption honorRefspec=true`,Jenkins Git 插件也会拉取所有分支。
|
- 原因:`127.0.0.1` 只代表当前执行阶段的 agent 自身;当 release agent 与 Git 服务不在同一台机器,或本机 Git/Web 服务临时不可用时,固定写死 localhost 会阻断 Jenkinsfile 内部源码/脚本 checkout。即使只使用域名 Git,如果 `GitSCM` 没有显式 refspec 并开启 `CloneOption honorRefspec=true`,Jenkins Git 插件也会拉取所有分支。
|
||||||
- 处理:Jenkins Job 的 `Pipeline script from SCM` 由 Windows controller 执行,SCM URL 使用公网域名 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。运行于 Linux agent 的 Jenkinsfile 首次 `checkout([$class: 'GitSCM', ...])` 层先尝试 `GIT_REMOTE_URL=http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后直接尝试 `GIT_REMOTE_FALLBACK_URL=https://git.genarrative.world/GenarrativeAI/Genarrative.git`,不再配置内网 IP fallback;所有生产 Jenkinsfile 的首次 checkout 都必须使用目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`。后续统一走 `scripts/jenkins-checkout-source.sh`,该脚本也按主地址、域名备用地址顺序重新 fetch 并把 `origin` 切到实际可用地址;`COMMIT_HASH` 为空时继续 `--depth=1 --no-tags`,只有指定 commit 时才允许加深历史做分支归属校验。
|
- 处理:Jenkins Job 的 `Pipeline script from SCM` 由 Windows controller 执行,SCM URL 使用公网域名 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。运行于 Linux agent 的 Jenkinsfile 首次 `checkout([$class: 'GitSCM', ...])` 层先尝试 `GIT_REMOTE_URL=http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后直接尝试 `GIT_REMOTE_FALLBACK_URL=https://git.genarrative.world/GenarrativeAI/Genarrative.git`,不再配置内网 IP fallback;所有生产 Jenkinsfile 的首次 checkout 都必须使用目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`。后续统一走 `scripts/jenkins-checkout-source.sh`,该脚本也按主地址、域名备用地址顺序重新 fetch 并把 `origin` 切到实际可用地址;`COMMIT_HASH` 为空时继续 `--depth=1 --no-tags`,只有指定 commit 时才允许加深历史做分支归属校验。
|
||||||
- 验证:扫描本地 Jenkins live job `config.xml`,确认 SCM `<url>` 都是 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;扫描所有生产 Jenkinsfile 的首次 `GitSCM checkout`,确认 `userRemoteConfigs` 带 `+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}`,`CloneOption` 带 `honorRefspec: true`;运行 `bash -n scripts/jenkins-checkout-source.sh`。
|
- 验证:扫描本地 Jenkins live job `config.xml`,确认 SCM `<url>` 都是 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;扫描所有生产 Jenkinsfile 的首次 `GitSCM checkout`,确认 `userRemoteConfigs` 带 `+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}`,`CloneOption` 带 `honorRefspec: true`;运行 `bash -n scripts/jenkins-checkout-source.sh`。
|
||||||
- 关联:`jenkins/Jenkinsfile.production-full-build-and-deploy`、`jenkins/Jenkinsfile.production-web-build`、`jenkins/Jenkinsfile.production-api-build`、`jenkins/Jenkinsfile.production-stdb-module-build`、`jenkins/Jenkinsfile.production-web-deploy`、`jenkins/Jenkinsfile.production-api-deploy`、`jenkins/Jenkinsfile.production-stdb-module-publish`、`jenkins/Jenkinsfile.production-server-provision`、`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`、`scripts/jenkins-checkout-source.sh`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
- 关联:`jenkins/Jenkinsfile.production-full-build-and-deploy`、`jenkins/Jenkinsfile.production-web-build`、`jenkins/Jenkinsfile.production-api-build`、`jenkins/Jenkinsfile.production-stdb-module-build`、`jenkins/Jenkinsfile.production-web-deploy`、`jenkins/Jenkinsfile.production-api-deploy`、`jenkins/Jenkinsfile.production-stdb-module-publish`、`jenkins/Jenkinsfile.production-server-provision`、`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`、`scripts/jenkins-checkout-source.sh`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## Jenkins 可选参数在 set -u 下不能裸读
|
## Jenkins 可选参数在 set -u 下不能裸读
|
||||||
|
|
||||||
@@ -614,7 +614,7 @@
|
|||||||
- 原因:Jenkins string/boolean 参数留空时不一定会导出同名环境变量,而生产数据库导入导出脚本块启用了 `set -u`。
|
- 原因:Jenkins string/boolean 参数留空时不一定会导出同名环境变量,而生产数据库导入导出脚本块启用了 `set -u`。
|
||||||
- 处理:进入 Bash 执行块后先使用 `${VAR:-}` 或 `${VAR:-默认值}` 收敛成本地变量;必填项使用 `${VAR:?中文错误}` 明确失败原因。
|
- 处理:进入 Bash 执行块后先使用 `${VAR:-}` 或 `${VAR:-默认值}` 收敛成本地变量;必填项使用 `${VAR:?中文错误}` 明确失败原因。
|
||||||
- 验证:扫描 `jenkins/Jenkinsfile.production-database-export` 与 `jenkins/Jenkinsfile.production-database-import`,确认 `INCLUDE_TABLES`、`CHUNK_SIZE`、`SERVER_BACKUP_DIRECTORY`、`SMOKE_HEALTH_URL` 等可选参数不再裸读。
|
- 验证:扫描 `jenkins/Jenkinsfile.production-database-export` 与 `jenkins/Jenkinsfile.production-database-import`,确认 `INCLUDE_TABLES`、`CHUNK_SIZE`、`SERVER_BACKUP_DIRECTORY`、`SMOKE_HEALTH_URL` 等可选参数不再裸读。
|
||||||
- 关联:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`、`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`。
|
- 关联:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`。
|
||||||
|
|
||||||
## 个人任务 scope 不得扩成 work/site/module
|
## 个人任务 scope 不得扩成 work/site/module
|
||||||
|
|
||||||
@@ -622,7 +622,7 @@
|
|||||||
- 原因:首版个人任务只支持用户维度,非 user scope 会造成任务进度读取语义错误。
|
- 原因:首版个人任务只支持用户维度,非 user scope 会造成任务进度读取语义错误。
|
||||||
- 处理:Admin 任务配置页不展示范围选择,保存时固定 `scopeKind: 'user'`;API 和领域构造层拒绝非 `User`。
|
- 处理:Admin 任务配置页不展示范围选择,保存时固定 `scopeKind: 'user'`;API 和领域构造层拒绝非 `User`。
|
||||||
- 验证:非 `user` scope 返回错误;相关测试覆盖 `Site` / `Module` / `Work` 被拒绝。
|
- 验证:非 `user` scope 返回错误;相关测试覆盖 `Site` / `Module` / `Work` 被拒绝。
|
||||||
- 关联:`docs/technical/RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md`、`docs/technical/ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图发布 409 不一定是接口故障
|
## 拼图发布 409 不一定是接口故障
|
||||||
|
|
||||||
@@ -630,7 +630,7 @@
|
|||||||
- 原因:`publish_puzzle_work` 是资产操作发布入口,发布前会预扣 `1` 枚泥点;余额不足时后端按业务冲突返回 `409 CONFLICT`,`details.message` 为 `泥点余额不足`。
|
- 原因:`publish_puzzle_work` 是资产操作发布入口,发布前会预扣 `1` 枚泥点;余额不足时后端按业务冲突返回 `409 CONFLICT`,`details.message` 为 `泥点余额不足`。
|
||||||
- 处理:前端发布弹窗在用户点击发布后必须保留并展示后端业务错误,不能只把错误写到弹窗背后的页面 banner。
|
- 处理:前端发布弹窗在用户点击发布后必须保留并展示后端业务错误,不能只把错误写到弹窗背后的页面 banner。
|
||||||
- 验证:`PuzzleResultView` 单测覆盖发布弹窗内展示 `泥点余额不足`。
|
- 验证:`PuzzleResultView` 单测覆盖发布弹窗内展示 `泥点余额不足`。
|
||||||
- 关联:`src/components/puzzle-result/PuzzleResultView.tsx`、`docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md`、`docs/technical/ASSET_GENERATION_POINTS_CONSUMPTION_2026-04-27.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## WebGL 画布在高 DPR 移动端放大溢出
|
## WebGL 画布在高 DPR 移动端放大溢出
|
||||||
|
|
||||||
@@ -638,7 +638,7 @@
|
|||||||
- 原因:`WebGLRenderer.setPixelRatio(...)` 会把绘图缓冲区乘上设备 DPR;如果没有给 `renderer.domElement` 单独设置 CSS `width/height: 100%` 和绝对铺满,浏览器可能把高 DPR 缓冲区尺寸当成页面显示尺寸。
|
- 原因:`WebGLRenderer.setPixelRatio(...)` 会把绘图缓冲区乘上设备 DPR;如果没有给 `renderer.domElement` 单独设置 CSS `width/height: 100%` 和绝对铺满,浏览器可能把高 DPR 缓冲区尺寸当成页面显示尺寸。
|
||||||
- 处理:中心棋盘和托盘预览的 WebGL canvas 统一套用 `position:absolute; inset:0; width:100%; height:100%; display:block`,`renderer.setSize(..., false)` 只负责同步绘图缓冲区。
|
- 处理:中心棋盘和托盘预览的 WebGL canvas 统一套用 `position:absolute; inset:0; width:100%; height:100%; display:block`,`renderer.setSize(..., false)` 只负责同步绘图缓冲区。
|
||||||
- 验证:强制移动端 `390x844`、DPR 2 截图,确认棋盘左右边界在视口内,canvas CSS 尺寸等于容器尺寸,内部 `width/height` 属性可大于 CSS 尺寸。
|
- 验证:强制移动端 `390x844`、DPR 2 截图,确认棋盘左右边界在视口内,canvas CSS 尺寸等于容器尺寸,内部 `width/height` 属性可大于 CSS 尺寸。
|
||||||
- 关联:`src/components/match3d-runtime/Match3DPhysicsBoard.tsx`、`docs/technical/MATCH3D_RUNTIME_3D_GEOMETRY_EXPERIMENT_2026-05-02.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## Hyper3D subscriptionKey 不要按固定短文本限长
|
## Hyper3D subscriptionKey 不要按固定短文本限长
|
||||||
|
|
||||||
@@ -646,7 +646,7 @@
|
|||||||
- 原因:`subscriptionKey` 是 Hyper3D 返回的 opaque token,长度由上游决定;后端状态查询曾复用普通文本校验,把它限制在 256 字符。
|
- 原因:`subscriptionKey` 是 Hyper3D 返回的 opaque token,长度由上游决定;后端状态查询曾复用普通文本校验,把它限制在 256 字符。
|
||||||
- 处理:`query_task_status` 对 `subscriptionKey` 只做 trim 和非空校验,不做固定长度限制;前端临时任务和 Match3D 草稿响应可继续展示该 token,但不要把它当作可编辑短文本。
|
- 处理:`query_task_status` 对 `subscriptionKey` 只做 trim 和非空校验,不做固定长度限制;前端临时任务和 Match3D 草稿响应可继续展示该 token,但不要把它当作可编辑短文本。
|
||||||
- 验证:`cargo test -p api-server accepts_opaque_subscription_key_without_length_cap --manifest-path server-rs/Cargo.toml`。
|
- 验证:`cargo test -p api-server accepts_opaque_subscription_key_without_length_cap --manifest-path server-rs/Cargo.toml`。
|
||||||
- 关联:`server-rs/crates/api-server/src/hyper3d_generation.rs`、`docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅新草稿不要再接回 Rodin 或 GLB 生成
|
## 抓大鹅新草稿不要再接回 Rodin 或 GLB 生成
|
||||||
|
|
||||||
@@ -654,7 +654,7 @@
|
|||||||
- 原因:仓库里保留了 Hyper3D 通用代理和历史模型字段,旧文档也曾要求草稿阶段同步生成 GLB。当前产品口径已经改为 2D 多视角素材。
|
- 原因:仓库里保留了 Hyper3D 通用代理和历史模型字段,旧文档也曾要求草稿阶段同步生成 GLB。当前产品口径已经改为 2D 多视角素材。
|
||||||
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个视角,单张 1K 素材图固定 5x5,最多承载 5 个物品,一行对应一个物品,不足 5 个物品也补齐到完整 5 行;超过 5 个物品自动分批并行生图。素材图 prompt 固定要求纯绿色绿幕背景,切割前先把绿幕处理为透明 alpha,再做格内内容前景边界校准并带留白,避免固定内缩切掉贴近格线的主体。`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]` 或首图引用。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
|
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个视角,单张 1K 素材图固定 5x5,最多承载 5 个物品,一行对应一个物品,不足 5 个物品也补齐到完整 5 行;超过 5 个物品自动分批并行生图。素材图 prompt 固定要求纯绿色绿幕背景,切割前先把绿幕处理为透明 alpha,再做格内内容前景边界校准并带留白,避免固定内缩切掉贴近格线的主体。`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]` 或首图引用。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
|
||||||
- 验证:`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run test -- src\services\miniGameDraftGenerationProgress.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`。
|
- 验证:`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run test -- src\services\miniGameDraftGenerationProgress.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`。
|
||||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅切图路径不能只用中文物品名
|
## 抓大鹅切图路径不能只用中文物品名
|
||||||
|
|
||||||
@@ -662,7 +662,7 @@
|
|||||||
- 原因:中文物品名经过 OSS 路径段清洗后都可能退化成 `item`,多张切割图片写到同一个 object key,后写入覆盖先写入。
|
- 原因:中文物品名经过 OSS 路径段清洗后都可能退化成 `item`,多张切割图片写到同一个 object key,后写入覆盖先写入。
|
||||||
- 处理:切割图上传路径必须带稳定唯一 `itemId` 前缀,例如 `items/match3d-item-1-item/views/view-01.png`;运行态读取 generated 私有图片时通过同源 `/api/assets/read-url` 换签,不直接请求裸 OSS 路径。
|
- 处理:切割图上传路径必须带稳定唯一 `itemId` 前缀,例如 `items/match3d-item-1-item/views/view-01.png`;运行态读取 generated 私有图片时通过同源 `/api/assets/read-url` 换签,不直接请求裸 OSS 路径。
|
||||||
- 验证:后端单测覆盖中文名路径唯一,前端运行态测试覆盖 generated 图片源解析。
|
- 验证:后端单测覆盖中文名路径唯一,前端运行态测试覆盖 generated 图片源解析。
|
||||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-result/Match3DResultView.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅生成素材不能只挂在 compile response
|
## 抓大鹅生成素材不能只挂在 compile response
|
||||||
|
|
||||||
@@ -670,7 +670,7 @@
|
|||||||
- 原因:`generatedItemAssets` 如果只附加在 `match3d_compile_draft` 的 HTTP response draft 上,刷新或重进时 `getMatch3DWorkDetail` 只能读取 SpacetimeDB 中的 `match3d_work_profile`;旧 mapper 返回空数组,自然无法恢复素材。拼图链路已经通过 `save_puzzle_generated_images` 把候选图和 levels 写回 work profile,抓大鹅也必须同样写持久字段。
|
- 原因:`generatedItemAssets` 如果只附加在 `match3d_compile_draft` 的 HTTP response draft 上,刷新或重进时 `getMatch3DWorkDetail` 只能读取 SpacetimeDB 中的 `match3d_work_profile`;旧 mapper 返回空数组,自然无法恢复素材。拼图链路已经通过 `save_puzzle_generated_images` 把候选图和 levels 写回 work profile,抓大鹅也必须同样写持久字段。
|
||||||
- 处理:compile 成功时把独立物品图片列表序列化写入 `match3d_work_profile.generated_item_assets_json`;`update_match3d_work` / `publish_match3d_work` 保留该字段;API work summary/detail 映射反序列化为 `generatedItemAssets`。前端保持“本次 draft 优先,重进 profile 兜底”的读取顺序。
|
- 处理:compile 成功时把独立物品图片列表序列化写入 `match3d_work_profile.generated_item_assets_json`;`update_match3d_work` / `publish_match3d_work` 保留该字段;API work summary/detail 映射反序列化为 `generatedItemAssets`。前端保持“本次 draft 优先,重进 profile 兜底”的读取顺序。
|
||||||
- 验证:`cargo test -p spacetime-client match3d --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`。
|
- 验证:`cargo test -p spacetime-client match3d --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`。
|
||||||
- 关联:`server-rs/crates/spacetime-module/src/match3d/*`、`server-rs/crates/spacetime-client/src/mapper.rs`、`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-result/Match3DResultView.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅试玩和正式运行态不要只读草稿页本地素材预览
|
## 抓大鹅试玩和正式运行态不要只读草稿页本地素材预览
|
||||||
|
|
||||||
@@ -678,7 +678,7 @@
|
|||||||
- 原因:结果页本地 `assetDrafts` 和作品 profile 的 `generatedItemAssets` 可能不同步;推荐流内嵌运行态若只读卡片摘要,卡片缺素材时会把已持久化 profile 素材丢掉;点击试玩时 React state 异步更新也可能让运行态第一帧读取旧 `match3dProfile`。
|
- 原因:结果页本地 `assetDrafts` 和作品 profile 的 `generatedItemAssets` 可能不同步;推荐流内嵌运行态若只读卡片摘要,卡片缺素材时会把已持久化 profile 素材丢掉;点击试玩时 React state 异步更新也可能让运行态第一帧读取旧 `match3dProfile`。
|
||||||
- 处理:删除、批量新增、音效生成或封面引用物品素材后,都把当前 `generatedItemAssets` 写回作品 profile;`Match3DResultView` 合并同 `itemId` 的 draft/profile 素材,用 profile 已有 `imageViews[]`、首图引用、`backgroundMusic` 或 `backgroundAsset` 补齐旧 draft;点击试玩前把试玩可用物品种类通过 `itemTypeCountOverride` 降到已生成 2D 素材数量;推荐流内嵌运行态启动前若卡片摘要没有物品图片素材,补读 `getMatch3DWorkDetail(profileId)` 并把详情资产传给 `Match3DRuntimeShell`。`PlatformEntryFlowShellImpl` 需要维护 `match3dRuntimeProfile`,在 `startMatch3DRunFromProfile` 创建 run 后立即锁定本次完整 profile,runtime 渲染时优先按 `run.profileId` 使用这份 profile,而不是等待普通 `match3dProfile` state 下一轮刷新。同 profile 下已有 `generatedItemAssets` 时不能因为图片完整性判断失败就覆盖为空数组。判断是否需要补读详情时只看 `imageViews[]` 或 `imageSrc/imageObjectKey`;背景、音乐、容器 UI 是附属运行态资产,不能单独证明物品素材已完整。
|
- 处理:删除、批量新增、音效生成或封面引用物品素材后,都把当前 `generatedItemAssets` 写回作品 profile;`Match3DResultView` 合并同 `itemId` 的 draft/profile 素材,用 profile 已有 `imageViews[]`、首图引用、`backgroundMusic` 或 `backgroundAsset` 补齐旧 draft;点击试玩前把试玩可用物品种类通过 `itemTypeCountOverride` 降到已生成 2D 素材数量;推荐流内嵌运行态启动前若卡片摘要没有物品图片素材,补读 `getMatch3DWorkDetail(profileId)` 并把详情资产传给 `Match3DRuntimeShell`。`PlatformEntryFlowShellImpl` 需要维护 `match3dRuntimeProfile`,在 `startMatch3DRunFromProfile` 创建 run 后立即锁定本次完整 profile,runtime 渲染时优先按 `run.profileId` 使用这份 profile,而不是等待普通 `match3dProfile` state 下一轮刷新。同 profile 下已有 `generatedItemAssets` 时不能因为图片完整性判断失败就覆盖为空数组。判断是否需要补读详情时只看 `imageViews[]` 或 `imageSrc/imageObjectKey`;背景、音乐、容器 UI 是附属运行态资产,不能单独证明物品素材已完整。
|
||||||
- 验证:执行 `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`,并检查历史草稿和公开 M3 作品的 Network 响应里 `generatedItemAssets[].imageViews/imageSrc/imageObjectKey`。
|
- 验证:执行 `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`,并检查历史草稿和公开 M3 作品的 Network 响应里 `generatedItemAssets[].imageViews/imageSrc/imageObjectKey`。
|
||||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/match3d-runtime/Match3DPhysicsBoard.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅 UI 背景和容器只在顶层字段时也要传进运行态
|
## 抓大鹅 UI 背景和容器只在顶层字段时也要传进运行态
|
||||||
|
|
||||||
@@ -686,7 +686,15 @@
|
|||||||
- 原因:部分链路把 UI 资产只放在作品顶层 `generatedBackgroundAsset` / `backgroundImageObjectKey`,没有同步放进首个 `generatedItemAssets[].backgroundAsset`;如果运行态入口只传 `generatedItemAssets` 和 `backgroundImageSrc`,`Match3DRuntimeShell` 就拿不到 `containerImageObjectKey`。
|
- 原因:部分链路把 UI 资产只放在作品顶层 `generatedBackgroundAsset` / `backgroundImageObjectKey`,没有同步放进首个 `generatedItemAssets[].backgroundAsset`;如果运行态入口只传 `generatedItemAssets` 和 `backgroundImageSrc`,`Match3DRuntimeShell` 就拿不到 `containerImageObjectKey`。
|
||||||
- 处理:`PlatformMatch3DGalleryCard`、`mapPublicWorkDetailToMatch3DWork`、`resolveMatch3DRuntimeGeneratedBackgroundAsset` 和 `Match3DRuntimeShell` 都必须保留并传递顶层 `generatedBackgroundAsset`;运行态背景读取顺序为 `backgroundImageSrc` / 顶层 `generatedBackgroundAsset.image*` / `generatedItemAssets[].backgroundAsset.image*`,容器读取顺序为顶层 `generatedBackgroundAsset.containerImage*` / `generatedItemAssets[].backgroundAsset.containerImage*`。
|
- 处理:`PlatformMatch3DGalleryCard`、`mapPublicWorkDetailToMatch3DWork`、`resolveMatch3DRuntimeGeneratedBackgroundAsset` 和 `Match3DRuntimeShell` 都必须保留并传递顶层 `generatedBackgroundAsset`;运行态背景读取顺序为 `backgroundImageSrc` / 顶层 `generatedBackgroundAsset.image*` / `generatedItemAssets[].backgroundAsset.image*`,容器读取顺序为顶层 `generatedBackgroundAsset.containerImage*` / `generatedItemAssets[].backgroundAsset.containerImage*`。
|
||||||
- 验证:执行 `npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx` 和 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "Match3D runtime"`;浏览器 Network 中背景和容器 generated path 应先请求 `/api/assets/read-url` 换签,局内出现 `match3d-background-image` 和 `match3d-container-image` 对应图片。
|
- 验证:执行 `npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx` 和 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "Match3D runtime"`;浏览器 Network 中背景和容器 generated path 应先请求 `/api/assets/read-url` 换签,局内出现 `match3d-background-image` 和 `match3d-container-image` 对应图片。
|
||||||
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/rpgEntryWorldPresentation.ts`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
|
## 抓大鹅重启时不要清空 generated 图片签名缓存
|
||||||
|
|
||||||
|
- 现象:抓大鹅进入局内时背景或生成物品首帧缺失;点击右上角重启后,局内短暂显示默认积木,过一段时间才换回实际生成素材。
|
||||||
|
- 原因:`Match3DRuntimeShell` 在 run / resources 变化时清空 `resolvedImageSources`,导致同一批 generated 私有图每次重启都重新换签;换签未完成期间渲染层把空图片当成“没有生成素材”,直接落到默认积木兜底。另一个常见遗漏是运行态预加载只覆盖物品图,没覆盖顶层或物品挂载的 UI 背景和容器图。
|
||||||
|
- 处理:运行态解析 generated 图片时按源列表保留已有签名 URL,只移除不再使用的源;generated 图仍在换签时先隐藏对应物品,不显示默认积木,只有没有生成源或换签失败后才使用兜底。`preloadMatch3DGeneratedRuntimeAssets` 必须同时预加载物品视角、纯背景和容器 UI;推荐流卡片摘要缺物品图或 UI 背景 / 容器字段时,进入运行态前补读 `getMatch3DWorkDetail`。
|
||||||
|
- 验证:执行 `npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx -t "generated 图片素材换签未完成前不显示默认积木|同一批 generated 图片素材在重启 run 时保留已解析地址|运行态会从顶层 UI 资产加载背景和容器图"`、`npm run test -- src/services/match3dGeneratedModelCache.test.ts`、`npm run typecheck` 和 `npm run check:encoding`。
|
||||||
|
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`src/services/match3dGeneratedModelCache.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅容器参考图必须走 edits 并接管棋盘外观
|
## 抓大鹅容器参考图必须走 edits 并接管棋盘外观
|
||||||
|
|
||||||
@@ -694,7 +702,7 @@
|
|||||||
- 原因:`/v1/images/generations` 的 `image` 数组更适合弱参考文生图,难以稳定锁定大尺寸轻俯视容器构图;即使生成了容器图,如果运行态继续保留默认 `rounded-full` 锅壳和 `overflow-hidden`,生成图也会被默认视觉覆盖或裁掉。
|
- 原因:`/v1/images/generations` 的 `image` 数组更适合弱参考文生图,难以稳定锁定大尺寸轻俯视容器构图;即使生成了容器图,如果运行态继续保留默认 `rounded-full` 锅壳和 `overflow-hidden`,生成图也会被默认视觉覆盖或裁掉。
|
||||||
- 处理:抓大鹅 `1:1` 容器 UI 图必须用 VectorEngine `POST /v1/images/edits` multipart,把 `public/match3d-background-references/pot-fused-reference.png` 作为 `image` part 上传;共享 GPT-image-2 HTTP client 承载 multipart 时强制 HTTP/1.1。`Match3DRuntimeShell` 在容器图换签并成功加载后,把棋盘外壳切为透明和 `overflow-visible`,只在容器缺失或加载失败时使用默认圆形容器。
|
- 处理:抓大鹅 `1:1` 容器 UI 图必须用 VectorEngine `POST /v1/images/edits` multipart,把 `public/match3d-background-references/pot-fused-reference.png` 作为 `image` part 上传;共享 GPT-image-2 HTTP client 承载 multipart 时强制 HTTP/1.1。`Match3DRuntimeShell` 在容器图换签并成功加载后,把棋盘外壳切为透明和 `overflow-visible`,只在容器缺失或加载失败时使用默认圆形容器。
|
||||||
- 验证:执行 `cargo test -p api-server vector_engine --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d_background --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`;真实联调看容器生成请求是否命中 `/v1/images/edits`,局内 `match3d-container-image` 是否渲染且 `match3d-board` 不再含默认 `rounded-full`。
|
- 验证:执行 `cargo test -p api-server vector_engine --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d_background --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`;真实联调看容器生成请求是否命中 `/v1/images/edits`,局内 `match3d-container-image` 是否渲染且 `match3d-board` 不再含默认 `rounded-full`。
|
||||||
- 关联:`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅结果页音频试听也要先换签
|
## 抓大鹅结果页音频试听也要先换签
|
||||||
|
|
||||||
@@ -702,7 +710,7 @@
|
|||||||
- 原因:结果页试听控件和运行态一样运行在浏览器里,不能直接读取 generated 私有对象;只在运行态换签会造成“运行态可能有声,结果页不能预览”的割裂。
|
- 原因:结果页试听控件和运行态一样运行在浏览器里,不能直接读取 generated 私有对象;只在运行态换签会造成“运行态可能有声,结果页不能预览”的割裂。
|
||||||
- 处理:结果页音频控件统一通过 `useResolvedAssetReadUrl` / `/api/assets/read-url` 取得签名 URL 后再传给 `<audio>`;换签失败时只显示“音频已绑定”,不要回退请求裸 generated path。
|
- 处理:结果页音频控件统一通过 `useResolvedAssetReadUrl` / `/api/assets/read-url` 取得签名 URL 后再传给 `<audio>`;换签失败时只显示“音频已绑定”,不要回退请求裸 generated path。
|
||||||
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx` 覆盖背景音乐和点击音效试听使用签名 URL。
|
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx` 覆盖背景音乐和点击音效试听使用签名 URL。
|
||||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`src/services/assetReadUrlService.ts`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 法律文档弹窗通过 portal 挂载时要显式带平台主题
|
## 法律文档弹窗通过 portal 挂载时要显式带平台主题
|
||||||
|
|
||||||
@@ -717,7 +725,7 @@
|
|||||||
- 原因:完成回调用 `selectionStageRef.current` 判断用户是否仍在生成页;如果执行 compile 前只调用 `setSelectionStage('*-generating')`,action 很快返回时 ref 仍可能是旧 stage。
|
- 原因:完成回调用 `selectionStageRef.current` 判断用户是否仍在生成页;如果执行 compile 前只调用 `setSelectionStage('*-generating')`,action 很快返回时 ref 仍可能是旧 stage。
|
||||||
- 处理:进入各玩法生成页时同步写 `selectionStageRef.current = '*-generating'`,再调用 `setSelectionStage('*-generating')`。这不是为渲染服务,而是给同一异步链路里的完成回调提供即时事实。
|
- 处理:进入各玩法生成页时同步写 `selectionStageRef.current = '*-generating'`,再调用 `setSelectionStage('*-generating')`。这不是为渲染服务,而是给同一异步链路里的完成回调提供即时事实。
|
||||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx` 覆盖抓大鹅和拼图生成后自动试玩 / 返回结果页。
|
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx` 覆盖抓大鹅和拼图生成后自动试玩 / 返回结果页。
|
||||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 微信支付回调验签不要用商户私钥
|
## 微信支付回调验签不要用商户私钥
|
||||||
|
|
||||||
@@ -726,7 +734,7 @@
|
|||||||
- 处理:api-server 真实微信支付配置同时需要商户私钥与微信平台公钥:`WECHAT_PAY_PRIVATE_KEY_*` 用于签名,`WECHAT_PAY_PLATFORM_PUBLIC_KEY_*` 与 `WECHAT_PAY_PLATFORM_SERIAL_NO` 用于通知验签,`WECHAT_PAY_API_V3_KEY` 只用于解密通知 resource。支付成功后只通过通知里的 `out_trade_no` 确认本地 pending 订单,并保存 `transaction_id` 到 `profile_recharge_order.provider_transaction_id`。
|
- 处理:api-server 真实微信支付配置同时需要商户私钥与微信平台公钥:`WECHAT_PAY_PRIVATE_KEY_*` 用于签名,`WECHAT_PAY_PLATFORM_PUBLIC_KEY_*` 与 `WECHAT_PAY_PLATFORM_SERIAL_NO` 用于通知验签,`WECHAT_PAY_API_V3_KEY` 只用于解密通知 resource。支付成功后只通过通知里的 `out_trade_no` 确认本地 pending 订单,并保存 `transaction_id` 到 `profile_recharge_order.provider_transaction_id`。
|
||||||
- APIv3 通知成功应答使用 HTTP `204 No Content`,不要沿用 V2 XML 成功报文;失败仍返回 4XX/5XX 让微信重试。
|
- APIv3 通知成功应答使用 HTTP `204 No Content`,不要沿用 V2 XML 成功报文;失败仍返回 4XX/5XX 让微信重试。
|
||||||
- 验证:mock 通知测试只能覆盖本地回调推进;真实环境还需用微信支付平台公钥、真实通知头和 API v3 密钥验证签名与解密链路。
|
- 验证:mock 通知测试只能覆盖本地回调推进;真实环境还需用微信支付平台公钥、真实通知头和 API v3 密钥验证签名与解密链路。
|
||||||
- 关联:`server-rs/crates/api-server/src/wechat_pay.rs`、`docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 微信支付 JSAPI 下单必须显式带 User-Agent
|
## 微信支付 JSAPI 下单必须显式带 User-Agent
|
||||||
|
|
||||||
@@ -734,7 +742,7 @@
|
|||||||
- 原因:`reqwest` 请求即使已设置 `Accept: application/json`,也不会默认附带业务侧 `User-Agent`;微信支付网关会校验这两个头。
|
- 原因:`reqwest` 请求即使已设置 `Accept: application/json`,也不会默认附带业务侧 `User-Agent`;微信支付网关会校验这两个头。
|
||||||
- 处理:`api-server` 的 JSAPI 下单请求统一通过 `with_wechat_pay_jsapi_headers(...)` 设置 `Accept: application/json`、`Content-Type: application/json` 和 `User-Agent: Genarrative-WechatPay/1.0`。
|
- 处理:`api-server` 的 JSAPI 下单请求统一通过 `with_wechat_pay_jsapi_headers(...)` 设置 `Accept: application/json`、`Content-Type: application/json` 和 `User-Agent: Genarrative-WechatPay/1.0`。
|
||||||
- 验证:执行 `cargo test -p api-server jsapi_order_request_sets_wechat_required_http_headers --manifest-path server-rs/Cargo.toml`。
|
- 验证:执行 `cargo test -p api-server jsapi_order_request_sets_wechat_required_http_headers --manifest-path server-rs/Cargo.toml`。
|
||||||
- 关联:`server-rs/crates/api-server/src/wechat_pay.rs`、`docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 后台表查询展示 SpacetimeDB 枚举时不要套用 Option 解码
|
## 后台表查询展示 SpacetimeDB 枚举时不要套用 Option 解码
|
||||||
|
|
||||||
@@ -742,7 +750,7 @@
|
|||||||
- 原因:SpacetimeDB HTTP SQL 对无载荷枚举会返回 SATS 形态 `[variant_index, []]`;后台通用 normalizer 曾把任何 `[0, value]` 都当作 `Option::Some(value)` 展开,导致 `[0, []]` 最终只剩 `[]`。
|
- 原因:SpacetimeDB HTTP SQL 对无载荷枚举会返回 SATS 形态 `[variant_index, []]`;后台通用 normalizer 曾把任何 `[0, value]` 都当作 `Option::Some(value)` 展开,导致 `[0, []]` 最终只剩 `[]`。
|
||||||
- 处理:通用表查询解析应先按表名和列名识别已知业务枚举,再落回 Option / Timestamp 通用展开;例如 `profile_recharge_order.kind` 映射为 `points` / `membership`,`profile_recharge_order.status` 映射为 `pending` / `paid` / `failed` / `closed` / `refunded`。
|
- 处理:通用表查询解析应先按表名和列名识别已知业务枚举,再落回 Option / Timestamp 通用展开;例如 `profile_recharge_order.kind` 映射为 `points` / `membership`,`profile_recharge_order.status` 映射为 `pending` / `paid` / `failed` / `closed` / `refunded`。
|
||||||
- 验证:执行 `cargo test -p api-server admin_database -- --nocapture`,并确认后台详情弹层的 `raw` 与表格 `cells` 都显示业务字符串。
|
- 验证:执行 `cargo test -p api-server admin_database -- --nocapture`,并确认后台详情弹层的 `raw` 与表格 `cells` 都显示业务字符串。
|
||||||
- 关联:`server-rs/crates/api-server/src/admin.rs`、`docs/technical/ADMIN_DATABASE_TABLE_QUERY_2026-05-08.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅历史草稿外部 Rodin GLB 链接必须转存后再试玩或发布
|
## 抓大鹅历史草稿外部 Rodin GLB 链接必须转存后再试玩或发布
|
||||||
|
|
||||||
@@ -750,7 +758,7 @@
|
|||||||
- 原因:历史结果页手动 `重新生成` 会把 Hyper3D/Rodin 的外部 CDN 下载链接直接保存到 `generatedItemAssets[].modelSrc`,同时 `modelObjectKey` 为空。外部链接可能过期、跨域、返回 HTML 错误页或非 GLB 内容;前端预览和运行态不能把它当作稳定私有资产。
|
- 原因:历史结果页手动 `重新生成` 会把 Hyper3D/Rodin 的外部 CDN 下载链接直接保存到 `generatedItemAssets[].modelSrc`,同时 `modelObjectKey` 为空。外部链接可能过期、跨域、返回 HTML 错误页或非 GLB 内容;前端预览和运行态不能把它当作稳定私有资产。
|
||||||
- 处理:该问题只适用于旧数据。结果页发现 `status = model_ready`、`modelSrc = https://...` 且无 `modelObjectKey` 时,可调用 `POST /api/creation/match3d/works/{profileId}/generated-models` 做一次性转存;新草稿和批量新增不得继续生成或依赖 GLB。若历史半修复数据同时保留外部 `modelSrc` 和平台 `modelObjectKey`,旧模型预览读取层优先用 `modelObjectKey`。
|
- 处理:该问题只适用于旧数据。结果页发现 `status = model_ready`、`modelSrc = https://...` 且无 `modelObjectKey` 时,可调用 `POST /api/creation/match3d/works/{profileId}/generated-models` 做一次性转存;新草稿和批量新增不得继续生成或依赖 GLB。若历史半修复数据同时保留外部 `modelSrc` 和平台 `modelObjectKey`,旧模型预览读取层优先用 `modelObjectKey`。
|
||||||
- 验证:`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`、`cargo test -p api-server match3d_model_download --manifest-path server-rs\Cargo.toml`,并检查修复后响应中的 `generatedItemAssets[].modelObjectKey` 不为空。
|
- 验证:`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`、`cargo test -p api-server match3d_model_download --manifest-path server-rs\Cargo.toml`,并检查修复后响应中的 `generatedItemAssets[].modelObjectKey` 不为空。
|
||||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-result/Match3DResultView.tsx`、`src/components/match3d-result/Match3DModelPreview.tsx`、`src/components/match3d-runtime/Match3DPhysicsBoard.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅难度配置的物品种类和消除次数必须分离
|
## 抓大鹅难度配置的物品种类和消除次数必须分离
|
||||||
|
|
||||||
@@ -758,7 +766,7 @@
|
|||||||
- 原因:旧运行态把消除次数和类型数量绑在一起,结果页文案又同时展示“素材图片 / 局内类型”,导致前端、发布校验和 run start 口径不一致。
|
- 原因:旧运行态把消除次数和类型数量绑在一起,结果页文案又同时展示“素材图片 / 局内类型”,导致前端、发布校验和 run start 口径不一致。
|
||||||
- 处理:统一使用 `物品种类` 口径:轻松 3、标准 9、进阶 15、硬核 21;历史 `clearCount=20` 且难度为硬核的运行态按新硬核升为 21 组三消,避免 20 组却要求 21 种素材。发布前按 `image_ready` 且有 `imageViews[]` 或 `imageSrc/imageObjectKey` 的生成素材数量阻断不足难度;试玩不阻断,但通过 `itemTypeCountOverride` 自动降到已生成 2D 素材数量。重启从已有 run 快照反推实际物品种类,保持同一局重开不变。
|
- 处理:统一使用 `物品种类` 口径:轻松 3、标准 9、进阶 15、硬核 21;历史 `clearCount=20` 且难度为硬核的运行态按新硬核升为 21 组三消,避免 20 组却要求 21 种素材。发布前按 `image_ready` 且有 `imageViews[]` 或 `imageSrc/imageObjectKey` 的生成素材数量阻断不足难度;试玩不阻断,但通过 `itemTypeCountOverride` 自动降到已生成 2D 素材数量。重启从已有 run 快照反推实际物品种类,保持同一局重开不变。
|
||||||
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx`、`cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`,涉及发布 reducer 时补跑 `cargo test -p spacetime-module match3d --manifest-path server-rs\Cargo.toml`。
|
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx`、`cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`,涉及发布 reducer 时补跑 `cargo test -p spacetime-module match3d --manifest-path server-rs\Cargo.toml`。
|
||||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`src/services/match3d-runtime/match3dRuntimeClient.ts`、`server-rs/crates/module-match3d/src/application.rs`、`server-rs/crates/spacetime-module/src/match3d/mod.rs`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅标签清洗不要把 `3D素材` 当编号剥掉
|
## 抓大鹅标签清洗不要把 `3D素材` 当编号剥掉
|
||||||
|
|
||||||
@@ -774,7 +782,7 @@
|
|||||||
- 原因:素材 sheet 可能是“每格内部绿幕、整张图外圈近白底”,内部绿幕不一定连通到 sheet 外边缘;旧 flood fill 只从外边缘找背景会漏掉这种绿幕块。白底抗锯齿如果不纳入抠像和边缘去污染,也会随裁剪输出成一圈白边。即使顺序已是先整张 sheet 去绿再裁剪,较厚的半透明或混色软绿边仍可能低于高置信绿幕阈值,被当作前景带进独立 PNG。
|
- 原因:素材 sheet 可能是“每格内部绿幕、整张图外圈近白底”,内部绿幕不一定连通到 sheet 外边缘;旧 flood fill 只从外边缘找背景会漏掉这种绿幕块。白底抗锯齿如果不纳入抠像和边缘去污染,也会随裁剪输出成一圈白边。即使顺序已是先整张 sheet 去绿再裁剪,较厚的半透明或混色软绿边仍可能低于高置信绿幕阈值,被当作前景带进独立 PNG。
|
||||||
- 处理:`api-server` 的 `slice_match3d_material_sheet` 必须先在整张 sheet 上做透明背景后处理:外边缘连通绿幕/近白底清 alpha,非连通但高置信纯绿块也清 alpha,沿整张 sheet 透明背景继续吃掉软绿边,边缘近白和绿幕抗锯齿做透明或去污染;同时保护不够纯的绿色主体像素。不要改成先裁剪单格再去绿。
|
- 处理:`api-server` 的 `slice_match3d_material_sheet` 必须先在整张 sheet 上做透明背景后处理:外边缘连通绿幕/近白底清 alpha,非连通但高置信纯绿块也清 alpha,沿整张 sheet 透明背景继续吃掉软绿边,边缘近白和绿幕抗锯齿做透明或去污染;同时保护不够纯的绿色主体像素。不要改成先裁剪单格再去绿。
|
||||||
- 验证:`cargo test -p api-server match3d_material_sheet_slicing --manifest-path server-rs\Cargo.toml` 覆盖非连通绿幕、白边、贴边主体保留和固定 5x5 切图。
|
- 验证:`cargo test -p api-server match3d_material_sheet_slicing --manifest-path server-rs\Cargo.toml` 覆盖非连通绿幕、白边、贴边主体保留和固定 5x5 切图。
|
||||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 抓大鹅物品详情大方格只做单张大图查看
|
## 抓大鹅物品详情大方格只做单张大图查看
|
||||||
|
|
||||||
@@ -782,7 +790,7 @@
|
|||||||
- 原因:旧预览把上方区域当作横向视角带,当前焦点只是带内缩略图的一张,视觉上不是“详细查看物品形象”的大图。
|
- 原因:旧预览把上方区域当作横向视角带,当前焦点只是带内缩略图的一张,视觉上不是“详细查看物品形象”的大图。
|
||||||
- 处理:上方方格只渲染当前选中的单张大图,使用 `object-contain` 和少量内边距放大查看;底部缩略图栏负责切换视角,缩略图可以保留选中态边框,但上方大图不渲染焦点内框或缩略图容器边框。
|
- 处理:上方方格只渲染当前选中的单张大图,使用 `object-contain` 和少量内边距放大查看;底部缩略图栏负责切换视角,缩略图可以保留选中态边框,但上方大图不渲染焦点内框或缩略图容器边框。
|
||||||
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx` 覆盖上方大图、底部缩略图和视角切换。
|
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx` 覆盖上方大图、底部缩略图和视角切换。
|
||||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 草稿页卡片有真实素材但仍显示黑卡先查摘要字段
|
## 草稿页卡片有真实素材但仍显示黑卡先查摘要字段
|
||||||
|
|
||||||
@@ -790,7 +798,7 @@
|
|||||||
- 原因:拼图列表摘要若不下发 `levels`,前端拿不到关卡 `coverImageSrc` / 候选图;抓大鹅列表摘要若只提供公开 URL、不保留 `generatedBackgroundAsset` 或 `generatedItemAssets` 中的 object key,前端无法换签读取私有生成图。卡片封面组件如果自带暗色默认背景,也会让兜底失败时看起来仍是黑卡。
|
- 原因:拼图列表摘要若不下发 `levels`,前端拿不到关卡 `coverImageSrc` / 候选图;抓大鹅列表摘要若只提供公开 URL、不保留 `generatedBackgroundAsset` 或 `generatedItemAssets` 中的 object key,前端无法换签读取私有生成图。卡片封面组件如果自带暗色默认背景,也会让兜底失败时看起来仍是黑卡。
|
||||||
- 处理:拼图 `map_puzzle_work_summary_response` 必须保留 `levels`;草稿页优先用关卡 `coverImageSrc`,再用候选图。抓大鹅货架封面解析必须读取 `backgroundImageObjectKey`、`generatedBackgroundAsset.imageObjectKey/containerImageObjectKey`、`generatedItemAssets[].imageObjectKey` 和 `imageViews[].imageObjectKey`。图片渲染统一交给 `ResolvedAssetImage` 换签,并给卡片传入玩法参考图与暖色底兜底。
|
- 处理:拼图 `map_puzzle_work_summary_response` 必须保留 `levels`;草稿页优先用关卡 `coverImageSrc`,再用候选图。抓大鹅货架封面解析必须读取 `backgroundImageObjectKey`、`generatedBackgroundAsset.imageObjectKey/containerImageObjectKey`、`generatedItemAssets[].imageObjectKey` 和 `imageViews[].imageObjectKey`。图片渲染统一交给 `ResolvedAssetImage` 换签,并给卡片传入玩法参考图与暖色底兜底。
|
||||||
- 验证:执行 `npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/hooks/useResolvedAssetReadUrl.test.tsx`、`cargo test -p api-server puzzle_work_summary_response_keeps_levels_for_shelf_cover --manifest-path server-rs\Cargo.toml`、`npm run typecheck`。
|
- 验证:执行 `npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/hooks/useResolvedAssetReadUrl.test.tsx`、`cargo test -p api-server puzzle_work_summary_response_keeps_levels_for_shelf_cover --manifest-path server-rs\Cargo.toml`、`npm run typecheck`。
|
||||||
- 关联:`src/components/custom-world-home/creationWorkShelf.ts`、`src/components/CustomWorldCoverArtwork.tsx`、`server-rs/crates/api-server/src/puzzle.rs`、`docs/technical/CREATION_WORK_SHELF_UNIFICATION_2026-04-25.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 用户标签不要直接外显,SpacetimeDB Vec 字段不要写 default 宏
|
## 用户标签不要直接外显,SpacetimeDB Vec 字段不要写 default 宏
|
||||||
|
|
||||||
@@ -798,7 +806,7 @@
|
|||||||
- 原因:SpacetimeDB 的 table default 宏会走编译期常量求值,不能直接使用有析构逻辑的堆分配类型默认值。
|
- 原因:SpacetimeDB 的 table default 宏会走编译期常量求值,不能直接使用有析构逻辑的堆分配类型默认值。
|
||||||
- 处理:`user_account.user_tags` 使用 `Option<Vec<String>>` + `#[default(None::<Vec<String>>)]` 表达数据库默认空,业务层统一把 `None` 归一化为空数组;邀请码授予标签复用 `metadata_json.userTags` 存储和解析,不再新增独立 Vec 列。用户标签原始值不得进入登录态、个人资料等通用响应,只能在明确业务白名单里投影,例如拼图排行榜 `visibleTags` 首版仅允许 `北科`。
|
- 处理:`user_account.user_tags` 使用 `Option<Vec<String>>` + `#[default(None::<Vec<String>>)]` 表达数据库默认空,业务层统一把 `None` 归一化为空数组;邀请码授予标签复用 `metadata_json.userTags` 存储和解析,不再新增独立 Vec 列。用户标签原始值不得进入登录态、个人资料等通用响应,只能在明确业务白名单里投影,例如拼图排行榜 `visibleTags` 首版仅允许 `北科`。
|
||||||
- 验证:`npm run spacetime:generate -- --rust-only` 能通过;`user_account` 旧迁移 JSON 缺字段时能导入,`profile_invite_code` 缺 `metadata_json` 时按 `{}` 兼容。
|
- 验证:`npm run spacetime:generate -- --rust-only` 能通过;`user_account` 旧迁移 JSON 缺字段时能导入,`profile_invite_code` 缺 `metadata_json` 时按 `{}` 兼容。
|
||||||
- 关联:`docs/technical/USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 公开作品详情深链找不到作品不能停在空详情页
|
## 公开作品详情深链找不到作品不能停在空详情页
|
||||||
|
|
||||||
@@ -806,7 +814,7 @@
|
|||||||
- 原因:旧恢复逻辑只覆盖 `/runtime/...`,没有覆盖 `/works/detail`。同时 `selectionStage === 'work-detail'` 且 `selectedPublicWorkDetail === null` 时没有兜底渲染,详情数据为空就只剩空页面。
|
- 原因:旧恢复逻辑只覆盖 `/runtime/...`,没有覆盖 `/works/detail`。同时 `selectionStage === 'work-detail'` 且 `selectedPublicWorkDetail === null` 时没有兜底渲染,详情数据为空就只剩空页面。
|
||||||
- 处理:公开详情失效统一走 `resolveWorkNotFoundRecoveryAction(...)`,覆盖 `/works/detail`、`/gallery/puzzle/detail` 和 `/gallery/visual-novel/detail`;搜索失败和拼图详情 404 分支清理详情/运行态临时状态并回首页;`work-detail` 空数据阶段显示轻量读取态,避免异步间隙白屏。
|
- 处理:公开详情失效统一走 `resolveWorkNotFoundRecoveryAction(...)`,覆盖 `/works/detail`、`/gallery/puzzle/detail` 和 `/gallery/visual-novel/detail`;搜索失败和拼图详情 404 分支清理详情/运行态临时状态并回首页;`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 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"`。
|
||||||
- 关联:`docs/technical/PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md`、`src/routing/runtimeNotFoundRecovery.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图 UI 背景只有 objectKey 时不要回退默认 UI
|
## 拼图 UI 背景只有 objectKey 时不要回退默认 UI
|
||||||
|
|
||||||
@@ -822,7 +830,7 @@
|
|||||||
- 原因:首关命名 LLM 旧契约只返回 `levelName`,自动 UI 背景阶段只能用作品名、作品描述、关卡描述和标签拼接确定性兜底提示词;如果模型返回截断 JSON,解析层还可能把 `levelNam` 这类字段名片段当作普通英文关卡名归一化通过。
|
- 原因:首关命名 LLM 旧契约只返回 `levelName`,自动 UI 背景阶段只能用作品名、作品描述、关卡描述和标签拼接确定性兜底提示词;如果模型返回截断 JSON,解析层还可能把 `levelNam` 这类字段名片段当作普通英文关卡名归一化通过。
|
||||||
- 处理:首关命名 LLM 契约必须同时返回 `{"levelName":"...","workDescription":"...","workTags":["..."],"uiBackgroundPrompt":"..."}`;解析层必须拒绝 `levelNam`、`levelName`、`workDescription`、`workTags`、`uiBackgroundPrompt` 等字段名片段作为关卡名。草稿自动 UI 背景生成优先使用该 AI 提示词,作品描述和 6 个作品标签默认填入草稿;视觉精修请求若返回新提示词或作品元信息则覆盖文本请求结果,否则保留文本请求结果。前端文本框只展示已保存的 `uiBackgroundPrompt` 或用户编辑值,字段为空时不展示本地兜底模板。
|
- 处理:首关命名 LLM 契约必须同时返回 `{"levelName":"...","workDescription":"...","workTags":["..."],"uiBackgroundPrompt":"..."}`;解析层必须拒绝 `levelNam`、`levelName`、`workDescription`、`workTags`、`uiBackgroundPrompt` 等字段名片段作为关卡名。草稿自动 UI 背景生成优先使用该 AI 提示词,作品描述和 6 个作品标签默认填入草稿;视觉精修请求若返回新提示词或作品元信息则覆盖文本请求结果,否则保留文本请求结果。前端文本框只展示已保存的 `uiBackgroundPrompt` 或用户编辑值,字段为空时不展示本地兜底模板。
|
||||||
- 验证:执行 `cargo test -p api-server puzzle_level_naming_parser --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server puzzle_first_level_name --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server puzzle_initial --manifest-path server-rs\Cargo.toml`、`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`。
|
- 验证:执行 `cargo test -p api-server puzzle_level_naming_parser --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server puzzle_first_level_name --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server puzzle_initial --manifest-path server-rs\Cargo.toml`、`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`。
|
||||||
- 关联:`server-rs/crates/api-server/src/prompt/puzzle/level_name.rs`、`server-rs/crates/api-server/src/puzzle.rs`、`src/components/puzzle-result/PuzzleResultView.tsx`、`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图 / 抓大鹅 UI 背景重生成报 No such procedure 先查 SpacetimeDB 版本漂移
|
## 拼图 / 抓大鹅 UI 背景重生成报 No such procedure 先查 SpacetimeDB 版本漂移
|
||||||
|
|
||||||
@@ -830,7 +838,7 @@
|
|||||||
- 原因:`api-server` 和 `spacetime-client` 已按新 bindings 调用 procedure,但目标 SpacetimeDB 数据库仍运行旧 wasm,尚未导出钱包扣退费、拼图 UI 背景保存或 Match3D 写回相关 procedure。
|
- 原因:`api-server` 和 `spacetime-client` 已按新 bindings 调用 procedure,但目标 SpacetimeDB 数据库仍运行旧 wasm,尚未导出钱包扣退费、拼图 UI 背景保存或 Match3D 写回相关 procedure。
|
||||||
- 处理:临时容错是把这类 `No such procedure` 当作后端版本漂移:泥点预扣阶段跳过扣费,图片已经生成但保存失败时返回本次内存快照 / 内存 profile,避免草稿页直接报错。长期修复仍是发布最新 `spacetime-module`、重新生成 bindings,并用 `spacetime describe` 或定向 smoke 确认 procedure 已导出。
|
- 处理:临时容错是把这类 `No such procedure` 当作后端版本漂移:泥点预扣阶段跳过扣费,图片已经生成但保存失败时返回本次内存快照 / 内存 profile,避免草稿页直接报错。长期修复仍是发布最新 `spacetime-module`、重新生成 bindings,并用 `spacetime describe` 或定向 smoke 确认 procedure 已导出。
|
||||||
- 验证:`cargo test -p api-server asset_operation_billing_skips_spacetime_connectivity_errors --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_fallback_work_profile_keeps_generated_background_asset --manifest-path server-rs\Cargo.toml`、`npm run api-server` 后检查 `/healthz`。
|
- 验证:`cargo test -p api-server asset_operation_billing_skips_spacetime_connectivity_errors --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_fallback_work_profile_keeps_generated_background_asset --manifest-path server-rs\Cargo.toml`、`npm run api-server` 后检查 `/healthz`。
|
||||||
- 关联:`server-rs/crates/api-server/src/asset_billing.rs`、`server-rs/crates/api-server/src/match3d.rs`、`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图合并块拖起后原位置出现红色块先查选中态泄漏
|
## 拼图合并块拖起后原位置出现红色块先查选中态泄漏
|
||||||
|
|
||||||
@@ -838,7 +846,7 @@
|
|||||||
- 原因:合并块拖拽的可见层来自 `mergedGroups` 绝对定位整体层,但 `pointerdown` 会同步写入 `selectedPieceId`;若棋盘格里的底层单块 DOM 先匹配选中态,再匹配合并态,整体层移开后就会露出单块选中填充色。
|
- 原因:合并块拖拽的可见层来自 `mergedGroups` 绝对定位整体层,但 `pointerdown` 会同步写入 `selectedPieceId`;若棋盘格里的底层单块 DOM 先匹配选中态,再匹配合并态,整体层移开后就会露出单块选中填充色。
|
||||||
- 处理:合并格底层 DOM 只作为透明定位占位,`isSelected` 必须排除 `isMerged`;合并格样式优先级高于单块选中态。
|
- 处理:合并格底层 DOM 只作为透明定位占位,`isSelected` 必须排除 `isMerged`;合并格样式优先级高于单块选中态。
|
||||||
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "拖拽合并大块时底层单格不显示选中色块"`,并确认合并块拖拽时底层 `[data-piece-id]` 仍为 `puzzle-runtime-piece--merged`。
|
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "拖拽合并大块时底层单格不显示选中色块"`,并确认合并块拖拽时底层 `[data-piece-id]` 仍为 `puzzle-runtime-piece--merged`。
|
||||||
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`、`src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`、`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`。
|
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|
||||||
## 拼图历史图片列表不要把账号归属当图片名
|
## 拼图历史图片列表不要把账号归属当图片名
|
||||||
|
|
||||||
@@ -846,4 +854,4 @@
|
|||||||
- 原因:`/api/assets/history?kind=puzzle_cover_image` 返回的 `ownerLabel` 是资产归属账号,不是图片标题;`createdAt` 可能是 SpacetimeDB / shared-kernel 秒级时间字符串,不能只用浏览器 `new Date(value)` 解析。历史图的 `imageSrc` 是 `/generated-*` 私有兼容路径,浏览器预览必须换签。
|
- 原因:`/api/assets/history?kind=puzzle_cover_image` 返回的 `ownerLabel` 是资产归属账号,不是图片标题;`createdAt` 可能是 SpacetimeDB / shared-kernel 秒级时间字符串,不能只用浏览器 `new Date(value)` 解析。历史图的 `imageSrc` 是 `/generated-*` 私有兼容路径,浏览器预览必须换签。
|
||||||
- 处理:前端标题和选中标签从 `imageSrc` 路径末尾推导,例如 `image.png`;时间解析兼容 ISO 与 `1713686400.000000Z`;创作页主图、历史列表图和结果页参考图继续用 `ResolvedAssetImage`,提交给后端时仍保留原始 `imageSrc`。
|
- 处理:前端标题和选中标签从 `imageSrc` 路径末尾推导,例如 `image.png`;时间解析兼容 ISO 与 `1713686400.000000Z`;创作页主图、历史列表图和结果页参考图继续用 `ResolvedAssetImage`,提交给后端时仍保留原始 `imageSrc`。
|
||||||
- 验证:`npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`,并执行 `npm run check:encoding`。
|
- 验证:`npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`,并执行 `npm run check:encoding`。
|
||||||
- 关联:`src/services/puzzle-works/puzzleHistoryAsset.ts`、`src/components/puzzle-agent/PuzzleHistoryAssetPickerDialog.tsx`、`docs/technical/ASSET_HISTORY_PUZZLE_COVER_KIND_FIX_2026-04-27.md`。
|
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
|
|||||||
@@ -1,143 +1,55 @@
|
|||||||
# Genarrative 项目共享概览
|
# Genarrative 项目共享概览
|
||||||
|
|
||||||
> 用途:给团队成员本地 Hermes 快速建立项目背景。内容应保持高层、稳定、可验证;细节以代码、`README.md`、`AGENTS.md` 和 `docs/` 最新文档为准。
|
更新时间:`2026-05-15`
|
||||||
|
|
||||||
## 一句话定位
|
## 一句话定位
|
||||||
|
|
||||||
Genarrative / AI Native Visual RPG 是一个以 **AI 叙事 + 本地规则 + 像素演出** 为核心的视觉 RPG 与 AI 原生游戏创作平台原型。
|
Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 AI 创作、作品草稿、公开分发、运行态、用户账号、钱包任务、后台管理和小程序外壳收在同一套工程中。
|
||||||
|
|
||||||
项目当前不只是单一 RPG demo,而是在同一平台内同时承载:
|
## 当前主要能力
|
||||||
|
|
||||||
- RPG / 自定义世界创作与运行时
|
- RPG / 自定义世界创作与运行时。
|
||||||
- 拼图玩法创作与运行时
|
- 拼图玩法创作、草稿、发布、运行态和排行榜。
|
||||||
- 大鱼吃小鱼玩法链路
|
- 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。
|
||||||
- 抓大鹅 Match3D 玩法链路
|
- 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。
|
||||||
- 用户账号、存档、钱包、任务、埋点、后台管理与生产部署链路
|
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
|
||||||
|
|
||||||
## 已具备的主要能力
|
## 当前入口
|
||||||
|
|
||||||
来自根目录 `README.md` 的当前主能力:
|
- 主站:`http://127.0.0.1:3000`。
|
||||||
|
- 后台:`http://127.0.0.1:3000/admin/` 或 `http://127.0.0.1:3102`。
|
||||||
|
- 后台前端工程:`apps/admin-web`。
|
||||||
|
- 小程序 WebView 外壳:`miniprogram/`。
|
||||||
|
- 法律文本:`media/files/user_agreement.md`、`media/files/privacy_policy.md`、`media/files/disclaimer.md`。
|
||||||
|
|
||||||
- 世界与角色选择
|
移动端一级 Tab:`推荐 / 发现 / 创作 / 草稿 / 我的`。
|
||||||
- AI 剧情推进与流式对话
|
|
||||||
- 战斗演出、NPC 战斗、切磋
|
|
||||||
- NPC 交易、送礼、求助、招募
|
|
||||||
- 宝藏交互
|
|
||||||
- 同伴跟随与战斗
|
|
||||||
- 游戏主流程内嵌的角色资产工坊、自定义世界实体编辑与角色形象编辑
|
|
||||||
- 自动存档与继续游戏
|
|
||||||
|
|
||||||
## 当前前端与平台入口
|
## 当前后端路线
|
||||||
|
|
||||||
- 主站默认地址:`http://127.0.0.1:3000`
|
唯一有效后端路线:
|
||||||
- 后台可从 `http://127.0.0.1:3000/admin/` 进入,也可直连 `http://127.0.0.1:3102`
|
|
||||||
- 主站、后台和 Rust 后端联调默认走 `npm run dev`
|
|
||||||
- 只启动前端页面可用 `npm run dev:web`,默认代理到本地 Rust `api-server`
|
|
||||||
- 后台管理独立前端工程为 `apps/admin-web`,管理端只做表现,数据和写操作走 `server-rs` 的 `/admin/api/*`
|
|
||||||
|
|
||||||
## 当前后端唯一落地口径
|
|
||||||
|
|
||||||
后端主线已经切到:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
server-rs + Axum + SpacetimeDB
|
server-rs + Axum + SpacetimeDB
|
||||||
```
|
```
|
||||||
|
|
||||||
当前唯一有效后端方向:
|
职责边界:
|
||||||
|
|
||||||
- HTTP 门面:Rust `api-server` / Axum
|
- `api-server`:HTTP / SSE / BFF 门面和外部副作用编排。
|
||||||
- 实时状态与业务真相:`server-rs/crates/spacetime-module` / SpacetimeDB
|
- `spacetime-module`:SpacetimeDB 表、reducer、procedure、事务 adapter 和 row mapper。
|
||||||
- 共享领域与契约:`server-rs` 多 crate 分层维护
|
- `spacetime-client`:后端访问 SpacetimeDB 的 typed facade。
|
||||||
- 前端职责:表现、输入采集、临时 UI 状态、服务端结果渲染
|
- `module-*`:纯领域模型、命令、应用规则、领域事件和领域错误。
|
||||||
|
- `platform-*`:OSS、LLM、认证、语音等外部平台能力。
|
||||||
|
- `shared-contracts` / `packages/shared`:前后端 DTO 和公开契约。
|
||||||
|
- 前端:表现、交互、临时 UI 状态和后端结果渲染。
|
||||||
|
|
||||||
明确不再作为正式兼容目标:
|
明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。
|
||||||
|
|
||||||
- `server-node` / Express / PostgreSQL 正式后端路线
|
## 当前文档入口
|
||||||
- Go 服务端试验路线
|
|
||||||
- 浏览器侧承担正式运行时逻辑、正式生成编排或正式数据真相的路线
|
|
||||||
|
|
||||||
## server-rs DDD 分层边界
|
- `docs/README.md`
|
||||||
|
- `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
|
||||||
|
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
|
||||||
|
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||||
|
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||||
|
|
||||||
DDD 分层边界以 `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`、`SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md` 和 `AGENTS.md` 为准:
|
旧 PRD、设计、审计、阶段计划和技术流水账已融合进上述文档;没有融合的旧材料不再作为实现依据。
|
||||||
|
|
||||||
- `module-*`:领域模型、命令、应用编排结果、领域事件、领域错误
|
|
||||||
- `spacetime-module`:SpacetimeDB 表、reducer、procedure、事务 adapter、mapper
|
|
||||||
- `spacetime-client`:后端访问 SpacetimeDB 的 typed facade
|
|
||||||
- `api-server`:HTTP / SSE / BFF adapter 与外部平台服务编排
|
|
||||||
- `platform-*`:LLM、OSS、SMS、微信等外部副作用
|
|
||||||
- `shared-contracts`:前后端 DTO 与公开协议
|
|
||||||
- `shared-kernel`:跨纯领域 crate 复用的基础字符串、ID、时间和归一化能力
|
|
||||||
- `tests-support`:`server-rs` workspace 共享测试支撑
|
|
||||||
|
|
||||||
## 当前 Rust workspace 主要 crate
|
|
||||||
|
|
||||||
以 `server-rs/Cargo.toml` 为准,当前主要成员包括:
|
|
||||||
|
|
||||||
- 业务领域:`module-ai`、`module-assets`、`module-auth`、`module-big-fish`、`module-combat`、`module-inventory`、`module-custom-world`、`module-match3d`、`module-npc`、`module-puzzle`、`module-progression`、`module-quest`、`module-runtime`、`module-runtime-story`、`module-runtime-item`、`module-story`
|
|
||||||
- 平台副作用:`platform-oss`、`platform-auth`、`platform-llm`
|
|
||||||
- 共享层:`shared-contracts`、`shared-kernel`、`shared-logging`
|
|
||||||
- SpacetimeDB 接入:`spacetime-client`、`spacetime-module`
|
|
||||||
- HTTP 服务与测试:`api-server`、`tests-support`
|
|
||||||
|
|
||||||
注意:`server-rs` 的默认 `cargo build` 只构建 `crates/api-server`,本地 SpacetimeDB 模块发布继续走 `spacetime publish --module-path ... --build-options="--debug"`。
|
|
||||||
|
|
||||||
Cargo 依赖口径:第三方依赖版本和 workspace 内部 crate path 统一维护在 `server-rs/Cargo.toml` 的 `[workspace.dependencies]`,成员 crate 默认继承 workspace 依赖,只保留自身 `features`、`optional` 或 target-specific 差异。
|
|
||||||
|
|
||||||
Rust 加密摘要依赖口径:新代码不再引入 `sha1`;OSS V4 签名、阿里云 OpenAPI V3 签名和 refresh session token 摘要统一使用 `sha2::Sha256`。
|
|
||||||
|
|
||||||
## SpacetimeDB 表域总览
|
|
||||||
|
|
||||||
以 `docs/technical/SPACETIMEDB_TABLE_CATALOG.md` 为持续维护入口。当前表域包括:
|
|
||||||
|
|
||||||
- 运维迁移:`database_migration_operator`、`database_migration_import_chunk`
|
|
||||||
- 认证:`auth_store_snapshot`、`user_account`、`auth_identity`、`refresh_session`
|
|
||||||
- 运行时档案:`runtime_setting`、`runtime_snapshot`、`user_browse_history`、`profile_dashboard_state`、`profile_wallet_ledger`、`analytics_date_dimension`、`tracking_event`、`tracking_daily_stat`、`profile_task_config`、`profile_task_progress`、`profile_task_reward_claim`
|
|
||||||
- RPG 运行时:`story_session`、`story_event`、`npc_state`、`inventory_slot`、`battle_state`、`treasure_record`、`quest_record`、`quest_log`、`player_progression`、`chapter_progression`
|
|
||||||
- 世界创作:`custom_world_profile`、`custom_world_session`、`custom_world_agent_session`、`custom_world_agent_message`、`custom_world_agent_operation`、`custom_world_draft_card`、`custom_world_gallery_entry`
|
|
||||||
- 拼图:`puzzle_agent_session`、`puzzle_agent_message`、`puzzle_work_profile`、`puzzle_event`、`puzzle_runtime_run`、`puzzle_leaderboard_entry`
|
|
||||||
- 抓大鹅 Match3D:`match3d_agent_session`、`match3d_agent_message`、`match3d_work_profile`、`match3d_runtime_run`
|
|
||||||
- 大鱼吃小鱼:`big_fish_creation_session`、`big_fish_agent_message`、`big_fish_asset_slot`、`big_fish_event`、`big_fish_runtime_run`
|
|
||||||
- 资产:`asset_object`、`asset_entity_binding`、`asset_event`
|
|
||||||
- AI 任务:`ai_task`、`ai_task_stage`、`ai_text_chunk`、`ai_result_reference`、`ai_task_event`
|
|
||||||
|
|
||||||
## 产品命名与运营口径
|
|
||||||
|
|
||||||
以 `docs/technical/PRODUCT_NAMING_BAIMENG_RENAME_2026-05-01.md` 为准:
|
|
||||||
|
|
||||||
- 产品展示名:陶泥儿
|
|
||||||
- 消费单位:泥点
|
|
||||||
- 公开账号标识:陶泥号
|
|
||||||
- 创作侧称谓:陶泥儿主
|
|
||||||
|
|
||||||
个人任务与埋点系统首版边界:
|
|
||||||
|
|
||||||
- 埋点原始事实写入 `tracking_event`
|
|
||||||
- 聚合投影写入 `tracking_daily_stat`
|
|
||||||
- 任务配置写入 `profile_task_config`
|
|
||||||
- 任务进度写入 `profile_task_progress`
|
|
||||||
- 领奖记录写入 `profile_task_reward_claim`
|
|
||||||
- 钱包流水写入 `profile_wallet_ledger`
|
|
||||||
- “星光”奖励复用现有“泥点”钱包,不新增第二种货币
|
|
||||||
- 个人任务 scope 首版仅支持 `user`
|
|
||||||
|
|
||||||
## 关键文档入口
|
|
||||||
|
|
||||||
- 根项目说明:`README.md`
|
|
||||||
- 项目总约束:`AGENTS.md`
|
|
||||||
- 文档总入口:`docs/README.md`
|
|
||||||
- 经验沉淀:`docs/experience/README.md`
|
|
||||||
- 审计与复盘:`docs/audits/README.md`
|
|
||||||
- 系统设计:`docs/design/README.md`
|
|
||||||
- 技术方案:`docs/technical/README.md`
|
|
||||||
- 规划与优先级:`docs/planning/README.md`
|
|
||||||
- 参考目录:`docs/reference/README.md`
|
|
||||||
- 埋点查询:`docs/tracking/README.md`
|
|
||||||
- 运营查询:`docs/operations/README.md`
|
|
||||||
- 后端当前基线:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
|
||||||
- 后端 DDD 总纲:`docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
|
|
||||||
- 后端并行任务清单:`docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
|
|
||||||
- 契约与路由矩阵:`docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
|
|
||||||
- SpacetimeDB 表结构变更约束:`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
|
|
||||||
- SpacetimeDB 表目录:`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
|
|
||||||
- Rust workspace 依赖集中配置:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`
|
|
||||||
- 生产部署计划:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
|
|
||||||
|
|||||||
@@ -1,100 +1,64 @@
|
|||||||
# 团队协作约定
|
# 团队协作约定
|
||||||
|
|
||||||
> 用途:约定 3 名开发人员在各自本地 Hermes 中协作开发、共享项目记忆的方式。
|
更新时间:`2026-05-15`
|
||||||
|
|
||||||
## 基本模式
|
## 基本模式
|
||||||
|
|
||||||
- 每位开发人员在自己的电脑上使用本地 Hermes。
|
- 每位开发人员在自己的电脑上使用本地 Hermes。
|
||||||
- 每位开发人员本地拉取同一个项目仓库,独立修改代码、运行测试、提交分支。
|
- 团队共享内容放在仓库 `.hermes/` 与 `docs/` 中,通过 Git 同步。
|
||||||
- 团队共享内容优先放在本仓库 `.hermes/` 与 `docs/` 中,通过 Git 同步。
|
- 不共享个人 `~/.hermes` 目录、密钥、会话、Token、Cookie、认证文件、本地私密路径或构建产物。
|
||||||
- 不共享个人 `~/.hermes` 目录。
|
|
||||||
|
|
||||||
## 共享与禁止共享
|
|
||||||
|
|
||||||
推荐共享:
|
|
||||||
|
|
||||||
- `.hermes/shared-memory/` 团队级长期记忆
|
|
||||||
- `.hermes/plans/` 阶段性实施计划
|
|
||||||
- `.hermes/todos/` 已确定需要执行、但尚未进入实施的共享 TODO 计划
|
|
||||||
- `.hermes/skills/` 未来可复用仓库级 skills
|
|
||||||
- `docs/` 中 PRD、设计、技术、经验、审计、查询手册
|
|
||||||
- `AGENTS.md` 项目级 Agent 约束
|
|
||||||
|
|
||||||
禁止提交:
|
|
||||||
|
|
||||||
- 个人 `~/.hermes/config.yaml`
|
|
||||||
- 个人 `~/.hermes/.env`
|
|
||||||
- 个人 `~/.hermes/sessions/`
|
|
||||||
- API Key、Token、Cookie、认证文件
|
|
||||||
- 个人本地私密路径和个人隐私信息
|
|
||||||
- 构建产物、日志、缓存、数据库 dump
|
|
||||||
|
|
||||||
## 开发前
|
## 开发前
|
||||||
|
|
||||||
1. 拉取最新代码。
|
1. 拉取最新代码。
|
||||||
2. 阅读 `AGENTS.md`。
|
2. 阅读 `AGENTS.md`。
|
||||||
3. 阅读 `.hermes/shared-memory/` 中与任务相关的文件。
|
3. 阅读 `.hermes/shared-memory/` 中与任务相关的文件。
|
||||||
4. 阅读 `docs/README.md` 和任务相关分类 README。
|
4. 阅读 `docs/README.md` 和 4 份当前融合文档中与任务相关的部分。
|
||||||
5. 阅读对应 PRD、设计、技术、经验或审计文档。
|
5. 如果文档不足以指导编码,先补充或修正文档。
|
||||||
6. 如果文档不足以指导编码,先补充或修正文档。
|
|
||||||
|
当前文档入口:
|
||||||
|
|
||||||
|
- `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
|
||||||
|
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
|
||||||
|
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||||
|
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||||
|
|
||||||
## 开发中
|
## 开发中
|
||||||
|
|
||||||
- 保持修改范围聚焦,不做无关重构。
|
- 保持修改范围聚焦,不做无关重构。
|
||||||
- 复用、修改、扩展现有系统优先,避免新建重复系统或页面。
|
- 优先复用、修改、扩展现有系统、页面、组件和弹层。
|
||||||
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;只在任务需要时重命名历史文档,避免无关大 diff。
|
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-YYYY-MM-DD.md`。
|
||||||
|
- 阶段性计划、一次性 TODO 和已关闭实验不要长期沉淀为仓库文档;仍有效内容合并进当前 `docs/` 或 `.hermes/shared-memory/`。
|
||||||
- 涉及中文文本时注意 UTF-8 编码和乱码排查。
|
- 涉及中文文本时注意 UTF-8 编码和乱码排查。
|
||||||
- 涉及后端时遵循 DDD 分层,不把业务真相下沉到前端或临时兼容层。
|
- 涉及后端时遵循 DDD 分层,不把业务真相下沉到前端或临时兼容层。
|
||||||
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求均视为历史残留,禁止新增、运行或引用;API smoke 统一使用 `npm run api-server` 与 `/healthz`。
|
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求均视为历史残留,禁止新增、运行或引用。
|
||||||
- 涉及 SpacetimeDB 表结构、发布或迁移时,先看 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md` 和 `SPACETIMEDB_TABLE_CATALOG.md`。
|
- API smoke 统一使用 `npm run api-server` 与 `/healthz`。
|
||||||
- 涉及生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
- 涉及 SpacetimeDB 表结构、发布或迁移时,先看当前后端架构文档的 schema 变更规则和表目录。
|
||||||
|
- 涉及生产发布、服务器配置、Jenkins Job 重建或回滚时,先看当前开发运维文档。
|
||||||
|
|
||||||
## 开发后
|
## 开发后
|
||||||
|
|
||||||
1. 运行与修改范围匹配的测试或验证命令。
|
1. 运行与修改范围匹配的测试或验证命令。
|
||||||
2. 更新相关 `docs/` 文档。
|
2. 更新相关 `docs/` 当前文档。
|
||||||
3. 新增或沉淀 Markdown 文档时,确认文件名已使用 `【标签名】` 前缀。
|
3. 若产生长期有效知识,更新 `.hermes/shared-memory/`。
|
||||||
4. 若产生长期有效知识,更新 `.hermes/shared-memory/`。
|
4. 若形成可复用流程,考虑沉淀到 `.hermes/skills/`。
|
||||||
5. 若形成可复用流程,考虑沉淀到 `.hermes/skills/`。
|
5. 提交信息区分代码变更、文档变更和共享记忆变更。
|
||||||
6. 在提交信息中区分代码变更与文档/记忆变更。
|
|
||||||
|
|
||||||
## 文档阅读顺序
|
|
||||||
|
|
||||||
通用任务建议:
|
|
||||||
|
|
||||||
1. `README.md`
|
|
||||||
2. `AGENTS.md`
|
|
||||||
3. `.hermes/shared-memory/`
|
|
||||||
4. `docs/README.md`
|
|
||||||
5. `docs/experience/README.md`
|
|
||||||
6. `docs/audits/README.md`
|
|
||||||
7. 任务所属分类:`docs/design/`、`docs/technical/`、`docs/planning/`、`docs/prd/`、`docs/reference/`、`docs/tracking/`、`docs/operations/`
|
|
||||||
|
|
||||||
后端任务建议:
|
|
||||||
|
|
||||||
1. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
|
||||||
2. `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
|
|
||||||
3. `docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`
|
|
||||||
4. `docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`
|
|
||||||
5. `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
|
|
||||||
6. `docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
|
|
||||||
7. `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`
|
|
||||||
|
|
||||||
## 共享记忆更新准则
|
## 共享记忆更新准则
|
||||||
|
|
||||||
适合更新:
|
适合更新:
|
||||||
|
|
||||||
- 新增稳定架构约定
|
- 稳定架构约定。
|
||||||
- 新增长期开发流程
|
- 长期开发流程。
|
||||||
- 已验证的踩坑和排障步骤
|
- 已验证的踩坑和排障步骤。
|
||||||
- 重要接口契约变化
|
- 重要接口契约变化。
|
||||||
- 团队协作规范变化
|
- 团队协作规范变化。
|
||||||
- 文档索引或阅读顺序变化
|
- 当前文档索引或阅读顺序变化。
|
||||||
|
|
||||||
不适合更新:
|
不适合更新:
|
||||||
|
|
||||||
- 一次性临时计划
|
- 一次性临时计划。
|
||||||
- 未验证猜测
|
- 未验证猜测。
|
||||||
- 个人偏好和个人路径
|
- 个人偏好和个人路径。
|
||||||
- 敏感信息
|
- 敏感信息。
|
||||||
- 大段聊天记录
|
- 大段聊天记录。
|
||||||
|
|||||||
@@ -280,26 +280,26 @@ fn anonymous_user_cannot_publish_generated_draft() {
|
|||||||
|
|
||||||
| 产物类型 | 推荐路径 | 适用场景 |
|
| 产物类型 | 推荐路径 | 适用场景 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 实施前分析 / 临时计划 | `.hermes/plans/<task-name>-bdd-scenarios.md` | 某次 Hermes 开发任务前,用于澄清行为、拆测试、辅助实现;不一定作为长期产品依据。 |
|
| 实施前分析 / 临时计划 | 当前任务说明或 `.tmp/<task-name>-bdd-scenarios.md` | 某次 Hermes 开发任务前,用于澄清行为、拆测试、辅助实现;不作为长期产品依据。 |
|
||||||
| 正式产品验收 / PRD 场景 | `docs/prd/<FEATURE>_BDD_YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 |
|
| 正式产品验收 / PRD 场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】<功能名>BDD场景-YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 |
|
||||||
| 技术/API/领域行为场景 | `docs/technical/<FEATURE>_BDD_YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 |
|
| 技术/API/领域行为场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【技术验收】<功能名>BDD场景-YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 |
|
||||||
| 自动化 Gherkin feature 文件 | `tests/features/*.feature` 或 `e2e/features/*.feature` | 项目已接入 Cucumber/Playwright BDD 等 Gherkin runner 时。未接入前不要随意新建测试 runner 目录。 |
|
| 自动化 Gherkin feature 文件 | `tests/features/*.feature` 或 `e2e/features/*.feature` | 项目已接入 Cucumber/Playwright BDD 等 Gherkin runner 时。未接入前不要随意新建测试 runner 目录。 |
|
||||||
| 稳定流程或团队经验 | `.hermes/shared-memory/` 或 `.hermes/skills/` | 不是某个功能验收,而是长期可复用的团队流程、坑点、执行规范。 |
|
| 稳定流程或团队经验 | `.hermes/shared-memory/` 或 `.hermes/skills/` | 不是某个功能验收,而是长期可复用的团队流程、坑点、执行规范。 |
|
||||||
|
|
||||||
默认规则:
|
默认规则:
|
||||||
|
|
||||||
1. 用户只说“先用 BDD 梳理一下/写场景/写 Gherkin”,默认写到 `.hermes/plans/<task-name>-bdd-scenarios.md`。
|
1. 用户只说“先用 BDD 梳理一下/写场景/写 Gherkin”,默认在当前任务上下文中输出;需要文件时写到 `.tmp/<task-name>-bdd-scenarios.md`。
|
||||||
2. 用户说“正式验收标准/PRD/产品文档/给测试验收”,写到 `docs/prd/<FEATURE>_BDD_YYYY-MM-DD.md`。
|
2. 用户说“正式验收标准/PRD/产品文档/给测试验收”,优先合并到当前 `docs/` 融合文档;无法容纳时新增带 `【产品验收】` 标签的 Markdown。
|
||||||
3. 用户说“API 行为/后端规则/状态机/埋点/异步任务/SpacetimeDB”,写到 `docs/technical/<FEATURE>_BDD_YYYY-MM-DD.md`。
|
3. 用户说“API 行为/后端规则/状态机/埋点/异步任务/SpacetimeDB”,优先合并到当前后端架构或开发运维文档;无法容纳时新增带 `【技术验收】` 标签的 Markdown。
|
||||||
4. 用户明确要求“可执行 feature 文件”且项目已有 runner,再写 `.feature` 文件;否则先写 Markdown BDD 文档,并在测试映射中标注未来自动化落点。
|
4. 用户明确要求“可执行 feature 文件”且项目已有 runner,再写 `.feature` 文件;否则先写 Markdown BDD 文档,并在测试映射中标注未来自动化落点。
|
||||||
5. 如果 BDD 场景会作为编码依据,文档中必须包含“测试映射”表,标注场景要落到哪些测试文件。
|
5. 如果 BDD 场景会作为编码依据,文档中必须包含“测试映射”表,标注场景要落到哪些测试文件。
|
||||||
|
|
||||||
命名建议:
|
命名建议:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.hermes/plans/profile-feedback-bdd-scenarios.md
|
.tmp/profile-feedback-bdd-scenarios.md
|
||||||
docs/prd/PROFILE_FEEDBACK_BDD_2026-05-11.md
|
docs/【产品验收】帮助与反馈BDD场景-2026-05-11.md
|
||||||
docs/technical/WORK_PLAY_TRACKING_BDD_2026-05-11.md
|
docs/【技术验收】作品游玩埋点BDD场景-2026-05-11.md
|
||||||
tests/features/profile-feedback.feature
|
tests/features/profile-feedback.feature
|
||||||
e2e/features/invite-code.feature
|
e2e/features/invite-code.feature
|
||||||
```
|
```
|
||||||
@@ -308,9 +308,9 @@ e2e/features/invite-code.feature
|
|||||||
|
|
||||||
除 BDD/Gherkin 场景外,相关配套内容可放在:
|
除 BDD/Gherkin 场景外,相关配套内容可放在:
|
||||||
|
|
||||||
- 实施计划:`.hermes/plans/<task-name>.md`
|
- 实施计划:当前任务上下文或 `.tmp/<task-name>.md`
|
||||||
- 产品/验收文档:`docs/prd/<FEATURE>_PRD_YYYY-MM-DD.md`
|
- 产品/验收文档:当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】中文标题-YYYY-MM-DD.md`
|
||||||
- 技术设计:`docs/technical/<FEATURE>_TECHNICAL_YYYY-MM-DD.md`
|
- 技术设计:当前 `docs/` 融合文档,必要时新增 `docs/【技术方案】中文标题-YYYY-MM-DD.md`
|
||||||
- 共享经验或稳定流程:`.hermes/shared-memory/` 或 `.hermes/skills/`
|
- 共享经验或稳定流程:`.hermes/shared-memory/` 或 `.hermes/skills/`
|
||||||
|
|
||||||
BDD 文档建议包含:
|
BDD 文档建议包含:
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ metadata:
|
|||||||
|
|
||||||
### 1. 先补技术方案文档
|
### 1. 先补技术方案文档
|
||||||
|
|
||||||
项目要求工程修改前先检查/补充落地文档。若没有明确文档,先写到 `docs/technical/`,至少说明:
|
项目要求工程修改前先检查/补充落地文档。优先更新当前融合文档;后台、接口、表查询、埋点和运营查询通常落到 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 或 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。只有现有文档无法容纳时,才新增 `docs/【标签名】中文标题-YYYY-MM-DD.md`,至少说明:
|
||||||
|
|
||||||
- 后台页面目标。
|
- 后台页面目标。
|
||||||
- 后端接口路径、鉴权、query/body、response。
|
- 后端接口路径、鉴权、query/body、response。
|
||||||
|
|||||||
@@ -52,4 +52,4 @@ git diff --check
|
|||||||
## 提交注意
|
## 提交注意
|
||||||
|
|
||||||
- 不要提交 `.env.local`、`.env.secrets.local` 或任何 token/密码/连接串。
|
- 不要提交 `.env.local`、`.env.secrets.local` 或任何 token/密码/连接串。
|
||||||
- 若工作区里有本地敏感文件,只提交明确改动的 Rust 文件和 `docs/technical/*` 文档。
|
- 若工作区里有本地敏感文件,只提交明确改动的 Rust 文件和当前 `docs/` 文档。
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
|
|
||||||
## 关键文件
|
## 关键文件
|
||||||
|
|
||||||
- `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
|
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||||
- 第 47 行左右写明:用户打开任务中心时后端幂等记录当日 `daily_login`;点击领取时校验进度和领奖记录。
|
- 当前任务、钱包、埋点和运营查询口径统一维护在该文档。
|
||||||
- 接口说明中写明 `GET /api/profile/tasks` 会读取任务中心并记录当日登录埋点。
|
- 历史阶段文档曾记录过:用户打开任务中心时后端幂等记录当日 `daily_login`;点击领取时校验进度和领奖记录。若当前代码已变更,以代码和当前融合文档为准。
|
||||||
- `server-rs/crates/api-server/src/runtime_profile.rs`
|
- `server-rs/crates/api-server/src/runtime_profile.rs`
|
||||||
- `get_profile_task_center` 调用 `state.spacetime_client().get_profile_task_center(user_id)`。
|
- `get_profile_task_center` 调用 `state.spacetime_client().get_profile_task_center(user_id)`。
|
||||||
- `claim_profile_task_reward` 调用 `state.spacetime_client().claim_profile_task_reward(user_id, task_id)`。
|
- `claim_profile_task_reward` 调用 `state.spacetime_client().claim_profile_task_reward(user_id, task_id)`。
|
||||||
@@ -36,11 +36,7 @@
|
|||||||
git grep -n "daily_login\|tracking_event\|get_profile_task_center\|claim_profile_task_reward" -- server-rs apps docs
|
git grep -n "daily_login\|tracking_event\|get_profile_task_center\|claim_profile_task_reward" -- server-rs apps docs
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 对照设计文档中的事件口径:
|
2. 对照当前开发运维文档中的任务、钱包、埋点和运营查询口径。
|
||||||
|
|
||||||
```bash
|
|
||||||
sed -n '35,58p' docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 追 API handler 到 SpacetimeDB reducer:
|
3. 追 API handler 到 SpacetimeDB reducer:
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ metadata:
|
|||||||
- refresh session 是否在 rotate 与 access token 签发成功后调用 helper。
|
- refresh session 是否在 rotate 与 access token 签发成功后调用 helper。
|
||||||
- 失败策略是否只 warning、不阻断响应。
|
- 失败策略是否只 warning、不阻断响应。
|
||||||
4. 如涉及 SpacetimeDB procedure/table/binding,按项目 SpacetimeDB skills 与文档同步检查绑定生成、`migration.rs`、private table 限制。
|
4. 如涉及 SpacetimeDB procedure/table/binding,按项目 SpacetimeDB skills 与文档同步检查绑定生成、`migration.rs`、private table 限制。
|
||||||
5. 修改前补齐 `docs/technical/` 中对应方案/根因;修改后同步更新。
|
5. 修改前补齐当前 `docs/` 中对应方案/根因;修改后同步更新当前融合文档和必要的共享记忆。
|
||||||
|
|
||||||
## 关键经验:已登录打开网页也要主动 refresh 才能写登录埋点
|
## 关键经验:已登录打开网页也要主动 refresh 才能写登录埋点
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ metadata:
|
|||||||
- `scripts/dev-stack-port-utils.mjs`
|
- `scripts/dev-stack-port-utils.mjs`
|
||||||
- `scripts/dev-rust-stack.sh`
|
- `scripts/dev-rust-stack.sh`
|
||||||
- `scripts/dev-web-rust.mjs`
|
- `scripts/dev-web-rust.mjs`
|
||||||
- `docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`
|
- `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
|
||||||
- `.hermes/shared-memory/pitfalls.md`
|
- `.hermes/shared-memory/pitfalls.md`
|
||||||
2. 优先改公共端口工具,不要把端口探测逻辑复制到多个脚本。
|
2. 优先改公共端口工具,不要把端口探测逻辑复制到多个脚本。
|
||||||
3. 对 Bash 脚本只做局部补丁,避免整文件重写导致中文注释或换行大面积变化。
|
3. 对 Bash 脚本只做局部补丁,避免整文件重写导致中文注释或换行大面积变化。
|
||||||
@@ -122,6 +122,6 @@ node scripts/dev-stack-port-utils.mjs resolve-dev-stack spacetime:127.0.0.1:0 ap
|
|||||||
- [ ] `dev-rust-stack.sh` 通过 `bash -n`。
|
- [ ] `dev-rust-stack.sh` 通过 `bash -n`。
|
||||||
- [ ] `npm run dev` / `npm run dev:rust` 的 SpacetimeDB、publish、api-server、主站 Vite、后台 Vite 都使用实际端口。
|
- [ ] `npm run dev` / `npm run dev:rust` 的 SpacetimeDB、publish、api-server、主站 Vite、后台 Vite 都使用实际端口。
|
||||||
- [ ] `npm run dev:web` 在主站端口不可用时能切换到可用端口。
|
- [ ] `npm run dev:web` 在主站端口不可用时能切换到可用端口。
|
||||||
- [ ] 文档同步更新 `docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`。
|
- [ ] 文档同步更新 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||||
- [ ] 长期踩坑同步更新 `.hermes/shared-memory/pitfalls.md`。
|
- [ ] 长期踩坑同步更新 `.hermes/shared-memory/pitfalls.md`。
|
||||||
- [ ] 修改中文文件后运行 `npm run check:encoding`。
|
- [ ] 修改中文文件后运行 `npm run check:encoding`。
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## 推荐顺序
|
## 推荐顺序
|
||||||
|
|
||||||
1. 先读仓库 `README.md`、`AGENTS.md`、相关 `/docs/technical` 与 `.hermes/plans`,确认当前阶段范围。
|
1. 先读仓库 `README.md`、`AGENTS.md` 和当前 `docs/` 融合文档,确认当前阶段范围。
|
||||||
2. 遵循 TDD:先在 `server-rs/crates/module-runtime/tests/` 写纯函数测试,验证缺失类型/函数导致 RED。
|
2. 遵循 TDD:先在 `server-rs/crates/module-runtime/tests/` 写纯函数测试,验证缺失类型/函数导致 RED。
|
||||||
3. 在 `module-runtime/src/domain.rs` 增加领域类型,例如:
|
3. 在 `module-runtime/src/domain.rs` 增加领域类型,例如:
|
||||||
- `AnalyticsGranularity`:`day | week | month | quarter | year`
|
- `AnalyticsGranularity`:`day | week | month | quarter | year`
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
11. 接 `api-server`:
|
11. 接 `api-server`:
|
||||||
- `src/runtime_profile.rs`:Query params / parser / handler / response builder。
|
- `src/runtime_profile.rs`:Query params / parser / handler / response builder。
|
||||||
- `src/app.rs`:挂路由,例如 profile 或 admin analytics endpoint;选择路径前确认产品定位。
|
- `src/app.rs`:挂路由,例如 profile 或 admin analytics endpoint;选择路径前确认产品定位。
|
||||||
12. 最后更新 docs/plan,并确认 diff 不只是生成物。
|
12. 最后更新当前 `docs/` 文档和必要的 `.hermes/shared-memory/` 摘要,并确认 diff 不只是生成物。
|
||||||
|
|
||||||
## 验证命令示例
|
## 验证命令示例
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ metadata:
|
|||||||
|
|
||||||
## 推荐实施顺序
|
## 推荐实施顺序
|
||||||
|
|
||||||
1. 读取 `.hermes/plans/...` 或产品文档,确认入口、路由、页面行为。
|
1. 读取当前融合文档,确认入口、路由、页面行为。
|
||||||
2. 若现有文档不足,先在 `docs/prd/` 增加可编码落地的 PRD。
|
2. 若现有文档不足,优先更新 `docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` 或 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`;只有无法容纳时才新增 `docs/【标签名】中文标题-YYYY-MM-DD.md`。
|
||||||
3. 增加 `SelectionStage` 与 `appPageRoutes` 映射,并先跑 `npm run typecheck`。
|
3. 增加 `SelectionStage` 与 `appPageRoutes` 映射,并先跑 `npm run typecheck`。
|
||||||
4. 新建独立页面组件,尽量通过 props 暴露 `onBack`/`onSubmit`,避免直接耦合全局状态。
|
4. 新建独立页面组件,尽量通过 props 暴露 `onBack`/`onSubmit`,避免直接耦合全局状态。
|
||||||
5. 在 `RpgEntryHomeView.tsx` 增加入口 prop 与按钮。
|
5. 在 `RpgEntryHomeView.tsx` 增加入口 prop 与按钮。
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
|
|
||||||
## 关键文件
|
## 关键文件
|
||||||
|
|
||||||
- `docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`
|
- `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
|
||||||
|
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||||
- `src/components/platform-entry/platformEntryTypes.ts`
|
- `src/components/platform-entry/platformEntryTypes.ts`
|
||||||
- `src/routing/appPageRoutes.ts`
|
- `src/routing/appPageRoutes.ts`
|
||||||
- `src/components/platform-entry/PlatformFeedbackView.tsx`
|
- `src/components/platform-entry/PlatformFeedbackView.tsx`
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
# 项目共享 TODO 计划
|
|
||||||
|
|
||||||
本目录用于存放已经讨论定稿、需要后续执行,但当前尚未实施的项目级计划文档。
|
|
||||||
|
|
||||||
## 使用规则
|
|
||||||
|
|
||||||
- 每个 TODO 计划单独一个 Markdown 文件,文件名优先使用可检索的中文标题与日期。
|
|
||||||
- 每个 TODO 计划文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`。
|
|
||||||
- 计划内容必须足够明确,后续开发者可以直接据此开始实施。
|
|
||||||
- 已执行完成的计划应迁移到对应 `docs/` 技术/规划文档,或在本文档中标记完成并移出 TODO 队列。
|
|
||||||
- 不在这里保存个人私密路径、密钥、临时聊天记录或未确认猜测。
|
|
||||||
|
|
||||||
## 当前待执行
|
|
||||||
|
|
||||||
- [【后端架构】api-server 能力模块化与图片资产 Adapter 收口计划](./【后端架构】api-server能力模块化与图片资产Adapter收口计划-2026-05-14.md)
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
# api-server 能力模块化与生成资产 Adapter 完整收口计划
|
|
||||||
|
|
||||||
状态:待执行
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
目标是把 `api-server` 从“超大 `app.rs` + 多个超大 handler 文件 + 多处生成资产重复链路”收成可长期维护的能力 Module 结构。
|
|
||||||
|
|
||||||
本计划完整覆盖:路由能力模块化、生成图片资产 Adapter、复杂媒体链路扩展、大 handler 瘦身、文档与验收。全程不改变 HTTP contract、DTO、SpacetimeDB schema、前端行为和计费语义,除非某阶段文档先明确提出并单独批准。
|
|
||||||
|
|
||||||
## 文档交付
|
|
||||||
|
|
||||||
先新增一个总纲,再按阶段新增单独执行文档:
|
|
||||||
|
|
||||||
- 总纲:`docs/technical/【后端架构】api-server能力模块化与生成资产Adapter总纲-2026-05-14.md`
|
|
||||||
- 阶段 1:`docs/technical/【后端架构】api-server路由能力模块化执行计划-2026-05-14.md`
|
|
||||||
- 阶段 2:`docs/technical/【后端架构】生成图片资产Adapter收口执行计划-2026-05-14.md`
|
|
||||||
- 阶段 3:`docs/technical/【后端架构】复杂媒体资产链路Adapter扩展计划-2026-05-14.md`
|
|
||||||
- 阶段 4:`docs/technical/【后端架构】api-server大Handler瘦身执行计划-2026-05-14.md`
|
|
||||||
- 同步更新 `server-rs/crates/api-server/README.md`、`docs/technical/README.md`、本 TODO 文档。
|
|
||||||
|
|
||||||
## 阶段 0:基线盘点与总纲冻结
|
|
||||||
|
|
||||||
- 盘点 `app.rs` 当前全部 route,按 `admin/auth/assets/profile/creation/runtime/story/platform/internal` 分类形成 route inventory。
|
|
||||||
- 盘点 Big Fish、Square Hole、Custom World、Puzzle、Match3D、Visual Novel、音频、视频、GLB 的生成资产链路,标出 provider、下载方式、OSS prefix、asset kind、entity binding、计费位置、降级行为。
|
|
||||||
- 在总纲文档中冻结边界:`api-server` 只做 HTTP/SSE/BFF、鉴权、DTO 映射、平台服务编排;领域规则仍归 `module-*`,SpacetimeDB 真相仍归 `spacetime-module`。
|
|
||||||
|
|
||||||
退出条件:总纲和 inventory 能指导后续编码,不再只是一段方向描述。
|
|
||||||
|
|
||||||
## 阶段 1:路由能力模块化完整收口
|
|
||||||
|
|
||||||
- 新增 `server-rs/crates/api-server/src/modules/`,每个能力 Module 暴露 `router(state) -> Router<AppState>`。
|
|
||||||
- 第一批迁移低风险路由:`admin`、`auth`、`assets`、`profile`、`internal`、`health`。
|
|
||||||
- 第二批迁移平台编排路由:`ai_tasks`、`llm`、`speech`、`wechat`、`creative_agent`、`visual_novel`、通用 audio。
|
|
||||||
- 第三批迁移玩法路由:`big_fish`、`square_hole`、`puzzle`、`match3d`、`custom_world`、`story`、runtime save/settings/chat/inventory。
|
|
||||||
- `app.rs` 最终只保留全局 middleware、TraceLayer、request context、tracking middleware、`.merge(modules::*::router(...))` 和少量顶层 glue。
|
|
||||||
- handler 实现第一阶段可以继续留在原文件;本阶段只改变路由装配位置,不混入业务重构。
|
|
||||||
|
|
||||||
验收:route inventory 中所有原 route 仍存在;旧明确下线 route 继续 404;`cargo test -p api-server app --manifest-path server-rs/Cargo.toml` 或等价 route 回归通过。
|
|
||||||
|
|
||||||
## 阶段 2:稳定单图生成资产 Adapter 收口
|
|
||||||
|
|
||||||
- 新增 `modules/assets/generated_image_assets` 内部 Module,Interface 覆盖“provider 生成 -> 下载/base64 解码 -> MIME/extension 归一 -> OSS private upload -> HEAD -> asset_object confirm -> entity binding”。
|
|
||||||
- Adapter 输入包含 provider、prompt、negative prompt、size、reference images、OSS prefix/path/file name、asset kind、entity kind/id、slot、owner/profile/source_job_id、metadata、可选透明背景后处理。
|
|
||||||
- Adapter 输出包含 `legacy_public_path`、`object_key`、`asset_object_id`、`mime_type`、`extension`、`task_id`、`actual_prompt`。
|
|
||||||
- 首批迁移:
|
|
||||||
- Big Fish 正式图片:主图、动作图、舞台背景。
|
|
||||||
- Square Hole 图片:保留生成成功但入库失败时回退 Data URL。
|
|
||||||
- Custom World 场景图、自动草稿场景图、生成封面图。
|
|
||||||
- `asset_billing.rs` 仍由调用方显式包裹;Adapter 不扣费、不退款、不读钱包。
|
|
||||||
|
|
||||||
验收:三类调用方都经过同一 Adapter;删除旧重复 persist 函数后行为不变;Big Fish、Square Hole、Custom World 定向测试通过。
|
|
||||||
|
|
||||||
## 阶段 3:复杂媒体资产链路扩展
|
|
||||||
|
|
||||||
- 扩展 Adapter,但不把玩法图像处理规则塞进公共 Interface。
|
|
||||||
- Puzzle:
|
|
||||||
- 收口普通 generations、edits/multipart 生成结果的下载、OSS、asset object、binding。
|
|
||||||
- 拼图关卡 JSON 更新、参考图策略、UI 背景落位仍留在 Puzzle 编排层。
|
|
||||||
- Match3D:
|
|
||||||
- APIMart material sheet、VectorEngine 背景/容器/封面生成接入统一入库能力。
|
|
||||||
- 5x5 切图、绿幕透明化、格内校准、批量新增补齐规则仍留在 Match3D 专属处理器。
|
|
||||||
- 音频:
|
|
||||||
- 评估是否抽 `generated_media_assets`,但背景音乐、点击音效的计费和落位语义不与图片 Adapter 混用。
|
|
||||||
- GLB/视频:
|
|
||||||
- 仅历史转存链路复用“OSS + asset_object + binding”底层持久化能力,不恢复新草稿 GLB 生产。
|
|
||||||
|
|
||||||
验收:Puzzle 与 Match3D 的 generated 私有资产仍通过 `/api/assets/read-url` 换签读取;Match3D 不回退 Rodin/GLB;音频试听和运行态仍可播放。
|
|
||||||
|
|
||||||
## 阶段 4:超大 handler 能力内瘦身
|
|
||||||
|
|
||||||
- 在路由已模块化、资产 Adapter 已稳定后,再拆大文件,避免同时改 route 和业务实现。
|
|
||||||
- 对 `match3d.rs`、`puzzle.rs`、`custom_world.rs`、`custom_world_ai.rs`、`big_fish.rs`、`square_hole.rs` 分别按能力拆:
|
|
||||||
- `router.rs` 只挂路由。
|
|
||||||
- `handlers.rs` 只做 Axum extract、鉴权、request/response。
|
|
||||||
- `application.rs` 做 api-server 层编排。
|
|
||||||
- `assets.rs` 只放玩法专属生成资产策略。
|
|
||||||
- `mapper.rs` 只做 DTO/record 映射。
|
|
||||||
- `errors.rs` 只做该能力错误映射。
|
|
||||||
- 不把领域规则留在 handler;发现领域规则时只登记迁出候选,不在本阶段直接扩大到 `module-*` 重构。
|
|
||||||
|
|
||||||
验收:每个原超大文件显著缩小;新文件按能力可读;定向玩法测试和全量 `api-server` 测试通过。
|
|
||||||
|
|
||||||
## 阶段 5:清理、文档和最终验收
|
|
||||||
|
|
||||||
- 删除旧重复 helper、过时注释和已迁移的私有函数。
|
|
||||||
- 更新 `api-server` README:目录规则、Router 暴露规则、Adapter 边界、禁止事项。
|
|
||||||
- 更新 `.hermes/shared-memory` 中长期有效的架构约定和排障经验。
|
|
||||||
- 最终验收命令:
|
|
||||||
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
|
||||||
- `cargo test -p api-server --manifest-path server-rs/Cargo.toml`
|
|
||||||
- `npm run check:server-rs-ddd`
|
|
||||||
- `npm run check:encoding`
|
|
||||||
- `git diff --check`
|
|
||||||
- `npm run api-server` 后检查 `/healthz`
|
|
||||||
- 禁止使用 `api-server:maincloud` 作为本轮 smoke。
|
|
||||||
|
|
||||||
## Public Interfaces
|
|
||||||
|
|
||||||
- HTTP route、DTO、error envelope、SpacetimeDB schema、前端调用方式默认不变。
|
|
||||||
- 新增的都是 `api-server` 内部 Rust Interface,不进入 `shared-contracts`。
|
|
||||||
- 若后续任何阶段发现必须改 contract,先更新对应阶段文档和 G1 route/contract 矩阵,再单独实施。
|
|
||||||
|
|
||||||
## Assumptions
|
|
||||||
|
|
||||||
- 本计划目标是完整收口,不是只完成第一阶段。
|
|
||||||
- 可以分阶段提交,但每个阶段都必须有文档、测试和明确退出条件。
|
|
||||||
- `generated_image_assets` 首版必须至少被三个真实调用方使用,否则不算形成有效 Module。
|
|
||||||
86
AGENTS.md
@@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
### Issue tracker
|
### Issue tracker
|
||||||
|
|
||||||
Issues are tracked in the self-hosted Gitea remote for this repo. Use Gitea Issues via the configured Gitea UI/API or `tea` CLI when available; do not use GitHub `gh` or GitLab `glab` unless the repo is migrated. See `docs/agents/issue-tracker.md`.
|
Issues are tracked in the self-hosted Gitea remote for this repo. Use Gitea Issues via the configured Gitea UI/API or `tea` CLI when available; do not use GitHub `gh` or GitLab `glab` unless the repo is migrated. Current issue workflow is summarized in `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`.
|
||||||
|
|
||||||
### Triage labels
|
### Triage labels
|
||||||
|
|
||||||
Use the default canonical triage labels: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.
|
Use the default canonical triage labels: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`.
|
||||||
|
|
||||||
### Domain docs
|
### Domain docs
|
||||||
|
|
||||||
Single-context layout: read root `CONTEXT.md` when present and architecture decisions from `docs/adr/`. See `docs/agents/domain.md`.
|
Single-context layout: read root `CONTEXT.md` when present. Current architecture and product constraints are consolidated under `docs/`.
|
||||||
|
|
||||||
## 项目约束
|
## 项目约束
|
||||||
- 代码需要有完善的中文注释
|
- 代码需要有完善的中文注释
|
||||||
@@ -47,16 +47,16 @@ Single-context layout: read root `CONTEXT.md` when present and architecture deci
|
|||||||
- 每个阶段任务完成后自动压缩上下文,确保后续阶段在清晰、低噪音的上下文基础上继续推进。
|
- 每个阶段任务完成后自动压缩上下文,确保后续阶段在清晰、低噪音的上下文基础上继续推进。
|
||||||
|
|
||||||
## 后端技术约束
|
## 后端技术约束
|
||||||
- 后端最新技术约束以 [`docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`](docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md) 为总纲;执行和收口状态以 [`docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`](docs/technical/SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md) 为准。
|
- 后端最新技术约束以 [`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`](docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md) 为准。
|
||||||
- 契约、路由、DTO 去留和 breaking change 以 [`docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`](docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md) 为准;不得在前端、`api-server` 或临时兼容层中重新发明旧接口。
|
- 契约、路由、DTO 去留和 breaking change 以当前后端架构文档、`server-rs/crates/api-server/src/app.rs`、`shared-contracts` 和 `packages/shared` 为准;不得在前端、`api-server` 或临时兼容层中重新发明旧接口。
|
||||||
- SpacetimeDB 表结构、自动迁移限制和冲突处理以 [`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`](docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md) 为准;涉及 table、reducer、procedure、row shape 或绑定变化时,必须同步 `migration.rs`、表目录和生成绑定。
|
- SpacetimeDB 表结构、自动迁移限制和冲突处理以当前后端架构文档的 schema 变更规则和表目录为准;涉及 table、reducer、procedure、row shape 或绑定变化时,必须同步 `migration.rs`、表目录和生成绑定。
|
||||||
- SpacetimeDB 已有表新增字段时,字段必须放在 Rust 表结构体最后,并设置明确默认值(例如 `#[default(...)]`);需要修改字段名时,必须先询问用户并确认迁移计划,再改代码,同时更新 `server-rs/crates/spacetime-module/src/migration.rs`、表目录和生成绑定。
|
- SpacetimeDB 已有表新增字段时,字段必须放在 Rust 表结构体最后,并设置明确默认值(例如 `#[default(...)]`);需要修改字段名时,必须先询问用户并确认迁移计划,再改代码,同时更新 `server-rs/crates/spacetime-module/src/migration.rs`、表目录和生成绑定。
|
||||||
- 修改 SpacetimeDB schema 后必须运行 `npm run check:spacetime-schema`;该检查会拦截新增字段缺 default、字段不在末尾、字段删除/改名/重排/改类型,以及漏改 `migration.rs`、表目录或生成绑定。
|
- 修改 SpacetimeDB schema 后必须运行 `npm run check:spacetime-schema`;该检查会拦截新增字段缺 default、字段不在末尾、字段删除/改名/重排/改类型,以及漏改 `migration.rs`、表目录或生成绑定。
|
||||||
- 后端路线固定为 `server-rs + Axum + SpacetimeDB`。旧 `server-node`、Express、PostgreSQL 不再作为兼容目标;历史实现只能作为迁移参考,若旧文档与 DDD 约束冲突,先修正文档和方案再编码。
|
- 后端路线固定为 `server-rs + Axum + SpacetimeDB`。旧 `server-node`、Express、PostgreSQL 不再作为兼容目标;历史实现只能作为迁移参考,若旧文档与 DDD 约束冲突,先修正文档和方案再编码。
|
||||||
- DDD 分层边界按总纲执行:领域规则沉到 `module-*`,SpacetimeDB 表和事务编排留在 `spacetime-module`,后端访问 SpacetimeDB 统一经 `spacetime-client` facade,HTTP/SSE/BFF 留在 `api-server`,外部副作用留在 `platform-*`,前后端 DTO 留在 `shared-contracts`。
|
- DDD 分层边界按总纲执行:领域规则沉到 `module-*`,SpacetimeDB 表和事务编排留在 `spacetime-module`,后端访问 SpacetimeDB 统一经 `spacetime-client` facade,HTTP/SSE/BFF 留在 `api-server`,外部副作用留在 `platform-*`,前后端 DTO 留在 `shared-contracts`。
|
||||||
- 前端只做表现、交互和临时 UI 状态,不承接正式业务真相,不绕过后端投影或后端 API 直接实现业务规则。
|
- 前端只做表现、交互和临时 UI 状态,不承接正式业务真相,不绕过后端投影或后端 API 直接实现业务规则。
|
||||||
- 修改后端代码后,按对应 DDD 文档中的验收命令执行测试;涉及 API smoke 时使用 `npm run api-server` 重新拉起后端并执行相应自动测试,同时确认 `/healthz`。
|
- 修改后端代码后,按对应 DDD 文档中的验收命令执行测试;涉及 API smoke 时使用 `npm run api-server` 重新拉起后端并执行相应自动测试,同时确认 `/healthz`。
|
||||||
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关脚本、环境变量、测试、文档要求和命名全部视为历史残留,禁止新增、运行或引用;若旧文档仍要求 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`,以 [`docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`](docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md) 和本文件为准,并先修正文档口径。
|
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关脚本、环境变量、测试、文档要求和命名全部视为历史残留,禁止新增、运行或引用;若旧材料仍要求 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`,以当前后端架构文档和本文件为准。
|
||||||
- 除 CI/CD 脚本内部受控用法外,人工命令、本地联调、排障步骤和文档示例禁止继续使用 `spacetime --root-dir`。本地数据隔离使用项目脚本或 `--data-dir`,发布目标必须显式传 `--server` / `--server-url`,身份问题通过同一 CLI 登录态、专用运行用户或显式 token 处理;若旧文档仍推荐 `--root-dir`,先修正文档口径再执行。
|
- 除 CI/CD 脚本内部受控用法外,人工命令、本地联调、排障步骤和文档示例禁止继续使用 `spacetime --root-dir`。本地数据隔离使用项目脚本或 `--data-dir`,发布目标必须显式传 `--server` / `--server-url`,身份问题通过同一 CLI 登录态、专用运行用户或显式 token 处理;若旧文档仍推荐 `--root-dir`,先修正文档口径再执行。
|
||||||
- 凡是涉及 SpacetimeDB 的设计、实现、脚本、调试、前端绑定接入,统一显式使用以下 skill 作为执行依据:
|
- 凡是涉及 SpacetimeDB 的设计、实现、脚本、调试、前端绑定接入,统一显式使用以下 skill 作为执行依据:
|
||||||
- [$spacetimedb-cli](.codex\\skills\\spacetimedb-cli\\SKILL.md)
|
- [$spacetimedb-cli](.codex\\skills\\spacetimedb-cli\\SKILL.md)
|
||||||
@@ -76,72 +76,8 @@ Single-context layout: read root `CONTEXT.md` when present and architecture deci
|
|||||||
```text
|
```text
|
||||||
docs/
|
docs/
|
||||||
├─ README.md
|
├─ README.md
|
||||||
├─ audits/
|
├─ 【项目基线】当前产品与工程约束-2026-05-15.md
|
||||||
│ ├─ README.md
|
├─ 【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md
|
||||||
│ ├─ FUNCTION_DESIGN_AUDIT_2026-04-03.md
|
├─ 【玩法创作】平台入口与玩法链路-2026-05-15.md
|
||||||
│ ├─ ITEM_AND_BUILD_PRD_AUDIT_2026-04-05.md
|
└─ 【开发运维】本地开发验证与生产运维-2026-05-15.md
|
||||||
│ ├─ engineering/
|
|
||||||
│ │ ├─ README.md
|
|
||||||
│ │ ├─ ENGINEERING_OPTIMIZATION_REVIEW_2026-03-29.md
|
|
||||||
│ │ ├─ ENGINEERING_OPTIMIZATION_REVIEW_2026-03-30.md
|
|
||||||
│ │ ├─ ENGINEERING_OPTIMIZATION_REVIEW_2026-04-01.md
|
|
||||||
│ │ └─ MONSTER_NPC_UNIFICATION_AUDIT_2026-04-06.md
|
|
||||||
│ └─ text/
|
|
||||||
│ ├─ README.md
|
|
||||||
│ ├─ CHINESE_MOJIBAKE_INVENTORY.md
|
|
||||||
│ ├─ EDITOR_GAME_PRESET_TEXT_AUDIT_2026-03-25.md
|
|
||||||
│ ├─ GAME_EDITOR_PRESET_TEXT_AUDIT_2026-03-29.md
|
|
||||||
│ ├─ GAME_EDITOR_PRESET_TEXT_AUDIT_2026-03-30.md
|
|
||||||
│ ├─ GAME_UI_PRESET_EDITOR_NPC_PROMPT_TEXT_AUDIT_2026-04-02_DEEP_SCAN.md
|
|
||||||
│ ├─ GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-03-30_CONTINUED.md
|
|
||||||
│ ├─ GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-03-31.md
|
|
||||||
│ ├─ GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-04-01.md
|
|
||||||
│ └─ GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-04-02.md
|
|
||||||
├─ design/
|
|
||||||
│ ├─ README.md
|
|
||||||
│ ├─ AI_NATIVE_RUNTIME_ITEM_SYSTEM_REDESIGN_2026-04-02.md
|
|
||||||
│ ├─ COMPANION_FIRST_CONTACT_RELATIONSHIP_AND_PRIVATE_CHAT_DESIGN_2026-04-04.md
|
|
||||||
│ ├─ CUSTOM_WORLD_CREATOR_INPUT_AND_AI_BOUNDARY_DESIGN_2026-04-06.md
|
|
||||||
│ ├─ EQUIPMENT_BUILD_AND_FORGE_LOOP_SYSTEM_DESIGN.md
|
|
||||||
│ └─ npc-conversation-situation-draft.md
|
|
||||||
├─ experience/
|
|
||||||
│ ├─ README.md
|
|
||||||
│ ├─ ADVENTURE_RUNTIME_DEV_EXPERIENCE.md
|
|
||||||
│ ├─ AGENT_UI_CHANGELOG.md
|
|
||||||
│ ├─ CODEX_IMPLEMENTATION_EXPERIENCE_2026-03-24.md
|
|
||||||
│ ├─ CODEX_PAST_WORK_EXPERIENCE_SUMMARY.md
|
|
||||||
│ ├─ MOBILE_UI_DEV_EXPERIENCE.md
|
|
||||||
│ ├─ PROJECT_DEVELOPMENT_EXPERIENCE.md
|
|
||||||
│ └─ PROJECT_WORK_EXPERIENCE_PLAYBOOK.md
|
|
||||||
├─ planning/
|
|
||||||
│ ├─ README.md
|
|
||||||
│ └─ CURRENT_GAME_ITERATION_PRIORITIES_2026-04-03.md
|
|
||||||
├─ prd/
|
|
||||||
│ ├─ AI_CHARACTER_VISUAL_ANIMATION_MVP_PRD_2026-04-04.md
|
|
||||||
│ ├─ AI_NATIVE_CLASSIC_RPG_EXPERIENCE_BENCHMARK_PRD_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_CROSS_GENRE_STORY_ENGINE_PRD_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_CUSTOM_WORLD_CREATION_FLOW_OPTIMIZATION_PRD_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_NARRATIVE_THREAD_ITEM_AND_WORLD_NPC_PRD_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_QUEST_SYSTEM_PRD_2026-04-02.md
|
|
||||||
│ ├─ AI_NATIVE_RUNTIME_ITEM_GENERATION_DESIGN.md
|
|
||||||
│ ├─ AI_NATIVE_STORY_ENGINE_PHASE1_IMPLEMENTATION_PLAN_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_STORY_ENGINE_PHASE2_IMPLEMENTATION_PLAN_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_STORY_ENGINE_PHASE3_IMPLEMENTATION_PLAN_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_STORY_ENGINE_PHASE4_IMPLEMENTATION_PLAN_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_STORY_ENGINE_PHASE5_IMPLEMENTATION_PLAN_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_STORY_ENGINE_PHASE6_IMPLEMENTATION_PLAN_2026-04-06.md
|
|
||||||
│ ├─ AI_NATIVE_UNIFIED_ROLE_ATTRIBUTE_SYSTEM_PRD_2026-04-02.md
|
|
||||||
│ ├─ BUILD_SYSTEM_ATTRIBUTE_SIMILARITY_PRD_2026-04-02.md
|
|
||||||
│ └─ RUNTIME_ITEM_GENERATION_CURRENT_SYSTEM_DESIGN.md
|
|
||||||
├─ reference/
|
|
||||||
│ ├─ README.md
|
|
||||||
│ └─ FUNCTION_SCRIPT_CATALOG_2026-04-04.md
|
|
||||||
└─ technical/
|
|
||||||
├─ README.md
|
|
||||||
├─ AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md
|
|
||||||
├─ GO_SERVER_RUNTIME_INTEGRATION_2026-04-07.md
|
|
||||||
├─ GO_SERVER_TASKLIST_2026-04-08.md
|
|
||||||
├─ NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md
|
|
||||||
├─ PIXELMOTION_TECHNICAL_BREAKDOWN_2026-04-04.md
|
|
||||||
└─ SERVER_DEPLOYMENT_AND_CORS_TECHNICAL_SOLUTION_2026-04-05.md
|
|
||||||
```
|
```
|
||||||
|
|||||||
41
README.md
@@ -96,33 +96,32 @@ npm run check:content
|
|||||||
|
|
||||||
主运行时:
|
主运行时:
|
||||||
|
|
||||||
- [src/App.tsx](/E:/Repos/Genarrative/src/App.tsx)
|
- [src/App.tsx](./src/App.tsx)
|
||||||
- [src/components/GameShell.tsx](/E:/Repos/Genarrative/src/components/GameShell.tsx)
|
- [src/components/GameShell.tsx](./src/components/GameShell.tsx)
|
||||||
- [src/hooks/useCombatFlow.ts](/E:/Repos/Genarrative/src/hooks/useCombatFlow.ts)
|
- [src/hooks/useCombatFlow.ts](./src/hooks/useCombatFlow.ts)
|
||||||
- [src/hooks/useStoryGeneration.ts](/E:/Repos/Genarrative/src/hooks/useStoryGeneration.ts)
|
- [src/hooks/useStoryGeneration.ts](./src/hooks/useStoryGeneration.ts)
|
||||||
|
|
||||||
主流程内嵌编辑能力:
|
主流程内嵌编辑能力:
|
||||||
|
|
||||||
- [src/components/CustomWorldEntityEditorModal.tsx](/E:/Repos/Genarrative/src/components/CustomWorldEntityEditorModal.tsx)
|
- [src/components/CustomWorldEntityEditorModal.tsx](./src/components/CustomWorldEntityEditorModal.tsx)
|
||||||
- [src/components/CustomWorldNpcVisualEditor.tsx](/E:/Repos/Genarrative/src/components/CustomWorldNpcVisualEditor.tsx)
|
- [src/components/CustomWorldNpcVisualEditor.tsx](./src/components/CustomWorldNpcVisualEditor.tsx)
|
||||||
- [src/components/CustomWorldRoleAssetStudioModal.tsx](/E:/Repos/Genarrative/src/components/CustomWorldRoleAssetStudioModal.tsx)
|
- [src/components/CustomWorldRoleAssetStudioModal.tsx](./src/components/CustomWorldRoleAssetStudioModal.tsx)
|
||||||
|
|
||||||
核心数据:
|
核心数据:
|
||||||
|
|
||||||
- [src/data/scenePresets.ts](/E:/Repos/Genarrative/src/data/scenePresets.ts)
|
- [src/data/scenePresets.ts](./src/data/scenePresets.ts)
|
||||||
- [src/data/characterPresets.ts](/E:/Repos/Genarrative/src/data/characterPresets.ts)
|
- [src/data/characterPresets.ts](./src/data/characterPresets.ts)
|
||||||
- [src/data/monsterPresets.ts](/E:/Repos/Genarrative/src/data/monsterPresets.ts)
|
- [src/data/monsterPresets.ts](./src/data/monsterPresets.ts)
|
||||||
- [src/data/npcInteractions.ts](/E:/Repos/Genarrative/src/data/npcInteractions.ts)
|
- [src/data/npcInteractions.ts](./src/data/npcInteractions.ts)
|
||||||
- [src/data/treasureInteractions.ts](/E:/Repos/Genarrative/src/data/treasureInteractions.ts)
|
- [src/data/treasureInteractions.ts](./src/data/treasureInteractions.ts)
|
||||||
|
|
||||||
## 文档入口
|
## 文档入口
|
||||||
|
|
||||||
- [docs/README.md](/E:/Repos/Genarrative/docs/README.md):文档总入口,按主题分类后的导航页
|
`docs/` 已在 `2026-05-15` 完成压缩整理,旧 PRD、设计、审计、阶段计划和技术流水账不再作为实现依据。当前只读取:
|
||||||
- [docs/experience/README.md](/E:/Repos/Genarrative/docs/experience/README.md):项目开发经验、UI 交接、历史实现经验
|
|
||||||
- [docs/audits/README.md](/E:/Repos/Genarrative/docs/audits/README.md):工程审查、文本审计、专项审计
|
- [docs/README.md](./docs/README.md):当前文档总入口。
|
||||||
- [docs/planning/README.md](/E:/Repos/Genarrative/docs/planning/README.md):当前阶段优先级与推进顺序
|
- [docs/【项目基线】当前产品与工程约束-2026-05-15.md](./docs/【项目基线】当前产品与工程约束-2026-05-15.md):产品、命名、UI、协作和废弃路线。
|
||||||
- [docs/design/README.md](/E:/Repos/Genarrative/docs/design/README.md):玩法、关系、物品与对话设计
|
- [docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](./docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md):DDD 边界、API 分组、SpacetimeDB schema 规则和表目录。
|
||||||
- [docs/technical/README.md](/E:/Repos/Genarrative/docs/technical/README.md):技术路线、服务端方案、外部产品拆解
|
- [docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](./docs/【玩法创作】平台入口与玩法链路-2026-05-15.md):创作入口、草稿架和各玩法当前口径。
|
||||||
- [docs/reference/README.md](/E:/Repos/Genarrative/docs/reference/README.md):Function 与脚本速查
|
- [docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](./docs/【开发运维】本地开发验证与生产运维-2026-05-15.md):本地启动、检查、部署、埋点和运营查询。
|
||||||
- [docs/prd/](/E:/Repos/Genarrative/docs/prd/):PRD 与阶段计划,原样保留
|
- [UI_CODING_STANDARD.md](./UI_CODING_STANDARD.md):像素 UI 资产与编码规范。
|
||||||
- [UI_CODING_STANDARD.md](/E:/Repos/Genarrative/UI_CODING_STANDARD.md):UI 资产与编码规范
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# UI Coding Standard
|
# UI Coding Standard
|
||||||
|
|
||||||
> **会话交接 / 改动总览**:见 `docs/experience/AGENT_UI_CHANGELOG.md`(文件映射、9-slice 架构、已知坑、未收尾项)。
|
> **当前文档入口**:项目文档已压缩到 `docs/README.md` 和 4 份当前文档;UI 资产和 9-slice 规则以本文为准,平台级 UI 约束见 `docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,19 @@
|
|||||||
# 文档总览
|
# Genarrative 文档入口
|
||||||
|
|
||||||
`docs/` 现在按主题拆成了 6 类;旧后端路线文档开始聚合和删除,后续实现以 Rust / SpacetimeDB 当前基线为准。
|
本目录已经在 `2026-05-15` 完成压缩整理:旧 PRD、阶段计划、修复记录、审计报告、技术方案和查询手册中的仍有效内容,统一融合到下列当前文档。旧文档不再作为实现依据。
|
||||||
|
|
||||||
## 快速入口
|
## 当前文档
|
||||||
|
|
||||||
- [经验沉淀](./experience/README.md):项目开发经验、UI 交接、历史实现经验。
|
- [【项目基线】当前产品与工程约束-2026-05-15.md](./【项目基线】当前产品与工程约束-2026-05-15.md):产品定位、平台入口、UI 约束、协作规则和废弃路线。
|
||||||
- [审计与复盘](./audits/README.md):工程审查、文本/乱码审计、专项落地审计。
|
- [【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](./【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md):server-rs DDD 边界、API 分组、SpacetimeDB schema 变更规则和当前表目录。
|
||||||
- [系统设计](./design/README.md):玩法、关系、物品与对话设计。
|
- [【玩法创作】平台入口与玩法链路-2026-05-15.md](./【玩法创作】平台入口与玩法链路-2026-05-15.md):创作 Tab、草稿架、拼图、抓大鹅、视觉小说、方洞、大鱼、汪汪声浪和儿童向玩法的当前口径。
|
||||||
- [技术方案](./technical/README.md):动画、服务端、外部产品形态拆解。
|
- [【开发运维】本地开发验证与生产运维-2026-05-15.md](./【开发运维】本地开发验证与生产运维-2026-05-15.md):本地启动、测试、编码检查、SpacetimeDB 变更流程、生产运维、埋点和运营查询。
|
||||||
- [规划与优先级](./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)。
|
|
||||||
- [埋点查询](./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)。
|
新增稳定知识优先合并到上述文档。只有上述文档无法容纳某个长期主题时,才新增同目录的 `【标签名】中文标题-YYYY-MM-DD.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)。
|
1. 代码和当前文档冲突时,以代码为准,并立即修正文档。
|
||||||
|
2. 新增 Markdown 文件名必须使用 `【标签名】中文标题-YYYY-MM-DD.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)。
|
3. 旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir`、Match3D 新草稿 Rodin/GLB、拼图和抓大鹅音频生成入口,均不再作为当前实现依据。
|
||||||
|
4. 涉及 SpacetimeDB 表、reducer、procedure、row shape 或 bindings 时,必须同步修改代码、迁移、生成绑定和 [后端架构文档](./【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.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),快速建立这个项目的开发共识。
|
|
||||||
2. 再看 [工程审查总览](./audits/engineering/README.md) 和 [文本审计总览](./audits/text/README.md),了解当前风险。
|
|
||||||
3. 需要排期时看 [规划与优先级](./planning/README.md)。
|
|
||||||
4. 需要补方案时进入 [系统设计](./design/README.md) / [技术方案](./technical/README.md);涉及后端先看 [当前后端实现基线](./technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md),涉及生产发布链路先看 [生产部署计划](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),涉及 SpacetimeDB 表结构变更时再看 [表结构变更约束](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。
|
|
||||||
5. 需要对齐目标边界时再进入 [PRD](./prd)。
|
|
||||||
|
|
||||||
## 分类规则
|
|
||||||
|
|
||||||
- `experience/`:偏方法论、交接经验、长期有效的开发结论。
|
|
||||||
- `audits/`:偏“现状扫描 / 问题定位 / 是否达标”的审查类文档。
|
|
||||||
- `design/`:偏玩法机制、叙事关系、系统结构设计。
|
|
||||||
- `technical/`:偏技术选型、实现路线、竞品/产品形态拆解。
|
|
||||||
- `planning/`:偏阶段优先级与推进顺序。
|
|
||||||
- `reference/`:偏目录、速查、检索辅助。
|
|
||||||
- `tracking/`:偏埋点原始事实和聚合投影查询,不放任务进度或钱包对账。
|
|
||||||
- `operations/`:偏后台运营核查、对账和排障查询。
|
|
||||||
|
|
||||||
## 文档命名规则
|
|
||||||
|
|
||||||
后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`。标签用于跨目录检索,不替代上面的目录分类;历史文档不要求批量重命名,除非本次任务明确涉及该文档。
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Issue tracker: Gitea
|
|
||||||
|
|
||||||
Issues and PRDs for this repo live as issues in the self-hosted Gitea remote:
|
|
||||||
|
|
||||||
- Remote: `https://git.genarrative.world/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.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
# Agent 聊天到草稿生成到进入游戏世界链路审计
|
|
||||||
|
|
||||||
更新时间:`2026-04-20`
|
|
||||||
|
|
||||||
## 0. 审计目标
|
|
||||||
|
|
||||||
本次审计只看一条链:
|
|
||||||
|
|
||||||
`Agent 聊天 -> 世界草稿生成 -> 结果页/作品库 -> 进入游戏世界`
|
|
||||||
|
|
||||||
聚焦回答四类问题:
|
|
||||||
|
|
||||||
1. 哪些数据在链路中断掉了
|
|
||||||
2. 哪些地方在代码里同时存在多条 pipeline
|
|
||||||
3. 哪些字段、功能、组件已经变成冗余或主链弱消费
|
|
||||||
4. 哪些能力在 contract、PRD 或代码结构里已经定义,但并没有真正实装到当前游戏主流程
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 结论先行
|
|
||||||
|
|
||||||
当前系统还没有形成“Agent 会话是唯一真相源、发布后再进入世界”的单一主链,而是处在多条 pipeline 并存、多个桥接层临时粘合的状态。
|
|
||||||
|
|
||||||
最关键的结论有 8 条:
|
|
||||||
|
|
||||||
1. 当前至少并存 `5` 条相关 pipeline,其中真正影响可玩流程的主链至少有 `3` 条。
|
|
||||||
2. 最大的数据断点是:`CustomWorldAgentSessionSnapshot.draftProfile` 不直接进入 runtime,前端 `buildCustomWorldProfileFromAgentDraft()` 会先把它本地编译成 legacy `CustomWorldProfile`,后面的结果页、自动保存、进入世界都只认这个 legacy profile。
|
|
||||||
3. 服务端内部也存在一次“先编成 legacy runtime profile,再转回 foundation draft”的双重编译,`draftProfile.legacyResultProfile` 是这个桥接层留下来的强耦合字段。
|
|
||||||
4. `packages/shared/src/contracts/customWorldAgent.ts`、`server-node/src/routes/customWorldAgent.ts`、`server-node/src/services/customWorldAgentOrchestrator.ts` 三层定义不一致,`publish_world / generate_scene_assets / sync_scene_assets / expand_long_tail / lock_cards / unlock_cards / regenerate_scope` 等关键动作没有形成真实可用链路。
|
|
||||||
5. `CustomWorldResultView.tsx` 仍保留“直接对 legacy profile 生成角色/地点、直接编辑 profile”的旧流程,会绕过 Agent session,是当前最明显的并行 pipeline 和冗余功能源。
|
|
||||||
6. “进入世界”和“发布世界”目前是两套平行逻辑。Agent 草稿结果页可以自动保存并直接进入世界,但 `publish_world` action 仍不可用,`qualityFindings / blocker` 校验也没有真正接入。
|
|
||||||
7. `listCustomWorldWorks()` 与 `CustomWorldWorkSummaryService` 已能聚合 Agent 草稿和已发布 profile,但平台 `create` tab 仍主要展示 `myEntries`,Agent draft session 不能自然回到主入口,恢复创作主要依赖 `activeSessionId`。
|
|
||||||
8. Agent 工作区主 UI 只接了头部、进度、线程、输入框、操作横幅等极简子集,PRD 里规划的锁定条、草稿抽屉、详情面板、澄清面板、快捷动作、发布校验结果等大部分还没有真正进入当前游戏主流程。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 目标链路
|
|
||||||
|
|
||||||
按 `docs/prd/AI_NATIVE_AGENT_FIRST_CUSTOM_WORLD_CREATOR_PRD_2026-04-12.md` 和 `docs/prd/AI_NATIVE_AGENT_FIRST_EIGHT_ANCHOR_CO_CREATION_FLOW_PRD_2026-04-16.md`,目标链路应当是:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Agent 对话
|
|
||||||
-> Express 后端维护结构化 eight-anchor / creatorIntent / lockState / draftSnapshot
|
|
||||||
-> foundation draft
|
|
||||||
-> 角色资产工坊 / 场景资产工坊
|
|
||||||
-> sync 回 Agent session draft
|
|
||||||
-> expand long tail
|
|
||||||
-> publish_world
|
|
||||||
-> 服务端执行 quality / blocker 校验
|
|
||||||
-> 服务端编译最终 CustomWorldProfile
|
|
||||||
-> 持久化到世界库
|
|
||||||
-> 进入世界
|
|
||||||
```
|
|
||||||
|
|
||||||
这条目标链路有 4 个硬约束:
|
|
||||||
|
|
||||||
1. Express 后端才是真实状态源,前端只负责展示和输入,不负责结构化草稿编译。
|
|
||||||
2. 未发布的 Agent 草稿不应该直接污染正式世界库,主入口里应该通过“继续创作”恢复。
|
|
||||||
3. 进入世界前应先经过 `publish_world`,并由发布校验阻止缺角色资产、缺场景资产、缺主线第一幕等 blocker。
|
|
||||||
4. 结果页不再是旧自定义世界编辑器的平移副本,而应更接近“最终预览 / 发布确认 / 进入世界”的收口层。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 当前真实链路
|
|
||||||
|
|
||||||
## 3.1 Agent 会话草稿链
|
|
||||||
|
|
||||||
当前新链路实际是:
|
|
||||||
|
|
||||||
```text
|
|
||||||
PreGameSelectionFlow.tsx
|
|
||||||
-> /api/runtime/custom-world/agent/sessions
|
|
||||||
-> CustomWorldAgentSessionStore
|
|
||||||
-> CustomWorldAgentOrchestrator
|
|
||||||
-> CustomWorldAgentFoundationDraftService
|
|
||||||
-> CustomWorldAgentAutoAssetService
|
|
||||||
-> session.draftProfile / draftCards / assetCoverage
|
|
||||||
-> 前端 buildCustomWorldProfileFromAgentDraft()
|
|
||||||
-> generatedCustomWorldProfile
|
|
||||||
-> upsertCustomWorldProfile()
|
|
||||||
-> handleCustomWorldSelect(profile)
|
|
||||||
-> runtime
|
|
||||||
```
|
|
||||||
|
|
||||||
关键特点:
|
|
||||||
|
|
||||||
1. Agent session 不是 runtime 直接消费的对象。
|
|
||||||
2. Agent 草稿完成后,会在前端先转成 `CustomWorldProfile`。
|
|
||||||
3. 结果页阶段会自动调用 `upsertCustomWorldProfile()`,把当前 profile 写进 `custom-world-library`。
|
|
||||||
4. “进入世界”按钮直接把这个 profile 送给 `handleCustomWorldSelect(...)`,不需要 `publish_world`。
|
|
||||||
|
|
||||||
主要证据:
|
|
||||||
|
|
||||||
- `src/components/game-shell/PreGameSelectionFlow.tsx`
|
|
||||||
- `src/services/customWorldAgentDraftResult.ts`
|
|
||||||
- `src/hooks/useGameFlow.ts`
|
|
||||||
|
|
||||||
## 3.2 旧自定义世界 session 链
|
|
||||||
|
|
||||||
旧链路仍然完整存在:
|
|
||||||
|
|
||||||
```text
|
|
||||||
aiService.generateCustomWorldProfile()
|
|
||||||
-> /api/runtime/custom-world/sessions
|
|
||||||
-> answerCustomWorldSessionQuestion()
|
|
||||||
-> /generate/stream
|
|
||||||
-> generateCustomWorldProfile()
|
|
||||||
-> CustomWorldProfile
|
|
||||||
-> 结果页 / 作品库 / 进入世界
|
|
||||||
```
|
|
||||||
|
|
||||||
关键特点:
|
|
||||||
|
|
||||||
1. `src/services/aiService.ts` 里的 `generateCustomWorldProfile()` 仍然会创建旧 `custom-world/sessions`。
|
|
||||||
2. 前端会先根据 `world_hook / player_premise / opening_situation / core_conflict` 自动补默认回答,再触发流式生成。
|
|
||||||
3. 这条链已经与 Agent 八锚点链并行存在,且依然可用。
|
|
||||||
|
|
||||||
主要证据:
|
|
||||||
|
|
||||||
- `src/services/aiService.ts`
|
|
||||||
- `server-node/src/routes/runtimeRoutes.ts`
|
|
||||||
- `server-node/src/services/customWorldSessionStore.ts`
|
|
||||||
|
|
||||||
## 3.3 已保存 profile / 作品库链
|
|
||||||
|
|
||||||
当前作品库链是:
|
|
||||||
|
|
||||||
```text
|
|
||||||
custom-world-library
|
|
||||||
-> upsert / delete / publish / unpublish
|
|
||||||
-> PlatformHomeView / saved profile detail
|
|
||||||
-> CustomWorldResultView
|
|
||||||
-> handleCustomWorldSelect(profile)
|
|
||||||
```
|
|
||||||
|
|
||||||
关键特点:
|
|
||||||
|
|
||||||
1. 这条链直接消费 `CustomWorldProfile`,不依赖 Agent session。
|
|
||||||
2. Agent 结果页自动保存后,也会落入这条链。
|
|
||||||
3. `publish/unpublish` 作用在作品库 profile 上,而不是 Agent session 上。
|
|
||||||
|
|
||||||
主要证据:
|
|
||||||
|
|
||||||
- `server-node/src/routes/runtimeRoutes.ts`
|
|
||||||
- `src/components/game-shell/PlatformHomeView.tsx`
|
|
||||||
- `src/components/game-shell/PreGameSelectionFlow.tsx`
|
|
||||||
|
|
||||||
## 3.4 结果页 legacy profile 直改链
|
|
||||||
|
|
||||||
`CustomWorldResultView.tsx` 仍保留旧能力:
|
|
||||||
|
|
||||||
1. `generateCustomWorldPlayableNpc({ profile })`
|
|
||||||
2. `generateCustomWorldStoryNpc({ profile })`
|
|
||||||
3. `generateCustomWorldLandmark({ profile })`
|
|
||||||
4. `CustomWorldEntityEditorModal`
|
|
||||||
|
|
||||||
这意味着结果页不仅是预览层,还是一套独立的“legacy profile 直改工作台”。这一套能力不会回写 Agent session 的结构化状态,也不会走 Agent action route。
|
|
||||||
|
|
||||||
主要证据:
|
|
||||||
|
|
||||||
- `src/components/CustomWorldResultView.tsx`
|
|
||||||
- `src/services/aiService.ts`
|
|
||||||
|
|
||||||
## 3.5 创作中心 works 聚合链
|
|
||||||
|
|
||||||
后端已经能聚合两类作品:
|
|
||||||
|
|
||||||
1. `sourceType: 'agent_session'`
|
|
||||||
2. `sourceType: 'published_profile'`
|
|
||||||
|
|
||||||
但主平台 `create` tab 现在仍主要展示 `myEntries`,没有把 `CustomWorldCreationHub.tsx` 作为主入口接上。
|
|
||||||
|
|
||||||
这导致:
|
|
||||||
|
|
||||||
1. works 聚合链存在
|
|
||||||
2. create tab 真实消费的是另一条链
|
|
||||||
3. Agent draft session 的继续创作入口没有真正收口到主平台
|
|
||||||
|
|
||||||
主要证据:
|
|
||||||
|
|
||||||
- `server-node/src/services/customWorldWorkSummaryService.ts`
|
|
||||||
- `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
- `src/components/game-shell/PlatformHomeView.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 数据断点
|
|
||||||
|
|
||||||
| 断点 | 当前现状 | 影响 | 主要证据 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| Agent session -> runtime | `buildCustomWorldProfileFromAgentDraft()` 在前端把 `session.draftProfile` 编译成 legacy `CustomWorldProfile`,后续结果页、自动保存、进入世界都只认 profile | 后端不再是最终唯一真相源,前端承担了结构化编译与字段裁决,容易产生字段丢失、语义漂移、状态失真 | `src/components/game-shell/PreGameSelectionFlow.tsx`、`src/services/customWorldAgentDraftResult.ts` |
|
|
||||||
| foundation draft 内部双重编译 | `CustomWorldAgentFoundationDraftService` 会先 `buildCompiledCustomWorldProfile(...)`,再 `convertRuntimeProfileToFoundationDraft(...)`,并把结果塞进 `legacyResultProfile` | Agent draft 不是原生生成,而是绕了一次 legacy profile,再回 draft;后续桥接层依赖这个字段继续工作 | `server-node/src/services/customWorldAgentFoundationDraftService.ts` |
|
|
||||||
| 创作态元数据进入最终 profile | 前端桥接时会把 `anchorContent / creatorIntent / anchorPack / lockState` 一并塞进 legacy profile;同时固定写入 `generationMode: 'fast'`、`generationStatus: 'key_only'` | 创作态数据污染运行时 profile 存储;`generationMode / generationStatus` 还会覆盖真实阶段语义 | `src/services/customWorldAgentDraftResult.ts` |
|
|
||||||
| Agent session 元数据在结果页后被截断 | `draftCards / pendingClarifications / suggestedActions / qualityFindings / checkpoints / operations` 大多停留在 session 层;结果页与 runtime 只继续消费 profile | 进入结果页后,Agent 会话层的大量结构化上下文被切断,发布门槛、锁定、局部重生成等信息无法自然继承 | `packages/shared/src/contracts/customWorldAgent.ts`、`server-node/src/services/customWorldAgentSessionStore.ts`、`src/components/custom-world-agent/CustomWorldAgentWorkspace.tsx` |
|
|
||||||
| works 聚合 -> 平台 create tab | 后端 `listCustomWorldWorkSummaries(...)` 能返回 draft 与 published,但 create tab 仍只渲染 `myEntries` | Agent draft session 无法稳定出现在主入口“我的创作”里,恢复创作入口割裂 | `server-node/src/services/customWorldWorkSummaryService.ts`、`src/components/game-shell/PlatformHomeView.tsx` |
|
|
||||||
| 发布状态 -> 可玩状态 | 结果页会自动 `upsertCustomWorldProfile()` 并允许直接 `onEnterWorld`;但 `publish_world` action 仍不可用 | “可玩”与“已发布”没有统一门槛,发布校验无法阻止未完成草稿进入世界 | `src/components/game-shell/PreGameSelectionFlow.tsx`、`server-node/src/services/customWorldAgentOrchestrator.ts` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 多条 Pipeline
|
|
||||||
|
|
||||||
## 5.1 主链级 pipeline
|
|
||||||
|
|
||||||
| pipeline | 真相源 | 当前是否在主流程可达 | 问题 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| Agent 会话草稿链 | `CustomWorldAgentSessionStore` + `draftProfile` | 是 | 后半段通过前端桥接成 legacy profile,未形成端到端单一真相源 |
|
|
||||||
| 旧 custom-world session 链 | `CustomWorldSessionStore` | 是 | 与 Agent 八锚点链重复,且前端仍在补默认回答 |
|
|
||||||
| 已保存 / 已发布 profile 链 | `custom-world-library` 中的 `CustomWorldProfile` | 是 | 与 Agent draft session 发布链平行存在 |
|
|
||||||
| 结果页 legacy profile 直改链 | 结果页本地 `profile` | 是 | 绕过 Agent session,属于并行编辑器 |
|
|
||||||
| works 创作中心链 | `listCustomWorldWorks()` 聚合数据 | 否,主平台未接主入口 | 后端已有聚合,但 UI 没真正切过去 |
|
|
||||||
|
|
||||||
## 5.2 资产子链 pipeline
|
|
||||||
|
|
||||||
资产相关还存在“自动补齐”和“人工工坊写回”并存:
|
|
||||||
|
|
||||||
1. `draft_foundation` 后,`CustomWorldAgentAutoAssetService` 会自动补角色主图和幕背景图。
|
|
||||||
2. 角色资产又存在 `generate_role_assets -> sync_role_assets` 的手动工坊写回链。
|
|
||||||
3. 场景资产在 contract 层定义了 `generate_scene_assets / sync_scene_assets`,但主 action 链未打通。
|
|
||||||
|
|
||||||
这导致当前资产链不是一条统一 pipeline,而是:
|
|
||||||
|
|
||||||
```text
|
|
||||||
自动补角色 / 自动补幕背景
|
|
||||||
并存
|
|
||||||
手动角色工坊 -> sync_role_assets
|
|
||||||
缺失
|
|
||||||
手动场景工坊 -> sync_scene_assets
|
|
||||||
```
|
|
||||||
|
|
||||||
主要证据:
|
|
||||||
|
|
||||||
- `server-node/src/services/customWorldAgentAutoAssetService.ts`
|
|
||||||
- `server-node/src/services/customWorldAgentRoleAssetStateService.ts`
|
|
||||||
- `packages/shared/src/contracts/customWorldAgent.ts`
|
|
||||||
- `server-node/src/services/customWorldAgentOrchestrator.ts`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 冗余字段与主链悬空字段
|
|
||||||
|
|
||||||
这里区分两类:
|
|
||||||
|
|
||||||
1. 已经明显承担桥接残留职责的冗余字段
|
|
||||||
2. 在 contract / session 里存在,但当前主流程几乎不消费的悬空字段
|
|
||||||
|
|
||||||
| 字段 | 类型 | 当前状态 | 判断 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `draftProfile.legacyResultProfile` | 桥接残留字段 | foundation draft 服务端先生成 legacy runtime profile,再把它塞回 draft,前端桥接又优先读它 | 明显冗余,属于临时兼容字段,不应长期成为主链依赖 |
|
|
||||||
| `generationMode: 'fast'` | 固定写死字段 | `buildCustomWorldProfileFromAgentDraft()` 固定写入 | 不是草稿真实状态,更像桥接层补丁 |
|
|
||||||
| `generationStatus: 'key_only'` | 固定写死字段 | `buildCustomWorldProfileFromAgentDraft()` 固定写入 | 同上,会掩盖真实生成阶段 |
|
|
||||||
| `anchorContent / creatorIntent / anchorPack / lockState` 被直接塞进 legacy profile | 创作态元数据 | 会跟随自动保存一起写进作品库 profile,但 runtime 并不以这些字段为正式运行时输入 | 当前更像创作态元数据泄漏进运行时 profile |
|
|
||||||
| `qualityFindings` | session / contract 字段 | contract、session store、测试里存在,但没形成生成、渲染、发布阻断闭环 | 当前主链悬空 |
|
|
||||||
| `checkpoints` | session 字段 | session store 会记录,但主工作区和结果页没有真实展示入口 | 当前主链悬空 |
|
|
||||||
| `suggestedActions` | session 字段 | session 会生成,但当前正式工作区没有对应消费面;旧 `QuickActions` 面板已在 `2026-04-21` 判定退出当前版本主链并物理删除 | 当前主链悬空 |
|
|
||||||
| `pendingClarifications` | session 字段 | session 有数据,但当前正式工作区没有对应消费面;旧澄清面板已在 `2026-04-21` 判定退出当前版本主链并物理删除 | 当前主链悬空 |
|
|
||||||
| `operations` 历史 | session 字段 | 主工作区只展示当前 `activeOperation` 横幅,不展示完整历史 | 当前主链弱消费 |
|
|
||||||
| `roleAssetSummaryLabel / cover* / counts` 等 works 字段 | works 聚合字段 | 后端能返回,但主平台 create tab 没走 `works` 入口 | 当前主链弱消费 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 冗余功能与冗余组件
|
|
||||||
|
|
||||||
## 7.1 冗余功能
|
|
||||||
|
|
||||||
| 功能 | 当前状态 | 问题 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 结果页直接生成 playable/story/landmark | `CustomWorldResultView.tsx` 仍可直接调用 AI 生成 | 与 Agent 对象精修链重复,且不会同步回 session |
|
|
||||||
| 结果页直接编辑 `CustomWorldProfile` | `CustomWorldEntityEditorModal` 仍挂在结果页 | 把结果页继续维持成旧编辑器,而不是 Agent 流程的收口层 |
|
|
||||||
| 旧 `custom-world/sessions` 世界生成 | `2026-04-20` 审计时仍完整可用;`2026-04-21` 已完成物理删除 | 与 Agent 八锚点世界创建重复;当前遗留问题已转为文档口径清理 |
|
|
||||||
| 作品库 `publish/unpublish` 与 Agent `publish_world` | 两套“发布”概念并行 | 一套作用于 library profile,一套想作用于 Agent session,但后者还未打通 |
|
|
||||||
| 结果页自动保存 | `generatedCustomWorldProfile` 变化时自动 `upsertCustomWorldProfile()` | 让“草稿保存”“作品库存档”“正式发布”语义混在一起 |
|
|
||||||
|
|
||||||
## 7.2 冗余或已退出当前版本主链的组件
|
|
||||||
|
|
||||||
`src/components/custom-world-agent/CustomWorldAgentWorkspace.tsx` 当前只真正接了:
|
|
||||||
|
|
||||||
1. `CustomWorldAgentHeader`
|
|
||||||
2. `EightAnchorProgressBar`
|
|
||||||
3. `CustomWorldAgentOperationBanner`
|
|
||||||
4. `CustomWorldAgentThread`
|
|
||||||
5. `CustomWorldAgentComposer`
|
|
||||||
|
|
||||||
在 `2026-04-21` 清理前,同目录下还存在一组未接线旧组件:
|
|
||||||
|
|
||||||
1. `CustomWorldAgentLockBar.tsx`
|
|
||||||
2. `CustomWorldAgentDraftDrawer.tsx`
|
|
||||||
3. `CustomWorldAgentDraftDetailPanel.tsx`
|
|
||||||
4. `CustomWorldAgentQuickActions.tsx`
|
|
||||||
5. `CustomWorldAgentSummaryPanel.tsx`
|
|
||||||
6. `CustomWorldAgentIntentSummaryPanel.tsx`
|
|
||||||
7. `CustomWorldAgentClarificationPanel.tsx`
|
|
||||||
8. `CustomWorldGenerateEntityModal.tsx`
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
1. `CustomWorldAgentDraftDrawer.tsx` 已在批次 A 清理中删除
|
|
||||||
2. `CustomWorldAgentLockBar.tsx`
|
|
||||||
3. `CustomWorldAgentDraftDetailPanel.tsx`
|
|
||||||
4. `CustomWorldAgentQuickActions.tsx`
|
|
||||||
5. `CustomWorldAgentSummaryPanel.tsx`
|
|
||||||
6. `CustomWorldAgentIntentSummaryPanel.tsx`
|
|
||||||
7. `CustomWorldAgentClarificationPanel.tsx`
|
|
||||||
8. `CustomWorldGenerateEntityModal.tsx`
|
|
||||||
|
|
||||||
已在批次 D 清理中判定为退出当前版本主链,并完成物理删除。
|
|
||||||
|
|
||||||
因此这里的审计结论需要更新为:
|
|
||||||
|
|
||||||
1. 它们不再属于“待接线组件”
|
|
||||||
2. 它们属于已确认退场的旧副面板链
|
|
||||||
3. 当前版本如果还要补 `suggestedActions / pendingClarifications / draftCards` 的消费面,应基于新的主链设计重新定义,而不是默认把旧面板接回来
|
|
||||||
|
|
||||||
另外,`src/components/custom-world-home/CustomWorldCreationHub.tsx` 也已存在,但平台 `create` tab 还没有把它接成主入口。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 当前没有真正实装到游戏主流程中的项
|
|
||||||
|
|
||||||
| 能力 | 设计 / 定义位置 | 当前状态 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `publish_world` 真正发布链 | contract、PRD、route、orchestrator | route 能接,orchestrator 直接 `throw badRequest('publish_world is not available in phase5')` |
|
|
||||||
| `generate_scene_assets` | contract、PRD | contract 定义了,但 action route 未接,主链无执行实现 |
|
|
||||||
| `sync_scene_assets` | contract、PRD | contract 定义了,但 action route 未接,主链无执行实现 |
|
|
||||||
| `expand_long_tail` | contract、PRD | contract 定义了,但主 action 链未接 |
|
|
||||||
| `lock_cards / unlock_cards` | contract、PRD | contract 定义了,但 route / UI / orchestrator 主链未接 |
|
|
||||||
| `regenerate_scope` | contract、PRD | contract 定义了,但 route / UI / orchestrator 主链未接 |
|
|
||||||
| `qualityFindings` 与 blocker 发布门禁 | contract、PRD、技术进度文档 | 字段存在,但没有真实的生成、展示、阻止发布闭环 |
|
|
||||||
| 场景资产工坊从 Agent workspace 打开并写回 | PRD | 主工作区未接详情面板与场景资产 action |
|
|
||||||
| 通过 works 统一恢复 Agent draft / 已发布作品 | works service + creation hub | 后端已有聚合,主平台入口未收口 |
|
|
||||||
| 发布前只允许预览、发布后再进入世界 | PRD | 当前 Agent 草稿结果页可自动保存并直接进入世界 |
|
|
||||||
|
|
||||||
补充说明:
|
|
||||||
|
|
||||||
`docs/technical/SCENE_MULTI_ACT_CREATOR_IMPLEMENTATION_PROGRESS_2026-04-20.md` 已明确写到,发布期 `qualityFindings / blocker` 正式接入仍未完成,这与当前代码状态一致。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 优先级建议
|
|
||||||
|
|
||||||
## P0:先收一条真正的单一主链
|
|
||||||
|
|
||||||
建议明确把下面这条定为唯一正式主链:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Agent session
|
|
||||||
-> 服务端 draft snapshot
|
|
||||||
-> 服务端质量检查 / 发布动作
|
|
||||||
-> 服务端编译 final CustomWorldProfile
|
|
||||||
-> 世界库
|
|
||||||
-> runtime
|
|
||||||
```
|
|
||||||
|
|
||||||
对应动作:
|
|
||||||
|
|
||||||
1. 结果页不再承担“主编辑器”职责,至少对 Agent draft 结果页关闭 legacy profile 直改能力。
|
|
||||||
2. 用服务端 preview / compile 接口替代前端 `buildCustomWorldProfileFromAgentDraft()` 的最终裁决职责。
|
|
||||||
3. `publish_world` 打通后,再决定是否允许“发布后立即进入世界”。
|
|
||||||
|
|
||||||
## P0:把“进入世界”和“发布世界”重新绑回同一门槛
|
|
||||||
|
|
||||||
建议收口为:
|
|
||||||
|
|
||||||
1. 未发布 Agent 草稿只能继续创作或查看预览。
|
|
||||||
2. 只有 `publish_world` 成功后,才产出正式 `CustomWorldProfile` 并允许主入口进入世界。
|
|
||||||
3. `qualityFindings / blocker` 必须在 foundation draft 完成、资产写回后、publish 前持续重跑。
|
|
||||||
|
|
||||||
## P1:继续做旧 world session 链的文档收口
|
|
||||||
|
|
||||||
`2026-04-21` 更新:
|
|
||||||
|
|
||||||
旧 `custom-world/sessions` 链已经完成物理删除。
|
|
||||||
|
|
||||||
因此这里不再是“保留还是淘汰”的开放问题,而是:
|
|
||||||
|
|
||||||
1. 继续清理由这条旧链残留在审计、PRD、知识图谱中的过时口径
|
|
||||||
2. 把当前正式主链与仍保留的兼容层边界写清楚
|
|
||||||
|
|
||||||
## P1:把 works 创作中心接回主平台
|
|
||||||
|
|
||||||
建议:
|
|
||||||
|
|
||||||
1. 平台 `create` tab 改成消费 `listCustomWorldWorks()`。
|
|
||||||
2. 草稿 session 通过“继续创作”恢复。
|
|
||||||
3. 已发布 profile 通过“进入世界”或“查看详情”进入。
|
|
||||||
4. `myEntries` 退回为作品库子集,而不是 create tab 的唯一数据源。
|
|
||||||
|
|
||||||
## P1:为悬空 session 字段重新定义最小闭环
|
|
||||||
|
|
||||||
`2026-04-21` 更新:
|
|
||||||
|
|
||||||
原文这里建议把旧 `QuickActions / DraftDrawer / DraftDetailPanel / ClarificationPanel` 接回主工作区。
|
|
||||||
|
|
||||||
但这些旧副面板已经在当前版本收口判断中被明确认定为:
|
|
||||||
|
|
||||||
1. 不属于现行主链
|
|
||||||
2. 不再作为当前版本默认待落地项
|
|
||||||
3. 已完成物理删除
|
|
||||||
|
|
||||||
因此当前更准确的建议应该是:
|
|
||||||
|
|
||||||
1. 如果 `suggestedActions / pendingClarifications / draftCards` 仍要进入正式主流程,需要先重新定义符合当前极简工作区的消费方式
|
|
||||||
2. 不应再以“把旧副面板接回来”作为默认方案
|
|
||||||
3. 在没有新主链设计前,这些字段继续标记为“主链悬空”
|
|
||||||
|
|
||||||
## P2:等主链收口后再清桥接字段
|
|
||||||
|
|
||||||
下面这些字段不建议现在立刻删,但应在主链收口后尽快移除:
|
|
||||||
|
|
||||||
1. `draftProfile.legacyResultProfile`
|
|
||||||
2. 前端桥接里固定写死的 `generationMode / generationStatus`
|
|
||||||
3. 仅为兼容旧编辑器而塞进 legacy profile 的创作态元数据
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 一句话总评
|
|
||||||
|
|
||||||
当前“Agent 聊天 -> 草稿生成 -> 进入世界”已经能跑通一条可玩链,但它还不是 PRD 要求的“后端单一真相源 + 发布门禁收口”的正式链路,而是 `Agent session`、`legacy profile`、`旧 session`、`作品库` 四层并存、靠前端桥接和结果页兼容能力临时拼起来的过渡态。
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
# 角色资产 Prompt 链路审计(2026-04-20)
|
|
||||||
|
|
||||||
更新时间:`2026-04-20`
|
|
||||||
|
|
||||||
## 0. 本次审计回答什么问题
|
|
||||||
|
|
||||||
本次只回答角色资产相关的 4 个问题:
|
|
||||||
|
|
||||||
1. `characterAssetPrompts.ts` 里的 `visualPromptText` 和 `animationPromptText`,是不是“生成角色形象 / 动作形象的默认描述”。
|
|
||||||
2. 生成角色形象的系统提示词在哪个文件,生成默认角色形象描述文本的提示词在哪个文件。
|
|
||||||
3. 生成角色动作的系统提示词在哪个文件,生成默认角色动作描述文本的提示词在哪个文件。
|
|
||||||
4. 当前链路里是否存在冗余流程、保留接口或无效代码。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 先说结论
|
|
||||||
|
|
||||||
结论不是“只有一套 prompt”,而是:
|
|
||||||
|
|
||||||
**当前角色资产链路仍然有两层 prompt,但“默认描述文本”已经统一成单一主源。**
|
|
||||||
|
|
||||||
### 1.1 默认描述文本层
|
|
||||||
|
|
||||||
这层的目标是:
|
|
||||||
|
|
||||||
**先给资产工坊里的输入框一个默认可编辑文本。**
|
|
||||||
|
|
||||||
这层不直接拿去生成图片或动作视频。
|
|
||||||
|
|
||||||
当前实际主链来源:
|
|
||||||
|
|
||||||
- `src/prompts/customWorldRolePromptDefaults.ts`
|
|
||||||
|
|
||||||
它会把角色已有字段映射成:
|
|
||||||
|
|
||||||
- `visualPromptText`
|
|
||||||
- `animationPromptText`
|
|
||||||
- `scenePromptText`
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
- `visualPromptText` 优先取 `visualDescription`
|
|
||||||
- `animationPromptText` 优先取 `actionDescription`
|
|
||||||
- `scenePromptText` 优先取 `sceneVisualDescription`
|
|
||||||
|
|
||||||
这层是**默认描述文本**,不是正式图像模型 prompt。
|
|
||||||
|
|
||||||
### 1.2 正式模型 prompt 层
|
|
||||||
|
|
||||||
这层的目标是:
|
|
||||||
|
|
||||||
**把“默认描述文本”进一步编译成正式给图像模型 / 动作模型的完整 prompt。**
|
|
||||||
|
|
||||||
当前主链来源:
|
|
||||||
|
|
||||||
- `server-node/src/prompts/characterAssetPrompts.ts`
|
|
||||||
- `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
1. 前端先有一段短文本
|
|
||||||
2. 后端再用正式 prompt builder 把它扩成模型真正使用的完整 prompt
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 角色形象生成链路
|
|
||||||
|
|
||||||
## 2.1 生成角色形象的系统提示词在哪
|
|
||||||
|
|
||||||
如果这里问的是“正式生成角色主图时,真正控制模型输出方向的 prompt 主源在哪”,答案是:
|
|
||||||
|
|
||||||
- `server-node/src/prompts/characterAssetPrompts.ts`
|
|
||||||
- `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
|
|
||||||
更准确说:
|
|
||||||
|
|
||||||
1. `buildNpcVisualPrompt`
|
|
||||||
- 文件:`server-node/src/prompts/characterAssetPrompts.ts`
|
|
||||||
- 作用:把短描述文本和角色摘要合并
|
|
||||||
2. `buildMasterPrompt`
|
|
||||||
- 文件:`packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
- 作用:提供正式的角色主图 prompt 骨架
|
|
||||||
|
|
||||||
最终角色形象正式生成请求使用的是:
|
|
||||||
|
|
||||||
- `buildNpcVisualPrompt(...)`
|
|
||||||
|
|
||||||
调用位置:
|
|
||||||
|
|
||||||
- `server-node/src/modules/assets/characterAssetRoutes.ts`
|
|
||||||
|
|
||||||
即:
|
|
||||||
|
|
||||||
**角色主图正式生成的系统提示词主链,不在前端默认值文件,而在后端 `characterAssetPrompts.ts` + 共享 `qwenSprite.ts`。**
|
|
||||||
|
|
||||||
## 2.2 生成默认角色形象描述文本的提示词在哪
|
|
||||||
|
|
||||||
当前资产工坊默认输入框实际使用:
|
|
||||||
|
|
||||||
- `src/prompts/customWorldRolePromptDefaults.ts`
|
|
||||||
|
|
||||||
这不是 LLM system prompt,而是本地字段映射规则。
|
|
||||||
|
|
||||||
换句话说,当前页面上的默认“形象描述”主要来自:
|
|
||||||
|
|
||||||
- `role.visualDescription`
|
|
||||||
- 或回退到 `role.description`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 角色动作生成链路
|
|
||||||
|
|
||||||
## 3.1 生成角色动作的系统提示词在哪
|
|
||||||
|
|
||||||
当前正式动作生成主链在:
|
|
||||||
|
|
||||||
- `server-node/src/prompts/characterAssetPrompts.ts`
|
|
||||||
- `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
|
|
||||||
其中分两类:
|
|
||||||
|
|
||||||
1. `buildArkCharacterAnimationPrompt`
|
|
||||||
- 当前图生视频动作链路主入口
|
|
||||||
2. `buildNpcAnimationPrompt`
|
|
||||||
- 通用动作视频 prompt builder
|
|
||||||
3. `buildImageSequencePrompt`
|
|
||||||
- 连续帧方案动作 prompt builder
|
|
||||||
4. `buildVideoActionPrompt`
|
|
||||||
- 共享动作模板骨架,在 `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
|
|
||||||
当前主动作链路更偏向:
|
|
||||||
|
|
||||||
- `buildArkCharacterAnimationPrompt`
|
|
||||||
|
|
||||||
调用位置:
|
|
||||||
|
|
||||||
- `server-node/src/modules/assets/characterAssetRoutes.ts`
|
|
||||||
|
|
||||||
## 3.2 生成默认角色动作描述文本的提示词在哪
|
|
||||||
|
|
||||||
当前资产工坊真实默认“动作描述”来源:
|
|
||||||
|
|
||||||
- `src/prompts/customWorldRolePromptDefaults.ts`
|
|
||||||
|
|
||||||
规则是:
|
|
||||||
|
|
||||||
- 优先 `actionDescription`
|
|
||||||
- 回退 `combatStyle`
|
|
||||||
|
|
||||||
这仍然是**默认描述文本层**,不是最终动作模型 prompt。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. `characterAssetPrompts.ts` 里的 `visualPromptText` / `animationPromptText` 到底是什么
|
|
||||||
|
|
||||||
这两个字段容易混淆,因为它们名字里带 `Prompt`。
|
|
||||||
|
|
||||||
但当前工程里它们更准确的定位是:
|
|
||||||
|
|
||||||
**“默认描述文本 bundle 字段名”,不是最终图像模型请求体里的最终 prompt 名称。**
|
|
||||||
|
|
||||||
也就是:
|
|
||||||
|
|
||||||
- `visualPromptText`
|
|
||||||
- 在 UI 里更像“角色形象描述默认文本”
|
|
||||||
- 之后会再被编译进正式图像 prompt
|
|
||||||
- `animationPromptText`
|
|
||||||
- 在 UI 里更像“角色动作描述默认文本”
|
|
||||||
- 之后会再被编译进正式动作 prompt
|
|
||||||
|
|
||||||
所以对你的问题可以直接回答为:
|
|
||||||
|
|
||||||
**是,它们在当前语义上确实可以看作“默认角色形象 / 动作描述文本”。**
|
|
||||||
|
|
||||||
但需要补一句:
|
|
||||||
|
|
||||||
**它们不是最终一步的正式模型系统提示词,而是正式模型 prompt 的上游输入。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 当前真实调用链
|
|
||||||
|
|
||||||
## 5.1 当前资产工坊页面初始默认值主链
|
|
||||||
|
|
||||||
当前真实主链:
|
|
||||||
|
|
||||||
1. 角色对象已有字段进入前端
|
|
||||||
2. `src/prompts/customWorldRolePromptDefaults.ts`
|
|
||||||
3. `CustomWorldRoleAssetStudioModal.tsx`
|
|
||||||
4. 输入框初始值:
|
|
||||||
- `visualPromptText`
|
|
||||||
- `animationPromptText`
|
|
||||||
|
|
||||||
这条链:
|
|
||||||
|
|
||||||
- 快
|
|
||||||
- 本地可控
|
|
||||||
- 不依赖额外一次 LLM 调用
|
|
||||||
|
|
||||||
## 5.2 当前正式角色主图生成主链
|
|
||||||
|
|
||||||
1. 前端把输入框里的 `visualPromptText` 提交到后端
|
|
||||||
2. `server-node/src/prompts/characterAssetPrompts.ts`
|
|
||||||
- `buildNpcVisualPrompt`
|
|
||||||
3. `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
- `buildMasterPrompt`
|
|
||||||
4. 图像模型正式生成
|
|
||||||
|
|
||||||
## 5.3 当前正式角色动作生成主链
|
|
||||||
|
|
||||||
1. 前端把输入框里的 `animationPromptText` 提交到后端
|
|
||||||
2. `server-node/src/prompts/characterAssetPrompts.ts`
|
|
||||||
- `buildArkCharacterAnimationPrompt`
|
|
||||||
- 或 `buildNpcAnimationPrompt`
|
|
||||||
- 或 `buildImageSequencePrompt`
|
|
||||||
3. `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
- `buildVideoActionPrompt`
|
|
||||||
4. 动作模型正式生成
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 冗余流程与当前问题
|
|
||||||
|
|
||||||
## 6.1 默认描述文本双链已收口
|
|
||||||
|
|
||||||
此前默认描述文本同时存在:
|
|
||||||
|
|
||||||
1. 前端本地字段映射
|
|
||||||
2. 后端 bundle 编译接口
|
|
||||||
|
|
||||||
本轮已经统一为:
|
|
||||||
|
|
||||||
- `src/prompts/customWorldRolePromptDefaults.ts`
|
|
||||||
|
|
||||||
也就是:
|
|
||||||
|
|
||||||
**默认描述文本现在只有一条真实主源。**
|
|
||||||
|
|
||||||
对应变化:
|
|
||||||
|
|
||||||
1. 不再保留后端独立的默认 bundle 编译接口。
|
|
||||||
2. 不再保留前端对应的 bundle 生成 API 壳层。
|
|
||||||
3. `server-node/src/prompts/characterAssetPrompts.ts` 只保留正式模型 prompt builder。
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**默认描述文本层的双份真相已经被消除。**
|
|
||||||
|
|
||||||
## 6.2 `scenePromptText` 结构存在,但当前资产工坊没有完整承接
|
|
||||||
|
|
||||||
当前这套链路里:
|
|
||||||
|
|
||||||
- `customWorldRolePromptDefaults.ts` 会返回 `scenePromptText`
|
|
||||||
- `characterAssetPrompts.ts` 也会返回 `scenePromptText`
|
|
||||||
|
|
||||||
但当前资产工坊 UI 里并没有完整对应输入框链路。
|
|
||||||
|
|
||||||
这说明:
|
|
||||||
|
|
||||||
**场景描述文本在结构层存在,但在当前角色资产工坊里没有形成完整的用户可编辑闭环。**
|
|
||||||
|
|
||||||
## 6.3 共享模板与工具模板存在相似实现,但职责不同
|
|
||||||
|
|
||||||
仓库里同时有:
|
|
||||||
|
|
||||||
- `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
- `src/prompts/qwenSpriteSheetToolPrompts.ts`
|
|
||||||
|
|
||||||
它们都提供类似的主图 / 动作模板能力。
|
|
||||||
|
|
||||||
但当前定位不同:
|
|
||||||
|
|
||||||
- `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
- 正式角色资产主链共享模板
|
|
||||||
- `src/prompts/qwenSpriteSheetToolPrompts.ts`
|
|
||||||
- Qwen 工具链 prompt
|
|
||||||
|
|
||||||
它们不是同一条业务主链里的重复实现,但确实容易让人误读为“双份正式模板”。
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**这是“职责上可解释,但认知上高混淆”的并行模板,不建议现在直接删,但需要文档明确边界。**
|
|
||||||
|
|
||||||
## 6.4 当前没有证据说明正式主图 / 动作 prompt builder 是无效代码
|
|
||||||
|
|
||||||
以下 builder 当前都有正式调用点:
|
|
||||||
|
|
||||||
- `buildNpcVisualPrompt`
|
|
||||||
- `buildNpcVisualNegativePrompt`
|
|
||||||
- `buildArkCharacterAnimationPrompt`
|
|
||||||
- `buildNpcAnimationPrompt`
|
|
||||||
- `buildImageSequencePrompt`
|
|
||||||
|
|
||||||
因此它们不能算“无效代码”。
|
|
||||||
|
|
||||||
真正已经被清理掉的保留链路,是此前未接入主 UI 的默认 bundle 接口:
|
|
||||||
|
|
||||||
- `CHARACTER_PROMPT_BUNDLE_SYSTEM_PROMPT`
|
|
||||||
- `buildCharacterPromptBundleUserPrompt`
|
|
||||||
- `/api/assets/character-prompts/generate`
|
|
||||||
|
|
||||||
这套链路已经不再保留在当前仓库主线中。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 本次建议
|
|
||||||
|
|
||||||
如果后续要继续收口,建议按顺序处理:
|
|
||||||
|
|
||||||
1. 继续以前端本地映射作为默认描述文本唯一主源。
|
|
||||||
2. 对 `scenePromptText` 做完整承接,不要继续停留在结构存在但 UI 不消费的状态。
|
|
||||||
3. 继续保留 `packages/shared/src/prompts/qwenSprite.ts` 与工具链 prompt 分层,但在文档里强制写清“正式主链 / 工具链”边界。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 本次审计覆盖文件
|
|
||||||
|
|
||||||
- `server-node/src/prompts/characterAssetPrompts.ts`
|
|
||||||
- `packages/shared/src/prompts/qwenSprite.ts`
|
|
||||||
- `server-node/src/modules/assets/characterAssetRoutes.ts`
|
|
||||||
- `src/prompts/customWorldRolePromptDefaults.ts`
|
|
||||||
- `src/components/CustomWorldRoleAssetStudioModal.tsx`
|
|
||||||
- `src/components/asset-studio/characterAssetWorkflowPersistence.ts`
|
|
||||||
- `src/prompts/qwenSpriteSheetToolPrompts.ts`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 一句话版结论
|
|
||||||
|
|
||||||
一句话总结就是:
|
|
||||||
|
|
||||||
**当前角色资产系统把“默认描述文本”和“正式模型 prompt”拆成了两层,这是合理的;默认描述文本层已经统一为前端本地映射单一主源,当前剩余主要问题不再是双主源,而是 `scenePromptText` 仍未形成完整 UI 闭环。**
|
|
||||||
@@ -1,444 +0,0 @@
|
|||||||
# 自定义世界创作工具问题审计与优化建议
|
|
||||||
|
|
||||||
更新时间:`2026-04-08`
|
|
||||||
|
|
||||||
## 0. 结论先说
|
|
||||||
|
|
||||||
当前自定义世界创作工具已经有了比较强的生成骨架、锚点结构和结果编辑能力,但整体仍处在一个很明显的“半收口状态”:
|
|
||||||
|
|
||||||
**设计目标已经走到“陶泥儿主工作台”,数据结构已经支持“锚点化输入”,但实际体验仍然更像“大文本生成器 + 大型结果总表编辑器”。**
|
|
||||||
|
|
||||||
如果用一句话概括当前问题,就是:
|
|
||||||
|
|
||||||
**高杠杆创作入口还不够强,低杠杆编辑负担还偏重,局部重生成与后端收口也还没有真正闭环。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 审计范围
|
|
||||||
|
|
||||||
本次审计主要对照了三类信息:
|
|
||||||
|
|
||||||
1. 目标设计与 PRD
|
|
||||||
- `docs/prd/AI_NATIVE_CUSTOM_WORLD_CREATION_FLOW_OPTIMIZATION_PRD_2026-04-06.md`
|
|
||||||
- `docs/design/CUSTOM_WORLD_CREATOR_INPUT_AND_AI_BOUNDARY_DESIGN_2026-04-06.md`
|
|
||||||
- `docs/design/CUSTOM_WORLD_SELF_OWNED_SETTING_LAYER_OPTIMIZATION_2026-04-08.md`
|
|
||||||
- `docs/design/CUSTOM_WORLD_TEMPLATE_DECOUPLING_AND_CROSS_GENRE_GENERALIZATION_DESIGN_2026-04-08.md`
|
|
||||||
|
|
||||||
2. 当前前端主流程与工作台
|
|
||||||
- `src/components/SelectionCustomizationModals.tsx`
|
|
||||||
- `src/components/game-shell/PreGameSelectionFlow.tsx`
|
|
||||||
- `src/components/CustomWorldGenerationView.tsx`
|
|
||||||
- `src/components/CustomWorldResultView.tsx`
|
|
||||||
- `src/components/CustomWorldEntityCatalog.tsx`
|
|
||||||
- `src/components/CustomWorldEntityEditorModal.tsx`
|
|
||||||
|
|
||||||
3. 当前生成链与后端会话层
|
|
||||||
- `src/services/ai.ts`
|
|
||||||
- `src/services/aiService.ts`
|
|
||||||
- `src/services/customWorldCreatorIntent.ts`
|
|
||||||
- `src/services/customWorld.ts`
|
|
||||||
- `server-node/src/services/customWorldSessionStore.ts`
|
|
||||||
- `server-node/src/services/customWorldGenerationService.ts`
|
|
||||||
- `server-node/src/bridges/legacyAiRuntimeBridge.ts`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 当前主要问题
|
|
||||||
|
|
||||||
## 2.1 输入层和意图结构已经脱节
|
|
||||||
|
|
||||||
当前数据结构已经支持:
|
|
||||||
|
|
||||||
- 世界一句话
|
|
||||||
- 主题关键词
|
|
||||||
- 气质约束
|
|
||||||
- 玩家身份
|
|
||||||
- 开局处境
|
|
||||||
- 核心冲突
|
|
||||||
- 关键势力
|
|
||||||
- 关键角色
|
|
||||||
- 关键地点
|
|
||||||
- 标志性要素
|
|
||||||
- 禁止事项
|
|
||||||
|
|
||||||
但实际入口 `src/components/SelectionCustomizationModals.tsx` 里,陶泥儿主弹窗仍然基本只有:
|
|
||||||
|
|
||||||
- 生成模式
|
|
||||||
- 一块大 textarea
|
|
||||||
|
|
||||||
这导致两个直接后果:
|
|
||||||
|
|
||||||
1. 设计里已经想清楚的“高杠杆锚点输入”,还没有真正变成主入口。
|
|
||||||
2. `CustomWorldCreatorIntent` 虽然已经能表达卡片化输入,但 UI 并没有把它转成真正可用的创作工作台。
|
|
||||||
|
|
||||||
目前 `card` 模式更多还停留在类型和测试层,没有成为真实用户路径。
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 把当前创建弹窗升级成“快速文本模式 / 创作卡片模式”双入口。
|
|
||||||
- 快速文本模式保留,但提交后应自动拆出建议锚点,而不是直接把整段文本原封不动送进生成链。
|
|
||||||
- 卡片模式先只做最关键的 `5~6` 张卡,不要一开始把所有高级字段都铺开。
|
|
||||||
- 允许空卡提交,但明确区分“已锁定锚点”和“允许 AI 自由补全”的内容。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.2 澄清机制已经存在,但没有真正服务陶泥儿主
|
|
||||||
|
|
||||||
`server-node/src/services/customWorldSessionStore.ts` 已经支持:
|
|
||||||
|
|
||||||
- 根据输入缺口生成澄清问题
|
|
||||||
- 世界核心不足时追问
|
|
||||||
- 玩家身份缺失时追问
|
|
||||||
- 开局处境缺失时追问
|
|
||||||
- 核心冲突缺失时追问
|
|
||||||
|
|
||||||
但 `src/services/aiService.ts` 当前做法是:
|
|
||||||
|
|
||||||
- 创建 session
|
|
||||||
- 读取问题
|
|
||||||
- 直接用 fallback 文案自动回答
|
|
||||||
- 然后继续生成
|
|
||||||
|
|
||||||
这意味着:
|
|
||||||
|
|
||||||
**系统表面上已经有“先澄清再生成”的能力,但实际体验里,陶泥儿主并没有真正参与这一步。**
|
|
||||||
|
|
||||||
结果就是:
|
|
||||||
|
|
||||||
- 低信息量输入并没有被真正补强
|
|
||||||
- 澄清问题变成了内部兜底,而不是创作协作
|
|
||||||
- 很容易继续生成出“完整但不够像用户想要的世界”
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 把 session question 真正接到前端,作为生成前的二次确认步骤。
|
|
||||||
- 每次只问 `1~3` 个最关键问题,不要把它做成问卷。
|
|
||||||
- 支持“一键使用系统建议”,但必须让陶泥儿主可见,而不是静默自动填充。
|
|
||||||
- 把回答结果回写到 `creatorIntent`,而不是只作为一次性会话答案。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.3 新建完成后的工作台闭环没有成立
|
|
||||||
|
|
||||||
设计文档里,自定义世界应该是:
|
|
||||||
|
|
||||||
`输入 -> 生成 -> 结果页确认/编辑 -> 保存并进入世界`
|
|
||||||
|
|
||||||
但当前 `src/components/game-shell/PreGameSelectionFlow.tsx` 里,新建世界生成成功后会:
|
|
||||||
|
|
||||||
- 直接保存到世界库
|
|
||||||
- 清空 `generatedCustomWorldProfile`
|
|
||||||
- 返回世界列表页
|
|
||||||
|
|
||||||
而不是进入 `custom-world-result` 结果工作台。
|
|
||||||
|
|
||||||
这会带来几个问题:
|
|
||||||
|
|
||||||
1. 新建后的第一时间确认感不够强。
|
|
||||||
2. 快速模式生成出的“关键对象预览”没有自然承接页。
|
|
||||||
3. 用户生成完后,如果想继续看结果、继续补全、继续编辑,还要再从世界列表里点一次“编辑”。
|
|
||||||
|
|
||||||
这条链路会让“创作中”与“已保存”之间的体验断开。
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 新建完成后默认进入结果工作台,而不是直接跳回世界列表。
|
|
||||||
- 保存动作留在结果页显式触发。
|
|
||||||
- 可以增加“自动保存草稿,但仍停留在结果页”的策略,兼顾安全感和连贯性。
|
|
||||||
- 世界列表更适合作为“已归档内容入口”,不适合作为新建完成页。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.4 结果页仍然偏“数据总表”,低杠杆编辑负担偏重
|
|
||||||
|
|
||||||
当前结果页已经有 `世界 / 锚点 / 可扮演角色 / 场景角色 / 场景` 五个页签,这是正确方向。
|
|
||||||
|
|
||||||
但问题在于,进入编辑器后暴露出来的字段仍然太“底层”了,例如:
|
|
||||||
|
|
||||||
- `backstoryReveal`
|
|
||||||
- 技能列表
|
|
||||||
- 初始物品
|
|
||||||
- 场景 NPC 分配
|
|
||||||
- 场景连接关系
|
|
||||||
|
|
||||||
这些字段对少数深度编辑场景有用,但不应该成为默认主编辑内容。
|
|
||||||
|
|
||||||
当前 `src/components/CustomWorldEntityEditorModal.tsx` 已经变成一个非常重的综合编辑器,里面同时承担:
|
|
||||||
|
|
||||||
- 角色完整档案编辑
|
|
||||||
- 形象编辑入口
|
|
||||||
- AI 资产生成入口
|
|
||||||
- 背景章节编辑
|
|
||||||
- 技能与初始物品编辑
|
|
||||||
- 场景内 NPC 分配
|
|
||||||
- 场景连接维护
|
|
||||||
|
|
||||||
这会带来三层问题:
|
|
||||||
|
|
||||||
1. 陶泥儿主负担过重
|
|
||||||
- 很多字段属于“系统编译层”,不属于“创作决策层”。
|
|
||||||
|
|
||||||
2. 移动端负担过重
|
|
||||||
- 大量长表单、长弹窗和多级编辑,对手机创作并不友好。
|
|
||||||
|
|
||||||
3. 工程复杂度过高
|
|
||||||
- 前端工作台承担了太多不同层级的编辑职责。
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 默认只暴露高杠杆编辑:
|
|
||||||
- 世界核心命题
|
|
||||||
- 主题与气质
|
|
||||||
- 玩家身份与开局
|
|
||||||
- 关键势力
|
|
||||||
- 关键角色
|
|
||||||
- 关键地点
|
|
||||||
- 标志性要素
|
|
||||||
- 把技能、初始物品、章节 reveal、连接网络等移到“高级模式”或“系统层编辑”。
|
|
||||||
- 结果页结构从“按对象字段堆表单”改成“按创作价值组织”。
|
|
||||||
- 移动端优先改成分段式面板或底部工作台,不要把长表单都塞进同一个大 modal。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.5 锁定与局部重生成机制还不完整
|
|
||||||
|
|
||||||
当前已经有两套看起来相关的能力:
|
|
||||||
|
|
||||||
1. `creatorIntent` 里的 `locked`
|
|
||||||
2. `lockState` 里的角色 / 地点 / 势力锁定字段
|
|
||||||
|
|
||||||
但实际重生成时,`src/components/game-shell/PreGameSelectionFlow.tsx` 里的合并逻辑只真正处理了:
|
|
||||||
|
|
||||||
- 已锁定角色
|
|
||||||
- 已锁定地点
|
|
||||||
|
|
||||||
而且还是按“名称匹配”保留,不是按稳定 id 或字段级锁定来处理。
|
|
||||||
|
|
||||||
这带来的问题很明显:
|
|
||||||
|
|
||||||
1. 角色或地点一旦重命名,锁定可能失效。
|
|
||||||
2. 势力、冲突、世界概述等高价值内容没有真正进入局部重生成保护范围。
|
|
||||||
3. 当前“锁定能力”更像一个早期过渡实现,还没有形成统一的重生成规则。
|
|
||||||
|
|
||||||
同时,结果页的“重新生成”提示文案仍然是“整世界覆盖式”的语义,这也会进一步削弱用户对重生成的信任感。
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 把锁定语义统一收口到后端,以 `lockState` 为唯一事实来源。
|
|
||||||
- 锁定粒度改成:
|
|
||||||
- 世界字段锁定
|
|
||||||
- 势力锁定
|
|
||||||
- 关键角色锁定
|
|
||||||
- 关键地点锁定
|
|
||||||
- 长尾内容可重生成
|
|
||||||
- 局部重生成至少拆成几类:
|
|
||||||
- 仅补长尾角色
|
|
||||||
- 仅补长尾场景
|
|
||||||
- 仅重做场景网络
|
|
||||||
- 仅重做支持性 NPC
|
|
||||||
- 合并逻辑不要再靠名称匹配,改成稳定 id 或锚点映射。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.6 快速模式还不够“快”,生成页也还不够“陶泥儿主视角”
|
|
||||||
|
|
||||||
当前快速模式的主要区别,是把数量降成:
|
|
||||||
|
|
||||||
- 可扮演角色 `3`
|
|
||||||
- 场景角色 `8`
|
|
||||||
- 场景 `4`
|
|
||||||
|
|
||||||
但主生成链本身仍然会继续跑:
|
|
||||||
|
|
||||||
- framework
|
|
||||||
- theme pack
|
|
||||||
- story graph
|
|
||||||
- role narrative
|
|
||||||
- role dossier
|
|
||||||
- narrative profile
|
|
||||||
- finalization
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
**现在的 fast 更像“缩数量版 full”,还不是“先出关键锚点与关键对象的创作预览模式”。**
|
|
||||||
|
|
||||||
同时,`src/components/CustomWorldGenerationView.tsx` 当前展示的重点仍然是:
|
|
||||||
|
|
||||||
- 当前批次
|
|
||||||
- 预计等待
|
|
||||||
- 计时
|
|
||||||
- 模型阶段
|
|
||||||
|
|
||||||
而不是陶泥儿主真正关心的:
|
|
||||||
|
|
||||||
- 关键角色有没有成型
|
|
||||||
- 核心冲突有没有稳定
|
|
||||||
- 哪些锚点已经锁定
|
|
||||||
- 当前正在补的是关键对象还是长尾内容
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 快速模式改成真正的“关键锚点预览模式”:
|
|
||||||
- 先只生成关键角色、关键地点、核心冲突摘要
|
|
||||||
- 暂不补全所有长尾档案
|
|
||||||
- 生成页改成“陶泥儿主视角进度”:
|
|
||||||
- 世界灵魂已确定
|
|
||||||
- 关键角色已成型
|
|
||||||
- 关键地点已落地
|
|
||||||
- 长尾扩展准备开始
|
|
||||||
- 把技术批次隐藏到二级信息里,默认只展示创作状态。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.7 自定义世界底层仍然没有完全脱离模板世界依赖
|
|
||||||
|
|
||||||
这部分设计文档和参考清单已经说得比较清楚,代码里也能看到对应痕迹:
|
|
||||||
|
|
||||||
- `templateWorldType`
|
|
||||||
- `WUXIA / XIANXIA` 兼容字段
|
|
||||||
- 规则层 fallback
|
|
||||||
- 视觉参考池 fallback
|
|
||||||
- 角色骨架与怪物池 fallback
|
|
||||||
|
|
||||||
当前问题不在于“还没完全去模板化”本身,而在于:
|
|
||||||
|
|
||||||
**这层依赖仍然深入到了生成、运行时规则、表现词汇和参考资源,不只是一个兼容字段。**
|
|
||||||
|
|
||||||
这会限制:
|
|
||||||
|
|
||||||
1. 跨题材表达稳定性
|
|
||||||
2. 自定义世界的自有设定层独立性
|
|
||||||
3. 后续真正做到“任何题材都能稳定跑”
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 继续按现有设计稿,把模板依赖逐步迁成:
|
|
||||||
- 语义锚层
|
|
||||||
- 规则层
|
|
||||||
- 表现层
|
|
||||||
- 原型参考层
|
|
||||||
- 兼容迁移层
|
|
||||||
- 在迁移完成前,保留兼容字段,但让新逻辑优先读取 `ownedSettingLayers`。
|
|
||||||
- 明确哪些是“兼容桥”,哪些还是“真实主依赖”,避免继续混用。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.8 前后端边界仍处于过渡态,和项目约束还有距离
|
|
||||||
|
|
||||||
当前自定义世界已经有了:
|
|
||||||
|
|
||||||
- Node 路由
|
|
||||||
- session
|
|
||||||
- 流式生成
|
|
||||||
- 世界库存储接口
|
|
||||||
|
|
||||||
这是对的。
|
|
||||||
|
|
||||||
但问题是,`server-node/src/services/customWorldGenerationService.ts` 仍然通过 `server-node/src/bridges/legacyAiRuntimeBridge.ts` 去桥接 `src/services/ai.ts`。
|
|
||||||
|
|
||||||
这说明:
|
|
||||||
|
|
||||||
**核心生成逻辑虽然已经被路由包起来了,但真正的生成实现还没有完全成为 Express 侧自己的领域服务。**
|
|
||||||
|
|
||||||
同时,前端仍然承担了不少流程语义:
|
|
||||||
|
|
||||||
- 锁定内容合并
|
|
||||||
- 重生成确认
|
|
||||||
- 结果页覆盖提示
|
|
||||||
- 工作台状态切换
|
|
||||||
|
|
||||||
这和当前仓库“前端只负责表现,逻辑与数据尽量收口到 Express 后端”的方向还有距离。
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 把自定义世界生成链正式下沉到 `server-node` 领域服务。
|
|
||||||
- 把锁定、局部重生成、澄清会话、结果归档规则都放到后端。
|
|
||||||
- 前端只负责:
|
|
||||||
- 输入展示
|
|
||||||
- 进度展示
|
|
||||||
- 结果工作台展示
|
|
||||||
- 明确的用户确认动作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.9 移动端工作台仍有明显压迫感
|
|
||||||
|
|
||||||
项目文档已经明确要求移动端优先,但当前自定义世界工作台里仍有几个典型问题:
|
|
||||||
|
|
||||||
- 大量长 modal
|
|
||||||
- 多段长表单
|
|
||||||
- `window.confirm / window.alert` 原生弹框较多
|
|
||||||
- 结果页与编辑器中同时承载太多操作密度
|
|
||||||
|
|
||||||
这些在桌面端还能勉强接受,但在手机上会很容易变成:
|
|
||||||
|
|
||||||
- 滚动层级混乱
|
|
||||||
- 退出成本高
|
|
||||||
- 修改焦点不明确
|
|
||||||
- 确认感不稳定
|
|
||||||
|
|
||||||
可优化点:
|
|
||||||
|
|
||||||
- 移动端改成底部工作台 / 分步面板 / 可折叠分区。
|
|
||||||
- 用项目内统一确认弹层替换 `window.confirm / window.alert`。
|
|
||||||
- 让“保存”“继续补全”“局部重生成”这些关键动作固定在底部安全区附近。
|
|
||||||
- 把搜索、批量删除、创建动作做成更轻的操作条,而不是持续挤压正文区。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 优先级建议
|
|
||||||
|
|
||||||
### P0:先修主链路闭环
|
|
||||||
|
|
||||||
- 补卡片化输入入口,至少把关键锚点输入真正开放出来。
|
|
||||||
- 把澄清问题正式接入陶泥儿主流程,不再静默自动兜底。
|
|
||||||
- 修正“新建完成后直接回世界列表”的流程,生成后默认进入结果工作台。
|
|
||||||
- 统一锁定与局部重生成规则,先让“陶泥儿主不怕重生成”成立。
|
|
||||||
|
|
||||||
### P1:再降低工作台负担
|
|
||||||
|
|
||||||
- 结果页默认只展示高杠杆编辑。
|
|
||||||
- 低杠杆字段进入高级模式。
|
|
||||||
- 快速模式改成真正的关键对象预览模式。
|
|
||||||
- 生成页改成陶泥儿主视角进度,而不是模型批次视角。
|
|
||||||
|
|
||||||
### P2:最后做架构收口与去模板化
|
|
||||||
|
|
||||||
- 把生成链、锁定规则、会话澄清彻底收回 Express 后端。
|
|
||||||
- 持续推进 `ownedSettingLayers` 成为真实主设定层。
|
|
||||||
- 逐步去掉自定义世界对模板世界的深依赖。
|
|
||||||
- 针对移动端重做工作台的操作密度和确认路径。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 推荐落地顺序
|
|
||||||
|
|
||||||
如果只按“最小投入、最大体验收益”排序,建议按下面四步做:
|
|
||||||
|
|
||||||
1. 先改输入与结果闭环
|
|
||||||
- 卡片化最小入口
|
|
||||||
- 澄清问题接入
|
|
||||||
- 新建后进入结果页
|
|
||||||
|
|
||||||
2. 再改锁定与局部重生成
|
|
||||||
- 用稳定 id
|
|
||||||
- 用统一 lockState
|
|
||||||
- 增加局部重生成类型
|
|
||||||
|
|
||||||
3. 再改结果工作台结构
|
|
||||||
- 默认高杠杆
|
|
||||||
- 高级模式收纳低杠杆字段
|
|
||||||
- 移动端拆分长表单
|
|
||||||
|
|
||||||
4. 最后做后端收口与去模板化
|
|
||||||
- 服务端领域化
|
|
||||||
- 设定层自有化
|
|
||||||
- 跨题材泛化
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 一句话判断
|
|
||||||
|
|
||||||
当前自定义世界创作工具最需要的,不是再继续补更多字段或更多生成步骤,而是:
|
|
||||||
|
|
||||||
**把“陶泥儿主先决定灵魂锚点,系统再稳定展开世界”这条主逻辑真正落到 UI、流程和后端边界上。**
|
|
||||||
@@ -1,621 +0,0 @@
|
|||||||
# 世界 Profile 到预设内容与实时生成规则映射审计
|
|
||||||
|
|
||||||
更新时间:`2026-04-18`
|
|
||||||
|
|
||||||
## 0. 审计目标
|
|
||||||
|
|
||||||
本次审计只回答一个问题:
|
|
||||||
|
|
||||||
**当前仓库里的世界 profile 设定,是否已经完整、合理地映射到游戏的预设内容与实时生成内容规则中。**
|
|
||||||
|
|
||||||
这里的“世界 profile”包含两层:
|
|
||||||
|
|
||||||
1. `CustomWorldProfile` 顶层世界数据
|
|
||||||
2. `ownedSettingLayers` 派生设定层
|
|
||||||
|
|
||||||
这里的“预设内容”包含:
|
|
||||||
|
|
||||||
1. 角色运行时预设
|
|
||||||
2. 场景预设
|
|
||||||
3. 默认视觉与怪物匹配
|
|
||||||
4. 初始装备 / 初始背包 / 经济与术语表现
|
|
||||||
|
|
||||||
这里的“实时生成规则”包含:
|
|
||||||
|
|
||||||
1. 主剧情 prompt
|
|
||||||
2. NPC 对话 / 招募 / 私聊 prompt
|
|
||||||
3. 任务生成
|
|
||||||
4. 运行时物品生成
|
|
||||||
5. 故事线程、可见性、叙事 QA 与推进规则
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 结论先行
|
|
||||||
|
|
||||||
结论不是“完全映射”,而是:
|
|
||||||
|
|
||||||
**已完成基础映射,但没有达到“完全且合理”的程度。**
|
|
||||||
|
|
||||||
当前状态更准确地说是:
|
|
||||||
|
|
||||||
1. `世界基础骨架 -> 角色 / 场景 / 属性 / prompt` 这条主链已经打通。
|
|
||||||
2. `叙事层 -> 主剧情/NPC 可见性规则` 已经有比较扎实的接入。
|
|
||||||
3. `规则层 -> UI术语 / 经济 / 属性` 已经接入。
|
|
||||||
4. 但 `模板兼容层` 仍然过强,跨题材世界会被粗暴压回 `WUXIA/XIANXIA`。
|
|
||||||
5. 但 `后端运行时任务/物品模块` 只拿到了瘦身版 profile,没有真正吃到完整世界叙事层。
|
|
||||||
6. 但 `世界级 items / faction / conflict` 仍然更多是文本种子,而不是可操作的游戏内容对象。
|
|
||||||
|
|
||||||
如果按结果判断:
|
|
||||||
|
|
||||||
1. **预设内容映射:部分完整,约 70%。**
|
|
||||||
2. **实时生成规则映射:前端剧情主链较完整,后端运行时子链不完整,整体约 60%。**
|
|
||||||
3. **跨题材合理性:明显不足。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 本次审计覆盖的核心文件
|
|
||||||
|
|
||||||
类型与编译链:
|
|
||||||
|
|
||||||
- `src/types/customWorld.ts`
|
|
||||||
- `src/services/customWorld.ts`
|
|
||||||
- `src/services/customWorldBuilder.ts`
|
|
||||||
- `src/services/customWorldOwnedSettingLayers.ts`
|
|
||||||
- `src/services/customWorldTheme.ts`
|
|
||||||
|
|
||||||
预设内容落地:
|
|
||||||
|
|
||||||
- `src/data/characterPresets.ts`
|
|
||||||
- `src/data/scenePresets.ts`
|
|
||||||
- `src/data/customWorldCharacterLoadout.ts`
|
|
||||||
- `src/data/customWorldRuntime.ts`
|
|
||||||
- `src/data/customWorldVisuals.ts`
|
|
||||||
- `src/data/customWorldNpcMonsters.ts`
|
|
||||||
- `src/data/worldAttributeSchemas.ts`
|
|
||||||
- `src/data/economy.ts`
|
|
||||||
- `src/services/customWorldPresentation.ts`
|
|
||||||
|
|
||||||
实时生成规则:
|
|
||||||
|
|
||||||
- `src/hooks/story/storyContextBuilder.ts`
|
|
||||||
- `src/services/prompt.ts`
|
|
||||||
- `src/services/characterChatPrompt.ts`
|
|
||||||
- `src/services/questPrompt.ts`
|
|
||||||
- `src/services/questDirector.ts`
|
|
||||||
- `src/services/runtimeItemAiPrompt.ts`
|
|
||||||
- `src/data/runtimeItemNarrative.ts`
|
|
||||||
- `src/services/storyEngine/themePack.ts`
|
|
||||||
- `src/services/storyEngine/worldStoryGraph.ts`
|
|
||||||
- `src/services/storyEngine/actorNarrativeProfile.ts`
|
|
||||||
- `src/services/storyEngine/knowledgeGraph.ts`
|
|
||||||
- `src/services/storyEngine/threadContract.ts`
|
|
||||||
- `src/services/storyEngine/visibilityEngine.ts`
|
|
||||||
- `src/services/storyEngine/authorialConstraintPack.ts`
|
|
||||||
- `src/hooks/story/progressionActions.ts`
|
|
||||||
|
|
||||||
后端运行时链:
|
|
||||||
|
|
||||||
- `server-node/src/modules/ai/customWorldOrchestrator.ts`
|
|
||||||
- `server-node/src/modules/runtime-item/runtimeItemModule.ts`
|
|
||||||
- `server-node/src/modules/quest/runtimeQuestModule.ts`
|
|
||||||
- `server-node/src/modules/runtime/runtimeSnapshotHydration.ts`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 映射总表
|
|
||||||
|
|
||||||
| 设定层/字段 | 映射到预设内容 | 映射到实时生成规则 | 判断 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `name/subtitle/summary/tone/playerGoal` | 已映射到角色 opening、场景提示、视觉匹配、程序化物品关键词 | 已映射到主剧情 prompt、任务 prompt、物品 prompt、ThemePack/StoryGraph 派生 | 基本成立 |
|
|
||||||
| `templateWorldType/compatibilityTemplateWorldType` | 强影响角色模板、场景图参考池、怪物池、兼容 schema | 影响 ThemePack fallback 与部分运行时回退 | 已接入,但合理性不足 |
|
|
||||||
| `majorFactions/coreConflicts` | 主要进入 ThemePack / StoryGraph / tension state,未落成具体 faction 实体 | 影响 authorial constraints、线程图谱、任务与剧情语义 | 有映射,但偏文本种子 |
|
|
||||||
| `camp` | 已映射为开局 camp scene、camp 图、camp 连接 | 通过世界参考文本和开局内容进入 prompt | 成立 |
|
|
||||||
| `attributeSchema` | 已映射到角色/NPC 属性、战斗面板、属性展示 | 已映射到 prompt 属性描述与运行时计算 | 成立 |
|
|
||||||
| `ownedSettingLayers.ruleProfile.resourceLabels` | 已映射到 UI 血量/法力/货币等术语 | 主要通过 UI/经济层体现,prompt 侧间接使用 | 成立 |
|
|
||||||
| `ownedSettingLayers.ruleProfile.economyProfile.initialCurrency` | 已映射到初始货币与快照恢复 | 对运行时奖励规则影响弱,更多是初始化 | 成立但范围有限 |
|
|
||||||
| `playableNpcs` | 已映射到可玩角色预设、技能变体、初始物品、home scene | 已映射到剧情 prompt、私聊 prompt、叙事档案 | 成立 |
|
|
||||||
| `storyNpcs` | 已映射到场景 NPC、怪物判定、角色运行时预设 | 已映射到遭遇 prompt、任务发布者、叙事可见性 | 成立 |
|
|
||||||
| `landmarks` | 已映射到 scene presets、连接网络、场景视觉、treasure hints | 已映射到世界参考文本、scene residues、故事线程关联 | 成立 |
|
|
||||||
| `items` | 生成主链默认清空,世界级 item 几乎未形成正式内容层 | `knowledgeFacts` 可支持 item,但主链无内容可用 | 映射明显不足 |
|
|
||||||
| `themePack/expressionProfile` | 已映射到视觉/命名/技能名/场景语义 | 已映射到 prompt 基调、reveal 风格、故事图谱 | 成立 |
|
|
||||||
| `referenceProfile.roleArchetypes` | 已映射到角色模板骨架选择 | 运行时规则直接消费较少 | 部分成立 |
|
|
||||||
| `referenceProfile.sceneBuckets` | 已映射到场景默认图匹配 | 运行时 prompt 直接消费较少 | 部分成立 |
|
|
||||||
| `referenceProfile.creatureArchetypes` | 已映射到怪物 preset 池筛选 | 运行时规则间接消费 | 部分成立 |
|
|
||||||
| `storyGraph` | 预设层主要影响 narrative residues 与 faction tension | 已映射到 active threads、constraints、visibility、chapter/campaign | 成立 |
|
|
||||||
| `narrativeProfile` | 已映射到 scene NPC 简介和遭遇资料 | 已映射到 prompt 可见性、任务/物品关系生成 | 成立 |
|
|
||||||
| `knowledgeFacts` | 不直接生成预设内容 | 已映射到 visibility slice 与 prompt 裁剪 | 成立 |
|
|
||||||
| `threadContracts` | 不直接生成预设内容 | 已映射到 story signal / thread update / QA | 成立 |
|
|
||||||
| `creatorIntent/anchorPack/lockState/anchorContent` | 主要留在创作工作区与结果页整理 | 几乎不直接进入正式游戏运行时 | 创作层有用,运行时映射弱 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 已经成立的映射链
|
|
||||||
|
|
||||||
## 4.1 世界基础骨架已经能稳定进入角色、场景与剧情主链
|
|
||||||
|
|
||||||
`CustomWorldProfile` 的基础字段已经不是“只存档不消费”的状态。
|
|
||||||
|
|
||||||
它们已经实际进入:
|
|
||||||
|
|
||||||
1. 角色开局文案与 opening 动机
|
|
||||||
2. 角色技能变体
|
|
||||||
3. 场景预设名称、描述、连接、treasure hints
|
|
||||||
4. 主剧情 prompt 中的世界补充档案
|
|
||||||
5. 私聊 / 任务 / 运行时物品 prompt 的世界摘要
|
|
||||||
|
|
||||||
这说明:
|
|
||||||
|
|
||||||
**世界 profile 的基础文本层已经真正进入游戏主链。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.2 规则层已经落到真实游戏表现
|
|
||||||
|
|
||||||
`ownedSettingLayers.ruleProfile` 目前已真实影响:
|
|
||||||
|
|
||||||
1. `attributeSchema`
|
|
||||||
- 角色/NPC 属性计算
|
|
||||||
- prompt 中的属性描述
|
|
||||||
- 面板展示
|
|
||||||
2. `resourceLabels`
|
|
||||||
- HP/MP/伤害/冷却/货币等 UI 术语
|
|
||||||
3. `economyProfile.initialCurrency`
|
|
||||||
- 自定义世界初始货币
|
|
||||||
- 快照恢复时的默认初始化
|
|
||||||
|
|
||||||
这部分不是空壳。
|
|
||||||
|
|
||||||
**规则层已经从 profile 进入真实结算和 UI。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.3 叙事层已经进入 prompt 可见性与推进规则
|
|
||||||
|
|
||||||
`themePack -> storyGraph -> narrativeProfile -> knowledgeFacts -> visibilitySlice`
|
|
||||||
这条链已经是当前自定义世界最完整的一条映射链。
|
|
||||||
|
|
||||||
它已经支撑:
|
|
||||||
|
|
||||||
1. NPC 首遇/低披露 prompt 裁剪
|
|
||||||
2. 当前线程可见性控制
|
|
||||||
3. 当前压力、错位、禁区、已解锁章节等信息分层
|
|
||||||
4. 章节/战役/约束/QA 的继续推进
|
|
||||||
|
|
||||||
这说明:
|
|
||||||
|
|
||||||
**世界 profile 里的叙事层不只是展示文本,而是真的在控制“模型这轮能知道什么、不能知道什么”。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 关键问题
|
|
||||||
|
|
||||||
## 5.1 高优先级问题:模板兼容层仍然是二元锚点,跨题材世界会被错误压缩
|
|
||||||
|
|
||||||
当前主生成链仍要求模型输出:
|
|
||||||
|
|
||||||
- `templateWorldType: WUXIA | XIANXIA`
|
|
||||||
|
|
||||||
而兼容解析也会把世界最终压回:
|
|
||||||
|
|
||||||
1. `arcane -> XIANXIA`
|
|
||||||
2. 其它几乎全部回到 `WUXIA`
|
|
||||||
|
|
||||||
这会直接影响:
|
|
||||||
|
|
||||||
1. 角色模板骨架选择
|
|
||||||
2. 场景默认图参考池
|
|
||||||
3. 怪物 preset 池
|
|
||||||
4. 兼容性 fallback
|
|
||||||
|
|
||||||
问题不在“有兼容字段”,而在于:
|
|
||||||
|
|
||||||
**当前兼容字段仍然过度参与真实内容映射。**
|
|
||||||
|
|
||||||
对现代金融、科幻 AI 战争、校园、都市、调查等题材来说,这种二元压缩并不合理。
|
|
||||||
|
|
||||||
仓库日志里已经出现了典型样本:
|
|
||||||
|
|
||||||
1. 股市世界被要求产出 `XIANXIA`
|
|
||||||
2. AI 战争世界也被要求产出 `XIANXIA`
|
|
||||||
3. 魔法科技融合世界被要求产出 `WUXIA`
|
|
||||||
|
|
||||||
这意味着:
|
|
||||||
|
|
||||||
**世界 profile 虽然支持跨题材文本输入,但底层预设内容映射仍带着明显的“武侠/仙侠残余偏置”。**
|
|
||||||
|
|
||||||
相关文件:
|
|
||||||
|
|
||||||
- `server-node/src/modules/ai/customWorldOrchestrator.ts`
|
|
||||||
- `src/services/customWorldTheme.ts`
|
|
||||||
- `src/data/customWorldVisuals.ts`
|
|
||||||
- `src/data/customWorldNpcMonsters.ts`
|
|
||||||
- `src/data/characterPresets.ts`
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**这是当前“合理映射”最大缺口。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.2 高优先级问题:后端运行时任务/物品模块只消费了瘦身版 world profile
|
|
||||||
|
|
||||||
前端剧情主链里,`customWorldProfile` 会带着:
|
|
||||||
|
|
||||||
1. `themePack`
|
|
||||||
2. `storyGraph`
|
|
||||||
3. `knowledgeFacts`
|
|
||||||
4. `threadContracts`
|
|
||||||
5. `ownedSettingLayers`
|
|
||||||
|
|
||||||
但后端运行时模块里:
|
|
||||||
|
|
||||||
1. `runtimeItemModule` 的 `customWorldProfile` 只有 `{ name, summary }`
|
|
||||||
2. `runtimeQuestModule` 的 `customWorldProfile` 也只有 `{ name, summary }`
|
|
||||||
|
|
||||||
这直接导致后端运行时生成无法真正读取:
|
|
||||||
|
|
||||||
1. 世界线程图谱
|
|
||||||
2. 世界可见性事实
|
|
||||||
3. 参考原型层
|
|
||||||
4. 规则层
|
|
||||||
5. 表达层
|
|
||||||
|
|
||||||
结果是:
|
|
||||||
|
|
||||||
1. 主剧情/NPC prompt 已经较强依赖世界叙事层
|
|
||||||
2. 但后端任务/物品生成还只是吃世界摘要
|
|
||||||
|
|
||||||
这会把系统拆成两种强度不同的世界消费链:
|
|
||||||
|
|
||||||
1. 前端剧情链较“懂世界”
|
|
||||||
2. 后端运行时奖励链较“不懂世界”
|
|
||||||
|
|
||||||
相关文件:
|
|
||||||
|
|
||||||
- `src/hooks/story/storyContextBuilder.ts`
|
|
||||||
- `src/services/runtimeItemAiPrompt.ts`
|
|
||||||
- `src/services/questPrompt.ts`
|
|
||||||
- `server-node/src/modules/runtime-item/runtimeItemModule.ts`
|
|
||||||
- `server-node/src/modules/quest/runtimeQuestModule.ts`
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**世界 profile 到实时生成规则的映射,在后端链路上是不完整的。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.3 高优先级问题:世界级 `items` 没有真正接进主生成链
|
|
||||||
|
|
||||||
类型里存在:
|
|
||||||
|
|
||||||
- `CustomWorldProfile.items`
|
|
||||||
|
|
||||||
构建器也支持:
|
|
||||||
|
|
||||||
1. item 归一化
|
|
||||||
2. `attributeResonance`
|
|
||||||
3. item knowledge facts
|
|
||||||
|
|
||||||
但真正的世界生成主链里:
|
|
||||||
|
|
||||||
1. orchestrator prompt 明确要求不要预生成物品档案
|
|
||||||
2. `attachRuntimeGenerationMetadata(...)` 会把 `items` 直接压成空数组
|
|
||||||
|
|
||||||
这会带来两个结果:
|
|
||||||
|
|
||||||
1. 世界 profile 的“世界级物品层”几乎为空
|
|
||||||
2. 运行时背包、掉落、交易更多依赖程序化生成和角色初始物品
|
|
||||||
|
|
||||||
于是目前的物品系统更像:
|
|
||||||
|
|
||||||
1. 有角色初始物品
|
|
||||||
2. 有运行时程序化物品
|
|
||||||
3. 但没有稳定的“世界物品图谱”
|
|
||||||
|
|
||||||
这意味着:
|
|
||||||
|
|
||||||
**世界 profile 在物品层没有形成完整映射。**
|
|
||||||
|
|
||||||
相关文件:
|
|
||||||
|
|
||||||
- `server-node/src/modules/ai/customWorldOrchestrator.ts`
|
|
||||||
- `src/services/customWorldBuilder.ts`
|
|
||||||
- `src/data/customWorldRuntime.ts`
|
|
||||||
- `src/data/customWorldCharacterLoadout.ts`
|
|
||||||
- `src/services/storyEngine/knowledgeGraph.ts`
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**这是“预设内容”和“实时生成规则”共同缺失的一块。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.4 中优先级问题:`majorFactions/coreConflicts` 仍然是文本种子,不是可操作游戏对象
|
|
||||||
|
|
||||||
当前 `majorFactions` 与 `coreConflicts` 已经被大量消费,但主要消费方式是:
|
|
||||||
|
|
||||||
1. 拼进 `ThemePack`
|
|
||||||
2. 派生 `WorldStoryGraph`
|
|
||||||
3. 派生 `AuthorialConstraintPack`
|
|
||||||
4. 派生 `FactionTensionState`
|
|
||||||
|
|
||||||
问题在于它们还没有形成:
|
|
||||||
|
|
||||||
1. 可索引 faction 实体
|
|
||||||
2. faction 与 NPC 的显式归属关系
|
|
||||||
3. faction 与场景/商店/敌对阵营的显式绑定
|
|
||||||
4. 冲突与任务/势力状态的强约束关系
|
|
||||||
|
|
||||||
当前更多是:
|
|
||||||
|
|
||||||
**“文本里提到过 -> 图谱做字符串匹配 -> 运行时拿去写 prompt”**
|
|
||||||
|
|
||||||
而不是:
|
|
||||||
|
|
||||||
**“世界里真的存在这些派系与冲突对象,并驱动交互规则”**
|
|
||||||
|
|
||||||
相关文件:
|
|
||||||
|
|
||||||
- `src/services/storyEngine/themePack.ts`
|
|
||||||
- `src/services/storyEngine/worldStoryGraph.ts`
|
|
||||||
- `src/services/storyEngine/factionTensionState.ts`
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**已有映射,但离“完全落地”为具体游戏内容对象还有明显距离。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.5 中优先级问题:场景预设会额外注入模板怪物,弱化 landmark 的原始设定控制力
|
|
||||||
|
|
||||||
`buildCustomScenePresets(profile)` 在每个 landmark scene 中,除了把 `landmark.sceneNpcIds` 指向的角色放进去,还会:
|
|
||||||
|
|
||||||
1. 从怪物 preset 池按 scene index 截两只怪
|
|
||||||
2. 直接拼进 `combinedNpcs`
|
|
||||||
|
|
||||||
这意味着即使 profile 本身没有明确要求某个 landmark 出现这些敌对实体,运行时场景仍会被额外补入模板怪物。
|
|
||||||
|
|
||||||
这会导致:
|
|
||||||
|
|
||||||
1. 场景内容不完全由 `landmark + storyNpcs` 决定
|
|
||||||
2. 地标设定与实际可战斗内容之间存在偏移
|
|
||||||
3. 跨题材世界会更容易被模板怪物池拖偏
|
|
||||||
|
|
||||||
相关文件:
|
|
||||||
|
|
||||||
- `src/data/scenePresets.ts`
|
|
||||||
- `src/data/customWorldNpcMonsters.ts`
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**landmark 到实际场景实体池的映射,不是完全忠实映射,而是“设定 + 模板补丁”。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.6 中优先级问题:后端运行时物品线程并不是真正世界线程
|
|
||||||
|
|
||||||
前端剧情链里的 `activeThreadIds` 来自:
|
|
||||||
|
|
||||||
1. `storyEngineMemory`
|
|
||||||
2. `storyGraph`
|
|
||||||
3. `knowledgeFacts`
|
|
||||||
4. `visibilitySlice`
|
|
||||||
|
|
||||||
但后端 `runtimeItemModule` 的 loose context 里,`activeThreadIds` 只是:
|
|
||||||
|
|
||||||
1. `thread:${encounter.id}`
|
|
||||||
2. 或 `thread:${scene.id}`
|
|
||||||
|
|
||||||
这不是世界线程图谱,而是临时合成 id。
|
|
||||||
|
|
||||||
结果是:
|
|
||||||
|
|
||||||
1. 名义上后端物品模块也有“active threads”
|
|
||||||
2. 实际上它拿到的并不是 `WorldStoryGraph` 中的真实线程
|
|
||||||
|
|
||||||
这会让运行时物品的“为什么现在出现”更像局部上下文推断,而不是来自世界故事结构。
|
|
||||||
|
|
||||||
相关文件:
|
|
||||||
|
|
||||||
- `server-node/src/modules/runtime-item/runtimeItemModule.ts`
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**这是实时生成规则层的结构性弱映射。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.7 中优先级问题:`referenceProfile.roleArchetypes` 只从 playableNpcs 派生,storyNpcs 覆盖不够
|
|
||||||
|
|
||||||
当前 `roleArchetypes` 的编译来源是:
|
|
||||||
|
|
||||||
1. `profile.playableNpcs.slice(0, 6)`
|
|
||||||
|
|
||||||
而不是:
|
|
||||||
|
|
||||||
1. `playableNpcs + storyNpcs` 的综合原型池
|
|
||||||
|
|
||||||
这导致两个问题:
|
|
||||||
|
|
||||||
1. 世界里的长尾 story NPC 原型没有进入 reference archetype 编译
|
|
||||||
2. 某些场景角色/怪物/平民的模板骨架选择更多依赖启发式 fallback
|
|
||||||
|
|
||||||
这会让:
|
|
||||||
|
|
||||||
1. 可玩角色映射较稳定
|
|
||||||
2. 长尾场景角色映射不够稳定
|
|
||||||
|
|
||||||
相关文件:
|
|
||||||
|
|
||||||
- `src/services/customWorldOwnedSettingLayers.ts`
|
|
||||||
- `src/services/customWorldReferenceSignals.ts`
|
|
||||||
- `src/data/characterPresets.ts`
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
**参考层映射存在明显“主角优先、长尾不足”的偏差。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.8 低优先级问题:创作元数据并未进入正式游戏运行时
|
|
||||||
|
|
||||||
`creatorIntent / anchorPack / lockState / anchorContent` 当前主要服务于:
|
|
||||||
|
|
||||||
1. 创作工作区
|
|
||||||
2. Agent session
|
|
||||||
3. 结果页和编辑器
|
|
||||||
|
|
||||||
它们对正式运行时的直接作用主要是:
|
|
||||||
|
|
||||||
1. 参与 `ownedSettingLayers` 的编译
|
|
||||||
|
|
||||||
但不会直接变成:
|
|
||||||
|
|
||||||
1. 正式战斗规则
|
|
||||||
2. 场景交互规则
|
|
||||||
3. faction 状态
|
|
||||||
4. 任务目标约束
|
|
||||||
|
|
||||||
这不一定是 bug,但如果把“世界 profile 设定”理解为所有 profile 元数据,那么:
|
|
||||||
|
|
||||||
**创作层数据目前并没有完整进入游戏运行时。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 分层判断
|
|
||||||
|
|
||||||
## 6.1 预设内容映射判断
|
|
||||||
|
|
||||||
### 已经合理接入的部分
|
|
||||||
|
|
||||||
1. 可玩角色
|
|
||||||
2. 场景角色
|
|
||||||
3. 地标场景
|
|
||||||
4. 属性 schema
|
|
||||||
5. 资源术语
|
|
||||||
6. 初始货币
|
|
||||||
7. camp 开局归处
|
|
||||||
8. 默认场景图匹配
|
|
||||||
|
|
||||||
### 仍然不足的部分
|
|
||||||
|
|
||||||
1. 世界级 items
|
|
||||||
2. faction 实体化
|
|
||||||
3. 冲突到任务/场景状态的强绑定
|
|
||||||
4. 跨题材世界的模板偏置问题
|
|
||||||
5. 地标与怪物注入之间的忠实性
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
**预设内容层是“能跑且已有骨架”,但还不是“设定完全落地”。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6.2 实时生成规则映射判断
|
|
||||||
|
|
||||||
### 已经合理接入的部分
|
|
||||||
|
|
||||||
1. 主剧情 prompt
|
|
||||||
2. NPC 可见性控制
|
|
||||||
3. 私聊与对话 prompt
|
|
||||||
4. 叙事线程图谱
|
|
||||||
5. 事实图谱
|
|
||||||
6. 作者性约束与 QA
|
|
||||||
|
|
||||||
### 仍然不足的部分
|
|
||||||
|
|
||||||
1. 后端任务模块 world profile 过瘦
|
|
||||||
2. 后端物品模块 world profile 过瘦
|
|
||||||
3. 后端物品线程是伪线程
|
|
||||||
4. 世界级 item 图谱为空
|
|
||||||
5. faction/conflict 仍偏语义层,不够规则化
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
**实时生成规则层呈现出“前端剧情链强、后端奖励链弱”的不均衡状态。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 最终判定
|
|
||||||
|
|
||||||
如果问题是:
|
|
||||||
|
|
||||||
**“世界 profile 设定是否已经完全地、合理地映射到游戏的预设内容、实时生成内容规则中?”**
|
|
||||||
|
|
||||||
我的结论是:
|
|
||||||
|
|
||||||
**没有。**
|
|
||||||
|
|
||||||
更准确地说:
|
|
||||||
|
|
||||||
1. 已经完成了主干映射。
|
|
||||||
2. 但还没有完成全量映射。
|
|
||||||
3. 也还没有完成跨题材下的合理映射。
|
|
||||||
|
|
||||||
当前系统最准确的状态是:
|
|
||||||
|
|
||||||
**世界 profile 已经成为真实驱动源之一,但还没有成为所有预设内容与实时规则的一致单一真相源。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 建议的修复优先级
|
|
||||||
|
|
||||||
## P1:先补“真实消费不完整”的链路
|
|
||||||
|
|
||||||
1. 让后端 `runtimeItemModule` / `runtimeQuestModule` 接收完整 `customWorldProfile` 子集
|
|
||||||
2. 至少补进:
|
|
||||||
- `ownedSettingLayers`
|
|
||||||
- `storyGraph`
|
|
||||||
- `knowledgeFacts`
|
|
||||||
- `themePack`
|
|
||||||
- `majorFactions/coreConflicts`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P1:把 `templateWorldType` 退回兼容字段,而不是主导字段
|
|
||||||
|
|
||||||
1. 生成期保留兼容输出
|
|
||||||
2. 运行时优先读取:
|
|
||||||
- `ownedSettingLayers`
|
|
||||||
- `themeMode`
|
|
||||||
- `referenceProfile`
|
|
||||||
3. 不再让 `WUXIA/XIANXIA` 主导现代/科幻/海洋/裂界世界的视觉与怪物选择
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P1:补世界级 item 层
|
|
||||||
|
|
||||||
1. 允许世界生成阶段产出一批世界级 items seed
|
|
||||||
2. 让 `knowledgeFacts / runtime item / quest reward / treasure hint` 能挂到这些 seed 上
|
|
||||||
3. 形成“世界物件图谱”,而不是只有角色初始物品和程序化临时物品
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P2:把 faction/conflict 从文本种子升级成结构对象
|
|
||||||
|
|
||||||
1. faction 实体
|
|
||||||
2. faction -> NPC 归属
|
|
||||||
3. faction -> 场景控制
|
|
||||||
4. conflict -> 任务/线程/场景压力绑定
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P2:去掉地标场景里的固定模板怪物补丁式注入
|
|
||||||
|
|
||||||
1. 优先使用 landmark 自己的敌对角色设计
|
|
||||||
2. 模板怪物只作为缺口补位
|
|
||||||
3. 补位也要受 landmark/theme/thread 约束
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P3:扩充 reference archetype 的来源
|
|
||||||
|
|
||||||
1. role archetypes 不只从 playableNpcs 编
|
|
||||||
2. storyNpcs 也应参与 archetype 归纳
|
|
||||||
3. 为平民、敌对、怪物、势力成员建立更细 archetype
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 一句话总评
|
|
||||||
|
|
||||||
**当前世界 profile 已经能驱动游戏,但还没有彻底收束成“所有预设内容与实时生成规则都优先读取它”的单一真相源。主链可用,边链仍散,跨题材合理性仍偏弱。**
|
|
||||||
@@ -1,437 +0,0 @@
|
|||||||
# 当前 Function 设计审计(2026-04-03)
|
|
||||||
|
|
||||||
## 审计范围
|
|
||||||
|
|
||||||
本次审计重点阅读并对照了这些位置:
|
|
||||||
|
|
||||||
- `docs/experience/ADVENTURE_RUNTIME_DEV_EXPERIENCE.md`
|
|
||||||
- `docs/experience/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md`
|
|
||||||
- `docs/experience/PROJECT_DEVELOPMENT_EXPERIENCE.md`
|
|
||||||
- `docs/audits/engineering/README.md`
|
|
||||||
- `src/data/stateFunctions.ts`
|
|
||||||
- `src/data/npcInteractions.ts`
|
|
||||||
- `src/data/treasureInteractions.ts`
|
|
||||||
- `src/hooks/useStoryGeneration.ts`
|
|
||||||
- `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
- `src/hooks/story/npcInteraction.ts`
|
|
||||||
- `src/hooks/story/progressionActions.ts`
|
|
||||||
- `src/hooks/story/storyGenerationState.ts`
|
|
||||||
- `src/services/ai.ts`
|
|
||||||
- `src/services/prompt.ts`
|
|
||||||
- `src/components/AdventurePanel.tsx`
|
|
||||||
- `src/components/StateFunctionEditor.tsx`
|
|
||||||
|
|
||||||
## 先说结论
|
|
||||||
|
|
||||||
当前运行时的“function”不是单一体系,而是至少分成了 4 层:
|
|
||||||
|
|
||||||
1. `stateFunctions.ts` 里的基础战斗 / 空闲 function。
|
|
||||||
2. `npcInteractions.ts` 里的 NPC 交互 function。
|
|
||||||
3. `treasureInteractions.ts` 里的宝藏交互 function。
|
|
||||||
4. `useStoryGeneration.ts` / `npcInteraction.ts` / 背包装备锻造里额外加出来的“流程控制型 functionId”。
|
|
||||||
|
|
||||||
“选完选项触发 function 后没有触发剧情推理”这类现象,当前主要有 3 种来源:
|
|
||||||
|
|
||||||
1. **按设计先走本地分流,不会在第一次点击时立刻推理。**
|
|
||||||
2. **剧情推理其实已经完成,但被“继续冒险”这道 UI 中转门挡住了。**
|
|
||||||
3. **真正的可见性 bug:`story_continue_adventure` 的文案常量已经乱码,导致 UI 提示失效,用户很容易误判为没继续推理。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 当前 function 体系分层
|
|
||||||
|
|
||||||
### 1.1 基础状态 function:`src/data/stateFunctions.ts`
|
|
||||||
|
|
||||||
这一层才是严格意义上的“状态 function 注册表”,由 `resolveFunctionOption` 统一解析。
|
|
||||||
|
|
||||||
当前运行时实际启用 12 个:
|
|
||||||
|
|
||||||
- 战斗类 7 个:
|
|
||||||
- `battle_all_in_crush`
|
|
||||||
- `battle_guard_break`
|
|
||||||
- `battle_probe_pressure`
|
|
||||||
- `battle_feint_step`
|
|
||||||
- `battle_recover_breath`
|
|
||||||
- `battle_finisher_window`
|
|
||||||
- `battle_escape_breakout`
|
|
||||||
- 空闲类 5 个:
|
|
||||||
- `idle_explore_forward`
|
|
||||||
- `idle_travel_next_scene`
|
|
||||||
- `idle_rest_focus`
|
|
||||||
- `idle_observe_signs`
|
|
||||||
- `idle_call_out`
|
|
||||||
|
|
||||||
关键设计点:
|
|
||||||
|
|
||||||
- 定义源头是 `BATTLE_FUNCTIONS` / `IDLE_FUNCTIONS`。
|
|
||||||
- 最终运行时集合由 `buildStateFunctionDefinitions` 产出。
|
|
||||||
- 可执行过滤走 `getExecutableFunctions`。
|
|
||||||
- 文案和视觉包装走 `resolveFunctionOption`。
|
|
||||||
- 默认选项池走 `getDefaultFunctionIdsForContext`。
|
|
||||||
|
|
||||||
注意:
|
|
||||||
|
|
||||||
- `idle_follow_clue` 仍然留在源码和 prompt 描述里,但在 `applyRuntimeFunctionAdjustments` 中被直接过滤掉,不会进入运行时 function 集合。
|
|
||||||
- `src/components/game-shell/useSceneTransitionModel.ts` 里仍然保留了 `idle_follow_clue` 的转场映射,这说明当前 function 清单并不完全一致。
|
|
||||||
|
|
||||||
### 1.2 NPC 交互 function:`src/data/npcInteractions.ts`
|
|
||||||
|
|
||||||
这一层不是通过 `resolveFunctionOption` 生成的,而是 `buildNpcEncounterStoryMoment` 直接拼出 `StoryOption`,并挂上 `interaction.kind = 'npc'`。
|
|
||||||
|
|
||||||
按功能类型看,当前会出现这些 `functionId`:
|
|
||||||
|
|
||||||
- `npc_preview_talk`
|
|
||||||
- `npc_trade`
|
|
||||||
- `npc_fight`
|
|
||||||
- `npc_spar`
|
|
||||||
- `npc_help`
|
|
||||||
- `npc_chat`(可重复出现 2 个以上,代表不同聊天话题)
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_recruit`
|
|
||||||
- `npc_quest_accept`
|
|
||||||
- `npc_quest_turn_in`
|
|
||||||
- `npc_leave`
|
|
||||||
|
|
||||||
关键设计点:
|
|
||||||
|
|
||||||
- 这一层的 function 是“眼前 NPC 交互目录”,不是基础状态机目录。
|
|
||||||
- 真正执行分流不在 `stateFunctions.ts`,而在 `handleNpcInteraction` / `resolveNpcInteractionDecision`。
|
|
||||||
- prompt 层通过 `availableOptions` 把这些 function 当作“固定可选项列表”交给模型,要求模型保留数量和 `functionId`。
|
|
||||||
|
|
||||||
### 1.3 宝藏交互 function:`src/data/treasureInteractions.ts`
|
|
||||||
|
|
||||||
这一层同样不是 `resolveFunctionOption` 体系,而是直接构造带 `interaction.kind = 'treasure'` 的选项。
|
|
||||||
|
|
||||||
当前有 3 个:
|
|
||||||
|
|
||||||
- `treasure_secure`
|
|
||||||
- `treasure_inspect`
|
|
||||||
- `treasure_leave`
|
|
||||||
|
|
||||||
执行时由 `useTreasureFlow.ts` 接管,最终再回到 `commitGeneratedState` 继续剧情推理。
|
|
||||||
|
|
||||||
### 1.4 流程控制 / 面板动作 functionId
|
|
||||||
|
|
||||||
这类 `functionId` 会进入 `lastFunctionId` 或 `commitGeneratedState`,但不在 `stateFunctions.ts` 注册表里:
|
|
||||||
|
|
||||||
- 流程控制:
|
|
||||||
- `story_continue_adventure`
|
|
||||||
- `camp_travel_home_scene`
|
|
||||||
- `story_opening_camp_dialogue`
|
|
||||||
- 面板动作:
|
|
||||||
- `inventory_use`
|
|
||||||
- `equipment_equip`
|
|
||||||
- `equipment_unequip`
|
|
||||||
- `forge_craft`
|
|
||||||
- `forge_dismantle`
|
|
||||||
- `forge_reforge`
|
|
||||||
|
|
||||||
这说明当前项目里“functionId”已经同时承担了 3 个角色:
|
|
||||||
|
|
||||||
- 运行时基础状态动作 ID
|
|
||||||
- NPC / 宝藏交互动作 ID
|
|
||||||
- 流程 / 面板事件 ID
|
|
||||||
|
|
||||||
这能跑,但可追踪性已经开始分裂。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 当前剧情推理触发链路
|
|
||||||
|
|
||||||
### 2.1 会立刻触发剧情推理的主链
|
|
||||||
|
|
||||||
这类点击后会直接走 AI 续推:
|
|
||||||
|
|
||||||
- 普通战斗 / 空闲 function
|
|
||||||
- `handleChoice`
|
|
||||||
- `buildResolvedChoiceState`
|
|
||||||
- `playResolvedChoice`
|
|
||||||
- `generateNextStep`
|
|
||||||
- 这些 NPC 交互
|
|
||||||
- `npc_preview_talk`
|
|
||||||
- `npc_help`
|
|
||||||
- `npc_chat`
|
|
||||||
- `npc_fight`
|
|
||||||
- `npc_spar`
|
|
||||||
- `npc_quest_accept`
|
|
||||||
- `npc_quest_turn_in`
|
|
||||||
- `npc_leave`
|
|
||||||
- 宝藏交互确认后
|
|
||||||
- `treasure_secure`
|
|
||||||
- `treasure_inspect`
|
|
||||||
- `treasure_leave`
|
|
||||||
- 面板动作确认后
|
|
||||||
- `inventory_use`
|
|
||||||
- `equipment_equip`
|
|
||||||
- `equipment_unequip`
|
|
||||||
- `forge_*`
|
|
||||||
|
|
||||||
共性是:
|
|
||||||
|
|
||||||
- 要么直接在 `handleChoice` 里调用 `generateNextStep`。
|
|
||||||
- 要么先走 `commitGeneratedState` / `commitGeneratedStateWithEncounterEntry`,再统一调用 `generateStoryForState`。
|
|
||||||
|
|
||||||
### 2.2 第一次点击不会立刻触发剧情推理的分流
|
|
||||||
|
|
||||||
这类最容易被误判成“function 触发了,但剧情没继续”:
|
|
||||||
|
|
||||||
- `npc_trade`
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_recruit`(队伍满时必进 modal;队伍未满时会先进招募对话流)
|
|
||||||
|
|
||||||
原因不是漏调,而是当前设计明确先走:
|
|
||||||
|
|
||||||
- `resolveNpcInteractionDecision`
|
|
||||||
- `trade_modal`
|
|
||||||
- `gift_modal`
|
|
||||||
- `recruit_modal`
|
|
||||||
- `recruit_immediate`
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
- 第一次点击只是**打开模态框 / 进入招募流程**。
|
|
||||||
- 真正推进剧情推理,要等到:
|
|
||||||
- `confirmTrade`
|
|
||||||
- `confirmGift`
|
|
||||||
- `confirmRecruit`
|
|
||||||
- `executeRecruitment`
|
|
||||||
|
|
||||||
如果产品预期是“用户每点一次选项就必须立即看到剧情继续”,这一层现在不满足。
|
|
||||||
|
|
||||||
### 2.3 `npc_chat` 是“先推理,再延迟展示选项”
|
|
||||||
|
|
||||||
`npc_chat` 不是普通的“生成下一幕 + 立刻给选项”,而是单独走这条链:
|
|
||||||
|
|
||||||
1. `commitNpcChatState` 先流式生成聊天正文。
|
|
||||||
2. 聊天结束后,再调用一次 `generateNextStep`。
|
|
||||||
3. 新一轮冒险选项不直接显示,而是塞进 `deferredOptions`。
|
|
||||||
4. 当前界面只先显示一个 `story_continue_adventure`。
|
|
||||||
5. 用户再点一次,才把 `deferredOptions` 放出来。
|
|
||||||
|
|
||||||
所以这里常见的误判是:
|
|
||||||
|
|
||||||
- **剧情推理其实已经做完了。**
|
|
||||||
- 只是 UI 先让用户“继续冒险”一次,才把新选项展示出来。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 本次排查发现的重点问题
|
|
||||||
|
|
||||||
### 3.1 真正最像“没触发剧情推理”的地方:模态框型 NPC function
|
|
||||||
|
|
||||||
定位:
|
|
||||||
|
|
||||||
- `src/hooks/story/npcEncounterActions.ts:427-449`
|
|
||||||
- `src/hooks/story/storyGenerationState.ts:41-80`
|
|
||||||
- `src/hooks/story/npcInteraction.ts:427-585`
|
|
||||||
|
|
||||||
现象:
|
|
||||||
|
|
||||||
- 点击 `npc_trade` / `npc_gift` / `npc_recruit` 后,故事文本区通常不会立刻变化。
|
|
||||||
- 当前故事也不会马上追加一段新的 `storyText`。
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这些 function 的第一次点击只做本地 UI 分流。
|
|
||||||
- 直到用户在 modal 里确认,才会真正调用 `commitGeneratedState` 继续推理。
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- **这不是单纯 bug,而是当前设计本身如此。**
|
|
||||||
- 但如果玩家从“选项即剧情推进”的心智出发,会非常像“点了没反应”。
|
|
||||||
|
|
||||||
建议优先级:高。
|
|
||||||
|
|
||||||
### 3.2 最关键的实际 bug:`story_continue_adventure` 文案乱码,导致“已推理但未显式提示”
|
|
||||||
|
|
||||||
定位:
|
|
||||||
|
|
||||||
- `src/hooks/useStoryGeneration.ts:92-96`
|
|
||||||
- `src/hooks/story/npcEncounterActions.ts:281-400`
|
|
||||||
- `src/components/AdventurePanel.tsx:534`
|
|
||||||
- `src/components/AdventurePanel.tsx:860`
|
|
||||||
- `src/components/AdventurePanel.tsx:879`
|
|
||||||
|
|
||||||
现象:
|
|
||||||
|
|
||||||
- `npc_chat` 完成后,系统本应展示一个“继续冒险”按钮,提示“剧情推理完成,继续后显示新的冒险选项”。
|
|
||||||
- 但当前 `CONTINUE_ADVENTURE_ACTION_TEXT` 常量已经写成了乱码:`缁х画鍐掗櫓`。
|
|
||||||
- `AdventurePanel` 又是靠 `option.actionText === '继续冒险'` 来识别这个特殊按钮。
|
|
||||||
|
|
||||||
直接后果:
|
|
||||||
|
|
||||||
- 特殊提示 UI 不会出现。
|
|
||||||
- 玩家只会看到一个普通且乱码的单选项。
|
|
||||||
- 实际上 `deferredOptions` 已经算好了,但用户极容易误会为“剧情没有继续推理”。
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- **这是本次排查里最明确的实现级 bug。**
|
|
||||||
- 它不会阻止底层推理发生,但会严重破坏“推理已完成”的可见性。
|
|
||||||
|
|
||||||
建议优先级:最高。
|
|
||||||
|
|
||||||
### 3.3 `StateFunctionEditor` 覆盖不到真正最容易出问题的 function
|
|
||||||
|
|
||||||
定位:
|
|
||||||
|
|
||||||
- `src/components/StateFunctionEditor.tsx:13-21`
|
|
||||||
- `src/components/StateFunctionEditor.tsx:488`
|
|
||||||
- `src/components/StateFunctionEditor.tsx:772-838`
|
|
||||||
- `src/components/StateFunctionEditor.tsx:992-1211`
|
|
||||||
|
|
||||||
现象:
|
|
||||||
|
|
||||||
- 编辑器预览只接了 `buildStateFunctionDefinitions` / `getAllStateFunctionDefinitions` / `resolveFunctionOption`。
|
|
||||||
- 也就是它只能预览 `stateFunctions.ts` 那 12 个基础 function。
|
|
||||||
|
|
||||||
覆盖不到的关键分支:
|
|
||||||
|
|
||||||
- `npc_*`
|
|
||||||
- `treasure_*`
|
|
||||||
- `npc_preview_talk`
|
|
||||||
- `story_continue_adventure`
|
|
||||||
- modal 分流
|
|
||||||
- `npc_chat` 的 `deferredOptions`
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- 这不是运行时 bug,但它解释了为什么这类问题很难在编辑器里提前暴露。
|
|
||||||
- 当前“Function 编辑器”并没有覆盖到最复杂、最容易让用户感知为异常的 function 链路。
|
|
||||||
|
|
||||||
建议优先级:高。
|
|
||||||
|
|
||||||
### 3.4 function 清单存在“源码有、运行时无、别处还在引用”的分裂
|
|
||||||
|
|
||||||
定位:
|
|
||||||
|
|
||||||
- `src/data/stateFunctions.ts:350`
|
|
||||||
- `src/data/stateFunctions.ts:429-441`
|
|
||||||
- `src/components/game-shell/useSceneTransitionModel.ts:19-25`
|
|
||||||
|
|
||||||
现象:
|
|
||||||
|
|
||||||
- `idle_follow_clue` 仍在定义表、提示词描述、若干 `switch` 分支中存在。
|
|
||||||
- 但最终在 `applyRuntimeFunctionAdjustments` 被过滤掉,不会进入运行时 function 集合。
|
|
||||||
- `useSceneTransitionModel` 里却还保留着它的转场模式映射。
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- 这不是“没触发剧情推理”的直接原因。
|
|
||||||
- 但会让维护者误判当前真实 function 清单,也会增加后续继续扩 function 时的混乱。
|
|
||||||
|
|
||||||
建议优先级:中。
|
|
||||||
|
|
||||||
### 3.5 自动化测试几乎没有覆盖“最容易让人误会没推理”的链路
|
|
||||||
|
|
||||||
定位:
|
|
||||||
|
|
||||||
- 当前已有测试主要是 `src/hooks/story/storyGenerationState.test.ts`
|
|
||||||
|
|
||||||
已覆盖:
|
|
||||||
|
|
||||||
- `trade_modal`
|
|
||||||
- `recruit_modal`
|
|
||||||
- 地图切场景
|
|
||||||
|
|
||||||
未覆盖:
|
|
||||||
|
|
||||||
- `npc_chat` 的 `deferredOptions -> story_continue_adventure -> 真正显示新选项`
|
|
||||||
- `npc_trade` / `npc_gift` / `npc_recruit` 确认后是否一定调用 `commitGeneratedState`
|
|
||||||
- `story_continue_adventure` 文案与 UI 特判是否一致
|
|
||||||
- `npc_preview_talk -> enterNpcInteraction -> generateStoryForState`
|
|
||||||
- 宝藏分支确认后是否稳定续推
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- 这类问题之所以能长期存在,一个核心原因就是**最关键的续推分支没有测试兜底**。
|
|
||||||
|
|
||||||
建议优先级:高。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 按“首次点击是否立即触发剧情推理”整理
|
|
||||||
|
|
||||||
### 4.1 首次点击就会继续推理
|
|
||||||
|
|
||||||
- `battle_*`
|
|
||||||
- `idle_*`
|
|
||||||
- `npc_preview_talk`
|
|
||||||
- `npc_help`
|
|
||||||
- `npc_chat`
|
|
||||||
- `npc_fight`
|
|
||||||
- `npc_spar`
|
|
||||||
- `npc_quest_accept`
|
|
||||||
- `npc_quest_turn_in`
|
|
||||||
- `npc_leave`
|
|
||||||
- `treasure_*`
|
|
||||||
- `inventory_use`
|
|
||||||
- `equipment_*`
|
|
||||||
- `forge_*`
|
|
||||||
|
|
||||||
### 4.2 首次点击不会立刻继续推理
|
|
||||||
|
|
||||||
- `npc_trade`
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_recruit`(至少会先进入确认 / 对话流)
|
|
||||||
- `story_continue_adventure`
|
|
||||||
|
|
||||||
这里要特别分清:
|
|
||||||
|
|
||||||
- `npc_trade` / `npc_gift` / `npc_recruit` 是**先分流,后确认,确认后再推理**。
|
|
||||||
- `story_continue_adventure` 是**推理已经完成,只是先把结果选项延后展示**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 建议的修正顺序
|
|
||||||
|
|
||||||
### P0
|
|
||||||
|
|
||||||
- 修掉 `CONTINUE_ADVENTURE_ACTION_TEXT` 的乱码。
|
|
||||||
- `AdventurePanel` 不要再靠 `actionText === '继续冒险'` 判定特殊按钮,改成按 `functionId === 'story_continue_adventure'` 判定。
|
|
||||||
|
|
||||||
### P1
|
|
||||||
|
|
||||||
- 明确产品规则:
|
|
||||||
- `npc_trade` / `npc_gift` / `npc_recruit` 第一次点击是否就应该写入一条“进入交易 / 送礼 / 招募确认”的剧情反馈。
|
|
||||||
- 如果答案是“应该”,那这些 modal 型 function 需要补一层轻量 story feedback,而不是只弹框。
|
|
||||||
|
|
||||||
### P1
|
|
||||||
|
|
||||||
- 给 `npc_chat` 补自动化测试:
|
|
||||||
- 聊天后必须出现 `story_continue_adventure`
|
|
||||||
- 第二次点击后必须能展示 `deferredOptions`
|
|
||||||
|
|
||||||
### P1
|
|
||||||
|
|
||||||
- 给 `npc_trade` / `npc_gift` / `npc_recruit` 补自动化测试:
|
|
||||||
- 第一次点击只分流
|
|
||||||
- 确认后一定触发 `commitGeneratedState`
|
|
||||||
- 后续一定能拿到新的 `StoryMoment`
|
|
||||||
|
|
||||||
### P2
|
|
||||||
|
|
||||||
- 扩展 `StateFunctionEditor` 或补新的“交互 function 预览器”,把 `npc_*` / `treasure_*` / `story_continue_adventure` 也纳入可预演范围。
|
|
||||||
|
|
||||||
### P2
|
|
||||||
|
|
||||||
- 清理 `idle_follow_clue` 这类“半退场”的 function,保证:
|
|
||||||
- 运行时集合
|
|
||||||
- prompt 描述
|
|
||||||
- 编辑器
|
|
||||||
- 转场映射
|
|
||||||
- 测试
|
|
||||||
|
|
||||||
保持一致。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 最后结论
|
|
||||||
|
|
||||||
当前最值得优先处理的,不是再继续加新的 function,而是先把这 3 个点收拢:
|
|
||||||
|
|
||||||
1. **把“首次点击只分流、不推理”的 function 明确标出来。**
|
|
||||||
2. **把 `npc_chat -> story_continue_adventure -> deferredOptions` 这条延迟展示链做清楚。**
|
|
||||||
3. **先修掉 `story_continue_adventure` 的乱码和基于文案的 UI 判断。**
|
|
||||||
|
|
||||||
如果不先处理这几个点,用户会持续把“按设计延迟”与“真实漏调剧情推理”混在一起感知,后面 function 越多,这类问题会越难排查。
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
# Function 需求完整性核查(2026-04-14)
|
|
||||||
|
|
||||||
## 1. 核查范围
|
|
||||||
|
|
||||||
本次核查按当前线程上下文,聚焦 function 体系相关文档与实现,不扩大到整个项目全部 PRD。
|
|
||||||
|
|
||||||
本次实际对照了这些文档:
|
|
||||||
|
|
||||||
- `docs/audits/FUNCTION_DESIGN_AUDIT_2026-04-03.md`
|
|
||||||
- `docs/reference/FUNCTION_SCRIPT_CATALOG_2026-04-04.md`
|
|
||||||
- `docs/experience/ADVENTURE_RUNTIME_DEV_EXPERIENCE.md`
|
|
||||||
- `docs/audits/engineering/README.md`
|
|
||||||
|
|
||||||
本次实际核对了这些实现入口:
|
|
||||||
|
|
||||||
- `src/data/functionCatalog/**`
|
|
||||||
- `src/data/stateFunctions.ts`
|
|
||||||
- `src/data/npcInteractions.ts`
|
|
||||||
- `src/components/AdventurePanel.tsx`
|
|
||||||
- `src/hooks/story/choiceActions.ts`
|
|
||||||
- `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
- `src/hooks/story/npcInteraction.ts`
|
|
||||||
- `src/services/runtimeStoryService.ts`
|
|
||||||
- `server-node/src/modules/story/**`
|
|
||||||
- `server-node/src/modules/inventory/**`
|
|
||||||
- `server-node/src/modules/runtime-item/**`
|
|
||||||
- `server-node/src/modules/quest/**`
|
|
||||||
|
|
||||||
## 2. 核查结论
|
|
||||||
|
|
||||||
结论先说:
|
|
||||||
|
|
||||||
- function 主链路需求已经基本落地,不需要再继续“为了完整而过度迭代”。
|
|
||||||
- 当前最主要的缺口不是再发明一套新 function 流程,而是把已有链路补齐回归测试,确保后续不会回退。
|
|
||||||
- 本轮核查后,已把两条仍缺直接测试兜底的核心链路补上。
|
|
||||||
|
|
||||||
## 3. 已确认已经落地的需求
|
|
||||||
|
|
||||||
### 3.1 function 目录化与分层收口
|
|
||||||
|
|
||||||
已实现:
|
|
||||||
|
|
||||||
- `state / npc / treasure / flow / panel` 五类 function 已统一收口到 `src/data/functionCatalog/`
|
|
||||||
- `src/data/functionCatalog/index.ts` 已提供统一导出
|
|
||||||
- `SERVER_RUNTIME_FUNCTION_IDS` 也已和 catalog 文档映射对齐
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- 这部分需求已完成,不需要继续重做结构。
|
|
||||||
|
|
||||||
### 3.2 `story_continue_adventure` 延迟展示链路
|
|
||||||
|
|
||||||
已实现:
|
|
||||||
|
|
||||||
- `npc_chat` 在 `npcEncounterActions.ts` 中先生成聊天正文,再把后续选项写入 `deferredOptions`
|
|
||||||
- `choiceActions.ts` 在点击 `story_continue_adventure` 时会直接恢复 `deferredOptions`
|
|
||||||
- `AdventurePanel.tsx` 已按 `functionId` 而不是 `actionText` 识别该特殊按钮
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- 这条主功能链路已经真正存在,不属于“文档写了、实现没跟上”。
|
|
||||||
|
|
||||||
### 3.3 modal 型 function 的首次点击分流
|
|
||||||
|
|
||||||
已实现:
|
|
||||||
|
|
||||||
- `npc_trade`
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_recruit`
|
|
||||||
|
|
||||||
这些 function 当前都明确是“首次点击先分流,再在确认后进入真正执行链”。
|
|
||||||
|
|
||||||
当前实现路径:
|
|
||||||
|
|
||||||
- 首次点击:
|
|
||||||
- `choiceActions.ts`
|
|
||||||
- `storyGenerationState.ts`
|
|
||||||
- `npcInteraction.ts`
|
|
||||||
- 确认后:
|
|
||||||
- `trade / gift / quest` 进入 server runtime action
|
|
||||||
- `recruit` 进入本地招募对白与本地状态提交链
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- 这不是未实现,而是当前架构设计如此。
|
|
||||||
- 文档里“确认后要继续推进剧情/结算”的要求已经满足。
|
|
||||||
|
|
||||||
### 3.4 Task6 function 的服务端化
|
|
||||||
|
|
||||||
已实现:
|
|
||||||
|
|
||||||
- `inventory_use`
|
|
||||||
- `equipment_equip`
|
|
||||||
- `equipment_unequip`
|
|
||||||
- `forge_craft`
|
|
||||||
- `forge_dismantle`
|
|
||||||
- `forge_reforge`
|
|
||||||
- `npc_trade`
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_quest_accept`
|
|
||||||
- `npc_quest_turn_in`
|
|
||||||
- `treasure_secure`
|
|
||||||
- `treasure_inspect`
|
|
||||||
- `treasure_leave`
|
|
||||||
|
|
||||||
这些 function 已经在前端 `runtimeStoryService.ts`、服务端 `story runtime / inventory / runtime-item / quest` 模块里形成闭环。
|
|
||||||
|
|
||||||
判断:
|
|
||||||
|
|
||||||
- 这部分不需要再回退到前端本地重写。
|
|
||||||
|
|
||||||
## 4. 本轮发现的真实缺口
|
|
||||||
|
|
||||||
本轮真正仍未完整落地的,不是功能行为本身,而是下面两条回归保护:
|
|
||||||
|
|
||||||
### 4.1 `npc_chat -> story_continue_adventure -> deferredOptions`
|
|
||||||
|
|
||||||
问题:
|
|
||||||
|
|
||||||
- 文档明确要求这条链路要清楚、可验证。
|
|
||||||
- 代码已经有,但之前没有直接测试“点击继续冒险后必须展示 deferredOptions”。
|
|
||||||
|
|
||||||
本轮已补:
|
|
||||||
|
|
||||||
- `src/hooks/story/choiceActions.test.ts`
|
|
||||||
- 新增 `reveals deferred adventure options when story_continue_adventure is selected`
|
|
||||||
|
|
||||||
### 4.2 `AdventurePanel` 对 continue option 的识别方式
|
|
||||||
|
|
||||||
问题:
|
|
||||||
|
|
||||||
- 文档明确要求不要再靠文案识别 continue option。
|
|
||||||
- 实现已经改成按 `functionId` 识别,但之前没有组件层测试锁住。
|
|
||||||
|
|
||||||
本轮已补:
|
|
||||||
|
|
||||||
- `src/components/AdventurePanel.test.tsx`
|
|
||||||
- 验证 `story_continue_adventure` 即使 actionText 改掉,仍然显示延迟选项提示
|
|
||||||
- 验证仅 actionText 相同但 functionId 不同,不会误触发提示
|
|
||||||
|
|
||||||
## 5. 本轮新增测试
|
|
||||||
|
|
||||||
本轮新增:
|
|
||||||
|
|
||||||
- `src/components/AdventurePanel.test.tsx`
|
|
||||||
- `src/hooks/story/choiceActions.test.ts`
|
|
||||||
- 新增 deferred options 恢复用例
|
|
||||||
|
|
||||||
## 6. 验收结论
|
|
||||||
|
|
||||||
按当前 function 相关文档要求判断:
|
|
||||||
|
|
||||||
- 核心运行时需求:已实现
|
|
||||||
- 延迟展示链路:已实现
|
|
||||||
- modal 分流架构:已实现
|
|
||||||
- Task6 服务端承接:已实现
|
|
||||||
- 回归测试盲区:本轮已补齐关键缺口
|
|
||||||
|
|
||||||
最终建议:
|
|
||||||
|
|
||||||
- 这块现在不应该继续过度迭代。
|
|
||||||
- 后续应以“新增需求再增量补测试”为主,而不是再次重构 function 主链路。
|
|
||||||
- 除非后续 PRD 明确要求“modal 首次点击也必须立刻生成一段剧情反馈”,否则不建议为了形式统一再强行改写当前分流模型。
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
# Function 运行时完整测试审计(2026-04-16)
|
|
||||||
|
|
||||||
## 1. 本次目标
|
|
||||||
|
|
||||||
本次不是泛泛地跑一遍前端页面,而是围绕“游戏运行中的 function 主链路”做系统化核查,重点确认下面 4 件事:
|
|
||||||
|
|
||||||
- 当前运行时 function 集合是否还能正确构建、过滤、排序和解析。
|
|
||||||
- function 命中的前端承接链路是否仍然稳定。
|
|
||||||
- 已服务端化的 runtime action function 是否还能闭环执行。
|
|
||||||
- 工程门禁是否处于可持续回归的状态。
|
|
||||||
|
|
||||||
## 2. 本次实际覆盖范围
|
|
||||||
|
|
||||||
本轮重点覆盖了这些实现入口:
|
|
||||||
|
|
||||||
- `src/data/stateFunctions.ts`
|
|
||||||
- `src/data/functionCatalog/**`
|
|
||||||
- `src/data/npcInteractions.ts`
|
|
||||||
- `src/hooks/story/**`
|
|
||||||
- `src/services/runtimeStoryService.ts`
|
|
||||||
- `src/hooks/useGameFlow.ts`
|
|
||||||
- `server-node/src/modules/story/**`
|
|
||||||
- `server-node/src/modules/inventory/**`
|
|
||||||
- `server-node/src/modules/runtime-item/**`
|
|
||||||
- `server-node/src/modules/quest/**`
|
|
||||||
- `scripts/smoke-server-node.ts`
|
|
||||||
|
|
||||||
## 3. 实际执行的测试与检查
|
|
||||||
|
|
||||||
### 3.1 前端 Vitest 全量测试
|
|
||||||
|
|
||||||
执行命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm.cmd test
|
|
||||||
```
|
|
||||||
|
|
||||||
结果:
|
|
||||||
|
|
||||||
- `110` 个测试文件全部通过
|
|
||||||
- `276` 条测试全部通过
|
|
||||||
|
|
||||||
其中与 function 主链路直接相关、并已确认通过的测试包括:
|
|
||||||
|
|
||||||
- `src/data/stateFunctions.test.ts`
|
|
||||||
- `src/data/functionCatalog/functionCatalog.test.ts`
|
|
||||||
- `src/data/npcInteractions.test.ts`
|
|
||||||
- `src/hooks/story/choiceActions.test.ts`
|
|
||||||
- `src/hooks/story/storyGenerationState.test.ts`
|
|
||||||
- `src/hooks/story/runtimeStoryCoordinator.test.ts`
|
|
||||||
- `src/components/AdventurePanel.test.tsx`
|
|
||||||
- `src/services/runtimeStoryService.test.ts`
|
|
||||||
|
|
||||||
补充说明:
|
|
||||||
|
|
||||||
- 运行 `hostileNpcPresets.test.ts` 时会看到 “network disabled in test” 的日志。
|
|
||||||
- 这不是本轮失败项,测试已验证在 LLM 不可达时会正确走 deterministic fallback。
|
|
||||||
|
|
||||||
### 3.2 内容数据校验
|
|
||||||
|
|
||||||
执行命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm.cmd run check:content
|
|
||||||
```
|
|
||||||
|
|
||||||
结果:
|
|
||||||
|
|
||||||
- `check:data` 通过
|
|
||||||
- `check:overrides` 通过
|
|
||||||
- `check:smoke` 通过
|
|
||||||
- 输出为:`Content validation passed. scenes=24 monsters=16 characters=5 functions=12`
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
- 当前基础内容数据、override 与 smoke 内容校验没有发现新的 function 数据层问题。
|
|
||||||
|
|
||||||
### 3.3 服务端测试
|
|
||||||
|
|
||||||
执行命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm.cmd run server-node:test
|
|
||||||
```
|
|
||||||
|
|
||||||
结果:
|
|
||||||
|
|
||||||
- `108` 条服务端测试全部通过
|
|
||||||
|
|
||||||
本轮已确认通过的 function 相关服务端能力包括:
|
|
||||||
|
|
||||||
- `inventory_use`
|
|
||||||
- `equipment_equip`
|
|
||||||
- `npc_trade`
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_quest_accept`
|
|
||||||
- `npc_quest_turn_in`
|
|
||||||
- `treasure_inspect`
|
|
||||||
- 战斗结算与 quest signal 推进
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
- 已服务端化的 runtime function 承接链路当前单测层面是稳定的。
|
|
||||||
|
|
||||||
### 3.4 服务端 smoke
|
|
||||||
|
|
||||||
执行命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm.cmd run server-node:smoke
|
|
||||||
```
|
|
||||||
|
|
||||||
结果:
|
|
||||||
|
|
||||||
- 失败
|
|
||||||
- 报错:
|
|
||||||
|
|
||||||
```text
|
|
||||||
TypeError: Cannot read properties of undefined (reading 'enabled')
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.5 TypeScript 类型检查
|
|
||||||
|
|
||||||
执行命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm.cmd run typecheck
|
|
||||||
```
|
|
||||||
|
|
||||||
结果:
|
|
||||||
|
|
||||||
- 失败
|
|
||||||
- 失败位置:
|
|
||||||
- `src/hooks/useGameFlow.ts:339`
|
|
||||||
|
|
||||||
### 3.6 生产构建
|
|
||||||
|
|
||||||
执行命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm.cmd run build
|
|
||||||
```
|
|
||||||
|
|
||||||
结果:
|
|
||||||
|
|
||||||
- 通过
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
- 当前代码可构建,但并不代表类型门禁与 smoke 门禁同样健康。
|
|
||||||
|
|
||||||
## 4. 本轮确认的问题
|
|
||||||
|
|
||||||
## P1:`server-node:smoke` 已失效,无法完成服务端运行时冒烟验证
|
|
||||||
|
|
||||||
- 现象:
|
|
||||||
- `npm.cmd run server-node:smoke` 无法启动临时 Express 服务,直接在 `createSmsVerificationService` 处报错。
|
|
||||||
- 直接原因:
|
|
||||||
- [`scripts/smoke-server-node.ts`](../../scripts/smoke-server-node.ts) 里的 `createSmokeConfig()` 只构造了 `llm`、`dashScope` 等字段,没有补齐后续新增的 `smsAuth`、`wechatAuth`、`authSession` 配置块。
|
|
||||||
- [`server-node/src/config.ts`](../../server-node/src/config.ts) 中 `AppConfig` 已把这些字段定义为必需项。
|
|
||||||
- [`server-node/src/services/smsVerificationService.ts`](../../server-node/src/services/smsVerificationService.ts) 在 `createSmsVerificationService` 中直接读取 `config.smsAuth.enabled`,因此 smoke 配置一进入 `createAppContext` 就会崩。
|
|
||||||
- 影响:
|
|
||||||
- 本地无法再用 smoke 脚本验证“服务端真实启动 + 认证 + runtime save/settings 回路”。
|
|
||||||
- 这会让 function 服务端化之后的真实接线路径少掉一层最接近运行时的保护。
|
|
||||||
- 判断:
|
|
||||||
- 这是本轮最明确、最稳定复现的真实 bug。
|
|
||||||
|
|
||||||
## P1:`typecheck` 门禁已破,`useGameFlow` 的 starter inventory 合并存在类型漂移
|
|
||||||
|
|
||||||
- 现象:
|
|
||||||
- `npm.cmd run typecheck` 在 [`src/hooks/useGameFlow.ts`](../../src/hooks/useGameFlow.ts) 第 `339` 行失败。
|
|
||||||
- 直接原因:
|
|
||||||
- 同文件中的 `mergeStarterInventoryItems<T extends { category: string; name: string }>` 会优先从显式 starter item 推断出一个“字段更严格”的 `T`。
|
|
||||||
- 但 fallback 侧传入的是 [`InventoryItem`](../../src/types/items.ts) 数组,而 `InventoryItem.description`、`equipmentSlotId`、`runtimeMetadata` 等字段本身是可选的。
|
|
||||||
- 于是 `explicitItems` 与 `fallbackItems` 在泛型推断后不再兼容,类型门禁被打穿。
|
|
||||||
- 影响:
|
|
||||||
- 当前 starter inventory 初始化链路虽然能跑、也能通过构建,但已经失去 TypeScript 对结构一致性的保护。
|
|
||||||
- 这类问题后续很容易演变成“自定义世界显式初始物品”和“默认初始物品”在字段形态上继续分叉。
|
|
||||||
- 判断:
|
|
||||||
- 这是一个真实的工程 bug,优先级高于纯测试文案问题。
|
|
||||||
|
|
||||||
## 5. 本轮已确认通过的结论
|
|
||||||
|
|
||||||
- state function 运行时构建、过滤、优先级、选项解析当前测试全绿。
|
|
||||||
- function catalog 文档映射、helper option、trade/gift/recruit modal helper 当前测试全绿。
|
|
||||||
- 前端 function 主链路相关测试在最新工作区状态下已全部通过。
|
|
||||||
- 服务端 runtime story action、inventory、runtime-item、quest 承接链路当前测试全绿。
|
|
||||||
- 内容数据层校验通过。
|
|
||||||
- 生产构建通过。
|
|
||||||
|
|
||||||
## 6. 结论
|
|
||||||
|
|
||||||
如果只看“游戏运行中的 function 主链路”,当前结论是:
|
|
||||||
|
|
||||||
- 前端 function 运行时逻辑:通过
|
|
||||||
- 服务端 function action 承接:通过
|
|
||||||
- 内容数据与 function 基础目录:通过
|
|
||||||
- 工程回归门禁:不完整
|
|
||||||
|
|
||||||
当前最需要处理的不是再扩 function 范围,而是先修复这两个门禁缺口:
|
|
||||||
|
|
||||||
1. 修好 `server-node:smoke` 的配置构造,让服务端冒烟恢复可用。
|
|
||||||
2. 修好 `useGameFlow` 的 starter inventory 类型漂移,让 `typecheck` 回到绿色基线。
|
|
||||||
|
|
||||||
## 7. 备注
|
|
||||||
|
|
||||||
本轮没有做两类事情:
|
|
||||||
|
|
||||||
- 没有接入真实外部 LLM 做在线回归,本轮依赖的是本地测试里已有的 fallback 断言。
|
|
||||||
- 没有人手逐个点击整局游戏所有 function 的视觉回放,本轮重点是自动化测试、服务端测试、内容校验与 smoke 门禁。
|
|
||||||
|
|
||||||
因此,本审计可以说明“当前 function 系统的自动化测试层状况”,但不等于“所有视觉演出与在线模型联动都已人工验证完毕”。
|
|
||||||
|
|
||||||
## 8. 执行回填(2026-04-28,修复聊天任务领取入口)
|
|
||||||
|
|
||||||
- 问题现象:
|
|
||||||
- NPC 聊天中的待领取任务,点击“查看任务”进入详情后,再点“领取任务”没有把面板切到正式已接任务状态,表现上像“无法领取”。
|
|
||||||
- 根因:
|
|
||||||
- `npcChatQuestOfferUi.acceptPendingOffer()` 会异步把 `npc_quest_accept` 发到服务端。
|
|
||||||
- 但 [`src/components/rpg-runtime-panels/RpgAdventurePanel.tsx`](../../src/components/rpg-runtime-panels/RpgAdventurePanel.tsx) 里“待领取任务详情弹层”的 `onAcceptPendingNpcQuestOffer` 只返回了 `questId`,没有把它写入共享的 `pendingAcceptedQuestId`。
|
|
||||||
- 结果是本来负责等待 quest 真正进入 `quests` 后再统一收口面板状态的 `useEffect` 根本不会触发。
|
|
||||||
- 修复:
|
|
||||||
- 在 `onAcceptPendingNpcQuestOffer` 中补写 `setPendingAcceptedQuestId(acceptedQuestId)`,让待领取任务详情弹层复用普通 `npc_quest_accept` 已有的异步收口链。
|
|
||||||
- 新增 [`src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx`](../../src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx),覆盖“查看任务 -> 领取任务 -> 服务端异步写回 quest log -> 面板切到正式任务状态”的回归路径。
|
|
||||||
- 本次回归验证:
|
|
||||||
- `src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx`
|
|
||||||
- `src/components/rpg-runtime-panels/RpgAdventurePanel.test.tsx`
|
|
||||||
- `src/hooks/rpg-runtime-story/npcEncounterActions.test.ts`
|
|
||||||
- `src/hooks/rpg-runtime-story/choiceActions.test.ts`
|
|
||||||
- `src/hooks/rpg-runtime-story/runtimeStoryCoordinator.test.ts`
|
|
||||||
- `src/hooks/rpg-runtime-story/sessionActions.test.ts`
|
|
||||||
- 共 `57` 条测试通过。
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Function 测试审计(2026-04-14)
|
|
||||||
|
|
||||||
补充更新:
|
|
||||||
|
|
||||||
- 本文记录的 2 个 bug 已在同日完成代码修复。
|
|
||||||
- 对应测试已经从“稳定复现旧行为”切换为“验证修复后行为”。
|
|
||||||
|
|
||||||
## 1. 本次新增测试
|
|
||||||
|
|
||||||
本轮新增了两组 function 相关测试:
|
|
||||||
|
|
||||||
- `src/data/stateFunctions.test.ts`
|
|
||||||
- 覆盖 state function 的运行时过滤、优先级、选项解析、排序逻辑。
|
|
||||||
- `src/data/functionCatalog/functionCatalog.test.ts`
|
|
||||||
- 覆盖 function 文档映射、flow helper、NPC helper modal 初始化逻辑。
|
|
||||||
|
|
||||||
这两组测试都直接挂在现有 `vitest` 体系里,没有新建独立测试框架。
|
|
||||||
|
|
||||||
## 2. 本次执行结果
|
|
||||||
|
|
||||||
本轮实际执行了以下测试:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx vitest run src/data/stateFunctions.test.ts src/data/functionCatalog/functionCatalog.test.ts
|
|
||||||
npx vitest run src/data/npcInteractions.test.ts src/hooks/story/storyGenerationState.test.ts src/services/runtimeStoryService.test.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
执行结果:
|
|
||||||
|
|
||||||
- 新增测试:`2` 个文件,`12` 条测试,全部通过。
|
|
||||||
- 复跑已有 function 相关测试:`3` 个文件,`17` 条测试,全部通过。
|
|
||||||
- 修复回归测试:`5` 个文件,`30` 条测试,全部通过。
|
|
||||||
- 编码检查:`1516` 个文件全部通过。
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
- 本轮不是“测试全绿就代表没有问题”。
|
|
||||||
- 最初定位出的 2 个问题,已经在后续修复回合里转成了回归测试。
|
|
||||||
|
|
||||||
## 3. 历史 bug 与修复结果
|
|
||||||
|
|
||||||
### 3.1 `battle_recover_breath`
|
|
||||||
|
|
||||||
- 所在位置:`src/data/stateFunctions.ts`
|
|
||||||
- 状态:已修复
|
|
||||||
- 原始问题表现:
|
|
||||||
- 当 `inBattle = true`,但当前没有存活敌人时,`battle_recover_breath` 仍会留在可执行 function 列表中。
|
|
||||||
- 直接原因:
|
|
||||||
- `matchesCategory` 对 `recovery` 分类只判断了是否处于战斗态,没有像 `battle` / `escape` 分类那样额外校验 `hasAliveMonsters(context.monsters)`。
|
|
||||||
- 修复方式:
|
|
||||||
- 已在 `matchesCategory` 的 `recovery` 分支中,为战斗恢复类 function 补上 `hasAliveMonsters(context.monsters)` 判断。
|
|
||||||
- 修复前影响:
|
|
||||||
- 在“敌人已死但战斗态尚未清理干净”的边界帧里,界面仍可能出现战斗恢复类选项。
|
|
||||||
- 这会让 function 池和真实战斗状态产生残留错位。
|
|
||||||
- 当前验证方式:
|
|
||||||
- `inBattle = true`
|
|
||||||
- `monsters = [{ hp: 0, ... }]`
|
|
||||||
- 调用 `getExecutableFunctions(context)`
|
|
||||||
- 现在返回结果应为空,不再包含 `battle_recover_breath`
|
|
||||||
- 对应用例:
|
|
||||||
- `src/data/stateFunctions.test.ts`
|
|
||||||
- 用例名:`removes battle_recover_breath when combat has no living monsters`
|
|
||||||
|
|
||||||
### 3.2 `npc_trade`
|
|
||||||
|
|
||||||
- 所在位置:`src/data/functionCatalog/npc/npcTrade.ts`
|
|
||||||
- 状态:已修复
|
|
||||||
- 原始问题表现:
|
|
||||||
- trade modal 初始化时,`selectedPlayerItemId` 直接取 `state.playerInventory[0]?.id`。
|
|
||||||
- 如果玩家背包第一项数量为 `0`,modal 默认会选中一件不可出售物品。
|
|
||||||
- 直接原因:
|
|
||||||
- `buildNpcTradeModalState` 没有过滤 `quantity <= 0` 的物品,也没有寻找第一个可交易物品。
|
|
||||||
- 修复方式:
|
|
||||||
- 已改为优先选择 `quantity > 0` 的可交易物品。
|
|
||||||
- 同时对 NPC 库存和玩家背包都使用同一条筛选规则,避免默认选中空物品。
|
|
||||||
- 修复前影响:
|
|
||||||
- 交易面板第一次打开时,默认状态可能就是不可确认的。
|
|
||||||
- 用户需要手动切换到第二件物品,才会进入可提交状态。
|
|
||||||
- 当前验证方式:
|
|
||||||
- `playerInventory[0].quantity = 0`
|
|
||||||
- `playerInventory[1].quantity > 0`
|
|
||||||
- 调用 `buildNpcTradeModalState(...)`
|
|
||||||
- 现在 `selectedPlayerItemId` 应该自动落到第一件可交易物品
|
|
||||||
- 对应用例:
|
|
||||||
- `src/data/functionCatalog/functionCatalog.test.ts`
|
|
||||||
- 用例名:`prefers the first tradable player item when zero-quantity items exist`
|
|
||||||
- `src/hooks/story/storyGenerationState.test.ts`
|
|
||||||
- 用例名:`skips zero-quantity player items when opening the trade modal`
|
|
||||||
|
|
||||||
## 4. 本轮已验证通过的 function 能力
|
|
||||||
|
|
||||||
以下内容本轮已通过测试验证,没有发现新的明显问题:
|
|
||||||
|
|
||||||
- state function runtime 构建:
|
|
||||||
- `idle_follow_clue` 已正确从运行时候选池移除。
|
|
||||||
- `idle_explore_forward` 的运行时文案覆盖仍然生效。
|
|
||||||
- state function 选项行为:
|
|
||||||
- 高压战斗下 `battle_recover_breath` 会被正确提权。
|
|
||||||
- 营地场景会正确隐藏 `idle_explore_forward`。
|
|
||||||
- `idle_travel_next_scene` 会强制使用运行时建议 actionText。
|
|
||||||
- `battle_all_in_crush` 会保留外部传入的自定义 actionText。
|
|
||||||
- story option 排序仍保持“前 2 个 model 锁定 + 后续按 priority 排序”。
|
|
||||||
- flow helper:
|
|
||||||
- `story_continue_adventure`
|
|
||||||
- `camp_travel_home_scene`
|
|
||||||
- NPC helper:
|
|
||||||
- `npc_preview_talk`
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_recruit`
|
|
||||||
- 文档映射:
|
|
||||||
- 当前 `SERVER_RUNTIME_FUNCTION_IDS` 全部都能在 function catalog 文档中找到对应条目。
|
|
||||||
- 本轮扫描到的 function `source` 路径全部存在,没有出现失效引用。
|
|
||||||
|
|
||||||
## 5. 本轮修复动作
|
|
||||||
|
|
||||||
- `battle_recover_breath`
|
|
||||||
- 已补充战斗恢复类 function 的存活敌人校验,避免战斗边界帧残留非法选项。
|
|
||||||
- `npc_trade`
|
|
||||||
- 已把 trade modal 默认选中逻辑改为优先寻找可交易物品,不再直接吃数组第一项。
|
|
||||||
- 回归测试
|
|
||||||
- 原先记录旧行为的测试已翻转为修复后预期,并额外补了一条 `storyGenerationState` 接入层测试。
|
|
||||||
|
|
||||||
## 6. 备注
|
|
||||||
|
|
||||||
这次测试资产的意义分两步:
|
|
||||||
|
|
||||||
- 第一步先把 bug 稳定复现出来,避免问题只停留在口头描述。
|
|
||||||
- 第二步在修复后把断言翻转成“正确行为”,让它们正式成为回归测试。
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
# 物品生成系统与 Build 标签系统 PRD 落地审计
|
|
||||||
|
|
||||||
审计时间:2026-04-05
|
|
||||||
|
|
||||||
审计范围:
|
|
||||||
|
|
||||||
- `docs/prd/AI_NATIVE_RUNTIME_ITEM_GENERATION_DESIGN.md`
|
|
||||||
- `docs/prd/RUNTIME_ITEM_GENERATION_CURRENT_SYSTEM_DESIGN.md`
|
|
||||||
- `docs/prd/BUILD_SYSTEM_ATTRIBUTE_SIMILARITY_PRD_2026-04-02.md`
|
|
||||||
- `docs/design/EQUIPMENT_BUILD_AND_FORGE_LOOP_SYSTEM_DESIGN.md`
|
|
||||||
|
|
||||||
## 结论速览
|
|
||||||
|
|
||||||
### 1. 物品生成系统
|
|
||||||
|
|
||||||
当前状态可以判定为:**主链已补齐落地**。
|
|
||||||
|
|
||||||
已经落地的是:
|
|
||||||
|
|
||||||
- 有独立的运行时上下文层、导演层、本地编译层、叙事回写层。
|
|
||||||
- 宝藏、怪物掉落、通用 NPC 商店已经能走统一的 runtime item director。
|
|
||||||
- 永久 build 标签物品、限时 build buff 物品、少量数值物品三种骨架都已经能编译出来,并接进现有背包 / 装备 / build 结算。
|
|
||||||
|
|
||||||
本轮已补齐的是:
|
|
||||||
|
|
||||||
- 新增了 runtime item AI 意图导演与 prompt,并接入 NPC 帮助奖励主链,失败时自动回退到本地导演。
|
|
||||||
- NPC 交易库存改成按玩家当前 build 生成,并通过 `tradeStockSignature` 只在 build 变化时刷新。
|
|
||||||
- NPC 帮助奖励、委托奖励已经统一接入 runtime item director。
|
|
||||||
- 怪物掉落已经改成“基础掉落 + 语义掉落”双层叠加。
|
|
||||||
|
|
||||||
### 2. Build 标签系统
|
|
||||||
|
|
||||||
当前状态可以判定为:**核心已按 PRD 落地,且实现范围比 PRD 更大**。
|
|
||||||
|
|
||||||
已经落地的是:
|
|
||||||
|
|
||||||
- `BuildTagDefinition.attributeAffinity` 已扩展。
|
|
||||||
- `buildDamage.ts` 已改成“标签分别匹配角色属性画像”的加法模型,不再做标签两两网络效应。
|
|
||||||
- Buff / 角色固有 / 武器 / 护甲 / 饰品 / 套装标签都能进入最终倍率。
|
|
||||||
- Character 面板已经切到“属性适配度”展示,并能拆出单标签的属性贡献明细。
|
|
||||||
- 有针对新公式的测试覆盖。
|
|
||||||
|
|
||||||
还存在的尾项主要是:
|
|
||||||
|
|
||||||
- 实现用的是“世界属性 schema 六轴模型”,不是 PRD 文字里的固定四维属性。
|
|
||||||
- 旧的标签相似度辅助能力没有完全清干净,重铸仍在用 `getSimilarBuildTags` 做候选标签替换。
|
|
||||||
|
|
||||||
## 物品生成系统审计
|
|
||||||
|
|
||||||
| PRD 项 | 当前实现 | 判定 | 代码证据 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| 上下文采样层 | 已有 `buildRuntimeItemGenerationContext` / `buildQuestRuntimeItemGenerationContext`,会收集场景、遭遇、关联 NPC、最近剧情、玩家 build 标签与 build gap。 | 已落地 | `src/data/runtimeItemContext.ts:157-188`、`src/data/runtimeItemContext.ts:191-252` |
|
|
||||||
| AI 意图层 | 已新增 `runtimeItemAiDirector` / `runtimeItemAiPrompt`,`buildRuntimeItemAiPromptInput` 已进入真实 prompt 组装;NPC 帮助奖励主链会先请求 AI 物品意图,再回落到本地意图导演。 | 已补齐 | `src/services/runtimeItemAiPrompt.ts`、`src/services/runtimeItemAiDirector.ts`、`src/data/runtimeItemDirector.ts`、`src/hooks/story/npcEncounterActions.ts` |
|
|
||||||
| 本地编译层 | 已按 channel / slot / permanence 做 rarity 与预算编译,并产出 `statProfile`、`useProfile.buildBuffs`、`buildProfile`、`runtimeMetadata`。 | 已落地 | `src/data/runtimeItemCompiler.ts:77-101`、`src/data/runtimeItemCompiler.ts:122-206`、`src/data/runtimeItemCompiler.ts:245-276` |
|
|
||||||
| 叙事回写层 | 会把锚点、来源理由、build 倾向回写进物品名和描述。 | 已落地 | `src/data/runtimeItemNarrative.ts:171-189` |
|
|
||||||
| 永久标签 / 限时标签 / 少量数值三类物品 | 永久物品走 `buildProfile`,限时物品走 `useProfile.buildBuffs`,并可附带少量数值。 | 已落地 | `src/data/runtimeItemCompiler.ts:104-155`、`src/data/runtimeItemCompiler.ts:157-206` |
|
|
||||||
| 宝藏入口 | 宝藏奖励已经走 `buildRuntimeItemGenerationContext + buildDirectedRuntimeReward`,并把结果写回 story hint。 | 已落地 | `src/data/treasureInteractions.ts:52-89`、`src/hooks/useTreasureFlow.ts:45-75` |
|
|
||||||
| NPC 交易入口 | 通用 NPC 商店已改成按玩家当前 build 生成;初始 NPC 状态、交易模态打开时刷新、场景预览读取都会带上完整 `GameState`,并用 `tradeStockSignature` 避免无意义重刷。角色型 NPC 仍保留既有角色装备/背包模板。 | 已补齐 | `src/data/npcInteractions.ts`、`src/hooks/story/npcInteraction.ts`、`src/hooks/useStoryGeneration.ts`、`src/components/NpcModals.tsx` |
|
|
||||||
| NPC 帮助 / 关系奖励 | `npc_reward` 已真正接入主链。帮助奖励现在会生成带 `runtimeMetadata` 的 runtime item,并保留数值恢复、冷却缩减与 story hint。 | 已补齐 | `src/data/npcInteractions.ts`、`src/hooks/story/npcEncounterActions.ts` |
|
|
||||||
| 委托奖励入口 | Quest reward 已改为走 runtime director,支持 `buildQuestRuntimeItemGenerationContext`,领取时发放的是 runtime item。主 NPC 接任务流程也已切到 `generateQuestForNpcEncounter`。 | 已补齐 | `src/data/questFlow.ts`、`src/hooks/story/npcEncounterActions.ts`、`src/services/questDirector.ts` |
|
|
||||||
| 怪物掉落双层设计 | 普通世界掉落已改成预设 `lootTable` 基础掉落与 `monster_drop` runtime 语义掉落并存,不再互相覆盖。 | 已补齐 | `src/data/hostileNpcPresets.ts`、`src/data/hostileNpcPresets.test.ts` |
|
|
||||||
|
|
||||||
### 物品系统的具体判断
|
|
||||||
|
|
||||||
#### 已经明显符合 PRD 的部分
|
|
||||||
|
|
||||||
1. 系统分层已经形成。
|
|
||||||
`runtimeItemContext -> runtimeItemDirector -> runtimeItemCompiler -> runtimeItemNarrative` 这条链已经非常接近 PRD 里“上下文采样 / AI 意图 / 本地编译 / 叙事回写”的结构。
|
|
||||||
|
|
||||||
2. build 导向优先于纯数值。
|
|
||||||
`buildRuntimeItemContext` 会先算 `playerBuildTags` 和 `playerBuildGaps`,导演层再优先把 gap tag 与现有 build tag 拼进 `targetBuildDirection`,编译层才决定数值预算。
|
|
||||||
|
|
||||||
3. 奖励已经进入真实玩法结算。
|
|
||||||
runtime item 生成出的 `buildProfile` 会进入装备 build 结算,`useProfile.buildBuffs` 会在使用物品时写入 `activeBuildBuffs`。
|
|
||||||
|
|
||||||
#### 本轮补齐后的说明
|
|
||||||
|
|
||||||
1. 运行时物品意图已经进入真实主链。
|
|
||||||
当前至少在 NPC 帮助奖励链路中,已经先走 AI 意图导演,再走本地编译与叙事回写;如果模型不可用,会回退到既有启发式导演,保证玩法不断。
|
|
||||||
|
|
||||||
2. 交易库存已经真正读取玩家当前构筑。
|
|
||||||
通用交易 NPC 的库存会基于玩家当前 build、装备标签和 build gap 生成,而不是继续依赖 NPC 自身偏好标签。
|
|
||||||
|
|
||||||
3. 帮助奖励、委托奖励、怪物掉落都已并入统一 runtime director。
|
|
||||||
现在三条链路都能产出带 relation anchor / source reason / runtime metadata 的 runtime item。
|
|
||||||
|
|
||||||
## Build 标签系统审计
|
|
||||||
|
|
||||||
| PRD 项 | 当前实现 | 判定 | 代码证据 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `BuildTagDefinition.attributeAffinity` 扩展 | 类型已扩展,标签注册表也会为每个标签注入 affinity。 | 已落地 | `src/types/build.ts:6-13`、`src/data/buildTags.ts:66-69` |
|
|
||||||
| 静态标签亲和度表 | 已有 `buildTagAttributeAffinity.ts`,提供标签到属性轴的静态 affinity 表。 | 已落地 | `src/data/buildTagAttributeAffinity.ts:127-183` |
|
|
||||||
| 从“标签互相影响”改为“标签分别匹配角色属性” | `buildDamage.ts` 已按单标签计算 `fitScore`、`bonusDelta` 和属性贡献,最后做加法累积,不再做 pair/cluster 乘法网络。 | 已落地 | `src/data/buildDamage.ts:236-318` |
|
|
||||||
| 来源系数 | Buff / 角色 / 武器 / 护甲 / 饰品 / 套装都有独立 source coefficient,和 PRD 基本一致。 | 已落地 | `src/data/buildDamage.ts:95-114` |
|
|
||||||
| 最终伤害接入 | `resolvePlayerOutgoingDamage` / `resolveCompanionOutgoingDamage` / `resolveMonsterOutgoingDamage` 都已接 `buildDamageMultiplier`。 | 已落地 | `src/data/buildDamage.ts:498-542` |
|
|
||||||
| 展示层从“标签协同”改成“属性适配度” | Character 面板已经按标签展示 bonus、来源、主导属性,并能打开明细弹窗。 | 已落地 | `src/components/CharacterPanel.tsx:205-247`、`src/components/CharacterPanel.tsx:497-539` |
|
|
||||||
| 验收测试 | 已覆盖单标签可拆分、删一个标签不重算其余标签、不同属性角色用同一套装倍率不同、Buff/套装来源正常进入等场景。 | 已落地 | `src/data/buildDamage.test.ts:125-313` |
|
|
||||||
| 四维属性口径 | PRD 写的是四维固定属性;实现已经提升为 world schema 六轴模型。目标一致,但口径不再一一对应。 | 已落地,但实现已外延扩展 | `src/types/attributes.ts:3-15`、`src/data/worldAttributeSchemas.ts:4-155` |
|
|
||||||
| 旧标签相似度清理 | 核心伤害结算已不再依赖旧矩阵,但 `getSimilarBuildTags` 仍存在,重铸继续用它挑候选标签,`generate:build-tags` 脚本也还保留。 | 收尾未完成 | `src/data/buildTags.ts:183-215`、`src/data/forgeSystem.ts:371-399`、`package.json:21-24` |
|
|
||||||
|
|
||||||
### Build 系统的具体判断
|
|
||||||
|
|
||||||
#### 已经符合 PRD 核心目标的部分
|
|
||||||
|
|
||||||
1. 可解释性已经建立。
|
|
||||||
现在每个标签都有自己的 `fitScore`、`attributeContributions` 和 `attributeModifierDeltas`,玩家可以看到“这个标签为什么强、强在哪条属性轴上”。
|
|
||||||
|
|
||||||
2. 新增标签不会反向扰动旧标签贡献。
|
|
||||||
`buildDamage.test.ts` 已专门验证“删掉一个标签,只会移除它自己的 row,不会重算其他 row”。
|
|
||||||
|
|
||||||
3. 套装标签、Buff 标签、装备标签都能统一进入同一公式。
|
|
||||||
这点和 PRD 的来源规则一致,且测试已经覆盖。
|
|
||||||
|
|
||||||
#### 需要注意但不构成核心未落地的问题
|
|
||||||
|
|
||||||
1. 实现已经超出 PRD 的四维设计。
|
|
||||||
现在不是固定 `strength / agility / intelligence / spirit` 四维,而是通过 `WorldAttributeSchema` 映射成武侠/仙侠/自定义世界都能共用的语义属性轴。这更适合当前仓库的世界化属性系统,但也意味着 PRD 文案需要同步。
|
|
||||||
|
|
||||||
2. 旧相似度能力没有完全退场。
|
|
||||||
核心伤害结算已经不用标签两两矩阵,但锻造重铸仍然会根据标签相似度挑换洗标签,所以“旧体系相关命名/脚本”还没彻底收尾。
|
|
||||||
|
|
||||||
## 综合判断
|
|
||||||
|
|
||||||
如果按“是否已经把 PRD 的主战场落到代码里”来判断:
|
|
||||||
|
|
||||||
- **Build 标签系统:可以认为已经落地。**
|
|
||||||
- **物品生成系统:可以认为主链已经落地,之前审计出的缺口已在本轮补齐。**
|
|
||||||
|
|
||||||
如果接下来继续做收尾,更建议做的是:
|
|
||||||
|
|
||||||
1. 把 runtime item AI 意图层继续扩到宝藏、怪物掉落、委托奖励以外的更多同步入口,减少启发式 fallback 的覆盖面。
|
|
||||||
2. 给 Build 系统补一版文档同步,明确当前实现已经从 PRD 四维模型升级为世界属性 schema 模型,并清理剩余旧相似度脚本/命名。
|
|
||||||
3. 评估 `origin: 'ai_compiled'` 的语义是否还要细分成“AI 意图 + 本地编译”与“纯本地 fallback”两档,方便后续观测与埋点。
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# 审计与复盘
|
|
||||||
|
|
||||||
这一组文档聚焦“当前状态是否健康、问题在哪里、和目标设计差多少”。
|
|
||||||
|
|
||||||
## 系列总览
|
|
||||||
|
|
||||||
- [engineering/README.md](./engineering/README.md):当前工程优化审查与历史结论聚合入口。
|
|
||||||
- [text/README.md](./text/README.md):文本、英文残留、乱码审计系列的融合入口。
|
|
||||||
|
|
||||||
## 专项审计
|
|
||||||
|
|
||||||
- [FUNCTION_DESIGN_AUDIT_2026-04-03.md](./FUNCTION_DESIGN_AUDIT_2026-04-03.md):Function 体系分层、职责边界和当前结构问题。
|
|
||||||
- [FUNCTION_REQUIREMENT_COMPLETENESS_AUDIT_2026-04-14.md](./FUNCTION_REQUIREMENT_COMPLETENESS_AUDIT_2026-04-14.md):Function 相关文档需求与当前实现对齐核查。
|
|
||||||
- [FUNCTION_TEST_AUDIT_2026-04-14.md](./FUNCTION_TEST_AUDIT_2026-04-14.md):Function 运行时测试补充、已确认 bug 与当前验证结果。
|
|
||||||
- [FUNCTION_RUNTIME_FULL_TEST_AUDIT_2026-04-16.md](./FUNCTION_RUNTIME_FULL_TEST_AUDIT_2026-04-16.md):Function 运行时完整测试、服务端承接验证与当前门禁缺口。
|
|
||||||
- [ITEM_AND_BUILD_PRD_AUDIT_2026-04-05.md](./ITEM_AND_BUILD_PRD_AUDIT_2026-04-05.md):物品生成与 Build 标签系统对 PRD 的落地情况。
|
|
||||||
- [CUSTOM_WORLD_CREATOR_TOOL_AUDIT_2026-04-08.md](./CUSTOM_WORLD_CREATOR_TOOL_AUDIT_2026-04-08.md):自定义世界创作工具当前问题、体验断层和优化优先级审计。
|
|
||||||
- [AGENT_TO_DRAFT_TO_WORLD_PIPELINE_AUDIT_2026-04-20.md](./AGENT_TO_DRAFT_TO_WORLD_PIPELINE_AUDIT_2026-04-20.md):Agent 聊天、草稿生成、作品库存储与进入世界之间的断点、多 pipeline、冗余与未实装项审计。
|
|
||||||
- [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` 工程清理审计的当前仓库复核,区分已完成项、仍存边界问题和新的热点迁移。
|
|
||||||
- [engineering/ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-19.md](./engineering/ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-19.md):未引用垃圾、旧入口残留、前后端双份真相与后端迁移项的专项审计。
|
|
||||||
|
|
||||||
## 推荐使用方式
|
|
||||||
|
|
||||||
1. 先读系列总览,确认“最新结论在哪一份”。
|
|
||||||
2. 再按需要进入具体日期文档,查看当时的证据和上下文。
|
|
||||||
3. 做方案设计前,优先把对应审计文档看完,避免重复踩已知问题。
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# RPG 复活后继续冒险链路修复记录 2026-04-28
|
|
||||||
|
|
||||||
## 问题现象
|
|
||||||
|
|
||||||
RPG 运行态里,角色在战斗死亡并复活后,面板会显示一个“继续前进”入口。
|
|
||||||
|
|
||||||
此前这一步只有 `story_continue_adventure` 控制项,没有同步挂出复活后首场景应该展示的 `deferredOptions`。因此玩家点击继续后,系统会把它当作一次普通剧情续推入口,而不是单纯展示“复活后的下一批可选动作”。
|
|
||||||
|
|
||||||
在带有场景章节主 NPC 的自定义世界里,这会让玩家看起来像是“刚复活就直接和对面主 NPC 聊天”,造成复活后第一拍体验被主 NPC 对话链抢走。
|
|
||||||
|
|
||||||
## 根因结论
|
|
||||||
|
|
||||||
根因不在 NPC 聊天函数本身,而在死亡复活链没有沿用 `story_continue_adventure -> deferredOptions` 的延迟展示协议。
|
|
||||||
|
|
||||||
- 战后胜利链已经使用 `story_continue_adventure + deferredOptions`
|
|
||||||
- 死亡复活链此前只保留了 `story_continue_adventure`
|
|
||||||
- 结果是“继续前进”点击后无法走纯展示分支,只能落回普通续推
|
|
||||||
|
|
||||||
## 本次修复
|
|
||||||
|
|
||||||
本次在 `src/hooks/rpg-runtime-story/postBattleFlow.ts` 与复活调用链中补齐:
|
|
||||||
|
|
||||||
- `buildDeathStory(...)` 现在支持在复活文案上同步挂出 `deferredOptions`
|
|
||||||
- 这些 `deferredOptions` 复用 `buildFallbackStoryForState(...)` 产出的复活后可用入口
|
|
||||||
- 点击“继续前进”时只揭示这些入口,不再额外触发一次普通剧情推演
|
|
||||||
|
|
||||||
## 当前行为规则
|
|
||||||
|
|
||||||
角色死亡复活后:
|
|
||||||
|
|
||||||
1. 先显示“你在战斗中倒下,随后重新醒来”
|
|
||||||
2. 面板只展示一个 `story_continue_adventure`
|
|
||||||
3. 点击后展示复活后首场景已有的后续动作
|
|
||||||
4. 不应直接自动推进到主 NPC 聊天执行态
|
|
||||||
|
|
||||||
## 回归覆盖
|
|
||||||
|
|
||||||
已补两条测试:
|
|
||||||
|
|
||||||
- `src/hooks/rpg-runtime-story/choiceActions.test.ts`
|
|
||||||
- 覆盖本地战斗失败后的复活链
|
|
||||||
- `src/hooks/rpg-runtime-story/storyChoiceRuntime.test.ts`
|
|
||||||
- 覆盖服务端战斗失败后的复活链
|
|
||||||
|
|
||||||
两条测试都要求复活文案返回:
|
|
||||||
|
|
||||||
- `story_continue_adventure`
|
|
||||||
- 非空 `deferredOptions`
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# RPG 运行时直读世界草稿 Profile 检查 2026-04-25
|
|
||||||
|
|
||||||
## 结论
|
|
||||||
|
|
||||||
RPG 运行时进入游戏时不应再通过 `resultPreview.preview` 或 legacy runtime profile 做中间转换,主数据源统一为 Agent session 的 `draftProfile`。
|
|
||||||
|
|
||||||
本次检查确认:
|
|
||||||
|
|
||||||
1. Rust 侧 `custom_world_foundation_draft` 已直接产出 `draftProfile`。
|
|
||||||
2. 前端原先 `buildCustomWorldProfileFromAgentSession()` 仍只读取 `session.resultPreview.preview`,这会绕过草稿 profile 中已经存在的角色形象、关系、压力等字段。
|
|
||||||
3. 角色选择页与游戏内角色本身可以消费 `CustomWorldProfile.playableNpcs[].imageSrc`,断点在“session -> profile”的入口,而不是角色选择页。
|
|
||||||
4. “进入世界”按钮原先还会先执行 `sync_result_profile`,把当前结果页旧快照再同步回 session;如果结果页 profile 没有最新角色图,会在进入角色选择页前覆盖掉 `draftProfile` 中的正确形象。
|
|
||||||
|
|
||||||
## 已修正
|
|
||||||
|
|
||||||
- `buildCustomWorldProfileFromAgentSession()` 改为直接归一化 `session.draftProfile`。
|
|
||||||
- `resultPreview` 只保留为发布质量、blocker、预览外壳信息,不再作为进入游戏 profile 的数据源。
|
|
||||||
- Agent 草稿结果进入游戏时直接使用最新 `agentSessionProfile`,不再把当前结果页 profile 回写成新的运行时 profile。
|
|
||||||
- 前端 `normalizeCustomWorldProfileRecord()` 补齐 rs 草稿角色字段兼容:
|
|
||||||
- `publicMask/publicIdentity` -> `description/visualDescription/personality` fallback
|
|
||||||
- `currentPressure/hiddenHook` -> `backstory/actionDescription/sceneVisualDescription` fallback
|
|
||||||
- `relationToPlayer` -> `motivation/relationshipHooks` fallback
|
|
||||||
- `imageSrc/generatedVisualAssetId/generatedAnimationSetId/animationMap` 保持直通
|
|
||||||
|
|
||||||
## 后续约束
|
|
||||||
|
|
||||||
- 新 RPG 运行时链路只允许读取 `draftProfile`。
|
|
||||||
- 不再为进入游戏构造额外 legacy profile,也不再把 `resultPreview.preview` 当作运行时真相源。
|
|
||||||
- 如果草稿中新增角色、场景、物品字段,应优先扩展 `draftProfile` 的归一化读取,而不是增加中间转换结构。
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
# RPG 世界草稿编辑后被旧设定覆盖问题审计 2026-04-28
|
|
||||||
|
|
||||||
## 1. 问题摘要
|
|
||||||
|
|
||||||
当前 RPG 世界草稿结果页存在一条明显的“本地编辑态与 Agent session 真相源分叉”问题链:
|
|
||||||
|
|
||||||
1. 用户在结果页编辑世界设定时,前端只更新本地 `generatedCustomWorldProfile`。
|
|
||||||
2. Agent 草稿会话里的 `draftProfile / resultPreview.preview` 并不会随着这次编辑同步更新。
|
|
||||||
3. 自动保存触发时,系统又会优先重新拉取 session 最新快照,并以 session 返回的 profile 作为最终保存内容。
|
|
||||||
4. 如果 session 里仍是编辑前旧设定,那么前端刚刚改过的内容就会被旧快照覆盖回来。
|
|
||||||
|
|
||||||
所以,这个问题表面看起来像“自动保存覆盖了我刚保存的内容”,本质上是:
|
|
||||||
|
|
||||||
- 结果页编辑写到了前端内存态;
|
|
||||||
- 自动保存落库前又改为优先相信 session 真相;
|
|
||||||
- 但 session 真相本身没有承接这次编辑。
|
|
||||||
|
|
||||||
## 2. 当前主链证据
|
|
||||||
|
|
||||||
### 2.1 结果页编辑只改前端本地 profile
|
|
||||||
|
|
||||||
`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx:2637-2640`
|
|
||||||
|
|
||||||
- `RpgCreationResultView` 的 `onProfileChange` 只调用:
|
|
||||||
- `sessionController.setGeneratedCustomWorldProfile(normalizeAgentBackedProfile(profile))`
|
|
||||||
- 这里没有触发任何 `update_draft_card`、`sync_result_profile` 或其它后端写回动作。
|
|
||||||
|
|
||||||
这意味着结果页里的“保存修改”目前只是更新前端内存中的 `generatedCustomWorldProfile`。
|
|
||||||
|
|
||||||
### 2.2 结果页展示数据优先来自 session 预览
|
|
||||||
|
|
||||||
`src/services/rpg-creation/rpgCreationPreviewAdapter.ts:27-33`
|
|
||||||
|
|
||||||
- `buildCustomWorldProfileFromAgentSession()` 当前优先读取:
|
|
||||||
- `session.resultPreview.preview`
|
|
||||||
- 若没有,再回退到:
|
|
||||||
- `session.draftProfile.legacyResultProfile`
|
|
||||||
|
|
||||||
也就是说,Agent 结果页最终重新打开、重新同步或重新计算时,主数据源仍是 session 快照,而不是用户刚改过的前端本地对象。
|
|
||||||
|
|
||||||
### 2.3 自动保存前会先刷新 session,并优先保存 session 返回的最新结果
|
|
||||||
|
|
||||||
`src/components/rpg-entry/useRpgCreationResultAutosave.ts:181-236`
|
|
||||||
|
|
||||||
- `syncAgentDraftResultProfile()` 在 session 与前端 profile 签名不一致时,不再把前端 profile 回写 session。
|
|
||||||
- 它只会执行:
|
|
||||||
- `syncAgentSessionSnapshot(activeAgentSessionId)`
|
|
||||||
- 然后把拉回来的 session profile 重新塞回:
|
|
||||||
- `setGeneratedCustomWorldProfile(latestProfile)`
|
|
||||||
|
|
||||||
`src/components/rpg-entry/useRpgCreationResultAutosave.ts:326-340`
|
|
||||||
|
|
||||||
- 自动保存延迟触发后,如果当前是 Agent 草稿结果页:
|
|
||||||
1. 先调用 `syncAgentDraftResultProfile(profileToSave)`
|
|
||||||
2. 再把 `syncedResult.profile ?? profileToSave` 作为最终要入库的 profile
|
|
||||||
|
|
||||||
也就是:
|
|
||||||
|
|
||||||
- 自动保存不是直接保存用户刚改的前端 profile;
|
|
||||||
- 它会先“向 session 对齐”;
|
|
||||||
- 对齐后优先保存 session 返回的新 profile。
|
|
||||||
|
|
||||||
如果 session 里还是旧设定,那么保存和界面都会一起回滚到旧设定。
|
|
||||||
|
|
||||||
## 3. 当前测试口径也在强化这条行为
|
|
||||||
|
|
||||||
### 3.1 单测明确要求“不触发 sync_result_profile,只保存 session 最新草稿”
|
|
||||||
|
|
||||||
`src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx:195-276`
|
|
||||||
|
|
||||||
这条测试验证的是:
|
|
||||||
|
|
||||||
1. 前端当前持有的是 `oldProfile`
|
|
||||||
2. `syncAgentSessionSnapshot()` 返回的是 `latestSession`
|
|
||||||
3. 自动保存最终必须保存 `latestSession.draftProfile`
|
|
||||||
4. 并且明确断言不应触发 `sync_result_profile`
|
|
||||||
|
|
||||||
这说明现有实现不是偶然漏掉了 session 写回,而是被当前测试和实现共同锁定为:
|
|
||||||
|
|
||||||
- “自动保存只刷新 session,不回写前端结果页编辑态”
|
|
||||||
|
|
||||||
### 3.2 交互测试也要求“结果页自动保存优先保存 session 最新快照”
|
|
||||||
|
|
||||||
`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx:2867-3006`
|
|
||||||
|
|
||||||
这条测试进一步验证:
|
|
||||||
|
|
||||||
1. 结果页最终保存的对象应来自 `syncedSession.resultPreview.preview`
|
|
||||||
2. 不应触发 `sync_result_profile`
|
|
||||||
|
|
||||||
所以现在的覆盖行为不是单点 bug,而是当前结果页保存架构的直接产物。
|
|
||||||
|
|
||||||
## 4. 系统层面的根因拆解
|
|
||||||
|
|
||||||
### 4.1 双真相源并存
|
|
||||||
|
|
||||||
当前至少同时存在两份“看起来都像真相”的数据:
|
|
||||||
|
|
||||||
1. 前端结果页本地 `generatedCustomWorldProfile`
|
|
||||||
2. Agent session 内的 `draftProfile / resultPreview.preview`
|
|
||||||
|
|
||||||
结果页编辑写前者,自动保存与恢复又优先读后者,所以只要两边没有同事务同步,就一定会出现覆盖风险。
|
|
||||||
|
|
||||||
### 4.2 编辑动作没有接入正式后端写回动作
|
|
||||||
|
|
||||||
仓库契约和 Rust 模块实际上已经存在两条正式动作:
|
|
||||||
|
|
||||||
- `update_draft_card`
|
|
||||||
- `sync_result_profile`
|
|
||||||
|
|
||||||
证据:
|
|
||||||
|
|
||||||
- `packages/shared/src/contracts/rpgAgentActions.ts`
|
|
||||||
- `server-rs/crates/spacetime-module/src/custom_world/mod.rs`
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
- `update_draft_card` 适合基于卡片/section 的结构化修改写回 `draftProfile`
|
|
||||||
- `sync_result_profile` 适合把结果页完整 profile 回写 session 并重建 preview
|
|
||||||
|
|
||||||
但当前结果页 `onProfileChange` 没有接入这两条正式链路,导致“编辑成功”只停留在本地内存态。
|
|
||||||
|
|
||||||
### 4.3 自动保存策略与编辑链路不一致
|
|
||||||
|
|
||||||
自动保存的当前设计目标是正确的:
|
|
||||||
|
|
||||||
- 不再轻易把旧前端快照反向污染 session
|
|
||||||
- 优先保存 session 最新快照,避免把陈旧 preview 写进作品库
|
|
||||||
|
|
||||||
但前提应该是:
|
|
||||||
|
|
||||||
- 结果页编辑本身已经先进入 session 真相源
|
|
||||||
|
|
||||||
现在前提不成立,于是自动保存的“防旧快照污染”策略,反过来变成了“用 session 旧值覆盖用户刚编辑的新值”。
|
|
||||||
|
|
||||||
### 4.4 结果页定位混乱
|
|
||||||
|
|
||||||
当前结果页同时承担了两种角色:
|
|
||||||
|
|
||||||
1. 预览/发布页
|
|
||||||
2. 可深度编辑的草稿编辑器
|
|
||||||
|
|
||||||
但现有数据链路更偏向第 1 种:
|
|
||||||
|
|
||||||
- 数据来自 session preview
|
|
||||||
- 自动保存优先对齐 session preview
|
|
||||||
|
|
||||||
而 UI 行为却提供了第 2 种能力:
|
|
||||||
|
|
||||||
- 用户可以直接改世界、角色、场景等内容
|
|
||||||
|
|
||||||
两种定位没有统一,最终表现就是“能编辑,但编辑不能稳定进入主真相源”。
|
|
||||||
|
|
||||||
## 5. 用户视角下的实际故障表现
|
|
||||||
|
|
||||||
从用户体感看,当前问题会表现为以下几种:
|
|
||||||
|
|
||||||
1. 刚改完字段,短暂显示新内容,随后自动回弹为旧内容。
|
|
||||||
2. 页面提示“已自动保存”,但重新进入草稿页后仍然是旧设定。
|
|
||||||
3. 某些字段改了能停留一会儿,触发自动保存或 session 刷新后又被覆盖。
|
|
||||||
4. 用户会误以为“保存按钮坏了”或“自动保存有缓存问题”,但真实原因是保存目标和展示真相源不一致。
|
|
||||||
|
|
||||||
## 6. 影响范围
|
|
||||||
|
|
||||||
这个问题不只影响“世界简介文本”,而是整个结果页编辑体系:
|
|
||||||
|
|
||||||
1. 世界基础信息编辑
|
|
||||||
2. 角色编辑
|
|
||||||
3. 场景编辑
|
|
||||||
4. 封面/派生内容依赖当前 profile 的场景
|
|
||||||
5. 结果页返回、恢复、自动打开、发布前预览一致性
|
|
||||||
|
|
||||||
只要编辑发生在 `generatedCustomWorldProfile`,但没有进入 session 真相源,就都有同类风险。
|
|
||||||
|
|
||||||
## 7. 建议修复方向
|
|
||||||
|
|
||||||
### 7.1 第一优先级:统一结果页编辑的唯一真相源
|
|
||||||
|
|
||||||
推荐二选一,不要继续混用:
|
|
||||||
|
|
||||||
1. 结果页只做预览,不允许直接编辑复杂设定
|
|
||||||
2. 结果页继续允许编辑,但每次保存必须先落到 session 真相源,再更新本地显示
|
|
||||||
|
|
||||||
按当前产品形态,更合理的是第 2 条。
|
|
||||||
|
|
||||||
### 7.2 如果保留结果页编辑,建议采用的正式链路
|
|
||||||
|
|
||||||
1. 世界/角色/场景编辑保存时,优先调用 Rust/SpacetimeDB 的正式动作写回 session。
|
|
||||||
2. 能用 `update_draft_card` 的地方尽量走结构化 section 更新。
|
|
||||||
3. 对无法被 card section 覆盖的完整 profile 级编辑,再评估是否保留 `sync_result_profile`,或新增更细粒度 reducer。
|
|
||||||
4. session 写回完成后,再重新读取 session 并刷新结果页。
|
|
||||||
5. 自动保存只负责“把已经写进 session 的最新真相同步到作品库”,不要再承担“猜测该保存前端还是 session”的职责。
|
|
||||||
|
|
||||||
### 7.3 自动保存策略需要降级为“作品库存档层”,不要兼任“session 真相协调层”
|
|
||||||
|
|
||||||
当前自动保存同时承担:
|
|
||||||
|
|
||||||
1. 防止旧前端快照污染 session
|
|
||||||
2. 选择最终保存哪份 profile
|
|
||||||
3. 刷新结果页显示
|
|
||||||
|
|
||||||
职责太重,也导致覆盖问题难以定位。
|
|
||||||
|
|
||||||
建议改为:
|
|
||||||
|
|
||||||
1. 编辑保存阶段:负责写 session
|
|
||||||
2. 自动保存阶段:只负责把 session 已确认真相落作品库
|
|
||||||
3. 结果页渲染阶段:只读 session 最新快照
|
|
||||||
|
|
||||||
### 7.4 需要补的测试口径
|
|
||||||
|
|
||||||
当前测试主要在保护“不要再触发 `sync_result_profile` 污染 session”。
|
|
||||||
|
|
||||||
后续修复后,至少要补以下测试:
|
|
||||||
|
|
||||||
1. 用户编辑世界基础字段后,session `draftProfile / resultPreview` 会同步更新。
|
|
||||||
2. 自动保存不会把 session 旧值覆盖到刚编辑的新值上。
|
|
||||||
3. 刷新页面或重新进入结果页后,看到的是编辑后的新设定。
|
|
||||||
4. 进入世界、发布世界、继续扩展时,消费的是同一份最新 session 真相。
|
|
||||||
|
|
||||||
## 8. 结论
|
|
||||||
|
|
||||||
这次问题的核心结论是:
|
|
||||||
|
|
||||||
1. 你的判断“像是自动保存问题”是对的,但更准确地说,是“自动保存对齐 session 时,覆盖了未写回 session 的本地编辑态”。
|
|
||||||
2. 当前结果页编辑没有接上正式的 session 写回链路,这是第一根断点。
|
|
||||||
3. 当前自动保存被设计成优先信任 session 最新快照,这是第二根断点。
|
|
||||||
4. 两个断点叠加后,就形成了“编辑后又自动变回原设定”的现象。
|
|
||||||
|
|
||||||
如果后续要真正修掉这个问题,重点不该是单独调 debounce 时间或加一个“防抖保存中”提示,而是:
|
|
||||||
|
|
||||||
- 把结果页编辑动作重新接回 Rust/SpacetimeDB 的 session 真相源;
|
|
||||||
- 让自动保存只负责作品库存档,不再替代编辑写回链路做裁决。
|
|
||||||
@@ -1,377 +0,0 @@
|
|||||||
# 安全漏洞扫描报告 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。
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# 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` 与编译 / 单测已通过。
|
|
||||||
- 若接口路由或测试名称后续调整,需要同步更新本门禁脚本与报告模板。
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 45 KiB |
@@ -1,369 +0,0 @@
|
|||||||
# 当前工程优化点盘点(2026-04-20)
|
|
||||||
|
|
||||||
更新时间:`2026-04-20`
|
|
||||||
|
|
||||||
## 0. 盘点目标
|
|
||||||
|
|
||||||
这份文档用于回答一个更直接的问题:
|
|
||||||
|
|
||||||
**基于当前仓库状态,接下来最值得投入工程时间的优化点是什么。**
|
|
||||||
|
|
||||||
本轮只做文档盘点,不直接修改业务代码;结论同时参考了当前工作区现状。
|
|
||||||
需要注意,仓库当前存在一批未提交改动,尤其集中在 `custom world`、`assets`、`platform shell` 相关模块,所以本文更强调“优先级与切入方式”,而不是要求做大范围整仓改写。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 当前快照
|
|
||||||
|
|
||||||
## 1.1 本轮复核方式
|
|
||||||
|
|
||||||
本轮主要复核了以下内容:
|
|
||||||
|
|
||||||
1. 现有工程优化审计文档与目录索引
|
|
||||||
2. `package.json`、`vite.config.ts`、`.eslintrc.cjs` 等门禁脚本
|
|
||||||
3. 当前前端、后端、脚本目录的大文件热点
|
|
||||||
4. 运行时、鉴权、自定义世界、资产链路的边界实现
|
|
||||||
5. 当前 `typecheck / lint / build` 状态
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.2 当前门禁结果
|
|
||||||
|
|
||||||
| 项目 | 结果 | 当前判断 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `npm run typecheck` | 失败 | 当前第一优先级问题,类型基线已失真 |
|
|
||||||
| `npm run lint:eslint` | 失败 | `136` 个 error、`4` 个 warning,且 `95` 个可自动修复 |
|
|
||||||
| `npm run build` | 通过 | 发布链路未红,但体积压力仍明显存在 |
|
|
||||||
|
|
||||||
### 关键说明
|
|
||||||
|
|
||||||
当前状态和 `2026-04-10` 那轮“build warning 直接拦截”的状态不同:
|
|
||||||
|
|
||||||
1. **构建现在可以通过。**
|
|
||||||
2. **真正变成第一阻塞项的是 `typecheck` 与 `lint`。**
|
|
||||||
3. **构建虽然通过,但主包、功能包、CSS 体积依然偏重,说明性能类优化仍然值得做。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.3 当前热点文件快照
|
|
||||||
|
|
||||||
本轮按源码目录统计的大文件热点如下:
|
|
||||||
|
|
||||||
| 文件 | 当前行数 | 判断 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `6122` | 当前前端最大热点 |
|
|
||||||
| `server-node/src/app.test.ts` | `3568` | 后端测试聚合度过高 |
|
|
||||||
| `server-node/src/modules/assets/characterAssetRoutes.ts` | `2802` | 资产路由职责过重 |
|
|
||||||
| `src/services/ai.ts` | `2432` | 浏览器侧 AI 编排仍然偏重 |
|
|
||||||
| `server-node/src/modules/story/storyActionRoutes.test.ts` | `2402` | 运行时路由测试聚合度过高 |
|
|
||||||
| `src/data/npcInteractions.ts` | `2274` | NPC 规则数据仍然集中 |
|
|
||||||
| `src/prompts/storyPromptBuilders.ts` | `1728` | prompt 构造成为新的复杂度中心 |
|
|
||||||
| `server-node/src/modules/custom-world/runtimeProfile.ts` | `1623` | custom world runtime 编译热点 |
|
|
||||||
| `src/hooks/story/npcEncounterActions.ts` | `1582` | NPC 行动流仍然偏重 |
|
|
||||||
| `src/components/game-shell/PlatformHomeView.tsx` | `1474` | 平台首页壳层继续膨胀 |
|
|
||||||
| `src/components/game-shell/PreGameSelectionFlow.tsx` | `1418` | 前置选择流程职责过多 |
|
|
||||||
| `src/services/customWorld.ts` | `1383` | 自定义世界服务虽然收缩,但仍偏大 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 结论先行
|
|
||||||
|
|
||||||
当前仓库的优化重点,已经不是“继续清旧 Vite 插件链路”或者“继续讨论前后端是否要分离”。
|
|
||||||
|
|
||||||
更准确地说,当前最值得做的优化点已经收敛成四类:
|
|
||||||
|
|
||||||
1. **先恢复可信的工程基线。**
|
|
||||||
`typecheck` 与 `lint` 当前都是红线,继续扩功能会放大返工成本。
|
|
||||||
2. **拆掉正在持续膨胀的新热点。**
|
|
||||||
热点已经从早期运行时主链,迁移到 `custom world`、`asset routes`、`platform shell`、`prompt builders`。
|
|
||||||
3. **继续把前端退出“运行时真相”和“鉴权真相”。**
|
|
||||||
当前前端仍保留本地快照镜像与自动登录凭证持久化。
|
|
||||||
4. **补一轮入口归档,减少疑似孤岛模块和大测试聚合文件。**
|
|
||||||
这部分不一定最急,但会持续拉低仓库可维护性。
|
|
||||||
|
|
||||||
一句话判断:
|
|
||||||
|
|
||||||
**当前最值得投入的不是横向加功能,而是把质量门禁重新拉绿,再把 custom world / asset / platform 这批新复杂度中心拆开。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 优化点清单
|
|
||||||
|
|
||||||
## 3.1 P0:先恢复类型基线
|
|
||||||
|
|
||||||
这是当前最优先的工程优化点。
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
`npm run typecheck` 当前失败,主要问题集中在两类:
|
|
||||||
|
|
||||||
1. `CustomWorldCampScene` 结构漂移
|
|
||||||
- `src/components/CustomWorldEntityEditorModal.test.tsx`
|
|
||||||
- `src/data/customWorldLibrary.ts`
|
|
||||||
- `src/services/customWorld.ts`
|
|
||||||
- `src/services/customWorldCamp.ts`
|
|
||||||
2. 局部实现与类型定义不同步
|
|
||||||
- `src/components/auth/AccountModal.test.tsx` 的测试数据缺少新增字段
|
|
||||||
- `src/components/game-canvas/GameCanvasShared.tsx` 引用了未定义的 `DEFAULT_IMAGE_STYLE`
|
|
||||||
|
|
||||||
### 影响
|
|
||||||
|
|
||||||
1. 类型系统已经不能提供可信回归信号。
|
|
||||||
2. 自定义世界链路当前正在迭代,如果继续在红线状态叠加修改,后续会反复出现“改 A 崩 B”的情况。
|
|
||||||
3. 测试 fixture 与正式类型脱节,会让测试文件逐渐失去文档价值。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 先补一个统一的 `CustomWorldCampScene` 构造/归一化入口,禁止在多个文件里手写不完整字面量。
|
|
||||||
2. 把 `auth`、`custom world` 的测试 fixture 改成工厂函数,避免字段新增后多处漏改。
|
|
||||||
3. 单独清掉 `GameCanvasShared.tsx` 这类“编译即失败”的确定性问题,优先恢复 `typecheck` 绿色基线。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.2 P0:恢复 lint 可信度,区分机械问题和真实问题
|
|
||||||
|
|
||||||
这项和类型基线同级。
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
`npm run lint:eslint` 当前结果是:
|
|
||||||
|
|
||||||
- `136` 个 error
|
|
||||||
- `4` 个 warning
|
|
||||||
- 其中 `95` 个问题可自动修复
|
|
||||||
|
|
||||||
当前 lint 问题明显分成两层:
|
|
||||||
|
|
||||||
1. 机械问题
|
|
||||||
- import 排序
|
|
||||||
- export 排序
|
|
||||||
- 未使用导入
|
|
||||||
2. 真实问题
|
|
||||||
- `server-node/src/modules/inventory/inventoryStoryActionService.ts` 出现 React Hook 规则错误
|
|
||||||
- `server-node/src/migrate.ts` 仍触发 `no-console`
|
|
||||||
- `packages/shared/src/http.ts` 触发 `@typescript-eslint/ban-types`
|
|
||||||
- 若干文件存在真正未使用变量、转义和规则误配问题
|
|
||||||
|
|
||||||
### 影响
|
|
||||||
|
|
||||||
1. 当前 lint 信号噪音仍然较高,不利于 review。
|
|
||||||
2. 真实问题会被大量机械问题掩盖。
|
|
||||||
3. 团队会更倾向于跳过 lint,而不是信任 lint。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 先跑一轮仅机械修复的清理批次,优先吃掉 import sort、unused imports 这类低风险项。
|
|
||||||
2. 再单独处理 Hook 误用、共享契约类型、脚本规则豁免这类语义问题。
|
|
||||||
3. 之后把“自动可修复问题”与“必须人工处理的问题”拆成两个门禁视角,减少下次再次堆积。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.3 P1:拆 custom world / asset / platform 新热点
|
|
||||||
|
|
||||||
这是当前最有性价比的结构性优化点。
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
当前复杂度最高的业务热点,已经集中在这些模块:
|
|
||||||
|
|
||||||
1. `src/components/CustomWorldEntityEditorModal.tsx`
|
|
||||||
2. `server-node/src/modules/assets/characterAssetRoutes.ts`
|
|
||||||
3. `src/services/ai.ts`
|
|
||||||
4. `src/prompts/storyPromptBuilders.ts`
|
|
||||||
5. `server-node/src/modules/custom-world/runtimeProfile.ts`
|
|
||||||
6. `src/components/game-shell/PlatformHomeView.tsx`
|
|
||||||
7. `src/components/game-shell/PreGameSelectionFlow.tsx`
|
|
||||||
8. `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
|
|
||||||
### 问题本质
|
|
||||||
|
|
||||||
这些文件并不是单纯“代码多”,而是同时承载了多类职责:
|
|
||||||
|
|
||||||
1. UI 状态
|
|
||||||
2. 领域规则
|
|
||||||
3. 请求编排
|
|
||||||
4. 文本构造
|
|
||||||
5. 运行时映射
|
|
||||||
6. 面板切换与流程控制
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. `CustomWorldEntityEditorModal.tsx`
|
|
||||||
- 先按“实体列表/表单区/资源区/高级设置/预览区”拆组件
|
|
||||||
- 再把数据准备与提交编排抽成 hook
|
|
||||||
2. `characterAssetRoutes.ts`
|
|
||||||
- 拆成 route、prompt payload、job orchestration、产物发布、错误响应五层
|
|
||||||
3. `PlatformHomeView.tsx` 与 `PreGameSelectionFlow.tsx`
|
|
||||||
- 把页面壳层、数据加载、卡片渲染、弹层控制拆开
|
|
||||||
4. `storyPromptBuilders.ts` 与 `runtimeProfile.ts`
|
|
||||||
- 把“模板片段”“上下文归一化”“规则裁剪”“最终拼接”分层
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.4 P1:继续控制构建产物体积
|
|
||||||
|
|
||||||
构建虽通过,但体积已经给出明显信号。
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
本轮 `npm run build` 输出里,几个值得关注的点是:
|
|
||||||
|
|
||||||
1. `dist/assets/AuthenticatedApp-*.js`:`794.77 kB`
|
|
||||||
2. `dist/assets/index-*.js`:`197.44 kB`
|
|
||||||
3. `dist/assets/CustomWorldResultView-*.js`:`163.38 kB`
|
|
||||||
4. `dist/assets/ai-*.js`:`131.73 kB`
|
|
||||||
5. `dist/assets/PreGameSelectionFlow-*.js`:`96.39 kB`
|
|
||||||
6. `dist/assets/index-*.css`:`201.44 kB`
|
|
||||||
|
|
||||||
### 影响
|
|
||||||
|
|
||||||
1. 虽然还没触发新的 build gate 红线,但首屏、缓存和移动端体验会继续承压。
|
|
||||||
2. `AuthenticatedApp` 主包偏大,说明平台壳层仍然装入了过多首屏不必需能力。
|
|
||||||
3. CSS 体积继续上涨,说明样式正在跨模块相互堆叠。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 继续把 custom world、asset studio、平台详情页、角色资产工具从主壳层路径中抽离。
|
|
||||||
2. 审查 `ai.ts`、`custom world result view`、`pregame selection` 是否还能再延迟加载。
|
|
||||||
3. 对全局样式做一次按模块归属清理,减少公共样式无限增长。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.5 P1:继续收紧前端与后端边界
|
|
||||||
|
|
||||||
这项已经不是“要不要做”的问题,而是“还剩多少尾巴没收完”。
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
1. `src/services/apiClient.ts`
|
|
||||||
- 当前仍把 `access token`
|
|
||||||
- 自动登录用户名
|
|
||||||
- 自动登录密码
|
|
||||||
写入 `window.localStorage`
|
|
||||||
2. `src/hooks/story/runtimeStoryCoordinator.ts`
|
|
||||||
- 当前仍会在调用后端运行时前先 `putSaveSnapshot(...)`
|
|
||||||
- 响应后继续 `rehydrateSavedSnapshot(...)`
|
|
||||||
3. `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
- 当前仍从前端动作流触发 `generateQuestForNpcEncounter(...)`
|
|
||||||
- 说明 NPC 任务“换单/重抽”分支尚未完全后端化
|
|
||||||
|
|
||||||
### 影响
|
|
||||||
|
|
||||||
1. 前端仍保留了一部分运行时真相与鉴权真相。
|
|
||||||
2. 自动登录凭证持久化在边界和安全上都不理想。
|
|
||||||
3. 运行时快照前置写入,会让“前端镜像状态”和“后端会话状态”继续纠缠。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 优先移除自动登录用户名/密码本地持久化,收敛到服务端 session / refresh 机制。
|
|
||||||
2. 把运行时快照改为“展示缓存”而不是“提交前真相源”。
|
|
||||||
3. 把 NPC 任务更换动作补齐到后端 runtime/session 边界,不再由前端直接发起生成决策。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.6 P2:做一次疑似孤岛模块与旧入口归档
|
|
||||||
|
|
||||||
这项不一定最紧急,但现在做会明显降低后续维护噪音。
|
|
||||||
|
|
||||||
### 当前现象
|
|
||||||
|
|
||||||
从当前入口关系看,以下模块值得做一次正式复核:
|
|
||||||
|
|
||||||
1. `src/components/GameShell.tsx`
|
|
||||||
2. `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
3. `src/components/custom-world-home/CustomWorldCreationLauncherModal.tsx`
|
|
||||||
4. `src/components/custom-world-agent/CustomWorldAgentLauncherModal.tsx`
|
|
||||||
5. `src/components/custom-world-agent/CustomWorldAgentDraftDrawer.tsx`
|
|
||||||
6. `src/hooks/story/storyBootstrap.ts`
|
|
||||||
7. `src/hooks/useEquipmentFlow.ts`
|
|
||||||
8. `src/hooks/useForgeFlow.ts`
|
|
||||||
9. `src/hooks/useInventoryFlow.ts`
|
|
||||||
10. `src/services/typewriter.ts`
|
|
||||||
|
|
||||||
### 当前判断
|
|
||||||
|
|
||||||
这批模块不一定全部是垃圾代码,但至少说明一件事:
|
|
||||||
|
|
||||||
**仓库里仍然存在一批“不是正式入口、也没有清晰归档标签”的过渡实现。**
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
把这类模块统一分成三类:
|
|
||||||
|
|
||||||
1. 正式保留并接回入口
|
|
||||||
2. 明确标记为实验稿
|
|
||||||
3. 直接归档或删除
|
|
||||||
|
|
||||||
这样可以减少后续开发时的误判成本。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.7 P2:拆测试聚合文件,恢复测试的定位能力
|
|
||||||
|
|
||||||
当前测试文件也已经出现“大一统热点”。
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
1. `server-node/src/app.test.ts`:`3568` 行
|
|
||||||
2. `server-node/src/modules/story/storyActionRoutes.test.ts`:`2402` 行
|
|
||||||
3. `server-node/src/modules/assets/characterAssetRoutes.test.ts`:`1235` 行
|
|
||||||
4. `src/hooks/story/npcEncounterActions.test.ts`:`1199` 行
|
|
||||||
|
|
||||||
### 影响
|
|
||||||
|
|
||||||
1. 失败定位成本高。
|
|
||||||
2. fixture 复用差,字段一变容易整片测试跟着漂移。
|
|
||||||
3. 测试文件本身开始变成新的维护热点。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 按领域动作拆测试文件,而不是继续堆到单一总测文件中。
|
|
||||||
2. 补 fixture builder / factory,减少字面量散落。
|
|
||||||
3. 对 `runtime / auth / custom world / assets` 这几条链路增加更明确的契约测试分层。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 推荐执行顺序
|
|
||||||
|
|
||||||
如果只按工程收益排序,建议按下面的顺序推进:
|
|
||||||
|
|
||||||
1. 先修 `typecheck`
|
|
||||||
2. 再把 `lint` 分成机械修复和语义修复两轮
|
|
||||||
3. 然后拆 `custom world / asset / platform` 热点
|
|
||||||
4. 再继续收前端运行时与鉴权边界
|
|
||||||
5. 最后处理孤岛模块归档和测试拆分
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 当前不建议优先做的事
|
|
||||||
|
|
||||||
1. 不建议在 `typecheck` 和 `lint` 仍为红线时继续横向扩功能。
|
|
||||||
2. 不建议直接在 `CustomWorldEntityEditorModal.tsx`、`characterAssetRoutes.ts`、`PlatformHomeView.tsx` 里继续堆新逻辑。
|
|
||||||
3. 不建议把 bundle 体积问题简单理解为“先放宽阈值”,当前更适合继续拆职责和延迟加载。
|
|
||||||
4. 不建议在未确认入口关系前随手删除可疑旧模块,先做归档分类更稳。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 本文依据
|
|
||||||
|
|
||||||
文档依据:
|
|
||||||
|
|
||||||
1. `docs/audits/engineering/README.md`
|
|
||||||
2. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
|
||||||
3. `docs/audits/engineering/ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-20.md`
|
|
||||||
4. `docs/experience/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md`
|
|
||||||
|
|
||||||
当前仓库复核依据:
|
|
||||||
|
|
||||||
1. `package.json`
|
|
||||||
2. `.eslintrc.cjs`
|
|
||||||
3. `vite.config.ts`
|
|
||||||
4. `scripts/build-gate.mjs`
|
|
||||||
5. `src/App.tsx`
|
|
||||||
6. `src/services/apiClient.ts`
|
|
||||||
7. `src/hooks/story/runtimeStoryCoordinator.ts`
|
|
||||||
8. `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
9. 当前源码大文件体量扫描结果
|
|
||||||
10. `npm run typecheck`
|
|
||||||
11. `npm run lint:eslint`
|
|
||||||
12. `npm run build`
|
|
||||||
@@ -1,606 +0,0 @@
|
|||||||
# 工程清理与后端边界审计(2026-04-19)
|
|
||||||
|
|
||||||
更新时间:`2026-04-20`
|
|
||||||
|
|
||||||
## 0.1 执行回填(2026-04-19)
|
|
||||||
|
|
||||||
本文审计项 `3.2` 与 `4.4` 已于 `2026-04-19` 当日完成首轮处置:
|
|
||||||
|
|
||||||
1. 已删除 `scripts/dev-server/localApiPlugins.ts`
|
|
||||||
2. 已删除 `scripts/dev-server/characterAssetStudioPlugins.ts`
|
|
||||||
3. 已删除 `scripts/dev-server/qwenSpriteSheetToolPlugins.ts`
|
|
||||||
4. `scripts/dev-server/` 目录仅保留迁移说明,不再保留旧 Vite 本地 API 实现代码
|
|
||||||
5. 当前正式入口统一为 `scripts/dev-node.mjs + vite proxy + server-node/src/modules/**`
|
|
||||||
|
|
||||||
本文其余段落保留为本次审计时的原始问题快照,用于解释为什么要做这轮删除。
|
|
||||||
|
|
||||||
## 0.2 执行回填(2026-04-19,仓库噪音产物)
|
|
||||||
|
|
||||||
本文审计项 `3.1` 已于 `2026-04-19` 当日完成首轮处置:
|
|
||||||
|
|
||||||
1. 已从版本库删除以下根目录历史扫描/截图产物:
|
|
||||||
- `npc-editor-dom.html`
|
|
||||||
- `npc-editor-shot.png`
|
|
||||||
- `temp-write-check.txt`
|
|
||||||
- `tmp_character_presets_scan.txt`
|
|
||||||
- `tmp_jsx_text_scan.txt`
|
|
||||||
- `tmp_runtime_text_scan.txt`
|
|
||||||
- `tmp_text_candidates.txt`
|
|
||||||
- `tmp_text_candidates_refined.txt`
|
|
||||||
- `tmp_visible_props_scan.txt`
|
|
||||||
- `tmp_volc_seedance_doc.html`
|
|
||||||
2. 已从版本库删除 `scripts/__pycache__/generate-build-tag-similarity.cpython-313.pyc`。
|
|
||||||
3. 已清理本地工作区中的 `.codex-*.log`、`.preview.*`、`npc-editor-console.log` 与 `temp-build-goal-check/`,清理前对应体量约为:
|
|
||||||
- 根目录噪音文件 `60` 个,约 `49.94 MB`
|
|
||||||
- `temp-build-goal-check/` 共 `15620` 个条目,约 `158.85 MB`
|
|
||||||
4. 已补齐 `.gitignore`、`.prettierignore` 与 `.eslintrc.cjs` 的忽略口径,显式覆盖 `tmp_*`、`tmp/`、`npc-editor-*`、`temp-write-check.txt`、`temp-build-goal-check/`、`__pycache__/`。
|
|
||||||
5. `scripts/dev-server/localApiPlugins.ts` 之外的后端边界收口项不在本轮噪音清理范围内,后续继续按本文第二至第四阶段推进。
|
|
||||||
|
|
||||||
## 0.3 执行回填(2026-04-19,运行时边界第一轮收口)
|
|
||||||
|
|
||||||
本文审计项 `4.1` 与 `5.1` 已于 `2026-04-19` 当日完成一轮工程收口:
|
|
||||||
|
|
||||||
1. `RuntimeStoryOptionView` 现在由后端直接附带 `interaction` 元数据。
|
|
||||||
2. `server-node/src/modules/story/runtimeSession.ts` 已成为 runtime option interaction 的唯一构建位置。
|
|
||||||
3. `src/services/runtimeStoryService.ts` 不再根据 `currentEncounter + functionId` 在前端本地重建一份 interaction 映射。
|
|
||||||
4. `/api/custom-world/scene-image` 已补齐服务端 prompt 兜底组装能力,允许前端只提交 `profile + landmark + userPrompt` 上下文。
|
|
||||||
5. `src/services/aiService.ts` 的场景图 SDK 已改为直接调用后端接口,不再为了该链路动态加载 `src/services/ai.ts`。
|
|
||||||
|
|
||||||
## 0.4 执行回填(2026-04-19,自定义世界后端边界第二轮收口)
|
|
||||||
|
|
||||||
本文审计项 `5.2` 与“第三阶段第 4 条:清理 `server-node -> src/**` 的反向依赖”已于 `2026-04-19` 当日完成第二轮工程收口:
|
|
||||||
|
|
||||||
1. `server-node/src/modules/custom-world/` 已新增服务端自持 runtime 模块,承接:
|
|
||||||
- `creator intent` 归一化
|
|
||||||
- `anchorPack / lockState` 推导
|
|
||||||
- custom world framework/profile compile 与 normalize
|
|
||||||
2. `server-node/src/modules/ai/customWorldOrchestrator.ts` 与 `server-node/src/services/customWorldAgentFoundationDraftService.ts` 已不再运行时依赖:
|
|
||||||
- `src/services/customWorld.js`
|
|
||||||
- `src/services/customWorldBuilder.js`
|
|
||||||
- `src/services/customWorldCreatorIntent.js`
|
|
||||||
- `src/types.js`
|
|
||||||
3. `server-node/src/prompts/customWorldPrompts.ts` 已成为后端自持的 custom world prompt source,`scene image` 与 `foundation draft` 相关 builder 不再从前端 `src/prompts/customWorldPrompts.ts` 反向 import。
|
|
||||||
4. 本轮只迁移 prompt source 位置,没有改动任何 custom world 提示词正文,也没有改动功能需求。
|
|
||||||
|
|
||||||
## 0.5 执行回填(2026-04-20,NPC 待接委托正式接取收口)
|
|
||||||
|
|
||||||
本文审计项 `5.3` 已于 `2026-04-20` 完成一轮补充收口:
|
|
||||||
|
|
||||||
1. `src/hooks/story/npcEncounterActions.ts` 中“聊天里的待接委托正式接取”已不再由前端本地直接写入:
|
|
||||||
- `quests`
|
|
||||||
- `runtimeStats.questsAccepted`
|
|
||||||
- `npcChatState.pendingQuestOffer`
|
|
||||||
2. `server-node/src/modules/quest/questStoryActionService.ts` 现在会优先读取服务端快照里已保存的 `pendingQuestOffer.quest`,按当前聊天态中已经展示给玩家的那份委托完成正式接取。
|
|
||||||
3. `server-node/src/modules/story/storyActionService.ts` 已补齐待接委托接取后的聊天态投影:
|
|
||||||
- 保留 NPC 对话展示模式
|
|
||||||
- 清空 `pendingQuestOffer`
|
|
||||||
- 回到既有的三条自由追问建议
|
|
||||||
4. 本轮没有新增任何 runtime functionId,也没有改动任务生成提示词或任务需求,只是把既有“接任务”正式结算权收回到后端。
|
|
||||||
|
|
||||||
## 0.6 执行回填(2026-04-20,NPC 聊天任务草案与浏览器 LLM fallback 收口)
|
|
||||||
|
|
||||||
本文审计项 `5.1` 与 `5.3` 已于 `2026-04-20` 完成一轮补充收口:
|
|
||||||
|
|
||||||
1. `server-node/src/modules/ai/chatOrchestrator.ts` 现在会基于 `NPC chat turn` 的运行时上下文,在后端判断是否触发 `pendingQuestOffer`,并把 quest draft 与引导文案一并回填给前端。
|
|
||||||
2. `src/hooks/story/npcEncounterActions.ts` 不再在 NPC 单轮聊天完成后本地调用 `generateQuestForNpcEncounter(...)` 再决定是否挂出待接委托。
|
|
||||||
3. `src/services/questDirector.ts` 浏览器端在后端失败时不再退回本地 LLM 生成 quest draft,而是直接走 deterministic fallback compile。
|
|
||||||
4. `src/services/runtimeItemAiDirector.ts` 浏览器端在后端失败时不再退回本地 LLM 生成 runtime item intent,而是直接返回 deterministic fallback intents。
|
|
||||||
5. 本轮仍未改动任何业务提示词正文,也没有改动 quest / runtime item 的需求能力面,只是继续清理浏览器里的正式 AI orchestration 残留。
|
|
||||||
|
|
||||||
## 0. 审计目标
|
|
||||||
|
|
||||||
本次审计只回答四类问题:
|
|
||||||
|
|
||||||
1. 项目里哪些内容已经是高置信度的垃圾、临时产物或无入口代码。
|
|
||||||
2. 哪些实现属于双份真相、重复映射或旧链路残留。
|
|
||||||
3. 哪些前端代码仍然承担了应迁移到 Express 后端的职责。
|
|
||||||
4. 哪些文件已经大到会持续拖累迭代效率,需要优先拆分。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 结论先行
|
|
||||||
|
|
||||||
当前仓库的主要问题不是“有一些小工具没人用”,而是四类结构性噪音同时存在:
|
|
||||||
|
|
||||||
1. **仓库噪音产物仍然很多。**
|
|
||||||
根目录残留了大量 `.codex-*.log`、`tmp_*`、旧截图/HTML,以及 `temp-build-goal-check/` 这类大体量检查产物,已经不是单个文件层面的脏数据,而是在持续污染工程视野。
|
|
||||||
2. **旧入口和新入口并存,形成了明显的冗余链路。**
|
|
||||||
`scripts/dev-server/localApiPlugins.ts` 已经退出当前正式开发入口,但仍保留了 LLM proxy、JSON 写盘、资产发布等整套旧 Vite 本地 API 机制。
|
|
||||||
3. **前端仍然承载了过多运行时规则与 AI 编排。**
|
|
||||||
`src/services/ai.ts`、`src/services/customWorld.ts`、`src/hooks/story/npcEncounterActions.ts` 这类文件,仍在浏览器里承担 prompt 组装、规则判定、奖励结算、剧情推进等职责。
|
|
||||||
4. **后端边界还没有真正闭合。**
|
|
||||||
`server-node` 虽然已经承接了大量路由和运行时动作,但仍直接 import `src/services/customWorld*.ts` 和 `src/types.ts`,说明后端领域层还没有完全从前端目录中独立出来。
|
|
||||||
|
|
||||||
一句话判断:
|
|
||||||
|
|
||||||
**这轮优先级不该再是继续堆功能,而是先清仓库噪音与无入口孤岛,再把前后端双份真相收口,最后拆新的巨型热点文件。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 本次审计方法与口径
|
|
||||||
|
|
||||||
### 2.1 方法
|
|
||||||
|
|
||||||
本次审计结合了四类证据:
|
|
||||||
|
|
||||||
1. 文档基线:
|
|
||||||
- `docs/audits/engineering/README.md`
|
|
||||||
- `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
|
||||||
- `scripts/dev-server/README.md`
|
|
||||||
2. 当前入口核对:
|
|
||||||
- `src/main.tsx`
|
|
||||||
- `src/routing/appRoutes.tsx`
|
|
||||||
- `src/App.tsx`
|
|
||||||
- `package.json`
|
|
||||||
- `server-node/package.json`
|
|
||||||
3. 静态依赖扫描:
|
|
||||||
- 对 `src/`、`server-node/src/`、`packages/shared/src/`、`scripts/` 共 `650` 个 TS/JS 文件做本地依赖图扫描。
|
|
||||||
4. 定向 grep:
|
|
||||||
- 核对旧 dev 插件入口、后端跨层 import、localStorage 使用、运行时快照双写、重复映射代码。
|
|
||||||
|
|
||||||
### 2.2 口径说明
|
|
||||||
|
|
||||||
为避免误判,本次审计明确排除了两类对象:
|
|
||||||
|
|
||||||
1. **包脚本入口**:例如 `scripts/build-gate.mjs`、`scripts/check-encoding.mjs`、`server-node/build.mjs` 这类由 `package.json` 直接执行的脚本,不因“无 import”而判为垃圾。
|
|
||||||
2. **字符串路径消费的资源**:例如 `src/data/itemOverrides.json`、`src/data/monsterOverrides.json` 会被校验脚本和 editor route 以文件路径读取,不按“无 import”处理。
|
|
||||||
|
|
||||||
另外,当前工作区存在未提交改动,因此本次结论以**已纳入当前主链且能确认未接线/重复/越界的内容**为主,不把明显的当日 WIP 文件计入垃圾结论。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 高置信度垃圾、临时产物与无入口代码
|
|
||||||
|
|
||||||
## 3.1 仓库噪音产物已经到了需要集中清理的程度
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
| 项目 | 当前证据 | 判断 |
|
|
||||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
||||||
| 根目录日志/临时文件 | 根目录命中 `60` 个 `.codex-*.log`、`.preview.*`、`tmp_*`、`npc-editor-*`、`temp-write-check.txt`,合计约 `52.36 MB` | 已经不是偶发临时文件,而是长期堆积的开发残留 |
|
|
||||||
| `temp-build-goal-check/` | 当前包含 `15099` 个文件,合计约 `166.56 MB` | 大体量检查产物,应该移出主工程视野 |
|
|
||||||
| Python 缓存 | 当前存在 `scripts/__pycache__/` | 纯缓存产物,不应长期留在仓库工作区中 |
|
|
||||||
|
|
||||||
### 影响
|
|
||||||
|
|
||||||
1. 根目录信噪比明显下降,真实工程文件被大量一次性产物淹没。
|
|
||||||
2. `temp-build-goal-check/` 虽然已被 `.gitignore` 和 `vite.config.ts` 的 watch 忽略模式覆盖,但 `.eslintrc.cjs` 的 `ignorePatterns` 里没有对应口径,仍存在工具口径不一致问题。
|
|
||||||
3. 这类目录会持续干扰检索、review、lint 判断和本地扫描速度。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 把根目录临时日志、扫描 txt/html、旧截图统一迁到单独的 `tmp/` 或本地缓存目录,默认不留在仓库根目录。
|
|
||||||
2. 把 `temp-build-goal-check/` 改成真正的外置检查产物目录,或者在 lint/脚本口径上一起排除。
|
|
||||||
3. 清理 `scripts/__pycache__/`,并统一补上 Python 缓存忽略规则。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.2 旧 Vite 本地 API 插件链已经退出主入口,但仍保留整套旧实现
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
1. `scripts/dev-server/README.md` 已明确写明:`scripts/dev-server/**` 不再是当前开发入口,只保留为迁移参考。
|
|
||||||
2. `scripts/dev-server/localApiPlugins.ts` 当前仍有 `1664` 行。
|
|
||||||
3. 仓库内已经找不到 `localApiPlugins` 的实际代码入口引用,当前只剩文档引用。
|
|
||||||
4. 该文件内部仍然同时定义和拼装:
|
|
||||||
- `createLlmProxyPlugin`
|
|
||||||
- `createJsonFileEditorPlugin`
|
|
||||||
- `createCustomWorldSceneImagePlugin`
|
|
||||||
- `createCharacterVisualPublishPlugin`
|
|
||||||
- `createCharacterAnimationPublishPlugin`
|
|
||||||
- `createCharacterAssetStudioPlugins`
|
|
||||||
- `createQwenSpriteSheetToolPlugins`
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这不是“一个小工具暂时没用”,而是**整条旧 editor/assets 本地 API 链路仍然完整保留在仓库里**。它在工程上已经属于高置信度的历史残留。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 如果只保留迁移证据,建议把 `scripts/dev-server/localApiPlugins.ts` 和相关说明迁到 `docs/reference/` 或单独的 `archive/` 目录。
|
|
||||||
2. 如果确实还要保留参考代码,至少要在文件顶部加更强的“只读参考、禁止继续扩展”标识,并从主工程扫描面上进一步隔离。
|
|
||||||
3. 不建议继续在这条旧链路里新增任何 `/api/*` 能力。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.3 当前存在一批“无运行时入口”或“仅测试引用”的孤岛模块
|
|
||||||
|
|
||||||
### 高置信度无入口/仅测试引用清单
|
|
||||||
|
|
||||||
| 模块 | 证据 | 判断 |
|
|
||||||
| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
||||||
| `src/components/GameShell.tsx` | 文件体量 `761` 行;当前 `src/App.tsx` 只接入 `components/game-shell/GameShellRuntime.tsx`;仓库内无其它 import | 旧版壳层残留 |
|
|
||||||
| `src/components/custom-world-home/CustomWorldCreationHub.tsx` | 仅被 `CustomWorldCreationHub.test.tsx` 和 `CustomWorldCreationHub.interaction.test.tsx` 引用;`src/routing/appRoutes.tsx` 只有 `game` 和 `qwen-sprite-tool` 两条路由 | 已做出 UI,但未进入正式入口 |
|
|
||||||
| `src/components/custom-world-home/CustomWorldCreationLauncherModal.tsx` | 当前无运行时引用 | 同属未接线入口壳层 |
|
|
||||||
| `src/components/custom-world-agent/*` 中 `9` 个子模块 | 当前合计约 `826` 行;典型文件包括 `CustomWorldAgentLauncherModal.tsx`、`CustomWorldAgentDraftDrawer.tsx`、`CustomWorldAgentLockBar.tsx`、`CustomWorldAgentQuickActions.tsx`、`CustomWorldAgentSummaryPanel.tsx`;部分文件完全无引用,部分仅被测试引用 | 处于“做了一部分 UI,但未进入主链”的孤岛状态 |
|
|
||||||
| `src/hooks/story/storyBootstrap.ts` | `250` 行,仓库内只定义不消费 | 已被新流程替代的可能性高 |
|
|
||||||
| `src/hooks/useEquipmentFlow.ts` / `useForgeFlow.ts` / `useInventoryFlow.ts` | 合计约 `393` 行,当前无运行时引用 | 旧流转层残留 |
|
|
||||||
| `src/editor/shared/cloneValue.ts` / `EditorEmptyState.tsx` / `EditorSelectionCard.tsx` / `useJsonSave.ts` | 当前无运行时引用 | editor 旧共享层碎片 |
|
|
||||||
| `src/services/customWorldPresentation.stub.ts` | 当前无引用,且文件本身就是 stub | 高置信度占位残留 |
|
|
||||||
| `src/services/typewriter.ts` | 当前无引用,仅提供一个 `getTypewriterDelay` | 已被其它链路内联实现替代 |
|
|
||||||
| `src/data/buildTagSimilarity.generated.ts` | 当前 `823` 行,仅能被生成脚本自身检索到,没有消费方 | 生成产物未接入任何业务链路 |
|
|
||||||
| `src/data/customWorldCharacterLoadout.stub.ts` | 当前无引用,且实现只返回空数组 | 占位残留 |
|
|
||||||
| `src/components/DeveloperTeamModal.tsx` / `src/components/LazySkillEffectPreview.tsx` | 当前无运行时引用 | 小体量零散孤岛 |
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这批文件不一定都应该“立刻删除”,但它们已经满足两个至少其一:
|
|
||||||
|
|
||||||
1. 当前正式入口完全不消费。
|
|
||||||
2. 只剩测试在消费,本体没有真实运行时位置。
|
|
||||||
|
|
||||||
所以它们至少都应该进入以下三选一处理:
|
|
||||||
|
|
||||||
1. 立即归档/删除。
|
|
||||||
2. 明确接回正式入口。
|
|
||||||
3. 改名或迁目录,标明“实验稿/参考稿/未接线”身份。
|
|
||||||
|
|
||||||
### 特别提醒
|
|
||||||
|
|
||||||
`src/components/custom-world-home/` 和 `src/components/custom-world-agent/` 这两组文件里,存在**已经有一定 UI 完成度、但没有进入真实路由/流程**的情况。
|
|
||||||
这类文件最危险的点不是体量,而是会让后来者误以为“这块功能已经在主链上”。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 冗余实现与双份真相
|
|
||||||
|
|
||||||
## 4.1 Story option interaction 映射在前后端各维护了一份
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
1. 前端 `src/services/runtimeStoryService.ts` 的 `buildRuntimeOptionInteraction` 维护了 `npcActionMap`、`treasureActionMap`。
|
|
||||||
2. 后端 `server-node/src/modules/story/storyActionService.ts` 的 `buildStoryOptionInteraction` 维护了几乎同构的一份 `npcActionMap`、`treasureActionMap`。
|
|
||||||
|
|
||||||
### 风险
|
|
||||||
|
|
||||||
1. 任何一个 functionId 增删改,前后端都要同步。
|
|
||||||
2. 一边先改、一边漏改时,表现层和运行时层会出现静默漂移。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
把 interaction/view model 映射收口到后端,前端只消费后端返回的结构,不再根据 `functionId` 本地重建一遍交互语义。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.2 浏览历史已经有后端接口,但前端仍维护本地真相与迁移状态
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
1. `src/components/game-shell/PreGameSelectionFlow.tsx` 中,`appendBrowseHistoryEntry` 先调用 `writePlatformBrowseHistory` 写本地,再调用 `upsertProfileBrowseHistory` 写后端。
|
|
||||||
2. 同文件启动阶段又会先读 `readPlatformBrowseHistory`,再根据 `hasPendingPlatformBrowseHistoryMigration` 把本地历史同步回后端。
|
|
||||||
3. 后端 `server-node/src/routes/runtimeRoutes.ts` 已经提供了 `/profile/browse-history` 路由,而前端 `src/services/storageService.ts` 也已有对应 API SDK。
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
当前浏览历史并不是单纯的“本地缓存”,而是**本地存储 + 远端持久化 + 迁移标记**三套状态并存。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 后端结果作为唯一真相源。
|
|
||||||
2. 前端如果要保留缓存,只保留一个明确的 cache wrapper,不再把它做成独立状态系统。
|
|
||||||
3. `markPlatformBrowseHistoryMigrated` 这种迁移标记应尽量在后端一次性收口,而不是长期停留在正式前端逻辑里。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.3 运行时快照依然由前端先落本地,再与后端会话互相回填
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
1. `src/hooks/story/runtimeStoryCoordinator.ts` 在读状态和提交 action 前都会先调用 `putSaveSnapshot`。
|
|
||||||
2. 同文件以及 `src/services/runtimeStoryService.ts` 又会在响应后多次 `rehydrateSavedSnapshot`。
|
|
||||||
3. 这意味着浏览器仍然在“后端 action 之前”先写一份自己的快照解释。
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这条链路说明当前运行时还处在**前端快照解释权没有完全退出**的过渡状态。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
1. 前端逐步退化为 view model 消费层。
|
|
||||||
2. 运行时快照、版本迁移、恢复解释权继续往后端收口。
|
|
||||||
3. 前端保留最小必要的离线展示缓存,但不再成为正式运行时状态真相来源。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.4 旧 Vite 本地 API 与正式 Express 路由仍然形成重复能力面
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
1. `scripts/dev-server/localApiPlugins.ts` 里仍有 JSON 编辑、场景图生成、角色视觉发布、角色动作发布等插件。
|
|
||||||
2. 当前正式路径已经迁到:
|
|
||||||
- `server-node/src/modules/editor/**`
|
|
||||||
- `server-node/src/modules/assets/**`
|
|
||||||
3. `scripts/dev-server/README.md` 已明确说明旧链路只保留为迁移参考。
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这属于典型的**旧能力未删除,新能力已落地,双链路长期并存**。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
尽快把旧 Vite 本地 API 参考实现移出主工程扫描面,避免后续继续被误用或被误认为正式入口。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 需要迁移到后端的代码
|
|
||||||
|
|
||||||
## 5.1 `src/services/ai.ts` 仍然承担了过多正式运行时职责
|
|
||||||
|
|
||||||
### 当前职责
|
|
||||||
|
|
||||||
`src/services/ai.ts` 当前约 `2632` 行,仍然同时承担:
|
|
||||||
|
|
||||||
1. function 可用性与 option 构造相关逻辑。
|
|
||||||
2. NPC 对话 / 招募 prompt 构造。
|
|
||||||
3. 自定义世界生成 prompt 与 JSON 修复请求。
|
|
||||||
4. 直接调用 `requestPlainTextCompletion` / `streamPlainTextCompletion`。
|
|
||||||
5. 浏览器内 fallback 与响应解析。
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这不是单纯的“前端请求 SDK”,而是**前端仍在承担正式运行时 AI orchestration**。
|
|
||||||
|
|
||||||
### 建议迁移方向
|
|
||||||
|
|
||||||
1. prompt 组装、模型调用、超时重试、JSON repair 继续收口到 `server-node/src/modules/ai/**`。
|
|
||||||
2. 前端只保留轻量 SDK 和展示态拼装。
|
|
||||||
3. fallback 如果必须保留,也应明确区分“开发兜底”与“正式运行时”。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.2 `src/services/customWorld.ts` 仍然是前端侧的大型规则中心
|
|
||||||
|
|
||||||
### 当前职责
|
|
||||||
|
|
||||||
`src/services/customWorld.ts` 当前约 `2413` 行,仍然承担:
|
|
||||||
|
|
||||||
1. 世界框架与角色/地标 outline 归一化。
|
|
||||||
2. 世界属性 schema 生成。
|
|
||||||
3. `ownedSettingLayers` 归一化。
|
|
||||||
4. 最终世界 profile 校验。
|
|
||||||
5. fallback story graph/theme pack 生成。
|
|
||||||
|
|
||||||
### 当前越界证据
|
|
||||||
|
|
||||||
后端目前直接从以下文件 import 这些能力:
|
|
||||||
|
|
||||||
1. `server-node/src/modules/ai/customWorldOrchestrator.ts`
|
|
||||||
2. `server-node/src/services/customWorldAgentFoundationDraftService.ts`
|
|
||||||
|
|
||||||
它们仍直接引用:
|
|
||||||
|
|
||||||
1. `src/services/customWorld.js`
|
|
||||||
2. `src/services/customWorldBuilder.js`
|
|
||||||
3. `src/services/customWorldCreatorIntent.js`
|
|
||||||
4. `src/types.js`
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这说明自定义世界的核心领域规则仍然以**前端目录为事实源**,后端只是在反向复用。
|
|
||||||
|
|
||||||
### 建议迁移方向
|
|
||||||
|
|
||||||
1. `types/schema/contracts` 抽到 `packages/shared`。
|
|
||||||
2. 规则编译、校验、fallback 与 AI 编排迁到 `server-node`。
|
|
||||||
3. 前端只保留编辑器表现层和字段草稿态。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.3 `src/hooks/story/npcEncounterActions.ts` 仍在浏览器里做任务、奖励、战斗与招募结算
|
|
||||||
|
|
||||||
### 当前职责
|
|
||||||
|
|
||||||
`src/hooks/story/npcEncounterActions.ts` 当前约 `1623` 行,仍然直接编排:
|
|
||||||
|
|
||||||
1. `quest_accept` / `quest_turn_in`
|
|
||||||
2. 招募、切磋、离开、帮助奖励
|
|
||||||
3. 掉落/背包写入
|
|
||||||
4. HP / MP / cooldown 奖励变化
|
|
||||||
5. NPC 亲和度变化
|
|
||||||
6. 战斗场景切换与遭遇状态推进
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这条链已经明显超出“前端表现协调层”的边界,仍属于**正式运行时规则在前端执行**。
|
|
||||||
|
|
||||||
### 建议迁移方向
|
|
||||||
|
|
||||||
1. quest 信号推进 -> `server-node/src/modules/quest/**`
|
|
||||||
2. 奖励与背包变更 -> `server-node/src/modules/inventory/**`
|
|
||||||
3. 招募/关系变化 -> `server-node/src/modules/npc/**`
|
|
||||||
4. 战斗结算 -> `server-node/src/modules/combat/**`
|
|
||||||
|
|
||||||
前端应该只保留选项触发、加载态、动画态和最终结果展示。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.4 `src/services/apiClient.ts` 仍保留了本地 token 与自动登录凭证存储
|
|
||||||
|
|
||||||
### 证据
|
|
||||||
|
|
||||||
`src/services/apiClient.ts` 当前仍把以下内容放在 `window.localStorage`:
|
|
||||||
|
|
||||||
1. access token
|
|
||||||
2. 自动登录用户名
|
|
||||||
3. 自动登录密码
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这既是安全面问题,也是边界问题。
|
|
||||||
在“后端负责鉴权、前端只做表现”的目标下,正式凭证体系不应长期依赖浏览器本地保存账号密码。
|
|
||||||
|
|
||||||
### 建议迁移方向
|
|
||||||
|
|
||||||
1. 正式态优先走服务端 session / HttpOnly cookie。
|
|
||||||
2. 自动登录不要继续保存明文用户名/密码。
|
|
||||||
3. 前端仅保留最小必要的登录态感知,不保留额外认证真相。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 需要优先优化和拆分的代码
|
|
||||||
|
|
||||||
## 6.1 `src/components/CustomWorldEntityEditorModal.tsx`
|
|
||||||
|
|
||||||
### 当前状态
|
|
||||||
|
|
||||||
文件体量约 `4487` 行,已同时吞下:
|
|
||||||
|
|
||||||
1. 世界营地编辑
|
|
||||||
2. playable NPC 编辑
|
|
||||||
3. story NPC 编辑
|
|
||||||
4. 地标与世界地图布局
|
|
||||||
5. 场景图生成
|
|
||||||
6. 技能编辑
|
|
||||||
7. 初始物品编辑
|
|
||||||
8. 资产工作台串联
|
|
||||||
9. 多层 modal 开关与保存逻辑
|
|
||||||
|
|
||||||
### 判断
|
|
||||||
|
|
||||||
这是当前前端最明显的“巨型工作台单体文件”。
|
|
||||||
|
|
||||||
### 建议拆分方向
|
|
||||||
|
|
||||||
1. 按实体拆:营地 / playable NPC / story NPC / 地标。
|
|
||||||
2. 按能力拆:基础信息 / 关系 / 技能 / 初始物品 / 视觉资产。
|
|
||||||
3. 把 AI 生成与资产工作流进一步外置成独立 coordinator。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6.2 `server-node/src/modules/assets/characterAssetRoutes.ts`
|
|
||||||
|
|
||||||
### 当前状态
|
|
||||||
|
|
||||||
文件体量约 `3579` 行,已同时承担:
|
|
||||||
|
|
||||||
1. route 注册
|
|
||||||
2. 请求解析
|
|
||||||
3. LLM prompt bundle 生成
|
|
||||||
4. JSON 解析与修复
|
|
||||||
5. 文件系统写盘
|
|
||||||
6. visual publish
|
|
||||||
7. animation publish
|
|
||||||
8. 资产目录管理
|
|
||||||
|
|
||||||
### 直接证据
|
|
||||||
|
|
||||||
文件内同时存在:
|
|
||||||
|
|
||||||
1. `mkdir` / `writeFile`
|
|
||||||
2. `UpstreamLlmClient`
|
|
||||||
3. `parseJsonResponseText`
|
|
||||||
4. 多条 publish 路径
|
|
||||||
5. 大量本地文件落盘逻辑
|
|
||||||
|
|
||||||
### 建议拆分方向
|
|
||||||
|
|
||||||
1. route 层
|
|
||||||
2. prompt bundle service
|
|
||||||
3. file publish service
|
|
||||||
4. animation persistence service
|
|
||||||
5. asset metadata service
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6.3 `src/services/ai.ts`
|
|
||||||
|
|
||||||
### 当前状态
|
|
||||||
|
|
||||||
文件体量约 `2632` 行,同时承载运行时 story、自定义世界、NPC 对话、招募等多条链路。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
即使短期内不能全部迁后端,也应该先按职责拆成:
|
|
||||||
|
|
||||||
1. runtime story client
|
|
||||||
2. npc dialogue client
|
|
||||||
3. recruit dialogue client
|
|
||||||
4. custom world generation client
|
|
||||||
5. parser / fallback / error helpers
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6.4 `src/services/customWorld.ts`
|
|
||||||
|
|
||||||
### 当前状态
|
|
||||||
|
|
||||||
文件体量约 `2413` 行,已经变成世界生成、校验、归一化、fallback 的综合体。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
至少拆成:
|
|
||||||
|
|
||||||
1. 世界框架与 outline schema
|
|
||||||
2. profile normalize / validate
|
|
||||||
3. role / landmark 编译器
|
|
||||||
4. fallback builder
|
|
||||||
5. world rule helpers
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6.5 `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
|
|
||||||
### 当前状态
|
|
||||||
|
|
||||||
文件体量约 `1623` 行,已经不是单纯 hook,而是前端运行时 action resolver。
|
|
||||||
|
|
||||||
### 建议
|
|
||||||
|
|
||||||
按动作域拆开:
|
|
||||||
|
|
||||||
1. npc chat / recruit
|
|
||||||
2. npc help / affinity
|
|
||||||
3. quest accept / turn-in
|
|
||||||
4. battle entry / exit
|
|
||||||
5. async streaming / typewriter / presentation glue
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 推荐执行顺序
|
|
||||||
|
|
||||||
### 第一阶段:先清仓库噪音和旧入口残留
|
|
||||||
|
|
||||||
1. 清根目录日志、扫描文件、旧截图、`__pycache__`
|
|
||||||
2. 迁出 `temp-build-goal-check/`
|
|
||||||
3. 明确处置 `scripts/dev-server/localApiPlugins.ts`
|
|
||||||
|
|
||||||
### 第二阶段:再处理无入口孤岛模块
|
|
||||||
|
|
||||||
1. 逐个确认 `GameShell.tsx`、custom-world-home、custom-world-agent、旧 flow hooks 是要接回还是归档
|
|
||||||
2. 对确认不再使用的 stub / helper / generated dead file 直接清理
|
|
||||||
|
|
||||||
### 第三阶段:把双份真相收口
|
|
||||||
|
|
||||||
1. runtime option interaction 映射只保留一份
|
|
||||||
2. 浏览历史以后端为真相源
|
|
||||||
3. 运行时快照解释权继续后移
|
|
||||||
4. 清理 `server-node -> src/**` 的反向依赖
|
|
||||||
|
|
||||||
### 第四阶段:最后拆巨型热点文件
|
|
||||||
|
|
||||||
1. `CustomWorldEntityEditorModal.tsx`
|
|
||||||
2. `characterAssetRoutes.ts`
|
|
||||||
3. `ai.ts`
|
|
||||||
4. `customWorld.ts`
|
|
||||||
5. `npcEncounterActions.ts`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 本文依据
|
|
||||||
|
|
||||||
文档依据:
|
|
||||||
|
|
||||||
1. `docs/audits/engineering/README.md`
|
|
||||||
2. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
|
||||||
3. `scripts/dev-server/README.md`
|
|
||||||
|
|
||||||
当前仓库扫描依据:
|
|
||||||
|
|
||||||
1. `src/main.tsx`
|
|
||||||
2. `src/routing/appRoutes.tsx`
|
|
||||||
3. `src/App.tsx`
|
|
||||||
4. `package.json`
|
|
||||||
5. `server-node/package.json`
|
|
||||||
6. `vite.config.ts`
|
|
||||||
7. `.eslintrc.cjs`
|
|
||||||
8. `git grep` 对关键模块引用、后端跨层 import、localStorage、旧 dev 插件入口的扫描结果
|
|
||||||
@@ -1,382 +0,0 @@
|
|||||||
# 工程清理与后端边界复核审计(2026-04-20)
|
|
||||||
|
|
||||||
更新时间:`2026-04-20`
|
|
||||||
|
|
||||||
## 0. 审计目标
|
|
||||||
|
|
||||||
这份文档不是重复 `2026-04-19` 的原始扫描,而是基于当前仓库状态做一轮复核,重点回答三个问题:
|
|
||||||
|
|
||||||
1. 昨天审计里已经提出的问题,哪些今天已经真正落地。
|
|
||||||
2. 哪些结论在当前代码里仍然成立,哪些表述需要纠正。
|
|
||||||
3. 当前工程热点和边界问题有没有发生迁移。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 结论先行
|
|
||||||
|
|
||||||
和 `2026-04-19` 那份基线相比,当前仓库已经有一批明确进展:
|
|
||||||
|
|
||||||
1. **旧 Vite 本地 API 链路已经真正出清。**
|
|
||||||
`scripts/dev-server/` 当前只剩一份 `README.md`,旧的 `localApiPlugins.ts`、角色资产插件、精灵表插件都不在仓库里了。
|
|
||||||
2. **根目录噪音产物已经清理完成。**
|
|
||||||
当前根目录临时日志/扫描产物扫描结果为空,`temp-build-goal-check/` 也不存在。
|
|
||||||
3. **`server-node -> src/**` 反向依赖已经收掉。**
|
|
||||||
当前复核没有再发现 `server-node/src/**` 直接 import 前端 `src/**` 的情况。
|
|
||||||
4. **runtime option interaction 已经收口成后端单一真相。**
|
|
||||||
这部分现在由 `server-node/src/modules/story/runtimeSession.ts` 统一构造,前端 `src/services/runtimeStoryService.ts` 不再本地再建一份映射表。
|
|
||||||
|
|
||||||
但这不代表边界问题已经结束,当前剩余问题主要集中在三块:
|
|
||||||
|
|
||||||
1. **前端仍保留运行时镜像与登录凭证本地真相。**
|
|
||||||
`runtimeStoryCoordinator.ts` 仍会先写本地快照,`apiClient.ts` 仍把 token/自动登录凭证放在 `localStorage`。
|
|
||||||
2. **NPC 聊天任务链路还没有完全后端化。**
|
|
||||||
“聊天后挂出待接委托”已经移到后端,但“更换待接委托”这条分支仍由前端 `npcEncounterActions.ts` 触发 `generateQuestForNpcEncounter(...)`。
|
|
||||||
3. **未接线孤岛和热点文件问题仍然明显。**
|
|
||||||
一批 UI/Hook/Prompt 残留模块还没有正式入口;同时热点已经从已删除的旧插件链路,转移到 `CustomWorldEntityEditorModal.tsx`、`storyPromptBuilders.ts`、`runtimeProfile.ts`、`PreGameSelectionFlow.tsx`、`PlatformHomeView.tsx` 等新中心。
|
|
||||||
|
|
||||||
一句话判断:
|
|
||||||
|
|
||||||
**当前仓库已经完成“清垃圾、拆旧入口、切断后端反向依赖”的第一阶段,但还没有完成“前端退出运行时真相”和“未接线孤岛归档”的第二阶段。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 已完成项复核
|
|
||||||
|
|
||||||
## 2.1 旧 dev-server 链路已经不是“逻辑上废弃”,而是“代码上删除”
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
| 项目 | 当前状态 | 结论 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `scripts/dev-server/` | 当前只剩 `README.md` 一份说明文件 | 旧 Vite 本地 API 链路已从仓库代码层出清 |
|
|
||||||
| `scripts/dev-server/README.md` | 已明确声明当前正式入口为 `scripts/dev-node.mjs + server-node/src/modules/**` | 文档与代码状态一致 |
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
`2026-04-19` 文档里关于旧本地 API 插件链路的清理结论,在当前仓库里已经可以确认成立,不再只是“计划删除”。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.2 根目录噪音产物已经从当前工作区移除
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
| 项目 | 当前状态 | 结论 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 根目录历史日志/扫描产物 | 本轮扫描结果为空 | 之前的 `.codex-*.log`、`tmp_*`、旧截图/HTML 不再占据当前工作区 |
|
|
||||||
| `temp-build-goal-check/` | 当前不存在 | 大体量检查产物已移出当前仓库视野 |
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
`2026-04-19` 文档中关于“仓库噪音产物”的问题,在当前工作区层面已经完成首轮治理。
|
|
||||||
这部分不再是当前工程第一优先级。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.3 `server-node -> src/**` 反向依赖已清零
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
本轮用脚本复核 `server-node/src/**` 中所有 `import` 后,当前结果为:
|
|
||||||
|
|
||||||
`NO_DIRECT_SERVER_TO_FRONTEND_SRC_IMPORTS`
|
|
||||||
|
|
||||||
同时,仓库里已经看不到类似下面这类旧反向依赖:
|
|
||||||
|
|
||||||
1. `server-node -> src/services/customWorld.js`
|
|
||||||
2. `server-node -> src/services/customWorldBuilder.js`
|
|
||||||
3. `server-node -> src/services/customWorldCreatorIntent.js`
|
|
||||||
4. `server-node -> src/types.js`
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
`2026-04-19` 文档里“清理 `server-node -> src/**` 反向依赖”的阶段性目标,在当前仓库里已经真正落地。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.4 runtime option interaction 已经收口到后端
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
1. `server-node/src/modules/story/runtimeSession.ts` 当前仍保留 `buildOptionInteraction(...)`,负责构造:
|
|
||||||
- `npcActionMap`
|
|
||||||
- `treasureActionMap`
|
|
||||||
2. `src/services/runtimeStoryService.ts` 当前只做:
|
|
||||||
- 直接读取 `option.interaction`
|
|
||||||
- 把后端返回的 interaction 投影成 `StoryOption`
|
|
||||||
3. 前端文件里已经找不到旧的 `buildRuntimeOptionInteraction` / `npcActionMap` / `treasureActionMap` 实现。
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
这项收口已经成立,当前不会再出现“前后端各维护一份 interaction 映射表”的旧问题。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.5 浏览器端的 quest/runtime item 本地 LLM fallback 已移除
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
1. `src/services/questDirector.ts`
|
|
||||||
- 浏览器路径先请求 `/api/runtime/quests/generate`
|
|
||||||
- 后端失败时只走 deterministic fallback compile
|
|
||||||
2. `src/services/runtimeItemAiDirector.ts`
|
|
||||||
- 浏览器路径先请求 `/api/runtime/items/runtime-intent`
|
|
||||||
- 后端失败时只返回 deterministic fallback intents
|
|
||||||
3. 这两个文件虽然仍保留 `requestChatMessageContent(...)` 分支,但那是非浏览器分支,不再是浏览器端正式兜底链路。
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
`2026-04-19` 文档里关于“浏览器本地 LLM fallback”这部分,当前应更新为:
|
|
||||||
|
|
||||||
**浏览器端本地 LLM fallback 已移除,但这两个模块仍然是双环境混合实现,还没有彻底后端化。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 需要纠正的旧文档表述
|
|
||||||
|
|
||||||
## 3.1 NPC 任务链路不是“全部后端化”,而是“挂单已后移、换单仍前触发”
|
|
||||||
|
|
||||||
### 需要纠正的点
|
|
||||||
|
|
||||||
`2026-04-19` 文档中的回填里有一条表述是:
|
|
||||||
|
|
||||||
“`src/hooks/story/npcEncounterActions.ts` 不再在 NPC 单轮聊天完成后本地调用 `generateQuestForNpcEncounter(...)` 再决定是否挂出待接委托。”
|
|
||||||
|
|
||||||
### 当前代码状态
|
|
||||||
|
|
||||||
这句话对“聊天后挂出待接委托”这条主链是成立的,因为当前后端 `server-node/src/modules/ai/chatOrchestrator.ts` 已经会回填 `pendingQuestOffer`。
|
|
||||||
|
|
||||||
但它对整条 NPC 任务链路来说并不完整,因为当前前端仍保留这条分支:
|
|
||||||
|
|
||||||
1. `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
2. `replacePendingNpcQuestOffer()`
|
|
||||||
3. `generateQuestForNpcEncounter(...)`
|
|
||||||
|
|
||||||
也就是:
|
|
||||||
|
|
||||||
**待接委托的“正式挂出”已后端化,但“更换委托”仍然由前端动作流发起。**
|
|
||||||
|
|
||||||
### 当前应改成的结论
|
|
||||||
|
|
||||||
更准确的描述应该是:
|
|
||||||
|
|
||||||
1. NPC 单轮聊天里“是否挂出待接委托”的决定权已收回后端。
|
|
||||||
2. 但待接委托的“换单/重抽”分支仍通过前端 `npcEncounterActions.ts -> questDirector.ts` 发起。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 当前仍然成立的遗留问题
|
|
||||||
|
|
||||||
## 4.1 未接线/仅测试引用孤岛模块仍然明显
|
|
||||||
|
|
||||||
本轮依赖图复核后,当前仍能确认一批高置信度孤岛模块:
|
|
||||||
|
|
||||||
| 模块 | 当前状态 | 说明 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `src/components/GameShell.tsx` | `765` 行,无运行时引用 | 旧版壳层残留仍在 |
|
|
||||||
| `src/components/custom-world-home/CustomWorldCreationHub.tsx` | `161` 行,仅测试引用 | UI 已有完成度,但仍未进入正式入口 |
|
|
||||||
| `src/components/custom-world-home/CustomWorldCreationLauncherModal.tsx` | `147` 行,无运行时引用 | 未接线入口壳层 |
|
|
||||||
| `src/components/custom-world-agent/CustomWorldAgentLauncherModal.tsx` | `91` 行,无运行时引用 | agent UI 孤岛仍在 |
|
|
||||||
| `src/components/custom-world-agent/CustomWorldAgentDraftDrawer.tsx` | `116` 行,无运行时引用 | agent UI 孤岛仍在 |
|
|
||||||
| `src/hooks/story/storyBootstrap.ts` | `250` 行,无运行时引用 | 旧 bootstrap hook 仍未归档 |
|
|
||||||
| `src/hooks/useEquipmentFlow.ts` | `134` 行,无运行时引用 | 旧 flow hook 残留 |
|
|
||||||
| `src/hooks/useForgeFlow.ts` | `159` 行,无运行时引用 | 旧 flow hook 残留 |
|
|
||||||
| `src/hooks/useInventoryFlow.ts` | `100` 行,无运行时引用 | 旧 flow hook 残留 |
|
|
||||||
| `src/services/customWorldPresentation.stub.ts` | `55` 行,无运行时引用 | 占位 stub 仍在 |
|
|
||||||
| `src/services/typewriter.ts` | `7` 行,无运行时引用 | 小型 helper 残留 |
|
|
||||||
| `src/prompts/customWorldOrchestratorPrompts.ts` | `9` 行,无运行时引用 | prompt source 已迁走后留下的孤岛 |
|
|
||||||
| `src/prompts/storyOrchestratorPrompts.ts` | `6` 行,无运行时引用 | prompt source 已迁走后留下的孤岛 |
|
|
||||||
| `src/data/buildTagSimilarity.generated.ts` | `823` 行,无运行时引用 | 生成产物未接入正式业务链路 |
|
|
||||||
|
|
||||||
### 说明
|
|
||||||
|
|
||||||
`src/data/itemOverrides.json`、`src/data/monsterOverrides.json` 这类文件虽然没有 import 引用,但会被脚本和 editor route 以路径消费,所以不计入垃圾判断。
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
仓库已经完成“删旧插件”,但还没有完成“清未接线孤岛”。
|
|
||||||
当前这批模块应该进入明确处置表:
|
|
||||||
|
|
||||||
1. 直接归档/删除
|
|
||||||
2. 正式接回入口
|
|
||||||
3. 改名/迁目录,标记为实验稿
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.2 前端仍保留运行时镜像真相
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
1. `src/hooks/story/runtimeStoryCoordinator.ts`
|
|
||||||
- 仍会在读状态和提交动作前先 `putSaveSnapshot(...)`
|
|
||||||
- 仍会在响应后多次 `rehydrateSavedSnapshot(...)`
|
|
||||||
2. `src/services/runtimeStoryService.ts`
|
|
||||||
- 仍对响应快照做 `rehydrateSavedSnapshot(...)`
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
当前运行时已经不是“前端主算”,但仍然是:
|
|
||||||
|
|
||||||
**前端先写一份本地镜像,再和后端会话互相回填。**
|
|
||||||
|
|
||||||
这说明前端还没有完全退出正式运行时状态解释层。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.3 前端仍保留本地登录凭证真相
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
`src/services/apiClient.ts` 当前仍把以下内容写入 `window.localStorage`:
|
|
||||||
|
|
||||||
1. `ACCESS_TOKEN_KEY`
|
|
||||||
2. `AUTO_AUTH_USERNAME_KEY`
|
|
||||||
3. `AUTO_AUTH_PASSWORD_KEY`
|
|
||||||
|
|
||||||
对应代码仍包括:
|
|
||||||
|
|
||||||
1. `window.localStorage.getItem(...)`
|
|
||||||
2. `window.localStorage.setItem(...)`
|
|
||||||
3. `window.localStorage.removeItem(...)`
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
这一点和“前端只做表现、后端负责鉴权”的目标仍然不一致。
|
|
||||||
尤其是自动登录用户名/密码继续存本地,风险和边界问题都还在。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.4 quest/runtime item 仍是双环境混合实现
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
1. `src/services/questDirector.ts`
|
|
||||||
- 浏览器路径走 `requestJson('/api/runtime/quests/generate')`
|
|
||||||
- 非浏览器路径仍有 `requestChatMessageContent(...)`
|
|
||||||
2. `src/services/runtimeItemAiDirector.ts`
|
|
||||||
- 浏览器路径走 `requestJson('/api/runtime/items/runtime-intent')`
|
|
||||||
- 非浏览器路径仍有 `requestChatMessageContent(...)`
|
|
||||||
3. `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
- 当前仍 import `generateQuestForNpcEncounter`
|
|
||||||
- `replacePendingNpcQuestOffer()` 仍会调用它
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
浏览器兜底已经收掉,但模块职责仍然是混合的:
|
|
||||||
|
|
||||||
1. 同一个文件同时承担前端 SDK 和非浏览器编排逻辑
|
|
||||||
2. NPC 换单动作仍由前端发起服务调用
|
|
||||||
|
|
||||||
这部分还不能算真正后端化完成。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.5 `src/services/ai.ts` 仍然是浏览器端正式 AI orchestration 热点
|
|
||||||
|
|
||||||
### 当前证据
|
|
||||||
|
|
||||||
`src/services/ai.ts` 当前约 `2608` 行,仍直接使用:
|
|
||||||
|
|
||||||
1. `requestChatMessageContent`
|
|
||||||
2. `requestPlainTextCompletion`
|
|
||||||
3. `streamPlainTextCompletion`
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
这说明浏览器侧的大型 AI orchestration 仍然没有真正退出主工程。
|
|
||||||
虽然部分链路已经迁走,但整体边界还没有收完。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 当前热点已经发生迁移
|
|
||||||
|
|
||||||
## 5.1 当前主要大文件快照
|
|
||||||
|
|
||||||
| 文件 | 当前行数 | 判断 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `4898` | 仍是前端最大热点 |
|
|
||||||
| `server-node/src/modules/assets/characterAssetRoutes.ts` | `3181` | 仍是后端资产链路最大热点 |
|
|
||||||
| `src/services/ai.ts` | `2608` | 浏览器 AI orchestration 热点仍在 |
|
|
||||||
| `src/data/npcInteractions.ts` | `2409` | 仍是大型规则数据中心 |
|
|
||||||
| `server-node/src/services/customWorldAgentFoundationDraftService.ts` | `1902` | custom world agent 后端热点上升 |
|
|
||||||
| `src/prompts/storyPromptBuilders.ts` | `1882` | prompt source 已成为新的前端热点 |
|
|
||||||
| `server-node/src/modules/custom-world/runtimeProfile.ts` | `1735` | custom world runtime 编译中心已转到后端 |
|
|
||||||
| `src/components/game-shell/PreGameSelectionFlow.tsx` | `1547` | 平台/入口流程热点上升 |
|
|
||||||
| `src/components/game-shell/PlatformHomeView.tsx` | `1522` | 平台首页热点上升 |
|
|
||||||
| `src/services/customWorld.ts` | `1489` | 仍然大,但已明显缩小 |
|
|
||||||
| `src/hooks/story/npcEncounterActions.ts` | `1434` | 仍然是前端 action 热点 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5.2 热点变化判断
|
|
||||||
|
|
||||||
和 `2026-04-19` 相比,当前热点不是单纯“没变”,而是出现了明显迁移:
|
|
||||||
|
|
||||||
1. `characterAssetRoutes.ts` 从 `3579` 行降到 `3181` 行,说明资产路由已经有过一轮拆分,但仍然偏大。
|
|
||||||
2. `src/services/customWorld.ts` 从 `2413` 行降到 `1489` 行,说明自定义世界规则已拆出一部分。
|
|
||||||
3. `src/hooks/story/npcEncounterActions.ts` 从 `1623` 行降到 `1434` 行,说明 NPC 运行时逻辑也有收口。
|
|
||||||
4. 新的复杂度中心开始转移到:
|
|
||||||
- `src/prompts/storyPromptBuilders.ts`
|
|
||||||
- `server-node/src/modules/custom-world/runtimeProfile.ts`
|
|
||||||
- `src/components/game-shell/PreGameSelectionFlow.tsx`
|
|
||||||
- `src/components/game-shell/PlatformHomeView.tsx`
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
|
|
||||||
当前问题已经不再是“原来的热点完全没动”,而是:
|
|
||||||
|
|
||||||
**部分旧热点正在缩小,但复杂度正在向 prompt source、custom world runtime profile、平台入口壳层继续迁移。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 最新建议执行顺序
|
|
||||||
|
|
||||||
### 第一阶段:先清理当前仍明确无入口的孤岛
|
|
||||||
|
|
||||||
1. 处理 `GameShell.tsx`
|
|
||||||
2. 处理 `custom-world-home/*`
|
|
||||||
3. 处理 `custom-world-agent/*`
|
|
||||||
4. 处理 `storyBootstrap.ts`、`useEquipmentFlow.ts`、`useForgeFlow.ts`、`useInventoryFlow.ts`
|
|
||||||
5. 处理已脱钩的 `src/prompts/*OrchestratorPrompts.ts`
|
|
||||||
|
|
||||||
### 第二阶段:再收运行时和鉴权真相
|
|
||||||
|
|
||||||
1. 收掉 `runtimeStoryCoordinator.ts` 的本地快照前置写入
|
|
||||||
2. 收掉 `apiClient.ts` 中的自动登录用户名/密码本地持久化
|
|
||||||
3. 优先把 token/session 统一到服务端鉴权边界
|
|
||||||
|
|
||||||
### 第三阶段:补完 NPC 任务链路的后端化
|
|
||||||
|
|
||||||
1. 把“更换待接委托”从 `npcEncounterActions.ts -> questDirector.ts` 继续迁到后端
|
|
||||||
2. 把 `questDirector.ts` / `runtimeItemAiDirector.ts` 拆成明确的后端服务与前端 SDK 两层
|
|
||||||
|
|
||||||
### 第四阶段:最后拆新热点
|
|
||||||
|
|
||||||
1. `CustomWorldEntityEditorModal.tsx`
|
|
||||||
2. `characterAssetRoutes.ts`
|
|
||||||
3. `storyPromptBuilders.ts`
|
|
||||||
4. `runtimeProfile.ts`
|
|
||||||
5. `PreGameSelectionFlow.tsx`
|
|
||||||
6. `PlatformHomeView.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 本文依据
|
|
||||||
|
|
||||||
文档依据:
|
|
||||||
|
|
||||||
1. `docs/audits/engineering/ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-19.md`
|
|
||||||
2. `docs/audits/engineering/README.md`
|
|
||||||
3. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
|
||||||
|
|
||||||
当前仓库复核依据:
|
|
||||||
|
|
||||||
1. `scripts/dev-server/README.md`
|
|
||||||
2. `server-node/src/modules/story/runtimeSession.ts`
|
|
||||||
3. `src/services/runtimeStoryService.ts`
|
|
||||||
4. `src/hooks/story/runtimeStoryCoordinator.ts`
|
|
||||||
5. `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
6. `src/services/questDirector.ts`
|
|
||||||
7. `src/services/runtimeItemAiDirector.ts`
|
|
||||||
8. `src/services/apiClient.ts`
|
|
||||||
9. 当前依赖图扫描结果与当前大文件体量扫描结果
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
# 工程死分支清理执行记录 A(2026-04-21)
|
|
||||||
|
|
||||||
更新时间:`2026-04-21`
|
|
||||||
|
|
||||||
## 0. 本批次目标
|
|
||||||
|
|
||||||
这份记录对应:
|
|
||||||
|
|
||||||
- `docs/planning/ENGINEERING_DEAD_CODE_AND_HIDDEN_BRANCH_CLEANUP_PLAN_2026-04-21.md`
|
|
||||||
- 其中的 `P0 + 批次 A`
|
|
||||||
|
|
||||||
本批次只做一件事:
|
|
||||||
|
|
||||||
**先清理高置信度、低耦合、无正式入口的小型孤岛与残留壳子。**
|
|
||||||
|
|
||||||
这批对象有一个共同特征:
|
|
||||||
|
|
||||||
1. 当前没有正式运行时引用
|
|
||||||
2. 没有当前主链计划要接回
|
|
||||||
3. 删除后有明确替代路径,或者本身只是历史占位
|
|
||||||
|
|
||||||
因此这批次不碰运行时真相链、不碰鉴权链、不碰任务物品主链,只先做低风险去噪。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 本批次已处理对象
|
|
||||||
|
|
||||||
## 1.1 已删除文件
|
|
||||||
|
|
||||||
| 文件 | 判定 | 删除原因 | 替代路径 / 当前真相源 | 验证口径 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/services/customWorldPresentation.stub.ts` | 无引用占位 stub | 文件本身就是占位实现,且正式逻辑已由 `customWorldPresentation.ts` 承接 | `src/services/customWorldPresentation.ts` | 符号级检索确认正式调用方都指向正式实现 |
|
|
||||||
| `src/services/typewriter.ts` | 无引用 helper 残留 | 独立 helper 已失效,正式链路已在 `storyPresentation.ts` / `storyRenderingHelpers.ts` 等处内联或迁移 | `src/hooks/story/storyPresentation.ts`、`src/hooks/story/storyRenderingHelpers.ts` | `getTypewriterDelay` 调用点未指向该文件 |
|
|
||||||
| `src/prompts/customWorldOrchestratorPrompts.ts` | 前端孤岛 prompt 壳 | 当前无正式 import,正式主编排 prompt 已收口到后端 prompt 目录,前端 `ai.ts` 也保留自己的现行实现 | `server-node/src/prompts/customWorldOrchestratorPrompts.ts`、`src/services/ai.ts` | 全仓检索仅剩文档引用,无代码消费 |
|
|
||||||
| `src/prompts/storyOrchestratorPrompts.ts` | 前端孤岛 prompt 壳 | 当前无正式 import,剧情语言修复 prompt 已由后端 prompt 目录承接,前端当前执行路径不依赖该文件 | `server-node/src/prompts/storyOrchestratorPrompts.ts`、`src/services/ai.ts` | 全仓检索仅剩文档引用,无代码消费 |
|
|
||||||
| `src/components/custom-world-home/CustomWorldCreationLauncherModal.tsx` | 无入口 UI 壳层 | 最近两轮工程审计都确认无运行时引用,当前平台主流程未接这条入口 | 当前平台正式入口链 | 文件级检索确认无组件 import |
|
|
||||||
| `src/components/custom-world-agent/CustomWorldAgentLauncherModal.tsx` | 无入口 UI 壳层 | Agent 创作主流程已切到当前工作区链路,这个旧 modal 没有接线价值 | 当前 Agent 工作区主链 | 文件级检索确认无组件 import |
|
|
||||||
| `src/components/custom-world-agent/CustomWorldAgentDraftDrawer.tsx` | 无入口 UI 壳层 | 只有孤立 UI 实现,没有正式调用链,也不在当前结果页 / 工作区主链中 | 当前 Agent 工作区与结果页正式链 | 文件级检索确认无组件 import |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 本批次为什么先删这 7 个
|
|
||||||
|
|
||||||
这批文件适合先处理,不是因为它们最大,而是因为它们最清晰:
|
|
||||||
|
|
||||||
1. **没有正式入口。**
|
|
||||||
本轮检索没有发现主工程 import。
|
|
||||||
2. **删除后不会形成职责空洞。**
|
|
||||||
要么已有正式替代路径,要么本身只是历史占位。
|
|
||||||
3. **不会误伤当前重点链路。**
|
|
||||||
这批不涉及运行时快照、鉴权、任务、物品、AI 正式编排主链。
|
|
||||||
4. **可以最快降低目录噪音。**
|
|
||||||
先把真假并存的壳子删掉,后面做批次 B/C/D 时判断成本会更低。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 本批次暂不处理对象
|
|
||||||
|
|
||||||
以下对象虽然已进入首轮台账,但本批次暂不删除:
|
|
||||||
|
|
||||||
1. `src/components/GameShell.tsx`
|
|
||||||
2. `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
3. `src/hooks/story/storyBootstrap.ts`
|
|
||||||
4. `src/hooks/useEquipmentFlow.ts`
|
|
||||||
5. `src/hooks/useForgeFlow.ts`
|
|
||||||
6. `src/hooks/useInventoryFlow.ts`
|
|
||||||
7. `src/data/buildTagSimilarity.generated.ts`
|
|
||||||
|
|
||||||
暂缓原因分别是:
|
|
||||||
|
|
||||||
1. 仍属于旧主流程 / 旧 flow 级别对象,删除前要先核对更多历史依赖和替代路径
|
|
||||||
2. 部分对象仍有测试引用或更大的上下文耦合
|
|
||||||
3. `buildTagSimilarity.generated.ts` 虽无正式业务 import,但属于生成产物,处理前还要确认脚本链与文档链
|
|
||||||
|
|
||||||
这批对象更适合进入:
|
|
||||||
|
|
||||||
1. `批次 B:旧 flow / 旧 shell / 旧 hook`
|
|
||||||
2. 或独立的数据产物复核批次
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 本批次同步更新的文档
|
|
||||||
|
|
||||||
本批次除了删文件,还同步做了文档回填:
|
|
||||||
|
|
||||||
1. 新增本执行记录,说明本批删了什么、为什么删、哪些对象暂缓
|
|
||||||
2. 更新 `docs/audits/engineering/README.md`,把这份执行记录加入当前审计入口
|
|
||||||
|
|
||||||
这样做的目的,是避免再次出现:
|
|
||||||
|
|
||||||
1. 代码删了
|
|
||||||
2. 但审计入口还是旧状态
|
|
||||||
3. 后续开发又从旧清单里重复判断一遍
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 验证方式
|
|
||||||
|
|
||||||
本批次验证采用两层口径:
|
|
||||||
|
|
||||||
## 5.1 删除前验证
|
|
||||||
|
|
||||||
1. 文件级检索确认无正式 import
|
|
||||||
2. 符号级检索确认关键导出没有被主链消费
|
|
||||||
3. 结合 `2026-04-20` 工程审计交叉确认这些对象已被标记为高置信度孤岛
|
|
||||||
|
|
||||||
## 5.2 删除后验证
|
|
||||||
|
|
||||||
建议至少执行:
|
|
||||||
|
|
||||||
1. `npm run check:encoding`
|
|
||||||
2. `npm run build`
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
- 当前仓库已知 `typecheck` 与 `lint` 仍处于红线阶段,因此本批不把它们作为“由本批引入的新失败”判断口径
|
|
||||||
- 本批主要验证目标是:删除小残留后,不产生新的导入断裂和构建断裂
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 本批次结果判断
|
|
||||||
|
|
||||||
本批次完成后,工程至少获得了 3 个直接收益:
|
|
||||||
|
|
||||||
1. `src/prompts/`、`src/services/`、`src/components/custom-world-*` 中少了一批无入口孤岛
|
|
||||||
2. 当前目录里“看起来像正式入口,其实已经废弃”的误导性对象减少
|
|
||||||
3. 后续可以把精力集中到真正高价值的批次 B/C/D,而不是继续被小残留分散判断成本
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 下一批建议
|
|
||||||
|
|
||||||
建议严格按计划继续往下推进:
|
|
||||||
|
|
||||||
1. 批次 B:`GameShell`、`storyBootstrap`、`useEquipmentFlow`、`useForgeFlow`、`useInventoryFlow`
|
|
||||||
2. 批次 C:`runtimeStoryCoordinator`、`runtimeStoryService`、`apiClient`
|
|
||||||
3. 批次 D:`npcEncounterActions`、`questDirector`、`runtimeItemAiDirector`、`ai.ts`
|
|
||||||
|
|
||||||
一句话总结本批次:
|
|
||||||
|
|
||||||
**先把最确定的死分支和占位壳子清掉,让主工程少一些假入口、假主源、假能力,再进入更重的主链收口。**
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
# 工程死分支清理执行记录 B(2026-04-21)
|
|
||||||
|
|
||||||
更新时间:`2026-04-21`
|
|
||||||
|
|
||||||
## 0. 本批次目标
|
|
||||||
|
|
||||||
这份记录对应清洗计划中的:
|
|
||||||
|
|
||||||
- `批次 B:旧 flow / 旧 shell / 旧 hook`
|
|
||||||
|
|
||||||
本批次聚焦的不是小型 stub,而是:
|
|
||||||
|
|
||||||
**已经退出正式主流程、但仍占着高辨识度命名和旧职责心智的壳层与流程 Hook。**
|
|
||||||
|
|
||||||
这类文件如果继续留在仓库里,问题比小 helper 更大,因为它们会持续制造误判:
|
|
||||||
|
|
||||||
1. 新人会以为它们还是正式入口
|
|
||||||
2. 后续开发会误判“应该往这里接逻辑”
|
|
||||||
3. review 时会多出一层“旧主链是不是还活着”的判断成本
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 本批次已处理对象
|
|
||||||
|
|
||||||
## 1.1 已删除文件
|
|
||||||
|
|
||||||
| 文件 | 判定 | 删除原因 | 替代路径 / 当前真相源 | 验证口径 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/GameShell.tsx` | 旧主流程壳层残留 | 当前正式壳层已由 `src/components/game-shell/GameShellRuntime.tsx` 承接,旧文件无正式 import | `src/components/game-shell/GameShellRuntime.tsx`、`src/hooks/useGameShellRuntime.ts` | 全仓检索未发现对旧 `GameShell` 组件的正式消费 |
|
|
||||||
| `src/hooks/story/storyBootstrap.ts` | 旧启动流程 Hook 残留 | 当前主剧情启动链已不再调用该 Hook,继续保留只会误导人以为它还是故事初始化入口 | 当前 story runtime / coordinator 链 | 全仓检索未发现 `useStoryBootstrap` 消费方 |
|
|
||||||
| `src/hooks/useEquipmentFlow.ts` | 旧装备流程 Hook 残留 | 当前正式背包与装备链未消费该 Hook,属于旧流程实现残留 | 当前 inventory / runtime 正式链 | 符号级检索仅命中定义文件自身 |
|
|
||||||
| `src/hooks/useForgeFlow.ts` | 旧锻造流程 Hook 残留 | 当前正式锻造入口未通过该 Hook 进入主链,保留会制造旧流程错觉 | 当前 inventory / runtime 正式链 | 符号级检索仅命中定义文件自身 |
|
|
||||||
| `src/hooks/useInventoryFlow.ts` | 旧背包使用流程 Hook 残留 | 当前主流程未消费该 Hook,属于旧状态推进实现残留 | 当前 inventory / runtime 正式链 | 符号级检索仅命中定义文件自身 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 为什么这批要紧跟批次 A 处理
|
|
||||||
|
|
||||||
批次 A 清掉的是“小型假入口”。
|
|
||||||
|
|
||||||
批次 B 清掉的是“高辨识度旧主链”。
|
|
||||||
|
|
||||||
这批必须紧跟着做,原因是:
|
|
||||||
|
|
||||||
1. 它们虽然比 stub 更大,但引用关系同样清楚
|
|
||||||
2. 它们的误导性比小残留更强
|
|
||||||
3. 不先处理这批,后面做批次 C/D 时,很容易继续有人拿旧 flow Hook 当候选接线点
|
|
||||||
|
|
||||||
一句话讲:
|
|
||||||
|
|
||||||
**批次 A 是去噪,批次 B 是拔掉旧路牌。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 本批次删除后的结构变化
|
|
||||||
|
|
||||||
本批次完成后,仓库里的流程心智会更清楚:
|
|
||||||
|
|
||||||
1. 游戏壳层正式入口继续收敛到 `src/components/game-shell/**`
|
|
||||||
2. 旧 `GameShell.tsx` 不再和 `GameShellRuntime.tsx` 并存
|
|
||||||
3. 旧的装备 / 锻造 / 背包单独 flow Hook 不再伪装成还在生效的正式实现
|
|
||||||
4. 旧 `storyBootstrap` 不再和当前 story runtime 链并存
|
|
||||||
|
|
||||||
这会直接减少两类误判:
|
|
||||||
|
|
||||||
1. “是不是还有旧主流程没迁完”
|
|
||||||
2. “我是不是应该把新逻辑继续补进这些旧 Hook”
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 本批次暂不处理对象
|
|
||||||
|
|
||||||
虽然批次 B 已经处理了旧 shell / old flow / old bootstrap,但以下对象仍暂缓:
|
|
||||||
|
|
||||||
1. `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
2. `src/data/buildTagSimilarity.generated.ts`
|
|
||||||
3. 批次 C 的运行时真相链:
|
|
||||||
- `src/hooks/story/runtimeStoryCoordinator.ts`
|
|
||||||
- `src/services/runtimeStoryService.ts`
|
|
||||||
- `src/services/apiClient.ts`
|
|
||||||
4. 批次 D 的混合执行层:
|
|
||||||
- `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
- `src/services/questDirector.ts`
|
|
||||||
- `src/services/runtimeItemAiDirector.ts`
|
|
||||||
- `src/services/ai.ts`
|
|
||||||
|
|
||||||
暂缓原因很明确:
|
|
||||||
|
|
||||||
1. 这些对象要么仍在当前正式链上
|
|
||||||
2. 要么涉及运行时真相与鉴权边界
|
|
||||||
3. 不能按“无引用旧壳”同一口径直接删除
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 本批次验证方式
|
|
||||||
|
|
||||||
## 5.1 删除前验证
|
|
||||||
|
|
||||||
1. 全仓检索 `GameShell` 旧组件消费方,确认当前正式壳层已切到 `game-shell/` 目录
|
|
||||||
2. 全仓检索 `useStoryBootstrap`
|
|
||||||
3. 全仓检索旧装备 / 锻造 / 背包 flow Hook 导出的 handler 名称
|
|
||||||
4. 交叉确认当前正式主链入口已存在替代实现
|
|
||||||
|
|
||||||
## 5.2 删除后验证
|
|
||||||
|
|
||||||
建议至少执行:
|
|
||||||
|
|
||||||
1. `npm run check:encoding`
|
|
||||||
2. `npm run build`
|
|
||||||
|
|
||||||
如果这两项通过,说明:
|
|
||||||
|
|
||||||
1. 删除没有引入新的导入断裂
|
|
||||||
2. 主工程构建链仍然成立
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 本批次结果判断
|
|
||||||
|
|
||||||
本批次完成后,工程获得的直接收益是:
|
|
||||||
|
|
||||||
1. 旧主流程壳层不再和现行壳层并存
|
|
||||||
2. 旧流程 Hook 不再占据 `src/hooks/` 的主路径注意力
|
|
||||||
3. 当前正式入口和历史残留的边界更清楚
|
|
||||||
4. 后续开发更不容易把新逻辑接回旧流程壳子
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 下一批建议
|
|
||||||
|
|
||||||
建议下一步进入真正有结构价值的收口:
|
|
||||||
|
|
||||||
1. `批次 C:运行时真相收口`
|
|
||||||
- `runtimeStoryCoordinator`
|
|
||||||
- `runtimeStoryService`
|
|
||||||
- `apiClient`
|
|
||||||
2. `批次 D:任务 / 物品 / AI 混合执行层收口`
|
|
||||||
- `npcEncounterActions`
|
|
||||||
- `questDirector`
|
|
||||||
- `runtimeItemAiDirector`
|
|
||||||
- `ai.ts`
|
|
||||||
|
|
||||||
一句话总结本批次:
|
|
||||||
|
|
||||||
**这一步不是在“删几个没用 Hook”,而是在把已经退场的旧主流程壳层和旧 flow 路牌从主工程里真正拔掉,让现行架构不再和历史壳子并排站着。**
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
# 工程死分支清理执行记录 C(2026-04-21)
|
|
||||||
|
|
||||||
更新时间:`2026-04-21`
|
|
||||||
|
|
||||||
## 0. 本批次目标
|
|
||||||
|
|
||||||
这份记录对应清洗计划中的:
|
|
||||||
|
|
||||||
- `批次 C:运行时真相收口`
|
|
||||||
|
|
||||||
但这次不是“一口气把运行时真相链全删干净”,而是先做其中最明确、风险最低、最不该继续拖的那一段:
|
|
||||||
|
|
||||||
1. **收掉前端本地自动登录用户名 / 密码真相**
|
|
||||||
2. **把登录恢复改成优先依赖服务端 session / refresh**
|
|
||||||
|
|
||||||
同时,这一批也明确记录了一件事:
|
|
||||||
|
|
||||||
**运行时快照前置写入链当前还不能直接砍。**
|
|
||||||
|
|
||||||
原因不是“不想动”,而是服务端当前 `runtime story` 动作入口仍然以远端快照作为执行基线。
|
|
||||||
在后端 contract 没先改好之前,前端不能假装自己已经退出这条链。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 本批次已处理对象
|
|
||||||
|
|
||||||
## 1.1 已收口的鉴权链
|
|
||||||
|
|
||||||
| 文件 | 处理动作 | 本批结论 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `src/services/apiClient.ts` | 删除本地自动登录用户名 / 密码存取逻辑 | 前端不再保存 auto auth 账号密码 |
|
|
||||||
| `src/services/authService.ts` | 去掉对本地游客凭证的读写依赖 | 自动游客登录改为仅本次生成凭证,不再长期落本地 |
|
|
||||||
| `src/components/auth/AuthGate.tsx` | 去掉“必须先有本地 access token 才尝试恢复”的前置假设 | 登录恢复改为优先尝试服务端 `getCurrentAuthUser()` / refresh session |
|
|
||||||
| `src/services/authService.test.ts` | 改写游客自动登录相关断言 | 验证改为“生成临时凭证并完成登录”,而不是“落本地账号密码” |
|
|
||||||
| `src/components/auth/AuthGate.test.tsx` | 改写登录恢复 mock | 验证改为“先尝试服务端会话恢复,再决定是否走游客兜底” |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 本批次为什么先做这段
|
|
||||||
|
|
||||||
这批优先级高,是因为它同时满足 4 条:
|
|
||||||
|
|
||||||
1. **风险明确。**
|
|
||||||
浏览器保存自动登录用户名 / 密码,本身就不符合“前端只做表现、后端负责鉴权真相”的方向。
|
|
||||||
2. **替代路径已经存在。**
|
|
||||||
后端已经有 refresh session cookie 与 `getCurrentAuthUser()`,不是没有可替代能力。
|
|
||||||
3. **改动边界清楚。**
|
|
||||||
这一段主要落在前端鉴权恢复逻辑和测试,不会直接波及运行时战斗、任务、物品、剧情主链。
|
|
||||||
4. **收益直接。**
|
|
||||||
一旦收掉,前端就少了一份最不该长期保留的高风险真相。
|
|
||||||
|
|
||||||
一句话讲:
|
|
||||||
|
|
||||||
**这一步先把“浏览器记住游客账号密码再重登”这条假真相链拔掉。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 本批次明确没做的事
|
|
||||||
|
|
||||||
## 3.1 没有直接删除 `runtimeStoryCoordinator.ts` 里的前置 `putSaveSnapshot(...)`
|
|
||||||
|
|
||||||
这不是漏做,而是明确暂缓。
|
|
||||||
|
|
||||||
当前复核结果是:
|
|
||||||
|
|
||||||
1. `server-node/src/modules/story/storyActionService.ts`
|
|
||||||
2. `server-node/src/routes/runtimeRoutes.ts`
|
|
||||||
3. `server-node/src/repositories/runtimeRepository.ts`
|
|
||||||
|
|
||||||
这条后端链当前仍然通过远端快照读取运行时状态,再执行:
|
|
||||||
|
|
||||||
1. `getRuntimeStoryState`
|
|
||||||
2. `resolveRuntimeStoryAction`
|
|
||||||
|
|
||||||
也就是说,当前真实情况不是“前端多写了一份完全没用的镜像”,而是:
|
|
||||||
|
|
||||||
**前端在提交动作前先把当前状态写回远端快照,后端再基于这份快照执行业务动作。**
|
|
||||||
|
|
||||||
在这个 contract 没先升级为“前端只发 action,后端自己持有完整 session 真相”之前,前端不能直接把这一步砍掉。
|
|
||||||
|
|
||||||
否则会出现:
|
|
||||||
|
|
||||||
1. 动作请求仍在走
|
|
||||||
2. 但服务端读取到的执行基线不完整
|
|
||||||
3. 最后不是收口真相,而是把主链打断
|
|
||||||
|
|
||||||
## 3.2 没有删除 `runtimeStoryService.ts` / `runtimeStoryCoordinator.ts` 的快照再水合逻辑
|
|
||||||
|
|
||||||
这一步本轮也做了复核,结论是:
|
|
||||||
|
|
||||||
1. 我曾尝试把 `runtimeStoryCoordinator.ts` 中对服务端返回快照的重复再水合去掉
|
|
||||||
2. 但对应的 `runtimeStoryCoordinator` 测试立即暴露出:当前后端返回的快照在部分战斗场景下还不是完整水合态
|
|
||||||
3. 说明前端当前这层再水合仍然有现实职责,不是纯多余代码
|
|
||||||
|
|
||||||
所以这一步本批明确结论是:
|
|
||||||
|
|
||||||
**暂不删除,等后端快照 contract 先补完整后再做。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 本批次验证结果
|
|
||||||
|
|
||||||
本批次已完成的定向验证:
|
|
||||||
|
|
||||||
1. `npx vitest run src/services/authService.test.ts`
|
|
||||||
2. `npx vitest run src/components/auth/AuthGate.test.tsx`
|
|
||||||
3. `npx vitest run src/hooks/story/runtimeStoryCoordinator.test.ts`
|
|
||||||
4. `npm run check:encoding`
|
|
||||||
|
|
||||||
结果:
|
|
||||||
|
|
||||||
1. `authService` 测试通过
|
|
||||||
2. `AuthGate` 测试通过
|
|
||||||
3. `runtimeStoryCoordinator` 测试通过
|
|
||||||
4. 编码检查通过
|
|
||||||
|
|
||||||
另外执行了:
|
|
||||||
|
|
||||||
1. `npm run build`
|
|
||||||
|
|
||||||
结果:
|
|
||||||
|
|
||||||
构建产物生成成功,但 `build-gate` 仍因主包 chunk warning 拦截失败。
|
|
||||||
当前失败点仍是已知的主包体积问题:
|
|
||||||
|
|
||||||
- `AuthenticatedApp-*.js` 超过当前 warning 门槛
|
|
||||||
|
|
||||||
这属于仓库当前既有工程问题,不是本批次引入的新断裂。
|
|
||||||
|
|
||||||
## 4.1 2026-04-21 补充修正:会话探测 401 自触发循环
|
|
||||||
|
|
||||||
在这批收口完成后,前端又暴露出一条更细的鉴权恢复回路问题:
|
|
||||||
|
|
||||||
1. `AuthGate` 启动时会调用 `getCurrentAuthUser()` 探测现有会话
|
|
||||||
2. `/api/auth/me` 返回 `401` 时,`apiClient.ts` 会默认广播一次 `AUTH_STATE_EVENT`
|
|
||||||
3. `AuthGate` 自己又监听这个事件并重新 `hydrate()`
|
|
||||||
4. 最终形成 `hydrate -> /auth/me 401 -> emit -> hydrate` 的自触发循环
|
|
||||||
|
|
||||||
这条链的问题不在“是否允许 401”,而在:
|
|
||||||
|
|
||||||
**会话探测请求把“未登录态探测”错误地当成了“全局登录态变更”。**
|
|
||||||
|
|
||||||
因此这里补了一条更细粒度的约束:
|
|
||||||
|
|
||||||
1. `apiClient.ts` 新增 `notifyAuthStateChange` 选项,默认仍保持原有广播行为
|
|
||||||
2. `getCurrentAuthUser()` 作为会话探测请求,显式关闭这类 401 广播
|
|
||||||
3. 真实登录、登出、刷新成功后,仍保留全局鉴权变更通知
|
|
||||||
|
|
||||||
这样修完后:
|
|
||||||
|
|
||||||
1. `AuthGate` 仍会优先尝试服务端会话恢复
|
|
||||||
2. 无会话时会正常落回未登录分支
|
|
||||||
3. 不会因为探测型 401 把自己重新唤醒并刷爆控制台
|
|
||||||
|
|
||||||
## 4.2 2026-04-22 补充修正:公开认证入口误触发 refresh
|
|
||||||
|
|
||||||
在登录弹窗链路继续联调时,又暴露出一条更细的请求边界问题:
|
|
||||||
|
|
||||||
1. 用户处于未登录态,浏览器本地没有 access token
|
|
||||||
2. 点击“获取验证码”会调用 `sendPhoneLoginCode()`
|
|
||||||
3. `authService.ts` 复用了通用 `requestJson(...)`
|
|
||||||
4. `apiClient.ts` 在“无本地 token 且未显式关闭 refresh”时,会先尝试 `POST /api/auth/refresh`
|
|
||||||
5. 若当前浏览器本来也没有 refresh session cookie,就会先打出一条 `401 Unauthorized`
|
|
||||||
6. 最终表现成:验证码接口真正发送前,前端控制台先报一次 `/api/auth/refresh 401`
|
|
||||||
|
|
||||||
这条链的问题不在“验证码接口失败”,而在:
|
|
||||||
|
|
||||||
**登录前公开认证入口被错误当成了需要先补票的受保护请求。**
|
|
||||||
|
|
||||||
因此这里再补一条明确约束:
|
|
||||||
|
|
||||||
1. `sendPhoneLoginCode()`
|
|
||||||
2. `loginWithPhoneCode()`
|
|
||||||
3. `authEntry()`
|
|
||||||
4. `getAuthLoginOptions()`
|
|
||||||
5. `startWechatLogin()`
|
|
||||||
|
|
||||||
以上这些“获取登录态之前”的公开认证入口,统一显式传入:
|
|
||||||
|
|
||||||
1. `skipAuth: true`
|
|
||||||
2. `skipRefresh: true`
|
|
||||||
|
|
||||||
这样修完后:
|
|
||||||
|
|
||||||
1. 未登录用户点击“获取验证码”不会先打 `/api/auth/refresh`
|
|
||||||
2. 公开认证入口不会误带旧 token,也不会制造无意义的 401 噪音
|
|
||||||
3. 真正需要 refresh 的仍然只有已拿到登录态后的受保护请求
|
|
||||||
|
|
||||||
本次补修的定向验证:
|
|
||||||
|
|
||||||
1. `npx vitest run src/services/authService.test.ts`
|
|
||||||
2. `npm run check:encoding`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 本批次完成后的实际收益
|
|
||||||
|
|
||||||
这一步完成后,工程在鉴权边界上有了两个明确改善:
|
|
||||||
|
|
||||||
1. **前端不再保存自动登录用户名 / 密码。**
|
|
||||||
浏览器只保留 access token,本地高风险游客凭证真相已经收掉。
|
|
||||||
2. **登录恢复逻辑更接近服务端为真相源。**
|
|
||||||
`AuthGate` 不再假设“没有本地 token 就一定还没登录”,而是优先尝试服务端会话恢复。
|
|
||||||
|
|
||||||
这意味着前端鉴权链已经从:
|
|
||||||
|
|
||||||
```text
|
|
||||||
本地用户名/密码 -> 再次 entry -> 拿 token
|
|
||||||
```
|
|
||||||
|
|
||||||
进一步收到了:
|
|
||||||
|
|
||||||
```text
|
|
||||||
refresh session / 当前会话 -> 恢复用户
|
|
||||||
兜底时才创建一次游客凭证
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 本批次后续建议
|
|
||||||
|
|
||||||
要继续完成批次 C,下一步不该直接在前端硬删,而应该先补后端 contract:
|
|
||||||
|
|
||||||
1. 让 `runtime story` 动作链逐步摆脱“前端先写远端快照”的依赖
|
|
||||||
2. 让服务端自己持有更完整的运行时 session 真相
|
|
||||||
3. 等后端返回快照已经稳定水合后,再删前端的重复再水合
|
|
||||||
|
|
||||||
换句话说,批次 C 的后半段应该拆成:
|
|
||||||
|
|
||||||
1. **C-1:鉴权真相收口**
|
|
||||||
本批已完成
|
|
||||||
2. **C-2:运行时快照 contract 后端化**
|
|
||||||
需要先改后端
|
|
||||||
3. **C-3:前端镜像写入与重复水合退场**
|
|
||||||
依赖 C-2
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 一句话总结
|
|
||||||
|
|
||||||
**批次 C 这一轮已经先把“浏览器长期保存游客账号密码”这条最不该存在的鉴权假真相链收掉了;而运行时快照前置写入这条链经过复核确认仍受后端 contract 约束,不能在服务端未先补齐前硬砍。**
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
# 工程死分支清理执行记录 D(2026-04-21)
|
|
||||||
|
|
||||||
更新时间:`2026-04-21`
|
|
||||||
|
|
||||||
## 0. 本批次目标
|
|
||||||
|
|
||||||
本批次继续清理上一轮复核后剩余的低风险数据产物与测试占位:
|
|
||||||
|
|
||||||
1. 未接入业务的生成产物
|
|
||||||
2. 只为测试替换真实实现的空 stub
|
|
||||||
3. 支撑这些残留的配置与脚本
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 已删除对象
|
|
||||||
|
|
||||||
| 文件 | 判定 | 删除原因 | 替代路径 / 当前真相源 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `src/data/buildTagSimilarity.generated.ts` | 未接入业务的生成产物 | 运行时代码不 import;Build 相似度当前由 `buildTags.ts` 中的属性亲和度逻辑计算 | `src/data/buildTags.ts` |
|
|
||||||
| `scripts/generate-build-tag-similarity.py` | 已无输出目标的生成脚本 | 只负责生成已删除的矩阵文件,继续保留会误导后续开发恢复旧主源 | `src/data/buildTags.ts` 的手工审表逻辑 |
|
|
||||||
| `src/data/customWorldCharacterLoadout.stub.ts` | 测试专用空 stub | 只通过 `vitest.config.ts` alias 替换真实实现;真实实现已经稳定存在 | `src/data/customWorldCharacterLoadout.ts` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 同步更新
|
|
||||||
|
|
||||||
本批次同步移除了:
|
|
||||||
|
|
||||||
1. `vitest.config.ts` 中指向 `customWorldCharacterLoadout.stub.ts` 的 alias
|
|
||||||
2. `BUILD_SYSTEM_ATTRIBUTE_SIMILARITY_PRD_2026-04-02.md` 中把旧 generated 矩阵描述为当前文件的表述
|
|
||||||
3. 清理计划里对 `buildTagSimilarity.generated.ts` 的未处理状态说明
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 验证口径
|
|
||||||
|
|
||||||
删除前已确认:
|
|
||||||
|
|
||||||
1. `buildTagSimilarity.generated.ts` 无运行时代码引用
|
|
||||||
2. `customWorldCharacterLoadout.stub.ts` 只被 `vitest.config.ts` alias 引用
|
|
||||||
3. 真实 `customWorldCharacterLoadout.ts` 仍被 `characterPresets.ts` 与 `npcInteractions.ts` 使用,不能删除
|
|
||||||
|
|
||||||
删除后建议验证:
|
|
||||||
|
|
||||||
1. `npm run check:encoding`
|
|
||||||
2. 与自定义世界开局物品相关的测试
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 当前结论
|
|
||||||
|
|
||||||
本批次完成后,剩余清理对象已经不再适合按“无引用直接删”推进。后续如果继续清,需要先改 contract 或主链职责:
|
|
||||||
|
|
||||||
1. 运行时快照真相链
|
|
||||||
2. 任务 / 物品 / AI 混合执行层
|
|
||||||
3. 大型主流程组件继续拆分,而不是直接删除
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
# 工程死分支清理执行记录 E(2026-04-21)
|
|
||||||
|
|
||||||
更新时间:`2026-04-21`
|
|
||||||
|
|
||||||
## 0. 本批次目标
|
|
||||||
|
|
||||||
本批次承接批次 D,继续清掉已经退出 RPG 游戏创作主流程、RPG 运行时玩法主流程、平台基本功能主流程的历史壳层。
|
|
||||||
|
|
||||||
本批次不处理仍需后端 contract 先收口的对象,例如:
|
|
||||||
|
|
||||||
1. `src/services/questDirector.ts`
|
|
||||||
2. `src/services/runtimeItemAiDirector.ts`
|
|
||||||
3. `src/hooks/rpg-runtime-story/runtimeStoryCoordinator.ts`
|
|
||||||
4. `src/services/apiClient.ts`
|
|
||||||
|
|
||||||
这些对象仍属于“前端越界逻辑继续后端化”的后续批次,不按无引用文件直接删除。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 删除判定口径
|
|
||||||
|
|
||||||
本批只删除满足下面条件之一的对象:
|
|
||||||
|
|
||||||
1. 无运行时入口、无脚本入口、无当前路由挂载。
|
|
||||||
2. 已有现行正式实现,旧文件只剩 re-export / facade / 兼容命名。
|
|
||||||
3. 只被测试验证旧壳自身,且该测试不再服务当前主流程门禁。
|
|
||||||
4. 文档已明确该对象处于“后续只允许收缩、不再接新逻辑”的兼容残留状态。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 本批次已处理对象
|
|
||||||
|
|
||||||
| 文件 | 判定 | 删除原因 | 替代路径 / 当前真相源 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `server-node/src/routes/rpgCreationAgentRoutes.ts` | 旧命名 re-export | 当前后端正式路由直接使用 `customWorldAgent.ts` | `server-node/src/routes/customWorldAgent.ts` |
|
|
||||||
| `server-node/src/routes/rpgWorldGalleryRoutes.ts` | 空路由骨架 | 世界广场实际列表和详情已经进入世界库路由 | `server-node/src/routes/rpg-entry/rpgWorldLibraryRoutes.ts` |
|
|
||||||
| `server-node/src/services/RpgAgentOrchestrator.ts` | 旧命名 re-export | 当前正式上下文直接使用 `CustomWorldAgentOrchestrator` | `server-node/src/services/customWorldAgentOrchestrator.ts` |
|
|
||||||
| `server-node/src/services/RpgAgentSessionStore.ts` | 旧命名 re-export | 当前正式上下文直接使用 `CustomWorldAgentSessionStore` | `server-node/src/services/customWorldAgentSessionStore.ts` |
|
|
||||||
| `server-node/src/services/customWorldWorkSummaryService.ts` | 旧兼容入口 | 测试和路由已改为直接使用 RPG 命名服务 | `server-node/src/services/RpgWorldWorkSummaryService.ts` |
|
|
||||||
| `server-node/src/services/customWorldAgentPublishGateService.ts` | 旧发布门禁实现 | 当前 action executor 与作品库发布链已统一走 PublishingService | `server-node/src/services/customWorldAgentPublishingService.ts` |
|
|
||||||
| `server-node/src/services/customWorldAgentPublishService.ts` | 旧发布实现 | 当前发布链不再编译旧 legacy result profile | `server-node/src/services/customWorldAgentPublishingService.ts` |
|
|
||||||
| `server-node/src/modules/custom-world/runtime-profile/runtimeProfileCompiler.ts` | 旧 facade | runtime profile 已拆到目录模块并由 `index.ts` / `runtimeProfile.ts` 承接 | `server-node/src/modules/custom-world/runtime-profile/index.ts` |
|
|
||||||
| `server-node/src/bridges/legacyBuildRuntimeBridge.ts` | 无引用旧桥 | 后端 runtime build / equipment 已直接在正式模块内使用 | `server-node/src/modules/runtime/**` |
|
|
||||||
| `server-node/src/bridges/legacyRuntimeItemResolutionBridge.ts` | 旧桥 | runtime item 解析服务一并删除,正式运行时使用 `runtimeItemModule.ts` | `server-node/src/modules/runtime-item/runtimeItemModule.ts` |
|
|
||||||
| `server-node/src/modules/runtime-item/runtimeItemResolutionService.ts` | 无正式入口 wrapper | 只被 barrel 和自身测试引用,未挂入 Express 运行时主链 | `server-node/src/modules/runtime-item/runtimeItemModule.ts` |
|
|
||||||
| `server-node/src/modules/**/index.ts` | 无引用 barrel | 这些 barrel 没有被当前后端入口消费,反而制造“公共模块入口仍存在”的错觉 | 直接 import 具体正式模块 |
|
|
||||||
| `server-node/src/routes/rpg-*/index.ts` | 无引用 barrel | 当前 Express app 直接 import 具体 route 文件 | `server-node/src/app.ts` 中的具体路由 |
|
|
||||||
| `server-node/src/repositories/rpg-*/index.ts` | 无引用 barrel | 当前上下文直接 import 具体 repository | `server-node/src/server.ts` 中的具体仓储 |
|
|
||||||
| `src/components/DeveloperTeamModal.tsx` | 无入口 UI | 平台主流程没有打开该弹窗的入口 | 无替代 UI,删除历史壳 |
|
|
||||||
| `src/components/LazySkillEffectPreview.tsx` | 无入口 lazy 壳 | 正式技能预览直接使用 `SkillEffectPreview` | `src/components/SkillEffectPreview.tsx` |
|
|
||||||
| `src/components/npcVisualEditorModel.ts` | 旧 NPC 形象写回模型 | 当前 RPG 创作编辑器使用 `CustomWorldNpcVisualEditor` 与结果页新入口 | `src/components/CustomWorldNpcVisualEditor.tsx`、`src/components/rpg-creation-editor/**` |
|
|
||||||
| `src/components/npcVisualEditorPersistence.ts` | 旧 NPC 形象写回持久层 | 只被旧持久化测试引用,正式编辑入口已迁移 | `src/components/rpg-creation-editor/**` |
|
|
||||||
| `src/components/rpg-creation-*/index.ts` | 无引用 barrel | 当前入口直接 import 具体 facade 文件,barrel 没有主流程消费 | 直接 import `RpgCreation*` 具体文件 |
|
|
||||||
| `src/components/rpg-creation-editor/CustomWorldSceneChapterEditorSection.tsx` | 旧 facade | 当前编辑器 section 直接在 `RpgCreationEntityEditorShared.tsx` 中分发 | `src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx` |
|
|
||||||
| `src/data/editorValidation.ts` | 旧预设编辑器校验 | 当前主流程和内容门禁不再调用 | `scripts/validate-overrides.ts`、后端 editor API |
|
|
||||||
| `src/editor/shared/EditorNotice.tsx` | 无入口共享 UI | 只被同批删除的 FormFields 使用 | 无替代 UI,删除历史编辑器壳 |
|
|
||||||
| `src/editor/shared/FormFields.tsx` | 无入口共享 UI | 旧编辑器共享表单未接主流程 | 当前 RPG 编辑器组件内聚在 `rpg-creation-editor/**` |
|
|
||||||
| `src/editor/shared/SectionCard.tsx` | 无入口共享 UI | 旧编辑器卡片未接主流程 | 当前 RPG 编辑器组件内聚在 `rpg-creation-editor/**` |
|
|
||||||
| `src/hooks/rpg-runtime-story/npcEncounterActions.ts` | 旧 wrapper | 正式实现已在 `useRpgRuntimeNpcInteraction.ts`,测试已改到正式文件 | `src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts` |
|
|
||||||
| `src/hooks/rpg-runtime-story/openingAdventure.ts` | 旧前端开局特殊流程 | 开局营地对白已由后端 `RpgRuntimeStoryActionDomain` 和当前 story context 承接 | `server-node/src/modules/rpg-runtime-story/RpgRuntimeStoryActionDomain.ts` |
|
|
||||||
| `src/hooks/rpg-runtime-story/storyCampCompanion.ts` | 旧前端营地同伴 helper | 只剩旧开局流程和自身测试引用,正式开局上下文已迁到当前 runtime story 链 | 后端 runtime story action domain 与 `storyContextBuilder.ts` |
|
|
||||||
| `src/hooks/rpg-runtime-story/storyRenderingHelpers.ts` | 无入口旧渲染 helper | 当前正式 story presentation 不再 import | `src/hooks/rpg-runtime-story/storyPresentation.ts` |
|
|
||||||
| `src/prompts/questPrompts.ts` | 前端 prompt 残留 | Quest prompt 真相已迁到后端 | `server-node/src/prompts/questPrompts.ts` |
|
|
||||||
| `src/prompts/runtimeItemPrompts.ts` | 前端 prompt 残留 | Runtime item prompt 真相已迁到后端 | `server-node/src/prompts/runtimeItemPrompts.ts` |
|
|
||||||
| `src/services/questPrompt.ts` | 前端 prompt re-export | 只指向同批删除的前端 prompt | `server-node/src/prompts/questPrompts.ts` |
|
|
||||||
| `src/services/runtimeItemAiPrompt.ts` | 前端 prompt re-export | 只指向同批删除的前端 prompt | `server-node/src/prompts/runtimeItemPrompts.ts` |
|
|
||||||
| `src/services/storyEngine/contentDependencyGraph.ts` | 实验性孤岛 | 只被自身测试引用,没有主流程消费 | 后续如需要重新设计到后端 story graph 服务 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 同步调整
|
|
||||||
|
|
||||||
1. `customWorldAgentPhase2/3/4` 测试改为直接实例化 `RpgWorldWorkSummaryService`。
|
|
||||||
2. `customWorldWorkSummaryService.integration.test.ts` 改为直接覆盖 `RpgWorldWorkSummaryService`。
|
|
||||||
3. `npcEncounterActions.test.ts` 改为直接覆盖 `useRpgRuntimeNpcInteraction.ts`,不再通过旧 wrapper。
|
|
||||||
4. `story_opening_camp_dialogue` 的 function catalog 执行路径改为后端 runtime action domain,不再指向已删除旧前端文件。
|
|
||||||
5. NPC function catalog 中 `npc_chat / npc_help / npc_leave / npc_fight / npc_spar / npc_preview_talk` 的 executor 路牌改到现行 `useRpgRuntimeNpcInteraction.ts`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 本批次暂缓对象
|
|
||||||
|
|
||||||
以下对象仍然保留,原因是它们不是“无引用死代码”,而是需要下一轮按 contract 或主链职责迁移:
|
|
||||||
|
|
||||||
1. `src/services/questDirector.ts`
|
|
||||||
2. `src/services/runtimeItemAiDirector.ts`
|
|
||||||
3. `src/services/ai.ts`
|
|
||||||
4. `src/data/sceneObservation.ts`
|
|
||||||
5. `server-node/ecosystem.config.cjs`
|
|
||||||
6. `server-node/src/scripts/syncCustomWorldSavedProfileAssets.ts`
|
|
||||||
|
|
||||||
其中 `ecosystem.config.cjs` 被部署脚本直接使用;`sceneObservation.ts` 被内容 smoke 脚本验证;`syncCustomWorldSavedProfileAssets.ts` 是一次性运维脚本,后续要单独按运维脚本治理口径确认是否归档。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 验证口径
|
|
||||||
|
|
||||||
本批删除后建议验证:
|
|
||||||
|
|
||||||
1. `npm run check:encoding`
|
|
||||||
2. `npx tsx --test server-node/src/services/customWorldWorkSummaryService.integration.test.ts`
|
|
||||||
3. `npx vitest run src/hooks/rpg-runtime-story/npcEncounterActions.test.ts`
|
|
||||||
4. `npm run server-node:build`
|
|
||||||
5. `npm run build`
|
|
||||||
|
|
||||||
如果 `npm run build` 仍被既有 chunk warning 拦截,需要单独记录为既有门禁问题,不归因到本批删除。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 当前结论
|
|
||||||
|
|
||||||
本批次进一步删除了“旧命名入口、旧 facade、旧 prompt 前端镜像、无入口编辑器壳层”这批容易误导后续开发的文件。
|
|
||||||
|
|
||||||
后续清理不应继续按“静态无引用”直接推进,而应进入两类工作:
|
|
||||||
|
|
||||||
1. 运行时 / 任务 / 物品 / AI 的后端 contract 收口。
|
|
||||||
2. RPG 创作编辑器与运行时热点文件的职责拆分。
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
# 工程死分支清理执行记录 F(2026-04-21)
|
|
||||||
|
|
||||||
更新时间:`2026-04-21`
|
|
||||||
|
|
||||||
## 0. 本批次目标
|
|
||||||
|
|
||||||
本批次承接批次 E 的验证结果,继续处理删除后暴露出的最后一组高置信残留:
|
|
||||||
|
|
||||||
1. 已经没有任何代码入口引用的前端任务生成 director。
|
|
||||||
2. 只被内容 smoke 牵住、但不再是正式运行时入口的旧观察文案 helper。
|
|
||||||
3. 带有固定用户、固定 session、固定 profile 的一次性历史同步脚本。
|
|
||||||
4. 清理后暴露出的 function catalog 契约覆盖缺口。
|
|
||||||
|
|
||||||
本批次仍然不按文件名直接删除 `legacy` 命名对象。经核对,`server-node/src/bridges/legacyInventoryRuntimeBridge.ts`、`legacyNpcTask6Bridge.ts`、`legacyQuestProgressBridge.ts`、`legacyQuestRuntimeBridge.ts`、`legacyRuntimeItemBridge.ts`、`legacyTreasureRuntimeBridge.ts` 仍被后端战斗、背包、任务、宝藏主链直接引用,不能按历史命名硬删。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 删除判定口径
|
|
||||||
|
|
||||||
本批删除对象必须同时满足:
|
|
||||||
|
|
||||||
1. 修正 `.js -> .ts` 后端源码解析、前端懒加载入口解析后,仍不可从正式入口到达。
|
|
||||||
2. 全仓库代码引用扫描没有正式入口引用。
|
|
||||||
3. 如只被 smoke 或测试牵住,先把 smoke / 测试改到当前正式主链,再删除旧对象。
|
|
||||||
4. 删除后通过对应门禁验证,没有新增悬空 import。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 本批次已处理对象
|
|
||||||
|
|
||||||
| 文件 | 判定 | 删除 / 调整原因 | 替代路径 / 当前真相源 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `src/services/questDirector.ts` | 无代码入口残留 | 正式 quest 生成已由后端 `/api/runtime/quests/generate` 与 `questService.ts` 承接,前端当前没有任何 import | `server-node/src/services/questService.ts`、`server-node/src/modules/quest/runtimeQuestModule.ts` |
|
|
||||||
| `src/data/sceneObservation.ts` | 旧观察文案 helper | 只被 `scripts/smoke-content.ts` 引用,正式观察动作已走 `idle_observe_signs` function 与运行时 story continuation | `src/data/functionCatalog/state/idleObserveSigns.ts`、`src/hooks/rpg-runtime-story/storyChoiceContinuation.ts` |
|
|
||||||
| `server-node/src/scripts/syncCustomWorldSavedProfileAssets.ts` | 一次性硬编码运维脚本 | 脚本内固定用户、session、profile,只服务历史补丁,没有 CLI 参数和当前运维入口 | 无替代;如未来需要,按参数化运维脚本重新设计 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 同步调整
|
|
||||||
|
|
||||||
1. `scripts/smoke-content.ts` 不再 import 旧 `sceneObservation.ts`,改为通过 `resolveFunctionOption('idle_observe_signs', ...)` 验证当前正式 function 目录。
|
|
||||||
2. `packages/shared/src/contracts/rpgRuntimeContracts.test.ts` 不再验证已移除的旧 `story` façade,改为直接验证当前拆分契约。
|
|
||||||
3. `src/data/functionCatalog/` 补齐仍在后端运行时契约中的 function 文档:
|
|
||||||
- `battle_attack_basic`
|
|
||||||
- `battle_use_skill`
|
|
||||||
- `npc_chat_quest_offer_view`
|
|
||||||
- `npc_chat_quest_offer_replace`
|
|
||||||
- `npc_chat_quest_offer_abandon`
|
|
||||||
4. `battle_attack_basic` 与 `battle_use_skill` 只作为后端契约文档登记,不进入 `STATE_FUNCTION_DEFINITIONS`,避免前端本地候选池生成缺少 `runtimePayload.skillId` 的假技能 option。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 本批次暂缓对象
|
|
||||||
|
|
||||||
以下对象经本批复核后继续保留:
|
|
||||||
|
|
||||||
1. `server-node/src/services/customWorldAgentRepositoryTestHelpers.ts`
|
|
||||||
2. `server-node/src/services/customWorldAgentTestHelpers.ts`
|
|
||||||
3. `server-node/src/testFixtures/runtimeCharacter.ts`
|
|
||||||
4. `server-node/src/testHttp.ts`
|
|
||||||
|
|
||||||
这些文件不属于正式运行时入口,但当前被后端测试、smoke 与路由边界门禁使用。它们不是 RPG 创作 / 运行时玩法主流程代码,但仍是平台基本质量门禁的一部分,不能在“删除冗余业务代码”批次里直接硬删。
|
|
||||||
|
|
||||||
另保留:
|
|
||||||
|
|
||||||
1. `src/services/runtimeItemAiDirector.ts`
|
|
||||||
2. `src/services/ai.ts`
|
|
||||||
3. `src/services/apiClient.ts`
|
|
||||||
|
|
||||||
这些文件仍被当前主链或前端 SDK 入口引用,后续如继续压缩,必须先完成对应 contract / SDK 拆分,不按无引用规则删除。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 验证结果
|
|
||||||
|
|
||||||
本批已通过:
|
|
||||||
|
|
||||||
1. `npx vitest run src/data/functionCatalog/functionCatalog.test.ts packages/shared/src/contracts/rpgRuntimeContracts.test.ts`
|
|
||||||
2. `npx tsx scripts/smoke-content.ts`
|
|
||||||
3. `npm run check:encoding`
|
|
||||||
|
|
||||||
并额外确认:
|
|
||||||
|
|
||||||
1. 全仓库代码中不再引用 `sceneObservation`、`questDirector`、`syncCustomWorldSavedProfileAssets`。
|
|
||||||
2. `buildStateFunctionDefinitions()` 中不会出现 `battle_attack_basic` / `battle_use_skill`,这两个 function 只由后端运行时 option 池下发。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 当前结论
|
|
||||||
|
|
||||||
本批次后,静态入口扫描中剩余的高置信“不可达源码”已经收敛为测试辅助、测试夹具和 smoke helper。继续删除前需要先重构测试基础设施或迁移剩余前端 SDK,而不应再按文件名或历史命名直接硬删。
|
|
||||||
@@ -1,553 +0,0 @@
|
|||||||
# 前端应迁后端逻辑审计(2026-04-21)
|
|
||||||
|
|
||||||
更新时间:`2026-04-21`
|
|
||||||
|
|
||||||
## 0. 审计目标
|
|
||||||
|
|
||||||
这份文档只回答一个问题:
|
|
||||||
|
|
||||||
**当前前端代码里,哪些逻辑已经明显越过“前端只做表现,Express 后端负责逻辑、数据与存储”的边界,应该继续迁到后端。**
|
|
||||||
|
|
||||||
本轮不改业务代码,只做:
|
|
||||||
|
|
||||||
1. 基于当前仓库状态给出高置信度候选点
|
|
||||||
2. 标明代码证据
|
|
||||||
3. 给出迁移优先级
|
|
||||||
4. 说明迁移后前端应该保留什么、移走什么
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 结论先行
|
|
||||||
|
|
||||||
结合当前代码与已有边界文档,前端里仍有 7 类逻辑应该继续后移:
|
|
||||||
|
|
||||||
1. **运行时快照前置写入与本地镜像解释**
|
|
||||||
2. **鉴权 token 的浏览器本地真相**
|
|
||||||
3. **平台浏览历史的本地真相与迁移状态**
|
|
||||||
4. **NPC 待接委托“换单”仍由前端直接触发正式生成**
|
|
||||||
5. **quest/runtime item 的双环境混合编排**
|
|
||||||
6. **浏览器侧大型 AI orchestration 与 prompt/repair/fallback 主链**
|
|
||||||
7. **NPC 招募对白之后的正式结算链路**
|
|
||||||
|
|
||||||
一句话判断:
|
|
||||||
|
|
||||||
**当前前端已经不是最早那种“大量主算”的状态,但仍然保留了运行时镜像、生成编排和部分正式真相。后端边界还需要再收一轮,前端才算真正退回表现层。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 审计依据
|
|
||||||
|
|
||||||
### 2.1 文档依据
|
|
||||||
|
|
||||||
1. `docs/experience/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md`
|
|
||||||
2. `docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`
|
|
||||||
3. `docs/technical/RUNTIME_STORY_BACKEND_BOUNDARY_MIGRATION_2026-04-19.md`
|
|
||||||
4. `docs/audits/engineering/ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-20.md`
|
|
||||||
5. `docs/audits/engineering/CURRENT_ENGINEERING_OPTIMIZATION_OPPORTUNITIES_2026-04-20.md`
|
|
||||||
|
|
||||||
### 2.2 当前代码依据
|
|
||||||
|
|
||||||
1. `src/hooks/story/runtimeStoryCoordinator.ts`
|
|
||||||
2. `src/services/apiClient.ts`
|
|
||||||
3. `src/services/platformBrowseHistory.ts`
|
|
||||||
4. `src/components/game-shell/PreGameSelectionFlow.tsx`
|
|
||||||
5. `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
6. `src/services/questDirector.ts`
|
|
||||||
7. `src/services/runtimeItemAiDirector.ts`
|
|
||||||
8. `src/services/ai.ts`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 当前高置信度应后移逻辑
|
|
||||||
|
|
||||||
## 3.0 本轮已完成后移
|
|
||||||
|
|
||||||
以下链路已在本轮或上一轮连续落地中完成后移,不再属于“仍残留在前端”的正式主链:
|
|
||||||
|
|
||||||
1. access token 浏览器本地真相
|
|
||||||
2. browse history 本地真相
|
|
||||||
3. runtime story 前置 `PUT /runtime/save/snapshot`
|
|
||||||
4. NPC 待接委托 `replace / abandon / accept`
|
|
||||||
5. custom world profile 正式浏览器入口
|
|
||||||
6. `questDirector` / `runtimeItemAiDirector` 浏览器正式编排
|
|
||||||
7. NPC 招募正式结算
|
|
||||||
|
|
||||||
其中 NPC 招募已从“前端本地改 companions / roster / npcStates / storyHistory”收回到后端 runtime action。
|
|
||||||
|
|
||||||
## 3.1 运行时快照前置写入仍在前端
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
`src/hooks/story/runtimeStoryCoordinator.ts` 当前仍存在以下链路:
|
|
||||||
|
|
||||||
1. `syncRuntimeSnapshot(...)`
|
|
||||||
2. `syncRuntimeSnapshot(...)` 内部直接调用 `putSaveSnapshot(...)`
|
|
||||||
3. `loadServerRuntimeOptionCatalog(...)` 在请求 `getRuntimeStoryState(...)` 之前先写本地快照
|
|
||||||
4. `resolveServerRuntimeChoice(...)` 在请求 `resolveRuntimeStoryAction(...)` 之前先写本地快照
|
|
||||||
|
|
||||||
对应位置:
|
|
||||||
|
|
||||||
1. `src/hooks/story/runtimeStoryCoordinator.ts:21`
|
|
||||||
2. `src/hooks/story/runtimeStoryCoordinator.ts:25`
|
|
||||||
3. `src/hooks/story/runtimeStoryCoordinator.ts:36`
|
|
||||||
4. `src/hooks/story/runtimeStoryCoordinator.ts:99`
|
|
||||||
|
|
||||||
### 当前问题
|
|
||||||
|
|
||||||
这意味着运行时正式动作发起前,前端仍会先落一份自己的快照真相,再去请求后端。
|
|
||||||
|
|
||||||
这条链的问题不是“有没有缓存”,而是:
|
|
||||||
|
|
||||||
1. 前端仍在承担正式提交前的状态镜像
|
|
||||||
2. 快照解释权没有完全收回到后端
|
|
||||||
3. 运行时主链仍处于“本地镜像 + 服务端会话”并存状态
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端继续承接:
|
|
||||||
|
|
||||||
1. 运行时快照写入
|
|
||||||
2. 快照版本解释
|
|
||||||
3. 动作提交前的状态一致性校验
|
|
||||||
|
|
||||||
前端只保留:
|
|
||||||
|
|
||||||
1. 当前展示用的 view model
|
|
||||||
2. 可选的只读恢复缓存
|
|
||||||
3. 纯表现态的 loading / transition / animation state
|
|
||||||
|
|
||||||
### 优先级
|
|
||||||
|
|
||||||
`P0`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.2 鉴权 token 仍由前端 localStorage 持有真相
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
`src/services/apiClient.ts` 当前仍直接访问 `window.localStorage` 保存 access token:
|
|
||||||
|
|
||||||
1. `getStoredAccessToken()`
|
|
||||||
2. `setStoredAccessToken(...)`
|
|
||||||
3. `clearStoredAccessToken(...)`
|
|
||||||
4. `withAuthorizationHeaders(...)` 直接从本地 token 组装请求头
|
|
||||||
|
|
||||||
对应位置:
|
|
||||||
|
|
||||||
1. `src/services/apiClient.ts:333`
|
|
||||||
2. `src/services/apiClient.ts:341`
|
|
||||||
3. `src/services/apiClient.ts:362`
|
|
||||||
4. `src/services/apiClient.ts:382`
|
|
||||||
|
|
||||||
### 当前问题
|
|
||||||
|
|
||||||
第三批清理已经收掉了“自动登录用户名/密码”本地真相,但 access token 仍然由浏览器长期持有。
|
|
||||||
|
|
||||||
这在当前项目边界下仍有两个问题:
|
|
||||||
|
|
||||||
1. 正式鉴权真相仍没有完全收回后端 session 边界
|
|
||||||
2. 前端 SDK 仍然负担 token 生命周期的关键部分
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端继续承接:
|
|
||||||
|
|
||||||
1. session / refresh / cookie 真相
|
|
||||||
2. 鉴权状态续期
|
|
||||||
3. token 更新与失效策略
|
|
||||||
|
|
||||||
前端只保留:
|
|
||||||
|
|
||||||
1. 当前是否已登录的展示态
|
|
||||||
2. 统一的请求封装
|
|
||||||
3. 401 后的 UI 响应
|
|
||||||
|
|
||||||
### 优先级
|
|
||||||
|
|
||||||
`P0`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.3 平台浏览历史仍是“前端本地历史 + 后端回填”的双真相
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
`src/services/platformBrowseHistory.ts` 当前仍维护一整套本地历史真相:
|
|
||||||
|
|
||||||
1. `readPlatformBrowseHistory(...)`
|
|
||||||
2. `writePlatformBrowseHistory(...)`
|
|
||||||
3. `hasPendingPlatformBrowseHistoryMigration(...)`
|
|
||||||
4. `markPlatformBrowseHistoryMigrated(...)`
|
|
||||||
|
|
||||||
对应位置:
|
|
||||||
|
|
||||||
1. `src/services/platformBrowseHistory.ts:77`
|
|
||||||
2. `src/services/platformBrowseHistory.ts:103`
|
|
||||||
3. `src/services/platformBrowseHistory.ts:151`
|
|
||||||
4. `src/services/platformBrowseHistory.ts:164`
|
|
||||||
|
|
||||||
`src/components/game-shell/PreGameSelectionFlow.tsx` 当前仍显式做:
|
|
||||||
|
|
||||||
1. 先 `writePlatformBrowseHistory(...)`
|
|
||||||
2. 再调用 `upsertProfileBrowseHistory(...)`
|
|
||||||
3. 同步成功后 `markPlatformBrowseHistoryMigrated(...)`
|
|
||||||
4. 启动阶段读取 `readPlatformBrowseHistory(...)`
|
|
||||||
5. 根据 `hasPendingPlatformBrowseHistoryMigration(...)` 决定是否补同步
|
|
||||||
|
|
||||||
对应位置:
|
|
||||||
|
|
||||||
1. `src/components/game-shell/PreGameSelectionFlow.tsx:383`
|
|
||||||
2. `src/components/game-shell/PreGameSelectionFlow.tsx:392`
|
|
||||||
3. `src/components/game-shell/PreGameSelectionFlow.tsx:394`
|
|
||||||
4. `src/components/game-shell/PreGameSelectionFlow.tsx:433`
|
|
||||||
5. `src/components/game-shell/PreGameSelectionFlow.tsx:466`
|
|
||||||
|
|
||||||
### 当前问题
|
|
||||||
|
|
||||||
这条链已经不是单纯缓存,而是:
|
|
||||||
|
|
||||||
1. 本地历史存储
|
|
||||||
2. 本地同步标记
|
|
||||||
3. 后端历史持久化
|
|
||||||
|
|
||||||
三套状态同时存在。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端继续承接:
|
|
||||||
|
|
||||||
1. 浏览历史唯一持久化真相
|
|
||||||
2. 历史去重、排序、截断
|
|
||||||
3. 迁移完成标记
|
|
||||||
|
|
||||||
前端只保留:
|
|
||||||
|
|
||||||
1. 展示缓存
|
|
||||||
2. 弱网下的临时 optimistic UI
|
|
||||||
3. 刷新后重新拉取远端结果
|
|
||||||
|
|
||||||
### 优先级
|
|
||||||
|
|
||||||
`P1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.4 NPC 待接委托“换单”仍由前端直接发起正式生成
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
`src/hooks/story/npcEncounterActions.ts` 当前仍保留:
|
|
||||||
|
|
||||||
1. `replacePendingNpcQuestOffer = async () => { ... }`
|
|
||||||
2. 内部直接调用 `generateQuestForNpcEncounter(...)`
|
|
||||||
|
|
||||||
对应位置:
|
|
||||||
|
|
||||||
1. `src/hooks/story/npcEncounterActions.ts:1561`
|
|
||||||
2. `src/hooks/story/npcEncounterActions.ts:1595`
|
|
||||||
|
|
||||||
### 当前问题
|
|
||||||
|
|
||||||
聊天后是否挂出待接委托已经后移,但“换一份委托”这条分支仍然是:
|
|
||||||
|
|
||||||
1. 前端组装上下文
|
|
||||||
2. 前端决定调用生成
|
|
||||||
3. 前端直接把结果写回当前 story UI
|
|
||||||
|
|
||||||
这仍属于正式运行时任务编排没有收干净。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端继续承接:
|
|
||||||
|
|
||||||
1. NPC 待接委托换单决策
|
|
||||||
2. 是否允许换单
|
|
||||||
3. 换单后的任务草案生成
|
|
||||||
4. 对应聊天态快照回填
|
|
||||||
|
|
||||||
前端只保留:
|
|
||||||
|
|
||||||
1. 点击“换一份委托”
|
|
||||||
2. loading / error 展示
|
|
||||||
3. 消费后端返回的新 pending quest offer
|
|
||||||
|
|
||||||
### 优先级
|
|
||||||
|
|
||||||
`P0`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.5 questDirector 仍是前端 SDK 与生成编排混合体
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
`src/services/questDirector.ts` 当前同时承担:
|
|
||||||
|
|
||||||
1. `generateQuestForNpcEncounter(...)`
|
|
||||||
2. 浏览器路径 `requestJson('/api/runtime/quests/generate')`
|
|
||||||
3. 非浏览器路径 `requestChatMessageContent(...)`
|
|
||||||
4. 本地 `compileQuestIntentToQuest(...)` fallback
|
|
||||||
|
|
||||||
对应位置:
|
|
||||||
|
|
||||||
1. `src/services/questDirector.ts:213`
|
|
||||||
2. `src/services/questDirector.ts:242`
|
|
||||||
3. `src/services/questDirector.ts:267`
|
|
||||||
4. `src/services/questDirector.ts:256`
|
|
||||||
5. `src/services/questDirector.ts:281`
|
|
||||||
6. `src/services/questDirector.ts:293`
|
|
||||||
|
|
||||||
### 当前问题
|
|
||||||
|
|
||||||
这类文件虽然浏览器正式路径已经优先走后端,但职责仍混在一起:
|
|
||||||
|
|
||||||
1. 前端 SDK
|
|
||||||
2. Quest prompt 编排
|
|
||||||
3. Quest intent 解析
|
|
||||||
4. deterministic fallback compile
|
|
||||||
|
|
||||||
这会导致边界长期模糊,也让前端仍像“半个服务端”。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端继续承接:
|
|
||||||
|
|
||||||
1. quest intent 生成
|
|
||||||
2. prompt 组装
|
|
||||||
3. JSON 解析
|
|
||||||
4. fallback compile
|
|
||||||
|
|
||||||
前端只保留:
|
|
||||||
|
|
||||||
1. `requestGenerateQuest(...)` 这类轻量 SDK
|
|
||||||
2. 请求参数组装
|
|
||||||
3. 结果消费
|
|
||||||
|
|
||||||
### 优先级
|
|
||||||
|
|
||||||
`P1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.6 runtimeItemAiDirector 仍是前端 SDK 与意图生成混合体
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
`src/services/runtimeItemAiDirector.ts` 当前同时承担:
|
|
||||||
|
|
||||||
1. `generateRuntimeItemAiIntents(...)`
|
|
||||||
2. 浏览器路径 `requestJson('/api/runtime/items/runtime-intent')`
|
|
||||||
3. 非浏览器路径 `requestChatMessageContent(...)`
|
|
||||||
4. 本地 `buildRuntimeItemAiIntent(...)` fallback
|
|
||||||
|
|
||||||
对应位置:
|
|
||||||
|
|
||||||
1. `src/services/runtimeItemAiDirector.ts:84`
|
|
||||||
2. `src/services/runtimeItemAiDirector.ts:94`
|
|
||||||
3. `src/services/runtimeItemAiDirector.ts:118`
|
|
||||||
|
|
||||||
### 当前问题
|
|
||||||
|
|
||||||
它和 `questDirector` 是同类问题:
|
|
||||||
|
|
||||||
1. 正式浏览器路径已经走后端
|
|
||||||
2. 但前端文件仍然承担完整生成逻辑认知
|
|
||||||
3. 文件职责仍然是双环境混合
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端继续承接:
|
|
||||||
|
|
||||||
1. runtime item intent prompt
|
|
||||||
2. 模型调用
|
|
||||||
3. 结果解析与 fallback
|
|
||||||
|
|
||||||
前端只保留:
|
|
||||||
|
|
||||||
1. 轻量请求 SDK
|
|
||||||
2. 结果到 UI 的映射
|
|
||||||
|
|
||||||
### 优先级
|
|
||||||
|
|
||||||
`P1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.7 `src/services/ai.ts` 仍是浏览器侧正式 AI orchestration 热点
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
当前 `src/services/ai.ts` 仍直接承担以下正式链路:
|
|
||||||
|
|
||||||
1. `requestChatMessageContent(...)`
|
|
||||||
2. `requestPlainTextCompletionFromClient(...)`
|
|
||||||
3. `streamPlainTextCompletionFromClient(...)`
|
|
||||||
4. `generateCustomWorldProfile(...)`
|
|
||||||
5. `generateInitialStory(...)`
|
|
||||||
6. `generateNextStep(...)`
|
|
||||||
7. `streamNpcChatDialogue(...)`
|
|
||||||
8. `streamNpcRecruitDialogue(...)`
|
|
||||||
|
|
||||||
对应位置:
|
|
||||||
|
|
||||||
1. `src/services/ai.ts:1732`
|
|
||||||
2. `src/services/ai.ts:1868`
|
|
||||||
3. `src/services/ai.ts:2038`
|
|
||||||
4. `src/services/ai.ts:2339`
|
|
||||||
5. `src/services/ai.ts:2447`
|
|
||||||
6. `src/services/ai.ts:2487`
|
|
||||||
7. `src/services/ai.ts:2529`
|
|
||||||
8. `src/services/ai.ts:2570`
|
|
||||||
|
|
||||||
并且文件内仍保留:
|
|
||||||
|
|
||||||
1. JSON repair
|
|
||||||
2. prompt 组装
|
|
||||||
3. response normalize
|
|
||||||
4. fallback/offline 响应
|
|
||||||
5. 角色聊天建议与摘要生成
|
|
||||||
|
|
||||||
### 当前问题
|
|
||||||
|
|
||||||
这说明浏览器端并不只是“请求一个后端接口”,而是还在承担:
|
|
||||||
|
|
||||||
1. prompt source
|
|
||||||
2. 生成策略
|
|
||||||
3. 错误修复
|
|
||||||
4. fallback 编排
|
|
||||||
5. 多类业务场景的正式 AI 出口
|
|
||||||
|
|
||||||
这与“前端只做表现”存在明确冲突。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端继续承接:
|
|
||||||
|
|
||||||
1. story / npc / recruit / custom-world 的 prompt 编排
|
|
||||||
2. JSON repair
|
|
||||||
3. fallback 策略
|
|
||||||
4. streaming orchestration
|
|
||||||
5. 模型调用与日志
|
|
||||||
|
|
||||||
前端只保留:
|
|
||||||
|
|
||||||
1. 轻量 AI SDK
|
|
||||||
2. SSE 文本流展示
|
|
||||||
3. UI fallback 呈现
|
|
||||||
|
|
||||||
### 优先级
|
|
||||||
|
|
||||||
`P0`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3.8 NPC 招募对白之后的正式结算链路已完成后移
|
|
||||||
|
|
||||||
### 本轮前状态
|
|
||||||
|
|
||||||
迁移前,`src/hooks/story/npcInteraction.ts` 中的 `buildRecruitmentOutcome / executeRecruitment / startRecruitmentSequence` 仍在前端本地正式结算:
|
|
||||||
|
|
||||||
1. 改 `npcStates`
|
|
||||||
2. 改 `companions`
|
|
||||||
3. 改 `roster`
|
|
||||||
4. 清 `currentEncounter / inBattle / sceneHostileNpcs`
|
|
||||||
5. 直接写 `storyHistory`
|
|
||||||
6. 再触发后续剧情推进
|
|
||||||
|
|
||||||
这与“前端只做表现,所有正式逻辑、数据都放到 Express 后端”直接冲突。
|
|
||||||
|
|
||||||
### 本轮后状态
|
|
||||||
|
|
||||||
本轮已完成:
|
|
||||||
|
|
||||||
1. `server-node/src/modules/story/runtimeSession.ts`
|
|
||||||
- 正式承接完整 `companions`
|
|
||||||
- 正式承接 `roster`
|
|
||||||
2. `server-node/src/modules/npc/npcInteractionService.ts`
|
|
||||||
- `npc_recruit` 已支持正常入队
|
|
||||||
- `npc_recruit` 已支持满员换队招募
|
|
||||||
3. `src/hooks/story/npcInteraction.ts`
|
|
||||||
- 前端只保留招募对白流式展示
|
|
||||||
- 正式招募结算改为调用后端 runtime action
|
|
||||||
|
|
||||||
### 当前判断
|
|
||||||
|
|
||||||
这一项已不再属于前端残留正式逻辑。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 可以暂时保留在前端的部分
|
|
||||||
|
|
||||||
下面这些内容即使和上述模块同文件出现,也不属于必须后移的对象:
|
|
||||||
|
|
||||||
1. 面板开关、loading、error、streaming 文本展示
|
|
||||||
2. 动画时间线、过场状态、临时 UI 回显
|
|
||||||
3. 表单草稿、筛选词、排序选项
|
|
||||||
4. 只影响表现、不影响正式真相的 view model 拼接
|
|
||||||
|
|
||||||
迁移时要注意:
|
|
||||||
|
|
||||||
**不是把所有前端代码都往后端搬,而是把“正式状态解释、规则裁决、生成编排、持久化真相”搬走。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 推荐迁移顺序
|
|
||||||
|
|
||||||
## 5.1 第一阶段
|
|
||||||
|
|
||||||
先收最危险的正式真相:
|
|
||||||
|
|
||||||
1. `runtimeStoryCoordinator.ts`
|
|
||||||
2. `apiClient.ts`
|
|
||||||
3. `npcEncounterActions.ts` 里的 quest replace 分支
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
1. 这三处最直接影响运行时真相和动作主链
|
|
||||||
2. 不先收这些,前端仍然不是纯表现层
|
|
||||||
|
|
||||||
## 5.2 第二阶段
|
|
||||||
|
|
||||||
再拆双环境混合服务:
|
|
||||||
|
|
||||||
1. `questDirector.ts`
|
|
||||||
2. `runtimeItemAiDirector.ts`
|
|
||||||
3. `platformBrowseHistory.ts`
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
1. 这几处已经有后端承接基础
|
|
||||||
2. 迁移成本相对可控
|
|
||||||
|
|
||||||
## 5.3 第三阶段
|
|
||||||
|
|
||||||
最后继续压缩浏览器 AI orchestration:
|
|
||||||
|
|
||||||
1. `src/services/ai.ts`
|
|
||||||
2. 相关 prompt builder / repair helper / offline fallback
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
1. 这部分体量大
|
|
||||||
2. 链路多
|
|
||||||
3. 更适合在前两阶段把 contract 稳住后集中拆
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 建议产出物
|
|
||||||
|
|
||||||
如果后续按这份文档继续落地,建议每一批都至少同步产出:
|
|
||||||
|
|
||||||
1. 一份落地文档,说明迁移了哪条链
|
|
||||||
2. 一组 contract/route 变更说明
|
|
||||||
3. 一组前端 SDK 收缩说明
|
|
||||||
4. 一组防回退测试
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 一句话结论
|
|
||||||
|
|
||||||
当前前端最需要继续后移的,不是零散小工具,而是:
|
|
||||||
|
|
||||||
**运行时快照前置写入、鉴权 token、本地浏览历史真相、NPC 委托换单、quest/runtime item 双环境混合编排,以及 `src/services/ai.ts` 里仍然留在浏览器的正式 AI orchestration。**
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
# 怪物-NPC 脚本统一整改审计
|
|
||||||
|
|
||||||
日期:2026-04-06
|
|
||||||
|
|
||||||
## 核心结论
|
|
||||||
|
|
||||||
当前工程仍然没有真正落实“怪物就是初始好感度为负数的 NPC”这一原则。
|
|
||||||
|
|
||||||
现状不是“NPC 脚本里支持 hostile 状态”,而是同时存在两条并行链路:
|
|
||||||
|
|
||||||
1. `npc / encounter / npcStates / npcInteraction`
|
|
||||||
2. `monster / hostileNpc / sceneMonsters / sceneHostileNpcs / hostileNpcPresets`
|
|
||||||
|
|
||||||
这会直接导致:
|
|
||||||
|
|
||||||
- 同一个敌对实体同时拥有 NPC 身份和 monster 身份。
|
|
||||||
- 场景、战斗、渲染、提示词都在维护两套入口。
|
|
||||||
- 后续修 bug 时,任何位置、死亡、血条、入场、掉落问题都要同时查两条链路。
|
|
||||||
|
|
||||||
本次文档的目标不是立刻改代码,而是先把应该删除的分叉脚本、应该降级成素材层的文件、以及必须合并的字段全部列清楚,作为后续统一改造的依据。
|
|
||||||
|
|
||||||
## 当前违背原则的根因
|
|
||||||
|
|
||||||
### 1. 场景数据仍然把“怪物”和“NPC”当成两类实体
|
|
||||||
|
|
||||||
当前场景层同时维护:
|
|
||||||
|
|
||||||
- `ScenePreset.monsterIds`
|
|
||||||
- `SceneNpc[]`
|
|
||||||
- `ScenePresetInfo.hostileNpcIds`
|
|
||||||
- `SceneNpc.monsterPresetId / hostileNpcPresetId`
|
|
||||||
|
|
||||||
这意味着场景里一个敌对单位既可以来自 `monsterIds`,也可以来自 `npcs`,甚至会被脚本再生成为 hostile scene npc。
|
|
||||||
|
|
||||||
### 2. 运行时仍然存在“怪物专用实体状态”
|
|
||||||
|
|
||||||
当前运行时仍然同时维护:
|
|
||||||
|
|
||||||
- `GameState.sceneMonsters`
|
|
||||||
- `GameState.sceneHostileNpcs`
|
|
||||||
- `SceneHostileNpc / SceneMonster`
|
|
||||||
|
|
||||||
这和“怪物本质上只是 hostile NPC”是冲突的。真正统一后,运行时只应该保留一套“场景 NPC / 战斗 NPC”状态。
|
|
||||||
|
|
||||||
### 3. 战斗脚本仍然把怪物当独立 actor
|
|
||||||
|
|
||||||
战斗层目前不是“NPC 战斗,只是 hostile 的那部分会出手”,而是显式写了:
|
|
||||||
|
|
||||||
- `TurnActor = 'player' | 'companion' | 'monster'`
|
|
||||||
- `getClosestMonster`
|
|
||||||
- `resetCombatPresentation(monsters, ...)`
|
|
||||||
- `sceneMonsters` 全链路结算
|
|
||||||
|
|
||||||
这会强制后面所有视觉、掉落、提示词、AI 上下文都跟着叫 monster。
|
|
||||||
|
|
||||||
### 4. 渲染层仍然有 monster 专属显示入口
|
|
||||||
|
|
||||||
画布层当前仍然依赖:
|
|
||||||
|
|
||||||
- `sceneMonsters`
|
|
||||||
- `sceneHostileNpcs`
|
|
||||||
- `monsterPresetId`
|
|
||||||
- `HostileNpcAnimator`
|
|
||||||
|
|
||||||
也就是“敌对 NPC 是否按 NPC 脚本渲染”这件事,到最终显示层仍然没有统一。
|
|
||||||
|
|
||||||
## 一级删除清单
|
|
||||||
|
|
||||||
下面这些文件属于“业务流程分叉脚本”,不是单纯资源适配层。后续统一时应优先删除或并入 NPC 主链路。
|
|
||||||
|
|
||||||
| 文件 | 当前分叉角色 | 处理建议 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `src/data/monsters.ts` | 对 `hostileNpcs.ts` 的别名出口 | 直接删除,禁止继续保留 monster 专属入口名。 |
|
|
||||||
| `src/data/hostileNpcs.ts` | 负责 monster 创建、编队、距离、朝向、变化、落位 | 按 hostile NPC 运行时工具重写并并入 NPC 体系;原文件名不应继续保留。 |
|
|
||||||
| `src/data/sceneEncounterPreviews.ts` | 单独构造 hostile encounter group、auto battle、hostile preview | 删除 monster 专线逻辑,改为“负好感 NPC 预览/入场/转战斗”。 |
|
|
||||||
| `src/hooks/combat/battlePlan.ts` | 使用 `monster` actor、`sceneMonsters`、`getClosestMonster` | 改成统一的 hostile NPC combatant 规划脚本;monster actor 概念应移除。 |
|
|
||||||
| `src/hooks/combat/playback.ts` | 使用 `sceneMonsters` 播放怪物战斗演出 | 改成统一 NPC 战斗回放;不再区分 monster 播放器。 |
|
|
||||||
| `src/components/game-canvas/GameCanvasRuntime.tsx` | 运行时按 `sceneMonsters / sceneHostileNpcs` 双数据源选敌方实体 | 删除双源兜底,统一成单一 hostile NPC 列表。 |
|
|
||||||
| `src/components/game-canvas/GameCanvasEntityLayer.tsx` | 敌方实体按 monsterPreset 分支渲染 | 改成同一套 NPC 实体渲染,视觉差异仅由 visual preset 决定。 |
|
|
||||||
| `src/components/game-canvas/GameCanvasShared.tsx` | 定义 `sceneMonsters / sceneHostileNpcs` props 与 monster 专属计算 | 删除这些字段与 helper,改成统一 NPC 画布协议。 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | 独立怪物预设编辑面板 | 并回 NPC hostile visual preset 面板,或删除该独立编辑器入口。 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetTab.tsx` | 独立怪物预设页签 | 与上面一并删除或并入 NPC preset 编辑器。 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | 仍然单独编辑 `monsterIds` | 删除 `monsterIds` 编辑项,只保留场景 NPC 列表。 |
|
|
||||||
|
|
||||||
## 二级归并清单
|
|
||||||
|
|
||||||
下面这些文件不一定需要物理删除,但它们当前仍然在放大 monster / NPC 分轨,必须在统一改造时一起收口。
|
|
||||||
|
|
||||||
| 文件 | 当前问题 | 处理建议 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `src/data/scenePresets.ts` | 通过 `monsterIds` 再生 hostile scene npc,并区分 `getSceneHostileNpcs / getSceneFriendlyNpcs` | 保留场景数据文件本身,但删除 `monsterIds` 体系,让敌对角色直接存在于 `npcs` 中。 |
|
|
||||||
| `src/data/customWorldNpcMonsters.ts` | 用单独脚本推导“怪物型 NPC”预设 | 可保留为 hostile visual preset 选择器,但不能再生成第二套实体语义。 |
|
|
||||||
| `src/data/hostileNpcPresets.ts` | 目前既是视觉预设库,也是独立 hostile 流程的数据源 | 降级为 hostile visual/combat preset 库;不再拥有独立实体生命周期。 |
|
|
||||||
| `src/components/HostileNpcAnimator.tsx` | 当前名字和调用语义都在暗示“独立怪物实体” | 可以保留为贴图播放器,但应改为 hostile NPC 的视觉适配组件,而不是独立物种脚本。 |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | 详情弹窗仍会优先查 monster preset / hostileNpcPreset | 统一读取 NPC 档案;视觉差异只通过 hostile preset 补充。 |
|
|
||||||
| `src/components/SkillEffectPreview.tsx` | 预览器直接使用 `sceneMonsters` 和 `createSceneMonstersFromIds` | 改成统一 hostile NPC 预览态。 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | 编辑器里仍然直接造 monster battle preview | 改成 hostile NPC preview。 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | 仍然暴露 `monsterPresetId` 字段 | 改成更明确的 hostile visual preset 字段,避免“怪物类型”和“NPC 类型”双语义。 |
|
|
||||||
| `src/hooks/story/npcEncounterActions.ts` | 虽然入口叫 npc,但内部仍然写 `sceneMonsters / sceneHostileNpcs` | 改成统一 hostile NPC 战斗状态字段。 |
|
|
||||||
| `src/hooks/story/choiceActions.ts` | 仍然有 `buildHostileNpcBattleReward` 和 `getResolvedSceneHostileNpcs` 这一层额外概念 | 统一到 hostile NPC 结算工具,不再把“敌对 NPC”和“monster”混称。 |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | 给 AI/剧情层传入 `sceneMonsters` 或 `sceneHostileNpcs` | 改成统一的 hostile NPC 上下文切片。 |
|
|
||||||
| `src/services/prompt.ts` | 仍然从 `monsterIds` 和 `createSceneMonstersFromIds` 组 prompt | 改成从场景 NPC 列表中筛出 hostile NPC。 |
|
|
||||||
| `src/services/questDirector.ts` | 仍然依赖 `monsterPresetId` 推导当前敌对目标 | 统一改为基于负好感或 hostile 标记的 NPC。 |
|
|
||||||
| `src/services/ai.ts` | 仍然混用 `monsterIds`、sceneNpc 的 hostile 判定 | 与场景统一后改成只读 NPC 列表。 |
|
|
||||||
| `src/services/questTypes.ts` | 仍然把 `hostileNpcIds / monsterIds` 当作 scene 快照字段 | 删除 `monsterIds`,保留 hostile NPC 语义。 |
|
|
||||||
|
|
||||||
## 可保留但必须降级为“素材/配置层”的内容
|
|
||||||
|
|
||||||
下面这些内容不一定要消失,但不能继续作为独立业务链路存在:
|
|
||||||
|
|
||||||
| 文件/内容 | 可以保留的原因 | 必须收口的边界 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `src/components/HostileNpcAnimator.tsx` | 怪物贴图是特殊资源,需要专门 sprite sheet 播放器 | 只负责画图,不再决定实体类型、战斗身份、交互入口。 |
|
|
||||||
| `src/data/hostileNpcPresets.ts` | hostile visual/combat preset 仍然有价值 | 只能作为 hostile NPC 的 visual/combat preset 库,不再驱动另一套“monster 实体”。 |
|
|
||||||
| `src/data/hostileNpcOverrides.json` | 资源级 override 仍可继续用 | 不能再配套出独立 hostile 流程。 |
|
|
||||||
| `src/data/monsterOverrides.json` | 如果只是素材映射,可迁移到 hostile visual preset override | 不应继续以 monster 专属命名长期存在。 |
|
|
||||||
| `src/data/customWorldNpcMonsters.ts` | 自定义世界里确实需要从文本匹配 hostile visual preset | 只能产出“NPC 使用哪个 hostile visual preset”,不能产出独立 monster 身份。 |
|
|
||||||
|
|
||||||
## 字段级必须合并的内容
|
|
||||||
|
|
||||||
后续改代码时,至少要把下面这些字段和类型一起收口:
|
|
||||||
|
|
||||||
| 当前字段/类型 | 问题 | 合并方向 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `ScenePreset.monsterIds` | 场景里额外保存一份怪物池 | 删除,只保留 `npcs`。 |
|
|
||||||
| `ScenePresetInfo.hostileNpcIds` | 历史遗留双字段 | 直接由 `npcs.filter(initialAffinity < 0 或 hostile)` 推导。 |
|
|
||||||
| `Encounter.monsterPresetId` | 把 hostile NPC 再次物种化 | 改成 hostile visual preset 字段,或并入统一 visualRef。 |
|
|
||||||
| `Encounter.hostileNpcPresetId` | 与 `monsterPresetId` 语义重叠 | 与上面合并为一个字段。 |
|
|
||||||
| `GameState.sceneMonsters` | 把敌对 NPC 单独塞进 monster 容器 | 改成统一 `sceneNpcCombatants` 或等价单一列表。 |
|
|
||||||
| `GameState.sceneHostileNpcs` | 历史兼容层,导致双数据源 | 删除。 |
|
|
||||||
| `SceneHostileNpc / SceneMonster` | 类型名直接固化了分轨 | 改成统一的 hostile NPC / scene combat NPC 类型。 |
|
|
||||||
| `SceneHostileNpcChange / SceneMonsterChange` | 继续复制同一套变更结构 | 合并成统一 NPC scene change。 |
|
|
||||||
| `SceneNpc.monsterPresetId / hostileNpcPresetId` | 同一实体上挂两套 preset 入口 | 收敛为一个 hostile visual/combat preset 字段。 |
|
|
||||||
|
|
||||||
## 本轮最优先的删除顺序
|
|
||||||
|
|
||||||
建议后续真正改代码时,按下面顺序删并,风险最低:
|
|
||||||
|
|
||||||
1. 先删字段入口:`monsterIds / sceneHostileNpcs / hostileNpcPresetId`
|
|
||||||
2. 再删运行时双轨:`src/data/monsters.ts`、`src/data/hostileNpcs.ts`、`src/data/sceneEncounterPreviews.ts`
|
|
||||||
3. 再删战斗双轨:`battlePlan.ts`、`playback.ts` 里的 `monster` actor 与 `sceneMonsters`
|
|
||||||
4. 再删画布双轨:`GameCanvasRuntime.tsx`、`GameCanvasEntityLayer.tsx`、`GameCanvasShared.tsx`
|
|
||||||
5. 最后清编辑器和提示词:`MonsterPresetPanel.tsx`、`MonsterPresetTab.tsx`、`prompt.ts`、`questDirector.ts`
|
|
||||||
|
|
||||||
## 改造后的目标形态
|
|
||||||
|
|
||||||
统一后应只剩下这一套语义:
|
|
||||||
|
|
||||||
- 场景中所有可见角色都放在 `npcs`
|
|
||||||
- 怪物 = `initialAffinity < 0` 或 `hostile = true` 的 NPC
|
|
||||||
- hostile 的视觉差异只来自 hostile visual preset
|
|
||||||
- 战斗中所有敌方单位都属于 hostile NPC combatant
|
|
||||||
- AI、任务、渲染、详情、掉落都只读同一套 NPC 数据
|
|
||||||
|
|
||||||
如果后面代码里还出现下面这些关键词,基本都说明分轨没有删干净:
|
|
||||||
|
|
||||||
- `sceneMonsters`
|
|
||||||
- `sceneHostileNpcs`
|
|
||||||
- `monsterIds`
|
|
||||||
- `hostileNpcPresetId`
|
|
||||||
- `createSceneMonstersFromIds`
|
|
||||||
- `getClosestMonster`
|
|
||||||
- `TurnActor = 'monster'`
|
|
||||||
|
|
||||||
## 这份文档的使用方式
|
|
||||||
|
|
||||||
后续正式开始改造时,建议把文件分成三批执行:
|
|
||||||
|
|
||||||
1. “直接删掉”的入口脚本
|
|
||||||
2. “改名并并回 NPC 主链路”的桥接脚本
|
|
||||||
3. “仅保留素材职责”的 renderer / preset 文件
|
|
||||||
|
|
||||||
不要继续接受“名字叫 NPC,但内部仍然先转成 monster 再跑”的中间态。
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# 工程优化审查总览
|
|
||||||
|
|
||||||
这一组只保留仍能指导当前 Rust / SpacetimeDB 主线的工程审查入口。早期连续扫描的有效结论已经合并到本 README 的“融合结论”,不再保留逐日旧稿。
|
|
||||||
|
|
||||||
## 当前推荐入口
|
|
||||||
|
|
||||||
1. [SERVER_NODE_FREEZE_AND_DEPRECATION_2026-04-24.md](./SERVER_NODE_FREEZE_AND_DEPRECATION_2026-04-24.md)
|
|
||||||
这一版是旧 Node 后端冻结、第一批物理删除与后续批次边界记录,明确当前工程只保留 Rust / SpacetimeDB 主线入口。
|
|
||||||
2. [ENGINEERING_DEAD_CODE_CLEANUP_BATCH_F_2026-04-21.md](./ENGINEERING_DEAD_CODE_CLEANUP_BATCH_F_2026-04-21.md)
|
|
||||||
这一版是第六批落地记录,聚焦删除无入口 `questDirector`、旧观察文案 helper、一次性硬编码同步脚本,并补齐后端运行时 function catalog 契约覆盖。
|
|
||||||
3. [ENGINEERING_DEAD_CODE_CLEANUP_BATCH_E_2026-04-21.md](./ENGINEERING_DEAD_CODE_CLEANUP_BATCH_E_2026-04-21.md)
|
|
||||||
这一版是第五批落地记录,聚焦旧命名 re-export、空路由骨架、旧发布服务、前端 prompt 镜像与无入口编辑器壳层的物理删除。
|
|
||||||
4. [RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_AUDIT_2026-04-28.md](./RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_AUDIT_2026-04-28.md)
|
|
||||||
这一版专项扫描 `src/` 下 RPG 开头脚本,明确运行时开局、快照、story engine、战斗后处理、NPC/背包规则和创作链残留后门中应迁到 `server-rs` 的逻辑。
|
|
||||||
5. [RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_COMPLETION_CHECK_2026-04-28.md](./RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_COMPLETION_CHECK_2026-04-28.md)
|
|
||||||
这一版复核 RPG 前端脚本后端迁移完成度,确认开局、快照、存档、story engine / prompt context、`camp_travel_home_scene`、NPC、背包/锻造、战斗后处理、结果页保存前 normalize 与角色资产 prompt 主链均已收口。
|
|
||||||
6. [FRONTEND_LOGIC_BACKEND_MIGRATION_AUDIT_2026-04-21.md](./FRONTEND_LOGIC_BACKEND_MIGRATION_AUDIT_2026-04-21.md)
|
|
||||||
这一版是本轮前端越界逻辑专项审计,专门汇总当前仍应继续迁到 `server-rs` 的运行时、鉴权、生成编排与本地真相残留。
|
|
||||||
7. [ENGINEERING_DEAD_CODE_CLEANUP_BATCH_D_2026-04-21.md](./ENGINEERING_DEAD_CODE_CLEANUP_BATCH_D_2026-04-21.md)
|
|
||||||
这一版是第四批落地记录,聚焦未接入业务的数据生成产物、测试专用 stub 与对应配置残留出清。
|
|
||||||
8. [ENGINEERING_DEAD_CODE_CLEANUP_BATCH_C_2026-04-21.md](./ENGINEERING_DEAD_CODE_CLEANUP_BATCH_C_2026-04-21.md)
|
|
||||||
这一版是第三批落地记录,聚焦鉴权真相收口,先移除前端保存自动登录用户名/密码的本地真相,并明确运行时快照前置写入为什么当前还不能硬砍。
|
|
||||||
9. [ENGINEERING_DEAD_CODE_CLEANUP_BATCH_B_2026-04-21.md](./ENGINEERING_DEAD_CODE_CLEANUP_BATCH_B_2026-04-21.md)
|
|
||||||
这一版是第二批落地记录,聚焦旧主流程壳层、旧 bootstrap 和旧 inventory / forge / equipment flow Hook 的正式出清。
|
|
||||||
10. [ENGINEERING_DEAD_CODE_CLEANUP_BATCH_A_2026-04-21.md](./ENGINEERING_DEAD_CODE_CLEANUP_BATCH_A_2026-04-21.md)
|
|
||||||
这一版是第一批落地记录,聚焦高置信度小型孤岛、prompt 壳子、stub 和无入口 modal 的首轮清理。
|
|
||||||
11. [CURRENT_ENGINEERING_OPTIMIZATION_OPPORTUNITIES_2026-04-20.md](./CURRENT_ENGINEERING_OPTIMIZATION_OPPORTUNITIES_2026-04-20.md)
|
|
||||||
这一版是面向当前仓库状态的优化点盘点,适合直接拿来排优先级和拆执行批次。
|
|
||||||
12. [ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-20.md](./ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-20.md)
|
|
||||||
这一版是对 `2026-04-19` 基线的当前仓库复核,明确哪些问题已经处理、哪些表述需要纠正、热点又迁移到了哪里。
|
|
||||||
13. [ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-19.md](./ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-19.md)
|
|
||||||
这一版保留原始问题快照和执行回填,适合回看“为什么会有这轮清理与边界收口”。
|
|
||||||
## 融合结论
|
|
||||||
|
|
||||||
- 最新专项审计已经把“前端哪些逻辑还该后移到后端”收敛到 6 类:运行时快照、本地 token、本地浏览历史、NPC 委托换单、quest/runtime item 混合编排、浏览器 AI orchestration。
|
|
||||||
- 工程大清洗已经开始进入实际执行阶段,首批高置信度小型孤岛和残留壳子已开始清理。
|
|
||||||
- 第二批已经开始清理旧主流程壳层与旧 flow Hook,当前主工程的“现行入口”和“历史入口”边界正在变得更清楚。
|
|
||||||
- 第三批已经先完成鉴权真相收口的一段,前端不再保存自动登录用户名/密码;运行时快照链仍需先补后端 contract,再继续往前删。
|
|
||||||
- 第四批已经继续收掉未接入业务的数据生成产物、测试专用 stub 与对应脚本/配置残留,主工程里的“假数据主源”进一步减少。
|
|
||||||
- 第五批已经继续收掉旧命名 re-export、空路由骨架、旧发布 service、前端 prompt 镜像与无入口编辑器壳层,主工程里的“假入口”和“假 prompt 主源”进一步减少。
|
|
||||||
- 第六批已经继续收掉无入口 `questDirector`、旧观察文案 helper、一次性硬编码同步脚本,并修复 function catalog 对后端运行时契约的覆盖缺口。
|
|
||||||
- 当前仓库已经完成“旧 dev 插件链路删除、根目录噪音清理、`server-node -> src/**` 反向依赖切断”这批第一阶段任务。
|
|
||||||
- 当前如果想直接判断“今天先优化什么”,优先看 `CURRENT_ENGINEERING_OPTIMIZATION_OPPORTUNITIES_2026-04-20.md`。
|
|
||||||
- 当前的新重点已经进一步收敛到三类:未接线孤岛模块、前端残留的运行时/鉴权真相、热点向 prompt/runtime profile/平台入口壳层迁移。
|
|
||||||
- 早期三轮工程扫描的结论已经聚合为一条长期规则:工程化不能只看目录和拆分动作,必须覆盖真实主链、质量门禁、绿色基线、关键模块豁免和 build warning。
|
|
||||||
- `2026-04-19` 这一轮把问题压实到了四类:仓库噪音、旧 dev 入口残留、前端越界运行时逻辑、巨型热点文件。
|
|
||||||
- `2026-04-20` 这一轮进一步确认:前两类已经阶段性完成,当前真正剩下的是边界尾巴和新热点迁移。
|
|
||||||
- 如果是要看当前清理和边界收口的最新状态,优先看 `ENGINEERING_CLEANUP_AND_BACKEND_BOUNDARY_AUDIT_2026-04-20.md`。
|
|
||||||
- 如果是要看“当前可执行的优化点清单”,优先看 `CURRENT_ENGINEERING_OPTIMIZATION_OPPORTUNITIES_2026-04-20.md`。
|
|
||||||
- 如果是要做长期重构方案,从 `2026-04-19`、`2026-04-20` 与当前 dead-code batch 记录开始即可。
|
|
||||||
@@ -1,650 +0,0 @@
|
|||||||
# RPG 前端脚本后端迁移审计(2026-04-28)
|
|
||||||
|
|
||||||
## 0. 审计目标
|
|
||||||
|
|
||||||
这份文档只回答一个问题:
|
|
||||||
|
|
||||||
**当前 `src/` 下 RPG 开头或 RPG 目录内的前端脚本中,哪些逻辑已经越过“前端只做表现”的边界,应该继续迁移到 `server-rs` / SpacetimeDB 后端。**
|
|
||||||
|
|
||||||
本轮只做审计与迁移拆分,不改业务代码。
|
|
||||||
|
|
||||||
## 1. 审计范围
|
|
||||||
|
|
||||||
本轮扫描范围:
|
|
||||||
|
|
||||||
1. `src/RpgRuntimeApp.tsx`
|
|
||||||
2. `src/components/rpg-*/*.ts`
|
|
||||||
3. `src/components/rpg-*/*.tsx`
|
|
||||||
4. `src/hooks/rpg-*/*.ts`
|
|
||||||
5. `src/services/rpg-*/*.ts`
|
|
||||||
6. `src/services/rpgRuntimeChatTypes.ts`
|
|
||||||
|
|
||||||
不把测试文件作为迁移对象,但会参考测试暴露的当前行为。
|
|
||||||
|
|
||||||
本轮依据:
|
|
||||||
|
|
||||||
1. `docs/reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md`
|
|
||||||
2. `docs/audits/engineering/FRONTEND_LOGIC_BACKEND_MIGRATION_AUDIT_2026-04-21.md`
|
|
||||||
3. `docs/technical/RPG_PROMPT_FRONTEND_REMOVAL_AND_SERVER_RS_MIGRATION_2026-04-28.md`
|
|
||||||
4. `docs/experience/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md`
|
|
||||||
5. `spacetimedb-concepts` / `spacetimedb-rust` / `spacetimedb-typescript` skill 约束
|
|
||||||
|
|
||||||
## 2. 判断标准
|
|
||||||
|
|
||||||
### 2.1 应迁后端
|
|
||||||
|
|
||||||
只要逻辑满足下面任一项,就不应继续以浏览器前端作为正式真相:
|
|
||||||
|
|
||||||
1. 生成或解释 `GameState`、任务、背包、装备、NPC 状态、战斗状态、剧情记忆。
|
|
||||||
2. 决定开局初始状态、场景落点、遭遇、初始物品、初始装备。
|
|
||||||
3. 决定 action 是否合法、价格、数量、奖励、掉落、复活、场景推进。
|
|
||||||
4. 组装正式 AI prompt / story context / fallback generation。
|
|
||||||
5. 持久化完整运行时快照,或让前端上传整份 `GameState` 作为后端写入依据。
|
|
||||||
6. 对服务端返回的快照做业务补丁,补齐战斗阵型、场景跳转、任务推进等正式状态。
|
|
||||||
7. 解释 Agent session / draft / result preview 哪份才是创作真相。
|
|
||||||
|
|
||||||
### 2.2 可留前端
|
|
||||||
|
|
||||||
下面这些可以继续留在前端:
|
|
||||||
|
|
||||||
1. 面板开关、tab、modal、loading、error、按钮禁用展示。
|
|
||||||
2. 动画播放、镜头、过场、打字机效果、临时视觉态。
|
|
||||||
3. API client、请求封装、SSE 文本消费。
|
|
||||||
4. 纯展示 view model 适配,但不能改变正式业务含义。
|
|
||||||
5. 用户正在编辑的表单草稿,但保存、校验、合并、发布门禁以后端为准。
|
|
||||||
|
|
||||||
## 3. 结论先行
|
|
||||||
|
|
||||||
当前 RPG 前端脚本中仍有 9 类逻辑应该迁移到后端:
|
|
||||||
|
|
||||||
1. `P0` 运行时开局 `GameState` 装配。
|
|
||||||
2. `P0` runtime story 网关中的“客户端带快照解析”和快照补丁。
|
|
||||||
3. `P0` 前端自动保存整份运行时快照。
|
|
||||||
4. `P0` 前端 story engine / chapter / world mutation / prompt context 编排。
|
|
||||||
5. `P0` 战斗胜负后处理、死亡复活、战斗后章节推进。
|
|
||||||
6. `P1` NPC 交易、送礼、价格、数量与库存校验。
|
|
||||||
7. `P1` 背包、装备、锻造可用性与配方视图。
|
|
||||||
8. `P1` RPG 创作 profile 生成的非浏览器 legacy AI 回退。
|
|
||||||
9. `P1` RPG 创作结果页自动保存、session/result preview 真相优先级与 legacy preview fallback。
|
|
||||||
|
|
||||||
一句话判断:
|
|
||||||
|
|
||||||
**前端已经大面积开始调用 server-rs,但仍保留了“后端兼容不完整时由前端补真相”的模式;这部分需要继续收口,否则 SpacetimeDB 表和 reducer 永远无法成为唯一运行时真相源。**
|
|
||||||
|
|
||||||
## 4. 高优先级迁移项
|
|
||||||
|
|
||||||
## 4.1 `P0` 运行时开局 `GameState` 装配仍在前端
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-session/useRpgSessionBootstrap.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. `PLAYER_BASE_MAX_HP = 180`
|
|
||||||
2. `mergeStarterInventoryItems(...)`
|
|
||||||
3. `normalizeExplicitStarterCategory(...)`
|
|
||||||
4. `inferExplicitStarterSlot(...)`
|
|
||||||
5. `buildExplicitCustomWorldRoleStarterState(...)`
|
|
||||||
6. `createInitialGameState()`
|
|
||||||
7. `handleCustomWorldSelect(...)`
|
|
||||||
8. `handleCharacterSelect(...)`
|
|
||||||
|
|
||||||
对应代码表现:
|
|
||||||
|
|
||||||
1. 第 `58` 行到第 `151` 行:前端把角色初始物品编译成 `InventoryItem`,推断装备槽,并直接生成 `runtimeMetadata`。
|
|
||||||
2. 第 `185` 行到第 `235` 行:前端创建完整初始 `GameState`。
|
|
||||||
3. 第 `517` 行到第 `563` 行:前端选择世界时重置运行时上下文、progression、story memory、战斗态。
|
|
||||||
4. 第 `572` 行到第 `710` 行:前端选角时决定开局场景、开局 NPC、初始 NPC state、初始装备、血蓝、货币、背包、任务、队伍、战斗字段。
|
|
||||||
|
|
||||||
### 为什么应迁
|
|
||||||
|
|
||||||
这些不是表现层逻辑,而是“新开局正式状态”的创建权。只要开局状态仍由浏览器本地装配,后端就只能被动接收一份客户端快照,无法成为 runtime session 的唯一真相。
|
|
||||||
|
|
||||||
### 后端落点
|
|
||||||
|
|
||||||
建议收口到:
|
|
||||||
|
|
||||||
1. `server-rs/crates/api-server/src/runtime_story.rs`
|
|
||||||
2. `server-rs/crates/api-server/src/story_sessions.rs`
|
|
||||||
3. `server-rs/crates/module-runtime/src/lib.rs`
|
|
||||||
4. `server-rs/crates/module-story/src/lib.rs`
|
|
||||||
5. `server-rs/crates/module-inventory/src/lib.rs`
|
|
||||||
6. `server-rs/crates/module-progression/src/lib.rs`
|
|
||||||
7. `server-rs/crates/module-runtime-story-compat/src/game_state.rs`
|
|
||||||
|
|
||||||
SpacetimeDB 方向:
|
|
||||||
|
|
||||||
1. 新增或扩展 `begin_rpg_runtime_session` reducer。
|
|
||||||
2. 由 reducer 基于 `ctx.sender()`、作品 profile id、角色 id 创建 runtime session。
|
|
||||||
3. 在表中写入初始场景、角色状态、背包、装备、NPC 状态、任务、progression。
|
|
||||||
4. 前端只调用 reducer / API 并订阅或读取后端返回的 session view model。
|
|
||||||
|
|
||||||
注意:外部 AI / 网络调用不能放进 reducer;如果开局需要 LLM,应由 `api-server` / `platform-llm` 完成生成,再把确定结果写入 SpacetimeDB。
|
|
||||||
|
|
||||||
## 4.2 `P0` runtime story 网关仍要求客户端携带快照解析
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-runtime-story/rpgRuntimeStoryGateway.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. 第 `112` 行到第 `123` 行:`buildRuntimeSnapshotRequest(...)` 把本地 `gameState` 和 `currentStory` 一并提交给后端。
|
|
||||||
2. 第 `125` 行到第 `213` 行:`bridgeServerSceneTravelSnapshot(...)` 在前端补齐服务端旅行快照。
|
|
||||||
3. 第 `216` 行到第 `317` 行:`bridgeServerNpcBattleSnapshot(...)` 在前端补齐 NPC 战斗阵型。
|
|
||||||
4. 第 `323` 行到第 `341` 行:拉取 option catalog 时仍基于客户端快照。
|
|
||||||
5. 第 `387` 行到第 `430` 行:正式动作结算后继续由前端对服务端快照做桥接修补。
|
|
||||||
|
|
||||||
### 为什么应迁
|
|
||||||
|
|
||||||
这类桥接逻辑意味着服务端返回结果不是完整业务真相,前端还在做二次裁决:
|
|
||||||
|
|
||||||
1. 推断下一场景。
|
|
||||||
2. 补齐场景 preset。
|
|
||||||
3. 补齐遭遇预览。
|
|
||||||
4. 补齐任务推进。
|
|
||||||
5. 补齐 NPC 战斗阵型。
|
|
||||||
6. 改写 `GameState` 中的战斗、场景、任务字段。
|
|
||||||
|
|
||||||
这些都应该由后端 runtime action resolver 在同一个事务边界内完成。
|
|
||||||
|
|
||||||
### 后端落点
|
|
||||||
|
|
||||||
建议收口到:
|
|
||||||
|
|
||||||
1. `server-rs/crates/api-server/src/runtime_story/compat/game_state.rs`
|
|
||||||
2. `server-rs/crates/api-server/src/runtime_story/compat/npc_actions.rs`
|
|
||||||
3. `server-rs/crates/api-server/src/runtime_story/compat/equipment_actions.rs`
|
|
||||||
4. `server-rs/crates/api-server/src/runtime_story/compat/quest_actions.rs`
|
|
||||||
5. `server-rs/crates/module-runtime-story-compat/src/options.rs`
|
|
||||||
6. `server-rs/crates/module-runtime-story-compat/src/battle.rs`
|
|
||||||
7. `server-rs/crates/module-runtime-story-compat/src/view_model.rs`
|
|
||||||
|
|
||||||
迁移后前端应该只提交:
|
|
||||||
|
|
||||||
1. `sessionId`
|
|
||||||
2. `functionId`
|
|
||||||
3. `runtimePayload`
|
|
||||||
4. 当前 UI 所需的弱表现参数
|
|
||||||
|
|
||||||
后端应该基于已存 session state 解析动作,不再要求前端上传完整 `GameState`。
|
|
||||||
|
|
||||||
### 本轮落地口径(2026-04-28)
|
|
||||||
|
|
||||||
本轮先收口 `runtime story` 网关这条 P0 主链:
|
|
||||||
|
|
||||||
1. 前端 `rpgRuntimeStoryGateway.ts` 不再构造或上传 `snapshot.gameState / currentStory`。
|
|
||||||
2. 前端 `rpgRuntimeStoryClient.ts` 的状态读取统一走 `GET /api/runtime/story/state/:sessionId`,动作结算统一走 `POST /api/runtime/story/actions/resolve` 且请求体只包含 `sessionId / clientVersion / action`。
|
|
||||||
3. `idle_travel_next_scene` 的下一场景、场景 preset、遭遇清理、战斗清理与旅行计数由 `server-rs/crates/api-server/src/runtime_story/compat.rs` 在持久化快照上完成。
|
|
||||||
4. `npc_fight / npc_spar` 的战斗阵型、`sceneHostileNpcs`、战前 encounter 归档与战斗字段由 `server-rs/crates/api-server/src/runtime_story/compat/npc_actions.rs` 在后端完成。
|
|
||||||
5. 前端只消费后端返回的 hydrated snapshot 和 presentation,不再保留 `bridgeServer*Snapshot` 业务补丁。
|
|
||||||
|
|
||||||
### 本轮落地验收(2026-04-28)
|
|
||||||
|
|
||||||
1. `rpgRuntimeStoryGateway.ts` 已删除 `buildRuntimeSnapshotRequest`、`bridgeServerSceneTravelSnapshot`、`bridgeServerNpcBattleSnapshot` 以及 NPC 战斗/旅行快照补丁依赖;option catalog 和 action resolve 都只提交 `sessionId / clientVersion / action / payload`。
|
|
||||||
2. `rpgRuntimeStoryClient.ts` 已移除前端 `RuntimeStorySnapshotRequest` 入参;状态读取固定为 `GET /api/runtime/story/state/:sessionId`,动作结算请求体不再带 `snapshot` 字段。
|
|
||||||
3. `server-rs` 已覆盖 `idle_travel_next_scene`、`npc_fight`、`npc_spar` 的后端快照补齐测试,并将 route 级测试改为从服务端持久化 session 读取,不再通过旧 save 端点上传整份前端快照铺垫。
|
|
||||||
4. 已执行验收:`cargo check -p api-server --message-format short`、`cargo test -p api-server runtime_story_ -- --nocapture`、`npm run test -- src/services/rpg-runtime/rpgRuntimeStoryClient.test.ts src/hooks/rpg-runtime-story/runtimeStoryCoordinator.test.ts`。
|
|
||||||
5. 搜索确认:`src/hooks/rpg-runtime-story/rpgRuntimeStoryGateway.ts` 与 `src/services/rpg-runtime/rpgRuntimeStoryClient.ts` 不再包含 `/state/resolve`、`buildRuntimeSnapshotRequest`、`bridgeServer*`、`params.snapshot` 或动作请求上传 `snapshot.gameState/currentStory` 的路径。
|
|
||||||
|
|
||||||
## 4.3 `P0` 自动保存仍由前端上传整份运行时快照
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-session/useRpgSessionPersistence.ts`
|
|
||||||
2. `src/services/rpg-runtime/rpgSnapshotClient.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. `useRpgSessionPersistence.ts` 第 `12` 行到第 `23` 行:前端判断哪些运行态可以入正式存档。
|
|
||||||
2. 第 `90` 行到第 `150` 行:前端把 `gameState / bottomTab / currentStory` 作为 payload 写入远端。
|
|
||||||
3. 第 `216` 行到第 `234` 行:前端监听本地 `gameState` 变化并自动保存。
|
|
||||||
4. 第 `236` 行到第 `263` 行:手动存档也上传整份本地快照。
|
|
||||||
5. `rpgSnapshotClient.ts` 第 `31` 行到第 `48` 行:`putRpgSaveSnapshot(...)` 直接发送 `SavedGameSnapshotInput`。
|
|
||||||
|
|
||||||
### 为什么应迁
|
|
||||||
|
|
||||||
运行时存档不是普通表单保存,它是玩家进度、任务、背包、战斗、NPC、故事状态的正式真相。当前模式仍是“前端本地状态变化 -> 上传整份快照 -> 后端持久化”,后端缺少对状态版本、动作来源、事务一致性的最终解释权。
|
|
||||||
|
|
||||||
### 后端落点
|
|
||||||
|
|
||||||
建议改成:
|
|
||||||
|
|
||||||
1. 后端 action resolver 每次动作结算时事务性写入 session state。
|
|
||||||
2. 自动保存只变成后端已有 session state 的归档或 checkpoint。
|
|
||||||
3. 前端最多提交 `sessionId`、`saveSlot`、`bottomTab` 等非业务 UI 状态。
|
|
||||||
4. `runtime_save.rs` 负责快照归档,不再信任浏览器传入的整份 `GameState`。
|
|
||||||
|
|
||||||
涉及表结构时同步检查并更新 `migration.rs`。
|
|
||||||
|
|
||||||
## 4.4 `P0` story engine / chapter / world mutation 仍在前端编排
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-runtime-story/progressionActions.ts`
|
|
||||||
2. `src/hooks/rpg-runtime-story/storyContextBuilder.ts`
|
|
||||||
3. `src/hooks/rpg-runtime-story/useRpgRuntimeStoryController.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. `progressionActions.ts` 第 `193` 行到第 `293` 行:前端生成章节任务并写入 `quests`。
|
|
||||||
2. 第 `295` 行到第 `395` 行:前端收集 story signals、推进 thread、同伴反应、章节、旅程 beat、阵营/世界 mutation、setpiece。
|
|
||||||
3. 第 `644` 行到第 `782` 行:前端提交生成后状态、追加 story history、执行 AI 后再做 recovery。
|
|
||||||
4. `storyContextBuilder.ts` 第 `1` 行到第 `51` 行:前端引入大量 story engine director / graph / memory / prompt context 组件。
|
|
||||||
5. `useRpgRuntimeStoryController.ts` 第 `80` 行到第 `103` 行:前端把 `generateInitialStory / generateNextStep` 注入本地 story request runtime。
|
|
||||||
|
|
||||||
### 为什么应迁
|
|
||||||
|
|
||||||
这些逻辑不是 UI,而是叙事引擎状态机:
|
|
||||||
|
|
||||||
1. 章节任务发放。
|
|
||||||
2. 线索和 thread 更新。
|
|
||||||
3. 同伴关系变化。
|
|
||||||
4. 营地事件与 setpiece 触发。
|
|
||||||
5. 世界 mutation。
|
|
||||||
6. 生成上下文组织。
|
|
||||||
7. fallback / recovery。
|
|
||||||
|
|
||||||
这些都应作为后端 story session 的 reducer / domain service,而不是 React hook 的本地副作用。
|
|
||||||
|
|
||||||
### 后端落点
|
|
||||||
|
|
||||||
建议收口到:
|
|
||||||
|
|
||||||
1. `server-rs/crates/api-server/src/runtime_story/compat/ai.rs`
|
|
||||||
2. `server-rs/crates/api-server/src/prompt/rpg/runtime_chat.rs`
|
|
||||||
3. `server-rs/crates/module-story/src/lib.rs`
|
|
||||||
4. `server-rs/crates/module-progression/src/lib.rs`
|
|
||||||
5. `server-rs/crates/module-runtime-story-compat/src/core.rs`
|
|
||||||
6. `server-rs/crates/module-runtime-story-compat/src/view_model.rs`
|
|
||||||
|
|
||||||
前端保留:
|
|
||||||
|
|
||||||
1. `currentStory` 展示。
|
|
||||||
2. loading / error。
|
|
||||||
3. SSE 增量文本。
|
|
||||||
4. 视觉过场。
|
|
||||||
|
|
||||||
## 4.5 `P0` 战斗后处理、死亡复活、章节推进仍在前端补真相
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-runtime-story/storyChoiceRuntime.ts`
|
|
||||||
2. `src/hooks/rpg-runtime-story/postBattleFlow.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. `storyChoiceRuntime.ts` 第 `1` 行到第 `6` 行仍引用掉落、背包合并等本地规则。
|
|
||||||
2. 第 `304` 行到第 `390` 行:服务端动作返回后,前端仍判断死亡、等待复活、构造复活状态、构造战斗胜利后状态与 story。
|
|
||||||
3. `postBattleFlow.ts` 第 `86` 行到第 `105` 行:前端构造战斗胜利后的 `GameState`。
|
|
||||||
4. 第 `107` 行到第 `158` 行:前端推进 scene act 并构造战斗后 story / deferred options。
|
|
||||||
5. 第 `161` 行到第 `205` 行:前端决定复活回第一场景、恢复血蓝、清战斗态、重建首幕 encounter preview。
|
|
||||||
|
|
||||||
### 为什么应迁
|
|
||||||
|
|
||||||
死亡、胜利、切磋完成、复活、掉落、战斗后章节推进都属于正式玩法结果。前端可以播死亡动画和过场,但不能决定:
|
|
||||||
|
|
||||||
1. 玩家是否死亡。
|
|
||||||
2. 玩家在哪里复活。
|
|
||||||
3. 复活后血蓝。
|
|
||||||
4. 战斗是否结束。
|
|
||||||
5. 任务或章节是否推进。
|
|
||||||
6. deferred options 里有哪些正式可选动作。
|
|
||||||
|
|
||||||
### 后端落点
|
|
||||||
|
|
||||||
建议收口到:
|
|
||||||
|
|
||||||
1. `server-rs/crates/module-combat/src/lib.rs`
|
|
||||||
2. `server-rs/crates/module-runtime-story-compat/src/battle.rs`
|
|
||||||
3. `server-rs/crates/module-runtime-story-compat/src/view_model.rs`
|
|
||||||
4. `server-rs/crates/api-server/src/story_battles.rs`
|
|
||||||
5. `server-rs/crates/api-server/src/runtime_story/compat/presentation.rs`
|
|
||||||
|
|
||||||
前端保留:
|
|
||||||
|
|
||||||
1. 根据后端返回的 `presentation.battle` 播动画。
|
|
||||||
2. 根据后端返回的 `nextStory` 展示文本和选项。
|
|
||||||
3. 根据后端返回的 `GameState` 或 view model 渲染血条、角色、敌人。
|
|
||||||
|
|
||||||
## 5. 中优先级迁移项
|
|
||||||
|
|
||||||
## 5.1 `P1` NPC 交易、送礼的价格和库存校验仍在前端
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-runtime-story/npcInteraction.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. 第 `186` 行到第 `218` 行:前端读取 NPC / 玩家物品,计算交易单价、最大数量、数量 clamp。
|
|
||||||
2. 第 `629` 行到第 `683` 行:前端按本地价格和库存判断买入 / 卖出是否允许,再提交后端。
|
|
||||||
3. 第 `685` 行到第 `702` 行:前端检查礼物是否存在,并构造送礼 action。
|
|
||||||
|
|
||||||
### 当前判断
|
|
||||||
|
|
||||||
这条链已经比早期更好:正式交易和送礼会调用 `resolveRpgRuntimeChoice(...)` 走后端。但前端仍在承担“能不能买、能不能卖、价格是多少、数量是否合法”的第一层裁决。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端应该:
|
|
||||||
|
|
||||||
1. 计算价格。
|
|
||||||
2. 校验库存和货币。
|
|
||||||
3. 校验 NPC 亲和度影响。
|
|
||||||
4. 原子更新玩家库存、NPC 库存、货币和 NPC state。
|
|
||||||
5. 返回可展示的交易 view model 与错误原因。
|
|
||||||
|
|
||||||
前端可以保留:
|
|
||||||
|
|
||||||
1. 当前选择的 itemId / quantity。
|
|
||||||
2. 后端返回价格的展示。
|
|
||||||
3. modal 开关和数量输入。
|
|
||||||
|
|
||||||
### 本次落地设计(2026-04-28)
|
|
||||||
|
|
||||||
为避免“前端只是少算了一处价格,但仍在提交前裁决库存”的半迁移,本次按下面边界收口:
|
|
||||||
|
|
||||||
1. `server-rs` 在 runtime story compat 状态桥中编译 `gameState.runtimeNpcInteraction` 派生 view,包含当前 NPC、玩家货币、购买列表、出售列表、赠礼列表、服务端单价、服务端库存上限、赠礼好感增益与不可选原因。
|
|
||||||
2. `resolve_npc_trade_action(...)` 与 `resolve_npc_gift_action(...)` 仍是唯一正式结算入口,提交后重新校验 mode、itemId、quantity、NPC 库存、玩家背包数量与玩家货币;前端展示的 view 只作为 UI 提示,不作为可信输入。
|
|
||||||
3. 前端 `npcInteraction.ts` 不再读取本地 NPC / 玩家物品计算单价、总价、最大数量,也不再因为本地库存或货币不足提前阻断提交;确认时只提交 `{ mode, itemId, quantity }`,后端返回错误即展示错误。
|
|
||||||
4. 前端 `NpcModals.tsx` 只渲染 `runtimeNpcInteraction` 中的价格、库存和赠礼增益;按钮禁用仅保留“未选择条目 / 服务端 view 标记不可选 / 当前操作正在提交”的表现态,不再用浏览器本地价格和货币重新裁决。
|
|
||||||
5. 验收测试必须覆盖:后端购买成功、NPC 库存不足拒绝、玩家货币不足拒绝、出售成功、玩家背包数量不足拒绝、赠礼成功、礼物不存在拒绝;前端测试覆盖确认交易/赠礼在本地库存或货币不满足时仍提交后端,由后端错误接管。
|
|
||||||
|
|
||||||
## 5.2 `P1` 背包、装备、锻造可用性与配方视图仍在前端
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-runtime-story/inventoryActions.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. 第 `44` 行到第 `52` 行:前端基于 `playerInventory / playerCurrency / worldType` 计算 forge recipe views。
|
|
||||||
2. 第 `97` 行到第 `180` 行:前端先查找物品 / 装备 / 配方,再提交后端 reducer 风格 action。
|
|
||||||
|
|
||||||
### 当前判断
|
|
||||||
|
|
||||||
正式动作已经走后端,这是正确方向。但 `forge recipe view` 和本地物品存在性判断仍会让前端表现出“它知道业务规则”的形态。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端应该提供:
|
|
||||||
|
|
||||||
1. 当前玩家背包 view。
|
|
||||||
2. 当前装备 view。
|
|
||||||
3. 当前可锻造配方 view。
|
|
||||||
4. 每个动作的 disabled / reason。
|
|
||||||
|
|
||||||
前端只渲染后端 view,并把用户选择提交给后端。
|
|
||||||
|
|
||||||
## 5.3 `P1` RPG 创作 profile 生成仍保留非浏览器 legacy AI 回退
|
|
||||||
|
|
||||||
### 迁移回填(2026-04-28)
|
|
||||||
|
|
||||||
已完成本项迁移:
|
|
||||||
|
|
||||||
1. `src/services/rpg-creation/rpgCreationGenerationClient.ts` 删除 `LegacyAiModule`、`loadLegacyAiModule` 与 `typeof window === 'undefined'` 分支。
|
|
||||||
2. `src/services/rpg-creation/rpgCreationGenerationClient.node.test.ts` 锁定 node 环境也只 mock `requestJson`,不再触发前端 legacy AI 模块。
|
|
||||||
3. `src/services/rpg-creation/index.ts` 删除 `generateLegacyCustomWorldProfile` 旧命名导出。
|
|
||||||
4. `server-rs/crates/api-server/src/app.rs` 挂载 `POST /api/runtime/custom-world/profile`。
|
|
||||||
5. `server-rs/crates/api-server/src/custom_world.rs` 复用 `generate_custom_world_foundation_draft(...)` 生成 profile,并补齐前端结果页所需稳定字段。
|
|
||||||
6. 迁移说明见 `docs/technical/RPG_CREATION_PROFILE_GENERATION_BACKEND_MIGRATION_2026-04-28.md`。
|
|
||||||
|
|
||||||
### 迁移前历史证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/services/rpg-creation/rpgCreationGenerationClient.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. 第 `9` 行到第 `19` 行:前端 client 仍定义 `LegacyAiModule = typeof import('../ai')` 并动态加载旧 AI 模块。
|
|
||||||
2. 第 `32` 行到第 `35` 行:非浏览器环境直接调用 `aiClient.generateCustomWorldProfile(...)`。
|
|
||||||
3. 第 `43` 行到第 `51` 行:浏览器环境才请求 `/api/runtime/custom-world/profile`。
|
|
||||||
|
|
||||||
### 为什么应迁
|
|
||||||
|
|
||||||
这与 `RPG_PROMPT_FRONTEND_REMOVAL_AND_SERVER_RS_MIGRATION_2026-04-28.md` 的目标冲突。即使该分支主要服务测试或 SSR,它仍保留了“前端可持有 RPG 生成逻辑和 prompt”的技术后门。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
1. 删除 `import('../ai')` 回退。
|
|
||||||
2. 非浏览器环境也应通过 `server-rs` client / test mock 调用后端 contract。
|
|
||||||
3. 若测试需要离线能力,应 mock `requestJson`,不要恢复前端生成链。
|
|
||||||
|
|
||||||
后端落点:
|
|
||||||
|
|
||||||
1. `server-rs/crates/api-server/src/custom_world.rs`
|
|
||||||
2. `server-rs/crates/api-server/src/custom_world_foundation_draft.rs`
|
|
||||||
3. `server-rs/crates/api-server/src/prompt/rpg/foundation_draft.rs`
|
|
||||||
4. `server-rs/crates/platform-llm/`
|
|
||||||
|
|
||||||
## 5.4 `P1` 创作结果页保存与 Agent session 真相优先级仍在前端编排
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/components/rpg-entry/useRpgCreationResultAutosave.ts`
|
|
||||||
2. `src/components/rpg-entry/useRpgEntryLibraryDetail.ts`
|
|
||||||
3. `src/services/rpg-creation/rpgCreationPreviewAdapter.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. `useRpgCreationResultAutosave.ts` 负责 profile signature、自动保存请求去重、`upsertRpgWorldProfile(...)`、Agent session refresh。
|
|
||||||
2. `useRpgEntryLibraryDetail.ts` 负责判断 draft work 应打开 Agent workspace、生成过程页还是结果页。
|
|
||||||
3. `rpgCreationPreviewAdapter.ts` 在 `resultPreview` 缺失时回退读取 `draftProfile.legacyResultProfile`。
|
|
||||||
|
|
||||||
### 当前判断
|
|
||||||
|
|
||||||
这部分大多是创作 UI 编排,不能简单全迁。但下面三件事属于后端真相:
|
|
||||||
|
|
||||||
1. 保存前 profile normalize。
|
|
||||||
2. Agent session / result preview / legacyResultProfile 的优先级。
|
|
||||||
3. 发布门禁与草稿是否可进入结果页。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端应该提供一个稳定的 `creation_result_view` 或 `work_detail_view`:
|
|
||||||
|
|
||||||
1. 已标准化 profile。
|
|
||||||
2. 当前 session / work 状态。
|
|
||||||
3. 是否可发布。
|
|
||||||
4. 应打开的前端 stage。
|
|
||||||
5. 缺失或失败时的恢复指令。
|
|
||||||
|
|
||||||
前端只根据 view model 切页面,不再自行解释 session 阶段。
|
|
||||||
|
|
||||||
## 5.5 `P1` 角色资产工坊默认 prompt 与缓存合并规则仍在前端
|
|
||||||
|
|
||||||
### 本轮落地状态(2026-04-28)
|
|
||||||
|
|
||||||
已完成默认 prompt 与缓存合并规则迁移:
|
|
||||||
|
|
||||||
1. 新增 `server-rs/crates/api-server/src/prompt/rpg/role_asset_studio.rs`,作为角色资产工坊默认 prompt、legacy prompt 过滤、逐动作 prompt 缓存合并的后端主源。
|
|
||||||
2. 新增 `POST /api/runtime/custom-world/asset-studio/role/{character_id}/workflow`,由前端提交当前正在编辑的角色快照,后端返回合并后的 workflow view。
|
|
||||||
3. 新增 `PUT /api/runtime/custom-world/asset-studio/role/{character_id}/workflow`,复用 OSS JSON 缓存保存,并补齐 `animationPromptTextByKey` 持久化。
|
|
||||||
4. `RpgCreationRoleAssetStudioModalImpl.tsx` 不再调用 `buildDefaultRolePromptBundle`,也不再包含 legacy prompt 判断和缓存合并函数。
|
|
||||||
5. 删除 `src/prompts/customWorldRolePromptDefaults.ts` 与旧兼容 re-export,避免 `src/` 继续持有角色资产默认 prompt 主源。
|
|
||||||
|
|
||||||
### 代码证据
|
|
||||||
|
|
||||||
主要文件:
|
|
||||||
|
|
||||||
1. `src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModalImpl.tsx`
|
|
||||||
2. `src/components/rpg-creation-asset-studio/useRoleVisualCandidateWorkflow.ts`
|
|
||||||
3. `src/components/rpg-creation-asset-studio/useRoleAnimationWorkflow.ts`
|
|
||||||
|
|
||||||
关键逻辑:
|
|
||||||
|
|
||||||
1. `RpgCreationRoleAssetStudioModalImpl.tsx` 第 `54` 行到第 `85` 行:前端合并默认动画 prompt、缓存 prompt、legacy prompt。
|
|
||||||
2. 第 `564` 行到第 `591` 行:前端调用 `buildDefaultRolePromptBundle(baseRole)` 生成默认视觉 / 动作 prompt。
|
|
||||||
3. 第 `807` 行到第 `830` 行:前端保存工作流缓存,包括 prompt、草稿、asset id、animation map。
|
|
||||||
4. `useRoleVisualCandidateWorkflow.ts` 和 `useRoleAnimationWorkflow.ts` 把 prompt 文本直接作为生成 payload。
|
|
||||||
|
|
||||||
### 当前判断
|
|
||||||
|
|
||||||
用户正在编辑的 prompt 草稿可以留在前端表单里,但默认 prompt 生成、legacy prompt 判断、缓存合并、生成参数默认值不应继续散落在 UI modal 内。
|
|
||||||
|
|
||||||
### 迁移建议
|
|
||||||
|
|
||||||
后端应该提供:
|
|
||||||
|
|
||||||
1. `GET /api/runtime/custom-world/asset-studio/role/:id/workflow`
|
|
||||||
2. `PUT /api/runtime/custom-world/asset-studio/role/:id/workflow`
|
|
||||||
3. `POST /api/runtime/custom-world/assets/role-visual-candidates`
|
|
||||||
4. `POST /api/runtime/custom-world/assets/role-animation`
|
|
||||||
|
|
||||||
并在 `server-rs/crates/api-server/src/prompt/rpg/` 或资产 prompt 模块中统一默认 prompt 生成。
|
|
||||||
|
|
||||||
前端只保留:
|
|
||||||
|
|
||||||
1. prompt 文本框。
|
|
||||||
2. 参考图上传 UI。
|
|
||||||
3. 候选图 / 动画预览。
|
|
||||||
4. 用户点击生成、应用、保存的交互入口。
|
|
||||||
|
|
||||||
## 6. 可保留在前端的 RPG 脚本
|
|
||||||
|
|
||||||
以下脚本当前主要承担表现层或 request client 职责,可以暂时保留:
|
|
||||||
|
|
||||||
1. `src/components/rpg-runtime-shell/*`
|
|
||||||
2. `src/components/rpg-runtime-panels/RpgAdventurePanel.tsx`
|
|
||||||
3. `src/components/rpg-runtime-panels/RpgAdventurePanelOverlays.tsx`
|
|
||||||
4. `src/components/rpg-entry/RpgEntryHomeView.tsx`
|
|
||||||
5. `src/components/rpg-entry/RpgEntryWorldDetailView.tsx`
|
|
||||||
6. `src/services/rpg-runtime/rpgRuntimeRequest.ts`
|
|
||||||
7. `src/services/rpg-runtime/rpgRuntimeStoryClient.ts`
|
|
||||||
8. `src/services/rpg-runtime/rpgSnapshotClient.ts`
|
|
||||||
9. `src/services/rpg-creation/rpgCreationAgentClient.ts`
|
|
||||||
10. `src/services/rpg-creation/rpgCreationAssetClient.ts`
|
|
||||||
11. `src/services/rpg-creation/rpgCreationLibraryClient.ts`
|
|
||||||
12. `src/services/rpg-creation/rpgCreationRuntimeClient.ts`
|
|
||||||
|
|
||||||
但要注意:
|
|
||||||
|
|
||||||
1. client 文件可以保留请求封装,但不应继续加入 fallback 生成逻辑。
|
|
||||||
2. UI 文件可以根据后端 view model 展示 disabled / reason,但不能本地重算业务可用性。
|
|
||||||
3. snapshot client 可以保留读取 / 删除 / 归档入口,但不应让前端上传整份 `GameState` 作为正式真相。
|
|
||||||
|
|
||||||
## 7. 推荐迁移顺序
|
|
||||||
|
|
||||||
### 第一阶段:收运行时真相
|
|
||||||
|
|
||||||
优先迁移:
|
|
||||||
|
|
||||||
1. `useRpgSessionBootstrap.ts`
|
|
||||||
2. `rpgRuntimeStoryGateway.ts`
|
|
||||||
3. `useRpgSessionPersistence.ts`
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
1. 后端创建 session。
|
|
||||||
2. 后端保存 session state。
|
|
||||||
3. runtime action 不再依赖客户端完整快照。
|
|
||||||
4. 前端不再补战斗 / 旅行快照。
|
|
||||||
|
|
||||||
### 第二阶段:收 story engine 与战斗后处理
|
|
||||||
|
|
||||||
优先迁移:
|
|
||||||
|
|
||||||
1. `progressionActions.ts`
|
|
||||||
2. `storyContextBuilder.ts`
|
|
||||||
3. `storyChoiceRuntime.ts`
|
|
||||||
4. `postBattleFlow.ts`
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
1. 后端统一处理 story history、thread、chapter、companion reaction、world mutation。
|
|
||||||
2. 后端统一处理死亡、复活、胜利、切磋完成、战斗后选项。
|
|
||||||
3. 前端只播放 presentation。
|
|
||||||
|
|
||||||
### 第三阶段:收 NPC / 背包 / 锻造可用性
|
|
||||||
|
|
||||||
优先迁移:
|
|
||||||
|
|
||||||
1. `npcInteraction.ts`
|
|
||||||
2. `inventoryActions.ts`
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
1. 后端提供交易 / 礼物 / 背包 / 锻造 view model。
|
|
||||||
2. 前端不再重算价格、数量、配方、动作合法性。
|
|
||||||
|
|
||||||
### 第四阶段:收创作链残留后门
|
|
||||||
|
|
||||||
优先迁移:
|
|
||||||
|
|
||||||
1. `rpgCreationGenerationClient.ts`
|
|
||||||
2. `useRpgCreationResultAutosave.ts`
|
|
||||||
3. `useRpgEntryLibraryDetail.ts`
|
|
||||||
4. `rpgCreationPreviewAdapter.ts`
|
|
||||||
5. `RpgCreationRoleAssetStudioModalImpl.tsx`
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
1. 移除 `import('../ai')`。
|
|
||||||
2. 后端输出稳定 result/work view。
|
|
||||||
3. 后端统一角色资产工坊默认 prompt 和缓存合并规则。
|
|
||||||
|
|
||||||
## 8. 后端实现注意事项
|
|
||||||
|
|
||||||
### 4.3 落地记录:自动保存 checkpoint 化(2026-04-28)
|
|
||||||
|
|
||||||
本轮已完成 `自动保存仍由前端上传整份运行时快照` 的迁移:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-session/useRpgSessionPersistence.ts` 自动保存与手动保存不再构造 `gameState / currentStory` 上传体,只提交 `sessionId / bottomTab` checkpoint 元数据。
|
|
||||||
2. `src/services/rpg-runtime/rpgSnapshotClient.ts` 的 `PUT /api/runtime/save/snapshot` 请求体改为 `RuntimeSaveCheckpointInput`,前端保存链路只请求后端刷新 checkpoint。
|
|
||||||
3. `server-rs/crates/api-server/src/runtime_save.rs` 改为读取已存在的服务端 `runtime_snapshot`,校验 `runtimeSessionId` 一致后刷新 `savedAt / bottomTab / runtimeStats.playTimeMs / lastPlayTickAt`,不再信任浏览器传入完整运行态。
|
|
||||||
4. 旧式 `gameState / currentStory` 上传体会被 `PutRuntimeSaveCheckpointRequest` 的 `deny_unknown_fields` 拒绝;无服务端快照、session 不一致、preview/test 快照都会返回冲突。
|
|
||||||
5. 本轮不涉及 SpacetimeDB 表结构变更,因此 `server-rs/crates/spacetime-module/src/migration.rs` 无需调整。
|
|
||||||
|
|
||||||
对应测试:
|
|
||||||
|
|
||||||
1. `src/services/rpg-runtime/rpgSnapshotClient.test.ts` 覆盖保存请求体不含 `gameState / currentStory`。
|
|
||||||
2. `src/hooks/runtimeAuthGuards.test.tsx` 覆盖自动保存只提交 checkpoint。
|
|
||||||
3. `server-rs/crates/api-server/src/runtime_save.rs` 覆盖旧式整快照上传拒绝、缺少服务端快照冲突、session 不一致冲突、成功 checkpoint 沿用服务端快照真相。
|
|
||||||
|
|
||||||
按 SpacetimeDB 约束,后续落地时要遵守:
|
|
||||||
|
|
||||||
1. reducer 不返回数据,前端通过订阅 / 查询读取 view model。
|
|
||||||
2. reducer 使用 `ctx.sender()` 做鉴权,不信任前端传入身份。
|
|
||||||
3. reducer 必须确定性,不能访问网络、文件、外部随机。
|
|
||||||
4. LLM、OSS、图片生成等外部 I/O 放在 `api-server` / `platform-*` crate 中,再把确定结果写回 SpacetimeDB。
|
|
||||||
5. 前端调用 reducer 使用生成绑定和对象参数,不编辑生成代码。
|
|
||||||
6. 涉及表结构修改时同步更新 `migration.rs`。
|
|
||||||
7. 修改后端代码后统一执行 `npm run api-server`,并跑对应自动测试。
|
|
||||||
|
|
||||||
## 9. 最小验收标准
|
|
||||||
|
|
||||||
后续迁移完成后,前端 RPG 脚本应满足:
|
|
||||||
|
|
||||||
1. 搜索 `src/hooks/rpg-*` 不再出现创建完整 `GameState` 的逻辑。
|
|
||||||
2. runtime action 请求不再携带完整 `gameState`。
|
|
||||||
3. 前端不再出现 `bridgeServer*Snapshot` 这类业务补丁函数。
|
|
||||||
4. 前端不再 `putSnapshot({ gameState, currentStory })`。
|
|
||||||
5. 前端不再动态 `import('../ai')`。
|
|
||||||
6. NPC 交易、送礼、背包、锻造的价格、合法性、结果都以后端 view model 为准。
|
|
||||||
7. 战斗死亡、复活、胜利后状态都以后端返回为准。
|
|
||||||
8. 创作结果页打开哪个 stage 由后端 work/session view 指示。
|
|
||||||
|
|
||||||
## 10. 一句话结论
|
|
||||||
|
|
||||||
当前 RPG 前端脚本最需要迁移的不是 UI 组件,而是仍残留在 hooks / gateway / client 里的:
|
|
||||||
|
|
||||||
**开局造状态、运行时带快照解析、前端补服务端快照、自动保存整份 GameState、story engine 编排、战斗后处理、NPC/背包/锻造规则裁决,以及 RPG 创作生成和结果预览的 legacy 后门。**
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
# RPG 前端脚本后端迁移完成度核验(2026-04-28)
|
|
||||||
|
|
||||||
## 1. 核验结论
|
|
||||||
|
|
||||||
本次按 `RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_AUDIT_2026-04-28.md` 中列出的应迁后端项逐项检查当前代码。
|
|
||||||
|
|
||||||
结论:**应迁移项已全部迁移完成。**
|
|
||||||
|
|
||||||
当前状态:
|
|
||||||
|
|
||||||
1. `已完成`:10 项。
|
|
||||||
2. `部分完成`:0 项。
|
|
||||||
3. `未发现完全未启动`:0 项。
|
|
||||||
|
|
||||||
本轮重新核查的变化:
|
|
||||||
|
|
||||||
1. 上次残留的 `RPG 创作结果页` 保存前 profile normalize 已完成后端化。
|
|
||||||
2. `camp_travel_home_scene` 已完成后端收口:正式点击统一走 `/api/runtime/story/actions/resolve`,目标场景、encounter preview、`scenesTraveled` 与快照持久化由 `server-rs` 裁决。
|
|
||||||
|
|
||||||
## 2. 核验口径
|
|
||||||
|
|
||||||
### 2.1 判定为已完成
|
|
||||||
|
|
||||||
满足以下条件才记为已完成:
|
|
||||||
|
|
||||||
1. 前端不再构造正式业务状态。
|
|
||||||
2. 前端不再上传完整 `GameState` 作为后端写入依据。
|
|
||||||
3. 前端不再用本地规则裁决价格、库存、掉落、复活、章节推进、结果页真相优先级。
|
|
||||||
4. 前端只保留请求封装、UI 展示、表单草稿、loading/error、动画表现和按钮禁用展示。
|
|
||||||
|
|
||||||
### 2.2 判定为部分完成
|
|
||||||
|
|
||||||
满足以下任一情况记为部分完成:
|
|
||||||
|
|
||||||
1. 主链已经迁到 `server-rs`,但旧前端分支仍可被正式入口调用。
|
|
||||||
2. 后端已经提供 view/action,但前端仍保留影响业务真相的 normalize、fallback 或 AI context 编排。
|
|
||||||
3. 后端只覆盖部分状态机,前端仍负责另一部分正式状态推进。
|
|
||||||
|
|
||||||
## 3. 逐项结果
|
|
||||||
|
|
||||||
| 原审计项 | 当前状态 | 核验结论 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `P0` 运行时开局 `GameState` 装配 | 已完成 | 正式开局状态由 `server-rs/crates/api-server/src/runtime_story/compat/bootstrap.rs` 创建并持久化;前端 `useRpgSessionBootstrap.ts` 只保留选择页占位态和 `beginRpgRuntimeStorySession(...)` 调用。 |
|
|
||||||
| `P0` runtime story 网关客户端快照解析/补丁 | 已完成 | `rpgRuntimeStoryGateway.ts` 不再有 `buildRuntimeSnapshotRequest` / `bridgeServer*Snapshot`;`rpgRuntimeStoryClient.ts` 读取 `/state/:sessionId`,动作提交 `/actions/resolve`,不再上传完整 `snapshot.gameState/currentStory`。 |
|
|
||||||
| `P0` 自动保存整份运行时快照 | 已完成 | `useRpgSessionPersistence.ts` / `rpgSnapshotClient.ts` 保存链路只提交 `sessionId/bottomTab` checkpoint;`runtime_save.rs` 从服务端已有快照刷新 checkpoint,并测试拒绝旧式完整快照上传。 |
|
|
||||||
| `P0` story engine / chapter / world mutation / prompt context 编排 | 已完成 | 后端已有 `project_story_engine_after_action(...)` 与 `build_runtime_story_prompt_context(...)`,`/story/initial`、`/story/continue` 带 `sessionId` 时只从服务端 snapshot 投影 world / character / history / prompt context;`camp_travel_home_scene` 正式点击也已统一进入后端 resolver,不再由前端拼装场景迁移、encounter preview 或 runtimeStats。 |
|
|
||||||
| `P0` 战斗胜负后处理、死亡复活、战斗后章节推进 | 已完成 | `battle_* / inventory_use` 正式点击统一走 `runServerRuntimeChoiceAction(...)` 与后端 `/actions/resolve`;`storyChoiceContinuation.ts` 对战斗 / 逃脱 / 物品动作加硬保护,不再裁决掉落、任务推进、死亡复活或战后 story;旧 `postBattleFlow.ts` 正式状态构造函数已删除。 |
|
|
||||||
| `P1` NPC 交易/送礼价格数量库存校验 | 已完成 | 前端 `npcInteraction.ts` 已改为消费 `runtimeNpcInteraction` view 并只提交 `{ mode, itemId, quantity }`;后端 `npc_actions.rs` / `npc_support.rs` 负责价格、库存、货币、赠礼好感和原子更新。 |
|
|
||||||
| `P1` 背包/装备/锻造可用性与配方视图 | 已完成 | 前端 `inventoryActions.ts` 读取 `loadRpgRuntimeInventoryView(...)`,根据后端 action/view 提交;后端 `view_model.rs` / `forge.rs` 生成背包、装备槽、配方、`canCraft/enabled/reason`。 |
|
|
||||||
| `P1` RPG 创作 profile 生成 legacy AI 回退 | 已完成 | `rpgCreationGenerationClient.ts` 只调用 `/api/runtime/custom-world/profile`,不再动态 `import('../ai')` 或导出 `generateLegacyCustomWorldProfile`。 |
|
|
||||||
| `P1` 创作结果页保存与 Agent session/result preview 真相优先级 | 已完成 | 后端已提供 `GET /api/runtime/custom-world/agent/sessions/:sessionId/result-view`,统一 `targetStage/profileSource/canAutosaveLibrary/canSyncResultProfile`,前端不再直接读取 `legacyResultProfile`;保存前 canonicalize 已迁到 `server-rs`,`normalizeRpgEntryAgentBackedProfile(...)` 现在只透传兼容旧导入。 |
|
|
||||||
| `P1` 角色资产工坊默认 prompt 与缓存合并规则 | 已完成 | 默认 prompt、legacy prompt 过滤、逐动作缓存合并已在 `server-rs/crates/api-server/src/prompt/rpg/role_asset_studio.rs`;前端 modal 只调用 workflow API、保存用户草稿和发起生成/发布。 |
|
|
||||||
|
|
||||||
## 4. 已完成的具体收尾点
|
|
||||||
|
|
||||||
### 4.1 story engine / prompt context 主链与 `camp_travel_home_scene` 已完成后端收口
|
|
||||||
|
|
||||||
当前后端已经处理动作结算后的确定性 story projector:
|
|
||||||
|
|
||||||
1. `server-rs/crates/module-runtime-story-compat/src/story_engine.rs`
|
|
||||||
2. `server-rs/crates/api-server/src/runtime_story/compat.rs`
|
|
||||||
|
|
||||||
本轮已补齐 prompt context 后端 projector:
|
|
||||||
|
|
||||||
1. `server-rs/crates/module-runtime-story-compat/src/prompt_context.rs`
|
|
||||||
2. `server-rs/crates/api-server/src/runtime_story/compat.rs`
|
|
||||||
3. `server-rs/crates/api-server/src/runtime_chat.rs`
|
|
||||||
4. `server-rs/crates/api-server/src/runtime_chat_plain.rs`
|
|
||||||
|
|
||||||
完成状态:
|
|
||||||
|
|
||||||
1. 前端不再决定 `conversationSituation`、`conversationPressure`、NPC 对话上下文、party relationship notes、scene pressure 文本等正式 prompt context。
|
|
||||||
2. 前端 story initial / continue 在有 `runtimeSessionId` 时只提交 `sessionId / clientVersion / choice / lastFunctionId / requestOptions` 等轻量字段。
|
|
||||||
3. 后端从已持久化 runtime snapshot 投影 `worldType / playerCharacter / sceneHostileNpcs / storyHistory / context`,旧 payload 字段只保留兼容。
|
|
||||||
4. 角色私聊、NPC 对话、NPC 单轮聊天、NPC 招募对话已支持 `sessionId`,有 session 时上下文同样以后端 snapshot 为准。
|
|
||||||
5. 前端奖励领取、NPC 聊天闭合与旧 `deferredRuntimeState` 兼容分支不再写入 `storyEngineMemory`,章节、scene act、thread、world mutation 等正式叙事记忆只以后端快照为准。
|
|
||||||
|
|
||||||
本轮验收:
|
|
||||||
|
|
||||||
1. `cargo test -p module-runtime-story-compat prompt_context --manifest-path server-rs\Cargo.toml` 覆盖后端 prompt context projector 对场景、NPC 披露阶段、对话压力和关系态度的投影。
|
|
||||||
2. `cargo test -p shared-contracts runtime_story_ai_request --manifest-path server-rs\Cargo.toml` 覆盖 story AI 请求可只携带 `sessionId` 的共享契约。
|
|
||||||
3. `cargo test -p api-server runtime_story_initial_uses_server_snapshot_prompt_context_when_session_id_present --manifest-path server-rs\Cargo.toml` 覆盖 `/story/initial` 在 session 模式下以后端快照覆盖浏览器传入的 world / character / context。
|
|
||||||
4. `npm run test -- src/services/ai.test.ts src/hooks/rpg-runtime-story/storyRequestCoordinator.test.ts src/hooks/rpg-runtime-story/useRpgRuntimeStoryController.test.tsx` 覆盖前端 story / chat 请求在 session 模式下只发送轻量 payload。
|
|
||||||
5. `npm run test -- src/hooks/rpg-runtime-story/sessionActions.test.ts src/hooks/rpg-runtime-story/choiceActions.test.ts src/hooks/rpg-runtime-story/npcEncounterActions.test.ts` 覆盖前端旧 UI 分支不再回写后端拥有的 `storyEngineMemory`。
|
|
||||||
|
|
||||||
本轮收尾:
|
|
||||||
|
|
||||||
1. `packages/shared/src/contracts/rpgRuntimeStoryAction.ts` 已把 `camp_travel_home_scene` 纳入 `TASK5_RUNTIME_FUNCTION_IDS` / `SERVER_RUNTIME_FUNCTION_IDS`。
|
|
||||||
2. `server-rs/crates/api-server/src/runtime_story/compat.rs` 的 `camp_travel_home_scene` resolver 已承接前端旧分支的正式状态职责:解析目标场景、写入 `currentScenePreset`、清理战斗/遭遇残留、递增 `scenesTraveled`、生成 encounter preview,并让后续故事和持久化继续走后端 snapshot 主链。
|
|
||||||
3. 目标场景解析以后端为准:优先接收兼容 payload 中的 `targetSceneId`,其次使用内置角色主场景映射,自定义世界按角色与 landmark 绑定解析,再回退到当前场景前向连接或首个冒险场景。
|
|
||||||
4. `src/hooks/rpg-runtime-story/choiceActions.ts` 不再调用 `runCampTravelHomeChoice(...)`,`camp_travel_home_scene` 即使命中旧展示 helper,也会按服务端 function id 统一进入 `runServerRuntimeChoiceAction(...)`。
|
|
||||||
5. `src/hooks/rpg-runtime-story/storyChoiceRuntime.ts` 已删除 `runCampTravelHomeChoice(...)`,前端不再保留正式场景迁移构造函数。
|
|
||||||
|
|
||||||
已消除风险:
|
|
||||||
|
|
||||||
1. `camp_travel_home_scene` 不再由浏览器决定目标场景、运行时统计或 encounter preview。
|
|
||||||
2. 正式离营状态已经满足“前端只提交 action,后端返回 hydrated snapshot”的边界。
|
|
||||||
|
|
||||||
本轮验收补充:
|
|
||||||
|
|
||||||
1. `cargo test -p api-server runtime_story_route_boundary_camp_travel_home_scene_is_server_owned --manifest-path server-rs\Cargo.toml` 覆盖点击后 hydrated snapshot 进入角色主场景、生成 encounter preview、递增 `scenesTraveled` 并持久化。
|
|
||||||
2. `cargo test -p api-server runtime_story --manifest-path server-rs\Cargo.toml` 覆盖 runtime story 相关后端回归。
|
|
||||||
3. `npm run test -- src/hooks/rpg-runtime-story/choiceActions.test.ts` 覆盖 `camp_travel_home_scene` 只调用后端 resolver,不触发旧本地旅行分支。
|
|
||||||
4. `npm run test -- src/hooks/rpg-runtime-story/storyChoiceRuntime.test.ts src/services/rpg-runtime/rpgRuntimeStoryClient.test.ts` 覆盖服务端 runtime choice presentation 与 story client 轻量 payload。
|
|
||||||
|
|
||||||
### 4.2 本地战斗 continuation 已收口到后端
|
|
||||||
|
|
||||||
当前后端已经有战斗终局收口:
|
|
||||||
|
|
||||||
1. `server-rs/crates/module-runtime-story-compat/src/post_battle.rs`
|
|
||||||
2. `server-rs/crates/module-runtime-story-compat/src/battle.rs`
|
|
||||||
3. `server-rs/crates/api-server/src/runtime_story/compat.rs`
|
|
||||||
|
|
||||||
本轮已完成前端旧正式分支收口:
|
|
||||||
|
|
||||||
1. `src/hooks/rpg-runtime-story/choiceActions.ts` 不再保留 `shouldResolveCombatChoiceLocally(...)`,所有服务端 function id 都统一进入 `runServerRuntimeChoiceAction(...)`。
|
|
||||||
2. `src/hooks/rpg-runtime-story/storyChoiceContinuation.ts` 对 `battle_* / inventory_use` 以及被分类为 `battle / escape` 的动作加硬保护,误入时只报错回退,不会写掉落、任务、复活或战后 story。
|
|
||||||
3. `src/hooks/rpg-runtime-story/storyChoiceRuntime.ts` 删除本地敌对 NPC 掉落 reward helper,不再调用 `rollHostileNpcLoot(...)` / `addInventoryItems(...)` 构造正式战斗奖励。
|
|
||||||
4. `src/hooks/rpg-runtime-story/postBattleFlow.ts` 与对应测试删除,前端不再保留 `buildPostBattleVictoryState(...)`、`buildPostBattleVictoryStory(...)`、`buildRevivedFirstSceneState(...)`、`buildDeathStory(...)` 作为正式状态构造入口。
|
|
||||||
|
|
||||||
已消除风险:
|
|
||||||
|
|
||||||
1. `battle_* / inventory_use` 不再因 `inBattle`、`currentBattleNpcId`、可见 story option 等状态回落到本地结算。
|
|
||||||
2. 本地 continuation 不再调用敌对 NPC 掉落、背包合并、敌对 NPC 任务推进。
|
|
||||||
3. 前端不再构造死亡复活状态、胜利后 story、deferred options 和章节推进。
|
|
||||||
|
|
||||||
本轮验收:
|
|
||||||
|
|
||||||
1. `choiceActions.test.ts` 覆盖 `battle_use_skill`、`battle_attack_basic` stale option、`inventory_use` 均只调用后端 resolver,不触发 `buildResolvedChoiceState(...)` / `playResolvedChoice(...)`。
|
|
||||||
2. `storyChoiceRuntime.test.ts` 保留服务端 battle presentation 验收,确认胜利 / 失败最终采用服务端 hydrated snapshot。
|
|
||||||
3. 搜索确认 `src/hooks/rpg-runtime-story` 不再包含 `shouldResolveCombatChoiceLocally`、`buildPostBattleVictory*`、`buildRevivedFirstSceneState`、`buildDeathStory`、`buildHostileNpcBattleReward`。
|
|
||||||
|
|
||||||
### 4.3 创作结果页保存前 normalize 已完成后端化
|
|
||||||
|
|
||||||
后端已经负责 Agent result-view:
|
|
||||||
|
|
||||||
1. `server-rs/crates/api-server/src/custom_world.rs`
|
|
||||||
2. `packages/shared/src/contracts/rpgCreationResultView.ts`
|
|
||||||
3. `src/services/rpg-creation/rpgCreationAgentClient.ts`
|
|
||||||
|
|
||||||
重新核查结果:
|
|
||||||
|
|
||||||
1. `src/components/rpg-entry/rpgEntryShared.ts`
|
|
||||||
2. `src/components/rpg-entry/useRpgCreationResultAutosave.ts`
|
|
||||||
3. `server-rs/crates/api-server/src/custom_world.rs`
|
|
||||||
|
|
||||||
当前完成状态:
|
|
||||||
|
|
||||||
1. `normalizeRpgEntryAgentBackedProfile(...)` 现在直接返回原始 `profile`,注释明确保存前 canonicalize 已迁到 `server-rs`。
|
|
||||||
2. `stringifyRpgEntryAgentBackedProfile(...)` 现在只做 `JSON.stringify(profile)`,不再触发前端 normalize。
|
|
||||||
3. `put_custom_world_library_profile(...)` 写入作品库前调用 `canonicalize_custom_world_library_profile_payload(payload.profile)`。
|
|
||||||
4. `serialize_sync_result_profile_action_payload(...)` 会在 Agent `sync_result_profile` action payload 中对 `profile` 执行 `canonicalize_custom_world_profile_before_save(...)`。
|
|
||||||
5. 后端测试 `sync_result_profile_payload_is_canonicalized_on_server` 与 `custom_world_library_profile_payload_is_canonicalized_on_server` 已覆盖保存前 canonicalize。
|
|
||||||
|
|
||||||
本项不再计为未迁移残留。
|
|
||||||
|
|
||||||
## 5. 已完成项的保留边界
|
|
||||||
|
|
||||||
以下前端残留可以保留,不视为未迁移:
|
|
||||||
|
|
||||||
1. `useRpgSessionBootstrap.ts` 的 `createSelectionGameState()`:只服务选择页占位,正式开局不使用它造运行时真相。
|
|
||||||
2. `NpcModals.tsx` 的数量 stepper、价格展示和按钮禁用:只消费后端 view,不重新裁决价格或库存。
|
|
||||||
3. `inventoryActions.ts` 的 `submitInventoryAction(...)`:只读取后端 action 的 `enabled/reason`,不本地计算规则。
|
|
||||||
4. 角色资产工坊 modal 中的 prompt 输入框与缓存保存:这是用户正在编辑的 UI 草稿,默认 prompt 和合并规则已由后端 workflow 输出。
|
|
||||||
5. `playServerBattlePresentation(...)`:只播放临时动画态,最终 `GameState/currentStory` 仍以服务端 snapshot 为准。
|
|
||||||
|
|
||||||
## 6. 后续建议
|
|
||||||
|
|
||||||
本轮核验范围内的应迁项已经收口。后续建议转为质量维护:
|
|
||||||
|
|
||||||
1. 继续把 function catalog / 旧文档里“本地规则结算”的历史描述逐批改成当前后端归属,避免误导后续开发。
|
|
||||||
2. 新增 runtime function 时先补后端 resolver / view / contract,再让前端接展示入口,保持“前端不造正式状态”的边界。
|
|
||||||
3. 对仍保留的前端本地 continuation 只允许处理非服务端 function id;凡进入 `SERVER_RUNTIME_FUNCTION_IDS` 的动作都应有 route 级测试。
|
|
||||||
|
|
||||||
## 7. 一句话结论
|
|
||||||
|
|
||||||
**当前迁移已经完成开局、快照、存档、story engine / prompt context 主链、`camp_travel_home_scene` 离营迁移、NPC、背包/锻造、战斗后处理、profile 生成、创作结果页 normalize 和角色资产 prompt 主链;本核验范围内不再保留前端正式状态裁决残留。**
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
# server-node 冻结隔离说明(2026-04-24)
|
|
||||||
|
|
||||||
## 1. 当前状态
|
|
||||||
|
|
||||||
`server-node/` 已进入冻结隔离状态,不再作为可运行、可扩展、可引用的后端工程使用。
|
|
||||||
|
|
||||||
冻结原因:项目后端主线已经切到 `server-rs/` 的 Rust + SpacetimeDB 多 crate 方案,继续保留可执行的 `server-node/` 入口会误导后续开发,并增加提示词资产、AI 工作流与运行态逻辑的迁移漂移风险。
|
|
||||||
|
|
||||||
## 2. 冻结边界
|
|
||||||
|
|
||||||
1. 禁止新增任何以 `server-node/` 为目标的运行脚本、开发入口、CI 入口或工程依赖。
|
|
||||||
2. 禁止新增从前端、Rust 后端、脚本或配置主动调用 `server-node/` 的逻辑。
|
|
||||||
3. 禁止在 `server-node/` 内继续新增业务能力;后续能力必须落到 `server-rs/` 对应 crate。
|
|
||||||
4. 历史文档、审计文档、迁移基线中允许保留 `server-node/` 作为旧系统来源说明,但不得把它描述成当前推荐实现。
|
|
||||||
5. 第一批物理删除后,提示词资产与提示词相关工作流继续按迁移核对项追踪,不再恢复旧工程目录。
|
|
||||||
|
|
||||||
## 3. 删除前迁移核对项
|
|
||||||
|
|
||||||
以下资产曾作为删除前核对项。第一批物理删除后,旧实现不再从工作区直接读取;如需继续核对能力缺口,只允许通过历史提交、迁移文档或 `server-rs/` 已迁移实现追溯:
|
|
||||||
|
|
||||||
1. `server-node/src/prompts/customWorldEntityPrompts.ts`:自定义世界实体生成 prompt。
|
|
||||||
2. `server-node/src/prompts/customWorldSceneNpcPrompts.ts`:自定义世界场景 NPC prompt。
|
|
||||||
3. `server-node/src/prompts/questPrompts.ts`:任务意图识别 prompt。
|
|
||||||
4. `server-node/src/prompts/runtimeItemPrompts.ts`:运行时物品意图识别 prompt。
|
|
||||||
5. `server-node/src/prompts/customWorldOrchestratorPrompts.ts`:旧 Custom World JSON 生成与修复 prompt。
|
|
||||||
6. `src/services/ai.ts` 与 `src/prompts/customWorldPrompts.ts` 中仍由前端承载的 AI orchestration / prompt 编排。
|
|
||||||
|
|
||||||
## 4. 工程防线
|
|
||||||
|
|
||||||
1. 第一批物理删除后,根目录 `package.json` 不再保留 `server-node:*`、`dev:node`、`check:server-node-freeze` 等旧入口。
|
|
||||||
2. Vite 与本地开发脚本默认只指向 Rust `api-server`,不再保留 Node/Rust 后端切换开关。
|
|
||||||
3. 历史文档允许保留旧 `server-node` 字样,但新增工程入口、脚本、依赖、运行说明不得再指向旧 Node 后端。
|
|
||||||
4. 若后续需要恢复旧能力,只能迁移到 `server-rs/` 对应 crate 或 Axum facade,不恢复 `server-node/` 工程目录。
|
|
||||||
|
|
||||||
## 5. 后续处理顺序
|
|
||||||
|
|
||||||
1. 继续核对提示词资产与 prompt 工作流是否已完整落到 Rust 主线。
|
|
||||||
2. 继续把前端残留业务编排迁入 `server-rs/`。
|
|
||||||
3. 清理技术索引中容易误导当前入口的 Node / Express 文案。
|
|
||||||
4. 保留历史审计材料,但不得把旧 Node 后端描述为当前推荐实现。
|
|
||||||
|
|
||||||
## 6. 已确认迁移项
|
|
||||||
|
|
||||||
### 6.1 场景幕背景图提示词
|
|
||||||
|
|
||||||
2026-04-25 已把旧 Node 自动资产链路中的场景幕背景图提示词包装迁移到 Rust 主线:
|
|
||||||
|
|
||||||
1. 旧来源:`server-node/src/services/customWorldAgentAutoAssetService.ts` 的 `buildSceneActPrompt(...)`。
|
|
||||||
2. 新主源:`server-rs/crates/api-server/src/custom_world.rs` 的 `build_scene_act_background_image_prompt(...)`。
|
|
||||||
3. 使用位置:`generate_draft_foundation_act_backgrounds(...)` 收集 `sceneChapterBlueprints[].acts[]` 后,先构造幕背景图专用提示词,再调用 `generate_custom_world_scene_image_for_profile(...)`。
|
|
||||||
4. 保留语义:世界名、场景名、幕标题、幕摘要、幕目标、过渡钩子、主角色、辅助角色、世界气质、背景描述,以及“只生成环境背景,不出现角色立绘、站位 UI、对白框、按钮或文字”的约束。
|
|
||||||
5. 迁移边界:`server-node/` 仅作为历史来源说明,不再参与运行;后续调整统一修改 Rust 主源。
|
|
||||||
|
|
||||||
## 7. 第一批安全删除记录(2026-04-25)
|
|
||||||
|
|
||||||
本批次开始把冻结隔离升级为物理删除。执行依据是项目后端主线已固定为 `server-rs/` 的 Rust + SpacetimeDB 多 crate 方案,旧 `server-node/` 不再作为可运行、可扩展、可引用的工程目录保留。
|
|
||||||
|
|
||||||
### 7.1 删除范围
|
|
||||||
|
|
||||||
1. 删除 `server-node/` 目录本体,旧实现只允许通过历史提交、迁移文档和已迁移到 `server-rs/` 的代码追溯。
|
|
||||||
2. 删除旧 Node 后端专用入口:`scripts/dev-node.mjs`、`scripts/server-node-frozen.mjs`、`scripts/check-server-node-freeze.mjs`、`scripts/server-node-freeze-baseline.json`、`scripts/smoke-server-node.ts`、`scripts/smoke-same-origin-stack.ts`、`scripts/m7-api-compare.ts`、`scripts/deploy.sh`、`scripts/update.sh`、`view-llm-logs.ps1`。
|
|
||||||
3. 根目录 `package.json` 删除 `server-node:*`、`dev:node`、`m7:api-compare` 与 `check:server-node-freeze` 等旧入口,并移除 `express`、`@types/express` 依赖。
|
|
||||||
4. `npm run dev` 改为启动 Rust 本地栈;Vite 默认只代理到 Rust `api-server`,不再保留 `GENARRATIVE_BACKEND_STACK` 的 Node/Rust 双栈切换口。
|
|
||||||
5. 清理 `.gitignore` 中只服务 `server-node/` 的忽略规则,并同步 `README.md`、`.env.example`、`server-rs/README.md` 与 `scripts/dev-server/README.md`。
|
|
||||||
|
|
||||||
### 7.2 暂不处理范围
|
|
||||||
|
|
||||||
1. 历史 PRD、审计、迁移基线中的 `server-node` 文案暂时保留为历史记录,不在第一批中大规模改写。
|
|
||||||
2. `backend-rewrite-tasklist/` 中以旧 Node 后端为对照的迁移材料暂时保留,作为后续核对 Rust 主线能力缺口的历史审计输入。
|
|
||||||
3. `src/services/ai.ts` 与 `src/prompts/customWorldPrompts.ts` 的前端残留编排不属于本批 Node 后端删除范围;后续继续按“前端只负责表现,业务逻辑进入 `server-rs/`”单独收口。
|
|
||||||
|
|
||||||
### 7.3 后续批次建议
|
|
||||||
|
|
||||||
1. 技术文档索引中的 Node / Express 后端条目只保留为历史资料,不再作为当前入口或推荐方案。
|
|
||||||
2. 后续如继续整理历史文档,只把仍描述 `Express / PostgreSQL` 为当前目标架构的文字修正为“历史阶段口径”。
|
|
||||||
3. 继续把前端残留业务逻辑迁入 `server-rs`;涉及 SpacetimeDB 的设计、实现、脚本和绑定继续显式使用相关 skill。
|
|
||||||
|
|
||||||
### 7.4 本轮安全核对结果
|
|
||||||
|
|
||||||
2026-04-25 本轮开始分批删除时,已确认第一批工程入口层面满足以下条件:
|
|
||||||
|
|
||||||
1. 工作区根目录下已不存在 `server-node/` 物理目录。
|
|
||||||
2. `scripts/` 下已不存在旧 Node 后端专用运行、冻结、smoke、API 对比脚本。
|
|
||||||
3. 根目录 `package.json` 不再包含 `server-node:*`、`dev:node`、`m7:api-compare` 与 `check:server-node-freeze` 入口。
|
|
||||||
4. `package.json` 与 `package-lock.json` 不再包含 `express`、`@types/express`、`pg`、`postgres` 依赖包。
|
|
||||||
5. `README.md`、`scripts/dev-server/README.md`、`server-rs/README.md`、`vite.config.ts`、`scripts/*.mjs`、`src/`、`packages/` 与 `server-rs/` 未发现仍主动启动或调用 `server-node` 的当前工程入口。
|
|
||||||
|
|
||||||
### 7.5 第二批删除边界
|
|
||||||
|
|
||||||
第二批不再删除可运行工程代码,而是清理“容易误导当前实现口径”的历史文档索引:
|
|
||||||
|
|
||||||
1. 只修正文档中仍把 `server-node`、Express 或 PostgreSQL 描述为当前推荐后端的句子。
|
|
||||||
2. 保留审计、PRD、迁移基线中作为历史事实、旧实现来源、能力对照的 `server-node` 引用。
|
|
||||||
3. 不大规模重写包含中文剧情、需求、审计结论的历史文档,避免把真实历史上下文抹平。
|
|
||||||
4. 若发现某个历史文档仍指导新开发继续写 Node 后端,先把该文档改为“历史阶段口径”,再继续工程处理。
|
|
||||||
|
|
||||||
## 8. 开发命令与脚本复核(2026-04-26)
|
|
||||||
|
|
||||||
本轮按“`server-node/` 已完全移除”的状态复核当前开发入口、脚本和工程配置,确认不再保留旧 Node 后端或 Express 运行路径。
|
|
||||||
|
|
||||||
### 8.1 已复核范围
|
|
||||||
|
|
||||||
1. 根目录 `package.json` 与 `package-lock.json`。
|
|
||||||
2. 根目录 `README.md`、`.env.example`、`.gitignore` 与 `vite.config.ts`。
|
|
||||||
3. `scripts/`、`.github/`、`jenkins/` 与 `server-rs/` 下的已跟踪文本文件。
|
|
||||||
|
|
||||||
### 8.2 复核结论
|
|
||||||
|
|
||||||
1. `package.json` 中不存在 `server-node:*`、`dev:node`、`m7:api-compare`、`check:server-node-freeze` 等旧入口。
|
|
||||||
2. `scripts/` 下不存在 `dev-node.mjs`、`smoke-server-node.ts`、`m7-api-compare.ts`、`smoke-same-origin-stack.ts` 等旧 Node 后端脚本。
|
|
||||||
3. `package.json` 与 `package-lock.json` 中不存在 `express`、`@types/express`、`pg`、`postgres` 依赖。
|
|
||||||
4. 当前开发入口继续固定为 `npm run dev`、`npm run dev:web`、`npm run api-server` 与 Rust / SpacetimeDB 相关脚本,不恢复旧 Node 后端切换开关。
|
|
||||||
|
|
||||||
## 9. Caddy 本地服务入口移除(2026-04-26)
|
|
||||||
|
|
||||||
`serve:caddy` 仅服务旧的 dist 本地代理验证链路,不再属于当前 Rust / SpacetimeDB 主开发入口。本轮删除该入口和配套文件,避免开发命令继续暴露第二套本地服务方式。
|
|
||||||
|
|
||||||
### 9.1 删除范围
|
|
||||||
|
|
||||||
1. 根目录 `package.json` 删除 `serve:caddy`。
|
|
||||||
2. 删除 `scripts/run-caddy-dev.mjs`。
|
|
||||||
3. 删除 `tools/Caddyfile.dev`。
|
|
||||||
4. `.env.example` 删除 `CADDY_API_UPSTREAM` 样例变量。
|
|
||||||
|
|
||||||
### 9.2 后续口径
|
|
||||||
|
|
||||||
1. 本地完整联调继续使用 `npm run dev`。
|
|
||||||
2. 单独前端联调继续使用 `npm run dev:web` 并通过 Vite 代理到 Rust `api-server`。
|
|
||||||
3. 生产包预览继续使用 Vite `preview`,不恢复 Caddy 专用开发入口。
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
# 中文乱码位置清单
|
|
||||||
|
|
||||||
更新时间:`2026-03-24`
|
|
||||||
|
|
||||||
## 说明
|
|
||||||
|
|
||||||
- 本文档用于记录仓库内已确认或高置信度疑似存在中文乱码的位置。
|
|
||||||
- 当前这份文档是重建版本;原有的 [`docs/audits/text/CHINESE_MOJIBAKE_INVENTORY.md`](/E:/Repos/Genarrative/docs/audits/text/CHINESE_MOJIBAKE_INVENTORY.md) 本身也已经乱码,因此已整体替换。
|
|
||||||
- 本次整理依据:
|
|
||||||
- 仓库内旧清单中的完整文件/行号信息
|
|
||||||
- 本轮人工复核时再次直接看到的明显乱码位置
|
|
||||||
- 由于仓库内同时存在“文件内容已写坏”和“终端/工具显示失真”两类情况,下面清单优先保留高置信位置,便于后续逐项修复。
|
|
||||||
|
|
||||||
## 扫描范围
|
|
||||||
|
|
||||||
- 已纳入:`src/`、`docs/`、根目录文档与元数据文件
|
|
||||||
- 已排除:`.git/`、`node_modules/`、`dist/`、纯图片资源目录
|
|
||||||
|
|
||||||
## 高置信位置
|
|
||||||
|
|
||||||
### 文档与元数据
|
|
||||||
|
|
||||||
- [`docs/experience/AGENT_UI_CHANGELOG.md`](/E:/Repos/Genarrative/docs/experience/AGENT_UI_CHANGELOG.md):1, 3, 7, 9, 11-18, 24, 26-28, 32-33, 37, 39, 41-43, 47, 49, 51, 53-66, 68, 72, 74, 77-79, 83, 87, 89-90, 94, 96, 98-100, 104, 106-112, 116
|
|
||||||
- [`UI_CODING_STANDARD.md`](/E:/Repos/Genarrative/UI_CODING_STANDARD.md):3, 91, 104, 108, 112, 156, 158, 160-166
|
|
||||||
- [`metadata.json`](/E:/Repos/Genarrative/metadata.json):2-3
|
|
||||||
|
|
||||||
### 组件层
|
|
||||||
|
|
||||||
- [`src/components/AdventurePanel.tsx`](/E:/Repos/Genarrative/src/components/AdventurePanel.tsx):57, 65
|
|
||||||
- [`src/components/CharacterPanel.tsx`](/E:/Repos/Genarrative/src/components/CharacterPanel.tsx):37, 65-66, 91-95, 102-103
|
|
||||||
- [`src/components/GameCanvas.tsx`](/E:/Repos/Genarrative/src/components/GameCanvas.tsx):240, 462
|
|
||||||
- [`src/components/GameShell.tsx`](/E:/Repos/Genarrative/src/components/GameShell.tsx):108, 116, 124, 138, 171, 181
|
|
||||||
- [`src/components/InventoryPanel.tsx`](/E:/Repos/Genarrative/src/components/InventoryPanel.tsx):55, 58, 82-83, 181-184, 189, 191
|
|
||||||
- [`src/components/MapModal.tsx`](/E:/Repos/Genarrative/src/components/MapModal.tsx):105, 108, 136
|
|
||||||
- [`src/components/MedievalNpcAnimator.tsx`](/E:/Repos/Genarrative/src/components/MedievalNpcAnimator.tsx):124
|
|
||||||
- [`src/components/NpcVisualEditor.tsx`](/E:/Repos/Genarrative/src/components/NpcVisualEditor.tsx):65, 69-71, 403, 440, 444, 446, 464, 467, 470, 482, 569, 571, 585, 610, 628, 662, 690, 694-695, 697, 722, 751, 759, 775, 777, 781, 824
|
|
||||||
- [`src/components/PresetEditor.tsx`](/E:/Repos/Genarrative/src/components/PresetEditor.tsx):34-37, 43-44, 94, 96, 349, 470, 472, 480, 482, 512, 516, 519, 525, 568, 612, 618, 637, 639, 643, 645, 652, 661, 677, 740, 769, 771, 779, 781, 806, 809, 820, 831, 835, 837, 840, 848, 871, 894, 916, 918, 930, 932, 950, 953, 956, 960, 962, 990, 1004, 1006, 1012, 1018, 1024, 1030, 1036, 1064, 1120, 1122, 1130, 1132, 1150, 1153, 1156, 1172-1175, 1180, 1182, 1186, 1188, 1199, 1203-1204, 1208, 1240, 1242
|
|
||||||
|
|
||||||
### 数据层
|
|
||||||
|
|
||||||
- [`src/data/characterPresets.ts`](/E:/Repos/Genarrative/src/data/characterPresets.ts):97, 102, 104, 107, 129, 132-133, 142, 144, 170, 276, 302, 470, 496, 531, 540, 566, 699-700, 729, 972
|
|
||||||
- [`src/data/medievalNpcVisuals.ts`](/E:/Repos/Genarrative/src/data/medievalNpcVisuals.ts):103, 115, 117, 119, 136, 154, 156, 161, 167, 174, 177, 189, 226, 235-236, 241, 244-245, 249-254, 256-257, 260, 262, 274, 278, 288, 451-453, 565, 568, 577, 592
|
|
||||||
- [`src/data/monsterPresets.ts`](/E:/Repos/Genarrative/src/data/monsterPresets.ts):41-42, 54, 60-61, 79-80, 92, 98-99, 117-118, 136-137, 155-156, 171-173, 185, 191-192, 204, 210-211, 229-230, 242, 248-249, 261, 267-268, 280, 286-287, 304-305, 323-324, 335
|
|
||||||
- [`src/data/monsters.ts`](/E:/Repos/Genarrative/src/data/monsters.ts):112
|
|
||||||
- [`src/data/npcInteractions.ts`](/E:/Repos/Genarrative/src/data/npcInteractions.ts):68-71, 80, 82-83, 161, 165, 173, 182, 188-190, 196, 198, 205, 231, 241, 245, 255-260, 272, 296, 319-320, 372, 444-445, 449, 451, 453, 507, 569-570, 578-579, 587-588, 597, 605-606, 615, 617-618, 626-627, 634, 641-643, 652, 661, 665, 670, 672, 676
|
|
||||||
- [`src/data/scenePresets.ts`](/E:/Repos/Genarrative/src/data/scenePresets.ts):115, 120, 122, 128, 133, 135, 141, 146, 148, 154, 159, 161, 167, 172, 174, 180, 185, 187, 192-193, 198, 200, 205-206, 211, 213, 219, 224, 226, 232, 237, 239, 245, 250, 252, 258, 263, 265, 274, 279, 281, 287, 292, 294, 299-300, 305, 307, 313, 318, 320, 326, 331, 333, 339, 344, 346, 352, 357, 359, 364-365, 370, 372, 377-378, 383, 385, 390-391, 396, 398, 404, 409, 411, 417, 422, 424, 509, 523, 525
|
|
||||||
- [`src/data/stateFunctions.ts`](/E:/Repos/Genarrative/src/data/stateFunctions.ts):72-73, 80, 95-96, 103, 117-118, 125, 139-140, 147, 161-162, 169, 186-187, 194, 209-210, 217, 237-238, 255-256, 273-274, 294, 311-312, 329-330, 420, 430-431, 433-435, 437-438, 440-442, 444-445, 447, 449, 451-452, 454-456, 458, 460-461, 464, 466, 468, 484-485, 487, 489, 491, 493, 601, 618
|
|
||||||
|
|
||||||
### Hooks 与服务层
|
|
||||||
|
|
||||||
- [`src/hooks/useCombatFlow.ts`](/E:/Repos/Genarrative/src/hooks/useCombatFlow.ts):54, 56-58, 566
|
|
||||||
- [`src/services/ai.ts`](/E:/Repos/Genarrative/src/services/ai.ts):200-201, 209-210, 234-235, 269-270, 309, 311, 317, 338, 341, 358, 382
|
|
||||||
- [`src/services/prompt.ts`](/E:/Repos/Genarrative/src/services/prompt.ts):7-8, 10, 13-15, 19-20, 25-40, 43, 55, 61-62, 64, 66, 74-76, 78-79, 83-84, 87-90, 96, 103-104, 112, 115, 157, 159, 161-162, 164-165, 167-168, 170, 172-173
|
|
||||||
|
|
||||||
### 其他源码
|
|
||||||
|
|
||||||
- [`src/uiAssets.ts`](/E:/Repos/Genarrative/src/uiAssets.ts):54, 115, 122, 129, 142, 173, 180
|
|
||||||
|
|
||||||
## 本轮人工复核补充
|
|
||||||
|
|
||||||
以下位置是在本轮实现过程中直接再次看到的明显乱码文本,建议优先复查:
|
|
||||||
|
|
||||||
- [`src/hooks/useCombatFlow.ts`](/E:/Repos/Genarrative/src/hooks/useCombatFlow.ts):1094, 1554, 1556-1557
|
|
||||||
|
|
||||||
## 处理优先级建议
|
|
||||||
|
|
||||||
### 第一批
|
|
||||||
|
|
||||||
- `src/components/GameShell.tsx`
|
|
||||||
- `src/components/InventoryPanel.tsx`
|
|
||||||
- `src/components/CharacterPanel.tsx`
|
|
||||||
- `src/data/characterPresets.ts`
|
|
||||||
- `src/data/npcInteractions.ts`
|
|
||||||
- `src/data/scenePresets.ts`
|
|
||||||
- `src/services/prompt.ts`
|
|
||||||
|
|
||||||
### 第二批
|
|
||||||
|
|
||||||
- `src/components/PresetEditor.tsx`
|
|
||||||
- `src/components/NpcVisualEditor.tsx`
|
|
||||||
- `src/data/monsterPresets.ts`
|
|
||||||
- `src/data/stateFunctions.ts`
|
|
||||||
- `docs/experience/AGENT_UI_CHANGELOG.md`
|
|
||||||
- `UI_CODING_STANDARD.md`
|
|
||||||
|
|
||||||
## 备注
|
|
||||||
|
|
||||||
- 当前文档的目标是“先把位置收拢清楚”,不是直接修复乱码。
|
|
||||||
- 如果你下一步要我继续,我可以基于这份清单继续做两件事之一:
|
|
||||||
- 逐文件修复中文乱码
|
|
||||||
- 先做一个“乱码修复优先级 + 替换建议”文档
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
# 当前游戏剧情原文整理与质量评测
|
|
||||||
|
|
||||||
日期:`2026-04-07`
|
|
||||||
|
|
||||||
## 总结先说
|
|
||||||
- 当前游戏的剧情骨架已经能让玩家在武侠、仙侠两个世界里感到“我正在追一件事”,整体判断为:**部分达到预期**。
|
|
||||||
- 强项在于:场景残痕、地图推进、NPC 保留、线程结构已经开始互相咬合。
|
|
||||||
- 短板也很明确:强回收、强情感爆点、真正能改写后续理解的长线后果还没有完全跑起来。
|
|
||||||
|
|
||||||
## 方法
|
|
||||||
- 先把当前仓库里的可扮演角色、场景、场景 NPC、宝藏残痕原文整理出来。
|
|
||||||
- 再用现有 story engine 模块补出 ThemePack、WorldStoryGraph、ActorNarrativeProfile、KnowledgeGraph、ThreadContract、QA Report 和 Release Gate。
|
|
||||||
- 最后按“玩家真实会感受到什么剧情”重组样章,并对照 PRD 的经典 RPG 体验目标做评测。
|
|
||||||
|
|
||||||
## 武侠世界
|
|
||||||
|
|
||||||
### 说明
|
|
||||||
- “原文”部分整理的是当前仓库角色、场景、NPC 和残痕里已经存在的中文文本。
|
|
||||||
- “引擎整理”部分是根据这些原文,经过 story engine 的主题包、线程图谱、角色叙事档案和 QA 规则重新编译出的结构化结果。
|
|
||||||
|
|
||||||
### 项目内原始剧情文本整理
|
|
||||||
### 可扮演角色原文
|
|
||||||
- 剑之公主 / 王庭剑姬
|
|
||||||
角色原文:以迅疾剑技和正面压制见长,适合喜欢凌厉推进的玩家。
|
|
||||||
背景原文:王庭旁支出身,自幼被当作执剑者培养。一次宫变让她失去旧有庇护,也背上了亲手追回王室誓剑与真相的责任。
|
|
||||||
表层来意:以迅疾剑技和正面压制见长,适合喜欢凌厉推进的玩家。
|
|
||||||
- 神箭游侠 / 流风弓卫
|
|
||||||
角色原文:擅长远距离压制与精准射击,节奏灵活,机动性很强。
|
|
||||||
背景原文:曾是边境游骑与斥候,被一场伏击逼得离开旧军阵。如今他只信自己亲眼见过的风向与箭路,却仍背着守住边境故土的旧誓。
|
|
||||||
表层来意:擅长远距离压制与精准射击,节奏灵活,机动性很强。
|
|
||||||
- 双刃旅者 / 疾影斥候
|
|
||||||
角色原文:速度快、侵略性强,适合喜欢持续推进和连段压迫的玩家。
|
|
||||||
背景原文:她在暗巷与帮派追杀中长大,学会靠速度、直觉和先手活下去。表面上轻快利落,心里却一直在追查那封改变命运的密信去向。
|
|
||||||
表层来意:速度快、侵略性强,适合喜欢持续推进和连段压迫的玩家。
|
|
||||||
|
|
||||||
### 场景角色原文
|
|
||||||
- 神箭游侠 / 流风弓卫
|
|
||||||
角色原文:擅长远距离压制与精准射击,节奏灵活,机动性很强。
|
|
||||||
保留线索:曾是边境游骑与斥候,被一场伏击逼得离开旧军…
|
|
||||||
- 青鳞毒蛇 / 敌对角色
|
|
||||||
角色原文:身形细长,吐信极快,最喜欢守在草木掩映和石缝交错之地。
|
|
||||||
保留线索:青鳞毒蛇长期出没于竹林古道。身形细长,吐信…
|
|
||||||
- 枯藤伏虫 / 敌对角色
|
|
||||||
角色原文:像一截会蠕动的枯藤,贴地潜行,适合在林地和湿地里伏击。
|
|
||||||
保留线索:枯藤伏虫长期出没于竹林古道。像一截会蠕动的…
|
|
||||||
- 樵夫老周 / 樵夫
|
|
||||||
角色原文:常在竹海边缘砍柴,对附近路数和兽踪了如指掌。
|
|
||||||
保留线索:樵夫老周长期出没于竹林古道。常在竹海边缘砍…
|
|
||||||
- 玄甲战锋 / 重装先锋
|
|
||||||
角色原文:攻守兼备,推进稳健,适合喜欢扎实前排风格的玩家。
|
|
||||||
保留线索:他长期担任重装前锋,习惯站在最危险的位置替…
|
|
||||||
- 石背蜗怪 / 敌对角色
|
|
||||||
角色原文:驮着厚重石壳缓慢爬行,常盘踞在石阶、桥边与潮湿山路上。
|
|
||||||
保留线索:石背蜗怪长期出没于山门石阶。驮着厚重石壳缓…
|
|
||||||
|
|
||||||
### 场景原文整理
|
|
||||||
- 竹林古道
|
|
||||||
场景原文:风过竹叶如刀鸣,窄道蜿蜒向深处,最适合藏伏毒物和游侠。
|
|
||||||
第一残痕:竹根旁半埋的刀鞘
|
|
||||||
场景角色:神箭游侠(流风弓卫)、青鳞毒蛇(敌对角色)、枯藤伏虫(敌对角色)
|
|
||||||
- 山门石阶
|
|
||||||
场景原文:青石阶层层向上,旧山门半开半掩,守山人与伏兽都能藏得很稳。
|
|
||||||
第一残痕:裂缝里的铜钥
|
|
||||||
场景角色:玄甲战锋(重装先锋)、石背蜗怪(敌对角色)、岩甲蛛兽(敌对角色)
|
|
||||||
- 雨夜长街
|
|
||||||
场景原文:长街积水映灯,屋檐下尽是藏身空隙,最易碰见追踪者与夜行客。
|
|
||||||
第一残痕:灯檐下浸湿的布包
|
|
||||||
场景角色:双刃旅者(疾影斥候)、夜牙潜兽(敌对角色)、孢爆菇灵(敌对角色)
|
|
||||||
- 荒村断垣
|
|
||||||
场景原文:残墙和空屋挤成一团,风里总像夹着旧哭声与游荡脚步。
|
|
||||||
第一残痕:断墙后压着的木匣
|
|
||||||
场景角色:断骨祟灵(敌对角色)、孢爆菇灵(敌对角色)、守村妇人(遗民)
|
|
||||||
- 古桥渡口
|
|
||||||
场景原文:桥面潮湿,渡口雾重,来往之人不多,但每个身影都藏着故事。
|
|
||||||
第一残痕:桥柱缝里的油纸包
|
|
||||||
场景角色:双刃旅者(疾影斥候)、石背蜗怪(敌对角色)、夜牙潜兽(敌对角色)
|
|
||||||
|
|
||||||
### 引擎整理出的明线
|
|
||||||
- 旧宫旧案仍在牵动江湖局势:旧宫旧案仍在牵动江湖局势,焦点常落在竹林古道。
|
|
||||||
- 护送线:边关与地宫残痕正在把旧事重新拖回台前,焦点常落在山门石阶。
|
|
||||||
- 回收线:当前武侠世界不是单点冒险,而是一张由边关军需、渡口风声、地宫旧痕和宫苑旧案交叉拉紧的追查网络。,焦点常落在雨夜长街。
|
|
||||||
- 分歧对峙线:沿着场景残痕和人物试探,一步步追清边关与宫苑旧案背后的真相,焦点常落在荒村断垣。
|
|
||||||
|
|
||||||
### 引擎整理出的暗线
|
|
||||||
- 神箭游侠的隐线:神箭游侠并不只是流风弓卫,他与旧宫旧案仍在牵动江湖局势之间还有一段未被说破的牵连。
|
|
||||||
- 青鳞毒蛇的隐线:青鳞毒蛇并不只是敌对角色,他与护送线之间还有一段未被说破的牵连。
|
|
||||||
- 枯藤伏虫的隐线:枯藤伏虫并不只是敌对角色,他与回收线之间还有一段未被说破的牵连。
|
|
||||||
- 樵夫老周的隐线:樵夫老周并不只是樵夫,他与分歧对峙线之间还有一段未被说破的牵连。
|
|
||||||
- 玄甲战锋的隐线:玄甲战锋并不只是重装先锋,他与旧宫旧案仍在牵动江湖局势之间还有一段未被说破的牵连。
|
|
||||||
|
|
||||||
### 场景旧痕
|
|
||||||
- 竹林古道留下的旧痕:表层残痕是“风过竹叶如刀鸣,窄道蜿蜒向深处,最适合藏伏毒物和游侠。”;压着的真相是“神箭游侠并不只是流风弓卫,他与旧宫旧案仍在牵动江湖局势之间还有一段未被说破的牵连。”
|
|
||||||
- 山门石阶留下的旧痕:表层残痕是“青石阶层层向上,旧山门半开半掩,守山人与伏兽都能藏得很稳。”;压着的真相是“青鳞毒蛇并不只是敌对角色,他与护送线之间还有一段未被说破的牵连。”
|
|
||||||
- 雨夜长街留下的旧痕:表层残痕是“长街积水映灯,屋檐下尽是藏身空隙,最易碰见追踪者与夜行客。”;压着的真相是“枯藤伏虫并不只是敌对角色,他与回收线之间还有一段未被说破的牵连。”
|
|
||||||
- 荒村断垣留下的旧痕:表层残痕是“残墙和空屋挤成一团,风里总像夹着旧哭声与游荡脚步。”;压着的真相是“樵夫老周并不只是樵夫,他与分歧对峙线之间还有一段未被说破的牵连。”
|
|
||||||
- 古桥渡口留下的旧痕:表层残痕是“桥面潮湿,渡口雾重,来往之人不多,但每个身影都藏着故事。”;压着的真相是“玄甲战锋并不只是重装先锋,他与旧宫旧案仍在牵动江湖局势之间还有一段未被说破的牵连。”
|
|
||||||
|
|
||||||
### 玩家在游戏中真实感受到的剧情样章
|
|
||||||
你来到这个武侠世界,是为追查失落王庭誓剑流入江湖的踪迹。此行最重要的目标,是在诸门派与野心家之前找回誓剑,并逼出宫变幕后之人。 第一眼看到的不是纯说明,而是 边关营地 里的环境压迫:营火与旌旗都带着风沙味,士卒、斥候和异兽都可能在这里短暂停留。
|
|
||||||
走进边关营地时,玩家实际感受到的核心不是“到了新地图”,而是“营火与旌旗都带着风沙味,士卒、斥候和异兽都可能在这里短暂停留。”。这句场景原文会立刻把体验拉回到“旧宫旧案仍在牵动江湖局势”这条明线。神箭游侠表面只是流风弓卫,但他的公开面是“擅长远距离压制与精准射击,节奏灵活,机动性很强。”,真正压在肩上的却是“找出贩卖军情的人,并截回被转移的军械账册”。而像“废营帐里的箭囊”这样的场景残痕,会把玩家往“这里一定还藏着别的事”那种感觉里继续推。如果继续追下去,边关营地背后会逐渐显出“神箭游侠并不只是流风弓卫,他与旧宫旧案仍在牵动江湖局势之间还有一段未被说破的牵连。”这层旧伤。
|
|
||||||
走进雨夜长街时,玩家实际感受到的核心不是“到了新地图”,而是“长街积水映灯,屋檐下尽是藏身空隙,最易碰见追踪者与夜行客。”。这句场景原文会立刻把体验拉回到“护送线”这条明线。双刃旅者表面只是疾影斥候,但他的公开面是“速度快、侵略性强,适合喜欢持续推进和连段压迫的玩家。”,真正压在肩上的却是“夺回密信,查清究竟是谁把你推上了被追杀的路”。而像“灯檐下浸湿的布包”这样的场景残痕,会把玩家往“这里一定还藏着别的事”那种感觉里继续推。如果继续追下去,雨夜长街背后会逐渐显出“青鳞毒蛇并不只是敌对角色,他与护送线之间还有一段未被说破的牵连。”这层旧伤。
|
|
||||||
走进古桥渡口时,玩家实际感受到的核心不是“到了新地图”,而是“桥面潮湿,渡口雾重,来往之人不多,但每个身影都藏着故事。”。这句场景原文会立刻把体验拉回到“回收线”这条明线。双刃旅者表面只是疾影斥候,但他的公开面是“速度快、侵略性强,适合喜欢持续推进和连段压迫的玩家。”,真正压在肩上的却是“夺回密信,查清究竟是谁把你推上了被追杀的路”。而像“桥柱缝里的油纸包”这样的场景残痕,会把玩家往“这里一定还藏着别的事”那种感觉里继续推。如果继续追下去,古桥渡口背后会逐渐显出“枯藤伏虫并不只是敌对角色,他与回收线之间还有一段未被说破的牵连。”这层旧伤。
|
|
||||||
因此,武侠世界目前最容易让玩家产生真实剧情感的地方,不是某一句高光台词,而是“场景描述 -> 人物保留 -> 残痕线索 -> 线程压力”这条连续链路。它已经能让玩家觉得自己在追一件还没完全揭开的事,但离“经典 RPG 式的强收束和强情感爆点”还差最后一层回响回收。
|
|
||||||
|
|
||||||
### 质量评测
|
|
||||||
整体判断:**部分达成预期**
|
|
||||||
|
|
||||||
### 维度评测
|
|
||||||
- 角色记忆点:达成。当前可扮演角色的人设、背景、开局动机和首遇目标已经能形成第一轮代入,玩家能记住“谁在上路、为什么上路”。
|
|
||||||
- 低关系也有戏:达成。低好感或首遇 NPC 不再只是“更冷淡”,而是能从当前压力、错位说辞和反应钩子里带出暗线存在感。
|
|
||||||
- 世界互文与旧史厚度:达成。场景、NPC、旧痕和线程已经能互相指向同一批旧事,不再只是各自独立的设定块。
|
|
||||||
- 空间与残痕叙事:达成。地点不是纯背景图,场景描述、宝藏线索和 narrative residue 已经能共同承担“空间会说话”的职责。
|
|
||||||
- 选择后果与主线抓手:部分达成。当前任务抓手和线程合约已经存在,但真正影响关系、理解和后续回响的后果层还没有被完全跑满。
|
|
||||||
- 长线回响与收束:未达成。从 QA 结果看,当前版本最明显的短板仍是“已经埋下的线,后面有没有被稳定回收”。这一步决定它能不能真正跨到经典 RPG 质感。
|
|
||||||
|
|
||||||
### 客观检查
|
|
||||||
- Narrative QA:4 条明线 / 1 条问题。
|
|
||||||
- Release Gate:warn。当前版本可继续观察,但仍有若干 narrative 风险。
|
|
||||||
- Simulation:共跑了 3 条 simulation,ending family 1 类,单次最高 QA 问题 1 条。
|
|
||||||
|
|
||||||
### 当前主要问题
|
|
||||||
- 有线程合约尚未在 chronicle 中留下足够的回收痕迹。
|
|
||||||
|
|
||||||
## 仙侠世界
|
|
||||||
|
|
||||||
### 说明
|
|
||||||
- “原文”部分整理的是当前仓库角色、场景、NPC 和残痕里已经存在的中文文本。
|
|
||||||
- “引擎整理”部分是根据这些原文,经过 story engine 的主题包、线程图谱、角色叙事档案和 QA 规则重新编译出的结构化结果。
|
|
||||||
|
|
||||||
### 项目内原始剧情文本整理
|
|
||||||
### 可扮演角色原文
|
|
||||||
- 剑之公主 / 王庭剑姬
|
|
||||||
角色原文:以迅疾剑技和正面压制见长,适合喜欢凌厉推进的玩家。
|
|
||||||
背景原文:王庭旁支出身,自幼被当作执剑者培养。一次宫变让她失去旧有庇护,也背上了亲手追回王室誓剑与真相的责任。
|
|
||||||
表层来意:以迅疾剑技和正面压制见长,适合喜欢凌厉推进的玩家。
|
|
||||||
- 神箭游侠 / 流风弓卫
|
|
||||||
角色原文:擅长远距离压制与精准射击,节奏灵活,机动性很强。
|
|
||||||
背景原文:曾是边境游骑与斥候,被一场伏击逼得离开旧军阵。如今他只信自己亲眼见过的风向与箭路,却仍背着守住边境故土的旧誓。
|
|
||||||
表层来意:擅长远距离压制与精准射击,节奏灵活,机动性很强。
|
|
||||||
- 双刃旅者 / 疾影斥候
|
|
||||||
角色原文:速度快、侵略性强,适合喜欢持续推进和连段压迫的玩家。
|
|
||||||
背景原文:她在暗巷与帮派追杀中长大,学会靠速度、直觉和先手活下去。表面上轻快利落,心里却一直在追查那封改变命运的密信去向。
|
|
||||||
表层来意:速度快、侵略性强,适合喜欢持续推进和连段压迫的玩家。
|
|
||||||
|
|
||||||
### 场景角色原文
|
|
||||||
- 神箭游侠 / 流风弓卫
|
|
||||||
角色原文:擅长远距离压制与精准射击,节奏灵活,机动性很强。
|
|
||||||
保留线索:曾是边境游骑与斥候,被一场伏击逼得离开旧军…
|
|
||||||
- 玄甲战锋 / 重装先锋
|
|
||||||
角色原文:攻守兼备,推进稳健,适合喜欢扎实前排风格的玩家。
|
|
||||||
保留线索:他长期担任重装前锋,习惯站在最危险的位置替…
|
|
||||||
- 秘匣书妖 / 敌对角色
|
|
||||||
角色原文:像会自行翻页的秘典与宝匣,常在仙门、遗迹与禁制附近浮游。
|
|
||||||
保留线索:秘匣书妖长期出没于云海仙门。像会自行翻页的…
|
|
||||||
- 噬雾飞蛾 / 敌对角色
|
|
||||||
角色原文:借雾气遮身,飞行轨迹诡谲,喜欢围着灵光和人影打转。
|
|
||||||
保留线索:噬雾飞蛾长期出没于云海仙门。借雾气遮身,飞…
|
|
||||||
- 守门灵官 / 门官
|
|
||||||
角色原文:站在门阙侧旁观来者,像在等一份迟迟未到的回报。
|
|
||||||
保留线索:守门灵官长期出没于云海仙门。站在门阙侧旁观…
|
|
||||||
- 幽烬灵蝠 / 敌对角色
|
|
||||||
角色原文:翅翼缭绕灰烬般的灵火,常成群出没于洞天、崖壁与灵脉附近。
|
|
||||||
保留线索:幽烬灵蝠长期出没于悬空仙岛。翅翼缭绕灰烬般…
|
|
||||||
|
|
||||||
### 场景原文整理
|
|
||||||
- 云海仙门
|
|
||||||
场景原文:云阶在脚下翻涌,门阙后方灵光不断,来客与守门异物都极显眼。
|
|
||||||
第一残痕:云阶尽头的灵符匣
|
|
||||||
场景角色:神箭游侠(流风弓卫)、玄甲战锋(重装先锋)、秘匣书妖(敌对角色)
|
|
||||||
- 悬空仙岛
|
|
||||||
场景原文:浮岛边缘风大云急,灵禽与飞蛾总绕着岛沿的光带盘旋。
|
|
||||||
第一残痕:浮岛边缘的灵羽匣
|
|
||||||
场景角色:幽烬灵蝠(敌对角色)、噬雾飞蛾(敌对角色)、云栖散修(散修)
|
|
||||||
- 天宫长廊
|
|
||||||
场景原文:廊柱之间回响着空灵风声,禁制和书妖都喜欢寄在这类高处回廊里。
|
|
||||||
第一残痕:廊柱暗槽里的玉简
|
|
||||||
场景角色:剑之公主(王庭剑姬)、玄甲战锋(重装先锋)、秘匣书妖(敌对角色)
|
|
||||||
- 灵药花圃
|
|
||||||
场景原文:灵草灵花层层叠开,香气诱人,却也最容易养出食灵的怪物。
|
|
||||||
第一残痕:药圃深处的灵壶
|
|
||||||
场景角色:噬灵妖花(敌对角色)、血瞳妖眼(敌对角色)、药圃执事(药师)
|
|
||||||
- 寒玉洞天
|
|
||||||
场景原文:洞壁结着寒玉光泽,地面湿滑,水灵和阴性异物都爱停在这里。
|
|
||||||
第一残痕:寒玉裂隙里的灵髓
|
|
||||||
场景角色:青腐泥灵(敌对角色)、幽烬灵蝠(敌对角色)、澄潮灵母(敌对角色)
|
|
||||||
|
|
||||||
### 引擎整理出的明线
|
|
||||||
- 灵脉与封印正在失衡:灵脉与封印正在失衡,焦点常落在云海仙门。
|
|
||||||
- 追索线:宗门旧案与秘境争夺彼此缠住了当下局势,焦点常落在悬空仙岛。
|
|
||||||
- 封印失衡线:当前仙侠世界由宗门秩序、秘境余波、灵脉封印和古仙残迹共同推着故事前进,玩家每深入一层,都会撞上新的旧事回响。,焦点常落在天宫长廊。
|
|
||||||
- 宗门旧案线:顺着灵痕、残识和人物保留,一层层摸清宗门旧案与秘境失衡的根源,焦点常落在灵药花圃。
|
|
||||||
|
|
||||||
### 引擎整理出的暗线
|
|
||||||
- 神箭游侠的隐线:神箭游侠并不只是流风弓卫,他与灵脉与封印正在失衡之间还有一段未被说破的牵连。
|
|
||||||
- 玄甲战锋的隐线:玄甲战锋并不只是重装先锋,他与追索线之间还有一段未被说破的牵连。
|
|
||||||
- 秘匣书妖的隐线:秘匣书妖并不只是敌对角色,他与封印失衡线之间还有一段未被说破的牵连。
|
|
||||||
- 噬雾飞蛾的隐线:噬雾飞蛾并不只是敌对角色,他与宗门旧案线之间还有一段未被说破的牵连。
|
|
||||||
- 守门灵官的隐线:守门灵官并不只是门官,他与灵脉与封印正在失衡之间还有一段未被说破的牵连。
|
|
||||||
|
|
||||||
### 场景旧痕
|
|
||||||
- 云海仙门留下的旧痕:表层残痕是“云阶在脚下翻涌,门阙后方灵光不断,来客与守门异物都极显眼。”;压着的真相是“神箭游侠并不只是流风弓卫,他与灵脉与封印正在失衡之间还有一段未被说破的牵连。”
|
|
||||||
- 悬空仙岛留下的旧痕:表层残痕是“浮岛边缘风大云急,灵禽与飞蛾总绕着岛沿的光带盘旋。”;压着的真相是“玄甲战锋并不只是重装先锋,他与追索线之间还有一段未被说破的牵连。”
|
|
||||||
- 天宫长廊留下的旧痕:表层残痕是“廊柱之间回响着空灵风声,禁制和书妖都喜欢寄在这类高处回廊里。”;压着的真相是“秘匣书妖并不只是敌对角色,他与封印失衡线之间还有一段未被说破的牵连。”
|
|
||||||
- 灵药花圃留下的旧痕:表层残痕是“灵草灵花层层叠开,香气诱人,却也最容易养出食灵的怪物。”;压着的真相是“噬雾飞蛾并不只是敌对角色,他与宗门旧案线之间还有一段未被说破的牵连。”
|
|
||||||
- 寒玉洞天留下的旧痕:表层残痕是“洞壁结着寒玉光泽,地面湿滑,水灵和阴性异物都爱停在这里。”;压着的真相是“守门灵官并不只是门官,他与灵脉与封印正在失衡之间还有一段未被说破的牵连。”
|
|
||||||
|
|
||||||
### 玩家在游戏中真实感受到的剧情样章
|
|
||||||
你来到这个仙侠世界,是因为王庭圣印坠入了云海裂隙。此行最重要的目标,是寻回圣印,截断那些企图借它开启天门禁制的野心。 第一眼看到的不是纯说明,而是 星舟甲板 里的环境压迫:甲板横在高天之上,风压和星光都很强,飞行异物最爱在这里盘旋。
|
|
||||||
走进星舟甲板时,玩家实际感受到的核心不是“到了新地图”,而是“甲板横在高天之上,风压和星光都很强,飞行异物最爱在这里盘旋。”。这句场景原文会立刻把体验拉回到“灵脉与封印正在失衡”这条明线。神箭游侠表面只是流风弓卫,但他的公开面是“擅长远距离压制与精准射击,节奏灵活,机动性很强。”,真正压在肩上的却是“找回星图核心,查清是谁击落了你的船队”。而像“舵台后的星图匣”这样的场景残痕,会把玩家往“这里一定还藏着别的事”那种感觉里继续推。如果继续追下去,星舟甲板背后会逐渐显出“神箭游侠并不只是流风弓卫,他与灵脉与封印正在失衡之间还有一段未被说破的牵连。”这层旧伤。
|
|
||||||
走进悬空仙岛时,玩家实际感受到的核心不是“到了新地图”,而是“浮岛边缘风大云急,灵禽与飞蛾总绕着岛沿的光带盘旋。”。这句场景原文会立刻把体验拉回到“追索线”这条明线。云栖散修表面只是散修,但他的公开面是“常坐在浮岛边缘打坐,对天风和禁制的变化很敏感。”,真正压在肩上的却是“在悬空仙岛守住自己不愿失去的那一层秩序。”。而像“浮岛边缘的灵羽匣”这样的场景残痕,会把玩家往“这里一定还藏着别的事”那种感觉里继续推。如果继续追下去,悬空仙岛背后会逐渐显出“玄甲战锋并不只是重装先锋,他与追索线之间还有一段未被说破的牵连。”这层旧伤。
|
|
||||||
走进月湖仙洲时,玩家实际感受到的核心不是“到了新地图”,而是“湖光像铺开的镜面,水灵、章灵与花影都可能从月色里浮出来。”。这句场景原文会立刻把体验拉回到“封印失衡线”这条明线。双刃旅者表面只是疾影斥候,但他的公开面是“速度快、侵略性强,适合喜欢持续推进和连段压迫的玩家。”,真正压在肩上的却是“找到残阵核心,并弄明白信里提到的“第二个你”究竟是谁”。而像“湖岸边漂来的玉匣”这样的场景残痕,会把玩家往“这里一定还藏着别的事”那种感觉里继续推。如果继续追下去,月湖仙洲背后会逐渐显出“秘匣书妖并不只是敌对角色,他与封印失衡线之间还有一段未被说破的牵连。”这层旧伤。
|
|
||||||
因此,仙侠世界目前最容易让玩家产生真实剧情感的地方,不是某一句高光台词,而是“场景描述 -> 人物保留 -> 残痕线索 -> 线程压力”这条连续链路。它已经能让玩家觉得自己在追一件还没完全揭开的事,但离“经典 RPG 式的强收束和强情感爆点”还差最后一层回响回收。
|
|
||||||
|
|
||||||
### 质量评测
|
|
||||||
整体判断:**部分达成预期**
|
|
||||||
|
|
||||||
### 维度评测
|
|
||||||
- 角色记忆点:达成。当前可扮演角色的人设、背景、开局动机和首遇目标已经能形成第一轮代入,玩家能记住“谁在上路、为什么上路”。
|
|
||||||
- 低关系也有戏:达成。低好感或首遇 NPC 不再只是“更冷淡”,而是能从当前压力、错位说辞和反应钩子里带出暗线存在感。
|
|
||||||
- 世界互文与旧史厚度:达成。场景、NPC、旧痕和线程已经能互相指向同一批旧事,不再只是各自独立的设定块。
|
|
||||||
- 空间与残痕叙事:达成。地点不是纯背景图,场景描述、宝藏线索和 narrative residue 已经能共同承担“空间会说话”的职责。
|
|
||||||
- 选择后果与主线抓手:部分达成。当前任务抓手和线程合约已经存在,但真正影响关系、理解和后续回响的后果层还没有被完全跑满。
|
|
||||||
- 长线回响与收束:未达成。从 QA 结果看,当前版本最明显的短板仍是“已经埋下的线,后面有没有被稳定回收”。这一步决定它能不能真正跨到经典 RPG 质感。
|
|
||||||
|
|
||||||
### 客观检查
|
|
||||||
- Narrative QA:4 条明线 / 1 条问题。
|
|
||||||
- Release Gate:warn。当前版本可继续观察,但仍有若干 narrative 风险。
|
|
||||||
- Simulation:共跑了 3 条 simulation,ending family 1 类,单次最高 QA 问题 1 条。
|
|
||||||
|
|
||||||
### 当前主要问题
|
|
||||||
- 有线程合约尚未在 chronicle 中留下足够的回收痕迹。
|
|
||||||
|
|
||||||
## 最终结论
|
|
||||||
- 如果目标只是“让玩家进入游戏后立刻感觉世界里有事正在发生”,当前文本资产已经够用,且部分环节已经明显跑起来了。
|
|
||||||
- 如果目标是“对标经典单机 RPG 的强角色回响、强关系后果、强主线收束”,当前版本还只能算走到了一半,最该补的是 payoff 和长线回响。
|
|
||||||
- 也就是说:**当前剧情原文的底座已经部分达到预期,但还没到可以完全放心交给玩家沉浸式吃剧情的程度。**
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
# 游戏 UI / 预设 / 编辑器 / npcInteraction / prompt 文本深度审计
|
|
||||||
|
|
||||||
日期:`2026-04-02`
|
|
||||||
|
|
||||||
## 说明
|
|
||||||
|
|
||||||
- 本文档基于当前仓库源码再次深搜,不直接沿用旧审计结论。
|
|
||||||
- 审计目标:找出“可能出现在游戏 UI、预设、编辑器 UI、生成链路中的文本”里,仍然存在的:
|
|
||||||
- `中文乱码`
|
|
||||||
- `英文直出`
|
|
||||||
- `中英混用`
|
|
||||||
- 本轮重点加查:
|
|
||||||
- `src/data/npcInteractions.ts`
|
|
||||||
- `src/services/prompt.ts`
|
|
||||||
- `src/services/characterChatPrompt.ts`
|
|
||||||
- `src/services/questPrompt.ts`
|
|
||||||
- 说明口径:
|
|
||||||
- “会显示给玩家/编辑器使用者”的文本,按高优先级记录。
|
|
||||||
- “内部英文枚举/键名,但可能泄露到 prompt、编辑器或预览”的内容,也单独记录。
|
|
||||||
- 已经转成中文且当前复查无明显问题的区域,会标记为“复查通过”。
|
|
||||||
|
|
||||||
## 结论摘要
|
|
||||||
|
|
||||||
- 当前最严重的文本污染源不是单一 UI,而是两条链路同时存在问题:
|
|
||||||
- 运行时弹窗 / 实体详情 / NPC 交易招募弹窗
|
|
||||||
- `prompt` 生成链路
|
|
||||||
- `src/services/prompt.ts` 是当前最重灾区,既有大面积中文乱码,也混有英文结构词,会直接影响 AI 生成质量。
|
|
||||||
- `src/data/npcInteractions.ts` 里“话题 actionText / detailText”大体已是正常中文,但商人来源匹配、库存种子物品类别/名称仍有明显乱码,而且阶段枚举仍是英文值。
|
|
||||||
- 预设编辑器仍然是英文和乱码高密度区,尤其是:
|
|
||||||
- `CharacterPresetPanel.tsx`
|
|
||||||
- `MonsterPresetPanel.tsx`
|
|
||||||
- `SceneNpcPresetPanel.tsx`
|
|
||||||
- `ScenePresetPanel.tsx`
|
|
||||||
- `StateFunctionEditor.tsx`
|
|
||||||
- 自定义世界 NPC/场景编辑器、NPC 视觉编辑器大体已转中文,但还残留 `NPC`、`AI`、`Shift` 等中英混用。
|
|
||||||
|
|
||||||
## 一、游戏主流程 UI 与运行时面板
|
|
||||||
|
|
||||||
### 1.1 复查通过
|
|
||||||
|
|
||||||
| 文件 | 行号 | 当前状态 | 说明 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `src/components/game-shell/PreGameSelectionFlow.tsx` | `47-49` | 通过 | 开场联系方式已改成 `QQ群`、`微信` |
|
|
||||||
| `src/components/game-shell/GameShellRuntime.tsx` | `147` | 通过 | 场景名兜底已改成 `当前区域` |
|
|
||||||
| `src/components/adventure-panel/AdventurePanelOverlays.tsx` | `137-158` | 通过 | 任务目标眉标、宝藏踪迹、切磋会话等主文案已是中文 |
|
|
||||||
|
|
||||||
### 1.2 仍有问题
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/game-shell/PreGameSelectionFlow.tsx` | `81`、`89` | 中英混用 | `正在生成核心NPC...` | `NPC` 仍在中文进度文案里直出 |
|
|
||||||
| `src/components/game-shell/GameShellOverlays.tsx` | `151` | 中文乱码 | `闃熶紞` / `鑳屽寘` | 队伍/背包弹层标题已写坏 |
|
|
||||||
| `src/components/game-shell/GameShellRuntime.tsx` | `200-201` | 英文直出 | `GENARRATIVE` | 运行时头部品牌字样仍为英文 |
|
|
||||||
| `src/components/adventure-panel/AdventurePanelOverlays.tsx` | `140` | 中英混用 | `未知敌对 NPC` | 已非乱码,但仍混入 `NPC` |
|
|
||||||
|
|
||||||
## 二、实体详情、NPC 交互弹窗、交易/招募 UI
|
|
||||||
|
|
||||||
### 2.1 `AdventureEntityModal.tsx`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | `111` | 中文乱码 | `鏁屽 NPC` | hostile badge 已坏,同时混入 `NPC` |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | `113`、`137-146` | 英文直出 | `NPC`、`Player`、`Companion` | 标题/副标题兜底值仍为英文 |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | `221`、`264`、`272`、`283`、`296`、`306` | 英文直出 | `Portrait`、`Status`、`Relation`、`Attributes`、`Encounter`、`Inventory` | 六个主区块标题都未本地化 |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | `266-267` | 英文直出 | `HP`、`MP` | 状态条标签未本地化 |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | `274-275` | 英文直出 | `Affinity`、`Recruited: Yes/No` | 关系区块仍是英文 |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | `298-301` | 英文直出 | `Name`、`Context`、`Type`、`Battle mode` | 遭遇信息区块仍是英文 |
|
|
||||||
|
|
||||||
### 2.2 `NpcModals.tsx`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/NpcModals.tsx` | `251-252` | 英文直出 | `NPC inventory` / `Your inventory` / `items` | 交易栏标题和数量单位仍是英文 |
|
|
||||||
| `src/components/NpcModals.tsx` | `272` | 英文直出 | `This NPC has nothing to sell right now.` / `You have nothing to sell right now.` | 空状态未本地化 |
|
|
||||||
| `src/components/NpcModals.tsx` | `290` | 英文直出 | `Purchase total` / `Sale total` | 交易总价标题未本地化 |
|
|
||||||
| `src/components/NpcModals.tsx` | `297` | 中文乱码 | 大段价格不足提示 | 购买资金不足提示整段已坏 |
|
|
||||||
| `src/components/NpcModals.tsx` | `303` | 中文乱码 | 大段默认说明 | 未选中物品时的说明区整段已坏 |
|
|
||||||
| `src/components/NpcModals.tsx` | `350-399` | 英文直出 | `Item details`、`NPC item`、`Stock`、`Value`、`Purchase price`、`Buyback price`、`Slot`、`Not equippable`、`Usable immediately`、`Tags`、`None` | 物品详情弹窗几乎整屏英文 |
|
|
||||||
| `src/components/NpcModals.tsx` | `404` | 中文乱码 + 中英混用 | 含 `MP` 的恢复说明 | 物品使用效果提示已坏 |
|
|
||||||
| `src/components/NpcModals.tsx` | `465` | 中文乱码 | 好感变动提示 | 交易结果反馈文案已坏 |
|
|
||||||
| `src/components/NpcModals.tsx` | `500-501` | 英文直出 | `Manage companion slot`、`Select a current companion to rotate out before recruiting this NPC.` | 招募替换同伴弹窗未本地化 |
|
|
||||||
|
|
||||||
## 三、预设编辑器与编辑器 UI
|
|
||||||
|
|
||||||
### 3.1 复查通过或基本通过
|
|
||||||
|
|
||||||
| 文件 | 行号 | 当前状态 | 说明 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `242` | 通过 | 图片路径占位文案已中文化,不再暴露 `URL` |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `463` | 基本通过 | 空状态是中文,但仍混入 `NPC` |
|
|
||||||
| `src/components/CustomWorldNpcVisualEditor.tsx` | `88-91`、`552-765` | 基本通过 | 主体编辑项、装备类型、素材、姿态等基本都为中文 |
|
|
||||||
|
|
||||||
### 3.2 标签、枚举、面板标题问题
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/shared.ts` | `43` | 英文直出 | `NPC` | 预设编辑器顶层页签仍用英文 |
|
|
||||||
| `src/components/preset-editor/shared.ts` | `62-67` | 英文枚举外露 | `idle`、`move`、`attack`、`die` | 怪物动画候选仍是英文值 |
|
|
||||||
| `src/components/preset-editor/shared.ts` | `69-74` | 英文枚举外露 | `steady`、`burst`、`mobility`、`finisher`、`projectile` | 技能风格候选仍是英文值 |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `702-708` | 中英混用 | `NPC 视觉编辑器` | 中文标题中混入 `NPC` |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `718` | 中英混用 | `当前 NPC` | 选择器标签混入 `NPC` |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `976-977` | 中英混用 | `拖动标记微调 NPC 的预览布局。移动时按住 Shift...` | 混入 `NPC`、`Shift` |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `482-483` | 中英混用 | `AI生成NPC形象`、`NPC 形象 AI 生成功能仍在开发中。` | 模态标题与副标题仍大量混用英文术语 |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `635-636` | 中英混用 | `新增 NPC`、`编辑 NPC` | 主标题和副标题混入 `NPC` |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `734`、`762-763` | 中英混用 | `AI生成`、`AI生成场景`、`场景图片 AI 生成功能仍在开发中。` | 场景生成入口仍混入 `AI` |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `792-793` | 中英混用 | `npc-...`、`自定义NPC...` | 新建 NPC 默认 ID/名称混有英文前缀与 `NPC` |
|
|
||||||
|
|
||||||
### 3.3 `CharacterPresetPanel.tsx`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `262`、`264`、`277` | 中文乱码 | `瑙掕壊`、`淇濆瓨瑙掕壊瑕嗙洊` | 选择卡标题、下拉标签、保存按钮均已坏 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `320`、`326` | 中文乱码 | `鍔ㄧ敾`、`闁煎啿...` | 动画/世界标签已坏 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `418`、`427` | 中文乱码 | `涓栫晫`、`棰勮鎬墿` | 技能预览区字段标签已坏 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `313-314`、`402-403` | 通过 | `角色详情`、`技能预览` | 主体段落标题已是正常中文 |
|
|
||||||
|
|
||||||
### 3.4 `MonsterPresetPanel.tsx`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `49-50` | 英文直出 | `Saved monster overrides...`、`Failed to save monster overrides.` | 保存反馈未本地化 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `58-59` | 中文乱码 | 空状态整段提示 | 无怪物时的空态文案已坏 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `124-126` | 英文直出 | `Section`、`Editor section.`、`Field` | 左侧选择卡是占位英文 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `135` | 中文乱码 | `闂?` | 世界与名字之间的分隔符已坏 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `139` | 英文直出 | `Save Monster Overrides` | 保存按钮未本地化 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `158-159` | 英文直出 | `Monster Override Preview`、`Editor section.` | 右侧预览卡标题/说明未本地化 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `165`、`218`、`223`、`228`、`240`、`248`、`256` | 英文直出 | `Field`、`Name`、`Intro Action` | 多个字段标签仍是英文或占位文案 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `335` | 中文乱码 | 一段动画配置标签乱码 | 帧数相关字段标题已坏 |
|
|
||||||
|
|
||||||
### 3.5 `SceneNpcPresetPanel.tsx`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `159` | 英文直出 | `No NPC presets are available.` | 空状态未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `177-193` | 英文直出 | `NPC Library`、`Browse and select an NPC preset.`、`NPC ID`、`Save NPC Overrides` | 选择区整块未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `221-238` | 英文直出 | `Skill Preview`、`Skill`、`World` | 技能预览区未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `258-259` | 中文乱码 | 空状态/说明整段乱码 | 预览区已有损坏文本 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `264-268` | 英文直出 | `Visual Preview`、`Hostile NPCs use monster presets...`、`Narrative NPCs can preview...` | 视觉预览说明未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `310-357` | 英文直出 | `NPC Details`、`NPC ID`、`Name`、`Role`、`Avatar`、`Linked Character ID`、`Monster Preset ID`、`Initial Affinity`、`Description` | 详情字段整体未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `363` | 中文乱码 | 预览说明大段乱码 | NPC 预览描述区已坏 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `371-375` | 英文直出 | `Visual Editor`、`Hostile NPCs cannot use the visual editor...` | 可视编辑器说明未本地化 |
|
|
||||||
|
|
||||||
### 3.6 `ScenePresetPanel.tsx`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `105-108` | 英文直出 | `Treasure Ahead`、`Treasure` | 宝藏预览实体名/头像仍是英文 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `132-133` | 英文直出 | `Scene Library`、`Browse and select a scene preset.` | 左侧选择区未本地化 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `162` | 英文直出 | `Save` | 保存按钮未本地化 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `170-181` | 英文直出 | `Scene Preview`、`Preview Mode`、`Monster Preview`、`NPC Preview`、`Treasure Preview`、`Empty` | 预览模式整组未本地化 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `207-226` | 英文直出 | `Hostile NPCs`、`NPCs`、`Treasure Hint`、`None` | 场景摘要卡未本地化 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `233-299` | 英文直出 | `Scene Details`、`Scene ID`、`World`、`Name`、`Description`、`Image Source`、`Forward Scene`、`Unset`、`Connected Scene IDs`、`Monster IDs`、`Treasure Hints`、`NPCs In Scene` | 详情编辑区整块未本地化 |
|
|
||||||
|
|
||||||
### 3.7 `StateFunctionEditor.tsx` 与共享请求层
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1060-1064` | 英文直出 | `Failed to save option behavior overrides`、`Option behavior overrides saved.` | 保存结果提示未本地化 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1138` | 中英混用 | `GameState` | 预览说明里仍混入英文类型名 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1143` | 中英混用 | `敌对NPC资源` | `NPC` 混入字段标签 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1185` | 英文枚举外露 | `AnimationState` 原始值 | 玩家动作下拉会直接显示英文枚举值 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1190` | 英文占位符外露 | `{monster}` | 占位提示面向编辑器用户直接可见 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1191` | 英文枚举外露 | `idle`、`move`、`attack` | 怪物动画下拉仍使用英文值 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1213-1217` | 基本通过 | `稳扎稳打`、`爆发`、`机动` 等 | 技能权重显示标签已是中文,但底层 key 仍是英文 |
|
|
||||||
| `src/editor/shared/jsonClient.ts` | `29`、`43` | 英文直出 | `Request failed`、`Save failed` | 通用网络错误兜底未本地化,所有编辑器接口都可能透出 |
|
|
||||||
|
|
||||||
## 四、`npcInteractions.ts` 重点复查
|
|
||||||
|
|
||||||
### 4.1 复查通过
|
|
||||||
|
|
||||||
| 文件 | 行号 | 当前状态 | 说明 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `src/data/npcInteractions.ts` | `507-657` | 通过 | `actionText` / `detailText` 话题库主体已是中文,适合作为后续清理基线 |
|
|
||||||
|
|
||||||
### 4.2 仍有问题
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/data/npcInteractions.ts` | `299-343` | 中文乱码 | `绋€鏈夊搧`、`鏉愭枡`、`琛屽晢鎶ょ`、`绮剧偧閾?`、`闃叉按琛屽泭`、`娌抽浘缃楃洏`、`鍏界毊`、`鐚庨拱缇藉潬`、`鎷撴湰鏂囧唽`、`娈嬬己鍦板浘`、`姝﹀櫒`、`鍒跺紡浣╁垁`、`鎶ょ敳`、`鎶よ噦`、`鏃у竷鍗?`、`闅忚韩鏃х墿` | 商人来源匹配、商品分类、商品名存在明显乱码,会直接流入交易 UI |
|
|
||||||
| `src/data/npcInteractions.ts` | `401-424` | 英文枚举外露 | `deep`、`honest`、`partial`、`guarded`、`warm`、`cooperative`、`neutral`、`distant`、`candid`、`true_but_incomplete`、`half_truth`、`situational_only` | 阶段/回答模式仍使用英文值,虽不一定直接给玩家看,但已进入 prompt 上下文 |
|
|
||||||
| `src/data/npcInteractions.ts` | `428-500` | 基本通过 | `已经愿意逐步谈到真实来历...` 等 | 描述层已是中文,可作为未来替换英文枚举时的文案来源 |
|
|
||||||
| `src/data/npcInteractions.ts` | `1199`、`1209`、`1243` | 中英混用 | `向该NPC送礼`、`邀请该NPC加入队伍`、`离开当前 NPC,重新回到探索状态。` | 选项和说明里仍混入 `NPC` |
|
|
||||||
| `src/data/npcInteractions.ts` | `1250` | 中英混用 | `当前好感为 ...` 同句包含 `NPC` | 遭遇总结文本仍有术语混入 |
|
|
||||||
|
|
||||||
## 五、`prompt` 链路重点复查
|
|
||||||
|
|
||||||
### 5.1 `src/services/prompt.ts`
|
|
||||||
|
|
||||||
这是当前最需要优先清理的文件。问题不是一两处,而是“结构性污染”:
|
|
||||||
|
|
||||||
- 前段中文描述已混入乱码。
|
|
||||||
- 中段人物/遭遇/场景/状态描述大面积乱码。
|
|
||||||
- 后段选项约束、战斗/观察/营地对话指令大面积乱码。
|
|
||||||
- 同时混有 `functionId`、`actionText`、`npc|treasure|none` 等英文结构词。
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/services/prompt.ts` | `145`、`151` | 中文乱码 | `锛堣嚜瀹氫箟...`、`鑷畾涔変笘鐣?...` | 自定义世界描述区已坏 |
|
|
||||||
| `src/services/prompt.ts` | `170-171`、`198-216` | 中文乱码 | 冒险开场理由、表层钩子、眼前顾虑、目标等整段乱码 | 角色开场信息会直接污染生成上下文 |
|
|
||||||
| `src/services/prompt.ts` | `226-233` | 中文乱码 + 英文枚举外露 | `NPC`、`deep`、`warm`、`answerMode` 等 | NPC 会话阶段控制段同时存在乱码和英文枚举 |
|
|
||||||
| `src/services/prompt.ts` | `378-451` | 中文乱码 + 中英混用 | 遭遇实体、敌对 `NPC`、玩家状态、场景说明等整段乱码 | 玩家、NPC、怪物、场景等核心提示都已受污染 |
|
|
||||||
| `src/services/prompt.ts` | `547-568` | 中文乱码 + 英文结构词外露 | `functionId`、`actionText`、`function` | 选项约束与动作重写规则段落已坏 |
|
|
||||||
| `src/services/prompt.ts` | `859-910` | 中文乱码 + 中英混用 | 空闲/遭遇函数说明、观察线索、开场营地跟进等整段乱码 | 后半段生成规则几乎不可维护 |
|
|
||||||
|
|
||||||
### 5.2 `src/services/characterChatPrompt.ts`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/services/characterChatPrompt.ts` | `42-54` | 英文直出 | `You are a companion character...`、`Generate exactly 3 player reply suggestions.`、`Summarize the evolving relationship...` | 系统 prompt 整段为英文 |
|
|
||||||
| `src/services/characterChatPrompt.ts` | `57-59` | 英文直出 | `Wuxia`、`Xianxia`、`Custom World` | 世界描述仍为英文 |
|
|
||||||
| `src/services/characterChatPrompt.ts` | `64`、`69-71`、`74-75` | 英文直出 | `Custom world reference`、`female`、`male`、`unknown`、`left`、`right` | 性别、朝向、扩展说明均为英文 |
|
|
||||||
| `src/services/characterChatPrompt.ts` | `99-112` | 英文直出 | `Recent story: none.`、`Earlier story summary`、`Most recent 3 story rounds` | 历史摘要模板为英文 |
|
|
||||||
| `src/services/characterChatPrompt.ts` | `123-155` | 英文直出 | `damage`、`mana`、`cooldown`、`arrival reason`、`current goal`、`world schema`、`top attributes` | 角色信息拼接模板整体为英文 |
|
|
||||||
|
|
||||||
### 5.3 `src/services/questPrompt.ts`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/services/questPrompt.ts` | `28-31` | 英文直出 | `issued by`、`No live quests` | 当前任务摘要模板为英文 |
|
|
||||||
| `src/services/questPrompt.ts` | `35-37` | 英文直出 | `Active companions`、`Roster companions` | 同伴摘要模板为英文 |
|
|
||||||
| `src/services/questPrompt.ts` | `41-52` | 英文直出 | `Player`、`HP`、`Mana`、`Inventory snapshot` | 玩家状态模板为英文 |
|
|
||||||
| `src/services/questPrompt.ts` | `67-95` | 英文直出 | `You are the quest director...` 等整段 | 任务意图系统 prompt 全英文 |
|
|
||||||
| `src/services/questPrompt.ts` | `107-123` | 英文直出 | `World`、`Issuer NPC`、`Encounter kind`、`Recent story moments` 等 | 最终拼接 prompt 仍是英文框架 |
|
|
||||||
|
|
||||||
### 5.4 `src/services/aiFallbacks.ts`
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/services/aiFallbacks.ts` | `5-17` | 英文直出 | `Player: ...`、`That question is not casual...`、`I accept...` | 离线 NPC 对话与招募兜底仍是英文 |
|
|
||||||
| `src/services/aiFallbacks.ts` | `28-35` | 英文直出 | `I will answer in my own way`、`I still remember...` | 离线角色私聊回复兜底为英文 |
|
|
||||||
| `src/services/aiFallbacks.ts` | `40-42` | 英文直出 | 三条候选回复 | 私聊建议兜底为英文 |
|
|
||||||
| `src/services/aiFallbacks.ts` | `52-57` | 英文直出 | `Player`、`Recent exchange`、`warmer toward the player` | 私聊摘要兜底为英文 |
|
|
||||||
|
|
||||||
## 六、其他会外溢到 UI / 预览 / 生成链路的文本源
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | `578-580` | 通过 | `前往...`、`离开营地...` | 旅行选项已中文化 |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | `599`、`602` | 英文直出 | `approaches from a short distance away...`、`steps into view...` | 初始同伴结果文本仍是英文 |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | `814` | 英文直出 | `Continue the camp exchange and organize the most natural next actions.` | 营地跟进生成指令仍是英文 |
|
|
||||||
| `src/data/scenePresets.ts` | `195`、`297` | 英文枚举外露 | `trade`、`fight`、`spar`、`help`、`chat`、`recruit`、`gift` | 场景函数列表仍是英文 ID |
|
|
||||||
| `src/data/stateFunctions.ts` | `33` | 英文枚举外露 | `idle|move|attack` | 怪物动画枚举仍是英文 |
|
|
||||||
| `src/data/stateFunctions.ts` | `130`、`152`、`174`、`196`、`221`、`244` | 英文枚举外露 | `steady`、`burst`、`mobility`、`finisher`、`projectile` | 技能权重 key 仍为英文 |
|
|
||||||
| `src/data/characterPresets.ts` | `60-76` | 英文枚举外露 | `blunt`、`wary`、`evasive`、`measured`、`gentle`、`teasing`、`dry`、`steady`、`direct`、`fragmented`、`deflecting` | 对话风格推断值仍为英文,已进入 `npcInteractions` / `prompt` 描述链路 |
|
|
||||||
|
|
||||||
## 七、优先级建议
|
|
||||||
|
|
||||||
### P0:先修,否则会持续污染生成结果或直接破坏主界面
|
|
||||||
|
|
||||||
1. `src/services/prompt.ts`
|
|
||||||
2. `src/components/NpcModals.tsx`
|
|
||||||
3. `src/components/AdventureEntityModal.tsx`
|
|
||||||
4. `src/data/npcInteractions.ts` 的商店库存种子乱码段
|
|
||||||
5. `src/components/game-shell/GameShellOverlays.tsx` 的标题乱码
|
|
||||||
|
|
||||||
### P1:紧接着修,编辑器体验当前已经明显受损
|
|
||||||
|
|
||||||
1. `src/components/preset-editor/MonsterPresetPanel.tsx`
|
|
||||||
2. `src/components/preset-editor/SceneNpcPresetPanel.tsx`
|
|
||||||
3. `src/components/preset-editor/ScenePresetPanel.tsx`
|
|
||||||
4. `src/components/preset-editor/CharacterPresetPanel.tsx`
|
|
||||||
5. `src/components/StateFunctionEditor.tsx`
|
|
||||||
6. `src/editor/shared/jsonClient.ts`
|
|
||||||
|
|
||||||
### P2:统一术语与风格,减少中英混用
|
|
||||||
|
|
||||||
1. 全局统一 `NPC`、`HP`、`MP`、`AI`、`Shift` 是否保留英文缩写
|
|
||||||
2. 将 `npcInteractions.ts` / `characterPresets.ts` / `stateFunctions.ts` 的英文枚举与键名整理为“内部值 + 中文展示层”
|
|
||||||
3. 补齐 `aiFallbacks.ts`、`characterChatPrompt.ts`、`questPrompt.ts` 的中文 prompt / fallback 版本
|
|
||||||
|
|
||||||
## 八、建议的修复顺序
|
|
||||||
|
|
||||||
1. 先修 `prompt` 和 `npcInteractions`,因为这两处会同时污染 AI 输出和运行时文本。
|
|
||||||
2. 再修 `NpcModals`、`AdventureEntityModal`、`GameShellOverlays`,优先恢复玩家可见界面。
|
|
||||||
3. 再批量处理四个预设编辑器面板和 `StateFunctionEditor`,最后统一共享请求层兜底文案。
|
|
||||||
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
# 游戏 UI / 预设 / 编辑器文本二次审计(扩展重查版)
|
|
||||||
|
|
||||||
日期:`2026-04-02`
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
- 本文档用于替换同名上一版审计。上一版确实漏掉了不少内容,尤其是:
|
|
||||||
- `AdventurePanel` 里的任务概览与奖励文案
|
|
||||||
- `npcInteractions.ts` 里的 `actionText` / `detailText`
|
|
||||||
- 自定义世界编辑器、NPC 视觉编辑器里的英文兜底和混合术语
|
|
||||||
- 预设编辑器里多组仍未本地化的标题、字段名、保存反馈
|
|
||||||
- 本次以当前仓库实际内容为准,不沿用旧结论;已经修掉的内容不再重复计入。
|
|
||||||
|
|
||||||
## 审计范围
|
|
||||||
|
|
||||||
- 扫描目录:
|
|
||||||
- `src/components/`
|
|
||||||
- `src/data/`
|
|
||||||
- `src/hooks/`
|
|
||||||
- `src/services/`
|
|
||||||
- `src/routing/`
|
|
||||||
- `src/editor/`
|
|
||||||
- 关注对象:
|
|
||||||
- 玩家在主流程、冒险面板、弹窗里会看到的文本
|
|
||||||
- 预设编辑器、自定义世界编辑器、NPC 视觉编辑器中会直接显示的文本
|
|
||||||
- 会透传到 UI、弹窗、编辑器预览中的数据层文本源
|
|
||||||
- 标记类型:
|
|
||||||
- `英文直出`:面向玩家 / 编辑器用户的英文文本仍直接显示
|
|
||||||
- `中英混用`:中文 UI 中混入 `NPC`、`HP`、`MP`、`AI`、`URL`、`Shift` 等术语
|
|
||||||
- `异常显示`:明显乱码、截断、异常问号替代、分隔符损坏
|
|
||||||
|
|
||||||
## 结论摘要
|
|
||||||
|
|
||||||
- 游戏主流程里仍有明显英文残留:`QQ Group`、`WeChat`、`GENARRATIVE`、`Current Area`。
|
|
||||||
- 游戏内任务 / 冒险面板仍有一批英文任务眉标和按钮辅助文案:`BOUNTY TARGET`、`CACHE TRACE`、`SPAR SESSION`、`Inspect reward item ...`、`Unknown monster`。
|
|
||||||
- `GameShellOverlays.tsx` 仍有整组 loading fallback 出现异常编码。
|
|
||||||
- 预设编辑器目前仍是问题最密集区域之一,`CharacterPresetPanel`、`MonsterPresetPanel`、`SceneNpcPresetPanel`、`ScenePresetPanel`、`StateFunctionEditor` 里都有明显英文直出或半成品占位。
|
|
||||||
- 数据层里仍有大量会透出到 UI / 编辑器的英文值,重点在:
|
|
||||||
- `npcInteractions.ts`
|
|
||||||
- `useStoryGeneration.ts`
|
|
||||||
- `storyGenerationState.ts`
|
|
||||||
- `npcEncounterActions.ts`
|
|
||||||
- `sceneObservation.ts`
|
|
||||||
- `characterPresets.ts`
|
|
||||||
- `stateFunctions.ts`
|
|
||||||
|
|
||||||
## 一、游戏主流程 UI
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/game-shell/PreGameSelectionFlow.tsx` | `48-49` | 英文直出 | `QQ Group`、`WeChat` | 开场页联系方式标签仍是英文 |
|
|
||||||
| `src/components/game-shell/PreGameSelectionFlow.tsx` | `351` | 中英混用 | `contact.label === 'QQ Group' ? 'QQ群' : '微信'` | 逻辑分支仍依赖英文标签 |
|
|
||||||
| `src/components/game-shell/GameShellOverlays.tsx` | `123` | 异常显示 | `姝e湪鍔犺浇鍐掗櫓璇︽儏...` | 冒险详情 loading fallback 乱码 |
|
|
||||||
| `src/components/game-shell/GameShellOverlays.tsx` | `162` | 异常显示 | `姝e湪鍔犺浇闃熶紞闈㈡澘` | 队伍面板 loading fallback 乱码 |
|
|
||||||
| `src/components/game-shell/GameShellOverlays.tsx` | `187` | 异常显示 | `姝e湪鍔犺浇鑳屽寘闈㈡澘` | 背包面板 loading fallback 乱码 |
|
|
||||||
| `src/components/game-shell/GameShellOverlays.tsx` | `214` | 异常显示 | `姝e湪鍔犺浇闃熶紞钀ュ湴...` | 营地弹窗 loading fallback 乱码 |
|
|
||||||
| `src/components/game-shell/GameShellOverlays.tsx` | `229` | 异常显示 | `姝e湪鍔犺浇鍦板浘...` | 地图 loading fallback 乱码 |
|
|
||||||
| `src/components/game-shell/GameShellOverlays.tsx` | `248` | 异常显示 | `姝e湪鍔犺浇瑙掕壊鑱婂ぉ...` | 角色聊天 loading fallback 乱码 |
|
|
||||||
| `src/components/game-shell/GameShellOverlays.tsx` | `261` | 异常显示 | `姝e湪鍔犺浇 NPC 浜や簰...` | NPC 交互 loading fallback 同时混入 `NPC` |
|
|
||||||
| `src/components/game-shell/GameShellRuntime.tsx` | `146` | 英文直出 | `Current Area` | 当前场景名缺失时的兜底文案仍是英文 |
|
|
||||||
| `src/components/game-shell/GameShellRuntime.tsx` | `200` | 英文直出 | `GENARRATIVE` | 顶部 logo 文案仍是英文 |
|
|
||||||
| `src/components/GameShell.tsx` | `311` | 英文直出 | `Current Area` | 旧壳组件里同样保留英文兜底 |
|
|
||||||
| `src/components/GameShell.tsx` | `365` | 英文直出 | `GENARRATIVE` | 旧壳组件里同样保留英文 logo |
|
|
||||||
| `src/components/DeveloperTeamModal.tsx` | `44` | 英文直出 | `aria-label="Close developer team modal"` | 开发团队弹窗关闭按钮辅助文案未本地化 |
|
|
||||||
|
|
||||||
## 二、游戏内面板 / 弹窗
|
|
||||||
|
|
||||||
### 2.1 冒险与任务面板
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `197-198` | 异常显示 | `适合进攻型构`、`适合防御型构` | 描述文本疑似被截断,正常语义应是“构筑” |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `206` | 异常显示 | `item.name + ' 奖励物品<E789A9>?';` | 奖励物品描述兜底尾部异常 |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `216` | 异常显示 | ``${hours}小时 ...<2E>?...秒`` | 时长格式字符串出现异常字符 |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `219` | 异常显示 | ``${minutes}<7D>?...秒`` | 分钟格式字符串同样异常 |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `248` | 英文直出 | ``aria-label={`Inspect reward item ${item.name}`}`` | 奖励物品按钮辅助文案是英文 |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `279` | 英文直出 | `BOUNTY TARGET` | 任务眉标未本地化 |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `283` | 英文直出 | `Unknown monster` | 目标怪物兜底文案是英文 |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `290` | 英文直出 | `CACHE TRACE` | 宝藏任务眉标未本地化 |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `295` | 英文直出 | `Inspect the hidden reward site` | 宝藏任务副文案未本地化 |
|
|
||||||
| `src/components/AdventurePanel.tsx` | `302` | 英文直出 | `SPAR SESSION` | 切磋任务眉标未本地化 |
|
|
||||||
|
|
||||||
### 2.2 NPC / 实体 / 交易相关弹窗
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | `895` | 中英混用 | `label="HP"` | 实体状态估计面板直接显示 `HP` |
|
|
||||||
| `src/components/AdventureEntityModal.tsx` | `901` | 中英混用 | `label="MP"` | 实体状态估计面板直接显示 `MP` |
|
|
||||||
| `src/components/NpcModals.tsx` | `252` | 中英混用 | `NPC 商品列表` / `你的背包列表` | 交易列表标题混入 `NPC` |
|
|
||||||
| `src/components/NpcModals.tsx` | `273` | 中英混用 | `这个 NPC 当前没有可售商品。` | 空状态文案混入 `NPC` |
|
|
||||||
| `src/components/NpcModals.tsx` | `356` | 中英混用 | `NPC 商品` | 详情弹窗标题混入 `NPC` |
|
|
||||||
| `src/components/NpcModals.tsx` | `408` | 中英混用 | `效果预览:HP +... / MP +... / 冷却 -...` | 数值预览里直接保留 `HP`、`MP` |
|
|
||||||
|
|
||||||
### 2.3 自定义世界结果页
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/CustomWorldResultView.tsx` | `58` | 中英混用 | `新增 NPC` | 结果页新增操作标签混入 `NPC` |
|
|
||||||
|
|
||||||
## 三、自定义世界 / NPC 视觉编辑
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `242` | 英文直出 | `URL` | 图片地址输入提示词未本地化 |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `464` | 英文直出 | `MedievalFantasyCharacters` | 形象编辑副标题里直接暴露素材包英文名 |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `482-483` | 中英混用 | `AI生成NPC形象`、`NPC 形象 AI 生成功能仍在开发中。` | 同时混入 `AI`、`NPC` |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `635-636` | 中英混用 | `新增 NPC`、`编辑 NPC:...` | NPC 档案编辑标题混入英文缩写 |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `663` | 中英混用 | `修改形象` | 本行本身无问题,但上下文仍指向 `NPC` 视觉编辑入口 |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `734` | 中英混用 | `AI生成` | 按钮文案混入 `AI` |
|
|
||||||
| `src/components/CustomWorldEntityEditorModal.tsx` | `762-763` | 中英混用 | `AI生成场景`、`场景图片 AI 生成功能仍在开发中。` | 场景生成弹窗混入 `AI` |
|
|
||||||
| `src/components/CustomWorldNpcVisualEditor.tsx` | `393` | 中英混用 | `AI生成` | 自定义 NPC 视觉编辑器按钮混入 `AI` |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `263-267` | 英文直出 | `Failed to load NPC visual overrides`、`Failed to load NPC layout config` | 编辑器首屏可见的加载失败兜底为英文 |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `284-308` | 英文直出 | `response was invalid, using bundled defaults` 等 | 多组降级提示未本地化 |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `463` | 中英混用 | `请先选择一个 NPC 进行编辑` | 空态文案混入 `NPC` |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `539` | 英文直出 | `Save failed` | 保存失败提示仍是英文 |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `702-708` | 中英混用 | `NPC 视觉编辑器`、`选择并编辑 NPC 的外观...` | 页面标题与简介多次混入 `NPC` |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `718` | 中英混用 | `当前 NPC` | 当前对象字段名混入 `NPC` |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `976-977` | 中英混用 | `拖动标记微调 NPC 的预览布局。移动时按住 Shift...` | 帮助提示同时混入 `NPC` 与 `Shift` |
|
|
||||||
| `src/components/NpcVisualEditor.tsx` | `1033-1052` | 英文直出 | `Unknown headgear`、`No headgear`、`Unknown main hand`、`No main hand`、`Unknown off hand`、`No off hand` | 预览状态说明仍是英文 |
|
|
||||||
|
|
||||||
## 四、预设编辑器
|
|
||||||
|
|
||||||
### 4.1 共享配置与选项枚举
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/shared.ts` | `43` | 中英混用 | `label: 'NPC'` | 预设编辑器顶部 tab 仍直接显示 `NPC` |
|
|
||||||
| `src/components/preset-editor/shared.ts` | `63-66` | 英文直出 | `idle`、`move`、`attack`、`die` | 怪物动画可选项是原始英文值 |
|
|
||||||
| `src/components/preset-editor/shared.ts` | `70-74` | 英文直出 | `steady`、`burst`、`mobility`、`finisher`、`projectile` | 技能风格可选项是原始英文值 |
|
|
||||||
|
|
||||||
### 4.2 角色预设面板
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `91-92` | 英文直出 | `Saved character preset overrides...`、`Failed to save character preset overrides.` | 保存反馈未本地化 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `264-279` | 英文直出 | `Characters`、`Browse the character roster...`、`Field`、`Save Character Overrides` | 左侧选择卡与保存栏均为英文 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `315-322` | 英文直出 | `Character Details`、`Field` | 详情卡标题和字段名未本地化 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `404-430` | 英文直出 | `Skill Preview`、`Preview ranged skills...`、`Preview Monster` | 技能预览区英文直出 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `450-467` | 英文直出 | `Skill Setup`、`Add Skill` | 技能配置区仍是英文 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `498-558` | 英文直出 | 大量 `label="Field"` | 多数字段仍显示占位词 `Field` |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `605` | 英文直出 | `Start Frame` | 动画字段未本地化 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `629`、`729` | 英文直出 | `Section`、`Editor section.` | 两个分区仍是半成品英文占位 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `697-698` | 英文直出 | `Attributes`、`Adjust the core character attributes.` | 属性面板未本地化 |
|
|
||||||
| `src/components/preset-editor/CharacterPresetPanel.tsx` | `755` | 英文直出 | `Unset` | 场景绑定下拉空值项为英文 |
|
|
||||||
|
|
||||||
### 4.3 怪物预设面板
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `49-50` | 英文直出 | `Saved monster overrides...`、`Failed to save monster overrides.` | 保存反馈未本地化 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `124-139` | 英文直出 | `Section`、`Editor section.`、`Field`、`Save Monster Overrides` | 左侧选择区和保存栏未本地化 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `135` | 异常显示 | ``${WORLD_LABELS[monster.worldType]} 闂?${optionMonster.name}`` | 选择列表分隔符损坏 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `158-159` | 英文直出 | `Monster Override Preview`、`Editor section.` | 预览区标题仍是英文 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `192-201` | 英文直出 | `Attack Range`、`Speed`、`HP`、`Max HP` | 预览摘要未本地化 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `212-234` | 英文直出 | `Monster ID`、`Name`、`Intro Action` | 核心字段名仍是英文 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `343` | 英文直出 | `FPS` | 动画字段未本地化 |
|
|
||||||
| `src/components/preset-editor/MonsterPresetPanel.tsx` | `207`、`275`、`307` | 英文直出 | `Section`、`Editor section.` | 多个分区标题仍为占位英文 |
|
|
||||||
|
|
||||||
### 4.4 场景 NPC 预设面板
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `127-128` | 英文直出 | `Saved NPC overrides.`、`Failed to save NPC overrides.` | 保存反馈未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `159` | 英文直出 | `No NPC presets are available.` | 空态文案未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `177-193` | 英文直出 | `NPC Library`、`Browse and select an NPC preset.`、`NPC ID`、`Save NPC Overrides` | 左侧选择区英文直出 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `221-237` | 英文直出 | `Skill Preview`、`Preview ranged skills from the linked character.`、`Skill`、`World` | 技能预览区未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `258` | 异常显示 | `闂?NPC` | 空态或提示文案已损坏 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `264-268` | 英文直出 | `Visual Preview`、`Hostile NPCs use monster presets...` | 视觉预览区标题与说明均未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `310-371` | 英文直出 | `NPC Details`、`Name`、`Role`、`Avatar`、`Linked Character ID`、`Monster Preset ID`、`Initial Affinity`、`Description`、`Visual Editor` | 详情区字段名大面积英文直出 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `374-375` | 英文直出 | `Hostile NPCs cannot use the visual editor...`、`Narrative NPC visual overrides can be previewed here.` | 视觉编辑区说明未本地化 |
|
|
||||||
| `src/components/preset-editor/SceneNpcPresetPanel.tsx` | `382-384` | 异常显示 | 三整段乱码说明 + `NPC` | 视觉编辑区空态说明已严重损坏 |
|
|
||||||
|
|
||||||
### 4.5 场景预设面板
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `52-53` | 英文直出 | `Saved scene overrides.`、`Failed to save scene overrides.` | 保存反馈未本地化 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `62` | 英文直出 | `No scene presets are available.` | 空态文案未本地化 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `105-107` | 英文直出 | `Treasure Ahead`、`Treasure` | 宝藏预览实体名仍是英文 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `132-171` | 英文直出 | `Scene Library`、`Browse and select a scene preset.`、`Save`、`Scene Preview`、`Preview monsters, NPCs, and treasure...` | 场景面板主框架仍多处英文 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `175-181` | 英文直出 | `Preview Mode`、`Monster Preview`、`NPC Preview`、`Treasure Preview` | 预览模式切换未本地化 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `207-226` | 英文直出 | `Hostile NPCs`、`NPCs`、`None` | 预览摘要区未本地化 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `233-290` | 英文直出 | `Scene Details`、`Edit the selected scene preset.`、`Scene ID`、`World`、`Name`、`Description`、`Image Source`、`Forward Scene`、`Connected Scene IDs`、`Monster IDs`、`Treasure Hints` | 详情编辑区字段名几乎全部英文 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `271` | 英文直出 | `Unset` | 下拉空值项为英文 |
|
|
||||||
| `src/components/preset-editor/ScenePresetPanel.tsx` | `299` | 英文直出 | `NPCs In Scene` | 分区标题未本地化 |
|
|
||||||
|
|
||||||
### 4.6 状态函数编辑器
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `310-325` | 英文直出 | `Preview`、`Treasure` | 预览 NPC / 宝藏 context 里保留英文 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1062-1064` | 英文直出 | `Option behavior overrides saved.`、`Failed to save option behavior overrides` | 保存反馈未本地化 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1128` | 英文直出 | `无模板` 之外的模板选择逻辑仍有英文结构 | 该区主体中文,但下游模板 ID / 类型依旧偏英文 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1185` | 英文直出 | `AnimationState` 原始值直接作为选项标签 | 动画值会直接显示英文枚举 |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1190` | 英文直出 | `placeholder="可使用 {monster} 占位符"` | 占位符本身暴露英文 key |
|
|
||||||
| `src/components/StateFunctionEditor.tsx` | `1191` | 英文直出 | `idle`、`move`、`attack` | 怪物动画下拉仍使用英文值 |
|
|
||||||
|
|
||||||
## 五、数据层 / 运行时文本源
|
|
||||||
|
|
||||||
### 5.1 直接驱动交互按钮、详情文案、结果文本的源头
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/data/npcInteractions.ts` | `444-580` | 重点补录 | 大量 `actionText` / `detailText` | 这是本轮补录重点,属于 NPC 交互选项直接展示源,需要逐条审校 |
|
|
||||||
| `src/data/npcInteractions.ts` | `327-349` | 英文直出 | `deep`、`honest`、`partial`、`guarded`、`warm`、`cooperative`、`neutral`、`distant`、`candid`、`true_but_incomplete`、`half_truth`、`situational_only` | 这些关系 / 说话风格值虽然是逻辑枚举,但后续很容易在编辑器、调试面板、覆盖配置中直接暴露 |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | `576-578` | 英文直出 | `Travel to ...`、`Leave camp and head toward ...` | 旅行选项和详情文案未本地化 |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | `654-659` | 英文直出 | `Speak with ...`、`Focus on the person in front of you first...` | 开场对话选项未本地化 |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | `827`、`1297` | 英文直出 | `Exchange an opening judgment with ... at camp` | 营地开场交互 actionText 未本地化 |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | `1097` | 英文直出 | `Begin the adventure` | 初始 actionText 未本地化 |
|
|
||||||
| `src/hooks/useStoryGeneration.ts` | `1374`、`1794` | 英文直出 | `Unknown AI error` | AI 兜底错误提示仍是英文 |
|
|
||||||
| `src/hooks/story/storyGenerationState.ts` | `137-141` | 英文直出 | `You leave ...`、`Travel to ...` | 旅行结果与 actionText 未本地化 |
|
|
||||||
| `src/hooks/story/npcEncounterActions.ts` | `277` | 英文直出 | `Victory reward: ${lootText}.` | 战斗胜利奖励结算文本未本地化 |
|
|
||||||
| `src/hooks/story/npcEncounterActions.ts` | `389` | 英文直出 | `NPC dialogue AI is unavailable.` | NPC 对话 AI 失败兜底是英文 |
|
|
||||||
| `src/hooks/story/characterChat.ts` | `68` | 英文直出 | `Player` | 聊天摘要拼接里保留英文说话人名 |
|
|
||||||
| `src/hooks/story/characterChat.ts` | `84` | 英文直出 | `Tell me more clearly what you mean.` | 建议起句未本地化 |
|
|
||||||
| `src/hooks/story/characterChat.ts` | `283` | 英文直出 | `Unknown AI error` | 私聊错误兜底未本地化 |
|
|
||||||
| `src/data/sceneObservation.ts` | `9-34` | 英文直出 | `You pause to listen...`、`Possible NPCs...`、`Possible hostile NPCs...`、`Possible treasure clues...`、`Boss clue...` | 观察环境的整组文本源仍是英文 |
|
|
||||||
|
|
||||||
### 5.2 会透出到编辑器 / 预览 / 覆盖系统的预设值
|
|
||||||
|
|
||||||
| 文件 | 行号 | 类型 | 当前文本 / 字段 | 说明 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `src/data/scenePresets.ts` | `141` | 中英混用 | `role: '敌对NPC'` | 场景 NPC 角色名混入 `NPC` |
|
|
||||||
| `src/data/scenePresets.ts` | `194`、`294` | 英文直出 | `['trade', 'fight', 'spar', 'help', 'chat', 'recruit', 'gift']` | 场景 NPC 功能数组是英文原始值,编辑器容易直出 |
|
|
||||||
| `src/data/stateFunctions.ts` | `33` | 英文直出 | `monsterAnimation?: 'idle' | 'move' | 'attack'` | 动画值原始英文会进入状态函数编辑器 |
|
|
||||||
| `src/data/stateFunctions.ts` | `130`、`152`、`174`、`196`、`221`、`244` | 英文直出 | `steady`、`burst`、`mobility`、`finisher`、`projectile` | 技能权重 key 为英文原始值 |
|
|
||||||
| `src/data/stateFunctions.ts` | `372`、`445`、`569`、`575-576` | 中英混用 | 描述里多次直接写 `NPC` | 虽然主体中文,但会透出到说明文字 |
|
|
||||||
| `src/data/characterPresets.ts` | `54-70` | 英文直出 | `blunt`、`wary`、`evasive`、`measured`、`gentle`、`teasing`、`dry`、`steady`、`direct`、`fragmented`、`deflecting` | 对话风格推断返回值全为英文 |
|
|
||||||
| `src/data/characterPresets.ts` | `362-382`、`519-539`、`738-758`、`833-853`、`1018-1041` | 英文直出 | `assetFolder`、`folder`、`prefix` 中大量英文,如 `Sword Princess`、`idle`、`Double Jump`、`Wall Slide`、`Attack` | 这些值会被预设编辑器动画区直接展示或编辑 |
|
|
||||||
| `src/data/characterPresets.ts` | `387-389`、`544-546`、`763-765`、`858-860`、`1046-1048` | 英文直出 | `guardStyle`、`warmStyle`、`truthStyle` 使用英文值 | 会透出到预设编辑器 / 调试视图 |
|
|
||||||
| `src/data/characterPresets.ts` | `410-501`、`575-720`、`794-1000`、`1069-1197` | 英文直出 | `style`、`delivery`、`phase`、`anchor`、`motion` 使用 `steady`、`mobility`、`ranged`、`travel`、`target`、`projectile` 等英文值 | 角色技能配置层大面积保留英文元数据 |
|
|
||||||
| `src/data/monsterPresets.ts` | `384-817` | 英文直出 | 多处 `common`、`uncommon`、`rare`、`epic` | 怪物稀有度原始值为英文,若编辑器未做映射会直接暴露 |
|
|
||||||
| `src/editor/shared/jsonClient.ts` | `29-43` | 英文直出 | `Request failed`、`Save failed` | 编辑器通用 JSON 客户端默认错误文案未本地化 |
|
|
||||||
|
|
||||||
## 六、本轮已复查、暂未记录明确问题的文件
|
|
||||||
|
|
||||||
以下文件本轮重新检查过,但当前内容里没有继续记录“英文直出 / 异常显示”的明确条目,暂可视为本轮通过:
|
|
||||||
|
|
||||||
- `src/routing/appRoutes.tsx`
|
|
||||||
- `src/components/game-shell/CharacterSelectionFlow.tsx`
|
|
||||||
- `src/components/game-shell/GameShellStoryPanels.tsx`
|
|
||||||
- `src/components/CharacterPanel.tsx`
|
|
||||||
- `src/components/InventoryPanel.tsx`
|
|
||||||
- `src/components/MapModal.tsx`
|
|
||||||
- `src/components/CharacterChatModal.tsx`
|
|
||||||
- `src/components/SelectionCustomizationModals.tsx`
|
|
||||||
- `src/components/adventure-panel/AdventurePanelOverlays.tsx`
|
|
||||||
- `src/data/sceneEncounterPreviews.ts`
|
|
||||||
- `src/data/customWorldVisuals.ts`
|
|
||||||
- `src/services/customWorldBuilder.ts`
|
|
||||||
|
|
||||||
## 建议修复顺序
|
|
||||||
|
|
||||||
1. 先修主流程直接暴露给玩家的文本:
|
|
||||||
- `PreGameSelectionFlow.tsx`
|
|
||||||
- `GameShellOverlays.tsx`
|
|
||||||
- `GameShellRuntime.tsx`
|
|
||||||
- `GameShell.tsx`
|
|
||||||
- `AdventurePanel.tsx`
|
|
||||||
2. 再修编辑器里最容易误导内容生产的面板:
|
|
||||||
- `CharacterPresetPanel.tsx`
|
|
||||||
- `MonsterPresetPanel.tsx`
|
|
||||||
- `SceneNpcPresetPanel.tsx`
|
|
||||||
- `ScenePresetPanel.tsx`
|
|
||||||
- `StateFunctionEditor.tsx`
|
|
||||||
3. 最后统一清理数据层文本源和枚举映射:
|
|
||||||
- `npcInteractions.ts`
|
|
||||||
- `useStoryGeneration.ts`
|
|
||||||
- `storyGenerationState.ts`
|
|
||||||
- `npcEncounterActions.ts`
|
|
||||||
- `sceneObservation.ts`
|
|
||||||
- `characterPresets.ts`
|
|
||||||
- `stateFunctions.ts`
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# 文本与乱码审计总览
|
|
||||||
|
|
||||||
这一组只保留当前仍需要执行的文本与乱码审计入口。早期逐日扫描已经聚合到当前结论,不再保留旧稿链路。
|
|
||||||
|
|
||||||
## 当前推荐入口
|
|
||||||
|
|
||||||
1. [CHINESE_MOJIBAKE_INVENTORY.md](./CHINESE_MOJIBAKE_INVENTORY.md)
|
|
||||||
偏全局库存清单,适合先确认问题分布范围。
|
|
||||||
2. [GAME_UI_PRESET_EDITOR_NPC_PROMPT_TEXT_AUDIT_2026-04-02_DEEP_SCAN.md](./GAME_UI_PRESET_EDITOR_NPC_PROMPT_TEXT_AUDIT_2026-04-02_DEEP_SCAN.md)
|
|
||||||
这是当前最完整的深度审计版本,已经扩到 `prompt`、`npcInteractions`、运行时弹窗与编辑器深层文本。
|
|
||||||
3. [GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-04-02.md](./GAME_UI_PRESET_EDITOR_TEXT_AUDIT_2026-04-02.md)
|
|
||||||
适合看“扩展重查版”的 UI / 预设 / 编辑器问题面。
|
|
||||||
|
|
||||||
## 融合结论
|
|
||||||
|
|
||||||
- 早期几轮扫描已经完成使命,核心结论是:不要把乱码当成普通文案改写,先确认真实编码;文本修复要优先处理会进入玩家体验和 AI 生成链路的内容。
|
|
||||||
- `2026-04-02` 两份文档开始把重点收敛到真正会影响玩家体验和 AI 生成质量的链路。
|
|
||||||
- 现在做文本修复时,不必从最早一份开始逐篇读;优先看 `CHINESE_MOJIBAKE_INVENTORY` 和 `2026-04-02` 两份即可。
|
|
||||||
@@ -1,650 +0,0 @@
|
|||||||
# AI 原生运行时物品生成系统重设计
|
|
||||||
|
|
||||||
更新时间:`2026-04-02`
|
|
||||||
|
|
||||||
## 0. 这次重设计要解决什么
|
|
||||||
|
|
||||||
基于当前仓库已经存在的系统,这次不再把“AI 原生物品生成”设计成一套独立玩法,而是把它重做成:
|
|
||||||
|
|
||||||
**挂在现有背包、build、宝藏、NPC、任务、自定义世界之上的统一运行时物品导演系统。**
|
|
||||||
|
|
||||||
它要解决的核心问题有 4 个:
|
|
||||||
|
|
||||||
1. 当前物品入口很多,但缺少统一导演层。
|
|
||||||
2. 当前奖励能发物品,但和场景背景、相关 NPC、最近事件的贴合度不够高。
|
|
||||||
3. 当前 build 标签体系已经存在,但运行时奖励还没有围绕“永久标签 / 限时标签 / 小数值补充”形成稳定规则。
|
|
||||||
4. AI 目前只负责叙事包装,没有真正进入运行时物品生成链路。
|
|
||||||
|
|
||||||
这次重设计的目标不是“让 AI 直接生成 `InventoryItem`”,而是:
|
|
||||||
|
|
||||||
- 让 AI 负责物品的**叙事意图、关系语义、世界贴合**
|
|
||||||
- 让本地规则负责物品的**标签、数值、稀有度、存档、平衡**
|
|
||||||
|
|
||||||
## 1. 设计结论先说
|
|
||||||
|
|
||||||
新的 AI 原生物品系统建议采用:
|
|
||||||
|
|
||||||
**上下文采样器 -> AI 物品意图层 -> 本地编译器 -> 渠道分发器 -> 叙事回写器**
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
- `InventoryItem` 继续作为唯一成品结构
|
|
||||||
- `ItemBuildProfile` 继续承载永久 build 标签
|
|
||||||
- `ItemUseProfile.buildBuffs` 继续承载限时 build 标签
|
|
||||||
- `ItemStatProfile` 继续承载小数值加成
|
|
||||||
- `buildTags.ts / buildDamage.ts` 继续承接战斗结算
|
|
||||||
- `treasureInteractions.ts / npcInteractions.ts / forgeSystem.ts / customWorldRuntime.ts` 改为调用统一导演层
|
|
||||||
|
|
||||||
一句话:
|
|
||||||
|
|
||||||
**不要再按“宝藏怎么发、NPC 怎么卖、任务怎么奖”各写一套;而是让所有入口共用同一个运行时物品生成管线。**
|
|
||||||
|
|
||||||
## 2. 这套系统必须遵守的边界
|
|
||||||
|
|
||||||
## 2.1 AI 负责语义,不负责数值
|
|
||||||
|
|
||||||
AI 可以决定:
|
|
||||||
|
|
||||||
- 这件物品像什么
|
|
||||||
- 它为什么在这里出现
|
|
||||||
- 它和哪个 NPC / 势力 / 地标 / 怪物有关
|
|
||||||
- 它更偏向什么 build 风格
|
|
||||||
- 它应该是永久物还是限时物
|
|
||||||
|
|
||||||
AI 不应该直接决定:
|
|
||||||
|
|
||||||
- 最终 `outgoingDamageBonus` 是多少
|
|
||||||
- 能带几个标签
|
|
||||||
- 允许不允许进入装备槽
|
|
||||||
- 价值多少
|
|
||||||
- 能不能掉落
|
|
||||||
- 能不能持久化
|
|
||||||
|
|
||||||
## 2.2 所有成品都必须编译回当前系统
|
|
||||||
|
|
||||||
新的系统不是新物品结构,而是新生成过程。
|
|
||||||
|
|
||||||
最终成品仍然必须落到:
|
|
||||||
|
|
||||||
- `InventoryItem`
|
|
||||||
- `ItemStatProfile`
|
|
||||||
- `ItemUseProfile`
|
|
||||||
- `ItemBuildProfile`
|
|
||||||
|
|
||||||
否则现有:
|
|
||||||
|
|
||||||
- 背包
|
|
||||||
- 装备
|
|
||||||
- build 计算
|
|
||||||
- 锻造
|
|
||||||
- 存档
|
|
||||||
- 交易
|
|
||||||
|
|
||||||
都接不上。
|
|
||||||
|
|
||||||
## 2.3 运行时物品的主收益必须是“构筑意义”
|
|
||||||
|
|
||||||
重新设计后,运行时物品的收益优先级建议固定为:
|
|
||||||
|
|
||||||
1. `build 标签`
|
|
||||||
2. `功能性节奏收益`
|
|
||||||
3. `小数值补充`
|
|
||||||
|
|
||||||
不建议反过来做成:
|
|
||||||
|
|
||||||
1. 大数值
|
|
||||||
2. build 标签只是点缀
|
|
||||||
|
|
||||||
因为当前项目最强的系统基础已经是 build 标签和叙事关系,而不是传统数值刷装。
|
|
||||||
|
|
||||||
## 3. 新系统的整体架构
|
|
||||||
|
|
||||||
## 3.1 模块拆分
|
|
||||||
|
|
||||||
建议新增 5 个模块:
|
|
||||||
|
|
||||||
- `src/types/runtimeItem.ts`
|
|
||||||
- `src/data/runtimeItemContext.ts`
|
|
||||||
- `src/data/runtimeItemDirector.ts`
|
|
||||||
- `src/data/runtimeItemCompiler.ts`
|
|
||||||
- `src/data/runtimeItemNarrative.ts`
|
|
||||||
|
|
||||||
职责如下。
|
|
||||||
|
|
||||||
### A. `runtimeItemContext.ts`
|
|
||||||
|
|
||||||
负责统一采样生成上下文,不直接生成物品。
|
|
||||||
|
|
||||||
输入来自:
|
|
||||||
|
|
||||||
- `GameState`
|
|
||||||
- `currentEncounter`
|
|
||||||
- `currentScenePreset`
|
|
||||||
- `npcStates`
|
|
||||||
- `storyHistory`
|
|
||||||
- `playerEquipment`
|
|
||||||
- `activeBuildBuffs`
|
|
||||||
|
|
||||||
输出统一上下文对象:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type RuntimeItemGenerationContext = {
|
|
||||||
worldType: WorldType | null;
|
|
||||||
customWorldProfile: CustomWorldProfile | null;
|
|
||||||
sceneId: string | null;
|
|
||||||
sceneName: string | null;
|
|
||||||
sceneDescription: string | null;
|
|
||||||
sceneTags: string[];
|
|
||||||
treasureHints: string[];
|
|
||||||
encounter: Encounter | null;
|
|
||||||
encounterNpcId: string | null;
|
|
||||||
encounterNpcName: string | null;
|
|
||||||
encounterContextText: string | null;
|
|
||||||
relatedNpcState: NpcPersistentState | null;
|
|
||||||
recentStorySummary: string;
|
|
||||||
recentActions: string[];
|
|
||||||
playerCharacterId: string;
|
|
||||||
playerBuildTags: string[];
|
|
||||||
playerBuildGaps: string[];
|
|
||||||
playerEquipmentTags: string[];
|
|
||||||
generationChannel: RuntimeItemGenerationChannel;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### B. `runtimeItemDirector.ts`
|
|
||||||
|
|
||||||
负责根据上下文决定:
|
|
||||||
|
|
||||||
- 这次该不该生成上下文化物品
|
|
||||||
- 生成几件
|
|
||||||
- 主奖励 / 副奖励是什么
|
|
||||||
- 是装备、消耗品、材料还是稀有物
|
|
||||||
- 偏永久 build、限时 build,还是纯功能补给
|
|
||||||
|
|
||||||
它的输出不是成品,而是“导演结果”:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type RuntimeItemPlan = {
|
|
||||||
slot: "primary" | "secondary" | "support";
|
|
||||||
itemKind: "equipment" | "consumable" | "material" | "relic" | "quest";
|
|
||||||
permanence: "permanent" | "timed" | "resource";
|
|
||||||
narrativeWeight: "light" | "medium" | "heavy";
|
|
||||||
targetBuildDirection: string[];
|
|
||||||
relationAnchor: RuntimeRelationAnchor;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### C. `runtimeItemCompiler.ts`
|
|
||||||
|
|
||||||
负责把导演计划 + AI 意图编译成正式 `InventoryItem`。
|
|
||||||
|
|
||||||
编译职责包括:
|
|
||||||
|
|
||||||
- build 标签规范化
|
|
||||||
- 稀有度预算分配
|
|
||||||
- 永久标签上限
|
|
||||||
- 限时标签时长
|
|
||||||
- 数值预算
|
|
||||||
- 装备槽判定
|
|
||||||
- 价值计算
|
|
||||||
- metadata 生成
|
|
||||||
|
|
||||||
### D. `runtimeItemNarrative.ts`
|
|
||||||
|
|
||||||
负责把已经生成好的物品回写成叙事文本:
|
|
||||||
|
|
||||||
- 物品命名
|
|
||||||
- 物品描述
|
|
||||||
- 物品来源说明
|
|
||||||
- 宝藏/NPC/任务文案嵌入
|
|
||||||
|
|
||||||
### E. `runtimeItem.ts`
|
|
||||||
|
|
||||||
负责声明:
|
|
||||||
|
|
||||||
- 渠道类型
|
|
||||||
- 关系锚点类型
|
|
||||||
- 运行时物品 metadata
|
|
||||||
- AI 意图结构
|
|
||||||
- 编译预算结构
|
|
||||||
|
|
||||||
## 3.2 AI 在新架构里的输入输出
|
|
||||||
|
|
||||||
建议 AI 只接触两种结构:
|
|
||||||
|
|
||||||
### 输入:压缩过的生成上下文
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type RuntimeItemAiPromptInput = {
|
|
||||||
worldSummary: string;
|
|
||||||
sceneSummary: string;
|
|
||||||
encounterSummary: string;
|
|
||||||
relatedNpcSummary: string;
|
|
||||||
recentStorySummary: string;
|
|
||||||
generationChannel: RuntimeItemGenerationChannel;
|
|
||||||
playerBuildDirection: string[];
|
|
||||||
playerBuildGaps: string[];
|
|
||||||
desiredItemKind: string;
|
|
||||||
permanence: string;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 输出:轻量意图
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type RuntimeItemAiIntent = {
|
|
||||||
shortNameSeed: string;
|
|
||||||
sourcePhrase: string;
|
|
||||||
reasonToAppear: string;
|
|
||||||
relationHooks: string[];
|
|
||||||
desiredBuildTags: string[];
|
|
||||||
desiredFunctionalBias: Array<"heal" | "mana" | "cooldown" | "guard" | "damage">;
|
|
||||||
tone: "grim" | "mysterious" | "martial" | "ritual" | "survival";
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
AI 输出长度必须短,目的明确,不允许直接产出成品 JSON 大对象。
|
|
||||||
|
|
||||||
## 4. 新系统里的物品收益模型
|
|
||||||
|
|
||||||
## 4.1 三种收益层
|
|
||||||
|
|
||||||
新系统建议把运行时物品分成三类收益层。
|
|
||||||
|
|
||||||
### 第一层:永久 build 标签
|
|
||||||
|
|
||||||
适用于:
|
|
||||||
|
|
||||||
- 武器
|
|
||||||
- 护甲
|
|
||||||
- 饰品
|
|
||||||
- 稀有遗物
|
|
||||||
|
|
||||||
载体:
|
|
||||||
|
|
||||||
- `buildProfile.role`
|
|
||||||
- `buildProfile.tags`
|
|
||||||
- `buildProfile.setId / setName`
|
|
||||||
|
|
||||||
建议限制:
|
|
||||||
|
|
||||||
- 普通运行时装备:`1` 个主标签
|
|
||||||
- 稀有以上:`1` 主标签 + `1` 协同标签
|
|
||||||
- 传说或剧情物:允许带 `2` 标签 + 关系锚点
|
|
||||||
|
|
||||||
### 第二层:限时 build 标签
|
|
||||||
|
|
||||||
适用于:
|
|
||||||
|
|
||||||
- 药剂
|
|
||||||
- 符箓
|
|
||||||
- 战场工具
|
|
||||||
- 一次性应急物
|
|
||||||
|
|
||||||
载体:
|
|
||||||
|
|
||||||
- `useProfile.buildBuffs`
|
|
||||||
|
|
||||||
建议限制:
|
|
||||||
|
|
||||||
- `1~2` 个标签
|
|
||||||
- `1~3` 回合
|
|
||||||
- 默认刷新持续时间,不建议无限叠层
|
|
||||||
|
|
||||||
### 第三层:少量直接数值
|
|
||||||
|
|
||||||
适用于:
|
|
||||||
|
|
||||||
- 补足 build 短板
|
|
||||||
- 强化渠道差异
|
|
||||||
- 给物品明确手感
|
|
||||||
|
|
||||||
载体:
|
|
||||||
|
|
||||||
- `statProfile`
|
|
||||||
- `useProfile`
|
|
||||||
|
|
||||||
建议数值定位:
|
|
||||||
|
|
||||||
- 永远是“辅助收益”
|
|
||||||
- 不和 build 标签争主导权
|
|
||||||
|
|
||||||
## 4.2 玩家 build 缺口驱动
|
|
||||||
|
|
||||||
建议新系统在生成物品时先判断当前玩家缺口,而不是先随机抽品类。
|
|
||||||
|
|
||||||
建议至少识别这些缺口:
|
|
||||||
|
|
||||||
- `survival_gap`
|
|
||||||
- 当前缺 `守御 / 护体 / 回复 / 续战`
|
|
||||||
- `mana_gap`
|
|
||||||
- 当前缺 `法力 / 冷却 / 节奏恢复`
|
|
||||||
- `finisher_gap`
|
|
||||||
- 当前缺 `爆发 / 重击 / 追击`
|
|
||||||
- `mobility_gap`
|
|
||||||
- 当前缺 `突进 / 快袭 / 风行 / 游击`
|
|
||||||
- `control_gap`
|
|
||||||
- 当前缺 `控场 / 符阵 / 镇邪`
|
|
||||||
|
|
||||||
运行时物品应优先:
|
|
||||||
|
|
||||||
- 补当前明显缺口
|
|
||||||
- 或强化当前已经成型的主方向
|
|
||||||
|
|
||||||
## 5. 如何让物品和背景 / NPC / 场景高度贴合
|
|
||||||
|
|
||||||
## 5.1 必须引入“关系锚点”
|
|
||||||
|
|
||||||
建议每个 AI 原生运行时物品都必须绑定至少一个 `relationAnchor`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type RuntimeRelationAnchor =
|
|
||||||
| { type: "npc"; npcId?: string; npcName: string; roleText?: string }
|
|
||||||
| { type: "scene"; sceneId?: string; sceneName: string }
|
|
||||||
| { type: "landmark"; landmarkName: string }
|
|
||||||
| { type: "monster"; monsterId?: string; monsterName: string }
|
|
||||||
| { type: "faction"; factionName: string }
|
|
||||||
| { type: "quest"; questId?: string; questName: string };
|
|
||||||
```
|
|
||||||
|
|
||||||
如果没有锚点,物品最多只能作为普通补给,不进入“AI 原生重点物品”。
|
|
||||||
|
|
||||||
## 5.2 不同渠道对应不同贴合逻辑
|
|
||||||
|
|
||||||
### 宝藏
|
|
||||||
|
|
||||||
物品必须同时贴合:
|
|
||||||
|
|
||||||
- 当前场景
|
|
||||||
- `treasureHints`
|
|
||||||
- 最近故事动作
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- 在矿道拿到的不是泛用剑,而是“矿脉巡火短铳”
|
|
||||||
- 在旧祭坛拿到的不是泛用药,而是“断誓回神香”
|
|
||||||
|
|
||||||
### NPC 交易
|
|
||||||
|
|
||||||
物品必须贴合:
|
|
||||||
|
|
||||||
- NPC 身份
|
|
||||||
- NPC 库存风格
|
|
||||||
- NPC 和玩家关系
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- 黑市牙人卖的是“快袭/情报/潜行”方向的东西
|
|
||||||
- 医修给的是“回复/续战/净化”方向的东西
|
|
||||||
|
|
||||||
### 怪物掉落
|
|
||||||
|
|
||||||
物品必须贴合:
|
|
||||||
|
|
||||||
- 怪物战斗风格
|
|
||||||
- 怪物生态来源
|
|
||||||
- 所在场景
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- 重甲守卫掉 `守御精粹`
|
|
||||||
- 雾林伏击者掉 `风行羽囊`
|
|
||||||
|
|
||||||
### 任务奖励
|
|
||||||
|
|
||||||
物品必须贴合:
|
|
||||||
|
|
||||||
- 发布人
|
|
||||||
- 任务目标
|
|
||||||
- 完成方式
|
|
||||||
- 当前线索推进
|
|
||||||
|
|
||||||
它最适合发“永久关系物”。
|
|
||||||
|
|
||||||
## 5.3 命名改成“锚点命名法”
|
|
||||||
|
|
||||||
建议运行时重点物品命名遵循:
|
|
||||||
|
|
||||||
```text
|
|
||||||
来源词 + 关系词 + 功能词
|
|
||||||
```
|
|
||||||
|
|
||||||
而不是:
|
|
||||||
|
|
||||||
```text
|
|
||||||
稀有前缀 + 通用品类名
|
|
||||||
```
|
|
||||||
|
|
||||||
例子:
|
|
||||||
|
|
||||||
- `锁风渡缉索短刃`
|
|
||||||
- `裂界巡守压纹符`
|
|
||||||
- `药谷回岚灵露`
|
|
||||||
- `断碑旧誓护心佩`
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
- 来源词来自场景/地标/势力
|
|
||||||
- 关系词来自 NPC / 怪物 / 事件
|
|
||||||
- 功能词来自 build 或物品品类
|
|
||||||
|
|
||||||
## 6. 新系统里的预算规则
|
|
||||||
|
|
||||||
## 6.1 稀有度预算
|
|
||||||
|
|
||||||
建议按稀有度控制:
|
|
||||||
|
|
||||||
- 能有几个 build 标签
|
|
||||||
- 能不能带关系锚点
|
|
||||||
- 能不能有 set 倾向
|
|
||||||
- 数值范围上限
|
|
||||||
|
|
||||||
建议预算如下:
|
|
||||||
|
|
||||||
| 稀有度 | build 标签 | 限时 buff | 数值强度 | 叙事强度 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| common | 0~1 | 1 | 很小 | 轻 |
|
|
||||||
| uncommon | 1 | 1~2 | 小 | 轻 |
|
|
||||||
| rare | 1~2 | 2 | 小到中 | 中 |
|
|
||||||
| epic | 2 | 2 | 中 | 中到高 |
|
|
||||||
| legendary | 2 + 关系锚点 | 2~3 | 中 | 高 |
|
|
||||||
|
|
||||||
## 6.2 渠道预算
|
|
||||||
|
|
||||||
不同渠道不该发同样强度的物品。
|
|
||||||
|
|
||||||
建议:
|
|
||||||
|
|
||||||
- `treasure`
|
|
||||||
- 偏单件强语义物
|
|
||||||
- `npc_trade`
|
|
||||||
- 偏稳定、可预期、可补短板
|
|
||||||
- `npc_reward`
|
|
||||||
- 偏关系定制与限时支援物
|
|
||||||
- `monster_drop`
|
|
||||||
- 偏材料 / 精粹 / 生态锚点物
|
|
||||||
- `quest_reward`
|
|
||||||
- 偏永久 build 锚点物
|
|
||||||
- `discovery`
|
|
||||||
- 偏线索物 / 过渡工具物
|
|
||||||
|
|
||||||
## 6.3 build 方向切换限制
|
|
||||||
|
|
||||||
新系统建议引入一条硬规则:
|
|
||||||
|
|
||||||
**普通运行时物品默认只能强化当前 build 或其邻近 build,不应该高频强制转流派。**
|
|
||||||
|
|
||||||
也就是:
|
|
||||||
|
|
||||||
- 当前偏 `快剑/突进`,更容易给 `追击/风行`
|
|
||||||
- 当前偏 `守御/护体`,更容易给 `续战/回复`
|
|
||||||
- 当前偏 `法修/雷法`,更容易给 `过载/冷却`
|
|
||||||
|
|
||||||
真正能强制开新流派的物品,应只出现在:
|
|
||||||
|
|
||||||
- 高价值任务奖励
|
|
||||||
- 关键宝藏
|
|
||||||
- 重要 NPC 关系突破
|
|
||||||
|
|
||||||
## 7. 建议新增的数据结构
|
|
||||||
|
|
||||||
## 7.1 运行时 metadata
|
|
||||||
|
|
||||||
建议在 `InventoryItem` 上新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface RuntimeItemMetadata {
|
|
||||||
origin: "catalog" | "procedural" | "ai_compiled";
|
|
||||||
generationChannel: RuntimeItemGenerationChannel;
|
|
||||||
seedKey: string;
|
|
||||||
relationAnchor?: RuntimeRelationAnchor;
|
|
||||||
sourceReason: string;
|
|
||||||
recentEventHook?: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
然后在 `InventoryItem` 上挂:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface InventoryItem {
|
|
||||||
runtimeMetadata?: RuntimeItemMetadata;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
这样后面:
|
|
||||||
|
|
||||||
- UI 可展示“来源”
|
|
||||||
- 日志可回放“为什么拿到”
|
|
||||||
- 剧情可引用“这是谁给你的”
|
|
||||||
|
|
||||||
## 7.2 运行时导演结果
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type DirectedRuntimeReward = {
|
|
||||||
primaryItem?: InventoryItem | null;
|
|
||||||
supportItems: InventoryItem[];
|
|
||||||
hp?: number;
|
|
||||||
mana?: number;
|
|
||||||
currency?: number;
|
|
||||||
storyHint?: string;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
这样可以统一替代宝藏、帮助奖励、任务奖励等零散结构。
|
|
||||||
|
|
||||||
## 8. 与现有模块的接入方式
|
|
||||||
|
|
||||||
## 8.1 `treasureInteractions.ts`
|
|
||||||
|
|
||||||
重构方式:
|
|
||||||
|
|
||||||
- 现在:内部直接从世界池挑物品
|
|
||||||
- 以后:调用 `runtimeItemDirector`
|
|
||||||
|
|
||||||
建议流程:
|
|
||||||
|
|
||||||
1. 从 `GameState + Encounter + ScenePreset` 组 context
|
|
||||||
2. 指定 `generationChannel = "treasure"`
|
|
||||||
3. director 产出 `DirectedRuntimeReward`
|
|
||||||
4. 再由 `buildTreasureResultText` 读 reward 回写文本
|
|
||||||
|
|
||||||
这是最适合作为第一落点的入口。
|
|
||||||
|
|
||||||
## 8.2 `npcInteractions.ts`
|
|
||||||
|
|
||||||
接入方向:
|
|
||||||
|
|
||||||
- 商店货物补货
|
|
||||||
- NPC 帮助奖励
|
|
||||||
- 高好感赠与
|
|
||||||
- 特殊 NPC 线索物
|
|
||||||
|
|
||||||
这里最适合体现“关系锚点”。
|
|
||||||
|
|
||||||
## 8.3 `forgeSystem.ts`
|
|
||||||
|
|
||||||
锻造系统不需要完全 AI 化,但应该读取 AI 原生物品遗留的 metadata:
|
|
||||||
|
|
||||||
- 拆解时保留关系信息
|
|
||||||
- 产出对应方向的精粹
|
|
||||||
- 重铸时优先在邻近 build 内滚动
|
|
||||||
|
|
||||||
这样 AI 原生物品与锻造闭环才是连通的。
|
|
||||||
|
|
||||||
## 8.4 `customWorldRuntime.ts`
|
|
||||||
|
|
||||||
这个模块不要废弃,而是改成:
|
|
||||||
|
|
||||||
- 继续负责“主题物品池”
|
|
||||||
- director 在需要 fallback 或大批量补货时调用它
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
- `customWorldRuntime.ts` 保留“池”
|
|
||||||
- `runtimeItemDirector.ts` 新增“导演”
|
|
||||||
|
|
||||||
## 9. 推荐实施顺序
|
|
||||||
|
|
||||||
## 阶段 A:先加类型和 metadata
|
|
||||||
|
|
||||||
先做:
|
|
||||||
|
|
||||||
- `runtimeItem.ts`
|
|
||||||
- `InventoryItem.runtimeMetadata`
|
|
||||||
- `RuntimeRelationAnchor`
|
|
||||||
- `RuntimeItemGenerationContext`
|
|
||||||
|
|
||||||
目的:
|
|
||||||
|
|
||||||
- 不改玩法,只把类型基础搭好
|
|
||||||
|
|
||||||
## 阶段 B:先做 director + compiler
|
|
||||||
|
|
||||||
先不接所有入口,只把:
|
|
||||||
|
|
||||||
- context 采样
|
|
||||||
- AI 意图结构
|
|
||||||
- 本地编译器
|
|
||||||
|
|
||||||
跑通。
|
|
||||||
|
|
||||||
## 阶段 C:先接宝藏
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 独立
|
|
||||||
- 风险低
|
|
||||||
- 最容易观察“背景贴合”提升
|
|
||||||
|
|
||||||
## 阶段 D:再接 NPC 奖励与交易
|
|
||||||
|
|
||||||
这一步会明显提升:
|
|
||||||
|
|
||||||
- 关系系统
|
|
||||||
- 世界贴脸感
|
|
||||||
- 物品来源可解释性
|
|
||||||
|
|
||||||
## 阶段 E:最后接任务奖励与怪物语义掉落
|
|
||||||
|
|
||||||
因为这两类和现有逻辑耦合更深,适合后置。
|
|
||||||
|
|
||||||
## 10. 和旧版方案相比,这次重设计改了什么
|
|
||||||
|
|
||||||
相比之前偏概念化的 AI 原生物品方案,这次重设计更明确了 5 点:
|
|
||||||
|
|
||||||
1. **不新起物品系统**
|
|
||||||
- 直接复用 `InventoryItem` 与现有 build 结构。
|
|
||||||
2. **不让 AI 直接出成品**
|
|
||||||
- 只让 AI 出意图,交给本地编译器落地。
|
|
||||||
3. **所有渠道走同一导演层**
|
|
||||||
- 不再把宝藏、NPC、任务各写一套。
|
|
||||||
4. **把“关系锚点”正式数据化**
|
|
||||||
- 不是只写在文案里。
|
|
||||||
5. **先从宝藏接入,再逐步扩展**
|
|
||||||
- 不是一次推全仓库。
|
|
||||||
|
|
||||||
## 11. 一句话总结
|
|
||||||
|
|
||||||
新的 AI 原生运行时物品生成系统,不应该是“让 AI 随机写几个看起来很酷的装备”,而应该是:
|
|
||||||
|
|
||||||
**让 AI 根据当前世界、场景、NPC、事件和玩家 build 给出物品意图,再由本地规则把这个意图编译成能进背包、能进 build、能进锻造、能进存档、还能被剧情解释的正式物品。**
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
# 陶泥儿线下展会易拉宝设计记录 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
|
|
||||||
```
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
# 陶泥儿 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 更接近装饰插画,产品主标凝聚力不足。
|
|
||||||
@@ -1,529 +0,0 @@
|
|||||||
# 儿童动作识别互动玩法 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. 后续待确认事项
|
|
||||||
|
|
||||||
当前暂无待确认事项。
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# 寓教于乐发现页临时入口设计
|
|
||||||
|
|
||||||
> 日期: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 默认详情、推荐运行态或错误的改造链路。
|
|
||||||
@@ -1,797 +0,0 @@
|
|||||||
# 角色首遇感、背景故事分层解锁与同伴私聊功能设计
|
|
||||||
|
|
||||||
更新时间:`2026-04-04`
|
|
||||||
|
|
||||||
## 0. 目标
|
|
||||||
|
|
||||||
这份方案针对当前仓库,解决 3 个连在一起的问题:
|
|
||||||
|
|
||||||
1. 玩家遇见每一个角色时,在该角色当前好感度对应的关系位置上,都应该有“第一次真正接触”的感觉,而不是一上来就像已经聊过很多轮。
|
|
||||||
2. 角色背景故事目前没有按好感度分层解锁,导致尚未建立关系时,模型和界面都可能提前拿到过深信息。
|
|
||||||
3. 队伍中的高好感同伴虽然已经有聊天弹窗底子,但没有被“好感度 + 面板入口 + 上下文边界”真正串成完整功能。
|
|
||||||
|
|
||||||
这次设计不另起一套独立系统,而是基于当前仓库已有的:
|
|
||||||
|
|
||||||
- `useStoryGeneration` 的角色遭遇与对话流
|
|
||||||
- `npcInteractions.ts` 的好感度与对话阶段规则
|
|
||||||
- `prompt.ts` 的上下文注入机制
|
|
||||||
- `useCharacterChatFlow` / `CharacterChatModal` 的私聊能力
|
|
||||||
|
|
||||||
继续往前补齐。
|
|
||||||
|
|
||||||
## 1. 当前现状与问题定位
|
|
||||||
|
|
||||||
## 1.1 当前“首遇感”不足的根因
|
|
||||||
|
|
||||||
当前问题不是只出在开局同伴,而是整套角色遭遇系统缺少“第一次真正接触”的通用状态。
|
|
||||||
|
|
||||||
主要有 4 个原因:
|
|
||||||
|
|
||||||
1. `src/data/npcInteractions.ts`
|
|
||||||
- `buildInitialNpcState(...)` 会给角色型 NPC 一个初始好感,但当前系统直接把这个好感映射成普通对话阶段。
|
|
||||||
- 也就是说,系统把“当前好感是多少”和“是否已经不是第一次接触”混成了一件事。
|
|
||||||
|
|
||||||
2. `src/data/npcInteractions.ts`
|
|
||||||
- `getNpcChatTopics(...)` 目前只看 `guarded / partial / honest / deep`,不看“这是不是第一次真正交流”。
|
|
||||||
- 结果是,角色只要初始好感不低,就会直接拿到像“追一点表层理由”“开始碰真正目标”这种更像熟人后续轮的聊天切口。
|
|
||||||
|
|
||||||
3. `src/services/prompt.ts`
|
|
||||||
- `describePlayerState(...)` 直接把 `character.backstory` 注入为“主角背景”。
|
|
||||||
- `describeFrontEntity(...)` 直接把 `encounterCharacter.backstory` 注入为“背景”。
|
|
||||||
- 这样模型即使被要求“像初见”,也已经在系统层知道太多,容易写成互背设定卡。
|
|
||||||
|
|
||||||
4. 当前仓库虽然有 `initial_companion / camp_companion` 的开场流,但它只覆盖开局这一种遭遇。
|
|
||||||
- 如果只靠开场特判修正,其他角色第一次见面时仍然会缺乏首遇感。
|
|
||||||
|
|
||||||
一句话总结:
|
|
||||||
|
|
||||||
**当前缺的不是“开场对白模板”,而是“所有角色共用的首遇状态机”。**
|
|
||||||
|
|
||||||
## 1.2 背景故事过早暴露的根因
|
|
||||||
|
|
||||||
当前角色数据只有一个平铺的 `character.backstory`,而没有“公开层 / 解锁层 / 核心秘密层”的结构。
|
|
||||||
|
|
||||||
结果是:
|
|
||||||
|
|
||||||
- 界面层很容易直接把完整背景展示出来。
|
|
||||||
- prompt 层也容易直接把完整背景塞给模型。
|
|
||||||
- 好感度虽然已经控制了 `guarded / partial / honest / deep`,但它只约束“说话方式”,没有约束“哪些背景事实可以进入上下文”。
|
|
||||||
|
|
||||||
这会带来两个后果:
|
|
||||||
|
|
||||||
1. 模型写出来的对白容易像彼此已经认识很久。
|
|
||||||
2. 玩家还没建立关系,就已经在系统层“知道了太多”。
|
|
||||||
|
|
||||||
## 1.3 私聊功能现状
|
|
||||||
|
|
||||||
当前仓库其实已经有私聊底座:
|
|
||||||
|
|
||||||
- `src/hooks/story/characterChat.ts`
|
|
||||||
- 已经有 `useCharacterChatFlow(...)`
|
|
||||||
- 已经能生成建议、流式回复、聊天总结,并写回 `gameState.characterChats`
|
|
||||||
|
|
||||||
- `src/components/CharacterChatModal.tsx`
|
|
||||||
- 已经有完整聊天弹窗
|
|
||||||
|
|
||||||
- `src/components/GameShell.tsx`
|
|
||||||
- 已经挂载了 `CharacterChatModal`
|
|
||||||
- 也把 `onOpenCharacterChat` 传给了 `CharacterPanel`
|
|
||||||
|
|
||||||
但缺的 3 个点还没有补上:
|
|
||||||
|
|
||||||
1. `CharacterPanel` 当前没有真正渲染聊天按钮。
|
|
||||||
2. 游戏内实际查看同伴详情时打开的是 `AdventureEntityModal`,它也没有聊天入口。
|
|
||||||
3. 私聊没有与“队伍成员 + 好感度阈值 + 已解锁背景”绑定。
|
|
||||||
|
|
||||||
所以现在属于“能力有了,但功能还没真正成立”。
|
|
||||||
|
|
||||||
## 2. 总体设计结论
|
|
||||||
|
|
||||||
建议把这次需求拆成 3 条链,同时落地:
|
|
||||||
|
|
||||||
1. **首遇感链**
|
|
||||||
- 所有 `encounter.characterId` 的角色型 NPC,第一次真正接触时都先走通用首遇规则。
|
|
||||||
- 首遇时的亲疏、开放程度和选项排序由“当前好感度 + 是否首遇”共同决定。
|
|
||||||
- `initial_companion / camp_companion` 只保留场景与演出意义,不再承担特殊对话规则本体。
|
|
||||||
|
|
||||||
2. **背景解锁链**
|
|
||||||
- 角色背景改成按好感度分章节解锁。
|
|
||||||
- 未解锁章节既不能在 UI 明文显示,也不能进入 prompt 上下文。
|
|
||||||
|
|
||||||
3. **私聊链**
|
|
||||||
- 队伍中的高好感同伴才能解锁私聊。
|
|
||||||
- 在同伴详情面板中点击按钮打开现有聊天弹窗。
|
|
||||||
- 私聊只吃“公开信息 + 已解锁背景 + 最近共同经历”,不能越权看到锁住的背景章节。
|
|
||||||
|
|
||||||
## 3. 所有角色通用的“首遇感”方案
|
|
||||||
|
|
||||||
## 3.1 核心原则
|
|
||||||
|
|
||||||
必须把下面两件事拆开:
|
|
||||||
|
|
||||||
1. 当前好感度高低
|
|
||||||
2. 这是不是第一次真正接触
|
|
||||||
|
|
||||||
因为:
|
|
||||||
|
|
||||||
- `好感低` 不等于 `第一次见面`
|
|
||||||
- `好感不低` 也不等于 `已经聊过很多轮`
|
|
||||||
|
|
||||||
正确做法应该是“双轴控制”:
|
|
||||||
|
|
||||||
- 好感决定:
|
|
||||||
- 语气亲疏
|
|
||||||
- 允许透露的信息深度
|
|
||||||
- 哪些功能当前有机会成立
|
|
||||||
|
|
||||||
- 首遇状态决定:
|
|
||||||
- 对话必须先像“第一次对上人”
|
|
||||||
- 优先说现场判断、态度试探、短钩子
|
|
||||||
- 不能一上来就跳到“已经熟悉彼此”的后续轮节奏
|
|
||||||
|
|
||||||
## 3.2 状态设计
|
|
||||||
|
|
||||||
建议给 `NpcPersistentState` 增加一个通用字段:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface NpcPersistentState {
|
|
||||||
affinity: number;
|
|
||||||
relationState?: RoleRelationState;
|
|
||||||
helpUsed: boolean;
|
|
||||||
chattedCount: number;
|
|
||||||
giftsGiven: number;
|
|
||||||
inventory: InventoryItem[];
|
|
||||||
recruited: boolean;
|
|
||||||
revealedFacts?: string[];
|
|
||||||
knownAttributeRumors?: string[];
|
|
||||||
firstMeaningfulContactResolved?: boolean;
|
|
||||||
seenBackstoryChapterIds?: string[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
- `firstMeaningfulContactResolved`
|
|
||||||
- 适用于所有角色型 NPC
|
|
||||||
- 表示“玩家是否已经和这个角色完成过第一轮真正接触”
|
|
||||||
- 不等于 `chattedCount > 0`
|
|
||||||
- 不等于 `recruited === true`
|
|
||||||
- 不等于 `affinity` 已经较高
|
|
||||||
|
|
||||||
- `seenBackstoryChapterIds`
|
|
||||||
- 用于背景章节首次解锁时的提示去重
|
|
||||||
|
|
||||||
建议补这组 helper:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function isNpcFirstMeaningfulContact(
|
|
||||||
encounter: Encounter,
|
|
||||||
npcState: NpcPersistentState,
|
|
||||||
): boolean
|
|
||||||
|
|
||||||
function markNpcFirstMeaningfulContactResolved(
|
|
||||||
npcState: NpcPersistentState,
|
|
||||||
): NpcPersistentState
|
|
||||||
```
|
|
||||||
|
|
||||||
判断建议:
|
|
||||||
|
|
||||||
- 仅对 `encounter.characterId` 的角色型 NPC 启用
|
|
||||||
- `firstMeaningfulContactResolved !== true` 时,进入首遇模式
|
|
||||||
|
|
||||||
## 3.3 首遇时的好感度矩阵
|
|
||||||
|
|
||||||
首遇不是永远冷冰冰,而是要落在“当前好感度对应的位置”上。
|
|
||||||
|
|
||||||
建议把首遇风格做成这张矩阵:
|
|
||||||
|
|
||||||
| 当前关系站位 | 好感区间参考 | 首遇时的感觉 | 信息上限 | 选项重心 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `guarded` | `<= 14` | 明显戒备、先观察你值不值得回应 | 只谈眼前局势、模糊钩子 | 观察、试探、判断 |
|
|
||||||
| `neutral` | `15 - 34` | 可以正常交流,但还不是熟人 | 可给表层理由,不给深层秘密 | 互探来意、确认立场 |
|
|
||||||
| `cooperative` | `35 - 59` | 带基础善意或认可,但仍是第一次正式接触 | 能谈轮廓,不谈全部底牌 | 确认合作、交换判断 |
|
|
||||||
| `bonded` | `>= 60` | 明显信任,但仍应像“第一次真正见到本人/第一次正面对接” | 可谈较深动机,但不一次讲完所有旧事 | 确认并肩关系、推进共同目标 |
|
|
||||||
|
|
||||||
重点不是绝对数值,而是这句话:
|
|
||||||
|
|
||||||
**第一次见面时,角色可以按当前好感更冷或更暖,但不能直接写成“已经是后续轮”。**
|
|
||||||
|
|
||||||
## 3.4 首遇选项改法
|
|
||||||
|
|
||||||
当前 `getNpcChatTopics(...)` 和 `buildNpcEncounterStoryMoment(...)` 更像“已进入常规互动层”后的选项生成。
|
|
||||||
|
|
||||||
建议新增一层:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function getNpcFirstContactTopics(
|
|
||||||
encounter: Encounter,
|
|
||||||
npcState: NpcPersistentState,
|
|
||||||
): StoryOption[]
|
|
||||||
|
|
||||||
function buildNpcFirstContactOptionCatalog(
|
|
||||||
encounter: Encounter,
|
|
||||||
npcState: NpcPersistentState,
|
|
||||||
baseOptions: StoryOption[],
|
|
||||||
): StoryOption[]
|
|
||||||
```
|
|
||||||
|
|
||||||
规则建议:
|
|
||||||
|
|
||||||
1. 只要是 `firstMeaningfulContactResolved !== true`,前 2 个选项必须来自首遇话题池。
|
|
||||||
|
|
||||||
2. 首遇话题池只做这几类:
|
|
||||||
- 现场判断
|
|
||||||
- 你为什么在这里
|
|
||||||
- 你刚才在观察什么
|
|
||||||
- 你对我的态度和判断
|
|
||||||
- 前面那件事到底哪里不对
|
|
||||||
|
|
||||||
3. 现有其他合法功能可以继续保留,但排序必须后置:
|
|
||||||
- `npc_trade`
|
|
||||||
- `npc_help`
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_quest_accept`
|
|
||||||
- `npc_recruit`
|
|
||||||
|
|
||||||
补一条实现约束:
|
|
||||||
|
|
||||||
- 首次进入 `npc_chat` 时,前端聊天状态里不允许直接塞预设对白充当首句。
|
|
||||||
- 角色第一次真正对玩家开口时说什么,必须由 `npc_chat` 对应的 prompt 约束来生成,并要求首句是自然招呼或开场判断。
|
|
||||||
- 不能再用“某人看着你,像是在等你把话接下去”这类第三人称占位旁白充当可见对话历史首句,也不能在聊天 state 里本地硬编码一条替代台词。
|
|
||||||
- 当玩家在场景中第一次真正撞上角色型 NPC 并进入聊天时,应直接触发一轮由 NPC 主动开口的模型回复;这一轮只生成 NPC 自己的首句与后续可选回应,不得代替玩家补写未说过的话。
|
|
||||||
- 负好感或敌对关系不应跳过主动开口;如果玩家从 NPC 交互面板点击 `npc_chat`,且该角色尚未完成 `firstMeaningfulContactResolved`,仍要走同一条 NPC 主动开场链路。负好感只影响语气、敌对聊天指令与后续可选功能,不影响“由角色先发言”的首遇行为。
|
|
||||||
- 好感度小于 `0` 的角色在聊天终止时不进入 `story_continue_adventure` 收束态。无论是玩家主动退出聊天,还是模型通过敌对聊天指令主动结束聊天,底部选项都必须收束为一个 `npc_fight` 与多个 `battle_escape_breakout`:`npc_fight` 的按钮文案保持“战斗”,点击后仍进入 NPC 战斗结算链路;逃跑类选项按当前场景相邻场景展开为“逃往{场景名}”,并额外提供“逃回当前场景起点”。逃跑选项需要在 `runtimePayload` 中携带目标场景信息,点击后复用现有主角向左转身跑出屏幕的逃离演出,再在目标场景从左侧入场并面向右侧。
|
|
||||||
|
|
||||||
4. 首遇状态下,不允许前两项直接变成:
|
|
||||||
- 深背景追问
|
|
||||||
- 直接招募
|
|
||||||
- 直接交易
|
|
||||||
- 直接切磋
|
|
||||||
- 直接熟人式寒暄
|
|
||||||
|
|
||||||
这里的关键不是“把功能全部禁掉”,而是:
|
|
||||||
|
|
||||||
**先保证玩家看到的是首遇节奏,再决定这一轮后面还要不要承接更深功能。**
|
|
||||||
|
|
||||||
## 3.5 首遇期功能排序规则
|
|
||||||
|
|
||||||
为了兼容“不同角色当前好感不同”的需求,建议不要把首遇期做成一刀切的硬封锁,而是做成排序和表达规则:
|
|
||||||
|
|
||||||
1. `npc_chat`
|
|
||||||
- 首遇期必须优先出现
|
|
||||||
- 至少两个切口都与“眼前”和“第一判断”有关
|
|
||||||
|
|
||||||
2. `npc_recruit`
|
|
||||||
- 如果按当前好感已经合法,可以出现
|
|
||||||
- 但不能排在前两项
|
|
||||||
- 文案要像“确认是否并肩同行”,不能像已经熟到直接收编
|
|
||||||
|
|
||||||
3. `npc_trade / npc_help / npc_quest_accept`
|
|
||||||
- 角色职业允许时可以出现
|
|
||||||
- 但文案必须像“先试着谈这件事”,不是默认彼此早就熟悉流程
|
|
||||||
|
|
||||||
4. `npc_fight / npc_spar`
|
|
||||||
- 如果剧情确实需要,可以保留
|
|
||||||
- 但 storyText 里仍要先体现“第一次正面对上”的张力
|
|
||||||
|
|
||||||
这样做的好处是:
|
|
||||||
|
|
||||||
- 既满足“每个角色都按当前好感落点来写”
|
|
||||||
- 又不会把首遇感做成只能用于开局同伴的特殊模板
|
|
||||||
|
|
||||||
## 3.6 首遇状态何时结束
|
|
||||||
|
|
||||||
建议当玩家与该角色完成一次真正交互结算后,就把 `firstMeaningfulContactResolved` 设为 `true`。
|
|
||||||
|
|
||||||
推荐计入“真正交互”的动作:
|
|
||||||
|
|
||||||
- `npc_preview_talk` 后进入并完成一次正式对白
|
|
||||||
- `npc_chat`
|
|
||||||
- `npc_help`
|
|
||||||
- `npc_trade`
|
|
||||||
- `npc_gift`
|
|
||||||
- `npc_recruit`
|
|
||||||
- `npc_quest_accept`
|
|
||||||
- `npc_spar`
|
|
||||||
- `npc_fight`
|
|
||||||
|
|
||||||
不建议计入:
|
|
||||||
|
|
||||||
- 仅看了一眼就离开
|
|
||||||
- 只打开 UI 没有完成结算
|
|
||||||
|
|
||||||
这样可以保证:
|
|
||||||
|
|
||||||
- 第一次接触是独立阶段
|
|
||||||
- 第二次开始才进入当前系统已有的常规关系推进节奏
|
|
||||||
|
|
||||||
## 3.7 prompt 与 fallback 约束
|
|
||||||
|
|
||||||
建议新增一个通用的首遇指令,而不是继续把首遇逻辑绑定在开场专用 helper 上。
|
|
||||||
|
|
||||||
### 1. prompt 上下文字段
|
|
||||||
|
|
||||||
建议在 `StoryGenerationContext` 增加:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
isFirstMeaningfulContact?: boolean;
|
|
||||||
firstContactRelationStance?: 'guarded' | 'neutral' | 'cooperative' | 'bonded' | null;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. prompt 约束
|
|
||||||
|
|
||||||
只要 `isFirstMeaningfulContact === true`:
|
|
||||||
|
|
||||||
- 不再把 `character.backstory` 作为“主角背景”直接注入
|
|
||||||
- 不再把 `encounterCharacter.backstory` 作为“背景”直接注入
|
|
||||||
- 只允许注入:
|
|
||||||
- `publicSummary`
|
|
||||||
- `surfaceHook`
|
|
||||||
- `immediateConcern`
|
|
||||||
- 已公开的角色描述
|
|
||||||
- 现场状态
|
|
||||||
- 当前关系站位
|
|
||||||
|
|
||||||
### 3. fallback 约束
|
|
||||||
|
|
||||||
如果保留现有 `buildInitialCompanionDialogueText(...)` 一类 helper:
|
|
||||||
|
|
||||||
- 它们只能作为“某个具体场景下调用通用首遇规则”的薄包装
|
|
||||||
- 不应继续承担独立的开场规则系统
|
|
||||||
- 更不能把本地预设对白直接写进 `npc_chat` 的可见对话历史里,`npc_chat` 首个角色台词必须由 prompt 生成
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
**开场营地只是首遇规则的一个调用场景,不是首遇规则本体。**
|
|
||||||
|
|
||||||
## 4. 背景故事分层解锁设计
|
|
||||||
|
|
||||||
## 4.1 核心原则
|
|
||||||
|
|
||||||
背景故事要拆成“能公开的层”和“需要建立关系后才能知道的层”。
|
|
||||||
|
|
||||||
约束是:
|
|
||||||
|
|
||||||
1. 未达到对应好感度前,该章节不能进入 prompt 上下文。
|
|
||||||
2. 未达到对应好感度前,该章节不能在角色详情中完整展示。
|
|
||||||
3. 即使模型之前已经从别的地方生成过相关话头,未解锁章节也不能被总结层写成稳定关系记忆。
|
|
||||||
|
|
||||||
## 4.2 数据结构
|
|
||||||
|
|
||||||
建议在 `Character` 上增加背景解锁配置:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CharacterBackstoryChapter {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
affinityRequired: number;
|
|
||||||
teaser: string;
|
|
||||||
content: string;
|
|
||||||
contextSnippet: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CharacterBackstoryRevealConfig {
|
|
||||||
publicSummary: string;
|
|
||||||
chapters: CharacterBackstoryChapter[];
|
|
||||||
privateChatUnlockAffinity?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Character {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
backstory: string;
|
|
||||||
backstoryReveal?: CharacterBackstoryRevealConfig;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
字段职责:
|
|
||||||
|
|
||||||
- `backstory`
|
|
||||||
- 保留为原始完整设定文本
|
|
||||||
- 不再默认直接进入运行时 prompt
|
|
||||||
|
|
||||||
- `publicSummary`
|
|
||||||
- 永远允许展示和注入
|
|
||||||
- 只用于“第一印象”和公开层信息
|
|
||||||
|
|
||||||
- `teaser`
|
|
||||||
- 章节未解锁时给 UI 的短提示
|
|
||||||
|
|
||||||
- `content`
|
|
||||||
- 玩家真正看到的章节正文
|
|
||||||
|
|
||||||
- `contextSnippet`
|
|
||||||
- 允许注入模型上下文的精简版
|
|
||||||
- 明确只包含“系统允许此阶段知道的事实”
|
|
||||||
|
|
||||||
## 4.3 推荐阈值
|
|
||||||
|
|
||||||
建议不要直接复用 `guarded / partial / honest / deep` 作为背景章节解锁阈值,因为当前对话阶段对“已招募”有额外放宽。
|
|
||||||
|
|
||||||
尤其是:
|
|
||||||
|
|
||||||
- `getNpcDisclosureStage(...)`
|
|
||||||
- `getNpcWarmthStage(...)`
|
|
||||||
|
|
||||||
目前都会在 `recruited === true` 时直接给更高阶段。
|
|
||||||
|
|
||||||
这适合控制说话语气,但不适合控制“背景章节是否已解锁”。
|
|
||||||
|
|
||||||
因此建议新增独立阈值:
|
|
||||||
|
|
||||||
| 层级 | 建议阈值 | 含义 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 公开层 | `0` | 只展示公开印象,不算真正解锁 |
|
|
||||||
| 第一章 | `20` | 来路表层、最近为何会出现在这里 |
|
|
||||||
| 第二章 | `40` | 旧事伤痕、过去某段关键经历 |
|
|
||||||
| 第三章 | `65` | 真正目标、必须完成的事 |
|
|
||||||
| 第四章 | `85` | 最深层秘密、最不愿轻易说出的事实 |
|
|
||||||
|
|
||||||
这样做的好处是:
|
|
||||||
|
|
||||||
- 首遇阶段仍然有“先认识再深入”的节奏
|
|
||||||
- 招募后也不会立刻把全部背景放开
|
|
||||||
- 高好感的成长曲线能真正体现在“知道了多少”
|
|
||||||
|
|
||||||
## 4.4 运行时规则
|
|
||||||
|
|
||||||
建议增加这组 helper:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function getUnlockedBackstoryChapters(
|
|
||||||
character: Character,
|
|
||||||
affinity: number,
|
|
||||||
): CharacterBackstoryChapter[]
|
|
||||||
|
|
||||||
function getNextLockedBackstoryChapter(
|
|
||||||
character: Character,
|
|
||||||
affinity: number,
|
|
||||||
): CharacterBackstoryChapter | null
|
|
||||||
|
|
||||||
function buildBackstoryPromptContext(
|
|
||||||
character: Character,
|
|
||||||
affinity: number,
|
|
||||||
): string[]
|
|
||||||
```
|
|
||||||
|
|
||||||
使用规则:
|
|
||||||
|
|
||||||
- UI 展示:
|
|
||||||
- `publicSummary` 永远可见
|
|
||||||
- 已解锁章节显示全文
|
|
||||||
- 未解锁章节显示 `title + teaser + 所需好感`
|
|
||||||
|
|
||||||
- prompt 注入:
|
|
||||||
- 只能使用 `publicSummary + unlocked.contextSnippet`
|
|
||||||
- 禁止直接把 `backstory` 全文塞给模型
|
|
||||||
|
|
||||||
- 总结沉淀:
|
|
||||||
- 关系总结、私聊总结只允许总结已解锁章节
|
|
||||||
- 未解锁章节即使被模型“猜中”,也不写入稳定状态
|
|
||||||
|
|
||||||
## 4.5 解锁提示
|
|
||||||
|
|
||||||
建议当好感跨越阈值时,在运行时给一次轻提示:
|
|
||||||
|
|
||||||
- 文案示例:
|
|
||||||
- `你对 宁霜 的过去有了更多了解:旧军密图`
|
|
||||||
- `你察觉到 萧烬 愿意谈及更深的一层旧事了`
|
|
||||||
|
|
||||||
实现上不需要新增复杂通知系统,可以先:
|
|
||||||
|
|
||||||
- 在好感变化后检查本次跨越了哪些章节阈值
|
|
||||||
- 若有新章节且 `seenBackstoryChapterIds` 未记录,则插入一条轻量 UI 提示或一条短故事结果文本
|
|
||||||
|
|
||||||
## 5. “未解锁背景不能加入上下文”的具体边界
|
|
||||||
|
|
||||||
这个需求要同时卡住 4 个入口:
|
|
||||||
|
|
||||||
## 5.1 主线剧情 prompt
|
|
||||||
|
|
||||||
`buildStoryContextFromState(...)` 与 `prompt.ts` 中:
|
|
||||||
|
|
||||||
- 角色背景不能再直接读取 `character.backstory`
|
|
||||||
- 应改成读取 `buildBackstoryPromptContext(...)`
|
|
||||||
|
|
||||||
## 5.2 NPC 聊天 prompt
|
|
||||||
|
|
||||||
`buildStrictNpcChatDialoguePrompt(...)` 中:
|
|
||||||
|
|
||||||
- 只能拿 `publicSummary + 已解锁章节 + 最近共同经历`
|
|
||||||
- 未解锁章节不能出现在“当前面前实体”描述里
|
|
||||||
- 若当前还是首遇模式,还要进一步收紧到“公开层 + 当前场景判断”
|
|
||||||
|
|
||||||
## 5.3 私聊 prompt
|
|
||||||
|
|
||||||
`useCharacterChatFlow(...)` 中:
|
|
||||||
|
|
||||||
- `streamCharacterPanelChatReply(...)`
|
|
||||||
- `generateCharacterPanelChatSuggestions(...)`
|
|
||||||
- `generateCharacterPanelChatSummary(...)`
|
|
||||||
|
|
||||||
都只能吃已解锁背景。
|
|
||||||
|
|
||||||
## 5.4 关系摘要回写
|
|
||||||
|
|
||||||
`gameState.characterChats[characterId].summary` 是会反向进入主线上下文的。
|
|
||||||
|
|
||||||
所以必须再加一层约束:
|
|
||||||
|
|
||||||
- 如果某段私聊总结提到了未解锁章节内容,则不允许写回稳定 summary
|
|
||||||
- 或者更稳妥地说,summary 生成 prompt 本身就只提供已解锁背景
|
|
||||||
|
|
||||||
推荐做法是后者,因为它更简单也更可控。
|
|
||||||
|
|
||||||
## 6. 高好感同伴私聊设计
|
|
||||||
|
|
||||||
## 6.1 解锁条件
|
|
||||||
|
|
||||||
建议私聊不是“所有招募同伴自动可用”,而是满足以下条件后才解锁:
|
|
||||||
|
|
||||||
1. 角色已在队伍体系中
|
|
||||||
- `gameState.companions`
|
|
||||||
- 或 `gameState.roster`
|
|
||||||
|
|
||||||
2. 对应 `npcState.recruited === true`
|
|
||||||
|
|
||||||
3. 当前好感度达到私聊阈值
|
|
||||||
- 默认建议 `70`
|
|
||||||
- 允许角色级配置覆盖:`backstoryReveal.privateChatUnlockAffinity`
|
|
||||||
|
|
||||||
之所以不用“已招募”直接等于“可私聊”,原因很简单:
|
|
||||||
|
|
||||||
- 当前系统里已招募会把对话阶段直接推高
|
|
||||||
- 但用户要求的是“高好感同伴解锁私聊”
|
|
||||||
- 所以私聊需要独立阈值,不应绑定到 `warm / deep`
|
|
||||||
|
|
||||||
## 6.2 入口位置
|
|
||||||
|
|
||||||
建议把入口放在两个地方,但以“同伴角色信息面板”为主:
|
|
||||||
|
|
||||||
### 1. `AdventureEntityModal`
|
|
||||||
|
|
||||||
这是游戏内点击同伴详情时实际打开的面板,优先级最高。
|
|
||||||
|
|
||||||
在 `selection.kind === 'companion'` 时增加:
|
|
||||||
|
|
||||||
- 已解锁:`聊天`
|
|
||||||
- 未解锁:置灰按钮 + 提示 `好感达到 70 后解锁,当前 58`
|
|
||||||
|
|
||||||
点击逻辑:
|
|
||||||
|
|
||||||
1. 关闭 `AdventureEntityModal`
|
|
||||||
2. 调 `characterChatUi.openChat(target)`
|
|
||||||
3. 打开现有 `CharacterChatModal`
|
|
||||||
|
|
||||||
### 2. `CharacterPanel`
|
|
||||||
|
|
||||||
如果以后存在不走 `AdventureEntityModal` 的详情流,也应在成员详情面板里显示同样按钮,保持一致。
|
|
||||||
|
|
||||||
## 6.3 聊天目标结构
|
|
||||||
|
|
||||||
建议扩展 `CharacterChatTarget`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type CharacterChatTarget = {
|
|
||||||
character: Character;
|
|
||||||
npcId: string | null;
|
|
||||||
roleLabel: string;
|
|
||||||
hp: number;
|
|
||||||
maxHp: number;
|
|
||||||
mana: number;
|
|
||||||
maxMana: number;
|
|
||||||
affinity?: number;
|
|
||||||
unlockedBackstoryChapterIds?: string[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
这样聊天弹窗里可以直接显示:
|
|
||||||
|
|
||||||
- 当前关系热度
|
|
||||||
- 已解锁背景章节数
|
|
||||||
- 下一段背景解锁阈值
|
|
||||||
|
|
||||||
## 6.4 私聊的上下文来源
|
|
||||||
|
|
||||||
私聊上下文建议固定由这几部分组成:
|
|
||||||
|
|
||||||
1. 角色公开信息
|
|
||||||
- `name / title / description / personality`
|
|
||||||
|
|
||||||
2. 当前关系信息
|
|
||||||
- 当前好感
|
|
||||||
- 是否出战 / 是否营地待命
|
|
||||||
- 最近共同经历
|
|
||||||
|
|
||||||
3. 已解锁背景章节
|
|
||||||
- 只传 `contextSnippet`
|
|
||||||
|
|
||||||
4. 最近私聊记录
|
|
||||||
- `characterChats[characterId].history`
|
|
||||||
- `characterChats[characterId].summary`
|
|
||||||
|
|
||||||
明确不传:
|
|
||||||
|
|
||||||
- 未解锁背景章节
|
|
||||||
- 角色完整 `backstory`
|
|
||||||
|
|
||||||
## 6.5 私聊对数值的影响
|
|
||||||
|
|
||||||
建议本期私聊先不直接改好感数值,理由是:
|
|
||||||
|
|
||||||
- 避免玩家通过无限聊天刷关系
|
|
||||||
- 先把“关系表达层”和“规则数值层”分开
|
|
||||||
- 当前已有送礼、聊天、切磋、求助、任务等好感入口,足够支撑成长
|
|
||||||
|
|
||||||
私聊本期的价值应放在:
|
|
||||||
|
|
||||||
- 沉淀角色关系摘要
|
|
||||||
- 解锁更深层的文本风格和背景信息
|
|
||||||
- 让队伍中的同伴真正像“会私下说话的人”
|
|
||||||
|
|
||||||
如果后续要给私聊轻量数值收益,建议再单独加冷却或每日首次奖励,不要在本期一起做。
|
|
||||||
|
|
||||||
## 7. UI 设计建议
|
|
||||||
|
|
||||||
## 7.1 同伴详情面板
|
|
||||||
|
|
||||||
建议增加一个“关系 / 档案”区块:
|
|
||||||
|
|
||||||
- 当前好感:`58`
|
|
||||||
- 关系阶段:`谨慎` / `熟络` / `深信`
|
|
||||||
- 首遇状态:
|
|
||||||
- `初次接触未完成`
|
|
||||||
- 或 `已完成第一次正式对接`
|
|
||||||
- 私聊状态:
|
|
||||||
- `未解锁(70)`
|
|
||||||
- 或 `已解锁`
|
|
||||||
|
|
||||||
下面接“背景档案”:
|
|
||||||
|
|
||||||
- 公开印象
|
|
||||||
- 章节卡片列表
|
|
||||||
|
|
||||||
章节卡片状态:
|
|
||||||
|
|
||||||
- 已解锁
|
|
||||||
- 标题
|
|
||||||
- 正文
|
|
||||||
|
|
||||||
- 未解锁
|
|
||||||
- 标题
|
|
||||||
- `需要好感 40`
|
|
||||||
- `teaser`
|
|
||||||
- 遮罩态
|
|
||||||
|
|
||||||
## 7.2 私聊按钮状态
|
|
||||||
|
|
||||||
按钮文案建议:
|
|
||||||
|
|
||||||
- 已解锁:`聊天`
|
|
||||||
- 未解锁:`聊天(70 解锁)`
|
|
||||||
|
|
||||||
按钮旁边可以补一行细字:
|
|
||||||
|
|
||||||
- `仅高好感同伴可进行私聊`
|
|
||||||
|
|
||||||
## 7.3 解锁反馈
|
|
||||||
|
|
||||||
建议当玩家在详情面板里刚好看到某一章解锁时:
|
|
||||||
|
|
||||||
- 章节卡片做一次轻量高亮
|
|
||||||
- 私聊按钮从置灰切到可点击时也做一次轻量强调
|
|
||||||
|
|
||||||
这样能把“关系成长”明确反馈给玩家,而不只是静态数字变化。
|
|
||||||
|
|
||||||
## 8. 落地文件建议
|
|
||||||
|
|
||||||
## 8.1 类型与数据
|
|
||||||
|
|
||||||
- `src/types/characters.ts`
|
|
||||||
- 增加 `CharacterBackstoryChapter`
|
|
||||||
- 增加 `CharacterBackstoryRevealConfig`
|
|
||||||
|
|
||||||
- `src/types/scene.ts`
|
|
||||||
- 给 `NpcPersistentState` 增加
|
|
||||||
- `firstMeaningfulContactResolved`
|
|
||||||
- `seenBackstoryChapterIds`
|
|
||||||
|
|
||||||
- `src/data/characterPresets.ts`
|
|
||||||
- 为可招募角色补 `backstoryReveal`
|
|
||||||
- 配置分章节阈值和 `privateChatUnlockAffinity`
|
|
||||||
|
|
||||||
## 8.2 关系与规则
|
|
||||||
|
|
||||||
- `src/data/npcInteractions.ts`
|
|
||||||
- 增加首遇状态 helper
|
|
||||||
- 增加首遇话题 helper
|
|
||||||
- 增加背景章节解锁 helper
|
|
||||||
- 增加私聊解锁 helper
|
|
||||||
- 调整首遇期的功能排序规则
|
|
||||||
|
|
||||||
## 8.3 剧情与 prompt
|
|
||||||
|
|
||||||
- `src/hooks/useStoryGeneration.ts`
|
|
||||||
- 新增通用首遇状态流转
|
|
||||||
- 给首遇角色注入统一的 first-contact context
|
|
||||||
- 处理好感跨章节时的解锁通知
|
|
||||||
|
|
||||||
- `src/services/aiTypes.ts`
|
|
||||||
- 给 `StoryGenerationContext` 增加
|
|
||||||
- `isFirstMeaningfulContact`
|
|
||||||
- `firstContactRelationStance`
|
|
||||||
- 已解锁背景片段字段
|
|
||||||
|
|
||||||
- `src/services/prompt.ts`
|
|
||||||
- 移除对完整 `backstory` 的直接注入
|
|
||||||
- 改为按已解锁章节构造角色背景上下文
|
|
||||||
- 首遇模式下额外收紧信息披露
|
|
||||||
|
|
||||||
## 8.4 UI 与交互
|
|
||||||
|
|
||||||
- `src/components/AdventureEntityModal.tsx`
|
|
||||||
- 显示同伴关系/档案区
|
|
||||||
- 增加聊天按钮
|
|
||||||
|
|
||||||
- `src/components/CharacterPanel.tsx`
|
|
||||||
- 补齐聊天按钮接线
|
|
||||||
- 若保留本地详情弹窗,也同步显示档案区
|
|
||||||
|
|
||||||
- `src/components/GameShell.tsx`
|
|
||||||
- 把 `characterChatUi.openChat` 透传给 `AdventureEntityModal`
|
|
||||||
|
|
||||||
- `src/hooks/story/characterChat.ts`
|
|
||||||
- 私聊 prompt 只使用已解锁章节
|
|
||||||
|
|
||||||
## 9. 推荐开发顺序
|
|
||||||
|
|
||||||
建议按这 4 步做,不要反过来:
|
|
||||||
|
|
||||||
1. 先补数据建模
|
|
||||||
- `backstoryReveal`
|
|
||||||
- `firstMeaningfulContactResolved`
|
|
||||||
- 解锁 helper
|
|
||||||
|
|
||||||
2. 再补规则
|
|
||||||
- 首遇状态判断
|
|
||||||
- 首遇选项排序
|
|
||||||
- 首遇完成后的状态流转
|
|
||||||
- 私聊解锁条件
|
|
||||||
|
|
||||||
3. 再补 prompt 上下文边界
|
|
||||||
- 禁止未解锁背景进入模型上下文
|
|
||||||
- 禁止首遇期被写成后续轮
|
|
||||||
|
|
||||||
4. 最后补 UI
|
|
||||||
- 详情面板档案区
|
|
||||||
- 聊天按钮
|
|
||||||
- 解锁提示
|
|
||||||
|
|
||||||
## 10. 验收标准
|
|
||||||
|
|
||||||
做到以下几点,才算这次需求真正完成:
|
|
||||||
|
|
||||||
1. 玩家第一次遇见任一角色型 NPC 时,对话会像第一次真正接触,而不是像已经聊过很多轮。
|
|
||||||
2. 同一个角色第一次见面时的冷暖程度,会落在该角色当前好感对应的位置上,而不是被强行写成统一冷场模板。
|
|
||||||
3. 首遇阶段的前两项选项会优先围绕“眼前局势、互相判断、来意试探”,而不是直接跳进深聊、招募或熟人功能。
|
|
||||||
4. 未达到阈值的背景章节在 UI 中不可完整查看,在 prompt 中也不可被注入。
|
|
||||||
5. 好感提高后,角色详情面板中的背景章节会逐步解锁。
|
|
||||||
6. 队伍中的高好感同伴会在详情面板中出现“聊天”按钮。
|
|
||||||
7. 点击“聊天”后,能直接弹出现有私聊弹窗。
|
|
||||||
8. 私聊内容、建议、总结都不会越权使用未解锁背景。
|
|
||||||
9. 旧存档读取后不会崩,缺省字段能平稳兜底。
|
|
||||||
|
|
||||||
## 11. 一句话收束
|
|
||||||
|
|
||||||
这次需求的关键不是“给开局补一段特判对白”,而是把:
|
|
||||||
|
|
||||||
- 首遇节奏
|
|
||||||
- 背景披露节奏
|
|
||||||
- 私聊入口
|
|
||||||
- 关系记忆
|
|
||||||
|
|
||||||
统一到同一套“好感度 + 首遇状态 + 信息解锁”的通用规则里。
|
|
||||||
|
|
||||||
只有这样,玩家遇见每一个角色时才都会有对应关系位置上的初识感,角色背景才会像一步步了解出来的,同伴私聊也才会真的有价值。
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# 创作页作品删除入口设计 2026-04-24
|
|
||||||
|
|
||||||
## 背景
|
|
||||||
|
|
||||||
创作页作品卡曾把删除作为底部大按钮展示,并且只对带 `profileId` 的 RPG 作品传入删除回调,导致大鱼、拼图、以及部分草稿作品没有删除入口。用户预期是:删除不是主操作,放在卡片右上角的小 icon 即可;任何作品都应该能删除。
|
|
||||||
|
|
||||||
## 落地规则
|
|
||||||
|
|
||||||
- 作品卡整体就是继续创作 / 继续完善 / 查看详情入口,不再在底部展示“继续完善”等重复主按钮。
|
|
||||||
- 作品卡右上角固定展示删除 icon,底部主操作区只保留体验等必须独立触发的正向操作。
|
|
||||||
- 点击作品卡任意非独立按钮区域都进入继续完善链路;点击删除或体验时不得冒泡触发作品卡打开。
|
|
||||||
- 作品卡保留键盘可访问性:焦点落在卡片时按 Enter 或空格等同点击作品,焦点落在删除 / 体验按钮时只执行对应按钮动作。
|
|
||||||
- 删除入口不按发布状态隐藏:草稿、已发布作品均可删除。
|
|
||||||
- 删除入口不按玩法类型隐藏:RPG、大鱼吃小鱼、拼图作品均应在创作页可删除。
|
|
||||||
- 点击删除前保留浏览器确认弹窗,避免误触;删除中仅禁用当前作品卡的删除 icon。
|
|
||||||
- 删除成功后刷新或替换对应玩法的作品列表,确保卡片立即消失。
|
|
||||||
|
|
||||||
## 工程边界
|
|
||||||
|
|
||||||
- 前端只负责表现和触发删除,实际删除由 `server-rs` API 与 SpacetimeDB 模块过程完成。
|
|
||||||
- 大鱼作品按 `sourceSessionId` 删除创作 session,并同步清理消息、素材槽和运行快照。
|
|
||||||
- 拼图作品按 `profileId` 删除作品 profile,并同步清理来源 Agent session、消息和入口运行快照。
|
|
||||||
- RPG 已发布/持久草稿按 `profileId` 走既有自定义世界删除链路;纯 Agent session 草稿按 `sessionId` 走 owner-only session 删除过程,并清理消息、操作与草稿卡。
|
|
||||||
- 自定义世界 Agent 的异步进度写回必须通过 `upsert_custom_world_agent_operation_progress` 过程落到 SpacetimeDB,`server-rs` 只做字符串入参与过程封装,不在 API 层维护额外进度状态。
|
|
||||||
- `server-rs` 的删除路由使用 Axum 标准 `Path(sessionId)` 提取参数,并在进入 SpacetimeDB 前做 owner-only 与空值校验,避免 handler 签名和过程入参漂移。
|
|
||||||
@@ -1,491 +0,0 @@
|
|||||||
# 自定义世界陶泥儿主输入与 AI 分工边界设计
|
|
||||||
|
|
||||||
更新时间:`2026-04-06`
|
|
||||||
|
|
||||||
## 0. 目标
|
|
||||||
|
|
||||||
这份文档回答一个非常关键的问题:
|
|
||||||
|
|
||||||
**在“低创作门槛、高创作自由度”的前提下,自定义世界里哪些内容应该交给陶泥儿主直接定义,哪些内容应该交给 AI 和系统完成。**
|
|
||||||
|
|
||||||
这里默认我们的陶泥儿主:
|
|
||||||
|
|
||||||
- 不需要有专业作家背景
|
|
||||||
- 不需要有专业游戏设计背景
|
|
||||||
- 但希望作品有明显个人风格,而不是只是在用一个会自动补全设定的模板工具
|
|
||||||
|
|
||||||
一句话目标:
|
|
||||||
|
|
||||||
**让陶泥儿主把精力放在“决定这个世界为什么值得被创作”,把 AI 用在“把这个世界展开、编译、铺开、校验、补足”。**
|
|
||||||
|
|
||||||
## 1. 总体结论
|
|
||||||
|
|
||||||
自定义世界的分工边界应该遵守 3 条硬原则:
|
|
||||||
|
|
||||||
1. 灵魂归陶泥儿主,杂活归 AI。
|
|
||||||
- 凡是决定作品气质、主题、冲突、人物关系、审美方向的内容,都应由陶泥儿主掌握。
|
|
||||||
|
|
||||||
2. 重点对象归陶泥儿主,长尾铺量归 AI。
|
|
||||||
- 陶泥儿主应重点塑造少量关键角色、关键地点、关键冲突、关键意象,而不是被迫手填几十个 NPC、几十个场景、几百条描述。
|
|
||||||
|
|
||||||
3. 决策归陶泥儿主,编译归 AI / 系统。
|
|
||||||
- 陶泥儿主负责说“这个世界要成为什么样”,AI / 系统负责把它编译成可运行的数据、规则、文本、关系钩子和运行时结构。
|
|
||||||
|
|
||||||
这意味着:
|
|
||||||
|
|
||||||
- 陶泥儿主应该主要编辑“高杠杆创作锚点”
|
|
||||||
- AI 应该主要承担“批量展开 + 结构编译 + 一致性维护 + 专业执行”
|
|
||||||
|
|
||||||
## 2. 什么内容应该交给陶泥儿主
|
|
||||||
|
|
||||||
真正应该交给陶泥儿主的,不是大量表格字段,而是下面这些会显著决定作品质量、且 AI 不擅长替代的内容。
|
|
||||||
|
|
||||||
## 2.1 世界核心命题
|
|
||||||
|
|
||||||
陶泥儿主应该直接定义:
|
|
||||||
|
|
||||||
- 这个世界的一句话设定
|
|
||||||
- 这个世界最吸引人的核心幻想
|
|
||||||
- 玩家来到这个世界,最想体验的感觉是什么
|
|
||||||
- 这个世界和常规同题材作品相比,最不同的地方是什么
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这是作品的创作方向盘
|
|
||||||
- 一旦这一层是空的,后面所有 AI 扩写都会变成“像一个世界”,而不是“这个世界”
|
|
||||||
|
|
||||||
## 2.2 主题、气质与边界
|
|
||||||
|
|
||||||
陶泥儿主应该直接定义:
|
|
||||||
|
|
||||||
- 主题关键词
|
|
||||||
- 情绪基调
|
|
||||||
- 审美偏好
|
|
||||||
- 禁忌内容 / 不希望出现的表达
|
|
||||||
- 可以接受的黑暗度、浪漫度、残酷度、神秘度
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这决定了 AI 后续生成时的“味道”
|
|
||||||
- 这类判断很难靠 AI 替代,因为它本质上不是信息补全,而是审美取舍
|
|
||||||
|
|
||||||
## 2.3 玩家身份与开局处境
|
|
||||||
|
|
||||||
陶泥儿主应该直接定义:
|
|
||||||
|
|
||||||
- 玩家扮演的是什么人
|
|
||||||
- 玩家一开始最缺什么、最想要什么
|
|
||||||
- 开局时玩家被卷入什么局面
|
|
||||||
- 玩家在这个世界里天然站在哪个位置上
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这决定了整个世界的观看视角
|
|
||||||
- 同一个世界,玩家视角不同,最终体验会完全不同
|
|
||||||
|
|
||||||
## 2.4 核心冲突与关键势力
|
|
||||||
|
|
||||||
陶泥儿主应该直接定义少量高价值内容:
|
|
||||||
|
|
||||||
- 世界当前最重要的 `2~4` 条明面冲突
|
|
||||||
- 世界背后最关键的 `1~3` 条暗面问题
|
|
||||||
- `2~6` 个关键势力
|
|
||||||
- 这些势力各自想要什么、害怕什么、互相卡住了什么
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 冲突结构决定世界是否“有戏”
|
|
||||||
- 势力关系是 AI 最容易写散、写平、写成百科介绍的部分
|
|
||||||
- 这一层由陶泥儿主把握,才能真正提高作品的辨识度
|
|
||||||
|
|
||||||
## 2.5 关键角色与关系张力
|
|
||||||
|
|
||||||
陶泥儿主应该直接定义少量关键角色,而不是所有 NPC。
|
|
||||||
|
|
||||||
建议重点交给陶泥儿主的,是:
|
|
||||||
|
|
||||||
- `3~8` 个关键角色
|
|
||||||
- 玩家与这些人的潜在关系
|
|
||||||
- 这些角色彼此之间的债、仇、秘密、误解、利益绑定
|
|
||||||
- 每个关键角色“表面上像什么、实际上压着什么”
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 角色关系是最能显著提升作品质量的部分之一
|
|
||||||
- 这也是 AI 最容易写得“完整但无味”的部分
|
|
||||||
- 陶泥儿主不需要写长篇背景,但应掌握这些角色真正的关系骨架
|
|
||||||
|
|
||||||
## 2.6 关键地点与空间记忆点
|
|
||||||
|
|
||||||
陶泥儿主应该直接定义:
|
|
||||||
|
|
||||||
- `4~12` 个关键地点 / 区域 / 地标
|
|
||||||
- 这些地方为什么重要
|
|
||||||
- 这些地方承载什么冲突、危险、秘密或情绪记忆
|
|
||||||
- 玩家第一次来到这里时应该感到什么
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- “地方感”是世界质量的重要来源
|
|
||||||
- 关键地点一旦成立,AI 后续才能稳定地生成周边事件、物件、NPC 和线索
|
|
||||||
|
|
||||||
## 2.7 标志性意象、物件、怪物、制度与规则
|
|
||||||
|
|
||||||
陶泥儿主应该优先控制世界里最能代表它的东西:
|
|
||||||
|
|
||||||
- 标志性物件
|
|
||||||
- 标志性怪物 / 生物
|
|
||||||
- 标志性能力体系 / 修炼体系 / 技术体系
|
|
||||||
- 标志性社会制度 / 宗教 / 仪式 / 禁忌
|
|
||||||
- 世界的硬规则
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这些内容决定世界的“手感”
|
|
||||||
- 它们不是普通细节,而是会反复影响命名、剧情、视觉、对话与玩法解释的母题
|
|
||||||
|
|
||||||
## 2.8 陶泥儿主应直接控制的“禁止事项”
|
|
||||||
|
|
||||||
陶泥儿主必须能明确锁定:
|
|
||||||
|
|
||||||
- 什么绝对不能改
|
|
||||||
- 什么不能被 AI 自动扩写到别的方向
|
|
||||||
- 哪些角色、地点、关系、设定是核心锚点
|
|
||||||
- 哪些内容允许 AI 自由发挥,哪些只能在锚点附近变体
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 高自由度不等于所有内容都开放漂移
|
|
||||||
- 如果没有“锁定机制”,AI 会把陶泥儿主真正关心的内容稀释掉
|
|
||||||
|
|
||||||
## 3. 什么内容应该交给 AI 和系统
|
|
||||||
|
|
||||||
应该交给 AI 的,不是“重要内容”,而是“重要内容之外的大量展开、编译、补缝、校验与专业执行”。
|
|
||||||
|
|
||||||
## 3.1 批量生成的长尾内容
|
|
||||||
|
|
||||||
应该主要交给 AI:
|
|
||||||
|
|
||||||
- 普通 NPC
|
|
||||||
- 路人、商贩、巡逻者、村民、杂兵
|
|
||||||
- 次级场景
|
|
||||||
- 场景支线事件
|
|
||||||
- 大量普通物品
|
|
||||||
- 世界的长尾命名与描述
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这些内容数量大、重复度高
|
|
||||||
- 它们需要“贴合世界”,但不需要都由陶泥儿主逐个手写
|
|
||||||
- AI 很适合做“围绕锚点的批量铺量”
|
|
||||||
|
|
||||||
## 3.2 从创作锚点到系统结构的编译
|
|
||||||
|
|
||||||
应该交给 AI / 系统:
|
|
||||||
|
|
||||||
- 从自然语言世界设定中提取题材词汇
|
|
||||||
- 从关键冲突中编译出世界叙事图谱
|
|
||||||
- 从关键角色卡编译出角色叙事档案
|
|
||||||
- 从陶泥儿主输入里自动生成标签、钩子、隐藏线索、章节摘要
|
|
||||||
- 从地点和关系中编译出场景连接、事件触发和叙事回响
|
|
||||||
|
|
||||||
对应当前仓库,下面这些结构更适合由 AI / 系统生成,而不是让玩家直接编辑:
|
|
||||||
|
|
||||||
- `ThemePack`
|
|
||||||
- `WorldStoryGraph`
|
|
||||||
- `ActorNarrativeProfile`
|
|
||||||
- `KnowledgeFact`
|
|
||||||
- `VisibilitySlice`
|
|
||||||
- `SceneNarrativeDirective`
|
|
||||||
- `CarrierStoryFingerprint`
|
|
||||||
- `ThreadContract`
|
|
||||||
- `StorySignal`
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这些是运行时结构,不是陶泥儿主真正想表达的作品内容
|
|
||||||
- 直接暴露给玩家,会把创作过程变成专业数据填表
|
|
||||||
|
|
||||||
## 3.3 专业化、规则化的任务
|
|
||||||
|
|
||||||
应该交给 AI / 系统:
|
|
||||||
|
|
||||||
- 数值平衡
|
|
||||||
- 标签归纳
|
|
||||||
- 稀有度预算
|
|
||||||
- 初始技能与初始物品的批量配置
|
|
||||||
- build 方向匹配
|
|
||||||
- 地图连接补全
|
|
||||||
- 触发条件与推进信号编译
|
|
||||||
- 背景章节拆分与 teaser 生成
|
|
||||||
- 运行时物件命名与叙事描述的变体生成
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这些工作要么重复、要么专业、要么容易做脏活累活
|
|
||||||
- 让非专业陶泥儿主处理,会显著提高门槛,却不一定显著提高质量
|
|
||||||
|
|
||||||
## 3.4 一致性、纠错与查漏补缺
|
|
||||||
|
|
||||||
应该交给 AI / 系统持续处理:
|
|
||||||
|
|
||||||
- 世界设定冲突检查
|
|
||||||
- 角色关系矛盾检查
|
|
||||||
- 同名 / 重复 / 设定撞车检查
|
|
||||||
- 信息越权泄露检查
|
|
||||||
- prompt 裁剪
|
|
||||||
- 风格一致性检查
|
|
||||||
- “这个角色/地点/物件是否真的和世界主线有关”的弱关联检查
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这是 AI 比人更适合做的“维护型工作”
|
|
||||||
- 它属于创作支持,不属于陶泥儿主必须亲手完成的创作
|
|
||||||
|
|
||||||
## 4. 最合理的边界不是二分法,而是三层分工
|
|
||||||
|
|
||||||
自定义世界最合理的结构,不是“玩家写”与“AI 写”的简单二选一,而是三层。
|
|
||||||
|
|
||||||
## 4.1 第一层:陶泥儿主必控层
|
|
||||||
|
|
||||||
这一层必须给陶泥儿主高自由度,且能被锁定:
|
|
||||||
|
|
||||||
- 世界核心命题
|
|
||||||
- 主题与气质
|
|
||||||
- 玩家身份与开局
|
|
||||||
- 核心冲突
|
|
||||||
- 关键势力
|
|
||||||
- 关键角色
|
|
||||||
- 关键地点
|
|
||||||
- 标志性物件 / 怪物 / 规则
|
|
||||||
- 禁止事项
|
|
||||||
|
|
||||||
这层的原则是:
|
|
||||||
|
|
||||||
**少而重。**
|
|
||||||
|
|
||||||
## 4.2 第二层:陶泥儿主可选强化层
|
|
||||||
|
|
||||||
这一层不应强制填写,但应该允许陶泥儿主继续深挖:
|
|
||||||
|
|
||||||
- 明线 / 暗线种子
|
|
||||||
- 角色之间的旧事
|
|
||||||
- 地点背后的旧伤
|
|
||||||
- 标志性物件的来历
|
|
||||||
- 关键角色的口头习惯、禁忌、执念
|
|
||||||
- 关键地点的视觉母题与情绪目标
|
|
||||||
|
|
||||||
这层的原则是:
|
|
||||||
|
|
||||||
**愿意细写的人可以拉高作品上限,不愿细写的人也不会被门槛卡住。**
|
|
||||||
|
|
||||||
## 4.3 第三层:AI 自动展开层
|
|
||||||
|
|
||||||
这一层默认交给 AI / 系统:
|
|
||||||
|
|
||||||
- 长尾 NPC
|
|
||||||
- 次级地点
|
|
||||||
- 章节拆分
|
|
||||||
- 初始技能
|
|
||||||
- 初始物品
|
|
||||||
- 标签与属性映射
|
|
||||||
- 任务 contract
|
|
||||||
- 物件叙事指纹
|
|
||||||
- 可见性裁剪
|
|
||||||
- 运行时导演指令
|
|
||||||
- 批量命名与文案变体
|
|
||||||
|
|
||||||
这层的原则是:
|
|
||||||
|
|
||||||
**AI 可以做多、做快、做杂,但不能越过第一层锁定内容。**
|
|
||||||
|
|
||||||
## 5. 具体模块的建议归属
|
|
||||||
|
|
||||||
| 模块 | 建议归属 | 陶泥儿主应控制什么 | AI / 系统应负责什么 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| 世界一句话设定、核心幻想、核心卖点 | 陶泥儿主直接控制 | 直接写、直接改、可锁定 | 给出备选表述和扩展方向 |
|
|
||||||
| 主题、基调、审美、禁忌 | 陶泥儿主直接控制 | 选择 / 改写 / 锁定 | 生成风格词、避雷词、提示词约束 |
|
|
||||||
| 玩家身份、开局处境、玩家目标 | 陶泥儿主直接控制 | 直接定义 | 补足开局钩子和初始叙事包装 |
|
|
||||||
| 关键势力与核心冲突 | 陶泥儿主控,AI 辅助 | 定义核心关系和立场 | 扩展冲突支路、生成世界线程 |
|
|
||||||
| 关键角色 | 陶泥儿主控,AI 辅助 | 定义角色骨架、关系张力、秘密方向 | 生成长背景、章节拆分、技能、物品、叙事档案 |
|
|
||||||
| 关键地点 | 陶泥儿主控,AI 辅助 | 定义地点意义、气氛、秘密 | 扩展场景细节、连接关系、遭遇分布 |
|
|
||||||
| 标志性物件 / 怪物 / 制度 / 规则 | 陶泥儿主控,AI 辅助 | 定义代表性要素与硬边界 | 扩展变体、命名、说明、运行时挂钩 |
|
|
||||||
| 普通 NPC / 路人 / 杂兵 / 次级地点 | 主要交给 AI | 仅在需要时抽查或替换 | 批量生成与风格保持 |
|
|
||||||
| 角色长背景、章节 teaser、context snippet | 主要交给 AI | 陶泥儿主只改关键角色即可 | 自动拆章、压缩、解锁节奏整理 |
|
|
||||||
| 技能、初始物品、标签、构筑倾向 | 主要交给 AI / 系统 | 提供偏好或少量 override | 按角色和世界规则自动编译 |
|
|
||||||
| 世界图谱、知识事实、可见性、导演指令 | AI / 系统内部层 | 不应默认暴露给玩家 | 运行时编译与维护 |
|
|
||||||
| 一致性检查、冲突检查、越权检查 | AI / 系统内部层 | 查看报告、决定是否采纳修改 | 自动扫描并提出修正建议 |
|
|
||||||
|
|
||||||
## 6. 不应该要求玩家直接填写的字段
|
|
||||||
|
|
||||||
为了真正做到低门槛,下面这些内容不应直接以“专业字段”形式强迫玩家填写。
|
|
||||||
|
|
||||||
## 6.1 不应该要求玩家手填原始数值
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- `initialAffinity`
|
|
||||||
- 精确数值型 build 倾向
|
|
||||||
- 复杂掉落预算
|
|
||||||
|
|
||||||
更合理的做法是让陶泥儿主填写直觉表达,例如:
|
|
||||||
|
|
||||||
- `初见就戒备`
|
|
||||||
- `容易合作`
|
|
||||||
- `这里非常危险`
|
|
||||||
- `偏爆发型`
|
|
||||||
|
|
||||||
再由系统编译成运行时数值。
|
|
||||||
|
|
||||||
## 6.2 不应该要求玩家手填技术型结构
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- `tags`
|
|
||||||
- `attributeSchema`
|
|
||||||
- `ThemePack`
|
|
||||||
- `WorldStoryGraph`
|
|
||||||
- `VisibilitySlice`
|
|
||||||
- `SceneNarrativeDirective`
|
|
||||||
- `ThreadContract`
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这些字段属于系统运行结构,不属于陶泥儿主自然的创作语言
|
|
||||||
- 直接让玩家填,会把工具变成只有懂系统的人才能用
|
|
||||||
|
|
||||||
## 6.3 不应该要求玩家逐个补完所有人物设定字段
|
|
||||||
|
|
||||||
当前 `CustomWorldRoleProfile` 里这些字段:
|
|
||||||
|
|
||||||
- `backstory`
|
|
||||||
- `personality`
|
|
||||||
- `motivation`
|
|
||||||
- `combatStyle`
|
|
||||||
- `backstoryReveal`
|
|
||||||
- `skills`
|
|
||||||
- `initialItems`
|
|
||||||
|
|
||||||
更适合的做法不是全部让玩家手写,而是先让玩家填写更自然的“角色卡”:
|
|
||||||
|
|
||||||
- 这个人表面上是什么样
|
|
||||||
- 这个人真正想要什么
|
|
||||||
- 这个人最不想被提到什么
|
|
||||||
- 这个人和玩家之间最可能形成什么关系
|
|
||||||
- 这个人和哪个地点 / 物件 / 旧事绑得最紧
|
|
||||||
|
|
||||||
再由 AI / 系统编译成当前结构。
|
|
||||||
|
|
||||||
## 7. 推荐的创作输入形态
|
|
||||||
|
|
||||||
要让非专业陶泥儿主也能高自由度创作,输入形态必须改成“自然语言创作卡”,而不是“系统字段表单”。
|
|
||||||
|
|
||||||
## 7.1 世界层卡片
|
|
||||||
|
|
||||||
建议至少有这些卡片:
|
|
||||||
|
|
||||||
1. 世界一句话
|
|
||||||
2. 主题与气质
|
|
||||||
3. 玩家是谁
|
|
||||||
4. 核心冲突
|
|
||||||
5. 关键势力
|
|
||||||
6. 关键角色
|
|
||||||
7. 关键地点
|
|
||||||
8. 标志性要素
|
|
||||||
9. 禁止事项
|
|
||||||
|
|
||||||
## 7.2 每张卡片都允许 3 种输入方式
|
|
||||||
|
|
||||||
1. 一句话自由输入
|
|
||||||
- 适合低门槛陶泥儿主
|
|
||||||
|
|
||||||
2. 标签 / 选项 / 语气滑条
|
|
||||||
- 适合不想写太多字的陶泥儿主
|
|
||||||
|
|
||||||
3. 高级补充
|
|
||||||
- 适合愿意继续深挖的人
|
|
||||||
|
|
||||||
这样才能做到:
|
|
||||||
|
|
||||||
- 不会逼着用户写长文
|
|
||||||
- 但也不会限制愿意创作的人继续把世界做深
|
|
||||||
|
|
||||||
## 7.3 必须支持“锁定”与“局部重生成”
|
|
||||||
|
|
||||||
这是高创作自由度里非常关键的一点。
|
|
||||||
|
|
||||||
陶泥儿主应当能:
|
|
||||||
|
|
||||||
- 锁定一个角色
|
|
||||||
- 锁定一个地点
|
|
||||||
- 锁定一条冲突
|
|
||||||
- 只重生成未锁定部分
|
|
||||||
- 围绕锁定内容重写其余世界
|
|
||||||
|
|
||||||
否则陶泥儿主每次调用 AI,都会有“好不容易想好的东西被洗掉”的感受。
|
|
||||||
|
|
||||||
## 8. 面向当前仓库的结构映射建议
|
|
||||||
|
|
||||||
为了便于后续落实现有系统,这份边界建议可以直接映射到当前结构:
|
|
||||||
|
|
||||||
## 8.1 陶泥儿主输入层
|
|
||||||
|
|
||||||
建议主要映射到:
|
|
||||||
|
|
||||||
- `CustomWorldProfile.settingText`
|
|
||||||
- `CustomWorldProfile.name`
|
|
||||||
- `CustomWorldProfile.subtitle`
|
|
||||||
- `CustomWorldProfile.summary`
|
|
||||||
- `CustomWorldProfile.tone`
|
|
||||||
- `CustomWorldProfile.playerGoal`
|
|
||||||
- `CustomWorldProfile.majorFactions`
|
|
||||||
- `CustomWorldProfile.coreConflicts`
|
|
||||||
|
|
||||||
以及关键角色、关键地点的创作卡输入。
|
|
||||||
|
|
||||||
## 8.2 AI 编译层
|
|
||||||
|
|
||||||
由 AI / 系统从陶泥儿主输入自动补出:
|
|
||||||
|
|
||||||
- `themePack`
|
|
||||||
- `storyGraph`
|
|
||||||
- `knowledgeFacts`
|
|
||||||
- `threadContracts`
|
|
||||||
- 每个关键角色的 `narrativeProfile`
|
|
||||||
- 每个角色的 `backstoryReveal`
|
|
||||||
- 每个角色的 `skills`
|
|
||||||
- 每个角色的 `initialItems`
|
|
||||||
|
|
||||||
## 8.3 运行时支持层
|
|
||||||
|
|
||||||
运行时继续由 AI / 系统维护:
|
|
||||||
|
|
||||||
- `VisibilitySlice`
|
|
||||||
- `SceneNarrativeDirective`
|
|
||||||
- `CarrierStoryFingerprint`
|
|
||||||
- `StorySignal`
|
|
||||||
|
|
||||||
这些内容应该是“系统如何把世界跑起来”,不是“陶泥儿主必须亲手写完的创作内容”。
|
|
||||||
|
|
||||||
## 9. 产品层面的最终结论
|
|
||||||
|
|
||||||
如果我们的目标真的是“低创作门槛、高创作自由度”,那么自定义世界不应该做成一个要求用户:
|
|
||||||
|
|
||||||
- 填很多字段
|
|
||||||
- 写很多长文
|
|
||||||
- 理解很多系统结构
|
|
||||||
- 自己负责平衡、命名、拆章节、补标签、补长尾内容
|
|
||||||
|
|
||||||
的专业编辑器。
|
|
||||||
|
|
||||||
它应该做成这样:
|
|
||||||
|
|
||||||
1. 陶泥儿主决定世界的灵魂锚点。
|
|
||||||
2. 陶泥儿主重点塑造少量关键人、关键地、关键冲突、关键物。
|
|
||||||
3. AI 围绕这些锚点批量展开长尾内容。
|
|
||||||
4. 系统把这些内容编译成可运行的图谱、可见性、任务、物件和关系结构。
|
|
||||||
5. 陶泥儿主随时可以锁定核心创意,并局部重生成其余部分。
|
|
||||||
|
|
||||||
一句话收束:
|
|
||||||
|
|
||||||
**陶泥儿主应该写“这个世界为什么动人”,AI 应该负责“让这个世界长出来并跑起来”。**
|
|
||||||
@@ -1,721 +0,0 @@
|
|||||||
# 自定义世界创作中手填、AI 可改与系统托管的平衡设计
|
|
||||||
|
|
||||||
更新时间:`2026-04-12`
|
|
||||||
|
|
||||||
## 0. 文档目标
|
|
||||||
|
|
||||||
这份文档用于回答一个更具体的问题:
|
|
||||||
|
|
||||||
**参考 RPG 专业剧情策划全流程后,在自定义世界创作工具里,哪些设定必须要求陶泥儿主手动填写,哪些设定应该由 AI 先生成但允许陶泥儿主修改,哪些设定应完全交给系统托管,才能在“尽可能降低门槛”和“尽可能提高作品质量”之间取一个平衡。**
|
|
||||||
|
|
||||||
这份文档不再只回答“陶泥儿主与 AI 怎么分工”,而是进一步把创作工作台收束成一个更可执行的三层输入结构:
|
|
||||||
|
|
||||||
1. 陶泥儿主必须手填的高杠杆锚点
|
|
||||||
2. AI 先生成、陶泥儿主可修改的内容草稿层
|
|
||||||
3. 系统自动编译和运行的托管层
|
|
||||||
|
|
||||||
一句话结论:
|
|
||||||
|
|
||||||
**让陶泥儿主只负责决定作品的灵魂、视角、冲突和关系钩子,让 AI 负责把这些锚点展开成可编辑的剧情草稿,让系统负责把草稿编译成可运行的结构。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 设计目标
|
|
||||||
|
|
||||||
这套平衡设计要同时满足 5 个目标:
|
|
||||||
|
|
||||||
1. 低门槛
|
|
||||||
- 新陶泥儿主不需要写长篇设定,也不需要理解底层系统结构。
|
|
||||||
|
|
||||||
2. 高辨识度
|
|
||||||
- 陶泥儿主写出来的世界,不应该只是“像一个世界”,而应该保留明显的个人方向。
|
|
||||||
|
|
||||||
3. 高可编辑性
|
|
||||||
- AI 不能一次生成后就不可控,陶泥儿主必须能改关键对象、关键关系和关键章节。
|
|
||||||
|
|
||||||
4. 高稳定性
|
|
||||||
- 任务、章节、关系、物件和可见性等运行层结构不能依赖陶泥儿主手填专业字段。
|
|
||||||
|
|
||||||
5. 可扩展
|
|
||||||
- 愿意深挖的陶泥儿主可以继续补充世界上限,不愿深挖的人也能快速产出质量不错的作品。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 核心原则
|
|
||||||
|
|
||||||
## 2.1 陶泥儿主手填的必须是“高杠杆决策”,不是“高工作量字段”
|
|
||||||
|
|
||||||
应该要求陶泥儿主手填的内容,必须同时满足下面两个条件:
|
|
||||||
|
|
||||||
1. 会显著决定作品气质和辨识度
|
|
||||||
2. AI 很难替代判断
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- 世界一句话
|
|
||||||
- 玩家身份
|
|
||||||
- 核心冲突
|
|
||||||
- 关系钩子
|
|
||||||
- 禁忌边界
|
|
||||||
|
|
||||||
而不应该强制手填:
|
|
||||||
|
|
||||||
- 全量 NPC
|
|
||||||
- 全量场景
|
|
||||||
- 技能列表
|
|
||||||
- 初始物品
|
|
||||||
- 章节拆分
|
|
||||||
- 运行时信号结构
|
|
||||||
|
|
||||||
## 2.2 陶泥儿主可改层应该承接“专业策划初稿”,而不是“原始底层字段”
|
|
||||||
|
|
||||||
AI 生成后允许陶泥儿主修改的,不应该是一堆技术型字段,而应该是一批已经成形的内容卡片,例如:
|
|
||||||
|
|
||||||
- 关键角色卡
|
|
||||||
- 势力卡
|
|
||||||
- 关键地点卡
|
|
||||||
- 主线章节卡
|
|
||||||
- 支线种子卡
|
|
||||||
- 场景章节卡
|
|
||||||
- 标志性物件卡
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
**AI 先给陶泥儿主一个像策划初稿的东西,而不是给一堆系统字段让陶泥儿主自己拼。**
|
|
||||||
|
|
||||||
## 2.3 系统托管层必须彻底隐藏专业运行结构
|
|
||||||
|
|
||||||
以下这类结构不应该默认要求陶泥儿主理解或编辑:
|
|
||||||
|
|
||||||
- `ThemePack`
|
|
||||||
- `WorldStoryGraph`
|
|
||||||
- `KnowledgeFact`
|
|
||||||
- `VisibilitySlice`
|
|
||||||
- `SceneNarrativeDirective`
|
|
||||||
- `StorySignal`
|
|
||||||
- `ThreadContract`
|
|
||||||
- 数值预算
|
|
||||||
- 稀有度映射
|
|
||||||
- 掉落和 build 权重
|
|
||||||
|
|
||||||
陶泥儿主应该编辑的是自然语言与内容卡,而不是运行时图结构。
|
|
||||||
|
|
||||||
## 2.4 先少量必填,再逐层展开
|
|
||||||
|
|
||||||
最合理的工作流不是“开局填一大页表”,而是:
|
|
||||||
|
|
||||||
```text
|
|
||||||
先填最小必填卡
|
|
||||||
-> AI 生成世界初稿
|
|
||||||
-> 陶泥儿主修改关键对象
|
|
||||||
-> 系统继续展开长尾
|
|
||||||
-> 陶泥儿主决定是否进入高级补充
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2.5 默认清爽,深度能力后置
|
|
||||||
|
|
||||||
结合当前项目约束,创作工作台默认不要把规则说明、底层字段、专业术语堆到 UI 面板里。
|
|
||||||
|
|
||||||
应该做到:
|
|
||||||
|
|
||||||
1. 默认只展示最有创作价值的卡片
|
|
||||||
2. 高级内容折叠到后置面板
|
|
||||||
3. 大多数系统结构不直接暴露
|
|
||||||
4. 移动端也能完成最小创作闭环
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 最终建议:三层分工
|
|
||||||
|
|
||||||
## 3.1 第一层:必须要求陶泥儿主手动填写
|
|
||||||
|
|
||||||
这一层只保留最影响作品质量的高杠杆锚点,建议默认强制填写 6 张卡。
|
|
||||||
|
|
||||||
## 3.2 第二层:AI 生成后支持陶泥儿主修改
|
|
||||||
|
|
||||||
这一层由 AI 根据第一层锚点自动展开成专业剧情策划初稿,陶泥儿主可以逐项修改、锁定、局部重生成。
|
|
||||||
|
|
||||||
## 3.3 第三层:其余都交给系统
|
|
||||||
|
|
||||||
这一层是把前两层编译成可运行游戏结构所需的系统字段、数值和运行时指令,默认不要求陶泥儿主处理。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 最低门槛方案:只强制手填 6 张卡
|
|
||||||
|
|
||||||
如果目标是尽可能降低门槛,同时又保留作品辨识度,建议只强制陶泥儿主填写以下 6 张卡。
|
|
||||||
|
|
||||||
## 4.1 卡 1:世界一句话与核心幻想
|
|
||||||
|
|
||||||
陶泥儿主必须手填:
|
|
||||||
|
|
||||||
- 世界一句话设定
|
|
||||||
- 玩家来到这个世界最想体验的感觉
|
|
||||||
- 这个世界和同类题材相比最不同的一点
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这是作品的方向盘
|
|
||||||
- 这是后续 AI 所有扩写的总锚点
|
|
||||||
|
|
||||||
推荐输入形态:
|
|
||||||
|
|
||||||
- 一句话文本
|
|
||||||
- `1~3` 个体验关键词
|
|
||||||
|
|
||||||
## 4.2 卡 2:玩家身份与开局困境
|
|
||||||
|
|
||||||
陶泥儿主必须手填:
|
|
||||||
|
|
||||||
- 玩家是谁
|
|
||||||
- 玩家开局最缺什么
|
|
||||||
- 玩家为什么必须进入这场故事
|
|
||||||
- 玩家天然站在什么位置上
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 玩家视角不清,后面所有剧情都会发散
|
|
||||||
- 这是主线入口、关系入口和任务入口的共同基础
|
|
||||||
|
|
||||||
## 4.3 卡 3:主题气质与禁忌边界
|
|
||||||
|
|
||||||
陶泥儿主必须手填:
|
|
||||||
|
|
||||||
- 主题关键词
|
|
||||||
- 情绪基调
|
|
||||||
- 审美方向
|
|
||||||
- 禁止出现或尽量避免的内容
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这决定世界“是什么味道”
|
|
||||||
- 这也是避免 AI 跑偏最有效的一层
|
|
||||||
|
|
||||||
推荐输入形态:
|
|
||||||
|
|
||||||
- 标签选择
|
|
||||||
- 语气滑条
|
|
||||||
- 一小段补充说明
|
|
||||||
|
|
||||||
## 4.4 卡 4:核心冲突
|
|
||||||
|
|
||||||
陶泥儿主必须手填:
|
|
||||||
|
|
||||||
- 当前世界最重要的 `1~3` 个明面冲突
|
|
||||||
- 至少 `1` 个隐藏问题或暗面危机
|
|
||||||
- 玩家最先接触的是哪条冲突
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 没有冲突,世界就只剩设定
|
|
||||||
- 没有暗面问题,后续剧情就难以产生层次和改判
|
|
||||||
|
|
||||||
## 4.5 卡 5:关键关系钩子
|
|
||||||
|
|
||||||
这里不强制陶泥儿主一开始填写完整角色档案,只要求填写更高杠杆的“关系骨架”。
|
|
||||||
|
|
||||||
陶泥儿主必须手填:
|
|
||||||
|
|
||||||
- `2~4` 条关键关系钩子
|
|
||||||
- 每条钩子至少说明:
|
|
||||||
- 谁和谁有关
|
|
||||||
- 关系是债、仇、误解、旧情、利用还是血缘
|
|
||||||
- 这条关系里压着什么秘密或代价
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 作品的人味和记忆点主要来自关系张力
|
|
||||||
- 关系钩子比完整角色长文更容易写,也更高杠杆
|
|
||||||
|
|
||||||
## 4.6 卡 6:标志性要素与硬规则
|
|
||||||
|
|
||||||
陶泥儿主必须手填:
|
|
||||||
|
|
||||||
- `2~5` 个标志性要素
|
|
||||||
- 物件
|
|
||||||
- 怪物
|
|
||||||
- 制度
|
|
||||||
- 仪式
|
|
||||||
- 能力体系
|
|
||||||
- 社会规则
|
|
||||||
- 至少 `1~3` 条不能被 AI 擅自改写的硬规则
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这决定世界是否有独特手感
|
|
||||||
- 后续命名、剧情、物件和场景都会反复依赖这些母题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 不建议强制手填,但应该让 AI 生成后支持陶泥儿主修改的设定
|
|
||||||
|
|
||||||
这一层是平衡“低门槛”和“高质量”的关键。
|
|
||||||
|
|
||||||
陶泥儿主不需要从零填写这些内容,但 AI 生成后必须能看、能改、能锁定、能局部重生成。
|
|
||||||
|
|
||||||
## 5.1 世界外观层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- 世界名称
|
|
||||||
- 副标题
|
|
||||||
- 世界简介
|
|
||||||
- 宣传短句
|
|
||||||
- 主题母题摘要
|
|
||||||
- 命名风格建议
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这些内容影响观感,但不值得强制占用开局填写成本
|
|
||||||
|
|
||||||
## 5.2 势力层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- `2~6` 个关键势力
|
|
||||||
- 每个势力的公开目标
|
|
||||||
- 每个势力的隐藏目标
|
|
||||||
- 势力间的主要矛盾
|
|
||||||
- 代表人物
|
|
||||||
- 势力资源与禁忌
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 势力很重要,但让新手一开始手写完整势力表太重
|
|
||||||
- 更合理的做法是让 AI 基于核心冲突先出草稿,再由陶泥儿主修正
|
|
||||||
|
|
||||||
## 5.3 关键角色层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- 关键角色姓名
|
|
||||||
- 外显身份
|
|
||||||
- 公众面具
|
|
||||||
- 当前压力
|
|
||||||
- 表面目标
|
|
||||||
- 真实目标
|
|
||||||
- 背景旧事
|
|
||||||
- 禁区
|
|
||||||
- 与玩家关系方向
|
|
||||||
- 角色个人线阶段
|
|
||||||
- 背景章节 teaser
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 陶泥儿主已经通过“关系钩子”给出最关键的人物骨架
|
|
||||||
- AI 负责把钩子展开成可编辑角色卡,陶泥儿主再做精修
|
|
||||||
|
|
||||||
## 5.4 关键地点层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- `4~10` 个关键地点
|
|
||||||
- 每个地点的功能定位
|
|
||||||
- 气氛和视觉母题
|
|
||||||
- 涉及的线程和秘密
|
|
||||||
- 首次进入时的情绪目标
|
|
||||||
- 关联角色和标志性载体
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 地点是世界感的重要来源
|
|
||||||
- 但新陶泥儿主未必能一开始就写出完整地点网络
|
|
||||||
|
|
||||||
## 5.5 世界线程层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- 明线线程
|
|
||||||
- 暗线线程
|
|
||||||
- 旧事伤痕
|
|
||||||
- 误导信息
|
|
||||||
- 主要 handoff
|
|
||||||
- 阶段揭示节奏
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 线程是专业剧情结构,适合 AI 先搭骨架
|
|
||||||
- 但陶泥儿主必须有权修正哪条线更重要、哪条线该隐藏
|
|
||||||
|
|
||||||
## 5.6 主线章节层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- 幕结构建议
|
|
||||||
- 章节标题
|
|
||||||
- 章节承诺
|
|
||||||
- 转折设计
|
|
||||||
- 高潮行动
|
|
||||||
- 章节 handoff
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 陶泥儿主已经给出了世界目标、冲突和关系
|
|
||||||
- AI 可以先把它们编成主线章节初稿
|
|
||||||
- 陶泥儿主再选择保留、删减或重排
|
|
||||||
|
|
||||||
## 5.7 支线、角色线、阵营线层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- 支线种子
|
|
||||||
- 角色线阶段事件
|
|
||||||
- 阵营线分歧点
|
|
||||||
- 私聊或同伴互动节点
|
|
||||||
- 支线和主线的互文关系
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这是最适合 AI 拉开内容宽度的部分
|
|
||||||
- 也是最需要陶泥儿主局部精修的部分
|
|
||||||
|
|
||||||
## 5.8 场景章节层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- 场景章节标题
|
|
||||||
- `opening / expansion / turning_point / climax / aftermath`
|
|
||||||
- 情感锚点 NPC
|
|
||||||
- 现场压力
|
|
||||||
- 转折信息
|
|
||||||
- 局部收束
|
|
||||||
- 下一跳 handoff
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 当前项目已经在走“场景 = 章节单元”的方向
|
|
||||||
- 这层非常适合 AI 编排出第一版,再由陶泥儿主补强记忆点
|
|
||||||
|
|
||||||
## 5.9 叙事载体层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- 标志性物件
|
|
||||||
- 文书
|
|
||||||
- 残痕
|
|
||||||
- 证物
|
|
||||||
- 场景遗物
|
|
||||||
- 怪物命名及其故事指向
|
|
||||||
|
|
||||||
陶泥儿主主要修改:
|
|
||||||
|
|
||||||
- 哪些载体最重要
|
|
||||||
- 哪些载体和哪条线程绑定
|
|
||||||
- 哪些载体需要更强的个人风格
|
|
||||||
|
|
||||||
## 5.10 文案包装层
|
|
||||||
|
|
||||||
建议 AI 先生成后可改:
|
|
||||||
|
|
||||||
- 角色简介
|
|
||||||
- 地点短描述
|
|
||||||
- 势力介绍
|
|
||||||
- 章节标题候选
|
|
||||||
- 任务标题与简述
|
|
||||||
- 物件命名候选
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这些内容适合 AI 批量铺量
|
|
||||||
- 陶泥儿主只需要挑、改、锁定,不必从零起草
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 其余设定应交给系统托管
|
|
||||||
|
|
||||||
以下内容不建议默认暴露给陶泥儿主编辑,应由系统根据前两层自动编译和维护。
|
|
||||||
|
|
||||||
## 6.1 题材与术语编译层
|
|
||||||
|
|
||||||
交给系统:
|
|
||||||
|
|
||||||
- `ThemePack`
|
|
||||||
- 题材词汇表
|
|
||||||
- 命名模式映射
|
|
||||||
- 母题标签映射
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这是系统为了统一生成风格而维护的内部层
|
|
||||||
|
|
||||||
## 6.2 世界图谱运行层
|
|
||||||
|
|
||||||
交给系统:
|
|
||||||
|
|
||||||
- `WorldStoryGraph`
|
|
||||||
- `KnowledgeFact`
|
|
||||||
- 事实 id
|
|
||||||
- 线程内部关联
|
|
||||||
- 旧事与角色的细粒度映射
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 陶泥儿主要的是“故事线能对”,不是维护图数据库
|
|
||||||
|
|
||||||
## 6.3 可见性和 prompt 裁剪层
|
|
||||||
|
|
||||||
交给系统:
|
|
||||||
|
|
||||||
- `VisibilitySlice`
|
|
||||||
- 禁止注入信息列表
|
|
||||||
- 当前可说信息
|
|
||||||
- 推测信息
|
|
||||||
- 越权泄露检查
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这层必须稳定、严格、自动化
|
|
||||||
- 不适合依赖陶泥儿主手动维护
|
|
||||||
|
|
||||||
## 6.4 运行时导演层
|
|
||||||
|
|
||||||
交给系统:
|
|
||||||
|
|
||||||
- `SceneNarrativeDirective`
|
|
||||||
- 节奏推进指令
|
|
||||||
- 披露预算
|
|
||||||
- 当前主压力判断
|
|
||||||
- 当前前景角色和前景载体
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这是剧情运行时调度逻辑,不是创作表达层
|
|
||||||
|
|
||||||
## 6.5 任务编译层
|
|
||||||
|
|
||||||
交给系统:
|
|
||||||
|
|
||||||
- `ThreadContract`
|
|
||||||
- `StorySignal`
|
|
||||||
- step id
|
|
||||||
- step 类型映射
|
|
||||||
- 触发条件编译
|
|
||||||
- 结算条件编译
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
- 陶泥儿主可以编辑“任务卡”和“章节卡”
|
|
||||||
- 但不应默认编辑底层 contract 结构
|
|
||||||
|
|
||||||
## 6.6 数值与配置层
|
|
||||||
|
|
||||||
交给系统:
|
|
||||||
|
|
||||||
- 技能数值
|
|
||||||
- 初始物品预算
|
|
||||||
- 稀有度分布
|
|
||||||
- 掉落权重
|
|
||||||
- build 标签映射
|
|
||||||
- 关系数值初始值
|
|
||||||
- 敌对强度预算
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
- 陶泥儿主可以给“偏向”
|
|
||||||
- 系统负责编译成具体数值
|
|
||||||
|
|
||||||
## 6.7 QA 与一致性层
|
|
||||||
|
|
||||||
交给系统:
|
|
||||||
|
|
||||||
- 设定冲突检查
|
|
||||||
- 同名检查
|
|
||||||
- 风格漂移检查
|
|
||||||
- 关系矛盾检查
|
|
||||||
- 主线与支线弱关联检查
|
|
||||||
- 未解锁信息泄露检查
|
|
||||||
- 长尾内容覆盖率检查
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
- 这属于高频维护型工作,最适合系统自动做
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 按模块划分的最终边界表
|
|
||||||
|
|
||||||
| 模块 | 必须手填 | AI 生成后可改 | 系统托管 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| 世界定位 | 世界一句话、核心幻想、差异点 | 世界名称、副标题、简介 | 题材词汇编译 |
|
|
||||||
| 玩家视角 | 玩家身份、开局困境、初始动机 | 开局剧情摘要、开局目标文案 | 开局状态初始化 |
|
|
||||||
| 主题边界 | 主题、气质、禁忌、硬边界 | 主题母题摘要、风格建议 | 风格约束编译 |
|
|
||||||
| 核心冲突 | 明面冲突、隐藏危机 | 线程草稿、旧事伤痕、误导设计 | 世界图谱、事实映射 |
|
|
||||||
| 关系骨架 | 关键关系钩子 | 关键角色卡、个人线阶段、背景章节 teaser | 关系数值、解锁条件 |
|
|
||||||
| 标志性要素 | 标志物、怪物、制度、规则 | 标志载体卡、命名候选、衍生变体 | 物件指纹、掉落映射 |
|
|
||||||
| 势力 | 不强制首轮手填 | 势力卡、代表人物、势力冲突 | 阵营状态映射 |
|
|
||||||
| 地点 | 不强制首轮手填 | 关键地点卡、场景网络、氛围描述 | 场景连接编译 |
|
|
||||||
| 主线 | 不强制首轮手写完整主线 | 幕结构、章节卡、高潮与 handoff | 章节状态编译 |
|
|
||||||
| 支线/角色线 | 不强制首轮手写完整矩阵 | 支线种子、角色线事件、阵营线分歧 | 任务 contract 编译 |
|
|
||||||
| 场景章节 | 不强制首轮手写全量章节 | 场景章节卡、阶段内容、章节载体 | signal 与导演层 |
|
|
||||||
| 运行时结构 | 不建议陶泥儿主接触 | 不建议默认编辑 | 可见性、导演、信号、编译、QA |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 推荐创作流程
|
|
||||||
|
|
||||||
## 8.1 第一步:只填写最小必填集
|
|
||||||
|
|
||||||
陶泥儿主只需要完成:
|
|
||||||
|
|
||||||
1. 世界一句话与核心幻想
|
|
||||||
2. 玩家身份与开局困境
|
|
||||||
3. 主题气质与禁忌边界
|
|
||||||
4. 核心冲突
|
|
||||||
5. 关键关系钩子
|
|
||||||
6. 标志性要素与硬规则
|
|
||||||
|
|
||||||
这一步应控制在:
|
|
||||||
|
|
||||||
- `5~15` 分钟
|
|
||||||
- `200~800` 字
|
|
||||||
- 或更少文字配合标签选择
|
|
||||||
|
|
||||||
## 8.2 第二步:AI 生成“策划初稿包”
|
|
||||||
|
|
||||||
系统根据最小输入,生成一份结构化初稿包,建议至少包括:
|
|
||||||
|
|
||||||
1. 世界标题与摘要
|
|
||||||
2. `3~5` 个关键角色卡
|
|
||||||
3. `2~4` 个势力卡
|
|
||||||
4. `4~8` 个关键地点卡
|
|
||||||
5. `3~5` 条世界线程
|
|
||||||
6. `3~6` 个场景章节卡
|
|
||||||
7. 一批支线种子和标志性载体
|
|
||||||
|
|
||||||
这里的重点不是一次补满全世界,而是先形成一个像样的内容骨架。
|
|
||||||
|
|
||||||
## 8.3 第三步:陶泥儿主只精修高价值卡片
|
|
||||||
|
|
||||||
建议默认优先让陶泥儿主编辑这 4 类卡片:
|
|
||||||
|
|
||||||
1. 关键角色
|
|
||||||
2. 核心冲突与线程
|
|
||||||
3. 关键地点
|
|
||||||
4. 主线第一幕或前几个场景章节
|
|
||||||
|
|
||||||
这样能以最低编辑成本,最大幅度提升作品质量。
|
|
||||||
|
|
||||||
## 8.4 第四步:系统继续展开长尾
|
|
||||||
|
|
||||||
在关键卡片被锁定后,再由系统补:
|
|
||||||
|
|
||||||
- 长尾 NPC
|
|
||||||
- 支持性地点
|
|
||||||
- 次级支线
|
|
||||||
- 普通物件
|
|
||||||
- 任务包装
|
|
||||||
- 文案变体
|
|
||||||
|
|
||||||
## 8.5 第五步:陶泥儿主按需进入高级模式
|
|
||||||
|
|
||||||
高级模式只对愿意深挖的人开放:
|
|
||||||
|
|
||||||
- 角色背景章节编辑
|
|
||||||
- 场景章节细化
|
|
||||||
- 支线矩阵补完
|
|
||||||
- 阵营线分歧补强
|
|
||||||
- 结局变量微调
|
|
||||||
|
|
||||||
这一步不是默认主流程。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 哪些内容应该支持“锁定 + 局部重生成”
|
|
||||||
|
|
||||||
为了既保证低门槛,又保证创作安全感,第二层内容必须支持锁定和局部重生成。
|
|
||||||
|
|
||||||
建议至少支持锁定这些对象:
|
|
||||||
|
|
||||||
1. 世界一句话与主题边界
|
|
||||||
2. 核心冲突
|
|
||||||
3. 关键角色
|
|
||||||
4. 关键地点
|
|
||||||
5. 势力卡
|
|
||||||
6. 主线章节卡
|
|
||||||
7. 场景章节卡
|
|
||||||
8. 标志性载体
|
|
||||||
|
|
||||||
建议至少支持这些局部重生成动作:
|
|
||||||
|
|
||||||
1. 仅重生成长尾角色
|
|
||||||
2. 仅重生成长尾地点
|
|
||||||
3. 仅重生成支线种子
|
|
||||||
4. 仅重生成物件与残痕
|
|
||||||
5. 仅重生成某个角色卡
|
|
||||||
6. 仅重生成某个场景章节
|
|
||||||
7. 围绕已锁定角色重做主线第一幕
|
|
||||||
|
|
||||||
原则是:
|
|
||||||
|
|
||||||
**越高价值的锚点越要支持锁定,越低价值的铺量内容越适合重生成。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 产品实现建议
|
|
||||||
|
|
||||||
## 10.1 默认 UI 只展示三段
|
|
||||||
|
|
||||||
建议工作台默认只展示:
|
|
||||||
|
|
||||||
1. 必填锚点
|
|
||||||
2. AI 初稿卡片
|
|
||||||
3. 高级模式入口
|
|
||||||
|
|
||||||
不要默认展示底层系统字段。
|
|
||||||
|
|
||||||
## 10.2 每张卡只保留自然语言输入
|
|
||||||
|
|
||||||
不要强迫陶泥儿主在首轮填写:
|
|
||||||
|
|
||||||
- tags
|
|
||||||
- ids
|
|
||||||
- 数值
|
|
||||||
- 稀有度
|
|
||||||
- 信号枚举
|
|
||||||
- step schema
|
|
||||||
|
|
||||||
更合理的做法是:
|
|
||||||
|
|
||||||
- 让陶泥儿主输入自然语言或选择直觉标签
|
|
||||||
- 再由系统编译成结构化字段
|
|
||||||
|
|
||||||
## 10.3 首轮生成后默认先看“精修建议”
|
|
||||||
|
|
||||||
AI 初稿生成后,不应该把陶泥儿主直接扔进一个大编辑器。
|
|
||||||
|
|
||||||
更好的做法是先给出:
|
|
||||||
|
|
||||||
1. 哪些卡片最值得改
|
|
||||||
2. 哪些内容已经比较稳定
|
|
||||||
3. 哪些内容仍然偏泛,需要陶泥儿主补个性
|
|
||||||
|
|
||||||
这样能明显提高陶泥儿主的修改效率。
|
|
||||||
|
|
||||||
## 10.4 移动端优先只保留高杠杆操作
|
|
||||||
|
|
||||||
移动端默认只应该支持:
|
|
||||||
|
|
||||||
1. 编辑必填卡
|
|
||||||
2. 浏览和修改关键角色卡
|
|
||||||
3. 浏览和修改关键地点卡
|
|
||||||
4. 锁定 / 重生成
|
|
||||||
5. 保存和继续创作
|
|
||||||
|
|
||||||
长表单和底层结构不要默认放在移动端主流程里。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. 最后结论
|
|
||||||
|
|
||||||
如果目标是在自定义世界创作中真正平衡“降低门槛”和“提高作品质量”,最好的做法不是让陶泥儿主填更多字段,也不是把一切都交给 AI。
|
|
||||||
|
|
||||||
更合理的平衡是:
|
|
||||||
|
|
||||||
1. 陶泥儿主必须手填最小但高杠杆的 6 张卡,掌握世界灵魂。
|
|
||||||
2. AI 根据这 6 张卡生成一套可编辑的专业剧情初稿,负责把骨架展开成角色、地点、线程、章节和载体。
|
|
||||||
3. 陶泥儿主只精修最有价值的关键对象,锁定真正重要的内容。
|
|
||||||
4. 其余运行结构、数值、可见性、任务编译和 QA 检查都交给系统托管。
|
|
||||||
|
|
||||||
一句话收束:
|
|
||||||
|
|
||||||
**陶泥儿主负责决定“这个世界为什么值得被创作”,AI 负责把它整理成可修改的策划初稿,系统负责把它稳定地跑成一个游戏世界。**
|
|
||||||
@@ -1,724 +0,0 @@
|
|||||||
# 纯 Agent 式对话创作工具与结构化创作方案的对比评估及转型设计
|
|
||||||
|
|
||||||
更新时间:`2026-04-12`
|
|
||||||
|
|
||||||
## 0. 文档目标
|
|
||||||
|
|
||||||
这份文档用于评估两种自定义世界创作形态:
|
|
||||||
|
|
||||||
1. 当前方向
|
|
||||||
- 基于“最小必填锚点 + AI 初稿卡片 + 系统托管层”的结构化创作方案
|
|
||||||
|
|
||||||
2. 纯 Agent 式方向
|
|
||||||
- 以前台对话为唯一主交互,陶泥儿主主要通过和 Agent 聊天来完成世界构建、角色塑造、剧情扩展和修改
|
|
||||||
|
|
||||||
文档需要回答 3 个问题:
|
|
||||||
|
|
||||||
1. 两种方案各自的优缺点是什么?
|
|
||||||
2. 对当前项目来说,纯 Agent 式是否更优?
|
|
||||||
3. 如果要转换成纯 Agent 式对话创作工具,应该怎么设计,才能不把质量和可控性一起丢掉?
|
|
||||||
|
|
||||||
一句话结论先说:
|
|
||||||
|
|
||||||
**纯 Agent 式对话创作工具更适合当“低门槛入口”和“陪创作过程”,但不适合把整个创作系统做成无结构、无锁定、无摘要、无编译层的纯聊天黑箱。**
|
|
||||||
|
|
||||||
更稳的方向不是“只有聊天”,而是:
|
|
||||||
|
|
||||||
**前台主交互纯 Agent,后台继续保留结构化世界模型、锁定机制、局部重生成、编译层和质量护栏。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 对比对象定义
|
|
||||||
|
|
||||||
## 1.1 当前结构化创作方案是什么
|
|
||||||
|
|
||||||
当前方案的核心是:
|
|
||||||
|
|
||||||
1. 陶泥儿主手填最小高杠杆锚点
|
|
||||||
2. AI 生成一批可编辑的剧情策划初稿卡片
|
|
||||||
3. 系统把内容编译成运行时结构
|
|
||||||
|
|
||||||
它本质上是:
|
|
||||||
|
|
||||||
**结构化工作台 + AI 协作生成。**
|
|
||||||
|
|
||||||
陶泥儿主的主要行为是:
|
|
||||||
|
|
||||||
1. 填写关键卡片
|
|
||||||
2. 修改关键角色、地点、势力、章节等内容卡
|
|
||||||
3. 锁定重要内容
|
|
||||||
4. 局部重生成次级内容
|
|
||||||
|
|
||||||
## 1.2 纯 Agent 式对话创作工具是什么
|
|
||||||
|
|
||||||
纯 Agent 式不是指“系统内部没有结构”,而是指:
|
|
||||||
|
|
||||||
**陶泥儿主前台几乎不需要面对表单和卡片编辑器,主要通过自然语言对话来完成创作。**
|
|
||||||
|
|
||||||
陶泥儿主的主要行为变成:
|
|
||||||
|
|
||||||
1. 用自然语言描述世界想法
|
|
||||||
2. 回答 Agent 的追问
|
|
||||||
3. 让 Agent 生成角色、地点、剧情和章节
|
|
||||||
4. 通过聊天指令要求修改、锁定、重做、总结和导出
|
|
||||||
|
|
||||||
它本质上是:
|
|
||||||
|
|
||||||
**对话式创作入口 + Agent 主导的协同编排。**
|
|
||||||
|
|
||||||
## 1.3 真正需要比较的不是“聊天 VS 表单”,而是“主交互模式 VS 后台结构”
|
|
||||||
|
|
||||||
很多产品会把问题误判成:
|
|
||||||
|
|
||||||
- 要么做聊天
|
|
||||||
- 要么做工作台
|
|
||||||
|
|
||||||
更准确的判断应该是:
|
|
||||||
|
|
||||||
1. 前台用户主要通过什么方式思考和输入?
|
|
||||||
2. 后台系统是否仍然有稳定的世界模型和编译层?
|
|
||||||
3. 陶泥儿主是否还能看见摘要、锁定内容和修改范围?
|
|
||||||
|
|
||||||
对当前项目来说,真正危险的不是“转成聊天”,而是:
|
|
||||||
|
|
||||||
**误把“纯 Agent 式”理解成“完全不需要结构化世界状态”。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 总体结论
|
|
||||||
|
|
||||||
## 2.1 纯 Agent 式的主要优势
|
|
||||||
|
|
||||||
纯 Agent 式最大的价值,在于降低开局压力和创作焦虑。
|
|
||||||
|
|
||||||
它更擅长:
|
|
||||||
|
|
||||||
1. 帮不擅长表单和结构思考的陶泥儿主起步
|
|
||||||
2. 在陶泥儿主思路模糊时做追问和陪创作
|
|
||||||
3. 把“我要做一个世界”变成一次自然聊天
|
|
||||||
4. 动态决定追问深度,而不是一上来摆很多字段
|
|
||||||
5. 让陶泥儿主感觉自己是在和一个懂 RPG 的剧情搭档共创
|
|
||||||
|
|
||||||
## 2.2 纯 Agent 式的主要问题
|
|
||||||
|
|
||||||
纯 Agent 式最大的问题,不是能不能生成内容,而是:
|
|
||||||
|
|
||||||
**长项目一旦进入中后期,它会天然丢失可控性、可扫描性、可局部编辑性和可审计性。**
|
|
||||||
|
|
||||||
它最容易出现这些问题:
|
|
||||||
|
|
||||||
1. 聊天很多,但世界状态越来越难总览
|
|
||||||
2. 角色、地点、势力和章节信息散落在多轮消息里
|
|
||||||
3. 锁定范围不清,重生成容易误伤已有内容
|
|
||||||
4. Agent 很容易“替陶泥儿主决定太多”
|
|
||||||
5. 长会话越来越贵,越来越慢,也越来越容易漂移
|
|
||||||
|
|
||||||
## 2.3 对当前项目的判断
|
|
||||||
|
|
||||||
对当前项目而言:
|
|
||||||
|
|
||||||
1. 纯 Agent 式非常适合做创作入口
|
|
||||||
2. 纯 Agent 式也很适合做关键对象的精修与头脑风暴
|
|
||||||
3. 纯 Agent 式不适合作为唯一内容管理方式
|
|
||||||
|
|
||||||
因此更推荐的方向是:
|
|
||||||
|
|
||||||
**Agent-first,而不是 Agent-only。**
|
|
||||||
|
|
||||||
也就是:
|
|
||||||
|
|
||||||
1. 前台以对话为主
|
|
||||||
2. 后台仍保留结构化世界状态
|
|
||||||
3. 关键内容仍然可被锁定、摘要、对比、局部重生成和导出
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 纯 Agent 式对比当前方案的优缺点
|
|
||||||
|
|
||||||
## 3.1 对比表
|
|
||||||
|
|
||||||
| 维度 | 当前结构化方案 | 纯 Agent 式方案 | 更优者 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| 首次上手门槛 | 比纯聊天高,需要理解少量卡片与阶段 | 最低,只需开口描述想法 | 纯 Agent |
|
|
||||||
| 创作陪伴感 | 中等,AI 更像工具 | 很强,Agent 更像搭档 | 纯 Agent |
|
|
||||||
| 思路模糊时的引导能力 | 有限,更多靠卡片提示 | 很强,可动态追问和启发 | 纯 Agent |
|
|
||||||
| 世界整体可扫描性 | 强,卡片和结构更容易总览 | 弱,聊天记录天然碎片化 | 当前方案 |
|
|
||||||
| 单对象精确编辑 | 强,适合定点修改 | 中等,容易在对话里带出额外变化 | 当前方案 |
|
|
||||||
| 锁定与局部重生成 | 容易做明确边界 | 容易模糊,需额外设计指令语义 | 当前方案 |
|
|
||||||
| 长项目稳定性 | 高,适合几十个对象持续维护 | 中等偏弱,越长越依赖摘要和记忆管理 | 当前方案 |
|
|
||||||
| 内容一致性维护 | 更容易做编译与 QA | 纯聊天很难稳定维护,需要后台隐藏编译 | 当前方案 |
|
|
||||||
| 移动端输入体验 | 表单负担偏大 | 聊天输入天然更友好 | 纯 Agent |
|
|
||||||
| 移动端结果总览 | 卡片更好浏览 | 长聊天记录不利于回看 | 当前方案 |
|
|
||||||
| 专业策划生产效率 | 中后期更高 | 前期更快,中后期容易反复确认 | 当前方案 |
|
|
||||||
| 新手用户心理压力 | 偏高,容易觉得要“填很多东西” | 低,更像在聊一个想法 | 纯 Agent |
|
|
||||||
| 实现复杂度 | 已有方向较明确 | 真正做好会更复杂,需要对话层和隐藏结构双系统 | 当前方案 |
|
|
||||||
| Token / 成本 / 延迟 | 更容易按模块调用 | 长会话上下文更重,成本更高 | 当前方案 |
|
|
||||||
| 可审计和交接 | 强,更适合多人协作 | 弱,需要额外导出和摘要机制 | 当前方案 |
|
|
||||||
|
|
||||||
## 3.2 当前结构化方案的主要优点
|
|
||||||
|
|
||||||
当前方案更强的地方在于:
|
|
||||||
|
|
||||||
1. 有明确的内容边界
|
|
||||||
2. 更容易做锁定、重生成和局部修改
|
|
||||||
3. 更适合中大型世界的长期维护
|
|
||||||
4. 更适合和后端编译层、任务层、章节层做稳定映射
|
|
||||||
5. 更容易把专业剧情策划流程映射成可执行数据
|
|
||||||
|
|
||||||
它的本质优势是:
|
|
||||||
|
|
||||||
**稳定、清楚、可扩展。**
|
|
||||||
|
|
||||||
## 3.3 当前结构化方案的主要缺点
|
|
||||||
|
|
||||||
当前方案更弱的地方在于:
|
|
||||||
|
|
||||||
1. 仍然有“我要开始填工具了”的压力
|
|
||||||
2. 对不擅长结构化思考的新手不够友好
|
|
||||||
3. 澄清、启发和陪创作感不够强
|
|
||||||
4. 很容易从“低门槛工作台”滑向“字段很多的编辑器”
|
|
||||||
5. 移动端如果处理不好,会有明显表单压迫感
|
|
||||||
|
|
||||||
## 3.4 纯 Agent 式方案的主要优点
|
|
||||||
|
|
||||||
纯 Agent 式更强的地方在于:
|
|
||||||
|
|
||||||
1. 入口极低
|
|
||||||
2. 更符合普通人“先说想法”的自然习惯
|
|
||||||
3. 更适合模糊创意逐步收束
|
|
||||||
4. 更容易把澄清问题变成真实协作
|
|
||||||
5. 更容易营造“有专业编剧陪你做世界”的体验
|
|
||||||
|
|
||||||
它的本质优势是:
|
|
||||||
|
|
||||||
**自然、轻松、像在共创。**
|
|
||||||
|
|
||||||
## 3.5 纯 Agent 式方案的主要缺点
|
|
||||||
|
|
||||||
纯 Agent 式更弱的地方在于:
|
|
||||||
|
|
||||||
1. 世界模型隐藏得太深时,陶泥儿主会失去整体掌控感
|
|
||||||
2. 多轮对话后,已确定内容不容易被清晰回看
|
|
||||||
3. 局部重做和精确编辑边界会变模糊
|
|
||||||
4. Agent 容易过度代写、过度主导
|
|
||||||
5. 没有强摘要和锁定机制时,创意很容易漂移
|
|
||||||
|
|
||||||
它的本质问题是:
|
|
||||||
|
|
||||||
**天然更擅长起步,不天然擅长收口。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 对当前项目是否值得转成纯 Agent 式的判断
|
|
||||||
|
|
||||||
## 4.1 值得转的部分
|
|
||||||
|
|
||||||
以下环节非常适合转成纯 Agent 主交互:
|
|
||||||
|
|
||||||
1. 首次创作入口
|
|
||||||
2. 世界灵魂锚点收集
|
|
||||||
3. 低信息量输入后的澄清与启发
|
|
||||||
4. 关键角色、关键地点、核心冲突的初稿展开
|
|
||||||
5. 对单个角色或单个章节做陪创作式精修
|
|
||||||
|
|
||||||
因为这些环节的关键问题不是“字段如何摆放”,而是:
|
|
||||||
|
|
||||||
**陶泥儿主有没有被真正引导出自己想做的世界。**
|
|
||||||
|
|
||||||
## 4.2 不值得直接转成纯聊天黑箱的部分
|
|
||||||
|
|
||||||
以下环节不适合彻底做成无结构纯聊天:
|
|
||||||
|
|
||||||
1. 长项目世界管理
|
|
||||||
2. 大量角色、地点、支线、章节的总览
|
|
||||||
3. 锁定与局部重生成
|
|
||||||
4. 运行时结构编译
|
|
||||||
5. 质量审计与一致性检查
|
|
||||||
6. 导出和交付
|
|
||||||
|
|
||||||
这些环节需要的是:
|
|
||||||
|
|
||||||
**稳定的结构化世界状态,而不是越来越长的聊天记录。**
|
|
||||||
|
|
||||||
## 4.3 最合理的判断
|
|
||||||
|
|
||||||
如果硬要二选一:
|
|
||||||
|
|
||||||
1. 对新手用户和移动端体验,纯 Agent 更有吸引力
|
|
||||||
2. 对专业生产、长期维护和内容质量,当前结构化方案更稳
|
|
||||||
|
|
||||||
所以真正适合当前项目的不是完全替换,而是:
|
|
||||||
|
|
||||||
**把当前方案的“结构和护栏”保留,把用户感受到的“入口和协作方式”改成纯 Agent。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 如果要转换成纯 Agent 式,对什么必须保持不变
|
|
||||||
|
|
||||||
纯 Agent 式可以改变前台交互,但不应该改变下面这些底层原则。
|
|
||||||
|
|
||||||
## 5.1 内容分层边界不能变
|
|
||||||
|
|
||||||
即使转成纯 Agent 式,也仍然要保留这三层:
|
|
||||||
|
|
||||||
1. 陶泥儿主必须确认的高杠杆锚点
|
|
||||||
2. AI 生成但允许陶泥儿主修改的策划初稿层
|
|
||||||
3. 系统托管的运行时编译层
|
|
||||||
|
|
||||||
变化的只是:
|
|
||||||
|
|
||||||
- 这些内容不一定通过卡片表单采集
|
|
||||||
- 可以通过对话逐步收集和确认
|
|
||||||
|
|
||||||
不应该变化的是:
|
|
||||||
|
|
||||||
- 谁来决定世界灵魂
|
|
||||||
- 谁来决定运行时结构
|
|
||||||
|
|
||||||
## 5.2 锁定机制不能变
|
|
||||||
|
|
||||||
纯 Agent 式必须仍然支持:
|
|
||||||
|
|
||||||
1. 锁定世界主题
|
|
||||||
2. 锁定核心冲突
|
|
||||||
3. 锁定关键角色
|
|
||||||
4. 锁定关键地点
|
|
||||||
5. 锁定主线章节
|
|
||||||
6. 锁定场景章节
|
|
||||||
7. 只重做未锁定部分
|
|
||||||
|
|
||||||
否则纯 Agent 式会很快变成:
|
|
||||||
|
|
||||||
**每次聊一句,世界都在偷偷漂移。**
|
|
||||||
|
|
||||||
## 5.3 局部重生成机制不能变
|
|
||||||
|
|
||||||
纯 Agent 式里也必须支持:
|
|
||||||
|
|
||||||
1. 只重生成长尾 NPC
|
|
||||||
2. 只重生成次级地点
|
|
||||||
3. 只重生成某个角色卡
|
|
||||||
4. 只重生成某个章节
|
|
||||||
5. 围绕锁定对象重做剩余草稿
|
|
||||||
|
|
||||||
如果这点没有做好,对话就会越来越像“整世界覆盖式重写”。
|
|
||||||
|
|
||||||
## 5.4 摘要、快照、差异对比不能变
|
|
||||||
|
|
||||||
纯 Agent 工具如果没有这些能力,后期一定失控:
|
|
||||||
|
|
||||||
1. 当前世界摘要
|
|
||||||
2. 已锁定内容清单
|
|
||||||
3. 本轮修改了什么
|
|
||||||
4. 当前有哪些待确认假设
|
|
||||||
5. 能否回退到上一版本
|
|
||||||
|
|
||||||
所以:
|
|
||||||
|
|
||||||
**前台可以纯聊天,后台不能没有版本化世界圣经。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 转成纯 Agent 式后的产品定义
|
|
||||||
|
|
||||||
## 6.1 定义
|
|
||||||
|
|
||||||
建议把转型后的工具定义为:
|
|
||||||
|
|
||||||
**以 Agent 对话为主交互的 RPG 世界共创工具。**
|
|
||||||
|
|
||||||
它不是:
|
|
||||||
|
|
||||||
- 单纯聊天框
|
|
||||||
- 一次性大文本生成器
|
|
||||||
- 没有状态的陪聊机器人
|
|
||||||
|
|
||||||
它应该是:
|
|
||||||
|
|
||||||
1. 会主动澄清
|
|
||||||
2. 会阶段性总结
|
|
||||||
3. 会把聊天结果沉淀成结构化世界状态
|
|
||||||
4. 会提醒风险和冲突
|
|
||||||
5. 会在陶泥儿主要求时进行局部重写和定向扩展
|
|
||||||
|
|
||||||
## 6.2 正确理解
|
|
||||||
|
|
||||||
最重要的一句定义是:
|
|
||||||
|
|
||||||
**界面可以纯 Agent,数据层绝不能纯会话。**
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
1. 陶泥儿主看到的是对话
|
|
||||||
2. 系统内部维护的是世界模型、锁定状态、摘要和编译结果
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 纯 Agent 式工具的推荐交互模型
|
|
||||||
|
|
||||||
## 7.1 阶段 A:创作意图收集
|
|
||||||
|
|
||||||
Agent 不直接要求用户填表,而是通过 `1~3` 轮自然对话收集最小锚点。
|
|
||||||
|
|
||||||
目标是确认:
|
|
||||||
|
|
||||||
1. 世界一句话
|
|
||||||
2. 玩家身份
|
|
||||||
3. 核心冲突
|
|
||||||
4. 主题气质
|
|
||||||
5. 关键关系钩子
|
|
||||||
6. 标志性要素
|
|
||||||
|
|
||||||
这实际上和当前“最小必填 6 张卡”是同一套内容,只是采集方式改成对话。
|
|
||||||
|
|
||||||
## 7.2 阶段 B:Agent 输出首轮世界底稿
|
|
||||||
|
|
||||||
Agent 首轮不应该直接铺满全世界,而应该给出一份简明底稿,例如:
|
|
||||||
|
|
||||||
1. 世界标题和摘要
|
|
||||||
2. 玩家开局定位
|
|
||||||
3. 核心冲突结构
|
|
||||||
4. `3~5` 个关键角色
|
|
||||||
5. `4~6` 个关键地点
|
|
||||||
6. `2~4` 个势力
|
|
||||||
7. 主线第一幕简稿
|
|
||||||
|
|
||||||
同时必须明确分成 3 类:
|
|
||||||
|
|
||||||
1. 已确认内容
|
|
||||||
2. 建议内容
|
|
||||||
3. 待确认内容
|
|
||||||
|
|
||||||
## 7.3 阶段 C:陶泥儿主锁定锚点
|
|
||||||
|
|
||||||
在纯 Agent 模式里,锁定行为必须被显式支持。
|
|
||||||
|
|
||||||
用户可以自然说:
|
|
||||||
|
|
||||||
- 这个世界观锁定
|
|
||||||
- 这个角色保留,不要再改
|
|
||||||
- 只把第一幕重做一下
|
|
||||||
- 势力关系别动,重新想地点
|
|
||||||
|
|
||||||
系统需要把这些自然语言翻译成正式的锁定状态和重生成范围。
|
|
||||||
|
|
||||||
## 7.4 阶段 D:按对象逐步精修
|
|
||||||
|
|
||||||
Agent 不应该每轮都继续扩全局,而应该支持“单对象工作模式”。
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
1. 只精修某个角色
|
|
||||||
2. 只精修某个地点
|
|
||||||
3. 只精修某个场景章节
|
|
||||||
4. 只精修主线第一幕
|
|
||||||
5. 只精修一条支线
|
|
||||||
|
|
||||||
这样可以避免每轮修改都把整个世界重新搅动一次。
|
|
||||||
|
|
||||||
## 7.5 阶段 E:系统后台自动编译与审计
|
|
||||||
|
|
||||||
每一轮重要修改后,系统后台应自动做:
|
|
||||||
|
|
||||||
1. 世界图谱更新
|
|
||||||
2. 可见性边界更新
|
|
||||||
3. 章节和任务编译
|
|
||||||
4. 设定冲突检查
|
|
||||||
5. 弱关联检查
|
|
||||||
6. 风格一致性检查
|
|
||||||
|
|
||||||
这些结果不一定全部展示,但必须被系统持续维护。
|
|
||||||
|
|
||||||
## 7.6 阶段 F:导出世界圣经和可编辑初稿
|
|
||||||
|
|
||||||
纯 Agent 模式的最终产物不能只是一串聊天记录。
|
|
||||||
|
|
||||||
至少要能导出:
|
|
||||||
|
|
||||||
1. 世界摘要
|
|
||||||
2. 锁定锚点
|
|
||||||
3. 关键角色卡
|
|
||||||
4. 关键地点卡
|
|
||||||
5. 势力卡
|
|
||||||
6. 主线章节简稿
|
|
||||||
7. 支线种子
|
|
||||||
8. 场景章节草稿
|
|
||||||
9. 风险与待确认项
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 纯 Agent 式工具需要的后台结构
|
|
||||||
|
|
||||||
## 8.1 会话层之外必须维护的核心状态
|
|
||||||
|
|
||||||
建议后台至少维护下面这些结构:
|
|
||||||
|
|
||||||
| 结构 | 作用 |
|
|
||||||
| --- | --- |
|
|
||||||
| `creatorIntentProfile` | 当前陶泥儿主最初和最新的创作意图 |
|
|
||||||
| `lockedAnchors` | 已确认不可自动改写的内容 |
|
|
||||||
| `worldDraftSnapshot` | 当前世界底稿快照 |
|
|
||||||
| `editableDraftCards` | 角色、地点、势力、章节等可编辑初稿 |
|
|
||||||
| `pendingClarifications` | 当前还未确认的问题 |
|
|
||||||
| `changeLog` | 每轮发生了什么变化 |
|
|
||||||
| `qualityFindings` | 冲突、泄露、弱关联和风格漂移结果 |
|
|
||||||
|
|
||||||
## 8.2 每轮对话后的处理流程
|
|
||||||
|
|
||||||
建议每次用户发言后走这条后台链:
|
|
||||||
|
|
||||||
```text
|
|
||||||
用户消息
|
|
||||||
-> 意图识别
|
|
||||||
-> 判断是在回答问题 / 修改对象 / 请求重生成 / 请求总结 / 请求锁定
|
|
||||||
-> 更新 creatorIntentProfile 或 worldDraftSnapshot
|
|
||||||
-> 重新编译相关草稿对象
|
|
||||||
-> 运行质量检查
|
|
||||||
-> 生成本轮回复
|
|
||||||
-> 同步更新摘要、待确认项和 changeLog
|
|
||||||
```
|
|
||||||
|
|
||||||
这条流程说明:
|
|
||||||
|
|
||||||
**纯 Agent 的前台体验背后,实际仍然是一个结构化内容状态机。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 纯 Agent 式前台应该如何设计
|
|
||||||
|
|
||||||
## 9.1 主界面以对话为主
|
|
||||||
|
|
||||||
主界面可以只有一个核心聊天线程,但不建议只有聊天气泡。
|
|
||||||
|
|
||||||
建议保留 3 个轻量辅助区:
|
|
||||||
|
|
||||||
1. 顶部固定摘要
|
|
||||||
- 当前世界名
|
|
||||||
- 当前阶段
|
|
||||||
- 当前聚焦对象
|
|
||||||
|
|
||||||
2. 锁定内容条
|
|
||||||
- 展示已锁定的世界观、角色、地点、章节
|
|
||||||
|
|
||||||
3. 当前草稿摘要抽屉
|
|
||||||
- 展示关键角色、关键地点、主线第一幕等的简要卡片
|
|
||||||
|
|
||||||
这些区域不是表单编辑器,而是:
|
|
||||||
|
|
||||||
**对话模式下帮助用户保持掌控感的最小结构提示。**
|
|
||||||
|
|
||||||
## 9.2 支持快捷动作
|
|
||||||
|
|
||||||
为了防止用户每次都要自己组织复杂自然语言,建议保留轻量快捷动作:
|
|
||||||
|
|
||||||
1. 总结当前设定
|
|
||||||
2. 锁定当前版本
|
|
||||||
3. 只重做这一项
|
|
||||||
4. 展开主线第一幕
|
|
||||||
5. 增加一个关键角色
|
|
||||||
6. 给我 3 个更有辨识度的版本
|
|
||||||
7. 检查是否有设定冲突
|
|
||||||
|
|
||||||
这类动作按钮不破坏纯 Agent 主交互,反而能显著降低误解成本。
|
|
||||||
|
|
||||||
## 9.3 Agent 的提问规则
|
|
||||||
|
|
||||||
Agent 不能像问卷系统,也不能一次追问太多。
|
|
||||||
|
|
||||||
建议规则:
|
|
||||||
|
|
||||||
1. 一次最多追问 `1~3` 个问题
|
|
||||||
2. 问题必须是当前最缺的高杠杆信息
|
|
||||||
3. 每次追问都给默认建议方向
|
|
||||||
4. 如果陶泥儿主不想细答,允许 Agent 先代补一个版本再确认
|
|
||||||
|
|
||||||
这样才能保持“像聊天”,而不是“像客服表单”。
|
|
||||||
|
|
||||||
## 9.4 Agent 的总结规则
|
|
||||||
|
|
||||||
纯 Agent 工具必须高频做阶段性总结。
|
|
||||||
|
|
||||||
建议在这些时机自动总结:
|
|
||||||
|
|
||||||
1. 首轮世界底稿生成后
|
|
||||||
2. 锁定任意关键锚点后
|
|
||||||
3. 完成某个角色精修后
|
|
||||||
4. 主线第一幕生成后
|
|
||||||
5. 每累计 `5~8` 轮重要对话后
|
|
||||||
|
|
||||||
总结必须包含:
|
|
||||||
|
|
||||||
1. 已确认内容
|
|
||||||
2. 新增内容
|
|
||||||
3. 待确认内容
|
|
||||||
4. 潜在风险
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 纯 Agent 式下的锁定、重生成与修改语义
|
|
||||||
|
|
||||||
## 10.1 锁定语义
|
|
||||||
|
|
||||||
建议支持以下语义:
|
|
||||||
|
|
||||||
1. 锁定对象
|
|
||||||
2. 锁定字段
|
|
||||||
3. 锁定关系
|
|
||||||
4. 锁定当前版本
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- 锁定这个角色的身份和秘密,但可以重写语气
|
|
||||||
- 锁定这条冲突,不要再改动它的基本方向
|
|
||||||
- 锁定第一幕结构,只优化角色高光
|
|
||||||
|
|
||||||
## 10.2 重生成语义
|
|
||||||
|
|
||||||
建议支持以下语义:
|
|
||||||
|
|
||||||
1. 完整替换
|
|
||||||
2. 保留锚点重做
|
|
||||||
3. 仅补长尾
|
|
||||||
4. 给出多个候选版本
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- 保留世界观和角色,重做关键地点
|
|
||||||
- 保留第一幕结构,给我三个更强的转折版本
|
|
||||||
- 只补 5 个更有辨识度的路人 NPC
|
|
||||||
|
|
||||||
## 10.3 修改语义
|
|
||||||
|
|
||||||
Agent 应能识别这些常见修改类型:
|
|
||||||
|
|
||||||
1. 收紧风格
|
|
||||||
2. 增强冲突
|
|
||||||
3. 提高角色辨识度
|
|
||||||
4. 减少套路感
|
|
||||||
5. 让地点更有故事残痕
|
|
||||||
6. 把支线和主线绑定得更紧
|
|
||||||
7. 提高队友反应和选择后果
|
|
||||||
|
|
||||||
这些应该是内容层意图,而不是要求用户直接碰底层字段。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. 纯 Agent 式的主要风险与防护
|
|
||||||
|
|
||||||
## 11.1 风险 1:对话越长,世界越散
|
|
||||||
|
|
||||||
防护方式:
|
|
||||||
|
|
||||||
1. 周期性强制摘要
|
|
||||||
2. 关键内容结构化落库
|
|
||||||
3. 锁定内容固定展示
|
|
||||||
4. 提供“当前世界圣经”入口
|
|
||||||
|
|
||||||
## 11.2 风险 2:Agent 过度代写,陶泥儿主失去作品归属感
|
|
||||||
|
|
||||||
防护方式:
|
|
||||||
|
|
||||||
1. 高杠杆锚点必须要求确认
|
|
||||||
2. 重要改动前先说“我准备改什么”
|
|
||||||
3. 默认优先给多个候选,而不是直接盖写
|
|
||||||
4. 允许陶泥儿主随时回退到旧版本
|
|
||||||
|
|
||||||
## 11.3 风险 3:局部修改带出全局漂移
|
|
||||||
|
|
||||||
防护方式:
|
|
||||||
|
|
||||||
1. 只在目标作用域内修改
|
|
||||||
2. 修改后自动展示影响范围
|
|
||||||
3. 对高风险改动要求二次确认
|
|
||||||
|
|
||||||
## 11.4 风险 4:看起来轻松,实际上难以收口
|
|
||||||
|
|
||||||
防护方式:
|
|
||||||
|
|
||||||
1. 阶段化工作流
|
|
||||||
2. 每阶段有明确产物
|
|
||||||
3. 不是无限聊天,而是要进入“底稿确认 -> 精修 -> 导出”
|
|
||||||
|
|
||||||
## 11.5 风险 5:成本和延迟持续上升
|
|
||||||
|
|
||||||
防护方式:
|
|
||||||
|
|
||||||
1. 长会话摘要压缩
|
|
||||||
2. 按对象加载上下文
|
|
||||||
3. 局部编译,而不是每轮重编整世界
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. 推荐转型路线
|
|
||||||
|
|
||||||
不建议一步到位把当前方案彻底替换成纯聊天。
|
|
||||||
|
|
||||||
更稳的路线是分 3 步走。
|
|
||||||
|
|
||||||
## 12.1 第一步:先把纯 Agent 做成默认入口
|
|
||||||
|
|
||||||
这一阶段:
|
|
||||||
|
|
||||||
1. 用户进入后直接和 Agent 聊
|
|
||||||
2. Agent 帮用户收集最小锚点
|
|
||||||
3. 系统在后台仍然生成当前方案里的结构化初稿
|
|
||||||
4. 结果页仍保留为可选工作台
|
|
||||||
|
|
||||||
这一阶段的目标是:
|
|
||||||
|
|
||||||
**把“起步方式”改成聊天,但不动后端结构主链。**
|
|
||||||
|
|
||||||
## 12.2 第二步:让关键对象编辑也支持 Agent 化
|
|
||||||
|
|
||||||
这一阶段:
|
|
||||||
|
|
||||||
1. 角色、地点、势力、主线第一幕都支持在聊天里精修
|
|
||||||
2. Agent 支持锁定、重做、总结、对比
|
|
||||||
3. 工作台逐步退成辅助视图,而不是默认主路径
|
|
||||||
|
|
||||||
这一阶段的目标是:
|
|
||||||
|
|
||||||
**让大多数高价值修改都可以通过聊天完成。**
|
|
||||||
|
|
||||||
## 12.3 第三步:工作台只保留总览和导出
|
|
||||||
|
|
||||||
到了这一阶段,前台已经基本纯 Agent 化,但仍建议保留:
|
|
||||||
|
|
||||||
1. 世界圣经总览
|
|
||||||
2. 已锁定对象列表
|
|
||||||
3. 版本快照
|
|
||||||
4. 风险与 QA 结果
|
|
||||||
5. 导出面板
|
|
||||||
|
|
||||||
这一阶段的目标不是消灭结构,而是:
|
|
||||||
|
|
||||||
**让结构从“编辑入口”退成“掌控和收口工具”。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 13. 最后结论
|
|
||||||
|
|
||||||
纯 Agent 式对话创作工具的最大优势,是把创作入口从“填写工具”变成“和懂创作的人对话”。
|
|
||||||
|
|
||||||
它会明显提升:
|
|
||||||
|
|
||||||
1. 首次上手意愿
|
|
||||||
2. 创作陪伴感
|
|
||||||
3. 模糊想法的收束效率
|
|
||||||
4. 移动端可用性
|
|
||||||
|
|
||||||
但它也天然会削弱:
|
|
||||||
|
|
||||||
1. 世界总览
|
|
||||||
2. 精确编辑
|
|
||||||
3. 局部重生成边界
|
|
||||||
4. 长项目稳定性
|
|
||||||
5. 质量审计与交接能力
|
|
||||||
|
|
||||||
因此,对当前项目最合理的方向不是彻底放弃结构化方案,而是把它升级成:
|
|
||||||
|
|
||||||
**前台纯 Agent 主交互,后台结构化世界模型持续存在,锁定、摘要、快照、局部重生成和质量护栏全部保留。**
|
|
||||||
|
|
||||||
一句话收束:
|
|
||||||
|
|
||||||
**可以把“创作入口”彻底 Agent 化,但绝不能把“世界状态管理”也做成纯聊天。**
|
|
||||||
@@ -1,660 +0,0 @@
|
|||||||
# 自定义世界自有设定层优化方案
|
|
||||||
|
|
||||||
更新时间:`2026-04-08`
|
|
||||||
|
|
||||||
## 0. 目标
|
|
||||||
|
|
||||||
这份文档要解决的问题是:
|
|
||||||
|
|
||||||
**当前自定义世界虽然已经是唯一正式可玩的世界入口,但它底层仍依赖武侠 / 仙侠模板设定。**
|
|
||||||
|
|
||||||
本次优化目标不是直接删除这些依赖,而是把它们逐步改造成:
|
|
||||||
|
|
||||||
**属于自定义世界自身的设定层,并且这套设定层必须能通用于任何题材。**
|
|
||||||
|
|
||||||
同时必须满足一条底线:
|
|
||||||
|
|
||||||
**不能破坏当前自定义世界生成流程的任何可用功能。**
|
|
||||||
|
|
||||||
一句话定义:
|
|
||||||
|
|
||||||
**让自定义世界从“借模板世界运行”,升级成“拥有自有设定层、可跨题材运行”的架构。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 设计原则
|
|
||||||
|
|
||||||
这次优化必须同时遵守 4 条原则:
|
|
||||||
|
|
||||||
1. 设定归自定义世界自身所有
|
|
||||||
- 运行时、生成期、表现层真正依赖的世界设定,应该落回 `CustomWorldProfile` 或由它直接编译出的配置。
|
|
||||||
|
|
||||||
2. 设定必须跨题材
|
|
||||||
- 新设定不能只是把“武侠 / 仙侠”换一个更抽象的名字。
|
|
||||||
- 它必须能支撑奇幻、科幻、悬疑、校园、末世、神话、现代、海洋、裂界等任何题材。
|
|
||||||
|
|
||||||
3. 优化优先做兼容迁移
|
|
||||||
- 不能先删旧字段,再补新结构。
|
|
||||||
- 必须先补新设定层,再逐步迁读,最后再让旧模板字段退化成兼容层。
|
|
||||||
|
|
||||||
4. 不能增加陶泥儿主负担
|
|
||||||
- 这次不是让陶泥儿主多填一堆底层 schema。
|
|
||||||
- 这些设定仍然应由 AI / 系统编译出来,只是所有权从模板世界转移到自定义世界自己。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 当前自定义世界实际依赖了什么
|
|
||||||
|
|
||||||
根据 [CUSTOM_WORLD_TEMPLATE_DEPENDENCY_INVENTORY_2026-04-08.md](../reference/CUSTOM_WORLD_TEMPLATE_DEPENDENCY_INVENTORY_2026-04-08.md),当前依赖可以归纳成 6 组:
|
|
||||||
|
|
||||||
1. 模板锚点字段
|
|
||||||
- `templateWorldType`
|
|
||||||
- `WorldTemplateType`
|
|
||||||
|
|
||||||
2. 规则桥接
|
|
||||||
- `resolveRuleWorldType(...)`
|
|
||||||
|
|
||||||
3. 主题与词汇底板
|
|
||||||
- `detectCustomWorldThemeMode(...)`
|
|
||||||
- `buildThemePackFromWorldProfile(...)`
|
|
||||||
|
|
||||||
4. 属性、资源词与经济 fallback
|
|
||||||
- 预设属性 schema
|
|
||||||
- 资源命名
|
|
||||||
- 初始货币规则
|
|
||||||
|
|
||||||
5. 内容骨架
|
|
||||||
- 角色模板骨架
|
|
||||||
- 怪物模板池
|
|
||||||
- 场景视觉参考池
|
|
||||||
|
|
||||||
6. prompt 兼容字段
|
|
||||||
- `framework` 生成仍要求输出 `templateWorldType`
|
|
||||||
|
|
||||||
这些东西现在还不能直接删,因为:
|
|
||||||
|
|
||||||
**它们不是单纯的预设世界残留,而是当前自定义世界生成与运行时的真实支撑层。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 本次优化的核心思路
|
|
||||||
|
|
||||||
这次不建议新增很多彼此平行的新系统,而是把现有模板依赖统一收束成:
|
|
||||||
|
|
||||||
**自定义世界自己的 5 层设定层。**
|
|
||||||
|
|
||||||
这 5 层分别是:
|
|
||||||
|
|
||||||
1. 语义锚层
|
|
||||||
2. 规则层
|
|
||||||
3. 表现层
|
|
||||||
4. 原型参考层
|
|
||||||
5. 兼容迁移层
|
|
||||||
|
|
||||||
这样可以让现在分散在:
|
|
||||||
|
|
||||||
- 模板世界枚举
|
|
||||||
- 预设 schema
|
|
||||||
- 视觉参考池
|
|
||||||
- 怪物池
|
|
||||||
- ThemePack 底板
|
|
||||||
|
|
||||||
这些地方的依赖,被重新编译回 `CustomWorldProfile` 自己的配置。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 目标结构
|
|
||||||
|
|
||||||
## 4.1 语义锚层:替代 `templateWorldType`
|
|
||||||
|
|
||||||
当前 `templateWorldType` 实际在回答的不是“你是不是武侠”,而是:
|
|
||||||
|
|
||||||
1. 这个世界更接近哪类冲突结构
|
|
||||||
2. 这个世界更接近哪类制度和禁忌
|
|
||||||
3. 这个世界更接近哪类叙事载体与力量来源
|
|
||||||
|
|
||||||
所以应把它升级成一套真正属于自定义世界自己的语义锚:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldSemanticAnchor {
|
|
||||||
genreSignals: string[];
|
|
||||||
conflictForms: string[];
|
|
||||||
institutionTypes: string[];
|
|
||||||
tabooTypes: string[];
|
|
||||||
carrierTypes: string[];
|
|
||||||
forceSystemTypes: string[];
|
|
||||||
atmosphereTags: string[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
这层应该回答:
|
|
||||||
|
|
||||||
1. 这个世界的主要矛盾像什么
|
|
||||||
2. 这个世界的秩序结构像什么
|
|
||||||
3. 这个世界的危险和禁忌像什么
|
|
||||||
4. 这个世界的线索、遗物、文书、证物、技术、仪式像什么
|
|
||||||
|
|
||||||
关键点:
|
|
||||||
|
|
||||||
- 这里不再出现“武侠 / 仙侠”的模板世界名
|
|
||||||
- 只保留通用语义
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- `institutionTypes`
|
|
||||||
- 宗门
|
|
||||||
- 公司
|
|
||||||
- 学园
|
|
||||||
- 教团
|
|
||||||
- 调查局
|
|
||||||
- 舰队
|
|
||||||
- 部族
|
|
||||||
|
|
||||||
- `forceSystemTypes`
|
|
||||||
- 灵脉
|
|
||||||
- 科技
|
|
||||||
- 仪式
|
|
||||||
- 契约
|
|
||||||
- 血统
|
|
||||||
- 神谕
|
|
||||||
- 污染
|
|
||||||
|
|
||||||
这就天然跨题材了。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.2 规则层:把 fallback 变成自定义世界自己的规则设定
|
|
||||||
|
|
||||||
当前很多规则仍然会回落到武侠 / 仙侠:
|
|
||||||
|
|
||||||
- 属性 schema
|
|
||||||
- 资源命名
|
|
||||||
- 经济命名
|
|
||||||
- 初始货币
|
|
||||||
|
|
||||||
优化后应由自定义世界自己持有:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldRuleProfile {
|
|
||||||
attributeSchema: WorldAttributeSchema;
|
|
||||||
resourceLabels: {
|
|
||||||
hp: string;
|
|
||||||
mp: string;
|
|
||||||
maxHp: string;
|
|
||||||
maxMp: string;
|
|
||||||
damage: string;
|
|
||||||
guard: string;
|
|
||||||
range: string;
|
|
||||||
cooldown: string;
|
|
||||||
manaCost: string;
|
|
||||||
currency: string;
|
|
||||||
};
|
|
||||||
economyProfile: {
|
|
||||||
initialCurrency: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
这意味着以后运行时读取逻辑应优先变成:
|
|
||||||
|
|
||||||
1. 先读 `profile.ruleProfile`
|
|
||||||
2. 再读 `profile.attributeSchema`
|
|
||||||
3. 最后才允许读兼容 fallback
|
|
||||||
|
|
||||||
而不是:
|
|
||||||
|
|
||||||
1. 先判断 `WUXIA / XIANXIA`
|
|
||||||
2. 再猜自定义世界应该像哪边
|
|
||||||
|
|
||||||
这层的原则是:
|
|
||||||
|
|
||||||
**规则是这个自定义世界自己的,不再借模板世界托管。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.3 表现层:把 ThemePack 变成自定义世界自身的派生物
|
|
||||||
|
|
||||||
当前 `ThemePack` 仍然是“预设底板 + 自定义词汇补丁”。
|
|
||||||
|
|
||||||
优化后应改成:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldExpressionProfile {
|
|
||||||
themePack: ThemePack;
|
|
||||||
presentationTone: string[];
|
|
||||||
namingDirectives: string[];
|
|
||||||
clueDirectives: string[];
|
|
||||||
revealDirectives: string[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
- `ThemePack` 仍然可以保留
|
|
||||||
- 但它不再被理解为“武侠底板 / 仙侠底板的延伸”
|
|
||||||
- 它应当是:
|
|
||||||
- `semanticAnchor`
|
|
||||||
- `creatorIntent`
|
|
||||||
- `majorFactions`
|
|
||||||
- `coreConflicts`
|
|
||||||
- `world text`
|
|
||||||
共同编译出来的结果
|
|
||||||
|
|
||||||
这一步非常关键,因为它会决定:
|
|
||||||
|
|
||||||
1. 物件命名
|
|
||||||
2. 势力命名
|
|
||||||
3. 线索形式
|
|
||||||
4. 提示词词汇风格
|
|
||||||
5. reveal 风格
|
|
||||||
|
|
||||||
只有把这层做成自定义世界自己的派生物,后面跨题材才会真的稳。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.4 原型参考层:把模板骨架改成通用原型库
|
|
||||||
|
|
||||||
当前自定义世界借用模板的最深部分是:
|
|
||||||
|
|
||||||
1. 角色模板骨架
|
|
||||||
2. 场景视觉参考池
|
|
||||||
3. 怪物模板池
|
|
||||||
|
|
||||||
这三类不能粗暴删除,但可以改造成:
|
|
||||||
|
|
||||||
**通用原型参考层。**
|
|
||||||
|
|
||||||
建议统一成:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldReferenceProfile {
|
|
||||||
roleArchetypes: RoleArchetypeProfile[];
|
|
||||||
sceneBuckets: SceneArchetypeBucket[];
|
|
||||||
creatureArchetypes: CreatureArchetypeProfile[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.4.1 角色原型
|
|
||||||
|
|
||||||
角色原型应描述的是:
|
|
||||||
|
|
||||||
- 正面推进型
|
|
||||||
- 远程压制型
|
|
||||||
- 控场解构型
|
|
||||||
- 续航承压型
|
|
||||||
- 潜行爆发型
|
|
||||||
|
|
||||||
而不是:
|
|
||||||
|
|
||||||
- 剑之公主
|
|
||||||
- 神箭游侠
|
|
||||||
- 双刃旅者
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
**保留战斗骨架和技能骨架,不保留模板角色人格设定。**
|
|
||||||
|
|
||||||
### 4.4.2 场景原型
|
|
||||||
|
|
||||||
场景原型应描述的是:
|
|
||||||
|
|
||||||
- 高压入口区
|
|
||||||
- 雨湿街巷区
|
|
||||||
- 临水渡口区
|
|
||||||
- 仪式神殿区
|
|
||||||
- 高空通路区
|
|
||||||
- 工业热区
|
|
||||||
- 地底遗迹区
|
|
||||||
- 群落聚居区
|
|
||||||
|
|
||||||
而不是:
|
|
||||||
|
|
||||||
- 竹林古道
|
|
||||||
- 云海仙门
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
**保留“空间语义 -> 视觉参考”的逻辑,不保留模板世界场景名作为中心。**
|
|
||||||
|
|
||||||
### 4.4.3 生物原型
|
|
||||||
|
|
||||||
生物原型应描述的是:
|
|
||||||
|
|
||||||
- 潜伏袭击者
|
|
||||||
- 重甲承压者
|
|
||||||
- 群居骚扰者
|
|
||||||
- 远程威胁者
|
|
||||||
- 异化污染体
|
|
||||||
- 灵体回响体
|
|
||||||
- 机关守卫体
|
|
||||||
|
|
||||||
而不是:
|
|
||||||
|
|
||||||
- 某个武侠怪
|
|
||||||
- 某个仙侠怪
|
|
||||||
|
|
||||||
这样之后:
|
|
||||||
|
|
||||||
- 自定义世界依赖的是“通用原型”
|
|
||||||
- 原型再映射到底层素材与 preset
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4.5 兼容迁移层:旧字段继续保留一段时间
|
|
||||||
|
|
||||||
为了不破坏当前流程,短期内不能直接删除:
|
|
||||||
|
|
||||||
- `templateWorldType`
|
|
||||||
- `WorldTemplateType`
|
|
||||||
- 以及相关 normalize / save / read 逻辑
|
|
||||||
|
|
||||||
这层应被降级成:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldCompatibilityProfile {
|
|
||||||
legacyTemplateWorldType?: 'WUXIA' | 'XIANXIA' | null;
|
|
||||||
migrationVersion: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
作用:
|
|
||||||
|
|
||||||
1. 旧存档兼容
|
|
||||||
2. 旧 prompt 兼容
|
|
||||||
3. 旧测试兼容
|
|
||||||
4. 旧工具链兼容
|
|
||||||
|
|
||||||
但它不再应是新生成世界的第一真相来源。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 现有每类依赖如何改造成自定义世界自己的设定
|
|
||||||
|
|
||||||
下面把现有依赖逐项映射成未来目标。
|
|
||||||
|
|
||||||
## 5.1 `templateWorldType`
|
|
||||||
|
|
||||||
当前作用:
|
|
||||||
|
|
||||||
- 世界锚点
|
|
||||||
- 规则 fallback
|
|
||||||
- 视觉和怪物参考入口
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 拆成:
|
|
||||||
- `semanticAnchor`
|
|
||||||
- `ruleProfile`
|
|
||||||
- `referenceProfile`
|
|
||||||
- `compatibilityProfile`
|
|
||||||
|
|
||||||
迁移方式:
|
|
||||||
|
|
||||||
1. 旧字段保留
|
|
||||||
2. 新字段生成后优先读新字段
|
|
||||||
3. 旧字段只做迁移 fallback
|
|
||||||
|
|
||||||
## 5.2 `resolveRuleWorldType(...)`
|
|
||||||
|
|
||||||
当前作用:
|
|
||||||
|
|
||||||
- 把 `CUSTOM` 解析回 `WUXIA / XIANXIA`
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 改成:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
resolveCustomWorldRuleProfile(profile)
|
|
||||||
```
|
|
||||||
|
|
||||||
运行时不再需要知道“它更像武侠还是仙侠”,而只需要知道:
|
|
||||||
|
|
||||||
- 这个世界的规则 profile 是什么
|
|
||||||
|
|
||||||
## 5.3 `getPresetWorldAttributeSchema(...)`
|
|
||||||
|
|
||||||
当前作用:
|
|
||||||
|
|
||||||
- 自定义世界 schema 的参考底板
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 把预设 schema 底板重构成:
|
|
||||||
- 通用 schema seeds
|
|
||||||
|
|
||||||
例如按功能分:
|
|
||||||
|
|
||||||
1. 承压轴
|
|
||||||
2. 机动轴
|
|
||||||
3. 洞察轴
|
|
||||||
4. 决断轴
|
|
||||||
5. 共鸣轴
|
|
||||||
6. 续航轴
|
|
||||||
|
|
||||||
然后由自定义世界的 `semanticAnchor + creatorIntent` 生成最终命名与说明。
|
|
||||||
|
|
||||||
## 5.4 `PRESET_CHARACTERS`
|
|
||||||
|
|
||||||
当前作用:
|
|
||||||
|
|
||||||
- 自定义世界角色的战斗模板和技能骨架
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 抽出:
|
|
||||||
- `RoleArchetypeProfile`
|
|
||||||
- `SkillArchetypeProfile`
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
- 还可以继续复用当前角色的战斗结构
|
|
||||||
- 但不应再让自定义世界依赖那些模板角色的人设文本
|
|
||||||
|
|
||||||
## 5.5 场景图片参考池
|
|
||||||
|
|
||||||
当前作用:
|
|
||||||
|
|
||||||
- 自定义世界默认场景图匹配
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 改成:
|
|
||||||
- `SceneArchetypeBucket`
|
|
||||||
|
|
||||||
每个 bucket 只表达通用空间语义,不表达模板世界名。
|
|
||||||
|
|
||||||
## 5.6 怪物池
|
|
||||||
|
|
||||||
当前作用:
|
|
||||||
|
|
||||||
- 自定义世界敌对单位匹配 preset
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 改成:
|
|
||||||
- `CreatureArchetypeProfile`
|
|
||||||
|
|
||||||
由 archetype 再映射到底层怪物素材与 preset。
|
|
||||||
|
|
||||||
## 5.7 `buildThemePackFromWorldProfile(...)`
|
|
||||||
|
|
||||||
当前作用:
|
|
||||||
|
|
||||||
- 以模板题材包为底生成自定义世界 ThemePack
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 变成:
|
|
||||||
- `buildThemePackFromCustomWorldSemanticAnchor(...)`
|
|
||||||
|
|
||||||
即 ThemePack 以自定义世界自己的语义锚和词汇锚为底。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 不破坏当前流程的迁移顺序
|
|
||||||
|
|
||||||
这是最关键的落地顺序。
|
|
||||||
|
|
||||||
## 阶段 A:先给 `CustomWorldProfile` 补新设定层
|
|
||||||
|
|
||||||
先补:
|
|
||||||
|
|
||||||
1. `semanticAnchor`
|
|
||||||
2. `ruleProfile`
|
|
||||||
3. `expressionProfile`
|
|
||||||
4. `referenceProfile`
|
|
||||||
5. `compatibilityProfile`
|
|
||||||
|
|
||||||
这一步只做新增,不删旧字段。
|
|
||||||
|
|
||||||
## 阶段 B:旧字段自动编译新字段
|
|
||||||
|
|
||||||
当前已有 profile、旧存档、旧生成结果,先统一经过:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
compileOwnedSettingLayersFromLegacyTemplate(profile)
|
|
||||||
```
|
|
||||||
|
|
||||||
让:
|
|
||||||
|
|
||||||
- 旧世界也拥有新设定层
|
|
||||||
- 新运行时可优先消费新字段
|
|
||||||
|
|
||||||
## 阶段 C:生成链开始直接产出新设定层
|
|
||||||
|
|
||||||
修改:
|
|
||||||
|
|
||||||
- `framework prompt`
|
|
||||||
- `normalizeCustomWorldGenerationFramework(...)`
|
|
||||||
- `customWorld.ts`
|
|
||||||
|
|
||||||
使新生成世界优先产出:
|
|
||||||
|
|
||||||
- `semanticAnchor`
|
|
||||||
- `ruleProfile hints`
|
|
||||||
- `referenceProfile hints`
|
|
||||||
|
|
||||||
而不是只产出 `templateWorldType`
|
|
||||||
|
|
||||||
## 阶段 D:运行时逐步改读新设定层
|
|
||||||
|
|
||||||
优先改:
|
|
||||||
|
|
||||||
1. 资源词与货币
|
|
||||||
2. attribute schema
|
|
||||||
3. ThemePack
|
|
||||||
4. 视觉参考
|
|
||||||
5. 怪物映射
|
|
||||||
6. 角色原型引用
|
|
||||||
|
|
||||||
要求:
|
|
||||||
|
|
||||||
- 每次只切一层
|
|
||||||
- 每层都保留 fallback
|
|
||||||
|
|
||||||
## 阶段 E:把旧模板字段降级为兼容层
|
|
||||||
|
|
||||||
当上面的读取已经都切走后:
|
|
||||||
|
|
||||||
- `templateWorldType` 就不再是主链依赖
|
|
||||||
- 只作为:
|
|
||||||
- migration
|
|
||||||
- 老存档兼容
|
|
||||||
- 老工具兼容
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 推荐新增的最小字段
|
|
||||||
|
|
||||||
为了避免系统膨胀,建议只先补最小集合。
|
|
||||||
|
|
||||||
## 7.1 `CustomWorldOwnedSettingLayers`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldOwnedSettingLayers {
|
|
||||||
semanticAnchor: CustomWorldSemanticAnchor;
|
|
||||||
ruleProfile: CustomWorldRuleProfile;
|
|
||||||
expressionProfile: CustomWorldExpressionProfile;
|
|
||||||
referenceProfile: CustomWorldReferenceProfile;
|
|
||||||
compatibilityProfile?: CustomWorldCompatibilityProfile | null;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
然后挂到:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldProfile {
|
|
||||||
ownedSettingLayers?: CustomWorldOwnedSettingLayers | null;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
这样好处是:
|
|
||||||
|
|
||||||
1. 不用在 `CustomWorldProfile` 顶层堆太多字段
|
|
||||||
2. 迁移更集中
|
|
||||||
3. 后续删除兼容层更容易
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 对当前功能链的保护要求
|
|
||||||
|
|
||||||
这次优化过程中,下面这些能力不能坏:
|
|
||||||
|
|
||||||
1. 自定义世界创建
|
|
||||||
2. 自定义世界保存 / 读取
|
|
||||||
3. 自定义世界角色生成
|
|
||||||
4. 自定义世界场景生成
|
|
||||||
5. 自定义世界开局
|
|
||||||
6. 自定义世界运行时的:
|
|
||||||
- 属性
|
|
||||||
- 资源词
|
|
||||||
- 经济
|
|
||||||
- 场景图
|
|
||||||
- 敌对实体
|
|
||||||
- ThemePack
|
|
||||||
- prompt 组织
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
**任何一次迭代都必须是“新层可用 + 旧层仍可兜底”。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 验收标准
|
|
||||||
|
|
||||||
当下面这些标准成立时,说明这套优化开始有效:
|
|
||||||
|
|
||||||
1. 新生成的自定义世界不再必须依赖 `templateWorldType` 才能表达自身设定。
|
|
||||||
2. 运行时优先读取 `ownedSettingLayers`,而不是先问武侠 / 仙侠。
|
|
||||||
3. 自定义世界的属性、资源词、经济、视觉参考、怪物映射、ThemePack 都能从自身设定层派生出来。
|
|
||||||
4. 新设定层描述的是通用语义,不是模板世界换皮。
|
|
||||||
5. 当前自定义世界生成流程、旧存档、旧结果页工作台仍然可用。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 最后结论
|
|
||||||
|
|
||||||
如果目标是:
|
|
||||||
|
|
||||||
**让这些依赖都变成属于自定义世界的设定,而且这些设定要通用于任何题材。**
|
|
||||||
|
|
||||||
那么最正确的方向不是“继续弱化武侠 / 仙侠字样”,而是:
|
|
||||||
|
|
||||||
**把模板支撑层整体迁移成自定义世界自己的设定层。**
|
|
||||||
|
|
||||||
更具体地说,就是把当前依赖重组为:
|
|
||||||
|
|
||||||
1. 自定义世界自己的语义锚
|
|
||||||
2. 自定义世界自己的规则 profile
|
|
||||||
3. 自定义世界自己的表达 profile
|
|
||||||
4. 自定义世界自己的原型参考 profile
|
|
||||||
5. 只负责兼容的旧模板字段
|
|
||||||
|
|
||||||
这样之后,自定义世界才会真正从:
|
|
||||||
|
|
||||||
**模板依赖型生成架构**
|
|
||||||
|
|
||||||
迁移成:
|
|
||||||
|
|
||||||
**跨题材、自有设定层、且兼容当前流程的生成架构。**
|
|
||||||
@@ -1,656 +0,0 @@
|
|||||||
# 自定义世界去模板依赖与跨题材通用化优化设计
|
|
||||||
|
|
||||||
更新时间:`2026-04-08`
|
|
||||||
|
|
||||||
## 0. 目标
|
|
||||||
|
|
||||||
这份文档解决的是一个已经明确暴露出来的问题:
|
|
||||||
|
|
||||||
**当前玩家主流程虽然已经移除了武侠 / 仙侠两个预设世界,但自定义世界底层仍然依赖武侠 / 仙侠模板设定。**
|
|
||||||
|
|
||||||
本次优化的目标不是简单“删掉模板字段”,而是要在不破坏当前自定义世界生成流程的前提下,把这些依赖逐步改造成:
|
|
||||||
|
|
||||||
**属于自定义世界自身、且能通用于任何题材的设定层。**
|
|
||||||
|
|
||||||
一句话定义:
|
|
||||||
|
|
||||||
**让自定义世界从“挂靠武侠 / 仙侠模板运行”,升级成“基于通用世界设定层独立运行”。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 优化原则
|
|
||||||
|
|
||||||
这次优化必须同时满足 3 条硬原则:
|
|
||||||
|
|
||||||
1. 设定归自定义世界自身所有
|
|
||||||
- 任何运行时、生成期、表现层真正依赖的设定,都应尽量落回 `CustomWorldProfile` 或由它直接编译出的通用配置,不再默认挂在 `WUXIA / XIANXIA` 两个模板世界上。
|
|
||||||
|
|
||||||
2. 设定必须跨题材通用
|
|
||||||
- 不能把“自定义世界去模板化”理解成“再做一套更抽象的武侠 / 仙侠替代字段”。
|
|
||||||
- 新结构必须能容纳奇幻、科幻、悬疑、末世、现代、神话、校园等任何题材。
|
|
||||||
|
|
||||||
3. 不能破坏当前自定义世界生成流程
|
|
||||||
- 现有 `framework -> themePack -> storyGraph -> role / landmark -> runtime` 主链必须继续能跑。
|
|
||||||
- 优化应以兼容迁移为主,而不是大爆破式重写。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 当前问题归纳
|
|
||||||
|
|
||||||
根据 [CUSTOM_WORLD_TEMPLATE_DEPENDENCY_INVENTORY_2026-04-08.md](../reference/CUSTOM_WORLD_TEMPLATE_DEPENDENCY_INVENTORY_2026-04-08.md),当前自定义世界仍依赖模板层的地方主要有:
|
|
||||||
|
|
||||||
1. 模板锚点类型
|
|
||||||
- `templateWorldType`
|
|
||||||
- `WorldTemplateType`
|
|
||||||
- `resolveRuleWorldType(...)`
|
|
||||||
|
|
||||||
2. 主题与规则回退
|
|
||||||
- `detectCustomWorldThemeMode(...)`
|
|
||||||
- `resolveCustomWorldAnchorWorldType(...)`
|
|
||||||
- `buildThemePackFromWorldProfile(...)` 底板
|
|
||||||
|
|
||||||
3. 属性与表现
|
|
||||||
- 预设世界属性 schema
|
|
||||||
- 资源词、数值命名、货币命名
|
|
||||||
|
|
||||||
4. 角色骨架
|
|
||||||
- `PRESET_CHARACTERS`
|
|
||||||
- 模板技能定义
|
|
||||||
- 模板 opening 接口
|
|
||||||
|
|
||||||
5. 场景与视觉参考
|
|
||||||
- 武侠 / 仙侠场景图片参考池
|
|
||||||
- 模板 camp 场景映射
|
|
||||||
|
|
||||||
6. 怪物模板池
|
|
||||||
- 武侠 / 仙侠怪物 preset 池
|
|
||||||
|
|
||||||
这些依赖本质上说明:
|
|
||||||
|
|
||||||
**当前自定义世界不是完全自足,而是“先生成自定义内容,再把它映射回两套模板世界骨架”。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 这次优化不应该怎么做
|
|
||||||
|
|
||||||
先明确几个错误方向:
|
|
||||||
|
|
||||||
## 3.1 不能直接删掉 `templateWorldType`
|
|
||||||
|
|
||||||
如果直接删除:
|
|
||||||
|
|
||||||
- `templateWorldType`
|
|
||||||
- `WorldTemplateType`
|
|
||||||
- `WUXIA / XIANXIA` 相关回退
|
|
||||||
|
|
||||||
而不先补新的通用设定层,那么当前自定义世界会立刻失去:
|
|
||||||
|
|
||||||
1. 规则桥接
|
|
||||||
2. 主题底板
|
|
||||||
3. 场景图参考
|
|
||||||
4. 怪物池匹配
|
|
||||||
5. 属性 schema fallback
|
|
||||||
|
|
||||||
这会直接打断现有生成和运行链路。
|
|
||||||
|
|
||||||
## 3.2 不能把新设定继续写成“武侠 / 仙侠的抽象别名”
|
|
||||||
|
|
||||||
例如下面这种思路是不够的:
|
|
||||||
|
|
||||||
- 把 `templateWorldType` 改名成 `worldFamily`
|
|
||||||
- 但值仍然只有两类近似武侠 / 仙侠的内部枚举
|
|
||||||
|
|
||||||
这不是真正跨题材,只是换了名字。
|
|
||||||
|
|
||||||
## 3.3 不能让陶泥儿主承担更多底层配置工作
|
|
||||||
|
|
||||||
这次优化不是让陶泥儿主额外填写:
|
|
||||||
|
|
||||||
- 怪物模板表
|
|
||||||
- 场景参考池
|
|
||||||
- 属性 schema 槽位
|
|
||||||
- 规则 profile
|
|
||||||
|
|
||||||
正确方向应该是:
|
|
||||||
|
|
||||||
**这些通用设定仍由系统生成 / 编译,但所有权回到自定义世界自身。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 目标结构:把模板依赖改造成自定义世界自己的 4 层通用设定
|
|
||||||
|
|
||||||
为了避免系统越改越散,这次建议不要新增很多彼此平行的新系统,而是把现有模板依赖统一收束成 4 层通用设定。
|
|
||||||
|
|
||||||
## 4.1 第一层:世界语义锚层
|
|
||||||
|
|
||||||
这是替代 `templateWorldType` 的核心层。
|
|
||||||
|
|
||||||
它不再回答“你更像武侠还是仙侠”,而回答:
|
|
||||||
|
|
||||||
1. 这个世界的主要冲突形式是什么
|
|
||||||
2. 这个世界的制度、禁忌、叙事载体、力量来源是什么
|
|
||||||
3. 这个世界更接近哪类表现模式
|
|
||||||
|
|
||||||
建议由自定义世界自己持有一个通用结构,例如:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldSemanticAnchor {
|
|
||||||
genreSignals: string[];
|
|
||||||
conflictModel: string[];
|
|
||||||
institutionHints: string[];
|
|
||||||
tabooHints: string[];
|
|
||||||
carrierHints: string[];
|
|
||||||
forceSystemHints: string[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
- 它是自定义世界自己的语义锚。
|
|
||||||
- 它可以表示:
|
|
||||||
- 宗门与灵脉
|
|
||||||
- 财团与实验体
|
|
||||||
- 学园与旧规
|
|
||||||
- 边境与裂界
|
|
||||||
- 海潮与失落群岛
|
|
||||||
- 神话与誓约
|
|
||||||
- 不再强制回落到武侠 / 仙侠二选一。
|
|
||||||
|
|
||||||
## 4.2 第二层:规则与表现配置层
|
|
||||||
|
|
||||||
这是替代:
|
|
||||||
|
|
||||||
- `resolveRuleWorldType(...)`
|
|
||||||
- 预设属性 schema fallback
|
|
||||||
- 资源命名 fallback
|
|
||||||
- 经济 fallback
|
|
||||||
|
|
||||||
建议改成由自定义世界持有一份通用 `WorldRuleProfile`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface WorldRuleProfile {
|
|
||||||
attributeSchema: WorldAttributeSchema;
|
|
||||||
resourceLabels: {
|
|
||||||
hp: string;
|
|
||||||
mp: string;
|
|
||||||
maxHp: string;
|
|
||||||
maxMp: string;
|
|
||||||
damage: string;
|
|
||||||
guard: string;
|
|
||||||
range: string;
|
|
||||||
cooldown: string;
|
|
||||||
manaCost: string;
|
|
||||||
currency: string;
|
|
||||||
};
|
|
||||||
economyProfile: {
|
|
||||||
initialCurrency: number;
|
|
||||||
rarityValueScale: Record<string, number>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
关键点:
|
|
||||||
|
|
||||||
1. 以后运行时不要再优先问“这是武侠还是仙侠”。
|
|
||||||
2. 应该直接问:
|
|
||||||
- `profile.ruleProfile.attributeSchema`
|
|
||||||
- `profile.ruleProfile.resourceLabels`
|
|
||||||
- `profile.ruleProfile.economyProfile`
|
|
||||||
|
|
||||||
这样:
|
|
||||||
|
|
||||||
- 奇幻世界可以叫“体魄 / 法力”
|
|
||||||
- 末世世界可以叫“生命 / 电量”
|
|
||||||
- 校园悬疑世界甚至可以弱化“mana”概念,改成“专注 / 压力”
|
|
||||||
|
|
||||||
## 4.3 第三层:内容骨架与参考层
|
|
||||||
|
|
||||||
这是替代:
|
|
||||||
|
|
||||||
- 预设角色模板骨架
|
|
||||||
- 武侠 / 仙侠场景图参考池
|
|
||||||
- 武侠 / 仙侠怪物 preset 池
|
|
||||||
|
|
||||||
这里不建议让自定义世界自己保存大批素材,而是建议让自定义世界持有:
|
|
||||||
|
|
||||||
**对内容骨架的“编译后引用配置”。**
|
|
||||||
|
|
||||||
建议统一成一个 `ContentReferenceProfile`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface ContentReferenceProfile {
|
|
||||||
roleArchetypes: RoleArchetypeProfile[];
|
|
||||||
sceneReferenceBuckets: SceneReferenceBucket[];
|
|
||||||
creatureArchetypes: CreatureArchetypeProfile[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
其中每个子项都应是通用语义,而不是模板世界名:
|
|
||||||
|
|
||||||
1. `RoleArchetypeProfile`
|
|
||||||
- 例如:
|
|
||||||
- 正面压制型
|
|
||||||
- 远程游击型
|
|
||||||
- 灵术控制型
|
|
||||||
- 重装承压型
|
|
||||||
- 潜行刺击型
|
|
||||||
|
|
||||||
2. `SceneReferenceBucket`
|
|
||||||
- 例如:
|
|
||||||
- 高压门禁区
|
|
||||||
- 雨夜街巷区
|
|
||||||
- 高空通道区
|
|
||||||
- 神殿 / 仪式区
|
|
||||||
- 工业热区
|
|
||||||
- 潮湿临水区
|
|
||||||
|
|
||||||
3. `CreatureArchetypeProfile`
|
|
||||||
- 例如:
|
|
||||||
- 潜伏掠食者
|
|
||||||
- 重甲承压者
|
|
||||||
- 群居灵体
|
|
||||||
- 远程骚扰者
|
|
||||||
- 寄生污染体
|
|
||||||
|
|
||||||
关键点:
|
|
||||||
|
|
||||||
- 这些 archetype 可以继续映射到底层素材和 preset。
|
|
||||||
- 但对自定义世界来说,它依赖的是“通用原型”,不是“武侠怪物池 / 仙侠怪物池”。
|
|
||||||
|
|
||||||
## 4.4 第四层:叙事与词汇编译层
|
|
||||||
|
|
||||||
这是替代:
|
|
||||||
|
|
||||||
- 以模板世界为底的 `ThemePack` fallback
|
|
||||||
- prompt 中的模板世界兼容字段
|
|
||||||
|
|
||||||
建议做法:
|
|
||||||
|
|
||||||
1. `ThemePack` 继续保留,但它的来源变成:
|
|
||||||
- `semanticAnchor + creatorIntent + world text + content reference profile`
|
|
||||||
|
|
||||||
2. 生成框架 prompt 不再要求输出:
|
|
||||||
- `templateWorldType: WUXIA|XIANXIA`
|
|
||||||
|
|
||||||
3. 改为要求输出:
|
|
||||||
- 世界语义锚
|
|
||||||
- 规则表现关键词
|
|
||||||
- 冲突和制度线索
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldGenerationFramework {
|
|
||||||
name: string;
|
|
||||||
subtitle: string;
|
|
||||||
summary: string;
|
|
||||||
tone: string;
|
|
||||||
playerGoal: string;
|
|
||||||
semanticAnchor: CustomWorldSemanticAnchor;
|
|
||||||
majorFactions: string[];
|
|
||||||
coreConflicts: string[];
|
|
||||||
camp: CampOutline;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
这样:
|
|
||||||
|
|
||||||
- 生成流程仍然是 `framework -> themePack -> storyGraph -> role / landmark`
|
|
||||||
- 只是 framework 的核心锚不再依赖预设世界枚举
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 现有依赖如何一一改造
|
|
||||||
|
|
||||||
## 5.1 `templateWorldType` -> `semanticAnchor + ruleProfile`
|
|
||||||
|
|
||||||
当前用途:
|
|
||||||
|
|
||||||
- 表示自定义世界挂靠武侠还是仙侠
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 运行时不再直接读取 `templateWorldType`
|
|
||||||
- 改为读取:
|
|
||||||
- `semanticAnchor`
|
|
||||||
- `ruleProfile`
|
|
||||||
|
|
||||||
迁移策略:
|
|
||||||
|
|
||||||
1. 先保留 `templateWorldType` 作为兼容字段
|
|
||||||
2. 新增 `semanticAnchor / ruleProfile`
|
|
||||||
3. 由旧字段自动编译出新字段
|
|
||||||
4. 所有读取逻辑逐步切到新字段
|
|
||||||
5. 最后把 `templateWorldType` 降级为 migration-only 字段
|
|
||||||
|
|
||||||
## 5.2 `resolveRuleWorldType(...)` -> `resolveWorldRuleProfile(...)`
|
|
||||||
|
|
||||||
当前用途:
|
|
||||||
|
|
||||||
- 把 `CUSTOM` 解析回 `WUXIA / XIANXIA`
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 不再返回模板 world type
|
|
||||||
- 直接返回自定义世界自己的 `ruleProfile`
|
|
||||||
|
|
||||||
即:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
resolveWorldRuleProfile(worldType, customWorldProfile)
|
|
||||||
```
|
|
||||||
|
|
||||||
返回:
|
|
||||||
|
|
||||||
- `attributeSchema`
|
|
||||||
- `resourceLabels`
|
|
||||||
- `economyProfile`
|
|
||||||
- 其它规则信息
|
|
||||||
|
|
||||||
## 5.3 预设属性 schema -> 通用 attribute schema seed
|
|
||||||
|
|
||||||
当前用途:
|
|
||||||
|
|
||||||
- 用武侠 / 仙侠 schema 作为自定义世界 schema 参考底板
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 不再直接引用“武侠六脉 / 仙侠六轴”作为唯一底板
|
|
||||||
- 改为维护一组通用 `attribute schema seeds`
|
|
||||||
|
|
||||||
例如可按世界体验而不是题材命名:
|
|
||||||
|
|
||||||
1. 正面对抗型
|
|
||||||
2. 机动博弈型
|
|
||||||
3. 灵知洞察型
|
|
||||||
4. 共鸣契约型
|
|
||||||
5. 生存续航型
|
|
||||||
6. 高危推进型
|
|
||||||
|
|
||||||
然后由自定义世界的 `semanticAnchor` 决定如何组合或命名这些槽位。
|
|
||||||
|
|
||||||
## 5.4 预设角色模板 -> 通用角色原型骨架
|
|
||||||
|
|
||||||
当前用途:
|
|
||||||
|
|
||||||
- 从 `PRESET_CHARACTERS` 选模板角色,再覆写自定义世界内容
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 把当前模板角色骨架抽成“通用战斗原型角色”
|
|
||||||
|
|
||||||
例如保留的是:
|
|
||||||
|
|
||||||
- 技能骨架
|
|
||||||
- 动作风格
|
|
||||||
- build 倾向
|
|
||||||
- 动画资源挂载方式
|
|
||||||
|
|
||||||
而不是保留“剑之公主 / 神箭游侠”这样的预设世界人格设定。
|
|
||||||
|
|
||||||
关键点:
|
|
||||||
|
|
||||||
- 动画素材和技能骨架可以保留
|
|
||||||
- 但运行时不应再依赖具体模板角色的人设文本
|
|
||||||
|
|
||||||
## 5.5 武侠 / 仙侠场景图参考池 -> 通用场景参考桶
|
|
||||||
|
|
||||||
当前用途:
|
|
||||||
|
|
||||||
- 用武侠 / 仙侠场景关键词为自定义世界匹配默认背景图
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 改成通用 `SceneReferenceBucket`
|
|
||||||
- 每个 bucket 对应一类空间语义
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
1. 落脚处 / 归舍
|
|
||||||
2. 高压入口
|
|
||||||
3. 雨湿街巷
|
|
||||||
4. 高空通路
|
|
||||||
5. 仪式空间
|
|
||||||
6. 工业热区
|
|
||||||
7. 临水空间
|
|
||||||
8. 地底遗迹
|
|
||||||
|
|
||||||
这样就能让:
|
|
||||||
|
|
||||||
- 科幻世界
|
|
||||||
- 校园世界
|
|
||||||
- 神话世界
|
|
||||||
- 末世世界
|
|
||||||
|
|
||||||
都共享同一套“空间语义 -> 视觉参考”的逻辑。
|
|
||||||
|
|
||||||
## 5.6 武侠 / 仙侠怪物池 -> 通用生物原型库
|
|
||||||
|
|
||||||
当前用途:
|
|
||||||
|
|
||||||
- 从武侠 / 仙侠怪物池里为自定义世界匹配怪物
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 改成通用 `CreatureArchetypeProfile`
|
|
||||||
- 再由 archetype 去映射底层 preset / 数值 / 动画素材
|
|
||||||
|
|
||||||
这样做的好处:
|
|
||||||
|
|
||||||
1. 自定义世界依赖的是“潜伏者 / 重压者 / 群居体 / 异化体”
|
|
||||||
2. 而不是“这更像武侠怪还是仙侠怪”
|
|
||||||
|
|
||||||
## 5.7 `ThemePack` 底板 -> 通用语义底板
|
|
||||||
|
|
||||||
当前用途:
|
|
||||||
|
|
||||||
- 自定义世界的 ThemePack 还是从预设题材包底板开始
|
|
||||||
|
|
||||||
目标改造:
|
|
||||||
|
|
||||||
- 让 ThemePack 直接从:
|
|
||||||
- `semanticAnchor`
|
|
||||||
- `creatorIntent`
|
|
||||||
- `majorFactions`
|
|
||||||
- `coreConflicts`
|
|
||||||
- `contentReferenceProfile`
|
|
||||||
编译出来
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
**ThemePack 应变成自定义世界自己的派生物,而不是模板世界的扩写版。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 不破坏现有生成流程的迁移方案
|
|
||||||
|
|
||||||
这是这份文档最重要的部分。
|
|
||||||
|
|
||||||
正确做法不是一口气替换,而是兼容迁移。
|
|
||||||
|
|
||||||
## 阶段 A:新增通用设定字段,但不删旧字段
|
|
||||||
|
|
||||||
先做:
|
|
||||||
|
|
||||||
1. 在 `CustomWorldProfile` 上新增:
|
|
||||||
- `semanticAnchor`
|
|
||||||
- `ruleProfile`
|
|
||||||
- `contentReferenceProfile`
|
|
||||||
|
|
||||||
2. 保留:
|
|
||||||
- `templateWorldType`
|
|
||||||
|
|
||||||
3. 由当前旧字段自动编译出新字段
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 先让新结构出现
|
|
||||||
- 但现有流程完全不受影响
|
|
||||||
|
|
||||||
## 阶段 B:生成流程改为优先产出新字段
|
|
||||||
|
|
||||||
先改:
|
|
||||||
|
|
||||||
- `framework prompt`
|
|
||||||
- `customWorld.ts`
|
|
||||||
|
|
||||||
让 AI 先输出:
|
|
||||||
|
|
||||||
- 通用语义锚
|
|
||||||
- 规则提示
|
|
||||||
- 通用 archetype 线索
|
|
||||||
|
|
||||||
同时在 normalize 层继续兼容旧的 `templateWorldType`
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 新生成的自定义世界开始“原生带通用设定”
|
|
||||||
- 旧存档仍可继续读取
|
|
||||||
|
|
||||||
## 阶段 C:运行时读取切到新设定
|
|
||||||
|
|
||||||
依次改:
|
|
||||||
|
|
||||||
1. 规则层
|
|
||||||
- 从 `resolveRuleWorldType` 切到 `resolveWorldRuleProfile`
|
|
||||||
|
|
||||||
2. 属性层
|
|
||||||
- 优先读 `profile.ruleProfile.attributeSchema`
|
|
||||||
|
|
||||||
3. 资源词与经济层
|
|
||||||
- 优先读 `profile.ruleProfile.resourceLabels / economyProfile`
|
|
||||||
|
|
||||||
4. 场景图与怪物映射
|
|
||||||
- 优先读 `contentReferenceProfile`
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 让模板世界字段不再是运行时第一来源
|
|
||||||
|
|
||||||
## 阶段 D:模板世界字段退化为兼容层
|
|
||||||
|
|
||||||
这一步完成后:
|
|
||||||
|
|
||||||
1. `templateWorldType` 只用于:
|
|
||||||
- 旧存档迁移
|
|
||||||
- 老测试兼容
|
|
||||||
- 数据修复 fallback
|
|
||||||
|
|
||||||
2. 不再用于:
|
|
||||||
- 正式生成主链
|
|
||||||
- 正式运行时主链
|
|
||||||
|
|
||||||
## 阶段 E:再做深清理
|
|
||||||
|
|
||||||
只有当上面几步都完成后,才适合继续清理:
|
|
||||||
|
|
||||||
1. 非必要的模板回退逻辑
|
|
||||||
2. 非必要的主流程模板枚举消费点
|
|
||||||
3. 非必要的审计 / 工具硬编码
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 对当前主链的兼容要求
|
|
||||||
|
|
||||||
这次优化过程中,下面这些链路必须始终可用:
|
|
||||||
|
|
||||||
1. `PreGameSelectionFlow -> generateCustomWorldProfile(...)`
|
|
||||||
2. `framework -> themePack -> storyGraph -> role / landmark`
|
|
||||||
3. 保存 / 读取自定义世界 profile
|
|
||||||
4. 自定义世界开局
|
|
||||||
5. 自定义世界角色与场景生成
|
|
||||||
6. 自定义世界运行时怪物 / 物品 / 场景图 /词汇表现
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
**任何迁移都必须先补新字段,再迁读,再退旧字段,不能先删旧字段。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 建议新增或改造的最小数据结构
|
|
||||||
|
|
||||||
为了避免系统膨胀,这次不建议引入很多彼此割裂的新系统,建议只在 `CustomWorldProfile` 周边增量补三块。
|
|
||||||
|
|
||||||
## 8.1 `semanticAnchor`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface CustomWorldSemanticAnchor {
|
|
||||||
genreSignals: string[];
|
|
||||||
conflictModel: string[];
|
|
||||||
institutionHints: string[];
|
|
||||||
tabooHints: string[];
|
|
||||||
carrierHints: string[];
|
|
||||||
forceSystemHints: string[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 8.2 `ruleProfile`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface WorldRuleProfile {
|
|
||||||
attributeSchema: WorldAttributeSchema;
|
|
||||||
resourceLabels: {
|
|
||||||
hp: string;
|
|
||||||
mp: string;
|
|
||||||
maxHp: string;
|
|
||||||
maxMp: string;
|
|
||||||
damage: string;
|
|
||||||
guard: string;
|
|
||||||
range: string;
|
|
||||||
cooldown: string;
|
|
||||||
manaCost: string;
|
|
||||||
currency: string;
|
|
||||||
};
|
|
||||||
economyProfile: {
|
|
||||||
initialCurrency: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 8.3 `contentReferenceProfile`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface ContentReferenceProfile {
|
|
||||||
roleArchetypes: string[];
|
|
||||||
sceneReferenceBuckets: string[];
|
|
||||||
creatureArchetypes: string[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
这三块足够作为第一轮去模板化的最小自定义世界设定层。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 验收标准
|
|
||||||
|
|
||||||
做到以下几点,才能说明自定义世界真正开始脱离模板依赖:
|
|
||||||
|
|
||||||
1. 新生成的自定义世界框架不再需要直接输出 `WUXIA|XIANXIA` 才能工作。
|
|
||||||
2. 运行时核心逻辑优先读取 `semanticAnchor / ruleProfile / contentReferenceProfile`。
|
|
||||||
3. 自定义世界的属性 schema、资源词、经济词不再默认直接回退到武侠 / 仙侠文案。
|
|
||||||
4. 自定义世界的角色骨架、场景视觉、怪物映射能通过通用 archetype 表达。
|
|
||||||
5. 现有生成流程、存档读取、运行时体验不受破坏。
|
|
||||||
6. 旧存档仍能通过兼容层运行。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 最后结论
|
|
||||||
|
|
||||||
当前自定义世界真正缺的,不是“再删一点武侠 / 仙侠字样”,而是:
|
|
||||||
|
|
||||||
**把模板世界支撑层改写成自定义世界自己的通用设定层。**
|
|
||||||
|
|
||||||
更准确地说,这次优化要完成的是:
|
|
||||||
|
|
||||||
1. 把模板锚点改成自定义世界自己的语义锚
|
|
||||||
2. 把模板回退改成自定义世界自己的规则配置
|
|
||||||
3. 把模板角色 / 场景 / 怪物参考改成通用原型引用
|
|
||||||
4. 把 ThemePack 和生成 prompt 从“依附模板世界”改成“直接从自定义世界自身编译”
|
|
||||||
|
|
||||||
同时整个过程必须遵守一条底线:
|
|
||||||
|
|
||||||
**任何优化都不能破坏当前自定义世界生成与运行主链。**
|
|
||||||
|
|
||||||
所以这不是“删模板”的问题,而是一次:
|
|
||||||
|
|
||||||
**在兼容现有流程前提下,把自定义世界从模板依赖型架构,迁移成真正跨题材、自足型架构。**
|
|
||||||
@@ -1,882 +0,0 @@
|
|||||||
# 配装构筑 + 合成/锻造闭环详细设计
|
|
||||||
|
|
||||||
更新时间:`2026-03-25`
|
|
||||||
|
|
||||||
## 0. 设计前提
|
|
||||||
|
|
||||||
这份方案基于当前仓库已经存在的运行时结构来设计,不另起一套独立系统。
|
|
||||||
|
|
||||||
- 现有物品结构已经有 `InventoryItem.tags`、`statProfile`、`useProfile`、`buildProfile`。
|
|
||||||
- 现有装备位只有 `weapon / armor / relic` 三槽,因此本期套装与 build 成型必须围绕 2 件和 3 件完成。
|
|
||||||
- 现有战斗结算已经有 `functionEffect.damageMultiplier / incomingDamageMultiplier`,但 `equipmentEffects.ts` 中的装备数值聚合仍然基本为空壳。
|
|
||||||
- 现有素材库中已经出现 `setId`、`setName`、`pieceName`、`synergy` 等 build 元数据,但尚未进入真实伤害结算。
|
|
||||||
- 现有角色、怪物、消耗品、掉落、宝藏、NPC 交易都已经形成了资源入口,适合继续补成“获取 -> 拆解 -> 锻造 -> 配装 -> 战斗 -> 再获取”的闭环。
|
|
||||||
|
|
||||||
因此,本方案的目标不是“再做一层 UI”,而是补齐以下 4 层:
|
|
||||||
|
|
||||||
1. 标签规范化层:把当前中英混用、结构标签与语义标签混用的问题拆开。
|
|
||||||
2. 语义相似度层:用 embedding 相似度把“相近标签”自动组织为 build 与套装簇。
|
|
||||||
3. 伤害修正层:把标签结果稳定接入当前伤害公式。
|
|
||||||
4. 合成/锻造闭环:让掉落、材料、装备、buff、交易真正循环起来。
|
|
||||||
|
|
||||||
## 1. 结合当前系统的落地判断
|
|
||||||
|
|
||||||
### 1.1 现有可复用基础
|
|
||||||
|
|
||||||
- `src/types.ts`
|
|
||||||
- 已有 `ItemStatProfile`
|
|
||||||
- 已有 `ItemUseProfile`
|
|
||||||
- 已有 `ItemBuildProfile`
|
|
||||||
- 已有 `EquipmentLoadout`
|
|
||||||
- 已有 `GameState.playerEquipment`
|
|
||||||
- `src/data/itemDesign.ts`
|
|
||||||
- 已经能为装备自动生成 `buildProfile`
|
|
||||||
- 已经有 `setId / setName / pieceName / synergy`
|
|
||||||
- 已经有一批 role/tag 原型,例如 `assassin / caster / ward / fate`
|
|
||||||
- `src/data/monsterPresets.ts`
|
|
||||||
- 已经有 `habitatTags`
|
|
||||||
- 已经有较完整的 `lootTable`
|
|
||||||
- `src/data/treasureInteractions.ts`
|
|
||||||
- 已经有材料、消耗品、稀有品产出入口
|
|
||||||
- `src/hooks/useCombatFlow.ts`
|
|
||||||
- 玩家、同伴、怪物三条伤害结算路径已经齐备
|
|
||||||
- 只差把 build 结果统一注入 `damage`
|
|
||||||
|
|
||||||
### 1.2 当前缺口
|
|
||||||
|
|
||||||
- 当前 `InventoryItem.tags` 同时承担了“分类标签”和“战斗语义标签”,例如 `weapon / armor / relic / material / mana / healing` 混在一起。
|
|
||||||
- 当前角色正式数据结构里没有稳定的 `combatTags`,角色标签主要还停留在 UI 展示常量。
|
|
||||||
- 当前怪物只有 `habitatTags`,更适合掉落/生态,不适合直接进入伤害 build。
|
|
||||||
- 当前技能/物品可以恢复数值,但还不能稳定施加“限回合 build buff 标签”。
|
|
||||||
- 当前 `getEquipmentBonuses()` 没有真正读取 `statProfile + buildProfile`,导致 build 无法进入伤害。
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
- 现有系统已经具备 build 系统的数据骨架。
|
|
||||||
- 真正需要补的是“标签语义层”和“统一伤害入口”。
|
|
||||||
|
|
||||||
## 2. Build 标签设计原则
|
|
||||||
|
|
||||||
### 2.1 标签必须贴近实体语义
|
|
||||||
|
|
||||||
参与 build 的标签应尽量是玩家能直觉理解的“风格词”,而不是纯系统词。
|
|
||||||
|
|
||||||
推荐使用:
|
|
||||||
|
|
||||||
- 行为风格:`快剑`、`突进`、`追击`、`反击`、`蓄力`、`控场`
|
|
||||||
- 输出方式:`重击`、`连段`、`远射`、`雷法`、`符阵`、`毒雾`
|
|
||||||
- 生存节奏:`护体`、`守御`、`回复`、`续战`、`压血`
|
|
||||||
- 材料/流派气质:`寒铁`、`星砂`、`灵木`、`骨纹`、`镇邪`
|
|
||||||
- 阵营/身份风格:`先锋`、`游击`、`法修`、`命纹`、`统御`
|
|
||||||
|
|
||||||
不推荐直接把以下内容当作伤害 build 标签:
|
|
||||||
|
|
||||||
- `weapon`
|
|
||||||
- `armor`
|
|
||||||
- `relic`
|
|
||||||
- `material`
|
|
||||||
- `piece:weapon`
|
|
||||||
- `world:xianxia`
|
|
||||||
|
|
||||||
这些更适合保留为筛选、配方、掉落、编辑器过滤用的结构标签。
|
|
||||||
|
|
||||||
### 2.2 标签分层
|
|
||||||
|
|
||||||
建议把标签拆成三层:
|
|
||||||
|
|
||||||
| 层级 | 用途 | 示例 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 结构标签 | 分类、筛选、配方、存档兼容 | `weapon`、`armor`、`material`、`set:steel` |
|
|
||||||
| 战斗语义标签 | 进入 build 相似度和伤害修正 | `快剑`、`突进`、`护体`、`雷法` |
|
|
||||||
| 生态/素材标签 | 掉落、锻造、配方倾向 | `矿道`、`雾林`、`寒铁`、`星砂` |
|
|
||||||
|
|
||||||
推荐约束:
|
|
||||||
|
|
||||||
- `InventoryItem.tags` 继续保留结构标签与通用筛选标签。
|
|
||||||
- `ItemBuildProfile.tags` 只保留会进入 build 计算的语义标签。
|
|
||||||
- `MonsterPreset.habitatTags` 保留生态用途,不直接参与伤害。
|
|
||||||
- 新增 `combatTags` 给角色/怪物,用于战斗 build。
|
|
||||||
|
|
||||||
### 2.3 中英混用兼容
|
|
||||||
|
|
||||||
当前素材生成中已有较多英文 role/tag,角色 UI 和自定义世界中又偏中文标签,因此需要加一层标签规范化。
|
|
||||||
|
|
||||||
建议建立 `buildTagRegistry`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type BuildTagDefinition = {
|
|
||||||
id: string; // ASCII 稳定 id,例如 "quickblade"
|
|
||||||
label: string; // 展示名,例如 "快剑"
|
|
||||||
category: "role" | "style" | "resource" | "element" | "defense" | "craft";
|
|
||||||
aliases: string[]; // 兼容 assassin / duelist / 快剑流 等历史写法
|
|
||||||
description: string; // 供 embedding 使用
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
示例映射:
|
|
||||||
|
|
||||||
| 现有值 | 规范标签 |
|
|
||||||
| --- | --- |
|
|
||||||
| `assassin` | `快袭` / `切后` / `突进` |
|
|
||||||
| `duelist` | `快剑` / `连段` / `对拼` |
|
|
||||||
| `vanguard` | `先锋` / `稳压` / `护体` |
|
|
||||||
| `ward` | `守御` / `镇邪` / `护体` |
|
|
||||||
| `berserker` | `压血` / `重击` / `爆发` |
|
|
||||||
| `caster` | `法修` / `法力` / `远程` |
|
|
||||||
| `support` | `护持` / `回复` / `增益` |
|
|
||||||
| `fortress` | `重甲` / `格挡` / `反击` |
|
|
||||||
| `fate` | `命纹` / `机缘` / `冷却` |
|
|
||||||
| `commander` | `统御` / `均衡` / `队伍增益` |
|
|
||||||
|
|
||||||
## 3. 标签来源设计
|
|
||||||
|
|
||||||
### 3.1 装备标签
|
|
||||||
|
|
||||||
装备是 build 的主来源,但要区分“结构标签”和“build 标签”。
|
|
||||||
|
|
||||||
建议:
|
|
||||||
|
|
||||||
- 武器、护甲、饰品各自最多提供 `2` 个核心 build 标签。
|
|
||||||
- 若装备存在 `setId`,运行时根据套装件数额外生成“合成标签”。
|
|
||||||
- 同一标签在多个装备上重复出现时,只记一次,不按件数无限叠加。
|
|
||||||
|
|
||||||
装备运行时推荐结构:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type RuntimeEquipmentBuildSource = {
|
|
||||||
itemId: string;
|
|
||||||
slot: "weapon" | "armor" | "relic";
|
|
||||||
coreTags: string[];
|
|
||||||
setId?: string;
|
|
||||||
setName?: string;
|
|
||||||
forgeRank?: number;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 角色/怪物标签
|
|
||||||
|
|
||||||
角色和怪物都应该有自己的 `combatTags`。
|
|
||||||
|
|
||||||
建议:
|
|
||||||
|
|
||||||
- 角色固定提供 `2~3` 个核心战斗语义标签。
|
|
||||||
- 怪物固定提供 `2~3` 个核心战斗语义标签。
|
|
||||||
- `habitatTags` 不直接参与 build 伤害,而是决定掉落材料簇、锻造路线和部分弱点设计。
|
|
||||||
|
|
||||||
例子:
|
|
||||||
|
|
||||||
- 剑系角色:`快剑`、`突进`、`压制`
|
|
||||||
- 重甲怪物:`重甲`、`震击`、`守势`
|
|
||||||
- 雾林怪物生态标签:`雾林`、`湿毒`、`潜伏`
|
|
||||||
- 其中 `湿毒`、`潜伏` 可提升为 `combatTags`
|
|
||||||
- `雾林` 保留为生态/掉落标签
|
|
||||||
|
|
||||||
### 3.3 Buff 标签
|
|
||||||
|
|
||||||
buff 标签是 build 的短时放大器,也是技能与物品进入构筑闭环的关键。
|
|
||||||
|
|
||||||
buff 标签来源:
|
|
||||||
|
|
||||||
- 技能施加的限回合标签
|
|
||||||
- 消耗品施加的限回合标签
|
|
||||||
- 锻造出的铭刻/附魔效果施加的限回合标签
|
|
||||||
|
|
||||||
建议新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type TimedBuildBuff = {
|
|
||||||
id: string;
|
|
||||||
sourceType: "skill" | "item" | "forge";
|
|
||||||
sourceId: string;
|
|
||||||
name: string;
|
|
||||||
tags: string[];
|
|
||||||
durationTurns: number;
|
|
||||||
maxStacks?: number;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
推荐时长:
|
|
||||||
|
|
||||||
- 强爆发 buff:`1~2` 回合
|
|
||||||
- 节奏/机动 buff:`2~3` 回合
|
|
||||||
- 防御/续航 buff:`2~4` 回合
|
|
||||||
|
|
||||||
重复规则:
|
|
||||||
|
|
||||||
- 同名 buff 默认刷新持续时间,不新增一套完全重复标签。
|
|
||||||
- 不同来源但规范化后相同的标签,只保留一个 build 标签实例。
|
|
||||||
- 重复来源只提高优先级或刷新,不增加额外 `+1` 基础值。
|
|
||||||
|
|
||||||
## 4. 语义 embedding 与套装 build 形成方式
|
|
||||||
|
|
||||||
### 4.1 为什么要引入 embedding
|
|
||||||
|
|
||||||
如果只靠显式 `setId`,build 会很死板。
|
|
||||||
|
|
||||||
引入 embedding 后,可以让这些组合自然成型:
|
|
||||||
|
|
||||||
- `快剑` + `突进` + `追击` + `风行`
|
|
||||||
- `重甲` + `护体` + `守御` + `反击`
|
|
||||||
- `雷法` + `法器` + `过载` + `法力`
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
- 显式套装仍然存在
|
|
||||||
- 隐式语义套装也能成立
|
|
||||||
- 两者都走同一套 build 标签计算,不必再写第二套特殊规则
|
|
||||||
|
|
||||||
### 4.2 embedding 计算对象
|
|
||||||
|
|
||||||
不要对每个物品实例现算 embedding,而是只对“规范标签定义”计算。
|
|
||||||
|
|
||||||
推荐流程:
|
|
||||||
|
|
||||||
1. 为 `buildTagRegistry` 中每个规范标签生成一条 embedding 文本。
|
|
||||||
2. 文本内容由 `label + aliases + description` 组成。
|
|
||||||
3. 预计算标签相似度矩阵并缓存到本地数据文件。
|
|
||||||
4. 运行时只读相似度,不现算模型。
|
|
||||||
|
|
||||||
示例 embedding 文本:
|
|
||||||
|
|
||||||
```text
|
|
||||||
快剑:以高速轻兵器、连续出手、贴身追击为核心的近战风格;别名 duelist, swift blade, 快袭。
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 相似度函数
|
|
||||||
|
|
||||||
建议使用余弦相似度,并且只保留正向相似。
|
|
||||||
|
|
||||||
```ts
|
|
||||||
similarity(a, b) = max(0, cosine(embedding(a), embedding(b)))
|
|
||||||
```
|
|
||||||
|
|
||||||
建议阈值:
|
|
||||||
|
|
||||||
- `< 0.35`:视为无关,按 `0`
|
|
||||||
- `0.35 ~ 0.65`:弱协同
|
|
||||||
- `0.65 ~ 0.82`:强协同
|
|
||||||
- `> 0.82`:视为同 build 簇强关联
|
|
||||||
|
|
||||||
### 4.4 语义套装簇形成
|
|
||||||
|
|
||||||
推荐同时保留两种 build 成型路径:
|
|
||||||
|
|
||||||
#### A. 显式套装
|
|
||||||
|
|
||||||
- 依据 `setId`
|
|
||||||
- 2 件时生成一个合成标签:`套装:<setName>`
|
|
||||||
- 3 件时再生成一个进阶标签:`宗匠:<setName>`
|
|
||||||
|
|
||||||
由于当前 runtime 只有三槽,所以 2 件和 3 件阈值最合适。
|
|
||||||
|
|
||||||
#### B. 隐式语义套装
|
|
||||||
|
|
||||||
当激活标签里存在 `3` 个及以上标签两两相似度超过阈值时,形成“语义 build 簇”。
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- `快剑`
|
|
||||||
- `突进`
|
|
||||||
- `追击`
|
|
||||||
- `风行`
|
|
||||||
|
|
||||||
它们无需拥有相同 `setId`,也能形成一组高相似 build。
|
|
||||||
|
|
||||||
## 5. 标签修正值与伤害公式
|
|
||||||
|
|
||||||
### 5.1 核心规则
|
|
||||||
|
|
||||||
用户给出的核心要求是:
|
|
||||||
|
|
||||||
- 每个标签修正值都是 `1`
|
|
||||||
- 每多一个标签修正值加一
|
|
||||||
- 同时所有标签的 `1` 需要额外乘上与新标签的相似度
|
|
||||||
- 修正结果作用于输出伤害
|
|
||||||
|
|
||||||
将这条规则做成稳定、顺序无关的公式,可以写成:
|
|
||||||
|
|
||||||
```text
|
|
||||||
rawBuildScore(T) = |T| + Σ similarity(t_i, t_j)
|
|
||||||
(i < j)
|
|
||||||
```
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
- `|T|` 是激活标签数量,每个标签天然贡献 `1`
|
|
||||||
- 每对标签之间再贡献一段相似度分
|
|
||||||
|
|
||||||
这个公式与“新增一个标签时,额外 +1,并让旧标签的 1 再乘上与新标签的相似度”完全等价。
|
|
||||||
|
|
||||||
对应的增量写法:
|
|
||||||
|
|
||||||
```text
|
|
||||||
score_1 = 1
|
|
||||||
score_k = score_(k-1) + 1 + Σ similarity(t_i, t_k)
|
|
||||||
(1 <= i < k)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 激活标签集的选取
|
|
||||||
|
|
||||||
为了防止标签爆炸,建议运行时只取 `MAX_ACTIVE_BUILD_TAGS = 8`。
|
|
||||||
|
|
||||||
推荐优先级:
|
|
||||||
|
|
||||||
1. 限回合 buff 标签
|
|
||||||
2. 角色/怪物核心标签
|
|
||||||
3. 武器 build 标签
|
|
||||||
4. 饰品 build 标签
|
|
||||||
5. 护甲 build 标签
|
|
||||||
6. 2 件/3 件套装合成标签
|
|
||||||
|
|
||||||
重复标签去重后再参与计算。
|
|
||||||
|
|
||||||
### 5.3 从 raw score 到伤害倍率
|
|
||||||
|
|
||||||
`rawBuildScore` 不直接等于伤害倍率,否则会过大。建议再做一层线性缩放与封顶。
|
|
||||||
|
|
||||||
```text
|
|
||||||
buildDamageBonus = clamp((rawBuildScore - 1) * 0.03, 0, 0.45)
|
|
||||||
buildDamageMultiplier = 1 + buildDamageBonus
|
|
||||||
```
|
|
||||||
|
|
||||||
推荐解释:
|
|
||||||
|
|
||||||
- 单标签时没有 build 成型,不给明显收益
|
|
||||||
- 2~4 标签形成初步风格,获得 5%~18% 左右伤害增益
|
|
||||||
- 5~8 标签形成成熟 build,伤害增益逐步逼近 45% 上限
|
|
||||||
|
|
||||||
### 5.4 与当前战斗公式的接法
|
|
||||||
|
|
||||||
当前战斗中,伤害基本来自:
|
|
||||||
|
|
||||||
- 技能基础伤害
|
|
||||||
- `functionEffect.damageMultiplier`
|
|
||||||
- 装备 stat 值
|
|
||||||
|
|
||||||
建议统一改成:
|
|
||||||
|
|
||||||
```text
|
|
||||||
finalOutgoingDamage =
|
|
||||||
round(
|
|
||||||
baseSkillDamage
|
|
||||||
* functionDamageMultiplier
|
|
||||||
* equipmentStatMultiplier
|
|
||||||
* buildDamageMultiplier
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
- `equipmentStatMultiplier` 来自武器/护甲/饰品的 `statProfile`
|
|
||||||
- `buildDamageMultiplier` 来自标签相似度 build
|
|
||||||
- 先乘完再 `round`
|
|
||||||
|
|
||||||
本期先只影响“输出伤害”。
|
|
||||||
|
|
||||||
也就是:
|
|
||||||
|
|
||||||
- 玩家攻击怪物时用玩家侧标签
|
|
||||||
- 同伴攻击怪物时用同伴侧标签
|
|
||||||
- 怪物攻击玩家/同伴时用怪物侧标签
|
|
||||||
|
|
||||||
防御端 build 抵抗可以放到下一期,不必一开始就加复杂。
|
|
||||||
|
|
||||||
### 5.5 示例
|
|
||||||
|
|
||||||
某角色当前激活标签为:
|
|
||||||
|
|
||||||
- `快剑`
|
|
||||||
- `突进`
|
|
||||||
- `追击`
|
|
||||||
- `风行`
|
|
||||||
- `套装:百炼争锋`
|
|
||||||
|
|
||||||
假设相似度如下:
|
|
||||||
|
|
||||||
- `快剑-突进 = 0.82`
|
|
||||||
- `快剑-追击 = 0.78`
|
|
||||||
- `快剑-风行 = 0.65`
|
|
||||||
- `突进-追击 = 0.80`
|
|
||||||
- `突进-风行 = 0.72`
|
|
||||||
- `追击-风行 = 0.70`
|
|
||||||
- 套装标签与前四者平均相似度 `0.76`
|
|
||||||
|
|
||||||
则:
|
|
||||||
|
|
||||||
```text
|
|
||||||
rawBuildScore
|
|
||||||
= 5
|
|
||||||
+ (0.82 + 0.78 + 0.65 + 0.80 + 0.72 + 0.70)
|
|
||||||
+ (0.76 * 4)
|
|
||||||
= 11.51
|
|
||||||
|
|
||||||
buildDamageBonus
|
|
||||||
= clamp((11.51 - 1) * 0.03, 0, 0.45)
|
|
||||||
= 0.3153
|
|
||||||
|
|
||||||
buildDamageMultiplier = 1.3153
|
|
||||||
```
|
|
||||||
|
|
||||||
若本次技能基础伤害为 `28`,动作倍率为 `1.2`,装备数值倍率为 `1.14`,则:
|
|
||||||
|
|
||||||
```text
|
|
||||||
finalDamage = round(28 * 1.2 * 1.14 * 1.3153) = 50
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. 合成 / 锻造 / 回收闭环设计
|
|
||||||
|
|
||||||
### 6.1 闭环目标
|
|
||||||
|
|
||||||
让当前已有入口真正连起来:
|
|
||||||
|
|
||||||
- 怪物掉落
|
|
||||||
- 宝藏奖励
|
|
||||||
- NPC 交易
|
|
||||||
- 背包积累
|
|
||||||
- 装备更替
|
|
||||||
- 消耗品 buff
|
|
||||||
- 锻造与回收
|
|
||||||
|
|
||||||
形成闭环后,物品系统就不再只是“剧情资源池”,而是成长系统。
|
|
||||||
|
|
||||||
### 6.2 资源分层
|
|
||||||
|
|
||||||
建议把资源拆成 4 类:
|
|
||||||
|
|
||||||
| 类型 | 作用 | 当前可复用入口 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 基础材料 | 进入配方、升级、重铸 | 怪物掉落、宝藏、NPC 交易 |
|
|
||||||
| 标签精粹 | 决定 build 方向 | 拆解装备、精炼材料 |
|
|
||||||
| 套装蓝图 | 决定显式 setId 结果 | 宝藏、精英怪、任务 |
|
|
||||||
| 催化剂 | 提升稀有度、锁词条、转流派 | 稀有品、商店、Boss 掉落 |
|
|
||||||
|
|
||||||
### 6.3 推荐闭环
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
A["遭遇 / 战斗 / 宝藏 / 交易"] --> B["获得装备 / 材料 / 消耗品 / 蓝图"]
|
|
||||||
B --> C["直接装备"]
|
|
||||||
B --> D["拆解回收"]
|
|
||||||
D --> E["基础材料"]
|
|
||||||
D --> F["标签精粹"]
|
|
||||||
D --> G["蓝图碎片"]
|
|
||||||
E --> H["合成精炼材料"]
|
|
||||||
F --> H
|
|
||||||
G --> I["完整蓝图"]
|
|
||||||
H --> J["锻造新装备"]
|
|
||||||
I --> J
|
|
||||||
J --> K["重铸 / 淬炼 / 铭刻"]
|
|
||||||
K --> C
|
|
||||||
C --> L["形成 build 与伤害提升"]
|
|
||||||
L --> A
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.4 关键环节
|
|
||||||
|
|
||||||
#### A. 拆解
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 回收无用装备
|
|
||||||
- 提取 build 倾向
|
|
||||||
- 让玩家可以主动转流派
|
|
||||||
|
|
||||||
建议产出:
|
|
||||||
|
|
||||||
- 基础材料:按装备部位与稀有度产出
|
|
||||||
- 标签精粹:按 `buildProfile.tags` 产出对应流派精粹
|
|
||||||
- 套装碎片:带 `setId` 的装备有概率掉落
|
|
||||||
|
|
||||||
推荐规则:
|
|
||||||
|
|
||||||
- 普通/优秀:返还 `1~2` 基础材料 + `1` 标签精粹
|
|
||||||
- 稀有/史诗:返还 `2~4` 基础材料 + `1~2` 标签精粹
|
|
||||||
- 传说:返还 `4+` 基础材料 + `2~3` 标签精粹 + 套装碎片
|
|
||||||
|
|
||||||
#### B. 合成
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 把零散材料往更高层资源转化
|
|
||||||
|
|
||||||
推荐配方:
|
|
||||||
|
|
||||||
- `3` 个同系基础材料 -> `1` 个精炼材料
|
|
||||||
- `2` 个相近标签精粹 -> `1` 个簇催化剂
|
|
||||||
- `3` 个蓝图碎片 -> `1` 个完整蓝图
|
|
||||||
|
|
||||||
#### C. 锻造
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 制造三槽位装备
|
|
||||||
- 明确把材料生态和 build 流派联系起来
|
|
||||||
|
|
||||||
建议锻造公式:
|
|
||||||
|
|
||||||
```text
|
|
||||||
成品 = 槽位模板 + 基础材料 + 标签精粹 + 蓝图/催化剂
|
|
||||||
```
|
|
||||||
|
|
||||||
推荐规则:
|
|
||||||
|
|
||||||
- 武器:决定主要输出 build 标签
|
|
||||||
- 护甲:决定生存/反击/续战方向
|
|
||||||
- 饰品:决定资源、冷却、机动、控场补短板
|
|
||||||
|
|
||||||
#### D. 重铸
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 保留 build 主方向
|
|
||||||
- 调整副标签
|
|
||||||
|
|
||||||
推荐规则:
|
|
||||||
|
|
||||||
- 保留主标签与 `setId`
|
|
||||||
- 只在同一语义簇内重掷副标签
|
|
||||||
- 花费标签精粹 + 货币
|
|
||||||
|
|
||||||
这样玩家不会因为一次重铸直接从 `快剑` 跳成 `重甲`。
|
|
||||||
|
|
||||||
#### E. 淬炼
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 提升已有装备而不是频繁换装
|
|
||||||
|
|
||||||
推荐效果:
|
|
||||||
|
|
||||||
- 提升 `forgeRank`
|
|
||||||
- 增加 `statProfile`
|
|
||||||
- 提高套装合成标签的优先级
|
|
||||||
|
|
||||||
#### F. 铭刻 / 附魔
|
|
||||||
|
|
||||||
目标:
|
|
||||||
|
|
||||||
- 让消耗品和锻造形成直接关系
|
|
||||||
|
|
||||||
推荐效果:
|
|
||||||
|
|
||||||
- 给装备附加可触发 buff 标签
|
|
||||||
- 或直接生产“一次性战斗铭符”
|
|
||||||
|
|
||||||
例子:
|
|
||||||
|
|
||||||
- `疾风符`:`2` 回合获得 `风行`、`突进`
|
|
||||||
- `镇岳印`:`2` 回合获得 `护体`、`守御`
|
|
||||||
- `雷纹油`:`1` 回合获得 `雷法`、`过载`
|
|
||||||
|
|
||||||
## 7. 当前三槽系统下的 build 形态
|
|
||||||
|
|
||||||
由于当前只有 `weapon / armor / relic` 三槽,建议本期 build 以“2 件成型、3 件毕业”为主。
|
|
||||||
|
|
||||||
### 7.1 套装成型方式
|
|
||||||
|
|
||||||
- 1 件:只提供本件核心标签
|
|
||||||
- 2 件:生成 `套装:<setName>` 合成标签
|
|
||||||
- 3 件:再生成 `宗匠:<setName>` 进阶标签
|
|
||||||
|
|
||||||
这两个“合成标签”本质上也是普通 build 标签,因此会自动进入 embedding 相似度和伤害公式。
|
|
||||||
|
|
||||||
### 7.2 推荐 build archetype
|
|
||||||
|
|
||||||
| build | 角色/怪物核心标签 | 装备标签方向 | buff 标签方向 | 锻造材料倾向 |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| 快剑追袭 | `快剑`、`突进`、`追击` | 武器补 `连段`,饰品补 `风行` | `疾风`、`破绽` | `百炼钢`、`风羽`、`轻皮` |
|
|
||||||
| 重甲反击 | `守御`、`重甲`、`反击` | 护甲补 `护体`,饰品补 `镇势` | `立壁`、`嘲压` | `寒铁`、`壳片`、`岩核` |
|
|
||||||
| 雷法过载 | `法修`、`雷法`、`法力` | 武器补 `过载`,饰品补 `聚灵` | `引雷`、`蓄放` | `星砂`、`雷纹石`、`灵晶` |
|
|
||||||
| 命纹机缘 | `命纹`、`冷却`、`机缘` | 饰品补 `连锁`,护甲补 `续战` | `转运`、`回转` | `命纹骨片`、`旧卷`、`秘印` |
|
|
||||||
| 医理续战 | `回复`、`护持`、`续战` | 护甲补 `守御`,饰品补 `凝神` | `回气`、`清心` | `灵木`、`药囊`、`泉露` |
|
|
||||||
|
|
||||||
## 8. 与当前掉落和地图生态的关系
|
|
||||||
|
|
||||||
### 8.1 怪物生态标签不直接进伤害
|
|
||||||
|
|
||||||
当前怪物已有 `habitatTags`,更适合驱动:
|
|
||||||
|
|
||||||
- 掉落材料倾向
|
|
||||||
- 可锻造流派倾向
|
|
||||||
- 宝藏/地图资源分布
|
|
||||||
|
|
||||||
例如:
|
|
||||||
|
|
||||||
- `矿道 / 铸坊 / 废城` -> `寒铁`、`锻火`、`重甲`
|
|
||||||
- `雾林 / 沼泽` -> `毒囊`、`湿毒`、`潜伏`
|
|
||||||
- `祭坛 / 遗迹 / 古迹` -> `残卷`、`纹石`、`镇邪`
|
|
||||||
- `月湖 / 灵泉 / 天河` -> `水灵`、`回气`、`法力`
|
|
||||||
|
|
||||||
### 8.2 当前掉落表可直接扩展
|
|
||||||
|
|
||||||
当前怪物掉落里已经有很多适合闭环的原型:
|
|
||||||
|
|
||||||
- `armor + material`
|
|
||||||
- `weapon + material`
|
|
||||||
- `relic + mana`
|
|
||||||
- `consumable + material`
|
|
||||||
|
|
||||||
下一步不必推翻,只要再补:
|
|
||||||
|
|
||||||
1. 掉落物的 `craftTags`
|
|
||||||
2. 掉落物的 `buildProfile`
|
|
||||||
3. 拆解产物表
|
|
||||||
|
|
||||||
就能把这些现有掉落自然接进锻造循环。
|
|
||||||
|
|
||||||
## 9. 数据结构建议
|
|
||||||
|
|
||||||
### 9.1 类型扩展
|
|
||||||
|
|
||||||
建议在现有结构上最小扩展:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type BuildTagCategory =
|
|
||||||
| "role"
|
|
||||||
| "style"
|
|
||||||
| "resource"
|
|
||||||
| "defense"
|
|
||||||
| "element"
|
|
||||||
| "craft";
|
|
||||||
|
|
||||||
interface BuildTagDefinition {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
category: BuildTagCategory;
|
|
||||||
aliases: string[];
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ItemBuildProfile {
|
|
||||||
role: string;
|
|
||||||
tags: string[]; // 只放战斗语义标签
|
|
||||||
setId?: string;
|
|
||||||
setName?: string;
|
|
||||||
pieceName?: string;
|
|
||||||
synergy?: string[];
|
|
||||||
craftTags?: string[]; // 新增:配方/材料倾向
|
|
||||||
forgeRank?: number; // 新增:淬炼等级
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CombatTaggedActor {
|
|
||||||
combatTags: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TimedBuildBuff {
|
|
||||||
id: string;
|
|
||||||
sourceType: "skill" | "item" | "forge";
|
|
||||||
sourceId: string;
|
|
||||||
name: string;
|
|
||||||
tags: string[];
|
|
||||||
durationTurns: number;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.2 GameState 扩展
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface GameState {
|
|
||||||
activeBuildBuffs: TimedBuildBuff[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.3 技能与物品扩展
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface ItemUseProfile {
|
|
||||||
hpRestore?: number;
|
|
||||||
manaRestore?: number;
|
|
||||||
cooldownReduction?: number;
|
|
||||||
buildBuffs?: TimedBuildBuff[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FunctionEffectConfig {
|
|
||||||
damageMultiplier?: number;
|
|
||||||
incomingDamageMultiplier?: number;
|
|
||||||
healAmount?: number;
|
|
||||||
manaRestore?: number;
|
|
||||||
cooldownTickBonus?: number;
|
|
||||||
grantedBuildBuffs?: TimedBuildBuff[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 10. 运行时接入点
|
|
||||||
|
|
||||||
### 10.1 必须新增的规则层
|
|
||||||
|
|
||||||
建议新增 3 个数据/规则文件:
|
|
||||||
|
|
||||||
- `src/data/buildTags.ts`
|
|
||||||
- 标签规范化
|
|
||||||
- alias 映射
|
|
||||||
- 相似度矩阵
|
|
||||||
- `src/data/buildDamage.ts`
|
|
||||||
- 聚合激活标签
|
|
||||||
- 计算 `rawBuildScore`
|
|
||||||
- 计算 `buildDamageMultiplier`
|
|
||||||
- `src/data/forgeRecipes.ts`
|
|
||||||
- 合成、锻造、拆解、重铸配方
|
|
||||||
|
|
||||||
### 10.2 当前代码里的关键接点
|
|
||||||
|
|
||||||
#### A. `src/data/equipmentEffects.ts`
|
|
||||||
|
|
||||||
需要从“空壳”升级为真正读取:
|
|
||||||
|
|
||||||
- `statProfile`
|
|
||||||
- `buildProfile`
|
|
||||||
- 套装件数
|
|
||||||
|
|
||||||
建议这里做:
|
|
||||||
|
|
||||||
- 读取三槽装备 stat 汇总
|
|
||||||
- 计算显式套装件数
|
|
||||||
- 产出装备侧 build 标签源
|
|
||||||
|
|
||||||
#### B. `src/hooks/useCombatFlow.ts`
|
|
||||||
|
|
||||||
这里是 build 进伤害的核心接点。
|
|
||||||
|
|
||||||
当前玩家、同伴、怪物都有单独 `damage = ...` 的结算片段,建议统一收敛成:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
resolveOutgoingDamage({
|
|
||||||
actor,
|
|
||||||
target,
|
|
||||||
skill,
|
|
||||||
functionEffect,
|
|
||||||
gameState,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
统一处理:
|
|
||||||
|
|
||||||
- actor 的 `combatTags`
|
|
||||||
- actor 装备 build 标签
|
|
||||||
- actor 当前限回合 buff 标签
|
|
||||||
- 显式套装合成标签
|
|
||||||
- embedding build 伤害修正
|
|
||||||
|
|
||||||
#### C. `src/data/characterPresets.ts`
|
|
||||||
|
|
||||||
角色需要正式持有 `combatTags`,不要再只放在 UI 常量里。
|
|
||||||
|
|
||||||
#### D. `src/data/monsterPresets.ts`
|
|
||||||
|
|
||||||
怪物新增:
|
|
||||||
|
|
||||||
- `combatTags`
|
|
||||||
- `craftTags`
|
|
||||||
|
|
||||||
保留:
|
|
||||||
|
|
||||||
- `habitatTags`
|
|
||||||
|
|
||||||
#### E. `src/hooks/useGamePersistence.ts`
|
|
||||||
|
|
||||||
存档兼容必须同步补:
|
|
||||||
|
|
||||||
- `activeBuildBuffs`
|
|
||||||
- 新字段默认值
|
|
||||||
- 旧存档自动补空数组
|
|
||||||
|
|
||||||
## 11. 编辑器与策划工作流
|
|
||||||
|
|
||||||
结合当前项目“先补类型和规则,再补 UI”的经验,编辑器建议按下面顺序补。
|
|
||||||
|
|
||||||
### 11.1 标签注册表
|
|
||||||
|
|
||||||
先做一个统一标签注册表,再让各编辑器引用它。
|
|
||||||
|
|
||||||
编辑器最少应支持:
|
|
||||||
|
|
||||||
- 选择规范标签
|
|
||||||
- 查看别名
|
|
||||||
- 查看所属 build 簇
|
|
||||||
- 查看与其他标签的相似度
|
|
||||||
|
|
||||||
### 11.2 Item Catalog Editor
|
|
||||||
|
|
||||||
当前它已经能展示 `buildProfile`,下一步建议补:
|
|
||||||
|
|
||||||
- 规范标签选择器
|
|
||||||
- 当前装备可形成的 build 预览
|
|
||||||
- 2 件/3 件套装合成标签预览
|
|
||||||
- 拆解产物预览
|
|
||||||
|
|
||||||
### 11.3 角色/怪物编辑
|
|
||||||
|
|
||||||
建议把角色和怪物的 `combatTags` 录入正式数据,不再只放展示文案。
|
|
||||||
|
|
||||||
### 11.4 相似度预计算脚本
|
|
||||||
|
|
||||||
建议加一个脚本,例如:
|
|
||||||
|
|
||||||
```text
|
|
||||||
scripts/build-tag-similarity.mjs
|
|
||||||
```
|
|
||||||
|
|
||||||
负责:
|
|
||||||
|
|
||||||
- 读取标签注册表
|
|
||||||
- 生成 embedding
|
|
||||||
- 输出相似度矩阵 JSON
|
|
||||||
|
|
||||||
这样运行时就不需要联网或现算。
|
|
||||||
|
|
||||||
## 12. 数值与反滥用约束
|
|
||||||
|
|
||||||
为了让系统长期可控,建议一开始就加以下约束:
|
|
||||||
|
|
||||||
1. 同规范标签去重,不允许同词条多件装备无限叠加基础 `+1`。
|
|
||||||
2. 激活标签上限 `8`,避免后期词条爆炸。
|
|
||||||
3. 伤害 build 增益封顶 `45%`,防止纯标签乘爆。
|
|
||||||
4. 2 件/3 件套只生成合成标签,不再额外套一层独立乘区,避免双重膨胀。
|
|
||||||
5. `habitatTags` 不直接进伤害,避免出现“矿道标签提高输出”这种语义跑偏。
|
|
||||||
6. buff 标签以刷新时长为主,不以无限叠层为主。
|
|
||||||
7. 重铸只在语义近邻内滚动,避免 build 完全变异。
|
|
||||||
8. 拆解返还率不要超过完整锻造成本的 `60%~70%`,避免无限刷循环。
|
|
||||||
|
|
||||||
## 13. 分阶段实施建议
|
|
||||||
|
|
||||||
按照当前项目文档里已经验证过的开发经验,推荐顺序是“类型 -> 规则 -> hook -> UI”,不要反过来。
|
|
||||||
|
|
||||||
### 阶段 A:先补数据骨架
|
|
||||||
|
|
||||||
- 新增 `buildTagRegistry`
|
|
||||||
- 为角色/怪物补 `combatTags`
|
|
||||||
- 为物品补 `craftTags / forgeRank`
|
|
||||||
- 为 `GameState` 补 `activeBuildBuffs`
|
|
||||||
|
|
||||||
### 阶段 B:再补规则
|
|
||||||
|
|
||||||
- 实现标签规范化
|
|
||||||
- 实现 embedding 相似度矩阵
|
|
||||||
- 实现 `rawBuildScore`
|
|
||||||
- 实现三槽位显式套装合成标签
|
|
||||||
- 实现 buff 标签衰减
|
|
||||||
|
|
||||||
### 阶段 C:接入战斗
|
|
||||||
|
|
||||||
- `useCombatFlow.ts` 改为统一伤害入口
|
|
||||||
- 玩家 / 同伴 / 怪物统一读取 build
|
|
||||||
- `equipmentEffects.ts` 正式生效
|
|
||||||
|
|
||||||
### 阶段 D:补合成/锻造闭环
|
|
||||||
|
|
||||||
- 拆解
|
|
||||||
- 合成
|
|
||||||
- 锻造
|
|
||||||
- 重铸
|
|
||||||
- 铭刻
|
|
||||||
|
|
||||||
### 阶段 E:最后补编辑器与 UI
|
|
||||||
|
|
||||||
- 物品 build 预览
|
|
||||||
- 套装预览
|
|
||||||
- 锻造页
|
|
||||||
- 材料来源与标签簇提示
|
|
||||||
|
|
||||||
## 14. 一句话总结
|
|
||||||
|
|
||||||
本方案的核心不是单独增加“套装数值”,而是把装备、角色/怪物、限回合 buff 都转成统一的语义 build 标签,再用“每标签基础值 1 + 标签间 embedding 相似度”的方式形成构筑强度,并把这份构筑强度直接接进当前伤害输出,同时让怪物掉落、宝藏、交易、拆解、合成、锻造、铭刻围绕同一套标签体系形成闭环。
|
|
||||||
@@ -1,887 +0,0 @@
|
|||||||
# 等级成长、章节经验节奏与 NPC 自动定级设计
|
|
||||||
|
|
||||||
更新时间:`2026-04-20`
|
|
||||||
|
|
||||||
## 实现进度(2026-04-20 第一批)
|
|
||||||
|
|
||||||
当前仓库已按本设计先落地第一批稳定能力:
|
|
||||||
|
|
||||||
1. 已新增 `playerProgression` 正式成长状态,包含等级、当前等级经验、总经验与下级阈值。
|
|
||||||
2. 已新增等级基准与经验结算服务,并接入前后端存档归一化,旧存档默认回填为 `Lv.1 / 0 XP`。
|
|
||||||
3. 已给 `QuestReward` 补上 `experience`,新生成任务会按当前等级与任务结构给出任务经验。
|
|
||||||
4. 已将 Express 后端 `npc_quest_turn_in` 接入经验发放与升级处理,任务交付结果会反馈 `经验 +N` 与升级信息。
|
|
||||||
5. 已在冒险主面板补充最小等级展示:`Lv.` 与细经验条;任务奖励面板可看到经验数值。
|
|
||||||
6. 已收回任务日志里的直接领奖入口,任务奖励结算当前以 NPC 交付链路为准。
|
|
||||||
|
|
||||||
## 实现进度(2026-04-20 第二批)
|
|
||||||
|
|
||||||
当前仓库已继续落地第二批成长能力:
|
|
||||||
|
|
||||||
1. 已给运行时敌对 NPC / 战斗遭遇补上 `levelProfile` 与 `experienceReward`,前后端快照、战斗态和恢复链路会保留这组元数据。
|
|
||||||
2. 已新增敌对成长解析服务,当前先以玩家当前等级为 fallback,为 `npc_fight` / 敌对战斗入口自动生成等级、参考强度、战斗生命值与击杀经验。
|
|
||||||
3. 已将 Express 后端战斗胜利结算接入 `hostile_npc` 经验发放,击败敌对 NPC 后会直接更新 `playerProgression`,并写回 `hostileNpcsDefeated` 统计。
|
|
||||||
4. 已在战斗画布中补上敌对 NPC 的最小 `Lv.` 徽标展示,保持 UI 极简表达。
|
|
||||||
|
|
||||||
## 实现进度(2026-04-20 第三批)
|
|
||||||
|
|
||||||
当前仓库已继续落地第三批“章节预算 / 自动定级”能力:
|
|
||||||
|
|
||||||
1. 已新增服务端 `chapterProgressionPlanner`,会基于 `sceneChapterBlueprints` 编译每章的 `entry / exit pseudo level`、总经验预算、任务经验份额、敌对经验份额与预计击杀数。
|
|
||||||
2. 已新增 `npcLevelResolver`,会根据当前章节阶段和当前 act 的 `primaryNpcId` 自动区分 `hostile_standard / hostile_elite / hostile_boss / rival`,并输出 `source = chapter_auto` 的等级档案。
|
|
||||||
3. 已将 `npc_fight` / `npc_spar` 开战入口接入章节上下文解析;当运行时存在章节蓝图、当前章和当前 act 信息时,敌对 NPC 不再只跟随玩家当前等级,而会按章节自动定级并生成更贴合本章预算的经验奖励。
|
|
||||||
4. 已补上规划器、定级器与路由级验证,确认同一玩家在不同章节和不同阶段触发敌对战斗时,会得到不同的等级与经验结果。
|
|
||||||
|
|
||||||
本轮仍未落地的部分:
|
|
||||||
|
|
||||||
1. `ChapterExperienceLedger` 的正式持久化、按章实际经验记账与偏差回看还未接入。
|
|
||||||
2. 同章重复刷敌的 `repeatPenalty` 与超预算衰减还未落地,当前仍是“预算规划 + 单次掉落”版本。
|
|
||||||
3. 当前自动定级已优先接入敌对战斗入口,友方 / 环境 NPC 的更广泛等级消费链路仍待继续铺开。
|
|
||||||
|
|
||||||
## 0. 目标
|
|
||||||
|
|
||||||
这次设计解决 5 个必须同时成立的问题:
|
|
||||||
|
|
||||||
1. 玩家需要正式拥有 `等级 / 当前经验 / 总经验 / 升级` 这条成长主链。
|
|
||||||
2. 经验只从两类明确来源进入:
|
|
||||||
- 完成任务
|
|
||||||
- 击败敌对 NPC
|
|
||||||
3. 同等级实体必须具备同一档 `参考强度`,不能再靠散落在各处的静态数值各自漂移。
|
|
||||||
4. 系统需要能按章节评估玩家经验获取速度,而不是只在整体通关后回看“升太快/升太慢”。
|
|
||||||
5. 不同章节里的 NPC 需要按章节目标等级自动定级,保证这一章的敌我强度、经验产出和升级节奏互相闭合。
|
|
||||||
|
|
||||||
一句话结论:
|
|
||||||
|
|
||||||
**等级必须成为后端统一裁决的成长基线;章节必须先产出“目标玩家等级带 + 经验预算”,再由这套预算反推任务经验、击杀经验和本章 NPC 自动等级。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 基于当前仓库的判断
|
|
||||||
|
|
||||||
结合当前代码与文档,现状已经有足够好的骨架,但等级系统这一层还完全缺位。
|
|
||||||
|
|
||||||
### 1.1 已经具备的基础
|
|
||||||
|
|
||||||
1. `src/data/questFlow.ts`
|
|
||||||
|
|
||||||
- 已有 `QuestLogEntry / QuestStep / QuestProgressSignal / chapter quest`。
|
|
||||||
- 已经能把场景章节任务接到运行时主链。
|
|
||||||
|
|
||||||
2. `server-node/src/modules/quest/questStoryActionService.ts`
|
|
||||||
|
|
||||||
- 已经把 `接任务 / 交任务` 收回后端。
|
|
||||||
- 任务结算时已经集中处理货币、背包、好感变化。
|
|
||||||
|
|
||||||
3. `server-node/src/modules/quest/questRuntimeSignalService.ts`
|
|
||||||
|
|
||||||
- 已经会在 `npc_chat / 击败敌对 NPC / 宝藏 / 切磋` 后投递 quest signal。
|
|
||||||
|
|
||||||
4. `src/services/storyEngine/chapterDirector.ts`
|
|
||||||
|
|
||||||
- 已经能用当前场景章节任务推导 `opening -> expansion -> turning_point -> climax -> aftermath`。
|
|
||||||
|
|
||||||
5. `src/types/customWorld.ts`
|
|
||||||
|
|
||||||
- 已经有 `sceneChapterBlueprints`,说明章节顺序、幕推进和 NPC 编排已经有正式挂点。
|
|
||||||
|
|
||||||
6. `src/types/attributes.ts`、`src/data/hostileNpcPresets.ts`
|
|
||||||
- 已经有统一属性画像、怪物/NPC 统一实体方向。
|
|
||||||
- 当前敌对实体已有 `baseStats / attributeProfile / behaviorVectors`,可以继续向“同级同参考强度”收束。
|
|
||||||
|
|
||||||
### 1.2 当前缺口
|
|
||||||
|
|
||||||
当前最核心的缺口有 6 个:
|
|
||||||
|
|
||||||
1. `GameState` 没有玩家等级成长状态。
|
|
||||||
2. `QuestReward` 没有经验字段。
|
|
||||||
3. `SceneHostileNpc / SceneNpc` 没有正式等级和击杀经验字段。
|
|
||||||
4. 当前 hostile preset 的 `hp/maxHp` 仍是静态绝对值,不受章节节奏控制。
|
|
||||||
5. 章节系统没有“本章目标入场等级 / 出章等级 / 经验预算”的结构。
|
|
||||||
6. 没有“按章节自动定级”的编译器,也没有“本章经验是否超发/欠发”的记账面板。
|
|
||||||
|
|
||||||
一句话总结:
|
|
||||||
|
|
||||||
**现在仓库里已经有章节、任务、NPC 和属性系统,但还没有“成长预算层”,所以强度、奖励和章节节奏仍然缺少同一把尺。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 核心决策
|
|
||||||
|
|
||||||
## 2.1 等级、经验与 NPC 定级全部由 Express 后端裁决
|
|
||||||
|
|
||||||
必须坚持:
|
|
||||||
|
|
||||||
1. 前端只展示 `等级 / 经验条 / 升级结果 / NPC 等级徽标`。
|
|
||||||
2. 经验发放、升级、章节经验预算、NPC 自动定级全部在 Express 后端计算。
|
|
||||||
3. 前端不本地推演“这次应该升几级”“这个 NPC 应该是多少级”。
|
|
||||||
|
|
||||||
推荐新增领域目录:
|
|
||||||
|
|
||||||
- `server-node/src/modules/progression/`
|
|
||||||
|
|
||||||
建议首批模块:
|
|
||||||
|
|
||||||
- `levelBenchmarks.ts`
|
|
||||||
- `playerProgressionService.ts`
|
|
||||||
- `chapterProgressionPlanner.ts`
|
|
||||||
- `chapterExperienceLedger.ts`
|
|
||||||
- `npcLevelResolver.ts`
|
|
||||||
- `progressionRuntimeSignalService.ts`
|
|
||||||
|
|
||||||
## 2.2 MVP 经验来源只认两类事件
|
|
||||||
|
|
||||||
首版只允许两类正式经验来源:
|
|
||||||
|
|
||||||
1. `quest_turned_in`
|
|
||||||
|
|
||||||
- 任务真正交付时发经验。
|
|
||||||
- 不在“接任务”“任务 ready_to_turn_in”时发经验。
|
|
||||||
|
|
||||||
2. `hostile_npc_defeated`
|
|
||||||
- 仅限敌对 NPC / 怪物胜利结算后发经验。
|
|
||||||
- 不对 `npc_spar_completed`、普通聊天、观察、宝藏直接发经验。
|
|
||||||
|
|
||||||
这样做的原因是:
|
|
||||||
|
|
||||||
1. 最容易和当前后端任务/战斗链路接上。
|
|
||||||
2. 经验来源清晰,便于做章节预算。
|
|
||||||
3. 避免系统一开始就被碎片经验源冲散。
|
|
||||||
|
|
||||||
## 2.3 同等级 = 同参考强度
|
|
||||||
|
|
||||||
这是本次设计最重要的规则:
|
|
||||||
|
|
||||||
1. 等级是所有可比较实体共享的强度基线。
|
|
||||||
2. 同等级玩家、敌对 NPC、可战斗剧情 NPC,必须共享同一档 `参考强度`。
|
|
||||||
3. 世界属性 schema 只决定“强在哪种风格上”,不决定“同级谁天然强一截”。
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
- `Lv.8` 的重甲敌人和 `Lv.8` 的迅捷刺客可以打法不同
|
|
||||||
- 但两者的 `参考强度预算` 必须是同一档
|
|
||||||
|
|
||||||
真正的强弱差只允许来自:
|
|
||||||
|
|
||||||
1. 等级差
|
|
||||||
2. 装备 / Build / Buff / Debuff
|
|
||||||
3. 章节中明确声明的 `boss / elite` 角色通过更高等级体现,而不是同级偷加隐藏倍数
|
|
||||||
|
|
||||||
## 2.4 章节先出经验预算,再反推等级
|
|
||||||
|
|
||||||
章节设计从这次开始必须按下面顺序计算:
|
|
||||||
|
|
||||||
```text
|
|
||||||
章节顺序
|
|
||||||
-> 本章玩家目标入场等级 / 出章等级
|
|
||||||
-> 本章总经验预算
|
|
||||||
-> 任务经验份额 / 击杀经验份额
|
|
||||||
-> 本章 NPC 自动等级
|
|
||||||
-> 本章实际经验记账与偏差评估
|
|
||||||
```
|
|
||||||
|
|
||||||
不能反过来先手写一堆 NPC 强度,再看玩家能不能接住。
|
|
||||||
|
|
||||||
## 2.5 UI 只做极简表达
|
|
||||||
|
|
||||||
为了符合当前项目“UI 不默认堆规则说明”的约束,前台只建议新增 4 个轻量展示:
|
|
||||||
|
|
||||||
1. 玩家信息区:
|
|
||||||
|
|
||||||
- `Lv. X`
|
|
||||||
- 一条细经验条
|
|
||||||
|
|
||||||
2. 敌对 NPC 名牌:
|
|
||||||
|
|
||||||
- `Lv. X`
|
|
||||||
|
|
||||||
3. 任务交付结果:
|
|
||||||
|
|
||||||
- `经验 +N`
|
|
||||||
|
|
||||||
4. 升级提示:
|
|
||||||
- 单条 toast 或单行系统反馈
|
|
||||||
|
|
||||||
不在界面里默认放:
|
|
||||||
|
|
||||||
- 经验公式说明
|
|
||||||
- 章节经验预算说明
|
|
||||||
- 等级规则解释文案
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 数据结构设计
|
|
||||||
|
|
||||||
## 3.1 玩家成长状态
|
|
||||||
|
|
||||||
建议新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export interface PlayerProgressionState {
|
|
||||||
level: number;
|
|
||||||
currentLevelXp: number;
|
|
||||||
totalXp: number;
|
|
||||||
xpToNextLevel: number;
|
|
||||||
pendingLevelUps?: number;
|
|
||||||
lastGrantedSource?: 'quest' | 'hostile_npc' | null;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
挂载位置建议:
|
|
||||||
|
|
||||||
- `src/types/game.ts`
|
|
||||||
- `GameState.playerProgression`
|
|
||||||
|
|
||||||
原则:
|
|
||||||
|
|
||||||
1. 这不是 `runtimeStats` 的一部分。
|
|
||||||
2. `runtimeStats` 继续做统计计数。
|
|
||||||
3. `playerProgression` 是正式玩法状态。
|
|
||||||
|
|
||||||
## 3.2 等级基准表
|
|
||||||
|
|
||||||
建议新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export interface LevelBenchmark {
|
|
||||||
level: number;
|
|
||||||
xpToNextLevel: number;
|
|
||||||
cumulativeXpRequired: number;
|
|
||||||
referenceStrength: number;
|
|
||||||
baseHp: number;
|
|
||||||
baseMana: number;
|
|
||||||
baselineDamageScale: number;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
单一真相源建议放在:
|
|
||||||
|
|
||||||
- `server-node/src/modules/progression/levelBenchmarks.ts`
|
|
||||||
|
|
||||||
前端只通过后端投影拿结果,不自己保存第二份表。
|
|
||||||
|
|
||||||
## 3.3 实体等级档案
|
|
||||||
|
|
||||||
建议新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export type ProgressionRole =
|
|
||||||
| 'guide'
|
|
||||||
| 'ambient'
|
|
||||||
| 'support'
|
|
||||||
| 'hostile_standard'
|
|
||||||
| 'hostile_elite'
|
|
||||||
| 'hostile_boss'
|
|
||||||
| 'rival';
|
|
||||||
|
|
||||||
export interface EntityLevelProfile {
|
|
||||||
level: number;
|
|
||||||
referenceStrength: number;
|
|
||||||
chapterId?: string | null;
|
|
||||||
chapterIndex?: number | null;
|
|
||||||
progressionRole: ProgressionRole;
|
|
||||||
source: 'chapter_auto' | 'preset_override' | 'manual';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
建议接入:
|
|
||||||
|
|
||||||
- `src/types/scene.ts`
|
|
||||||
- `SceneNpc.levelProfile?: EntityLevelProfile`
|
|
||||||
- `SceneHostileNpc.levelProfile?: EntityLevelProfile`
|
|
||||||
|
|
||||||
## 3.4 任务奖励扩展
|
|
||||||
|
|
||||||
建议扩展:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export interface QuestReward {
|
|
||||||
affinityBonus: number;
|
|
||||||
currency: number;
|
|
||||||
experience: number;
|
|
||||||
items: InventoryItem[];
|
|
||||||
storyHint?: string;
|
|
||||||
intel?: { ... };
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
1. 经验是任务奖励的一等字段。
|
|
||||||
2. 经验文本不走 story hint 兜底。
|
|
||||||
3. 任务经验由后端编译,不交给 AI 决定。
|
|
||||||
|
|
||||||
## 3.5 敌对 NPC 经验掉落
|
|
||||||
|
|
||||||
建议扩展:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export interface SceneHostileNpc {
|
|
||||||
...
|
|
||||||
experienceReward?: number;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
首版只给运行时敌对 NPC 挂经验值,不强行把它沉到所有 preset 原始数据中。
|
|
||||||
|
|
||||||
原因:
|
|
||||||
|
|
||||||
1. 经验应该跟章节定级一起编译。
|
|
||||||
2. 同一个 hostile preset 出现在不同章节时,等级和经验都应不同。
|
|
||||||
3. 静态 preset 继续只表达“风格”和“原型”,不再表达最终强度。
|
|
||||||
|
|
||||||
## 3.6 章节成长计划
|
|
||||||
|
|
||||||
建议新增运行时编译结果:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export interface ChapterProgressionPlan {
|
|
||||||
chapterId: string;
|
|
||||||
chapterIndex: number;
|
|
||||||
totalChapters: number;
|
|
||||||
entryPseudoLevel: number;
|
|
||||||
exitPseudoLevel: number;
|
|
||||||
entryLevel: number;
|
|
||||||
exitLevel: number;
|
|
||||||
totalXpBudget: number;
|
|
||||||
questXpBudget: number;
|
|
||||||
hostileXpBudget: number;
|
|
||||||
expectedHostileDefeatCount: number;
|
|
||||||
paceBand: 'opening_fast' | 'steady' | 'pressure' | 'finale_dense';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
建议作为后端运行时编译结果缓存,不作为陶泥儿主直接编辑字段。
|
|
||||||
|
|
||||||
## 3.7 章节经验记账
|
|
||||||
|
|
||||||
建议新增:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export interface ChapterExperienceLedger {
|
|
||||||
chapterId: string;
|
|
||||||
chapterIndex: number;
|
|
||||||
levelAtEntry: number;
|
|
||||||
levelAtExit?: number | null;
|
|
||||||
plannedTotalXp: number;
|
|
||||||
plannedQuestXp: number;
|
|
||||||
plannedHostileXp: number;
|
|
||||||
actualQuestXp: number;
|
|
||||||
actualHostileXp: number;
|
|
||||||
expectedHostileDefeatCount: number;
|
|
||||||
actualHostileDefeatCount: number;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
用途:
|
|
||||||
|
|
||||||
1. 评估每一章经验速度。
|
|
||||||
2. 判断本章是否超发/欠发。
|
|
||||||
3. 为下一轮调参提供依据。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 等级曲线与参考强度
|
|
||||||
|
|
||||||
## 4.1 首版等级目标
|
|
||||||
|
|
||||||
首版建议:
|
|
||||||
|
|
||||||
1. 系统支持 `Lv.1 ~ Lv.20`
|
|
||||||
2. 当前主线正常通章目标不是满级
|
|
||||||
3. 标准单轮战役通关目标等级建议落在 `Lv.14 ~ Lv.15`
|
|
||||||
|
|
||||||
这样做的原因是:
|
|
||||||
|
|
||||||
1. 级差足够表达章节成长
|
|
||||||
2. 不会让前期升级过细、后期又没有空间
|
|
||||||
3. 还保留后续营地、精英支线、长期养成的余量
|
|
||||||
|
|
||||||
## 4.2 升级经验公式
|
|
||||||
|
|
||||||
建议基线公式:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
xpToNextLevel(level) = 60 + 20 * (level - 1) + 8 * (level - 1) * (level - 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
由此生成 `LevelBenchmark[]`,不在业务代码里散落重复公式。
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
1. 前期升级快,便于建立成长反馈
|
|
||||||
2. 中后期门槛逐步拉开,避免章节尾段失控
|
|
||||||
3. 可直接序列化成常量表用于测试
|
|
||||||
|
|
||||||
## 4.3 参考强度公式
|
|
||||||
|
|
||||||
建议基线公式:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
referenceStrength(level) =
|
|
||||||
100 + 16 * (level - 1) + 6 * (level - 1) * (level - 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
并同步产出:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
baseHp(level);
|
|
||||||
baseMana(level);
|
|
||||||
baselineDamageScale(level);
|
|
||||||
```
|
|
||||||
|
|
||||||
重要约束:
|
|
||||||
|
|
||||||
1. `referenceStrength` 是同级比较标尺。
|
|
||||||
2. style 只允许在同一档预算内重分布,不允许抬高总强度。
|
|
||||||
3. `elite / boss` 不允许用同级隐藏倍率偷强度,必须通过更高等级体现。
|
|
||||||
|
|
||||||
## 4.4 现有静态数值如何迁移
|
|
||||||
|
|
||||||
当前 `src/data/hostileNpcPresets.ts` 里的:
|
|
||||||
|
|
||||||
- `baseStats.hp`
|
|
||||||
- `baseStats.maxHp`
|
|
||||||
- `speed`
|
|
||||||
- `attackRange`
|
|
||||||
|
|
||||||
不建议继续全部视为最终强度。
|
|
||||||
|
|
||||||
迁移原则:
|
|
||||||
|
|
||||||
1. `attackRange / speed` 继续保留为战斗风格参数。
|
|
||||||
2. `hp / maxHp` 改为“风格形状参考”,最终值由 `等级基准 + 风格分布` 决定。
|
|
||||||
3. 现有 preset 的高血量、高机动、高压制,只用于决定“同级下怎么分布”,不改变同级总参考强度。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 经验发放规则
|
|
||||||
|
|
||||||
## 5.1 任务经验
|
|
||||||
|
|
||||||
任务经验只在 `turn_in` 时发放。
|
|
||||||
|
|
||||||
建议公式:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
baseQuestXp(targetLevel) = xpToNextLevel(targetLevel) * 0.45;
|
|
||||||
|
|
||||||
questXp =
|
|
||||||
baseQuestXp(targetLevel) *
|
|
||||||
stepCountMultiplier *
|
|
||||||
narrativeTypeMultiplier *
|
|
||||||
urgencyMultiplier;
|
|
||||||
```
|
|
||||||
|
|
||||||
建议倍率:
|
|
||||||
|
|
||||||
| 条件 | 倍率 |
|
|
||||||
| ------------------------------------------ | ------ |
|
|
||||||
| `steps = 1` | `0.85` |
|
|
||||||
| `steps = 2` | `1.0` |
|
|
||||||
| `steps >= 3` | `1.12` |
|
|
||||||
| `investigation / retrieval / relationship` | `1.0` |
|
|
||||||
| `trial / bounty` | `1.08` |
|
|
||||||
| `urgency = high` | `1.05` |
|
|
||||||
|
|
||||||
最终规则:
|
|
||||||
|
|
||||||
1. 结果四舍五入到 `5` 的倍数。
|
|
||||||
2. 章节主任务优先从本章 `questXpBudget` 出数。
|
|
||||||
3. 普通 NPC 支线如果不绑定章节,则按 `targetLevel` 单独计算。
|
|
||||||
|
|
||||||
## 5.2 击败敌对 NPC 经验
|
|
||||||
|
|
||||||
建议公式:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
baseKillXp(targetLevel) = xpToNextLevel(targetLevel) * 0.08;
|
|
||||||
|
|
||||||
killXp =
|
|
||||||
baseKillXp(targetLevel) *
|
|
||||||
stageMultiplier *
|
|
||||||
levelDeltaMultiplier *
|
|
||||||
repeatPenalty;
|
|
||||||
```
|
|
||||||
|
|
||||||
建议倍率:
|
|
||||||
|
|
||||||
| 条件 | 倍率 |
|
|
||||||
| -------------------------------- | ----------------- |
|
|
||||||
| `opening` | `0.9` |
|
|
||||||
| `expansion` | `1.0` |
|
|
||||||
| `turning_point` | `1.05` |
|
|
||||||
| `climax` | `1.15` |
|
|
||||||
| 玩家高于目标 `2` 级 | `0.7` |
|
|
||||||
| 玩家高于目标 `4` 级 | `0.3` |
|
|
||||||
| 玩家低于目标 `2` 级 | `1.15` |
|
|
||||||
| 同章同类敌对实体超过预计击杀数后 | `0.5 -> 0.2 -> 0` |
|
|
||||||
|
|
||||||
解释:
|
|
||||||
|
|
||||||
1. 同章重复刷怪必须衰减。
|
|
||||||
2. 击杀经验要响应等级差,避免低章 farming。
|
|
||||||
3. 高潮压轴敌人可以给更多经验,但仍受章节预算约束。
|
|
||||||
|
|
||||||
## 5.3 经验发放顺序
|
|
||||||
|
|
||||||
推荐统一顺序:
|
|
||||||
|
|
||||||
```text
|
|
||||||
规则动作成功
|
|
||||||
-> 生成经验 grant
|
|
||||||
-> 写入 playerProgression.totalXp / currentLevelXp
|
|
||||||
-> 处理升级
|
|
||||||
-> 回写章节 ledger
|
|
||||||
-> 生成前端提示
|
|
||||||
```
|
|
||||||
|
|
||||||
不要把经验结算拆在前端多个回调里各自加一次。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 章节经验速度评估
|
|
||||||
|
|
||||||
## 6.1 章节顺序来源
|
|
||||||
|
|
||||||
章节索引 `chapterIndex` 建议按下面顺序解析:
|
|
||||||
|
|
||||||
1. 有 `campaign pack` 时,优先用 campaign 正式顺序
|
|
||||||
2. 否则有 `sceneChapterBlueprints` 时,用蓝图顺序
|
|
||||||
3. 再否则,对 `landmarks` 从营地出发做最短路径排序
|
|
||||||
4. 若存在并列,则回退到稳定的 landmark 原始顺序
|
|
||||||
|
|
||||||
这样才能给每章一个稳定的“这是第几章”。
|
|
||||||
|
|
||||||
## 6.2 目标等级带
|
|
||||||
|
|
||||||
建议先计算“伪等级进度”,再换算成经验预算:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
chapterBoundaryPseudoLevel(i) =
|
|
||||||
1 + curve(i / totalChapters) * (terminalStoryLevel - 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
建议 `curve` 用轻微前快后稳的函数:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
curve(progress) = Math.pow(progress, 0.92);
|
|
||||||
```
|
|
||||||
|
|
||||||
随后:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
entryPseudoLevel = chapterBoundaryPseudoLevel(chapterIndex - 1);
|
|
||||||
exitPseudoLevel = chapterBoundaryPseudoLevel(chapterIndex);
|
|
||||||
chapterXpBudget =
|
|
||||||
xpForPseudoLevel(exitPseudoLevel) - xpForPseudoLevel(entryPseudoLevel);
|
|
||||||
```
|
|
||||||
|
|
||||||
这样做的好处是:
|
|
||||||
|
|
||||||
1. 每一章都有明确的入章/出章目标
|
|
||||||
2. 等级增幅随章节自然变慢
|
|
||||||
3. 经验速度评估可以直接落成表格
|
|
||||||
|
|
||||||
## 6.3 章节经验份额
|
|
||||||
|
|
||||||
默认建议:
|
|
||||||
|
|
||||||
| 章节类型 | 任务经验占比 | 击杀经验占比 |
|
|
||||||
| --------------- | ------------ | ------------ |
|
|
||||||
| 调查/关系型章节 | `75%` | `25%` |
|
|
||||||
| 平衡型章节 | `65%` | `35%` |
|
|
||||||
| 战斗/试炼型章节 | `55%` | `45%` |
|
|
||||||
|
|
||||||
章节类型判定可由下面几项共同决定:
|
|
||||||
|
|
||||||
1. `SceneChapterBlueprint.acts` 数量
|
|
||||||
2. 当前章节 hostile NPC 数量
|
|
||||||
3. 当前章节任务 step 中战斗目标占比
|
|
||||||
5. linked thread 是否为主线高压线程
|
|
||||||
|
|
||||||
## 6.4 实际速度评估规则
|
|
||||||
|
|
||||||
每章结束后,至少计算下面三个值:
|
|
||||||
|
|
||||||
1. `actualTotalXp / plannedTotalXp`
|
|
||||||
2. `actualHostileXp / plannedHostileXp`
|
|
||||||
3. `levelAtExit - plannedExitLevel`
|
|
||||||
|
|
||||||
建议判定:
|
|
||||||
|
|
||||||
| 偏差 | 判断 |
|
|
||||||
| ----------- | -------- |
|
|
||||||
| `±10%` 内 | 正常 |
|
|
||||||
| `10% ~ 20%` | 需观察 |
|
|
||||||
| `> 20%` | 必须调参 |
|
|
||||||
|
|
||||||
这就是“评估每一章获得经验速度”的正式口径,不再用主观感觉判断。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. NPC 自动定级规则
|
|
||||||
|
|
||||||
## 7.1 默认角色分类
|
|
||||||
|
|
||||||
建议默认按当前幕和敌我属性推导 `progressionRole`:
|
|
||||||
|
|
||||||
1. 当前幕 `primaryNpcId`
|
|
||||||
|
|
||||||
- 若 hostile:`hostile_elite` 或 `hostile_boss`
|
|
||||||
- 若非 hostile:`guide` 或 `rival`
|
|
||||||
|
|
||||||
2. 非主角色 hostile NPC
|
|
||||||
|
|
||||||
- `hostile_standard`
|
|
||||||
|
|
||||||
3. 非主角色友方 NPC
|
|
||||||
- `support` 或 `ambient`
|
|
||||||
|
|
||||||
如需修正,再允许章节蓝图加可选 override,但不要求陶泥儿主每次手填。
|
|
||||||
|
|
||||||
## 7.2 等级锚点
|
|
||||||
|
|
||||||
每章先得到:
|
|
||||||
|
|
||||||
1. `entryLevel`
|
|
||||||
2. `exitLevel`
|
|
||||||
|
|
||||||
然后按当前阶段得到阶段锚点:
|
|
||||||
|
|
||||||
| 阶段 | 目标锚点 |
|
|
||||||
| --------------- | ----------------------------- |
|
|
||||||
| `opening` | 接近 `entryLevel` |
|
|
||||||
| `expansion` | `entryLevel ~ exitLevel` 中段 |
|
|
||||||
| `turning_point` | 接近 `exitLevel` |
|
|
||||||
| `climax` | `exitLevel` |
|
|
||||||
| `aftermath` | `exitLevel - 1` 或持平 |
|
|
||||||
|
|
||||||
## 7.3 最终定级
|
|
||||||
|
|
||||||
建议公式:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
baseStageLevel = interpolate(entryLevel, exitLevel, stageProgress);
|
|
||||||
|
|
||||||
npcLevel = round(baseStageLevel) + roleOffset(progressionRole);
|
|
||||||
```
|
|
||||||
|
|
||||||
建议 offset:
|
|
||||||
|
|
||||||
| role | offset |
|
|
||||||
| ------------------ | -------- |
|
|
||||||
| `ambient` | `-1` |
|
|
||||||
| `support` | `0` |
|
|
||||||
| `guide` | `0` |
|
|
||||||
| `rival` | `0 ~ +1` |
|
|
||||||
| `hostile_standard` | `0` |
|
|
||||||
| `hostile_elite` | `+1` |
|
|
||||||
| `hostile_boss` | `+2` |
|
|
||||||
|
|
||||||
约束:
|
|
||||||
|
|
||||||
1. 统一 clamp 到 `1 ~ terminalStoryLevel + 2`
|
|
||||||
2. 不允许出现“第 3 章普通怪高于第 6 章精英”的跨章倒挂
|
|
||||||
3. `hostile_boss` 如果需要更强,必须给更高等级,不准同级偷倍数
|
|
||||||
|
|
||||||
## 7.4 同级不同风格
|
|
||||||
|
|
||||||
NPC 等级确定后,再把 `referenceStrength` 套到具体风格:
|
|
||||||
|
|
||||||
1. 重装型:
|
|
||||||
|
|
||||||
- 生命占比更高
|
|
||||||
- 爆发占比更低
|
|
||||||
|
|
||||||
2. 迅捷型:
|
|
||||||
|
|
||||||
- 生命占比更低
|
|
||||||
- 出手与压制占比更高
|
|
||||||
|
|
||||||
3. 控场型:
|
|
||||||
- 法力/控制预算更高
|
|
||||||
|
|
||||||
但这一步只能做“分布调整”,不能改变同级总参考强度。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 与当前仓库的接入点
|
|
||||||
|
|
||||||
## 8.1 第一批必须改的类型
|
|
||||||
|
|
||||||
1. `src/types/game.ts`
|
|
||||||
|
|
||||||
- 新增 `playerProgression`
|
|
||||||
|
|
||||||
2. `src/types/story.ts`
|
|
||||||
|
|
||||||
- `QuestReward.experience`
|
|
||||||
|
|
||||||
3. `src/types/scene.ts`
|
|
||||||
|
|
||||||
- `SceneNpc.levelProfile`
|
|
||||||
- `SceneHostileNpc.levelProfile`
|
|
||||||
- `SceneHostileNpc.experienceReward`
|
|
||||||
|
|
||||||
4. `packages/shared/src/contracts/story.ts`
|
|
||||||
- 如果需要让前后端合同正式共享等级展示字段,在这里补最小契约
|
|
||||||
|
|
||||||
## 8.2 第一批必须改的后端模块
|
|
||||||
|
|
||||||
1. `server-node/src/modules/quest/questStoryActionService.ts`
|
|
||||||
|
|
||||||
- `resolveQuestTurnInAction(...)` 里追加任务经验发放
|
|
||||||
|
|
||||||
2. `server-node/src/modules/quest/questRuntimeSignalService.ts`
|
|
||||||
|
|
||||||
- 保持 quest signal 职责
|
|
||||||
- 不直接负责经验裁决,只把可用信号交给 progression 模块
|
|
||||||
|
|
||||||
3. `server-node/src/modules/combat/**`
|
|
||||||
|
|
||||||
- 在胜利结算后发 hostile NPC 经验
|
|
||||||
|
|
||||||
4. `server-node/src/modules/story/**`
|
|
||||||
|
|
||||||
- 在切章、进场、恢复场景时接入章节成长计划与 ledger
|
|
||||||
|
|
||||||
5. 新增 `server-node/src/modules/progression/**`
|
|
||||||
- 成为等级、经验、章节定级唯一真相源
|
|
||||||
|
|
||||||
## 8.3 第一批不建议重写的部分
|
|
||||||
|
|
||||||
这轮不建议一开始就重写:
|
|
||||||
|
|
||||||
1. 整套前端战斗 UI
|
|
||||||
2. 整套属性系统
|
|
||||||
3. Quest UI 大面板结构
|
|
||||||
4. 所有 hostile preset 原始配置文件
|
|
||||||
|
|
||||||
更稳的做法是:
|
|
||||||
|
|
||||||
1. 先让后端算出等级与经验
|
|
||||||
2. 再把结果投影到现有运行时字段
|
|
||||||
3. 最后再逐步清理旧静态强度残留
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 迁移策略
|
|
||||||
|
|
||||||
## 9.1 旧存档兼容
|
|
||||||
|
|
||||||
旧存档没有 `playerProgression` 时:
|
|
||||||
|
|
||||||
1. 默认初始化为 `Lv.1`
|
|
||||||
2. `totalXp = 0`
|
|
||||||
3. `currentLevelXp = 0`
|
|
||||||
4. `xpToNextLevel = benchmark[1].xpToNextLevel`
|
|
||||||
|
|
||||||
如果后续希望更平滑,可在第二轮增加“按当前章节进度反推起始等级”的迁移脚本,但首版先不要让迁移复杂化。
|
|
||||||
|
|
||||||
## 9.2 旧 hostile preset 兼容
|
|
||||||
|
|
||||||
旧 preset 里的 `hp/maxHp` 首版处理建议:
|
|
||||||
|
|
||||||
1. 先保留原字段作为 style hint
|
|
||||||
2. 运行时用 level benchmark 覆盖最终 `hp/maxHp`
|
|
||||||
3. 保证当前素材和行为标签不需要重做
|
|
||||||
|
|
||||||
## 9.3 旧任务兼容
|
|
||||||
|
|
||||||
旧任务没有 `reward.experience` 时:
|
|
||||||
|
|
||||||
1. 默认按 `0` 处理
|
|
||||||
2. 仅新生成或重新编译的任务带经验
|
|
||||||
3. 章节主任务优先切到新编译链
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 开发顺序
|
|
||||||
|
|
||||||
## 阶段 A:先把等级状态立住
|
|
||||||
|
|
||||||
先做:
|
|
||||||
|
|
||||||
1. `PlayerProgressionState`
|
|
||||||
2. `LevelBenchmark[]`
|
|
||||||
3. 经验加点与升级服务
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
1. 后端能正确加经验与升级
|
|
||||||
2. 前端能稳定展示 `Lv. X / 经验条`
|
|
||||||
|
|
||||||
## 阶段 B:接任务经验
|
|
||||||
|
|
||||||
先做:
|
|
||||||
|
|
||||||
1. `QuestReward.experience`
|
|
||||||
2. `quest turn-in` 经验发放
|
|
||||||
3. 任务结果文案里补 `经验 +N`
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
1. 交付任务后能加经验
|
|
||||||
2. 升级时能正确连跳
|
|
||||||
|
|
||||||
## 阶段 C:接章节预算与 NPC 自动定级
|
|
||||||
|
|
||||||
先做:
|
|
||||||
|
|
||||||
1. `ChapterProgressionPlan`
|
|
||||||
2. `npcLevelResolver`
|
|
||||||
3. runtime hostile NPC 经验值生成
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
1. 进入不同章节时 NPC 等级自动变化
|
|
||||||
2. 同级不同风格但参考强度一致
|
|
||||||
|
|
||||||
## 阶段 D:接击败敌对 NPC 经验与章节 ledger
|
|
||||||
|
|
||||||
先做:
|
|
||||||
|
|
||||||
1. hostile defeat 经验
|
|
||||||
2. `ChapterExperienceLedger`
|
|
||||||
3. 章节偏差评估输出
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
1. 每章都能看到计划/实际经验偏差
|
|
||||||
2. 重复刷同章敌对 NPC 不会破坏曲线
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. 验收标准
|
|
||||||
|
|
||||||
做到下面这些,才算这次等级系统设计真正落地:
|
|
||||||
|
|
||||||
1. 玩家正式拥有 `等级 + 经验 + 升级` 主链。
|
|
||||||
2. 经验来源只通过后端发放,前端不本地算经验。
|
|
||||||
3. 同等级实体共享同一档 `参考强度`。
|
|
||||||
4. 每章都能生成 `入章等级 / 出章等级 / 经验预算`。
|
|
||||||
5. 每章的 NPC 都能按章节自动定级。
|
|
||||||
6. 完成任务、击败敌对 NPC 都能稳定获得经验。
|
|
||||||
7. 章节结束后能评估“这一章经验速度是否正常”。
|
|
||||||
8. 现有任务、章节、属性和 hostile NPC 主链不被推翻,只是在其上新增成长预算层。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. 最后结论
|
|
||||||
|
|
||||||
这次等级系统设计的重点,不是简单在 UI 上加一个 `Lv.1`,而是把当前仓库里已经存在的:
|
|
||||||
|
|
||||||
1. 章节闭环
|
|
||||||
2. 任务结算
|
|
||||||
3. 敌对 NPC 胜利事件
|
|
||||||
4. 统一属性与 hostile preset
|
|
||||||
|
|
||||||
收束到一条新的成长主链:
|
|
||||||
|
|
||||||
**章节先给出目标等级与经验速度,系统再按这套速度自动设置 NPC 等级,并把任务交付与击败敌对 NPC 统一变成可控的经验入口。**
|
|
||||||
|
|
||||||
这样之后,等级不再只是一个展示数字,而会真正变成:
|
|
||||||
|
|
||||||
- 玩家成长速度的刻度
|
|
||||||
- 同级参考强度的刻度
|
|
||||||
- 章节节奏是否合理的刻度
|
|
||||||
- 不同章节 NPC 强度自动落位的刻度
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# 移动端创作页新建作品紧凑布局设计
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
移动端创作页顶部的新建作品模块只承担快速进入创作模板的作用,不承担规则解释和长说明承载。模块在首屏中最多占用约 1/3 高度,把更多空间留给作品列表和筛选操作。
|
|
||||||
|
|
||||||
## 落地范围
|
|
||||||
|
|
||||||
- 入口组件:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`
|
|
||||||
- 外层页面:`src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
- 模板元数据继续复用 `PLATFORM_CREATION_TYPES`,不新增前端业务逻辑。
|
|
||||||
|
|
||||||
## 移动端布局规则
|
|
||||||
|
|
||||||
1. 顶部标题行压缩成单行:左侧标题,右侧仅保留简短状态,不再显示说明段落。
|
|
||||||
2. 模板入口在手机端使用横向滚动胶囊卡片,每个卡片保持单行动作感,不堆叠成长列表。
|
|
||||||
3. 卡片高度控制在约 4rem 内,标题与状态信息并排组织,避免大留白。
|
|
||||||
4. 模块本体使用 `max-height: 33svh` 作为硬约束,内容超出时优先在模板入口行内横向滚动,不撑高页面。
|
|
||||||
5. 桌面端保持网格入口,但同步收紧内边距和卡片留白,避免移动端与桌面端表现割裂。
|
|
||||||
6. 横向滚动模板行必须隐藏原生滚动条,保留滑动能力,避免底部出现过粗的视觉条。
|
|
||||||
7. 模板入口排序以可创作为第一优先级:可创建卡片保持原配置内相对顺序排在前面,锁定且展示“敬请期待”的卡片保持原配置内相对顺序排在后面。
|
|
||||||
|
|
||||||
## 文案约束
|
|
||||||
|
|
||||||
- UI 不新增规则说明类文案。
|
|
||||||
- 原有“直接选择游戏创作模板,立刻进入对应的共创工作台。”说明在移动端隐藏,桌面端保留为辅助说明。
|
|
||||||
- 可创建的模板卡不展示“可创建”状态标签,只保留标题、短副标题和进入箭头。
|
|
||||||
- 锁定的模板卡统一以“敬请期待”作为状态标注,不再显示“锁定”。
|
|
||||||
- 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分钟创作一个精品互动玩法”标题,玩法参考图卡带直接作为首屏入口;移动端卡带必须支持横向拖动滑动。
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# 移动端草稿页作品列表统一卡片设计 2026-04-29
|
|
||||||
|
|
||||||
## 背景
|
|
||||||
|
|
||||||
草稿页的作品模块需要同时承载 RPG、拼图和大鱼吃小鱼等玩法。不同玩法卡片不能各自展示阶段、素材、主题等细节标签,否则作品列表会在移动端显得拥挤,并且草稿作品会暴露过多编辑态信息。
|
|
||||||
|
|
||||||
本次将作品列表卡片收口成统一信息结构:草稿只用于快速识别和继续创作,已发布作品才展示公开数据;删除与分享等低频操作收进长按动作面板,避免列表常态被按钮挤占。
|
|
||||||
|
|
||||||
## 落地范围
|
|
||||||
|
|
||||||
- 列表容器:`src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
|
||||||
- 作品卡片:`src/components/custom-world-home/CustomWorldWorkCard.tsx`
|
|
||||||
- 不改动作品数据聚合、筛选、打开和体验逻辑。
|
|
||||||
- 已发布作品保留分享能力,卡片右上角常驻分享图标;可删除作品保留删除能力,但常态不显示删除按钮。
|
|
||||||
|
|
||||||
## 卡片结构规则
|
|
||||||
|
|
||||||
1. 卡片整体对齐发现 / 分类页的横向作品列表结构:内容层承载标题、状态、类型、摘要与必要数据,封面作为不占内容布局的右半区透明背景层。
|
|
||||||
2. 不再显示阶段、主题、素材完成度、作者、作品号等额外标签。
|
|
||||||
3. 标题区域保留作品状态与游戏类型;草稿和已发布状态只用图标化 UI 标识,不再在卡片上显示“草稿 / 已发布”文字。
|
|
||||||
4. 草稿卡片到作品描述为止,不显示右侧“继续创作”等固定动作、统计、作品号或公开指标。
|
|
||||||
5. 已发布卡片在描述下方显示三项公开指标:游玩数、改造数、点赞数。
|
|
||||||
6. 已发布卡片右上角显示分享图标,点击后复制作品分享文案,不触发卡片打开。
|
|
||||||
7. 长按草稿作品弹出独立动作面板,只展示删除作品;长按已发布作品弹出独立动作面板,展示分享和删除。动作按钮点击后不得触发卡片打开。
|
|
||||||
8. 卡片不显示最后修改时间;`updatedAt` 只用于作品列表排序。
|
|
||||||
9. 生成中的卡片在整卡上叠加半透明蒙版、旋转等待符号和“生成中...”标识;蒙版不能移除标题、状态、类型、摘要、右侧封面等原有信息。
|
|
||||||
|
|
||||||
## 公开指标重点展示补充
|
|
||||||
|
|
||||||
1. 已发布作品的三项公开指标不得继续使用标签样式展示,必须参考作品详情页的统计区,采用“小标签 + 大数字 + 单位”的重点信息结构。
|
|
||||||
2. 指标文案统一为“游玩”“改造”“点赞”,不得在创作页卡片中展示 `Remix` 英文。
|
|
||||||
3. 用户每次进入创作页时,前端读取上一次进入该页面缓存的公开指标快照;当已发布作品卡片滑动进入视口后,数字从缓存值增长到本次接口返回的最新值。
|
|
||||||
4. 若最新值高于缓存值,动画完成后在对应指标右下角展示红色向上箭头和本次上涨的具体数值,字号低于主数字,避免抢占主信息层级。
|
|
||||||
5. 若没有缓存值、缓存值不低于最新值或作品仍是草稿,则直接显示最新值,不展示上涨标记。
|
|
||||||
6. 每张作品卡片继续使用作品封面作为右侧半区透明背景主视觉;封面从右向左渐隐,不能出现独立方形边界,也不能因为列表收缩挤占正文布局或被拉伸变形。
|
|
||||||
7. 作品列表按 `updatedAt` 倒序排列;前端排序需要兼容 ISO 时间和 Rust 后端常用的 `seconds.microsZ` 时间文本。
|
|
||||||
8. 若玩法摘要缺少 `coverImageSrc`,允许从同一作品的正式关卡图、背景图或素材图里取第一张可用图片作为卡片背景兜底。
|
|
||||||
9. 若作品真实图片为空、私有资源换签失败或浏览器图片加载失败,卡片必须切到对应玩法的 `public/creation-type-references/` 参考图;最终兜底底色使用平台浅粉暖白色系,不允许退回黑色普通面板。
|
|
||||||
|
|
||||||
## 移动端布局规则
|
|
||||||
|
|
||||||
1. 作品列表默认使用单列纵向列表,视觉上与发现页分类列表保持一致。
|
|
||||||
2. 移动端每张卡片使用绝对定位右半区封面背景层,封面在右边缘最清晰、向左渐隐;草稿卡即使复用分类页基础类名,也必须用自身选择器覆盖分类页的 `4.3rem + 正文 + action` 三列规则,避免正文被压进窄列。
|
|
||||||
3. 已发布作品的公开指标在卡片正文内保留,但需要压缩字号和间距,不能依赖右侧封面列参与排版。
|
|
||||||
4. 小屏卡片降低高度、内边距、标题字号和徽标尺寸,避免长标题或中文描述撑破容器。
|
|
||||||
5. 右侧封面层本身必须带玩法参考图 CSS 背景兜底;`img` 的真实封面或 `ResolvedAssetImage` fallback 加载失败时,也不能出现空白或黑卡。
|
|
||||||
|
|
||||||
## 网页端布局规则
|
|
||||||
|
|
||||||
1. 网页端作品架不能继续拉成整行超宽列表;从 `768px` 起使用多列卡片式网格,默认两列,宽屏提升到三列。
|
|
||||||
2. 网页端卡片保留移动端同一信息结构,但卡片高度增加,正文区可显示更多摘要与公开指标,右侧封面改为更高的半透明视觉区。
|
|
||||||
3. 删除与分享仍然只在长按动作面板或键盘揭示态显示;默认态不得透出红色删除底层。
|
|
||||||
|
|
||||||
## 文案约束
|
|
||||||
|
|
||||||
- 不新增功能说明类文案。
|
|
||||||
- 空态和错误态沿用现有文案。
|
|
||||||
- 中文标题、描述和指标需要在卡片内截断或换行,不得因长文本破坏布局。
|
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
# 高好感角色聊天内委托触发与领取流程设计
|
|
||||||
|
|
||||||
更新时间:`2026-04-19`
|
|
||||||
|
|
||||||
## 0. 目标
|
|
||||||
|
|
||||||
这次迭代解决的是一个很具体的体验断层:
|
|
||||||
|
|
||||||
1. 当前角色委托主要还是从 NPC 互动菜单里直接出现,缺少“先聊上 1-2 轮,再顺着上下文自然托付任务”的过渡。
|
|
||||||
2. 聊天态虽然已经有多轮对话、自定义输入和选项建议,但没有“临时任务 offer”这一层中间状态。
|
|
||||||
3. 现有任务详情面板已经能看任务、领奖励,但还不能承接“任务尚未正式入日志,只是对方刚提出委托”的场景。
|
|
||||||
|
|
||||||
目标不是新造一套聊天任务系统,而是把现有:
|
|
||||||
|
|
||||||
- `npc_chat` 多轮聊天流
|
|
||||||
- `generateQuestForNpcEncounter(...)` 任务生成链
|
|
||||||
- `QuestLogEntry` 任务日志结构
|
|
||||||
- `AdventurePanelOverlays` 任务详情面板
|
|
||||||
|
|
||||||
串成一条更自然的“聊天内委托”链路。
|
|
||||||
|
|
||||||
一句话目标:
|
|
||||||
|
|
||||||
**当玩家与好感度大于 0 的角色聊天时,先寒暄 1-2 轮,再由角色顺着上下文提出委托;玩家可查看、换任务、放弃任务,确认领取后任务才正式进入日志,并恢复自由聊天。**
|
|
||||||
|
|
||||||
## 1. 这次不做什么
|
|
||||||
|
|
||||||
为了避免系统边界漂移,这次明确不做下面这些事:
|
|
||||||
|
|
||||||
1. 不重写任务生成器。
|
|
||||||
- “任务”和“更换任务”都必须复用现有 `generateQuestForNpcEncounter(...)` 链路。
|
|
||||||
- 也就是说,仍然走现在的 `evaluateQuestOpportunity -> AI / fallback quest intent -> compileQuestIntentToQuest`。
|
|
||||||
|
|
||||||
2. 不把聊天态任务 offer 直接视为已接任务。
|
|
||||||
- 对方刚把委托提出来时,它还只是 `pending offer`,不应立即写进 `gameState.quests`。
|
|
||||||
- 只有玩家点击“领取任务”后,才正式调用现有任务接取写入逻辑。
|
|
||||||
|
|
||||||
3. 不在 UI 默认堆说明文字。
|
|
||||||
- 聊天态只切换三项操作:
|
|
||||||
- `查看任务`
|
|
||||||
- `更换任务`
|
|
||||||
- `放弃任务`
|
|
||||||
- 不额外在主界面堆功能说明。
|
|
||||||
|
|
||||||
4. 不改成必须走服务端聊天。
|
|
||||||
- 当前多轮 NPC 聊天仍沿用前端本地的 `handleNpcChatTurn(...)` 流程。
|
|
||||||
- 这次只是在聊天流程里插入任务 offer 状态。
|
|
||||||
|
|
||||||
## 2. 核心流程
|
|
||||||
|
|
||||||
## 2.1 触发条件
|
|
||||||
|
|
||||||
只有同时满足下面条件时,聊天中才允许提出委托:
|
|
||||||
|
|
||||||
1. 当前遭遇是角色型 NPC。
|
|
||||||
- `encounter.characterId` 存在。
|
|
||||||
|
|
||||||
2. 当前好感度大于 `0`。
|
|
||||||
|
|
||||||
3. 当前角色没有未结清任务。
|
|
||||||
- 复用 `getQuestForIssuer(...)` 判断。
|
|
||||||
|
|
||||||
4. 当前聊天里还没有待处理的任务 offer。
|
|
||||||
|
|
||||||
5. 已经完成前置寒暄轮次。
|
|
||||||
- 默认要求先完成 `1-2` 轮自然聊天。
|
|
||||||
- 建议规则:
|
|
||||||
- `affinity >= 30` 时,完成 `1` 轮后即可进入委托时机。
|
|
||||||
- `0 < affinity < 30` 时,完成 `2` 轮后再进入委托时机。
|
|
||||||
|
|
||||||
## 2.2 聊天轮次切换
|
|
||||||
|
|
||||||
正常聊天时:
|
|
||||||
|
|
||||||
- 保持现有三条 `npc_chat` 建议选项
|
|
||||||
- 保持自定义输入可用
|
|
||||||
|
|
||||||
当触发委托时:
|
|
||||||
|
|
||||||
1. 先正常生成本轮 NPC 回复。
|
|
||||||
2. 随后调用现有 `generateQuestForNpcEncounter(...)` 生成一份 `pending quest offer`。
|
|
||||||
3. 在当前轮次追加一段 NPC 委托台词。
|
|
||||||
4. 把当前轮次选项替换成:
|
|
||||||
- 第一项:`查看任务`
|
|
||||||
- 第二项:`更换任务`
|
|
||||||
- 第三项:`放弃任务`
|
|
||||||
5. 临时隐藏自定义输入。
|
|
||||||
|
|
||||||
这意味着聊天态此时进入一个短暂的“任务处理态”,直到玩家:
|
|
||||||
|
|
||||||
- 查看并领取
|
|
||||||
- 更换
|
|
||||||
- 放弃
|
|
||||||
|
|
||||||
其中任一分支结算完成后,再恢复自由聊天。
|
|
||||||
|
|
||||||
## 2.3 查看任务
|
|
||||||
|
|
||||||
点击 `查看任务` 时:
|
|
||||||
|
|
||||||
1. 不立即写入任务日志。
|
|
||||||
2. 直接复用现有任务详情弹层展示 `pending quest offer` 的详情。
|
|
||||||
3. 任务详情面板在这类任务上新增主按钮:
|
|
||||||
- `领取任务`
|
|
||||||
|
|
||||||
这一步的关键是:
|
|
||||||
|
|
||||||
**查看任务只是看,不等于接。**
|
|
||||||
|
|
||||||
## 2.4 领取任务
|
|
||||||
|
|
||||||
点击 `领取任务` 时:
|
|
||||||
|
|
||||||
1. 使用当前 `pending quest offer` 的 `QuestLogEntry`,调用现有任务接取写入逻辑,把任务正式写入 `gameState.quests`。
|
|
||||||
2. 同时把这轮动作写回聊天:
|
|
||||||
- 追加玩家一句明确接受委托的话。
|
|
||||||
- 可追加一条简短 NPC 确认回应,或直接用现有结果文案转成对话语义。
|
|
||||||
3. 更新 `storyHistory`,确保后续聊天上下文知道“这份委托已经接下”。
|
|
||||||
4. 清空 `pending quest offer`。
|
|
||||||
5. 恢复正常 `npc_chat` 建议选项与自定义输入。
|
|
||||||
|
|
||||||
## 2.5 更换任务
|
|
||||||
|
|
||||||
点击 `更换任务` 时:
|
|
||||||
|
|
||||||
1. 必须再次调用现有 `generateQuestForNpcEncounter(...)`。
|
|
||||||
2. 旧的 `pending quest offer` 被新的覆盖。
|
|
||||||
3. 当前聊天追加一条“对方换了个委托”的回应。
|
|
||||||
4. 仍然维持任务处理态:
|
|
||||||
- 继续显示
|
|
||||||
- `查看任务`
|
|
||||||
- `更换任务`
|
|
||||||
- `放弃任务`
|
|
||||||
- 自定义输入仍隐藏
|
|
||||||
|
|
||||||
这里的关键约束是:
|
|
||||||
|
|
||||||
**更换任务不是本地改标题或改描述,而是重新走现有任务生成链。**
|
|
||||||
|
|
||||||
## 2.6 放弃任务
|
|
||||||
|
|
||||||
点击 `放弃任务` 时:
|
|
||||||
|
|
||||||
1. 直接丢弃当前 `pending quest offer`。
|
|
||||||
2. 在对话里补一条“玩家暂时不接”的回应。
|
|
||||||
3. 恢复自由聊天:
|
|
||||||
- 再次显示正常 `npc_chat` 建议
|
|
||||||
- 恢复自定义输入
|
|
||||||
|
|
||||||
放弃这里只作用于“待领取委托”,不会影响已经入日志的正式任务。
|
|
||||||
|
|
||||||
## 3. 数据与状态设计
|
|
||||||
|
|
||||||
## 3.1 聊天态新增待领取任务状态
|
|
||||||
|
|
||||||
建议把这次临时状态挂在 `StoryNpcChatState` 上,而不是直接写入 `GameState.quests`。
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface StoryNpcQuestOfferState {
|
|
||||||
quest: QuestLogEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StoryNpcChatState {
|
|
||||||
npcId: string;
|
|
||||||
npcName: string;
|
|
||||||
turnCount: number;
|
|
||||||
customInputPlaceholder?: string;
|
|
||||||
pendingQuestOffer?: StoryNpcQuestOfferState | null;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
这样有 3 个好处:
|
|
||||||
|
|
||||||
1. 任务 offer 只属于当前聊天上下文,不污染正式任务日志。
|
|
||||||
2. AdventurePanel 可以直接从 `currentStory.npcChatState` 判断是否进入任务处理态。
|
|
||||||
3. 任务详情面板可以直接读取这份 `QuestLogEntry` 展示,而不用再造一套展示结构。
|
|
||||||
|
|
||||||
## 3.2 任务处理态的选项表达
|
|
||||||
|
|
||||||
建议不要把“查看 / 更换 / 放弃”接进服务端 runtime action。
|
|
||||||
|
|
||||||
原因是:
|
|
||||||
|
|
||||||
1. `查看任务` 只是 UI 行为,不需要服务端结算。
|
|
||||||
2. `更换任务` 和 `放弃任务` 都是当前聊天态内部状态流转。
|
|
||||||
3. 这三项更适合作为本地聊天态专用选项,由 `AdventurePanel + npcEncounterActions` 协同处理。
|
|
||||||
|
|
||||||
建议做成 3 个本地专用 `StoryOption`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
functionId: 'npc_chat_quest_offer_view',
|
|
||||||
actionText: '查看任务',
|
|
||||||
runtimePayload: { npcChatQuestOfferAction: 'view' }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
其余两个同理:
|
|
||||||
|
|
||||||
- `replace`
|
|
||||||
- `abandon`
|
|
||||||
|
|
||||||
## 3.3 接取后的正式写入
|
|
||||||
|
|
||||||
正式领取后才进入任务日志:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
nextState = {
|
|
||||||
...state,
|
|
||||||
quests: acceptQuest(state.quests, pendingQuest.quest),
|
|
||||||
runtimeStats: incrementGameRuntimeStats(state.runtimeStats, {
|
|
||||||
questsAccepted: 1,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
- `pending offer` 不计入 `questsAccepted`
|
|
||||||
- 真正点击 `领取任务` 才计数
|
|
||||||
|
|
||||||
## 4. UI 落点
|
|
||||||
|
|
||||||
## 4.1 聊天面板
|
|
||||||
|
|
||||||
`AdventurePanel` 中增加一个判断:
|
|
||||||
|
|
||||||
1. `currentStory.npcChatState?.pendingQuestOffer` 存在时:
|
|
||||||
- 只显示三项任务处理选项
|
|
||||||
- 隐藏自定义输入
|
|
||||||
|
|
||||||
2. 不存在时:
|
|
||||||
- 保持现有聊天输入与 `npc_chat` 建议
|
|
||||||
|
|
||||||
## 4.2 任务详情弹层
|
|
||||||
|
|
||||||
`AdventurePanelOverlays` 里的任务详情弹层继续复用,但要区分两种任务来源:
|
|
||||||
|
|
||||||
1. 已在任务日志中的正式任务
|
|
||||||
- 保持现有逻辑
|
|
||||||
- 完成后仍显示 `领取奖励`
|
|
||||||
|
|
||||||
2. 聊天里的 `pending quest offer`
|
|
||||||
- 底部显示 `领取任务`
|
|
||||||
- 不显示 `领取奖励`
|
|
||||||
|
|
||||||
点击 `领取任务` 后:
|
|
||||||
|
|
||||||
- 关闭详情弹层
|
|
||||||
- 回到聊天界面
|
|
||||||
- 当前聊天追加“我愿意接下”这一步
|
|
||||||
|
|
||||||
## 5. 代码改动建议
|
|
||||||
|
|
||||||
建议落地在这些文件:
|
|
||||||
|
|
||||||
1. `src/types/story.ts`
|
|
||||||
- 扩展 `StoryNpcChatState`
|
|
||||||
|
|
||||||
2. `src/hooks/story/npcEncounterActions.ts`
|
|
||||||
- 增加聊天内任务 offer 触发判断
|
|
||||||
- 接入 `generateQuestForNpcEncounter(...)`
|
|
||||||
- 增加
|
|
||||||
- 更换任务
|
|
||||||
- 放弃任务
|
|
||||||
- 领取任务
|
|
||||||
对应的本地状态流转
|
|
||||||
|
|
||||||
3. `src/hooks/story/useStoryInteractionCoordinator.ts`
|
|
||||||
- 向上暴露聊天内任务 offer 的操作方法
|
|
||||||
|
|
||||||
4. `src/hooks/useStoryGeneration.ts`
|
|
||||||
- 把聊天内任务 offer UI 能力透传给面板
|
|
||||||
|
|
||||||
5. `src/components/AdventurePanel.tsx`
|
|
||||||
- 聊天态隐藏 / 恢复输入
|
|
||||||
- 拦截 `查看任务 / 更换任务 / 放弃任务`
|
|
||||||
- 让 pending quest 也能进入任务详情弹层
|
|
||||||
|
|
||||||
6. `src/components/adventure-panel/AdventurePanelOverlays.tsx`
|
|
||||||
- 为 pending quest 增加 `领取任务` 按钮
|
|
||||||
|
|
||||||
7. `src/components/AdventurePanel.test.tsx`
|
|
||||||
- 补聊天态输入隐藏测试
|
|
||||||
|
|
||||||
8. `src/hooks/story/npcEncounterActions.test.ts`
|
|
||||||
- 补任务 offer 触发 / 更换 / 接取测试
|
|
||||||
|
|
||||||
## 6. 验收标准
|
|
||||||
|
|
||||||
做到以下几点,才算这次需求成立:
|
|
||||||
|
|
||||||
1. 与好感度大于 `0` 的角色聊天时,不会一上来立刻塞任务,前 `1-2` 轮先正常寒暄。
|
|
||||||
2. 达到委托时机后,系统会调用现有 `generateQuestForNpcEncounter(...)` 生成一份待领取任务。
|
|
||||||
3. 当前聊天轮次会出现一段明确的委托台词。
|
|
||||||
4. 这一轮聊天选项会切成:
|
|
||||||
- `查看任务`
|
|
||||||
- `更换任务`
|
|
||||||
- `放弃任务`
|
|
||||||
5. 任务处理态下,自定义输入会被临时隐藏。
|
|
||||||
6. 点击 `查看任务` 会弹出现有任务详情面板。
|
|
||||||
7. 点击 `领取任务` 后,任务才正式进入任务日志,并在对话里体现“玩家愿意接下”。
|
|
||||||
8. 领取完成后,聊天会恢复正常输入与自由继续对话。
|
|
||||||
9. 点击 `更换任务` 时,必须重新调用现有任务生成链,而不是本地改文案。
|
|
||||||
|
|
||||||
## 7. 一句话收束
|
|
||||||
|
|
||||||
这次要做的,不是“让聊天里多一个任务按钮”,而是把:
|
|
||||||
|
|
||||||
- 高好感聊天
|
|
||||||
- 上下文化任务生成
|
|
||||||
- 临时任务 offer
|
|
||||||
- 任务详情查看
|
|
||||||
- 正式领取后回流聊天
|
|
||||||
|
|
||||||
整合成一个更自然的叙事交接过程。
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# 平台入口隐藏大鱼吃小鱼创作入口设计
|
|
||||||
|
|
||||||
日期:`2026-04-28`
|
|
||||||
|
|
||||||
## 1. 变更背景
|
|
||||||
|
|
||||||
平台当前“选择创作类型”弹层同时暴露 RPG、大鱼吃小鱼、拼图三类入口。
|
|
||||||
|
|
||||||
本轮需求只要求在平台里隐藏“大鱼吃小鱼”的创作入口,不要求删除已有玩法实现、运行时路由、作品数据或后台能力。
|
|
||||||
|
|
||||||
## 2. 落地边界
|
|
||||||
|
|
||||||
- 只调整平台入口层展示,不修改大鱼吃小鱼已有前后端链路。
|
|
||||||
- 不删除 `big-fish` 相关路由、服务、作品详情、运行时与数据结构。
|
|
||||||
- 隐藏策略应收敛到统一配置层,避免首页、弹层、后续复用入口出现显示状态漂移。
|
|
||||||
|
|
||||||
## 3. 实现方案
|
|
||||||
|
|
||||||
1. 在 `src/components/platform-entry/platformEntryCreationTypes.ts` 的创作类型元数据中增加 `hidden` 字段。
|
|
||||||
2. 将 `big-fish` 类型标记为 `hidden: true`。
|
|
||||||
3. 平台创作类型弹层渲染前统一过滤 `hidden` 项。
|
|
||||||
|
|
||||||
这样可以保证:
|
|
||||||
|
|
||||||
- 平台用户看不到“大鱼吃小鱼”创作入口。
|
|
||||||
- 若后续重新开放,只需改回配置,不必再拆 UI 逻辑。
|
|
||||||
- 不影响既有直达路由、历史作品数据和开发中的玩法链路。
|
|
||||||
|
|
||||||
## 4. 验收点
|
|
||||||
|
|
||||||
- 平台“选择创作类型”弹层不再显示“大鱼吃小鱼”卡片。
|
|
||||||
- 可创建卡片排在前面,展示“敬请期待”的锁定卡片排在后面,交互状态保持稳定。
|
|
||||||
- 代码层不引入对 Big Fish 运行时或结果页的额外耦合修改。
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
# 平台入口分类与创作 Tab 强化设计
|
|
||||||
|
|
||||||
## 1. 目标
|
|
||||||
|
|
||||||
在不新建平台入口系统的前提下,直接扩展现有 `RpgEntryHomeView` 主 Tab:
|
|
||||||
|
|
||||||
- 新增“分类” Tab,用作品标签聚合所有公开发布作品。
|
|
||||||
- 强化“创作” Tab 的导航视觉权重,让它在底部导航中居中并更醒目。
|
|
||||||
- 登录态底部导航顺序为:推荐、发现、创作、草稿、我的。
|
|
||||||
- 未登录态底部导航只保留:推荐、创作、发现,其中创作保持居中。
|
|
||||||
|
|
||||||
## 2. 数据边界
|
|
||||||
|
|
||||||
本次只做前端展示重排,不新增后端接口:
|
|
||||||
|
|
||||||
- 分类数据来源使用现有 `latestEntries` 与 `featuredEntries` 的公开作品列表。
|
|
||||||
- 标签来源沿用 `buildPlatformWorldTags(entry)`,公开作品会映射为题材、角色数、地标数。
|
|
||||||
- 同一公开作品若同时出现在精选与最新中,按 `ownerUserId + profileId` 去重。
|
|
||||||
- 点击分类作品继续走现有 `onOpenGalleryDetail`,不改变详情页和登录拦截逻辑。
|
|
||||||
|
|
||||||
## 3. 交互规则
|
|
||||||
|
|
||||||
### 3.1 登录态
|
|
||||||
|
|
||||||
底部导航展示 5 个入口:
|
|
||||||
|
|
||||||
1. 推荐
|
|
||||||
2. 发现
|
|
||||||
3. 创作
|
|
||||||
4. 草稿
|
|
||||||
5. 我的
|
|
||||||
|
|
||||||
创作入口位于第三位,视觉上使用更大的图标壳、轻微上浮、渐变高亮和阴影,保证它是主行动入口。
|
|
||||||
|
|
||||||
### 3.2 未登录态
|
|
||||||
|
|
||||||
底部导航展示 3 个入口:
|
|
||||||
|
|
||||||
1. 推荐
|
|
||||||
2. 创作
|
|
||||||
3. 发现
|
|
||||||
|
|
||||||
不展示“草稿”和“我的”,避免未登录用户在底部导航看到必须登录后才有价值的入口。创作入口位于第二位,保持几何居中,并沿用原推荐 Tab 的星光图标;推荐 Tab 改用游戏手柄图标。
|
|
||||||
|
|
||||||
### 3.3 桌面端
|
|
||||||
|
|
||||||
桌面侧栏同步增加“分类”,但保持纵向导航,不强行做居中布局。创作入口仍使用强调样式。
|
|
||||||
|
|
||||||
## 4. 分类页布局
|
|
||||||
|
|
||||||
分类页为独立 Tab 面板,不在首页下方展开:
|
|
||||||
|
|
||||||
- 顶部展示标签胶囊,默认选中作品数量最多的标签。
|
|
||||||
- 标签切换后,下方网格展示该标签下所有公开作品。
|
|
||||||
- 无公开作品时展示现有空状态组件。
|
|
||||||
- 分类页不写玩法规则说明类长文案,只保留必要标题、短状态文案和作品卡片。
|
|
||||||
|
|
||||||
## 5. 验收点
|
|
||||||
|
|
||||||
- 登录态移动端底部导航顺序准确,创作在 5 个 Tab 中居中。
|
|
||||||
- 未登录态移动端底部导航只显示 3 个 Tab,创作在中间。
|
|
||||||
- 发现 Tab 能按标签切换并展示公开作品。
|
|
||||||
- 创作 Tab 在移动端和桌面端都比普通 Tab 更醒目。
|
|
||||||
- 不修改 server-node,不新增后端逻辑。
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
# 平台创作 Tab 模板入口设计
|
|
||||||
|
|
||||||
更新时间:`2026-05-14`
|
|
||||||
|
|
||||||
## 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. `visual-novel` 暂时从创作页完全隐藏,不出现在模板 Tab、旧选择弹层和创作 Hub 卡片中;既有作品、详情、运行链路继续保留。
|
|
||||||
6. `creative-agent` 不出现在模板 Tab 和选择弹层中,不再作为创作 Tab 首屏入口。
|
|
||||||
7. 方洞挑战暂时从创作页完全隐藏,不出现在模板 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 中不补充玩法规则说明文案,保持创作入口清爽。
|
|
||||||
6. 拼图创作表单在未上传主图时,画面描述输入框右下角保留一个参考图上传入口;支持多选,最多 5 张,上传后以下方小缩略图展示,点击缩略图可放大预览。
|
|
||||||
7. 当前拼图后端只消费第一张有效参考图做生成,前端仍需保留数组输入,方便后续扩展多参考图能力。
|
|
||||||