TOYPICK v2 — Release v1.2.6
發布日期:2026-06-05 正式站:https://www.wait2order.com
摘要
從根本修復排點長年反覆出現的 bug 類別最後一個觸發點:「某局進行中但尚未存分時,有新球員報到,導致正在進行的那局排點被改變」。
v1.2.1 到 v1.2.5 連續多版都在修排點問題,每次修好一個觸發點(存分、亂序存分、legacy 空 teams、存分當下重算),但客戶仍能測出新的變形。本版找出共同根因並一次封堵:排點配對是「報到名單的純函數」,而「釘住不重洗」的記憶只存在資料庫,且過去只有存過分數的局才會寫進資料庫。進行中但尚未存分的局沒有任何持久化記憶,名單一旦變大(報到)就會被用更大的 template 整盤重算——連正在打的那局也一起重配。
本版把「快照進資料庫」的動作從「只在存分時」擴展到「報到造成名單成長時」,讓進行中的局在名單變大前就先被釘住,徹底消除這個 bug 類別的立足點。
問題與根因
- 現象:場上報到 4 人、第一局進行中(尚未存分),第 5 人報到的當下,進行中第一局的對戰組合被改變。
- 根因:排點
buildDynamicCourtGames是無狀態渲染,配對完全由當前報到名單決定。「進行中」的局正常情況下只是渲染時推算出來的(最後一個完成局的下一格),它並未被釘住。先前的修復(v1.2.5 的 WYSIWYS)只在存分路徑把進行中局持久化進資料庫;但「報到」不走存分路徑,名單成長後重新渲染時,引擎手上沒有「這局剛剛長怎樣」的記憶,只能用更大的 template 重算,於是連進行中的第一局都被重新配對。 - 為何前幾版補不乾淨:每一版都在補「某一個會觸發重算的入口」,但只要排程仍是衍生狀態,就永遠有下一個入口(存分、報到、改名單)會再炸。根治之道是讓進行中的局在任何名單變動發生前就成為持久化狀態。
修復方式
- 報到時快照釘選(persist-on-check-in):新增純函式
buildCheckInPersistencePlan,與存分時的buildSavePersistencePlan對稱。當一名球員報到(checkedIn由 false 轉 true、造成該場名單成長)時,在名單成長之前,先以操作員當下看到的名單渲染排程,把進行中的那一局連同它的配對快照寫入CourtScheduleScore。該局因此進入「已釘住」集合,名單變大後的重新渲染只會在尾端追加新局,不會重洗進行中或已完成的任何一局。 - 掛載點:快照動作放在
setRegistrationCheckedIn服務中、管理員權限驗證之後、資料庫翻牌之前,且只在「false→true 的成長型報到」時觸發。 - 冪等且永不覆蓋:若進行中局已持久化過隊伍,計畫為空(重複報到不重複寫);永不覆蓋任何已有比分的完成局。快照失敗採 best-effort,不阻擋報到本身。
測試強化
- 新增 FUZZ 3(check-in 路徑):模擬真實報到流程(成長前先 snapshot、再成長名單、再重渲染),以固定種子 PRNG 跑 400 場隨機 session,斷言「報到時進行中的局配對永久不變、所有完成局保持凍結」。
- 新增 GUARD(check-in 冪等 / no-clobber):驗證
buildCheckInPersistencePlan對已釘住狀態為 no-op、永不覆蓋已存比分的完成局(防雙重報到或重試覆蓋線上資料)。 - 離線 harness
tests/repro-late-checkin.mjs驗證 4→5→6→7→8 連續報到,進行中第一局始終穩定。
驗證
node --test tests/court-schedule-fuzz.test.mjs(8/8 pass,含新 check-in 根治與冪等斷言)- 全套件
node --test tests/*.test.mjs(94/94 pass) npx tsc --noEmit(綠燈)npm run build(綠燈)