Skip to content
This repository was archived by the owner on Sep 20, 2026. It is now read-only.
This repository was archived by the owner on Sep 20, 2026. It is now read-only.

RFC: F8 Game History 整体优化与 Runs/Ghost 关联 #72

Description

@pengx17

Important

RFC status: Draft
Scope: F8 Game History 端到端信息架构、数据模型与 Mod/API/backend contract
Implementation gate: 完成服务端源码核对与独立 red-team review,修订 RFC 并获得维护者确认后,才拆分和实施子任务。

Context

F8 Game History 目前把 Runs 与 Ghost 做成两个割裂的数据视图。目标应是统一的对局历史中心:

  • 以 Run 为主时间线,查看该 Run 自己打过的本地 PVP。
  • 在对应 Run / Day 下看到其他玩家后来挑战该构筑产生的 Ghost 对局。
  • Ghost 页保留全局收件箱能力,但可反向跳到来源 Run / Day。
  • 上传云端后,分页拉取“BPP 云端已经记录到的相关对局”,而不是只展示本机固定数量的最近记录。

这里的“全部”只指 BPP 云端已成功接收并保存的对局;不承诺覆盖未安装 BPP、上传失败或官方服务未向 BPP 暴露的比赛。

Current state / evidence

  • Ghost 模式会隐藏 Runs 区域,两个视图没有关联入口:src/BazaarPlusPlus/Game/HistoryPanel/Ui/HistoryPanelUiToolkitView.cs:336-387。
  • 选择 Run 后只查询 source = 'LOCAL' 的 battle:src/BazaarPlusPlus/Game/HistoryPanel/Storage/HistoryPanelRepository.cs:117-160。
  • Ghost 入库时写入 run_id = NULL,schema 也约束 Ghost 不得使用现有 run_id:src/BazaarPlusPlus/Game/HistoryPanel/Storage/HistoryPanelRepository.cs:367-405、src/BazaarPlusPlus.Storage/RunLog/RunLogSchema.cs:100-145。
  • 当前 Ghost API 只有 player_account_id + limit,上限 200;返回 DTO 没有 origin Run / battle / day:src/BazaarPlusPlus.ModApi/Clients/GhostBattleClient.cs:24-73、src/BazaarPlusPlus.ModApi/Clients/GhostBattleClient.cs:169-213。
  • F8 本地加载固定为最近 40 Runs / 100 Ghost battles:src/BazaarPlusPlus/Game/HistoryPanel/HistoryPanelCoordinator.cs:118-155。
  • 未下载 Ghost 元数据 14 天后会被本地软删除:src/BazaarPlusPlus/Game/HistoryPanel/Storage/HistoryPanelRepository.cs:519-537。
  • Run bundle 已上传双方 hand / skills snapshot,可供服务端建立稳定构筑指纹:src/BazaarPlusPlus/Game/RunLogging/Upload/RunBundleUploadStore.cs:174-245、src/BazaarPlusPlus/Game/RunLogging/Upload/RunBundleUploadStore.cs:438-455。
  • 云端实现位于独立仓库 bazaarplusplus-server;本仓库只能验证 Mod/API 客户端:README.md:52-56。

Proposed decision: end-to-end F8 optimization

该需求不能定义成单纯 UI 调整。若目标包含“上传后按 Run 拉回后来发生的 Ghost 挑战”,后端必须参与:

  1. 当前 Ghost 元数据没有来源 Run 标识,客户端无法完整、稳定地归属。
  2. 客户端只能对已下载 replay 做指纹匹配,无法覆盖 metadata-only 记录。
  3. 固定 100/200 条限制无法支持完整历史,需要 cursor pagination。
  4. Ghost payload 现有 RunId 来自挑战者上传的 Run,不等于本地玩家被挑战构筑的来源 Run,不能复用:src/BazaarPlusPlus/Game/HistoryPanel/Ghost/GhostBattleSyncService.cs:265-315。

本父 Issue 覆盖完整产品与数据链路;确认服务端仓库后,在那里建立后端子 Issue,并从本 Issue 链接依赖。

Proposed domain model

Origin Run
  └─ Origin Battle / Day Build
       └─ 0..N Ghost Challenges

不要重载现有 battles.run_id。该字段继续表示本地/上传方真实 Run;Ghost 来源归属使用独立、可审计的派生关系,例如:

ghost_origin_links
- ghost_battle_id
- owner_account_id
- origin_run_id
- origin_battle_id
- origin_day
- fingerprint_version
- match_status        # matched / ambiguous / unmatched
- match_method
- matched_at_utc

来源关系属于 BPP 的派生匹配结果,不应伪装成游戏官方提供的 truth。

Stable build fingerprint v1

  • 包含:hero;hand / skills 分区;稳定 template ID;size/type;section/socket;tier;enchantment;重复卡数量。
  • 忽略:运行时 instance ID、显示名、本地化文本。
  • day、level、victories、prestige、时间只用于缩小候选和消歧,不替代构筑内容。
  • 只有唯一、精确、高置信匹配才写入 matched;多候选保留 ambiguous。
  • 支持先上传来源 Run、后上传 Ghost,以及相反顺序;两条路径均需幂等回填。

Proposed F8 information architecture

Runs — primary timeline

  • Run 行显示聚合:Ghost 7 · 5胜2负。
  • 展开 Run 后保留原有 local battle/day。
  • battle/day 行显示:被挑战 ×3 · 2胜1负。
  • 继续展开后展示挑战者、时间、结果、replay 状态。
  • Ghost challenge 作为来源 battle 的关联活动,不伪装成本地玩家亲自打过的普通 battle。

Ghost — global inbox

  • 保留 All / I Won / I Lost / time filters 和 replay 操作。
  • 已关联记录显示 来自这局 · D13,可跳到 Runs 对应位置。
  • unmatched / ambiguous 仍正常显示,但不提供虚假的 Run 链接。
  • 文案使用“云端已记录的 Ghost 对局”,避免表达成官方全量历史。

API direction

建议增加 cursor pagination 和服务端确认的来源关系:

GET /ghost-battles
  ?player_account_id=...
  &origin_run_id=...
  &cursor=...
  &limit=50

响应记录新增:

origin_run_id
origin_battle_id
origin_day
origin_match_status
origin_match_method

服务端也可提供按 Run 查询的专用 endpoint;最终形式在读取 bazaarplusplus-server 后确定,但客户端不得在每次打开 F8 时重新猜来源。

Out of scope

  • 从 The Bazaar 官方后端抓取 BPP 从未接收到的比赛。
  • 用 display name、卡牌 instance ID 或 Ghost payload 的挑战者 RunId 做归属。
  • 首版自动合并 ambiguous 记录。
  • 删除本地 Run 时级联删除云端 Ghost;删除只能解除本地展示/缓存,不能删除他人产生的挑战事实。

Execution checklist

Phase 1 — contract and discovery

  • 定稿 Run、Local Battle、Origin Build、Ghost Challenge、Cloud-known 的产品术语。
  • 定稿删除语义、历史完整性承诺、ambiguous/unmatched 展示规则。
  • 定位并读取 bazaarplusplus-server 当前 schema、upload、ghost query 与 artifact storage。
  • 在服务端仓库创建并链接后端子 Issue;若无后端改动即可达成,必须用现有 API 字段逐条证明。
  • 对完整设计做独立 red-team review;修订并回到本 Issue确认后再实现。

Phase 2 — backend

  • 实现版本化 build fingerprinter,用 fixture 固定 canonical serialization/hash。
  • 增加独立 origin-link schema、索引、唯一性和审计字段。
  • 在来源 Run 与 Ghost 任一上传顺序下触发幂等匹配。
  • 对历史 artifact 执行可重入 backfill;记录 matched / ambiguous / unmatched 指标。
  • 增加 cursor pagination 与 origin Run filter/fields。
  • 明确账号隔离、删除/保留策略和最大 page size。
  • 添加契约、顺序、重复上传与 >200 条分页测试。

Phase 3 — Mod/API/storage

  • 扩展 BazaarPlusPlus.ModApi DTO/客户端,消费 cursor 和 origin fields。
  • 为本地 SQLite 增加独立 Ghost-origin relation/cache;按规则 bump schema/version。
  • Repository 支持 Run 聚合、Run/day Ghost 列表、全局 inbox、deep link。
  • 固定加载改成渐进分页/按需加载。
  • 重新定义 14 天 metadata retention,避免来源聚合无提示消失。
  • 保持 Ghost 的本地玩家视角投影及胜负统计一致。

Phase 4 — F8 UI

  • Runs 行增加 Ghost 总数与胜负聚合。
  • Battle/day 行增加“被挑战”聚合与挑战列表。
  • Ghost 行增加来源 Run/day 状态与 deep link。
  • 设计 unmatched、ambiguous、offline、loading、partial pagination、empty states。
  • 复用现有 UI Toolkit/HistoryPanel 组件与虚拟化模式。
  • 完成中英文本地化与 CJK 字体路径。

Phase 5 — verification

  • 指纹覆盖 instance ID 改变、重复卡、卡序变化、相同构筑多候选、版本演进。
  • 端到端覆盖 Run→Ghost、Ghost→Run、重复/延迟上传、回填、分页恢复。
  • 验证 Ghost 视角翻转后的胜负与聚合统计。
  • 验证 0、1、100、>200、历史回填记录下的加载与滚动。
  • 验证删除本地 Run 不会错误删除/串联 Ghost。
  • 运行相关测试、./run.sh test、Debug/Release build。
  • Steam 真机验收布局、过滤、deep link、replay 与断网恢复。

Phase 6 — wrap-up

  • 更新必要的架构/ADR,记录派生关联、完整性边界和 API contract。
  • 自查 diff,确认无旧双路径或客户端猜测 fallback。
  • 通过 feature branch → PR 交付,链接本 Issue 与后端子 Issue。
  • 在本 Issue 评论真实验证、已知局限和回填状态。

Acceptance criteria

  • 用户可从 Run 看到本地 battles,以及云端已关联到该 Run/day 的 Ghost challenges。
  • 用户可从 Ghost inbox 跳回来源 Run/day;来源不确定时明确显示未关联。
  • >200 条云端记录可分页完整遍历,不受固定窗口截断。
  • 关联由服务端持久化、可回填、可审计;客户端不依赖下载 replay 后现场推断。
  • UI 明确区分“本人当时打的 battle”和“别人后来挑战该构筑的 Ghost challenge”。
  • 未上传到 BPP 的官方对局不被宣传为已覆盖。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageMaintainer needs to evaluate this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions