import { Pilot, GroundTarget, SkyZone } from "../../types"; import { FlightPhysicsEngine } from "../flightModel"; import { destructible, control } from "../../types/components"; import { BotAISystem } from "../botAISystem"; import { FlightCommand } from "../../types"; import { MAP_REGISTRY } from "../content/maps/registry"; const FIXED_DT = 1 / 60; export class SimulationSystem { public updateSimulation( accumulator: number, deltaMs: number, matchEnded: boolean, tickCallback: () => void ): number { if (matchEnded) return accumulator; let newAccumulator = accumulator + (deltaMs / 1000); if (newAccumulator > 0.1) newAccumulator = 0.1; // clamp death spiral while (newAccumulator >= FIXED_DT) { tickCallback(); newAccumulator -= FIXED_DT; } return newAccumulator; } public tickPlayers( pilots: Map, statePlayers: any, getNextInput: (id: string, neutral: FlightCommand) => { command: FlightCommand, seq?: number }, neutralCommand: () => FlightCommand, tickCooldowns: (p: Pilot, dt: number) => void, updateDeadPilot: (p: Pilot, dt: number) => void, enforceMapBoundary: (p: Pilot, dt: number) => void, handleWeaponFiring: (p: Pilot, pF: boolean, sF: boolean, dt: number, seq?: number) => void, syncRapierBody: (id: string, p: Pilot) => void, recordHistory: (id: string, p: Pilot) => void, mapId: string ) { for (const [sessionId, pilot] of pilots.entries()) { if (pilot.isBot) continue; const { command, seq } = getNextInput(sessionId, neutralCommand()); const schemaPlayer = statePlayers.get(sessionId); if (schemaPlayer && seq !== undefined) { schemaPlayer.lastProcessedSeq = seq; } if (destructible(pilot.entity).damageModel!.fuselage <= 0) { updateDeadPilot(pilot, FIXED_DT); } else { tickCooldowns(pilot, FIXED_DT); FlightPhysicsEngine.update(pilot, command, FIXED_DT, mapId); enforceMapBoundary(pilot, FIXED_DT); handleWeaponFiring(pilot, command.primaryFire, command.secondaryFire, FIXED_DT, seq); } syncRapierBody(sessionId, pilot); recordHistory(sessionId, pilot); } } public tickBots( pilots: Map, groundTargets: GroundTarget[], skyZones: SkyZone[], tickCooldowns: (p: Pilot, dt: number) => void, updateDeadPilot: (p: Pilot, dt: number) => void, enforceMapBoundary: (p: Pilot, dt: number) => void, handleWeaponFiring: (p: Pilot, pF: boolean, sF: boolean, dt: number, seq?: number) => void, syncRapierBody: (id: string, p: Pilot) => void, recordHistory: (id: string, p: Pilot) => void, mapId: string, neutralCommand: () => FlightCommand ) { for (const [botId, pilot] of pilots.entries()) { if (!pilot.isBot) continue; if (destructible(pilot.entity).damageModel!.fuselage <= 0) { updateDeadPilot(pilot, FIXED_DT); } else { tickCooldowns(pilot, FIXED_DT); BotAISystem.runAIConsensus( pilot, FIXED_DT, Array.from(pilots.values()), groundTargets, skyZones, (p, prim, sec, d) => handleWeaponFiring(p, prim, sec, d), MAP_REGISTRY[mapId]?.world.radius ?? 6000 ); const botCtrl = control(pilot.entity); const cmd = botCtrl.lastCommand || neutralCommand(); FlightPhysicsEngine.update(pilot, cmd, FIXED_DT, mapId); enforceMapBoundary(pilot, FIXED_DT); } syncRapierBody(botId, pilot); recordHistory(botId, pilot); } } }