The frame pipeline defines the exact order used to update runtime state,
compose the world view, add overlays, and finally present the image to the
window. In this codebase, that order is fixed and intentional.
Per-frame order
`draw_frame(app)` builds the whole image in one deterministic pass
The frame loop updates state first, renders world layers in order, then presents one final composed image. This ordering is what keeps gameplay, layering, and overlays coherent.
mlx_loop_hook(..., draw_frame, app)
`draw_frame` runs once per loop tick and owns the whole render pipeline.
1
update timing state
refresh `delta_time` and clamp large spikes
2
update_player_input state
consume intent and update player position / camera
3
update bonus doors bonus
door transitions and passability state
4
update bonus HUD bonus
timers, face state, and transient HUD state
5
update bonus pickups bonus
contact checks and stat changes before render
6
retro_begin bonus
swap frame target if retro mode is active
7
draw background world
clear ceiling and floor colors into the frame buffer
8
raycast_scene world
cast one ray per column and draw walls
9
draw sprites bonus
project and depth-test bonus sprites
10
draw minimap bonus
overlay local map information onto the world view
11
draw vignette post
apply post-world shading or edge darkening
12
retro_render bonus
upscale retro framebuffer into output image when enabled
13
draw HUD bonus
final screen-space overlay drawn after world effects
This ordering keeps gameplay state coherent with the frame being drawn.
Frame buffer
A frame is built layer by layer inside `frame.addr`
Each loop tick writes the background first, then textured walls on top. The window receives the image only at the end with one mlx_put_image_to_window call.
ceiling written
if (y < app->frame.height / 2)
put_pixel(&app->frame, x, y, app->ceiling_color);
What is read
`app->ceiling_color` and `app->floor_color` are drawable ints.
`app->tex_no/so/we/ea.addr` contains pixels loaded during `init_mlx`.
`app->map.grid` is read by raycasting to find walls.
What is written
`frame.addr` receives ceiling and floor first.
Wall columns overwrite only the region between `draw_start` and `draw_end`.
`frame.img_ptr` is sent to the window when the image is complete.
The mandatory path still goes through the same draw_frame(...) entry point.
Bonus systems are integrated through dedicated modules and no-op fallbacks, so
shared calls remain safe when a feature is inactive.
If retro mode is disabled, the normal frame buffer is presented directly. If it
is enabled, the world is rendered to the retro target and then upscaled before
final presentation.
The pipeline ends with one mlx_put_image_to_window(...) call.
That matters because the engine writes pixels into memory first, then performs a
single display flush, which is much cleaner and cheaper than per-pixel window calls.