MatchRPG new_player_defaults fallback 未對齊常數表:staminaMax hardcode 問題排查、修復與無上限常數處理策略¶
文檔資訊
- 分類: debugging
- 難度: intermediate
- 預估閱讀時間: 7 分鐘
- 標籤:
MatchRPG,new_player_defaults,FixedValueConfig,stamina,hardcode,fallback,DB migration,constants table,cap strategy,ExciteTarget,TODO governance
摘要¶
前端讀到 staminaMax=30 但常數表設定為 100。根因是 new_player_defaults fallback 的 staminaMax hardcode 而非讀取常數表。修復後延伸出重要架構模式:常數表填 99999999999 表示「遊戲設計上無上限」,但伺服器端必須 cap 在設計文件規定的安全上限(如 2000)以保護 DB。另發現 ExciteTarget 使用了不存在的常數 key 作為註解,已改為 TODO 待企劃確認。
關鍵學習¶
-
new_player_defaults 是新玩家初始化的 fallback,其數值必須與 FixedValueConfig.xlsx 保持一致,否則會產生靜默錯誤(數值錯但不報錯)
-
「直接下 SQL 改」優於為一次性資料修正建立 migration 檔案,避免過度工程化
-
常數表填 99999999999 是企劃表達「遊戲設計上無上限」的慣例,伺服器不能照單全收,必須 cap 在設計文件規定的 DB 保護上限
-
Cap 策略:讀取常數表值,但 cap 在伺服器安全上限,不取最小值(因為常數表值是設計意圖,不是真實數字)
-
hardcode 值如果對應的常數 key 尚未由企劃定義,應改為 TODO 而非隨意填寫不存在的 key
-
系統性掃描 hardcode 值後,需區分:(a) 真正的錯誤 fallback、(b) 故意的 DB 保護上限、© 待企劃確認的 TODO
技術細節¶
問題現象¶
API player.v1.PlayerService/GetPlayerData 回傳:
但 FixedValueConfig.xlsx 常數表設定的體力上限應為 100,不是 30。
根因分析¶
new_player_defaults 設定檔(用於新玩家初始化的 fallback)中,staminaMax 被 hardcode 為 30,沒有讀取常數表的值。
理論上 new_player_defaults 是**純 fallback**,所有數值都應該參照常數表的設定。任何常數表有定義的欄位,fallback 都不應該自行 hardcode 不同的值。
修復確認¶
修復後,線上玩家 API 回傳:
確認 staminaMax 已正確為 100。無上限常數的 Cap 處理策略¶
在系統性掃描 hardcode 時,發現部分欄位常數表填 99999999999,企劃用這個表示「遊戲設計上無上限」。
問題:伺服器不能照單全收 99999999999,否則可能炸 DB。
解法(依設計文件):
- Cap 值(如 2000)是**設計文件明確規定的 DB 保護上限**,不是隨意的 magic number
- 邏輯:讀取常數表,確認其為「無上限」設計意圖,但實際存取/回傳時 cap 在 2000
- 不採用 min(fallback_cap, constants_value) 的做法,因為 99999999999 不是真實數字而是設計語義
// 正確做法:讀常數表 + 伺服器端 cap
const MaxInventoryCapacity = 2000 // DB 保護上限,見容量設計文件
func getInventoryCapacity(cfg *FixedValueConfig) int {
rawCap := cfg.GetInventoryCap() // 可能讀到 99999999999
if rawCap > MaxInventoryCapacity {
return MaxInventoryCapacity // cap 保護
}
return rawCap
}
ExciteTarget 錯誤註解問題¶
teach_models.go:41 中:
問題:99960000027 這個 key 在企劃設計中從未定義,是開發者自行猜測的 key。
正確處理:改為 TODO,等企劃給出正確的常數 key:
同類問題:MaxExciteValue 也需要確認是否有對應的常數表 key。
What Changed¶
staminaMax 問題修復:new_player_defaults 中的 staminaMax 從 hardcode 30 修正為對齊常數表的 100,並直接對線上 DB 執行 SQL 更新既有非 bot 玩家記錄。修復後 API 回傳確認 staminaMax=100。
無上限常數處理策略確立:發現常數表中 99999999999 是企劃表達「遊戲設計上無上限」的慣例,但伺服器端已有設計文件規定的 DB 保護上限(如 2000)。實作為讀取常數表後 cap 在保護上限,而非直接使用常數表的大數值。
ExciteTarget 錯誤 key 修正:teach_models.go:41 的 ExciteTarget=1000 有一個不存在的 key 99960000027 作為註解,已移除錯誤 key 並改為 TODO,等企劃確認正式 key。
So What¶
這個 session 揭示了兩層問題:一是 fallback 設定的靜默錯誤(數值錯但不報錯),二是「無上限」的設計語義如何在伺服器端安全處理。
特別是「無上限 = 99999999999」這個企劃慣例,如果不知道這個規則,很容易直接把常數表值存入 DB,造成整數溢出或其他問題。建立 「常數表大數 = 設計無上限,伺服器端需 cap」 的規範非常重要。
另外,錯誤的常數 key 註解比沒有註解更危險,因為它給人一種「已接入常數表」的假象,實際上 key 根本不存在。
Trade-offs¶
- 直接 SQL 更新 vs Migration 檔案:一次性資料修正直接下 SQL 更快,不需要建立 migration 檔;migration 適合 schema 變更,不適合純資料更新
- Cap 固定值 vs 讀常數表 cap:2000 是設計文件規定的 DB 保護上限,hardcode 在伺服器端是合理的(有文件依據),不需要每次讀常數表來決定保護上限
- 99999999999 的處理:不採用
min(fallback, constants_value)是因為常數表值不是真實數字,是設計語義;正確做法是理解語義後在伺服器端 cap - 重啟 vs rebuild:只改 fallback 設定值不需要 rebuild,重啟即可載入新設定;改了 code 邏輯則需要重新 build
Try It Fast¶
# 確認線上玩家的 stamina_max 分布
SELECT stamina_max, COUNT(*) FROM players GROUP BY stamina_max;
# 將非 bot 玩家的 stamina_max 改為 100
UPDATE players SET stamina_max = 100 WHERE is_bot = false;
# 確認修改結果
SELECT stamina_max, COUNT(*) FROM players WHERE is_bot = false GROUP BY stamina_max;
# 掃描 codebase 中可能未接常數表的 hardcode 值
grep -rn 'ExciteTarget\|MaxExciteValue\|staminaMax\|StaminaMax' --include='*.go' .
# 確認哪些地方有用到 99999999999 這個大數
grep -rn '99999999999\|9999999999' --include='*.go' .
Recommendation¶
- 建立規範:
new_player_defaults中每個欄位都必須有對應的常數表來源,禁止 hardcode 與常數表衝突的值 - 建立「無上限常數」處理規範:常數表填 99999999999 = 設計無上限,伺服器端必須 cap 在設計文件規定的安全上限
- 禁止在 code 中填寫未經企劃確認的常數 key;如果 key 未定義,改為
// TODO: 待企劃定義對應常數表 key - 系統性掃描 hardcode 值時,需區分三類:(a) 錯誤 fallback(需修正)、(b) 有設計依據的保護上限(需加文件引用)、© 待企劃確認的 TODO(需追蹤)
- 定期做靜態掃描:grep codebase 中的數字 literal,確認初始化相關的 hardcode 值都有對應常數表設定或明確的設計文件依據
- 發現數值異常時,優先追查初始化 fallback 路徑,而不只是看 API handler 邏輯