Merge branch 'master' into codex/puzzle-clear-template-runtime-fixes

This commit is contained in:
kdletters
2026-06-06 20:01:52 +08:00
425 changed files with 16451 additions and 6022 deletions

View File

@@ -33,6 +33,37 @@
- 影响范围:`CONTEXT.md`、拼消消 PRD / 技术方案、平台玩法链路文档、`shared-contracts` / `packages/shared``api-server``spacetime-module``spacetime-client`、作品架 / 广场 / 统一作品详情 / runtime 前端分流。
- 验证方式PRD 和技术方案必须覆盖资产槽位、素材工作表风险、切片验证、恢复语义、API 命名空间和验证命令;实现侧至少运行 `npm run spacetime:generate``npm run check:spacetime-schema``npm run check:spacetime-runtime-access``npm run check:server-rs-ddd``npm run typecheck``npm run check:encoding`、相关前端测试和 `cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml`
- 关联文档:`docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md``docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-06-05 Server-Provision 全程在目标部署 agent 执行且不安装构建链
- 背景:`Genarrative-Server-Provision``DEPLOY_TARGET=development` 语义是部署到 dev 服务器,不是构建机 dry-run。旧流水线把 development 映射到 `linux && genarrative-build`,还先在 build 节点准备 `provision-tools/` 再 stash 给后续阶段,导致真实 dev 初始化可能跑到 Jenkins controller / build 节点;脚本还安装 clang / lld / pkg-config / OpenSSL headers / sccache 等构建链依赖,超出了服务器初始化职责。
- 决策Server-Provision 只做服务器初始化,全程运行在目标部署 agentdevelopment 使用 `linux && genarrative-dev-deploy`release 使用 `linux && genarrative-release-deploy``Prepare Provision Tools``Provision Server` 在同一个目标 agent workspace 顺序执行,不再切到 `linux && genarrative-build`,不再 `stash/unstash` 工具包。`scripts/jenkins-server-provision.sh` 不再安装 clang / lld / pkg-config / libssl-dev / sccache非 dry-run 仍要求目标 dev / release agent 具备 root 权限,因为 provision 会写 systemd、Nginx、`/etc` 和系统用户。Job 的 `Pipeline script from SCM` 与 Jenkinsfile 参数 `SOURCE_GIT_REMOTE_URL` 都必须使用本机路径或目标 agent 可访问的内网 Git 源,不允许公网 Git fallback。
- 影响范围:`jenkins/Jenkinsfile.production-server-provision``scripts/jenkins-server-provision.sh`、生产运维文档、Server-Provision 排障口径。
- 验证方式Jenkins 日志中 Server-Provision 的 `Prepare``Checkout Provision Files``Prepare Provision Tools``Provision Server` 都在目标 dev / release agent 上执行;日志不出现 `Running on Jenkins``linux && genarrative-build``stash 'server-provision-tools'``Git 主地址拉取失败...改用备用地址``https://git.genarrative.world/GenarrativeAI/Genarrative.git` 或构建依赖 / sccache 安装步骤;`bash -n scripts/jenkins-server-provision.sh` 和编码检查通过。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-06-05 api-server 重启先摘流再排空并持久化 outbox
- 背景:生产部署重启 api-server 时,如果只用 `/healthz` 判断存活并直接停止进程,运行中的 HTTP 请求和本地 tracking outbox active 文件都可能被中断,容易造成用户请求失败或内存/本地缓冲数据延迟丢失。
- 决策:`/healthz` 只表示进程存活,发布和生产接流检查统一使用 `/readyz`。api-server 收到 `SIGINT` / `SIGTERM` 后先把 readiness 标记为不可用,再交给 Axum graceful shutdown 排空已有 HTTP 请求;退出前在 `GENARRATIVE_API_SHUTDOWN_OUTBOX_FLUSH_TIMEOUT_MS` 窗口内封存 active tracking outbox 并尽力 flush sealed 文件失败或超时则保留本地文件给下次启动重试。systemd 停机窗口统一放到 `TimeoutStopSec=90`
- 影响范围:`server-rs/crates/api-server``deploy/systemd/genarrative-api.service`、生产 API deploy 脚本、Jenkins API deploy 参数、Nginx 公网健康检查暴露策略、开发运维文档。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml readyz_reports_readiness_and_draining_state``cargo test -p api-server --manifest-path server-rs/Cargo.toml shutdown_flush_seals_active_file_for_later_retry``cargo check -p api-server --manifest-path server-rs/Cargo.toml`、部署脚本 `bash -n``/readyz` 本机 smoke。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-06-05 OSS 平台适配器输出结构化日志
- 背景AI 生成资产、浏览器直传签名、私有读签名和对象确认都依赖 OSS如果 OSS 侧只有错误字符串,排查资产写入 / 确认失败时很难按操作、对象、状态码和耗时下钻。
- 决策:`server-rs/crates/platform-oss` 统一为 `sign_post_object``sign_get_object_url``head_object``put_object` 输出结构化日志。日志固定携带 `provider=aliyun-oss``operation``bucket``endpoint``object_key` / `key_prefix``access``content_type``content_length``status``status_class``error_kind``elapsed_ms` 等排障字段;禁止输出 AccessKey、policy、signature、Authorization header 或完整 signed URL。
- 影响范围:`server-rs/crates/platform-oss``api-server` 资产签名 / 上传 / 确认链路、OTLP logs、本地 `logs/api-server/` 与运维排障文档。
- 验证方式:`cargo test -p platform-oss --manifest-path server-rs/Cargo.toml`;真实联调时按 `provider=aliyun-oss``operation` 过滤日志,确认只出现对象定位和状态字段,不出现签名材料。
- 关联文档:`server-rs/crates/platform-oss/README.md``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-06-05 跳一跳返回按钮改为独立主题资产
- 背景:跳一跳运行态曾把左上角返回按钮视觉锚点写进背景 image2 prompt导致返回按钮像静态背景元素不能替代真实可点击按钮。
- 决策:跳一跳背景 prompt 禁止生成任何 UI 或左上角图标;返回按钮由 `backButtonAsset` 单独生成 1:1 纯绿 key 图,后端去绿后作为透明 PNG 持久化到作品 profile运行态左上角真实按钮优先渲染该资产。顶部得分 HUD 复用拼图模板结构,包含陶泥儿 IP logo、标题牌和下挂数字卡。
- 影响范围:`packages/shared/src/contracts/jumpHop.ts``shared-contracts``spacetime-module` / `spacetime-client` bindings、`api-server` 跳一跳生成链路、`JumpHopRuntimeShell`、玩法链路文档和后端数据契约文档。
- 验证方式:`npm run spacetime:generate``cargo test -p api-server jump_hop --manifest-path server-rs/Cargo.toml``npm run test -- src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx``npm run check:spacetime-schema`
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-06-03 创作入口关闭不下架已发布作品
@@ -201,7 +232,7 @@
## 2026-05-26 推荐页拼图下一关 pending 时保留当前运行态
- 背景:推荐页嵌入拼图在点击“下一关”时,`advancePuzzleNextLevel` 的服务端请求会短暂处于 pending。旧逻辑把推荐卡的 `isStartingRecommendEntry` 和拼图局部 busy 混在一起,导致外层直接切回“加载中...”,把当前 `PuzzleRuntimeShell` 一起卸载,视觉上像是切关闪回。
- 决策:推荐页嵌入拼图切关 pending 期间必须保留当前运行态与棋盘,只让拼图壳内部 busy 表现承接同步;`isStartingRecommendEntry` 只表示推荐作品尚未真正启动出来,不再把已有嵌入拼图 run 的局部 busy 一并当成整卡加载态。若下一关落到相似作品,前端还必须把新作品写回推荐缓存并同步 `activeRecommendEntryKey`,避免运行态进入新作品但推荐卡元信息、分享 / 点赞 / 改造和后续“下一个”仍锚定旧作品。
- 决策:推荐页嵌入拼图切关 pending 期间必须保留当前运行态与棋盘,只让拼图壳内部 busy 表现承接同步;`isStartingRecommendEntry` 只表示推荐作品尚未真正启动出来,不再把已有嵌入拼图 run 的局部 busy 一并当成整卡加载态。若下一关落到相似作品,前端还必须把新作品写回推荐缓存并同步 `activeRecommendEntryKey`,避免运行态进入新作品但推荐卡元信息、分享 / 点赞 / 改造和后续“下一个”仍锚定旧作品;但这个同步仍属于同一个 run 内部推进,不得触发推荐 rail 切卡动画、纵向位移或启动封面重置
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/RpgEntryHomeView.tsx`、推荐页拼图切关测试与平台链路文档。
- 验证方式:点击推荐页拼图“下一关”后,在 `advancePuzzleNextLevel` 未返回前,页面仍应保留 `puzzle-board`,且不出现 `加载中...` 占位;返回相似作品后,当前推荐卡的 `作品信息` 应显示新作品标题。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
@@ -461,7 +492,7 @@
## 2026-05-19 生产 provision 改为 Windows 下载包后由目标机本地安装
- 后续更新:该口径`2026-06-01 生产 Jenkins 流水线统一改为 Linux 优先并先查 localhost` 取代;当前 `Genarrative-Server-Provision`走 Windows 下载阶段,而是在 Linux build 节点直接准备 `provision-tools/`
- 后续更新:该口径被 2026-06-01 Linux 优先方案取代,又在 2026-06-05 被 Server-Provision 专用口径覆盖;当前 `Genarrative-Server-Provision` 不走 Windows 下载阶段,也不在 Linux build 节点中转工具包,而是在目标 dev / release agent 内准备 `provision-tools/`
- 背景:当前 `development` provision 目标实际就是 Linux agent `genarrative-build-01`,之前把 `Prepare Provision Tools` 放在 `linux && genarrative-build` 会让目标机自己连 GitHub 和 `install.spacetimedb.com`违背“Windows 本机先下载再传到目标机”的运维要求。
- 决策:`Genarrative-Server-Provision` 拆成 Windows 下载阶段和 Linux 目标机安装阶段。Windows 节点的 `Download Provision Tool Archives` 只下载 `spacetime-x86_64-unknown-linux-gnu.tar.gz``otelcol-contrib_0.151.0_linux_amd64.tar.gz`,通过 `stash/unstash` 传到目标 Linux 节点;目标机执行 `scripts/prepare-server-provision-tools.sh` 时设置 `PROVISION_REQUIRE_LOCAL_DOWNLOADS=true`,只消费已下载件生成 `provision-tools/`,缺包直接失败,不回退外网下载。
- 追加决策Server-Provision 的 Windows helper 不再对 Jenkins `writeFile` 刚写出的 `.ps1` 做原地 UTF-8 BOM 重写,而是由显式 `powershell.exe` 按 UTF-8 读入脚本文本,并用 `ScriptBlock::Create(...)` 在内存中执行;这样既保留中文脚本内容,又避免同一个 workspace 脚本被立即重写时触发 `拒绝访问`
@@ -1119,6 +1150,7 @@
## 2026-06-01 生产 Jenkins 流水线统一改为 Linux 优先并先查 localhost
- 后续更新:该条仍适用于常规构建 / 发布流水线;`Genarrative-Server-Provision` 已在 2026-06-05 改为目标部署 agent 全程执行,并禁止公网 Git fallback 与 build 节点工具包中转。
- 背景:生产流水线长期混用 Windows、Linux 和公网 Git 入口,导致构建 / 发布 / provision 的 checkout 口径分叉;同时 `Genarrative-Server-Provision` 还残留过 Windows 下载 helper和当前 Linux 构建 / 发布部署路径不一致。
- 决策:生产 Jenkins 流水线统一把执行节点收口到 Linux label`Pipeline script from SCM` 仍保留公网域名,但所有生产流水线首次 `GitSCM checkout` 先尝试 `http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后再回退到 `https://git.genarrative.world/GenarrativeAI/Genarrative.git``Genarrative-Stdb-Module-Build``Genarrative-Server-Provision``Genarrative-Notify-Email` 也都切到 Linux 节点。`Genarrative-Server-Provision` 的工具准备不再依赖 Windows helper而是在 Linux build 节点直接生成 `provision-tools/` 后交给后续 Linux 发布阶段。
- 影响范围:`jenkins/Jenkinsfile.production-*``scripts/jenkins-checkout-source.sh``scripts/prepare-server-provision-tools.sh`、生产运维文档。
@@ -1157,13 +1189,38 @@
- 验证方式:从平台推荐或公开详情进入跳一跳作品时,路由 source type 为 `jump-hop`、public code 为 `JH-*`,运行态启动消费后端返回的完整 profile / run 数据;后端 smoke 统一使用 `npm run dev:api-server` 启动并检查 `/healthz`
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 2026-05-26 跳一跳地块图集改为专用 2x3 六格切分
## 2026-05-28 跳一跳重设计为 5x5 地块图集与弹弓拖拽
- 背景:跳一跳创作在地块生图阶段误用了通用系列素材图集 helper`item_names.len() > grid_size` 的校验会让 6 个地块类型在 `grid_size = 3` 时直接失败;即使绕过校验,通用 helper 仍以“每物品多视图”语义切图,不符合跳一跳地块的一次性六格资产模型
- 决策:跳一跳地块图集固定采用专用 `2行*3列` 六格布局,按 `start / normal / target / finish / bonus / accent` 顺序切分并分别持久化为独立 PNG 资产;图集 prompt 不再调用通用系列素材 `build_generated_asset_sheet_prompt`
- 影响范围:`server-rs/crates/api-server/src/jump_hop.rs``docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 验证方式:`cargo test -p api-server jump_hop_tile_atlas -- --nocapture` 通过;六张切片都应有独立 OSS 对象与 `JumpHopTileAsset` 记录,不再只有 atlas 预览路径
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`
- 背景:跳一跳模板仍保留角色生图、有限路径、score/combo 和 `2x3` 地块图集口径,和当前“俯视角平台跳跃 + 主题生成地块池 + 无限路径”的产品需求不一致
- 决策:`jump-hop` v1 创作端只保留主题输入image2 生成一张 `5x5`、共 25 个 2D 地块图标的图集,后端按均匀网格切出 25 个 `JumpHopTileAsset`。角色不再单独生图,运行态使用陶泥儿 logo 透明 PNG 角色;运行态输入为按住后拉蓄力、松手反向弹出,前端提交 `chargeMs + dragVectorX + dragVectorY`,后端裁决落点。草稿试玩必须使用 `runtimeMode=draft`,正式作品使用 `runtimeMode=published`;排行榜按作品维度每玩家只保留 1 条最佳记录,排序为成功跳跃次数降序、游戏时长升序、更新时间升序
- 决策补充:跳一跳创作入口的事实源仍是 SpacetimeDB `creation_entry_type_config`。默认种子和旧默认行都必须同步迁移到 `subtitle=主题驱动平台跳跃``image_src=/creation-type-references/jump-hop.webp`;后端只在系统默认旧值命中时自动纠偏,避免覆盖后台手动配置
- 影响范围:`jump-hop` PRD、`api-server` 生成编排、`module-jump-hop` 领域规则、`spacetime-module` / `spacetime-client` 跳一跳契约、前端工作台 / 结果页 / runtime / 平台壳调用链
- 验证方式:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml``cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run check:spacetime-schema`、跳一跳工作台和 runtime 定向前端测试
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 2026-06-01 跳一跳运行态地块视觉尺寸和命中半径统一放大一倍
- 背景:当前跳一跳运行态里地块视觉尺寸偏小,玩家反馈“很难跳上去”,但仅放大前端展示会造成画面和后端裁决脱节。
- 决策:`jump-hop` 运行态的地块视觉尺寸、`width/height` 玩法世界尺寸以及 `landingRadius/perfectRadius` 同步乘以 2前端平台渲染抽成统一尺寸 helper保证单测可以直接校验放大结果。
- 影响范围:`server-rs/crates/module-jump-hop/src/application.rs``src/services/jump-hop/jumpHopRuntimeModel.ts``src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、对应定向测试。
- 验证方式:`npm test -- src/services/jump-hop/jumpHopRuntimeModel.test.ts src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx``cargo test -p module-jump-hop --manifest-path server-rs/Cargo.toml -- --nocapture`
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-06-02 跳一跳起跳距离减半并加入飞行动画缓冲
- 背景:用户反馈当前跳跃到目标位置需要拖得太远,且松手后缺少角色翻腾到目标地块的过渡动画,导致跳跃手感偏硬。
- 决策:`jump-hop``chargeToDistanceRatio` 统一从 `0.004` 提升到 `0.008`,让同等跳跃距离所需拖动距离减半;前端 runtime 把“后端真实 run”和“当前屏幕显示态”拆开松手瞬间先生成 `visualJump`,用当前角色位置作为起点、前端预测落点作为终点,播放约 `560ms` 的飞行动画;该路径不得等待后端新 run。角色弹到预测落点后若新 run 尚未返回,必须停在预测落点等待,再进入约 `1440ms` 的相机层推进过渡。推进期间地块 DOM 层和 DOM 角色层统一包在同一个 camera layer 下移动,旧当前地块自然离开视野,新预览地块从上方露出,避免 p1/p2 单独 top/left 过渡导致角色和地块不同步。相机推进必须同时使用 X/Y 偏移,从旧目标地块位置斜向滑到新当前地块聚焦位置,不能先横向瞬切居中再纵向推进。地块保留当前 / 目标 / 预览的深度尺寸差异,但该差异通过固定基准宽高上的 CSS transform scale 表达,并在相机推进期间同样使用 `1440ms` 缓动;当前态不再额外叠 CSS scale。
- 影响范围:`server-rs/crates/module-jump-hop/src/application.rs``src/services/jump-hop/jumpHopRuntimeModel.ts``src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、跳一跳运行态定向测试。
- 验证方式:`npm test -- src/services/jump-hop/jumpHopRuntimeModel.test.ts src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx``cargo test -p module-jump-hop --manifest-path server-rs/Cargo.toml -- --nocapture``npm run check:encoding`
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-06-03 跳一跳角色形象改为陶泥儿 logo 透明 PNG
- 背景:跳一跳运行态此前仍使用旧内置 / CSS 角色形象,和用户要求的陶泥儿 logo 角色不一致,也容易和 DOM 地块层出现遮挡层级问题。
- 决策:`jump-hop` v1 不再渲染内置 3D 角色几何体;运行态和结果页统一使用 `public/branding/jump-hop-taonier-character.png`,该文件由陶泥儿 logo 处理为透明 PNG 后接入。蓄力时角色沿拖拽方向明显拉长,落地后向反方向回弹两次。`characterAsset` 继续仅作为历史兼容描述字段,不能重新打开角色生图槽或把角色图片作为创作者可配置输入。
- 影响范围:`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx``src/components/jump-hop-result/JumpHopResultView.tsx`、跳一跳 PRD 和平台链路文档。
- 验证方式:跳一跳运行态 / 结果页测试需要断言角色图片 src 为 `/branding/jump-hop-taonier-character.png`,并确认旧默认角色 fallback 不再出现。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
# 2026-05-20 陶泥儿主视觉配色回收为暖白/陶土橙

View File

@@ -205,7 +205,7 @@ npm run check:server-rs-ddd
- 使用 `npm run dev:api-server` 重新拉起后端。
- 禁止使用 `npm run api-server:maincloud``npm.cmd run api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径;这些只属于历史残留。
- 检查 `/healthz`
- 本地 smoke 检查 `/healthz`;发布后或确认实例可接生产流量时检查 `/readyz`
- 执行对应自动测试。
- 涉及 SpacetimeDB 表、reducer、procedure、row shape 或绑定变化时,同步更新 `migration.rs`、表目录和生成绑定。
- SpacetimeDB 已有表新增字段必须放在 Rust 表结构体最后,并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划,再同步更新 `server-rs/crates/spacetime-module/src/migration.rs`、表目录和生成绑定。
@@ -224,7 +224,7 @@ npm run check:server-rs-ddd
## 生产压测与观测默认口径
- 作品列表 50 HTTP req/s 压测使用 `scripts/loadtest/README.md` 中的 K6 命令;当前脚本一次 iteration 请求两个公开列表接口,因此目标 50 HTTP req/s 对应 `PEAK_RPS=25`
- 生产 `api-server` 默认 backlog、worker threads、HTTP 并发背压、systemd 限制、Nginx upstream timing log 和 OTLP 开关以 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` 为准。
- 生产 `api-server` 默认 backlog、worker threads、HTTP 并发背压、`/readyz` 接流检查、systemd 优雅停机窗口、Nginx upstream timing log 和 OTLP 开关以 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` 为准。
- OpenTelemetry 现阶段可选发送 traces / metrics / logs但不会取代本地 `journalctl -u genarrative-api.service``logs/api-server/``/var/log/nginx/genarrative.*.log`
- 指标 label 不写 raw URI、userId、profileId 或 request_idrequest_id 只用于 trace/log 串联。

View File

@@ -15,6 +15,14 @@
- 关联:相关文件、文档、提交或 Issue
```
## 小程序 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`
## 平台异步错误必须带来源弹窗,不要只显示裸错误
- 现象:用户先后触发多个拼图或草稿生成时,旧请求失败后会在当前页面显示“图片生成失败”等裸错误,容易误判为当前正在看的拼图失败;错误文本也不便复制给开发排查。
@@ -35,17 +43,17 @@
- 现象:`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`,按上游网关或审核失败另行处理,不要归到传输超时。
- 处理:先按 `provider/failureStage/statusClass` 聚合,再用 `user_id` / `profile_id``metadata_json.userId/profileId/requestId` 定位触发者、草稿 / 作品和同一次 HTTP 请求;`request_send + timeout=true` 优先查 provider 日志的 `source_chain`、请求体大小、参考图数量、出口网络、代理/Nginx、VectorEngine 当时可用性和同一 request_id 日志。当前 `platform-image``request_send``timeout` / `connect` 错误最多重试 3 次multipart `/v1/images/edits` 每次重试都必须重建 form看到 `VectorEngine 图片请求发送失败,准备重试` 只是单次 attempt 失败,最终 `external_api_call_failure` 才代表该用户请求整体失败。若记录有 `502``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_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`
## “我的”页不要恢复旧的填邀请码次级按钮
@@ -151,6 +159,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`
## 移动端草稿卡不要长按选中文字
- 现象:移动端草稿页长按作品卡标题或摘要时触发系统文字选区,容易误触并打断作品架操作。
@@ -1219,6 +1235,7 @@
## Jenkins 生产流水线拉 Git 先本机再域名备用
- 后续更新:该条仍适用于常规构建 / 发布流水线;`Genarrative-Server-Provision` 已在 2026-06-05 改为服务器初始化专用口径,不允许公网 Git fallbackJob 的 `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 时才允许加深历史做分支归属校验。
@@ -1241,12 +1258,12 @@
- 验证:运行 `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 全程运行在目标部署 agentdevelopment 使用 `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`
## 个人任务 scope 不得扩成 work/site/module
@@ -1624,10 +1641,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 共用同一写成绩路径,未发布或未确认资源的草稿试玩会污染正式单局、排行榜和作品统计。
@@ -1675,14 +1700,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 行模型
## 跳一跳地块图集固定走 5x5 地块池
- 现象:跳一跳初始草稿生成时报 `系列素材图集的物品行数不能超过 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 地块图集 -> 25 个等权地块池 -> 无限路径”,旧的物品行数 / 固定类型模型都会把创作链路带偏
- 处理:跳一跳地块固定生成一张 `5x5` 主题图集,后端按均匀网格切出 25 张 PNG并对每张切片各自走 OSS 上传、asset_object 确认和 entity bind不要再恢复 `2行*3列``start / normal / target / finish / bonus / accent` 六格口径
- 验证:`jump_hop.rs` 不应再调用通用物品行数模型处理地块图集;公开结果里应能拿到 25 个独立 `JumpHopTileAsset`,运行态无限路径从地块池随机取材
- 关联:`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"`
- 原因25 个落点图集 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` 只是路径平台的玩法类型标签25 个 atlas 切片里会重复出现 `normal / target / bonus / accent` 等类型。若后端持久化时用 `tileType` 生成 slot/path同类型切片会写入同一个 `/generated-jump-hop-assets/<profile>/<slot>/image.png`,后上传的切片覆盖先上传的切片,前端换签缓存也会读到重复或旧对象。
- 处理:后端切图后必须按 atlas 单元格写入 `tile-01``tile-25` 的唯一 slot/path前端结果页和运行态展示生成图时用 `assetObjectId` 作为 `refreshKey`,避免重生成后复用旧签名或旧图片缓存。
- 验证:`cargo test -p api-server jump_hop --manifest-path server-rs/Cargo.toml -- --nocapture` 应包含 `jump_hop_tile_asset_slots_are_unique_for_twenty_five_slices`;前端运行态测试应断言地块换签带 `assetObjectId` 刷新键。
- 关联:`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`
## 跳一跳落点辅助和后端裁决必须统一坐标换算
- 现象:落点辅助标识已经压在目标地块图片上,松手后后端仍判定失败,玩家看到的是“明明瞄准了却没落上去”。
- 原因:前端辅助标识使用屏幕像素坐标绘制,而后端裁决使用世界坐标。屏幕 y 轴向下为正、世界 y 轴向上为正;同时屏幕 x/y 每个世界单位对应的像素比例不同。若前端直接把屏幕像素拖拽向量发给后端,辅助点和后端落点方向会不一致。
- 处理:前端运行态保留原始屏幕拖拽向量用于画弹弓和辅助点,但提交后端前必须按当前地块到目标地块的屏幕跨度 / 世界跨度把 x、y 分别换算成世界尺度一致的向量;后端继续只负责反向弹射和落点裁决。
- 验证:前端回归测试要同时覆盖辅助点完整拖拽到目标地块,以及提交给后端的向量已完成世界尺度换算;后端领域测试覆盖屏幕向后下拉时应向世界 y 正方向跳出并命中。
- 关联:`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,...`,终端日志会被数百万字符淹没。
@@ -1761,6 +1825,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(...)` 不等价于文件上传 partVectorEngine 转码层会认为没有收到图片。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 起,移动端“我的”页顶部改为品牌行 + 扫码 / 设置按钮,设置区和次级入口不再提供独立的 `存档` 按钮;用户仍可在“玩过”弹窗里查看可继续存档。
@@ -1769,6 +1853,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.3.0`,确认本机 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 残留顶替作品归属,导致刚注册就看到别人的草稿或已发布作品。
@@ -1903,3 +2003,42 @@
- 处理:开局和补牌后的重排必须先排除现成消除,再用真实交换 / 落位模拟判断是否会产生新消除;`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`,角色在当前窗口内插值飞向目标地块;约 `300ms` 后再把 `displayRun` 切到最新后端 run并进入约 `1440ms``platformAdvancing` 表现态。推进期间地块 DOM 层和 Three.js 角色层必须统一包在同一个 camera layer 下移动,旧当前地块用相机偏移自然离开视野,新预览地块从上方露出;不要再让 p1/p2 各自 top/left 过渡。相机层必须同时设置 `--jump-hop-camera-shift-x``--jump-hop-camera-shift-y`,从旧目标地块位置斜向滑到新当前地块聚焦位置,避免先横向瞬切居中再纵向推进。地块保留当前 / 目标 / 预览的深度尺寸差异,但深度差异必须用固定宽高 + 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`Three 角色层与地块层同在 `jump-hop-camera-layer` 内,通过 `--jump-hop-camera-shift-x``--jump-hop-camera-shift-y` 完成相机斜向推进,并校验可见地块按深度保留不同视觉尺寸、运行态平台宽高使用固定基准值、推进态 transform transition 为 `1440ms`、推进态角色 transition 不包含 `left/top`
- 关联:`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 原型地块。
- 处理exiting 地块继续使用稳定 `platformId` key让旧图片组件在推进期复用有真实 `resolvedUrl` 且未错误时直接保留真实 `<img>`,只在无 URL 或加载失败时显示 fallback当前 3 块之外的后续地块通过隐藏预加载图片提前解析签名 URL 和浏览器缓存。
- 验证:`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`
## 跳一跳地块抠图不要用绿幕或近白底识别
- 现象:跳一跳生成草地、花、雪地、白石或云朵地块时,透明化会把绿色 / 白色主体局部扣掉,运行态看到平台缺口、变薄或主体消失。
- 原因:通用图集默认按绿幕和近白底做透明化,适合 UI / 普通物品,但跳一跳地块天然高频包含绿色和白色;如果继续用 `#00FF00` 绿幕或近白背景识别,素材本体会落入背景分数。旧逻辑还会清理非边缘连通的高置信 key 色块,遇到主体内部撞色时也可能误伤。
- 处理:跳一跳地块图集 prompt 固定要求单一纯洋红 `#FF00FF` key 背景;切片前后透明化调用 `GeneratedAssetSheetAlphaOptions::jump_hop_magenta_screen()`,只扣洋红 key关闭近白扣除并且不清理非边缘连通 key 色像素。通用绿幕函数保持默认绿幕 / 近白兼容,避免影响拼图、抓大鹅和敲木鱼。
- 验证:`cargo test -p platform-image --manifest-path server-rs/Cargo.toml generated_asset_sheet -- --nocapture` 覆盖洋红 key 保留绿色、白色和非边缘连通 key 色主体;`cargo test -p api-server jump_hop --manifest-path server-rs/Cargo.toml -- --nocapture` 覆盖跳一跳洋红 prompt 与绿 / 白地块切片。
- 关联:`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`