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:
@@ -358,22 +358,19 @@ function processNetworkTick(
|
||||
repairSpeedBonus: number,
|
||||
hotStandbyTicks: number,
|
||||
redundancyBonus: number,
|
||||
): { switchRepairCosts: number; notifications: TickNotification[]; dirty: boolean } {
|
||||
): { switchRepairCosts: number; notifications: TickNotification[]; dirtyDCs: Set<string> } {
|
||||
const notifications: TickNotification[] = [];
|
||||
let switchRepairCosts = 0;
|
||||
let dirty = false;
|
||||
const dirtyDCs = new Set<string>();
|
||||
|
||||
const healthyByTier: Partial<Record<SwitchTier, NetworkSwitch[]>> = {};
|
||||
const repairing: NetworkSwitch[] = [];
|
||||
const failed: NetworkSwitch[] = [];
|
||||
|
||||
for (const sw of Object.values(registry)) {
|
||||
if (sw.status === 'healthy') {
|
||||
(healthyByTier[sw.tier] ??= []).push(sw);
|
||||
} else if (sw.status === 'repairing') {
|
||||
repairing.push(sw);
|
||||
} else if (sw.status === 'failed') {
|
||||
failed.push(sw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,9 +394,9 @@ function processNetworkTick(
|
||||
sw.repairProgress = 0;
|
||||
sw.repairTotal = repairTime;
|
||||
newlyFailed.push(sw);
|
||||
if (sw.dcId) dirtyDCs.add(sw.dcId);
|
||||
switchRepairCosts += SWITCH_TIER_CONFIGS[tier].baseCost * SWITCH_REPAIR_COST_FRACTION;
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,13 +406,14 @@ function processNetworkTick(
|
||||
sw.status = 'healthy';
|
||||
sw.repairProgress = 0;
|
||||
sw.repairTotal = 0;
|
||||
dirty = true;
|
||||
if (sw.dcId) dirtyDCs.add(sw.dcId);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
if (dirtyDCs.size > 0) {
|
||||
for (const sw of Object.values(registry)) {
|
||||
if (sw.uplinkIds.length === 0) continue;
|
||||
if (sw.dcId && !dirtyDCs.has(sw.dcId)) continue;
|
||||
let active = 0;
|
||||
for (const upId of sw.uplinkIds) {
|
||||
if (registry[upId]?.status === 'healthy') active++;
|
||||
@@ -435,7 +433,7 @@ function processNetworkTick(
|
||||
}
|
||||
}
|
||||
|
||||
return { switchRepairCosts, notifications, dirty };
|
||||
return { switchRepairCosts, notifications, dirtyDCs };
|
||||
}
|
||||
|
||||
// --- Interconnect Training Multiplier ---
|
||||
@@ -478,16 +476,13 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
|
||||
const hotStandbyTicks = state.research.completedResearch.includes('network-hot-standby') ? 5 : 0;
|
||||
const redundancyBonus = state.research.completedResearch.includes('network-redundancy') ? 1 : 0;
|
||||
|
||||
// Clone switch registry for mutable operations this tick
|
||||
const registry: Record<string, NetworkSwitch> = {};
|
||||
for (const [id, sw] of Object.entries(state.infrastructure.switchRegistry)) {
|
||||
registry[id] = { ...sw, uplinkIds: [...sw.uplinkIds], downlinkIds: [...sw.downlinkIds] };
|
||||
}
|
||||
// Mutate registry in-place — infrastructure returns a new state anyway
|
||||
const registry = state.infrastructure.switchRegistry;
|
||||
|
||||
// Process network failures/repairs globally
|
||||
const netResult = processNetworkTick(registry, networkResearchBonus, opsEff, repairSpeedBonus, hotStandbyTicks, redundancyBonus);
|
||||
repairCosts += netResult.switchRepairCosts;
|
||||
notifications.push(...netResult.notifications);
|
||||
if (netResult.notifications.length > 0) notifications.push(...netResult.notifications);
|
||||
|
||||
let totalFlops = 0;
|
||||
let totalTrainingFlops = 0;
|
||||
@@ -671,8 +666,8 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
|
||||
|
||||
repairCosts += dcRepairCosts;
|
||||
|
||||
// Recompute DC network summary after failures/repairs
|
||||
if (netResult.dirty && networkSummary.switchIds.length > 0) {
|
||||
// Recompute DC network summary after failures/repairs (only if this DC's switches changed)
|
||||
if (netResult.dirtyDCs.has(dc.id) && networkSummary.switchIds.length > 0) {
|
||||
networkSummary = buildDCSummary(
|
||||
networkSummary.switchIds, networkSummary.networkRackCount, registry,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user