Files
AIHostingTycoon/packages/game-simulation/src/simulate.ts
T
josh 416b6bfe8d
Balance Check / balance-simulation (push) Successful in 11m19s
Balance Check / multi-run-balance (push) Has been cancelled
CI / build-and-push (push) Successful in 40s
Add research money costs, longer research times, era-scaled talent costs, and persona strategy
Research now costs money (drained per-tick) with ~2.5-3.5x longer durations by category.
Early-game talent budget costs reduced via era multiplier (startup 0.2x → bigtech 1.0x).
New seed-driven PersonaStrategy with 8 axes of variation for meaningful multi-run testing.
CI multi-run switched from greedy to persona strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 16:14:27 -04:00

80 lines
2.9 KiB
TypeScript

import { runSimulation } from './runner';
import { GreedyStrategy } from './strategies/greedy';
import { RandomStrategy } from './strategies/random';
import { PersonaStrategy } from './strategies/persona';
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()
: strategyName === 'persona' ? new PersonaStrategy(seed ?? 42)
: 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);
}