forked from tas/major_tom
6.6 KiB
6.6 KiB
AGENTS.md — JNR Engine
Important Files
Before starting any task, always read:
- DESIGN.md — Game design document with vision, mechanics, and level plans
- TODO.md — Current roadmap and next tasks to implement
Project Overview
2D side-scrolling platformer (run-and-gun) written in C11 using SDL2, SDL2_image, and SDL2_mixer.
Binary: jnr. Targets Linux (native), WebAssembly (Emscripten), Windows (MinGW cross-compile).
Build Commands
make # Release build (Linux) → ./jnr
make run # Build + run
make debug # Debug build: -g -O0 -DDEBUG
make DEBUG=1 # Alternative debug flag
make web # WASM build → dist-web/
make web-serve # WASM build + HTTP server on :8080
make windows # Cross-compile → dist-win64/
make clean # Remove all build artifacts
Compiler flags: -Wall -Wextra -std=c11 -I include -I src
There are no test or lint targets. Verify changes by building with make and confirming zero warnings.
Cross-platform prerequisites
- WASM builds require the Emscripten SDK. The
emsdk/directory in the project root is gitignored; source the environment before building:source ~/emsdk/emsdk_env.sh # or wherever emsdk is installed make web - Windows cross-compilation requires MinGW (
x86_64-w64-mingw32-gcc) and vendored SDL2 development libraries indeps/win64/(also gitignored).
Project Structure
include/ Global headers (config.h)
src/
engine/ Engine subsystems (.c/.h pairs): physics, tilemap, entity, camera, etc.
game/ Game logic (.c/.h pairs): player, enemy, level, levelgen, editor, etc.
util/ Header-only utilities: vec2.h, darray.h
main.c Entry point, game mode switching, level transitions
assets/
levels/ .lvl level files (plain text)
sounds/ .wav/.ogg audio
sprites/ PNG spritesheets
tiles/ Tileset PNGs
web/ Emscripten HTML shell
Engine code lives in src/engine/, game code in src/game/. Each subsystem is a .c/.h pair.
Header-only utilities use static inline functions.
Code Style
Formatting
- 4 spaces for indentation (no tabs in source; Makefile uses tabs)
- K&R brace style: opening brace on same line
- Pointer declaration:
Type *name(space before*) constfor input-only pointer params:const Tilemap *map- No-parameter functions use
void:void physics_init(void) - Unused parameters:
(void)param;
Naming Conventions
| Kind | Convention | Example |
|---|---|---|
| Functions | snake_case, module-prefixed |
player_update(), tilemap_load() |
| Types/Structs | PascalCase |
Entity, PlayerData, Tilemap |
| Enums | PascalCase type, UPPER_SNAKE values |
EntityType / ENT_PLAYER |
| Macros/Constants | UPPER_SNAKE_CASE |
MAX_ENTITIES, TILE_SIZE |
| Static (file-scope) vars | s_ prefix |
s_gravity, s_renderer |
| Global vars | g_ prefix |
g_engine, g_spritesheet |
| Local vars | Short snake_case |
dt, pos, em, tx |
| Function pointer types | PascalCase + Fn |
EntityUpdateFn |
Includes
Order within each file:
- Own module header (
"game/player.h") - Other project headers (
"engine/physics.h","game/sprites.h") - Standard library (
<stdlib.h>,<string.h>,<math.h>) - SDL headers (
<SDL2/SDL.h>) - Platform-conditional (
#ifdef __EMSCRIPTEN__)
Paths are forward-slash, relative to src/ or include/: "engine/core.h", "config.h".
Comments
- Section headers:
/* ═══...═══ */box-drawing block - Subsections:
/* ── Name ──────── */light-line style - Inline/doc comments:
/* ... */(C89-style, not//) - Struct field comments: trailing, aligned with whitespace padding
Header Guards
#ifndef JNR_MODULE_H
#define JNR_MODULE_H
...
#endif /* JNR_MODULE_H */
Types
floatfor positions, velocities, timers, physics (notdouble)Vec2(float x, y) for all 2D quantitiesboolfrom<stdbool.h>uint16_tfor tile IDs;uint32_tfor bitfield flags and seedsSDL_Colorfor colors;SDL_Rectfor integer rectangles
Error Handling
- Return
boolfor success/failure - Return
NULLfrom creation functions on failure - Errors to
stderrviafprintf(stderr, "...") - Info to
stdoutviaprintf(...) - Warnings use
"Warning: ..."prefix - Early return on failure; no goto-based cleanup
Memory Management
calloc(1, sizeof(T))for entity data (zero-initialized)free(ptr); ptr = NULL;in destroy callbacksmemset(ptr, 0, sizeof(*ptr))for struct re-initialization- Fixed-size arrays for most collections (entity pool, tile defs)
- Dynamic allocation only for tile layers (
uint16_t *) snprintf/strncpywith explicit size limits for stringsASSET_PATH_MAX(256) for path buffers
Architecture Patterns
Entity System
- Fixed pool of
MAX_ENTITIES(512) inEntityManager - Dispatch tables:
update_fn[type],render_fn[type],destroy_fn[type] void *datafor type-specific data (cast in callbacks)- Each entity type:
_register()sets callbacks,_spawn()creates instances - Entity registry maps string names to spawn functions for level loading
Module Pattern
- Public API: declared in header, module-prefixed (
camera_init,camera_follow) - Private helpers:
staticin.conly - File-scope state:
staticvariables withs_prefix - Forward declarations to break circular includes
Level System
.lvlfiles: plain-text directives + tile grid datalevel_load()for handcrafted levels from fileslevel_load_generated()for procedural levels fromTilemapstructs- Exit zones trigger transitions; target strings: file path,
"generate","generate:station", or empty (victory) - Procedural generator: segment-based, theme-driven, difficulty-scaled
Rendering
- Sprite batching: submit to queue via
renderer_submit(), flush layer-by-layer - Draw layers: BG → entities → FG → particles → HUD
- Camera transforms world coords to screen coords
Commit Messages
- Imperative mood, concise
- No co-authored-by or AI attribution
- Example: "Add in-game level editor with auto-discovered tile/entity palettes"
Key Constants (config.h)
| Constant | Value | Notes |
|---|---|---|
SCREEN_WIDTH |
640 | Logical resolution |
SCREEN_HEIGHT |
360 | |
TILE_SIZE |
16 | Pixels per tile |
TICK_RATE |
60 | Fixed timestep Hz |
DEFAULT_GRAVITY |
980.0f | px/s² |
MAX_ENTITIES |
512 | Entity pool size |
MAX_ENTITY_SPAWNS |
128 | Per-level spawn slots |
MAX_EXIT_ZONES |
8 | Per-level exit zones |