Files
ark-stronghold-protocol/public/dev/simcheck/stronghold.js
T
sganggs 221e6b8dd2 0.2.5-dev: community round 30 and follow-ups
- Stock: fixed trait gifts (缪尔赛思 / 变形者集群 变形同构体, 耶拉 不融冰, 蜜蜡 / 卡涅利安 迅捷作战粮, 诗怀雅 盟约之币) still occupy stock but are granted past the cap; an operator card already shown in the shop or a reward can be taken after the pool runs out and overdraws (GitHub #498, the owner's decision); item buys and item rewards keep the stock check. 洛洛 / 焰尾 sub-pools use the measured weights (PR #497 by @2321Robin; the occupancy idea of PR #496 by @2321Robin re-implemented differently).
- Boss pairs are drawn at random once per match and fixed to battle start; the Hidden Core reuses them; a leaver does not split a full pair (the owner's decision).
- 阿戈尔 in 联防 no longer re-applies the opening devour HP loss or a false revive; Touch's team medic stays for the match after its holder leaves (owner-approved, official lifetime unverified).
- Auto-skill ranges: 司霆惊蛰 S2, 斩业星熊 S3 against air, 予愿安洁莉娜 S3; 自在's post-rebirth invulnerability is also untargetable (#510); Horn / Clementia / catapult / 忍冬 first hit (PR #502 by @Xinglan233); 不融冰 / tear gas trigger on real damage.
- Bond layer-up sound in round start / draft / prep / battle (#482): one channel, a new cue replaces the old, no extra cooldown; a prep update that adds layers and activates a bond plays one cue.
- Client: multi-swing fix (#506), refresh single-flight (PR #507 by @robll-v1), LP tower keeps the settled value with a −N tick (PR #504 by @robll-v1), export with only 潜能 / 练度 changes (PR #503 by @Xinglan233), spectator rosters list DIY members (PR #505 by @Xinglan233), banned members beside bond tooltips (PR #494 by @YuriRestia), recovery action above the away overlay (PR #511 by @RainoutOvO), the /dev/sim-check.html consistency page (PR #492 by @Cloudnyco), Korean (PR #490 by @D-Roote) and English Victoria wording (PR #499 by @YuriRestia).
- golden: 45 of 287 scenarios move against 0.2.4 (roster 17, bonds 3, fields 6, matches 17, standins 2), each attributed in docs/history/0.2.5.md §30.9 / §30.16.
2026-10-12 14:07:08 +08:00

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; },
};
}