Files
ark-stronghold-protocol/public/dev/simcheck/stronghold.js
T

63 lines
3.6 KiB
JavaScript

// Stronghold Protocol's battles for simcheck (core.js): the golden battle corpus — tools/golden.mjs's families roster,
// bonds, fields, standins and diy (its matches are not single battles) — as BattleSpecs (written by
// tools/sim-check.mjs), each built with /sim/spec.js createBattleFromSpec as the browser runner does
// (public/js/battle/runner.js) and stepped tick by tick; a field battle stops at its golden cap like the corpus.
// state after every tick: the battle time, the RNG state and per unit FIELDS (exact bits)
// view b.snapshot(): what viewers are sent
// deep every value reachable from the units, the definitions and the process-wide sequence numbers left out
// result resultDigest(b.result()).hash: what the server's SP_VERIFY compares
// The page (sim-check.js) and the tool (tools/sim-check.mjs) both use it, with the sim and its data loaded their way.
import { deepHash } from './core.js';
/** The data files the sim reads: public/js/battle/runner.js SIM_DATA_FILES (test/sim/simcheck.test.js keeps them equal). */
export const SIM_DATA_FILES = Object.freeze(['chess', 'enemies', 'tokens', 'stages', 'waves', 'bonds', 'items', 'garrisons', 'bands', 'effects', 'backups']);
/** Per unit in the state vector. */
export const FIELDS = Object.freeze(['alive', 'removed', 'deployed', 'x', 'y', 'hp', 'atkCd', 'sp', 'burn', 'neural', 'necrosis', 'apoptosis', 'erosion',
'dmg', 'taken', 'heal', 'blocking']);
// left out of the deep state: references back to definitions and the battle, and buffs.js's process-wide `seq` (it
// depends on what ran before in the same page or process)
const DEEP_SKIP = Object.freeze(['def', 'profile', 'kit', 'battle', 'b', 'seq']);
const QUIET = Object.freeze({ error() {}, warn() {}, info() {}, debug() {} });
/** The scenario list of a specs document: id, golden family, in the fast subset. */
export const scenariosOf = (doc) => doc.specs.map((s) => ({ id: s.id, family: s.fam, fast: s.fast }));
/**
* @param {{ spec: { createBattleFromSpec: Function, resultDigest: Function }, ds: object, doc: { specs: object[] }, logger?: object }} o
* spec: the /sim/spec.js module; ds: a simdata DataSource over SIM_DATA_FILES; doc: the specs document
*/
export function strongholdAdapter({ spec, ds, doc, logger = QUIET }) {
const byId = new Map(doc.specs.map((s) => [s.id, s]));
return {
maxTicks: 200000,
start(sc) {
const s = byId.get(sc.id);
return { b: spec.createBattleFromSpec(s.spec, ds, { logger }), cap: s.cap ?? null };
},
step(r) {
const b = r.b;
if (b.finished) return false;
if (r.cap != null && b.time >= r.cap - 1e-9) { b.forceEnd('forced'); return false; }
b.step();
return true;
},
state(r) {
const b = r.b, v = [b.time, b.rng.state() >>> 0];
for (const u of b.units) {
const e = u.elem || {}, s = u.stats || {};
v.push(u.alive ? 1 : 0, u.removed ? 1 : 0, u.deployed ? 1 : 0, u.x, u.y, u.hp, u.atkCd, u.skill && u.skill.sp != null ? u.skill.sp : -1,
e.burn ?? 0, e.neural ?? 0, e.necrosis ?? 0, e.apoptosis ?? 0, e.erosion ?? 0, s.dmg ?? 0, s.taken ?? 0, s.heal ?? 0, (u.blocking || []).length);
}
return v;
},
labels(r) {
const out = ['time', 'rng'];
for (const u of r.b.units) { const name = `${u.side === 'enemy' ? 'enemy' : 'ally'} ${u.defId}#${u.id}`; for (const f of FIELDS) out.push(`${name}.${f}`); }
return out;
},
view(r) { return r.b.snapshot(); },
deep(r) { return deepHash(r.b.units, { skip: DEEP_SKIP }); },
result(r) { return spec.resultDigest(r.b.result()).hash; },
};
}