merge: master into codex/bark-battle

This commit is contained in:
kdletters
2026-05-19 17:04:32 +08:00
307 changed files with 40711 additions and 26022 deletions

View File

@@ -40,6 +40,12 @@ server-rs + Axum + SpacetimeDB
npm run check:server-rs-ddd
```
## `spacetime-client` mapper 组织
`server-rs/crates/spacetime-client/src/mapper.rs` 只作为聚合入口,负责声明 `src/mapper/` 下的领域子模块并 re-export 原有 record / mapper 能力;不要在该文件继续堆叠大段映射实现。
当前子模块按调用领域拆分:`assets.rs``auth.rs``runtime.rs``runtime_profile.rs``custom_world.rs``puzzle.rs``match3d.rs``square_hole.rs``visual_novel.rs``big_fish.rs``story.rs``ai.rs``bark_battle.rs``combat.rs``inventory.rs``npc.rs`,跨领域轻量 helper 和共享 record 统一放在 `common.rs`。该拆分只改变 `spacetime-client` 文件组织,不改变 SpacetimeDB schema、生成绑定、procedure result 契约或外部 DTO后续新增 mapper 时优先落到对应领域子模块,不得重新引入跨层 JSON 字符串兼容结构。
## API 路由分组
路由树由 `server-rs/crates/api-server/src/app.rs` 统一构造。当前主要分组:
@@ -73,6 +79,33 @@ npm run check:server-rs-ddd
2. `app.rs` 只保留全局 middleware、TraceLayer、request context、tracking middleware、入口开关和少量顶层 glue。
3. 路由迁移和业务重构分阶段处理;先移动路由装配,再拆 handler 内部实现。
4. 大 handler 拆分时优先按 `router.rs``handlers.rs``application.rs``assets.rs``mapper.rs``errors.rs` 分层。`handlers.rs` 只做 Axum extract、鉴权和 request/response业务规则继续下沉到 `module-*`
5. 手写 Rust 模块入口统一使用同名 `.rs` 文件,例如 `puzzle.rs` + `puzzle/*.rs``match3d.rs` + `match3d/*.rs`;不要再新增 `mod.rs` 入口。生成的 SpacetimeDB Rust bindings 也由生成脚本同步为 `module_bindings.rs` + `module_bindings/*.rs` 布局。
拼图 `api-server` 内部拆分:
- `server-rs/crates/api-server/src/modules/puzzle.rs` 只负责路由装配、鉴权层和参考图 body limit对外继续引用同一批 handler 名称。
- `server-rs/crates/api-server/src/puzzle.rs` 只作为聚合入口,保留共享 import / 常量、内部模块声明和 handler re-export不继续承载大段实现。
- `server-rs/crates/api-server/src/puzzle/handlers.rs` 承接 Axum handler负责 extract、鉴权上下文、调用 SpacetimeDB facade / 编排 helper并返回 HTTP/SSE 响应。
- `server-rs/crates/api-server/src/puzzle/draft.rs` 承接表单草稿保存、草稿编译、首关命名、UI 背景 prompt、降级 snapshot 和初始资产就绪校验。
- `server-rs/crates/api-server/src/puzzle/generation.rs` 承接拼图图片与 UI 背景的生成编排、计费包裹和 reference image 路径选择。
- `server-rs/crates/api-server/src/puzzle/vector_engine.rs` 承接 VectorEngine 请求体、HTTP 调用、下载 / base64 解码、OSS 写入、asset object / binding 持久化和上游错误归一。
- `server-rs/crates/api-server/src/puzzle/mappers.rs` 承接 SpacetimeDB record 到 shared-contracts DTO 的映射。
- `server-rs/crates/api-server/src/puzzle/tags.rs` 保留拼图标签生成、拼图通用错误映射和 SSE helper。
该拆分只改变 `api-server` 文件组织,不改变 `/api/runtime/puzzle/*` route、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义或计费语义;后续继续细分时也必须先保持行为不变,再单独讨论领域规则下沉。
抓大鹅 Match3D `api-server` 内部拆分:
- `server-rs/crates/api-server/src/modules/match3d.rs` 继续负责路由装配和 body limit对外 handler 名称保持不变。
- `server-rs/crates/api-server/src/match3d.rs` 只作为聚合入口,保留共享 import / 常量 / 内部类型、模块声明和 handler re-export。
- `server-rs/crates/api-server/src/match3d/handlers.rs` 承接 Axum handler负责 extract、鉴权上下文、调用 SpacetimeDB facade / 编排 helper并返回 HTTP 响应。
- `server-rs/crates/api-server/src/match3d/draft.rs` 承接 Agent session、草稿编译、题材 / 难度 / 物品计划和草稿持久化编排。
- `server-rs/crates/api-server/src/match3d/works.rs` 承接作品 CRUD、封面 / 背景 / 容器资产生成入口、发布 / Remix / 点赞 / 游玩记录和作品级 helper。
- `server-rs/crates/api-server/src/match3d/item_assets.rs` 承接物品 sheet 生成、绿幕 / 近白底透明化、切图、append / replace / delete / sort / merge 和素材持久化。
- `server-rs/crates/api-server/src/match3d/vector_engine_gemini.rs` 承接 VectorEngine Gemini 请求体、响应解析、base64 图片下载和上游错误归一。
- `server-rs/crates/api-server/src/match3d/runtime.rs` 保留运行态轻量归一 helper`mappers.rs` / `tags.rs` / `tests.rs` 分别承接 DTO 映射、标签 / 通用错误 helper 和原有单测。
该拆分只改变 `api-server` 文件组织,不改变 `/api/creation/match3d/*``/api/runtime/match3d/*` route、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义、VectorEngine / OSS 副作用边界或计费语义;后续继续细分时也必须先保持行为不变,再单独讨论领域规则下沉到 `module-match3d`
生成资产 Adapter 规则:
@@ -84,14 +117,19 @@ npm run check:server-rs-ddd
## SpacetimeDB schema 变更规则
1. 任何 table、reducer、procedure、row shape 或 bindings 变化,都必须同步 `server-rs/crates/spacetime-module/src/migration.rs`、本文件表目录和生成绑定
1. 任何 table、view、reducer、procedure、row shape 或 bindings 变化,都必须同步本文件表 / view 目录和生成绑定;真实 table 变化还必须同步 `server-rs/crates/spacetime-module/src/migration.rs`view 属于派生投影,不写入迁移导入导出表清单
2. 已有表新增字段必须放在 Rust 表结构体最后,并设置明确 `#[default(...)]`
3. 删除字段、改名、重排字段、改类型或修改字段属性前,必须先询问用户并确认迁移计划。
4. Vec 字段不要直接写无法 const 求值的 default需要默认空集合时优先使用 `Option<Vec<T>>``#[default(None::<Vec<T>>)]`,业务层归一为空数组。
5. 修改后运行:
5. 运行态读表必须按已声明索引访问。只要 table 上存在覆盖查询前缀的 `#[index(...)]` 或主键 / unique accessor列表、详情、快照组装和计数都先用对应 accessor `.filter(...)` / `.find(...)`,再在内存中处理索引无法覆盖的残余条件;不得用 `.iter().filter(...)` 扫整表替代现成索引。
6. 面向公开列表的只读投影优先做成 public view / public 读模型表,并由 `api-server``spacetime-client` 长期订阅后读本地 cache。短期不把作品列表整体交给浏览器前端直接订阅不要让 HTTP 列表接口每次请求都调用 procedure 重新组装全量列表。需要请求时间窗口的轻量统计可订阅公开统计表后在 `api-server` 本地聚合,需要写入副作用的详情、点赞、游玩记录仍可走 procedure / reducer。中期如要让前端可选直连订阅只能新增或统一稳定的专用 public read model例如 `public_work_gallery_entry`,并保持字段、排序键、公开权限和降级语义由后端投影定义;前端不得直接订阅 `puzzle_work_profile``custom_world_profile` 等领域源表,也不得自己做 join、聚合或权限逻辑。首屏、排序、字段归一、权限降级和 HTTP fallback 仍由 `api-server` BFF 维持。
7. 多列索引按 SpacetimeDB 绑定生成的元组参数直接传入,例如 `.filter((source_type, profile_id, played_day))`;前缀查询只传前缀元组,例如 `.filter((scope_kind, scope_id.as_str()))`。不要为了绕过类型问题退回整表遍历。
8. procedure result 必须返回 typed snapshot / typed value。`spacetime-client` mapper 不得再通过 `row_json/session_json/work_json/items_json/run_json/event_json/feedback_json: Option<String>` 做跨层 JSON 字符串传输,也不得在 mapper 里反序列化旧 `*JsonRecord` 兼容结构。业务内部持久化字段如 `profile_payload_json``levels_json` 等不属于 procedure result 载荷例外,仍按各自表契约处理。
9. 修改后运行:
```bash
npm run spacetime:generate
npm run check:spacetime-runtime-access
npm run check:spacetime-schema
npm run check:server-rs-ddd
```
@@ -222,7 +260,7 @@ npm run check:server-rs-ddd
### `battle_state`
- Rust 结构体:`BattleState`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `big_fish_agent_message`
@@ -238,6 +276,7 @@ npm run check:server-rs-ddd
- Rust 结构体:`BigFishCreationSession`
- 源码:`server-rs/crates/spacetime-module/src/big_fish/tables.rs`
- 索引:`by_big_fish_session_owner_user_id``by_big_fish_session_stage`。公开广场 view 使用 `by_big_fish_session_stage` 读取已发布会话,避免扫整表。
### `big_fish_event`
@@ -249,10 +288,17 @@ npm run check:server-rs-ddd
- Rust 结构体:`BigFishRuntimeRun`
- 源码:`server-rs/crates/spacetime-module/src/big_fish/tables.rs`
### SpacetimeDB view`big_fish_gallery_view`
- Rust view`big_fish_gallery_view`
- 返回类型:`Vec<BigFishWorkSummarySnapshot>`
- 源码:`server-rs/crates/spacetime-module/src/big_fish/session.rs`
- 说明:大鱼吃小鱼公开广场列表投影,只从 `Published` creation session 组装公开卡片字段;`api-server``spacetime-client` 长期订阅 `SELECT * FROM big_fish_gallery_view``SELECT * FROM public_work_play_daily_stat WHERE source_type = 'big-fish'` 后,从本地 cache 构造 `/api/runtime/big-fish/gallery` 响应。公开列表不再每个 HTTP 请求调用 `list_big_fish_works` procedure个人作品列表、详情、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
### `chapter_progression`
- Rust 结构体:`ChapterProgression`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `creation_entry_config`
@@ -267,37 +313,38 @@ npm run check:server-rs-ddd
### `custom_world_agent_message`
- Rust 结构体:`CustomWorldAgentMessage`
- 源码:`server-rs/crates/spacetime-module/src/custom_world/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
### `custom_world_agent_operation`
- Rust 结构体:`CustomWorldAgentOperation`
- 源码:`server-rs/crates/spacetime-module/src/custom_world/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
### `custom_world_agent_session`
- Rust 结构体:`CustomWorldAgentSession`
- 源码:`server-rs/crates/spacetime-module/src/custom_world/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
### `custom_world_draft_card`
- Rust 结构体:`CustomWorldDraftCard`
- 源码:`server-rs/crates/spacetime-module/src/custom_world/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
### `custom_world_gallery_entry`
- Rust 结构体:`CustomWorldGalleryEntry`
- 源码:`server-rs/crates/spacetime-module/src/custom_world/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
- 作用:自定义世界公开作品列表读模型。`api-server``spacetime-client` 长期订阅 `SELECT * FROM custom_world_gallery_entry``SELECT * FROM public_work_play_daily_stat WHERE source_type = 'custom-world'``/api/runtime/custom-world-gallery` 从本地 cache 排序并聚合 `recentPlayCount7d`,不再每个 HTTP 请求调用 `list_custom_world_gallery_entries` procedure。旧 procedure 只用于兼容旧库缺少 gallery 读模型行时的一次性同步兜底。
### `custom_world_profile`
- Rust 结构体:`CustomWorldProfile`
- 源码:`server-rs/crates/spacetime-module/src/custom_world/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
### `custom_world_session`
- Rust 结构体:`CustomWorldSession`
- 源码:`server-rs/crates/spacetime-module/src/custom_world/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
### `database_migration_import_chunk`
@@ -312,7 +359,7 @@ npm run check:server-rs-ddd
### `inventory_slot`
- Rust 结构体:`InventorySlot`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `match3d_agent_message`
@@ -334,15 +381,22 @@ npm run check:server-rs-ddd
- Rust 结构体:`Match3DWorkProfileRow`
- 源码:`server-rs/crates/spacetime-module/src/match3d/tables.rs`
### SpacetimeDB view`match_3_d_gallery_view`
- Rust view`match3d_gallery_view`
- 返回类型:`Vec<Match3DGalleryViewRow>`
- 源码:`server-rs/crates/spacetime-module/src/match3d.rs`
- 说明:抓大鹅公开广场列表投影,只暴露 `publication_status = published` 的作品卡片字段;`api-server``spacetime-client` 长期订阅 `SELECT * FROM match_3_d_gallery_view``SELECT * FROM public_work_play_daily_stat WHERE source_type = 'match3d'` 后,从本地 cache 构造 `/api/runtime/match3d/gallery` 响应。公开列表不再每个 HTTP 请求调用 `list_match3d_works` procedure个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
### `npc_state`
- Rust 结构体:`NpcState`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `player_progression`
- Rust 结构体:`PlayerProgression`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `profile_dashboard_state`
@@ -460,15 +514,64 @@ npm run check:server-rs-ddd
- Rust 结构体:`PuzzleWorkProfileRow`
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
### SpacetimeDB view`puzzle_gallery_view`
- Rust view`puzzle_gallery_view`
- 返回类型:`Vec<PuzzleWorkProfile>`
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
- 说明:拼图广场公开详情兼容投影,只暴露 `publication_status = Published` 的作品,但返回完整 `PuzzleWorkProfile`,包含 levels / anchor_pack 等详情级字段;公开列表主路径不再订阅该 view。
### SpacetimeDB view`puzzle_gallery_card_view`
- Rust view`puzzle_gallery_card_view`
- 返回类型:`Vec<PuzzleGalleryCardViewRow>`
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
- 说明:拼图广场公开列表卡片投影,只暴露前端列表卡片需要的公开字段,不携带 levels / anchor_pack 等详情级载荷;`api-server``spacetime-client` 长期订阅 `SELECT * FROM puzzle_gallery_card_view``SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle'` 后,从本地 cache 构造 `/api/runtime/puzzle/gallery` 响应,并在本地按当前请求时间聚合 `recentPlayCount7d`,不再每个 HTTP 请求调用 `list_puzzle_gallery` procedure。
### 拼图公开列表 HTTP 窗口缓存
- 接口:`GET /api/runtime/puzzle/gallery`
- 响应契约:保留 `items` 字段兼容旧前端;当前 `items` 只返回前 10 个完整卡片,新增 `previewRefs` 返回后 10 个 `workId/profileId` 引用,并返回 `hasMore``nextCursor``totalCount`
- 缓存策略:`api-server``PuzzleGalleryCache` 中缓存最终 `PuzzleGalleryResponse` 的预序列化 data JSON。缓存 miss / 过期时单飞重建避免并发请求重复排序、映射、DTO 深拷贝和 `serde_json::Value` 树构造;开启响应 envelope 时只按请求拼接轻量 meta缓存短 TTL 刷新 `recentPlayCount7d`,后台 cleanup task 周期清理超过最大空闲窗口的旧响应。OTLP 通过 `genarrative.puzzle_gallery.cache.*``genarrative.spacetime.read.*``genarrative.http.server.response_bodies.in_flight``genarrative.http.server.request_permits.available` 区分缓存重建、SpacetimeDB 本地订阅读、响应 body 生命周期和 HTTP 背压状态。
- 详情路径:公开详情、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理;前端拿到 `previewRefs` 后如果需要展开更多内容,应优先使用后续列表窗口能力或详情 cache不要把自动详情预取变成新的 procedure 热点。
### api-server 长期订阅读模型
`spacetime-client` 建立每个池连接时会等待下列订阅初始同步:
- `SELECT * FROM puzzle_gallery_card_view`
- `SELECT * FROM custom_world_gallery_entry`
- `SELECT * FROM match_3_d_gallery_view`
- `SELECT * FROM square_hole_gallery_view`
- `SELECT * FROM visual_novel_gallery_view`
- `SELECT * FROM big_fish_gallery_view`
下列订阅用于统计或配置缓存,订阅失败不会让公开列表连接整体不可用,调用方保留兼容兜底:
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle'`
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'custom-world'`
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'match3d'`
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'square-hole'`
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'visual-novel'`
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'big-fish'`
- `SELECT * FROM creation_entry_config`
- `SELECT * FROM creation_entry_type_config`
拼图、自定义世界、抓大鹅、方洞挑战、视觉小说和大鱼吃小鱼的公开列表 HTTP 路由都从订阅 cache 读取公开 read model / view。各玩法的个人作品列表、详情、发布、点赞、游玩记录、Remix 和其它需要鉴权或写入副作用的路径继续走 procedure / reducer不要为了公开列表性能把这些 owner-specific 或 mutation 语义混进 public view。
`GET /api/creation-entry/config` 和入口熔断优先从订阅 cache 读取创作入口配置cache 缺失时使用最近一次成功读取的内存快照,再兜底调用 `get_creation_entry_config` procedure 完成空库种子或旧库兼容。
未来可选:若发现页、推荐流和各玩法广场需要统一给浏览器前端直接订阅公开作品列表,只新增 / 统一专用 public read model例如 `public_work_gallery_entry`。该 read model 必须是后端投影后的公开作品卡片契约,覆盖作品类型、公开作品号、标题、摘要、封面、作者展示名、排序键、公开统计和入口开关后的可见性,不暴露玩法领域源表 row shape。前端可选择订阅这个稳定投影来减少 HTTP 拉取,但不能订阅 `puzzle_work_profile``custom_world_profile` 等源表后自行拼装列表BFF 仍保留首屏、SEO / 分享、旧客户端、订阅失败和灰度期间的 HTTP fallback。
### `quest_log`
- Rust 结构体:`QuestLog`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `quest_record`
- Rust 结构体:`QuestRecord`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `refresh_session`
@@ -505,30 +608,39 @@ npm run check:server-rs-ddd
- Rust 结构体:`SquareHoleWorkProfileRow`
- 源码:`server-rs/crates/spacetime-module/src/square_hole/tables.rs`
### SpacetimeDB view`square_hole_gallery_view`
- Rust view`square_hole_gallery_view`
- 返回类型:`Vec<SquareHoleGalleryViewRow>`
- 源码:`server-rs/crates/spacetime-module/src/square_hole.rs`
- 说明:方洞挑战公开广场列表投影,只暴露 `publication_status = published` 的作品卡片字段;`api-server``spacetime-client` 长期订阅 `SELECT * FROM square_hole_gallery_view``SELECT * FROM public_work_play_daily_stat WHERE source_type = 'square-hole'` 后,从本地 cache 构造 `/api/runtime/square-hole/gallery` 响应。公开列表不再每个 HTTP 请求调用 `list_square_hole_works` procedure个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
### `story_event`
- Rust 结构体:`StoryEvent`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `story_session`
- Rust 结构体:`StorySession`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `tracking_daily_stat`
- Rust 结构体:`TrackingDailyStat`
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
- 写入:由单条或批量 tracking procedure 在同一事务中随 `tracking_event` 更新,作为运营查询和个人任务进度的聚合投影。
### `tracking_event`
- Rust 结构体:`TrackingEvent`
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
- 写入:关键业务埋点同步调用单条 procedure普通 HTTP route tracking 由 `api-server` 本机 outbox 批量调用 `record_tracking_events_and_return`。outbox 到达批量阈值时先封存 active 文件并切新 active后台 worker 异步 flush sealed 文件HTTP 请求线程不等待 SpacetimeDB。`FLUSH_INTERVAL_MS` 只负责兜底封存长时间未满批的 active 文件,`MAX_BYTES` 只做磁盘保护阈值。`event_id` 必须稳定且全局唯一,批量重试时用唯一索引做幂等跳过。
### `treasure_record`
- Rust 结构体:`TreasureRecord`
- 源码:`server-rs/crates/spacetime-module/src/gameplay/mod.rs`
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
### `user_account`
@@ -569,3 +681,10 @@ npm run check:server-rs-ddd
- Rust 结构体:`VisualNovelWorkProfileRow`
- 源码:`server-rs/crates/spacetime-module/src/visual_novel.rs`
### SpacetimeDB view`visual_novel_gallery_view`
- Rust view`visual_novel_gallery_view`
- 返回类型:`Vec<VisualNovelGalleryViewRow>`
- 源码:`server-rs/crates/spacetime-module/src/visual_novel.rs`
- 说明:视觉小说公开广场列表投影,只暴露 `publication_status = published` 的作品卡片字段,不把完整 `draft` 暴露给公开列表订阅;`api-server``spacetime-client` 长期订阅 `SELECT * FROM visual_novel_gallery_view``SELECT * FROM public_work_play_daily_stat WHERE source_type = 'visual-novel'` 后,从本地 cache 构造 `/api/runtime/visual-novel/gallery` 响应。公开列表不再每个 HTTP 请求调用 `list_visual_novel_works` procedure个人历史、详情、运行态和发布仍按原有 procedure / reducer 路径处理。

View File

@@ -79,6 +79,8 @@ npm run lint
npm run check
```
`npm run build``scripts/build-gate.mjs` 串行构建主站和后台;该门禁会把 Vite warning 当成失败处理。若看到 `Build gate failed because warnings were emitted`,先看 warning 原文,例如 chunk 体积超过 `vite.config.ts` / `apps/admin-web/vite.config.ts``chunkSizeWarningLimit`,不要先按 Rust 编译失败排查。
视觉小说负向扫描与验收门禁:
```bash
@@ -147,8 +149,53 @@ Nginx 负责站点和反向代理
Jenkins 按 web / api / Spacetime module / build / deploy / publish 拆分
```
Windows Stdb module 构建流水线运行在 Jenkins `windows` 节点上。该流水线需要执行 PowerShell 逻辑时,统一通过 `bat` 显式调用 `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe`,不要直接使用 Jenkins `powershell` step本地 Jenkins durable-task 曾在 `Genarrative-Stdb-Module-Build` workspace 中启动裸 `powershell` 时触发 `CreateProcess error=5, 拒绝访问`。临时 `.ps1` 由 Jenkins `writeFile` 写出后要先转成 UTF-8 with BOM 再交给 Windows PowerShell 5.1 `-File` 解析,避免中文错误消息在无 BOM UTF-8 下被当成本地 ANSI 误解码。Checkout 阶段要优先复用 Jenkins GitSCM 已经完成的结果:`COMMIT_HASH` 为空或与当前 `HEAD` 一致时,不要再额外 `git clean` / `git checkout`,只在确实需要切到别的指定 commit 时才补 fetch、校验和切换。排查时先看对应 build log、`@tmp/durable-*` 下的 `powershellWrapper.ps1`,以及日志中的 `[jenkins-powershell] user/exe`
生产环境变量模板:`deploy/env/api-server.env.example`。真实密钥只放服务器,不提交 Git不写入文档示例。
`Genarrative-Server-Provision` 会安装基础构建依赖、systemd 模板和 Nginx 站点模板。Ubuntu / apt 目标机会额外安装 `libnginx-mod-http-brotli-filter``libnginx-mod-http-brotli-static`,随后由 `scripts/jenkins-server-provision.sh` 通过临时 `nginx -t` 配置探测 Brotli 指令是否可用;该临时配置必须先 `include /etc/nginx/modules-enabled/*.conf`,因为 apt 安装的 Brotli 是动态模块,不会出现在普通 `nginx -V` 编译参数里。探测成功才在渲染后的 `deploy/nginx/genarrative.conf` / `genarrative-dev-http.conf` 中启用 Brotli避免未安装模块的机器直接写入无效配置。
50 HTTP req/s 首版压测优化口径:
- `api-server` 生产模板默认 `GENARRATIVE_API_LISTEN_BACKLOG=1024``GENARRATIVE_API_WORKER_THREADS=4`;本地未设置 worker threads 时继续使用 Tokio 默认值。
- `GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512` 开启应用内 HTTP 并发背压;`GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS=320``GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS=64``GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS=16` 分别限制公开列表、公开详情和后台 API 热路径。超过许可时直接返回 `429 Too Many Requests``Retry-After: 1``/healthz` 不受该限制。这些值不是 RPS 限速;如果压测中 429 上升但内存和 p95 收敛,说明背压正在保护进程。直连 `api-server` 的极高 RPS 压测若出现 `connection refused`,通常已经打到 TCP 监听 / accept 层,应同时检查 backlog、Nginx upstream keepalive 和前置限流。
- `genarrative-api.service` 设置 `LimitNOFILE=65535``TasksMax=2048`;上线后用 `systemctl show genarrative-api.service -p LimitNOFILE -p TasksMax``cat /proc/$(pidof api-server)/limits` 核对。
- Server provision 不在目标机下载 SpacetimeDB 或 `otelcol-contrib`。Jenkins 的 `Prepare Provision Tools` 阶段在 `linux && genarrative-build` 构建机执行 `scripts/prepare-server-provision-tools.sh`SpacetimeDB 仍通过官方安装入口 `https://install.spacetimedb.com` 准备;`otelcol-contrib` 默认要求在 Jenkins 参数 `OTELCOL_CONTRIB_ARCHIVE` 手动上传 `otelcol-contrib_0.151.0_linux_amd64.tar.gz`,再从上传包解出 `provision-tools/otelcol-contrib`。最终工具包通过 `stash/unstash` 上传到 release 部署 agent。目标机上的 `scripts/jenkins-server-provision.sh` 只从该工作区工具包安装 `/stdb/spacetime``/stdb/bin/current/*``/usr/local/bin/otelcol-contrib`。注意 `scripts/jenkins-checkout-source.sh` 会执行 `git reset --hard` / `git clean`,因此被直接执行的新增脚本必须以 Git `100755` 模式提交,或在二次 checkout 之后再补 `chmod +x`
- `otelcol-contrib.service` 作为可选系统服务加入 provision默认监听 `127.0.0.1:4317/4318` 并使用 `deploy/otelcol/genarrative-debug.yaml`。api-server 是否发送 OTLP 仍由 `GENARRATIVE_OTEL_ENABLED` 控制,服务 unit 见 `deploy/systemd/otelcol-contrib.service`
- Nginx `/api/``/admin/api/` 通过 `genarrative_api` upstream 代理到 `127.0.0.1:8082`upstream keepalive 为 64`limit_conn` 负责连接 / 并发保护,`limit_req` 负责入口 RPS 快拒绝。当前模板把公开 gallery list 单独放到 `genarrative_gallery_rps`,默认 `rate=5000r/s``burst=4096``limit_conn=320`;公开详情和普通 API 放到 `genarrative_api_rps`,后台 API 放到 `genarrative_admin_rps`。压测时看 `/var/log/nginx/genarrative.access.log` 中的 `request_time``upstream_connect_time``upstream_header_time``upstream_response_time``upstream_status``request_id`
- 作品列表 K6 脚本一次 iteration 默认请求两个公开接口,因此约 50 HTTP req/s 的目标命令使用 `SCENARIO=spike START_RPS=5 PEAK_RPS=25 HOLD=60s END_RPS=5 DETAIL_RATIO=0 npm run loadtest:k6:works`
- 作品列表短期继续由 `api-server` / BFF 订阅 SpacetimeDB 公开 read model 后读本地 cache不让浏览器前端直接订阅完整列表未来如新增 `public_work_gallery_entry` 等专用公开作品列表 read model前端只可订阅稳定、低基数、公开的专用投影禁止订阅 `puzzle_work_profile``custom_world_profile` 等玩法源表后自行 join、聚合或判断权限。前端直订阅落地前必须先补齐权限、字段契约、排序 / 分页、埋点和 BFF 回退策略。
- 50 HTTP req/s 验收目标为 `http_req_failed < 1%``p95 < 2s``dropped_iterations = 0`,同时压测窗口内 Nginx 无新增 502。2026-05-19 容器 2C / 2G 连续 10 轮不重启 SpacetimeDB 压测:`PEAK_RPS=2500` 等价约 5000 HTTP req/s平均实际吞吐约 `4219 HTTP req/s`10 轮总计 `1,897,357` 个 200、`212,542` 个 429、`0` 个 5xx200 请求平均 `p95=123ms``p99=234ms`;该档会把 SpacetimeDB 容器内存从约 `366MiB` 推到约 `885MiB / 896MiB`,因此当前不要继续抬公开 gallery 入口并发,应优先处理 SpacetimeDB 侧连接 / 订阅 / tracking 写入后的内存高水位。
容器化压测与隔离部署方案单独放在 `deploy/container/`,用于本机或预发模拟 Linux release + Nginx + OTLP Collector 拓扑,不替换当前生产 `systemd + Nginx + Jenkins` 发布路径。当前容器模拟参数按 `genarrative-release` 采样值收口为 2 vCPU / 2 GiB RAM / `nofile=4096` / `worker_connections=768`,并在 compose 里落实到 `spacetimedb cpus=1.0 mem_limit=768m``api-server cpus=2.0 mem_limit=1g``nginx cpus=0.25 mem_limit=128m``otelcol cpus=0.25 mem_limit=128m``k6 cpus=0.5 mem_limit=512m`。容器 `api-server` 默认 `GENARRATIVE_API_WORKER_THREADS=4`,只增加 Tokio worker 调度并发,不突破 `api-server cpus=2.0` 的 CPU 配额:
```bash
npm run container:init
npm run container:config
npm run container:build
npm run container:up
npm run container:k6
npm run container:down
```
容器方案默认暴露 `http://127.0.0.1:18080``api-server` 在容器内监听 `0.0.0.0:8082`Nginx 通过 `api-server:8082` upstream 反代 `/api/``/admin/api/`。SpacetimeDB 也纳入 compose容器内由 `spacetimedb:3101` 提供服务,宿主机通过 `http://127.0.0.1:13101` 进行模块发布Collector 镜像使用 `otel/opentelemetry-collector-contrib:0.151.0`。生产 provision 侧则通过 Jenkins 构建机准备的 `provision-tools/otelcol-contrib` 安装本机 `otelcol-contrib.service`真实库名、token 和外部服务密钥只写本地 `deploy/container/api-server.env`,不提交 Git。完整拓扑、端口、k6 参数和 OTLP debug exporter 使用方法见 `deploy/container/README.md`
`npm run container:config` 默认只做 quiet 校验,避免把本地 env 中的 token 展开到终端;确需排查完整 compose 时再传 `-- --print`
OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs但本地日志与 Nginx 文件日志仍保留:
- 生产与容器 `api-server` env 模板默认 `GENARRATIVE_OTEL_ENABLED=true`;压测、排障或短期要关闭 OTLP 时,必须显式设置 `GENARRATIVE_OTEL_ENABLED=false`
- Collector 使用官方 `otelcol-contrib`,安装与启用仍由 `ENABLE_OTELCOL` / provision 控制,只监听 `127.0.0.1:4317/4318`;本地用 `npm run otel:debug` 启动 debug exporter`npm run otel:rider` 转发到 Rider再接 Jaeger、Tempo、Prometheus、Grafana 或托管平台。
- api-server 发送 OTLP HTTP 时,生产模板使用 `OTEL_SERVICE_NAME=genarrative-api``OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318`,容器模板使用 `OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcol:4318`
- `OTEL_EXPORTER_OTLP_ENDPOINT` 必须指向 Collector 的 HTTP base endpoint不要填 gRPC `4317`,也不要直接填 Rider 端口Rider 由 Collector 通过 `RIDER_OTLP_GRPC_ENDPOINT` 转发。
- 应用日志仍通过 `journalctl -u genarrative-api.service` 查看Nginx 日志仍写文件;日志等级继续用 `GENARRATIVE_API_LOG` / `RUST_LOG` 控制,例如 `info,tower_http=info,spacetime_client=info`
- debug exporter / Rider 转发都会同时接收 traces、metrics 和 logs。
- api-server 会随 metrics 发送进程级指标:`process.memory.usage``process.memory.virtual``process.cpu.time``genarrative.process.cpu.usage_percent``process.thread.count``genarrative.process.memory.private`Windows 额外发送 `process.windows.handle.count`Linux 额外发送 `process.unix.file_descriptor.count`。这些指标只描述当前进程,不携带请求、用户或作品 label。
- HTTP 运行态补充发送 `genarrative.http.server.response_bodies.in_flight``genarrative.http.server.request_permits.available`,后者带低基数 `pool=default|gallery|detail|admin` label用于区分业务 handler / 背压 permit 是否仍被占用;拼图广场热点缓存补充发送 `genarrative.puzzle_gallery.cache.*` 指标,记录 fresh hit、stale hit、未命中、后台刷新开始 / 失败、重建耗时和预序列化 data JSON 字节数。
- SpacetimeDB 观测分为两类procedure / reducer 调用继续用 `genarrative.spacetime.procedure.*`,订阅本地 cache 读使用 `genarrative.spacetime.read.*``read=list_puzzle_gallery` 表示拼图广场当前从 `puzzle_gallery_card_view` 本地 cache 读取,不再每个 HTTP 请求调用 `list_puzzle_gallery` procedure。
- 本地 Windows 直连压测的内存高水位要结合 K6 VU / 连接数解释。250 RPS 下过高 `PREALLOCATED_VUS` 可能让 300 个本地 Established 连接把 `api-server` private memory 瞬时推到 GB 级,且 `/healthz` 小响应也能复现;若压测结束后回落、`response_bodies.in_flight` 和背压 permit 未显示业务积压,应优先按连接 / 发送链路高水位处理,而不是判断为 SpacetimeDB 或 JSON 缓存泄漏。
- Rider 的 Logs 面板只展示 log event 自身字段,不会自动展开父 span 的全部 attributes请求完成日志会直接带 `request_id``http.request.method``http.route``url.scheme``url.path``http.response.status_code``status_class``latency_ms``slow_request`,完整链路继续到 Traces 面板按 trace/span 查看。
- 指标 label 只允许低基数字段HTTP 使用 `method``route``status_class`SpacetimeDB 调用使用 `procedure``status_class``request_id` 只进入 trace/log attribute不进入 metric label。
常见外部服务变量:
- `GENARRATIVE_SPACETIME_SERVER_URL`
@@ -164,9 +211,33 @@ Jenkins 按 web / api / Spacetime module / build / deploy / publish 拆分
- `WECHAT_*`
- `ALIYUN_OSS_*`
### 手机验证码短信
手机验证码发送走阿里云普通短信 `SendSms`,验证码由 `module-auth` 在当前 `api-server` 进程内生成、哈希存储和校验,不再调用阿里云托管验证码的 `SendSmsVerifyCode` / `CheckSmsVerifyCode`。因此 `api-server` 重启后,已发送但未校验的验证码会失效。
生产默认短信配置:
```env
ALIYUN_SMS_ENDPOINT=dysmsapi.aliyuncs.com
ALIYUN_SMS_SIGN_NAME=北京亓盒网络科技
ALIYUN_SMS_TEMPLATE_CODE=SMS_506245486
ALIYUN_SMS_TEMPLATE_PARAM_KEY=code
```
阿里云模板参数固定发送为 `{"code":"<验证码>"}`。旧托管验证码相关变量如 `ALIYUN_SMS_CODE_LENGTH``ALIYUN_SMS_CODE_TYPE``ALIYUN_SMS_RETURN_VERIFY_CODE``ALIYUN_SMS_CASE_AUTH_POLICY``ALIYUN_SMS_SCHEME_NAME` 不再影响真实阿里云校验;验证码长度、有效期、冷却和失败次数由后端本地逻辑控制。真实短信联调仍需 `SMS_AUTH_PROVIDER=aliyun``SMS_AUTH_ENABLED=true` 和有效 `ALIYUN_SMS_ACCESS_KEY_*`。修改 `.env.local` 后必须重启 `api-server`,再用 `/api/auth/login-options` 确认返回包含 `phone`;如果通过 shell 临时覆盖PowerShell 使用 `$env:SMS_AUTH_ENABLED="true"`cmd 使用 `set SMS_AUTH_ENABLED=true`,不要把引号作为环境变量值的一部分传给进程。
如需在本地确认平台层确实调用阿里云 `SendSms`,可手动运行默认忽略的真实短信测试。该测试会向 `ALIYUN_SMS_REAL_TEST_PHONE_NUMBER` 发送验证码短信,普通 `cargo test` 不会执行:
```powershell
$env:ALIYUN_SMS_ACCESS_KEY_ID="..."
$env:ALIYUN_SMS_ACCESS_KEY_SECRET="..."
$env:ALIYUN_SMS_REAL_TEST_PHONE_NUMBER="13800138000"
cargo test -p platform-auth --manifest-path server-rs/Cargo.toml aliyun_send_sms_real_provider_sends_verify_code -- --ignored --nocapture
```
## 埋点与运营查询
用户行为埋点原始事实写入 `tracking_event`,聚合投影写入 `tracking_daily_stat`。任务配置、进度、领奖、钱包流水分别写入:
用户行为埋点原始事实写入 `tracking_event`,聚合投影写入 `tracking_daily_stat`高频 HTTP route tracking 不直接阻塞请求链路:`api-server` 将普通 route tracking 先写入本机 tracking outbox再由后台 worker 按数量或时间阈值批量写入 SpacetimeDB`daily_login`、作品游玩 `work_play_start`、付费、任务领奖和钱包相关关键事件继续同步直写数据库,避免用户任务进度、游玩统计或支付状态出现可感知延迟。任务配置、进度、领奖、钱包流水分别写入:
- `profile_task_config`
- `profile_task_progress`
@@ -175,6 +246,18 @@ Jenkins 按 web / api / Spacetime module / build / deploy / publish 拆分
个人任务首版 scope 仅支持 `user`。后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 等特定链路按 tracking 中间件排除规则处理;作品游玩统一使用 `work_play_start`
tracking outbox 默认配置:
```env
GENARRATIVE_TRACKING_OUTBOX_ENABLED=true
GENARRATIVE_TRACKING_OUTBOX_DIR=/var/lib/genarrative/tracking-outbox
GENARRATIVE_TRACKING_OUTBOX_BATCH_SIZE=500
GENARRATIVE_TRACKING_OUTBOX_FLUSH_INTERVAL_MS=1000
GENARRATIVE_TRACKING_OUTBOX_MAX_BYTES=268435456
```
outbox 采用 NDJSON 文件保存原始事件。达到 `BATCH_SIZE` 时会立刻把当前 active 文件原子封存为 sealed 文件,并马上切到新的 active 继续写入;后台 worker 异步 flush sealed 文件HTTP 请求线程不等待 SpacetimeDB。`FLUSH_INTERVAL_MS` 只负责兜底封存长时间未满批的 active 文件。SpacetimeDB 批量 procedure 返回成功后删除 sealed 文件,失败则保留文件并重试。`MAX_BYTES` 是磁盘保护阈值,不是 flush 阈值;超过后低价值 route tracking 可以被丢弃并记录日志 / 指标关键同步事件不进入该丢弃路径。sealed 文件若出现无法解析的坏行,会重命名为 `corrupt-*` 隔离并记录 `genarrative.tracking_outbox.files.corrupt` 指标,避免一个坏文件阻塞后续批量入库。该机制提供至少一次投递语义,依赖 `tracking_event.event_id` 幂等跳过重复事件。
常用检查思路:
```sql

View File

@@ -8,13 +8,17 @@
当前创作 Tab 固定为智能创作首页与模板入口,草稿 Tab 承接作品架。点击独立入口后应切换到对应内嵌创作表单或生成页;不要额外做一张平行配置页,除非玩法本身需要完整独立工作台。
`PlatformEntryFlowShellImpl.tsx` 仍是平台入口编排壳,后续维护时应优先把独立 UI 片段、公开作品映射、草稿生成 notice 和运行态状态 helper 拆到 `src/components/platform-entry/PlatformEntryFlowShellImpl/` 或同目录紧邻 helper 文件。拆分只允许改变文件组织不改变入口配置事实源、默认导出、props、页面阶段、UI 文案或现有交互;其中拼图首访 onboarding 已拆为 `PlatformEntryFlowShellImpl/PuzzleOnboardingView.tsx`
## 草稿与作品架
1. 草稿页作品卡对齐发现页列表卡风格:左侧信息,右侧封面图,移动端单列,桌面两到三列。
2. 草稿 / 已发布状态尽量图标化,不使用大段状态文案。
3. 草稿卡常态不外露低频动作;已发布作品卡右上角可直接显示无边框分享 icon删除等破坏性动作继续收口到左滑或长按操作层。
4. 生成中作品在整卡上加等待遮罩,但不移除作品基础信息。
5. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取
5. 生成中状态不能只存在前端内存 notice。后端作品摘要必须下发可恢复的 `generationStatus`;前端刷新或退出产品后,作品架优先用摘要状态恢复等待遮罩,本轮内存 notice 只作为即时反馈
6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 使用作品摘要 `updatedAt` 推导。
7. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取。
## 拼图
@@ -27,9 +31,17 @@
当前口径:
- 图像输入复用 `CreativeImageInputPanel`
- 支持画面描述生图、多参考图生图、上传主图后 AI 重绘、上传主图后不重绘。
- 草稿生成会保留关卡图和 UI 背景;当前不自动生成背景音乐。
- 支持画面描述生图、多参考图生图、上传或历史生成主图后 AI 重绘、上传或历史生成主图后不重绘;关闭 AI 重绘时,前端可提交本地上传 Data URL 或历史 `/generated-*` 图片路径,后端统一解析为首关正式图后再持久化
- 草稿生成会先持久化 `generationStatus=generating` 的作品摘要生成完成并回写关卡图、UI 背景后再变为 `ready`;首关关卡图和 UI 背景在命名稳定后并行启动,当前不自动生成背景音乐。
- 作品架拼图草稿的“生成中”遮罩只表示初始草稿还没有可查看结果;只要作品摘要、首关封面或任一关卡候选图已经可用,后续 UI 背景重生成和追加关卡生图都必须作为结果页局部生成态处理,不能阻止打开草稿结果页。
- 拼图草稿编译是长耗时 action前端 action 请求默认等待 `1_000_000ms` 且不自动重试,生成页预计完成时间按 `5` 分钟展示;生成页恢复时必须沿用作品摘要 `updatedAt` 作为原始 `startedAtMs`,失败/完成态用 `finishedAtMs` 冻结耗时,不能在锁屏或返回草稿页后重新从 0 计时。
- 若浏览器锁屏、息屏或网络切换导致 compile 请求失败,前端在标记失败前必须先复读 `getPuzzleAgentSession(sessionId)`;只有最新 session 仍缺 `draft.coverImageSrc`、首关 `coverImageSrc` 或候选图时才展示失败,复读到已生成草稿时按成功收尾、刷新作品架并继续自动试玩/结果页链路。
- 拼图参考图 AI 重绘优先走 VectorEngine `/v1/images/edits`;若编辑接口超时,`api-server` 会降级为 `/v1/images/generations`,并把同一参考图塞进 `image` 数组继续生成,避免参考图草稿整单失败。
- 结果页素材配置当前只保留 UI 相关能力;旧背景音乐入口隐藏。
- 结果页允许多关卡并行编辑和生成;某一关卡图片生成完成回包只静默更新该关卡素材与生成态,不得自动打开或切换关卡详情面板,避免打断用户正在编辑的其它关卡。
- 结果页 UI 背景重生成只禁用 UI 背景自己的按钮和确认动作,不禁用“新增关卡”、关卡图片生成、关卡详情编辑和结果页导航;关卡图片生成也只标记对应关卡的局部生成进度。
- 结果页生成关卡图时若关卡名为空,前端必须传 `shouldAutoNameLevel=true`,后端复用首关命名契约先按画面描述生成关卡名,再在图片生成后用视觉命名结果精修,并把生成名和 UI 背景提示词随本次关卡快照写回。
- 拼图 UI 背景是作品运行态背景,不只属于第一关;本地试玩、直达指定关卡和正式 `next-level` 推进时,目标关卡缺 `uiBackgroundImageSrc/uiBackgroundImageObjectKey` 必须继承同作品首个可用 UI 背景,仍缺失时才沿用当前运行态快照背景或默认 UI。
- 拼图运行态棋盘不叠加分块蒙版、描边、阴影、选中底色或合并块 SVG 轮廓;拼图片本体需要裁切为圆角形状,单块使用独立圆角裁切,合并块使用 SVG 原生 `clipPath` 裁切整体外轮廓,外凸角和内凹角分别计算半径,内凹角半径要比外凸角更明显以避免手机 WebView 中看起来仍是直角。原图道具只在用户主动确认后打开独立原图查看层,不在当前拼图棋盘上叠加原图。
- 拼图运行态拖拽必须完全跟随手指或鼠标位置,`pointermove` 期间即时写入可见拼块的 transform不依赖等待后端回包、React 重渲染或下一帧动画队列;进入拖动后不展示拼块选中态或“已选择”提示,松手后再提交目标格同步规则真相。
- 拼图运行态的提示、设置等点击弹层跟随当前运行态主色主题,使用普通圆角主题面板,不复用像素九宫格素材框。
@@ -51,24 +63,24 @@
难度映射:
| 难度 | clearCount | difficulty | 总物品数 | 物品种类 |
| --- | ---: | ---: | ---: | ---: |
| 轻松 | 8 | 2 | 24 | 3 |
| 标准 | 12 | 4 | 36 | 9 |
| 进阶 | 16 | 6 | 48 | 15 |
| 硬核 | 21 | 8 | 63 | 21 |
| ---- | ---------: | ---------: | -------: | -------: |
| 轻松 | 8 | 2 | 24 | 3 |
| 标准 | 12 | 4 | 36 | 9 |
| 进阶 | 16 | 6 | 48 | 15 |
| 硬核 | 21 | 8 | 63 | 21 |
当前素材生成流水线:
1. 点击生成前弹出泥点确认,草稿生成固定消耗 `10` 泥点。
2. 先写入可恢复草稿 profile再执行文本计划、图片生成、切图、OSS 上传、背景和容器生成;草稿完成条件不包含 `backgroundMusic`
2. 先写入可恢复草稿 profile再执行文本计划、图片生成、切图、OSS 上传、背景和容器生成;作品摘要在素材或背景未完整时下发 `generationStatus=generating`,素材和背景完整后下发 `ready`草稿完成条件不包含 `backgroundMusic`
3. 物品素材不再调用 Hyper3D Rodin不再生成 GLB。新草稿和批量新增固定生成 2D 五视角素材。
4. 物品 sheet 走 VectorEngine Gemini `gemini-3-pro-image-preview` 原生 `generateContent`,单张 `1:1` 图固定 `5*5`,每张承载 `5` 个物品、每个物品 `5` 个视角。
5. 切图前先在整张 sheet 上做绿幕 / 近白底透明化和边缘去污染,再按格子导出独立 PNG每个视角图再以扩大的 PNG 边界带为种子,把连通的浅绿 / 近白抗锯齿边直接改为透明,并按剩余可见主体二次收紧;不要先裁剪单格再各自去绿。
5. 切图前先在整张 sheet 上做绿幕 / 近白底透明化和边缘去污染,再按格子导出独立 PNG每个视角图再以扩大的 PNG 边界带为种子,把连通的浅绿 / 近白抗锯齿边直接改为透明,并对贴透明背景的弱绿 / 暗绿轮廓像素做去绿污染处理,最后按剩余可见主体二次收紧;不要先裁剪单格再各自去绿。
6. `generatedItemAssets[].imageViews[]` 是新素材主字段,`imageSrc/imageObjectKey` 只兼容首张视角。
7. 文本生成物品名称时必须同时生成 `itemSize`,只允许 `大``中``小`。该字段随 `generatedItemAssets[].itemSize` 持久化并下发;历史缺失字段的素材按 `大` 兼容,模型缺失或非法值按物品名本地推断。
8. 局内 `9:16` 纯背景图和 `1:1` 中心容器 UI 图分开生成。纯背景不得包含锅、盘、托盘、HUD、按钮、文字或物品且入库前必须合成为全画幅不透明图片不允许出现透明区域容器图走 `/v1/images/edits` 参考透明容器图。
9. 当前抓大鹅音频生成关闭:入口无 `生成音效`,草稿不生成背景音乐或点击音效,结果页不展示背景音乐 Tab 或点击音效生成入口。历史 `backgroundMusic` / `clickSound` 字段继续兼容传递。
10. UI 背景和容器资产的持久化真相仍在 `generatedItemAssets[].backgroundAsset`Agent session、work summary/detail、结果页和运行态入口都必须把该字段提升为 `backgroundImageSrc/backgroundImageObjectKey/generatedBackgroundAsset` 读取,避免草稿重进、结果页预览或试玩退回默认素材
10. UI 背景和容器资产的持久化真相仍在 `generatedItemAssets[].backgroundAsset`Agent session、work summary/detail、结果页和运行态入口都必须把该字段提升为 `backgroundImageSrc/backgroundImageObjectKey/generatedBackgroundAsset` 读取。草稿编译后的 `draftJson` 自身也必须携带 `generatedItemAssets` 快照HTTP facade 不能只依赖 work detail 回读补齐 UI 资产,外部回读为空时也不得清空草稿内已有的背景 / 容器图。平台壳层从作品架、广场、生成完成回调、结果页保存 / 发布 / 试玩回调进入 Match3D profile 时也要先归一化并提升,避免首次试玩、手动试玩、推荐流或公开详情运行态退回默认背景 / 默认容器
结果页当前结构:
@@ -81,15 +93,16 @@
运行态当前口径:
- 规则真相在后端;前端只做即时表现、点击候选、飞入、入槽、三消和胜负过渡。
- 物品选择只在 `pointerup` 时提交;`pointerdown` / `pointermove` 只更新候选样式。松手时按当前位置和最新快照命中一个最上层可点击物品。
- 物品选择只在 `pointerup` 时提交;`pointerdown` / `pointermove` 只更新候选样式。松手时按当前位置和最新快照命中一个最上层可点击物品;生成 2D PNG 物品必须按当前展示图的 alpha 像素做热区精筛,透明像素、`object-contain` 留白和 `itemSize` 缩小后的空白区不能响应点击
- 物品 DOM 只负责展示,不通过自身 `click` 事件直接提交,避免浏览器后续 click 绕过松手判定造成重复提交。
- 初始物品坐标围绕容器口中心生成,并保留内缩安全距离,避免贴边和局部角落聚集。
- 本地试玩与 Rust `module-match3d` 后端领域生成使用同一套中心铺开口径;生成点覆盖四象限且均值接近中心。
- 运行态优先消费 2D 生成图;默认积木 / 程序化 3D 表现只作为视觉分支和兜底,不改变规则真相。
- 运行态启动前要预加载 `generatedItemAssets[].imageViews[]`、顶层 `generatedBackgroundAsset`、物品挂载 `backgroundAsset` 中的背景和容器图;卡片摘要缺 UI 背景或容器字段时,进入运行态前必须补读 work detail。补读后的 profile 也要再次提升 `generatedItemAssets[].backgroundAsset`,确保背景和容器字段传给 `Match3DRuntimeShell`
- 局内容器图在移动端宽度接近屏幕宽度并居中显示,保持原图比例不拉伸;生成容器图加载成功后棋盘外壳透明且 `overflow-visible`,只有生成图缺失或加载失败时才显示透明参考容器兜底。
- 运行态启动前要预加载 `generatedItemAssets[].imageViews[]`、顶层 `generatedBackgroundAsset`、物品挂载 `backgroundAsset` 中的背景和容器图;首次生成自动试玩、结果页手动试玩、推荐流和公开详情启动都必须传入提升后的 profile。卡片摘要缺 UI 背景或容器字段时,进入运行态前必须补读 work detail。补读后的 profile 也要再次提升 `generatedItemAssets[].backgroundAsset`,确保背景和容器字段传给 `Match3DRuntimeShell`
- 局内容器图在移动端宽度大于屏幕宽度并略向下压,当前运行态使用 `w-[min(116vw,42rem)]``top-[54%]` 放大和下移容器图本体,保持原图比例不拉伸且不改变后端物品布局、点击半径或消除规则;生成容器图加载成功后棋盘外壳透明且 `overflow-visible`,只有生成图缺失或加载失败时才显示透明参考容器兜底。
- generated 私有图换签未完成时,局内物品先隐藏等待,不得短暂显示默认积木;同一批资源在重启 run 时保留已解析签名 URL只有资源源列表变化或换签失败后才允许进入兜底视觉。
- `itemSize` 只缩放生成 2D 图片本体:`大` 使用当前默认显示尺寸`中``小` 缩小显示;不改变后端下发的布局半径、点击半径或三消规则。
- `itemSize` 只缩放生成 2D 图片本体:`大``中``小` 均按相对尺寸缩放,其中 `大` 也比原始图片略小`中``小` 进一步缩小;不改变后端下发的布局半径、点击半径或三消规则。
- 物品进入底部物品栏时按同类型插入:如果物品栏已有同类物品,新物品插到该类型最后一个物品后面,后续物品整体后移;没有同类时追加到当前末尾。达到三件同类时,在飞入物品栏动画结束后,左侧和右侧同类物品向中间合成,三件一起消失,播放合成音效,不展示星星图标,后面的物品再向前补位。该动效只是前端表现层,后端和本地试玩仍负责权威插入、指定点击类型清除与补位后的槽位快照。
- 抓大鹅运行态右上角常驻设置入口,不直接暴露重新开始按钮;重新开始收口到设置面板内,结算弹层仍保留结果态的再来一局动作。
- 高 DPR 移动端 WebGL canvas 必须锁定 CSS 尺寸,避免右下溢出。
@@ -172,3 +185,4 @@
3. 生成失败时,后端应返回可操作 `details.reason` / `details.missingEnv`,前端优先展示具体原因。
4. 半配置 OSS 不应阻断 `api-server` 启动;具体生成或换签接口在需要时返回配置缺失。
5. 历史 generated path 可以兼容读取,但新链路不要把裸 path 当公开静态资源。
6. 发现页 / 推荐流公开作品卡封面必须兼容旧移动浏览器内核:封面容器不能只依赖 CSS `aspect-ratio` 撑高,必须保留 16:9 或对应沉浸卡比例的可见高度兜底generated 私有封面换签失败时要回落到玩法类型参考图,避免卡片整体黑底。

View File

@@ -39,6 +39,12 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台。当
内部状态值可继续复用历史 `home/category/create/saves/profile`,但用户可见文案按上面的新口径展示。
## 账户与登录
1. 主站登录弹窗必须稳定展示 `短信登录``密码登录` 两个核心入口;`GET /api/auth/login-options` 只能补充微信等环境相关入口,不能决定是否隐藏短信或密码登录。
2. `login-options` 为空、失败、只返回 `phone` 或只返回 `password` 时,前端仍要同时展示验证码登录页签和密码登录页签;短信能力真实可用性由发送验证码接口返回结果表达。
3. 登录弹窗继续复用现有独立 modal 和页签结构,不在页面中新增功能说明类文案,也不把邀请码输入放回登录面板。
## 账户与充值
1. “我的”页账户充值弹窗包含 `泥点充值``会员卡充值` 两个页签,入口必须打开独立弹窗,不在当前面板下方展开。