歷史記錄 · 2026-08-12¶
重大重構里程碑¶
2026-08-12 ─ 首頁專案時間線與自適應歷史壓縮¶
規格站首頁新增由每日歷史生成的專案沿革。每日條目以可選的隱藏 marker 表示是否進入首頁; 未入選條目與 ADR 不補空 importance 欄位。首批 12 筆依專案身份、階段、核心架構與公開營運 邊界篩選,從 2026-04-28 的最早可稽核企劃基線一路呈現至首次公開發布鏈定案。
超過 12 筆後,生成器依最早至最晚標記日期的總跨度,逐步改採月、季、半年或年度 bucket; importance 5、全域最早與最新事件永久保留。首頁只存 managed block,不另建沿革頁或 JSON 登錄檔;呈現採無 JavaScript 的 responsive roadbook。
決策檔:D-20260812-01
2026-08-12 ─ 觀戰 deterministic replay 與精簡 fallback¶
觀戰主資料面由 10Hz 全量 presentation 快照改為唯讀、非權威 deterministic replay:觀戰端以 相同世界描述、已簽 input、週期 checksum 與按需 SavedState 重建同一場比賽,並共用參賽端的 world builder、renderer 與 HUD 派生。版本不符或裝置能力不足時才降級為有界精簡快照。
不採固定播放延遲;2–6 幀 buffer 只吸收網路抖動。displayName 改由觀戰端依 PeerId 解析,
fallback 正面表列公開呈現欄位,加入拒因補齊 wrong-password。
同輪完成 replay 呈現收口:觀戰端沿用共用 mapper/回合規則本地導出速度、馬達音高、碰撞與
武器音效、小地圖及回合摘要;移除從未有生產者的 race-event。起跑倒數因 readiness 使
descriptor 到達時點不固定,保留低頻、非權威 countdown 控制;UI 明示建立世界、等待
checkpoint、追趕、live 或 reduced,不再顯示含糊的 snapshot 接收狀態。
決策檔:D-20260812-02
2026-08-12 ─ 賽前協議窗改用絕對 deadline¶
loadout 提交窗與 race-ready stall/resend 不再把相對 setTimeout 當協議時鐘;兩者保存注入的
單調時鐘 deadline,timer callback 只重算剩餘時間。瀏覽器回前景時由 RaceSession 單一
visibility 訂閱 fan-out,立即定案已逾期的提交/stall,或對已到期 resend 補送一次,不形成
追趕 burst。賽內 rollback 的背景恢復判定仍沿用同一事件來源,物理幀與結算時序未改。
決策檔:D-20260802-02
2026-08-12 ─ signaling session 關閉傳播與重連¶
WebSocket close/error 現在會經 SignalingSession 與 mux liveness contract 向 room dialer
傳播。dialer 原子停用 dead session、釋放舊 transport,重跑原先設定的 provider chain,並把
替代 session 綁回同一個 room presence 與 participant/spectator 撥號入口;重連期間所有握手
操作明確回 offline,不再靜默送進已關閉 socket。同時只允許一個重連,回前景可補試一次;
離房、明示 close/dispose 與延遲完成的舊重連都不會重新掛回。
決策檔:D-20260724-01
2026-08-12 ─ 公開房公告 lease 抗背景節流¶
公開房 announcement 與 member presence 保留 30 秒名目刷新,但 lease 從沒有節流餘裕的 60 秒
調整為 120 秒,足以跨過一分鐘級背景 timer 喚醒的一次漏拍與網路延遲,同時維持 300 秒接收
硬上限,避免 crash 房長期滯留。刷新以絕對 nextRefreshAt 判定,延遲 callback 只補一次;
頁面回 visible 時立即發布最新 room snapshot,離房則解除 lifecycle 訂閱並照常發 close/left。
決策檔:D-20260802-08
2026-08-12 ─ AudioContext 可重入恢復¶
音訊的「曾取得使用者手勢」不再和 AudioContext 當下是否 running 共用一個布林閂。SFX 與 world context 在 suspended-running-suspended 流程可再次 resume;真引擎先檢查 state 並以 single-flight 合流,running/closed 不做事,失敗則留待下一次手勢。全域 pointerdown 不再 once-only,頁面回 visible 時只重試已解鎖 context,不重建或重播 BGM。
決策檔:D-20260704-01
2026-08-12 ─ 跟車偏好 page lifecycle 收口¶
RacePage 在 pagehide、freeze 或轉為 hidden 時會取消跟車距離 debounce,立即啟動
best-effort 本機寫入;頁面銷毀時移除全部 lifecycle listener。gamepad 取樣在 visibility 切換
時重設基準,單次 elapsed 再夾到 250ms,長時間背景停頓不會在第一幀被當成持續推桿而把距離
瞬間跳到 0.30m/2.00m 邊界。
決策檔:D-20260810-01
2026-08-12 ─ 本機安全事件記錄與去識別匯出¶
sanitize Worker/editor admission 拒收、P2P 驗章拒絕與 CSP violation 現在接入同一個 app-scope 記憶體環形記錄。資料在寫入時即依事件類型正面表列;payload、signature、PeerId、未知欄位與 URL path/query/fragment 不會先落地,CSP URL 最多留下 origin 或 scheme。
設定頁維護區可由玩家主動下載版本化 JSON;記錄不持久化、重新整理即清空,亦沒有自動 telemetry 或遠端 transport。匯出使用同一份已去識別 snapshot,不額外蒐集裝置或環境資訊。
決策檔:D-20260812-03
2026-08-12 ─ 玩家安全與隱私必要揭露補齊¶
登入頁在可能輸入備份碼前警告第三方 client 風險;設定頁隱私分類固定列出第三方部署、P2P 直連 IP 可見性與聊天簽章證據三項告知;說明頁新增完整文章供日後回查。IP 文案明確區分直連與 實際 TURN relay,不把「有設定 TURN」誤寫成一律隱藏位址。
玩家介面沿用各語系既有的備份碼/backup phrase 名稱,BIP39「助記詞」只作技術文件術語; 告知不增加每場重複確認或遊戲流程阻擋。
2026-08-12 ─ CSP 逐頁雜湊與不安全請求升級¶
production prerender 後不再以靜態常數描述 CSP;生成器逐份 HTML 計算 inline import map、JSON-LD
與必要 handler 的 SHA-256。script-src 不使用 unsafe-inline,只有頁面確實含 inline handler 時
才加入 unsafe-hashes 與對應 hash。
固定 baseline 補回 upgrade-insecure-requests。專案所用 Chromium 實測 localhost 仍保留 HTTP
請求,因此不需要為本機 production smoke 犧牲正式站的縱深防禦;meta CSP 的 frame-ancestors
限制與 connect-src scheme 級節點選擇權維持不變。
決策檔:D-20260812-04
2026-08-12 ─ 首發前 Wire 與 Ledger 欄位必填化¶
尚未發布階段不再替不存在的舊 producer 保留 optional:RoomStateWire.spectators 與
spectatorSources 成對必填,participant source 只接受 host 權威配置明確指向自己的觀戰連線;
缺欄不再退回空名冊或本機選舉。
RoundResult.endReason 同步在主程式、Ledger admission、正式 MatchSummary 與 pinning vendor
設為必填並納入多簽內容。deterministic replay 的精簡 presentation fallback 與語意性選填的
disallowChip 不受影響,也不建立不存在的 production migration。
決策檔:D-20260812-05
2026-08-12 ─ 治理信任根與比賽傳輸契約單一化¶
Ledger 不再接受 getSignerSet option 另行注入治理 signer。首個 checkpoint 只讀 genesis
EconomyConfig.governanceSigners,後續只讀前一個已採納 checkpoint 的 derived state;pinning
部署的 GOVERNANCE_SIGNERS 會在啟動時寫入 genesis,空集合維持 fail-closed。
比賽同步的 NetworkLayer.broadcast 與定向 RaceWirePort.sendRaceTo 同步收斂為必回傳
boolean。false 明確代表未交付並保留重試/correlation 狀態,測試 stub 不再以 void
假裝成功;best-effort fanout sendRace 的 void 契約不變。
決策檔:D-20260812-06
2026-08-12 ─ i18n 使用面 strict gate¶
決策檔:D-20260811-18
四語系字典完成缺 key 與零引用 key 清理後,CI 除了既有 locale-to-locale schema 驗證, 也會以 strict 模式掃描 production TypeScript/HTML 的實際使用面。靜態 key、樣板與加號動態 前綴、registry 宣告前綴及 ICU 參數集合都有 fixture;任何缺字典或 dead translation 會直接失敗。
2026-08-12 ─ UI 錯誤翻譯鍵命名收斂¶
頁面、共享領域與共用控制項仍保留各自合理的 namespace;不為單一 root 犧牲跨頁重用。
UI-facing reject key 則統一為 errors.<domain>.<reason>:garage、rating、submit 與 spectator
共 28 個鍵及其 producer/consumer 已原子遷移,翻譯文字與產品行為不變。taxonomy regression
現在同時掃產品字面與四語系字典,避免平行 domain.reject.* 再次出現。
2026-08-12 ─ 觀戰相機跨裝置快捷鍵接線¶
外部觀戰者與本回合已淘汰玩家現在共用同一組 camera action:C 切換自由/巡禮,手把
LB/RB 與畫面左/右緣循環上一/下一個固定 roster 目標。手把採 rising-edge,鍵盤在
文字輸入焦點內停用;仍在比賽的 participant 不攔截這些快捷鍵。四語系提示收進現行
pages.race.camera 命名空間,不復活已移除 WatchPage 的死鍵。
2026-08-12 ─ 建房前場地容量閉環¶
Race Config 的場地選項現在攜帶 canonical GLB root extras 的 auto_player_max,逐回合取最小值;
設定人數超額時立即顯示四語系拒因並停用建房。未解析或非法容量維持 fail closed,正式
matchmaking provider 在進入 room service 前以同一資料重驗,房間則繼續以已驗證的
maxPlayers 原子限制同時 admission。舊 loadoutMissing 沒有獨立 producer,且與既有
carRequired/onChainCarRequired/resourcesUnresolved 重疊,因此不建立平行分支。
2026-08-12 ─ 淘汰轉觀戰的 hold 輸入釋放¶
RacePage 現在統一追蹤已送入 session、尚未 release 的鍵盤與指標技能槽。玩家從參賽切成 本回合淘汰時會一次 flush 全部槽位,讓 RaceLoop 停止 hold tick 並清掉 HUD active;淘汰後只 拒絕新的 press,必要的 release 仍可通過。blur 與後到的 keyup/pointerup 共用同一冪等路徑, 下一回合恢復參賽時也不會殘留鍵盤 auto-repeat latch。
2026-08-12 ─ 賽中聊天連線狀態可見化¶
RacePage 的 participant 與 spectator chat 現在共用 idle/connecting/ready/unavailable/error
狀態機;連線中、能力不可用與加入失敗不再顯示成空白面板,error 可 single-flight 重試。
route 或角色切換後的過期成功會立即 leave,Promise rejection 也會收斂成可見錯誤而非
unhandled rejection。RoomPage 同步改用共享 chat.connection.* 四語系文案,不建立已移除
WatchPage 或 RacePage 的平行命名空間。
2026-08-12 ─ 共用聊天送出回饋閉環¶
O4ChatPanel 現在顯示 trim 後即時字數與 500 字上限,空白禁用、超限 inline 提示。送出改為 single-flight 等待 handle Result:成功且草稿未被玩家改寫才清空;限速與一般傳輸失敗都保留 草稿並顯示共享四語系安全文案,Promise rejection 不再成為 unhandled rejection,也不會把 provider 內部錯誤直接呈現。收端的驗章、封鎖與畸形訊息拒絕仍維持靜默。
2026-08-12 ─ 常數與追溯閘的 CI 現況誠實化¶
程式參數表不再把本機可執行的 check:constants/check:trace 寫成已部署的 live CI 保證。
現況明列為完整 workspace 的本機硬閘;主 repo CI 尚未取得 immutable specs checkout,也尚未
呼叫兩支檢查。後續由 open-4wd-workflow #000062 隨跨 repo checkout 接線,完成時再同批把
canonical 措辭更新為 CI 強制,避免規格先行承諾不存在的執法。
2026-08-12 ─ 主題降級原因在設定頁可見¶
設定頁主題區現在讀取 ThemeService 的單一 fallbackReason,分別以四語系安全文案呈現主題 查無、已退役與資源載入失敗;啟動時既有失敗與頁內切換/重設失敗使用同一警告。成功套用 任一主題後會清除警告,既有 persisted themeId、自癒與目前可用主題保留規則不變。
2026-08-12 ─ 宣告註解治理拆批與常數群規則¶
exported API 與 private declaration 現在採同一自足用途註解要求;相鄰且同一語意邊界的 exported 常數允許由一則群組 TSDoc 涵蓋,避免逐列名稱重述。現有缺口依責任域拆成八批, 各批都以路徑內歸零驗收,不使用 baseline 或豁免;全部清零後才將 strict checker 接入硬閘。
2026-08-12 ─ 重複碼量測與分階段零容忍(issue 000389)¶
決策檔:D-20260812-07
跨檔 clone detector 採精確鎖版 jscpd,不以 npx 動態下載;production/tests 分開量測,
generated、vendor、fixture、specs 與特殊證據面明確排除。Production 先 report-only 完成分類與
清理,再啟用 threshold 0;tests 維持 report-only,不建立債務 baseline,也不為數字抽掉測試意圖。