遊戲分享碼怎麼重現結果?從 seed、選擇紀錄到版本相容
玩家傳來一句「最後一回合算錯了」,開發者卻重現不出來,問題通常不只在亂數。玩家的起始條件、按過的選項,以及當時使用的規則,都可能少記了一項。這篇以本站「社畜職涯模擬器」為例,走一次從分享碼還原輸入、執行引擎,到比較中途差異的流程。
本文分析的是原始碼版本 1a6b177,下載包也凍結在這個版本。你需要會執行 JavaScript;範例使用 Node.js 22.23.2 驗證,不需要啟動網站,也不需要安裝套件。它適合回合制、選項式模擬與可重播的 bug 回報;即時物理遊戲還需要處理時間步進與平台差異,這份範例沒有涵蓋。
先定義什麼叫「同一局」
這個引擎的輸入包含 baseSeed、學歷、產業、職涯目標,以及依序記錄的 choices。seed 只決定亂數序列,不會替你補回其他欄位。即使 seed 相同,換一個起始產業,事件權重與後續可選項目也可能不同。要能重現,這些輸入與執行規則都必須固定。
const payload = {
version: 3,
baseSeed: 20260921,
education: 'publicUniversity',
industry: 'technology',
goal: 'balance',
choices: [0, 1, 2, 0, 1, 2, 0, 1, 2, 0],
};choices 的數字是各回合的選項索引,不是永久不變的語意。0 代表當時事件中的第一個選項;日後如果把選項順序調換,同一個 0 就可能代表另一件事。因此分享碼裡的 v3 應視為格式與規則演進的警示,不要把它當成所有未來程式都能重現舊結果的保證。
亂數要能注入,呼叫順序也要穩定
本站引擎用 mulberry32 建立自己的亂數產生器,simulateCareer 每次都從輸入 seed 重新開始。一般的 Math.random 不能透過標準 API 指定或重設 seed,因此不能直接拿來當這種重播介面。這裡需要的是可重現的事件抽樣,不是密碼學用途的亂數。
還有一個容易忽略的條件:相同 seed 只保證你能取得相同序列,前提是用同一個演算法、照同樣的次序取值。若把裝飾動畫也接到同一個產生器,動畫多取一次亂數,下一個事件便可能改變。實作時應把畫面裝飾的亂數與規則抽樣分開,並記錄每回合選到的事件 ID。
下載範例,先跑出一個可比較的結果
下載固定版引擎、驗證程式與預期輸出(ZIP)unzip replay-guide.zip
cd replay-guide
node verify.mjs壓縮檔內的 engine.ts 保留原始 TypeScript;engine.mjs 是可直接執行的 JavaScript;verify.mjs 負責檢查;result.json 保留本次輸出。程式若發現不一致會拋出 assertion 錯誤並以非零狀態結束。要保留自己的輸出,可執行 node verify.mjs > my-result.json,再與 result.json 比較。
| 項目 | 結果 |
|---|---|
| 分享碼 | v3-c29fd-0010120120120 |
| 固定樣本結局 ID | foreverManager |
| 設定組合 | 64 個 seed × 4 學歷 × 4 產業 × 4 目標 = 4,096 |
| 每組檢查 | 編碼/解碼往返;重跑後 fingerprint 相同 |
| 無效輸入 | 5 個案例皆拒絕 |
| 環境 | Node.js 22.23.2;固定引擎版本 1a6b177 |
4,096 組用的是同一條十回合選擇序列,並沒有窮舉所有決策路線。這個結果支持「在這份引擎與測試輸入內可重現」,沒有證明遊戲平衡、每個結局都可達成,或每個裝置都運作正常。測試問題要先定義清楚,數字才有意義。
比較過程,才能找到真正的分歧
兩局都得到同一個結局,並不表示中間完全一致。careerFingerprint 會整理事件 ID、實際選擇 ID、承諾回應、數值、旗標與結局因素,再轉成可比較的字串。SHA-256 是這個字串的摘要,方便確認檔案與結果是否一致;它不是遊戲公平性的證明。
import { simulateCareer, careerFingerprint } from './engine.mjs';
const first = simulateCareer(payload);
const second = simulateCareer(payload);
console.log(careerFingerprint(first) === careerFingerprint(second));
console.table(first.moments.map((m, turn) => ({
turn: turn + 1, event: m.nodeId, choice: m.choiceId,
}))); // Run after defining payload above如果結果不同,先找第一個事件 ID 不同的回合。事件先不同,通常要查當時狀態、事件權重或亂數呼叫;事件相同而選項不同,則查索引、選項鎖定條件與替代選項。這個引擎在要求的選項不存在或不可用時,會選可用的替代項,因此除錯紀錄應同時保留玩家要求的索引與引擎最後採用的 choiceId。
舊分享碼可以被接受,卻不一定是舊局重播
本站 v2 分享碼轉入 v3 時,會保留 seed、學歷與產業,清空目標與選擇紀錄,讓玩家重新選擇。這是遷移策略,不是舊結局的還原。若你的產品需要回放歷史比賽,就應保留舊引擎或足以重建狀態的事件資料,並把規則版本獨立保存;只有在網址前面加 v3 並不足夠。
最後,把分享碼當成使用者輸入驗證:seed 範圍、設定順序、每個選項允許的數值與最多回合數都要檢查。這份格式能攜帶遊戲輸入,不能證明某人真的玩過那一局;任何人都能構造分享碼。若牽涉排行榜或獎勵,成績可信度需要另外設計。
接著看:推箱子為什麼要把遊戲與求解器分開