Mostly vibe-coded via gemini-2.5-pro over 2 days. I'm a bit astonished, honestly. Now starting to mix my own coding with gemini's.
Controls: left click to fire, space to activate the force field, Enter or click to start/continue.
Look in makefile and point RAYLIB_NATIVE_PATH to the right spot. You will need a native build of raylib. Download a build of raylib from here.
Make sure libcurl, libssl headers are installed: sudo apt install libcurl4-openssl-dev
make
make run
Look in the Makefile and point EMSDK_PATH and RAYLIB_EMSCRIPTEN_PATH to the right spot. You will need a build of emscripten raylib.
make web
make webserve
Now open http://localhost:8000/tailgunner.html and you should see the game.
These notes describe the high-level organization of the code and the role of src/main.c.
main.cis the application bootstrap and primary game loop. It is intentionally small: it initializes subsystems, runs input -> update -> render each frame, and performs teardown.- Subsystems are encapsulated in small "manager" structs (stack-allocated in
main.c):EnemyManager,LaserManager, andForceFieldManager. Each subsystem exportsInit/Update/Drawfunctions that accept a pointer to the manager instance. This avoids module-level globals and makes the state easier to reason about or move to heap if needed. - Audio resources (sounds) are loaded in
main.cand currently played by bothmain.cand some subsystems. Prefer playing audio at the caller level (top-level) for clearer separation of concerns — seesrc/laser.cfor an example where a hit returns a count and the caller plays the explosion sound. - The game loop order is: input handling (fire / forcefield), Update subsystems (lasers, starfield, enemies, forcefield), then Render (3D mode: starfield, enemies, lasers; 2D overlays and UI).
- Important configuration values (counts, lifetimes, radii) live in
src/config.hso tuning is centralized.