Files
snakePlus/AGENTS.md
T

250 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.nds) & [`snakePlus.elf`](snakePlus.elf)
- **Primary Toolchain:** [devkitPro](https://devkitpro.org) / `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_bg.h # BackgroundContext struct, animated dual-screen BG 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_bg.cpp # Dual-screen procedural tilemaps, scrolling & VBlank palette cycling
│ ├── 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`](include/game_state.h#L9-L12)):
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()`](source/main.cpp#L127-L163).
- Interrupt handler [`handle_vblank()`](source/main.cpp#L117-L125) 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`](source/main.cpp#L91-L112) 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 & Progressive Speed Mechanics
- **Normal Mode:** Starts with length 4, base timer frequency 5.0 Hz (`SPEED_BASE_NORMAL_MHZ = 5000`), scaling up to 13.0 Hz (`SPEED_MAX_NORMAL_MHZ = 13000`).
- **Hard Mode:** Starts with length 2, base timer frequency 9.0 Hz (`SPEED_BASE_HARD_MHZ = 9000`), scaling up to 17.0 Hz (`SPEED_MAX_HARD_MHZ = 17000`), and spawns rotten apples.
- **Progressive Speed Scaling:** Each consumed regular apple increases `state.applesEaten`. Game speed scales linearly with consumed apples up to `SPEED_APPLES_CAP = 40`, granting smooth +200 mHz acceleration per apple. Timer 0 reload registers are recomputed on the fly (`calculate_timer_reload()`).
- **Progressive Background Intensity Scaling:** Visual effects intensify at the exact same rate (`calculate_intensity_31()`):
- Hardware drift scroll speeds accelerate on both top and bottom screens.
- Celestial node oscillation & twinkling breathing frequencies increase via fixed-point phase accumulation (`anim_phase_fp`).
- Color palettes dynamically interpolate from calm cyber tones towards blazing electric plasma cyan (Normal) or volcanic hyper-crimson / solar white-gold (Hard).
- Deep-space backdrop colors illuminate in tandem with the intensity level.
- Apple pickup shockwave pulses flare with heightened luminance.
---
## 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:
```bash
# 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
```bash
# 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`](.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`](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`](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.png) + [`gfx/tilemap.grit`](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/`](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 Background Pipeline ([`include/game_bg.h`](include/game_bg.h), [`source/game_bg.cpp`](source/game_bg.cpp))
- **Dual-Engine Tiled Layers:** Both Main and Sub 2D engines allocate Background Layer 2 (`BgType_Text4bpp`, `BgSize_T_256x256`, `mapBase = 28`, `tileBase = 1`).
- **Main Engine:** BG2 renders a dynamic cyber horizon, corner brackets, and starfield behind the top-screen console HUD and menu options (`bgSetPriority(topScreen.bgId, 0)`, `bgSetPriority(bgMainId, 3)`).
- **Sub Engine:** BG2 renders an animated retro cyber-grid with 8x8 cell alignment dots, ambient celestial nodes, and circuit pathways behind the snake and apple sprites (`bgSetPriority(bottomScreen.bgId, 1)`, `bgSetPriority(bgSubId, 3)`, sprites at priority 0).
- **Smooth 60 FPS Hardware Scrolling:** Both background layers use fixed-point 8.8 subpixel accumulators with `bgSetScroll()` and `bgUpdate()` during VBlank for silky smooth drift at 0% CPU cost. Drift speed accelerates dynamically as apples are consumed.
- **Dynamic VBlank Palette Animation & Progressive Intensity:**
- **Normal Mode:** Neon cyan / teal / midnight blue theme with oscillating sine-wave twinkling stars and breathing grid nodes, transitioning smoothly towards blazing electric plasma cyan at higher apple counts.
- **Hard Mode:** Fiery amber / crimson cyber grid with faster pulse rate, transitioning towards volcanic hyper-crimson and incandescent solar white-gold.
- **Cursed Mode:** Radioactive violet / magenta glitch strobe and high-speed warp drift during inverted controls (further intensified with apple count).
- **Apple Pickup Wave:** Eating an apple triggers a glowing shockwave pulse (`bg_trigger_pulse()`) that ripples through the grid with luminance scaled by current intensity.
- **Deep-Space Backdrop Glow:** Screen backdrops illuminate in sync with intensity levels.
- **Game Over:** Palette dims to a somber dark charcoal / muted red tone with gentle drift deceleration.
### 6.4 Static Analysis & Tooling
- **Clang-Tidy:** Configured in [`.clang-tidy`](.clang-tidy). Enforces modern C++ guidelines, bugprone checks, readability conventions, and snake_case / CamelCase identifiers.
- **Clangd:** Configured in [`.clangd`](.clangd). Removes `-mthumb-interwork` (which clang warns against) and explicitly includes devkitARM GCC 16.1.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`](CMakeLists.txt).
5. **Code Style:**
- Follow the naming and check options specified in [`.clang-tidy`](.clang-tidy) (functions/variables in `snake_case`, classes/structs in `CamelCase`).
- Run `cmake --build build` to verify clean compilation without warnings or errors.
6. **Commit Message Attribution:**
- Every commit message generated by an AI agent **must** include a `Co-authored-by` trailer, 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>
```