77 lines
4.1 KiB
JavaScript
77 lines
4.1 KiB
JavaScript
"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);
|