63 lines
3.6 KiB
JavaScript
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; },
|
|
};
|
|
}
|