Files
ark-stronghold-protocol/test/golden/README.md
T
sganggs 8a43624935 feedback5 WE: 联防 is played on its own map, the escaped template level (GitHub #41, item 3) — act2autochess constData escapedBattleTemplateMapSinglePlayer / MultiPlayer = level_act1autochess_escaped_single / _multi name the level of the 联防 battle, and its map is the field: two road halves (cols 3–9 and 11–17, rows 9–12) joined at col 10, no water, crates or devices (until 0.1.3 the round's stage, opened to both halves). tools/build-data.mjs builds both into data/stages.json after the 11 battle stages (kind 'unite', helpers 1 / 2, weight 0, inactive, no modes, the name 联防阵地(N名玩家) the remake's own label [ASSUMED]; validation ties config.unite.templates to them); regenerated offline, a JSON compare shows only the two added records. unite.js uniteStageId picks the map of the helper count (escaped_single's routes enter at col 10, escaped_multi's at col 18 through (9,10)); match/unitePhase.js fields the 联防 battle on it (the round's stage only with data that lacks it); each helper on its prep tiles, the first of two shifted 8 columns onto the right half as before (the official halves; player_map_lr_offset 8). Client: screens/game.js draws the shown field's own map and restores the board's on leaving it (a 2-line change in a file of the R5 split; render/app.js enterBattle already loads m.field stageId); the dev mock's 联防 follows. The 联防 BGM (PR #110) is unchanged. Tests: test/match/feedback5-unite-map.test.js (both combat modes, 1 and 2 helpers, degraded data), test/data.test.js, test/render/tiles.test.js. Docs: DATA §12, META §4, DESIGN §3, PLAYING §5. Golden: fields unite-1 / unite-2 (the battle's stage → the 联防 map) and the 6 co-op match scenarios with a 联防 round (coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-FUNNY-6, coop4-ABYSS-7, coop2-NORMAL-8-serverrun); roster / bonds unchanged (tools/golden.mjs leaves the 联防 maps out of its stage list)
2026-10-05 18:17:08 +08:00

4.6 KiB
Raw Blame History

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).
  • 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 16 bot-only matches to the end in virtual time: solo 标准 / 险境 / 绝境 / 终极 × 2 seeds, co-op 2 / 3 / 4, one server-run combat match, two runs boosted to the Hidden Core

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, five matches — 53 of the 133). 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).