8 月 5 日,我詢問 AI 一個問題:
長休按「跳過此步驟」後只蓋得住一部分螢幕,短休正常
這是我遇過最奇怪的 bug,而且它會發生在我每一次長休息。
Pomofocus 的休息視窗要蓋住所有螢幕:主螢幕顯示引導內容,其他螢幕鋪一層遮罩。
長休息的時候,如果我在「走動」那一步按跳過,畫面就只剩一部分被蓋住。
難的地方在於:從程式的角度看,一切正常。
節點的 visible 是 true,位置是對的,尺寸是對的,測試也全過。用無畫面模式跑自動測試,看不出任何異狀。
交接文件 12.24 記錄的診斷:
_layout_fullscreen()每次都無條件重寫borderless/always_on_top/unfocusable。Godot 的unfocusablesetter →_update_window_style()會重算 Win32 style,而 borderless 視窗重算出來的 style 不含WS_VISIBLE;borderless的 setter 後面有補一次ShowWindow(),unfocusable沒有 → 對已顯示的無邊框子視窗重寫同一個unfocusable值,視窗就在 OS 層被隱藏,但節點的visible仍是true。
拆開來說:
unfocusable 這個旗標再寫一次(即使值沒變)。borderless 的路徑後面有補一行「顯示視窗」把它救回來,但設定 unfocusable 的路徑沒有。修法只有一句話:只在值真的要改變時才寫入。
只有長休會中招:
_on_step_done裡key == "walk"是唯一會在休息途中重跑_apply_intensity()的路徑,而walk只存在於LONG_PLAN。使用者的長休設定為 1 分鐘 → 排程 = 望遠 20s + 走動 40s,而望遠鎖定期正好 20 秒,所以「跳過此步驟」實際上只有在走動卡片上按得動,每次都踩中。
只有「走動」這個步驟會在中途重跑版面,而走動只有長休息才有。再加上我自己把長休設成 1 分鐘,導致望遠鎖定一結束就剛好輪到走動——所以我每次長休都會踩到,短休則完全不會。
三個條件疊在一起,症狀才出現。這種 bug 如果沒有系統性地查,只會得到「有時候怪怪的」這種結論。
因為節點層看不出問題,驗證方式只能從外面看:
跑一次性 SceneTree 腳本走完長休流程,同時用 PowerShell + Win32
GetTopWindow/GetWindow(GW_HWNDNEXT)/IsWindowVisible每 1.3 秒列舉 class 為Engine的視窗(桌面截圖不行,GDI BitBlt 抓不到 Godot 的 GL 視窗)。修正前:跳過走動的那一秒起休息視窗vis=False且持續不恢復;修正後:三顆螢幕全程vis=True。
直接問作業系統「這個視窗現在可見嗎」,每 1.3 秒問一次,把結果印出來。修正前後的差別一眼就看得出來。
而且順便發現「截圖驗證」這條路走不通——Windows 的舊式截圖 API 抓不到 Godot 的畫面。
走動期間也蓋住所有螢幕:原本按「我出發了」之後,視窗會縮成一個小卡片,所有螢幕的遮罩都拿掉——結果就是我起來走動的時候,螢幕又是亮的、內容又看得見。改成視窗幾何完全不動,只把內容縮成右下角的小卡。
遮罩改成重複使用:原本每次還原版面都把遮罩視窗砍掉重建。這不是 bug 的原因,但每次走動結束都銷毀再重建幾個原生視窗是白工,改成建立一次之後只更新位置和可見性。
一、只在值真的要改變時才寫入。
# 有副作用的 setter,別無條件重寫
if window_flag != new_value:
set_window_flag(new_value)
看起來像微不足道的最佳化,實際上它是這個 bug 的完整解法。凡是「寫入會觸發重算」的東西——視窗旗標、樣式、訂閱、監聽器——重寫同一個值都可能有副作用。這條規則的成本是一個 if,收益是一整類 bug 不會發生。
二、跨到 OS 層的功能,要從外面驗證。
節點說 visible = true 只代表程式這麼認為。真正的問題是作業系統怎麼想。
所以驗證要從外部問系統:
IsWindowVisible、列舉 z-order我這次是每 1.3 秒列舉一次視窗,修正前「跳過走動的那一秒起 vis=False 且不再恢復」,修正後「三顆螢幕全程 vis=True」。這種輸出沒有模糊空間。
**驗證腳本寫完就刪,但把方法寫進文件。**下次再遇到同類症狀,要的不是那支腳本,而是「原來可以這樣驗」這件事。
明天講這個 bug 的延伸問題:當自動測試根本看不到畫面時,要怎麼確認一個「畫面有沒有蓋住」的功能是對的。