# Double Buffer Architecture Research v18
## Focus: v1/v2 失敗的精準診斷方案 + v6 實作藍圖

---

## v1 vs v4 的本質差異

只有一個：**誰管理 buffer，誰做 screen write**

| | v4 (成功) | v1/v6 (目標) |
|---|-----------|-------------|
| Buffer 建立者 | PaintText (s_hdcPaintBuf) | WndProc (g_hSharedBuf) |
| PaintText 畫到 | 自己的 buffer | WndProc 的 buffer |
| Screen capture | PaintText (每幀) | WndProc (首次/resize) |
| Screen write | ScanAndDrawUda | WndProc |
| Caret 管理 | ScanAndDrawUda | WndProc |
| HideCaret | ScanAndDrawUda | WndProc |

## 為什麼「誰管理」會影響結果？

### PaintText 管理 (v4 成功)
```
PaintText:
  BeginPaint → ps
  hdcScreen = GetDC
  BitBlt(自己的 buffer ← hdcScreen)   ← PaintText 控制 capture 時機
  SelectObject(自己的 buffer, hFont)   ← PaintText 控制 font
  TextOut → 自己的 buffer              ← PaintText 控制繪製
  g_hPaintBuf = 自己的 buffer          ← 匯出給 ScanAndDrawUda
  ReleaseDC + EndPaint                 ← PaintText 清理
```

### WndProc 管理 (v1 失敗)
```
WndProc:
  建 g_hSharedBuf                      ← WndProc 建 buffer
  g_hPaintDC = g_hSharedBuf            ← 通知 PaintText

PaintText:
  BeginPaint → ps
  hdcScreen = GetDC
  BitBlt(g_hPaintDC ← hdcScreen)       ← PaintText 用 WndProc 的 buffer
  ★ SelectObject(g_hPaintDC, hFont)    ← font 選入 WndProc 的 buffer — 這步是否執行？
  TextOut → g_hPaintDC                  ← 畫到 WndProc 的 buffer
  ★ 跳過 final BitBlt                  ← 由 bUseMemDC 控制
  ReleaseDC + EndPaint
```

### ★ 關鍵問題：SelectObject 有沒有正確執行？

在 v1 的 PaintText 中：
```c
hdc = g_hPaintDC;  // 使用 WndProc 的 buffer
bUseMemDC = 2;     // 跳過 final BitBlt

// ... later (line 3265) ...
#if defined( UNICODE )
   SelectObject( hdc, pWVT->hFont );  // hdc = g_hPaintDC → ★ 正確！
#endif
   SetTextAlign( hdc, TA_LEFT );      // ★ 正確！
```

**SelectObject 確實在 g_hPaintDC 上執行了。** font 被選入 WndProc 的 buffer。

### 那 TextOut 呢？

```c
hb_gt_wvt_TextOut( pWVT, hdc, startCol, iRow, iOldColor, pWVT->TextLine, len );
// hdc = g_hPaintDC → 畫到 WndProc 的 buffer → 應該正確
```

**TextOut 也在 g_hPaintDC 上執行。應該正確。**

---

## 新假說：v1 的 capture 位置錯誤

### v4 的 capture 位置
```c
// 在 PaintText 內部，BeginPaint 之後
BeginPaint( pWVT->hWnd, &ps );         // 驗證 update region
hdcScreen = GetDC( pWVT->hWnd );       // 取得 screen DC
BitBlt( s_hdcPaintBuf ← hdcScreen );   // ★ capture AFTER BeginPaint
```

### v1 的 capture 位置
```c
// 也在 PaintText 內部（v1 code 也走 PaintText）
BeginPaint( pWVT->hWnd, &ps );
hdcScreen = GetDC( pWVT->hWnd );
BitBlt( g_hPaintDC ← hdcScreen );      // ★ 一樣 AFTER BeginPaint
```

**capture 位置相同。** 不是 capture 的問題。

---

## 新假說：v1 的 WndProc BitBlt 與 EndPaint 衝突

### v4 的螢幕寫入
```
PaintText:
  EndPaint  ← 驗證 update region（caret 可能被 show）
  
ScanAndDrawUda:
  HideCaret ← 再次隱藏
  BitBlt(screen ← buffer)  ← 螢幕寫入（覆蓋 caret）
  CreateCaret + ShowCaret   ← 顯示新 caret
  ReleaseDC
```

### v1 的螢幕寫入
```
PaintText:
  EndPaint  ← 驗證 update region（caret show）
  return

WndProc:
  ScanAndDrawUda（計算 caret 參數，不操作 caret）
  ★ GetDC
  ★ BitBlt(screen ← g_hSharedBuf)  ← 螢幕寫入（覆蓋 EndPaint 的 caret）
  ★ CreateCaret + ShowCaret
  ★ ReleaseDC
```

**差異：v1 的 GetDC/BitBlt/ReleaseDC 在 WndProc 裡（WM_PAINT handler 內）。**
**v4 的 GetDC/BitBlt/ReleaseDC 在 ScanAndDrawUda 裡（也在 WM_PAINT handler 內）。**

**結構上是相同的！** 都在 WM_PAINT handler 返回之前做螢幕寫入。

---

## ★★★ 最終假說：v1 根本沒有無限循環 ★★★

如果 SelectObject/TextOut 都正確，capture 位置也正確，
螢幕寫入時序也相同...

**v1 的「無法輸入」可能不是循環，而是：**

### 1. 畫面空白 — 使用者看不到輸入
g_hSharedBuf 的初始化可能有問題：
- v1 WndProc 建 buffer 時做 `BitBlt(buffer ← screen)` 全螢幕 capture
- 但這是在 CallWindowProcW 之前（BeginPaint 之前）
- 此時 screen 可能有 stale content
- ★ 或 buffer 建好後，PaintText 的 BeginPaint 清除了 update region
- PaintText 重繪 dirty area → 只覆蓋部分
- WndProc BitBlt 全螢幕寫回 → 包含未被 PaintText 覆蓋的 stale 區域

### 2. WndProc BitBlt 全螢幕 vs dirty region
v1：`BitBlt(screen ← buffer, 0, 0, fullW, fullH)` — 全螢幕
v4：`BitBlt(screen ← buffer, dirty rows)` — 只 dirty rows

**v1 全螢幕 BitBlt 太慢？ 不太可能（一次 BitBlt < 1ms）。**

### 3. ★ 登入畫面的特殊狀態
使用者說「登入畫面正常顯示但無法輸入」。
- 登入畫面用 AskPassWord → GET/READ
- GET/READ 在 GETSYS.PRG 裡
- InKey() 等待輸入 → ProcessMessages → WM_PAINT
- 如果 WM_PAINT 很快返回，InKey() 繼續等待
- 使用者按鍵 → InKey 返回 → 處理
- **如果按鍵不被處理... 可能是鍵盤事件被消耗了**

### ★★ 假說：WndProc 的 GetDC/BitBlt 消耗了鍵盤事件 ★★

不可能。GetDC/BitBlt 是 GDI 操作，不消耗訊息。

### ★★ 假說：WM_PAINT 處理時間過長，鍵盤事件被延遲 ★★

v1 WndProc 做的事比 v4 多：
- 建 buffer（每次檢查大小）
- 設 g_hPaintDC
- HideCaret
- CallWindowProcW (PaintText)
- ScanAndDrawUda
- GetDC + BitBlt + ReleaseDC（額外）
- CreateCaret + ShowCaret（額外）
- 清理 g_hPaintDC

v4 WndProc 做的事：
- g_bSharedPaint = 1
- CallWindowProcW (PaintText)
- ScanAndDrawUda（含螢幕寫入 + caret）
- g_bSharedPaint = 0

**v1 的額外操作：buffer 建立 + GetDC/BitBlt/ReleaseDC + HideCaret/CreateCaret/ShowCaret**

這些操作加起來可能 < 2ms。不應該造成「無法輸入」。

---

## ★ 唯一可行的驗證方法

**用 debug log 確認 v1 是否真的有無限循環。**

```c
// v1 WndProc WM_PAINT 入口：
static int s_nPaintCount = 0;
s_nPaintCount++;
WriteDebugLogC("V1 PAINT #" + s_nPaintCount);
if( s_nPaintCount > 1000 )
{
   // Force break
   s_bInsidePaint = 0;
   return 0;
}
```

如果 log 顯示 paint count 持續增長（> 1000/秒）→ 確認循環。
如果 paint count 正常（< 100/秒）→ 不是循環，找其他原因。

---

## v6 實作藍圖

### Phase 1：診斷 v1 失敗
1. 重新套用 v1 code（從 bak_dblbuf_20260430_1200 重建）
2. 加 paint counter debug
3. 測試，看 log
4. 確認是循環還是其他原因

### Phase 2：根據診斷結果修正
- 如果循環 → 找 InvalidateRect 來源，在正確位置阻斷
- 如果 font → 確保 SelectObject 在 g_hPaintDC 上
- 如果其他 → 根據 log 判斷

### Phase 3：v6 穩定化
- 通過所有測試（登入、選單、BUYSELL、QUOT、F4、resize、Alt+Tab）
- 備份為 v6 OK

### Phase 4：清理
- 移除 s_hdcPaintBuf（不再需要）
- 移除 s_hdcDblBuf（已不使用）
- 移除 g_bSharedPaint / g_hPaintBuf（被 g_hPaintDC 取代）
- 最簡架構

---

## 結論

**v1/v2「無法輸入」的根因尚未確認。** v8 的 ShowCaret 循環假說可能錯誤（ShowCaret 用 XOR 不觸發 WM_PAINT）。需要實際 debug 驗證。

**v6 的實作路徑清晰：先診斷 v1，再修正，再清理。** 估計 2-3 小時的集中工作。
