102e05c8ba
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>
77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
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);
|
|
}
|