# Double Buffer Architecture Research v12
## Focus: v5 跳過 capture — 逐幀模擬驗證

---

## 模擬場景：使用者在 BUYSELL 表身輸入一個字元 'A'

### Frame 0（穩定狀態，buffer 有上一幀完整內容）

```
buffer 內容：[row 0-24 完整 ASCII + UDA]
螢幕內容：  [與 buffer 相同]
```

### 使用者按 'A' → Harbour 處理

```
1. InKey() 返回 'A'
2. GETSYS GetApplyKey 處理 → oGet:Insert('A') 或 oGet:Overstrike('A')
3. oGet:display() → 更新 Harbour screen buffer 的 1-2 個 cell
4. Harbour GT: prevBuffer vs screenBuffer → cell 變了 → 呼叫 Redraw
5. Redraw → InvalidateRect(遊標所在的 1-2 cell 範圍)
6. ProcessMessages → WM_PAINT
```

### Frame 1（WM_PAINT for 'A' 按鍵）

```
ps.rcPaint = [遊標行的部分區域，例如 row 9, col 30-32]

WndProc:
  g_bSharedPaint = 1
  
PaintText:
  BeginPaint → ps.rcPaint = {col30*cellW, row9*cellH, col32*cellW, row10*cellH}
  
  ★ 目前：BitBlt(buffer ← screen, ps.rcPaint 區域)
  ★ v5：  跳過這步（buffer 已有 row9 的上一幀內容）
  
  rcRect = {row9, col30, row9, col32}（只重繪這幾個 column）
  TextOut → buffer（在 col30-32 寫入新的 'A' 和周圍字元）
  
  g_hPaintBuf = buffer
  EndPaint
  
ScanAndDrawUda:
  dirty rows = [row 9]
  pre-scan: row 9 有 UDA 嗎？
    → 如果沒有 UDA → 早期返回 + writeback(buffer → screen, row 9)
    → 如果有 UDA → 畫 UDA 到 buffer → writeback(buffer → screen, row 9)
```

### v5 跳過 capture 的影響

**buffer 在 Frame 0 結束時的內容：**
- row 9 的所有 cell 都有正確內容（上一幀的 ASCII + UDA writeback）

**v5 跳過 capture 後，PaintText 只重繪 col 30-32：**
- col 30-32：新的 'A' 寫入（TextOut 覆蓋）
- col 0-29, col 33-79：保留 Frame 0 的內容（正確）

**ScanAndDrawUda 的 writeback：**
- 從 buffer 寫回 row 9 整行到螢幕
- row 9 的內容 = 上一幀的 + 新的 'A' = 正確

**結論：v5 在此場景下是安全的。**

---

## 模擬場景：遊標移動到下一行

### Frame 0
```
buffer: row 9 有遊標（Caret），row 10 無遊標
螢幕: 同 buffer
```

### 使用者按 ↓

```
1. InKey 返回 K_DOWN
2. GETSYS 移動遊標到 row 10
3. Harbour GT: row 9 和 row 10 的 cell 可能都變了（色彩屬性改變）
4. Redraw: row 9 + row 10 都被 InvalidateRect
5. WM_PAINT: ps.rcPaint 覆蓋 row 9-10
```

### Frame 1
```
PaintText:
  ★ v5：跳過 capture
  rcRect = {row 9, col 0, row 10, col 79}（兩整行）
  TextOut → buffer（row 9 和 row 10 的所有 cell）
  → buffer 的 row 9-10 被完全覆蓋為新內容

ScanAndDrawUda:
  dirty rows = [9, 10]
  writeback: buffer row 9-10 → screen
```

**v5 影響：無。TextOut 重繪了完整的 row 9-10，覆蓋了 buffer 中的舊內容。**

---

## 模擬場景：SaveScreen / RestScreen

### Frame 0（FitList 彈出前）
```
buffer: BUYSELL 的完整畫面
螢幕: 同 buffer
```

### F4 觸發 FitList → SaveScreen → 畫 FitList

```
1. SaveScreen 保存螢幕內容
2. FitList 畫框 + ArrayBrowse → 多次 WM_PAINT
3. 每次 WM_PAINT：
   - PaintText capture → buffer（如果 v5 跳過，buffer 保留上一幀的 BUYSELL 內容！）
   - TextOut → buffer（FitList 的新內容覆蓋了部分 row）
   - ScanAndDrawUda → writeback
   
   ★ 問題：v5 跳過 capture 時，buffer 的未被 TextOut 覆蓋的區域
            仍然是 BUYSELL 的內容，不是 FitList 框下面的空白

   ★ 但是：FitList 的 @ SAY / BOX / CLEAR TO 會覆蓋所有 FitList 區域
            ps.rcPaint 會包含所有被 FitList 修改的區域
            TextOut 會重繪這些區域
```

### FitList 離開 → RestScreen

```
1. RestScreen 寫回保存的螢幕內容
2. 所有 cell 都變了（RestScreen 覆蓋整個保存區域）
3. Harbour GT: 全部 cell dirty → Redraw → InvalidateRect 全區域
4. WM_PAINT: ps.rcPaint = 全區域
5. PaintText:
   - ★ v5：跳過 capture（buffer 有 FitList 的殘留內容）
   - TextOut → buffer（BUYSELL 的內容覆蓋整個區域）
   - buffer 被完全覆蓋 → 正確
```

**結論：RestScreen 場景安全。全區域 dirty → TextOut 完全覆蓋 buffer。**

---

## 模擬場景：★ 危險情境 — 部分行 dirty，UDA 殘留

### Frame 0
```
buffer: row 5 有 UDA 字元（造字）在 col 10-11
螢幕: 同 buffer
```

### Harbour 更新 row 5 col 0-5（只改了前幾個 cell）

```
1. Redraw: row 5, col 0-5 dirty
2. WM_PAINT: ps.rcPaint = row 5 的 col 0-5 像素範圍
```

### Frame 1
```
PaintText:
  rcRect = {row 5, col 0, row 5, col 5}（只重繪 col 0-5）
  ★ v5：跳過 capture
  TextOut → buffer 的 col 0-5（新的 ASCII）
  buffer 的 col 6-79 保留 Frame 0 的內容（含 col 10-11 的 UDA）

ScanAndDrawUda:
  dirty rows = [row 5]
  掃描 row 5 所有 cell → 找到 col 10-11 有 UDA
  畫 UDA 到 buffer 的 col 10-11（覆蓋上一幀的 UDA — 但可能是相同字元）
  writeback: buffer row 5 → screen
```

**分析：**
- col 0-5：PaintText 重繪了新 ASCII → 正確
- col 6-9：buffer 保留 Frame 0 的 ASCII → 正確（沒變）
- col 10-11：buffer 有 Frame 0 的 UDA，ScanAndDrawUda 又畫了一次 → 正確
- col 12-79：buffer 保留 Frame 0 的內容 → 正確（沒變）

**但如果 col 10-11 的 UDA 在 Frame 1 變成了普通 ASCII 字元呢？**

```
Harbour 更新了 col 10-11 為 ASCII → 但 Redraw 只標記 col 0-5 dirty
→ ps.rcPaint 不包含 col 10-11
→ PaintText 不重繪 col 10-11
→ buffer 的 col 10-11 仍是 Frame 0 的 UDA 渲染結果
→ ScanAndDrawUda 掃描 Harbour screen buffer → col 10-11 是 ASCII（非 UDA）
→ ScanAndDrawUda 不畫 UDA
→ writeback: buffer col 10-11 = Frame 0 的 UDA 圖像 → 寫到螢幕
→ ★★★ 螢幕顯示舊的 UDA 圖像，但實際資料是 ASCII → 錯誤！★★★
```

### 這個情境會發生嗎？

**關鍵問題：Harbour 更新 col 10-11 時，Redraw 是否被呼叫？**

答案：**是的。** 如果 Harbour screen buffer 的 col 10-11 從 UDA 變成 ASCII，
RedrawDiff 會偵測到變化（character 值改變），呼叫 Redraw → InvalidateRect。

**但** InvalidateRect 的範圍會不會與 col 0-5 的範圍合併？

答案：**是的。** Windows 會合併多個 InvalidateRect 為一個 union rect。
所以 ps.rcPaint 會包含 col 0-11（或更大），PaintText 會重繪 col 0-11。

**結論：這個危險情境不會發生。** Harbour 的 RedrawDiff 會偵測所有變化的 cell，
InvalidateRect 會合併所有 dirty 區域。PaintText 會重繪整個合併後的區域。

---

## 唯一真正的危險情境

**CJK trail marker 不觸發 Redraw 的情境（v2 Research 的 Issue #3）：**

如果 UDA lead 變了但 trail marker（0xFFFE）沒變，Harbour 不呼叫 Redraw for trail cell。
但 lead cell 的 Redraw 已經擴展 InvalidateRect 包含 trail cell（GTWVT CJK 擴展邏輯）。
所以 PaintText 仍會重繪 trail cell。

**真正的風險：buffer 中殘留的 UDA 渲染結果。**

但 PaintText 用 ETO_OPAQUE ExtTextOut，會填充背景色 → 完全覆蓋舊的 UDA。

**結論：v5 在所有已知場景下都是安全的。**

---

## 最終結論

| 場景 | capture 必要？ | v5 安全？ |
|------|---------------|----------|
| 正常按鍵（部分 cell dirty） | ❌ | ✅ |
| 遊標移動（整行 dirty） | ❌ | ✅ |
| SaveScreen/RestScreen | ❌ | ✅（全區域重繪） |
| FitList 彈出/關閉 | ❌ | ✅（全區域重繪） |
| UDA → ASCII 轉換 | ❌ | ✅（Redraw 擴展覆蓋） |
| Buffer 首次建立 | ✅ 需要 | ✅（g_bBufferValid=0） |
| Resize | ✅ 需要 | ✅（g_bBufferValid=0） |
| WM_DISPLAYCHANGE | ✅ 需要 | ✅（g_bBufferValid=0） |

**v5 可以安全實作。**
