SpaceCities / test /bench.test.js
Claude
Claude Sonnet 5
T-014 (partial): commit tools/bench.js, wire a temporary /__bench endpoint
f8aa93f unverified
Raw History Blame Contribute Delete
4.4 kB
import { test } from "node:test";
import assert from "node:assert/strict";
import { summarizeTimings, benchNaturalMatch, benchStress, benchMemory, runBenchSuite } from "../tools/bench.js";
// tools/bench.js (TASKS.md T-014) is the committed, re-runnable form of the two throwaway spikes
// in docs/analysis/00-feasibility-spikes.md β€” "natural matches" (tools/selfplay.js AI-vs-AI, real
// games) and "stress" (large armies seeded directly and sent at each other on a Gigantic map),
// plus a memory-per-match measurement neither spike took. Wall-clock TIMING is inherently
// non-deterministic and untestable for an exact value, so these tests pin the two things that
// ARE guaranteeable: the SHAPE of what comes back (a scoreboard has to have the right fields to be
// useful), and the underlying SIMULATION's own determinism (same seed, same units/ticks/outcome β€”
// engine/ is already proven deterministic elsewhere; this just confirms the bench harness doesn't
// smuggle in a wall-clock or unseeded pick, the same property test/ailab.test.js's own first test
// guards for tools/ailab.js).
test("summarizeTimings computes mean/p50/p99/max from a sample array, with the expected ordering", () => {
const s = summarizeTimings([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
assert.equal(s.max, 10);
assert.ok(s.p99 <= s.max);
assert.ok(s.p50 <= s.p99);
assert.ok(s.mean > 0 && s.mean <= s.max);
});
test("summarizeTimings on a single sample: every stat equals that one value", () => {
const s = summarizeTimings([4.5]);
assert.deepEqual(s, { mean: 4.5, p50: 4.5, p99: 4.5, max: 4.5 });
});
test("benchNaturalMatch returns a well-formed row and actually advances the match", () => {
const r = benchNaturalMatch({ planetId: "ferros", seed: 1, maxSeconds: 60 });
assert.equal(r.scenario.includes("ferros"), true);
assert.ok(r.ticks > 0, "at least one tick ran");
assert.ok(r.time > 0, "sim time advanced");
assert.ok(r.units >= 0);
assert.ok(r.mean >= 0 && r.p99 >= r.p50 && r.max >= r.p99, "timing stats are internally consistent");
});
test("benchNaturalMatch is deterministic in everything but wall-clock timing: same seed, same tick count and outcome", () => {
const a = benchNaturalMatch({ planetId: "ferros", seed: 7, maxSeconds: 120 });
const b = benchNaturalMatch({ planetId: "ferros", seed: 7, maxSeconds: 120 });
assert.equal(a.ticks, b.ticks);
assert.equal(a.time, b.time);
assert.equal(a.over, b.over);
assert.equal(a.winner, b.winner);
assert.equal(a.units, b.units);
});
test("benchStress seeds two armies of the requested size on a Gigantic map and runs the requested tick count", () => {
const r = benchStress({ armySize: 20, ticks: 30, seed: 1 });
assert.equal(r.armySize, 20);
assert.equal(r.ticks, 30);
assert.ok(r.mean >= 0 && r.p99 >= r.p50 && r.max >= r.p99);
});
test("benchStress is deterministic: same seed and army size produce the same per-tick unit-count curve", () => {
const a = benchStress({ armySize: 16, ticks: 20, seed: 3 });
const b = benchStress({ armySize: 16, ticks: 20, seed: 3 });
assert.deepEqual(a.finalUnitsAlive, b.finalUnitsAlive);
});
test("benchStress's two armies actually fight β€” losses occur by the end of a long enough run", () => {
const r = benchStress({ armySize: 30, ticks: 400, seed: 5 });
assert.ok(r.finalUnitsAlive < 60, "combat over 400 ticks should have destroyed at least some of the 60 seeded units");
});
test("benchMemory returns a non-negative per-match figure and reports whether it could force a GC pass", () => {
const r = benchMemory({ matchCount: 5 });
assert.equal(r.matchCount, 5);
assert.equal(typeof r.gcForced, "boolean");
assert.ok(Number.isFinite(r.perMatchMB));
});
test("runBenchSuite assembles every section into one JSON-able result, with the environment info a reader needs to judge the numbers", () => {
const r = runBenchSuite({ quick: true }); // quick: true keeps this test's own runtime short β€” see that option's own doc below
assert.ok(r.node.startsWith("v"));
assert.ok(r.cpus >= 1);
assert.ok(Array.isArray(r.natural) && r.natural.length > 0);
assert.ok(Array.isArray(r.stress) && r.stress.length > 0);
assert.ok(r.memory && typeof r.memory.perMatchMB === "number");
assert.doesNotThrow(() => JSON.stringify(r), "the whole result must be plain, serializable data β€” this is what an HTTP endpoint returns verbatim");
});