extends Node ## Sim tick loop owner. Sim runs at 20 Hz, render at 60 Hz, decoupled. ## ## Speed factor controls how many sim ticks queue per render frame: ## 1× — 1 tick per 3 render frames (20 ticks/s) ## Fast — 5 ticks per 3 render frames (~100 ticks/s) ## Ultra — 12 ticks per 3 render frames (~240 ticks/s) ## Pause — 0 ## ## Saves are taken between ticks only — never mid-tick — so JobRunner mid-toil ## state round-trips cleanly. See docs/architecture.md "Time / tick model". const SIM_HZ: int = 20 const TICK_INTERVAL_S: float = 1.0 / float(SIM_HZ) enum Speed { PAUSE, NORMAL, FAST, ULTRA } const SPEED_FACTOR: Dictionary = { Speed.PAUSE: 0, Speed.NORMAL: 1, Speed.FAST: 5, Speed.ULTRA: 12, } var current_speed: Speed = Speed.NORMAL var tick: int = 0 var _accum: float = 0.0 func set_speed(new_speed: Speed) -> void: if new_speed == current_speed: return Audit.log("sim", "speed %s → %s (tick %d)" % [Speed.keys()[current_speed], Speed.keys()[new_speed], tick]) current_speed = new_speed _accum = 0.0 # Prevent burst-tick after long pauses. EventBus.speed_changed.emit(int(new_speed)) func _process(delta: float) -> void: if current_speed == Speed.PAUSE: return _accum += delta * SPEED_FACTOR[current_speed] while _accum >= TICK_INTERVAL_S: _accum -= TICK_INTERVAL_S tick += 1 EventBus.sim_tick.emit(tick)