# Double Buffer Architecture Research v13
## Focus: v5 跳過 capture — 邊界情境壓力測試

---

## 壓力測試 1：快速連續按鍵

```
使用者快速輸入 "ABCDE"（每 50ms 一個字）

Frame 1: 'A' → dirty col 30 → TextOut col 30 → writeback row 9
Frame 2: 'B' → dirty col 31 → TextOut col 31 → writeback row 9
Frame 3: 'C' → dirty col 32 → TextOut col 32 → writeback row 9
...

每一幀：
- v5 跳過 capture（buffer 保留上一幀）
- TextOut 只覆蓋 dirty column
- writeback 寫整行
```

**問題：如果 Frame 2 的 WM_PAINT 在 Frame 1 完全處理完之前觸發？**

答案：不會。s_bInsidePaint 防止重入。Frame 1 完成後才處理 Frame 2。
如果多個 InvalidateRect 在 Frame 1 期間累積，Windows 合併為一個 WM_PAINT，
ps.rcPaint 包含所有累積的 dirty area。

**v5 安全。**

---

## 壓力測試 2：Scroll 操作（表身捲動）

```
BUYSELL 表身 PgDn → Scroll(nBeginRow, 0, nBeginRow+9, 79, 1)

Harbour Scroll 操作：
1. 螢幕 buffer：row 8-16 的內容上移一行
2. row 8 = 原 row 9，row 9 = 原 row 10，...
3. row 16 = 空行
4. Redraw：所有受影響的行
5. WM_PAINT：ps.rcPaint 覆蓋 row 8-16
```

### v5 分析

```
PaintText:
- 跳過 capture（buffer 有上一幀的 row 8-16 內容）
- TextOut 重繪 row 8-16 所有 cell（因為 ps.rcPaint 覆蓋全部）
- buffer 的 row 8-16 被新內容覆蓋

ScanAndDrawUda:
- dirty rows = 8-16
- 掃描 + 畫 UDA
- writeback row 8-16 → screen
```

**buffer 中 row 8-16 的舊內容（上一幀）被 TextOut 完全覆蓋。v5 安全。**

---

## 壓力測試 3：對話框覆蓋（MessageBox）

```
MessageBox 出現 → 覆蓋部分螢幕
MessageBox 關閉 → Windows 送 WM_PAINT 恢復被覆蓋區域
```

### v5 分析

```
WM_PAINT:
- ps.rcPaint = MessageBox 覆蓋的區域
- PaintText 跳過 capture
- buffer 仍有 MessageBox 出現「之前」的內容（正確的 BUYSELL 畫面）
- TextOut 重繪 ps.rcPaint 範圍的 cell
- ScanAndDrawUda writeback → screen

★ 但 MessageBox 期間，buffer 沒被更新。MessageBox 畫面不在 buffer 中。
  這不是問題 — MessageBox 有自己的繪製邏輯，不走 PaintText。
  MessageBox 關閉後，WM_PAINT 重繪 BUYSELL 的內容 — buffer 有正確內容。
```

**v5 安全。**

---

## 壓力測試 4：Alt+Tab 切換

```
1. 使用者 Alt+Tab 到其他程式
2. 其他程式覆蓋 GTWVT 視窗
3. 使用者 Alt+Tab 回來
4. Windows 送 WM_PAINT 恢復覆蓋區域
```

### v5 分析

```
buffer 在 Alt+Tab 期間：完整的 BUYSELL 畫面（memory DC，不受螢幕影響）
WM_PAINT:
- ps.rcPaint = 被覆蓋的區域（可能是全螢幕）
- PaintText 跳過 capture（buffer 有效）
- TextOut 重繪 dirty rows（可能全部 25 行）
- ScanAndDrawUda writeback

★ TextOut 只重繪 Harbour screen buffer 中的內容。
  如果 Harbour screen buffer 沒變（使用者沒操作），TextOut 畫的內容 = buffer 中已有的內容。
  效果等同於直接 writeback buffer → screen（跳過 TextOut 也可以）。
```

**v5 安全。甚至可以更進一步：如果 buffer valid 且 Harbour screen buffer 沒變，
整個 PaintText 都可以跳過，直接 writeback。（v6 優化方向）**

---

## 壓力測試 5：★ 視窗部分被遮蔽又移開

```
1. 另一個視窗覆蓋 GTWVT 的右半邊（col 40-79）
2. 使用者在 GET 輸入（col 10）→ Harbour 更新 col 10
3. Redraw: col 10 dirty → InvalidateRect
4. WM_PAINT: ps.rcPaint = col 10 的像素區域
   ★ 注意：col 40-79 仍被遮蔽，Windows 不會在 ps.rcPaint 中包含被遮蔽的區域
5. PaintText 只重繪 col 10 → buffer 更新 col 10
6. writeback: 只寫 row 的 dirty 部分到螢幕
   ★ 但 col 40-79 在螢幕上被其他視窗遮蔽，寫了也看不到

7. 其他視窗移開 → Windows 送 WM_PAINT for col 40-79
8. ps.rcPaint = col 40-79 的像素區域
9. PaintText 跳過 capture → buffer 有 col 40-79 的上一幀內容
10. TextOut 重繪 col 40-79 → buffer 更新
11. writeback → screen → 正確
```

**v5 安全。buffer 的 col 40-79 保留了上一幀的內容（正確），
直到 Windows 要求重繪時 TextOut 覆蓋。**

---

## 壓力測試 6：★ buffer 建立後第一幀

```
程式啟動 → USRFONT 載入 → hook 啟用 → 第一次 WM_PAINT

buffer 狀態：剛由 PaintText 建立，SelectObject(bitmap) 後內容未初始化
g_bBufferValid = 0（初始值）
```

### v5 分析

```
g_bBufferValid == 0 → 不跳過 capture
BitBlt(buffer ← screen) → buffer 取得螢幕內容（正確）
TextOut → buffer
writeback → screen
g_bBufferValid = 1
```

**下一幀起跳過 capture。安全。**

---

## 壓力測試 7：★ Resize

```
使用者拖曳視窗邊框 → WM_SIZE → buffer 重建
g_bBufferValid 需要重設為 0
```

### 重設時機

buffer 重建的程式碼在 PaintText 內部（line 3153-3169）：
```c
if( !s_hdcPaintBuf || rcClient.right != s_nPaintBufW || rcClient.bottom != s_nPaintBufH )
{
   // delete + recreate buffer
   ...
   // ★ 這裡需要設 g_bBufferValid = 0
}
```

**重設 g_bBufferValid 的位置：就在 buffer 重建的 if 區塊內。**

---

## 總結

| 壓力測試 | v5 安全？ | 原因 |
|---------|----------|------|
| 快速連續按鍵 | ✅ | s_bInsidePaint 防重入，InvalidateRect 合併 |
| Scroll 捲動 | ✅ | 全行 dirty → TextOut 完全覆蓋 |
| MessageBox | ✅ | buffer 不受螢幕覆蓋影響 |
| Alt+Tab | ✅ | buffer 有效，TextOut 重繪 |
| 部分遮蔽 | ✅ | Windows 分別處理遮蔽/露出區域 |
| 首次啟動 | ✅ | g_bBufferValid=0 → 不跳過 |
| Resize | ✅ | g_bBufferValid=0 → 不跳過 |

**v5 在所有壓力測試下都是安全的。可以實作。**
