Builder ManualPlay
Builder Manual/Sandbox School

LEARN / Sandbox School

Sandbox School

Eight guided experiments from first frame to reproducible replay.

Sandbox School: Start Here

Super Mango is a working C11/raylib game you can take apart. Learn one mechanism at a time, then combine them in the playground. You need basic C expressions, functions, structs and pointers; raylib experience is optional.

First successful build

Install the dependencies in the repository README, including CMake and a current Python (3.12+ recommended). Node.js is needed for the full verification tools. Linux editor dialogs use zenity. From the repository root:

make builder CC=clang
make run-editor CC=clang
make run-level-debug LEVEL=levels/labs/01_collision.toml

builder builds the game and editor together. Open a lab in the editor, use Save As to create your own copy, and press F5 to playtest. Playtests use --no-save, so experiments do not change your personal game profile. Use make debug for debugger symbols in out/debug/, or make release for optimized executables in out/release/. Keep runtime working directories at the repository root (or the extracted release directory) so asset paths resolve.

Read the code as a lesson

Simple loops and explanatory comments are deliberate. Follow one concrete frame before trying to generalize the engine:

  1. src/main.c chooses configuration; src/core/app_session.c owns the window, audio device and active screen. A menu/game transition changes the screen, not the process-wide graphics context.
  2. src/input/input_backend.c collects ordered commands. game_events.c decides who handles them; held movement is sampled separately in game_input.c.
  3. src/core/game_loop.c updates the model and asks src/render/game_render.c to draw. src/shared/graphics.c presents the logical canvas in the window.
  4. src/editor/editor_frame.c shows the same sequence for immediate-mode widgets. src/shared/ui.h explains staging an edit before changing the model.

BeginDrawing, DrawTexturePro and UpdateMusicStream are raylib APIs. display_present, sprite_draw, font_load and sound_play are small project helpers; read their headers for ownership, units and failure behavior. This distinction matters when comparing the project with raylib’s own examples.

After each experiment, prefer one clear change over a compact rewrite. Keep the comments that explain an unfamiliar C operation or a resource’s lifetime, and update them when the behavior changes. The Developer Guide describes the conventions used by the rest of the code.

How to use each lab

Before changing anything, predict the result. Make one change, observe it, and explain any difference from your prediction. Record the command, seed and result. Use the mechanics museum for compact examples and Controls for inspection keys.

Lab 1 — Your first frame

  • Goal: distinguish world, logical-screen and window coordinates.
  • Read: src/main.c, src/core/game_loop.c, src/render/game_render.c and src/collectibles/coin.c.
  • Run: make run-level-debug LEVEL=levels/labs/01_collision.toml.
  • Change: move its coin 32 logical pixels right in a copied TOML file.
  • Observe: the coin moves relative to the platform; changing window scale does not change its world coordinates.
  • Proof: explain why rendering subtracts camera.x, but the stored coin position does not. Validate your copied level through the editor before playtesting.

Lab 2 — Motion and numerical integration

  • Goal: understand acceleration, friction and timestep error.
  • Read: src/player/player_motion.c, src/player/player.c, src/core/game_timing.c.
  • Run: make run-level-debug LEVEL=levels/labs/06_camera.toml and make timing-lab.
  • Change: select ground_friction with F6; use minus/equal to adjust it. F7 restores authored/default values.
  • Observe: releasing movement changes stopping distance. F4 slows simulated time; F2 freezes and F3 advances one 1/60-second step.
  • Proof: explain why multiplying by dt gives units of distance but does not eliminate numerical error. The timing lab compares 30/60/144 render rates and a fixed simulation step.

Lab 3 — One-way collisions

  • Goal: understand a crossing test rather than only overlap.
  • Read: src/player/player_surfaces.c and tests/session_test.c (nearest_surface_is_order_independent).
  • Run: make run-level-debug LEVEL=levels/labs/01_collision.toml.
  • Change: lower the second ledge in an editor copy; jump through it and land.
  • Observe: the stored previous foot position determines whether a descending player crossed a surface. The cyan/green foot marker makes that point visible.
  • Proof: the player passes upward through a ledge and lands downward. Run make test after changing collision code; explain why a nearer surface must win regardless of array order.

Lab 4 — State machines

  • Goal: follow transitions between waiting, moving and damaging states.
  • Read: src/hazards/blue_flame.c, src/entities/fish.c, src/core/game_hazards.c and src/collision/game_collision.c.
  • Run: make run-level-debug LEVEL=levels/labs/05_hazards.toml.
  • Change: change one flame duration constant; rebuild and compare against the original.
  • Observe: freeze/step the active hitbox, and compare it with the hurt-immunity timer after a hit.
  • Proof: explain why collision uses the hazard’s updated position, and why a waiting flame must not damage the player. make test exercises the same-frame saw boundary.

Lab 5 — Data-driven design

  • Goal: trace TOML → validated placement → live entity → editor round-trip.
  • Read: the entity walkthrough, src/shared/serializer_parse.c, src/shared/serializer_load_collectibles.c and src/levels/level_loader.c.
  • Run: make run-editor; open a copied collision lab.
  • Change: place a coin, change its coordinates, save, close, reopen, then playtest.
  • Observe: the saved values survive and match the runtime position.
  • Proof: make test checks round-trips and invalid inputs; a fractional integer, NaN or over-capacity array must fail without replacing the active document.

Lab 6 — Ownership and failure

  • Goal: distinguish an owning pointer, a borrowed pointer and a failed construction.
  • Read: src/core/game_lifecycle.c, src/core/game_resources.c, src/shared/serializer_io.c.
  • Run: make sanitize CC=clang.
  • Experiment: read the simulated missing-saw-texture scenario in tests/simulation_test.c; it temporarily clears an in-memory texture slot and restores ownership before cleanup. Do not delete shared assets to perform this exercise.
  • Observe: required gameplay assets fail clearly; the error identifies the path. Failed save/load operations preserve existing data.
  • Proof: explain why if (pointer) free(pointer) alone does not prevent a second free, and why an owner clears its pointer after release.

Lab 7 — Editor commands

  • Goal: understand reversible operations and the distinction between history and document state.
  • Read: src/editor/undo.c, src/editor/editor_undo_apply.c, src/editor/editor_session.c.
  • Run: make run-editor.
  • Change: move a coin, edit the level description, undo both, redo one, then make another edit.
  • Observe: undo restores values; the new edit invalidates redo; returning to the saved contents removes the dirty marker.
  • Proof: run make test. Explain why entity commands use inline values while configuration commands own separately allocated snapshot pairs, and how ownership transfers between stacks.

Lab 8 — Reproducible experiments and portability

  • Goal: separate semantic input and simulation time from keyboard layout and rendering.
  • Read: src/input/game_web_input.c, web/touch-controls.js, src/core/game_experiment.c, src/core/game_random.c.
  • Run: ./out/super-mango --debug --no-save --seed 7 --level levels/labs/06_camera.toml.
  • Experiment: press F8 to restart and record; move, jump and change a tuning field; press F9 to export. Native saves a uniquely named TOML file in the working directory; the browser initiates an explicit download.
  • Replay: ./out/super-mango --level levels/labs/06_camera.toml --experiment mango-experiment-N.toml (replace N with the exported filename).
  • Proof: the replay freezes after the captured simulation steps. make test compares position, velocity, elapsed time, score and checkpoint against the recorded run despite opposing live input. Use unchanged level bytes and the same engine revision. Floating-point/platform differences can still prevent bit-identical cross-platform results.

Completion portfolio

Keep a small learning log: prediction, changed file/property, run command, observed result and explanation. A useful milestone is being able to add a collectible, show its round-trip and undo behavior, and share a reproducible experiment. That is more informative than counting how many documentation pages you opened.