iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Vibe Coding

從 Vibe Coding 到系統架構:Gemini x Claude Code 雙 AI 協同開發 Godot 2D Roguelike 卡牌遊戲實戰系列 第 18 篇

Day 18:【除錯實戰】AI 連續三次診斷都錯,靠架設對照組環境才拆穿

  • 分享至 

  • xImage
  •  

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
  • canvas 的 getBoundingClientRect()
  • canvas 實際緩衝區尺寸
  • 最後一次觸控的三組座標
  • 再用 Godot 原本的換算方式回推遊戲座標

這次不再猜「是不是哪個節點蓋住了」。
直接看數字。
結果是:

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 模式下,瀏覽器回報的座標系統性偏移。

這次看起來終於抓到真相了。
但其實還沒有。


五、第三次診斷:以為是 standalone 和 iframe

拿到 60px 偏移的數據後,一個很自然的推論是:

會不會是 iframe 造成的?

遊戲當時是嵌在 itch.io 頁面裡執行,而加入主畫面後,又是 itch.io 的頁面在觸發 standalone 模式。

如果問題出在這一層,我能控制的東西其實很少。
所以當時考慮的處理方式,是不要硬修座標,而是:

偵測到 standalone + 橫向時,直接蓋一層提示,請玩家轉回直向。

因為當時的資料看起來像是:

  • standalone + 直向:正常
  • standalone + 橫向:有問題

但這裡其實把兩件事情綁在一起了:

standalone 和 iframe 都只是嫌疑犯,還沒有證據證明它們就是原因。

這時候,我決定架一個最簡單的對照組。


六、對照組出場:GitHub Pages

原本架一個 GitHub Pages 版本,只是想驗證:

iframe 是不是元凶?

結果這個對照組,最後成了整個除錯過程裡最有用的工具。
先做第一個 A/B 測試。

只把魔王立繪設成不可見,其他東西完全不動,再匯出一版。
結果:

圖不見了,問題照樣發生。

立繪洗清。

接著再比較 GitHub Pages 和 itch.io。
一個沒有 iframe,一個有 iframe。
再各自測試「一開始就橫向」和「直向載入後再轉橫向」。
結果非常直接:

環境 一開始就橫向 直向載入後轉橫向
GitHub Pages(無 iframe) 正常 壞
itch.io(有 iframe) 正常 壞

兩個環境的結果完全一樣。
iframe 的嫌疑,洗清。

而另一個細節也突然變得非常重要:
一開始就用橫向開啟是正常的,只有「轉向」之後才會壞。
這讓問題第一次真正指向了「方向切換後的重新排版」。


七、真正的根因:轉向後,canvas 留了一段 62px 的內縮

除錯覆蓋層又量了一次轉向前後的數據。

直向時:

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 裡,有沒有遇過連續好幾個「聽起來很合理」的診斷都錯,最後靠一個簡單的對照實驗才拆穿的經驗?歡迎留言聊聊你當時是怎麼想到要架那個對照組的。


上一篇
Day 17:【UI 設計】AI 聲稱的「天花板」,可能是他自己設的。
系列文
從 Vibe Coding 到系統架構:Gemini x Claude Code 雙 AI 協同開發 Godot 2D Roguelike 卡牌遊戲實戰 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言