№ · 任务清单 CH.07.07
主页 · 📃 任务清单 · 2026-07-15-p2p-timeout-reconnect

2026-07-15-p2p-timeout-reconnect

📅 2026-07-07 · 📦 7.1 KB · 📃 任务清单 · 📝 MD 文档 🆕 14d 🏷️ Buff🏷️ 联机🏷️ 计划🏷️ 拍卖🏷️ 时间

⟦tag:v2|task|2026-07-15-p2p-timeout-reconnect⟧ kind: task id: 2026-07-15-p2p-timeout-reconnect lane: R8 title: P2P 5s 流拍 + 断线重连(Day 3 主,6h) objective: 收口 W29 5 日计划 R8 lane——把 R7 落的实时竞价补成"流拍可重连",含 5s 流拍机制 + 客机/房主断线重连 + 房主转移 + 顶部条幅 UI。 scope: docs/plans/active/2026-07-13-5日开发计划-v1.md § 2, docs/design/active/联机P2P房间骨架-v1.md, src/data/multiplayer/**(只读 + 新增), src/utils/multiplayer/**(只新增), src/components/multiplayer-lobby/**(只新增) acceptance:

  • src/utils/multiplayer/timeout.ts 落盘,导出 applyTimeout(snapshot, now): RealtimeBidSnapshot,出价后 5s 无新出价 → settled 并广播给所有 seat
  • src/utils/multiplayer/reconnect.ts 落盘,导出 3 个纯函数 + 1 个 type
    • ReconnectState:联合类型 'online' | 'reconnecting' | 'recovered' | 'offline'
    • applyDisconnect(roomState, seatId, now): RoomState —— 标记 reconnecting,30s 内重连 → recovered;30s 后 → offline
    • applyReconnect(roomState, seatId, now): RoomState —— 恢复 seat + bid 状态
    • transferHost(roomState, now): RoomState —— 房主 30s 内不重连 → 转移给第一个在线 seat
  • 单元测试:timeout.regression.ts + reconnect.regression.ts 5 场景全过 = ① 5s 流拍 ② 客机掉线 30s 重连 ③ 客机掉线 60s 不重连 ④ 房主掉线 30s 重连 ⑤ 房主掉线 60s 转移
  • src/components/multiplayer-lobby/ReconnectBanner.tsx 落盘,顶部条幅:任一 seat 处于 reconnecting 时显「XX 正在重连…」+ 倒计时,30s 后变「XX 已离线」
  • 跟 R7 的 RealtimeBidPanel.tsx 集成:settled 触发回调时显「本轮拍卖结束」
  • cmd /c npm run build 通过,npm run lint 无新增错误
  • src/data/auction/** / src/utils/auctionRules.ts / src/components/auction/** 任何代码被改动
  • ReconnectState 跟现有 MultiplayerRoomStateRoomPhase 平级不冲突
  • src/App.tsx 主导航被改动(只动 multiplayer tab 入口,R10 lane 范围) current state: documented blockers: none next action: 主会话 7.15 上午按本卡 prompt 段派工,短工回执时按 ## 回执模板 段格式 summary:
  • 这是 W29 5 日计划 R8 lane(2026-07-15 Day 3 主轴,6h 工作量),上接 R7 实时竞价(7.14 Day 2 收口),下接 R10 UI 集成(7.16 Day 4)。
  • 5s 流拍跟 auctionRules.ts 现有 5s 流拍语义独立,本卡不读 auctionRules.ts,所有 timeout 走 src/utils/multiplayer/timeout.ts
  • 不实现真实 WebSocket / 跨设备同步 / 历史出价回放(留 v1.4+)。

prompt(给短工 / 子会话的指令)

角色:[短工]#联机-P2P流拍重连@v1.0 lane:R8 时间窗:2026-07-15 上午 9:00 ~ 2026-07-15 下午 18:00(6h 工作量,单日) 承接文档:docs/plans/active/2026-07-13-5日开发计划-v1.md § 2 前置依赖:R7 实时竞价已完成(realtimeBidding.ts + RealtimeBidState 落盘)

任务清单(按顺序做)

  1. timeout 层:src/utils/multiplayer/timeout.ts

    • 导出 applyTimeout(snapshot: RealtimeBidSnapshot, now: number): RealtimeBidSnapshot
    • 规则:state === 'bidding' || state === 'antiSnipe'now > deadlineAt → 返回 state: 'settled', extends: snapshot.extends;其他态返回原 snapshot
    • 禁止 setTimeout / setInterval,接受 now: number 注入
    • 5s 流拍 = R7 的 deadlineAt 已经由 R7 算出,本函数只判断超时;R7 的 deadlineAt 是从 placeBid 算起的「最后出价 +5s」(注:R7 文档已隐含此语义,本卡不重新设计)
  2. reconnect 层:src/utils/multiplayer/reconnect.ts

    • 导出 ReconnectState type:'online' | 'reconnecting' | 'recovered' | 'offline'
    • 导出 applyDisconnect(roomState, seatId, now): RoomState —— 30s 内重连窗口 = now + 30_000
    • 导出 applyReconnect(roomState, seatId, now): RoomState —— 恢复 seat + bid 状态
    • 导出 transferHost(roomState, now): RoomState —— 房主 30s 不重连 → 转移给 seats.find(s => s.status === 'online' && s.id !== hostId) 第一个
    • 房主转移后,旧 host 标记 offline,新 host 标记 'online' + isHost: true
    • 禁止 直接调 Date.now(),全部接受 now: number 注入
  3. UI 层:src/components/multiplayer-lobby/ReconnectBanner.tsx

    • 接收 roomState: RoomState + now: number props
    • 顶部条幅:任一 seat status === 'reconnecting' → 显「{seatId} 正在重连…({countdown}s)」+ 倒计时
    • 30s 后变「{seatId} 已离线」
    • 房主转移时显「房主已转移给 {newHostId}」3 秒淡出
    • 接主导航
  4. 集成:RealtimeBidPanel.tsx 集成 applyTimeout

    • 当 snapshot.state 变成 settled 时,触发 onSettled(snapshot.currentBid, snapshot.currentBidder) 回调
    • UI 显「本轮拍卖结束,中标:{currentBidder} @ {currentBid}」
  5. 测试层

    • src/utils/multiplayer/timeout.regression.ts 5 场景
    • src/utils/multiplayer/reconnect.regression.ts 5 场景
    • 用项目现有的 node --test 或 vitest 跑通,输出 PASS/FAIL
  6. 验收

    • cmd /c npm run build 通过
    • cmd /c npm run lint 无新增错误
    • 10 场景测试全过(timeout 5 + reconnect 5)

文件边界(强制)

  • ✅ 新增:src/utils/multiplayer/timeout.ts
  • ✅ 新增:src/utils/multiplayer/timeout.regression.ts
  • ✅ 新增:src/utils/multiplayer/reconnect.ts
  • ✅ 新增:src/utils/multiplayer/reconnect.regression.ts
  • ✅ 新增:src/components/multiplayer-lobby/ReconnectBanner.tsx
  • 🔧 修改:src/components/multiplayer-lobby/RealtimeBidPanel.tsx(只动 applyTimeout 集成,不加新功能)
  • ❌ 不碰:src/data/auction/** / src/utils/auctionRules.ts / src/components/auction/**
  • ❌ 不碰:src/utils/multiplayer/realtimeBidding.ts(R7 lane 已收口,本卡不重写)
  • ❌ 不碰:src/App.tsx 主导航(R10 lane 范围)

不做(明示)

  • ❌ 真实 WebSocket / WebRTC(留 v1.4+)
  • ❌ 跨设备同步(留 Phase 7)
  • ❌ 历史出价回放
  • ❌ 房主转移的二次确认弹窗(留 W30 UI 升级)
  • ❌ 重连后回放最后 N 秒出价历史(留 v1.4)

风险 + 缓解

  • 5s 流拍跟 auctionRules.ts 5s 流拍语义冲突 → 严格遵守文件边界,新目录平级实现,本卡全程不读 auctionRules.ts
  • 房主转移竞态(多个 seat 同时在线时选哪个)→ transferHostseats 数组顺序选第一个 online 非原 host
  • applyReconnectapplyTimeout 时间窗口交叉 → applyReconnect 只在 status === 'reconnecting' 有效,其他态拒绝

回执模板(短工完成时按这个格式回报主会话)

[R8 #联机-P2P流拍重连@v1.0 回执]
- 状态:✅ 完成 / ⚠️ 部分完成 / ❌ 阻塞
- 新增文件:<列出 5 个新增文件实际路径>
- 修改文件:<列出 1 个修改文件实际路径>
- timeout 5 场景测试:<PASS/FAIL 列表>
- reconnect 5 场景测试:<PASS/FAIL 列表>
- `npm run build`:✅ / ❌
- `npm run lint`:无新增 / 新增 N 个
- 风险 / 阻塞:<如有,列出;如无,写 "无">
- 残余风险:<如有,列出;如无,写 "无">
- 建议给 R10 接力点:<一句话>

(End of R8 task card)