14 KiB
14 KiB
AGENTS.md - Developer & Agent Guide for snakePlus
This document provides a comprehensive overview of the snakePlus codebase for AI agents and developers. It details the project architecture, target platform, hardware constraints, build workflows, code organization, and development guidelines.
1. Project Overview
- Project Name: Snake Plus (
snakePlus) - Subtitle: Snake with a twist
- Author: Phillip Kühne
- Platform: Nintendo DS (NDS) Homebrew
- Target CPU: ARM946E-S (
armv5te,-mtune=arm946e-s,-marm,-mthumb-interwork) - ROM Output:
snakePlus.nds&snakePlus.elf - Primary Toolchain: devkitPro /
devkitARM,libnds,maxmod9,grit,mmutil,bin2s,ndstool - Build System: CMake (>= 3.13) with Ninja generator
2. Directory Structure & File Map
snakePlus/
├── CMakeLists.txt # Primary CMake cross-compilation & asset build rules
├── compile_commands.json # Exported compilation database for clangd / clang-tidy
├── icon.bmp # 32x32 banner icon embedded into the .nds ROM
├── melonDS.ini # Local configuration for melonDS emulator
│
├── include/ # C++ Header declarations
│ ├── consts.h # Grid dimensions, scores, tick rates, legacy bitmaps
│ ├── game_audio.h # AudioContext struct, MaxMod sound & BGM declarations
│ ├── game_input.h # InputContext struct, button scanning & menu logic
│ ├── game_logic.h # Snake mechanics, collision detection, apple spawning
│ ├── game_render.h # RenderAssets struct, dual-screen console/OAM draw routines
│ └── game_state.h # Core GameState struct, Scene enum, coordinate containers
│
├── source/ # C++ Implementation files
│ ├── main.cpp # Hardware setup, VBlank IRQ, timer ticks, game bootstrap
│ ├── game_audio.cpp # MaxMod soundbank loading, BGM loop, SFX dispatch
│ ├── game_input.cpp # Key mapping, inverted control logic, reverse-denial, menu navigation
│ ├── game_logic.cpp # Position update, edge wrapping, collision resolution, RNG
│ └── game_render.cpp # OAM sprite rendering, console text HUD & menu screens
│
├── gfx/ # Graphical assets
│ ├── tilemap.png # 8bpp tile sheet (snake head/body/tail, apples)
│ ├── tilemap.grit # Grit conversion configuration (-gt, -gB8, -mRtf, etc.)
│ └── tilemap.xcf # GIMP source image
│
├── sounds/ # Audio assets (built into soundbank by mmutil)
│ ├── 0rigin.xm # FastTracker II background music track (MOD_0RIGIN)
│ ├── 273792__pacomav__plop.wav # Movement plop SFX
│ ├── 332629__treasuresounds__item-pickup.wav # Apple pickup SFX
│ ├── Boom.wav # Rotten / Cursed apple boom SFX
│ ├── denied.wav # Reverse-direction denied thud SFX (synthesized)
│ ├── 167126__crisstanza__unpause.wav # Unpause SFX
│ ├── 167127__crisstanza__pause.wav # Pause SFX
│ └── 73750__timbre__remix-of-benboncan-sad-trombone-more-wah-bright-de-clicked.wav # Fail SFX
│
├── tools/ # Tooling & automation scripts
│ └── clang_tidy_watch.sh # File watcher script running clang-tidy on file change
│
├── .vscode/ # VS Code configuration
│ ├── launch.json # GDB debugger launch for melonDS (port 3333)
│ ├── settings.json # clangd and editor settings
│ └── tasks.json # CMake build, run, clean, and clang-tidy tasks
│
├── .clang-tidy # Clang-tidy rules and naming conventions
├── .clangd # Clangd LSP configuration and system include paths
└── .gitignore # Git ignore rules for build artifacts and caches
3. Game Mechanics & State Flow
3.1 Grid & Screen Setup
- The playfield is a 32 × 24 grid (
X_MAX = 31,Y_MAX = 23). - Each cell corresponds to an 8 × 8 pixel sprite tile.
- Moving outside the boundary wraps around to the opposing edge.
- Dual-Screen Layout:
- Top Screen: Console text display (Score, status effects like "CONTROLS INVERTED!", pause state, debug internals HUD).
- Bottom Screen: Sub-engine 2D graphics (
oamSub), rendering the snake and apples using 8x8 256-color sprites. - Can be swapped in real-time by pressing the R trigger (
lcdSwap()).
3.2 Scenes & State Machine
The game operates in one of two scenes (Scene):
Scene::Menu:- Menu options: Start Game, Hard Mode: ON/OFF, Music: ON/OFF.
- Navigated via D-Pad Up/Down; toggled/started via A or START.
Scene::Playing:- Driven by hardware Timer 0 executing
do_tick(). - Interrupt handler
handle_vblank()handles VBlank frame counting, OAM updates (oamUpdate(&oamSub)), and top-screen stat refreshes.
- Driven by hardware Timer 0 executing
3.3 Game Elements & Apples
- Snake Body: Represented as
std::deque<std::array<int, 3>>where each entry is{x, y, direction}. Alternating segments use regular and small sprite tiles. Head and tail sprites flip dynamically according to direction. - Normal Apple (Red): Spawns if count < 1. Awards +10 score, increases length by 1, plays pickup sound. Flashes/blinks every 4 ticks.
- Rotten Apple (Brown/Green): Spawns in Hard Mode (periodic chance up to 20 on screen). Decreases length by 1, plays explosion SFX. If length drops below 2, triggers Game Over.
- Cursed Apple (Purple): Spawns occasionally. Inverts player steering controls (Up ↔ Down, Left ↔ Right) for
CURSE_DURATION_TICKS(40 ticks). The sprite is procedurally constructed at startup inmain.cpp:91-112by copying the rotten apple tile pixels and mapping them to dedicated palette entries (CURSE_PAL_SHADOW,CURSE_PAL_OUTLINE,CURSE_PAL_FILL,CURSE_PAL_GLOW). - Reverse-Direction Denial: Pressing the D-Pad in the exact opposite direction (i.e. into the snake's own neck) is silently denied instead of causing an immediate Game Over. The game computes the would-be next head position (including edge-wrapping) and compares it to the neck segment; if they match, the direction change is rejected and a subtle "denied" thud SFX plays.
3.4 Game Modes
- Normal Mode: Starts with length 4, timer frequency
TIMER_FREQ_1024(5), background colorRGB15(5,5,5). - Hard Mode: Starts with length 2, timer frequency
TIMER_FREQ_1024(9)(faster), background colorRGB15(10,5,5), spawns rotten apples.
4. Input & Control Mapping
| Context | Button | Action |
|---|---|---|
| Menu | D-Pad Up / Down | Change selected menu item |
| Menu | A / START | Select / Toggle option / Start game |
| Playing | D-Pad | Steer snake (inverted if cursed; reversing into neck is denied with subtle SFX) |
| Playing | START | Pause / Unpause |
| Playing | R | Swap LCD displays (lcdSwap()) |
| Playing | SELECT | Toggle debug internals HUD overlay |
| Playing | START + L | Exit main loop / Quit |
| Paused | B | Return to Main Menu |
| Paused | X | Toggle background music on/off |
| Paused | Y | Toggle Normal/Hard mode and restart |
| Game Over | START | Restart game |
| Game Over | B | Return to Main Menu |
5. Build System & Toolchain
Prerequisites
DEVKITPROenvironment variable pointing to the devkitPro root (e.g./opt/devkitpro).devkitARMtoolchain with GCC cross-compiler (arm-none-eabi-gcc,arm-none-eabi-g++).- Tools on PATH or in
$DEVKITPRO/tools/bin:ndstool,grit,mmutil,bin2s. - Build tools:
cmake(>= 3.13),ninja. - Emulator:
melonDS.
Build Commands
To configure and build from the command line:
# Configure with Ninja generator
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# Copy compile_commands.json to root for clangd
cmake -E copy_if_different build/compile_commands.json compile_commands.json
# Build the NDS ROM image
cmake --build build
Running & Emulation
# Run using the custom CMake target (launches melonDS)
cmake --build build --target run
# Or launch directly with melonDS
melonDS snakePlus.nds
VS Code Integration
The repository includes preconfigured tasks in .vscode/tasks.json:
Run new build(Default Build Task, Ctrl+Shift+B): Configures, compiles, and runs inmelonDS.cmake build: Compiles the project.cmake clean: Cleans build artifacts.clang-tidy (all): Runsrun-clang-tidyacross all source and header files.clang-tidy (watch changed): Runs the background watchertools/clang_tidy_watch.sh.Copy new build to SD: Copies ROM to external SD card mount.
6. Architecture & Implementation Details
6.1 Hardware Subsystems (source/main.cpp)
- Video Modes: Both Main and Sub 2D engines are initialized into
MODE_0_2D. - VRAM Allocation:
VRAM_A: Assigned to Main Engine Background (VRAM_A_MAIN_BG).VRAM_C: Assigned to Sub Engine Background (VRAM_C_SUB_BG).VRAM_D: Assigned to Sub Engine Sprites (VRAM_D_SUB_SPRITE).
- Consoles: Standard text consoles are attached to both screens for debug / stats / menus.
- OAM: Sub-engine OAM is initialized via
oamInit(&oamSub, SpriteMapping_1D_32, false). Sprites are allocated withoamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color). - Timer & IRQ: Hardware Timer 0 triggers
do_tickusingTIMER_FREQ_1024(). InterruptIRQ_VBLANKrunshandle_vblank().
6.2 Asset Pipeline
- Graphics Pipeline:
gfx/tilemap.png+gfx/tilemap.gritare processed bygritinto${CMAKE_CURRENT_BINARY_DIR}/tilemap.sandtilemap.h.- Sprites are referenced by index: tile 1 (segment), 2 (small segment), 3 (head horizontal), 4 (head vertical), 5/6 (apple flash frames), 7/8/9/10 (tail variations), 11 (rotten apple). All 12 sprite tiles are preloaded once into VRAM at startup; directional and blinking variants are rendered via pointer selection and hardware flipping flags without mid-frame DMA transfers.
- Active OAM sprites are clamped at hardware limit (128) and trailing unused sprites are hidden every frame via
oamClear(&oamSub, oamId, 128 - oamId). - Console text HUD rendering in
draw_stats()is throttled via dirty-flag caching (HudCache), eliminating redundantiprintfcalls on VBlank.
- Audio Pipeline:
- Audio files in
sounds/are compiled bymmutilintosoundbank.binand headersoundbank.h. bin2sembedssoundbank.bininto assembly sourcesoundbank_bin.Sand headersoundbank_bin.h.- Audio runs via
libnds'smaxmod9(mm9).
- Audio files in
6.3 Static Analysis & Tooling
- Clang-Tidy: Configured in
.clang-tidy. Enforces modern C++ guidelines, bugprone checks, readability conventions, and snake_case / CamelCase identifiers. - Clangd: Configured in
.clangd. Removes-mthumb-interwork(which clang warns against) and explicitly includes devkitARM GCC 15.2.0 system headers.
7. Guidelines for AI Agents Modifying the Codebase
- Embedded / Bare-Metal Constraints:
- The ARM9 CPU lacks an OS runtime. Avoid throwing exceptions or relying on RTTI (
-fno-exceptions -fno-rttiare compiler flags). - Prefer lightweight containers (
std::array,std::deque,std::vectorwith controlled growth). - Be mindful of OAM hardware limitations (Sub engine supports up to 128 sprites; current game allocates sprite slots per apple and snake segment).
- The ARM9 CPU lacks an OS runtime. Avoid throwing exceptions or relying on RTTI (
- Interrupt & Concurrency Safety:
- Variables accessed across both the main loop, timer ticks, and VBlank IRQ (such as
ticks,frame,snakelength,snakeDirection,internals) must maintain appropriatevolatilequalifiers or thread-safe synchronization.
- Variables accessed across both the main loop, timer ticks, and VBlank IRQ (such as
- Hardware Registers & Memory Banking:
- Graphic tiles and palettes must be transferred using DMA (
dmaCopy) or direct memory copies into allocated VRAM / OAM memory banks. - Do not modify VRAM allocation configurations without ensuring compatibility with console text generators and OAM sub-engines.
- Graphic tiles and palettes must be transferred using DMA (
- Toolchain Flags:
- Keep
-mthumb-interwork,-march=armv5te,-mtune=arm946e-s, and specs-specs=${DEVKITPRO_ROOT}/calico/share/ds9.specsintact inCMakeLists.txt.
- Keep
- Code Style:
- Follow the naming and check options specified in
.clang-tidy(functions/variables insnake_case, classes/structs inCamelCase). - Run
cmake --build buildto verify clean compilation without warnings or errors.
- Follow the naming and check options specified in
- Commit Message Attribution:
- Every commit message generated by an AI agent must include a
Co-authored-bytrailer, separated from the subject/body by a blank line. Each agent should substitute its own name and contact address:Co-authored-by: <Agent Name> <agent-email-or-url> - Example commit message format:
feat: add cursed apple spawn rate tuning Expose CURSE_SPAWN_CHANCE constant in consts.h so it can be adjusted without touching game logic. Co-authored-by: <Agent Name> <agent-email-or-url>
- Every commit message generated by an AI agent must include a