T03: game-kernel SOTA survey with measured boardgame.io baseline harness
This commit is contained in:
parent
38ffd8b7fd
commit
a7e31d4210
7 changed files with 376 additions and 1 deletions
116
research/CB-RES-0001-harness/boardgame-io/bench.js
Normal file
116
research/CB-RES-0001-harness/boardgame-io/bench.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Baseline harness for CB-RES-0001 — boardgame.io v0.50.2, headless client.
|
||||
// Measures: (1) throughput of applying moves to authoritative state,
|
||||
// (2) determinism across two seeded runs, (3) commit/reveal-style stage flow.
|
||||
// Fidelity note: synthetic GROUND-shaped workload (3 players, relationship
|
||||
// map, per-player hands, commit list), not a real GROUND implementation.
|
||||
// Run: node bench.js [numMoves]
|
||||
|
||||
const { Client } = require('boardgame.io/client');
|
||||
const { Stage } = require('boardgame.io/core');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const NUM_MOVES = parseInt(process.argv[2] || '100000', 10);
|
||||
const NUM_PLAYERS = 3;
|
||||
|
||||
const SyntheticGround = {
|
||||
name: 'synthetic-ground',
|
||||
setup: ({ ctx }) => ({
|
||||
round: 0,
|
||||
relationships: {}, // "a->b" -> {kind, strength}
|
||||
hands: Object.fromEntries(
|
||||
Array.from({ length: ctx.numPlayers }, (_, i) => [String(i), Array.from({ length: 7 }, (_, c) => c)])
|
||||
),
|
||||
committed: {},
|
||||
revealed: [],
|
||||
score: Object.fromEntries(Array.from({ length: ctx.numPlayers }, (_, i) => [String(i), 0])),
|
||||
}),
|
||||
moves: {
|
||||
commitAction: ({ G, playerID, random }, targetOffset) => {
|
||||
const target = String((Number(playerID) + targetOffset) % NUM_PLAYERS);
|
||||
const card = G.hands[playerID].length > 0 ? G.hands[playerID][0] : random.D6();
|
||||
G.committed[playerID] = { target, card };
|
||||
},
|
||||
resolveAll: ({ G, random }) => {
|
||||
const order = Object.keys(G.committed).sort();
|
||||
for (const p of order) {
|
||||
const { target, card } = G.committed[p];
|
||||
const key = `${p}->${target}`;
|
||||
const rel = G.relationships[key] || { kind: 'rivalry', strength: 0 };
|
||||
rel.strength = (rel.strength + card + random.D6()) % 10;
|
||||
G.relationships[key] = rel;
|
||||
G.score[p] += rel.strength;
|
||||
G.revealed.push({ p, target, card });
|
||||
if (G.revealed.length > 50) G.revealed.shift();
|
||||
}
|
||||
G.committed = {};
|
||||
G.round += 1;
|
||||
},
|
||||
},
|
||||
turn: { activePlayers: { all: Stage.NULL } },
|
||||
};
|
||||
|
||||
function makeClient(seed) {
|
||||
return Client({
|
||||
game: { ...SyntheticGround, seed },
|
||||
numPlayers: NUM_PLAYERS,
|
||||
playerID: '0',
|
||||
});
|
||||
}
|
||||
|
||||
function stateHash(client) {
|
||||
const { G } = client.getState();
|
||||
return crypto.createHash('sha256').update(JSON.stringify(G)).digest('hex');
|
||||
}
|
||||
|
||||
function run(seed, numMoves, collectHash) {
|
||||
const client = makeClient(seed);
|
||||
// Drive all players through commit windows via events/moves.
|
||||
// Each "round" = NUM_PLAYERS commitAction moves + 1 resolveAll.
|
||||
const rounds = Math.floor(numMoves / (NUM_PLAYERS + 1));
|
||||
const t0 = process.hrtime.bigint();
|
||||
for (let r = 0; r < rounds; r++) {
|
||||
for (let p = 0; p < NUM_PLAYERS; p++) {
|
||||
client.updatePlayerID(String(p));
|
||||
client.moves.commitAction(1 + (r % (NUM_PLAYERS - 1)));
|
||||
}
|
||||
client.updatePlayerID('0');
|
||||
client.moves.resolveAll();
|
||||
}
|
||||
const t1 = process.hrtime.bigint();
|
||||
const elapsedMs = Number(t1 - t0) / 1e6;
|
||||
const applied = rounds * (NUM_PLAYERS + 1);
|
||||
return {
|
||||
applied,
|
||||
elapsedMs,
|
||||
movesPerSec: Math.round(applied / (elapsedMs / 1000)),
|
||||
hash: collectHash ? stateHash(client) : null,
|
||||
finalRound: client.getState().G.round,
|
||||
};
|
||||
}
|
||||
|
||||
// --- throughput ---
|
||||
const warm = run(42, Math.min(NUM_MOVES, 5000), false); // warmup/JIT
|
||||
const main = run(42, NUM_MOVES, true);
|
||||
|
||||
// --- determinism: same seed twice, different seed once ---
|
||||
const detA = run(7, 4000, true);
|
||||
const detB = run(7, 4000, true);
|
||||
const detC = run(8, 4000, true);
|
||||
|
||||
// --- memory ---
|
||||
const mem = process.memoryUsage();
|
||||
|
||||
const result = {
|
||||
library: 'boardgame.io@0.50.2',
|
||||
node: process.version,
|
||||
workload: `synthetic-ground ${NUM_PLAYERS}p commit/reveal`,
|
||||
applied_moves: main.applied,
|
||||
elapsed_ms: Math.round(main.elapsedMs),
|
||||
moves_per_sec: main.movesPerSec,
|
||||
final_round: main.finalRound,
|
||||
determinism_same_seed: detA.hash === detB.hash,
|
||||
determinism_diff_seed_differs: detA.hash !== detC.hash,
|
||||
rss_mb: Math.round(mem.rss / 1048576),
|
||||
heap_mb: Math.round(mem.heapUsed / 1048576),
|
||||
};
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
Loading…
Add table
Add a link
Reference in a new issue