Game Loop

One fixed 1/60 s step per update, a fixed order inside it, and a frame drawn from the result (as of 2026-11-10).

The loop itself is CNA's: Game::Run() calls GalaxyEggbertGame::Update() and Draw(). The game sets the target elapsed time to 1/60 s, and every Update() advances the simulation by exactly dt = 1.0f / 60.0f; the measured frame time is never used for the simulation. The functions named here are in src/GalaxyEggbertCNA/GalaxyEggbertGameUpdate.cpp unless stated otherwise.

Update()

Each call does at most one tick, in this order:

Update(gameTime)
  first pass only: ApplyFrameRate()                 the Setup's frame rate
  resume replay running?   ReplayBatch(); return    up to 600 recorded ticks, nothing drawn
  frame rate above 60?     less than 1/60 s since the last step: return (draw in between)
  UpdateMusic()
  demo playing?            UpdateDemo(); return     recorded input drives the same UpdateTick()
  one-time start modes     --golden-capture, --golden-capture-trace, --mission N
  mission just started?    StartResumeLog()         (and a demo recording, if armed)
  autoplay?                StepAutoplay()           the script's keys for this tick
  CaptureInput()                                    one input record for the tick
  UpdateDemoMenu()                                  the demo positions' screens
  UpdateTick()
  append the input record to the resume log (and to a demo being recorded)

Because the whole tick reads its input from that one record, the same records replayed give the same game: this is how mission resume and the demos work (Input, Save Data & Resume).

UpdateTick()

  1. The controls help (F1) is checked first; while it is open the screen is frozen and nothing else runs.
  2. A pending phase fade freezes all input and simulation until it commits (1.0 s; 0.25 s into the Setup screens), as in the original.
  3. UpdateTickScreens() handles every phase's own input: the main menu, Setup, Pause, Win and Lost, the Resume question, the editor, the demo screens, and in Play the on-screen pad, pausing, the game-speed keys, typed cheats and F12 (UpdateScreenPlay()). It may end the tick.
  4. The menu's title scene is loaded and animated when the menu or the editor's world list shows.
  5. Outside the Play phase the tick ends here. In Play: simDt = dt * GameSpeedFactor(gameSpeed_), then worldRuntime_.Update(simDt) and UpdateTickWorld().

The Play tick, in order

#StepCode
1World clock. The 20 Hz animation phase and each object's phase advance; the terrain's small animated meshes are rebuilt when the phase changes. WorldRuntime::Update(simDt), TerrainRenderer::Update()
2Input. Directions from the keyboard (arrows or WASD), the on-screen pad and the accelerometer; jump, crouch, look up, back up, the turn-around. ReadPlayDirections(), InputPad::FilterGameplayKeyboard(), AccelTurnInput()
3Blupi. Movement, collision, gravity, swimming, vehicles, crouching, bars, his animation state. BlupiController::Step()
4Tile actions. What he touches or uses: hazards, springs, water, world signs, teleporters, fans, switches, the action key; the lightning's zaps. UpdateTickActions(), PlayBlitzZaps()
5Interactions. Lifts and creature patrols, pickups, enemy and hazard contact, projectiles, dynamite, crates; results come back as events. InteractionSystem::Update()
6World changes. Changed blocks (switches, bridges, doors) patch the terrain from the world's change journal, or rebuild it for unknown history. RefreshTerrainMeshIfChanged()
7Requests. A pickup's flight to the HUD, a death lock, a two-stage pickup; the pollution puff and the magic trail. ResolvePendingVoyage(), ResolveDeathLock(), ResolvePickupFreeze()
8Events. Camera shakes, shots, glue, the vehicle's motor sound, a life lost or the exit reached (the checkpoint is saved here), powers, the balloon. UpdateTickEvents()
9Camera and presentation. F3 first/third person, the chase camera, mouse look, Blupi's clip and its time. UpdateTickCamera()

Each of these helpers returns true when the tick has to end early (the mission was left, a phase took over). Sounds are started where their cause happens, through Sound, heard from Blupi's position. Why the order is what it is, and which system may know what, is on Architecture.

Drawing

Draw() (in GalaxyEggbertGameDraw.cpp) draws nothing during a resume replay. Otherwise it calls DrawFrame():

Draw()
  frame rate above 60, between two steps: move the camera, Blupi and moving objects part way
  DrawFrame()
    the world is visible in this phase?
      BuildSceneFrame()                         GalaxyEggbertGameScene.cpp: the draw list
      WorldRenderer::RenderShadowMap()          3D effects only
      (Ultra: geometry pass; post-process target)
      DrawWorld()  ->  WorldRenderer::Draw(device, sceneFrame_, assets)
      the editor's gizmos, against the world's depth
      finish the post-process, if any
    DrawOverlay()                               HUD, on-screen pad, menus, Pause/Win/Lost, Setup,
                                                demo screens, editor UI, cheat menu, F1 help
    DrawFrameCaptures()                         golden frames, autoplay shots, thumbnails
  put the camera and objects back where the simulation left them

The world is not drawn in the Setup screens (they cover the window) and, in the main menu and the loading screen, only when the title scene is loaded. See Rendering for the passes.

Phases

The game is always in one GalaxyEggbert::Def::GamePhase (include/GalaxyEggbert/Def/GamePhase.hpp), changed through SetPhase() (GalaxyEggbertGameMissions.cpp).

PhaseWhat it is
WaitThe loading screen at start (it lasts a fixed 5 s), also the galaxy's loading screen.
Resume"Continue or menu?" when a resume log or a checkpoint exists.
InitThe main menu: the three players, Play, Setup, Editor, Galaxy, Demo, Demos, Multiplayer, Help, the language's flag, Fullscreen/Window, Exit.
PlayThe game itself.
PauseThe pause screen: continue, restart, back, Setup, menu, and Galaxy's Record (and Original for an edited shipped world).
Win, LostThe end screens (Win only after the true ending, the showcase and play-tests).
MainSetup, PlaySetupThe Setup screen, from the menu or from Pause.
EditorThe 3D world editor and its world and galaxy screens (not an original phase).
NoneUsed as "no fade pending".

First, Trial and Ranking mirror the original's enum and are not used. At start the game shows Wait, then reads resume.txt and goes to Resume if there is a mission to continue or a checkpoint, else to Init.

20 Hz reference ticks, 60 Hz steps

The original game counts time in ticks of 1/20 s. Galaxy steps three times as often but keeps the original's clock: WorldRuntime::Update() advances its animation phase once per 1/20 s of simulated time, object phases run at the same rate, and each tile type divides it by its own divisor (fans and saws every tick, lava every 2, water every 3, spikes every 4). Crates, lifts, creatures and many action timers are defined in 20 Hz ticks copied from the original, and BlupiController converts the original's per-tick speeds. Worlds and autoplay scripts are tuned to those numbers, so a small timing change can break a playthrough without any compile error.

Game speeds

Def::GameSpeed (include/GalaxyEggbert/Def/GameSpeed.hpp) scales simDt, the time the simulation advances per step.

KeySpeedFactor
F5Normal1
F6Fast2
F7, F8Faster, Fastest (only with the quick cheat)4, 8
TabSlow → SuperSlow → UltraSlow → Normal0.5, 0.25, 0.125, 1

Frame rates above 60

The Setup's frame rate is 60, 120, 144 or 240 (frameRate= in savedata.txt). Above 60, ApplyFrameRate() sets CNA's target time to 1/rate and turns vertical sync off. Update() then runs that often, but the game still steps once per 1/60 s of accumulated time, at most one step per pass (no catching up). Between two steps Draw() places the camera (with its shake), Blupi, every moving object and the particles part way between their previous and current positions; nothing in the simulation changes. Captures, autoplay, demo verification and resume replays always run at one step per pass without interpolation. Set GALAXY_INTERP_TRACE=<file> to log each drawn frame's step, fraction and camera.

Determinism

Given the same world, start state, seed and input records, the Play path produces the same result on every run:

Autoplay runs unthrottled (as fast as the machine draws) and still matches tick for tick; AUTOPLAY_REALTIME=1 keeps the 60 Hz pace for watching. The golden frames and trace, the autoplay playthroughs, the resume self-test (GALAXY_RESUME_SELFTEST=N) and the demo checks all rely on this (Testing).

Keep it deterministic. Do not add wall-clock time, unseeded randomness or anything that depends on the frame rate to the simulation. A change that alters the simulation also changes how the shipped demos replay: tools/verify_demos.sh reports it, and the demos must then be recorded again with a new simulation revision (Demos & Recordings).