Merge remote-tracking branch 'origin/master' into codex/puzzle-clear-template-runtime-fixes
# Conflicts: # .hermes/shared-memory/decision-log.md # src/index.test.ts
This commit is contained in:
@@ -15,6 +15,46 @@
|
||||
- 关联:相关文件、文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 生产冷备份后 API 不能只依赖 SpacetimeDB 自恢复
|
||||
|
||||
- 现象:release 机器 `03:20` 冷备份后,`spacetimedb.service` 已恢复,但作品列表、创作入口配置或公开 gallery 继续超时 / 502 / 504,`genarrative-api.service` 保持 stopped。
|
||||
- 原因:`genarrative-api.service` 配置了 `Requires=spacetimedb.service`,冷备份停止 `spacetimedb.service` 时 API 会被 systemd 依赖关系一并停止;如果 `genarrative-database-backup.service` 只传 `--stop-service spacetimedb.service` 而漏掉 `--restart-service-after genarrative-api.service`,备份脚本只会恢复数据库,不会再拉起 API。
|
||||
- 处理:生产冷备份 unit 和发布脚本必须带 `--restart-service-after genarrative-api.service`;仓库用 `npm run check:production-ops` 检查 systemd 模板、API build/deploy 归档和健康巡检链路。现场修复后执行 `systemctl daemon-reload`,但不要为了验证而手动触发冷备份。
|
||||
- 验证:`systemctl cat genarrative-database-backup.service` 应包含该参数;`systemctl is-active spacetimedb.service genarrative-api.service nginx.service` 全为 `active`;`curl -fsS http://127.0.0.1:3101/v1/ping`、`/healthz`、`/readyz` 和代表性 `/api/runtime/puzzle/gallery` 均成功。
|
||||
- 关联:`deploy/systemd/genarrative-database-backup.service`、`scripts/database-backup-to-oss.mjs`、`scripts/ops/production-health-patrol.mjs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## SpacetimeDB 45 秒超时要看 api-server 记录的阶段
|
||||
|
||||
- 现象:release 上 Nginx 能立刻连到 `api-server`,但 `/api/runtime/*/gallery`、`/api/creation-entry/config` 等请求在约 `GENARRATIVE_SPACETIME_PROCEDURE_TIMEOUT_SECONDS` 后返回 `502` / `504`。
|
||||
- 原因:旧日志只能看到 HTTP 总耗时和最终状态,无法区分卡在连接池、SDK 建连、等待 `on_connect`、订阅 read model、等待 procedure / reducer 回调还是本地订阅 cache 读取。
|
||||
- 处理:`spacetime-client` 内置阶段化健康检查和失败日志;`/readyz` 用 `GENARRATIVE_SPACETIME_HEALTH_CHECK_TIMEOUT_SECONDS` 短窗口检查 SpacetimeDB 连接租约,业务失败日志包含 `operation_kind`、`operation_name`、`spacetime_stage`、`elapsed_ms`。
|
||||
- 验证:`/readyz` 失败时看 `details.spacetime.stage`;业务请求超时时查 `journalctl -u genarrative-api.service` 中同一时间窗口的 `SpacetimeDB client operation failed`,优先按 `pool_acquire`、`connect_build`、`connect_handshake`、`read_model_subscribe`、`procedure_result`、`reducer_result`、`read_cache` 分阶段处理。
|
||||
- 关联:`server-rs/crates/spacetime-client/src/lib.rs`、`server-rs/crates/api-server/src/health.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 新建草稿扣费不能和入口卡泥点配置分离
|
||||
|
||||
- 现象:后台修改创作入口的 `mudPointCost` 后,入口卡和前置余额提示可能显示新数值,但用户真实钱包流水仍按代码常量扣除。
|
||||
- 原因:早期约定把 `creationTypes[].unifiedCreationSpec.mudPointCost` 只当展示字段,拼图、抓大鹅和汪汪声浪初始生成各自保留了 `2`、`10`、三次单图 `1` 的硬编码扣费路径。
|
||||
- 处理:新建草稿初始生成成本必须统一从 `GET /api/creation-entry/config` 的 `unifiedCreationSpec.mudPointCost` 解析;前端预校验、拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成同源。汪汪声浪结果页单图重新生成仍按单图资产操作成本,不套初始草稿总成本。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "mud points"`、`npm run test -- src/services/bark-battle-creation/barkBattleCreationClient.test.ts`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml resolves_mud_point_cost initial_generation_slot_cost_splits_creation_entry_total_cost -- --nocapture`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`server-rs/crates/api-server/src/creation_entry_config.rs`、`server-rs/crates/api-server/src/puzzle/handlers.rs`、`server-rs/crates/api-server/src/match3d/draft.rs`、`server-rs/crates/api-server/src/bark_battle.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## generated 图片重复下载不要改成服务端本地磁盘缓存
|
||||
|
||||
- 现象:同一张 OSS generated 图片每次展示都重新从 OSS 拉取,或者完整 OSS 私有 URL 裸请求返回 403。
|
||||
- 原因:前端输入如果是 `https://*.oss-*.aliyuncs.com/generated-*`,会被当普通绝对 URL 直连,绕过 `/api/assets/read-url` 和 signed URL 本地缓存;旧 OSS 对象如果缺少 `Cache-Control`,浏览器只能依赖 `ETag` / `Last-Modified` 做 304 协商缓存,不会长期强缓存。
|
||||
- 处理:完整 OSS generated URL 先归一成 `/generated-*` legacy public path,再走 `/api/assets/read-url` 换签;`refreshKey` 是 signed URL 缓存版本号,同一路径、同一版本且未临近过期时必须复用,不要每次渲染都强制重新换签。新上传 generated 私有对象由 `platform-oss` 在 `PostObject` form fields / policy 和服务端 `PutObject` 请求头中写入 `Cache-Control: public, max-age=31536000, immutable`。不要把 api-server 变成图片静态代理,也不要把 OSS 内容 fallback 到服务器磁盘。
|
||||
- 验证:前端测试应看到完整 OSS generated URL 调用 `/api/assets/read-url?legacyPublicPath=...`,且相同 `refreshKey` 不重复换签;`cargo test -p platform-oss --manifest-path server-rs/Cargo.toml` 应覆盖 `Cache-Control` policy、form field、PutObject headers 和 V4 `AdditionalHeaders`;线上旧对象可用 `curl -I` 观察是否只有 `ETag` / `Last-Modified` 或已经补齐 `Cache-Control`。
|
||||
- 关联:`src/services/assetReadUrlService.ts`、`server-rs/crates/platform-oss/src/lib.rs`、`server-rs/crates/platform-oss/README.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 小程序 H5 导航不能清掉宿主 query
|
||||
|
||||
- 现象:微信小程序首次进入 H5 后,点击需要登录的入口没有返回小程序原生授权页,而是弹出 Web 端登录窗口;充值渠道也可能被误判为普通网页环境。
|
||||
- 原因:小程序 `web-view` 入口通过 `clientType=mini_program`、`clientRuntime=wechat_mini_program`、`miniProgramEnv` 标记宿主环境,但 H5 内部 `pushAppHistoryPath(...)` 阶段导航会默认清空 query;首点时微信 JS bridge 也可能尚未就绪,导致 `isWechatMiniProgramWebViewRuntime()` 和充值平台判断读不到小程序上下文。
|
||||
- 处理:路由层统一把 `clientType`、`clientRuntime`、`miniProgramEnv` 当作 app runtime context,在普通路径归一、显式 query 路由和同一创作流跳转时都跨导航保留;小程序环境识别同时用 `MicroMessenger + miniProgram` User-Agent 兜底首点 bridge 未就绪场景;创作恢复参数仍只在同玩法创作流内保留,离开创作流时继续清理。
|
||||
- 验证:`npm exec vitest run src/routing/appPageRoutes.test.ts src/components/auth/AuthGate.test.tsx src/services/authService.test.ts src/services/payment/paymentPlatform.test.ts`。
|
||||
- 关联:`src/routing/appPageRoutes.ts`、`src/services/authService.ts`、`src/services/payment/paymentPlatform.ts`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||
|
||||
## 平台异步错误必须带来源弹窗,不要只显示裸错误
|
||||
|
||||
- 现象:用户先后触发多个拼图或草稿生成时,旧请求失败后会在当前页面显示“图片生成失败”等裸错误,容易误判为当前正在看的拼图失败;错误文本也不便复制给开发排查。
|
||||
@@ -23,6 +63,22 @@
|
||||
- 验证:触发任一平台级异步失败时,页面应出现包含“错误来源”和“错误内容”的弹窗;复制内容应包含来源和错误正文;旧页面内错误 banner 不再重复出现。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/PlatformErrorDialog.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 自定义世界旧公开作品不要用 published_at 判断是否存在
|
||||
|
||||
- 现象:RPG / 自定义世界作品详情能打开,但点赞时报 `custom_world 已发布作品不存在,无法点赞`,错误来源是 `作品详情 CW-*` 或其它自定义世界历史公开号。
|
||||
- 原因:部分历史 `custom_world_profile` 已是 `publication_status=Published`,但 `published_at` 为空;统一公开详情会用 `updated_at` 兜底展示,旧点赞 / 游玩 / Remix 判断却额外要求 `published_at.is_some()`。
|
||||
- 处理:公开互动存在性统一按 `Published + deleted_at=None + visible=true` 判断;`custom_world_gallery_entry` 同步和公开展示时间在 `published_at` 缺失时回退 `updated_at`。
|
||||
- 验证:`cargo test -p spacetime-module custom_world_public_interactions_accept_legacy_missing_published_at --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联:`server-rs/crates/spacetime-module/src/custom_world.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`。
|
||||
|
||||
## 推荐页 WF 点赞不要落到 RPG / custom-world
|
||||
|
||||
- 现象:推荐页里给 `WF-*` 敲木鱼作品点赞时,平台错误弹窗显示 `custom_world 已发布作品不存在,无法点赞`。
|
||||
- 原因:推荐页点赞统一走 `likePublicWork`,但敲木鱼尚未接入点赞后端;缺少 `wooden-fish` 分支时会落入默认 RPG / custom-world 点赞路径,把敲木鱼的 owner/profile 传给 custom-world reducer。
|
||||
- 处理:所有公开作品互动必须先按 `packages/shared/src/contracts/playTypes.ts` 中的全局 `sourceType` 分流;暂未接入点赞的玩法直接报“该作品类型暂不支持点赞”,禁止显示开放兜底文案,也禁止用默认 RPG / custom-world 分支兜底。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation wooden fish like does not call RPG gallery like"`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`。
|
||||
|
||||
## 暗色创作进度卡不要被 platform-remap-surface 改成深色文字
|
||||
|
||||
- 现象:统一创作页里的暗色进度卡背景是深绿 / 深蓝,但“创作进度”、百分比和进度提示显示成深色,移动端几乎看不清。
|
||||
@@ -31,21 +87,21 @@
|
||||
- 验证:`CreationAgentWorkspace` 测试应断言进度标题、百分比和提示文本带专属 class;`src/index.test.ts` 应断言这些 class 在 remap surface 内有白色覆盖规则;移动端截图中暗色卡片文字应保持可读。
|
||||
- 关联:`src/components/creation-agent/CreationAgentWorkspace.tsx`、`src/components/creation-agent/CreationAgentWorkspace.test.tsx`、`src/index.css`、`src/index.test.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## VectorEngine 图片生成 SendRequest 超时要按传输失败排查
|
||||
## VectorEngine 图片生成 request_send 传输错误要按可重试网络抖动排查
|
||||
|
||||
- 现象:`external_api_call_failure` 里看到 `failureStage=request_send`、`timeout=true`、`statusCode=null`,`errorSource` 可能是 `client error (SendRequest)` 或更完整的 reqwest 底层错误链,前端只知道图片生成失败。
|
||||
- 原因:`timeout=true` 来自 `reqwest::Error::is_timeout()`,不是业务代码固定写死;`SendRequest` 是 Hyper 发送请求阶段的错误来源标签,只说明请求未拿到可归类的 HTTP 响应,不会包含上游 JSON 错误体。
|
||||
- 处理:先按 `provider/failureStage/statusClass` 聚合,再用 `user_id` / `profile_id` 和 `metadata_json.userId/profileId/requestId` 定位触发者、草稿 / 作品和同一次 HTTP 请求;`request_send + timeout=true` 优先查 provider 日志的 `source_chain`、请求体大小、参考图数量、出口网络、代理/Nginx、VectorEngine 当时可用性和同一 request_id 日志。若记录有 `502` 或 `429 moderation_blocked`,按上游网关或审核失败另行处理,不要归到传输超时。
|
||||
- 现象:`external_api_call_failure` 里看到 `failureStage=request_send`、`statusCode=null`,`errorSource` 可能是 `client error (SendRequest)`、`[35] SSL connect error (Recv failure: Connection reset by peer)`、`[56] Failure when receiving data from the peer (... unexpected eof while reading ...)`;也可能看到 `failureStage=upstream_status`、`statusCode=502`、错误体是 Nginx HTML `502 Bad Gateway`。前端只知道图片生成失败。
|
||||
- 原因:`request_send` 表示请求未拿到可归类的 HTTP 响应,不会包含上游 JSON 错误体;`upstream_status=502/5xx/429/408` 表示拿到了上游错误响应但仍属于可重试的过载 / 网关抖动。`timeout=true` 来自超时判定,`connect=true` 会同时覆盖 DNS / connect 失败以及 libcurl 35 SSL 握手、libcurl 56 收包提前 EOF、connection reset 这类临时传输错误。
|
||||
- 处理:先按 `provider/failureStage/statusClass` 聚合,再用 `user_id` / `profile_id` 和 `metadata_json.userId/profileId/requestId` 定位触发者、草稿 / 作品和同一次 HTTP 请求;`request_send + timeout/connect=true` 或 `upstream_status + statusCode=408/429/5xx` 优先查 provider 日志的 `source_chain`、请求体大小、参考图数量、出口网络、代理/Nginx、VectorEngine 当时可用性和同一 request_id 日志。当前 `platform-image` 对 request_send 的 timeout / connect / SSL connect reset / recv error / unexpected eof / send error,以及 upstream_status 的 408 / 429 / 5xx 最多发送 5 次,multipart `/v1/images/edits` 每次重试都会重新构造 form;看到 `VectorEngine 图片请求发送失败,准备重试` 或 `VectorEngine 图片上游状态可重试,准备重试` 只是单次 attempt 失败,最终 `external_api_call_failure` 才代表该用户请求整体失败。若记录有 `429 moderation_blocked` 或明确审核错误,按审核失败另行处理,不要归到网络抖动。
|
||||
- 拼图关卡资产生成按 `level_scene -> ui_spritesheet -> level_background` 顺序执行,每个资产会输出 `slot`、`asset_kind`、`elapsed_ms`;排查拼图草稿失败时优先看同一 request_id 下最后一个失败 slot。
|
||||
- 验证:`cargo check -p api-server --manifest-path server-rs/Cargo.toml`;查询 `tracking_event` 时失败记录应能看到触发者 `user_id` 和可用的 `profile_id`。
|
||||
- 关联:`server-rs/crates/api-server/src/external_api_audit.rs`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
- 验证:`cargo test -p platform-image --manifest-path server-rs/Cargo.toml vector_engine_send_retry_policy -- --nocapture`、`cargo test -p platform-image --manifest-path server-rs/Cargo.toml vector_engine_image_edit_retries_send_timeout_once_and_succeeds`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`;查询 `tracking_event` 时失败记录应能看到触发者 `user_id` 和可用的 `profile_id`。
|
||||
- 关联:`server-rs/crates/platform-image/src/vector_engine/client.rs`、`server-rs/crates/api-server/src/external_api_audit.rs`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## “我的”页每日任务卡不要硬编码进度
|
||||
## “我的”页每日任务卡不要硬编码进度,也不要跨日保留旧状态
|
||||
|
||||
- 现象:用户完成或领取每日任务后,任务中心弹窗里的任务状态已经变化,但“我的”页卡片仍显示 `0 / 1` 和“去完成”。
|
||||
- 原因:卡片首版只写了静态展示文案,没有读取 `/api/profile/tasks` 返回的 `ProfileTaskCenterResponse`,领取接口返回的新 `center` 也只用于弹窗。
|
||||
- 处理:进入“我的”页时读取任务中心,卡片用当前可操作任务或已领取任务派生奖励、进度条和操作状态;`claimRpgProfileTaskReward(...)` 成功后用响应里的 `center` 覆盖本地任务中心。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应覆盖卡片从后端任务摘要显示 `1 / 1`,领取后显示已完成。
|
||||
- 原因:卡片首版只写了静态展示文案,没有读取 `/api/profile/tasks` 返回的 `ProfileTaskCenterResponse`,领取接口返回的新 `center` 也只用于弹窗;后来虽然后端按北京时间 0 点切换业务日,但前端停留在“我的”页时不会跨日刷新,可能继续展示上一日已领取状态。
|
||||
- 处理:进入“我的”页时读取任务中心,卡片用当前可操作任务或已领取任务派生奖励、进度条和操作状态;`claimRpgProfileTaskReward(...)` 成功后用响应里的 `center` 覆盖本地任务中心;停留在“我的”页跨过北京时间 0 点时,先非阻断 refresh 登录态写入新业务日 `daily_login`,再重拉任务中心。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应覆盖卡片从后端任务摘要显示 `1 / 1`、领取后显示已完成,以及北京时间 0 点自动 refresh 后重拉任务中心。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||
|
||||
## “我的”页不要恢复旧的填邀请码次级按钮
|
||||
@@ -88,6 +144,14 @@
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft"`,并确认恢复生成中草稿后 `getPuzzleAgentSession` 不会因为进度刷新继续连发。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/usePlatformCreationAgentFlowController.ts`、`src/components/platform-entry/usePlatformCreationAgentFlowController.test.tsx`。
|
||||
|
||||
## 小游戏恢复生成页不要只用请求 busy 判定是否生成中
|
||||
|
||||
- 现象:敲木鱼作品架里的生成中草稿点击进入生成页后,页面会显示“重新生成草稿”按钮,而不是继续显示素材生成中的等待态。
|
||||
- 原因:平台壳恢复 `generationStatus=generating` 草稿时会把 `isBusy` 置回 false,只保留 `MiniGameDraftGenerationState` 作为生成事实;生成页如果只把请求 busy 传给 `isGenerating`,共用生成页会误判为空闲态并展示重试按钮。
|
||||
- 处理:小游戏生成页的 `isGenerating` 必须由 `isBusy || isMiniGameDraftGenerating(generationState)` 推导;跳一跳、拼消消、敲木鱼等从作品架恢复的生成页都要使用同一口径。
|
||||
- 验证:`npm run test -- src/components/platform-entry/PlatformEntryFlowShellImpl.test.ts` 应覆盖 `busy=false` 但敲木鱼 generation state 仍在生成中时继续隐藏重试入口。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/unified-creation/UnifiedGenerationPage.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 拼图试玩恢复 query 必须先切到运行态路径再写
|
||||
|
||||
- 现象:拼图试玩或正式运行态打开后,刷新会停在“正在进入拼图关卡”,或地址栏只有 `runtimeProfileId`,缺少草稿 `runtimeSessionId`。
|
||||
@@ -167,6 +231,14 @@
|
||||
- 验证:后台保存两条以上公告后,点击底部加号进入创作入口页应自动轮播这些后台配置项;`CustomWorldCreationHub` 相关测试应断言标题来自后端配置。
|
||||
- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`server-rs/crates/module-runtime/src/application.rs`、`apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx`。
|
||||
|
||||
## 创作入口 banner 默认图片路径必须真实存在
|
||||
|
||||
- 现象:创作页顶部 banner 返回旧结构化 `eventBanner` 时,前端 `<img>` 请求 `/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png`,但 `public/` 下没有该文件,导致 banner 背景图加载失败。
|
||||
- 原因:旧库 `event_banners_json=None` 时,读取层把旧单条结构化 banner 当成 `eventBanners` 优先数组下发;同时旧结构化默认 `coverImageSrc` 指向已经不存在的品牌素材路径。
|
||||
- 处理:`module-runtime` 在 `event_banners_json` 缺失或不可解析时回到默认公告数组;默认 HTML 公告和旧结构化默认 `coverImageSrc` 都引用 `public/` 下真实存在的 `/creation-type-references/puzzle.webp`。
|
||||
- 验证:`cargo test -p module-runtime creation_entry_event_banners_none_returns_default_announcements --manifest-path server-rs/Cargo.toml`;重启本地 `api-server` 后 `GET /api/creation-entry/config` 的 `eventBanners[0]` 不再指向缺失的 `/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png`。
|
||||
- 关联:`server-rs/crates/module-runtime/src/application.rs`、`server-rs/crates/module-runtime/src/domain.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 移动端草稿卡不要长按选中文字
|
||||
|
||||
- 现象:移动端草稿页长按作品卡标题或摘要时触发系统文字选区,容易误触并打断作品架操作。
|
||||
@@ -187,7 +259,7 @@
|
||||
- 现象:创作 Tab 两列玩法卡上图能看到,但标题、描述或预计消耗泥点在白底信息区里看不见,或只剩泥点小图标。
|
||||
- 原因:旧 `platform-creation-reference-card` 是给暗图蒙版卡用的全局样式,会把卡片及全部子元素强制成白色文字;参考图要求的是“上图 + 下方白底信息区”,继续复用旧类会让白底上的文字消失。
|
||||
- 处理:创作 Tab 首屏模板卡使用独立 `creation-template-card`、`creation-template-card__body`、`creation-template-card__title`、`creation-template-card__subtitle` 和 `creation-template-card__cost` 结构,不挂 `platform-creation-reference-card`;旧弹层如果仍是暗图蒙版卡,可以继续保留旧类。
|
||||
- 验证:浏览器创作 Tab 中每张卡都应显示标题、描述和“预计消耗 10-20 泥点”;`npm test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx -t "creation start card renders reference-aligned banner and template metadata"` 应通过。
|
||||
- 验证:浏览器创作 Tab 中每张开放态卡都应显示标题、描述和后台契约 `mudPointCost` 数量经前端格式化后的泥点消耗文案;旧契约缺字段时兜底显示 `10泥点数`;`npm test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx -t "creation start card renders reference-aligned banner and template metadata"` 应通过。
|
||||
- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/index.css`、`src/components/custom-world-home/CustomWorldCreationHub.test.tsx`。
|
||||
|
||||
## 创作首屏开放态卡片不要再显示左上状态标签
|
||||
@@ -417,6 +489,14 @@
|
||||
- 验证:未登录推荐页可以直接进入跳一跳运行态,且 `work_play_start` 事件仍会落库或出现在 outbox 中,metadata 含匿名标记。
|
||||
- 关联:`server-rs/crates/api-server/src/jump_hop.rs`、`server-rs/crates/api-server/src/auth.rs`、`server-rs/crates/api-server/src/work_play_tracking.rs`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
||||
|
||||
## 跳一跳直接打开空 runtime 路由不能停在加载态
|
||||
|
||||
- 现象:直接访问 `/runtime/jump-hop` 时页面看起来一直停在“正在载入游戏 / 正在加载内容”,DOM 内部只有空的跳一跳运行态,没有平台、地块或 run 数据。
|
||||
- 原因:`appPageRoutes` 会把该路径解析为 `jump-hop-runtime`,但裸路径没有 `work=JH-*` 公开作品码,也没有从详情页启动后写入的 `jumpHopRun`,平台壳仍挂载 `JumpHopRuntimeShell`。
|
||||
- 处理:平台壳在 `jump-hop-runtime` 且缺少 run 时先看 `work` 参数;有 `JH-*` 则通过公开 gallery detail 回读 profile 并启动 published run,没有则回到平台首页。全局作品码恢复 effect 在跳一跳 runtime 阶段要跳过,避免和运行态恢复互相抢路由。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct jump hop runtime route"`;浏览器 smoke 分别打开 `/`、`/runtime/jump-hop` 和 `/runtime/jump-hop?work=JH-*`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/routing/appPageRoutes.ts`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`。
|
||||
|
||||
## release tracking outbox 权限错误先查 env 缺失
|
||||
|
||||
- 现象:release 机器 `journalctl -u genarrative-api.service` 每秒刷 `tracking outbox 定时封存 active 文件失败 error=Permission denied (os error 13)` 和 `tracking outbox 批量写入 SpacetimeDB 失败`。
|
||||
@@ -1008,8 +1088,8 @@
|
||||
## 拼图生成完成后图片只显示破图或 alt 文案
|
||||
|
||||
- 现象:拼图结果页生成完成后,“画面图”区域出现破图图标和作品名,图片无法正常预览;但打开历史拼图素材时同一张图可能可以正常预览。
|
||||
- 原因:拼图正式图保存为 `/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` 直读代理。
|
||||
- 原因:拼图正式图保存为 `/generated-puzzle-assets/*` 兼容标识,旧 `/generated-*` 直读代理已删除;如果前端没有通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签,或收到无前导斜杠的 `generated-puzzle-assets/*` object key 后未识别为 generated 私有资源,浏览器会直接请求裸路径并失败。生成完成后的结果图还会传入 `refreshKey`,它只能作为 signed URL 缓存版本号,不能给 OSS V4 签名 URL 追加 `_v`;OSS 会把 query 纳入签名,额外参数会让签名失效。
|
||||
- 处理:拼图结果页、发布预览、运行态和历史素材预览都走 `ResolvedAssetImage` 或 `useResolvedAssetReadUrl`;generated 私有资源识别必须同时覆盖 `/generated-*`、`generated-*` 和 `https://*.oss-*.aliyuncs.com/generated-*`;`refreshKey` 变化时重新换签,同一路径同一 `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。
|
||||
- 关联:`src/services/assetReadUrlService.ts`、`src/components/ResolvedAssetImage.tsx`、`docs/technical/PUZZLE_IMAGE_ASSET_PROXY_FIX_2026-04-27.md`。
|
||||
|
||||
@@ -1071,6 +1151,8 @@
|
||||
- 现象:刷新网页后,用户明明有本地 access token,却回到未登录状态。
|
||||
- 原因:`AuthGate` hydrate 曾先强制调用 `refreshStoredAccessToken()`;当 refresh cookie 临时失效、代理错配或后端返回 `401` 时,该方法会先清空本地 access token,随后 `/api/auth/me` 只能恢复成未登录。
|
||||
- 处理:`refreshStoredAccessToken()` 增加 `clearOnFailure` 选项;`AuthGate` 在已有本地 access token 时先用 `/api/auth/me` 确认用户,确认成功后再后台 refresh 续期与写每日登录埋点,后台 refresh 失败不清 token。
|
||||
- 追加处理:`/api/auth/refresh` 只有明确返回 `401` / `403` 时才代表登录态权威失效,可以清本地 access token 并触发全局 auth 变化;服务器重启、Nginx 502/503/504、浏览器 `Failed to fetch` 或 refresh 响应契约异常都属于暂时不可用,不能把已有本地 token 清掉,否则重启窗口会把所有打开页面踢成未登录。
|
||||
- 契约:`/api/auth/refresh` 成功响应按共享契约 `RefreshSessionResponse { token }` 解析;测试 mock 不要额外塞 `{ ok: true, 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"`。
|
||||
- 关联:`src/services/apiClient.ts`、`src/components/auth/AuthGate.tsx`、`docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`。
|
||||
|
||||
@@ -1083,6 +1165,7 @@
|
||||
- 追加处理:generated 私有图片换签 `/api/assets/read-url` 也属于展示层后台请求;推荐页拼图运行态挂载后会立即解析封面图,若换签 401 触发全局鉴权事件,也会表现成“进入拼图作品后瞬间未登录”。资源换签失败只应让当前图片为空,不应清 token、广播 auth 事件或主动 refresh。
|
||||
- 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,`startPuzzleRun`、通关自动 `submitPuzzleLeaderboard`、下一关 `advancePuzzleNextLevel` 和重开同样属于当前玩法局部同步;这些请求失败时只应留在拼图错误态,不应清 token 或广播 auth 事件。
|
||||
- 追加处理:通关后 `refreshSaveArchives()`、首屏 bootstrap 的个人看板/作品架/浏览历史读写也只是平台投影刷新,失败应显示局部错误,不能充当全局登录态判定。
|
||||
- 追加处理:未登录推荐页启动任一公开正式玩法时,`/api/runtime/*` 局内路由必须使用 `RuntimePrincipal`,前端通过 `PlatformEntryFlowShellImpl` 的统一 request options helper 给 start / checkpoint / finish / input / drop / click / restart / time-up / leaderboard / next-level 等动作透传 runtime guest token;公开 runtime detail 读取如跳一跳、敲木鱼必须显式 `skipAuth/skipRefresh`,匿名推荐流不能补读受保护创作详情,否则会在真正开局前打出 `/api/auth/refresh 401`。
|
||||
- 验证:`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`。
|
||||
|
||||
@@ -1235,10 +1318,11 @@
|
||||
|
||||
## Jenkins 生产流水线拉 Git 先本机再域名备用
|
||||
|
||||
- 后续更新:该条仍适用于常规构建 / 发布流水线;`Genarrative-Server-Provision` 已在 2026-06-05 改为服务器初始化专用口径,不允许公网 Git fallback,Job 的 `Pipeline script from SCM` 和 Jenkinsfile 内部 checkout 都必须使用本机路径或目标 agent 可访问的内网 Git 源。
|
||||
- 现象:生产发布、数据库导入导出、服务器配置、构建或 `Genarrative-Full-Build-And-Deploy` 流水线执行 `GitSCM checkout` 时,如果 Jenkins 生成的 fetch 是 `+refs/heads/*:refs/remotes/origin/*`,公网 Git 链路可能在收包阶段以 `git-remote-https died of signal 15`、`curl 56 GnuTLS recv error (-9)`、`early EOF`、`invalid index-pack output` 失败;发布类流水线还可能先遇到 `http://127.0.0.1:3000/GenarrativeAI/Genarrative.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 && genarrative-build` 的 `Genarrative-Full-Build-And-Deploy` 源码解析阶段、`Genarrative-Web-Build` checkout 阶段,以及部署/发布类 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;这些首次 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 Job 的 `Pipeline script from SCM` 由 Windows controller 执行,SCM URL 使用公网域名 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。运行于 `linux && genarrative-build` 的 `Genarrative-Full-Build-And-Deploy` 源码解析阶段、`Genarrative-Web-Build` checkout 阶段,以及部署/发布类 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;这些首次 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 时也先保持 `depth=1` 校验,浅历史无法证明归属时才按 `GENARRATIVE_JENKINS_CHECKOUT_DEEPEN_STEPS` 逐步加深,最后才展开完整历史。发布流水线不得为了缩短 checkout 时间清空上游构建传入的 `COMMIT_HASH`。
|
||||
- 验证:扫描本地 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`;扫描发布流水线确认传给 `scripts/jenkins-checkout-source.sh` 的 `COMMIT_HASH` 未被硬编码为空;运行 `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 可选参数在 set -u 下不能裸读
|
||||
@@ -1257,14 +1341,22 @@
|
||||
- 验证:运行 `git ls-files --stage scripts/prepare-server-provision-tools.sh`,确认 mode 为 `100755`;重新跑 `Genarrative-Server-Provision` 时应进入工具下载/打包日志,而不是停在 `Permission denied`。
|
||||
- 关联:`jenkins/Jenkinsfile.production-server-provision`、`scripts/prepare-server-provision-tools.sh`、`scripts/jenkins-checkout-source.sh`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## Server-Provision 工具准备只在 Linux build 节点做一次
|
||||
## Server-Provision 工具准备只在目标部署 agent 内做一次
|
||||
|
||||
- 现象:`Genarrative-Server-Provision` 在后续目标发布节点重复执行 `scripts/prepare-server-provision-tools.sh`,或日志里出现目标节点继续访问 GitHub release / `install.spacetimedb.com`。
|
||||
- 原因:当前流水线已经改成 Linux build 节点一次性准备 `provision-tools/` 并 stash 给目标发布阶段;如果目标发布阶段又重新准备工具包,就会重复下载并把目标节点暴露到外网依赖。
|
||||
- 处理:只允许 `Prepare Provision Tools` 阶段在 `linux && genarrative-build` 节点生成 `provision-tools/`;后续 `Provision Server` 阶段只 `unstash 'server-provision-tools'` 并安装其中的 `spacetime` 与 `otelcol-contrib`,不要再运行 `prepare-server-provision-tools.sh`。
|
||||
- 验证:Jenkins 日志应先在 Linux build 节点出现 `[prepare-provision-tools] 工具包已准备`,后续目标发布节点只出现安装 / systemd / Nginx provision 日志;目标节点不应出现 `下载 otelcol-contrib:` 或 `下载 SpacetimeDB 官方安装器脚本:`。
|
||||
- 现象:`Genarrative-Server-Provision` 选择 `DEPLOY_TARGET=development` 时如果阶段跑在 `Running on Jenkins` 或 `linux && genarrative-build`,真实 provision 会落到构建机 / controller,而不是 dev 服务器。
|
||||
- 原因:Server-Provision 是服务器初始化流水线,dev / release 都是目标服务器,不应把 development 当成 build 节点预览目标,也不应通过 build 节点 stash 工具包再切回目标机;同时公网 Git fallback 会让目标 agent 内网源不可达时悄悄改从公网拉源码,掩盖服务器路由问题。
|
||||
- 处理:Server-Provision 全程运行在目标部署 agent:development 使用 `linux && genarrative-dev-deploy`,release 使用 `linux && genarrative-release-deploy`。`Prepare Provision Tools` 和 `Provision Server` 在同一个目标 agent workspace 内顺序执行,不再使用 `linux && genarrative-build`,也不再 `stash/unstash` 工具包。Job 的 `Pipeline script from SCM` 与参数 `SOURCE_GIT_REMOTE_URL` 都必须指向本机路径或内网 Git 源,不允许 `https://git.genarrative.world/...` 公网地址。
|
||||
- 验证:Jenkins 日志中 `Provision Target` 下的 `Prepare`、`Checkout Provision Files`、`Prepare Provision Tools` 和 `Provision Server` 都应运行在目标 dev / release agent;日志不应出现 `stash 'server-provision-tools'`、目标阶段 `unstash`、`Git 主地址拉取失败...改用备用地址` 或 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。
|
||||
- 关联:`jenkins/Jenkinsfile.production-server-provision`、`scripts/prepare-server-provision-tools.sh`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## Server-Provision 不要无条件下载工具包
|
||||
|
||||
- 现象:目标 dev / release 机器已经安装正确版本的 SpacetimeDB 或 `otelcol-contrib`,但 `Prepare Provision Tools` 仍每次下载 release tarball,网络慢或 GitHub 不稳时会把服务器初始化卡在准备阶段。
|
||||
- 原因:工具准备阶段如果只按“生成交付包”理解,会忽略它已经运行在目标部署 agent 上这一事实;此时目标机本地的 `/usr/local/bin/otelcol-contrib` 与 `${SPACETIME_ROOT}/bin/current` 就是可信状态源。
|
||||
- 处理:`scripts/prepare-server-provision-tools.sh` 必须先检查目标机状态:`otelcol-contrib --version` 命中 `OTELCOL_VERSION` 时复制现有二进制;`spacetimedb-cli --version` 命中 `SPACETIME_EXPECTED_VERSION` 或 `SPACETIME_DOWNLOAD_ROOT` 推导出的版本且 standalone 同时存在时,复制 `${SPACETIME_ROOT}/bin` 并生成 wrapper。只有缺失、不可执行或版本不匹配时,才查 `PROVISION_DOWNLOADS_DIR` 或下载源。
|
||||
- 验证:运行 `bash scripts/check-server-provision-tools.sh`;Jenkins 日志应先出现“检查目标机 ...”,已有版本命中时出现“复用目标机已有 ...”,且不出现“下载 ...”。
|
||||
- 关联:`scripts/prepare-server-provision-tools.sh`、`jenkins/Jenkinsfile.production-server-provision`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 个人任务 scope 不得扩成 work/site/module
|
||||
|
||||
- 现象:个人任务配置为 `work` / `site` / `module` 后进度串桶或静默按 0 处理。
|
||||
@@ -1292,10 +1384,10 @@
|
||||
## 拼图会过早进入待发布态,结果页可能空图但仍显示可发布
|
||||
|
||||
- 现象:拼图创作有时刚结束就跳到“待发布”结果页,但结果页里的正式图还是空的,发布检查随后又会拦住,用户会感觉“已经完成了却又不能发布”。
|
||||
- 原因:拼图的待发布判定太弱,`build_result_preview` / `validate_publish_requirements` 和 `is_puzzle_session_snapshot_publish_ready` 只检查了作品名、简介、标签、关卡名和 cover 图,没有要求 `level_scene_image_src`、`ui_spritesheet_image_src`、`level_background_image_src` 等完整资产都齐;前端恢复链路里的 `hasRecoverableGeneratedPuzzleDraft` / `normalizeRecoveredPuzzleDraftSession` 也只要有 cover 或候选图就会把草稿当成已完成。
|
||||
- 处理:待修复时要把“待发布”门槛收紧到整套拼图资产包完整,再让恢复逻辑只在完整草稿下抬高为完成态,避免半成品直接进入结果页。
|
||||
- 验证:当某个拼图草稿只补齐首图、但关卡背景或 UI spritesheet 仍缺失时,不应再进入 `ready_to_publish`;结果页也不应把这类草稿误判为已完成。
|
||||
- 关联:`server-rs/crates/module-puzzle/src/application.rs`、`server-rs/crates/api-server/src/puzzle/tags.rs`、`server-rs/crates/api-server/src/puzzle/draft.rs`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`。
|
||||
- 原因:拼图的待发布判定太弱,`build_result_preview` / `validate_publish_requirements` 和 `is_puzzle_session_snapshot_publish_ready` 只检查了作品名、简介、标签、关卡名和 cover 图,没有要求 `level_scene_image_src`、`ui_spritesheet_image_src`、`level_background_image_src` 等完整资产都齐;历史前端恢复链路里的 `hasRecoverableGeneratedPuzzleDraft` / `normalizeRecoveredPuzzleDraftSession` 也只要有 cover 或候选图就会把草稿当成已完成。
|
||||
- 处理:前端恢复链路已收口到 `platformPuzzleDraftRecoveryModel.ts`,只有首图、关卡画面、UI spritesheet 与关卡背景资产包完整时才把恢复草稿抬为完成态;后端 `build_result_preview` / `validate_publish_requirements` / `is_puzzle_session_snapshot_publish_ready` 也已收紧到同一完整资产包门槛。
|
||||
- 验证:当某个拼图草稿只补齐首图、但关卡背景或 UI spritesheet 仍缺失时,前端恢复链路不应把它误判为已完成,后端也不应进入 `ready_to_publish` 或返回 `publishReady=true`。
|
||||
- 关联:`server-rs/crates/module-puzzle/src/application.rs`、`server-rs/crates/api-server/src/puzzle/tags.rs`、`server-rs/crates/api-server/src/puzzle/draft.rs`、`src/components/platform-entry/platformPuzzleDraftRecoveryModel.ts`、`src/components/puzzle-result/PuzzleResultView.tsx`。
|
||||
|
||||
## WebGL 画布在高 DPR 移动端放大溢出
|
||||
|
||||
@@ -1531,7 +1623,7 @@
|
||||
|
||||
## 推荐页嵌入拼图通关结算不要放在运行态内部 absolute 层
|
||||
|
||||
- 现象:推荐页里玩拼图通关后,结算面板只显示上半部分,排行榜、下一关按钮或相似作品卡被截断。
|
||||
- 现象:推荐页里玩拼图通关后,结算面板只显示上半部分,排行榜或下一关按钮被截断。
|
||||
- 原因:推荐页把运行态放在滑动作品卡的视觉区内,`platform-recommend-swipe-page`、`platform-recommend-swipe-card__visual` 和 `platform-recommend-runtime-viewport` 都是 `overflow: hidden`;拼图通关结算如果仍是运行态内部 `absolute inset-0` 弹层,就只能在半屏卡片区域里显示。
|
||||
- 处理:`PuzzleRuntimeShell` 在 `embedded` 模式下把通关结算层通过 portal 挂到 `document.body`,使用 `puzzle-runtime-modal-overlay--fixed` 页面级 fixed 浮层;非嵌入态继续使用运行态内部覆盖层。
|
||||
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "推荐页嵌入拼图通关结算使用页面级浮层避免卡片裁剪"`,确认弹层不再位于 `.platform-recommend-runtime-viewport` 内。
|
||||
@@ -1640,10 +1732,18 @@
|
||||
|
||||
- 现象:拼图生成页已经收到 VectorEngine 图片编辑失败并进入重试态,但用户返回草稿 Tab 后,同一草稿仍显示“生成中”;连续触发多个拼图生成时,失败后还可能只剩一条新增草稿,或者只看到标题为“第1关”的半成品空壳;抓大鹅后台失败时也可能没有任何通知,点击草稿又像重新开始生成。
|
||||
- 原因:前端失败 notice 只更新生成页局部状态,pending 作品架条目在失败时被清掉或被非 `generating` 状态误映射为 `ready`;后端作品摘要也可能短暂仍是 `generationStatus=generating`。如果失败消息没有写入 notice,用户离开生成页后不会弹出 `PlatformErrorDialog`;如果打开草稿只看持久化 `generating`,就会绕过失败态恢复。
|
||||
- 处理:失败时按 session 保留 pending 作品架条目并标记 `failed`,失败 notice 保存错误消息并触发带来源的 `PlatformErrorDialog`;拼图契约没有 `failed` 枚举,pending 拼图映射为 `idle`,同时用本地失败 notice 覆盖持久化生成中状态和旧的“正在生成”摘要。点击失败草稿应优先用 notice / 后端 session / fallback payload 组装失败生成页,不能重新从 0 秒启动新进度;拼图失败半成品没有有效 `workTitle` 时,作品架标题回退为“拼图草稿”。
|
||||
- 处理:失败时按 session 保留 pending 作品架条目并标记 `failed`,失败 notice 保存错误消息并触发带来源的 `PlatformErrorDialog`;拼图契约没有 `failed` 枚举,pending 拼图映射为 `idle`,同时用本地失败 notice 覆盖持久化生成中状态和旧的“正在生成”摘要。点击失败草稿应优先用 notice / 后端 session / fallback payload 组装失败生成页,不能重新从 0 秒启动新进度;失败页点击重新生成必须优先复用当前 `sessionId` 执行编译 action,不得因存在表单缓存 payload 就调用 create-session。拼图失败半成品没有有效 `workTitle` 时,作品架标题回退为“拼图草稿”。
|
||||
- 验证:`node node_modules/vitest/vitest.mjs run src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "failed parallel puzzle|background match3d"`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/custom-world-home/creationWorkShelf.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 生成失败重试不要走新建草稿
|
||||
|
||||
- 现象:拼图或抓大鹅生成失败后,在失败页点击“重新生成”,作品架里多出一份新的草稿,原失败草稿仍留在列表里。
|
||||
- 原因:重试 handler 曾优先读取缓存的表单 payload 并调用 create-session 路径;失败草稿按 session 留在作品架是正确行为,于是重试动作额外创建了第二份草稿。
|
||||
- 处理:只要当前失败页还能恢复到原 `sessionId`,重试就走该 session 的 compile action;只有没有可恢复 session 时,才允许用表单 payload 重新创建草稿。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "failed .* draft retry reuses current session"`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 汪汪声浪草稿试玩不要写正式 run
|
||||
|
||||
- 现象:如果草稿结果页试玩和发布后 runtime 共用同一写成绩路径,未发布或未确认资源的草稿试玩会污染正式单局、排行榜和作品统计。
|
||||
@@ -1691,14 +1791,53 @@
|
||||
- 验证:`npm run typecheck`,并跑 `npm test -- src/routing/appPageRoutes.test.ts` 覆盖 JumpHop 阶段路径。
|
||||
- 关联:`src/components/platform-entry/platformEntryTypes.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 跳一跳地块图集不要套通用系列素材 n 行模型
|
||||
## 跳一跳地块图集固定走 18 个 UV 大单元
|
||||
|
||||
- 现象:跳一跳初始草稿生成时报 `系列素材图集的物品行数不能超过 n。`,或者即使绕过报错也只生成了 atlas 预览路径,地块切片没有真正落盘。
|
||||
- 原因:跳一跳地块只有 6 个固定 tileType,但旧实现把它塞进通用系列素材 helper,并使用 `grid_size = 3` / `item_names = 6` 的语义冲突模型;随后又只保留 atlas 资产与模拟路径,没把六个切片逐一上传并确认到 `JumpHopTileAsset`。
|
||||
- 处理:跳一跳地块改用专用 `2行*3列` 图集 prompt,按 `start / normal / target / finish / bonus / accent` 顺序切 6 张 PNG,并对每张切片各自走 OSS 上传、asset_object 确认和 entity bind。
|
||||
- 验证:`cargo test -p api-server jump_hop_tile_atlas -- --nocapture` 通过后,再看 `jump_hop.rs` 不应再调用 `build_generated_asset_sheet_prompt` 处理地块图集;公开结果里应能拿到 6 个独立 `JumpHopTileAsset`。
|
||||
- 现象:跳一跳初始草稿生成时报 `系列素材图集的物品行数不能超过 n。`,或者生成完成后只有 atlas 预览路径,地块切片没有真正落盘。
|
||||
- 原因:旧模板先后尝试过通用系列素材 helper、`2x3` 六格固定 tileType 和 `5x5` 单贴图池,但当前跳一跳已经重设计为“主题 -> 一张 `1024x1536` 图集 -> 18 个 `3列*6行` UV 大单元 -> 每格 `4列*3行` 六面贴图 -> 无限路径”,旧的物品行数 / 固定类型模型都会把创作链路带偏。
|
||||
- 处理:跳一跳地块固定只生成一张 `1024x1536` 主题 UV 展开图集,后端先切出 18 个大单元,再从每格固定 UV 网切出 top/front/right/back/left/bottom 六张 `256x256` 不透明 PNG,并对 108 张面贴图各自走 OSS 上传、asset_object 确认和 entity bind;不要再恢复 `2行*3列`、`5x5` 单贴图、`start / normal / target / finish / bonus / accent` 六格口径。
|
||||
- 验证:`jump_hop.rs` 不应再调用通用物品行数模型处理地块图集;公开结果里应能拿到 18 个独立 `JumpHopTileAsset` 且每个新资产包含 `faceAssets` 六面贴图,运行态无限路径从地块池随机取材;旧资产没有 `faceAssets` 时仍能用 `imageSrc` 单贴图 fallback。
|
||||
- 关联:`server-rs/crates/api-server/src/jump_hop.rs`、`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 跳一跳宝可梦主题地块图集 safety rejection 只做专项改写
|
||||
|
||||
- 现象:跳一跳草稿使用“宝可梦 / Pokemon / 皮卡丘 / 精灵球”等主题时,背景底图和返回按钮可能已生成成功,但地块图集的 VectorEngine 请求返回 `Your request was rejected by the safety system`,日志里 `failure_context="跳一跳地块图集生成失败"`、`status=429`、`code="invalid_prompt"`。
|
||||
- 原因:18 个立方体主题物体 UV 展开图集 prompt 会把这些词放进“主题物体图集”语境,容易被上游理解为要求生成具体宝可梦角色或标志道具,触发安全拦截;这不是普通平台造型词、抠图或超时问题。
|
||||
- 处理:仅在跳一跳图片生成 prompt 文本命中宝可梦相关词时做生成侧替换,把 `宝可梦 / 神奇宝贝 / 口袋妖怪 / Pokemon` 改为“原创幻想萌宠冒险道具”,把 `精灵球` 改为“彩色冒险能量球”,把 `皮卡丘 / Pikachu` 改为“黄色闪电萌宠符号”;不要把所有主题都加全局 IP 禁止约束,用户草稿标题和主题展示也不改。
|
||||
- 验证:`cargo test -p api-server jump_hop --manifest-path server-rs/Cargo.toml` 应覆盖宝可梦词专项替换;真实联调时同一草稿重试后,地块图集请求的 prompt 不再包含宝可梦相关词。
|
||||
- 关联:`server-rs/crates/api-server/src/jump_hop.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 跳一跳地块切片不要按 tileType 复用资产槽位
|
||||
|
||||
- 现象:跳一跳生成完成后,运行态看起来仍像在显示默认几何地块,或者地块图片在加载时频闪;结果页地块池也可能只看到少量重复素材。
|
||||
- 原因:`tileType` 只是路径平台的玩法类型标签,18 个 atlas 大单元里会重复出现 `normal / target / bonus / accent` 等类型。若后端持久化时用 `tileType` 生成 slot/path,同类型切片会写入同一个 `/generated-jump-hop-assets/<profile>/<slot>/image.png`,后上传的切片覆盖先上传的切片,前端换签缓存也会读到重复或旧对象。
|
||||
- 处理:后端切图后必须按 atlas 单元格写入 `tile-01` 到 `tile-18` 的唯一 tile slot,并把六面贴图写入 `tile-XX-top/front/right/back/left/bottom` 唯一 face slot;前端结果页和运行态展示生成图时用 `assetObjectId` 作为 `refreshKey`,避免重生成后复用旧签名或旧图片缓存。
|
||||
- 验证:`cargo test -p api-server jump_hop --manifest-path server-rs/Cargo.toml -- --nocapture` 应包含 `jump_hop_tile_asset_slots_are_unique_for_eighteen_slices`;前端运行态测试应断言地块换签带 `assetObjectId` 刷新键,并覆盖新 UV 资产会解析六张面贴图。
|
||||
- 关联:`server-rs/crates/api-server/src/jump_hop.rs`、`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、`src/components/jump-hop-result/JumpHopResultView.tsx`。
|
||||
|
||||
## 跳一跳落点辅助标识不要再用舞台高度常量拍脑袋投影
|
||||
|
||||
- 现象:拖拽时落点辅助标识虽然会动,但看起来像静态点位漂移,和真实可落地的位置对不上。
|
||||
- 原因:辅助标识如果只按 `stageSize.height` 和一个固定比例估算投影距离,再去跟拖拽向量合成,就会和当前地块到目标地块的真实屏幕跨度脱节;三维场景层级过高时还会把辅助点直接盖住。
|
||||
- 处理:辅助标识必须使用当前地块与目标地块之间的真实屏幕距离和后端 `chargeToDistanceRatio` 做投影,再映射到屏幕坐标;同时把辅助层 z-index 放到三维角色层之上,避免被场景层遮挡。
|
||||
- 验证:拖拽半程时辅助点应落在当前地块和目标地块之间,完整拖拽时应逼近目标地块中心;运行态截图里辅助点必须始终压在地块与角色之上。
|
||||
- 关联:`src/services/jump-hop/jumpHopRuntimeModel.ts`、`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`。
|
||||
|
||||
## 跳一跳长按蓄力不能再消费拖拽方向
|
||||
|
||||
- 现象:跳一跳改成长按蓄力后,如果前端或后端仍消费 `dragVectorX/dragVectorY`,玩家手指轻微移动就会改变跳跃方向,和“始终朝下一块中心跳”的体验不一致。
|
||||
- 原因:历史弹弓拖拽版本把屏幕拖拽方向作为正式裁决输入,契约字段仍为兼容旧客户端保留,容易被误认为仍是当前玩法规则。
|
||||
- 处理:前端运行态只用长按时长提交 `dragDistance` 兼容字段,不再发送方向字段;落点预测按当前地块中心到下一块地块中心的方向投影。后端 `module-jump-hop` 即使收到旧客户端 `dragVectorX/dragVectorY` 也必须忽略,只按当前地块到下一块地块中心的单位向量裁决。
|
||||
- 验证:前端回归测试覆盖手指移动不改变提交方向、预测落点忽略旧方向字段;后端领域测试覆盖旧客户端传错误方向时仍按下一块中心命中。
|
||||
- 关联:`src/services/jump-hop/jumpHopRuntimeModel.ts`、`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、`server-rs/crates/module-jump-hop/src/application.rs`。
|
||||
|
||||
## 跳一跳创作入口旧文案先查 SpacetimeDB 配置
|
||||
|
||||
- 现象:`JumpHopWorkspace` 已只剩主题输入,但创作 Tab 的跳一跳模板卡仍显示旧的“俯视角跳跃闯关”或拼图参考图。
|
||||
- 原因:创作入口卡片事实源是 SpacetimeDB `creation_entry_type_config` 和 `/api/creation-entry/config`,前端只做展示派生;如果只改工作台、PRD 或前端组件,已有库里的旧入口行不会自动变化。当前 `api-server` 读取入口配置时优先订阅缓存,缓存命中后不会再走 procedure 播种,所以只把迁移写在 `get_creation_entry_config` 里不够。
|
||||
- 处理:同步更新 `module-runtime` 默认入口种子,并在 `spacetime-module/src/runtime/creation_entry_config.rs` 加只命中旧系统默认值的迁移;同时在 `spacetime-client` 的入口配置读模型里做同一条旧系统默认行的读路径纠偏。跳一跳当前默认值为 `subtitle=主题驱动平台跳跃`、`image_src=/creation-type-references/jump-hop.webp`。
|
||||
- 验证:本地 `GET /api/creation-entry/config` 的 `jump-hop` 项应返回新 subtitle 和新 imageSrc;若仍旧,检查本地 SpacetimeDB 是否已发布当前 `spacetime-module`,以及后台是否手动覆盖过入口配置。若缓存路径和 procedure 路径返回不一致,优先怀疑读模型映射没做纠偏,而不是前端展示层。
|
||||
|
||||
## image2 dry-run 带参考图时不要直接打印 data URL
|
||||
|
||||
- 现象:使用 VectorEngine `gpt-image-2-all` 生成带参考图的概念图时,如果 dry-run 直接打印完整请求体,参考图会被转成超长 `data:image/png;base64,...`,终端日志会被数百万字符淹没。
|
||||
@@ -1777,6 +1916,26 @@
|
||||
- 验证:定向测试 `cargo test -p api-server generated_asset_sheet_two_items_per_row --manifest-path server-rs/Cargo.toml -- --nocapture` 应通过,且错位透明样本应按连通域切出完整视图。
|
||||
- 关联:`server-rs/crates/api-server/src/generated_asset_sheets.rs`、`server-rs/crates/api-server/src/match3d/item_assets.rs`。
|
||||
|
||||
## 腾讯云 release 上 VectorEngine `SendRequest` 超时先查出口链路与重试
|
||||
|
||||
- 现象:release 机器调用 VectorEngine `gpt-image-2` 的 `/v1/images/generations` 或 `/v1/images/edits` 偶发 `client error (SendRequest) -> connection error -> Connection timed out (os error 110)`,应用层表现为 504;本地通常正常。
|
||||
- 原因:本地 DNS 可能走代理 / 加速出口,而腾讯云 release 直接解析到 VectorEngine 真实边缘节点。实测同一张约 2.37MB PNG、同一 edits 请求,`curl` 5/5 成功,但 `reqwest/hyper` 会间歇性超时;固定 `40.160.33.47` 也只能改善,不能根治。
|
||||
- 处理:不要优先关闭 multipart,也不要直接把 `SendRequest` 解释成上游业务拒绝。VectorEngine 图片 `generations` / `edits` 上游 POST 单独使用 `libcurl`;参考图下载和响应图片 URL 下载仍用 `reqwest`。send 阶段 timeout / connect error 在 `platform-image` 内最多重试 5 次,使用指数退避和短抖动;日志字段 `attempt`、`max_attempts`、`retry_delay_ms`、`reference_image_bytes_total`、`request_params` 是定位依据。
|
||||
|
||||
### api-server libcurl / OpenSSL 3.2 runtime
|
||||
|
||||
- 症状:release 部署新 `api-server` 后服务反复 `exit-code`,`LD_TRACE_LOADED_OBJECTS=1 /opt/genarrative/current/api-server` 或 `ldd` 报 `/lib/x86_64-linux-gnu/libssl.so.3: version 'OPENSSL_3.2.0' not found`。
|
||||
- 根因:`platform-image` 使用 `libcurl` 后,Linux release 构建产物可能直接要求 `OPENSSL_3.2.0` 符号;Ubuntu 24.04 apt 默认 OpenSSL 仍是 `3.0.13`,不能满足该符号版本。
|
||||
- 处理:`Genarrative-Server-Provision` 独立安装 OpenSSL `3.2.0` 到 `/opt/genarrative/openssl-3.2.0`,并只通过 `genarrative-api.service` 的 `LD_LIBRARY_PATH=/opt/genarrative/openssl-3.2.0/lib64:/opt/genarrative/openssl-3.2.0/lib` 给 api-server 使用,避免替换系统 OpenSSL。
|
||||
|
||||
### VectorEngine edits multipart image part
|
||||
|
||||
- 症状:拼图参考图链路请求 `/v1/images/edits` 返回 `500 image is required`,但应用日志里 `reference_image_count=1`、`reference_image_bytes_total>0`,`request_params.referenceImages[0]` 也有 `field=image`、文件名、MIME 和 bytes。
|
||||
- 根因:Rust `curl::easy::Form` 中 `contents(...).filename(...)` 不等价于文件上传 part;VectorEngine 转码层会认为没有收到图片。release 上用 curl CLI `-F image=@file` 可成功,证明字段名和上游接口本身没变。
|
||||
- 处理:multipart 参考图必须用 `Form::buffer(file_name, bytes)` 并设置 `content_type(...)`,让 libcurl 生成真正的 `name="image"; filename="..."` 文件 part。
|
||||
- 验证:release 上先看 `journalctl -u genarrative-api.service` 中 `VectorEngine 图片请求发送失败,准备重试` 与最终 `HTTP 返回`;若仍失败,再用同一图片分别跑 curl 与最小 reqwest 探针对照。
|
||||
- 关联:`server-rs/crates/platform-image/src/vector_engine/client.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 个人中心不再保留直达“存档”按钮入口
|
||||
|
||||
- 现象:2026-05-25 起,移动端“我的”页顶部改为品牌行 + 扫码 / 设置按钮,设置区和次级入口不再提供独立的 `存档` 按钮;用户仍可在“玩过”弹窗里查看可继续存档。
|
||||
@@ -1785,6 +1944,22 @@
|
||||
- 验证:`npm test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile profile page matches the reference layout sections|profile scan action opens camera scanner instead of recharge panel"`。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 旧创作入口先确认是不是旧 worktree 在响应
|
||||
|
||||
- 现象:浏览器里明明还看到跳一跳旧入口,比如 `俯视角跳跃闯关` 和 `puzzle.webp`,但当前 worktree 里已经改成了 `主题驱动平台跳跃` 和 `jump-hop.webp`。
|
||||
- 原因:本机常同时存在两个开发栈,旧 worktree 可能还在占用 `3000/8082/3101/3102`,而当前 worktree 可能跑在另一组端口。只看页面文案就下结论,容易把旧进程误认成当前改动没生效。
|
||||
- 处理:先用 `Get-NetTCPConnection` / `Get-CimInstance Win32_Process` 确认端口对应的可执行文件和命令行,再分别请求 `/api/creation-entry/config` 比对旧端口与当前 worktree 端口。必要时以当前 worktree 的实际端口为准重新打开页面。
|
||||
- 验证:旧端口返回旧跳一跳入口,当前 worktree 端口返回新跳一跳入口;两边的 `api-server` / `vite-cli` 命令行应指向不同仓库路径。
|
||||
- 关联:`scripts/dev.mjs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 3001 无法访问先查旧 worktree 占端口和 SpacetimeDB 版本
|
||||
|
||||
- 现象:`http://127.0.0.1:3001/` 打不开,但 `3000 / 3101 / 8082` 仍有进程;`npm run dev` 直接退出,没有把新栈拉起来。
|
||||
- 原因:旧 worktree 的 `api-server`、`spacetime-standalone` 和 Vite 还活着,或者当前 worktree 的本机 SpacetimeDB CLI 默认版本低于仓库锁定版本,`scripts/dev.mjs` 会先校验版本再启动并直接报错退出。
|
||||
- 处理:先停掉占用端口的旧进程,再执行 `spacetime version list` 和 `spacetime version use 2.4.1`,确认本机 CLI/standalone 与仓库一致后重新启动 `npm run dev -- --no-interactive --web-port 3001 --api-port 8083 --spacetime-port 3103 --admin-web-port 3104`。
|
||||
- 验证:`http://127.0.0.1:3001/`、`http://127.0.0.1:8083/healthz`、`http://127.0.0.1:3103/v1/ping` 都返回 200,且进程命令行指向当前 worktree 路径而不是别的仓库。
|
||||
- 关联:`scripts/dev.mjs`、`.hermes/shared-memory/pitfalls.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 微信历史孤儿作品不要让新注册账号顶替
|
||||
|
||||
- 现象:清空用户数据或迁移历史数据后,旧作品的 `owner_user_id` 为空或失效,新注册用户会因为顺序号复用或旧 ID 残留顶替作品归属,导致刚注册就看到别人的草稿或已发布作品。
|
||||
@@ -1935,3 +2110,115 @@
|
||||
- 处理:开局和补牌后的重排必须先排除现成消除,再用真实交换 / 落位模拟判断是否会产生新消除;`1x2` 永远不进入半锁定组,半锁定只允许 `1x3`、`2x2`、`2x3`。
|
||||
- 验证:`npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx` 与 `cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml -- --nocapture` 通过后,开局盘面不应直接出现 completed group。
|
||||
- 关联:`src/services/puzzle-clear/puzzleClearLocalRuntime.ts`、`server-rs/crates/module-puzzle-clear/src/application.rs`。
|
||||
## 推荐页作品 key 漏玩法会导致运行内容和标题作者错位
|
||||
|
||||
- 现象:移动端推荐页进入跳一跳或敲木鱼等作品时,游戏运行内容已经切到当前作品,但下方标题、作者和头像仍显示第一条拼图或其它推荐作品。
|
||||
- 原因:平台壳层用 `getPlatformPublicGalleryEntryKey(...)` 写入 `activeRecommendEntryKey`,而 `RpgEntryHomeView` 内部的 `buildPublicGalleryCardKey(...)` 漏掉新玩法 `sourceType` 分支,导致当前 key 查不到条目后回退到推荐列表第一条。
|
||||
- 处理:推荐页和平台壳层的公开作品 key 规则必须复用 `buildPlatformPublicGalleryCardKey(...)`,覆盖同一批 `sourceType`,至少包括 `big-fish`、`puzzle`、`jump-hop`、`wooden-fish`、`match3d`、`square-hole`、`visual-novel`、`bark-battle` 和 `edutainment:<templateId>`;新增玩法公开推荐流时先补这个共享 helper。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile recommend meta matches active"` 应覆盖跳一跳和敲木鱼的当前运行内容、标题和作者一致。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 跳一跳飞行动画不要直接用最新 run 重绘地块窗口
|
||||
|
||||
- 现象:跳一跳松手后如果后端很快返回下一帧 run,地块窗口会立刻前移,角色翻腾动画看起来像没播放;若同时刷新图片资产,还可能被误认为地块频闪。
|
||||
- 原因:后端 run 是规则真相,前端 runtime 又需要低延迟表现。如果 DOM 平台层直接用最新 `run.currentPlatformIndex` 渲染,后端回包会抢在动画前完成视觉切换。
|
||||
- 处理:前端保留独立 `displayRun`,松手后先进入 `isJumpAnimating=true`,角色在当前显示窗口内飞向前端预测真实落点;视觉预测必须用当前显示窗口的 current/next 地块作为方向来源,不能拿已经提前返回的后端新 run 目标配旧窗口角色,否则下一跳会朝实际目标反方向飞。飞行动画完成后再把 `displayRun` 切到最新后端 run,并进入约 `1440ms` 的 `platformAdvancing` 表现态。成功后的角色显示必须使用 `lastJump.landedX/landedY` 映射出的真实偏移,不要吸附到目标地块中心。推进期间地块 DOM 层和 DOM 角色层必须统一包在同一个 camera layer 下移动,旧当前地块先跟随相机偏移离开主视野,之后只保留在屏幕后方;不要给旧地块加独立向上 / 向下飞走 keyframes,也不要因为旧地块还在保留列表里阻塞下一跳。玩家继续向前跳时,已完成旧地块继续被新的相机推进自然带离屏幕,超过离屏阈值后销毁。相机层必须同时设置 `--jump-hop-camera-shift-x` 与 `--jump-hop-camera-shift-y`,并以旧窗口真实落点和新窗口真实落点为锚点,避免先横向瞬切居中再纵向推进;运行态相机层当前为约 `1.3x` 近距缩放。地块保留当前 / 目标 / 预览的深度尺寸差异,但深度差异必须用固定宽高 + CSS transform scale 缓动实现,不能直接改宽高瞬切;当前态不要额外叠 CSS scale。相机推进期间角色自身也不能保留 `left/top` transition,否则 `displayRun` 切换造成的角色局部坐标变更会和父级 camera layer 位移叠加,视觉上像落地后又从屏幕外飞回;角色推进期只允许 transform / opacity transition。正式胜负、成功跳跃次数、时长和排行榜仍以后端 run 为准,前端只延迟显示态。
|
||||
- 验证:`npm test -- src/services/jump-hop/jumpHopRuntimeModel.test.ts src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx` 应覆盖动画期间平台仍停在旧窗口,成功落地保留真实落点偏移,动画结束后进入 `data-platform-advancing=true`,DOM 角色层与地块层同在 `jump-hop-camera-layer` 内,通过 `--jump-hop-camera-shift-x` 和 `--jump-hop-camera-shift-y` 完成相机斜向推进,并校验可见地块按深度保留不同视觉尺寸、运行态平台宽高使用固定基准值、推进态 transform transition 为 `1440ms`、推进态角色 transition 不包含 `left/top`、旧地块没有独立 `jump-hop-platform-exit-drift` keyframes 且下一跳不会被旧地块保留态阻塞。
|
||||
- 关联:`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、`src/services/jump-hop/jumpHopRuntimeModel.ts`、`server-rs/crates/module-jump-hop/src/application.rs`。
|
||||
|
||||
## 跳一跳相机推进不要让地块图片回退到原型方块
|
||||
|
||||
- 现象:角色落到下一块后,相机推进时旧地块图片突然消失,或新预览地块先露出浅色原型方块,随后真实 image2 切片才出现。
|
||||
- 原因:旧地块进入 exiting 状态时如果 React key 从 `platformId` 变成 `platformId-exiting`,图片组件会重新挂载并丢失已加载状态;同时 `JumpHopTileImage` 曾在真实图片 URL 已存在但 `onLoad` 尚未触发时显示 fallback 原型地块。Three.js 平台层接入后,如果隐藏预加载只让浏览器缓存 `<img>`,但没有把未来 `platformId` 的纹理 URL 写入 `platformTextureUrlsByRenderKey`,相机推进时新预览地块会短暂缺 Three 贴图;若旧 blob 贴图在空 URL 回调时先被 revoke,再继续保留在 state 中,也会留下一个看似 ready、实际已失效的贴图地址。
|
||||
- 处理:exiting 地块继续使用稳定 `platformId` key,让旧图片组件在推进期复用;有真实 `resolvedUrl` 且未错误时直接保留真实 `<img>`,只在无 URL 或加载失败时显示 fallback;当前 3 块之外的后续地块通过隐藏预加载图片提前解析签名 URL 和浏览器缓存,并同步按未来 `platformId` 发布 Three 纹理 URL。Three 平台层在当前 render items 全部有贴图 URL 后继续承接包含 exiting 地块在内的 3D 渲染;退出地块只随相机推进自然离屏,不播放独立飞走动画,避免退出期露出被放大的平面贴图或重复飞多次;贴图 URL 替换必须等新 URL 到达后再释放旧 parent-owned blob,空 URL 回调不得清空或 revoke 仍在活跃 / 预加载 key 上的旧贴图。
|
||||
- 验证:`npm run test -- src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/services/jump-hop/jumpHopRuntimeModel.test.ts` 应覆盖真实 tile URL 不露出 `.jump-hop-runtime__fallback-tile`,并存在 `jump-hop-tile-preload-image`。
|
||||
- 关联:`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、`src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx`。
|
||||
|
||||
## 跳一跳 Three.js 平台层不能左右镜像 DOM 坐标
|
||||
|
||||
- 现象:视觉上下一块地块在角色右侧,但蓄力引导和角色飞行动画朝左侧;后端回包后地块窗口又闪现摆回正确位置,像是先按反方向飞、再由快照刷新纠正。
|
||||
- 原因:Three.js 平台层如果把相机 `up` 设置成反向,或在 Three 容器上做左右镜像,会让 WebGL 地块的屏幕 X 轴和 DOM 角色 / 落点预测的屏幕 X 轴相反。规则层仍沿当前地块中心到下一块中心裁决,所以后端快照会把状态纠正回来,表现为跳后刷新。
|
||||
- 处理:Three 相机保持 `up=(0, 1, 0)`,再用内部投影公式抵消 45° 下压导致的 Y 轴压缩;不要通过反向 `camera.up` 解决上下方向。DOM 角色、蓄力引导、落点预测和 Three 平台层必须共用同向屏幕坐标。
|
||||
- 验证:`npm run test -- src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/services/jump-hop/jumpHopRuntimeModel.test.ts` 应覆盖 `JUMP_HOP_THREE_CAMERA_UP_Y=1`,并断言 Three 投影与 DOM 屏幕坐标同向。
|
||||
- 关联:`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、`src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx`。
|
||||
|
||||
## 跳一跳立方体贴图不要走透明主体切片
|
||||
|
||||
- 现象:水果等主题生成成功后,运行态地块看起来像薄的纯水果 PNG、果切贴纸、透明 cutout;或者反过来六个面都是同一张平铺果皮 / 果肉材质,无法组合成方块苹果 / 方块香蕉这类完整主题对象表达。
|
||||
- 原因:跳一跳地板已经改为 Three.js 标准 `1x1x1` 等比极小倒角立方体复用几何体,运行态视角固定为近距相机和 45° 下压视角;image2 应生成 `1024x1536` 的 18 个 cube object UV unwrap,每个大单元内的 top/front/right/back/left/bottom 六面要共同包装同一个主题物体。只强调 full-bleed 容易让水果主题退化成果皮、果肉、叶脉等表面纹理;如果仍把一张图贴给六个面,模型也不需要理解正反和跨面连续特征。旧切图链路若把洋红 key 转 alpha、裁边、只保留最大 alpha 连通主体并补透明安全边,会把整格贴图重新抠成苹果 / 香蕉 / 果切等居中主体,贴到立方体上后四角和侧面都变透明。
|
||||
- 处理:跳一跳地板图集 prompt 固定要求 `cube object UV unwrap atlas / 立方体主题物体六面展开图集`,一张图只生成 18 个大单元,每个大单元固定 `4列*3行` UV 网:第 1 行第 2 列 top,第 2 行 left/front/right/back,第 3 行第 2 列 bottom;水果主题要明确生成能一眼说出名称的方块苹果、方块香蕉、方块橙子、方块西瓜等可识别对象,并要求果柄叶片、剥皮条带、放射切面、红瓤黑籽等身份特征跨面连续。禁止自然圆形水果、自然长条香蕉、非方块化完整水果、果切小贴纸、居中小物体、透明背景和留白,同时也禁止“单纯平铺材质 / 抽象纹理 / 只铺主题颜色 / 纯果皮材质 / 纯果肉纹理 / 纯叶脉纹理”。后端按 3x6 大单元和 4x3 UV 网切出 108 张 `256x256` 不透明面贴图,不再调用透明化、最大 alpha 连通主体保留或透明补边。洋红 `#FF00FF` 只作为图集安全缝 / UV 空位 / 外圈 key 色,裁切后若仍有极少残留则转成不透明材质底色;绿色、白色、雪地、云朵、草地、花朵、果肉粉色和浅黄色等主题颜色必须完整保留。
|
||||
- 验证:`cargo test -p api-server jump_hop --manifest-path server-rs/Cargo.toml -- --nocapture` 覆盖跳一跳 UV unwrap prompt、18 个大单元、108 张不透明面贴图、绿色 / 白色材质不被透明化、洋红 key 残留不作为透明洞;前端 `JumpHopRuntimeShell` 测试覆盖新 UV 资产会解析六张面贴图,旧单贴图资产仍可 fallback。
|
||||
- 关联:`server-rs/crates/platform-image/src/generated_asset_sheets/alpha.rs`、`server-rs/crates/platform-image/src/generated_asset_sheets/sheet.rs`、`server-rs/crates/api-server/src/jump_hop.rs`。
|
||||
|
||||
## 含中文 image2 live 验证不要用 PowerShell 管道喂 Node 源码
|
||||
|
||||
- 现象:本地用 `@'...'@ | node -` 跑 VectorEngine / gpt-image-2 live 验证时,`request.json` 里的中文 prompt 可能全部变成 `????`,生成图会变成完全不相关的 UI、建筑海报或其它随机内容,容易误判为模型不服从提示词。
|
||||
- 原因:Windows PowerShell 管道到 Node stdin 时可能按本机非 UTF-8 编码传输脚本文本,JS 源码里的中文字符串在进入 Node 前已经损坏;Rust 后端真实请求不会走这条编码路径。
|
||||
- 处理:含中文提示词的 live 验证优先写成 UTF-8 `.mjs` 文件再执行,或使用能确认 UTF-8 的运行入口;执行后先检查本次 `request.json` 是否保留真实中文,再判断生图质量。不要基于 `????` prompt 生成的图片调整项目提示词。
|
||||
- 验证:生成前后检查 `request.json`,其中 `prompt` 字段应显示中文而不是问号;同一提示词在 UTF-8 文件脚本下应能得到符合主题的图。
|
||||
- 关联:`.codex/skills/gpt-image-2-apimart/SKILL.md`、`server-rs/crates/api-server/src/jump_hop.rs`。
|
||||
|
||||
## 自动试玩退出不要回到生成页
|
||||
|
||||
- 现象:拼图草稿生成完成后自动进入试玩,用户从试玩退出或使用系统返回时落回生成进度页,页面还暴露“重新生成”按钮。
|
||||
- 原因:自动试玩前如果没有先把 `/creation/puzzle/result` 写成 `/runtime/puzzle` 的浏览器历史前一站,系统返回会命中旧的生成页历史项;仅靠运行态内部 `returnStage='puzzle-result'` 只能覆盖运行态按钮返回,不能覆盖浏览器 / WebView 系统返回。
|
||||
- 处理:所有“生成完成后自动进入草稿试玩”的分支在 `openPuzzleRuntimeStage(...)` 前都必须调用结果页历史写入 helper,把 `/creation/puzzle/result` 与当前 `sessionId/profileId/workId` 写入历史;运行态按钮返回到 `puzzle-result` 时也同步写回创作恢复 query。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial and runtime back opens draft result"`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 推荐页 ready 不能只等主图或首次 DOM 图片
|
||||
|
||||
- 现象:移动端推荐页卡面遮罩在作品主图加载后就渐隐,但游戏内 UI 图集、背景、道具图或换签中的 generated 图片还没有准备好,用户会看到运行态半成品或资源闪入。
|
||||
- 原因:推荐页 ready probe 如果只扫描首次挂载时已有的 `<img>`,就会漏掉 React effect、`/api/assets/read-url` 换签、spritesheet 解析或后续 state 更新才新增的资源。
|
||||
- 处理:推荐页 runtime 遮罩必须持续观察运行态 DOM 内新增图片、内联 `background-image` 和 `data-runtime-resource-pending` 隐藏标记;各玩法对换签中、解析中的资源源头要暴露 pending 标记,失败后释放标记并交给玩法兜底,避免遮罩永久卡住。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile recommend cover waits for async runtime resources beyond the main image|mobile recommend cover waits until runtime images are ready"`。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/common/RuntimeResourcePendingMarker.tsx`、`src/components/ResolvedAssetImage.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 拼图文字直创的 compile 回包不等于生成完成
|
||||
|
||||
- 现象:只输入文字点击生成拼图时,页面刚进入生成页就弹出“生成任务已完成,可以继续查看草稿。”,随后又提示“请先选择一张正式拼图图片。”,结果页关卡里也没有图。
|
||||
- 原因:统一创作表单路径把 `compile_puzzle_draft` 的同步回包无条件当成 ready;但后端在 AI 重绘路径会先返回 `stage=image_refining`、`progressPercent=88` 的会话,只表示首关草稿已编译且后台首图 / UI 资产任务已启动,还没有正式封面或候选图。
|
||||
- 处理:前端必须继续用 `isPuzzleCompileActionReady(...)` 判断回包 session;没有 `draft.coverImageSrc`、首关 `coverImageSrc` 或候选图时保持生成中,不弹完成、不把作品架 pending 标 ready、不自动试玩。生成页轮询合并 session 进度时,未进入编译态或进度无变化就返回原 state,避免轮询制造重复 render。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle text-only form stays generating|puzzle draft generation auto starts trial|running puzzle draft opens generation progress"`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## CreativeImageInputPanel 主图点击默认预览
|
||||
|
||||
- 现象:复用 `CreativeImageInputPanel` 的结果页 / 编辑页已有主图时,用户点击图片却触发上传,无法直接查看大图;不同玩法若各自手写上传按钮会让主图、历史图、AI 重绘和参考图行为再次分叉。
|
||||
- 原因:旧主图卡整卡是上传 label,缺少主图预览模式和上传 / 历史入口的显式控制参数。
|
||||
- 处理:通用面板已有主图时默认点击主图打开全屏预览,上传 / 更换收口到右下角 `ImagePlus` 图标按钮;无图时仍允许点击空图卡上传。调用方用 `canUploadMainImage` 和 `canUseImageHistory` 分别控制上传与历史按钮,不要复制面板或用样式遮挡按钮。
|
||||
- 验证:`npm run test -- src/components/common/CreativeImageInputPanel.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`。
|
||||
- 关联:`src/components/common/CreativeImageInputPanel.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 统一创作页短表单软键盘打开不要露出黑底
|
||||
|
||||
- 现象:小程序 / H5 移动端点击拼图或敲木鱼创作输入框后,输入框和键盘之间出现一大片黑色区域;H5 还会明显弹一下。跳一跳因为按钮区用 `mt-auto` 撑开页面,看起来没有同样问题。
|
||||
- 原因:旧移动键盘处理会用 `--platform-keyboard-focus-offset` 把 `.platform-viewport-shell` 整体上移;但 H5 浏览器和小程序 `web-view` 已会自行处理输入框可见性,二次整体上移会造成页面弹跳并露出 `body` 或原生 `page` 的黑色宿主底色。统一创作短表单若内容区按短内容收缩,也会放大这个黑底暴露。
|
||||
- 处理:`UnifiedCreationPage` 根容器必须保留 `bg-[image:var(--platform-body-fill)]` 和 `overscroll-contain`,内容区必须用 `flex-1 min-h-0` 占满统一页剩余高度;移动端键盘打开时只记录 `data-mobile-keyboard-open`、隐藏底部 dock、设置键盘 inset 和浅色 `--platform-keyboard-exposed-fill`,不要再对 `.platform-viewport-shell` 做全局 `transform`;小程序 `pages/web-view` 的 `page` 和 web-view class 也要用浅色背景。不要只给某个玩法工作台单独加高度补丁。
|
||||
- 验证:`npm run test -- src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedCreationWorkspace.test.tsx src/mobileViewportKeyboardFocus.test.ts src/index.test.ts miniprogram/pages/web-view/index.style.test.js`;移动端点击拼图、敲木鱼、跳一跳输入框时,页面不应整体弹起,键盘上方应持续显示平台浅色背景。
|
||||
- 关联:`src/components/unified-creation/UnifiedCreationPage.tsx`、`src/mobileViewportKeyboardFocus.ts`、`src/index.css`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 小程序订阅消息授权不要依赖 web-view bindmessage
|
||||
|
||||
- 现象:拼图点击生成后,H5 以为已经请求了生成结果订阅授权,但小程序没有弹出 `wx.requestSubscribeMessage` 授权框。
|
||||
- 原因:`web-view bindmessage` / `wx.miniProgram.postMessage` 不适合承接“当前用户点击后立刻请求授权”的时序,消息可能等到 web-view 后退、分享或销毁时才派发,导致授权请求没有发生在 `compile_puzzle_draft` 前。
|
||||
- 处理:不要在原生页 `onLoad` 自动触发 `wx.requestSubscribeMessage`,真机会闪页返回且不弹授权框。H5 在 `compile_puzzle_draft` 前应先进入生成进度态并立即发起生成 action,再通过微信 JS SDK `miniProgram.navigateTo` 非阻塞跳转到小程序原生订阅页尝试请求授权;用户接受、拒绝或返回都不能阻塞生成。原生页不要改写上一页 `webViewUrl`,否则 web-view 可能重新加载首页并丢失进度页状态。后端发送订阅消息仍只允许在拼图资产成功或失败终态后执行。
|
||||
- 验证:`npm run test -- src/services/wechatMiniProgramSubscribe.test.ts miniprogram/pages/subscribe-message/index.test.js`。
|
||||
- 关联:`src/services/wechatMiniProgramSubscribe.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`miniprogram/pages/subscribe-message/index.shared.js`、`miniprogram/pages/web-view/index.js`。
|
||||
|
||||
## 微信订阅消息 time 字段不能用内部时间戳
|
||||
|
||||
- 现象:dev 服务器拼图资产生成终态后已经调用订阅消息发送,但日志出现 `微信订阅消息发送失败:argument invalid! data.time4.value invalid`,用户收不到生成结果通知。
|
||||
- 原因:微信模板 `time` 字段不接受内部微秒时间戳、秒级时间戳或带 `Z` / 时区后缀的字符串;发送 `1713686401.234567Z` 或类似 `2026-06-08 08:09:18Z` 会被微信拒绝。
|
||||
- 处理:`api-server` 构造生成结果订阅消息时,`time4` 固定格式化为北京时间 `YYYY-MM-DD HH:mm`;不要复用 `shared_kernel::format_timestamp_micros`。
|
||||
- 验证:`cargo test --manifest-path server-rs\Cargo.toml -p api-server generation_result_template -- --nocapture`;dev 日志中不应再出现 `data.time4.value invalid`。
|
||||
- 关联:`server-rs/crates/api-server/src/wechat_subscribe_message.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 待解决:跳一跳生成超时后可能后台继续成功
|
||||
|
||||
- 风险程度:高。
|
||||
- 现象:跳一跳生成页可能在 `98% 写入正式草稿` 后报“请求超时,请稍后重试”,但后端仍在继续生成,稍后才把同一 session 写成 `DraftCompiled=100`。2026-06-08 排查 `jump-hop-session-6db8fa7af57c4fa2a71e6430cc808412` 时,背景底图 image2 成功但耗时约 `18分25秒`,返回按钮约 `2分44秒`,地板图集约 `1分46秒`,总耗时超过前端 20 分钟等待窗口,最终在前端超时后约 3 分钟写草稿成功。
|
||||
- 原因:跳一跳创作链路仍把背景、返回按钮、地板图集、切片和 OSS 写入串在一次 HTTP 请求里;VectorEngine image2 单步 timeout/connect 失败会在后端重试,单步耗时可能超过前端总等待窗口。中间资产和真实阶段没有落库,session 在完成前仍显示 `Collecting`、`progress_percent=0`,前端只能按时间显示假进度;超时后重试同一 session 时,后端还可能因为 session 没有中间素材而重新从背景开始生成。
|
||||
- 待处理:将跳一跳生成改为后端任务化 / 可轮询真实阶段进度,按背景、返回按钮、图集、切片、持久化、写草稿分阶段落库;统一后端全局生成 deadline、VectorEngine 重试预算、前端等待窗口和失败态回写。超时后再次进入同一 session 应优先恢复正在运行或已完成的任务,不应重复生图。
|
||||
- 验证:模拟首张 image2 超长耗时或超时重试时,生成页应显示真实阶段和可恢复状态;前端请求超时不应把最终成功草稿标记为失败;刷新 `/creation/jump-hop/generating?sessionId=<id>` 后应能恢复到后端真实状态;同一 session 重试不得重复生成已完成阶段。
|
||||
- 关联:`src/services/jump-hop/jumpHopClient.ts`、`src/services/miniGameDraftGenerationProgress.ts`、`server-rs/crates/api-server/src/jump_hop.rs`、`server-rs/crates/platform-image/src/vector_engine/client.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
Reference in New Issue
Block a user