File size: 4,399 Bytes
f8aa93f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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");
});