Files
ark-stronghold-protocol/test/golden
sganggs 696085ac8e feedback5 WL: 突袭 counts a passive skill that is on as 技能就绪, so 缄默德克萨斯 / 宴 jump as soon as a ground enemy can be reached (GitHub #49)
PRTS 盟约记录 【突袭】 「干员10秒内未进行攻击或技能就绪时,若范围内没有敌人,则保留技力立即再部署至一名地面敌人周围」.
skills.js `ready` is false for every passive skill, so raidPoll left passive members only the 10 s idle trigger; the
reporter's footage shows 缄默德克萨斯 jumping within her passive's 10 s. raidPoll alone (the global `ready` stays) now
counts a passive skill that is on as 技能就绪 [ASSUMED: the engine keeps a passive on for the whole deployment]. The
GitHub #51 landing rule (the target in range on the landing tile) is kept, so a member lands in range and stays while
its enemy is there. Charged non-passive skills and the idle trigger are unchanged.

Golden moved (scenarios with a passive 突袭 member): roster-039 (斯卡蒂 S2 跃浪击), roster-046 (耀骑士临光 S2 逐夜烁光),
bond-raidShip-low, bond-raidShip-high, boss-boss_2-pair, boss-boss_4-pair, boss-boss_6-pair (elite 缄默德克萨斯),
standin-01, standin-06 (缄默德克萨斯 fielded as Misery, all passive), coop2-HARD-4, coop2-NORMAL-14-standins (突袭
members in the lineups). Docs: research 02 §3.18.
2026-10-05 20:52:45 +08:00
..

Golden results

The simulation is deterministic: the same seed and inputs give the same battle and the same match, bit for bit. These files record the exact outcome of a fixed corpus of seeded scenarios, so a change that alters gameplay — even one meant to be a pure refactor that only reorders random draws, hook order or iteration order — fails loudly instead of slipping through.

  • tools/golden.mjs builds the corpus from data/*.json (fixed order, fixed seeds) and reduces every scenario to a digest. Its header documents the families and the digest fields.
  • test/golden/<family>.json hold the digests (roster, bonds, fields, matches, standins).
  • test/golden.test.js recomputes them and compares. On a mismatch it prints the scenario, the field and old → new.

The corpus

family what runs
roster 49 battles: every visible chess record (normal and elite) with every selectable skill and module (DESIGN §16), 12 operators per battle on a real stage (all 11 in turn) against the round's real wave three times over, every non-leader enemy kind of data/enemies.json as extra spawns (half of them bounties), placeable summons on the board, every equipment item, band, battle-side 机变 card and stage map card in turn, bonds from the board
bonds 46 battles: every bond at its activation threshold (1 layer) and at its top tier (999 layers)
fields 22 battles: every Final Assault / Hidden Core leader on a pair and a solo template (shared pool, 200 s cap) and the 联防 field with 1 and 2 helpers on its own map (carried HP / SP, a knocked-out operator, two leakers' enemies)
matches 17 matches to the end in virtual time: 16 bot-only (solo 标准 / 险境 / 绝境 / 终极 × 2 seeds, co-op 2 / 3 / 4, one server-run combat match, two runs boosted to the Hidden Core) and one co-op match whose human seat (AI 托管, offline) does not own 7 NORMAL chess — they fight as their 补位 stand-ins (its digest lists them per round, standIns)
standins 10 battles: every NORMAL chess record (normal and elite, 110) fielded as its 补位 stand-in (standIn: true: the stand-in's body, backup skill / module and kit — all 17 stand-ins, every skill a chess names for them), 12 per battle by strength band, laid out by the stand-in's position on a real stage, against the round's real wave three times over plus 8 ground enemy kinds (melee stand-ins always meet an enemy), an item each, bonds from the board

Battles go through the production BattleSpec path (server/sim/spec.js, as browsers and the server's headless fields run them); matches construct Match directly with a VirtualScheduler. Every option is explicit, so a change to a test-harness default never moves a digest.

What a digest ignores on purpose (it does not affect gameplay): object key order, engine unit ids (snapshot ids are renumbered by first appearance, units are named by board uid or def id), board piece uids, the order of client events within a tick (they are counted, not hashed) and wall-clock time. Damage / healing sums are rounded to integers and HP to 2 decimals.

npm test runs the fast subset (the scenarios marked "fast": true: every chess record with its default loadout, every stage and every non-leader enemy kind, every bond at its top tier, six fields, six matches, the five stand-in battles of the normal records — 59 of the 144). GOLDEN_FULL=1 checks everything.

Workflow

GOLDEN_FULL=1 node --test test/golden.test.js   # the whole corpus (≈ 20 s with 4 worker threads)
node --test test/golden.test.js                 # the fast subset
npm run golden                                  # the same comparison from the tool (all families, a short report)
npm run golden:update                           # recompute and rewrite test/golden/*.json
node tools/golden.mjs --twice                   # determinism: the corpus twice in one process, the second pass reversed
  • A refactor commit must never change these files. Run GOLDEN_FULL=1 node --test test/golden.test.js before committing it. If a digest moves, the refactor changed behaviour: find out why (the first differing value of snaps says when two runs parted, in 10-game-second steps) instead of updating the files.
  • An intended gameplay change (a fix, new content, a data rebuild): run npm run golden:update and commit the changed files together with the change, saying in the commit message which scenarios moved and why.
  • Reviewing the diff: every scenario is a block of its own and every unit / enemy / round is one line, so a diff shows which scenarios moved and which values changed. A change confined to the scenarios that contain the touched operator, enemy or rule is expected; a change everywhere (every rngDraws, every snaps) means something global moved — the RNG draw order, the tick order, a shared rule.
  • Adding to the corpus (a new family, more scenarios, a new digest field) changes the files by design: do it in a commit of its own, never together with a refactor.
  • A new engine hook, event or chess record does not change the digests of the existing scenarios by itself — the hook list is frozen in the tool and the corpus is regenerated only on golden:update — but new data records do join the corpus there (the roster family enumerates data/chess.json).