Files
snakePlus/AGENTS.md
T

13 KiB
Raw Blame History

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):

  1. 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.
  2. 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.

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 in main.cpp:91-112 by 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 color RGB15(5,5,5).
  • Hard Mode: Starts with length 2, timer frequency TIMER_FREQ_1024(9) (faster), background color RGB15(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

  • DEVKITPRO environment variable pointing to the devkitPro root (e.g. /opt/devkitpro).
  • devkitARM toolchain 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 in melonDS.
  • cmake build: Compiles the project.
  • cmake clean: Cleans build artifacts.
  • clang-tidy (all): Runs run-clang-tidy across all source and header files.
  • clang-tidy (watch changed): Runs the background watcher tools/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 with oamAllocateGfx(&oamSub, SpriteSize_8x8, SpriteColorFormat_256Color).
  • Timer & IRQ: Hardware Timer 0 triggers do_tick using TIMER_FREQ_1024(). Interrupt IRQ_VBLANK runs handle_vblank().

6.2 Asset Pipeline

  • Graphics Pipeline:
    • gfx/tilemap.png + gfx/tilemap.grit are processed by grit into ${CMAKE_CURRENT_BINARY_DIR}/tilemap.s and tilemap.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 redundant iprintf calls on VBlank.
  • Audio Pipeline:
    • Audio files in sounds/ are compiled by mmutil into soundbank.bin and header soundbank.h.
    • bin2s embeds soundbank.bin into assembly source soundbank_bin.S and header soundbank_bin.h.
    • Audio runs via libnds's maxmod9 (mm9).

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

  1. Embedded / Bare-Metal Constraints:
    • The ARM9 CPU lacks an OS runtime. Avoid throwing exceptions or relying on RTTI (-fno-exceptions -fno-rtti are compiler flags).
    • Prefer lightweight containers (std::array, std::deque, std::vector with 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).
  2. Interrupt & Concurrency Safety:
    • Variables accessed across both the main loop, timer ticks, and VBlank IRQ (such as ticks, frame, snakelength, snakeDirection, internals) must maintain appropriate volatile qualifiers or thread-safe synchronization.
  3. 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.
  4. Toolchain Flags:
    • Keep -mthumb-interwork, -march=armv5te, -mtune=arm946e-s, and specs -specs=${DEVKITPRO_ROOT}/calico/share/ds9.specs intact in CMakeLists.txt.
  5. Code Style:
    • Follow the naming and check options specified in .clang-tidy (functions/variables in snake_case, classes/structs in CamelCase).
    • Run cmake --build build to verify clean compilation without warnings or errors.