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()
- The controls help (F1) is checked first; while it is open the screen is frozen and nothing else runs.
- 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.
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.- The menu's title scene is loaded and animated when the menu or the editor's world list shows.
- Outside the Play phase the tick ends here. In Play:
simDt = dt * GameSpeedFactor(gameSpeed_), thenworldRuntime_.Update(simDt)andUpdateTickWorld().
The Play tick, in order
| # | Step | Code |
|---|---|---|
| 1 | World 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() |
| 2 | Input. 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() |
| 3 | Blupi. Movement, collision, gravity, swimming, vehicles, crouching, bars, his animation state. | BlupiController::Step() |
| 4 | Tile actions. What he touches or uses: hazards, springs, water, world signs, teleporters, fans, switches, the action key; the lightning's zaps. | UpdateTickActions(), PlayBlitzZaps() |
| 5 | Interactions. Lifts and creature patrols, pickups, enemy and hazard contact, projectiles, dynamite, crates; results come back as events. | InteractionSystem::Update() |
| 6 | World changes. Changed blocks (switches, bridges, doors) patch the terrain from the world's change journal, or rebuild it for unknown history. | RefreshTerrainMeshIfChanged() |
| 7 | Requests. A pickup's flight to the HUD, a death lock, a two-stage pickup; the pollution puff and the magic trail. | ResolvePendingVoyage(), ResolveDeathLock(), ResolvePickupFreeze() |
| 8 | Events. 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() |
| 9 | Camera 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).
| Phase | What it is |
|---|---|
Wait | The 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. |
Init | The main menu: the three players, Play, Setup, Editor, Galaxy, Demo, Demos, Multiplayer, Help, the language's flag, Fullscreen/Window, Exit. |
Play | The game itself. |
Pause | The pause screen: continue, restart, back, Setup, menu, and Galaxy's Record (and Original for an edited shipped world). |
Win, Lost | The end screens (Win only after the true ending, the showcase and play-tests). |
MainSetup, PlaySetup | The Setup screen, from the menu or from Pause. |
Editor | The 3D world editor and its world and galaxy screens (not an original phase). |
None | Used 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.
| Key | Speed | Factor |
|---|---|---|
| F5 | Normal | 1 |
| F6 | Fast | 2 |
| F7, F8 | Faster, Fastest (only with the quick cheat) | 4, 8 |
| Tab | Slow → SuperSlow → UltraSlow → Normal | 0.5, 0.25, 0.125, 1 |
- Only the simulation is scaled. The camera, sound timers and menus keep the real 1/60 s, and steering is divided by the factor so Blupi turns at the player's pace at any speed.
- Without
quickor ghost, a speed aboveFastdrops back toNormal. - The original's Tab toggled 0.5/1; the three slow steps are Galaxy's (2026-11-09). The enum's raw values are
stored in demo files, so compare speeds with its helpers (
SpeedFactor(),IsSlow()), not by order.
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:
- the step is the constant 1/60 s, never the measured frame time;
- each mission gets a random seed when it loads (
InteractionSystem::SeedRandom()), and the seed is stored in the resume log and in demo files so a replay uses the same one; - nothing in the simulation reads the clock (the real-time clock in
Update()only times the menu's 40-second idle wait before a demo); - the 3D effects are presentation only (the golden trace and the autoplay traces are equal with them on), and interpolation above 60 frames a second only moves what is drawn.
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).
tools/verify_demos.sh reports it, and the demos must then be recorded again with a new
simulation revision (Demos & Recordings).