"use strict"; // Layer: simulation/system-order // Names the update order in one place. Prefer concrete simulation systems, // with the phase facade kept as a stable named entry point. (function (global) { function phase(name) { return global.TarinaiSimulationSystems.phases?.[name] || null; } const SYSTEM_ORDER = Object.freeze([ { id: "clock", run(worldRef, ctx) { phase("updateClock")?.(worldRef, ctx.dt); } }, { id: "frame_signals", run(worldRef, ctx) { phase("updateFrameSignals")?.(worldRef, ctx.dt); } }, { id: "environment", run(worldRef, ctx) { phase("updateEnvironment")?.(worldRef, ctx.dt); } }, { id: "spatial_prepare", run(worldRef) { phase("prepareSpatialFrame")?.(worldRef); } }, { id: "items_and_ants", run(worldRef, ctx) { ctx.mobile = phase("updateItemsAndAnts")?.(worldRef, ctx.dt) || null; } }, { id: "effects", run(worldRef, ctx) { phase("updateEffects")?.(worldRef, ctx.dt); } }, { id: "creatures", run(worldRef, ctx) { phase("updateCreatures")?.(worldRef, ctx.dt); } }, { id: "trap_holds", run(worldRef) { global.TarinaiItemEnvironmentHazardSystem?.enforceTrapHolds?.(worldRef); } }, { id: "seesaws", run(worldRef, ctx) { global.TarinaiSeesawSystem?.updateWorld?.(worldRef, ctx.dt); } }, { id: "maintenance", run(worldRef, ctx) { phase("runMaintenance")?.(worldRef, ctx.dt, ctx.mobile?.itemCountBefore ?? (worldRef.items || []).length); } }, { id: "phase_events", run(worldRef) { phase("emitPhaseChange")?.(worldRef); } }, { id: "ambient_tarinai", run(worldRef, ctx) { phase("spawnAmbientTarinai")?.(worldRef, ctx.dt); } }, { id: "ambient_grass", run(worldRef, ctx) { phase("spawnAmbientGrass")?.(worldRef, ctx.dt); } }, { id: "rain_water", run(worldRef, ctx) { phase("spawnRainWater")?.(worldRef, ctx.dt); } }, { id: "diagnostics", run(worldRef) { phase("finalizeDiagnostics")?.(worldRef); } }, ]); function runSimulationStep(worldRef, stepDt) { const ctx = { dt: stepDt, mobile: null }; const profiler = global.TarinaiPerf; for (const system of SYSTEM_ORDER) { const end = profiler.begin(`update.phase.${system.id}`) || null; try { system.run(worldRef, ctx); } finally { if (end) end(); } } } const FIXED_SIMULATION_STEP = 0.025; const MAX_FIXED_STEPS_PER_FRAME = 40; function update(worldRef, dt) { const normalize = phase("normalizeDelta"); if (!normalize) throw new Error("Tarinai simulation normalize phase is not available"); const scaledDt = normalize(worldRef, dt); if (scaledDt === null) return; // Always advance the world with one fixed simulation quantum. Previously // slow speeds used many tiny render-frame-sized updates while fast speeds // used fewer 0.05-second chunks. Any per-update random choice, cooldown // edge, collision pass, or AI decision could therefore change simply by // changing the speed control. A fixed accumulator keeps the number and // size of simulation updates identical for the same amount of game time. let accumulator = Math.max(0, Number(worldRef._fixedSimulationAccumulator || 0) || 0); accumulator += Math.max(0, Number(scaledDt) || 0); // Main-loop dt is capped and the public speed control tops out at x16, so // this guard still covers the worst supported frame without dropping time. let steps = 0; while (accumulator + 1e-10 >= FIXED_SIMULATION_STEP && steps < MAX_FIXED_STEPS_PER_FRAME) { runSimulationStep(worldRef, FIXED_SIMULATION_STEP); accumulator -= FIXED_SIMULATION_STEP; steps += 1; } // Keep a bounded remainder if an external caller supplies an unsupported // delta. Normal gameplay never reaches this branch, but avoiding an // unbounded backlog is safer than a permanent catch-up spiral. if (steps >= MAX_FIXED_STEPS_PER_FRAME && accumulator >= FIXED_SIMULATION_STEP) { accumulator = Math.min(accumulator, FIXED_SIMULATION_STEP * 0.999999); } worldRef._fixedSimulationAccumulator = Math.max(0, accumulator); } global.TarinaiSystemOrder = Object.freeze({ update }); })(typeof window !== "undefined" ? window : globalThis);