Add game-simulation package with multi-run balance testing, fix stalled-pipeline trap
Adds a full simulation harness (game-simulation package) with greedy/random strategies, 36-metric diagnostics, multi-run orchestration via child processes, and a statistical interpreter. Includes 2.3x engine performance optimizations (research bonus caching, per-DC dirty tracking, reduced allocations in tick pipeline, single-pass loops). Fixes a critical balance bug where training pipelines stalled on insufficient VRAM would permanently block training slots — the engine never re-checked stalled pipelines, and the greedy strategy didn't pre-check VRAM requirements. This caused 20-25% of seeds to get stuck in Scale-up era. All three fixes (engine un-stalling, strategy VRAM pre-check, stalled pipeline cancellation) bring pass rate from 75% to 100% across 20 random seeds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { runSimulation } from './runner';
|
||||
import { GreedyStrategy } from './strategies/greedy';
|
||||
import { RandomStrategy } from './strategies/random';
|
||||
import { printConsoleReport, generateJsonReport } from './analysis/report';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { SimulationMetrics } from './strategies/types';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
function getArg(name: string, defaultValue: string): string {
|
||||
const idx = args.indexOf(`--${name}`);
|
||||
return idx !== -1 && args[idx + 1] ? args[idx + 1] : defaultValue;
|
||||
}
|
||||
|
||||
function hasFlag(name: string): boolean {
|
||||
return args.includes(`--${name}`);
|
||||
}
|
||||
|
||||
const strategyName = getArg('strategy', 'greedy');
|
||||
const totalTicks = parseInt(getArg('ticks', '28800'), 10);
|
||||
const decisionInterval = parseInt(getArg('interval', '60'), 10);
|
||||
const seedStr = getArg('seed', '');
|
||||
const seed = seedStr ? parseInt(seedStr, 10) : undefined;
|
||||
const jsonOutput = hasFlag('json');
|
||||
const verbose = hasFlag('verbose');
|
||||
const csvOutput = hasFlag('csv');
|
||||
|
||||
const strategy = strategyName === 'random' ? new RandomStrategy() : new GreedyStrategy();
|
||||
|
||||
console.log(`Running ${strategyName} simulation: ${totalTicks.toLocaleString()} ticks, interval ${decisionInterval}${seed !== undefined ? `, seed ${seed}` : ''}...`);
|
||||
|
||||
const result = runSimulation({ totalTicks, decisionInterval, strategy, seed, verbose });
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
if (csvOutput) {
|
||||
const allMetrics = result.metrics;
|
||||
if (allMetrics.length > 0) {
|
||||
const scalarKeys = Object.keys(allMetrics[0]).filter(k => k !== 'completedResearchIds') as (keyof SimulationMetrics)[];
|
||||
const headers = scalarKeys.join(',');
|
||||
const rows = allMetrics.map(m =>
|
||||
scalarKeys.map(k => {
|
||||
const v = m[k];
|
||||
return typeof v === 'number' ? v : String(v);
|
||||
}).join(','),
|
||||
);
|
||||
const csv = [headers, ...rows].join('\n');
|
||||
const csvPath = resolve(__dirname, '..', 'balance-metrics.csv');
|
||||
writeFileSync(csvPath, csv);
|
||||
console.log(`Metrics CSV written to ${csvPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonOutput) {
|
||||
const report = generateJsonReport(result, { totalTicks, decisionInterval, strategy, seed });
|
||||
|
||||
const outPath = resolve(__dirname, '..', 'balance-report.json');
|
||||
writeFileSync(outPath, JSON.stringify(report, null, 2));
|
||||
console.log(`Report written to ${outPath}`);
|
||||
|
||||
printConsoleReport(result, { totalTicks, decisionInterval, strategy, seed }, verbose);
|
||||
|
||||
if (!report.passed) {
|
||||
console.log('FAILED:');
|
||||
for (const reason of report.failureReasons) {
|
||||
console.log(` - ${reason}`);
|
||||
}
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('PASSED: All balance checks within thresholds.');
|
||||
}
|
||||
} else {
|
||||
printConsoleReport(result, { totalTicks, decisionInterval, strategy, seed }, verbose);
|
||||
}
|
||||
Reference in New Issue
Block a user