Files
huajishe-tts/docs/dev-log/07-bugfixes-round-1.md
Claude 24822d0d6a docs(dev-log): add chronological development log
Per user request: split the development process into per-topic docs under
docs/dev-log/. Each entry follows the same structure:
  What was asked → Problems → How solved

Files:
- README.md            index
- 01-initial-scope-and-design.md
- 02-ui-primitives-api-design.md
- 03-monorepo-scaffold.md
- 04-ui-primitive-impl.md
- 05-drag-and-drop.md
- 06-three-games.md
- 07-bugfixes-round-1.md
- lessons-learned.md   all gotchas consolidated
- decisions.md         locked decisions with dates

Total: 1071 lines across 10 files.
2026-08-17 16:49:09 +08:00

128 lines
3.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 07 · Bug 修复 · 第一轮
## 做什么
用户手动验证 3 个游戏后报告 2 个 bug
1. **War**UI 上看不到第二个玩家的牌堆;翻牌逻辑也有问题
2. **DragTest**:在已经拖到桌的牌上继续拖会重复加;拖走之后拖不回手牌
## 遇到什么问题
### Bug 1War 看不到 P1 牌堆
**根因**4 个 zone 都设了 `owner='0'` / `owner='1'`Board 的 owner 过滤逻辑让对方的私有 zone 完全隐藏。
### Bug 2War 翻牌"啥也没看到"
**根因**:原 `flip` move 一次完成翻+收pile 永远只显示 0 张。牌翻出来立刻被赢家收走,玩家看不到"对比"。
### Bug 3DragTest 桌→桌重复加卡
**根因 Immer 陷阱**
```ts
const fromArr = G[fromKey]; // 假设 fromKey === toKey
const toArr = G[toKey];
G[fromKey] = fromArr.filter(...); // 改了 G[fromKey](也是 G[toKey]
G[toKey] = [...toArr, ...moved]; // toArr 是修改前的旧引用!
```
`fromKey === toKey` 时,`toArr` 仍指向过滤前的原数组,所以 `[...toArr, ...moved]` 把已经"过滤掉"的牌又加回来。
具体场景:桌子有 `[2♠]`,拖 2♠ 又到桌:
- `fromArr = toArr = [2♠]`
- `moved = [2♠]`
- `G.table = [2♠].filter(!idSet.has) = []`
- `G.table = [...[2♠], 2♠] = [2♠, 2♠]` ❌ 重复
### Bug 4DragTest 拖不回手牌
**根因**`hand` zone 没配 `dropMove`,所以 drop 处理器什么都不做。
## 怎么解决
### 修复 1去掉 War zone 的 owner
```ts
ui: {
zones: [
{ id: 'p0-deck', type: 'pile', position: { x: 100, y: 50 }, faceDown: true, collection: 'p0Deck' },
{ id: 'p1-deck', type: 'pile', position: { x: 700, y: 50 }, faceDown: true, collection: 'p1Deck' },
// 关键pile 用 type='discard',渲染时强制 face-up
{ id: 'p0-pile', type: 'discard', position: { x: 300, y: 300 }, collection: 'p0Pile' },
{ id: 'p1-pile', type: 'discard', position: { x: 500, y: 300 }, collection: 'p1Pile' },
],
actions: [
{ label: '翻牌', move: 'flip' },
{ label: '收牌', move: 'collect' },
],
}
```
### 修复 2拆 flip → flip + collect
```ts
moves: {
flip: ({ G }) => {
if (G.winner || !G.p0Deck.length || !G.p1Deck.length) return;
// 翻牌到 pile记录 lastWinner但不收
G.p0Pile.push(p0CardId);
G.p1Pile.push(p1CardId);
G.lastWinner = ...;
},
collect: ({ G }) => {
// 按 lastWinner 把 pile 收给赢家
if (G.lastWinner === '0') G.p0Deck = [...G.p0Deck, ...shuffle([...G.p0Pile, ...G.p1Pile])];
G.p0Pile = [];
G.p1Pile = [];
G.lastWinner = null;
},
}
```
UI 上两个按钮:翻牌 → 收牌 → 翻牌 ... 循环。
### 修复 3transferCards 同 zone 直接 return
```ts
moves: {
transferCards: ({ G }, cardIds, fromZoneId, toZoneId) => {
if (fromZoneId === toZoneId) return; // no-op
// ... 原有逻辑
},
}
```
### 修复 4hand zone 加 dropMove
```ts
{
id: 'hand',
type: 'hand',
position: { x: 250, y: 500 },
owner: 'self',
collection: 'hand',
layout: { fan: 0, spacing: 70 },
dropMove: 'transferCards', // ← 新增
dropArgs: (cardIds, fromZoneId, toZoneId) => [cardIds, fromZoneId, toZoneId],
},
```
## 验证
Playwright 端到端:
- War 翻牌4 张牌在桌上可见 → 翻牌 → pile 各 1 张 face-upK♠ / 8♦→ 收牌 → p0 deck 25→27
- DragTest 桌→桌:拖 2♠ 到桌hand=4, table=1→ 拖 2♠ 又到桌(仍然 hand=4, table=1不重复
- DragTest 桌→手:拖 2♠ 回手牌hand=5, table=0
## 成果
- Commit `e9a7737 fix(war,drag-test): public piles, two-button UX, same-zone no-op`
## 关联
- [05-drag-and-drop.md](./05-drag-and-drop.md) — 拖拽实现(产生 bug 的源头)
- [06-three-games.md](./06-three-games.md) — 三个游戏bug 暴露的场景)
- [lessons-learned.md §19-22](./lessons-learned.md) — War 拆 move / Immer 同 key 的详细内容