architecture - jonathanperis/super-mango-editor GitHub Wiki

Architecture

← Home


Overview

Super Mango follows a classic init β†’ loop β†’ cleanup pattern. A single GameState struct is the owner of every resource in the game and is passed by pointer to every function that needs to read or modify it.


Startup Sequence

main()
  β”œβ”€β”€ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)
  β”œβ”€β”€ IMG_Init(IMG_INIT_PNG)
  β”œβ”€β”€ TTF_Init()
  β”œβ”€β”€ Mix_OpenAudio(44100, stereo, 2048 buffer)
  β”‚
  β”œβ”€β”€ game_init(&gs)
  β”‚     β”œβ”€β”€ SDL_CreateWindow  β†’ gs.window
  β”‚     β”œβ”€β”€ SDL_CreateRenderer β†’ gs.renderer
  β”‚     β”œβ”€β”€ SDL_RenderSetLogicalSize(GAME_W, GAME_H)
  β”‚     β”‚
  β”‚     β”‚   ── Load all textures (engine resources) ──
  β”‚     β”œβ”€β”€ parallax_init(&gs.parallax, gs.renderer)  (multi-layer background)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.floor_tile        (grass_tileset.png β€” fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.platform_tex      (platform.png β€” fatal)
  β”‚     β”œβ”€β”€ water_init(&gs.water, gs.renderer)      (water.png)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.spider_tex        (spider.png β€” fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.jumping_spider_tex (jumping_spider.png β€” fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.bird_tex          (bird.png β€” fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.faster_bird_tex   (faster_bird.png β€” fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.fish_tex          (fish.png β€” fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.coin_tex          (coin.png β€” fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.bouncepad_medium_tex (bouncepad_medium.png β€” fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.vine_tex          (vine.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.ladder_tex        (ladder.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.rope_tex          (rope.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.bouncepad_small_tex  (bouncepad_small.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.bouncepad_high_tex   (bouncepad_high.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.rail_tex          (rail.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.spike_block_tex   (spike_block.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.float_platform_tex (float_platform.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.bridge_tex        (bridge.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.yellow_star_tex   (yellow_star.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.axe_trap_tex      (axe_trap.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.circular_saw_tex  (circular_saw.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.blue_flame_tex    (blue_flame.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.faster_fish_tex   (faster_fish.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.spike_tex         (spike.png β€” non-fatal)
  β”‚     β”œβ”€β”€ IMG_LoadTexture β†’ gs.spike_platform_tex (spike_platform.png β€” non-fatal)
  β”‚     β”‚
  β”‚     β”‚   ── Load all sound effects ──
  β”‚     β”œβ”€β”€ Mix_LoadWAV     β†’ gs.snd_spring        (bouncepad.wav β€” non-fatal)
  β”‚     β”œβ”€β”€ Mix_LoadWAV     β†’ gs.snd_axe           (axe_trap.wav β€” non-fatal)
  β”‚     β”œβ”€β”€ Mix_LoadWAV     β†’ gs.snd_flap          (bird.wav β€” non-fatal)
  β”‚     β”œβ”€β”€ Mix_LoadWAV     β†’ gs.snd_spider_attack (spider.wav β€” non-fatal)
  β”‚     β”œβ”€β”€ Mix_LoadWAV     β†’ gs.snd_dive          (fish.wav β€” non-fatal)
  β”‚     β”œβ”€β”€ Mix_LoadWAV     β†’ gs.snd_jump          (player_jump.wav β€” fatal)
  β”‚     β”œβ”€β”€ Mix_LoadWAV     β†’ gs.snd_coin          (coin.wav β€” non-fatal)
  β”‚     β”œβ”€β”€ Mix_LoadWAV     β†’ gs.snd_hit           (player_hit.wav β€” non-fatal)
  β”‚     β”œβ”€β”€ Mix_LoadMUS     β†’ gs.music             (game_music.wav β€” fatal)
  β”‚     β”œβ”€β”€ Mix_PlayMusic(-1)                      (loop forever, 10% volume)
  β”‚     β”‚
  β”‚     β”‚   ── Initialise game objects ──
  β”‚     β”œβ”€β”€ player_init(&gs.player, gs.renderer)
  β”‚     β”œβ”€β”€ fog_init(&gs.fog, gs.renderer)         (fog_background_1.png, fog_background_2.png)
  β”‚     β”œβ”€β”€ hud_init(&gs.hud, gs.renderer)
  β”‚     β”œβ”€β”€ if (debug_mode) debug_init(&gs.debug)
  β”‚     β”œβ”€β”€ level_load(&gs, level_path)             (TOML level file β†’ entity inits, sea gaps, backgrounds, music)
  β”‚     β”œβ”€β”€ hearts/lives/score/score_life_next initialisation
  β”‚     β”œβ”€β”€ SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) β€” lazy init, non-fatal
  β”‚     └── scan joysticks for first connected gamepad
  β”‚
  β”œβ”€β”€ game_loop(&gs)          ← see Game Loop section below
  β”‚
  └── game_cleanup(&gs)       ← reverse init order
        β”œβ”€β”€ SDL_GameControllerClose(gs->controller)  ← if non-NULL
        β”œβ”€β”€ SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER)
        β”œβ”€β”€ hud_cleanup
        β”œβ”€β”€ fog_cleanup
        β”œβ”€β”€ player_cleanup
        β”œβ”€β”€ Mix_HaltMusic + Mix_FreeMusic
        β”œβ”€β”€ Mix_FreeChunk (snd_jump)
        β”œβ”€β”€ Mix_FreeChunk (snd_coin)
        β”œβ”€β”€ Mix_FreeChunk (snd_hit)
        β”œβ”€β”€ Mix_FreeChunk (snd_spring)
        β”œβ”€β”€ Mix_FreeChunk (snd_axe)
        β”œβ”€β”€ Mix_FreeChunk (snd_flap)
        β”œβ”€β”€ Mix_FreeChunk (snd_spider_attack)
        β”œβ”€β”€ Mix_FreeChunk (snd_dive)
        β”œβ”€β”€ water_cleanup
        β”œβ”€β”€ SDL_DestroyTexture (blue_flame_tex)
        β”œβ”€β”€ SDL_DestroyTexture (axe_trap_tex)
        β”œβ”€β”€ SDL_DestroyTexture (circular_saw_tex)
        β”œβ”€β”€ SDL_DestroyTexture (spike_platform_tex)
        β”œβ”€β”€ SDL_DestroyTexture (spike_tex)
        β”œβ”€β”€ SDL_DestroyTexture (spike_block_tex)
        β”œβ”€β”€ SDL_DestroyTexture (bridge_tex)
        β”œβ”€β”€ SDL_DestroyTexture (float_platform_tex)
        β”œβ”€β”€ SDL_DestroyTexture (rail_tex)
        β”œβ”€β”€ SDL_DestroyTexture (bouncepad_high_tex)
        β”œβ”€β”€ SDL_DestroyTexture (bouncepad_medium_tex)
        β”œβ”€β”€ SDL_DestroyTexture (bouncepad_small_tex)
        β”œβ”€β”€ SDL_DestroyTexture (rope_tex)
        β”œβ”€β”€ SDL_DestroyTexture (ladder_tex)
        β”œβ”€β”€ SDL_DestroyTexture (vine_tex)
        β”œβ”€β”€ last_star_cleanup
        β”œβ”€β”€ SDL_DestroyTexture (yellow_star_tex)
        β”œβ”€β”€ SDL_DestroyTexture (coin_tex)
        β”œβ”€β”€ SDL_DestroyTexture (faster_fish_tex)
        β”œβ”€β”€ SDL_DestroyTexture (fish_tex)
        β”œβ”€β”€ SDL_DestroyTexture (faster_bird_tex)
        β”œβ”€β”€ SDL_DestroyTexture (bird_tex)
        β”œβ”€β”€ SDL_DestroyTexture (jumping_spider_tex)
        β”œβ”€β”€ SDL_DestroyTexture (spider_tex)
        β”œβ”€β”€ SDL_DestroyTexture (platform_tex)
        β”œβ”€β”€ SDL_DestroyTexture (floor_tile)
        β”œβ”€β”€ parallax_cleanup
        β”œβ”€β”€ SDL_DestroyRenderer
        └── SDL_DestroyWindow
  β”‚
  β”œβ”€β”€ Mix_CloseAudio
  β”œβ”€β”€ TTF_Quit
  β”œβ”€β”€ IMG_Quit
  └── SDL_Quit

Game Loop

The loop runs at 60 FPS, capped via VSync + a manual SDL_Delay fallback. Each frame has four distinct phases:

while (gs.running) {
  1. Delta Time   β€” measure ms since last frame β†’ dt (seconds)
  2. Events       β€” SDL_PollEvent (quit / ESC key)
                    SDL_CONTROLLERDEVICEADDED   β€” opens a newly plugged-in controller
                    SDL_CONTROLLERDEVICEREMOVED β€” closes and NULLs gs->controller when unplugged
                    SDL_CONTROLLERBUTTONDOWN (START) β€” sets gs->running = 0 to quit
  3. Update       β€” player_handle_input β†’ player_update (incl. bouncepad, float-platform, bridge landing)
                    β†’ bouncepad response (animation + spring sound)
                    β†’ spiders_update β†’ jumping_spiders_update β†’ birds_update β†’ faster_birds_update
                    β†’ fish_update β†’ faster_fish_update β†’ spike_blocks_update β†’ spikes_update
                    β†’ spike_platforms_update β†’ circular_saws_update β†’ axe_traps_update
                    β†’ blue_flames_update β†’ float_platforms_update β†’ bridges_update
                    β†’ spider collision β†’ jumping_spider collision β†’ bird collision β†’ faster_bird collision
                    β†’ fish collision β†’ faster_fish collision β†’ spike_block collision (+ push impulse)
                    β†’ spike collision β†’ spike_platform collision β†’ circular_saw collision
                    β†’ axe_trap collision β†’ blue_flame collision
                    β†’ sea gap fall detection (instant death)
                    β†’ coin–player collision β†’ yellow_star–player collision β†’ last_star–player collision
                    β†’ heart/lives/score_life_next logic
                    β†’ water_update β†’ fog_update β†’ bouncepads_update (small, medium, high)
                    β†’ debug_update (if --debug)
  4. Render       β€” clear β†’ parallax background β†’ platforms β†’ floor tiles
                    β†’ float platforms β†’ spike rows β†’ spike platforms β†’ bridges
                    β†’ bouncepads (medium, small, high) β†’ rails
                    β†’ vines β†’ ladders β†’ ropes β†’ coins β†’ yellow stars β†’ last star
                    β†’ blue flames β†’ fish β†’ faster fish β†’ water
                    β†’ spike blocks β†’ axe traps β†’ circular saws
                    β†’ spiders β†’ jumping spiders β†’ birds β†’ faster birds
                    β†’ player β†’ fog β†’ hud
                    β†’ debug overlay (if --debug) β†’ present
}

Delta Time

Uint64 now = SDL_GetTicks64();
float  dt  = (float)(now - prev) / 1000.0f;
prev = now;

All velocities are expressed in pixels per second. Multiplying by dt (seconds) gives the correct displacement per frame regardless of the actual frame rate.

Render Order (back to front)

Layer What How
1 Background 7 layers from assets/ (parallax_sky.png, parallax_clouds_bg.png, parallax_glacial_mountains.png, parallax_clouds_mg_1/2/3.png, parallax_cloud_lonely.png) tiled horizontally, each scrolling at a different speed fraction of cam_x
2 Platforms platform.png 9-slice tiled pillar stacks (drawn before floor so pillars sink into ground)
3 Floor grass_tileset.png 9-slice tiled across world width at FLOOR_Y, with sea-gap openings
4 Float platforms float_platform.png 3-slice hovering surfaces (static, crumble, rail modes)
5 Spike rows spike.png ground-level spike strips on the floor surface
6 Spike platforms spike_platform.png elevated spike hazard surfaces
7 Bridges bridge.png tiled crumble walkways
8 Bouncepads (medium) bouncepad_medium.png standard-launch spring pads
9 Bouncepads (small) bouncepad_small.png low-launch spring pads
10 Bouncepads (high) bouncepad_high.png high-launch spring pads
11 Rails rail.png bitmask tile tracks for spike blocks and float platforms
12 Vines vine.png climbable plant decorations hanging from platforms
13 Ladders ladder.png climbable ladder structures
14 Ropes rope.png climbable rope segments
15 Coins coin.png collectible sprites drawn on top of platforms
16 Yellow stars yellow_star.png collectible star pickups
17 Last star end-of-level star collectible (uses HUD star sprite)
18 Blue flames blue_flame.png animated flame hazards erupting from sea gaps
19 Fish fish.png animated jumping enemies, drawn before water for submerged look
20 Faster fish faster_fish.png fast aggressive jumping fish enemies
21 Water water.png animated scrolling strip at the bottom
22 Spike blocks spike_block.png rotating rail-riding hazards
23 Axe traps axe_trap.png swinging axe hazards
24 Circular saws circular_saw.png spinning blade hazards
25 Spiders spider.png animated ground patrol enemies
26 Jumping spiders jumping_spider.png animated jumping patrol enemies
27 Birds bird.png slow sine-wave sky patrol enemies
28 Faster birds faster_bird.png fast aggressive sky patrol enemies
29 Player Animated sprite sheet, drawn on top of environment
30 Fog fog_background_1.png / fog_background_2.png semi-transparent sliding overlay
31 HUD hud_render: hearts, lives, score -- always drawn on top
32 Debug debug_render: FPS counter, collision boxes, event log β€” when --debug active

Note: Additional foreground layers (fog, water overlays) can be added per-level via [foreground_layers](/jonathanperis/super-mango-editor/wiki/foreground_layers) in the TOML file, adding up to 8 extra layers above the HUD. The base 32 layers are always present.


Coordinate System

SDL's Y-axis increases downward. The origin (0, 0) is at the top-left of the logical canvas.

(0,0) ──────────────────► x  (GAME_W = 400)
  β”‚
  β”‚   LOGICAL CANVAS (400 Γ— 300)
  β”‚
  β–Ό
  y
(GAME_H = 300)
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚ ←──────── GAME_W (400 px) ─────────────► β”‚
  FLOOR_Y ──►│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
  (300-48=252)β”‚          Grass Tileset (48px tall)        β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

SDL_RenderSetLogicalSize(renderer, 400, 300) makes SDL scale this canvas 2x to fill the 800x600 OS window automatically, giving the chunky pixel-art look with no changes to game logic.


GameState Struct

Defined in game.h. The single container for every runtime resource.

typedef struct {
    SDL_Window         *window;      // OS window handle
    SDL_Renderer       *renderer;    // GPU drawing context
    SDL_GameController *controller;  // first connected gamepad; NULL = none
    ParallaxSystem      parallax;    // multi-layer scrolling background

    SDL_Texture   *floor_tile;       // grass_tileset.png (GPU)
    SDL_Texture   *platform_tex;     // Shared tile for platform pillars (GPU)

    SDL_Texture   *spider_tex;       // Shared texture for all spiders (GPU)
    Spider         spiders[MAX_SPIDERS];
    int            spider_count;

    SDL_Texture   *jumping_spider_tex;  // Shared texture for jumping spiders (GPU)
    JumpingSpider  jumping_spiders[MAX_JUMPING_SPIDERS];
    int            jumping_spider_count;

    SDL_Texture   *bird_tex;         // Shared texture for Bird enemies (GPU)
    Bird           birds[MAX_BIRDS];
    int            bird_count;

    SDL_Texture   *faster_bird_tex;  // Shared texture for FasterBird (GPU)
    FasterBird     faster_birds[MAX_FASTER_BIRDS];
    int            faster_bird_count;

    SDL_Texture   *fish_tex;         // Shared texture for all fish enemies (GPU)
    Fish           fish[MAX_FISH];
    int            fish_count;

    SDL_Texture   *faster_fish_tex;  // Shared texture for faster fish enemies (GPU)
    FasterFish     faster_fish[MAX_FASTER_FISH];
    int            faster_fish_count;

    SDL_Texture   *coin_tex;         // Shared texture for all coin collectibles (GPU)
    Coin           coins[MAX_COINS];
    int            coin_count;

    SDL_Texture   *yellow_star_tex;  // Shared texture for yellow star pickups (GPU)
    YellowStar     yellow_stars[MAX_YELLOW_STARS];
    int            yellow_star_count;

    LastStar       last_star;        // Special end-of-level star collectible

    SDL_Texture   *vine_tex;         // Shared texture for vine decorations (GPU)
    VineDecor      vines[MAX_VINES];
    int            vine_count;

    SDL_Texture   *ladder_tex;       // Shared texture for ladders (GPU)
    LadderDecor    ladders[MAX_LADDERS];
    int            ladder_count;

    SDL_Texture   *rope_tex;         // Shared texture for ropes (GPU)
    RopeDecor      ropes[MAX_ROPES];
    int            rope_count;

    SDL_Texture   *bouncepad_small_tex;    // Shared texture for small bouncepads (GPU)
    Bouncepad      bouncepads_small[MAX_BOUNCEPADS_SMALL];
    int            bouncepad_small_count;

    SDL_Texture   *bouncepad_medium_tex;   // Shared texture for medium bouncepads (GPU)
    Bouncepad      bouncepads_medium[MAX_BOUNCEPADS_MEDIUM];
    int            bouncepad_medium_count;

    SDL_Texture   *bouncepad_high_tex;     // Shared texture for high bouncepads (GPU)
    Bouncepad      bouncepads_high[MAX_BOUNCEPADS_HIGH];
    int            bouncepad_high_count;

    SDL_Texture   *rail_tex;         // Shared texture for all rail tiles (GPU)
    Rail           rails[MAX_RAILS];
    int            rail_count;

    SDL_Texture   *spike_block_tex;  // Shared texture for spike blocks (GPU)
    SpikeBlock     spike_blocks[MAX_SPIKE_BLOCKS];
    int            spike_block_count;

    SDL_Texture   *spike_tex;        // Shared texture for ground spikes (GPU)
    SpikeRow       spike_rows[MAX_SPIKE_ROWS];
    int            spike_row_count;

    SDL_Texture   *spike_platform_tex;  // Shared texture for spike platforms (GPU)
    SpikePlatform  spike_platforms[MAX_SPIKE_PLATFORMS];
    int            spike_platform_count;

    SDL_Texture   *circular_saw_tex;    // Shared texture for circular saws (GPU)
    CircularSaw    circular_saws[MAX_CIRCULAR_SAWS];
    int            circular_saw_count;

    SDL_Texture   *axe_trap_tex;        // Shared texture for axe traps (GPU)
    AxeTrap        axe_traps[MAX_AXE_TRAPS];
    int            axe_trap_count;

    SDL_Texture   *blue_flame_tex;      // Shared texture for blue flames (GPU)
    BlueFlame      blue_flames[MAX_BLUE_FLAMES];
    int            blue_flame_count;

    SDL_Texture   *float_platform_tex;  // float_platform.png 3-slice (GPU)
    FloatPlatform  float_platforms[MAX_FLOAT_PLATFORMS];
    int            float_platform_count;

    SDL_Texture   *bridge_tex;       // bridge.png tile (GPU)
    Bridge         bridges[MAX_BRIDGES];
    int            bridge_count;

    Mix_Chunk     *snd_jump;         // Player jump sound effect (WAV)
    Mix_Chunk     *snd_coin;         // Coin collect sound effect (WAV)
    Mix_Chunk     *snd_hit;          // Player hurt sound effect (WAV)
    Mix_Chunk     *snd_spring;       // Bouncepad spring sound effect (WAV)
    Mix_Chunk     *snd_axe;          // Axe trap swing sound effect (WAV)
    Mix_Chunk     *snd_flap;         // Bird flap sound effect (WAV)
    Mix_Chunk     *snd_spider_attack;// Spider attack sound effect (WAV)
    Mix_Chunk     *snd_dive;         // Fish dive sound effect (WAV)
    Mix_Music     *music;            // Background music stream (WAV)

    Player         player;           // Player data β€” stored by value
    Platform       platforms[MAX_PLATFORMS];
    int            platform_count;
    Water          water;            // Animated water strip at the bottom
    FogSystem      fog;              // Atmospheric fog overlay β€” topmost layer

    int            sea_gaps[MAX_SEA_GAPS];
    int            sea_gap_count;

    Hud            hud;              // HUD display: hearts, lives, score
    int            hearts;           // Current hit points (0-MAX_HEARTS)
    int            lives;            // Remaining lives; <0 triggers game over
    int            score;            // Cumulative score from collecting coins/stars
    int            score_life_next;  // Score threshold for next bonus life

    Camera         camera;           // Viewport scroll position; updated every frame
    int            running;          // Loop flag: 1 = keep going, 0 = quit
    int            paused;           // 1 = window lost focus; physics/music frozen
    int            debug_mode;       // 1 = debug overlays active (--debug flag)
    DebugOverlay   debug;            // FPS counter, collision vis, event log

    // ---- Loop state (persists across frames for emscripten callback) ----
    Uint64         loop_prev_ticks;  // timestamp of previous frame
    int            fp_prev_riding;   // float platform player stood on last frame
} GameState;

Key design decisions:

  • Player is embedded by value, not a pointer. This avoids a heap allocation and keeps the struct self-contained. The same applies to Platform, Water, FogSystem, and all entity arrays.
  • Every pointer is set to NULL after freeing, making accidental double-frees safe.
  • Initialised with GameState gs = {0} so every field starts as 0 / NULL.

Error Handling Strategy

Situation Action
SDL subsystem init failure (in main) fprintf(stderr, ...) β†’ clean up already-inited subsystems β†’ return EXIT_FAILURE
Resource load failure (in game_init) fprintf(stderr, ...) β†’ destroy already-created resources β†’ exit(EXIT_FAILURE)
Sound load failure (non-fatal pattern) fprintf(stderr, ...) then continue -- play is guarded by if (snd_jump)
Optional texture load failure (non-fatal) fprintf(stderr, ...) then continue -- render is guarded by if (texture)

All SDL error strings are retrieved with SDL_GetError(), IMG_GetError(), or Mix_GetError() and printed to stderr.