# Double Buffer Architecture Research v17
## Focus: v6 理想架構 — 一個共用 buffer，從 v8 的 ShowCaret 發現出發

---

## v6 目標

```
目前 v4:                              v6 目標:
2 個 buffer (s_hdcPaintBuf 使用中)     1 個 buffer (g_hSharedBuf)
PaintText 管理自己的 buffer            WndProc 管理共用 buffer
PaintText capture screen → buffer     無 capture（buffer 持久累積）
PaintText TextOut → buffer            PaintText TextOut → g_hSharedBuf
ScanAndDrawUda UDA → buffer           ScanAndDrawUda UDA → g_hSharedBuf
ScanAndDrawUda writeback → screen     WndProc writeback → screen
2 次螢幕操作 (1R + 1W)                1 次螢幕操作 (1W only)
```

## v1/v2 失敗回顧（v8 Research）

v1/v2 架構 = v6 的目標架構。失敗原因：

```
WM_PAINT handler:
  g_hPaintDC = g_hSharedBuf
  CallWindowProcW → PaintText → TextOut to g_hSharedBuf
  ScanAndDrawUda → UDA to g_hSharedBuf
  GetDC + BitBlt(screen ← g_hSharedBuf) + ReleaseDC    ← 螢幕寫入
  DestroyCaret + CreateCaret + ShowCaret                 ← ★ ShowCaret!
  g_hPaintDC = NULL
  s_bInsidePaint = 0                                     ← ★ 清除 guard
  return
  
→ ShowCaret 觸發 InvalidateRect(caret area)
→ s_bInsidePaint = 0 → 新 WM_PAINT 進入 hook
→ 又 ShowCaret → 又 InvalidateRect → 無限循環
→ 「無法輸入」
```

## v6 修正方案：ShowCaret 在 s_bInsidePaint=1 期間完成

```
WM_PAINT handler (v6):
  s_bInsidePaint = 1
  g_hPaintDC = g_hSharedBuf
  
  HideCaret(hWnd)
  CallWindowProcW → PaintText
  ScanAndDrawUda（只計算 caret 參數，不操作 caret）
  
  GetDC + BitBlt(screen ← g_hSharedBuf, dirty region) + ReleaseDC
  
  // ★ Caret 在 s_bInsidePaint=1 期間完成
  DestroyCaret + CreateCaret + SetCaretPos + ShowCaret
  // ShowCaret 觸發 InvalidateRect → 排入佇列
  // 但 WM_PAINT 不會立即處理（不在 ProcessMessages 裡）
  
  g_hPaintDC = NULL
  s_bInsidePaint = 0
  // ★ 此時佇列中有 ShowCaret 的 WM_PAINT
  return
  
→ DispatchMessage 返回
→ 下一個 PeekMessage 取得 ShowCaret 的 WM_PAINT
→ s_bInsidePaint = 0 → 進入 hook
→ 但 caret area 很小（1-2 cell）
→ PaintText 重繪 1-2 cell → g_hSharedBuf
→ ScanAndDrawUda writeback 1-2 cell → screen
→ ShowCaret → InvalidateRect(caret area)
→ 又新 WM_PAINT...

★★★ 還是循環！★★★
```

## 為什麼 ShowCaret 一定觸發循環？

```
ShowCaret 的行為：
1. 在視窗上顯示 caret
2. Windows 可能 InvalidateRect(caret area) 來繪製 caret
3. InvalidateRect 排入 WM_PAINT
4. WM_PAINT 觸發 PaintText → 重繪 caret 所在行
5. 重繪覆蓋了 caret → Windows 需要重繪 caret
6. → 回到步驟 1
```

## 為什麼 v4 不循環？

```
v4 的 ShowCaret 在 ScanAndDrawUda 內部：
  ScanAndDrawUda:
    ... draw UDA ...
    BitBlt(screen ← buffer, dirty rows)     ← 螢幕寫入
    DestroyCaret + CreateCaret + ShowCaret   ← caret 操作
    ReleaseDC
    return
  
  WndProc:
    s_bInsidePaint = 0
    return

→ ShowCaret 觸發 InvalidateRect
→ 新 WM_PAINT 進入 hook（s_bInsidePaint=0）
→ PaintText capture screen → buffer（screen 有新 caret）
→ TextOut → buffer（覆蓋 caret 位置的 cell）
→ ScanAndDrawUda:
   → BitBlt(screen ← buffer)
   → ShowCaret → InvalidateRect
→ 又新 WM_PAINT...

★★★ v4 也應該循環！但實際上不循環。為什麼？★★★
```

## ★★★ 關鍵發現 ★★★

### Windows 的 ShowCaret 不一定觸發 InvalidateRect！

查 Windows 文件：
- ShowCaret 只是「允許 caret 被顯示」（遞減 hide 計數器）
- 當 hide 計數器到 0 時，caret 變為可見
- Windows 用 **XOR 繪製** caret（不觸發 WM_PAINT）
- caret 是在現有螢幕內容上 XOR 疊加，不需要 InvalidateRect
- WM_PAINT 只在 caret **大小/位置改變**時可能觸發

### 那 CreateCaret 呢？

- CreateCaret 建立新 caret（可能銷毀舊的）
- 如果大小/位置改變，Windows 需要清除舊 caret + 繪製新 caret
- 清除舊 caret = XOR 舊位置（恢復原內容）
- 繪製新 caret = XOR 新位置
- **仍然是 XOR 操作，不觸發 WM_PAINT**

### 那 v1/v2 為什麼「無法輸入」？

如果 ShowCaret 不觸發 WM_PAINT，v1/v2 不應該循環。
「無法輸入」的原因可能不是 ShowCaret 循環。

### 重新審視 v1/v2 失敗

可能原因：
1. **PaintText 的 BeginPaint 內部觸發了什麼** — BeginPaint 驗證 update region。如果 WndProc 的 BitBlt 寫螢幕後立即有新的 InvalidateRect（非來自 ShowCaret），循環就可能發生。

2. **WndProc 的 GetDC + BitBlt 覆蓋了 BeginPaint 的已驗證區域** — EndPaint 已經清除 update region。WndProc 的 BitBlt 寫螢幕不應該產生新 update region。

3. **★ PaintText 的 EndPaint 與 WndProc 的 BitBlt 的時序** — EndPaint 可能讓 Windows 重繪 caret。然後 WndProc 的 BitBlt 覆蓋了 caret。Windows 偵測到 caret 被覆蓋 → InvalidateRect caret area → WM_PAINT → 循環。

4. **★★ 可能完全不是循環！** 可能是 PaintText 用 g_hPaintDC（WndProc 的新 buffer）時，某些 GDI 狀態未設定（如 font 未 SelectObject）→ TextOut 失敗 → 畫面空白 → 使用者以為「無法輸入」但其實是「看不到輸入」。

---

## v6 需要驗證的假設

在實作 v6 之前，必須確認：

### 驗證 1：v1/v2 是否真的「無限循環」
方法：在 v1 的 WndProc WM_PAINT 入口加計數器
```c
static int s_nPaintCount = 0;
s_nPaintCount++;
if( s_nPaintCount > 100 )
{
   WriteDebugLogC("PAINT LOOP DETECTED!");
   // force exit
}
```

### 驗證 2：g_hPaintDC 的 font 是否有效
方法：在 PaintText 使用 g_hPaintDC 前確認 font 已選入
```c
if( g_hPaintDC )
{
   SelectObject( g_hPaintDC, pWVT->hFont );  // 確保 font
   SetTextAlign( g_hPaintDC, TA_LEFT );       // 確保 align
}
```

### 驗證 3：PaintText 的 margin 填充是否在 g_hPaintDC 上正確
方法：maximized 視窗測試

---

## v6 實作步驟（待驗證後）

### Step A：在 v1 code 加 paint counter + font setup
- 確認不是無限循環
- 確認 font 有效

### Step B：如果是循環 → 找出 InvalidateRect 來源
- 在 WndProc 加 GetUpdateRect log

### Step C：如果不是循環 → 找出 TextOut 失敗原因
- 在 PaintText 加 TextOut 返回值 log

### Step D：修正後測試
- 登入、選單、BUYSELL、QUOT、F4、resize、Alt+Tab

---

## 結論

**v6 的實作需要先解決 v1/v2 失敗的確切原因。** 
v8 Research 的 ShowCaret 循環假說需要驗證。
可能是循環，也可能是 font 未選入的簡單問題。

**下一步：回到 v1 code，加 debug 驗證。**
