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:
@@ -38,17 +38,18 @@ export function processCompetitors(state: GameState): CompetitorState {
|
||||
|
||||
const updated = { ...rival };
|
||||
|
||||
// Freshness decay each tick
|
||||
updated.modelFreshness = Math.max(0, updated.modelFreshness - FRESHNESS_DECAY_RATE);
|
||||
|
||||
// Developer ecosystem growth based on personality
|
||||
const ecoGrowth = rival.personality.openSourceTendency * 0.1 + rival.personality.marketingFocus * 0.05;
|
||||
updated.developerEcosystemScore = Math.min(100,
|
||||
updated.developerEcosystemScore + ecoGrowth * 0.01,
|
||||
);
|
||||
|
||||
// Catch-up: if any market share < threshold, cut prices
|
||||
const minShare = Math.min(...Object.values(updated.marketShares));
|
||||
const shares = Object.values(updated.marketShares);
|
||||
let minShare = shares[0];
|
||||
for (let i = 1; i < shares.length; i++) {
|
||||
if (shares[i] < minShare) minShare = shares[i];
|
||||
}
|
||||
if (minShare < COMPETITOR_CATCHUP_SHARE_THRESHOLD) {
|
||||
updated.pricingStrategy = {
|
||||
...updated.pricingStrategy,
|
||||
@@ -61,7 +62,6 @@ export function processCompetitors(state: GameState): CompetitorState {
|
||||
return updated;
|
||||
}
|
||||
|
||||
// Milestone reached — capability jump + model release
|
||||
const { personality } = rival;
|
||||
const capGrowth = (2 + personality.researchFocus * 5 + personality.riskTolerance * 3) *
|
||||
(1 + tick * 0.00005);
|
||||
@@ -84,7 +84,6 @@ export function processCompetitors(state: GameState): CompetitorState {
|
||||
const modelIdx = Math.floor(updated.estimatedCapability / 10);
|
||||
updated.latestModelName = `${rival.name.split(' ')[0]}-${modelNames[Math.min(modelIdx, modelNames.length - 1)]}`;
|
||||
|
||||
// Model release resets freshness
|
||||
updated.modelFreshness = 1.0;
|
||||
updated.lastModelReleaseTick = tick;
|
||||
|
||||
@@ -96,11 +95,12 @@ export function processCompetitors(state: GameState): CompetitorState {
|
||||
return updated;
|
||||
});
|
||||
|
||||
const allCaps = [
|
||||
...rivals.filter(r => r.status === 'active').map(r => r.estimatedCapability),
|
||||
state.models.bestDeployedModelScore,
|
||||
];
|
||||
const industryBenchmark = allCaps.length > 0 ? Math.max(...allCaps) : 0;
|
||||
let industryBenchmark = state.models.bestDeployedModelScore;
|
||||
for (const r of rivals) {
|
||||
if (r.status === 'active' && r.estimatedCapability > industryBenchmark) {
|
||||
industryBenchmark = r.estimatedCapability;
|
||||
}
|
||||
}
|
||||
|
||||
return { rivals, industryBenchmark };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user