Day 17 講的是 AI 把自己設定的條件當成了「天花板」。
這次遇到的是另一種錯。
AI 是真的相信每一次診斷,而且每一次都能講出一套聽起來合理的原因。
問題是,三次診斷,三次都錯。
最後真正拆穿問題的,是架了一個簡單的對照環境。
《異世界救援》上線後,有使用者拿 iPhone 把網頁版加入主畫面測試,回報了一句:
點完卡片,橫向時,結束回合不能點,要轉成直向才能點。
後來又補了兩句:
我是把網頁存成加入主畫面,會沒有網址列,目前會有問題
我剛試了一下用 siri 直接開,有網址列,是正常的
比對截圖後,才看清楚實際發生了什麼。
使用者點的是「結束回合」,但回合沒有結束,反而打出了它正上方的那張手牌。
能量減一、棄牌堆加一,日誌也停在同一回合。
看起來就像是整個觸控座標往上偏了。
問題開始了。
到底是哪一層出了問題?
第一個念頭是戰鬥日誌的 LogLabel 使用了 fit_content。
會不會是日誌內容變長之後,把上面的 VBox 撐高,最後把按鈕擠出原本的位置?
這個假設其實很好驗證。
直接量按鈕的座標。
實測結果是按鈕落在 y 632 到 676 之間。就算把日誌灌到四行長句,按鈕的位置也沒有被往下推。
這個診斷在量出數字的那一刻,就被打臉了。
所以問題不是日誌把版面撐開。
那會不會是有其他東西蓋住按鈕?
第二個念頭是魔王立繪。
如果立繪節點疊在按鈕上面,而且 mouse_filter 沒有設成 IGNORE,觸控事件就可能被上層節點吃掉。
這次一樣沒有只靠猜。
把所有跟按鈕矩形重疊,而且 mouse_filter 不是 IGNORE 的節點掃過一次。
結果只有根節點和按鈕自己。
魔王立繪那一層的 IGNORE 也確定有正常生效。
所以第二個診斷也被排除了。
這時候我開始發現,前兩次其實有一個共同問題:
都是先有一個看起來合理的原因,再去找資料驗證。
第三次,我換一個方式。
不要再猜是哪個 Godot 節點有問題。
讓裝置自己回答。
我在 HTML 裡加了一塊固定位置的除錯覆蓋層,用綠字即時顯示:
innerWidth / innerHeight
visualViewport
scrollY
getBoundingClientRect()
這次不再猜「是不是哪個節點蓋住了」。
直接看數字。
結果是:
| Safari(正常) | standalone(壞) | |
|---|---|---|
navigator.standalone |
false | true |
| 可視區尺寸 | 750 × 292 | 750 × 402 |
| canvas 矩形 | x0 y0 750×292 | x0 y0 750×402 |
| 換算出的遊戲 y | 657(對) | 547(錯) |
兩邊的緩衝區和矩形比例,都剛好符合裝置像素比。
換句話說,Godot 的座標換算本身沒有算錯。
真正奇怪的是瀏覽器餵進來的觸控座標。
它偏小了大約 60 CSS px。
而 60px,剛好接近 iPhone 狀態列的高度。
於是第三個診斷出現了:
standalone 模式下,瀏覽器回報的座標系統性偏移。
這次看起來終於抓到真相了。
但其實還沒有。
拿到 60px 偏移的數據後,一個很自然的推論是:
會不會是 iframe 造成的?
遊戲當時是嵌在 itch.io 頁面裡執行,而加入主畫面後,又是 itch.io 的頁面在觸發 standalone 模式。
如果問題出在這一層,我能控制的東西其實很少。
所以當時考慮的處理方式,是不要硬修座標,而是:
偵測到 standalone + 橫向時,直接蓋一層提示,請玩家轉回直向。
因為當時的資料看起來像是:
但這裡其實把兩件事情綁在一起了:
standalone 和 iframe 都只是嫌疑犯,還沒有證據證明它們就是原因。
這時候,我決定架一個最簡單的對照組。
原本架一個 GitHub Pages 版本,只是想驗證:
iframe 是不是元凶?
結果這個對照組,最後成了整個除錯過程裡最有用的工具。
先做第一個 A/B 測試。
只把魔王立繪設成不可見,其他東西完全不動,再匯出一版。
結果:
圖不見了,問題照樣發生。
立繪洗清。
接著再比較 GitHub Pages 和 itch.io。
一個沒有 iframe,一個有 iframe。
再各自測試「一開始就橫向」和「直向載入後再轉橫向」。
結果非常直接:
| 環境 | 一開始就橫向 | 直向載入後轉橫向 |
|---|---|---|
| GitHub Pages(無 iframe) | 正常 | 壞 |
| itch.io(有 iframe) | 正常 | 壞 |
兩個環境的結果完全一樣。
iframe 的嫌疑,洗清。
而另一個細節也突然變得非常重要:
一開始就用橫向開啟是正常的,只有「轉向」之後才會壞。
這讓問題第一次真正指向了「方向切換後的重新排版」。
除錯覆蓋層又量了一次轉向前後的數據。
直向時:
canvas:x0 y0 402×812
轉成橫向後:
canvas:x0 y62 750×402
注意那個 y62。
直向時有一段狀態列高度造成的內縮,轉成橫向之後,這段位置卻沒有被清掉。
結果就是:
canvas 整個往下推了 62px。
底下那 62px 的遊戲畫面,包括「結束回合」按鈕,也跟著被推出可視範圍。
所以玩家看到的畫面雖然像是:
「結束回合」就在那裡,為什麼按不到?
實際上,玩家手指按下去的位置,已經不是按鈕了。
而是按鈕原本所在位置的上一層手牌。
到這裡,真正的根因才算找到。
一個 GitHub Pages 對照環境,加上兩種方向切換測試,就把前面幾個嫌疑犯逐一排除了。比起在同一個環境裡繼續推理,這個對照組有效得多。
找到根因後,修法分成兩半。
第一段 CSS:
把 html、body 固定在滿版位置,讓 canvas 的矩形 y 座標維持在 0。
第二段 JavaScript:
在轉向事件觸發後,分五個時間點連續補送 resize 事件,讓 Godot 有機會重新量出正確尺寸。
這兩段缺一不可。
只做 CSS,Godot 不知道尺寸變了,不會重新排版。
只補送 resize,canvas 原本的位置還是錯的。
兩個合起來,第一次轉向正常了。
但接著再測一次:
直向 → 橫向 → 直向 → 橫向。
結果又錯了。
這三段修法,只夠撐過一次轉向。
最後沒有繼續把這個瀏覽器/Godot/standalone 的組合修到完美,而是先做傷害控制:
轉成橫向時跳出提示,請玩家關掉遊戲,直接以橫向重新開啟。
如果一開始就是橫向開啟,則完全正常。
這不是最漂亮的解法,但至少把已知的錯誤行為限制住了。
到這裡還有兩個很奇怪的現象。
第一個是:
除錯覆蓋層拿掉之後,bug 又出現了。
這件事一開始很容易被當成雜訊。
明明只是多了一塊綠色文字,為什麼有它就正常,拿掉就壞?
繼續追才發現,除錯覆蓋層在事件捕獲階段讀取了 canvas 的:
getBoundingClientRect()
這個讀取動作會逼瀏覽器把當下還沒完成的版面計算做完。
而除錯覆蓋層執行的時間,又比 Godot 自己後面的處理更早。
所以原本只是「通知瀏覽器尺寸改變」的 resize,在有除錯覆蓋層的情況下,多了一個會強迫瀏覽器重新計算版面的動作。
結果就是:
工具本身改變了被驗證的行為。
這也讓「加了工具就好、拿掉就壞」變成了一個很重要的線索。
不是雜訊。
另一個現象是使用者提到:
轉向的時候,畫面會縮小又放大一下。
第一反應很容易覺得這是視覺上的瑕疵,應該把這個過渡去掉,讓轉場更順。
結果一改,bug 立刻復發。
原因是 Godot 需要被連續通知好幾次,才能把內部矩形真正換過來。
那段短暫的過渡畫面,其實就是修法正在工作的過程。
所以這次學到一件很實際的事:
看到畫面閃動,不一定代表它是需要消除的問題。
有時候,它反而是在告訴你:
某個修正真的正在發生。
事情到這裡還沒有結束。
寫完偵測方案後,使用者補了一句更正:
回想我說之前 standalone 可以玩應該是我的錯覺。我第一次用 standalone 時,就發現結束回合被 Siri 橫條擋住,那是第一次的要求,所以應該沒有在 standalone 可以正常玩這件事
這句話讓「立繪無關、bug 從一開始就在」變得更確定。
但同時,也讓另一個更早的診斷變得可疑。
之前同樣是「按鈕看得到、按下去沒反應」,當時判斷是 Home Indicator 攔截觸控,做法是在畫面下緣留出 44px。
這個做法有代價:
日誌原本可以顯示 5 行,後來只能顯示 4 行。
那麼,既然現在知道 standalone 本身還有座標偏移,之前那次是不是其實也判斷錯了?
我沒有直接把舊結論撤掉。
而是重新驗證。
使用者在一般 Safari 分頁橫向測試,結果發現:
即使已經留了 44px,Home Indicator 那條白色橫條仍然幾乎貼著按鈕下緣。
拿掉那段留白後,按鈕確實會被吃掉。
所以:
舊診斷是對的。
那 32px 的日誌空間,沒有白花。
這次反而發現了另一件事:
兩個不同的問題,可以疊在同一顆按鈕上。
Home Indicator 吃掉最下緣的觸控是真的。
standalone 在轉向後產生座標偏移,也是真的。
第一個修好之後,第二個還會繼續存在。
所以使用者描述的症狀幾乎一模一樣,但背後可能有兩個完全不同的原因。
回頭看這次除錯,其實有三次診斷被推翻:
第一次,認為是 LogLabel 撐開版面。
量座標後,排除。
第二次,認為是魔王立繪蓋住按鈕。
掃 mouse_filter 後,排除。
第三次,拿到 60px 偏移後,懷疑是 standalone + iframe。
架 GitHub Pages 做對照後,才發現真正關鍵的是:
轉向之後,canvas 沒有重新排版。
前面每一次推理,其實都不是完全沒有道理。
問題在於:
合理的機制,不代表它就是實際發生的機制。
所以每一次診斷,都需要一個能把它推翻的測試。
量座標、掃節點、換環境、改變一個條件。
一個簡單的對照組,有時候比在同一個環境裡多推理十輪更有效。
而這次甚至連驗證工具自己都成了變數。
除錯覆蓋層會影響瀏覽器的版面計算,畫面閃動也不是單純的視覺瑕疵,而是修法的一部分。
最後,當使用者補了一個可能推翻舊結論的新說法時,也沒有直接照單全收。
重新測一次,再決定舊結論要不要撤回。
症狀相同,不代表成因相同。
新的懷疑聽起來合理,也不代表舊的診斷一定錯。
明天 Day 19 講把專案送上 Web、Android、iOS 三個平台:程式碼一行都不用為了這三個平台改,全部的工作是設定與踩坑,還有一次「指令可以交給 AI、資安判斷不能交出去」的具體示範。
💬 你查過的 bug 裡,有沒有遇過連續好幾個「聽起來很合理」的診斷都錯,最後靠一個簡單的對照實驗才拆穿的經驗?歡迎留言聊聊你當時是怎麼想到要架那個對照組的。