diff --git a/.cursor/rules/synced-docs.mdc b/.cursor/rules/synced-docs.mdc new file mode 100644 index 0000000..61b7074 --- /dev/null +++ b/.cursor/rules/synced-docs.mdc @@ -0,0 +1,20 @@ +--- +description: Public prose/API doc trees are synced from PixelRoot32-Game-Engine; edit the engine then npm run sync-docs. +globs: tools/**,examples/**,guide/**,api/**,architecture/**,philosophy/**,migration/**,scripts/sync-docs-from-engine.mjs +--- + +# Generated documentation + +Do **not** hand-edit synced trees except by running `npm run sync-docs` (or `node scripts/sync-docs-from-engine.mjs --engine `) after changing sources in **PixelRoot32-Game-Engine**: + +| Site | Engine (`docs/` unless noted) | +|------|-------------------------------| +| `tools/` | `tools/` | +| `examples/*.md` | `examples/` READMEs + `guide/entities-scene-tutorial.md` | +| `guide/` | `guide/` (includes coding style, guidelines, platform compatibility, `performance/`) | +| `api/` | `api/` | +| `architecture/` | `architecture/` | +| `philosophy/` | `philosophy/` | +| `migration/` | `migration/` | + +See [CONTRIBUTING.md](../../CONTRIBUTING.md) at the repo root. diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index c12d0e7..d83267e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -7,6 +7,8 @@ on: - 'guide/**' - 'api/**' - 'architecture/**' + - 'philosophy/**' + - 'migration/**' - 'examples/**' - 'tools/**' - 'index.md' @@ -14,6 +16,7 @@ on: - '.vitepress/**' - 'package.json' - 'package-lock.json' + - 'scripts/sync-docs-from-engine.mjs' - '.github/workflows/documentation.yml' pull_request: branches: [main, develop] @@ -21,6 +24,8 @@ on: - 'guide/**' - 'api/**' - 'architecture/**' + - 'philosophy/**' + - 'migration/**' - 'examples/**' - 'tools/**' - 'index.md' @@ -28,6 +33,7 @@ on: - '.vitepress/**' - 'package.json' - 'package-lock.json' + - 'scripts/sync-docs-from-engine.mjs' - '.github/workflows/documentation.yml' permissions: @@ -48,6 +54,13 @@ jobs: with: fetch-depth: 1 + - name: Checkout engine (canonical documentation source) + uses: actions/checkout@v4 + with: + repository: PixelRoot32-Game-Engine/PixelRoot32-Game-Engine + path: engine + ref: ${{ vars.ENGINE_DOCS_REF || 'main' }} + - name: Setup Pages uses: actions/configure-pages@v4 @@ -60,6 +73,12 @@ jobs: - name: Install dependencies run: npm ci + - name: Sync all engine docs, verify no drift + run: | + node scripts/sync-docs-from-engine.mjs --engine ./engine + rm -rf ./engine + git diff --stat tools examples guide api architecture philosophy migration + - name: Build documentation env: # Default: GitHub project pages https://.github.io// diff --git a/.gitignore b/.gitignore index ae6b9fa..87d194c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # --- Project (PixelRoot32-Docs) --- # Local scripts not versioned -scripts/ +# scripts/ # (disabled for CI sync script) # Drafts / auxiliary files with prefix _ _* diff --git a/.vitepress/buildApiGeneratedSidebar.ts b/.vitepress/buildApiGeneratedSidebar.ts new file mode 100644 index 0000000..fc49d2b --- /dev/null +++ b/.vitepress/buildApiGeneratedSidebar.ts @@ -0,0 +1,65 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import type { DefaultTheme } from 'vitepress' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +/** Subfolders under `api/generated/` (excluding `index.md` at root). */ +const CATEGORY_ORDER = [ + 'audio', + 'core', + 'math', + 'physics', + 'graphics', + 'input', + 'drivers', + 'platforms', + 'test', +] as const + +const CATEGORY_LABEL: Record = { + audio: 'Audio', + core: 'Core', + math: 'Math', + physics: 'Physics', + graphics: 'Graphics', + input: 'Input', + drivers: 'Drivers', + platforms: 'Platforms', + test: 'Test', +} + +/** + * Sidebar items for **Reference (Auto)**: index + one collapsible group per + * generated category (scanned from disk so new types appear after sync). + */ +export function buildApiGeneratedSidebarItems(): DefaultTheme.SidebarItem[] { + const root = path.join(__dirname, '..', 'api', 'generated') + const items: DefaultTheme.SidebarItem[] = [ + { text: 'Index', link: '/api/generated/' }, + ] + + for (const cat of CATEGORY_ORDER) { + const dir = path.join(root, cat) + if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) continue + + const files = fs + .readdirSync(dir) + .filter((f) => f.endsWith('.md') && f !== 'index.md') + .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })) + + const subItems: DefaultTheme.SidebarItem[] = files.map((f) => { + const name = path.basename(f, '.md') + return { text: name, link: `/api/generated/${cat}/${name}` } + }) + + items.push({ + text: CATEGORY_LABEL[cat] ?? cat, + collapsed: true, + items: subItems, + }) + } + + return items +} diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 4a9d10a..1cd91a5 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -1,4 +1,5 @@ import { defineConfig } from 'vitepress' +import { buildApiGeneratedSidebarItems } from './buildApiGeneratedSidebar' /** Deploy base: `/` locally; CI sets VITEPRESS_BASE (e.g. `/RepoName/` for GitHub project pages). */ function siteBase(): string { @@ -40,8 +41,11 @@ export default defineConfig({ title: 'PixelRoot32 Doc', description: 'A lightweight, modular 2D game engine written in C++17 and designed specifically for ESP32 microcontrollers.', + /** Example READMEs link to repo-relative source paths (./src/...) that are not VitePress routes. */ + ignoreDeadLinks: [/^https?:\/\//, /^\.\//, /^\.\.\//, /\/api\/graphics\/sprite/, /\/api\/graphics\/color/], + /** Repo README: contributor setup only, not public doc pages */ - srcExclude: ['README.md'], + srcExclude: ['README.md', '_legacy_vitepress/**'], head: [ ['link', { rel: 'icon', href: assetUrl('favicon.ico') }], @@ -60,27 +64,30 @@ export default defineConfig({ themeConfig: { logo: assetUrl('logo.png'), - + nav: [ { text: 'Guide', link: '/' }, - { text: 'Tools', link: '/tools/' }, - { text: 'Architecture', link: '/architecture/overview' }, + { text: 'Architecture', link: '/architecture/' }, { text: 'API', link: '/api/' }, - { text: 'Examples', link: '/examples/demos' } + { text: 'Migration', link: '/migration/' }, + { text: 'Philosophy', link: '/philosophy/' }, + { text: 'Tools', link: '/tools/' }, + { text: 'Examples', link: '/examples/demos' }, ], sidebar: { '/': [ + { text: 'Guide home', link: '/guide/' }, { text: 'Introduction', items: [ { text: 'Getting Started', link: '/' }, { text: 'Core Concepts', link: '/guide/core-concepts' }, { text: 'Game Loop', link: '/guide/game-loop' }, - ] + ], }, { - text: 'Systems', + text: 'Game systems', items: [ { text: 'Scenes', link: '/guide/scenes' }, { text: 'Entities & Actors', link: '/guide/entities-actors' }, @@ -89,47 +96,48 @@ export default defineConfig({ { text: 'Physics', link: '/guide/physics' }, { text: 'Audio', link: '/guide/audio' }, { text: 'UI System', link: '/guide/ui-system' }, - ] + ], }, { text: 'Advanced', items: [ - { text: 'Memory Management', link: '/guide/memory' }, - { text: 'Resolution Scaling', link: '/guide/resolution-scaling' }, - { text: 'Tilemaps', link: '/guide/tilemaps' }, - { text: 'Multi-Palette', link: '/guide/multi-palette' }, - { text: 'Platform Configuration', link: '/guide/platform-config' }, - ] + { text: 'Memory', link: '/guide/memory' }, + { text: 'ESP32 Performance', link: '/guide/performance/esp32-performance' }, + { text: 'Graphics Techniques', link: '/guide/graphics-techniques' }, + ], }, { - text: 'Migrations', + text: 'Contributing & quality', items: [ - { text: 'Overview', link: '/guide/migrations/overview' }, - { text: 'v1.0.0', link: '/guide/migrations/v1.0.0' }, - { text: 'v1.1.0', link: '/guide/migrations/v1.1.0' }, - { text: 'v1.2.0', link: '/guide/migrations/v1.2.0' }, - ] + { text: 'Testing', link: '/guide/testing' }, + { text: 'Extending (drivers & hooks)', link: '/guide/extending-pixelroot32' }, + { text: 'Gameplay guidelines', link: '/guide/gameplay-guidelines' }, + { text: 'Entities tutorial', link: '/guide/entities-scene-tutorial' }, + { text: 'Music player', link: '/guide/music-player-guide' }, + ], }, { - text: 'Contributing & tooling', + text: 'Standards & compatibility', items: [ - { text: 'Testing', link: '/guide/testing' }, - { text: 'Extending the engine', link: '/guide/extending' }, - { text: 'Music player', link: '/guide/music-player' }, - ] - } + { text: 'Coding style', link: '/guide/coding-style' }, + { text: 'Graphics guidelines', link: '/guide/graphics-guidelines' }, + { text: 'UI guidelines', link: '/guide/ui-guidelines' }, + { text: 'Platform compatibility', link: '/guide/platform-compatibility' }, + ], + }, ], '/guide/': [ + { text: 'Guide home', link: '/guide/' }, { text: 'Introduction', items: [ { text: 'Getting Started', link: '/' }, { text: 'Core Concepts', link: '/guide/core-concepts' }, { text: 'Game Loop', link: '/guide/game-loop' }, - ] + ], }, { - text: 'Systems', + text: 'Game systems', items: [ { text: 'Scenes', link: '/guide/scenes' }, { text: 'Entities & Actors', link: '/guide/entities-actors' }, @@ -138,35 +146,35 @@ export default defineConfig({ { text: 'Physics', link: '/guide/physics' }, { text: 'Audio', link: '/guide/audio' }, { text: 'UI System', link: '/guide/ui-system' }, - ] + ], }, { text: 'Advanced', items: [ - { text: 'Memory Management', link: '/guide/memory' }, - { text: 'Resolution Scaling', link: '/guide/resolution-scaling' }, - { text: 'Tilemaps', link: '/guide/tilemaps' }, - { text: 'Multi-Palette', link: '/guide/multi-palette' }, - { text: 'Platform Configuration', link: '/guide/platform-config' }, - ] + { text: 'Memory', link: '/guide/memory' }, + { text: 'ESP32 Performance', link: '/guide/performance/esp32-performance' }, + { text: 'Graphics Techniques', link: '/guide/graphics-techniques' }, + ], }, { - text: 'Migrations', + text: 'Contributing & quality', items: [ - { text: 'Overview', link: '/guide/migrations/overview' }, - { text: 'v1.0.0', link: '/guide/migrations/v1.0.0' }, - { text: 'v1.1.0', link: '/guide/migrations/v1.1.0' }, - { text: 'v1.2.0', link: '/guide/migrations/v1.2.0' }, - ] + { text: 'Testing', link: '/guide/testing' }, + { text: 'Extending (drivers & hooks)', link: '/guide/extending-pixelroot32' }, + { text: 'Gameplay guidelines', link: '/guide/gameplay-guidelines' }, + { text: 'Entities tutorial', link: '/guide/entities-scene-tutorial' }, + { text: 'Music player', link: '/guide/music-player-guide' }, + ], }, { - text: 'Contributing & tooling', + text: 'Standards & compatibility', items: [ - { text: 'Testing', link: '/guide/testing' }, - { text: 'Extending the engine', link: '/guide/extending' }, - { text: 'Music player', link: '/guide/music-player' }, - ] - } + { text: 'Coding style', link: '/guide/coding-style' }, + { text: 'Graphics guidelines', link: '/guide/graphics-guidelines' }, + { text: 'UI guidelines', link: '/guide/ui-guidelines' }, + { text: 'Platform compatibility', link: '/guide/platform-compatibility' }, + ], + }, ], '/tools/': [ { @@ -188,8 +196,11 @@ export default defineConfig({ collapsed: true, items: [ { text: 'Overview', link: '/tools/tilemap-editor/overview' }, + { text: 'Quick start', link: '/tools/tilemap-editor/quick-start' }, { text: 'Installation', link: '/tools/tilemap-editor/installation' }, { text: 'Usage guide', link: '/tools/tilemap-editor/usage-guide' }, + { text: 'Advanced guide', link: '/tools/tilemap-editor/advanced-guide' }, + { text: 'Technical reference', link: '/tools/tilemap-editor/technical-reference' }, ] }, ] @@ -199,121 +210,95 @@ export default defineConfig({ { text: 'Architecture', items: [ - { text: 'Overview', link: '/architecture/overview' }, - { text: 'Layer Hierarchy', link: '/architecture/layers' }, - { text: 'Modules', link: '/architecture/modules' }, - { text: 'Design Patterns', link: '/architecture/patterns' }, - ] + { text: 'Overview', link: '/architecture/' }, + ], }, { - text: 'Subsystems', + text: 'Layers', items: [ - { text: 'Rendering Pipeline', link: '/architecture/rendering-pipeline' }, - { text: 'Physics System', link: '/architecture/physics-system' }, - { text: 'Audio Architecture', link: '/architecture/audio-architecture' }, - { text: 'Memory System', link: '/architecture/memory-system' }, - ] + { text: 'Layer 0 — Hardware', link: '/architecture/layer-hardware' }, + { text: 'Layer 1 — Drivers', link: '/architecture/layer-drivers' }, + { text: 'Layer 2 — Abstraction', link: '/architecture/layer-abstraction' }, + { text: 'Layer 3 — Systems', link: '/architecture/layer-systems' }, + { text: 'Layer 4 — Scene', link: '/architecture/layer-scene' }, + ], }, { - text: 'Deep dives', + text: 'Subsystems', items: [ - { text: 'Touch input', link: '/architecture/ARCH_TOUCH_INPUT' }, - { text: 'Resolution scaling', link: '/architecture/ARCH_RESOLUTION_SCALING' }, - { text: 'Tile animation', link: '/architecture/ARCH_TILE_ANIMATION' }, - ] - } + { text: 'Audio subsystem', link: '/architecture/audio-subsystem' }, + { text: 'Physics subsystem', link: '/architecture/physics-subsystem' }, + { text: 'Memory system', link: '/architecture/memory-system' }, + { text: 'Resolution scaling', link: '/architecture/resolution-scaling' }, + { text: 'Tile animation', link: '/architecture/tile-animation' }, + { text: 'Touch input', link: '/architecture/touch-input' }, + ], + }, ], '/api/': [ { text: 'Overview', - items: [{ text: 'API home', link: '/api/' }] - }, - { - text: 'Core', - items: [ - { text: 'Engine', link: '/api/core/engine' }, - { text: 'Scene', link: '/api/core/scene' }, - { text: 'Entity', link: '/api/core/entity' }, - { text: 'Actor', link: '/api/core/actor' }, - { text: 'SceneManager', link: '/api/core/scene-manager' }, - ] - }, - { - text: 'Graphics', - items: [ - { text: 'Renderer', link: '/api/graphics/renderer' }, - { text: 'DrawSurface', link: '/api/graphics/draw-surface' }, - { text: 'Sprite', link: '/api/graphics/sprite' }, - { text: 'TileMap', link: '/api/graphics/tilemap' }, - { text: 'Color', link: '/api/graphics/color' }, - { text: 'Font', link: '/api/graphics/font' }, - ] - }, - { - text: 'Physics', - items: [ - { text: 'CollisionSystem', link: '/api/physics/collision-system' }, - { text: 'KinematicActor', link: '/api/physics/kinematic-actor' }, - { text: 'RigidActor', link: '/api/physics/rigid-actor' }, - { text: 'StaticActor', link: '/api/physics/static-actor' }, - { text: 'SensorActor', link: '/api/physics/sensor-actor' }, - ] + items: [{ text: 'Overview', link: '/api/' }], }, { - text: 'Audio', + text: 'Modules', items: [ - { text: 'AudioEngine', link: '/api/audio/audio-engine' }, - { text: 'MusicPlayer', link: '/api/audio/music-player' }, - { text: 'AudioScheduler', link: '/api/audio/audio-scheduler' }, - ] + { text: 'Configuration', link: '/api/config' }, + { text: 'Math', link: '/api/math' }, + { text: 'Core', link: '/api/core' }, + { text: 'Physics', link: '/api/physics' }, + { text: 'Graphics', link: '/api/graphics' }, + { text: 'UI', link: '/api/ui' }, + { text: 'Audio', link: '/api/audio' }, + { text: 'Input', link: '/api/input' }, + { text: 'Platform', link: '/api/platform' }, + ], }, { - text: 'Input', - items: [ - { text: 'InputManager', link: '/api/input/input-manager' }, - { text: 'Touch System', link: '/api/input/touch-system' }, - ] + text: 'Reference (Auto)', + items: buildApiGeneratedSidebarItems(), }, + ], + '/philosophy/': [ { - text: 'Math', + text: 'Philosophy', items: [ - { text: 'Scalar', link: '/api/math/scalar' }, - { text: 'Vector2', link: '/api/math/vector2' }, - { text: 'Rect', link: '/api/math/rect' }, - ] + { text: 'Overview', link: '/philosophy/' }, + { text: 'Engine philosophy', link: '/philosophy/engine-philosophy' }, + ], }, + ], + '/migration/': [ { - text: 'Platform', + text: 'Migration', items: [ - { text: 'EngineConfig', link: '/api/platform/engine-config' }, - { text: 'PlatformCapabilities', link: '/api/platform/platform-capabilities' }, - { text: 'PlatformMemory', link: '/api/platform/platform-memory' }, - ] + { text: 'Overview', link: '/migration/' }, + { text: 'v1.0.0', link: '/migration/migration-v1-0-0' }, + { text: 'v1.1.0', link: '/migration/migration-v1-1-0' }, + { text: 'v1.2.0', link: '/migration/migration-v1-2-0' }, + ], }, - { - text: 'Module docs', - items: [ - { text: 'Configuration flags', link: '/api/modules/configuration' }, - { text: 'UI module', link: '/api/modules/ui' }, - ] - } ], '/examples/': [ { text: 'Examples', items: [ - { text: 'Samples index (repo catalogue)', link: '/examples/demos' }, + { text: 'Samples (repo catalogue)', link: '/examples/demos' }, { text: 'Hello World', link: '/examples/hello-world' }, { text: 'Camera', link: '/examples/camera' }, { text: 'Dual Palette', link: '/examples/dual-palette' }, { text: 'Sprites', link: '/examples/sprite-animation' }, { text: 'Snake', link: '/examples/snake' }, + { text: 'Brick Breaker', link: '/examples/brick-breaker' }, + { text: 'Space Invaders', link: '/examples/space-invaders' }, { text: 'Physics', link: '/examples/physics-demo' }, { text: 'Metroidvania', link: '/examples/metroidvania' }, { text: 'Animated Tilemap', link: '/examples/animated-tilemap' }, { text: 'Tilemaps (overview)', link: '/examples/tilemap-scene' }, - { text: 'Tic Tac Toe', link: '/examples/ui-layout' }, + { text: 'Tic Tac Toe', link: '/examples/tic-tac-toe' }, + { text: 'UI layout (Tic Tac Toe)', link: '/examples/ui-layout' }, { text: 'Flappy Bird', link: '/examples/flappy-bird' }, + { text: 'Music demo (audio)', link: '/examples/music-demo' }, { text: 'Audio (Snake + Tic Tac Toe)', link: '/examples/audio-playback' }, { text: 'Entities tutorial (not in repo)', link: '/examples/basic-usage' }, ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b3725b9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing to PixelRoot32-Docs + +## Do not edit synced documentation by hand + +These paths are **produced from the [PixelRoot32-Game-Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository** and must match `npm run sync-docs`: + +| Site path | Engine source | +|-----------|----------------| +| `tools/**` | `docs/tools/` | +| `examples/*.md` | `examples/*/README.md`, `examples/README.md`, and `docs/guide/entities-scene-tutorial.md` → `basic-usage.md` | +| `guide/**` | `docs/guide/` (includes standards: coding style, guidelines, platform compatibility, performance) | +| `api/**` | `docs/api/` | +| `architecture/**` | `docs/architecture/` | +| `philosophy/**` | `docs/philosophy/` | +| `migration/**` | `docs/migration/` | + +**Edit the engine**, then refresh the site copy: + +```bash +# From this repo (PixelRoot32-Docs), with the engine cloned alongside or anywhere: +set PIXELROOT32_ENGINE_ROOT=C:\path\to\PixelRoot32-Game-Engine +npm run sync-docs +``` + +Or: + +```bash +node scripts/sync-docs-from-engine.mjs --engine /path/to/PixelRoot32-Game-Engine +``` + +Commit the updated synced trees so CI `git diff` after sync stays clean. + +**CI:** the workflow checks out the engine (`ref` from repo variable `ENGINE_DOCS_REF`, default `main`), runs the same sync, and **fails the build** if any of `tools/`, `examples/`, `guide/`, `api/`, `architecture/`, `philosophy/`, or `migration/` differ from the generated output. + +## Site-only files (not synced) + +- `.vitepress/**` — theme, nav, sidebars, build config +- `index.md` / `home.md` if present outside rewrites +- Root `README.md` (excluded from the public site) + +When adding new engine doc pages, update `.vitepress/config.ts` sidebars if they should appear in navigation. diff --git a/README.md b/README.md index 10dacb6..e44e04b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,16 @@ This repository contains the **official documentation site** for [PixelRoot32](h **Live site:** [docs.pixelroot32.org](https://docs.pixelroot32.org) +## Sync from the engine + +`tools/` and `examples/*.md` are generated from the game engine repo. After changing engine docs or example READMEs, run: + +```bash +npm run sync-docs +``` + +(requires `PIXELROOT32_ENGINE_ROOT` or `--engine`; see [CONTRIBUTING.md](./CONTRIBUTING.md).) + ## License MIT License — see the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository for details. diff --git a/api/audio.md b/api/audio.md new file mode 100644 index 0000000..d5a419e --- /dev/null +++ b/api/audio.md @@ -0,0 +1,120 @@ +# API Reference: Audio Module + +> **Source of truth:** +> - `include/audio/AudioEngine.h` +> - `include/audio/MusicPlayer.h` +> - `include/audio/AudioTypes.h` +> - `include/audio/AudioMusicTypes.h` +> - `include/audio/AudioConfig.h` +> - `include/audio/ApuCore.h` + +## Overview + +The Audio module provides a **NES-inspired** synthesis stack (Pulse, Triangle, Noise, **SINE**, **SAW**) backed by a **dynamic voice pool** inside **`ApuCore`** (default **`MAX_VOICES = 8`**), a lightweight melody subsystem for background music, an optional **master bitcrush**, **linear frequency sweep** on eligible one-shots, a **tick-aligned arpeggiator**, and an optional **post-mix mono hook** on the final buffer. Games still author sounds with **`WaveType`** on **`AudioEvent`** / **`MusicTrack`**; allocation and optional **voice stealing** happen inside `ApuCore`. Synthesis, mixing, and sequencing live in **`ApuCore`**; `AudioEngine` and the platform schedulers are thin facades. + +> **Note**: The audio system is only available if `PIXELROOT32_ENABLE_AUDIO=1` + +## Key Concepts + +### WaveType & VoiceType + +- `PULSE`: Square wave with variable duty cycle. +- `TRIANGLE`: Triangle wave (fixed volume/duty). +- `NOISE`: LFSR-based noise (deterministic, NES-style 15-bit polynomial). +- `SINE`: Band-limited sine via LUT. +- `SAW`: Sawtooth from a linear phase ramp. + +All `WaveType` values share the same **`MAX_VOICES`** pool. Under contention the implementation may steal a voice (shortest remaining time) to make room for a new event. Internally, `VoiceType` mirrors these for allocation logic. + +### Predefined Instrument Presets + +Melodic instruments: `INSTR_PULSE_LEAD`, `INSTR_PULSE_HARMONY`, `INSTR_PULSE_PAD`, `INSTR_PULSE_BASS`, `INSTR_TRIANGLE_LEAD`, `INSTR_TRIANGLE_PAD`, `INSTR_TRIANGLE_BASS`. + +Percussion instruments (duty=0, use with WaveType::NOISE): `INSTR_KICK`, `INSTR_SNARE`, `INSTR_HIHAT`. + +## Usage Examples + +### Playing Sound Effects + +```cpp +auto& audio = engine.getAudioEngine(); + +AudioEvent evt{}; +evt.type = WaveType::PULSE; +evt.frequency = 1500.0f; +evt.duration = 0.12f; +evt.volume = 0.8f; +evt.duty = 0.5f; + +audio.playEvent(evt); +``` + +Sweep example (pulse or triangle only; ignored for `NOISE`): + +```cpp +AudioEvent sweep{}; +sweep.type = WaveType::PULSE; +sweep.frequency = 2000.0f; // start Hz +sweep.sweepEndHz = 400.0f; // end Hz +sweep.sweepDurationSec = 0.15f; // active when both this and sweepEndHz > 0 +sweep.duration = 0.25f; +sweep.volume = 0.7f; +sweep.duty = 0.5f; +audio.playEvent(sweep); +``` + +### Playing Music + +```cpp +using namespace pixelroot32::audio; + +static const MusicNote MELODY[] = { + makeNote(INSTR_PULSE_LEAD, Note::C, 0.20f), + makeNote(INSTR_PULSE_LEAD, Note::E, 0.20f), + makeNote(INSTR_PULSE_LEAD, Note::G, 0.25f), + makeRest(0.10f), +}; + +static const MusicTrack GAME_MUSIC = { + MELODY, + sizeof(MELODY) / sizeof(MusicNote), + true, + WaveType::PULSE, + 0.5f +}; + +void MyScene::init() { + engine.getMusicPlayer().play(GAME_MUSIC); +} +``` + +## Architecture Notes + +- **ApuCore**: Synthesis, non-linear mixing, HPF, master bitcrush, post-mix hooks, sequencing, and the SPSC command queue live here. `DefaultAudioScheduler` and platform variants decide when `generateSamples` runs. +- **ESP32 buffer notes**: I2S backends typically aggregate 1024 samples per DMA transaction. Internal DAC output uses I2S in `I2S_MODE_DAC_BUILT_IN`. +- On **no-FPU** ESP32, `ApuCore` uses an integer oscillator mirror and a precomputed mixer LUT so the inner loop avoids soft-float. +- **Noise / LFSR**: Deterministic everywhere (no `rand()`). + +## Configuration + +| Flag / Concept | Default | Description | +|----------------|---------|-------------| +| `PIXELROOT32_ENABLE_AUDIO` | `1` | Enable/disable entire audio subsystem | +| `PIXELROOT32_NO_DAC_AUDIO` | - | Disable internal DAC backend on classic ESP32 | +| `PIXELROOT32_NO_I2S_AUDIO` | - | Disable I2S audio backend | +| `ApuCore::MAX_VOICES` | `8` | Synthesis voice pool size | +| `AudioCommandQueue::CAPACITY` | `128` | SPSC ring capacity | + +## Related Types + +- `AudioEngine` → `include/audio/AudioEngine.h` +- `MusicPlayer` → `include/audio/MusicPlayer.h` +- `AudioEvent`, `AudioCommand` → `include/audio/AudioTypes.h` +- `MusicNote`, `MusicTrack`, `InstrumentPreset` → `include/audio/AudioMusicTypes.h` +- `AudioConfig` → `include/audio/AudioConfig.h` + +## Related Documentation + +- [API Reference](index.md) - Main index +- [API Core](core.md) - Engine, Scene +- [Architecture Index](../architecture/architecture-index.md) \ No newline at end of file diff --git a/api/audio/audio-engine.md b/api/audio/audio-engine.md deleted file mode 100644 index d07e364..0000000 --- a/api/audio/audio-engine.md +++ /dev/null @@ -1,106 +0,0 @@ -# AudioEngine - -> **Source:** [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) — aligned with [`ARCH_AUDIO_SUBSYSTEM.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/architecture/ARCH_AUDIO_SUBSYSTEM.md). - -## Role - -**`AudioEngine`** is a **facade**: it enqueues **`AudioCommand`**s for the active **`AudioScheduler`** (`playEvent`, `setMasterVolume`, `submitCommand`) and forwards **`generateSamples`** to that scheduler. Stock schedulers delegate to **`ApuCore`**, which owns **mixing**, **channel state**, the **SPSC queue**, and **music sequencing** — not the `AudioEngine` class itself. - ---- - -## AudioEngine - -**Header:** `audio/AudioEngine.h` — **Namespace:** `pixelroot32::audio` - -### Constructors - -- **`AudioEngine(const AudioConfig& config, const PlatformCapabilities& caps = PlatformCapabilities())`** - Selects scheduler by platform (`ESP32` → `ESP32AudioScheduler`, native → `NativeAudioScheduler` when not unit testing, else `DefaultAudioScheduler`). - -- **`AudioEngine(const AudioConfig& config)`** - Equivalent to default `PlatformCapabilities`. - -### Methods - -- **`void init()`** - Initializes the scheduler and backend (`scheduler->init` / `start`, then `backend->init`). - -- **`void generateSamples(int16_t* stream, int length)`** - Fills PCM output; called from the SDL callback or ESP32 backend task. Delegates to **`AudioScheduler::generateSamples`**. - -- **`void playEvent(const AudioEvent& event)`** - Enqueues **`PLAY_EVENT`**. The scheduler picks a channel of the matching **`WaveType`**, applies **voice stealing** if needed, and converts **`duration`** to **`remainingSamples`**. - -- **`void setMasterVolume(float volume)`** - Clamps to **[0, 1]** and enqueues **`SET_MASTER_VOLUME`** (audio thread applies it). - -- **`float getMasterVolume() const`** - Returns cached value from the game thread side; actual mix uses the scheduler after commands are processed. - -- **`void submitCommand(const AudioCommand& cmd)`** - Low-level enqueue (music, stop channel, tempo, BPM, etc.). - -- **`bool isMusicPlaying() const`** / **`bool isMusicPaused() const`** - Read transport flags maintained by **`ApuCore`** (atomics). Use with **`MusicPlayer::isPlaying()`** semantics or custom UI. - -- **`void setScheduler(std::unique_ptr scheduler)`** - Replaces the scheduler (advanced). Takes **`std::unique_ptr`**, not `shared_ptr`. - -### Typical usage - -```cpp -auto& audio = engine.getAudioEngine(); - -AudioEvent evt{}; -evt.type = WaveType::PULSE; -evt.frequency = 1500.0f; -evt.duration = 0.12f; -evt.volume = 0.8f; -evt.duty = 0.5f; - -audio.playEvent(evt); -``` - ---- - -## Data structures - -### `WaveType` (enum class) - -- **`PULSE`** — Square wave; **`AudioEvent::duty`** active. -- **`TRIANGLE`** — Triangle wave. -- **`NOISE`** — LFSR-based noise in **`ApuCore`** (same polynomial on every platform). **`frequency`** / **`noisePeriod`** semantics are those of the **`AudioEvent`** fields below (noise **clock**, not musical pitch). Schedulers only control **when** `generateSamples` runs, not how noise is clocked. - -### `AudioEvent` (struct) - -- **`WaveType type`** -- **`float frequency`** — For **PULSE** / **TRIANGLE**: pitch (Hz). For **NOISE**: drives the **LFSR step rate** when **`noisePeriod == 0`** (not a musical pitch). -- **`float duration`** — Seconds. -- **`float volume`** — **[0, 1]**. -- **`float duty`** — Pulse duty **[0, 1]**; unused for triangle/noise. -- **`uint8_t noisePeriod`** — For **NOISE**: **`0`** = derive period from **`frequency`**; **`> 0`** = fixed LFSR period in samples (percussion presets). -- **`const struct InstrumentPreset* preset`** — Optional pointer to instrument preset for ADSR/LFO/waveform parameters. When nullptr, falls back to legacy behavior. Must point to static/constexpr/global instance. - -### `AudioChannel` (struct, scheduler-owned) - -Relevant noise fields on **all** platforms (see engine `AudioTypes.h`): - -- **`lfsrState`** — 15-bit NES-style LFSR. -- **`noisePeriodSamples`**, **`noiseCountdown`** — Step the LFSR at sub-sample-rate intervals for percussion-like noise (deterministic; no `rand()` in the audio path). - -### `AudioConfig` (struct) - -- **`AudioBackend* backend`** — Platform backend instance (SDL2, I2S, DAC); must outlive/engine init as configured in your `main`/setup. -- **`int sampleRate`** — e.g. 22050, 44100. - -### Command queue (SPSC) - -Commands go through **`AudioCommandQueue`** inside **`ApuCore`**: **128** slots, **single producer / single consumer**. If full, **`enqueue`** fails and the **newest** command is dropped. **`ApuCore::getDroppedCommands()`** counts drops; throttled warnings may appear when **`PIXELROOT32_DEBUG_MODE`** is defined. - ---- - -## See also - -- **[AudioScheduler](/api/audio/audio-scheduler)** — Implementations and where mixing runs. -- **[MusicPlayer](/api/audio/music-player)** — **`MusicTrack`** / **`MusicNote`** API. -- **[Audio architecture](/architecture/audio-architecture)** — Full subsystem narrative. diff --git a/api/audio/audio-scheduler.md b/api/audio/audio-scheduler.md deleted file mode 100644 index 0f121e9..0000000 --- a/api/audio/audio-scheduler.md +++ /dev/null @@ -1,46 +0,0 @@ -# AudioScheduler - -> **Source:** [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) — see [`ARCH_AUDIO_SUBSYSTEM.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/architecture/ARCH_AUDIO_SUBSYSTEM.md) §4 and [`ApuCore.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/ApuCore.h). - -## Role - -**`AudioScheduler`** chooses **when** the audio consumer runs. In all stock implementations it owns an **`ApuCore`** instance and forwards **`submitCommand`** / **`generateSamples`** to it. - -**`ApuCore`** is the real **consumer** in the SPSC pipeline: it **dequeues** **`AudioCommand`**s, owns the **four `AudioChannel`** slots, runs **sample-accurate** music stepping, and implements **mixing** (non-linear compressor: FPU path or **LUT** on no-FPU ESP32, e.g. ESP32-C3). Optional **one-pole HPF** runs on the FPU / native float path after the mix. - -**`AudioEngine::generateSamples`** forwards to **`AudioScheduler::generateSamples`**, which calls **`ApuCore::generateSamples`**. - -Stock implementations also override **`isMusicPlaying()`** / **`isMusicPaused()`** to expose **`ApuCore`** transport atomics. - ---- - -## Implementations - -| Scheduler | Typical use | Where `ApuCore::generateSamples` runs | -|-----------|-------------|----------------------------------------| -| **`NativeAudioScheduler`** | `PLATFORM_NATIVE` / SDL2 (not unit tests) | Dedicated **`std::thread`** fills a ring buffer; SDL callback drains via **`AudioEngine::generateSamples`**. | -| **`ESP32AudioScheduler`** | `ESP32` | Same OS context as the **backend audio task** (I2S/DAC); backend calls **`engine->generateSamples`**. | -| **`DefaultAudioScheduler`** | Unit tests / minimal hosts | Whatever thread invokes **`generateSamples`**. | - -**ESP32 note:** **`ESP32AudioScheduler`** does **not** create the FreeRTOS task. **`ESP32_I2S_AudioBackend`** / **`ESP32_DAC_AudioBackend`** call **`xTaskCreatePinnedToCore`** using **`PlatformCapabilities::audioCoreId`** and **`audioPriority`**. Constructor arguments on **`ESP32AudioScheduler`** are reserved; affinity is **not** stored on the scheduler object. - ---- - -## Noise generation (unified) - -All platforms use the same **15-bit NES-style LFSR** and **`noisePeriodSamples` / `noiseCountdown`** stepping model inside **`ApuCore`**. There is **no** `rand()` path in current engine code. - ---- - -## Music sequencer - -Tick-based sequencing lives in **`ApuCore::updateMusicSequencer`**. If **`generateSamples`** is not invoked for a long wall-clock gap, many ticks may be processed in one call (CPU scales with backlog; there is no **`MAX_NOTES_PER_FRAME`** cap in the current implementation). - ---- - -## See also - -- **[AudioEngine](/api/audio/audio-engine)** — Facade and `AudioConfig`. -- **[MusicPlayer](/api/audio/music-player)** — Produces music **`AudioCommand`**s. -- **[Audio architecture](/architecture/audio-architecture)** — Subsystem narrative. -- **Engine:** [`ApuCore.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/src/audio/ApuCore.cpp) — implementation reference. diff --git a/api/audio/music-player.md b/api/audio/music-player.md deleted file mode 100644 index f80a764..0000000 --- a/api/audio/music-player.md +++ /dev/null @@ -1,184 +0,0 @@ -# MusicPlayer - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Music data structures - -Defined in **[`AudioMusicTypes.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioMusicTypes.h)**. - -### `MAX_MUSIC_TRACKS` - -- **`constexpr size_t MAX_MUSIC_TRACKS = 4`**: at most **one main** `MusicTrack` plus **three** optional parallel layers (`secondVoice`, `thirdVoice`, `percussion`). - -### `MusicNote` - -- **`Note note`**: pitch or **`Note::Rest`**. -- **`uint8_t octave`**: octave (0–8). For authored percussion without a per-note preset, octave can act as a drum selector; prefer **`InstrumentPreset`** on the note when possible. -- **`float duration`**: length in **seconds** (unless a percussion preset fixes duration via **`defaultDuration`**). -- **`float volume`**: 0.0–1.0. -- **`const InstrumentPreset* preset`**: optional; **`nullptr`** uses the **`MusicTrack`** layer’s **`channelType`** / **`duty`** with legacy envelope behavior (no per-note preset). When set, **`makeNote`** stores the pointer and the sequencer uses that preset’s ADSR/LFO/wave options; for **percussion** (**`duty == 0`**), the preset drives hit duration (**`defaultDuration`**), **`noisePeriod`**, and the frequency passed into the noise path via **`instrumentToFrequency`**. - -### `MusicTrack` - -- **`const MusicNote* notes`**, **`size_t count`**, **`bool loop`**. -- **`WaveType channelType`**, **`float duty`**: waveform for that layer (**`NOISE`** + **`duty == 0`** for drum tracks). -- **`const MusicTrack* secondVoice`**, **`thirdVoice`**, **`percussion`**: optional pointers to **other** `MusicTrack` instances (static data). **`nullptr`** disables that layer. - -### `InstrumentPreset` - -Complete instrument preset with ADSR envelope, LFO modulation, and waveform refinements: - -**Basic Parameters** -- **`float baseVolume`**: Default volume for notes (0.0 - 1.0). -- **`float duty`**: Duty cycle for Pulse waves (0.0 - 1.0). For percussion, use 0.0 to select NOISE channel. -- **`uint8_t defaultOctave`**: Default octave. For percussion: 1=Kick, 2=Snare, 3+=Hi-HAT. -- **`float defaultDuration`**: Fixed duration for percussion hits (0.0 = use `note.duration`, >0 = fixed duration). -- **`uint8_t noisePeriod`**: LFSR period for NOISE channel (0 = calculate from frequency, >0 = explicit period). - -**ADSR Envelope** -- **`float attackTime`**: Attack time in seconds (0.0 = instant, default 0.002f). -- **`float decayTime`**: Decay time in seconds (0.0 = skip decay, default 0.0f). -- **`float sustainLevel`**: Sustain level as fraction of peak volume (0.0-1.0, default 1.0f). -- **`float releaseTime`**: Release time in seconds (0.0 = instant off, clamped to 100ms max, default 0.005f). - -**LFO Modulation** -- **`LfoTarget lfoTarget`**: Modulation target (`NONE`, `PITCH`, or `VOLUME`). -- **`float lfoFrequency`**: LFO frequency in Hz (0.0 = disabled). -- **`float lfoDepth`**: Modulation depth. For PITCH: ratio (e.g., 0.05 for ~0.34 semitones). For VOLUME: fraction (0.0-1.0). -- **`float lfoDelay`**: Delay before LFO starts in seconds. - -**Waveform Refinements** -- **`bool noiseShortMode`**: For NOISE channel: true = metallic 93-step LFSR, false = standard 32767-step. -- **`float dutySweep`**: For PULSE channel: duty cycle change per second for PWM-like timbral effects. - -**Percussion** presets use **`duty == 0`** (noise path in **`instrumentToFrequency`** / sequencer). - -#### Built-in presets - -**Melodic** - -| Preset | Role | -|--------|------| -| **`INSTR_PULSE_LEAD`** | Main lead pulse, octave 4, duty 0.5 | -| **`INSTR_PULSE_HARMONY`** | Harmony pulse, octave 5, duty 0.125 | -| **`INSTR_PULSE_PAD`** | Atmospheric pad pulse, octave 4, duty 0.25, slow pitch drift | -| **`INSTR_PULSE_BASS`** | Punchy bass pulse, octave 2, duty 0.25 | -| **`INSTR_TRIANGLE_LEAD`** | Smooth triangle lead, octave 5, gentle vibrato | -| **`INSTR_TRIANGLE_PAD`** | Soft atmospheric triangle pad, octave 4, tremolo | -| **`INSTR_TRIANGLE_BASS`** | Triangle bass, octave 3, duty 0.5 | - -**Percussion** (use with **`WaveType::NOISE`**; tuned **`defaultDuration`** / **`noisePeriod`**) - -| Preset | Role | -|--------|------| -| **`INSTR_KICK`** | Kick: defaultOctave 1, duration 0.12s, noisePeriod 25 | -| **`INSTR_SNARE`** | Snare: defaultOctave 2, duration 0.15s, noisePeriod 50 | -| **`INSTR_HIHAT`** | Hi-hat: defaultOctave 3, duration 0.05s, noisePeriod 12 | - -#### Helpers - -- **`MusicNote makeNote(const InstrumentPreset& preset, Note note, float duration)`** -- **`MusicNote makeNote(const InstrumentPreset& preset, Note note, uint8_t octave, float duration)`** -- **`MusicNote makeRest(float duration)`** -- **`float instrumentToFrequency(const InstrumentPreset& preset, Note, uint8_t)`** — If **`preset.duty == 0`** (percussion), returns a **fixed noise-clock rate in Hz** for the NOISE channel (**Kick ≈ 80**, **Snare ≈ 150**, **Hi-hat ≈ 3000** from **`defaultOctave`** 1 / 2 / 3+); these control **density/brightness**, not musical pitch like **`noteToFrequency`**. **`Note`** / **`octave`** are ignored for that path. If **`duty > 0`**, the helper is not used for normal melodic sequencing (implementation falls back to **440 Hz**; melodic pitch comes from **`noteToFrequency`** in the music path). - -### `AudioCommand` (multi-track) - -**[`AudioTypes.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioTypes.h)** extends **`AudioCommand`** with: - -- **`static constexpr size_t MAX_SUB_TRACKS = 3`** -- **`const MusicTrack* subTracks[MAX_SUB_TRACKS]`** -- **`size_t subTrackCount`** - -**`MusicPlayer::play`** fills **`track`** with the main `MusicTrack` and pushes non-null **`secondVoice` / `thirdVoice` / `percussion`** into **`subTracks`** in that order. - ---- - -## `MusicPlayer` class - -**Inherits:** none. - -High-level helper that **enqueues** music-related **`AudioCommand`**s on **`AudioEngine`**. **Sample-accurate**, **tick-based** sequencing runs in **`ApuCore::updateMusicSequencer`** on the audio consumer path (scheduler + backend), not on the game thread. - -**`isPlaying()`** combines **`AudioEngine::isMusicPlaying()`** / **`isMusicPaused()`** (atomics owned by **`ApuCore`**) with a short **in-flight** window after **`play()`** so the game thread does not flicker to **false** before **`MUSIC_PLAY`** is consumed. While **paused**, **`isPlaying()`** is **false**. If the **SPSC** queue drops a command, local flags can disagree with **`ApuCore`** — avoid saturating the queue. - -### Public API - -- **`MusicPlayer(AudioEngine& engine)`** -- **`void play(const MusicTrack& track)`** — starts from the beginning; packs **main + up to 3 sub-tracks** into one **`MUSIC_PLAY`** command. -- **`void stop()`** / **`void pause()`** / **`void resume()`** -- **`bool isPlaying() const`** -- **`void setTempoFactor(float factor)`** / **`float getTempoFactor() const`** — **`MUSIC_SET_TEMPO`** (clamped **≥ 0.1** in implementation). -- **`void setBPM(float bpm)`** / **`float getBPM() const`** — **`MUSIC_SET_BPM`**; engine clamps to **[30, 300]**; default **150** BPM with **4 ticks per beat** inside **`ApuCore`**. -- **`size_t getActiveTrackCount() const`** — after **`play()`**, returns **1–4** according to which of **`secondVoice` / `thirdVoice` / `percussion`** were non-null; **0** if not **`playing`** or no **`currentTrack`**. - -### Typical usage (single track) - -```cpp -using namespace pixelroot32::audio; - -static const MusicNote MELODY[] = { - makeNote(INSTR_PULSE_LEAD, Note::C, 0.20f), - makeNote(INSTR_PULSE_LEAD, Note::E, 0.20f), - makeNote(INSTR_PULSE_LEAD, Note::G, 0.25f), - makeRest(0.10f), -}; - -static const MusicTrack GAME_MUSIC = { - MELODY, - sizeof(MELODY) / sizeof(MusicNote), - true, - WaveType::PULSE, - 0.5f, -}; - -void MyScene::init(pr32::core::Engine& engine) { - engine.getMusicPlayer().play(GAME_MUSIC); -} -``` - -### Multi-track usage - -```cpp -static const MusicNote BASS[] = { - makeNote(INSTR_TRIANGLE_BASS, Note::C, 2, 0.5f), - makeNote(INSTR_TRIANGLE_BASS, Note::G, 2, 0.5f), -}; -static const MusicTrack BASS_TRACK = { - BASS, sizeof(BASS) / sizeof(MusicNote), true, WaveType::TRIANGLE, 0.5f, -}; - -static const MusicNote DRUMS[] = { - makeNote(INSTR_KICK, Note::C, 0.12f), - makeRest(0.12f), - makeNote(INSTR_SNARE, Note::C, 0.15f), - makeRest(0.13f), -}; -static const MusicTrack DRUM_TRACK = { - DRUMS, sizeof(DRUMS) / sizeof(MusicNote), true, WaveType::NOISE, 0.0f, -}; - -static const MusicNote LEAD[] = { - makeNote(INSTR_PULSE_LEAD, Note::C, 4, 0.25f), - makeNote(INSTR_PULSE_LEAD, Note::E, 4, 0.25f), -}; -static const MusicTrack FULL = { - LEAD, - sizeof(LEAD) / sizeof(MusicNote), - true, - WaveType::PULSE, - 0.5f, - &BASS_TRACK, - nullptr, - &DRUM_TRACK, -}; - -// musicPlayer.play(FULL); -// size_t layers = musicPlayer.getActiveTrackCount(); // 3 -``` - -On **`DRUMS`**, **`Note::C`** is only a placeholder; **`Note::Rest`** is equally valid for percussion presets—the noise clock comes from **`instrumentToFrequency`** and the preset. - -For a full sample project, see **[`examples/music_demo`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo)**. - ---- diff --git a/api/platform/engine-config.md b/api/config.md similarity index 57% rename from api/platform/engine-config.md rename to api/config.md index 90ff048..2c94223 100644 --- a/api/platform/engine-config.md +++ b/api/config.md @@ -1,14 +1,12 @@ -# EngineConfig & build flags - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - # API Reference: Configuration -This document covers global configuration options, build flags, and compile-time constants for the PixelRoot32 Game Engine. +> **Source of truth:** +> - `include/platforms/EngineConfig.h` +> - `include/platforms/PlatformDefaults.h` -> **Note:** This is part of the [API Reference](/api/). See the main index for complete documentation. +## Overview ---- +This document covers global configuration options, build flags, and compile-time constants for the PixelRoot32 Game Engine. Most of these configurations can be overridden in your `platformio.ini` file without modifying the engine source code. ## Platform Macros (Build Flags) @@ -21,15 +19,13 @@ This document covers global configuration options, build flags, and compile-time | `PIXELROOT32_USE_U8G2_DRIVER` | Enable U8G2 display driver support for monochromatic OLEDs. | Disabled | | `PIXELROOT32_NO_TFT_ESPI` | Disable default TFT_eSPI driver support. | Enabled | ---- - ## Modular Compilation Flags | Macro | Description | Default | |-------|-------------|---------| -| `PIXELROOT32_ENABLE_AUDIO` | Enable audio subsystem (AudioEngine + MusicPlayer). | `1` | -| `PIXELROOT32_ENABLE_PHYSICS` | Enable physics system (CollisionSystem). | `1` | -| `PIXELROOT32_ENABLE_UI_SYSTEM` | Enable UI system (UIButton, UILabel, etc.). | `1` | +| `PIXELROOT32_ENABLE_AUDIO` | Enable audio subsystem. | `1` | +| `PIXELROOT32_ENABLE_PHYSICS` | Enable physics system. | `1` | +| `PIXELROOT32_ENABLE_UI_SYSTEM` | Enable UI system. | `1` | | `PIXELROOT32_ENABLE_PARTICLES` | Enable particle system. | `1` | | `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Enable FPS/RAM/CPU debug overlay. | Disabled | | `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enable tile animation system. | `1` | @@ -37,17 +33,15 @@ This document covers global configuration options, build flags, and compile-time | `PIXELROOT32_ENABLE_4BPP_SPRITES` | Enable 4bpp sprite support. | Disabled | | `PIXELROOT32_ENABLE_SCENE_ARENA` | Enable scene memory arena. | Disabled | | `PIXELROOT32_ENABLE_PROFILING` | Enable profiling hooks in physics pipeline. | Disabled | -| `PIXELROOT32_ENABLE_TOUCH` | Enable automatic touch processing in Engine (mouse-to-touch on Native, touch point injection on ESP32). | `0` (disabled) | -| `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enable **`StaticTilemapLayerCache`** (4bpp direct logical framebuffer snapshot). Set `0` to save ~W×H RAM or force full redraw. | `1` (`PlatformDefaults.h`) | -| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK` | TFT_eSPI `sendBufferScaled`: RGB565 lines per DMA batch (when `PIXELROOT32_USE_TFT_ESPI_DRIVER`). Higher = fewer DMA rounds, more RAM for line buffers. | `60` (`PlatformDefaults.h`) | -| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK_FALLBACK` | Line batch size retried if primary DMA line buffers cannot be allocated. | `30` (`PlatformDefaults.h`) | +| `PIXELROOT32_ENABLE_TOUCH` | Enable automatic touch processing. | `0` (disabled) | +| `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enable **`StaticTilemapLayerCache`** (4bpp FB snapshot). | `1` | +| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK` | TFT_eSPI DMA line batch size. | `60` | +| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK_FALLBACK` | Fallback DMA batch size if memory fails. | `30` | | `PIXELROOT32_DEBUG_MODE` | Enable unified logging system. | Disabled | -| `PIXELROOT32_ENABLE_PHYSICS_FIXED_TIMESTEP` | Enable PhysicsScheduler for consistent physics across variable frame rates. | `1` (profile_full/arcade) | +| `PIXELROOT32_ENABLE_PHYSICS_FIXED_TIMESTEP` | Enable PhysicsScheduler for consistent physics. | `1` | | `PIXELROOT32_VELOCITY_DAMPING` | Per-frame velocity damping factor (0.0-1.0). | `0.999` | | `PIXELROOT32_MAX_VELOCITY` | Maximum velocity cap in units/s. | `500` | -| `PIXELROOT32_HAS_FAST_RSQRT` | Enable fast reciprocal square root (1/sqrt). | `1` (profile_full/arcade) | - ---- +| `PIXELROOT32_HAS_FAST_RSQRT` | Enable fast reciprocal square root. | `1` | ## Memory Savings by Subsystem @@ -59,8 +53,6 @@ This document covers global configuration options, build flags, and compile-time | `PIXELROOT32_ENABLE_PARTICLES=0` | ~6 KB | ~10 KB | | `PIXELROOT32_ENABLE_TOUCH=0` | ~200 bytes | ~2 KB | ---- - ## Build Profiles (platformio.ini) ```ini @@ -93,9 +85,7 @@ build_flags = -D PIXELROOT32_ENABLE_UI_SYSTEM=0 ``` ---- - -## Recommended Profiles by Game Type +### Recommended Profiles by Game Type | Game Type | Recommended Profile | Rationale | |-----------|-------------------|-----------| @@ -104,44 +94,20 @@ build_flags = | Retro / Minimal | `retro` | Minimal footprint, custom collision | | Educational / Tool | `puzzle` or custom | UI for menus | ---- - -## Constants - -- **`DISPLAY_WIDTH`** - The width of the display in pixels. Default is `240`. - -- **`DISPLAY_HEIGHT`** - The height of the display in pixels. Default is `240`. - -- **`int xOffset`** - The horizontal offset for the display alignment. Default is `0`. - -- **`int yOffset`** - The vertical offset for the display alignment. Default is `0`. - -- **`PHYSICS_MAX_PAIRS`** - Maximum number of collision pairs considered in broadphase. Default is `128`. - -- **`PHYSICS_MAX_CONTACTS`** - Maximum number of simultaneous contacts in the solver (fixed pool, no heap per frame). Default is `128`. When exceeded, additional contacts are dropped. - -- **`VELOCITY_ITERATIONS`** - Number of impulse solver passes per frame. Higher values improve stacking stability but increase CPU load. Default is `2`. - -- **`SPATIAL_GRID_CELL_SIZE`** - Size of each cell in the broadphase grid (in pixels). Default is `32`. - -- **`SPATIAL_GRID_MAX_ENTITIES_PER_CELL`** - Legacy: maximum entities per cell when using a single grid. Default is `24`. - -- **`SPATIAL_GRID_MAX_STATIC_PER_CELL`** - Maximum static (immovable) actors per grid cell. Default is `12`. Used by the static layer of the spatial grid. - -- **`SPATIAL_GRID_MAX_DYNAMIC_PER_CELL`** - Maximum dynamic (RIGID/KINEMATIC) actors per grid cell. Default is `12`. Used by the dynamic layer of the spatial grid. - ---- +## Core Constants + +| Constant | Default | Description | +|----------|---------|-------------| +| `DISPLAY_WIDTH` | `240` | The logical width of the display in pixels. | +| `DISPLAY_HEIGHT` | `240` | The logical height of the display in pixels. | +| `xOffset` / `yOffset` | `0` | Coordinate offsets for hardware alignment. | +| `PHYSICS_MAX_PAIRS` | `128` | Maximum collision pairs considered in broadphase. | +| `PHYSICS_MAX_CONTACTS` | `128` | Maximum simultaneous contacts in the physics solver. | +| `VELOCITY_ITERATIONS` | `2` | Number of impulse solver passes per frame. | +| `SPATIAL_GRID_CELL_SIZE` | `32` | Size of each cell in the broadphase grid (pixels). | +| `SPATIAL_GRID_MAX_ENTITIES_PER_CELL` | `24` | (Legacy) max entities per cell. | +| `SPATIAL_GRID_MAX_STATIC_PER_CELL` | `12` | Max static actors per grid cell. | +| `SPATIAL_GRID_MAX_DYNAMIC_PER_CELL` | `12` | Max dynamic actors per grid cell. | ## Custom Scene Limits @@ -157,10 +123,8 @@ build_flags = -DMAX_ENTITIES=64 ``` ---- - ## Related Documentation -- [API Reference](/api/) - Main index -- [Platform Compatibility Guide](/guide/platform-config) -- [Extending PixelRoot32](/guide/extending) +- [API Reference](index.md) - Main index +- [Platform Compatibility Guide](../guide/platform-compatibility.md) +- [Extending PixelRoot32](../guide/extending-pixelroot32.md) \ No newline at end of file diff --git a/api/core.md b/api/core.md new file mode 100644 index 0000000..53ad23f --- /dev/null +++ b/api/core.md @@ -0,0 +1,111 @@ +# API Reference: Core Module + +> **Source of truth:** +> - `include/core/Engine.h` +> - `include/core/Entity.h` +> - `include/core/Scene.h` +> - `include/core/SceneManager.h` +> - `include/platforms/PlatformCapabilities.h` +> - `include/graphics/DisplayConfig.h` + +## Overview + +This document covers the core engine classes, entity system, and scene management in PixelRoot32. The `Engine` acts as the central hub, initializing and managing the Renderer, InputManager, and SceneManager. It runs the main game loop, handling timing (delta time), updating the current scene, and rendering frames. + +## Key Concepts + +### Engine + +The main engine class that manages the game loop and core subsystems. Each iteration calls **`update()`**; **`draw()`** and **`present()`** run only when **`SceneManager::aggregateShouldRedrawFramebuffer()`** is `true` (any stacked scene may request a pass). + +### Entity + +Abstract base class for all game objects. Entities are the fundamental building blocks of the scene. They have a position, size, and lifecycle methods (`update`, `draw`). + +**Properties Summary:** +- Position (`x`, `y`) in world space. +- Dimensions (`width`, `height`). +- Visibility and enabled state. +- `renderLayer`: Logical render layer (0 = background, 1 = gameplay, 2 = UI). + +**Modular Compilation Notes:** +Specialized subclasses may be affected by compilation flags: +- **UI Elements**: Requires `PIXELROOT32_ENABLE_UI_SYSTEM=1` +- **Physics Actors**: Requires `PIXELROOT32_ENABLE_PHYSICS=1` +- **Particle Systems**: Requires `PIXELROOT32_ENABLE_PARTICLES=1` + +### Scene + +Represents a game level or screen containing entities. A Scene manages a collection of Entities, an optional **PhysicsScheduler**, and a **CollisionSystem**. It is responsible for updating and drawing all entities it contains. + +**Overriding Scene Limits:** +The engine defines default limits in `platforms/EngineConfig.h`: `MAX_LAYERS` (default 3) and `MAX_ENTITIES` (default 32). In your project (e.g. `platformio.ini`), add defines to `build_flags` to override: +```ini +build_flags = + -DMAX_LAYERS=5 + -DMAX_ENTITIES=64 +``` + +### SceneManager + +Manages the stack of active scenes. Allows for scene transitions (replacing) and stacking (push/pop), useful for pausing or menus. `aggregateShouldRedrawFramebuffer()` ensures that menus don't suppress background scenes that still need drawing. + +### SceneArena (Memory Management) + +An optional memory arena for zero-allocation scenes (enabled via `PIXELROOT32_ENABLE_SCENE_ARENA`). Pre-allocates a fixed memory block for temporary data or entity storage, avoiding heap fragmentation on embedded devices. +- **Benefits**: Predictable memory usage, no `new`/`delete` in the scene, reduced fragmentation. +- **Costs**: Fixed buffer size; freed only when the arena is reset or the scene ends. + +## Configuration & Structures + +### PlatformCapabilities + +Detected hardware capabilities, used to optimize task pinning and threading. + +| Field | Description | +|-------|-------------| +| `hasDualCore` | True if the hardware has more than one CPU core. | +| `coreCount` | Total number of CPU cores detected. | +| `audioCoreId` | Recommended CPU core for audio tasks. | +| `mainCoreId` | Recommended CPU core for the main game loop. | +| `audioPriority` | Recommended priority for audio tasks. | + +### DisplayConfig + +Configuration settings for display initialization and scaling. + +| Field | Description | +|-------|-------------| +| `type` | Display type (ST7789, ST7735, OLED, NONE, CUSTOM). | +| `rotation` | Display rotation (0-3). | +| `physicalWidth` / `physicalHeight` | Actual hardware resolution. | +| `logicalWidth` / `logicalHeight` | Virtual rendering resolution. | +| `xOffset` / `yOffset` | Offsets for hardware alignment. | +| **Pins** | `clockPin`, `dataPin`, `csPin`, `dcPin`, `resetPin`, `useHardwareI2C`. | + +## Optional: Debug Statistics Overlay + +When the engine is built with the preprocessor define **`PIXELROOT32_ENABLE_DEBUG_OVERLAY`**, it draws a technical overlay with real-time metrics. + +- **FPS**: Frames per second (green). +- **RAM**: Memory used in KB (cyan). ESP32 specific. +- **CPU**: Estimated processor load percentage based on frame processing time (yellow). + +The metrics are drawn in the top-right area of the screen, fixed and independent of the camera. Values are recalculated and formatted only every **16 frames** (`DEBUG_UPDATE_INTERVAL`); the cached strings are drawn every frame. This ensures minimal overhead while providing useful development data. + +## Related Types + +- `Engine` → `include/core/Engine.h` +- `Entity` → `include/core/Entity.h` +- `Scene` → `include/core/Scene.h` +- `SceneManager` → `include/core/SceneManager.h` +- `SceneArena` → `include/core/Scene.h` +- `PlatformCapabilities` → `include/platforms/PlatformCapabilities.h` +- `DisplayConfig` → `include/graphics/DisplayConfig.h` + +## Related Documentation + +- [API Reference](index.md) - Main index +- [Physics Module](physics.md) - Physics actors and collision system +- [Graphics Module](graphics.md) - Rendering and sprites +- [UI Module](ui.md) - User interface system \ No newline at end of file diff --git a/api/core/actor.md b/api/core/actor.md deleted file mode 100644 index 6505ed5..0000000 --- a/api/core/actor.md +++ /dev/null @@ -1,46 +0,0 @@ -# Actor (physics) - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Actor - -**Include:** `core/Actor.h` - -**Inherits:** [Entity](/api/core/engine.md#entity) - -The base class for all objects capable of collision. Actors extend Entity with collision layers, masks, and shape definitions. Note: You should typically use a specialized subclass like `RigidActor` or `KinematicActor` instead of this base class. - -### Constants - -- **`enum CollisionShape`** - - `AABB`: Axis-aligned bounding box (Rectangle). - - `CIRCLE`: Circular collision body. - -### Properties - -- **`uint16_t entityId`**: Unique id assigned by `CollisionSystem::addEntity` (used for pair deduplication). `0` = unregistered. -- **`int queryId`**: Used internally by the spatial grid for deduplication in `getPotentialColliders`. -- **`CollisionLayer layer`**: Bitmask representing the layers this actor belongs to. -- **`CollisionLayer mask`**: Bitmask representing the layers this actor scans for collisions. - -### Public Methods - -- **`Actor(Scalar x, Scalar y, int w, int h)`** - Constructs a new Actor. - -- **`void setCollisionLayer(CollisionLayer l)`** - Sets the collision layer this actor belongs to. - -- **`void setCollisionMask(CollisionLayer m)`** - Sets the collision layers this actor interacts with. - -- **`bool isInLayer(uint16_t targetLayer) const`** - Checks if the Actor belongs to a specific collision layer. - -- **`virtual Rect getHitBox()`** - Returns the bounding rectangle for AABB detection or the bounding box of the circle. - -- **`virtual void onCollision(Actor* other)`** - Callback invoked when a collision is detected. **Note:** All collision responses (velocity/position changes) are handled by the `CollisionSystem`. This method is for gameplay notifications only. - ---- diff --git a/api/core/engine.md b/api/core/engine.md deleted file mode 100644 index f4b2527..0000000 --- a/api/core/engine.md +++ /dev/null @@ -1,151 +0,0 @@ -# Engine - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Engine - -**Inherits:** None - -The main engine class that manages the game loop and core subsystems. `Engine` acts as the central hub, initializing and managing the Renderer, InputManager, and SceneManager. It runs the main game loop, handling timing (delta time), updating the current scene, and rendering frames. - -### Public Methods - -- **`Engine(DisplayConfig&& displayConfig, const InputConfig& inputConfig, const AudioConfig& audioConfig)`** - Constructs the engine with custom display, input and audio configurations. - -- **`Engine(DisplayConfig&& displayConfig, const InputConfig& inputConfig)`** - Constructs the engine with custom display and input configurations. - -- **`Engine(DisplayConfig&& displayConfig)`** - Constructs the engine with custom display configuration and default input settings. - -- **`void init()`** - Initializes the engine subsystems. Must be called before `run()`. - -- **`void run()`** - Starts the main game loop. This method contains the infinite loop that calls `update()` and `draw()` repeatedly. - -- **`unsigned long getDeltaTime() const`** - Returns the time elapsed since the last frame in milliseconds. - -- **`unsigned long getMillis() const`** - Returns the number of milliseconds since the engine started. - -- **`void setScene(Scene* newScene)`** - Sets the current active scene. - -- **`std::optional getCurrentScene() const`** - Retrieves the currently active scene, or std::nullopt if no scene is active. - -- **`Renderer& getRenderer()`** - Provides access to the Renderer subsystem. - -- **`void setRenderer(pixelroot32::graphics::Renderer&& newRenderer)`** - Replaces the current renderer instance. - -- **`InputManager& getInputManager()`** - Provides access to the InputManager subsystem. - -- **`TouchEventDispatcher& getTouchDispatcher()`** - Provides access to the touch system for injecting touch points. Use this on ESP32 to connect TouchManager with Engine's touch processing pipeline. - - **Note**: Only available if `PIXELROOT32_ENABLE_TOUCH=1` - -- **`bool hasTouchEvents() const`** - Returns true if there are pending touch events in the queue. - - **Note**: Only available if `PIXELROOT32_ENABLE_TOUCH=1` - -- **`void setTouchManager(pixelroot32::input::TouchManager* touchManager)`** - Registers a TouchManager instance for automatic touch processing on ESP32. When set, Engine automatically: - - Polls `touchManager.getTouchPoints()` each frame - - Detects touch releases (when count goes from >0 to 0) - - Processes touch events through the internal TouchEventDispatcher - - Sends gesture events to the current scene via `Scene::processTouchEvents()` - - Usage (ESP32): - - ```cpp - touchManager.init(); - engine.setTouchManager(&touchManager); // 1 línea - - void loop() { - touchManager.update(frameDt); - engine.run(); // Engine maneja todo automáticamente - } - ``` - - - **Note**: Only available if `PIXELROOT32_ENABLE_TOUCH=1` - -- **`AudioEngine& getAudioEngine()`** - Provides access to the AudioEngine subsystem. - - **Note**: Only available if `PIXELROOT32_ENABLE_AUDIO=1` - -- **`MusicPlayer& getMusicPlayer()`** - Provides access to the MusicPlayer subsystem. - - **Note**: Only available if `PIXELROOT32_ENABLE_AUDIO=1` - -- **`const PlatformCapabilities& getPlatformCapabilities() const`** - Returns the detected hardware capabilities for the current platform. - ---- - - -## PlatformCapabilities (Struct) - -**Namespace:** `pixelroot32::platforms` - -A structure that holds detected hardware capabilities, used to optimize task pinning and threading. - -- **`bool hasDualCore`**: True if the hardware has more than one CPU core. -- **`int coreCount`**: Total number of CPU cores detected. -- **`int audioCoreId`**: Recommended CPU core for audio tasks. -- **`int mainCoreId`**: Recommended CPU core for the main game loop. -- **`int audioPriority`**: Recommended priority for audio tasks. - -### Static Methods - -- **`static PlatformCapabilities detect()`**: Automatically detects hardware capabilities based on the platform and configuration. It respects the defaults defined in `platforms/PlatformDefaults.h` and any compile-time overrides. - ---- - - -## DisplayConfig (Struct) - -Configuration settings for display initialization and scaling. - -- **`DisplayType type`**: Type of display (ST7789, ST7735, OLED_SSD1306, OLED_SH1106, NONE, CUSTOM). -- **`int rotation`**: Display rotation (0-3 or degrees). -- **`uint16_t physicalWidth`**: Actual hardware width. -- **`uint16_t physicalHeight`**: Actual hardware height. -- **`uint16_t logicalWidth`**: Virtual rendering width. -- **`uint16_t logicalHeight`**: Virtual rendering height. -- **`int xOffset`**: X coordinate offset for hardware alignment. -- **`int yOffset`**: Y coordinate offset for hardware alignment. - -### Pin Configuration (Optional) - -- **`uint8_t clockPin`**: SPI SCK / I2C SCL. -- **`uint8_t dataPin`**: SPI MOSI / I2C SDA. -- **`uint8_t csPin`**: SPI CS (Chip Select). -- **`uint8_t dcPin`**: SPI DC (Data/Command). -- **`uint8_t resetPin`**: Reset pin. -- **`bool useHardwareI2C`**: If true, uses hardware I2C peripheral (default true). - ---- - - -## Optional: Debug Statistics Overlay (build flag) - -When the engine is built with the preprocessor define **`PIXELROOT32_ENABLE_DEBUG_OVERLAY`**, the engine draws a technical overlay with real-time metrics. - -- **Metrics Included**: - - **FPS**: Frames per second (green). - - **RAM**: Memory used in KB (cyan). ESP32 specific. - - **CPU**: Estimated processor load percentage based on frame processing time (yellow). -- **Behavior**: The metrics are drawn in the top-right area of the screen, fixed and independent of the camera. -- **Performance**: Values are recalculated and formatted only every **16 frames** (`DEBUG_UPDATE_INTERVAL`); the cached strings are drawn every frame. This ensures minimal overhead while providing useful development data. -- **Usage**: Add to your build flags, e.g. in `platformio.ini`: - `build_flags = -D PIXELROOT32_ENABLE_DEBUG_OVERLAY` - This flag is also available in `EngineConfig.h`. -- **Internal**: Implemented by the private method `Engine::drawDebugOverlay(Renderer& r)`. - ---- diff --git a/api/core/entity.md b/api/core/entity.md deleted file mode 100644 index 713e434..0000000 --- a/api/core/entity.md +++ /dev/null @@ -1,50 +0,0 @@ -# Entity - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Entity - -**Inherits:** None - -Abstract base class for all game objects. Entities are the fundamental building blocks of the scene. They have a position, size, and lifecycle methods (`update`, `draw`). - -### Properties - -- **`Scalar x, y`**: Position in world space. -- **`int width, height`**: Dimensions of the entity. -- **`bool isVisible`**: If false, the entity is skipped during rendering. -- **`bool isEnabled`**: If false, the entity is skipped during updates. -- **`unsigned char renderLayer`**: Logical render layer for this entity (0 = background, 1 = gameplay, 2 = UI). - -### Public Methods - -- **`Entity(Scalar x, Scalar y, int w, int h, EntityType t)`** - Constructs a new Entity. - -- **`void setVisible(bool v)`** - Sets the visibility of the entity. - -- **`void setEnabled(bool e)`** - Sets the enabled state of the entity. - -- **`unsigned char getRenderLayer() const`** - Returns the current render layer. - -- **`void setRenderLayer(unsigned char layer)`** - Sets the logical render layer for this entity. The value is clamped to the range `[0, MAX_LAYERS - 1]`. - -- **`virtual void update(unsigned long deltaTime)`** - Updates the entity's logic. Must be overridden by subclasses. - -- **`virtual void draw(Renderer& renderer)`** - Renders the entity. Must be overridden by subclasses. - -### Modular Compilation Notes - -The Entity class is always available. However, specialized subclasses may be affected by modular compilation flags: - -- **UI Elements** (UIButton, UILabel, etc.): Only available if `PIXELROOT32_ENABLE_UI_SYSTEM=1` -- **Physics Actors** (PhysicsActor, RigidActor, etc.): Only available if `PIXELROOT32_ENABLE_PHYSICS=1` -- **Particle Systems**: Only available if `PIXELROOT32_ENABLE_PARTICLES=1` - ---- diff --git a/api/core/scene-manager.md b/api/core/scene-manager.md deleted file mode 100644 index 45d0532..0000000 --- a/api/core/scene-manager.md +++ /dev/null @@ -1,54 +0,0 @@ -# SceneManager & SceneArena - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## SceneManager - -**Inherits:** None - -Manages the stack of active scenes. Allows for scene transitions (replacing) and stacking (push/pop), useful for pausing or menus. - -### Public Methods - -- **`void setCurrentScene(Scene* newScene)`** - Replaces the current scene with a new one. - -- **`void pushScene(Scene* newScene)`** - Pushes a new scene onto the stack, pausing the previous one. - -- **`void popScene()`** - Removes the top scene from the stack, resuming the previous one. - -- **`std::optional getCurrentScene() const`** - Gets the currently active scene, or std::nullopt if no scene is active. - ---- - - -## SceneArena (Memory Management) - -**Include:** `core/Scene.h` - -**Namespace:** `pixelroot32::core` - -**Inherits:** None - -An optional memory arena for zero-allocation scenes. This feature is enabled via the `PIXELROOT32_ENABLE_SCENE_ARENA` macro. It allows scenes to pre-allocate a fixed memory block for temporary data or entity storage, avoiding heap fragmentation on embedded devices. - -On ESP32, the main trade-off is: - -- **Benefits**: predictable memory usage, no `new`/`delete` in the scene, reduced fragmentation. -- **Costs**: the buffer size is fixed (over-allocating wastes RAM, under-allocating returns `nullptr`), and all allocations are freed only when the arena is reset or the scene ends. - -### Public Methods - -- **`void init(void* memory, std::size_t size)`** - Initializes the arena with a pre-allocated memory buffer. - -- **`void reset()`** - Resets the allocation offset to zero. This "frees" all memory in the arena instantly. - -- **`void* allocate(std::size_t size, std::size_t alignment)`** - Allocates a block of memory from the arena. Returns `nullptr` if the arena is full. - ---- diff --git a/api/core/scene.md b/api/core/scene.md deleted file mode 100644 index cc09577..0000000 --- a/api/core/scene.md +++ /dev/null @@ -1,64 +0,0 @@ -# Scene - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Scene - -**Inherits:** None - -Represents a game level or screen containing entities. A Scene manages a collection of Entities, a **PhysicsScheduler**, and a CollisionSystem. It is responsible for updating and drawing all entities it contains. - -### Public Properties - -- **`physics::CollisionSystem collisionSystem`** (when `PIXELROOT32_ENABLE_PHYSICS=1`) - The collision system used for physics simulation. Managed automatically by the scene. - -- **`physics::PhysicsScheduler physicsScheduler`** (when `PIXELROOT32_ENABLE_PHYSICS_FIXED_TIMESTEP=1`) - The fixed timestep scheduler for consistent physics. Uses time accumulator to ensure consistent physics regardless of frame rate variations. - -### Public Methods - -- **`virtual void init()`** - Called when the scene is initialized or entered. - -- **`virtual void update(unsigned long deltaTime)`** - Updates all entities in the scene and runs the physics pipeline. Touch **`UITouchElement`** instances are updated here only when they are registered as **entities** (e.g. via a **`UILayout`** with **`addEntity`**); **`UIManager::update`** is a no-op. - -- **`virtual void draw(Renderer& renderer)`** - Draws all visible entities in the scene, iterating them by logical render layers (0 = background, 1 = gameplay, 2 = UI). Touch widgets draw through this path as entities; **`UIManager::draw`** is a no-op. - -- **`virtual void processTouchEvents(TouchEvent* events, uint8_t count)`** - Runs the central touch pipeline for one frame: if `PIXELROOT32_ENABLE_UI_SYSTEM`, **`UIManager::processEvents`** runs first and may mark events consumed; then **`onUnconsumedTouchEvent`** is called for each unconsumed event. Override in a subclass only if you need preprocessing before the base implementation; otherwise override **`onUnconsumedTouchEvent`** for gameplay. - -- **`virtual void onUnconsumedTouchEvent(const TouchEvent& event)`** - Hook for touch not handled by UI. Default is no-op. Typical use: forward to **`ActorTouchController::handleTouch`** or custom gestures. - -- **`void addEntity(Entity* entity)`** - Adds an entity to the scene. - > **Note:** The scene does **not** take ownership of the entity. You must ensure the entity remains valid as long as it is in the scene (typically by holding it in a `std::unique_ptr` within your Scene class). - -- **`void removeEntity(Entity* entity)`** - Removes a specific entity from the scene (and from the collision system when physics is enabled). - -- **`void clearEntities()`** - Removes all entities from the scene. Does not delete the entity objects. - -### Overriding scene limits (MAX_LAYERS / MAX_ENTITIES) - -The engine defines default limits in `platforms/EngineConfig.h`: `MAX_LAYERS` (default 3) and `MAX_ENTITIES` (default 32). These are guarded with `#ifndef`, so you can override them from your project without modifying the engine. - -> **Note:** The default of 3 for `MAX_LAYERS` is due to ESP32 platform constraints (memory and draw-loop cost). On native/PC you can safely use a higher value; on ESP32, increasing it may affect performance or memory. - -**Compiler flags (recommended)** - -In your project (e.g. in `platformio.ini`), add the defines to `build_flags` for the environment you use: - -```ini -build_flags = - -DMAX_LAYERS=5 - -DMAX_ENTITIES=64 -``` - -The compiler defines `MAX_LAYERS` and `MAX_ENTITIES` before processing any `.cpp` file. Because `Scene.h` uses `#ifndef MAX_LAYERS` / `#ifndef MAX_ENTITIES`, it will not redefine them and your values will be used. This affects how many render layers are drawn (see `Scene::draw`) and, on Arduino, the capacity of the scene entity queue when constructed with `MAX_ENTITIES`. - ---- diff --git a/api/generated/audio/ApuCore.md b/api/generated/audio/ApuCore.md new file mode 100644 index 0000000..f150c97 --- /dev/null +++ b/api/generated/audio/ApuCore.md @@ -0,0 +1,56 @@ +# ApuCore + + + +**Source:** `ApuCore.h` + +## Description + +Shared NES-style APU core used by every AudioScheduler. + +Owns the 4 channels (2x PULSE, 1x TRIANGLE, 1x NOISE), the SPSC command +queue and the music sequencer. Platform-specific schedulers +(DefaultAudioScheduler, ESP32AudioScheduler, NativeAudioScheduler) are +thin orchestrators that decide *when* generateSamples() runs; all +synthesis, mixing and sequencing lives here to eliminate the three-way +duplication that existed before. + +Mixing curve: + per channel: s_c = wave_c(phase) * volume_c * MIXER_SCALE + sum: S = Σ s_c (bounded to [-1.6, 1.6]) + compressor: y = S / (1 + |S| * MIXER_K) + output: y * masterVolume * 32767, passed through a single-pole + DC-blocker to remove offset + transient clicks. + +On cores without an FPU (ESP32-C3) the integer-optimised path uses +`audio_mixer_lut` which is pre-fitted to the same curve. + +## Methods + +### `void init(int sampleRate)` + +### `bool submitCommand(const AudioCommand& cmd)` + +**Returns:** false if the SPSC queue was full. + +### `void generateSamples(int16_t* stream, int length)` + +### `uint32_t getDroppedCommands() const` + +### `void setSequencerNoteLimit(size_t limit)` + +### `size_t getSequencerNoteLimit() const` + +### `size_t getDeferredNotes() const` + +### `bool isMusicPlaying() const` + +### `bool isMusicPaused() const` + +### `int getSampleRate() const` + +### `void reset()` + +### `void setPostMixMono(void (*fn)(int16_t* mono, int length, void* user), void* user)` + +### `void getAndResetProfileStats(ProfileEntry* out, uint8_t& count)` diff --git a/api/generated/audio/AudioBackend.md b/api/generated/audio/AudioBackend.md new file mode 100644 index 0000000..868b236 --- /dev/null +++ b/api/generated/audio/AudioBackend.md @@ -0,0 +1,23 @@ +# AudioBackend + + + +**Source:** `AudioBackend.h` + +## Description + +Abstract interface for platform-specific audio drivers. + +This class abstracts the underlying audio hardware or API (e.g., SDL2, I2S). +It is responsible for requesting audio samples from the AudioEngine and +pushing them to the output device. + +## Methods + +### `virtual int getSampleRate() const` + +**Description:** + +Returns the configured sample rate of the backend. + +**Returns:** Sample rate in Hz (e.g., 22050, 44100). diff --git a/api/generated/audio/AudioChannel.md b/api/generated/audio/AudioChannel.md new file mode 100644 index 0000000..fc92e6b --- /dev/null +++ b/api/generated/audio/AudioChannel.md @@ -0,0 +1,15 @@ +# AudioChannel + + + +**Source:** `AudioTypes.h` + +## Description + +Represents the internal state of a single audio channel. + +Designed to be static and memory-efficient. + +## Methods + +### `void reset()` diff --git a/api/generated/audio/AudioCommand.md b/api/generated/audio/AudioCommand.md new file mode 100644 index 0000000..e969eb6 --- /dev/null +++ b/api/generated/audio/AudioCommand.md @@ -0,0 +1,9 @@ +# AudioCommand + + + +**Source:** `AudioTypes.h` + +## Description + +Internal command to communicate between game and audio threads. diff --git a/api/generated/audio/AudioCommandQueue.md b/api/generated/audio/AudioCommandQueue.md new file mode 100644 index 0000000..60ddedb --- /dev/null +++ b/api/generated/audio/AudioCommandQueue.md @@ -0,0 +1,52 @@ +# AudioCommandQueue + + + +**Source:** `AudioCommandQueue.h` + +## Description + +Multi-Producer Single-Consumer lock-free ring buffer for AudioCommands. + +Fixed size, no allocation. Supports multiple concurrent producer threads. +If the queue is full, the newest command is dropped and droppedCommands is incremented. +Uses compare-and-swap (CAS) for atomic ring index advancement. + +## Methods + +### `bool enqueue(const AudioCommand& cmd)` + +**Description:** + +Enqueues a command. Thread-safe for multiple producers. + +**Parameters:** + +- `cmd`: The command to enqueue. + +**Returns:** true if successful, false if the queue is full (dropped). + +### `bool dequeue(AudioCommand& outCmd)` + +**Description:** + +Dequeues a command. Called from the consumer (Audio Thread). + +**Parameters:** + +- `outCmd`: Reference to store the dequeued command. + +**Returns:** true if a command was dequeued, false if the queue is empty. + +### `bool isEmpty() const` + +**Description:** + +Checks if the queue is empty. + +### `size_t getDroppedCommands() const` + +**Description:** + +Returns the count of dropped commands due to queue full. +Thread-safe for concurrent reads from multiple producers. diff --git a/api/generated/audio/AudioConfig.md b/api/generated/audio/AudioConfig.md new file mode 100644 index 0000000..04b06c7 --- /dev/null +++ b/api/generated/audio/AudioConfig.md @@ -0,0 +1,16 @@ +# AudioConfig + + + +**Source:** `AudioConfig.h` + +## Description + +Configuration for the Audio subsystem. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `backend` | `AudioBackend*` | Pointer to the platform-specific audio backend. | +| `sampleRate` | `int` | Desired sample rate in Hz. | diff --git a/api/generated/audio/AudioEngine.md b/api/generated/audio/AudioEngine.md new file mode 100644 index 0000000..134844f --- /dev/null +++ b/api/generated/audio/AudioEngine.md @@ -0,0 +1,42 @@ +# AudioEngine + + + +**Source:** `AudioEngine.h` + +## Description + +Core class for the NES-like audio subsystem. + +## Methods + +### `void init()` + +### `void generateSamples(int16_t* stream, int length)` + +### `void playEvent(const AudioEvent& event)` + +### `void setMasterVolume(float volume)` + +### `float getMasterVolume() const` + +### `void setMasterBitcrush(uint8_t bits)` + +### `uint8_t getMasterBitcrush() const` + +### `void submitCommand(const AudioCommand& cmd)` + +### `bool isMusicPlaying() const` + +**Description:** + +Reports the real-time music transport state from the + underlying scheduler/ApuCore (not a cached flag). + +### `bool isMusicPaused() const` + +### `AudioScheduler* getScheduler() const` + +**Description:** + +Gets the underlying scheduler for diagnostics/profiling. diff --git a/api/generated/audio/AudioEvent.md b/api/generated/audio/AudioEvent.md new file mode 100644 index 0000000..266d0c2 --- /dev/null +++ b/api/generated/audio/AudioEvent.md @@ -0,0 +1,9 @@ +# AudioEvent + + + +**Source:** `AudioTypes.h` + +## Description + +A fire-and-forget sound event triggered by the game. diff --git a/api/generated/audio/AudioScheduler.md b/api/generated/audio/AudioScheduler.md new file mode 100644 index 0000000..2d76313 --- /dev/null +++ b/api/generated/audio/AudioScheduler.md @@ -0,0 +1,67 @@ +# AudioScheduler + + + +**Source:** `AudioScheduler.h` + +## Description + +Abstract interface for the audio execution context. + +The scheduler is responsible for owning the audio state, processing commands, +and generating samples. It can run in the same thread as the game loop +or in a dedicated audio thread. + +## Methods + +### `virtual void submitCommand(const AudioCommand& cmd)` + +**Description:** + +Submits a command to the scheduler. + +**Parameters:** + +- `cmd`: The command to execute. + +### `virtual void start()` + +**Description:** + +Starts the scheduler execution. + +### `virtual void stop()` + +**Description:** + +Stops the scheduler execution. + +### `virtual bool isIndependent() const` + +**Description:** + +Checks if the scheduler runs in an independent thread. + +### `virtual void generateSamples(int16_t* stream, int length)` + +**Description:** + +Generates samples. Should be called by the backend or scheduler thread. + +### `virtual bool isMusicPlaying() const` + +**Description:** + +Reports whether a music track is currently being sequenced. + +### `virtual bool isMusicPaused() const` + +**Description:** + +Reports whether the music sequencer is paused. + +### `virtual ApuCore& getApuCore()` + +**Description:** + +Gets reference to the underlying ApuCore for diagnostics/profiling. diff --git a/api/generated/audio/DefaultAudioScheduler.md b/api/generated/audio/DefaultAudioScheduler.md new file mode 100644 index 0000000..cb483d3 --- /dev/null +++ b/api/generated/audio/DefaultAudioScheduler.md @@ -0,0 +1,26 @@ +# DefaultAudioScheduler + + + +**Source:** `DefaultAudioScheduler.h` + +**Inherits from:** [AudioScheduler](./AudioScheduler.md) + +## Description + +Backend-driven scheduler used on platforms without a dedicated + audio task. + +Delegates all synthesis to ApuCore; generateSamples() runs in whichever +context the backend invokes it (tests, simulators without a thread, +etc.). + +## Inheritance + +[AudioScheduler](./AudioScheduler.md) → `DefaultAudioScheduler` + +## Methods + +### `const ApuCore& core() const` + +### `ApuCore& core()` diff --git a/api/generated/audio/InstrumentPreset.md b/api/generated/audio/InstrumentPreset.md new file mode 100644 index 0000000..6d7bea0 --- /dev/null +++ b/api/generated/audio/InstrumentPreset.md @@ -0,0 +1,30 @@ +# InstrumentPreset + + + +**Source:** `AudioMusicTypes.h` + +## Description + +Defines instrument characteristics for playback. + +For melodic instruments (duty > 0): + - defaultOctave: the base octave for notes + - duty: duty cycle for PULSE wave (e.g., 0.5, 0.125) + +For percussion instruments (duty == 0): + - defaultOctave: drum type selector (1=Kick, 2=Snare, 3+=Hi-HAT) + - defaultDuration: fixed duration for each hit (0.0 = use note.duration) + - noisePeriod: LFSR period for noise channel (0 = calc from frequency, >0 = direct period) + +## Methods + +### `inline float instrumentToFrequency(const InstrumentPreset& preset, Note /*note*/, uint8_t /*octave*/)` + +**Parameters:** + +- `preset`: The instrument preset. +- `note`: The note (ignored for percussion). +- `octave`: The octave (ignored for percussion). + +**Returns:** Frequency in Hz. diff --git a/api/generated/audio/MusicNote.md b/api/generated/audio/MusicNote.md new file mode 100644 index 0000000..e32e976 --- /dev/null +++ b/api/generated/audio/MusicNote.md @@ -0,0 +1,13 @@ +# MusicNote + + + +**Source:** `AudioMusicTypes.h` + +## Description + +Represents a single note in a melody. + +For percussion (note.preset && preset.duty == 0): + - preset defines frequency and defaultDuration + - octave still determines drum type if preset not available diff --git a/api/generated/audio/MusicPlayer.md b/api/generated/audio/MusicPlayer.md new file mode 100644 index 0000000..ebac94b --- /dev/null +++ b/api/generated/audio/MusicPlayer.md @@ -0,0 +1,109 @@ +# MusicPlayer + + + +**Source:** `MusicPlayer.h` + +## Description + +Simple sequencer to play MusicTracks. + +## Methods + +### `void play(const MusicTrack& track)` + +**Description:** + +Starts playing a track. + +**Parameters:** + +- `track`: The track to play. + +### `void stop()` + +**Description:** + +Stops playback and silences the channel. + +### `void pause()` + +**Description:** + +Pauses playback. + +### `void resume()` + +**Description:** + +Resumes playback. + +### `bool isPlaying() const` + +**Description:** + +Checks if a track is currently playing. + +**Returns:** true if playing, false otherwise. + +### `void setTempoFactor(float factor)` + +**Description:** + +Sets the global tempo scaling factor. + +**Parameters:** + +- `factor`: 1.0f is normal speed, 2.0f is double speed. + +### `float getTempoFactor() const` + +**Description:** + +Gets the current tempo scaling factor. + +**Returns:** Current factor (default 1.0f). + +### `void setBPM(float bpm)` + +**Description:** + +Sets the tempo in BPM (beats per minute). + +**Parameters:** + +- `bpm`: Beats per minute (default 150). + +### `float getBPM() const` + +**Description:** + +Gets the current BPM setting. + +**Returns:** Current BPM (default 150). + +### `size_t getActiveTrackCount() const` + +**Description:** + +Gets the number of currently active tracks. + +**Returns:** Number of active tracks (1-4), 0 if not playing. + +### `void setMasterVolume(float volume)` + +**Description:** + +Sets the master volume level. + +**Parameters:** + +- `volume`: Volume level (0.0f = silent, 1.0f = full volume). + +### `float getMasterVolume() const` + +**Description:** + +Gets the current master volume level. + +**Returns:** Current volume (0.0f - 1.0f). diff --git a/api/generated/core/Actor.md b/api/generated/core/Actor.md new file mode 100644 index 0000000..57cb517 --- /dev/null +++ b/api/generated/core/Actor.md @@ -0,0 +1,46 @@ +# Actor + + + +**Source:** `Actor.h` + +**Inherits from:** [Entity](./Entity.md) + +## Description + +An Entity capable of physical interaction and collision. + +Adds collision layers and masks for dynamic game objects like players, +enemies, and projectiles. + +## Inheritance + +[Entity](./Entity.md) → `Actor` + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `entityId` | `uint16_t` | Unique id per CollisionSystem registration; used for pair deduplication. | +| `queryId` | `int` | Used for optimized grid queries. | +| `layer` | `pixelroot32::physics::CollisionLayer` | The collision layer this actor belongs to. | +| `mask` | `pixelroot32::physics::CollisionLayer` | The collision layers this actor interacts with. | +| `collisionSystem` | `pixelroot32::physics::CollisionSystem*` | Reference to the collision system. | + +## Methods + +### `bool isInLayer(uint16_t targetLayer) const` + +### `virtual Rect getHitBox()` + +### `virtual bool isPhysicsBody() const` + +### `virtual void onCollision(Actor* other)` + +**Description:** + +Callback invoked when a collision occurs. Notification only — no physics changes. + +**Parameters:** + +- `other`: The actor that this actor collided with. diff --git a/api/generated/core/CollisionShape.md b/api/generated/core/CollisionShape.md new file mode 100644 index 0000000..0e3209c --- /dev/null +++ b/api/generated/core/CollisionShape.md @@ -0,0 +1,9 @@ +# CollisionShape + + + +**Source:** `PhysicsActor.h` + +## Description + +Defines the geometric shape used for collision detection. diff --git a/api/generated/core/Engine.md b/api/generated/core/Engine.md new file mode 100644 index 0000000..e8d531a --- /dev/null +++ b/api/generated/core/Engine.md @@ -0,0 +1,110 @@ +# Engine + + + +**Source:** `Engine.h` + +## Description + +The main engine class that manages the game loop and core subsystems. + +Engine acts as the central hub of the game engine. It initializes and manages +the Renderer, InputManager, AudioEngine, SceneManager, and optionally Touch system. +It runs the main game loop, handling timing (delta time), updating the current +scene, and rendering frames. + +## Touch Pipeline (PIXELROOT32_ENABLE_TOUCH) + +When touch is enabled (default), Engine automatically: + 1. Processes mouse events (Native) or receives injected touch points (ESP32) + 2. Applies gesture detection (Click, DoubleClick, LongPress, Drag) + 3. Sends events to the current scene via Scene::processTouchEvents() + +The order inside Scene::processTouchEvents is guaranteed: + UIManager::processEvents (marks consumed) → onUnconsumedTouchEvent (virtual). + +Set PIXELROOT32_ENABLE_TOUCH=0 in platform defines to disable (saves ~200 bytes). + +## Methods + +### `void init()` + +**Description:** + +Initializes the engine subsystems. + +### `void run()` + +**Description:** + +Starts the main game loop. + +### `unsigned long getDeltaTime() const` + +**Description:** + +Gets the time elapsed since the last frame. + +**Returns:** The delta time in milliseconds. + +### `unsigned long getMillis() const` + +**Description:** + +Gets the number of milliseconds since the engine started. + +**Returns:** The time in milliseconds. + +### `void setScene(Scene* newScene)` + +**Description:** + +Sets the current active scene. + +**Parameters:** + +- `newScene`: Pointer to the new Scene to become active. + +### `* Use this to inject touch points on ESP32(via TouchManager)` + +### `bool hasTouchEvents() const` + +**Description:** + +Check if there are pending touch events. + +**Returns:** true if there are events in the queue. + +### `* On ESP32, call this once in setup() after touchManager.init()` + +### `void connectInputToDrawer()` + +**Description:** + +Connect InputManager to Drawer for mouse-to-touch mapping. + +**Parameters:** + +- `inputManager`: Pointer to the InputManager. + +Called automatically in init() for Native builds. + +### `const PlatformCapabilities& getPlatformCapabilities() const` + +**Description:** + +Gets the capabilities of the current hardware platform. + +**Returns:** Reference to the PlatformCapabilities. + +### `void update()` + +**Description:** + +Updates the game logic. + +### `void draw()` + +**Description:** + +Renders the current frame. diff --git a/api/generated/core/Entity.md b/api/generated/core/Entity.md new file mode 100644 index 0000000..1113600 --- /dev/null +++ b/api/generated/core/Entity.md @@ -0,0 +1,74 @@ +# Entity + + + +**Source:** `Entity.h` + +## Description + +Abstract base class for all game objects. + +Entities are the fundamental building blocks of the scene. They have a position, +size, and lifecycle methods (update, draw). + +Uses adaptable Scalar type for position to ensure consistent physics across platforms. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `position` | `pixelroot32::math::Vector2` | Position in world space. | +| `width` | `int` | Width and Height of the entity. | +| `type` | `EntityType` | The specific type of this entity. | +| `isVisible` | `bool` | If false, the entity's draw method will not be called. | +| `isEnabled` | `bool` | If false, the entity's update method will not be called. | + +## Methods + +### `virtual void setVisible(bool v)` + +**Description:** + +Sets the visibility of the entity. + +**Parameters:** + +- `v`: true to show, false to hide. + +### `virtual void setEnabled(bool e)` + +**Description:** + +Sets the enabled state of the entity. + +**Parameters:** + +- `e`: true to enable, false to disable. + +### `unsigned char getRenderLayer() const` + +**Description:** + +Gets the current render layer. + +**Returns:** The layer index (0-255). + +### `virtual void setRenderLayer(unsigned char layer)` + +**Description:** + +Sets the render layer. + +**Parameters:** + +- `layer`: The layer index (0 to MaxLayers-1). Clamped if exceeded. + +### `virtual void update(unsigned long deltaTime)` + +**Description:** + +Updates the entity's logic. + +**Parameters:** + +- `deltaTime`: Time elapsed since the last frame in milliseconds. diff --git a/api/generated/core/EntityType.md b/api/generated/core/EntityType.md new file mode 100644 index 0000000..f85ebf7 --- /dev/null +++ b/api/generated/core/EntityType.md @@ -0,0 +1,9 @@ +# EntityType + + + +**Source:** `Entity.h` + +## Description + +Categorizes entities for type-safe casting and logic differentiation. diff --git a/api/generated/core/LimitRect.md b/api/generated/core/LimitRect.md new file mode 100644 index 0000000..2c5bf4e --- /dev/null +++ b/api/generated/core/LimitRect.md @@ -0,0 +1,29 @@ +# LimitRect + + + +**Source:** `PhysicsActor.h` + +## Description + +Defines a rectangular boundary for actor movement. + +Used to constrain an actor within a specific area of the world. +Values of -1 indicate no limit on that side. + +## Methods + +### `int width() const` + +**Description:** + +Constructs a new LimitRect. + +**Parameters:** + +- `l`: Left limit. +- `t`: Top limit. +- `r`: Right limit. +- `b`: Bottom limit. + +### `int height() const` diff --git a/api/generated/core/LogLevel.md b/api/generated/core/LogLevel.md new file mode 100644 index 0000000..a702fb6 --- /dev/null +++ b/api/generated/core/LogLevel.md @@ -0,0 +1,66 @@ +# LogLevel + + + +**Source:** `Log.h` + +## Description + +Enumeration of log levels. + +## Methods + +### `const char* levelToString(LogLevel level)` + +**Description:** + +Converts a log level to its string representation. + +**Parameters:** + +- `level`: The log level to convert. + +**Returns:** A string representation of the log level. + +### `void platformPrint(const char* text)` + +**Description:** + +Prints text to the platform-specific output. + +**Parameters:** + +- `text`: The text to print. + +### `void logInternal(LogLevel level, const char* fmt, va_list args)` + +**Description:** + +Internal logging function that handles formatted message output. + +**Parameters:** + +- `level`: The log level for the message. +- `fmt`: The format string (printf-style). +- `args`: The va_list of arguments for the format string. + +### `inline void log(LogLevel level, const char* fmt, ...)` + +**Description:** + +Logs a message with the specified level. + +**Parameters:** + +- `level`: The log level. +- `fmt`: The format string (printf-style). + +### `inline void log(const char* fmt, ...)` + +**Description:** + +Logs a message with the default Info level. + +**Parameters:** + +- `fmt`: The format string (printf-style). diff --git a/api/generated/core/PhysicsActor.md b/api/generated/core/PhysicsActor.md new file mode 100644 index 0000000..9643765 --- /dev/null +++ b/api/generated/core/PhysicsActor.md @@ -0,0 +1,220 @@ +# PhysicsActor + + + +**Source:** `PhysicsActor.h` + +**Inherits from:** [Actor](./Actor.md) + +## Description + +An actor with basic 2D physics properties using adaptable Scalar type. + +Handles velocity, acceleration (via integration), and collision with world boundaries. +Automatically adapts to use float or Fixed16 based on the platform configuration. + +## Inheritance + +[Actor](./Actor.md) → `PhysicsActor` + +## Methods + +### `void setLimits(const LimitRect& limitRect)` + +**Description:** + +Sets custom movement limits for the actor. + +**Parameters:** + +- `limitRect`: The LimitRect structure defining the boundaries. + +### `void setLimits(int left, int top, int right, int bottom)` + +**Description:** + +Sets custom movement limits for the actor. + +**Parameters:** + +- `left`: Left limit. +- `top`: Top limit. +- `right`: Right limit. +- `bottom`: Bottom limit. + +### `void setWorldBounds(int w, int h)` + +**Description:** + +Defines the world size for boundary checking. + +**Parameters:** + +- `w`: Width of the world. +- `h`: Height of the world. + +### `void setWorldSize(int w, int h)` + +**Description:** + +Legacy alias for setWorldBounds. + +**Parameters:** + +- `w`: Width of the world. +- `h`: Height of the world. + +### `WorldCollisionInfo getWorldCollisionInfo() const` + +**Description:** + +Gets information about collisions with the world boundaries. + +**Returns:** A WorldCollisionInfo struct containing collision flags. + +### `void resetWorldCollisionInfo()` + +**Description:** + +Resets the world collision flags for the current frame. + +### `PhysicsBodyType getBodyType() const` + +**Description:** + +Gets the simulation body type. + +**Returns:** The PhysicsBodyType of this actor. + +### `void setBodyType(PhysicsBodyType type)` + +**Description:** + +Sets the simulation body type. + +**Parameters:** + +- `type`: The new PhysicsBodyType. + +### `void setMass(float m)` + +**Description:** + +Sets the mass of the actor. + +**Parameters:** + +- `m`: Mass value. + +### `virtual void resolveWorldBounds()` + +**Description:** + +Resolves collisions with the defined world or custom bounds. + +### `void setVelocity(T x, T y)` + +**Description:** + +Sets the linear velocity of the actor using floats. + +**Parameters:** + +- `x`: Horizontal velocity. +- `y`: Vertical velocity. + +### `CollisionShape getShape() const` + +**Description:** + +Gets the collision shape type. + +**Returns:** The CollisionShape of this actor. + +### `void setShape(CollisionShape s)` + +**Description:** + +Sets the collision shape type. + +**Parameters:** + +- `s`: The new CollisionShape. + +### `void setUserData(void* data)` + +**Description:** + +Set user data pointer for custom metadata. + +**Parameters:** + +- `data`: Opaque pointer. Engine does not manage lifetime. + +### `void* getUserData() const` + +**Description:** + +Get user data pointer. + +**Returns:** Pointer set via setUserData, or nullptr if never set. + +### `void setSensor(bool s)` + +**Description:** + +Sets whether this body is a sensor (trigger). + +**Parameters:** + +- `s`: true = sensor (events only, no physics response); false = solid (default). + +### `bool isSensor() const` + +**Description:** + +Returns true if this body is a sensor (trigger). + +### `void setOneWay(bool w)` + +**Description:** + +Sets whether this body is a one-way platform (blocks only from one side). + +**Parameters:** + +- `w`: true = one-way (e.g. land from above, pass through from below); false = solid from all sides. + +### `bool isOneWay() const` + +**Description:** + +Returns true if this body is a one-way platform. + +### `void setBounce(bool b)` + +**Description:** + +Sets whether this body bounces on collision. + +**Parameters:** + +- `b`: true = bounce (velocity reflected on static contact); false = no bounce (velocity zeroed). + +### `bool isBounce() const` + +**Description:** + +Returns true if this body bounces on collision. + +### `void updatePreviousPosition()` + +**Description:** + +Updates the previous position to the current position. + +### `virtual void onWorldCollision()` + +**Description:** + +Callback triggered when this actor collides with world boundaries. diff --git a/api/generated/core/PhysicsBodyType.md b/api/generated/core/PhysicsBodyType.md new file mode 100644 index 0000000..ee805cd --- /dev/null +++ b/api/generated/core/PhysicsBodyType.md @@ -0,0 +1,9 @@ +# PhysicsBodyType + + + +**Source:** `PhysicsActor.h` + +## Description + +Defines the simulation behavior of a PhysicsActor. diff --git a/api/generated/core/Rect.md b/api/generated/core/Rect.md new file mode 100644 index 0000000..8ef5db2 --- /dev/null +++ b/api/generated/core/Rect.md @@ -0,0 +1,32 @@ +# Rect + + + +**Source:** `Entity.h` + +## Description + +Represents a 2D rectangle, typically used for hitboxes or bounds. + +Uses adaptable Scalar type for coordinates to support both float and fixed-point math. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `position` | `pixelroot32::math::Vector2` | Top-left corner coordinates. | +| `width` | `int` | Dimensions of the rectangle. | + +## Methods + +### `bool intersects(const Rect& other) const` + +**Description:** + +Checks if this rectangle intersects with another. + +**Parameters:** + +- `other`: The other rectangle to check against. + +**Returns:** true if the rectangles overlap, false otherwise. diff --git a/api/generated/core/Scene.md b/api/generated/core/Scene.md new file mode 100644 index 0000000..eca1760 --- /dev/null +++ b/api/generated/core/Scene.md @@ -0,0 +1,78 @@ +# Scene + + + +**Source:** `Scene.h` + +## Description + +Represents a game level or screen containing entities. + +## Methods + +### `virtual void init()` + +**Description:** + +Initializes the scene. Called when entering the scene. + +### `virtual void initUI()` + +**Description:** + +Initialize the UI system for this scene. +Called during scene init. Add UI elements here. + +### `virtual void updateUI(unsigned long deltaTime)` + +**Description:** + +Update the UI system. + +**Parameters:** + +- `deltaTime`: Time elapsed in ms. + +### `* Execution order(deterministic)` + +### `virtual void update(unsigned long deltaTime)` + +**Description:** + +Updates all entities in the scene and handles collisions. + +**Parameters:** + +- `deltaTime`: Time elapsed in ms. + +### `virtual bool shouldRedrawFramebuffer() const` + +**Description:** + +When false, Engine may skip `draw()` and `present()` for this iteration (after `update()`). + +### `void addEntity(Entity* entity)` + +**Description:** + +Adds an entity to the scene. + +**Parameters:** + +- `entity`: Pointer to the Entity to add. + +### `void removeEntity(Entity* entity)` + +**Description:** + +Removes an entity from the scene. + +**Parameters:** + +- `entity`: Pointer to the Entity to remove. + +### `void clearEntities()` + +**Description:** + +Removes all entities from the scene. diff --git a/api/generated/core/SceneManager.md b/api/generated/core/SceneManager.md new file mode 100644 index 0000000..174fb9c --- /dev/null +++ b/api/generated/core/SceneManager.md @@ -0,0 +1,72 @@ +# SceneManager + + + +**Source:** `SceneManager.h` + +## Description + +Manages the stack of active scenes. + +The SceneManager allows for scene transitions (replacing scenes) and +stacking scenes (push/pop), which is useful for pausing or menus. + +## Methods + +### `void setCurrentScene(Scene* newScene)` + +**Description:** + +Replaces the current scene with a new one. + +**Parameters:** + +- `newScene`: The new scene to switch to. + +### `void pushScene(Scene* newScene)` + +**Description:** + +Pushes a new scene onto the stack, pausing the previous one. + +**Parameters:** + +- `newScene`: The new scene to become active. + +### `void popScene()` + +**Description:** + +Removes the top scene from the stack, resuming the previous one. + +### `void update(unsigned long dt)` + +**Description:** + +Updates the currently active scene. + +**Parameters:** + +- `dt`: Delta time in ms. + +### `bool aggregateShouldRedrawFramebuffer() const` + +**Description:** + +True if any scene on the stack needs a framebuffer pass this iteration. + +### `int getSceneCount() const` + +**Description:** + +Gets the number of scenes in the stack. + +**Returns:** The number of scenes. + +### `bool isEmpty() const` + +**Description:** + +Checks if the scene stack is empty. + +**Returns:** True if there are no scenes. diff --git a/api/generated/core/WorldCollisionInfo.md b/api/generated/core/WorldCollisionInfo.md new file mode 100644 index 0000000..5ed329b --- /dev/null +++ b/api/generated/core/WorldCollisionInfo.md @@ -0,0 +1,9 @@ +# WorldCollisionInfo + + + +**Source:** `PhysicsActor.h` + +## Description + +Stores flags indicating which world boundaries were hit in the current frame. diff --git a/api/generated/drivers/ESP32AudioScheduler.md b/api/generated/drivers/ESP32AudioScheduler.md new file mode 100644 index 0000000..618cf3f --- /dev/null +++ b/api/generated/drivers/ESP32AudioScheduler.md @@ -0,0 +1,26 @@ +# ESP32AudioScheduler + + + +**Source:** `ESP32AudioScheduler.h` + +**Inherits from:** [AudioScheduler](../audio/AudioScheduler.md) + +## Description + +Audio scheduler for ESP32 targets. + +The I2S/DAC backend creates the FreeRTOS task and calls generateSamples(); +this class does not spawn its own task. All synthesis is delegated to +ApuCore so ESP32-classic / S3 / C3 share the exact same logic. +Constructor arguments are reserved for API stability. + +## Inheritance + +[AudioScheduler](../audio/AudioScheduler.md) → `ESP32AudioScheduler` + +## Methods + +### `const ApuCore& core() const` + +### `ApuCore& core()` diff --git a/api/generated/drivers/ESP32_DAC_AudioBackend.md b/api/generated/drivers/ESP32_DAC_AudioBackend.md new file mode 100644 index 0000000..b2263a8 --- /dev/null +++ b/api/generated/drivers/ESP32_DAC_AudioBackend.md @@ -0,0 +1,26 @@ +# ESP32_DAC_AudioBackend + + + +**Source:** `ESP32_DAC_AudioBackend.h` + +**Inherits from:** [AudioBackend](../audio/AudioBackend.md) + +## Description + +Audio backend for ESP32 classic / S2 internal 8-bit DAC. + +Uses **I2S in DAC-built-in mode** so samples are pushed to the DAC via +DMA instead of the previous per-sample `dacWrite()` spin loop. This +frees ~15-25% of CPU on Core 0 at 22050 Hz and removes the per-sample +pinmux/mutex overhead that `dacWrite()` incurs on every call. + +Target: ESP32 (original), ESP32-S2. The DAC does not exist on S3/C3. + +## Inheritance + +[AudioBackend](../audio/AudioBackend.md) → `ESP32_DAC_AudioBackend` + +## Methods + +### `void audioTaskLoop()` diff --git a/api/generated/drivers/ESP32_I2S_AudioBackend.md b/api/generated/drivers/ESP32_I2S_AudioBackend.md new file mode 100644 index 0000000..515dd52 --- /dev/null +++ b/api/generated/drivers/ESP32_I2S_AudioBackend.md @@ -0,0 +1,22 @@ +# ESP32_I2S_AudioBackend + + + +**Source:** `ESP32_I2S_AudioBackend.h` + +**Inherits from:** [AudioBackend](../audio/AudioBackend.md) + +## Description + +Audio backend implementation for ESP32 using I2S. + +Uses a FreeRTOS task to continuously feed the I2S DMA buffer +to ensure smooth playback independent of the game loop frame rate. + +## Inheritance + +[AudioBackend](../audio/AudioBackend.md) → `ESP32_I2S_AudioBackend` + +## Methods + +### `void audioTaskLoop()` diff --git a/api/generated/drivers/NativeAudioScheduler.md b/api/generated/drivers/NativeAudioScheduler.md new file mode 100644 index 0000000..51c9f10 --- /dev/null +++ b/api/generated/drivers/NativeAudioScheduler.md @@ -0,0 +1,28 @@ +# NativeAudioScheduler + + + +**Source:** `NativeAudioScheduler.h` + +**Inherits from:** [AudioScheduler](../audio/AudioScheduler.md) + +## Description + +Audio scheduler for native builds. + +Runs ApuCore in its own std::thread and double-buffers samples through +a lock-free ring, mirroring the dual-core ESP32 behaviour. All +synthesis / sequencer logic lives in ApuCore; this class owns only +threading and the ring buffer. + +## Inheritance + +[AudioScheduler](../audio/AudioScheduler.md) → `NativeAudioScheduler` + +## Methods + +### `explicit NativeAudioScheduler(size_t ringBufferSize = 4096)` + +### `const ApuCore& core() const` + +### `ApuCore& core()` diff --git a/api/generated/drivers/SDL2_AudioBackend.md b/api/generated/drivers/SDL2_AudioBackend.md new file mode 100644 index 0000000..c2b78e4 --- /dev/null +++ b/api/generated/drivers/SDL2_AudioBackend.md @@ -0,0 +1,19 @@ +# SDL2_AudioBackend + + + +**Source:** `SDL2_AudioBackend.h` + +**Inherits from:** [AudioBackend](../audio/AudioBackend.md) + +## Description + +Audio backend implementation for SDL2 (Windows/Linux/Mac). + +## Inheritance + +[AudioBackend](../audio/AudioBackend.md) → `SDL2_AudioBackend` + +## Methods + +### `void audioCallback(uint8_t* stream, int len)` diff --git a/api/generated/drivers/SDL2_Drawer.md b/api/generated/drivers/SDL2_Drawer.md new file mode 100644 index 0000000..d31d9d7 --- /dev/null +++ b/api/generated/drivers/SDL2_Drawer.md @@ -0,0 +1,15 @@ +# SDL2_Drawer + + + +**Source:** `SDL2_Drawer.h` + +**Inherits from:** [BaseDrawSurface](../graphics/BaseDrawSurface.md) + +## Description + +SDL2-backed draw surface for native desktop builds. + +## Inheritance + +[BaseDrawSurface](../graphics/BaseDrawSurface.md) → `SDL2_Drawer` diff --git a/api/generated/drivers/TFT_eSPI_Drawer.md b/api/generated/drivers/TFT_eSPI_Drawer.md new file mode 100644 index 0000000..fcf095b --- /dev/null +++ b/api/generated/drivers/TFT_eSPI_Drawer.md @@ -0,0 +1,52 @@ +# TFT_eSPI_Drawer + + + +**Source:** `TFT_eSPI_Drawer.h` + +**Inherits from:** [BaseDrawSurface](../graphics/BaseDrawSurface.md) + +## Description + +Concrete implementation of DrawSurface for ESP32 using the TFT_eSPI library. + +This class handles low-level interaction with the display hardware via SPI. +It uses a sprite (framebuffer) to minimize flickering and tearing. + +## Inheritance + +[BaseDrawSurface](../graphics/BaseDrawSurface.md) → `TFT_eSPI_Drawer` + +## Methods + +### `bool needsScaling() const` + +**Description:** + +Checks if scaling is needed. + +**Returns:** true if logical != physical resolution. + +### `void buildScaleLUTs()` + +**Description:** + +Builds the X and Y scaling lookup tables. + +### `void freeScalingBuffers()` + +**Description:** + +Frees scaling-related memory. + +### `void sendBufferScaled()` + +**Description:** + +Sends the buffer using hardware DMA and software scaling. + +### `void scaleLine(const uint8_t* spriteBase, int srcY, uint16_t* dst)` + +**Description:** + +Scales a single line from 8bpp logical to 16bpp physical. diff --git a/api/generated/drivers/U8G2_Drawer.md b/api/generated/drivers/U8G2_Drawer.md new file mode 100644 index 0000000..a5f6ff3 --- /dev/null +++ b/api/generated/drivers/U8G2_Drawer.md @@ -0,0 +1,53 @@ +# U8G2_Drawer + + + +**Source:** `U8G2_Drawer.h` + +**Inherits from:** [BaseDrawSurface](../graphics/BaseDrawSurface.md) + +## Description + +Implementation of DrawSurface using the U8G2 library for monochromatic OLED displays. + +## Inheritance + +[BaseDrawSurface](../graphics/BaseDrawSurface.md) → `U8G2_Drawer` + +## Methods + +### `U8G2* getU8g2() const` + +### `bool needsScaling() const` + +**Description:** + +Checks if scaling is needed. + +**Returns:** true if logical != physical resolution. + +### `void buildScaleLUTs()` + +**Description:** + +Builds the X and Y scaling lookup tables. + +### `void freeScalingBuffers()` + +**Description:** + +Frees scaling-related memory. + +### `void sendBufferScaled()` + +**Description:** + +Sends the buffer using software scaling. + +### `inline uint8_t rgb565To1Bit(uint16_t color)` + +**Description:** + +Internal helper to convert RGB565 to 1-bit monochromatic. + +**Returns:** 1 for "on", 0 for "off". diff --git a/api/generated/graphics/Anchor.md b/api/generated/graphics/Anchor.md new file mode 100644 index 0000000..d298e94 --- /dev/null +++ b/api/generated/graphics/Anchor.md @@ -0,0 +1,9 @@ +# Anchor + + + +**Source:** `UIAnchorLayout.h` + +## Description + +Defines anchor points for positioning UI elements. diff --git a/api/generated/graphics/BaseDrawSurface.md b/api/generated/graphics/BaseDrawSurface.md new file mode 100644 index 0000000..d919b5f --- /dev/null +++ b/api/generated/graphics/BaseDrawSurface.md @@ -0,0 +1,22 @@ +# BaseDrawSurface + + + +**Source:** `BaseDrawSurface.h` + +**Inherits from:** [DrawSurface](./DrawSurface.md) + +## Description + +Optional base class for DrawSurface implementations that provides default primitive rendering. + +Users can inherit from this class to avoid implementing every single primitive. +At minimum, a subclass should implement: +- init() +- drawPixel() +- sendBuffer() +- clearBuffer() + +## Inheritance + +[DrawSurface](./DrawSurface.md) → `BaseDrawSurface` diff --git a/api/generated/graphics/Camera2D.md b/api/generated/graphics/Camera2D.md new file mode 100644 index 0000000..ff590f1 --- /dev/null +++ b/api/generated/graphics/Camera2D.md @@ -0,0 +1,32 @@ +# Camera2D + + + +**Source:** `Camera2D.h` + +## Description + +2D camera system for managing viewports and scrolling. + +## Methods + +### `void apply(Renderer& renderer) const` + +**Description:** + +Applies the camera transformation to a renderer. + +**Parameters:** + +- `renderer`: The renderer to apply the camera to. + +### `void setViewportSize(int width, int height)` + +**Description:** + +Sets the viewport size (usually logical resolution). + +**Parameters:** + +- `width`: Viewport width. +- `height`: Viewport height. diff --git a/api/generated/graphics/DisplayType.md b/api/generated/graphics/DisplayType.md new file mode 100644 index 0000000..7232e5f --- /dev/null +++ b/api/generated/graphics/DisplayType.md @@ -0,0 +1,78 @@ +# DisplayType + + + +**Source:** `DisplayConfig.h` + +## Description + +Identifies the type of display driver to use. + +## Methods + +### `static DisplayConfig createCustom(DrawSurface* surface, uint16_t w, uint16_t h, int rot = 0)` + +**Description:** + +Static factory to create a DisplayConfig with a custom DrawSurface. + +**Parameters:** + +- `surface`: Pointer to the custom DrawSurface implementation (ownership is transferred). +- `w`: Physical and logical width. +- `h`: Physical and logical height. +- `rot`: Rotation. + +### `bool needsScaling() const` + +**Description:** + +Checks if scaling is needed (logical != physical). + +**Returns:** true if the engine needs to scale output. + +### `float getScaleX() const` + +**Description:** + +Gets horizontal scaling factor. + +**Returns:** Scale factor (physical / logical). + +### `float getScaleY() const` + +**Description:** + +Gets vertical scaling factor. + +**Returns:** Scale factor (physical / logical). + +### `uint16_t width() const` + +**Description:** + +Deprecated, gets logical width. + +**Returns:** The logical width. + +### `uint16_t height() const` + +**Description:** + +Deprecated, gets logical height. + +**Returns:** The logical height. + +### `DrawSurface& getDrawSurface() const` + +**Description:** + +Gets the underlying DrawSurface implementation. + +**Returns:** Reference to the DrawSurface. + +### `void initDrawSurface()` + +**Description:** + +Initializes the underlying draw surface. diff --git a/api/generated/graphics/DrawSurface.md b/api/generated/graphics/DrawSurface.md new file mode 100644 index 0000000..ff7534f --- /dev/null +++ b/api/generated/graphics/DrawSurface.md @@ -0,0 +1,262 @@ +# DrawSurface + + + +**Source:** `DrawSurface.h` + +## Description + +Abstract interface for platform-specific drawing operations. + +This class defines the contract for any graphics driver (e.g., TFT_eSPI for ESP32, +SDL2 for Windows). It implements the Bridge pattern, allowing the Renderer to +remain platform-agnostic. + +## Methods + +### `virtual void init()` + +**Description:** + +Initializes the hardware or window. + +### `virtual void setRotation(uint16_t rotation)` + +**Description:** + +Sets the display rotation. + +**Parameters:** + +- `rotation`: Rotation value. Can be index (0-3) or degrees (0, 90, 180, 270). + +### `virtual void clearBuffer()` + +**Description:** + +Clears the frame buffer (fills with black or background color). + +### `virtual void sendBuffer()` + +**Description:** + +Sends the frame buffer to the physical display. + +### `virtual void drawFilledCircle(int x, int y, int radius, uint16_t color)` + +**Description:** + +Draws a filled circle. + +**Parameters:** + +- `x`: Center X coordinate. +- `y`: Center Y coordinate. +- `radius`: Radius of the circle. +- `color`: The fill color. + +### `virtual void drawCircle(int x, int y, int radius, uint16_t color)` + +**Description:** + +Draws a circle outline. + +**Parameters:** + +- `x`: Center X coordinate. +- `y`: Center Y coordinate. +- `radius`: Radius of the circle. +- `color`: The outline color. + +### `virtual void drawRectangle(int x, int y, int width, int height, uint16_t color)` + +**Description:** + +Draws a rectangle outline. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `width`: Width of the rectangle. +- `height`: Height of the rectangle. +- `color`: The outline color. + +### `virtual void drawFilledRectangle(int x, int y, int width, int height, uint16_t color)` + +**Description:** + +Draws a filled rectangle. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `width`: Width of the rectangle. +- `height`: Height of the rectangle. +- `color`: The fill color. + +### `virtual void drawLine(int x1, int y1, int x2, int y2, uint16_t color)` + +**Description:** + +Draws a line between two points. + +**Parameters:** + +- `x1`: Start X coordinate. +- `y1`: Start Y coordinate. +- `x2`: End X coordinate. +- `y2`: End Y coordinate. +- `color`: The line color. + +### `virtual void drawBitmap(int x, int y, int width, int height, const uint8_t *bitmap, uint16_t color)` + +**Description:** + +Draws a bitmap. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `width`: Width of the bitmap. +- `height`: Height of the bitmap. +- `bitmap`: Pointer to the bitmap data. +- `color`: The color to draw. + +### `virtual void drawPixel(int x, int y, uint16_t color)` + +**Description:** + +Draws a single pixel. + +**Parameters:** + +- `x`: The X coordinate. +- `y`: The Y coordinate. +- `color`: The pixel color. + +### `virtual void drawTileDirect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data)` + +**Description:** + +Direct tile write to sprite buffer (optimized for tilemap rendering). + +**Parameters:** + +- `x`: Tile X position in sprite coordinates +- `y`: Tile Y position in sprite coordinates +- `width`: Tile width in pixels +- `height`: Tile height in pixels +- `data`: Pointer to 8bpp tile data (one byte per pixel, index into palette) + +### `virtual uint8_t* getSpriteBuffer()` + +**Description:** + +Get pointer to sprite buffer for direct manipulation. + +**Returns:** Pointer to 8bpp sprite buffer, or nullptr if not supported + +### `virtual void setContrast(uint8_t level)` + +**Description:** + +Sets the display contrast/brightness. + +**Parameters:** + +- `level`: Contrast level (0-255). + +### `virtual void setTextColor(uint16_t color)` + +**Description:** + +Sets the text color. + +**Parameters:** + +- `color`: The text color. + +### `virtual void setTextSize(uint8_t size)` + +**Description:** + +Sets the text size. + +**Parameters:** + +- `size`: The text size multiplier. + +### `virtual void setCursor(int16_t x, int16_t y)` + +**Description:** + +Sets the cursor position for text drawing. + +**Parameters:** + +- `x`: The X coordinate. +- `y`: The Y coordinate. + +### `virtual uint16_t color565(uint8_t r, uint8_t g, uint8_t b)` + +**Description:** + +Converts RGB888 color to RGB565 format. + +**Parameters:** + +- `r`: Red component (0-255). +- `g`: Green component (0-255). +- `b`: Blue component (0-255). + +**Returns:** 16-bit color value. + +### `virtual void setDisplaySize(int w, int h)` + +**Description:** + +Sets the logical display size (rendering resolution). + +**Parameters:** + +- `w`: Width of the logical framebuffer. +- `h`: Height of the logical framebuffer. + +### `virtual void setPhysicalSize(int w, int h)` + +**Description:** + +Sets the physical display size (hardware resolution). + +**Parameters:** + +- `w`: Physical display width. +- `h`: Physical display height. + +### `virtual void setOffset(int x, int y)` + +**Description:** + +Sets the display offset (positioning of the active area). + +**Parameters:** + +- `x`: X offset. +- `y`: Y offset. + +### `virtual bool processEvents()` + +**Description:** + +Processes platform events (e.g., SDL window events). + +**Returns:** false if the application should quit, true otherwise. + +### `virtual void present()` + +**Description:** + +Swaps buffers (for double-buffered systems like SDL). diff --git a/api/generated/graphics/Font.md b/api/generated/graphics/Font.md new file mode 100644 index 0000000..494bcb5 --- /dev/null +++ b/api/generated/graphics/Font.md @@ -0,0 +1,44 @@ +# Font + + + +**Source:** `Font.h` + +## Description + +Descriptor for a bitmap font using 1bpp sprites. + +A Font contains an array of Sprite structures, one for each character +in the font's character set. Each glyph is rendered as a 1bpp sprite, +allowing consistent rendering across platforms. + +The font uses fixed-width glyphs for simplicity and performance. +All glyphs share the same width and height, with spacing between +characters controlled by the `spacing` field. + +Font data should be stored in flash memory (const/constexpr) + to minimize RAM usage on embedded systems. +Include "Renderer.h" when using Font in implementation files + to get the full Sprite definition. + +::: tip +Font data should be stored in flash memory (const/constexpr) + to minimize RAM usage on embedded systems. +::: + +::: tip +Include "Renderer.h" when using Font in implementation files + to get the full Sprite definition. +::: + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `Sprite` | `const` | Array of sprites, one per character (indexed by character code - firstChar) | +| `firstChar` | `uint8_t` | First character code in the font (e.g., 32 for space ' ') | +| `lastChar` | `uint8_t` | Last character code in the font (e.g., 126 for '~') | +| `glyphWidth` | `uint8_t` | Fixed width of each glyph in pixels | +| `glyphHeight` | `uint8_t` | Fixed height of each glyph in pixels | +| `spacing` | `uint8_t` | Horizontal spacing between characters in pixels | +| `lineHeight` | `uint8_t` | Total line height including vertical spacing (glyphHeight + vertical spacing) | diff --git a/api/generated/graphics/FontManager.md b/api/generated/graphics/FontManager.md new file mode 100644 index 0000000..1a73180 --- /dev/null +++ b/api/generated/graphics/FontManager.md @@ -0,0 +1,78 @@ +# FontManager + + + +**Source:** `FontManager.h` + +## Description + +Static utility class for managing bitmap fonts. + +FontManager provides functions to: +- Set and retrieve the default font +- Calculate text width for layout purposes +- Convert character codes to glyph indices + +The default font is used when no font is explicitly specified +in rendering calls. + +## Methods + +### `static void setDefaultFont(const Font* font)` + +**Description:** + +Sets the default font used for text rendering. + +**Parameters:** + +- `font`: Pointer to a Font structure. Must remain valid for the lifetime of its use. + Pass nullptr to clear the default font (not recommended). + +### `static const Font* getDefaultFont()` + +**Description:** + +Gets the current default font. + +**Returns:** Pointer to the default font, or nullptr if no font is set. + +### `static int16_t textWidth(const Font* font, const char* text, uint8_t size = 1)` + +**Description:** + +Calculates the width in pixels of a text string when rendered. + +**Parameters:** + +- `font`: Pointer to the font to use. If nullptr, uses the default font. +- `text`: The text string to measure. +- `size`: Text size multiplier (1 = normal, 2 = double size, etc.). + +**Returns:** Width in pixels, or 0 if font is invalid or text is empty. + +### `static uint8_t getGlyphIndex(char c, const Font* font = nullptr)` + +**Description:** + +Gets the glyph index for a character code. + +**Parameters:** + +- `c`: The character code. +- `font`: Pointer to the font to use. If nullptr, uses the default font. + +**Returns:** Glyph index (0-based) if character is in font range, or 255 if not found. + +### `static bool isCharSupported(char c, const Font* font = nullptr)` + +**Description:** + +Checks if a character is supported by a font. + +**Parameters:** + +- `c`: The character code. +- `font`: Pointer to the font to check. If nullptr, uses the default font. + +**Returns:** true if the character is in the font's range, false otherwise. diff --git a/api/generated/graphics/Particle.md b/api/generated/graphics/Particle.md new file mode 100644 index 0000000..3534b09 --- /dev/null +++ b/api/generated/graphics/Particle.md @@ -0,0 +1,24 @@ +# Particle + + + +**Source:** `Particle.h` + +## Description + +Represents a single particle in the particle system. + +Designed to be lightweight to fit many instances in memory (RAM optimization). + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `position` | `pixelroot32::math::Vector2` | Current position. | +| `velocity` | `pixelroot32::math::Vector2` | Velocity vector. | +| `color` | `uint16_t` | Current color (RGB565). | +| `startColor` | `Color` | Initial color for interpolation. | +| `endColor` | `Color` | Final color for interpolation. | +| `life` | `uint8_t` | Current remaining life (frames or ticks). | +| `maxLife` | `uint8_t` | Total life duration. | +| `active` | `bool` | Whether the particle is currently in use. | diff --git a/api/generated/graphics/ParticleConfig.md b/api/generated/graphics/ParticleConfig.md new file mode 100644 index 0000000..9b82cc5 --- /dev/null +++ b/api/generated/graphics/ParticleConfig.md @@ -0,0 +1,28 @@ +# ParticleConfig + + + +**Source:** `ParticleConfig.h` + +## Description + +Configuration parameters for a particle emitter. + +Defines the behavior and appearance of particles generated by an emitter. +Allows defining ranges for randomness (speed, life). + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `startColor` | `Color` | Color at the beginning of the particle's life. | +| `endColor` | `Color` | Color at the end of the particle's life. | +| `minSpeed` | `pixelroot32::math::Scalar` | Minimum initial speed. | +| `maxSpeed` | `pixelroot32::math::Scalar` | Maximum initial speed. | +| `gravity` | `pixelroot32::math::Scalar` | Gravity force applied to Y velocity each frame. | +| `friction` | `pixelroot32::math::Scalar` | Air resistance factor (0.0 - 1.0) applied to velocity. | +| `minLife` | `uint8_t` | Minimum lifetime in frames/ticks. | +| `maxLife` | `uint8_t` | Maximum lifetime in frames/ticks. | +| `fadeColor` | `bool` | If true, interpolates color from startColor to endColor. | +| `minAngleDeg` | `pixelroot32::math::Scalar` | Minimum emission angle in degrees (0 = right). | +| `maxAngleDeg` | `pixelroot32::math::Scalar` | Maximum emission angle in degrees. | diff --git a/api/generated/graphics/ParticleEmitter.md b/api/generated/graphics/ParticleEmitter.md new file mode 100644 index 0000000..baa5944 --- /dev/null +++ b/api/generated/graphics/ParticleEmitter.md @@ -0,0 +1,18 @@ +# ParticleEmitter + + + +**Source:** `ParticleEmitter.h` + +**Inherits from:** [Entity](../core/Entity.md) + +## Description + +Manages a pool of particles to create visual effects. + +Participates in the scene update/draw loop. +Uses a fixed-size array for particles to avoid dynamic allocation during runtime. + +## Inheritance + +[Entity](../core/Entity.md) → `ParticleEmitter` diff --git a/api/generated/graphics/Renderer.md b/api/generated/graphics/Renderer.md new file mode 100644 index 0000000..af1b309 --- /dev/null +++ b/api/generated/graphics/Renderer.md @@ -0,0 +1,403 @@ +# Renderer + + + +**Source:** `Renderer.h` + +## Description + +High-level graphics rendering system. + +The Renderer class provides a unified API for drawing shapes, text, and images. +It abstracts the underlying hardware implementation (DrawSurface) and manages +display configuration, including rotation and offsets. + +## Methods + +### `void init()` + +**Description:** + +Initializes the renderer and the underlying draw surface. + +### `void beginFrame()` + +**Description:** + +Prepares the buffer for a new frame (clears screen). + +### `void endFrame()` + +**Description:** + +Finalizes the frame and sends the buffer to the display. + +### `DrawSurface& getDrawSurface()` + +**Description:** + +Gets the underlying DrawSurface implementation. + +**Returns:** Reference to the DrawSurface. + +### `void drawFilledCircle(int x, int y, int radius, Color color)` + +**Description:** + +Draws a filled circle. + +**Parameters:** + +- `x`: Center X coordinate. +- `y`: Center Y coordinate. +- `radius`: Radius of the circle. +- `color`: Fill color. + +### `void drawCircle(int x, int y, int radius, Color color)` + +**Description:** + +Draws a circle outline. + +**Parameters:** + +- `x`: Center X coordinate. +- `y`: Center Y coordinate. +- `radius`: Radius of the circle. +- `color`: Outline color. + +### `void drawRectangle(int x, int y, int width, int height, Color color)` + +**Description:** + +Draws a rectangle outline. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `width`: Width of the rectangle. +- `height`: Height of the rectangle. +- `color`: Outline color. + +### `void drawFilledRectangle(int x, int y, int width, int height, Color color)` + +**Description:** + +Draws a filled rectangle. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `width`: Width of the rectangle. +- `height`: Height of the rectangle. +- `color`: Fill color. + +### `void drawFilledRectangleW(int x, int y, int width, int height, uint16_t color)` + +**Description:** + +Draws a filled rectangle with a 16-bit color. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `width`: Width of the rectangle. +- `height`: Height of the rectangle. +- `color`: Fill color (RGB565). + +### `void drawLine(int x1, int y1, int x2, int y2, Color color)` + +**Description:** + +Draws a line between two points. + +**Parameters:** + +- `x1`: Start X. +- `y1`: Start Y. +- `x2`: End X. +- `y2`: End Y. +- `color`: Line color. + +### `void drawBitmap(int x, int y, int width, int height, const uint8_t *bitmap, Color color)` + +**Description:** + +Draws a bitmap image. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `width`: Width of the bitmap. +- `height`: Height of the bitmap. +- `bitmap`: Pointer to the bitmap data. +- `color`: Color to draw the bitmap pixels (if monochrome) or ignored. + +### `void drawPixel(int x, int y, Color color)` + +**Description:** + +Draws a single pixel. + +**Parameters:** + +- `x`: X coordinate. +- `y`: Y coordinate. +- `color`: Pixel color. + +### `void setDisplaySize(int w, int h)` + +**Description:** + +Sets the logical display size (rendering resolution). + +**Parameters:** + +- `w`: Logical width. +- `h`: Logical height. + +### `int getLogicalWidth() const` + +### `int getLogicalHeight() const` + +### `void setDisplayOffset(int x, int y)` + +**Description:** + +Sets a global offset for all drawing operations. + +**Parameters:** + +- `x`: X offset. +- `y`: Y offset. + +### `void setContrast(uint8_t level)` + +**Description:** + +Sets the display contrast (brightness). + +**Parameters:** + +- `level`: Contrast level (0-255). + +### `void setFont(const uint8_t* font)` + +**Description:** + +Sets the font for text rendering. + +**Parameters:** + +- `font`: Pointer to the font data array. + +### `int getXOffset() const` + +**Description:** + +Gets the current global X offset. + +**Returns:** The X offset. + +### `int getYOffset() const` + +**Description:** + +Gets the current global Y offset. + +**Returns:** The Y offset. + +### `void setRenderContext(PaletteContext* context)` + +**Description:** + +Sets the render context for palette selection. + +**Parameters:** + +- `context`: The palette context to use (Background or Sprite). + Pass nullptr to use method-specific defaults. + +### `PaletteContext* getRenderContext() const` + +**Description:** + +Gets the current render context. + +**Returns:** Pointer to the current context, or nullptr if using defaults. + +### `void setSpritePaletteSlotContext(uint8_t slot)` + +**Description:** + +Sets the sprite palette slot context for multi-palette sprites. + +**Parameters:** + +- `slot`: Palette slot (0-7). To disable context, call with 0 or use default. + +### `uint8_t getSpritePaletteSlotContext() const` + +**Description:** + +Gets the current sprite palette slot context. + +**Returns:** Current palette slot, or 0xFF if context is inactive. + +### `void drawSprite(const Sprite& sprite, int x, int y, Color color, bool flipX = false)` + +**Description:** + +Draws a 1bpp monochrome sprite using the Sprite descriptor. + +**Parameters:** + +- `sprite`: Sprite descriptor (data, width, height). +- `x`: Top-left X coordinate in logical screen space. +- `y`: Top-left Y coordinate in logical screen space. +- `color`: Color used for "on" pixels. +- `flipX`: If true, sprite is mirrored horizontally. + +### `void drawSprite(const Sprite& sprite, int x, int y, float scaleX, float scaleY, Color color, bool flipX = false)` + +**Description:** + +Draws a scaled 1bpp monochrome sprite. + +**Parameters:** + +- `sprite`: Sprite descriptor. +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `scaleX`: Horizontal scaling factor (e.g., 1.25). +- `scaleY`: Vertical scaling factor (e.g., 1.25). +- `color`: Color used for "on" pixels. +- `flipX`: If true, sprite is mirrored horizontally before scaling. + +### `void drawSprite(const Sprite2bpp& sprite, int x, int y, uint8_t paletteSlot = 0, bool flipX = false)` + +**Description:** + +Draws a 2bpp sprite using a specific palette slot. + +**Parameters:** + +- `sprite`: The 2bpp sprite descriptor. +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `paletteSlot`: The palette slot to use. +- `flipX`: True to mirror horizontally. + +### `void drawSprite(const Sprite4bpp& sprite, int x, int y, uint8_t paletteSlot = 0, bool flipX = false)` + +**Description:** + +Draws a 4bpp sprite using a specific palette slot. + +**Parameters:** + +- `sprite`: The 4bpp sprite descriptor. +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `paletteSlot`: The palette slot to use. +- `flipX`: True to mirror horizontally. + +### `void drawSprite(const Sprite2bpp& sprite, int x, int y, bool flipX)` + +**Description:** + +Draws a 2bpp sprite (legacy). + +**Parameters:** + +- `sprite`: The 2bpp sprite descriptor. +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `flipX`: True to mirror horizontally. + +### `void drawSprite(const Sprite4bpp& sprite, int x, int y, bool flipX)` + +**Description:** + +Draws a 4bpp sprite (legacy). + +**Parameters:** + +- `sprite`: The 4bpp sprite descriptor. +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `flipX`: True to mirror horizontally. + +### `* Each layer is rendered in array order using the existing drawSprite()` + +### `void drawMultiSprite(const MultiSprite& sprite, int x, int y)` + +**Description:** + +Draws a multi-layer sprite composed of several 1bpp layers. + +**Parameters:** + +- `sprite`: Multi-layer sprite descriptor. +- `x`: Top-left X coordinate in logical screen space. +- `y`: Top-left Y coordinate in logical screen space. + +### `void drawMultiSprite(const MultiSprite& sprite, int x, int y, float scaleX, float scaleY)` + +**Description:** + +Draws a scaled multi-layer sprite. + +**Parameters:** + +- `sprite`: Multi-layer sprite descriptor. +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `scaleX`: Horizontal scaling factor. +- `scaleY`: Vertical scaling factor. + +### `void drawTileMap(const TileMap2bpp& map, int originX, int originY)` + +**Description:** + +Draws a tilemap of 2bpp sprites. + +**Parameters:** + +- `map`: The tilemap descriptor. +- `originX`: X coordinate of the top-left corner. +- `originY`: Y coordinate of the top-left corner. + +### `void drawTileMap(const TileMap4bpp& map, int originX, int originY)` + +**Description:** + +Draws a tilemap of 4bpp sprites. + +**Parameters:** + +- `map`: The tilemap descriptor. +- `originX`: X coordinate of the top-left corner. +- `originY`: Y coordinate of the top-left corner. + +### `void setOffsetBypass(bool bypass)` + +**Description:** + +Enables or disables ignoring global offsets for subsequent draw calls. + +**Parameters:** + +- `bypass`: True to ignore offsets, false to apply them (default). + +### `bool isOffsetBypassEnabled() const` + +**Description:** + +Checks if offset bypass is currently enabled. + +**Returns:** True if offsets are being ignored. diff --git a/api/generated/graphics/ScrollBehavior.md b/api/generated/graphics/ScrollBehavior.md new file mode 100644 index 0000000..8399b14 --- /dev/null +++ b/api/generated/graphics/ScrollBehavior.md @@ -0,0 +1,9 @@ +# ScrollBehavior + + + +**Source:** `UILayout.h` + +## Description + +Defines how scrolling behaves in layouts. diff --git a/api/generated/graphics/Sprite2bpp.md b/api/generated/graphics/Sprite2bpp.md new file mode 100644 index 0000000..3b93758 --- /dev/null +++ b/api/generated/graphics/Sprite2bpp.md @@ -0,0 +1,9 @@ +# Sprite2bpp + + + +**Source:** `Renderer.h` + +## Description + +Sprite descriptor for 2bpp (4-color) multi-color sprites. diff --git a/api/generated/graphics/Sprite4bpp.md b/api/generated/graphics/Sprite4bpp.md new file mode 100644 index 0000000..84e6ea8 --- /dev/null +++ b/api/generated/graphics/Sprite4bpp.md @@ -0,0 +1,9 @@ +# Sprite4bpp + + + +**Source:** `Renderer.h` + +## Description + +Sprite descriptor for 4bpp (16-color) multi-color sprites. diff --git a/api/generated/graphics/TileMapGeneric.md b/api/generated/graphics/TileMapGeneric.md new file mode 100644 index 0000000..c425c03 --- /dev/null +++ b/api/generated/graphics/TileMapGeneric.md @@ -0,0 +1,79 @@ +# TileMapGeneric + + + +**Source:** `Renderer.h` + +## Description + +Generic tilemap structure supporting 1bpp, 2bpp, or 4bpp tile graphics. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `runtimeMask` | `uint8_t*` | Bitmask for runtime tile activation (1 bit per tile, nullptr = all active) | +| `animManager` | `TileAnimationManager*` | Optional animation manager for tile animations | + +## Methods + +### `inline void initRuntimeMask()` + +**Description:** + +Initialize runtime mask buffer for tile activation control. + +::: tip +Must be called before using isTileActive() or setTileActive() +::: + +::: tip +Existing mask is freed if already allocated +::: + +### `inline bool isTileActive(int x, int y) const` + +**Description:** + +Check if a tile is currently active (visible). + +**Parameters:** + +- `x`: Tile X coordinate +- `y`: Tile Y coordinate + +**Returns:** true if tile is active, false if inactive + +::: tip +Returns true for out-of-bounds coordinates or when no mask is initialized +::: + +### `inline void setTileActive(int x, int y, bool active)` + +**Description:** + +Set tile activation state. + +**Parameters:** + +- `x`: Tile X coordinate +- `y`: Tile Y coordinate +- `active`: true to activate tile (visible), false to deactivate (hidden) + +::: tip +Out-of-bounds coordinates are ignored +::: + +### `inline uint8_t* getRuntimeMask() const` + +**Description:** + +Get pointer to runtime mask buffer. + +**Returns:** Pointer to runtime mask array, or nullptr if not initialized + +### `inline void cleanupRuntimeMask()` + +**Description:** + +Destructor cleanup for runtime mask. diff --git a/api/generated/graphics/TouchConfig.md b/api/generated/graphics/TouchConfig.md new file mode 100644 index 0000000..71249ad --- /dev/null +++ b/api/generated/graphics/TouchConfig.md @@ -0,0 +1,32 @@ +# TouchConfig + + + +**Source:** `TouchConfig.h` + +## Description + +Configuration for touch controller + +Add to DisplayConfig or use standalone. +Define one of TOUCH_DRIVER_XPT2046 or TOUCH_DRIVER_GT911 in build flags. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `controller` | `TouchController` | Active controller | + +## Methods + +### `static TouchConfig createXPT2046(uint8_t cs, uint8_t irq = 255)` + +**Description:** + +Constructor for XPT2046 + +### `static TouchConfig createGT911(uint8_t irq = 4)` + +**Description:** + +Constructor for GT911 diff --git a/api/generated/graphics/TouchController.md b/api/generated/graphics/TouchController.md new file mode 100644 index 0000000..8987102 --- /dev/null +++ b/api/generated/graphics/TouchController.md @@ -0,0 +1,9 @@ +# TouchController + + + +**Source:** `TouchConfig.h` + +## Description + +Supported touch controller types diff --git a/api/generated/graphics/UIAnchorLayout.md b/api/generated/graphics/UIAnchorLayout.md new file mode 100644 index 0000000..d393784 --- /dev/null +++ b/api/generated/graphics/UIAnchorLayout.md @@ -0,0 +1,43 @@ +# UIAnchorLayout + + + +**Source:** `UIAnchorLayout.h` + +**Inherits from:** [UILayout](./UILayout.md) + +## Description + +Layout that positions elements at fixed anchor points on the screen. + +This layout positions UI elements at fixed anchor points (corners, center, etc.) +without reflow. Very efficient for HUDs, debug UI, and fixed-position elements. +Positions are calculated once or when screen size changes. + +## Inheritance + +[UILayout](./UILayout.md) → `UIAnchorLayout` + +## Methods + +### `void addElement(UIElement* element, Anchor anchor)` + +**Description:** + +Adds a UI element with a specific anchor point. + +**Parameters:** + +- `element`: Pointer to the element to add. +- `anchor`: Anchor point for positioning. + +### `void setScreenSize(int screenWidth, int screenHeight)` + +**Description:** + +Sets the screen size for anchor calculations. + +**Parameters:** + +- `screenWidth`: Screen width in pixels. +- `screenHeight`: Screen height in pixels. diff --git a/api/generated/graphics/UIButton.md b/api/generated/graphics/UIButton.md new file mode 100644 index 0000000..e2f5e24 --- /dev/null +++ b/api/generated/graphics/UIButton.md @@ -0,0 +1,69 @@ +# UIButton + + + +**Source:** `UIButton.h` + +**Inherits from:** [UIElement](./UIElement.md) + +## Description + +A clickable button UI element. + +Supports both physical (keyboard/gamepad) and touch input. +Can trigger a callback function when pressed. + +## Inheritance + +[UIElement](./UIElement.md) → `UIButton` + +## Methods + +### `void setStyle(Color textCol, Color bgCol, bool drawBg)` + +**Description:** + +Configures the button's visual style. + +**Parameters:** + +- `textCol`: Color of the text. +- `bgCol`: Color of the background. +- `drawBg`: Whether to draw the background rectangle. + +### `void setSelected(bool selected)` + +**Description:** + +Sets the selection state (e.g., focused via D-pad). + +**Parameters:** + +- `selected`: True if selected. + +### `bool getSelected() const` + +**Description:** + +Checks if the button is currently selected. + +**Returns:** true if selected. + +### `void press()` + +**Description:** + +Manually triggers the button's action. + +### `bool isPointInside(int px, int py) const` + +**Description:** + +Internal helper to check if a point is inside the button's bounds. + +**Parameters:** + +- `px`: Point X coordinate. +- `py`: Point Y coordinate. + +**Returns:** true if point is inside. diff --git a/api/generated/graphics/UICheckBox.md b/api/generated/graphics/UICheckBox.md new file mode 100644 index 0000000..5fa7b2a --- /dev/null +++ b/api/generated/graphics/UICheckBox.md @@ -0,0 +1,87 @@ +# UICheckBox + + + +**Source:** `UICheckbox.h` + +**Inherits from:** [UIElement](./UIElement.md) + +## Description + +A clickable checkbox UI element. + +Supports both physical (keyboard/gamepad) and touch input. +Can trigger a callback function when its state changes. + +## Inheritance + +[UIElement](./UIElement.md) → `UICheckBox` + +## Methods + +### `void setStyle(Color textCol, Color bgCol, bool drawBg = false)` + +**Description:** + +Configures the checkbox's visual style. + +**Parameters:** + +- `textCol`: Color of the text. +- `bgCol`: Color of the background. +- `drawBg`: Whether to draw the background rectangle. + +### `void setChecked(bool checked)` + +**Description:** + +Sets the checked state. + +**Parameters:** + +- `checked`: True if checked. + +### `bool isChecked() const` + +**Description:** + +Checks if the checkbox is currently checked. + +**Returns:** true if checked. + +### `void setSelected(bool selected)` + +**Description:** + +Sets the selection state (e.g., focused via D-pad). + +**Parameters:** + +- `selected`: True if selected. + +### `bool getSelected() const` + +**Description:** + +Checks if the checkbox is currently selected. + +**Returns:** true if selected. + +### `void toggle()` + +**Description:** + +Toggles the checkbox state. + +### `bool isPointInside(int px, int py) const` + +**Description:** + +Internal helper to check if a point is inside the checkbox's bounds. + +**Parameters:** + +- `px`: Point X coordinate. +- `py`: Point Y coordinate. + +**Returns:** true if point is inside. diff --git a/api/generated/graphics/UIElement.md b/api/generated/graphics/UIElement.md new file mode 100644 index 0000000..c426620 --- /dev/null +++ b/api/generated/graphics/UIElement.md @@ -0,0 +1,54 @@ +# UIElement + + + +**Source:** `UIElement.h` + +**Inherits from:** [Entity](../core/Entity.md) + +## Description + +Base class for all user interface elements (buttons, labels, etc.). + +Integrates with the scene graph and sets EntityType to UI_ELEMENT. + +## Inheritance + +[Entity](../core/Entity.md) → `UIElement` + +## Methods + +### `UIElementType getType() const` + +**Description:** + +Gets the type of the UI element. + +**Returns:** The UIElementType. + +### `virtual bool isFocusable() const` + +**Description:** + +Checks if the element is focusable/selectable. +Use this for navigation logic. + +**Returns:** true if focusable, false otherwise. + +### `void setFixedPosition(bool fixed)` + +**Description:** + +Sets whether the element is in a fixed position (HUD/Overlay). + +**Parameters:** + +- `fixed`: True to enable fixed position. + +### `bool isFixedPosition() const` + +**Description:** + +Checks if the element is in a fixed position. + +**Returns:** True if fixed position is enabled. diff --git a/api/generated/graphics/UIGridLayout.md b/api/generated/graphics/UIGridLayout.md new file mode 100644 index 0000000..e5c3b3a --- /dev/null +++ b/api/generated/graphics/UIGridLayout.md @@ -0,0 +1,85 @@ +# UIGridLayout + + + +**Source:** `UIGridLayout.h` + +**Inherits from:** [UILayout](./UILayout.md) + +## Description + +Grid layout container for organizing elements in a matrix. + +Organizes UI elements in a fixed grid of rows and columns. Supports +navigation in 4 directions (UP/DOWN/LEFT/RIGHT) and automatic +positioning based on grid coordinates. + +## Inheritance + +[UILayout](./UILayout.md) → `UIGridLayout` + +## Methods + +### `void setColumns(uint8_t cols)` + +**Description:** + +Sets the number of columns in the grid. + +**Parameters:** + +- `cols`: Number of columns (must be > 0). + +### `uint8_t getColumns() const` + +**Description:** + +Gets the number of columns. + +**Returns:** Number of columns. + +### `uint8_t getRows() const` + +**Description:** + +Gets the number of rows (calculated). + +**Returns:** Number of rows. + +### `int getSelectedIndex() const` + +**Description:** + +Gets the currently selected element index. + +**Returns:** Selected index, or -1 if none selected. + +### `void setSelectedIndex(int index)` + +**Description:** + +Sets the selected element index. + +**Parameters:** + +- `index`: Index to select (-1 to deselect). + +### `UIElement* getSelectedElement() const` + +**Description:** + +Gets the selected element. + +**Returns:** Pointer to selected element, or nullptr if none selected. + +### `void calculateRows()` + +**Description:** + +Calculates the number of rows based on element count and columns. + +### `void calculateCellDimensions()` + +**Description:** + +Calculates cell dimensions based on layout size and grid configuration. diff --git a/api/generated/graphics/UIHitTest.md b/api/generated/graphics/UIHitTest.md new file mode 100644 index 0000000..7400781 --- /dev/null +++ b/api/generated/graphics/UIHitTest.md @@ -0,0 +1,105 @@ +# UIHitTest + + + +**Source:** `UIHitTest.h` + +## Description + +AABB hit testing for touch UI widgets + +Provides hit testing for touch widgets. Iterates through all widgets +in reverse order (top-most first) to find the first hit. +Supports both UITouchWidget* and UITouchElement* (Entity) arrays. + +## Methods + +### `static bool hitTest(const UITouchWidget& widget, int16_t px, int16_t py)` + +**Description:** + +Check if a point hits a single widget (AABB) + +**Parameters:** + +- `widget`: The widget to test +- `px`: X coordinate +- `py`: Y coordinate + +**Returns:** true if point is inside widget bounds + +### `static bool hitTest(const UITouchElement& element, int16_t px, int16_t py)` + +**Description:** + +Check if a point hits a UITouchElement (Entity) + +**Parameters:** + +- `element`: The element to test +- `px`: X coordinate +- `py`: Y coordinate + +**Returns:** true if point is inside element bounds + +### `static UITouchWidget* findHit(UITouchWidget* widgets[], uint8_t count, int16_t px, int16_t py)` + +**Description:** + +Find the top-most widget that contains the point + +**Parameters:** + +- `widgets`: Array of widgets to search +- `count`: Number of widgets in array +- `px`: X coordinate +- `py`: Y coordinate + +**Returns:** Pointer to hit widget, or nullptr if no hit + +Searches in reverse order (last widget = top-most) for O(1) best case + +### `static UITouchElement* findHit(UITouchElement* elements[], uint8_t count, int16_t px, int16_t py)` + +**Description:** + +Find the top-most element that contains the point (UITouchElement version) + +**Parameters:** + +- `elements`: Array of elements to search +- `count`: Number of elements in array +- `px`: X coordinate +- `py`: Y coordinate + +**Returns:** Pointer to hit element, or nullptr if no hit + +### `static const UITouchWidget* findHit(const UITouchWidget* widgets[], uint8_t count, int16_t px, int16_t py)` + +**Description:** + +Find the top-most widget that contains the point (const version) + +**Parameters:** + +- `widgets`: Array of widgets to search +- `count`: Number of widgets in array +- `px`: X coordinate +- `py`: Y coordinate + +**Returns:** Pointer to hit widget, or nullptr if no hit + +### `static const UITouchElement* findHit(const UITouchElement* elements[], uint8_t count, int16_t px, int16_t py)` + +**Description:** + +Find the top-most element that contains the point (const UITouchElement version) + +**Parameters:** + +- `elements`: Array of elements to search +- `count`: Number of elements in array +- `px`: X coordinate +- `py`: Y coordinate + +**Returns:** Pointer to hit element, or nullptr if no hit diff --git a/api/generated/graphics/UIHorizontalLayout.md b/api/generated/graphics/UIHorizontalLayout.md new file mode 100644 index 0000000..103dee8 --- /dev/null +++ b/api/generated/graphics/UIHorizontalLayout.md @@ -0,0 +1,102 @@ +# UIHorizontalLayout + + + +**Source:** `UIHorizontalLayout.h` + +**Inherits from:** [UILayout](./UILayout.md) + +## Description + +Horizontal layout container with scroll support. + +Organizes UI elements horizontally, one next to another. Supports scrolling +when content exceeds the visible viewport. Handles keyboard/D-pad +navigation automatically. + +## Inheritance + +[UILayout](./UILayout.md) → `UIHorizontalLayout` + +## Methods + +### `void setScrollEnabled(bool enable)` + +**Description:** + +Enables or disables scrolling. + +**Parameters:** + +- `enable`: True to enable scrolling. + +### `void enableScroll(bool enable)` + +**Description:** + +Enables or disables scrolling (alias for setScrollEnabled). + +**Parameters:** + +- `enable`: True to enable scrolling. + +### `int getSelectedIndex() const` + +**Description:** + +Gets the currently selected element index. + +**Returns:** Selected index, or -1 if none selected. + +### `void setSelectedIndex(int index)` + +**Description:** + +Sets the selected element index. + +**Parameters:** + +- `index`: Index to select (-1 to deselect). + +### `UIElement* getSelectedElement() const` + +**Description:** + +Gets the selected element. + +**Returns:** Pointer to selected element, or nullptr if none selected. + +### `void setNavigationButtons(uint8_t leftButton, uint8_t rightButton)` + +**Description:** + +Sets the navigation button indices. + +**Parameters:** + +- `leftButton`: Button index for LEFT navigation. +- `rightButton`: Button index for RIGHT navigation. + +### `void calculateContentWidth()` + +**Description:** + +Calculates the total content width. + +### `void updateElementVisibility()` + +**Description:** + +Updates element visibility based on scroll position. + +### `void ensureSelectedVisible()` + +**Description:** + +Ensures the selected element is visible by adjusting scroll. + +### `void clampScrollOffset()` + +**Description:** + +Clamps scroll offset to valid range. diff --git a/api/generated/graphics/UILabel.md b/api/generated/graphics/UILabel.md new file mode 100644 index 0000000..f32146b --- /dev/null +++ b/api/generated/graphics/UILabel.md @@ -0,0 +1,45 @@ +# UILabel + + + +**Source:** `UILabel.h` + +**Inherits from:** [UIElement](./UIElement.md) + +## Description + +A simple text label UI element. + +Displays a string of text on the screen. Auto-calculates its bounds based on text length and size. + +## Inheritance + +[UIElement](./UIElement.md) → `UILabel` + +## Methods + +### `void setVisible(bool v)` + +**Description:** + +Sets visibility. + +**Parameters:** + +- `v`: True to show, false to hide. + +### `void centerX(int screenWidth)` + +**Description:** + +Centers the label horizontally on the screen. + +**Parameters:** + +- `screenWidth`: Width of the screen/container. + +### `void recalcSize()` + +**Description:** + +Recalculates width and height based on current text and font size. diff --git a/api/generated/graphics/UILayout.md b/api/generated/graphics/UILayout.md new file mode 100644 index 0000000..4ef2315 --- /dev/null +++ b/api/generated/graphics/UILayout.md @@ -0,0 +1,92 @@ +# UILayout + + + +**Source:** `UILayout.h` + +**Inherits from:** [UIElement](./UIElement.md) + +## Description + +Base class for UI layout containers. + +Layouts organize UI elements automatically, handling positioning, +spacing, and optional scrolling. Layouts are themselves UI elements +that can be added to scenes. + +## Inheritance + +[UIElement](./UIElement.md) → `UILayout` + +## Methods + +### `virtual void addElement(UIElement* element)` + +**Description:** + +Adds a UI element to the layout. + +**Parameters:** + +- `element`: Pointer to the element to add. + +### `virtual void removeElement(UIElement* element)` + +**Description:** + +Removes a UI element from the layout. + +**Parameters:** + +- `element`: Pointer to the element to remove. + +### `virtual void updateLayout()` + +**Description:** + +Recalculates positions of all elements in the layout. +Should be called automatically when elements are added/removed. + +### `size_t getElementCount() const` + +**Description:** + +Gets the number of elements in the layout. + +**Returns:** Element count. + +### `UIElement* getElement(size_t index) const` + +**Description:** + +Gets the element at a specific index. + +**Parameters:** + +- `index`: Element index. + +**Returns:** Pointer to the element, or nullptr if index is invalid. + +### `void clearElements()` + +**Description:** + +Clears all elements from the layout. + +### `void setScrollingEnabled(bool enabled)` + +**Description:** + +Enables or disables scrolling for this layout. + +**Parameters:** + +- `enabled`: True to enable scrolling, false to disable. + +### `bool isScrollingEnabled() const` + +**Description:** + +Checks if scrolling is enabled. + +**Returns:** True if scrolling is enabled. diff --git a/api/generated/graphics/UIManager.md b/api/generated/graphics/UIManager.md new file mode 100644 index 0000000..b00e300 --- /dev/null +++ b/api/generated/graphics/UIManager.md @@ -0,0 +1,85 @@ +# UIManager + + + +**Source:** `UIManager.h` + +## Description + +Registry of touch UI elements for event routing (non-owning pointers). + +The scene (or another owner) constructs widgets and registers them with addElement. +clear/removeElement only unregister pointers — they never destroy objects. + +Lifetime Contract + +UIManager holds NON-OWNING pointers to UITouchElement instances. The widget +lifetime is managed exclusively by the caller (Scene, game code, arena allocator). + +Ownership Rules +- UIManager does NOT delete widgets +- Widgets must call removeElement() BEFORE being destroyed +- Failure to unregister results in dangling pointers and potential crashes + +Safe Destruction Sequence +// CORRECTO: Desregistrar antes de destruir +uiManager.removeElement(myButton.get()); +myButton.reset(); // or delete myButton; + +// INCORRECTO: Destruir sin desregistrar +myButton.reset(); // Widget destruido +// UIManager::capturedWidget o hoverWidget ahora son dangling! +Captured Widget Safety +UIManager automatically clears capturedWidget when removeElement() is called. +However, if a widget is deleted directly without removeElement(), the caller +MUST call uiManager.releaseCapture() to avoid use-after-free. + +## Methods + +### `bool addElement(UITouchElement* element)` + +**Description:** + +Register an element for touch hit-testing and processEvents. + +**Parameters:** + +- `element`: Non-null; must outlive registration (or until remove/clear). + +**Returns:** false if full, duplicate pointer, or element is null + +### `bool removeElement(uint8_t id)` + +### `bool removeElement(UITouchWidget* widget)` + +### `UITouchElement* getElement(uint8_t id) const` + +### `UITouchElement* getElementAt(uint8_t index) const` + +### `uint8_t getElementCount() const` + +### `uint8_t getMaxElements() const` + +### `bool isFull() const` + +### `void clear()` + +### `UITouchWidget* getActiveWidget() const` + +### `UITouchWidget* getHoverWidget() const` + +### `UITouchElement** getElements()` + +### `UITouchElement* const* getElements() const` + +### `void updateHover(int16_t x, int16_t y)` + +### `void clearConsumeFlags()` + +### `UITouchWidget* getCapturedWidget() const` + +### `void releaseCapture()` + +### `void update(unsigned long deltaTime)` + +### `int8_t findFreeSlot() const` diff --git a/api/generated/graphics/UIPaddingContainer.md b/api/generated/graphics/UIPaddingContainer.md new file mode 100644 index 0000000..c60305d --- /dev/null +++ b/api/generated/graphics/UIPaddingContainer.md @@ -0,0 +1,45 @@ +# UIPaddingContainer + + + +**Source:** `UIPaddingContainer.h` + +**Inherits from:** [UIElement](./UIElement.md) + +## Description + +Container that wraps a single UI element and applies padding. + +This container adds padding/margin around a single child element without +organizing multiple elements. Useful for adding spacing to individual +elements or nesting layouts with custom padding. + +## Inheritance + +[UIElement](./UIElement.md) → `UIPaddingContainer` + +## Methods + +### `void setChild(UIElement* element)` + +**Description:** + +Sets the child element. + +**Parameters:** + +- `element`: Pointer to the UI element to wrap. + +### `UIElement* getChild() const` + +**Description:** + +Gets the child element. + +**Returns:** Pointer to the child element, or nullptr if none set. + +### `void updateChildPosition()` + +**Description:** + +Updates the child element's position based on padding. diff --git a/api/generated/graphics/UIPanel.md b/api/generated/graphics/UIPanel.md new file mode 100644 index 0000000..f4d7e77 --- /dev/null +++ b/api/generated/graphics/UIPanel.md @@ -0,0 +1,63 @@ +# UIPanel + + + +**Source:** `UIPanel.h` + +**Inherits from:** [UIElement](./UIElement.md) + +## Description + +Visual container that draws a background and border around a child element. + +This container provides a retro-style window/panel appearance with a background +color and border. Typically contains a UILayout or other UI elements. Useful for +dialogs, menus, and information panels. + +## Inheritance + +[UIElement](./UIElement.md) → `UIPanel` + +## Methods + +### `void setChild(UIElement* element)` + +**Description:** + +Sets the child element. + +**Parameters:** + +- `element`: Pointer to the UI element to wrap (typically a UILayout). + +### `UIElement* getChild() const` + +**Description:** + +Gets the child element. + +**Returns:** Pointer to the child element, or nullptr if none set. + +### `void setBorderWidth(uint8_t width)` + +**Description:** + +Sets the border width. + +**Parameters:** + +- `width`: Border width in pixels. + +### `uint8_t getBorderWidth() const` + +**Description:** + +Gets the border width. + +**Returns:** Border width in pixels. + +### `void updateChildPosition()` + +**Description:** + +Updates the child element's position to match the panel. diff --git a/api/generated/graphics/UITouchButton.md b/api/generated/graphics/UITouchButton.md new file mode 100644 index 0000000..6f41729 --- /dev/null +++ b/api/generated/graphics/UITouchButton.md @@ -0,0 +1,198 @@ +# UITouchButton + + + +**Source:** `UITouchButton.h` + +**Inherits from:** [UITouchElement](./UITouchElement.md) + +## Description + +Touch-optimized button widget. + +Provides touch input support and the Entity update/draw interface. +Construct with position/size; register with UIManager::addElement for touch routing. +States: Idle, Pressed, Hover +Events: OnDown, OnUp, OnClick + +## Inheritance + +[UITouchElement](./UITouchElement.md) → `UITouchButton` + +## Methods + +### `void setColors(Color normal, Color pressed, Color disabled)` + +**Description:** + +Set button colors + +**Parameters:** + +- `normal`: Color for normal state +- `pressed`: Color for pressed state +- `disabled`: Color for disabled state + +### `Color getNormalColor() const` + +**Description:** + +Get normal color + +**Returns:** Normal state color + +### `Color getPressedColor() const` + +**Description:** + +Get pressed color + +**Returns:** Pressed state color + +### `Color getDisabledColor() const` + +**Description:** + +Get disabled color + +**Returns:** Disabled state color + +### `Color getBorderColor() const` + +**Description:** + +Get border color + +**Returns:** Border color + +### `Color getDisabledBorderColor() const` + +**Description:** + +Get disabled border color + +**Returns:** Disabled border color + +### `void setFontSize(int size)` + +**Description:** + +Set font size for text rendering + +**Parameters:** + +- `size`: Font size (pixels/8, typically 1-4) + +### `int getFontSize() const` + +**Description:** + +Get current font size + +**Returns:** Font size + +### `void setTextAlignment(TextAlignment align)` + +**Description:** + +Set text alignment + +**Parameters:** + +- `align`: Text alignment (LEFT, CENTER, RIGHT) + +### `TextAlignment getTextAlignment() const` + +**Description:** + +Get current text alignment + +**Returns:** Text alignment + +### `void setOnDown(UIElementVoidCallback callback)` + +**Description:** + +Set the OnDown callback + +**Parameters:** + +- `callback`: Function to call when touch goes down + +### `void setOnUp(UIElementVoidCallback callback)` + +**Description:** + +Set the OnUp callback + +**Parameters:** + +- `callback`: Function to call when touch goes up + +### `void setOnClick(UIElementVoidCallback callback)` + +**Description:** + +Set the OnClick callback + +**Parameters:** + +- `callback`: Function to call when button is clicked + +### `UIElementVoidCallback getOnDown() const` + +**Description:** + +Get the OnDown callback + +**Returns:** The current OnDown callback + +### `UIElementVoidCallback getOnUp() const` + +**Description:** + +Get the OnUp callback + +**Returns:** The current OnUp callback + +### `UIElementVoidCallback getOnClick() const` + +**Description:** + +Get the OnClick callback + +**Returns:** The current OnClick callback + +### `void reset()` + +**Description:** + +Reset button state + +### `void autoSize(uint8_t padding = 4)` + +**Description:** + +Auto-size button width to fit the current label + +**Parameters:** + +- `padding`: Extra pixels to add around text (default: 4) + +### `void setActive()` + +**Description:** + +Set active flag + +### `void clearActive()` + +**Description:** + +Clear active flag + +### `Color getCurrentColor() const` + +**Description:** + +Get color based on current state diff --git a/api/generated/graphics/UITouchCheckbox.md b/api/generated/graphics/UITouchCheckbox.md new file mode 100644 index 0000000..6addc0a --- /dev/null +++ b/api/generated/graphics/UITouchCheckbox.md @@ -0,0 +1,158 @@ +# UITouchCheckbox + + + +**Source:** `UITouchCheckbox.h` + +**Inherits from:** [UITouchElement](./UITouchElement.md) + +## Description + +Touch-optimized checkbox widget. + +Provides touch input support and the Entity update/draw interface. +Construct with position/size; register with UIManager::addElement for touch routing. +States: Idle, Pressed (transient), checked/unchecked +Events: OnChanged (when checked state changes) + +## Inheritance + +[UITouchElement](./UITouchElement.md) → `UITouchCheckbox` + +## Methods + +### `void setFontSize(int size)` + +**Description:** + +Set font size for text rendering + +**Parameters:** + +- `size`: Font size multiplier + +### `int getFontSize() const` + +**Description:** + +Get current font size + +**Returns:** Font size multiplier + +### `void setChecked(bool checked)` + +**Description:** + +Set the checked state + +**Parameters:** + +- `checked`: True to check, false to uncheck + +### `bool isChecked() const` + +**Description:** + +Get the current checked state + +**Returns:** True if checked + +### `void toggle()` + +**Description:** + +Toggle the checked state + +### `void setColors(Color normal, Color checked, Color disabled)` + +**Description:** + +Set checkbox colors + +**Parameters:** + +- `normal`: Color for normal/unchecked state +- `checked`: Color for checked state +- `disabled`: Color for disabled state + +### `Color getNormalColor() const` + +**Description:** + +Get normal color + +**Returns:** Normal state color + +### `Color getCheckedColor() const` + +**Description:** + +Get checked color + +**Returns:** Checked state color + +### `Color getDisabledColor() const` + +**Description:** + +Get disabled color + +**Returns:** Disabled state color + +### `Color getBorderColor() const` + +**Description:** + +Get border color + +**Returns:** Border color + +### `Color getDisabledBorderColor() const` + +**Description:** + +Get disabled border color + +**Returns:** Disabled border color + +### `void setOnChanged(UIElementBoolCallback callback)` + +**Description:** + +Set the OnChanged callback + +**Parameters:** + +- `callback`: Function to call when checked state changes + +### `UIElementBoolCallback getOnChanged() const` + +**Description:** + +Get the OnChanged callback + +**Returns:** The current OnChanged callback + +### `void reset()` + +**Description:** + +Reset checkbox state + +### `void setActive()` + +**Description:** + +Set active flag (visual pressed state) + +### `void clearActive()` + +**Description:** + +Clear active flag + +### `Color getCurrentColor() const` + +**Description:** + +Get color based on current state diff --git a/api/generated/graphics/UITouchElement.md b/api/generated/graphics/UITouchElement.md new file mode 100644 index 0000000..3e1d52e --- /dev/null +++ b/api/generated/graphics/UITouchElement.md @@ -0,0 +1,123 @@ +# UITouchElement + + + +**Source:** `UITouchElement.h` + +**Inherits from:** [UIElement](./UIElement.md) + +## Description + +UIElement with embedded UITouchWidget data for touch interaction. + +Embeds widget data directly (x, y, width, height, flags, state) +to avoid memory corruption from overlapping placement new. +Integrates with the UILayout system. + +Memory: Owns widget data inline - no external allocation needed. + +## Inheritance + +[UIElement](./UIElement.md) → `UITouchElement` + +## Methods + +### `virtual uint8_t getWidgetState() const` + +**Description:** + +Get widget state + +**Returns:** Current UIWidgetState + +### `virtual bool isPressed() const` + +**Description:** + +Check if widget is currently pressed + +**Returns:** true if state is Pressed + +### `virtual bool isEnabled() const` + +**Description:** + +Check if widget is enabled + +**Returns:** true if widget is enabled + +### `virtual bool isVisible() const` + +**Description:** + +Check if widget is visible + +**Returns:** true if widget is visible + +### `void setWidgetVisible(bool visible)` + +**Description:** + +Set widget visibility + +**Parameters:** + +- `visible`: True to make visible + +### `void setWidgetEnabled(bool enabled)` + +**Description:** + +Set widget enabled state + +**Parameters:** + +- `enabled`: True to enable + +### `int16_t getX() const` + +**Description:** + +Get widget x position + +**Returns:** X position + +### `int16_t getY() const` + +**Description:** + +Get widget y position + +**Returns:** Y position + +### `uint16_t getWidgetWidth() const` + +**Description:** + +Get widget width + +**Returns:** Width + +### `uint16_t getWidgetHeight() const` + +**Description:** + +Get widget height + +**Returns:** Height + +### `UITouchWidget& getWidgetData()` + +**Description:** + +Get reference to embedded widget data + +**Returns:** Reference to UITouchWidget data + +### `const UITouchWidget& getWidgetData() const` + +**Description:** + +Get const reference to embedded widget data + +**Returns:** Const reference to UITouchWidget data diff --git a/api/generated/graphics/UITouchSlider.md b/api/generated/graphics/UITouchSlider.md new file mode 100644 index 0000000..abadd47 --- /dev/null +++ b/api/generated/graphics/UITouchSlider.md @@ -0,0 +1,188 @@ +# UITouchSlider + + + +**Source:** `UITouchSlider.h` + +**Inherits from:** [UITouchElement](./UITouchElement.md) + +## Description + +Touch-optimized slider widget. + +Provides touch input support and the Entity update/draw interface. +Construct with position/size; register with UIManager::addElement for touch routing. +Value range: 0-100 +States: Idle, Dragging +Events: OnValueChanged, OnDragStart, OnDragEnd + +## Inheritance + +[UITouchElement](./UITouchElement.md) → `UITouchSlider` + +## Methods + +### `explicit UITouchSlider(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t initialValue)` + +**Description:** + +Construct a new UITouchSlider + +**Parameters:** + +- `x`: X position +- `y`: Y position +- `w`: Width +- `h`: Height +- `initialValue`: Initial value (0-100) + +### `void setColors(Color track, Color thumb)` + +**Description:** + +Set track and thumb colors + +**Parameters:** + +- `track`: Color for track +- `thumb`: Color for thumb + +### `void setOnValueChanged(SliderCallback callback)` + +**Description:** + +Set the OnValueChanged callback + +**Parameters:** + +- `callback`: Function to call when value changes (receives new value) + +### `void setOnDragStart(SliderCallback callback)` + +**Description:** + +Set the OnDragStart callback + +**Parameters:** + +- `callback`: Function to call when drag starts + +### `void setOnDragEnd(SliderCallback callback)` + +**Description:** + +Set the OnDragEnd callback + +**Parameters:** + +- `callback`: Function to call when drag ends + +### `SliderCallback getOnValueChanged() const` + +**Description:** + +Get the OnValueChanged callback + +**Returns:** The current OnValueChanged callback + +### `SliderCallback getOnDragStart() const` + +**Description:** + +Get the OnDragStart callback + +**Returns:** The current OnDragStart callback + +### `SliderCallback getOnDragEnd() const` + +**Description:** + +Get the OnDragEnd callback + +**Returns:** The current OnDragEnd callback + +### `Color getTrackColor() const` + +**Description:** + +Get track color + +**Returns:** Current track color + +### `Color getThumbColor() const` + +**Description:** + +Get thumb color + +**Returns:** Current thumb color + +### `Color getDisabledColor() const` + +**Description:** + +Get disabled color + +**Returns:** Current disabled color + +### `uint8_t getValue() const` + +**Description:** + +Get the current value + +**Returns:** Current value (0-100) + +### `void setValue(uint8_t newValue)` + +**Description:** + +Set the value + +**Parameters:** + +- `newValue`: New value (0-100) + +### `uint8_t getPreviousValue() const` + +**Description:** + +Get the previous value + +**Returns:** Previous value (0-100) + +### `bool hasValueChanged() const` + +**Description:** + +Check if value changed since last frame + +**Returns:** true if value changed + +### `void reset()` + +**Description:** + +Reset slider state + +### `void updateValueFromPosition(int16_t xPos)` + +**Description:** + +Update value based on X position + +**Parameters:** + +- `xPos`: X position + +### `void setActive()` + +**Description:** + +Set active flag + +### `void clearActive()` + +**Description:** + +Clear active flag diff --git a/api/generated/graphics/UITouchWidget.md b/api/generated/graphics/UITouchWidget.md new file mode 100644 index 0000000..075e7e1 --- /dev/null +++ b/api/generated/graphics/UITouchWidget.md @@ -0,0 +1,114 @@ +# UITouchWidget + + + +**Source:** `UITouchWidget.h` + +## Description + +Base touch widget structure + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `type` | `UIWidgetType` | Widget type | +| `state` | `UIWidgetState` | Current state | +| `flags` | `UIWidgetFlags` | Widget flags | +| `id` | `uint8_t` | Unique widget ID | +| `x` | `int16_t` | X position (top-left) | +| `y` | `int16_t` | Y position (top-left) | +| `width` | `uint16_t` | Widget width | +| `height` | `uint16_t` | Widget height | + +## Methods + +### `, id(0)` + +### `, x(0)` + +### `, y(0)` + +### `, width(0)` + +### `, height(0)` + +### `, id(widgetId)` + +### `, x(xPos)` + +### `, y(yPos)` + +### `, width(w)` + +### `, height(h)` + +### `bool isEnabled() const` + +**Description:** + +Check if widget is enabled + +**Returns:** true if Enabled flag is set + +### `bool isVisible() const` + +**Description:** + +Check if widget is visible + +**Returns:** true if Visible flag is set + +### `bool isActive() const` + +**Description:** + +Check if widget is active (being interacted with) + +**Returns:** true if Active flag is set + +### `bool isConsumed() const` + +**Description:** + +Check if event was consumed + +**Returns:** true if Consumed flag is set + +### `void setEnabled(bool enabled)` + +**Description:** + +Enable or disable the widget + +**Parameters:** + +- `enabled`: True to enable + +### `void setVisible(bool visible)` + +**Description:** + +Show or hide the widget + +**Parameters:** + +- `visible`: True to show + +### `void consume()` + +**Description:** + +Mark event as consumed + +### `void clearConsume()` + +**Description:** + +Clear consumed flag for next frame + +### `bool contains(int16_t px, int16_t py) const` + +**Description:** + +Check if point is inside widget bounds (AABB) diff --git a/api/generated/graphics/UIVerticalLayout.md b/api/generated/graphics/UIVerticalLayout.md new file mode 100644 index 0000000..2862169 --- /dev/null +++ b/api/generated/graphics/UIVerticalLayout.md @@ -0,0 +1,102 @@ +# UIVerticalLayout + + + +**Source:** `UIVerticalLayout.h` + +**Inherits from:** [UILayout](./UILayout.md) + +## Description + +Vertical layout container with scroll support. + +Organizes UI elements vertically, one below another. Supports scrolling +when content exceeds the visible viewport. Handles keyboard/D-pad +navigation automatically. + +## Inheritance + +[UILayout](./UILayout.md) → `UIVerticalLayout` + +## Methods + +### `void setScrollEnabled(bool enable)` + +**Description:** + +Enables or disables scrolling. + +**Parameters:** + +- `enable`: True to enable scrolling. + +### `void enableScroll(bool enable)` + +**Description:** + +Enables or disables scrolling (alias for setScrollEnabled). + +**Parameters:** + +- `enable`: True to enable scrolling. + +### `int getSelectedIndex() const` + +**Description:** + +Gets the currently selected element index. + +**Returns:** Selected index, or -1 if none selected. + +### `void setSelectedIndex(int index)` + +**Description:** + +Sets the selected element index. + +**Parameters:** + +- `index`: Index to select (-1 to deselect). + +### `UIElement* getSelectedElement() const` + +**Description:** + +Gets the selected element. + +**Returns:** Pointer to selected element, or nullptr if none selected. + +### `void setNavigationButtons(uint8_t upButton, uint8_t downButton)` + +**Description:** + +Sets the navigation button indices. + +**Parameters:** + +- `upButton`: Button index for UP navigation. +- `downButton`: Button index for DOWN navigation. + +### `void calculateContentHeight()` + +**Description:** + +Calculates the total content height. + +### `void updateElementVisibility()` + +**Description:** + +Updates element visibility based on scroll position. + +### `void ensureSelectedVisible()` + +**Description:** + +Ensures the selected element is visible by adjusting scroll. + +### `void clampScrollOffset()` + +**Description:** + +Clamps scroll offset to valid range. diff --git a/api/generated/graphics/UIWidgetFlags.md b/api/generated/graphics/UIWidgetFlags.md new file mode 100644 index 0000000..350070d --- /dev/null +++ b/api/generated/graphics/UIWidgetFlags.md @@ -0,0 +1,9 @@ +# UIWidgetFlags + + + +**Source:** `UITouchWidget.h` + +## Description + +Flags for widget behavior diff --git a/api/generated/graphics/UIWidgetState.md b/api/generated/graphics/UIWidgetState.md new file mode 100644 index 0000000..8231683 --- /dev/null +++ b/api/generated/graphics/UIWidgetState.md @@ -0,0 +1,9 @@ +# UIWidgetState + + + +**Source:** `UITouchWidget.h` + +## Description + +Current state of a touch widget diff --git a/api/generated/graphics/UIWidgetType.md b/api/generated/graphics/UIWidgetType.md new file mode 100644 index 0000000..ac3a58a --- /dev/null +++ b/api/generated/graphics/UIWidgetType.md @@ -0,0 +1,9 @@ +# UIWidgetType + + + +**Source:** `UITouchWidget.h` + +## Description + +Types of touch UI widgets diff --git a/api/generated/index.md b/api/generated/index.md new file mode 100644 index 0000000..ce0fe11 --- /dev/null +++ b/api/generated/index.md @@ -0,0 +1,152 @@ +# API Reference (Generated) + +Auto-generated API documentation from C++ header files. + +## Audio + +- [ApuCore](./audio/ApuCore.md) — Shared NES-style APU core used by every AudioScheduler. +- [AudioBackend](./audio/AudioBackend.md) — Abstract interface for platform-specific audio drivers. +- [AudioChannel](./audio/AudioChannel.md) — Represents the internal state of a single audio channel. +- [AudioCommand](./audio/AudioCommand.md) — Internal command to communicate between game and audio threads. +- [AudioCommandQueue](./audio/AudioCommandQueue.md) — Multi-Producer Single-Consumer lock-free ring buffer for AudioCommands. +- [AudioConfig](./audio/AudioConfig.md) — Configuration for the Audio subsystem. +- [AudioEngine](./audio/AudioEngine.md) — Core class for the NES-like audio subsystem. +- [AudioEvent](./audio/AudioEvent.md) — A fire-and-forget sound event triggered by the game. +- [AudioScheduler](./audio/AudioScheduler.md) — Abstract interface for the audio execution context. +- [DefaultAudioScheduler](./audio/DefaultAudioScheduler.md) — Backend-driven scheduler used on platforms without a dedicated + audio task. +- [InstrumentPreset](./audio/InstrumentPreset.md) — Defines instrument characteristics for playback. +- [MusicNote](./audio/MusicNote.md) — Represents a single note in a melody. +- [MusicPlayer](./audio/MusicPlayer.md) — Simple sequencer to play MusicTracks. + +## Core + +- [Actor](./core/Actor.md) — An Entity capable of physical interaction and collision. +- [CollisionShape](./core/CollisionShape.md) — Defines the geometric shape used for collision detection. +- [Engine](./core/Engine.md) — The main engine class that manages the game loop and core subsystems. +- [Entity](./core/Entity.md) — Abstract base class for all game objects. +- [EntityType](./core/EntityType.md) — Categorizes entities for type-safe casting and logic differentiation. +- [LimitRect](./core/LimitRect.md) — Defines a rectangular boundary for actor movement. +- [LogLevel](./core/LogLevel.md) — Enumeration of log levels. +- [PhysicsActor](./core/PhysicsActor.md) — An actor with basic 2D physics properties using adaptable Scalar type. +- [PhysicsBodyType](./core/PhysicsBodyType.md) — Defines the simulation behavior of a PhysicsActor. +- [Rect](./core/Rect.md) — Represents a 2D rectangle, typically used for hitboxes or bounds. +- [Scene](./core/Scene.md) — Represents a game level or screen containing entities. +- [SceneManager](./core/SceneManager.md) — Manages the stack of active scenes. +- [WorldCollisionInfo](./core/WorldCollisionInfo.md) — Stores flags indicating which world boundaries were hit in the current frame. + +## Drivers + +- [ESP32AudioScheduler](./drivers/ESP32AudioScheduler.md) — Audio scheduler for ESP32 targets. +- [ESP32_DAC_AudioBackend](./drivers/ESP32_DAC_AudioBackend.md) — Audio backend for ESP32 classic / S2 internal 8-bit DAC. +- [ESP32_I2S_AudioBackend](./drivers/ESP32_I2S_AudioBackend.md) — Audio backend implementation for ESP32 using I2S. +- [NativeAudioScheduler](./drivers/NativeAudioScheduler.md) — Audio scheduler for native builds. +- [SDL2_AudioBackend](./drivers/SDL2_AudioBackend.md) — Audio backend implementation for SDL2 (Windows/Linux/Mac). +- [SDL2_Drawer](./drivers/SDL2_Drawer.md) — SDL2-backed draw surface for native desktop builds. +- [TFT_eSPI_Drawer](./drivers/TFT_eSPI_Drawer.md) — Concrete implementation of DrawSurface for ESP32 using the TFT_eSPI library. +- [U8G2_Drawer](./drivers/U8G2_Drawer.md) — Implementation of DrawSurface using the U8G2 library for monochromatic OLED displays. + +## Graphics + +- [Anchor](./graphics/Anchor.md) — Defines anchor points for positioning UI elements. +- [BaseDrawSurface](./graphics/BaseDrawSurface.md) — Optional base class for DrawSurface implementations that provides default primitive rendering. +- [Camera2D](./graphics/Camera2D.md) — 2D camera system for managing viewports and scrolling. +- [DisplayType](./graphics/DisplayType.md) — Identifies the type of display driver to use. +- [DrawSurface](./graphics/DrawSurface.md) — Abstract interface for platform-specific drawing operations. +- [Font](./graphics/Font.md) — Descriptor for a bitmap font using 1bpp sprites. +- [FontManager](./graphics/FontManager.md) — Static utility class for managing bitmap fonts. +- [Particle](./graphics/Particle.md) — Represents a single particle in the particle system. +- [ParticleConfig](./graphics/ParticleConfig.md) — Configuration parameters for a particle emitter. +- [ParticleEmitter](./graphics/ParticleEmitter.md) — Manages a pool of particles to create visual effects. +- [Renderer](./graphics/Renderer.md) — High-level graphics rendering system. +- [ScrollBehavior](./graphics/ScrollBehavior.md) — Defines how scrolling behaves in layouts. +- [Sprite2bpp](./graphics/Sprite2bpp.md) — Sprite descriptor for 2bpp (4-color) multi-color sprites. +- [Sprite4bpp](./graphics/Sprite4bpp.md) — Sprite descriptor for 4bpp (16-color) multi-color sprites. +- [TileMapGeneric](./graphics/TileMapGeneric.md) — Generic tilemap structure supporting 1bpp, 2bpp, or 4bpp tile graphics. +- [TouchConfig](./graphics/TouchConfig.md) — Configuration for touch controller +- [TouchController](./graphics/TouchController.md) — Supported touch controller types +- [UIAnchorLayout](./graphics/UIAnchorLayout.md) — Layout that positions elements at fixed anchor points on the screen. +- [UIButton](./graphics/UIButton.md) — A clickable button UI element. +- [UICheckBox](./graphics/UICheckBox.md) — A clickable checkbox UI element. +- [UIElement](./graphics/UIElement.md) — Base class for all user interface elements (buttons, labels, etc.). +- [UIGridLayout](./graphics/UIGridLayout.md) — Grid layout container for organizing elements in a matrix. +- [UIHitTest](./graphics/UIHitTest.md) — AABB hit testing for touch UI widgets +- [UIHorizontalLayout](./graphics/UIHorizontalLayout.md) — Horizontal layout container with scroll support. +- [UILabel](./graphics/UILabel.md) — A simple text label UI element. +- [UILayout](./graphics/UILayout.md) — Base class for UI layout containers. +- [UIManager](./graphics/UIManager.md) — Registry of touch UI elements for event routing (non-owning pointers). +- [UIPaddingContainer](./graphics/UIPaddingContainer.md) — Container that wraps a single UI element and applies padding. +- [UIPanel](./graphics/UIPanel.md) — Visual container that draws a background and border around a child element. +- [UITouchButton](./graphics/UITouchButton.md) — Touch-optimized button widget. +- [UITouchCheckbox](./graphics/UITouchCheckbox.md) — Touch-optimized checkbox widget. +- [UITouchElement](./graphics/UITouchElement.md) — UIElement with embedded UITouchWidget data for touch interaction. +- [UITouchSlider](./graphics/UITouchSlider.md) — Touch-optimized slider widget. +- [UITouchWidget](./graphics/UITouchWidget.md) — Base touch widget structure +- [UIVerticalLayout](./graphics/UIVerticalLayout.md) — Vertical layout container with scroll support. +- [UIWidgetFlags](./graphics/UIWidgetFlags.md) — Flags for widget behavior +- [UIWidgetState](./graphics/UIWidgetState.md) — Current state of a touch widget +- [UIWidgetType](./graphics/UIWidgetType.md) — Types of touch UI widgets + +## Input + +- [ActorPool](./input/ActorPool.md) — Fixed-size pool for managing draggable actors +- [ActorTouchController](./input/ActorTouchController.md) — Handles touch-based dragging of game actors +- [DisplayPreset](./input/DisplayPreset.md) — Display presets for common displays +- [GT911Adapter](./input/GT911Adapter.md) — GT911 I2C touch controller driver +- [InputConfig](./input/InputConfig.md) — Configuration structure for the InputManager. +- [InputManager](./input/InputManager.md) — Handles input from physical buttons, keyboard (on PC), and touch/mouse. +- [TouchAdapterBase](./input/TouchAdapterBase.md) — Base class requirements for touch adapters (conceptual) +- [TouchCalibration](./input/TouchCalibration.md) — Calibration parameters for coordinate transformation +- [TouchEvent](./input/TouchEvent.md) — Compact touch event structure (12 bytes total, naturally aligned) +- [TouchEventDispatcher](./input/TouchEventDispatcher.md) — Pull-based touch event dispatcher +- [TouchEventFlags](./input/TouchEventFlags.md) — Flags for touch events +- [TouchEventHistory](./input/TouchEventHistory.md) — Ring buffer for touch events (for gesture detection) +- [TouchEventQueue](./input/TouchEventQueue.md) — Ring buffer for touch events (192 bytes total) +- [TouchEventType](./input/TouchEventType.md) — High-level touch event types for gesture detection +- [TouchFactory](./input/TouchFactory.md) — Factory for creating touch system configurations +- [TouchManager](./input/TouchManager.md) — Touch event aggregation layer +- [TouchPoint](./input/TouchPoint.md) — Normalized touch data structure. +- [TouchPointBuffer](./input/TouchPointBuffer.md) — Ring buffer for storing touch points +- [TouchRotation](./input/TouchRotation.md) — Display rotation modes for calibration +- [TouchState](./input/TouchState.md) — Internal states for touch gesture detection +- [TouchStateData](./input/TouchStateData.md) — Per-touch-ID state tracking +- [TouchStateMachine](./input/TouchStateMachine.md) — State machine for touch gesture detection +- [XPT2046Adapter](./input/XPT2046Adapter.md) — XPT2046 SPI touch controller driver + +## Math + +- [Fixed16](./math/Fixed16.md) — Fixed-point 16.16 number implementation optimized for RISC-V. +- [Vector2](./math/Vector2.md) — 2D vector using the configured Scalar type (float or Fixed16). + +## Physics + +- [Circle](./physics/Circle.md) — Represents a 2D circle for collision detection. +- [CollisionSystem](./physics/CollisionSystem.md) — Manages physics simulation and collision detection for all actors. +- [Contact](./physics/Contact.md) — Represents a contact point between two physics bodies. +- [KinematicActor](./physics/KinematicActor.md) — A physics body moved via script/manual velocity with collision detection. +- [KinematicCollision](./physics/KinematicCollision.md) — Contains information about a collision involving a KinematicActor. +- [RigidActor](./physics/RigidActor.md) — A physics body fully simulated by the engine. +- [Segment](./physics/Segment.md) — Represents a 2D line segment for collision detection. +- [SensorActor](./physics/SensorActor.md) — A static body that acts as a trigger: detects overlap but produces no physical response. +- [SpatialGrid](./physics/SpatialGrid.md) — Optimized spatial partitioning with separate static/dynamic layers. +- [StaticActor](./physics/StaticActor.md) — A physics body that does not move. +- [TileBehaviorLayer](./physics/TileBehaviorLayer.md) — Runtime representation of exported behavior layer for O(1) flag lookup. +- [TileCollisionBehavior](./physics/TileCollisionBehavior.md) — Defines how a tile collider behaves in the physics system. +- [TileCollisionBuilder](./physics/TileCollisionBuilder.md) — Helper class for creating physics bodies from exported behavior layers. +- [TileCollisionBuilderConfig](./physics/TileCollisionBuilderConfig.md) — Configuration for tile collision building. +- [TileConsumptionConfig](./physics/TileConsumptionConfig.md) — Configuration for tile consumption operations. +- [TileConsumptionHelper](./physics/TileConsumptionHelper.md) — Helper class for consuming tiles (removing bodies and updating visuals). +- [TileFlags](./physics/TileFlags.md) — Bit flags for tile behavior attributes (8-bit, 1 byte per tile). +Optimized for ESP32 runtime with bit operations only. + +## Platforms + +- [MockAudioBackend](./platforms/MockAudioBackend.md) — Mock implementation of AudioBackend for unit testing. +- [PlatformCapabilities](./platforms/PlatformCapabilities.md) — Represents the hardware capabilities of the current platform. +- [SPIClass](./platforms/SPIClass.md) — Mocks the Arduino SPI class for native platform. + +## Test + +- [PhysicsSnapshot](./test/PhysicsSnapshot.md) — Captures physics state for determinism validation +- [PhysicsTestSuite](./test/PhysicsTestSuite.md) — Comprehensive testing for Flat Solver +- [StressTestScene](./test/StressTestScene.md) — Scene for stress testing physics performance. diff --git a/api/generated/input/ActorPool.md b/api/generated/input/ActorPool.md new file mode 100644 index 0000000..1768256 --- /dev/null +++ b/api/generated/input/ActorPool.md @@ -0,0 +1,12 @@ +# ActorPool + + + +**Source:** `ActorTouchController.h` + +## Description + +Fixed-size pool for managing draggable actors + +Uses a fixed array to avoid dynamic memory allocation. +Maximum 8 actors can be registered for touch dragging. diff --git a/api/generated/input/ActorTouchController.md b/api/generated/input/ActorTouchController.md new file mode 100644 index 0000000..814f8a5 --- /dev/null +++ b/api/generated/input/ActorTouchController.md @@ -0,0 +1,108 @@ +# ActorTouchController + + + +**Source:** `ActorTouchController.h` + +## Description + +Handles touch-based dragging of game actors + +This controller manages a pool of actors that can be dragged via touch input. +It implements: +- Drag threshold (5 pixels) to ignore jitter +- Offset preservation (actor moves relative to initial touch position) +- Single drag (only one actor dragged at a time) +- Fixed pool (no dynamic memory allocation) + +Usage: +ActorTouchController controller; +controller.registerActor(&myActor); +// In game loop: +TouchEvent events[16]; +uint8_t count = dispatcher.getEvents(events, 16); +for (uint8_t i = 0; i < count; i++) { + controller.handleTouch(events[i]); +} +@endcode + +## Methods + +### `void reset()` + +**Description:** + +Clear registered actors and drag state (e.g. scene reset / arena recycle). + +### `void setTouchHitSlop(int16_t expandPixels)` + +**Description:** + +Expand hit-test rectangles by this many pixels on each side (0 = exact hitbox only). + Useful when calibrated screen coords lag the visual sprite on resistive panels. + +### `int16_t getTouchHitSlop() const` + +**Description:** + +Current hit slop in pixels (per side). + +### `void handleTouch(const TouchEvent& event)` + +**Description:** + +Handle a touch event + +**Parameters:** + +- `event`: The touch event to process + +Routes events based on type: +- TouchDown: Check for hit, begin drag if threshold exceeded +- DragMove: Update dragged actor position +- TouchUp: End drag + +### `bool isDragging() const` + +**Description:** + +Check if currently dragging an actor + +**Returns:** true if a drag operation is in progress + +### `void onTouchDown(const TouchEvent& event)` + +**Description:** + +Handle touch down event + +**Parameters:** + +- `event`: The touch down event + +### `void onTouchMove(const TouchEvent& event)` + +**Description:** + +Handle drag move event + +**Parameters:** + +- `event`: The drag move event + +### `void onTouchUp(const TouchEvent& event)` + +**Description:** + +Handle touch up event + +**Parameters:** + +- `event`: The touch up event + +### `void onDragStart(const TouchEvent& event)` + +**Description:** + +Handle drag start (movement exceeded threshold after TouchDown). + If TouchDown missed the actor but the finger is now on one, start dragging. diff --git a/api/generated/input/DisplayPreset.md b/api/generated/input/DisplayPreset.md new file mode 100644 index 0000000..95f8beb --- /dev/null +++ b/api/generated/input/DisplayPreset.md @@ -0,0 +1,9 @@ +# DisplayPreset + + + +**Source:** `TouchAdapter.h` + +## Description + +Display presets for common displays diff --git a/api/generated/input/GT911Adapter.md b/api/generated/input/GT911Adapter.md new file mode 100644 index 0000000..4ec5b27 --- /dev/null +++ b/api/generated/input/GT911Adapter.md @@ -0,0 +1,107 @@ +# GT911Adapter + + + +**Source:** `GT911Adapter.h` + +## Description + +GT911 I2C touch controller driver + +Hardware: GT911 (Goodix) - common on higher-quality touch panels +Protocol: I2C (independent bus from display SPI) +Sampling: 100Hz+ +Filtering: Minimal (passthrough - controller does processing) + +I2C bus is independent - no DMA coordination needed +Supports 5-point multi-touch + +::: tip +I2C bus is independent - no DMA coordination needed +::: + +::: tip +Supports 5-point multi-touch +::: + +## Methods + +### `static bool initImpl()` + +**Description:** + +Initialize GT911 hardware + +**Returns:** true if successful + +### `static bool readImpl(TouchPoint* points, uint8_t& count)` + +**Description:** + +Read touch data from GT911 + +**Parameters:** + +- `points`: Output buffer +- `count`: Number of points read + +**Returns:** true if successful + +### `static void setCalibrationImpl(const TouchCalibration& calib)` + +**Description:** + +Set calibration parameters + +**Parameters:** + +- `calib`: Calibration data + +### `static bool isConnectedImpl()` + +**Description:** + +Check if controller is connected + +**Returns:** true if responding + +### `static uint8_t readRegister(uint8_t addr)` + +**Description:** + +Read a single register from GT911 + +**Parameters:** + +- `addr`: Register address + +**Returns:** Register value + +### `static int16_t readCoordinate(uint8_t addr)` + +**Description:** + +Read a 16-bit coordinate from GT911 + +**Parameters:** + +- `addr`: Register address (LSB first) + +**Returns:** Coordinate value + +### `static void writeRegister(uint8_t addr, uint8_t value)` + +**Description:** + +Write a register to GT911 + +**Parameters:** + +- `addr`: Register address +- `value`: Value to write + +### `static uint32_t millis()` + +**Description:** + +Platform-specific millis() function diff --git a/api/generated/input/InputConfig.md b/api/generated/input/InputConfig.md new file mode 100644 index 0000000..3ae042e --- /dev/null +++ b/api/generated/input/InputConfig.md @@ -0,0 +1,22 @@ +# InputConfig + + + +**Source:** `InputConfig.h` + +## Description + +Configuration structure for the InputManager. + +Defines the mapping between logical inputs and physical pins (ESP32) +or keyboard keys (Native/SDL2). + +Uses variadic arguments to allow flexible configuration of input count. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `buttonNames` | `std::vector` | Array of button mappings (scancodes) for Native. | +| `inputPins` | `std::vector` | Array of GPIO pin numbers for ESP32. | +| `count` | `int` | Total number of configured inputs. | diff --git a/api/generated/input/InputManager.md b/api/generated/input/InputManager.md new file mode 100644 index 0000000..f9ee81a --- /dev/null +++ b/api/generated/input/InputManager.md @@ -0,0 +1,157 @@ +# InputManager + + + +**Source:** `InputManager.h` + +## Description + +Handles input from physical buttons, keyboard (on PC), and touch/mouse. + +The InputManager polls configured pins, handles debouncing, and tracks button states +(Pressed, Released, Down, Clicked). It also provides touch event processing for +both ESP32 (via TouchManager) and Native (via mouse-to-touch mapping). + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `constexpr` | `static` | Maximum number of buttons supported. | + +## Methods + +### `void init()` + +**Description:** + +Initializes the input pins. + +### `void update(unsigned long dt, const uint8_t* keyboardState)` + +**Description:** + +Updates input state based on SDL keyboard state. + +**Parameters:** + +- `dt`: Delta time. +- `keyboardState`: Pointer to the SDL keyboard state array. + +### `void update(unsigned long dt)` + +**Description:** + +Updates input state by polling hardware pins. + +**Parameters:** + +- `dt`: Delta time. + +### `bool isButtonPressed(uint8_t buttonIndex) const` + +**Description:** + +Checks if a button was just pressed this frame. + +**Parameters:** + +- `buttonIndex`: Index of the button to check. + +**Returns:** true if the button transitioned from UP to DOWN this frame. + +### `bool isButtonReleased(uint8_t buttonIndex) const` + +**Description:** + +Checks if a button was just released this frame. + +**Parameters:** + +- `buttonIndex`: Index of the button to check. + +**Returns:** true if the button transitioned from DOWN to UP this frame. + +### `bool isButtonClicked(uint8_t buttonIndex) const` + +**Description:** + +Checks if a button was clicked (pressed and released). + +**Parameters:** + +- `buttonIndex`: Index of the button to check. + +**Returns:** true if the button was clicked. + +### `bool isButtonDown(uint8_t buttonIndex) const` + +**Description:** + +Checks if a button is currently held down. + +**Parameters:** + +- `buttonIndex`: Index of the button to check. + +**Returns:** true if the button is currently in the DOWN state. + +### `uint8_t getTouchEvents(TouchEvent* buffer, uint8_t maxCount)` + +**Description:** + +Get touch events from the event dispatcher. + +**Parameters:** + +- `buffer`: Caller-provided buffer for events. +- `maxCount`: Maximum number of events to retrieve. + +**Returns:** Number of events retrieved (removed from queue). + +### `bool hasTouchEvents() const` + +**Description:** + +Check if there are pending touch events. + +**Returns:** true if there are events in the queue. + +### `TouchState getTouchState(uint8_t touchId) const` + +**Description:** + +Get the current state of a specific touch ID. + +**Parameters:** + +- `touchId`: Touch identifier (0-4). + +**Returns:** Current touch state. + +### `void processSDLEvent(const void* sdlEvent)` + +**Description:** + +Process an SDL event (mouse/keyboard). + +**Parameters:** + +- `sdlEvent`: The SDL event to process. + +This method handles: +- SDL_MOUSEBUTTONDOWN/UP: Maps to touch events +- SDL_MOUSEMOTION: Maps to drag events when button is held +- SDL_KEYDOWN/SDL_KEYUP: Maps to button events (existing) + +### `void injectTouchPoint(int16_t x, int16_t y, bool pressed, uint8_t id, uint32_t timestamp)` + +**Description:** + +Inject a raw touch point from external source (e.g., TouchManager). + +**Parameters:** + +- `point`: The touch point to inject. +- `timestamp`: Current timestamp in ms. + +Used by ESP32 examples to connect TouchManager with InputManager. diff --git a/api/generated/input/TouchAdapterBase.md b/api/generated/input/TouchAdapterBase.md new file mode 100644 index 0000000..c2195cd --- /dev/null +++ b/api/generated/input/TouchAdapterBase.md @@ -0,0 +1,55 @@ +# TouchAdapterBase + + + +**Source:** `TouchAdapter.h` + +## Description + +Base class requirements for touch adapters (conceptual) + +This defines the interface that ALL touch adapters must implement. +Uses template static dispatch instead of virtual functions for ESP32. + +Adapter Concrete adapter type (XPT2046Adapter or GT911Adapter) + +## Methods + +### `static bool init()` + +**Description:** + +Initialize the touch controller + +**Returns:** true if initialization successful + +### `static bool read(TouchPoint* points, uint8_t& count)` + +**Description:** + +Read touch points from controller + +**Parameters:** + +- `points`: Output buffer for touch points +- `count`: Number of touch points read + +**Returns:** true if read successful + +### `static void setCalibration(const TouchCalibration& calib)` + +**Description:** + +Set calibration parameters + +**Parameters:** + +- `calib`: Calibration data + +### `static bool isConnected()` + +**Description:** + +Check if controller is connected + +**Returns:** true if controller responds diff --git a/api/generated/input/TouchCalibration.md b/api/generated/input/TouchCalibration.md new file mode 100644 index 0000000..44b59ff --- /dev/null +++ b/api/generated/input/TouchCalibration.md @@ -0,0 +1,94 @@ +# TouchCalibration + + + +**Source:** `TouchAdapter.h` + +## Description + +Calibration parameters for coordinate transformation + +Supports: +- Scale factors for X/Y axes +- Offset adjustments +- Display rotation transformation +- Factory presets for common displays + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `scaleX` | `float` | X axis scale factor | +| `scaleY` | `float` | Y axis scale factor | +| `offsetX` | `int16_t` | X axis offset | +| `offsetY` | `int16_t` | Y axis offset | +| `displayWidth` | `int16_t` | Display width for clamping | +| `displayHeight` | `int16_t` | Display height for clamping | + +## Methods + +### `static TouchCalibration fromPreset(DisplayPreset preset)` + +**Description:** + +Create calibration from preset + +**Parameters:** + +- `preset`: Display preset + +**Returns:** TouchCalibration instance + +### `static TouchCalibration forResolution(int16_t width, int16_t height)` + +**Description:** + +Create calibration for custom resolution + +**Parameters:** + +- `width`: Display width +- `height`: Display height + +**Returns:** TouchCalibration instance + +### `TouchPoint transform(int16_t rawX, int16_t rawY, bool pressed, uint8_t id, uint32_t ts) const` + +**Description:** + +Transform raw coordinates to screen space + +**Parameters:** + +- `rawX`: Raw X coordinate +- `rawY`: Raw Y coordinate +- `pressed`: Touch pressed state +- `id`: Touch ID +- `ts`: Timestamp + +**Returns:** Transformed TouchPoint + +### `void setRotation(TouchRotation rot)` + +**Description:** + +Set rotation mode + +### `void applyRotation(int16_t x, int16_t y, int16_t& outX, int16_t& outY) const` + +**Description:** + +Apply rotation transformation to coordinates + +**Parameters:** + +- `x`: Input X coordinate +- `y`: Input Y coordinate +- `outX`: Output X coordinate +- `outY`: Output Y coordinate + +### `TouchRotation inverted() const` + +**Description:** + +Invert rotation (for opposite rotation) diff --git a/api/generated/input/TouchEvent.md b/api/generated/input/TouchEvent.md new file mode 100644 index 0000000..914397a --- /dev/null +++ b/api/generated/input/TouchEvent.md @@ -0,0 +1,149 @@ +# TouchEvent + + + +**Source:** `TouchEvent.h` + +## Description + +Compact touch event structure (12 bytes total, naturally aligned) + +Memory layout (naturally aligned, no packing needed): +- timestamp: 4 bytes (offset 0) +- x: 2 bytes (offset 4) +- y: 2 bytes (offset 6) +- type: 1 byte (offset 8) +- flags: 1 byte (offset 9) +- id: 1 byte (offset 10) +- _padding: 1 byte (offset 11) +Total: 12 bytes + +Invariants: +- timestamp always monotonically increasing per touch ID +- x, y always valid (within display bounds) +- type always non-None when queued + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `timestamp` | `uint32_t` | Timestamp in milliseconds | +| `x` | `int16_t` | X coordinate | +| `y` | `int16_t` | Y coordinate | +| `type` | `uint8_t` | Event type | +| `flags` | `uint8_t` | Event flags | +| `id` | `uint8_t` | Touch ID (0-4) | +| `_padding` | `uint8_t` | Explicit padding for alignment | + +## Methods + +### `, x(0)` + +**Description:** + +Default constructor - creates empty event + +### `, y(0)` + +### `, type(0)` + +### `, flags(0)` + +### `, id(0)` + +### `, _padding(0)` + +### `, x(xPos)` + +**Description:** + +Construct touch event with all fields + +**Parameters:** + +- `eventType`: Type of event +- `touchId`: Touch identifier +- `xPos`: X coordinate +- `yPos`: Y coordinate +- `ts`: Timestamp in ms +- `eventFlags`: Event flags + +### `, y(yPos)` + +### `, type(static_cast(eventType))` + +### `, flags(static_cast(eventFlags))` + +### `, id(touchId)` + +### `TouchEventType getType() const` + +**Description:** + +Get event type as enum + +**Returns:** Event type + +### `TouchEventFlags getFlags() const` + +**Description:** + +Get event flags as enum + +**Returns:** Event flags + +### `void setType(TouchEventType eventType)` + +**Description:** + +Set event type from enum + +**Parameters:** + +- `eventType`: Type of event + +### `void setFlags(TouchEventFlags eventFlags)` + +**Description:** + +Set event flags from enum + +**Parameters:** + +- `eventFlags`: Event flags + +### `bool isValid() const` + +**Description:** + +Check if event is valid (has a type) + +**Returns:** true if type is not None + +### `bool isPrimary() const` + +**Description:** + +Check if this is a primary touch + +**Returns:** true if Primary flag is set + +### `bool isConsumed() const` + +**Description:** + +Check if event was consumed + +**Returns:** true if Consumed flag is set + +### `void consume()` + +**Description:** + +Mark event as consumed + +### `void setPrimary()` + +**Description:** + +Set primary flag diff --git a/api/generated/input/TouchEventDispatcher.md b/api/generated/input/TouchEventDispatcher.md new file mode 100644 index 0000000..fa9986d --- /dev/null +++ b/api/generated/input/TouchEventDispatcher.md @@ -0,0 +1,105 @@ +# TouchEventDispatcher + + + +**Source:** `TouchEventDispatcher.h` + +## Description + +Pull-based touch event dispatcher + +This is the main API for consumers to receive touch events. +It combines the state machine and event queue into a unified interface. + +Usage pattern (pull-based): +TouchEvent events[16]; +uint8_t count = dispatcher.getEvents(events, 16); +for (uint8_t i = 0; i < count; i++) { + handleEvent(events[i]); +} +Or for checking without consuming: +if (dispatcher.hasEvents()) { + TouchEvent event; + dispatcher.peek(event); + // inspect without removing +} +@endcode + +## Methods + +### `uint8_t getEvents(TouchEvent* events, uint8_t maxCount)` + +**Description:** + +Get events using caller-provided buffer (pull-based) + +**Parameters:** + +- `events`: Caller-provided buffer for events +- `maxCount`: Maximum events to retrieve + +**Returns:** Number of events retrieved (removed from queue) + +This is the primary API for consumers. +Events are removed from the internal queue. + +### `uint8_t peekEvents(TouchEvent* events, uint8_t maxCount) const` + +**Description:** + +Peek at events without removing them + +**Parameters:** + +- `events`: Caller-provided buffer +- `maxCount`: Maximum events to peek + +**Returns:** Number of events peeked + +### `bool hasEvents() const` + +**Description:** + +Check if events are available + +**Returns:** true if queue has events + +### `uint8_t getEventCount() const` + +**Description:** + +Get number of pending events + +**Returns:** Number of events in queue + +### `void clearEvents()` + +**Description:** + +Clear all pending events + +### `void reset()` + +**Description:** + +Reset state machine (force all touches to idle) + +### `TouchState getTouchState(uint8_t touchId) const` + +**Description:** + +Get current state for a touch ID + +**Parameters:** + +- `touchId`: Touch identifier + +**Returns:** Current state + +### `bool isTouchActive() const` + +**Description:** + +Check if any touch is active + +**Returns:** true if any touch is in progress diff --git a/api/generated/input/TouchEventFlags.md b/api/generated/input/TouchEventFlags.md new file mode 100644 index 0000000..6b075ba --- /dev/null +++ b/api/generated/input/TouchEventFlags.md @@ -0,0 +1,9 @@ +# TouchEventFlags + + + +**Source:** `TouchEventTypes.h` + +## Description + +Flags for touch events diff --git a/api/generated/input/TouchEventHistory.md b/api/generated/input/TouchEventHistory.md new file mode 100644 index 0000000..b88d5b7 --- /dev/null +++ b/api/generated/input/TouchEventHistory.md @@ -0,0 +1,31 @@ +# TouchEventHistory + + + +**Source:** `TouchTypes.h` + +## Description + +Ring buffer for touch events (for gesture detection) + +Use TouchEventQueue from TouchEventQueue.h instead + +## Methods + +### `void push(const TouchEvent& event)` + +**Description:** + +Add event to history + +### `void clear()` + +**Description:** + +Clear history + +### `const TouchEvent* mostRecent() const` + +**Description:** + +Get most recent event diff --git a/api/generated/input/TouchEventQueue.md b/api/generated/input/TouchEventQueue.md new file mode 100644 index 0000000..e252ba0 --- /dev/null +++ b/api/generated/input/TouchEventQueue.md @@ -0,0 +1,143 @@ +# TouchEventQueue + + + +**Source:** `TouchEventQueue.h` + +## Description + +Ring buffer for touch events (192 bytes total) + +Fixed-size circular buffer with O(1) enqueue/dequeue operations. +Uses a static array - no dynamic memory allocation. + +Memory layout: +- events[16]: 16 * 12 = 192 bytes +- head: 1 byte +- tail: 1 byte +- count: 1 byte +Total: ~195 bytes (with padding) + +## Methods + +### `bool isEmpty() const` + +**Description:** + +Check if queue is empty + +**Returns:** true if no events in queue + +### `bool isFull() const` + +**Description:** + +Check if queue is full + +**Returns:** true if queue cannot accept more events + +### `uint8_t getCount() const` + +**Description:** + +Get number of events in queue + +**Returns:** Number of events currently queued + +### `constexpr uint8_t getCapacity() const` + +**Description:** + +Get capacity of queue + +**Returns:** Maximum number of events (16) + +### `bool enqueue(const TouchEvent& event)` + +**Description:** + +Enqueue an event (add to tail) + +**Parameters:** + +- `event`: Event to add + +**Returns:** true if event was enqueued, false if queue was full + +### `bool dequeue(TouchEvent& event)` + +**Description:** + +Dequeue an event (remove from head) + +**Parameters:** + +- `event`: Output parameter for dequeued event + +**Returns:** true if event was dequeued, false if queue was empty + +### `bool peek(TouchEvent& event) const` + +**Description:** + +Peek at head event without removing + +**Parameters:** + +- `event`: Output parameter for peeked event + +**Returns:** true if event exists, false if queue empty + +### `uint8_t peekMultiple(TouchEvent* events, uint8_t maxCount) const` + +**Description:** + +Peek at multiple events from head + +**Parameters:** + +- `events`: Output buffer for events +- `maxCount`: Maximum number of events to peek + +**Returns:** Number of events peeked + +### `void clear()` + +**Description:** + +Clear all events from queue + +### `uint8_t drop(uint8_t count)` + +**Description:** + +Remove and discard n events from head + +**Parameters:** + +- `count`: Number of events to drop + +**Returns:** Number of events actually dropped + +### `uint8_t getEvents(TouchEvent* events, uint8_t maxCount)` + +**Description:** + +Get events by providing caller-owned buffer + +**Parameters:** + +- `events`: Caller-provided buffer for events +- `maxCount`: Maximum events to retrieve + +**Returns:** Number of events retrieved + +This is the pull-based API: consumer provides the buffer. + +### `bool hasEvents() const` + +**Description:** + +Check if events are available (hasEvents) + +**Returns:** true if queue has events diff --git a/api/generated/input/TouchEventType.md b/api/generated/input/TouchEventType.md new file mode 100644 index 0000000..9a504f2 --- /dev/null +++ b/api/generated/input/TouchEventType.md @@ -0,0 +1,12 @@ +# TouchEventType + + + +**Source:** `TouchEventTypes.h` + +## Description + +High-level touch event types for gesture detection + +These events represent the semantic meaning of touch interactions, +not the raw hardware events (which are handled by TouchAdapter). diff --git a/api/generated/input/TouchFactory.md b/api/generated/input/TouchFactory.md new file mode 100644 index 0000000..3fad3fb --- /dev/null +++ b/api/generated/input/TouchFactory.md @@ -0,0 +1,128 @@ +# TouchFactory + + + +**Source:** `TouchFactory.h` + +## Description + +Factory for creating touch system configurations + +Provides convenient factory methods to create TouchManager +with common display presets and configurations. + +Usage: + auto manager = TouchFactory::createForILI9341(); + manager->init(); + +## Methods + +### `static TouchManager createForILI9341()` + +**Description:** + +Create TouchManager for ILI9341 display (320x240) + +**Returns:** Initialized TouchManager + +### `TouchManager manager(320, 240)` + +### `static TouchManager createForST7789_240x320()` + +**Description:** + +Create TouchManager for ST7789 display (240x320 portrait) + +**Returns:** Initialized TouchManager + +### `TouchManager manager(240, 320)` + +### `static TouchManager createForST7789_240x240()` + +**Description:** + +Create TouchManager for ST7789 display (240x240 round) + +**Returns:** Initialized TouchManager + +### `TouchManager manager(240, 240)` + +### `static TouchManager createForST7735_128x160()` + +**Description:** + +Create TouchManager for ST7735 display (128x160) + +**Returns:** Initialized TouchManager + +### `TouchManager manager(128, 160)` + +### `static TouchManager createForST7735_128x128()` + +**Description:** + +Create TouchManager for ST7735 display (128x128) + +**Returns:** Initialized TouchManager + +### `TouchManager manager(128, 128)` + +### `static TouchManager createForILI9488()` + +**Description:** + +Create TouchManager for ILI9488 display (320x480) + +**Returns:** Initialized TouchManager + +### `TouchManager manager(320, 480)` + +### `static TouchManager createForGC9A01()` + +**Description:** + +Create TouchManager for GC9A01 display (240x240 round) + +**Returns:** Initialized TouchManager + +### `static TouchManager createForResolution(uint16_t width, uint16_t height)` + +**Description:** + +Create TouchManager for custom resolution + +**Parameters:** + +- `width`: Display width +- `height`: Display height + +**Returns:** Initialized TouchManager + +### `TouchManager manager(width, height)` + +### `static TouchManager createFromPreset(DisplayPreset preset)` + +**Description:** + +Create TouchManager from DisplayPreset enum + +**Parameters:** + +- `preset`: Display preset + +**Returns:** Initialized TouchManager + +### `TouchManager manager(calib.displayWidth, calib.displayHeight)` + +### `static DisplayPreset detectPreset(uint16_t width, uint16_t height)` + +**Description:** + +Get display preset from width/height + +**Parameters:** + +- `width`: Display width +- `height`: Display height + +**Returns:** Matching DisplayPreset or Custom if no match diff --git a/api/generated/input/TouchManager.md b/api/generated/input/TouchManager.md new file mode 100644 index 0000000..2943018 --- /dev/null +++ b/api/generated/input/TouchManager.md @@ -0,0 +1,140 @@ +# TouchManager + + + +**Source:** `TouchManager.h` + +## Description + +Touch event aggregation layer + +This class is COMPLETELY INDEPENDENT of the touch adapter. +It receives normalized TouchPoints and provides: +- Circular buffer of recent touch points +- Active touch count query +- Coordinate mapping integration point for InputManager + +The pipeline is: +[XPT2046/GT911 Adapter] → [TouchManager] → [UI System] + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `constexpr` | `static` | Fixed-size circular buffer | + +## Methods + +### `bool init()` + +**Description:** + +Initialize touch system + +**Returns:** true if initialization successful + +### `void update(unsigned long dt)` + +**Description:** + +Update touch state - call this every frame + +**Parameters:** + +- `dt`: Delta time in milliseconds + +### `uint8_t getTouchPoints(TouchPoint* points) const` + +**Description:** + +Get all active touch points + +**Parameters:** + +- `points`: Output buffer + +**Returns:** Number of active touch points + +### `uint8_t getActiveCount() const` + +**Description:** + +Get number of active touch points + +**Returns:** Count of currently pressed touches + +### `bool isTouchActive() const` + +**Description:** + +Check if any touch is active + +**Returns:** true if any touch is pressed + +### `const TouchPoint& getTouchPoint(uint8_t index) const` + +**Description:** + +Get touch point at specific index + +**Parameters:** + +- `index`: Index (0 to getActiveCount() - 1) + +**Returns:** TouchPoint reference + +### `bool isTouchedInArea(int16_t x, int16_t y, int16_t radius) const` + +**Description:** + +Check if specific area is touched + +**Parameters:** + +- `x`: X coordinate +- `y`: Y coordinate +- `radius`: Touch hit radius + +**Returns:** true if touch detected in area + +### `void setCalibration(const TouchCalibration& calib)` + +**Description:** + +Set calibration parameters + +**Parameters:** + +- `calib`: Calibration data + +### `bool isConnected() const` + +**Description:** + +Check if touch controller is connected + +**Returns:** true if responding + +### `void addTouchPoint(const TouchPoint& point)` + +**Description:** + +Add touch point to circular buffer + +**Parameters:** + +- `point`: Touch point to add + +### `void clearBuffer()` + +**Description:** + +Clear all touch points + +### `static int16_t distance(int16_t x1, int16_t y1, int16_t x2, int16_t y2)` + +**Description:** + +Calculate distance between two points + +**Returns:** Distance in pixels diff --git a/api/generated/input/TouchPoint.md b/api/generated/input/TouchPoint.md new file mode 100644 index 0000000..0024ffc --- /dev/null +++ b/api/generated/input/TouchPoint.md @@ -0,0 +1,51 @@ +# TouchPoint + + + +**Source:** `TouchPoint.h` + +## Description + +Normalized touch data structure. + +This struct is the CONTRACT between TouchAdapter and the engine. +It MUST remain unchanged regardless of the underlying touch controller. + +Invariants: +- Coordinates always valid: 0 <= x <= W, 0 <= y <= H +- No extreme noise (filtered by adapter) +- pressed state consistent +- timestamps monotonic + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `x` | `int16_t` | X coordinate (0 = left edge) | +| `y` | `int16_t` | Y coordinate (0 = top edge) | +| `pressed` | `bool` | True if touch is active | +| `id` | `uint8_t` | Touch ID (0 for single-touch, 0-4 for multi-touch) | +| `ts` | `uint32_t` | Timestamp in milliseconds | + +## Methods + +### `bool isValid(int16_t maxX, int16_t maxY) const` + +**Description:** + +Check if this touch point is valid (within bounds) + +**Parameters:** + +- `maxX`: Maximum X value (display width - 1) +- `maxY`: Maximum Y value (display height - 1) + +**Returns:** true if coordinates are in valid range + +### `bool isEmpty() const` + +**Description:** + +Check if this is a null/empty touch point + +**Returns:** true if not pressed diff --git a/api/generated/input/TouchPointBuffer.md b/api/generated/input/TouchPointBuffer.md new file mode 100644 index 0000000..cfa3345 --- /dev/null +++ b/api/generated/input/TouchPointBuffer.md @@ -0,0 +1,111 @@ +# TouchPointBuffer + + + +**Source:** `TouchPointBuffer.h` + +## Description + +Ring buffer for storing touch points + +Implements a fixed-size circular buffer with: +- O(1) push and pop operations +- No dynamic memory allocation +- Thread-safe (single consumer assumed) + +Invariants: +- count <= TOUCH_MAX_POINTS +- head always points to next write position +- oldest point is at (head + 1) % TOUCH_MAX_POINTS when count > 0 + +## Methods + +### `bool push(const TouchPoint& point)` + +**Description:** + +Push a touch point to the buffer + +**Parameters:** + +- `point`: Touch point to add + +**Returns:** true if added successfully + +### `bool pop(TouchPoint& outPoint)` + +**Description:** + +Pop the oldest touch point + +**Parameters:** + +- `outPoint`: Output for popped point + +**Returns:** true if point was available + +### `const TouchPoint* peekOldest() const` + +**Description:** + +Peek at oldest point without removing + +**Returns:** Pointer to oldest point, nullptr if empty + +### `const TouchPoint* peekNewest() const` + +**Description:** + +Peek at newest point without removing + +**Returns:** Pointer to newest point, nullptr if empty + +### `const TouchPoint* at(uint8_t index) const` + +**Description:** + +Get point at specific index + +**Parameters:** + +- `index`: Index (0 = oldest, count-1 = newest) + +**Returns:** Pointer to point, nullptr if index out of range + +### `void clear()` + +**Description:** + +Clear all points from buffer + +### `uint8_t count() const` + +**Description:** + +Get current count of points in buffer + +**Returns:** Number of points + +### `bool isEmpty() const` + +**Description:** + +Check if buffer is empty + +**Returns:** true if no points + +### `bool isFull() const` + +**Description:** + +Check if buffer is full + +**Returns:** true if no more points can be added + +### `constexpr uint8_t capacity() const` + +**Description:** + +Get capacity of buffer + +**Returns:** Maximum number of points diff --git a/api/generated/input/TouchRotation.md b/api/generated/input/TouchRotation.md new file mode 100644 index 0000000..4aa84dc --- /dev/null +++ b/api/generated/input/TouchRotation.md @@ -0,0 +1,9 @@ +# TouchRotation + + + +**Source:** `TouchAdapter.h` + +## Description + +Display rotation modes for calibration diff --git a/api/generated/input/TouchState.md b/api/generated/input/TouchState.md new file mode 100644 index 0000000..fbd5bb0 --- /dev/null +++ b/api/generated/input/TouchState.md @@ -0,0 +1,9 @@ +# TouchState + + + +**Source:** `TouchStateMachine.h` + +## Description + +Internal states for touch gesture detection diff --git a/api/generated/input/TouchStateData.md b/api/generated/input/TouchStateData.md new file mode 100644 index 0000000..7c3a62a --- /dev/null +++ b/api/generated/input/TouchStateData.md @@ -0,0 +1,38 @@ +# TouchStateData + + + +**Source:** `TouchStateMachine.h` + +## Description + +Per-touch-ID state tracking + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `state` | `TouchState` | Current state | +| `pressTime` | `uint32_t` | When press started (ms) | +| `pressX` | `int16_t` | X position at press | +| `pressY` | `int16_t` | Y position at press | +| `lastX` | `int16_t` | Last known X position | +| `lastY` | `int16_t` | Last known Y position | +| `longPressFired` | `bool` | Long press already fired | +| `dragStarted` | `bool` | Drag already started | + +## Methods + +### `, pressTime(0)` + +### `, pressX(0)` + +### `, pressY(0)` + +### `, lastX(0)` + +### `, lastY(0)` + +### `, longPressFired(false)` + +### `, dragStarted(false)` diff --git a/api/generated/input/TouchStateMachine.md b/api/generated/input/TouchStateMachine.md new file mode 100644 index 0000000..e23d952 --- /dev/null +++ b/api/generated/input/TouchStateMachine.md @@ -0,0 +1,82 @@ +# TouchStateMachine + + + +**Source:** `TouchStateMachine.h` + +## Description + +State machine for touch gesture detection + +State transitions: +Idle → Pressed (on touch down) +Pressed → LongPress (after LONG_PRESS_THRESHOLD without release) +Pressed → Dragging (after DRAG_THRESHOLD movement) +Pressed → Idle (on touch up → generate Click/DoubleClick) +LongPress → Idle (on touch up) +Dragging → Idle (on touch up → generate DragEnd) + +O(1) update - deterministic timing + +## Methods + +### `void reset(uint8_t touchId)` + +**Description:** + +Reset state for a specific touch ID + +**Parameters:** + +- `touchId`: Touch identifier to reset + +### `void resetAll()` + +**Description:** + +Reset all touch states + +### `TouchState getState(uint8_t touchId) const` + +**Description:** + +Get current state for a touch ID + +**Parameters:** + +- `touchId`: Touch identifier + +**Returns:** Current state + +### `bool isActive() const` + +**Description:** + +Check if a touch is in progress + +**Returns:** true if any touch is active + +### `uint32_t getPressDuration(uint8_t touchId, uint32_t currentTime) const` + +**Description:** + +Get time since press for a touch + +**Parameters:** + +- `touchId`: Touch identifier +- `currentTime`: Current timestamp in ms (same source as update()) + +**Returns:** Milliseconds since press started, 0 if not pressed + +### `static int16_t distance(int16_t x1, int16_t y1, int16_t x2, int16_t y2)` + +**Description:** + +Calculate Manhattan distance between two points + +### `void checkLongPress(uint8_t touchId, uint32_t timestamp, TouchEventQueue& queue)` + +**Description:** + +Check and generate long press if needed diff --git a/api/generated/input/XPT2046Adapter.md b/api/generated/input/XPT2046Adapter.md new file mode 100644 index 0000000..4f3cbfc --- /dev/null +++ b/api/generated/input/XPT2046Adapter.md @@ -0,0 +1,121 @@ +# XPT2046Adapter + + + +**Source:** `XPT2046Adapter.h` + +## Description + +XPT2046 SPI touch controller driver + +Hardware: XPT2046 (common on TFT touchscreens) +Protocol: SPI — usually shares the TFT bus; ESP32-2432S028R uses a separate GPIO bit-bang bus + (build with -D XPT2046_USE_GPIO_SPI; pins overridable via XPT2046_GPIO_* macros). + Use -D XPT2046_GPIO_SWAP_AXES=1 when finger vertical/horizontal tracks the wrong screen axis. + Use -D XPT2046_GPIO_MIRROR_X=1 when left/right are inverted (horizontal flip in screen space). + GPIO path order: map → vendor swap → MIRROR_X → CAL_OFFSET_* → clamp (offsets = final nudge). +Sampling: Up to 125Hz +Filtering: Heavy (median + debounce + pressure threshold) + +SPI bus MUST be coordinated with display DMA +Requires display-specific calibration + +::: tip +SPI bus MUST be coordinated with display DMA +::: + +::: tip +Requires display-specific calibration +::: + +## Methods + +### `static bool initImpl()` + +**Description:** + +Initialize XPT2046 hardware + +**Returns:** true if successful + +### `static bool readImpl(TouchPoint* points, uint8_t& count)` + +**Description:** + +Read touch data from XPT2046 + +**Parameters:** + +- `points`: Output buffer +- `count`: Number of points read + +**Returns:** true if successful + +### `static void setCalibrationImpl(const TouchCalibration& calib)` + +**Description:** + +Set calibration parameters + +**Parameters:** + +- `calib`: Calibration data + +### `static int16_t readRawX()` + +**Description:** + +Read raw X coordinate from XPT2046 + +**Returns:** Raw ADC value (0-4095) + +### `static int16_t readRawY()` + +**Description:** + +Read raw Y coordinate from XPT2046 + +**Returns:** Raw ADC value (0-4095) + +### `static uint16_t readPressure()` + +**Description:** + +Read pressure (Z) from XPT2046 + +**Returns:** Pressure value + +### `static int16_t medianFilter(int16_t value, bool isX)` + +**Description:** + +Median filter implementation + +**Parameters:** + +- `value`: New sample +- `isX`: True for X coordinate + +**Returns:** Filtered value + +### `static bool isConnectedImpl()` + +**Description:** + +Check if controller is connected + +**Returns:** true if responding + +### `static bool waitForDMADone()` + +**Description:** + +Wait for DMA operation to complete (SPI bus coordination) + +**Returns:** true if bus is available, false if DMA still active + +### `static uint32_t millis()` + +**Description:** + +Platform-specific millis() function diff --git a/api/generated/math/Fixed16.md b/api/generated/math/Fixed16.md new file mode 100644 index 0000000..598c94f --- /dev/null +++ b/api/generated/math/Fixed16.md @@ -0,0 +1,157 @@ +# Fixed16 + + + +**Source:** `Fixed16.h` + +## Description + +Fixed-point 16.16 number implementation optimized for RISC-V. + +Uses 32-bit integer storage: 16 bits for integer part, 16 bits for fractional part. +Designed for platforms without FPU (ESP32-C3, C2, C6). + +## Methods + +### `constexpr Fixed16()` + +**Description:** + +Default constructor (initializes to 0). + +### `constexpr explicit Fixed16(int32_t rawValue, bool /*isRaw*/)` + +**Description:** + +Raw value constructor. + +**Parameters:** + +- `rawValue`: The raw 32-bit representation. +- `isRaw`: Dummy parameter to distinguish from integer constructor. + +### `constexpr Fixed16(int v)` + +**Description:** + +Construct from integer. + +**Parameters:** + +- `v`: The integer value. + +### `constexpr Fixed16(float v)` + +**Description:** + +Construct from float. + +**Parameters:** + +- `v`: The float value. + +### `constexpr Fixed16(double v)` + +**Description:** + +Construct from double. + +**Parameters:** + +- `v`: The double value. + +### `static constexpr Fixed16 fromRaw(int32_t raw)` + +**Description:** + +Factory method to create a Fixed16 directly from a raw 32-bit value. + +**Parameters:** + +- `raw`: The raw 32-bit representation. + +**Returns:** The created Fixed16 instance. + +### `constexpr int toInt() const` + +**Description:** + +Converts to integer (truncating fractional part). + +**Returns:** The integer value. + +### `constexpr float toFloat() const` + +**Description:** + +Converts to float. + +**Returns:** The floating-point value. + +### `constexpr double toDouble() const` + +**Description:** + +Converts to double. + +**Returns:** The double-precision floating-point value. + +### `constexpr int roundToInt() const` + +**Description:** + +Rounds to nearest integer. + +**Returns:** The rounded integer value. + +### `constexpr int floorToInt() const` + +**Description:** + +Computes floor and returns as integer. + +**Returns:** The floor integer value. + +### `constexpr int ceilToInt() const` + +**Description:** + +Computes ceiling and returns as integer. + +**Returns:** The ceiling integer value. + +### `explicit constexpr operator int() const` + +**Description:** + +Cast to int. + +**Returns:** The truncated integer value. + +### `explicit constexpr operator float() const` + +**Description:** + +Cast to float. + +**Returns:** The floating-point value. + +### `explicit constexpr operator double() const` + +**Description:** + +Cast to double. + +**Returns:** The double value. + +### `static Fixed16 sqrt(Fixed16 x)` + +**Description:** + +Computes the square root. + +**Parameters:** + +- `x`: The value to compute square root for. + +**Returns:** The square root of x. diff --git a/api/generated/math/Vector2.md b/api/generated/math/Vector2.md new file mode 100644 index 0000000..855de00 --- /dev/null +++ b/api/generated/math/Vector2.md @@ -0,0 +1,363 @@ +# Vector2 + + + +**Source:** `Vector2.h` + +## Description + +2D vector using the configured Scalar type (float or Fixed16). + +Automatically adapts to the architecture's FPU availability. + +## Methods + +### `constexpr Vector2()` + +**Description:** + +Default constructor, initializes to (0, 0). + +### `constexpr Vector2(Scalar _x, Scalar _y)` + +**Description:** + +Constructor with given x and y components. + +**Parameters:** + +- `_x`: X component. +- `_y`: Y component. + +### `constexpr Vector2(const Vector2& other)` + +**Description:** + +Copy constructor. + +**Parameters:** + +- `other`: The other vector to copy from. + +### `constexpr Vector2(int _x, int _y)` + +**Description:** + +Constructor with integer components. + +**Parameters:** + +- `_x`: X component. +- `_y`: Y component. + +### `static constexpr Vector2 ZERO()` + +**Description:** + +Returns vector (0, 0). @return (0, 0) vector. + +**Returns:** (0, 0) vector. + +### `static constexpr Vector2 ONE()` + +**Description:** + +Returns vector (1, 1). @return (1, 1) vector. + +**Returns:** (1, 1) vector. + +### `static constexpr Vector2 UP()` + +**Description:** + +Returns vector (0, -1). @return (0, -1) vector. + +**Returns:** (0, -1) vector. + +### `static constexpr Vector2 DOWN()` + +**Description:** + +Returns vector (0, 1). @return (0, 1) vector. + +**Returns:** (0, 1) vector. + +### `static constexpr Vector2 LEFT()` + +**Description:** + +Returns vector (-1, 0). @return (-1, 0) vector. + +**Returns:** (-1, 0) vector. + +### `static constexpr Vector2 RIGHT()` + +**Description:** + +Returns vector (1, 0). @return (1, 0) vector. + +**Returns:** (1, 0) vector. + +### `constexpr Scalar lengthSquared() const` + +**Description:** + +Computes squared length. @return Squared length of the vector. + +**Returns:** Squared length of the vector. + +### `inline Scalar length() const` + +**Description:** + +Computes length (magnitude). @return Length of the vector. + +**Returns:** Length of the vector. + +### `inline void normalize()` + +**Description:** + +Normalizes the vector in place. + +### `inline Vector2 normalized() const` + +**Description:** + +Returns a normalized copy. @return Normalized vector. + +**Returns:** Normalized vector. + +### `inline Scalar dot(const Vector2& other) const` + +**Description:** + +Dot product with another vector. @param other Vector to compute dot product with. @return Dot product result. + +**Parameters:** + +- `other`: Vector to compute dot product with. + +**Returns:** Dot product result. + +### `inline Scalar cross(const Vector2& other) const` + +**Description:** + +2D Cross product with another vector. @param other Vector to compute cross product with. @return Cross product result. + +**Parameters:** + +- `other`: Vector to compute cross product with. + +**Returns:** Cross product result. + +### `inline Scalar angle() const` + +**Description:** + +Angle of the vector. @return Angle in radians. + +**Returns:** Angle in radians. + +### `inline Scalar angle_to(const Vector2& to) const` + +**Description:** + +Angle to another vector. @param to The target vector. @return Angle difference in radians. + +**Parameters:** + +- `to`: The target vector. + +**Returns:** Angle difference in radians. + +### `inline Scalar angle_to_point(const Vector2& to) const` + +**Description:** + +Angle to a point. @param to Target point. @return Angle in radians. + +**Parameters:** + +- `to`: Target point. + +**Returns:** Angle in radians. + +### `inline Vector2 direction_to(const Vector2& to) const` + +**Description:** + +Direction to another point. @param to Target point. @return Normalized direction vector. + +**Parameters:** + +- `to`: Target point. + +**Returns:** Normalized direction vector. + +### `Vector2 ret(to.x - x, to.y - y)` + +### `inline Scalar distance_to(const Vector2& to) const` + +**Description:** + +Distance to another point. @param to Target point. @return Distance. + +**Parameters:** + +- `to`: Target point. + +**Returns:** Distance. + +### `inline Scalar distance_squared_to(const Vector2& to) const` + +**Description:** + +Squared distance to another point. @param to Target point. @return Squared distance. + +**Parameters:** + +- `to`: Target point. + +**Returns:** Squared distance. + +### `inline Vector2 limit_length(Scalar max_len = toScalar(1)) const` + +**Description:** + +Returns vector with length limited. @param max_len Maximum allowed length. @return Resulting vector. + +**Parameters:** + +- `max_len`: Maximum allowed length. + +**Returns:** Resulting vector. + +### `inline Vector2 clamp(Vector2 min, Vector2 max) const` + +**Description:** + +Clamps components between bounds. @param min Minimum bounds. @param max Maximum bounds. @return Clamped vector. + +**Parameters:** + +- `min`: Minimum bounds. + +**Returns:** Clamped vector. + +### `inline Vector2 lerp(const Vector2& to, Scalar weight) const` + +**Description:** + +Linearly interpolates towards another vector. @param to Target vector. @param weight Interpolation weight. @return Interpolated vector. + +**Parameters:** + +- `to`: Target vector. + +**Returns:** Interpolated vector. + +### `inline Vector2 rotated(Scalar phi) const` + +**Description:** + +Rotates vector by an angle. @param phi Angle in radians. @return Rotated vector. + +**Parameters:** + +- `phi`: Angle in radians. + +**Returns:** Rotated vector. + +### `inline Vector2 move_toward(const Vector2& to, Scalar delta) const` + +**Description:** + +Moves toward another point by a delta. @param to Target point. @param delta Step amount. @return Resulting vector. + +**Parameters:** + +- `to`: Target point. + +**Returns:** Resulting vector. + +### `inline Vector2 slide(const Vector2& n) const` + +**Description:** + +Slides vector along a surface normal. @param n Surface normal. @return Slid vector. + +**Parameters:** + +- `n`: Surface normal. + +**Returns:** Slid vector. + +### `inline Vector2 reflect(const Vector2& n) const` + +**Description:** + +Reflects vector across a surface normal. @param n Surface normal. @return Reflected vector. + +**Parameters:** + +- `n`: Surface normal. + +**Returns:** Reflected vector. + +### `inline Vector2 project(const Vector2& b) const` + +**Description:** + +Projects vector onto another. @param b Vector to project onto. @return Projected vector. + +**Parameters:** + +- `b`: Vector to project onto. + +**Returns:** Projected vector. + +### `inline Vector2 abs() const` + +**Description:** + +Returns absolute values of components. @return Absolute vector. + +**Returns:** Absolute vector. + +### `inline Vector2 sign() const` + +**Description:** + +Returns signs of components. @return Sign vector. + +**Returns:** Sign vector. + +### `inline bool is_normalized() const` + +**Description:** + +Checks if vector is normalized. @return True if approximately normalized. + +**Returns:** True if approximately normalized. + +### `inline bool is_zero_approx() const` + +**Description:** + +Checks if vector is near zero. @return True if approximately zero. + +**Returns:** True if approximately zero. + +### `inline bool is_equal_approx(const Vector2& other) const` + +**Description:** + +Checks if approximately equal to another. @param other Vector to compare. @return True if approximately equal. + +**Parameters:** + +- `other`: Vector to compare. + +**Returns:** True if approximately equal. diff --git a/api/generated/physics/Circle.md b/api/generated/physics/Circle.md new file mode 100644 index 0000000..7120df5 --- /dev/null +++ b/api/generated/physics/Circle.md @@ -0,0 +1,17 @@ +# Circle + + + +**Source:** `CollisionTypes.h` + +## Description + +Represents a 2D circle for collision detection. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `x` | `pixelroot32::math::Scalar` | Center X coordinate. | +| `y` | `pixelroot32::math::Scalar` | Center Y coordinate. | +| `radius` | `pixelroot32::math::Scalar` | Radius of the circle. | diff --git a/api/generated/physics/CollisionSystem.md b/api/generated/physics/CollisionSystem.md new file mode 100644 index 0000000..14b9ade --- /dev/null +++ b/api/generated/physics/CollisionSystem.md @@ -0,0 +1,61 @@ +# CollisionSystem + + + +**Source:** `CollisionSystem.h` + +## Description + +Manages physics simulation and collision detection for all actors. + +## Methods + +### `void update()` + +**Description:** + +Performs one complete physics update step. + +### `void detectCollisions()` + +**Description:** + +Detects collisions between all registered bodies. + +### `void solveVelocity()` + +**Description:** + +Solves velocities for all contacts. + +### `void integratePositions()` + +**Description:** + +Integrates positions for all dynamic bodies. + +### `void solvePenetration()` + +**Description:** + +Solves penetration to separate overlapping bodies. + +### `void triggerCallbacks()` + +**Description:** + +Triggers collision callbacks for all valid contacts. + +### `size_t getEntityCount() const` + +**Description:** + +Gets the total number of registered entities. + +**Returns:** Number of entities. + +### `void clear()` + +**Description:** + +Clears the collision system state. diff --git a/api/generated/physics/Contact.md b/api/generated/physics/Contact.md new file mode 100644 index 0000000..dad3959 --- /dev/null +++ b/api/generated/physics/Contact.md @@ -0,0 +1,21 @@ +# Contact + + + +**Source:** `CollisionSystem.h` + +## Description + +Represents a contact point between two physics bodies. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `bodyA` | `pixelroot32::core::PhysicsActor*` | First body in the contact. | +| `bodyB` | `pixelroot32::core::PhysicsActor*` | Second body in the contact. | +| `normal` | `pixelroot32::math::Vector2` | Contact normal vector. | +| `contactPoint` | `pixelroot32::math::Vector2` | Point of contact. | +| `penetration` | `pixelroot32::math::Scalar` | Penetration depth. | +| `restitution` | `pixelroot32::math::Scalar` | Combined restitution coefficient. | +| `isSensorContact` | `bool` | True if either body is a sensor; no physics response applied. | diff --git a/api/generated/physics/KinematicActor.md b/api/generated/physics/KinematicActor.md new file mode 100644 index 0000000..324f865 --- /dev/null +++ b/api/generated/physics/KinematicActor.md @@ -0,0 +1,39 @@ +# KinematicActor + + + +**Source:** `KinematicActor.h` + +**Inherits from:** [PhysicsActor](../core/PhysicsActor.md) + +## Description + +A physics body moved via script/manual velocity with collision detection. + +Kinematic actors are not affected by world gravity or forces but can detect +and react to collisions during movement. They provide methods like +moveAndSlide for complex character movement. + +## Inheritance + +[PhysicsActor](../core/PhysicsActor.md) → `KinematicActor` + +## Methods + +### `inline bool is_on_ceiling() const` + +**Description:** + +Returns true if the body collided with the ceiling. + +### `inline bool is_on_floor() const` + +**Description:** + +Returns true if the body collided with the floor. + +### `inline bool is_on_wall() const` + +**Description:** + +Returns true if the body collided with a wall. diff --git a/api/generated/physics/KinematicCollision.md b/api/generated/physics/KinematicCollision.md new file mode 100644 index 0000000..8158636 --- /dev/null +++ b/api/generated/physics/KinematicCollision.md @@ -0,0 +1,19 @@ +# KinematicCollision + + + +**Source:** `CollisionSystem.h` + +## Description + +Contains information about a collision involving a KinematicActor. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `collider` | `pixelroot32::core::Actor*` | The other actor involved in the collision. | +| `normal` | `pixelroot32::math::Vector2` | The collision normal vector. | +| `position` | `pixelroot32::math::Vector2` | The position of the collision. | +| `travel` | `pixelroot32::math::Scalar` | Distance traveled before collision. | +| `remainder` | `pixelroot32::math::Scalar` | Remaining distance to travel. | diff --git a/api/generated/physics/RigidActor.md b/api/generated/physics/RigidActor.md new file mode 100644 index 0000000..0f022c7 --- /dev/null +++ b/api/generated/physics/RigidActor.md @@ -0,0 +1,18 @@ +# RigidActor + + + +**Source:** `RigidActor.h` + +**Inherits from:** [PhysicsActor](../core/PhysicsActor.md) + +## Description + +A physics body fully simulated by the engine. + +Rigid actors respond to gravity, forces, and impulses. They are used for +dynamic objects that should behave naturally, like falling crates or debris. + +## Inheritance + +[PhysicsActor](../core/PhysicsActor.md) → `RigidActor` diff --git a/api/generated/physics/Segment.md b/api/generated/physics/Segment.md new file mode 100644 index 0000000..f6baf98 --- /dev/null +++ b/api/generated/physics/Segment.md @@ -0,0 +1,33 @@ +# Segment + + + +**Source:** `CollisionTypes.h` + +## Description + +Represents a 2D line segment for collision detection. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `x1` | `pixelroot32::math::Scalar` | Start X coordinate. | +| `y1` | `pixelroot32::math::Scalar` | Start Y coordinate. | +| `x2` | `pixelroot32::math::Scalar` | End X coordinate. | +| `y2` | `pixelroot32::math::Scalar` | End Y coordinate. | + +## Methods + +### `bool intersects(const Circle& a, const Circle& b)` + +**Description:** + +Checks intersection between two circles. + +**Parameters:** + +- `a`: First circle. +- `b`: Second circle. + +**Returns:** True if circles intersect. diff --git a/api/generated/physics/SensorActor.md b/api/generated/physics/SensorActor.md new file mode 100644 index 0000000..cde2299 --- /dev/null +++ b/api/generated/physics/SensorActor.md @@ -0,0 +1,18 @@ +# SensorActor + + + +**Source:** `SensorActor.h` + +**Inherits from:** [StaticActor](./StaticActor.md) + +## Description + +A static body that acts as a trigger: detects overlap but produces no physical response. + +Use for collectibles, checkpoints, damage zones, or any area that should fire +onCollision() without pushing or blocking the other body. + +## Inheritance + +[StaticActor](./StaticActor.md) → `SensorActor` diff --git a/api/generated/physics/SpatialGrid.md b/api/generated/physics/SpatialGrid.md new file mode 100644 index 0000000..52016a1 --- /dev/null +++ b/api/generated/physics/SpatialGrid.md @@ -0,0 +1,33 @@ +# SpatialGrid + + + +**Source:** `SpatialGrid.h` + +## Description + +Optimized spatial partitioning with separate static/dynamic layers. + +Static layer: built once per level (or when entities change), not cleared each frame. +Dynamic layer: cleared and refilled every frame (RIGID, KINEMATIC). +Reduces per-frame cost when many static tiles are present. + +## Methods + +### `void clearDynamic()` + +**Description:** + +Clears all dynamic entities from the grid. + +### `void clear()` + +**Description:** + +Clears all entities (static and dynamic) from the grid. + +### `void markStaticDirty()` + +**Description:** + +Marks the static layer as dirty, requiring a rebuild. diff --git a/api/generated/physics/StaticActor.md b/api/generated/physics/StaticActor.md new file mode 100644 index 0000000..2fce15f --- /dev/null +++ b/api/generated/physics/StaticActor.md @@ -0,0 +1,19 @@ +# StaticActor + + + +**Source:** `StaticActor.h` + +**Inherits from:** [PhysicsActor](../core/PhysicsActor.md) + +## Description + +A physics body that does not move. + +Static actors are used for environment elements like floors, walls, and platforms +that should block other actors but are themselves immovable. +They are optimized to skip integration and world bound resolution. + +## Inheritance + +[PhysicsActor](../core/PhysicsActor.md) → `StaticActor` diff --git a/api/generated/physics/TileBehaviorLayer.md b/api/generated/physics/TileBehaviorLayer.md new file mode 100644 index 0000000..78028b6 --- /dev/null +++ b/api/generated/physics/TileBehaviorLayer.md @@ -0,0 +1,36 @@ +# TileBehaviorLayer + + + +**Source:** `TileAttributes.h` + +## Description + +Runtime representation of exported behavior layer for O(1) flag lookup. + +This structure matches the format exported by the Tilemap Editor +and provides efficient access to tile behavior flags without runtime strings. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `uint8_t` | `const` | Pointer to dense uint8_t array (1 byte per tile) | +| `width` | `uint16_t` | Layer width in tiles | +| `height` | `uint16_t` | Layer height in tiles | + +## Methods + +### `inline uint8_t getTileFlags(const TileBehaviorLayer& layer, int x, int y)` + +**Description:** + +Get TileFlags for a specific tile position in a behavior layer. + +**Parameters:** + +- `layer`: Behavior layer structure containing the data +- `x`: X coordinate in tiles +- `y`: Y coordinate in tiles + +**Returns:** TileFlags combination (0 = TILE_NONE if out of bounds) diff --git a/api/generated/physics/TileCollisionBehavior.md b/api/generated/physics/TileCollisionBehavior.md new file mode 100644 index 0000000..285ef4a --- /dev/null +++ b/api/generated/physics/TileCollisionBehavior.md @@ -0,0 +1,118 @@ +# TileCollisionBehavior + + + +**Source:** `TileAttributes.h` + +## Description + +Defines how a tile collider behaves in the physics system. + +Use TileFlags for new implementations. Kept for backward compatibility. + +## Methods + +### `inline uintptr_t packTileData(uint16_t x, uint16_t y, TileFlags flags)` + +**Description:** + +Pack tile coordinates and TileFlags into a single value for userData. +Encoding: bits [0-9]=x, [10-19]=y, [20-27]=flags. Max 1024x1024 tiles. + +**Parameters:** + +- `x`: Tile X (0..1023). +- `y`: Tile Y (0..1023). +- `flags`: TileFlags combination. + +**Returns:** Packed value to store via setUserData(reinterpret_cast(packed)). + +### `inline void unpackTileData(uintptr_t packed, uint16_t& x, uint16_t& y, TileFlags& flags)` + +**Description:** + +Unpack tile data from userData with TileFlags. + +**Parameters:** + +- `packed`: Packed value from userData. +- `x`: Output tile X coordinate. +- `y`: Output tile Y coordinate. +- `flags`: Output TileFlags combination. + +### `inline uintptr_t packTileData(uint16_t x, uint16_t y, TileCollisionBehavior behavior)` + +**Description:** + +Pack tile coordinates and TileCollisionBehavior into a single value for userData. +Encoding: bits [0-9]=x, [10-19]=y, [20-23]=behavior. Max 1024x1024 tiles. + +**Parameters:** + +- `x`: Tile X (0..1023). +- `y`: Tile Y (0..1023). +- `behavior`: TileCollisionBehavior. + +**Returns:** Packed value to store via setUserData(reinterpret_cast(packed)). + +### `inline void unpackTileData(uintptr_t packed, uint16_t& x, uint16_t& y, TileCollisionBehavior& behavior)` + +**Description:** + +Unpack tile data from userData with TileCollisionBehavior. + +**Parameters:** + +- `packed`: Packed value from userData. +- `x`: Output tile X coordinate. +- `y`: Output tile Y coordinate. +- `behavior`: Output TileCollisionBehavior. + +### `inline bool isSensorTile(TileFlags flags)` + +**Description:** + +Helper to derive sensor flag from TileFlags combination. + +**Parameters:** + +- `flags`: TileFlags combination. + +**Returns:** true if tile should be configured as sensor. + +### `inline bool isOneWayTile(TileFlags flags)` + +**Description:** + +Helper to derive one-way flag from TileFlags combination. + +**Parameters:** + +- `flags`: TileFlags combination. + +**Returns:** true if tile should be configured as one-way platform. + +### `inline bool isSolidTile(TileFlags flags)` + +**Description:** + +Helper to derive solid flag from TileFlags combination. + +**Parameters:** + +- `flags`: TileFlags combination. + +**Returns:** true if tile should be configured as solid body. + +### `inline uintptr_t packCoord(uint16_t x, uint16_t y)` + +**Description:** + +Legacy: pack only coordinates (16+16 bits). Compatible with existing userData usage. +Use when TileCollisionBehavior is not needed (max 65535x65535 tiles). + +### `inline void unpackCoord(uintptr_t packed, uint16_t& x, uint16_t& y)` + +**Description:** + +Legacy: unpack coordinates from legacy encoding. diff --git a/api/generated/physics/TileCollisionBuilder.md b/api/generated/physics/TileCollisionBuilder.md new file mode 100644 index 0000000..1a1eef2 --- /dev/null +++ b/api/generated/physics/TileCollisionBuilder.md @@ -0,0 +1,52 @@ +# TileCollisionBuilder + + + +**Source:** `TileCollisionBuilder.h` + +## Description + +Helper class for creating physics bodies from exported behavior layers. + +This builder follows the exact plan specification: +- Iterates behavior layers looking for non-TILE_NONE flags +- Creates StaticActor or SensorActor based on TileFlags +- Configures sensor/one-way properties from flags +- Packs tile coordinates and flags into userData for gameplay callbacks +- Registers bodies with the scene's entity system and collision system + +Usage: +```cpp +TileBehaviorLayer layer = { behaviorData, 32, 32 }; +TileCollisionBuilder builder(scene, config); +int entitiesCreated = builder.buildFromBehaviorLayer(layer, 0); +``` + +## Methods + +### `int buildFromBehaviorLayer(const TileBehaviorLayer& layer, uint8_t layerIndex = 0)` + +**Description:** + +Creates physics bodies from a behavior layer. + +**Parameters:** + +- `layer`: Behavior layer containing tile flags +- `layerIndex`: Index of the layer (for debugging/logging) + +**Returns:** Number of entities created, or -1 if entity limit was exceeded + +### `int getEntitiesCreated() const` + +**Description:** + +Gets the number of entities created by this builder. + +**Returns:** Entity count + +### `void reset()` + +**Description:** + +Resets the entity counter. diff --git a/api/generated/physics/TileCollisionBuilderConfig.md b/api/generated/physics/TileCollisionBuilderConfig.md new file mode 100644 index 0000000..c7dcb19 --- /dev/null +++ b/api/generated/physics/TileCollisionBuilderConfig.md @@ -0,0 +1,17 @@ +# TileCollisionBuilderConfig + + + +**Source:** `TileCollisionBuilder.h` + +## Description + +Configuration for tile collision building. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `tileWidth` | `uint8_t` | Width of each tile in world units | +| `tileHeight` | `uint8_t` | Height of each tile in world units | +| `maxEntities` | `uint16_t` | Maximum entities to create (safety limit) | diff --git a/api/generated/physics/TileConsumptionConfig.md b/api/generated/physics/TileConsumptionConfig.md new file mode 100644 index 0000000..d256f01 --- /dev/null +++ b/api/generated/physics/TileConsumptionConfig.md @@ -0,0 +1,17 @@ +# TileConsumptionConfig + + + +**Source:** `TileConsumptionHelper.h` + +## Description + +Configuration for tile consumption operations. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `updateTilemap` | `bool` | Update tilemap runtimeMask to hide consumed tiles | +| `logConsumption` | `bool` | Log consumption events for debugging | +| `validateCoordinates` | `bool` | Validate tile coordinates before consumption | diff --git a/api/generated/physics/TileConsumptionHelper.md b/api/generated/physics/TileConsumptionHelper.md new file mode 100644 index 0000000..7f767b9 --- /dev/null +++ b/api/generated/physics/TileConsumptionHelper.md @@ -0,0 +1,88 @@ +# TileConsumptionHelper + + + +**Source:** `TileConsumptionHelper.h` + +## Description + +Helper class for consuming tiles (removing bodies and updating visuals). + +This class implements Phase 7 of the tile attribute system: +1. Remove tile body from Scene (CollisionSystem no longer considers it) +2. Update TileMapGeneric::runtimeMask to hide consumed tiles +3. Reuse existing runtimeMask instead of creating separate consumedMask + +Usage: +```cpp +TileConsumptionHelper helper(scene, tilemap, config); +bool consumed = helper.consumeTile(tileActor, tileX, tileY); +``` + +## Methods + +### `bool isTileConsumed(uint16_t tileX, uint16_t tileY) const` + +**Description:** + +Check if a tile has been consumed (hidden in tilemap). + +**Parameters:** + +- `tileX`: Tile X coordinate +- `tileY`: Tile Y coordinate + +**Returns:** true if tile is consumed (inactive), false if still visible + +### `bool restoreTile(uint16_t tileX, uint16_t tileY)` + +**Description:** + +Restore a consumed tile (for debugging or special game mechanics). + +**Parameters:** + +- `tileX`: Tile X coordinate +- `tileY`: Tile Y coordinate + +**Returns:** true if tile was restored, false if tile was not consumed + +### `void extractTilemapDimensions()` + +**Description:** + +Extract tilemap dimensions from the tilemap pointer. + +### `void updateTilemapRuntimeMask(uint16_t tileX, uint16_t tileY, bool active)` + +**Description:** + +Template method to update tilemap runtimeMask. + +### `bool checkTilemapRuntimeMask(uint16_t tileX, uint16_t tileY) const` + +**Description:** + +Template method to check tilemap runtimeMask state. + +### `bool validateCoordinates(uint16_t tileX, uint16_t tileY) const` + +**Description:** + +Validate tile coordinates against tilemap dimensions. + +### `TileConsumptionHelper helper(scene, tilemap, config)` + +**Description:** + +Convenience function for consuming tiles from collision callbacks. + +**Parameters:** + +- `tileActor`: Pointer to the tile physics actor +- `packedUserData`: Packed userData from tileActor +- `scene`: Reference to the scene +- `tilemap`: Pointer to the tilemap (TileMapGeneric*) +- `config`: Optional consumption configuration + +**Returns:** true if tile was consumed, false otherwise diff --git a/api/generated/physics/TileFlags.md b/api/generated/physics/TileFlags.md new file mode 100644 index 0000000..69d5044 --- /dev/null +++ b/api/generated/physics/TileFlags.md @@ -0,0 +1,10 @@ +# TileFlags + + + +**Source:** `TileAttributes.h` + +## Description + +Bit flags for tile behavior attributes (8-bit, 1 byte per tile). +Optimized for ESP32 runtime with bit operations only. diff --git a/api/generated/platforms/MockAudioBackend.md b/api/generated/platforms/MockAudioBackend.md new file mode 100644 index 0000000..3011ebe --- /dev/null +++ b/api/generated/platforms/MockAudioBackend.md @@ -0,0 +1,26 @@ +# MockAudioBackend + + + +**Source:** `MockAudioBackend.h` + +**Inherits from:** [AudioBackend](../audio/AudioBackend.md) + +## Description + +Mock implementation of AudioBackend for unit testing. + +This mock captures initialization state and provides test hooks +for verifying AudioScheduler and AudioEngine interactions. + +## Inheritance + +[AudioBackend](../audio/AudioBackend.md) → `MockAudioBackend` + +## Methods + +### `bool wasInitCalled() const` + +### `AudioEngine* getEngine() const` + +### `void reset()` diff --git a/api/generated/platforms/PlatformCapabilities.md b/api/generated/platforms/PlatformCapabilities.md new file mode 100644 index 0000000..80ab048 --- /dev/null +++ b/api/generated/platforms/PlatformCapabilities.md @@ -0,0 +1,22 @@ +# PlatformCapabilities + + + +**Source:** `PlatformCapabilities.h` + +## Description + +Represents the hardware capabilities of the current platform. + +This structure allows the engine to adapt to different hardware configurations +(e.g., single-core vs dual-core ESP32) without excessive #ifdefs. + +## Methods + +### `static PlatformCapabilities detect()` + +**Description:** + +Detects capabilities of the current platform. + +**Returns:** A populated PlatformCapabilities struct. diff --git a/api/generated/platforms/SPIClass.md b/api/generated/platforms/SPIClass.md new file mode 100644 index 0000000..b3883eb --- /dev/null +++ b/api/generated/platforms/SPIClass.md @@ -0,0 +1,24 @@ +# SPIClass + + + +**Source:** `MockSPI.h` + +## Description + +Mocks the Arduino SPI class for native platform. + +Provides a dummy implementation of SPI methods to allow compilation +of drivers that depend on SPI.h. + +## Methods + +### `void begin()` + +### `void end()` + +### `uint8_t transfer(uint8_t data)` + +**Description:** + +Mocks data transfer. Returns input data (loopback). diff --git a/api/generated/test/PhysicsSnapshot.md b/api/generated/test/PhysicsSnapshot.md new file mode 100644 index 0000000..e438373 --- /dev/null +++ b/api/generated/test/PhysicsSnapshot.md @@ -0,0 +1,9 @@ +# PhysicsSnapshot + + + +**Source:** `PhysicsTestSuite.h` + +## Description + +Captures physics state for determinism validation diff --git a/api/generated/test/PhysicsTestSuite.md b/api/generated/test/PhysicsTestSuite.md new file mode 100644 index 0000000..f70b89a --- /dev/null +++ b/api/generated/test/PhysicsTestSuite.md @@ -0,0 +1,23 @@ +# PhysicsTestSuite + + + +**Source:** `PhysicsTestSuite.h` + +## Description + +Comprehensive testing for Flat Solver + +## Methods + +### `static TestResult testDeterminism(int numRuns = 100, int numFrames = 600)` + +### `static TestResult testEnergyConservation(int numFrames = 1000)` + +### `static TestResult testStackingStability(int numBoxes = 10, int numFrames = 600)` + +### `static TestResult testBounceConsistency(int numBounces = 100)` + +### `static TestResult testPerformance(int numBodies = 20, int numFrames = 600)` + +### `static void runAllTests()` diff --git a/api/generated/test/StressTestScene.md b/api/generated/test/StressTestScene.md new file mode 100644 index 0000000..d52f4b7 --- /dev/null +++ b/api/generated/test/StressTestScene.md @@ -0,0 +1,21 @@ +# StressTestScene + + + +**Source:** `PhysicsTestSuite.h` + +**Inherits from:** [Scene](../core/Scene.md) + +## Description + +Scene for stress testing physics performance. + +## Inheritance + +[Scene](../core/Scene.md) → `StressTestScene` + +## Methods + +### `const Metrics& getMetrics() const` + +### `void resetMetrics()` diff --git a/api/graphics.md b/api/graphics.md new file mode 100644 index 0000000..c52b416 --- /dev/null +++ b/api/graphics.md @@ -0,0 +1,101 @@ +# API Reference: Graphics Module + +> **Source of truth:** +> - `include/graphics/Renderer.h` +> - `include/graphics/Camera2D.h` +> - `include/graphics/Color.h` +> - `include/graphics/Font.h`, `include/graphics/FontManager.h` +> - `include/graphics/StaticTilemapLayerCache.h` +> - `include/graphics/TileAnimation.h` +> - `include/graphics/DrawSurface.h`, `include/graphics/BaseDrawSurface.h` +> - `include/graphics/particles/*.h` + +## Overview + +This document covers the rendering system, sprites, tilemaps, colors, fonts, and particle system in PixelRoot32. The `Renderer` provides a unified high-level API for drawing shapes, text, and images, abstracting the underlying hardware implementation (such as `DrawSurface`). + +## Platform Optimizations (ESP32) + +The engine includes several low-level optimizations for the ESP32 platform to maximize performance: + +- **DMA Support**: Buffer transfers to the display are handled via DMA (`pushImageDMA`), allowing the CPU to process the next frame concurrently. +- **IRAM Execution**: Critical rendering functions (`drawPixel`, `drawSpriteInternal`, `resolveColor`, `drawTileMap`) run from internal RAM (`IRAM_ATTR`). +- **Palette Caching**: Tilemaps cache the resolved RGB565 LUT per tile. +- **Viewport Culling**: All tilemap rendering functions automatically skip tiles outside the screen boundaries. +- **Direct logical framebuffer**: The `TFT_eSPI` driver exposes an 8bpp sprite memory buffer, enabling `Renderer` to write packed 2bpp/4bpp pixels directly without virtual function overhead. + +### Multi-layer 4bpp tilemap framebuffer snapshot (`StaticTilemapLayerCache`) + +Avoids redrawing “static” **4bpp** tilemaps every frame. It caches the static group of tiles into an internal buffer and restores it via `memcpy` each frame, so only dynamic elements need redrawing until the camera moves. + +## Key Concepts + +### Camera2D + +Manages the viewport and scrolling of the game world. Handles coordinate transformations and target following with configurable dead zones. + +### Color and Palettes + +The engine supports indexed colors via the `Color` enumeration. +- **PaletteType**: `PR32`, `NES`, `GB`, `GBC`, `PICO8`. +- Supports single palette mode or dual palette mode (separate background and sprite palettes). + +### Font System + +Uses a native bitmap font system via 1bpp sprites (`struct Font`). +- **`FONT_5X7`**: A built-in 5x7 pixel bitmap font (ASCII 32-126). +- `FontManager` manages the active default font and text width calculations. + +### Sprite Structures + +- **`Sprite`**: Compact 1bpp monochrome bitmap descriptor. +- **`Sprite2bpp` / `Sprite4bpp`**: Packed multi-color sprites with a local palette (requires compile flags). +- **`SpriteLayer` / `MultiSprite`**: Layered multi-color sprites built from monochrome layers. + +### TileMaps + +Generic descriptors for tile-based backgrounds, instantiated as `TileMap` (1bpp), `TileMap2bpp`, or `TileMap4bpp`. +- **Tilemap rendering notes**: `drawTileMap` always applies viewport culling. For static 4bpp reuse, see `StaticTilemapLayerCache`. + +### Tile Animation System + +Enables frame-based tile animations (water, lava, fire) while maintaining static tilemap data. Pacing uses high-resolution wall time so animations stay correct regardless of frame skipping. +- A `TileAnimationManager` computes the `VisualSignature` to inform the scene if a redraw is necessary. + +### Tile Attribute System + +Provides runtime access to custom metadata attached to tiles. Attributes are stored in Flash memory on the ESP32. + +> [!IMPORTANT] +> Since attributes are stored in Flash memory on ESP32, you must use **`PIXELROOT32_STRCMP_P`** or **`PIXELROOT32_MEMCPY_P`** to compare or copy the returned values. + +### Particle System + +*(Requires `PIXELROOT32_ENABLE_PARTICLES=1`)* +Provides lightweight visual effects using a fixed-size pool of `Particle` objects. Managed by a `ParticleEmitter` entity. +- **ParticlePresets**: Predefined configs (`Fire`, `Explosion`, `Sparks`, `Smoke`, `Dust`). + +### SpriteAnimation + +Lightweight, step-based animation controller for advancing through an array of `SpriteAnimationFrame`. + +### DrawSurface / BaseDrawSurface + +Abstract interfaces for platform-specific drawing operations (e.g., `SDL2_Drawer`, `TFT_eSPI_Drawer`). + +## Related Types + +- `Renderer` → `include/graphics/Renderer.h` +- `Camera2D` → `include/graphics/Camera2D.h` +- `Color`, `PaletteType` → `include/graphics/Color.h` +- `Font`, `FontManager` → `include/graphics/FontManager.h` +- `Sprite`, `TileMap` → `include/graphics/Renderer.h` +- `ParticleEmitter`, `ParticleConfig` → `include/graphics/particles/ParticleEmitter.h` +- `TileAnimationManager` → `include/graphics/TileAnimation.h` + +## Related Documentation + +- [API Reference](index.md) - Main index +- [Core Module](core.md) - Engine, Entity, Scene +- [Physics Module](physics.md) - Collision system +- [UI Module](ui.md) - User interface system \ No newline at end of file diff --git a/api/graphics/color.md b/api/graphics/color.md deleted file mode 100644 index 79dc707..0000000 --- a/api/graphics/color.md +++ /dev/null @@ -1,83 +0,0 @@ -# Color - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Color - -**Inherits:** None - -The `Color` module manages the engine's color palettes and provides the `Color` enumeration for referencing colors within the active palette. - -### PaletteType (Enum) - -Each built-in palette has a **RGB565 swatch strip** in the site’s `palettes/` folder (same order as in-engine tables). - -#### `PR32` (default) - -The standard PixelRoot32 palette. - -![PR32 palette swatches](../../public/palettes/palette_PR32.png) - -#### `NES` - -Nintendo Entertainment System inspired palette. - -![NES palette swatches](../../public/palettes/palette_NES.png) - -#### `GB` - -Game Boy inspired palette (4 greens). - -![Game Boy palette swatches](../../public/palettes/palette_GB.png) - -#### `GBC` - -Game Boy Color inspired palette. - -![Game Boy Color palette swatches](../../public/palettes/palette_GBC.png) - -#### `PICO8` - -PICO-8 fantasy console palette. - -![PICO-8 palette swatches](../../public/palettes/palette_PICO8.png) - -### Public Methods - -- **`static void setPalette(PaletteType type)`** - Sets the active color palette for the engine (Single Palette Mode). - -- **`static void setCustomPalette(const uint16_t* palette)`** - Sets a custom color palette defined by the user. - -- **`static void enableDualPaletteMode(bool enable)`** - Enables or disables dual palette mode. - -- **`static void setBackgroundPalette(PaletteType palette)`** - Sets the background palette (for backgrounds, tilemaps, etc.). - -- **`static void setSpritePalette(PaletteType palette)`** - Sets the sprite palette (for sprites, characters, etc.). - -- **`static void setDualPalette(PaletteType bgPalette, PaletteType spritePalette)`** - Convenience function that sets both background and sprite palettes at once. - -- **`static uint16_t resolveColor(Color color)`** - Converts a `Color` enum value to its corresponding RGB565 `uint16_t` representation. - -- **`static uint16_t resolveColor(Color color, PaletteContext context)`** - Converts a `Color` enum value to RGB565 based on the context (dual palette mode). - -### Color (Enum) - -- `Black`, `White`, `LightGray`, `DarkGray` -- `Red`, `DarkRed`, `Green`, `DarkGreen`, `Blue`, `DarkBlue` -- `Yellow`, `Orange`, `Brown` -- `Purple`, `Pink`, `Cyan` -- `LightBlue`, `LightGreen`, `LightRed` -- `Navy`, `Teal`, `Olive` -- `Gold`, `Silver` -- `Transparent` (special value, not rendered) -- `DebugRed`, `DebugGreen`, `DebugBlue` (debug colors) - ---- diff --git a/api/graphics/draw-surface.md b/api/graphics/draw-surface.md deleted file mode 100644 index 6129923..0000000 --- a/api/graphics/draw-surface.md +++ /dev/null @@ -1,32 +0,0 @@ -# DrawSurface - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## DrawSurface - -Abstract interface for platform-specific drawing operations. - -### Public Methods - -- **`virtual void init()`**: Initializes the hardware or window. -- **`virtual void setRotation(uint8_t rotation)`**: Sets display rotation. -- **`virtual void clearBuffer()`**: Clears the frame buffer. -- **`virtual void sendBuffer()`**: Sends the frame buffer to the display. -- **`virtual void drawPixel(int x, int y, uint16_t color)`**: Draws a single pixel. -- **`virtual uint8_t* getSpriteBuffer()`**: Returns a pointer to the **logical** framebuffer for direct CPU writes when supported (**TFT_eSPI_Drawer** 8bpp sprite); default returns **`nullptr`** (e.g. **SDL2_Drawer**). -- **`virtual void drawTileDirect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t* data)`**: Optional 8bpp tile blit into that buffer; default no-op. -- **`virtual void drawLine(...)`**, **`drawRectangle(...)`**, **`drawCircle(...)`**, etc. - ---- - - -## BaseDrawSurface - -**Inherits:** [DrawSurface](#drawsurface) - -Optional base class that provides default primitive rendering. - -- At minimum, implement: `init()`, `drawPixel()`, `sendBuffer()`, `clearBuffer()` -- Default implementations use `drawPixel()` - slow but functional. - ---- diff --git a/api/graphics/font.md b/api/graphics/font.md deleted file mode 100644 index be2a52e..0000000 --- a/api/graphics/font.md +++ /dev/null @@ -1,43 +0,0 @@ -# Font system - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Font System - -The engine includes a native bitmap font system that uses 1bpp sprites to render text. - -### Font Structure - -**Type:** `struct Font` - -- **`const Sprite* glyphs`**: Array of sprite structures, one per character. -- **`uint8_t firstChar`**: First character code in the font (e.g., 32 for space). -- **`uint8_t lastChar`**: Last character code in the font (e.g., 126 for tilde). -- **`uint8_t glyphWidth`**: Fixed width of each glyph in pixels. -- **`uint8_t glyphHeight`**: Fixed height of each glyph in pixels. -- **`uint8_t spacing`**: Horizontal spacing between characters in pixels. -- **`uint8_t lineHeight`**: Vertical line height. - -### FontManager - -**Type:** `class FontManager` - -Static utility class for managing fonts and calculating text dimensions. - -- **`static void setDefaultFont(const Font* font)`** - Sets the default font used by `Renderer::drawText()`. - -- **`static const Font* getDefaultFont()`** - Returns the currently active default font. - -- **`static int16_t textWidth(const Font* font, std::string_view text, uint8_t size = 1)`** - Calculates the pixel width of a text string. - -- **`static bool isCharSupported(char c, const Font* font = nullptr)`** - Checks if a character is supported by the font. - -### Built-in Font: FONT_5X7 - -A built-in 5x7 pixel bitmap font containing ASCII characters from space (32) to tilde (126). - ---- diff --git a/api/graphics/renderer.md b/api/graphics/renderer.md deleted file mode 100644 index a1e26fc..0000000 --- a/api/graphics/renderer.md +++ /dev/null @@ -1,184 +0,0 @@ -# Renderer & Camera2D - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Renderer - -**Inherits:** None - -High-level graphics rendering system. Provides a unified API for drawing shapes, text, and images, abstracting the underlying hardware implementation. - -### Public Methods - -- **`void beginFrame()`** - Prepares the buffer for a new frame (clears screen). On drivers that support it (e.g. **TFT_eSPI_Drawer**), refreshes the internal pointer used for **direct logical framebuffer writes** (`DrawSurface::getSpriteBuffer()`) before clearing, so **2bpp / 4bpp** tile and sprite paths can avoid per-pixel virtual `drawPixel` calls. - -- **`void endFrame()`** - Finalizes the frame and sends the buffer to the display. - -- **`void setOffsetBypass(bool bypass)`** - Enables or disables camera offset bypass. When enabled, subsequent draw calls will ignore global x/y offsets (scrolling). This is typically managed automatically by `UILayout` when `fixedPosition` is enabled. - -- **`bool isOffsetBypassEnabled() const`** - Returns whether the offset bypass is currently active. - -- **`void drawText(std::string_view text, int16_t x, int16_t y, Color color, uint8_t size)`** - Draws a string of text using the native bitmap font system. Uses the default font set in `FontManager`, or a custom font if provided via the overloaded version. - - **text**: The string to render (ASCII characters 32-126 are supported). - - **x, y**: Position where text starts (top-left corner). - - **color**: Color from the `Color` enum (uses sprite palette context). - - **size**: Scale multiplier (1 = normal, 2 = double, 3 = triple, etc.). - -- **`void drawText(std::string_view text, int16_t x, int16_t y, Color color, uint8_t size, const Font* font)`** - Draws text using a specific font. If `font` is `nullptr`, uses the default font from `FontManager`. - -- **`void drawTextCentered(std::string_view text, int16_t y, Color color, uint8_t size)`** - Draws text centered horizontally at a given Y coordinate using the default font. - -- **`void drawTextCentered(std::string_view text, int16_t y, Color color, uint8_t size, const Font* font)`** - Draws text centered horizontally using a specific font. If `font` is `nullptr`, uses the default font from `FontManager`. - -- **`void drawFilledCircle(int x, int y, int radius, uint16_t color)`** - Draws a filled circle. - -- **`void drawCircle(int x, int y, int radius, uint16_t color)`** - Draws a circle outline. - -- **`void drawRectangle(int x, int y, int width, int height, uint16_t color)`** - Draws a rectangle outline. - -- **`void drawFilledRectangle(int x, int y, int width, int height, uint16_t color)`** - Draws a filled rectangle. - -- **`void drawLine(int x1, int y1, int x2, int y2, uint16_t color)`** - Draws a line between two points. - -- **`void drawBitmap(int x, int y, int width, int height, const uint8_t *bitmap, uint16_t color)`** - Draws a bitmap image. - -- **`void drawPixel(int x, int y, uint16_t color)`** - Draws a single pixel. - -- **`void setOffset(int x, int y)`** - Sets the hardware alignment offset for the display. - -- **`void setRotation(uint8_t rotation)`** - Sets the hardware rotation of the display. - -- **`void drawSprite(const Sprite& sprite, int x, int y, Color color, bool flipX = false)`** - Draws a 1bpp monochrome sprite described by a `Sprite` struct using a palette `Color`. Bit 0 of each row is the leftmost pixel, bit (`width - 1`) is the rightmost pixel. - -- **`void drawSprite(const Sprite2bpp& sprite, int x, int y, uint8_t paletteSlot = 0, bool flipX = false)`** - Available when `PIXELROOT32_ENABLE_2BPP_SPRITES` is defined. Draws a packed 2bpp sprite using the specified sprite palette slot. Index `0` is treated as transparent. - -- **`void drawSprite(const Sprite4bpp& sprite, int x, int y, uint8_t paletteSlot = 0, bool flipX = false)`** - Available when `PIXELROOT32_ENABLE_4BPP_SPRITES` is defined. Draws a packed 4bpp sprite using the specified sprite palette slot. Index `0` is treated as transparent. - -- **`void drawSprite(const Sprite2bpp& sprite, int x, int y, bool flipX = false)`** - Legacy overload for backward compatibility. Equivalent to `drawSprite(sprite, x, y, 0, flipX)`. - -- **`void drawSprite(const Sprite4bpp& sprite, int x, int y, bool flipX = false)`** - Legacy overload for backward compatibility. Equivalent to `drawSprite(sprite, x, y, 0, flipX)`. - -- **`void setSpritePaletteSlotContext(uint8_t slot)`** - Sets the sprite palette slot context for multi-palette sprites. When active, all subsequent `drawSprite` calls for 2bpp/4bpp sprites will use this slot regardless of the `paletteSlot` parameter. - -- **`uint8_t getSpritePaletteSlotContext() const`** - Gets the current sprite palette slot context. - -- **`void drawMultiSprite(const MultiSprite& sprite, int x, int y)`** - Draws a layered sprite composed of multiple 1bpp `SpriteLayer` entries. - -- **`void drawTileMap(const TileMap& map, int originX, int originY, Color color)`** - Draws a tile-based background using a compact `TileMap` descriptor built on 1bpp `Sprite` tiles. Includes automatic Viewport Culling. - -- **`void drawTileMap(const TileMap2bpp& map, int originX, int originY)`** - Available when `PIXELROOT32_ENABLE_2BPP_SPRITES` is defined. Draws a 2bpp tilemap. - -- **`void drawTileMap(const TileMap4bpp& map, int originX, int originY)`** - Available when `PIXELROOT32_ENABLE_4BPP_SPRITES` is defined. Draws a 4bpp tilemap. - -- **`void setDisplaySize(int w, int h)`** - Sets the logical display size. - -- **`void setDisplayOffset(int x, int y)`** - Sets a global offset for all drawing operations. - -- **`void setContrast(uint8_t level)`** - Sets the display contrast/brightness (0-255). - ---- - - -## Platform Optimizations (ESP32) - -The engine includes several low-level optimizations for the ESP32 platform to maximize performance: - -- **DMA Support**: Buffer transfers to the display are handled via DMA (`pushImageDMA`), allowing the CPU to process the next frame while the current one is being sent to the hardware. -- **IRAM Execution**: Critical rendering functions (`drawPixel`, `drawSpriteInternal`, `resolveColor`, `drawTileMap`) are decorated with `IRAM_ATTR` to run from internal RAM, bypassing the slow SPI Flash latency. -- **Palette Caching**: Tilemaps cache the resolved RGB565 LUT per tile. -- **Viewport Culling**: All tilemap rendering functions automatically skip tiles that are outside the current screen boundaries. -- **Direct logical framebuffer**: **`DrawSurface::getSpriteBuffer()`** exposes the **TFT_eSPI** 8bpp sprite memory when available; **`Renderer::beginFrame()`** caches that pointer so **2bpp / 4bpp** rasterization can write packed pixels directly (same packing as **`TFT_eSprite::drawPixel`** for 8bpp). **`DrawSurface::drawTileDirect()`** allows blitting pre-packed 8bpp tile rows where the driver implements it. - -### Multi-layer 4bpp tilemap framebuffer snapshot: `StaticTilemapLayerCache` - -**Header:** `graphics/StaticTilemapLayerCache.h` (engine API). - -Use this when a **direct logical 8bpp sprite buffer** exists (`DrawSurface::getSpriteBuffer()` after `beginFrame`) to avoid redrawing “static” **4bpp** tilemaps every frame: the engine draws the static group, copies the framebuffer into an internal buffer, then each frame restores with **`memcpy`** and redraws only the **dynamic** group until the sampled camera changes or you **invalidate**. - -| Type / method | Role | -|---------------|------| -| **`TileMap4bppDrawSpec`** | `{ const TileMap4bpp* map; int originX; int originY; }` — `map == nullptr` entries are skipped. | -| **`allocateForLogicalSize(w,h)`** / **`allocateForRenderer(renderer)`** | Pre-allocate **W×H** bytes during **`Scene::init()`** (not in `draw`/`update`). Returns `false` if allocation fails → full-draw fallback. | -| **`invalidate()`** | Mark cache stale (tile/palette/mask changes, or **`step()`** on animators bound to **static** layers). | -| **`draw(renderer, cameraSampleX, cameraSampleY, staticSpecs, staticCount, dynamicSpecs, dynamicCount)`** | Camera samples are typically **`-renderer.getXOffset()`** / **`-renderer.getYOffset()`** so scroll triggers rebuild. | -| **`setFramebufferCacheEnabled(false)`** | Runtime opt-out per scene (e.g. profiling); compile-time: **`PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE=0`**. | - -**Memory:** about **W×H** bytes (malloc-backed in `allocate*`; no heap use inside `draw`). If **`getSpriteBuffer()`** is **`nullptr`**, the implementation draws all groups every frame (same as SDL2 / non-sprite drivers). - -**Example:** **`examples/animated_tilemap`** — `AnimatedTilemapScene` holds a **`StaticTilemapLayerCache`**, calls **`allocateForRenderer(engine.getRenderer())`** in **`init()`**, builds **`TileMap4bppDrawSpec`** arrays for **background + ground** (static) and **details** (dynamic), and exposes **`invalidateStaticLayerCache()`** as a thin wrapper over **`invalidate()`**. - -For the full pipeline diagram and layering context, see [Architecture — ESP32 rendering pipeline and tilemap caching](/architecture/overview#esp32-rendering-pipeline-and-tilemap-caching). - ---- - - -## Camera2D - -**Inherits:** None - -The `Camera2D` class provides a 2D camera system for managing the viewport and scrolling of the game world. It handles coordinate transformations and target following with configurable dead zones. - -### Public Methods - -- **`Camera2D(int viewportWidth, int viewportHeight)`** - Constructs a new `Camera2D` with the specified viewport dimensions. - -- **`void setBounds(float minX, float maxX)`** - Sets the horizontal boundaries for the camera. - -- **`void setVerticalBounds(float minY, float maxY)`** - Sets the vertical boundaries for the camera. - -- **`void setPosition(float x, float y)`** - Sets the camera's position directly. - -- **`void followTarget(float targetX)`** - Updates the camera position to follow a target's x coordinate. - -- **`void followTarget(float targetX, float targetY)`** - Updates the camera position to follow a target's x and y coordinates. - -- **`float getX() const`** - Returns the current x position of the camera. - -- **`float getY() const`** - Returns the current y position of the camera. - -- **`void apply(Renderer& renderer) const`** - Applies the camera's transformation to the renderer. - -- **`void setViewportSize(int width, int height)`** - Updates the viewport size. - ---- diff --git a/api/graphics/sprite.md b/api/graphics/sprite.md deleted file mode 100644 index 77f119e..0000000 --- a/api/graphics/sprite.md +++ /dev/null @@ -1,66 +0,0 @@ -# Sprites & SpriteAnimation - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Sprite Structures - -### Sprite - -Compact descriptor for monochrome bitmapped sprites used by `Renderer::drawSprite`. - -- **`const uint16_t* data`** - Pointer to an array of 16-bit rows. Each `uint16_t` packs pixels for one row. - -- **`uint8_t width`** - Sprite width in pixels (typically ≤ 16). - -- **`uint8_t height`** - Sprite height in pixels. - -### Sprite2bpp - -Optional descriptor for packed 2bpp sprites, enabled when `PIXELROOT32_ENABLE_2BPP_SPRITES` is defined. - -- **`const uint8_t* data`**: Packed 2bpp bitmap data. -- **`const Color* palette`**: Sprite-local palette. -- **`uint8_t width, height`**: Dimensions. -- **`uint8_t paletteSize`**: Number of palette entries. - -### Sprite4bpp - -Optional descriptor for packed 4bpp sprites, enabled when `PIXELROOT32_ENABLE_4BPP_SPRITES` is defined. - -- **`const uint8_t* data`**: Packed 4bpp bitmap data. -- **`const Color* palette`**: Sprite-local palette. -- **`uint8_t width, height`**: Dimensions. -- **`uint8_t paletteSize`**: Number of palette entries. - -### SpriteLayer - -Single monochrome layer used by layered sprites (`MultiSprite`). - -### MultiSprite - -Multi-layer, multi-color sprite built from one or more `SpriteLayer` entries. - ---- - - -## SpriteAnimation - -Lightweight, step-based animation controller for sprite frames. - -### Properties - -- **`const SpriteAnimationFrame* frames`**: Pointer to frame table. -- **`uint8_t frameCount`**: Number of frames. -- **`uint8_t current`**: Current frame index. - -### Public Methods - -- **`void reset()`**: Resets animation to first frame. -- **`void step()`**: Advances by one frame, wraps at end. -- **`const SpriteAnimationFrame& getCurrentFrame() const`**: Returns current frame. -- **`const Sprite* getCurrentSprite() const`**: Convenience accessor. - ---- diff --git a/api/graphics/tilemap.md b/api/graphics/tilemap.md deleted file mode 100644 index 4db3d95..0000000 --- a/api/graphics/tilemap.md +++ /dev/null @@ -1,106 +0,0 @@ -# TileMap & tile animation - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## TileMap - -### TileMapGeneric (Template) - -Generic descriptor for tile-based backgrounds. - -#### Template Parameters - -- **`T`**: The sprite type used for tiles (e.g., `Sprite`, `Sprite2bpp`, `Sprite4bpp`). - -#### Properties - -- **`uint8_t* indices`**: Array of tile indices. -- **`uint8_t width, height`**: Dimensions in tiles. -- **`const T* tiles`**: Pointer to the tileset array. -- **`uint8_t tileWidth, tileHeight`**: Tile dimensions in pixels. -- **`uint16_t tileCount`**: Number of unique tiles. -- **`uint8_t* runtimeMask`**: Optional bitmask for runtime tile activation. -- **`const uint8_t* paletteIndices`**: Optional per-cell background palette index. -- **`TileAnimationManager* animManager`**: Optional pointer for tile animations. - -### Type Aliases - -- **`TileMap`** = `TileMapGeneric` (1bpp) -- **`TileMap2bpp`** = `TileMapGeneric` (2bpp, conditional) -- **`TileMap4bpp`** = `TileMapGeneric` (4bpp, conditional) - ---- - - -## Tile Animation System - -The Tile Animation System enables frame-based tile animations (water, lava, fire, etc.) while maintaining static tilemap data and ESP32-optimized performance. - -### TileAnimation - -**Namespace:** `pixelroot32::graphics` - -- **`uint8_t baseTileIndex`**: First tile in the animation sequence. -- **`uint8_t frameCount`**: Number of frames in the animation. -- **`uint8_t frameDuration`**: How many **60 Hz logical ticks** each animation cell is held (1–255). Pacing uses **wall time** between `step` calls (see engine `TileAnimationManager`); speed does not follow main-loop iteration count when **`draw`/`present`** are skipped. - -### TileAnimationManager - -Manages tile animations for a tilemap. - -#### Public Methods - -- **`void step(unsigned long deltaTimeMs)`** - Advances animations from elapsed time. Pass **`Scene::update`**’s **`deltaTime`**. High-resolution pacing between calls keeps speed correct when **`millis()`** delta is often **0** on tight loops. - -- **`void reset()`** - Resets all animations to frame 0. - -- **`uint8_t resolveFrame(uint8_t tileIndex)`** - Resolves tile index to current animated frame. O(1) lookup. - ---- - - -## Tilemap rendering notes - -`Renderer::drawTileMap` always applies **viewport culling** and per-tile rasterization. Optional **`drawTileDirect()`** on `DrawSurface` (when implemented by the driver) can blit pre-packed 8bpp tile rows into the logical sprite buffer. For static **4bpp** layer reuse, use **`StaticTilemapLayerCache`** and **`PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE`** (see main Graphics / configuration docs). - ---- - - -## Tile Attribute System - -The tile attribute system provides runtime access to custom metadata attached to tiles in tilemaps. - -### TileAttribute - -- **`const char* key`**: Attribute key (PROGMEM string). -- **`const char* value`**: Attribute value (PROGMEM string). - -### TileAttributeEntry - -- **`uint16_t x, y`**: Tile coordinates in layer space. -- **`uint8_t num_attributes`**: Number of attributes. -- **`const TileAttribute* attributes`**: PROGMEM array of key-value pairs. - -### LayerAttributes - -- **`const char* layer_name`**: Layer name (PROGMEM string). -- **`uint16_t num_tiles_with_attributes`**: Number of tiles with attributes. -- **`const TileAttributeEntry* tiles`**: PROGMEM array of tiles with attributes. - -### Query Functions - -**Namespace:** `pixelroot32::graphics` - -- **`const char* get_tile_attribute(const LayerAttributes* layers, uint8_t num_layers, uint8_t layer_idx, uint16_t x, uint16_t y, const char* key)`** - Returns the value of a specific attribute for a tile. - -- **`bool tile_has_attributes(const LayerAttributes* layers, uint8_t num_layers, uint8_t layer_idx, uint16_t x, uint16_t y)`** - Returns `true` if the tile has any attributes. - -> [!IMPORTANT] -> Since attributes are stored in Flash memory on ESP32, you must use **`PIXELROOT32_STRCMP_P`** or **`PIXELROOT32_MEMCPY_P`** to compare or copy the returned values. - ---- diff --git a/api/index.md b/api/index.md index 8b9f6eb..dcd09f6 100644 --- a/api/index.md +++ b/api/index.md @@ -1,16 +1,108 @@ -# API reference - -## API overview - -Modular reference aligned with the engine source tree. Use the sidebar for class-level pages. - -| Area | Entry points | -|------|----------------| -| Core | [Engine](/api/core/engine), [Scene](/api/core/scene), [Entity](/api/core/entity), [Actor](/api/core/actor), [SceneManager](/api/core/scene-manager) | -| Graphics | [Renderer](/api/graphics/renderer), [DrawSurface](/api/graphics/draw-surface), [Sprite](/api/graphics/sprite), [TileMap](/api/graphics/tilemap), [Color](/api/graphics/color), [Font](/api/graphics/font) | -| Physics | [CollisionSystem](/api/physics/collision-system), [KinematicActor](/api/physics/kinematic-actor), [RigidActor](/api/physics/rigid-actor), [StaticActor](/api/physics/static-actor), [SensorActor](/api/physics/sensor-actor) | -| Audio | [AudioEngine](/api/audio/audio-engine), [MusicPlayer](/api/audio/music-player), [AudioScheduler](/api/audio/audio-scheduler) — synthesis in **`ApuCore`** (engine) | -| Input | [InputManager](/api/input/input-manager), [Touch](/api/input/touch-system) | -| Math | [Scalar](/api/math/scalar), [Vector2](/api/math/vector2), [Rect](/api/math/rect) | -| Platform | [EngineConfig](/api/platform/engine-config), [Capabilities](/api/platform/platform-capabilities), [PlatformMemory](/api/platform/platform-memory) | -| Other | [Configuration flags](/api/modules/configuration), [UI module](/api/modules/ui) | +# API Reference + +This document provides a high-level conceptual reference for the PixelRoot32 Game Engine public API. + +> **Source of Truth:** +> The Markdown files in this directory are **high-level conceptual guides**. For detailed, method-level documentation (signatures, parameter descriptions, return values), the **C++ header files (`include/**/*.h`)** serve as the single source of truth. + +> **Note:** For the most up-to-date and comprehensive API documentation with examples and cross-references, visit the [official documentation](https://docs.pixelroot32.org/api_reference/). + +## Table of Contents + +The API documentation has been split into modular conceptual guides for easier maintenance. Click on a topic to jump to the detailed documentation. + +### Core Reference + +| Topic | Description | +|-------|-------------| +| [Configuration](config.md) | Build flags, modular compilation, constants | +| [Math Module](math.md) | Scalar, Vector2, MathUtil, PRNG | +| [Core Module](core.md) | Engine, Entity, Scene, SceneManager | +| [Physics Module](physics.md) | CollisionSystem, PhysicsActor, RigidActor, collision helpers | +| [Graphics Module](graphics.md) | Renderer, sprites, tilemaps, particles, Camera2D | +| [UI Module](ui.md) | UI system, touch widgets, layouts | +| [Audio Module](audio.md) | AudioEngine, MusicPlayer, music tracks | +| [Input Module](input.md) | InputManager, TouchManager, touch calibration | +| [Platform Abstractions](platform.md) | Logging, PlatformMemory, hardware capabilities | + +--- + +## Quick Reference by Feature + +### Basic Setup + +```cpp +// Include the engine +#include "core/Engine.h" + +int main() { + // Configure display + pixelroot32::graphics::DisplayConfig config; + config.type = pixelroot32::graphics::DisplayType::ST7789; + config.physicalWidth = 240; + config.physicalHeight = 240; + config.logicalWidth = 240; + config.logicalHeight = 240; + + // Create engine + pixelroot32::core::Engine engine(std::move(config)); + engine.init(); + + // Run game loop + engine.run(); + + return 0; +} +``` + +### Scene Creation + +```cpp +#include "core/Scene.h" + +class MyScene : public pixelroot32::core::Scene { +public: + void init() override { + // Initialize scene + } + + void update(unsigned long deltaTime) override { + // Update game logic + } + + void draw(pixelroot32::graphics::Renderer& renderer) override { + renderer.beginFrame(); + // Draw everything + renderer.endFrame(); + } +}; +``` + +--- + +## Module Availability + +Some modules are optional and can be disabled to save memory: + +| Module | Macro | Default | +|--------|-------|---------| +| Audio | `PIXELROOT32_ENABLE_AUDIO` | Enabled | +| Physics | `PIXELROOT32_ENABLE_PHYSICS` | Enabled | +| UI System | `PIXELROOT32_ENABLE_UI_SYSTEM` | Enabled | +| Particles | `PIXELROOT32_ENABLE_PARTICLES` | Enabled | +| Touch Input | `PIXELROOT32_ENABLE_TOUCH` | Disabled | +| 2bpp Sprites | `PIXELROOT32_ENABLE_2BPP_SPRITES` | Disabled | +| 4bpp Sprites | `PIXELROOT32_ENABLE_4BPP_SPRITES` | Disabled | +| Tile Animations | `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enabled | +| Static tilemap FB cache (4bpp) | `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enabled (`PlatformDefaults.h`) | +| Debug Overlay | `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Disabled | + +--- + +## Related Documentation + +- [Architecture](../architecture/architecture-index.md) (includes [ESP32 rendering and tilemap caching](../architecture/architecture-index.md#esp32-rendering-pipeline-and-tilemap-caching)) +- [Platform Compatibility Guide](../guide/platform-compatibility.md) +- [Getting Started Guide](../../README.md) +- [Extending PixelRoot32](../guide/extending-pixelroot32.md) +- [Touch Input Architecture](../architecture/touch-input.md) \ No newline at end of file diff --git a/api/input.md b/api/input.md new file mode 100644 index 0000000..06c0484 --- /dev/null +++ b/api/input.md @@ -0,0 +1,84 @@ +# API Reference: Input Module + +> **Source of truth:** +> - `include/input/InputManager.h` +> - `include/input/InputConfig.h` +> - `include/input/TouchManager.h` +> - `include/input/TouchAdapter.h` +> - `include/input/ActorTouchController.h` + +## Overview + +The Input module manages user interactions across different control methods (keyboard, gamepad, touch). The `InputManager` acts as a unified abstraction layer, polling the underlying hardware (e.g., SDL2 events on PC, or physical buttons/touch drivers on ESP32) and exposing a simple state API for the game. + +## Key Concepts + +### InputManager + +The main interface for querying input state. It maps physical buttons or keys to abstract game actions (Up, Down, Left, Right, A, B, Start, Select). +- Can be queried per-frame (e.g., `isPressed(InputButton::A)` or `justPressed(InputButton::A)`). + +### InputConfig + +Defines the hardware mapping for physical buttons on the ESP32. +- **Fields**: `pinUp`, `pinDown`, `pinLeft`, `pinRight`, `pinA`, `pinB`, `pinStart`, `pinSelect`. +- Passed during `Engine` initialization. + +### Touch Input System + +*(Requires `PIXELROOT32_ENABLE_TOUCH=1`)* + +A comprehensive touch processing pipeline designed for resistive screens (like the XPT2046) but adaptable to others. It handles raw sampling, noise filtering, calibration, gesture recognition, and event dispatching. + +#### Usage Example + +```cpp +auto& input = engine.getInputManager(); +if (input.hasTouch()) { + auto& touch = input.getTouchManager(); + if (touch.isTouched()) { + auto point = touch.getLastPoint(); + // point.x and point.y are already mapped to screen coordinates + } +} +``` + +### TouchManager + +The central hub for touch processing. It coordinates the hardware adapter, applies calibration matrices, manages the touch state machine (tracking PRESS, DRAG, RELEASE), and dispatches `TouchEvent`s to registered listeners. + +### TouchCalibration + +Handles the conversion from raw ADC values to logical screen coordinates using a 3-point calibration matrix. +- Often requires a separate calibration scene to capture the points (`updateCalibrationPoint`) and compute the matrix (`computeMatrix`), which should then be saved to non-volatile storage. + +### TouchPoint & TouchEvent + +- **`TouchPoint`**: Represents a physical coordinate (`x`, `y`, `z`/pressure). +- **`TouchEvent`**: An event dispatched through the UI system (`type`, `point`, `delta`, `handled` flag). Types include `PRESS`, `RELEASE`, `DRAG`, `CLICK`, and `LONG_PRESS`. + +### ActorTouchController + +A utility component that allows a `PhysicsActor` (or any `Entity`) to respond to touch events. By registering with the `TouchManager`, the controller automatically performs hit-testing against the actor's bounds and translates events. + +## Configuration & Notes + +### XPT2046 Build Flags + +If using the default XPT2046 adapter on ESP32, you must configure the SPI pins in `platformio.ini`: + +| Macro | Description | +|-------|-------------| +| `TOUCH_CS` | Chip Select pin | +| `TOUCH_IRQ` | Interrupt Request pin | +| `TOUCH_MOSI` | MOSI pin (can share with display) | +| `TOUCH_MISO` | MISO pin (can share with display) | +| `TOUCH_CLK` | Clock pin (can share with display) | + +**Order Pipeline Note**: Events flow from Hardware -> `TouchAdapter` -> `TouchManager` (filters & state machine) -> UI System (top-to-bottom) -> `ActorTouchController` -> Game logic. + +## Related Documentation + +- [API Reference](index.md) - Main index +- [UI Module](ui.md) - Touch widgets and hit testing +- [Touch Input Architecture](../architecture/touch-input.md) - Deep dive into the touch pipeline \ No newline at end of file diff --git a/api/input/input-manager.md b/api/input/input-manager.md deleted file mode 100644 index c8c8dd1..0000000 --- a/api/input/input-manager.md +++ /dev/null @@ -1,71 +0,0 @@ -# InputManager & InputConfig - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Input Module Overview - -Handles **physical buttons** / **keyboard** via `InputManager`, and **touch screens** via `TouchManager` and optional `ActorTouchController`. - ---- - - -## InputManager - -**Include:** `input/InputManager.h` - -**Inherits:** None - -Handles input polling, debouncing, and state tracking for physical buttons (ESP32) or keyboard (Native/SDL2). - -### Public Methods - -- **`InputManager(const InputConfig& config)`** - Constructs the InputManager with a specific configuration. - -- **`void init()`** - Initializes the input pins. - -- **`void update(unsigned long dt)`** (ESP32) - Updates input state by polling hardware pins. - -- **`void update(unsigned long dt, const uint8_t* keyboardState)`** (Native/SDL2) - Updates input state based on SDL keyboard state. - -- **`bool isButtonPressed(uint8_t buttonIndex) const`** - Returns true if button was just pressed this frame (UP → DOWN). - -- **`bool isButtonReleased(uint8_t buttonIndex) const`** - Returns true if button was just released this frame (DOWN → UP). - -- **`bool isButtonDown(uint8_t buttonIndex) const`** - Returns true if button is currently held down. - -- **`bool isButtonClicked(uint8_t buttonIndex) const`** - Returns true if button was clicked (pressed and released in same frame). - ---- - - -## InputConfig - -**Inherits:** None - -Configuration structure for `InputManager`. Defines the mapping between logical inputs and physical pins (ESP32) or keyboard keys (Native/SDL2). - -- **`std::vector inputPins`**: (ESP32) List of GPIO pins. -- **`std::vector buttonNames`**: (Native) List of scancodes/keys. -- **`int count`**: Total number of configured inputs. - -### Constructor - -- **`InputConfig(int count, ...)`** - Variadic constructor to easily list pins/keys. - -### Example - -```cpp -// 3 inputs: Left, Right, Jump -InputConfig input(3, 12, 14, 27); -``` - ---- diff --git a/api/input/touch-system.md b/api/input/touch-system.md deleted file mode 100644 index cda7512..0000000 --- a/api/input/touch-system.md +++ /dev/null @@ -1,129 +0,0 @@ -# Touch system - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Touch Input Overview - -Touch is **not** wired through `Engine::run()` automatically. Platform code should call `TouchManager::update`, then `getEvents`, then feed the current `Scene` via `processTouchEvents` (see `Engine.h` class comment and [Touch Input Architecture](/architecture/ARCH_TOUCH_INPUT)). - -**Typical loop fragment** - -```cpp -touchManager.update(frameDt); -pixelroot32::input::TouchEvent events[pixelroot32::input::TOUCH_EVENT_QUEUE_SIZE]; -uint8_t n = touchManager.getEvents(events, pixelroot32::input::TOUCH_EVENT_QUEUE_SIZE); -if (n > 0) { - auto scene = engine.getCurrentScene(); - if (scene.has_value() && scene.value()) { - scene.value()->processTouchEvents(events, n); - } -} -engine.run(); -``` - -**Scene hooks** (see [API Core](/api/core/engine.md#scene)): `processTouchEvents` dispatches to UI (if enabled) then `onUnconsumedTouchEvent`. - ---- - - -## TouchManager - -**Include:** `input/TouchManager.h` - -Touch event aggregation layer that polls the active touch adapter (XPT2046 or GT911), clamps coordinates to display bounds, and provides raw touch points. Gesture detection is handled by Engine's TouchEventDispatcher. - -### Public Methods - -| Method | Description | -|--------|-------------| -| `TouchManager(int16_t maxX, int16_t maxY)` | Clamping bounds (use physical panel size). | -| `bool init()` | Initializes adapter; call **`setCalibration` first** (see [Touch Input Architecture](/architecture/ARCH_TOUCH_INPUT)). | -| `void update(unsigned long dt)` | Poll hardware and refresh internal buffer. | -| `void setCalibration(const TouchCalibration&)` | Copies calibration to manager **and** adapter. | -| `uint8_t getTouchPoints(TouchPoint* points) const` | Raw pressed points for the current frame. Returns only active touches - use `Engine::setTouchManager()` for automatic release detection. | -| `uint8_t getActiveCount() const` | Number of active points. | -| `bool isTouchActive() const` | Any finger/stylus down. | -| `bool isConnected() const` | Adapter initialization / health. | - -**Constants:** `TouchManager::CIRCULAR_BUFFER_SIZE`, `TOUCH_MAX_POINTS` (5). - ---- - - -## TouchCalibration - -**Include:** `input/TouchAdapter.h` (class `TouchCalibration`) - -Maps raw controller coordinates to screen space; optional **rotation** enum `TouchRotation`. - -### API - -| Method | Description | -|--------|-------------| -| `static TouchCalibration forResolution(int16_t w, int16_t h)` | Sets `displayWidth` / `displayHeight` and heuristic `scaleX` / `scaleY` (12-bit ADC span). | -| `TouchPoint transform(int16_t rawX, int16_t rawY, bool pressed, uint8_t id, uint32_t ts) const` | Scale, offsets, rotation, clamp. | -| `void setRotation(TouchRotation)` / `applyRotation` | Match TFT rotation when needed. | - -### Public Fields - -- `scaleX`, `scaleY`, `offsetX`, `offsetY` -- `displayWidth`, `displayHeight`, `rotation` - ---- - - -## TouchPoint and TouchEvent - -**Includes:** `input/TouchPoint.h`, `input/TouchEvent.h`, `input/TouchEventTypes.h` - -### TouchPoint - -- **`x`, `y`**, **`pressed`**, **`id`**, timestamp — normalized sample after adapter + clamp. - -### TouchEvent - -- compact gesture record — **`TouchEventType`** (`TouchDown`, `TouchUp`, `DragStart`, `DragMove`, `DragEnd`, `Click`, …), **`x`**, **`y`**, **`flags`** (e.g. consumed), **`id`**, **`timestamp`**. - -Use **`isConsumed()`**, **`setConsumed()`** for UI routing. - ---- - - -## ActorTouchController - -**Include:** `input/ActorTouchController.h` - -Registers up to 8 `Actor*` targets; on `handleTouch`, performs hit test (with optional slop), drag threshold (`kDragThreshold`), and updates `Actor::position` on drag. - -### Public Methods - -| Method | Description | -|--------|-------------| -| `bool registerActor(Actor*)` / `unregisterActor` | Pool membership. | -| `void handleTouch(const TouchEvent&)` | Route by `TouchEventType`. | -| `void setTouchHitSlop(int16_t pixels)` | Expand hit-test rect **per side** (resistive calibration slack). | -| `int16_t getTouchHitSlop() const` | Current slop. | -| `bool isDragging() const` | Threshold exceeded and actor locked. | -| `Actor* getDraggedActor() const` | Hit actor (may be non-null before drag threshold). | -| `void reset()` | Clears pool and drag state (not slop). | - ---- - - -## XPT2046 Build Flags (ESP32) - -Used by `src/input/adapters/XPT2046Adapter.cpp` (SPI or **`XPT2046_USE_GPIO_SPI`** bit-bang). Define via `build_flags` in `platformio.ini`. - -| Macro | Default | Purpose | -|-------|---------|---------| -| `XPT2046_USE_GPIO_SPI` | off | Separate GPIO bus (e.g. 2432S028R); pins: `XPT2046_GPIO_IRQ`, `MOSI`, `MISO`, `CLK`, `CS`. | -| `XPT2046_GPIO_SWAP_AXES` | `0` | Swap ADC axes before mapping (vertical/horizontal swapped on screen). | -| `XPT2046_GPIO_MIRROR_X` | `0` | Flip X in screen space after map: `x = displayWidth - x`. | -| `XPT2046_GPIO_VENDOR_COORDS` | `0` | Vendor-style X/Y swap (see adapter). | -| `XPT2046_GPIO_USE_RAW_RANGE` | `0` | Linear map `XPT2046_RAW_*_LO/HI` → full width/height instead of `transform` heuristics. | -| `XPT2046_RAW_X_LO`, `XPT2046_RAW_X_HI`, `XPT2046_RAW_Y_LO`, `XPT2046_RAW_Y_HI` | code defaults | Usable ADC window for raw-range mode. | -| `XPT2046_CAL_OFFSET_X`, `XPT2046_CAL_OFFSET_Y` | optional | Pixel nudge **after** mirror. | - -Order after read: **map → vendor coords → mirror X → CAL offsets → clamp** (see [Touch Input Architecture](/architecture/ARCH_TOUCH_INPUT)). - ---- diff --git a/api/math.md b/api/math.md new file mode 100644 index 0000000..15ca67c --- /dev/null +++ b/api/math.md @@ -0,0 +1,124 @@ +# API Reference: Math Module + +> **Source of truth:** +> - `include/math/Scalar.h` +> - `include/math/MathUtil.h` +> - `include/math/Vector2.h` +> - `include/math/Fixed16.h` + +## Overview + +The Math module provides foundational mathematical structures and utilities tailored for embedded game development. It includes the `Scalar` abstraction for handling either floating-point or fixed-point arithmetic, a 2D vector class (`Vector2`), common math utilities, and a fast Pseudo-Random Number Generator (PRNG). + +## Key Concepts + +### The `Scalar` Type + +To support both FPU-equipped devices (like the ESP32) and FPU-less devices, PixelRoot32 uses a `Scalar` typedef throughout its physics and geometry systems. + +- **Floating-Point Mode (Default)**: `Scalar` maps to `float`. +- **Fixed-Point Mode**: `Scalar` maps to `Fixed16` (a custom Q16.16 fixed-point implementation) to avoid soft-float overhead. + +This abstraction ensures that engine code remains performant and cross-platform without `#ifdef` clutter in high-level game logic. + +### Vector2 + +The standard 2D vector class (`Vector2`) is templated by default but typedef'd to `Vector2f` (using `float`) or a custom type based on the `Scalar` definition. It supports standard vector operations: addition, subtraction, dot product, cross product, length, normalization, and distance calculations. + +### MathUtil + +Provides optimized, commonly used math functions. On devices with `PIXELROOT32_HAS_FAST_RSQRT`, the engine uses a fast reciprocal square root algorithm for normalization, bypassing costly standard library calls. + +## Pseudo-Random Number Generator (PRNG) + +The engine provides a high-performance **Xoroshiro128+** implementation, replacing the standard `rand()` for game logic and particle systems. + +**Why Xoroshiro128+?** +- Significantly faster than `rand()`. +- Generates higher quality noise with a longer period. +- Avoids thread contention (standard `rand()` uses a hidden global lock, which causes extreme frame drops when called concurrently from the Audio and Main cores). + +### Thread Safety + +> [!WARNING] +> The **Global PRNG** (`MathUtil::random()`) is **NOT thread-safe**. Do not call global random functions from the audio thread or network thread. Doing so will cause race conditions and undefined behavior. + +For multi-threaded environments, each thread (e.g., the audio synthesizer) must instantiate its own local `Random` object. + +### Instance-Based RNG (`Random`) + +A standalone struct wrapping the Xoroshiro128+ state. It allows you to create isolated random streams (e.g., for procedural generation where you want a reproducible seed independent of the global game state). + +## Common Usage Patterns + +### Vector Math + +```cpp +using pixelroot32::math::Vector2; + +Vector2 playerPos(10.0f, 20.0f); +Vector2 enemyPos(50.0f, 60.0f); + +// Distance +float dist = playerPos.distanceTo(enemyPos); + +// Direction vector +Vector2 direction = enemyPos - playerPos; +direction.normalize(); // Now a unit vector + +// Move player +float speed = 150.0f; +float dt = 0.016f; +playerPos += direction * (speed * dt); +``` + +### Random Numbers + +```cpp +using pixelroot32::math::MathUtil; + +// Seed the global generator (usually done once at startup) +MathUtil::seedRandom(1337); + +// Random float between 0.0 and 1.0 +float chance = MathUtil::randomFloat(); +if (chance < 0.25f) { + // 25% chance +} + +// Random integer between 1 and 10 (inclusive) +int damage = MathUtil::randomInt(1, 10); + +// Random float within a range +float x = MathUtil::randomRange(100.0f, 200.0f); +``` + +### Reproducible Procedural Generation + +```cpp +using pixelroot32::math::Random; + +// Create a local generator with a specific seed +Random mapGen(987654321); + +// Generate terrain heights (always the same for this seed) +for (int i = 0; i < 100; ++i) { + float height = mapGen.randomRange(10.0f, 50.0f); + // ... +} +``` + +## Constants + +| Constant | Description | +|----------|-------------| +| `PI` | `3.14159265359f` | +| `TWO_PI` | `6.28318530718f` | +| `HALF_PI` | `1.57079632679f` | +| `DEG_TO_RAD` | `0.0174532925f` | +| `RAD_TO_DEG` | `57.295779513f` | + +## Related Documentation + +- [API Reference](index.md) - Main index +- [Physics Module](physics.md) - The primary consumer of the Math module \ No newline at end of file diff --git a/api/math/prng.md b/api/math/prng.md deleted file mode 100644 index 6a396c4..0000000 --- a/api/math/prng.md +++ /dev/null @@ -1,203 +0,0 @@ -# PRNG (Random Number Generation) - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Random Number Generation (PRNG) - -The `math` namespace provides a deterministic pseudo-random number generator (PRNG) based on the Xorshift32 algorithm. - -### PRNG System Overview - -The PRNG uses the **Xorshift32 algorithm**, a lightweight, high-quality pseudo-random number generator that: - -- Uses only bitwise operations (XOR, shifts) - no multiplication or division -- Produces deterministic sequences based on seed value -- Has excellent statistical properties for game development -- Is compact and efficient for embedded systems (ESP32) - -**Note:** This PRNG is suitable for gameplay mechanics (procedural generation, dice rolls, random events) but is **not cryptographically secure**. - ---- - -### Thread Safety and Concurrency - -**Global PRNG functions are NOT thread-safe.** The global PRNG state (`set_seed`, `rand01`, `rand_int`, etc.) should not be accessed concurrently from multiple threads or ISRs without external synchronization. - -**For concurrent or multi-threaded scenarios, use the `Random` struct:** - -```cpp -// Each thread has its own Random instance -thread_local Random threadRNG(generate_unique_seed()); - -// Safe concurrent access - no locks needed -void threadWorker() { - int value = threadRNG.rand_int(1, 100); -} -``` - -**Recommendation:** - -- Single-threaded games: Use global functions for simplicity -- Multi-threaded scenarios: Create `Random` instances per thread/context -- Per-entity randomness: Give each entity its own `Random` instance - ---- - -### Implementation Quality - -The PRNG implementation includes several quality improvements: - -- **Bias-free integer generation**: Uses rejection sampling to ensure uniform distribution for any range -- **Fixed16 optimization**: On platforms without FPU, `rand01()` uses bit-shifting (no float operations) -- **Unified core algorithm**: Single `xorshift32()` function ensures consistent behavior -- **State validation**: Automatically prevents PRNG from entering invalid zero state - ---- - -### Global PRNG Functions - -- **`void set_seed(uint32_t seed)`** - Initializes the PRNG with a specific seed. If seed is 0, uses fallback constant `0xDEADBEEF`. - - ```cpp - // Initialize with specific seed for reproducible gameplay - set_seed(12345); - - // Reset with 0 (will use fallback seed) - set_seed(0); - ``` - -- **`Scalar rand01()`** - Returns random Scalar in range [0, 1]. Works with both float and Fixed16 Scalars. - - ```cpp - Scalar roll = rand01(); // 0.0 to 1.0 - ``` - -- **`Scalar rand_range(Scalar min, Scalar max)`** - Returns random Scalar in inclusive range [min, max]. - - ```cpp - // Random position between 10 and 100 - Scalar posX = rand_range(toScalar(10), toScalar(100)); - - // Random damage between 5 and 15 - Scalar damage = rand_range(toScalar(5), toScalar(15)); - ``` - -- **`int32_t rand_int(int32_t min, int32_t max)`** - Returns random integer in inclusive range [min, max]. - - ```cpp - // Roll a 6-sided die - int roll = rand_int(1, 6); - - // Random array index - int index = rand_int(0, arraySize - 1); - ``` - -- **`bool rand_chance(Scalar p)`** - Returns true with probability p (0.0 to 1.0). - - ```cpp - // 30% chance to spawn power-up - if (rand_chance(toScalar(0.3f))) { - spawnPowerUp(); - } - - // Guaranteed event - if (rand_chance(toScalar(1.0f))) { /* always true */ } - ``` - -- **`Scalar rand_sign()`** - Returns -1 or 1 as Scalar (50% probability each). - - ```cpp - // Random direction - Scalar direction = rand_sign(); // -1 or 1 - velocity.x = speed * direction; - ``` - ---- - -### Instance-Based RNG: Random Struct - -For scenarios requiring multiple independent random sequences (e.g., per-entity RNG, separate generators for different systems): - -```cpp -// Create independent RNG instances -Random enemyRNG(12345); // For enemy spawns -Random lootRNG(67890); // For loot drops -Random visualRNG(11111); // For visual effects - -// Use independently - each has its own state -Scalar enemyX = enemyRNG.rand_range(toScalar(0), toScalar(100)); -int lootTier = lootRNG.rand_int(1, 5); -``` - -The `Random` struct provides the same methods as global functions: - -- `next()` - Generate next uint32_t value -- `rand01()` - Random Scalar in [0, 1] -- `rand_range(min, max)` - Random Scalar in range -- `rand_int(min, max)` - Random integer in range -- `rand_chance(p)` - Boolean with probability p -- `rand_sign()` - -1 or 1 - ---- - -### Common Usage Patterns - -**Pattern 1: Seeded Procedural Generation** - -```cpp -// Same seed always produces same level -set_seed(levelSeed); -for (int i = 0; i < 100; i++) { - Scalar x = rand_range(toScalar(0), worldWidth); - Scalar y = rand_range(toScalar(0), worldHeight); - spawnTree(x, y); -} -``` - -**Pattern 2: Deterministic Dice Rolls** - -```cpp -set_seed(turnNumber); // Reproducible combat -int attackRoll = rand_int(1, 20); -int damageRoll = rand_int(1, 8); -``` - -**Pattern 3: Random Spawning with Probability** - -```cpp -void update() { - // 1% chance per frame to spawn enemy - if (rand_chance(toScalar(0.01f))) { - spawnEnemy(); - } -} -``` - -**Pattern 4: Shuffle Array (Fisher-Yates)** - -```cpp -template -void shuffleArray(T* array, int count) { - for (int i = count - 1; i > 0; i--) { - int j = rand_int(0, i); - swap(array[i], array[j]); - } -} -``` - ---- - -### Math Constants - -- **`kPi`** - π (3.14159265) -- **`kDegToRad`** - Degrees to radians conversion factor -- **`kRadToDeg`** - Radians to degrees conversion factor -- **`kEpsilon`** - Small value for approximate equality checks - ---- diff --git a/api/math/rect.md b/api/math/rect.md deleted file mode 100644 index 3d34be7..0000000 --- a/api/math/rect.md +++ /dev/null @@ -1,221 +0,0 @@ -# MathUtil, PRNG & Rect - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -The axis-aligned `Rect` type lives in the math headers (`math/Rect.h`) and is used throughout physics and rendering. See also [Vector2](/api/math/vector2) for overlap helpers. - -## MathUtil - -**Inherits:** None - -Collection of helper functions. - -### Public Methods - -- **`float lerp(float a, float b, float t)`** - Linear interpolation. -- **`float clamp(float v, float min, float max)`** - Clamps a value between min and max. - ---- - - -## Random Number Generation (PRNG) - -The `math` namespace provides a deterministic pseudo-random number generator (PRNG) based on the Xorshift32 algorithm. - -### PRNG System Overview - -The PRNG uses the **Xorshift32 algorithm**, a lightweight, high-quality pseudo-random number generator that: - -- Uses only bitwise operations (XOR, shifts) - no multiplication or division -- Produces deterministic sequences based on seed value -- Has excellent statistical properties for game development -- Is compact and efficient for embedded systems (ESP32) - -**Note:** This PRNG is suitable for gameplay mechanics (procedural generation, dice rolls, random events) but is **not cryptographically secure**. - ---- - -### Thread Safety and Concurrency - -**Global PRNG functions are NOT thread-safe.** The global PRNG state (`set_seed`, `rand01`, `rand_int`, etc.) should not be accessed concurrently from multiple threads or ISRs without external synchronization. - -**For concurrent or multi-threaded scenarios, use the `Random` struct:** - -```cpp -// Each thread has its own Random instance -thread_local Random threadRNG(generate_unique_seed()); - -// Safe concurrent access - no locks needed -void threadWorker() { - int value = threadRNG.rand_int(1, 100); -} -``` - -**Recommendation:** - -- Single-threaded games: Use global functions for simplicity -- Multi-threaded scenarios: Create `Random` instances per thread/context -- Per-entity randomness: Give each entity its own `Random` instance - ---- - -### Implementation Quality - -The PRNG implementation includes several quality improvements: - -- **Bias-free integer generation**: Uses rejection sampling to ensure uniform distribution for any range -- **Fixed16 optimization**: On platforms without FPU, `rand01()` uses bit-shifting (no float operations) -- **Unified core algorithm**: Single `xorshift32()` function ensures consistent behavior -- **State validation**: Automatically prevents PRNG from entering invalid zero state - ---- - -### Global PRNG Functions - -- **`void set_seed(uint32_t seed)`** - Initializes the PRNG with a specific seed. If seed is 0, uses fallback constant `0xDEADBEEF`. - - ```cpp - // Initialize with specific seed for reproducible gameplay - set_seed(12345); - - // Reset with 0 (will use fallback seed) - set_seed(0); - ``` - -- **`Scalar rand01()`** - Returns random Scalar in range [0, 1]. Works with both float and Fixed16 Scalars. - - ```cpp - Scalar roll = rand01(); // 0.0 to 1.0 - ``` - -- **`Scalar rand_range(Scalar min, Scalar max)`** - Returns random Scalar in inclusive range [min, max]. - - ```cpp - // Random position between 10 and 100 - Scalar posX = rand_range(toScalar(10), toScalar(100)); - - // Random damage between 5 and 15 - Scalar damage = rand_range(toScalar(5), toScalar(15)); - ``` - -- **`int32_t rand_int(int32_t min, int32_t max)`** - Returns random integer in inclusive range [min, max]. - - ```cpp - // Roll a 6-sided die - int roll = rand_int(1, 6); - - // Random array index - int index = rand_int(0, arraySize - 1); - ``` - -- **`bool rand_chance(Scalar p)`** - Returns true with probability p (0.0 to 1.0). - - ```cpp - // 30% chance to spawn power-up - if (rand_chance(toScalar(0.3f))) { - spawnPowerUp(); - } - - // Guaranteed event - if (rand_chance(toScalar(1.0f))) { /* always true */ } - ``` - -- **`Scalar rand_sign()`** - Returns -1 or 1 as Scalar (50% probability each). - - ```cpp - // Random direction - Scalar direction = rand_sign(); // -1 or 1 - velocity.x = speed * direction; - ``` - ---- - -### Instance-Based RNG: Random Struct - -For scenarios requiring multiple independent random sequences (e.g., per-entity RNG, separate generators for different systems): - -```cpp -// Create independent RNG instances -Random enemyRNG(12345); // For enemy spawns -Random lootRNG(67890); // For loot drops -Random visualRNG(11111); // For visual effects - -// Use independently - each has its own state -Scalar enemyX = enemyRNG.rand_range(toScalar(0), toScalar(100)); -int lootTier = lootRNG.rand_int(1, 5); -``` - -The `Random` struct provides the same methods as global functions: - -- `next()` - Generate next uint32_t value -- `rand01()` - Random Scalar in [0, 1] -- `rand_range(min, max)` - Random Scalar in range -- `rand_int(min, max)` - Random integer in range -- `rand_chance(p)` - Boolean with probability p -- `rand_sign()` - -1 or 1 - ---- - -### Common Usage Patterns - -**Pattern 1: Seeded Procedural Generation** - -```cpp -// Same seed always produces same level -set_seed(levelSeed); -for (int i = 0; i < 100; i++) { - Scalar x = rand_range(toScalar(0), worldWidth); - Scalar y = rand_range(toScalar(0), worldHeight); - spawnTree(x, y); -} -``` - -**Pattern 2: Deterministic Dice Rolls** - -```cpp -set_seed(turnNumber); // Reproducible combat -int attackRoll = rand_int(1, 20); -int damageRoll = rand_int(1, 8); -``` - -**Pattern 3: Random Spawning with Probability** - -```cpp -void update() { - // 1% chance per frame to spawn enemy - if (rand_chance(toScalar(0.01f))) { - spawnEnemy(); - } -} -``` - -**Pattern 4: Shuffle Array (Fisher-Yates)** - -```cpp -template -void shuffleArray(T* array, int count) { - for (int i = count - 1; i > 0; i--) { - int j = rand_int(0, i); - swap(array[i], array[j]); - } -} -``` - ---- - -### Math Constants - -- **`kPi`** - π (3.14159265) -- **`kDegToRad`** - Degrees to radians conversion factor -- **`kRadToDeg`** - Radians to degrees conversion factor -- **`kEpsilon`** - Small value for approximate equality checks - ---- diff --git a/api/math/scalar.md b/api/math/scalar.md deleted file mode 100644 index b5532e3..0000000 --- a/api/math/scalar.md +++ /dev/null @@ -1,97 +0,0 @@ -# Scalar & math helpers - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Scalar Type - -**Namespace:** `pixelroot32::math` - -`Scalar` is the fundamental numeric type used throughout the engine for physics, positioning, and logic. - -- **On FPU platforms (ESP32, S3):** `Scalar` is an alias for `float`. -- **On non-FPU platforms (C3, S2, C6):** `Scalar` is an alias for `Fixed16`. - -### Fixed16 (16.16 Fixed Point) - -On platforms without a Hardware Floating Point Unit (FPU), the engine uses `Fixed16` for all calculations. - -- **Storage**: 32-bit signed integer. -- **Precision**: 16 bits for the integer part, 16 bits for the fractional part (approx. 0.000015 resolution). -- **Literal**: Use the `_fp` suffix for literals on non-FPU platforms for compile-time conversion. - *Example:* `Scalar gravity = 9.8_fp;` - ---- - - -## Helper Functions - -- **`Scalar toScalar(float value)`** - Converts a floating-point literal or variable to `Scalar`. - *Usage:* `Scalar speed = toScalar(2.5f);` - -- **`Scalar toScalar(int value)`** - Converts an integer to `Scalar`. - -- **`int toInt(Scalar value)`** - Converts a `Scalar` back to an integer (truncating decimals). - -- **`int roundToInt(Scalar value)`** - Converts a `Scalar` to an integer, rounding to the nearest whole number. Essential for mapping logical positions to pixel coordinates without jitter. - -- **`int floorToInt(Scalar value)`** - Returns the largest integer less than or equal to the scalar value. - -- **`int ceilToInt(Scalar value)`** - Returns the smallest integer greater than or equal to the scalar value. - -- **`float toFloat(Scalar value)`** - Converts a `Scalar` to `float`. **Warning:** Use sparingly on non-FPU platforms. - -- **`Scalar abs(Scalar v)`** - Returns the absolute value. - -- **`Scalar sqrt(Scalar v)`** - Returns the square root. **Warning:** Expensive operation. Prefer squared distances for comparisons. - -- **`Scalar min(Scalar a, Scalar b)`** - Returns the smaller of two values. - -- **`Scalar max(Scalar a, Scalar b)`** - Returns the larger of two values. - -- **`Scalar clamp(Scalar v, Scalar minVal, Scalar maxVal)`** - Clamps a value between a minimum and maximum. - -- **`Scalar lerp(Scalar a, Scalar b, Scalar t)`** - Linearly interpolates between `a` and `b` by `t` (where `t` is 0.0 to 1.0). - -- **`Scalar sin(Scalar x)`** - Returns the sine of the angle `x` (in radians). - -- **`Scalar cos(Scalar x)`** - Returns the cosine of the angle `x` (in radians). - -- **`Scalar atan2(Scalar y, Scalar x)`** - Returns the arc tangent of y/x (in radians). - -- **`Scalar sign(Scalar x)`** - Returns the sign of x (-1, 0, or 1). - -- **`bool is_equal_approx(Scalar a, Scalar b)`** - Returns true if a and b are approximately equal. - -- **`bool is_zero_approx(Scalar x)`** - Returns true if x is approximately zero. - -### Constants - -- **`Scalar kPi`** - Value of PI (3.14159...). - -- **`Scalar kDegToRad`** - Conversion factor from degrees to radians. - -- **`Scalar kRadToDeg`** - Conversion factor from radians to degrees. - ---- diff --git a/api/math/vector2.md b/api/math/vector2.md deleted file mode 100644 index 8c76e70..0000000 --- a/api/math/vector2.md +++ /dev/null @@ -1,93 +0,0 @@ -# Vector2 - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Vector2 - -**Namespace:** `pixelroot32::math` - -A 2D vector structure composed of two `Scalar` components. - -### Members - -- **`Scalar x`** -- **`Scalar y`** - -### Methods - -- **`Vector2(Scalar x, Scalar y)`** - Constructor. - -- **`Scalar lengthSquared() const`** - Returns the squared magnitude of the vector. **Preferred over `length()` for comparisons.** - -- **`Scalar length() const`** - Returns the magnitude of the vector. - -- **`Vector2 normalized() const`** - Returns a normalized (unit length) version of the vector. - -- **`Scalar dot(const Vector2& other) const`** - Returns the dot product with another vector. - -- **`Scalar cross(const Vector2& other) const`** - Returns the cross product with another vector (2D analog). - -- **`Scalar angle() const`** - Returns the angle of the vector in radians. - -- **`Scalar angle_to(const Vector2& to) const`** - Returns the angle to another vector in radians. - -- **`Scalar angle_to_point(const Vector2& to) const`** - Returns the angle from this point to another point. - -- **`Vector2 direction_to(const Vector2& to) const`** - Returns the normalized direction vector pointing to the target. - -- **`Scalar distance_to(const Vector2& to) const`** - Returns the distance to another point. - -- **`Scalar distance_squared_to(const Vector2& to) const`** - Returns the squared distance to another point. - -- **`Vector2 limit_length(Scalar max_len) const`** - Returns the vector with its length limited to `max_len`. - -- **`Vector2 clamp(Vector2 min, Vector2 max) const`** - Returns the vector clamped between min and max vectors. - -- **`Vector2 lerp(const Vector2& to, Scalar weight) const`** - Linear interpolation between this vector and `to`. - -- **`Vector2 rotated(Scalar phi) const`** - Returns the vector rotated by `phi` radians. - -- **`Vector2 move_toward(const Vector2& to, Scalar delta) const`** - Moves the vector toward `to` by a maximum of `delta` distance. - -- **`Vector2 slide(const Vector2& n) const`** - Returns the component of the vector along the sliding plane defined by normal `n`. - -- **`Vector2 reflect(const Vector2& n) const`** - Returns the vector reflected across the plane defined by normal `n`. - -- **`Vector2 project(const Vector2& b) const`** - Returns the projection of this vector onto vector `b`. - -- **`Vector2 abs() const`** - Returns a new vector with absolute values of components. - -- **`Vector2 sign() const`** - Returns a new vector with sign of components. - -- **`bool is_normalized() const`** - Returns true if the vector is normalized. - -- **`bool is_zero_approx() const`** - Returns true if the vector is approximately zero. - -- **`bool is_equal_approx(const Vector2& other) const`** - Returns true if the vector is approximately equal to `other`. - ---- diff --git a/api/modules/configuration.md b/api/modules/configuration.md deleted file mode 100644 index 21135cf..0000000 --- a/api/modules/configuration.md +++ /dev/null @@ -1,162 +0,0 @@ -# Configuration (module flags) - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -# API Reference: Configuration - -This document covers global configuration options, build flags, and compile-time constants for the PixelRoot32 Game Engine. - -> **Note:** This is part of the [API Reference](/api/). See the main index for complete documentation. - ---- - -## Platform Macros (Build Flags) - -| Macro | Description | Default (ESP32) | -|-------|-------------|-----------------| -| `PR32_DEFAULT_AUDIO_CORE` | CPU core assigned to audio tasks. | `0` | -| `PR32_DEFAULT_MAIN_CORE` | CPU core assigned to the main game loop. | `1` | -| `PIXELROOT32_NO_DAC_AUDIO` | Disable Internal DAC support on classic ESP32. | Enabled | -| `PIXELROOT32_NO_I2S_AUDIO` | Disable I2S audio support. | Enabled | -| `PIXELROOT32_USE_U8G2_DRIVER` | Enable U8G2 display driver support for monochromatic OLEDs. | Disabled | -| `PIXELROOT32_NO_TFT_ESPI` | Disable default TFT_eSPI driver support. | Enabled | - ---- - -## Modular Compilation Flags - -| Macro | Description | Default | -|-------|-------------|---------| -| `PIXELROOT32_ENABLE_AUDIO` | Enable audio subsystem (AudioEngine + MusicPlayer). | `1` | -| `PIXELROOT32_ENABLE_PHYSICS` | Enable physics system (CollisionSystem). | `1` | -| `PIXELROOT32_ENABLE_UI_SYSTEM` | Enable UI system (UIButton, UILabel, etc.). | `1` | -| `PIXELROOT32_ENABLE_PARTICLES` | Enable particle system. | `1` | -| `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Enable FPS/RAM/CPU debug overlay. | Disabled | -| `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enable tile animation system. | `1` | -| `PIXELROOT32_ENABLE_2BPP_SPRITES` | Enable 2bpp sprite support. | Disabled | -| `PIXELROOT32_ENABLE_4BPP_SPRITES` | Enable 4bpp sprite support. | Disabled | -| `PIXELROOT32_ENABLE_SCENE_ARENA` | Enable scene memory arena. | Disabled | -| `PIXELROOT32_ENABLE_PROFILING` | Enable profiling hooks in physics pipeline. | Disabled | -| `PIXELROOT32_ENABLE_TOUCH` | Enable automatic touch processing in Engine (mouse-to-touch on Native, touch point injection on ESP32). | `0` (disabled) | -| `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enable **`StaticTilemapLayerCache`** (4bpp direct logical framebuffer snapshot). Set `0` to save ~W×H RAM or force full redraw. | `1` (`PlatformDefaults.h`) | -| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK` | TFT_eSPI `sendBufferScaled`: RGB565 lines per DMA batch (when `PIXELROOT32_USE_TFT_ESPI_DRIVER`). Higher = fewer DMA rounds, more RAM for line buffers. | `60` (`PlatformDefaults.h`) | -| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK_FALLBACK` | Line batch size retried if primary DMA line buffers cannot be allocated. | `30` (`PlatformDefaults.h`) | -| `PIXELROOT32_DEBUG_MODE` | Enable unified logging system. | Disabled | - ---- - -## Memory Savings by Subsystem - -| Subsystem Disabled | RAM Savings | Flash Savings | -|-------------------|-------------|--------------| -| `PIXELROOT32_ENABLE_AUDIO=0` | ~8 KB | ~15 KB | -| `PIXELROOT32_ENABLE_PHYSICS=0` | ~12 KB | ~25 KB | -| `PIXELROOT32_ENABLE_UI_SYSTEM=0` | ~4 KB | ~20 KB | -| `PIXELROOT32_ENABLE_PARTICLES=0` | ~6 KB | ~10 KB | -| `PIXELROOT32_ENABLE_TOUCH=0` | ~200 bytes | ~2 KB | - ---- - -## Build Profiles (platformio.ini) - -```ini -[profile_full] ; All features enabled -build_flags = - -D PIXELROOT32_ENABLE_AUDIO=1 - -D PIXELROOT32_ENABLE_PHYSICS=1 - -D PIXELROOT32_ENABLE_PARTICLES=1 - -D PIXELROOT32_ENABLE_UI_SYSTEM=1 - -[profile_arcade] ; Audio + Physics + Particles, no UI -build_flags = - -D PIXELROOT32_ENABLE_AUDIO=1 - -D PIXELROOT32_ENABLE_PHYSICS=1 - -D PIXELROOT32_ENABLE_PARTICLES=1 - -D PIXELROOT32_ENABLE_UI_SYSTEM=0 - -[profile_puzzle] ; Audio + UI only, no physics/particles -build_flags = - -D PIXELROOT32_ENABLE_AUDIO=1 - -D PIXELROOT32_ENABLE_PHYSICS=0 - -D PIXELROOT32_ENABLE_PARTICLES=0 - -D PIXELROOT32_ENABLE_UI_SYSTEM=1 - -[profile_retro] ; Minimal: no subsystems -build_flags = - -D PIXELROOT32_ENABLE_AUDIO=0 - -D PIXELROOT32_ENABLE_PHYSICS=0 - -D PIXELROOT32_ENABLE_PARTICLES=0 - -D PIXELROOT32_ENABLE_UI_SYSTEM=0 -``` - ---- - -## Recommended Profiles by Game Type - -| Game Type | Recommended Profile | Rationale | -|-----------|-------------------|-----------| -| Arcade (shooters, platformers) | `arcade` or `full` | Physics + particles + optional UI | -| Puzzle / Casual | `puzzle` | UI for menus, simple collision logic | -| Retro / Minimal | `retro` | Minimal footprint, custom collision | -| Educational / Tool | `puzzle` or custom | UI for menus | - ---- - -## Constants - -- **`DISPLAY_WIDTH`** - The width of the display in pixels. Default is `240`. - -- **`DISPLAY_HEIGHT`** - The height of the display in pixels. Default is `240`. - -- **`int xOffset`** - The horizontal offset for the display alignment. Default is `0`. - -- **`int yOffset`** - The vertical offset for the display alignment. Default is `0`. - -- **`PHYSICS_MAX_PAIRS`** - Maximum number of collision pairs considered in broadphase. Default is `128`. - -- **`PHYSICS_MAX_CONTACTS`** - Maximum number of simultaneous contacts in the solver (fixed pool, no heap per frame). Default is `128`. When exceeded, additional contacts are dropped. - -- **`VELOCITY_ITERATIONS`** - Number of impulse solver passes per frame. Higher values improve stacking stability but increase CPU load. Default is `2`. - -- **`SPATIAL_GRID_CELL_SIZE`** - Size of each cell in the broadphase grid (in pixels). Default is `32`. - -- **`SPATIAL_GRID_MAX_ENTITIES_PER_CELL`** - Legacy: maximum entities per cell when using a single grid. Default is `24`. - -- **`SPATIAL_GRID_MAX_STATIC_PER_CELL`** - Maximum static (immovable) actors per grid cell. Default is `12`. Used by the static layer of the spatial grid. - -- **`SPATIAL_GRID_MAX_DYNAMIC_PER_CELL`** - Maximum dynamic (RIGID/KINEMATIC) actors per grid cell. Default is `12`. Used by the dynamic layer of the spatial grid. - ---- - -## Custom Scene Limits - -The engine defines default limits in `platforms/EngineConfig.h`: `MAX_LAYERS` (default 3) and `MAX_ENTITIES` (default 32). These are guarded with `#ifndef`, so you can override them from your project without modifying the engine. - -**Compiler flags (recommended)** - -In your project (e.g. in `platformio.ini`), add the defines to `build_flags`: - -```ini -build_flags = - -DMAX_LAYERS=5 - -DMAX_ENTITIES=64 -``` - ---- - -## Related Documentation - -- [API Reference](/api/) - Main index -- [Platform Compatibility Guide](/guide/platform-config) -- [Extending PixelRoot32](/guide/extending) diff --git a/api/modules/ui.md b/api/modules/ui.md deleted file mode 100644 index 0f44b32..0000000 --- a/api/modules/ui.md +++ /dev/null @@ -1,764 +0,0 @@ -# UI module - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -# API Reference: UI Module - -This document covers the user interface system, touch widgets, and layout containers in PixelRoot32. - -> **Note:** This is part of the [API Reference](/api/). See the main index for complete documentation. - -> **Note**: The UI system is only available if `PIXELROOT32_ENABLE_UI_SYSTEM=1` - ---- - -## UIElement - -**Include:** `graphics/ui/UIElement.h` - -**Inherits:** [Entity](/api/core/engine.md#entity) - -Base class for all UI elements. Provides positioning, sizing, and layout capabilities. - -### Properties - -- **`UIElementType type`**: The type of UI element (GENERIC, BUTTON, LABEL, CHECKBOX, LAYOUT). -- **`bool fixedPosition`**: If true, the element ignores camera scroll and stays fixed on screen. - -### Public Methods - -- **`UIElement(Vector2 position, int w, int h, UIElementType t)`** - Constructs a new UIElement. - -- **`UIElement(Scalar x, Scalar y, int w, int h, UIElementType t)`** - Constructs a new UIElement with scalar coordinates. - -- **`UIElementType getType() const`** - Returns the type of UI element. - -- **`virtual bool isFocusable() const`** - Returns true if the element can receive focus for navigation. - -- **`void setFixedPosition(bool fixed)`** - Sets whether the element ignores camera scroll (true = fixed HUD position). - -- **`bool isFixedPosition() const`** - Returns true if the element is in fixed position mode. - -- **`virtual void setPosition(Scalar newX, Scalar newY)`** - Sets the position of the element. - -- **`virtual void getPreferredSize(Scalar& preferredWidth, Scalar& preferredHeight) const`** - Gets the preferred size of the element for layout calculations. - ---- - -## UIButton - -**Include:** `graphics/ui/UIButton.h` - -**Inherits:** [UIElement](#uielement) - -A clickable button UI element with support for physical buttons and D-pad navigation. - -### Public Methods - -- **`UIButton(std::string_view t, uint8_t index, Vector2 position, Vector2 size, UIElementVoidCallback callback, TextAlignment textAlign = CENTER, int fontSize = 2)`** - Constructs a new UIButton. `UIElementVoidCallback` is `void(*)()` (function pointer, not `std::function`). - -- **`void setStyle(Color textCol, Color bgCol, bool drawBg)`** - Configures the button's visual style. - -- **`void setSelected(bool selected)`** - Sets the selection state (for D-pad navigation). - -- **`bool getSelected() const`** - Returns true if the button is selected. - -- **`bool isFocusable() const override`** - Returns true (buttons are always focusable). - -- **`void handleInput(const InputManager& input)`** - Handles input events (touch or button press). - -- **`void press()`** - Manually triggers the button's callback. - ---- - -## UILabel - -**Include:** `graphics/ui/UILabel.h` - -**Inherits:** [UIElement](#uielement) - -A simple text label for displaying information. - -### Public Methods - -- **`UILabel(std::string_view t, Vector2 position, Color col, uint8_t sz)`** - Constructs a new UILabel. - -- **`void setText(std::string_view t)`** - Updates the label's text and recalculates dimensions. - -- **`void setVisible(bool v)`** - Sets visibility. - -- **`void centerX(int screenWidth)`** - Centers the label horizontally. - ---- - -## UIPanel - -**Include:** `graphics/ui/UIPanel.h` - -**Inherits:** [UIElement](#uielement) - -A visual container that draws a background and border around child elements. Useful for dialogs and menus. - -### Public Methods - -- **`UIPanel(Scalar x, Scalar y, int w, int h)`** - Constructs a new UIPanel. - -- **`UIPanel(Vector2 position, int w, int h)`** - Constructs a new UIPanel with vector position. - -- **`void setChild(UIElement* element)`** - Sets the child element to display inside the panel. - -- **`UIElement* getChild() const`** - Gets the child element. - -- **`void setBackgroundColor(Color color)`** - Sets the background color. - -- **`void setBorderColor(Color color)`** - Sets the border color. - -- **`void setBorderWidth(uint8_t width)`** - Sets the border width in pixels. - -- **`uint8_t getBorderWidth() const`** - Gets the border width. - -- **`void setPosition(Scalar newX, Scalar newY) override`** - Sets position and updates child position. - ---- - -## UILayout - -**Include:** `graphics/ui/UILayout.h` - -**Inherits:** [UIElement](#uielement) - -Base class for UI layout containers. Provides automatic element positioning, spacing, and optional scrolling. - -### Types - -- **`enum class ScrollBehavior`** - - `NONE`: No scrolling allowed - - `SCROLL`: Scroll freely within bounds - - `CLAMP`: Scroll but clamp to content bounds - -### Public Methods - -- **`UILayout(Scalar x, Scalar y, int w, int h)`** - Constructs a new UILayout. - -- **`UILayout(Vector2 position, int w, int h)`** - Constructs a new UILayout with vector position. - -- **`virtual void addElement(UIElement* element)`** - Adds a UI element to the layout. - -- **`virtual void removeElement(UIElement* element)`** - Removes a UI element from the layout. - -- **`virtual void updateLayout()`** - Recalculates positions of all elements. - -- **`virtual void handleInput(const InputManager& input)`** - Handles input for navigation/scrolling. - -- **`void setPadding(Scalar p)`** - Sets the internal padding. - -- **`Scalar getPadding() const`** - Gets the current padding. - -- **`void setSpacing(Scalar s)`** - Sets the spacing between elements. - -- **`Scalar getSpacing() const`** - Gets the current spacing. - -- **`size_t getElementCount() const`** - Gets the number of elements in the layout. - -- **`UIElement* getElement(size_t index) const`** - Gets the element at a specific index. - -- **`void clearElements()`** - Removes all elements from the layout. - -- **`void setScrollingEnabled(bool enabled)`** - Enables or disables scrolling. - -- **`bool isScrollingEnabled() const`** - Returns true if scrolling is enabled. - ---- - -## UIGridLayout - -**Include:** `graphics/ui/UIGridLayout.h` - -**Inherits:** [UILayout](#uilayout) - -Grid layout container that organizes elements in a matrix with 4-direction navigation support. - -### Public Methods - -- **`UIGridLayout(Scalar x, Scalar y, int w, int h)`** - Constructs a new UIGridLayout. - -- **`UIGridLayout(Vector2 position, int w, int h)`** - Constructs a new UIGridLayout with vector position. - -- **`void addElement(UIElement* element)`** - Adds a UI element to the grid. - -- **`void removeElement(UIElement* element)`** - Removes a UI element from the grid. - -- **`void updateLayout()`** - Recalculates positions of all elements. - -- **`void handleInput(const InputManager& input)`** - Handles navigation input (UP/DOWN/LEFT/RIGHT). - -- **`void setColumns(uint8_t cols)`** - Sets the number of columns. - -- **`uint8_t getColumns() const`** - Gets the number of columns. - -- **`uint8_t getRows() const`** - Gets the calculated number of rows. - -- **`int getSelectedIndex() const`** - Gets the currently selected element index. - -- **`void setSelectedIndex(int index)`** - Sets the selected element by index. - -- **`UIElement* getSelectedElement() const`** - Gets the currently selected element. - -- **`void setNavigationButtons(uint8_t upButton, uint8_t downButton, uint8_t leftButton, uint8_t rightButton)`** - Configures button indices for navigation. - -- **`void setButtonStyle(Color selectedTextCol, Color selectedBgCol, Color unselectedTextCol, Color unselectedBgCol)`** - Sets style colors for selected/unselected states. - ---- - -## UICheckBox - -**Include:** `graphics/ui/UICheckbox.h` - -**Inherits:** [UIElement](#uielement) - -A checkbox UI element with support for physical buttons and D-pad navigation. - -### Public Methods - -- **`UICheckBox(std::string_view label, uint8_t index, Vector2 position, Vector2 size, bool checked = false, UIElementBoolCallback callback = nullptr, int fontSize = 2)`** - Constructs a new UICheckBox. `UIElementBoolCallback` is `void(*)(bool)` (function pointer, not `std::function`). - -- **`void setStyle(Color textCol, Color bgCol, bool drawBg = false)`** - Configures the checkbox's visual style. - -- **`void setChecked(bool checked)`** - Sets the checked state. - -- **`bool isChecked() const`** - Returns true if the checkbox is checked. - -- **`void setSelected(bool selected)`** - Sets the selection state (for D-pad navigation). - -- **`bool getSelected() const`** - Returns true if the checkbox is selected. - -- **`bool isFocusable() const override`** - Returns true (checkboxes are always focusable). - -- **`void handleInput(const InputManager& input)`** - Handles input events. - -- **`void toggle()`** - Toggles the checkbox state. - ---- - -## UIVerticalLayout - -**Inherits:** [UILayout](#uilayout) - -Vertical layout container with scroll support. Organizes UI elements vertically, one below another. - -### Public Methods - -- **`UIVerticalLayout(float x, float y, float w, float h)`** - Constructs a new UIVerticalLayout. - -- **`void setScrollEnabled(bool enable)`** - Enables or disables scrolling. - -- **`float getScrollOffset() const`** - Gets the current scroll offset in pixels. - -- **`void setScrollOffset(float offset)`** - Sets the scroll offset directly. - -- **`float getContentHeight() const`** - Gets the total content height. - -- **`int getSelectedIndex() const`** - Gets the currently selected element index. - -- **`void setSelectedIndex(int index)`** - Sets the selected element index. - -- **`void setScrollSpeed(float speed)`** - Sets the scroll speed for smooth scrolling. - -- **`void setNavigationButtons(uint8_t upButton, uint8_t downButton)`** - Sets the navigation button indices. - -- **`void setButtonStyle(...)`** - Sets style colors for selected/unselected buttons. - ---- - -## UIHorizontalLayout - -**Inherits:** [UILayout](#uilayout) - -Horizontal layout container with scroll support. Organizes UI elements horizontally, one next to another. - -### Public Methods - -- **`UIHorizontalLayout(float x, float y, float w, float h)`** - Constructs a new UIHorizontalLayout. - -- **`void setScrollEnabled(bool enable)`** - Enables or disables scrolling. - -- **`float getScrollOffset() const`** - Gets the current scroll offset. - -- **`float getContentWidth() const`** - Gets the total content width. - -- **`int getSelectedIndex() const`** - Gets the currently selected element index. - -- **`void setSelectedIndex(int index)`** - Sets the selected element index. - -- **`void setNavigationButtons(uint8_t leftButton, uint8_t rightButton)`** - Sets navigation button indices. - -- **`void setButtonStyle(...)`** - Sets style colors. - ---- - -## UIAnchorLayout - -**Inherits:** [UILayout](#uilayout) - -Layout that positions elements at fixed anchor points on the screen without reflow. Very efficient for HUDs. - -### Public Methods - -- **`UIAnchorLayout(float x, float y, float w, float h)`** - Constructs a new UIAnchorLayout. - -- **`void addElement(UIElement* element, Anchor anchor)`** - Adds a UI element with a specific anchor point. - -- **`void removeElement(UIElement* element)`** - Removes a UI element. - -- **`void setScreenSize(float screenWidth, float screenHeight)`** - Sets the screen size for anchor calculations. - -### Anchor Values - -- `TOP_LEFT`, `TOP_RIGHT`, `BOTTOM_LEFT`, `BOTTOM_RIGHT` -- `CENTER`, `TOP_CENTER`, `BOTTOM_CENTER`, `LEFT_CENTER`, `RIGHT_CENTER` - ---- - -## UIPaddingContainer - -**Inherits:** [UIElement](#uielement) - -Container that wraps a single UI element and applies padding. - -### Public Methods - -- **`UIPaddingContainer(float x, float y, float w, float h)`** - Constructs a new UIPaddingContainer. - -- **`void setChild(UIElement* element)`** - Sets the child element to wrap. - -- **`UIElement* getChild() const`** - Gets the child element. - -- **`void setPadding(float p)`** - Sets uniform padding on all sides. - -- **`void setPadding(float left, float right, float top, float bottom)`** - Sets asymmetric padding. - ---- - -## Touch Widgets - -The touch widget system provides optimized UI elements for touchscreen input. It uses a memory-efficient pool pattern where `UITouchWidget` structs are allocated from a fixed pool, and `UITouchElement` classes wrap them to provide Entity interface. - -**Architecture:** - -``` -UITouchWidget (struct) → UITouchElement (class: UIElement) → UITouchButton/UITouchSlider -``` - -> **Note:** UITouchElement inherits from UIElement (not Entity), enabling integration with UILayout containers. - ---- - -## UITouchWidget - -**Include:** `graphics/ui/UITouchWidget.h` - -Lightweight struct stored in a fixed-size pool. Contains position, size, state, and flags. - -### Properties - -- **`UIWidgetType type`**: Type of touch widget (Button, Slider) -- **`int16_t x, y`**: Position -- **`uint16_t width, height`**: Dimensions -- **`UIWidgetState state`**: Current state (Idle, Pressed, Dragging, Hover) -- **`UIWidgetFlags flags`**: Enabled, Visible, Active flags - -### Public Methods - -- **`UITouchWidget(UIWidgetType t, uint8_t index, int16_t x, int16_t y, uint16_t w, uint16_t h)`** - Constructs a new touch widget. - -- **`void setEnabled(bool enabled)`** - Enables or disables the widget. - -- **`bool isEnabled() const`** - Returns true if enabled. - -- **`void setVisible(bool visible)`** - Sets visibility. - -- **`bool isVisible() const`** - Returns true if visible. - -- **`void setPosition(int16_t x, int16_t y)`** - Sets position. - -- **`void setSize(uint16_t w, uint16_t h)`** - Sets size. - -- **`bool contains(int16_t px, int16_t py) const`** - Checks if point is within bounds. - ---- - -## UITouchElement - -**Include:** `graphics/ui/UITouchElement.h` - -**Inherits:** [UIElement](#uielement) - -Abstract base class for touch-optimized UI elements. Contains an embedded **`UITouchWidget`**. - -### Public Methods - -- **`UITouchElement(int16_t x, int16_t y, uint16_t w, uint16_t h, UIWidgetType type)`** - Constructs a touch element. - -- **`virtual bool processEvent(const TouchEvent& event) = 0`** - Implemented by subclasses. Returns `true` when the event should be consumed. - -- **`void update(unsigned long deltaTime) override`** - Base implementation is empty; subclasses may override. - -- **`void draw(Renderer& renderer) override`** - Base implementation is empty; concrete widgets override. - -- **`void setPosition(Scalar newX, Scalar newY) override`** - Sets position and synchronizes both Entity position and widget data. - -- **`uint8_t getWidgetState() const`** - Returns the current widget state. - -- **`bool isPressed() const`** - Returns `true` if the element is currently pressed. - -- **`bool isEnabled() const`** - Returns `true` if the element is enabled. - -- **`bool isVisible() const`** - Returns `true` if the element is visible. - -- **`UITouchWidget& getWidgetData()`** - Returns reference to the embedded widget data. - ---- - -## UITouchButton - -**Include:** `graphics/ui/UITouchButton.h` - -**Inherits:** [UITouchElement](#uitouchelement) - -Touch-optimized button with press, release, and click callbacks. - -### Public Methods - -- **`UITouchButton(std::string_view t, int16_t x, int16_t y, uint16_t w, uint16_t h)`** - Constructs a touch button. - -- **`void setLabel(std::string_view t)`** - Sets the button label. - -- **`std::string_view getLabel() const`** - Gets the button label. - -- **`void setColors(Color normal, Color pressed, Color disabled)`** - Sets the colors for different states. - -- **`ButtonCallback`** — type alias for **`void (*)()`** - -- **`void setOnDown(ButtonCallback callback)`** / **`setOnUp`** / **`setOnClick`** - Sets callbacks for touch down, up, and click. - -- **`bool processEvent(const TouchEvent& event) override`** - Handles hit testing and state. - -### Example - -```cpp -void onPressed() { /* ... */ } - -auto btn = std::make_unique("Press Me", 50, 100, 120, 40); -getUIManager().addElement(btn.get()); -btn->setColors(Color::White, Color::Cyan, Color::Gray); -btn->setOnClick(onPressed); -``` - ---- - -## UITouchSlider - -**Include:** `graphics/ui/UITouchSlider.h` - -**Inherits:** [UITouchElement](#uitouchelement) - -Touch-optimized slider with draggable thumb. Supports horizontal orientation with value range 0-100. - -### Public Methods - -- **`UITouchSlider(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t initialValue = 50)`** - Constructs a touch slider. - -- **`uint8_t getValue() const`** - Gets the current value (0-100). - -- **`void setValue(uint8_t value)`** - Sets the value (clamped to 0-100). - -- **`void setColors(Color track, Color thumb)`** - Sets the track and thumb colors. - -- **`SliderCallback`** — type alias **`void (*)(uint8_t)`** - -- **`void setOnValueChanged(SliderCallback callback)`** - Called when the value changes. - -- **`void setOnDragStart(SliderCallback callback)`** / **`setOnDragEnd`** - Optional drag lifecycle hooks. - -- **`bool processEvent(const TouchEvent& event) override`** - Handles drag and hit testing. - -### Example - -```cpp -void onVol(uint8_t v) { volume = v / 100.0f; } - -auto slider = std::make_unique(50, 150, 200, 30, 75); -getUIManager().addElement(slider.get()); -slider->setColors(Color::Gray, Color::White); -slider->setOnValueChanged(onVol); -``` - ---- - -## UITouchCheckbox - -**Include:** `graphics/ui/UITouchCheckbox.h` - -**Inherits:** [UITouchElement](#uitouchelement) - -Touch-optimized checkbox widget. - -### Public Methods - -- **`UITouchCheckbox(std::string_view label, int16_t x, int16_t y, uint16_t w, uint16_t h, bool initialChecked = false)`** - Constructs a touch checkbox. - -- **`void setChecked(bool checked)`** - Sets the checked state. - -- **`bool isChecked() const`** - Gets the current checked state. - -- **`void toggle()`** - Toggles the checked state. - -- **`void setLabel(std::string_view label)`** - Sets the checkbox label. - -- **`std::string_view getLabel() const`** - Gets the current label. - -- **`void setColors(Color normal, Color checked, Color disabled)`** - Sets checkbox colors. - -- **`CheckboxCallback`** — type alias **`void (*)(bool)`** - -- **`void setOnChanged(CheckboxCallback callback)`** - Sets callback for state changes. - -- **`bool processEvent(const TouchEvent& event) override`** - Handles hit testing and toggle semantics. - -- **`void draw(Renderer& renderer) override`** - Renders the checkbox. - -### Example - -```cpp -void onSound(bool enabled) { soundEnabled = enabled; } - -auto checkbox = std::make_unique("Enable Sound", 50, 200, 150, 30, true); -getUIManager().addElement(checkbox.get()); -checkbox->setColors(Color::White, Color::Cyan, Color::Gray); -checkbox->setOnChanged(onSound); -``` - ---- - -## UIManager - -**Include:** `graphics/ui/UIManager.h` - -**Inherits:** None - -**Non-owning registry** for touch UI elements. Holds up to **`MAX_ELEMENTS`** (16) pointers for hit testing and event dispatch. - -> **⚠️ Lifetime Contract:** Widgets **MUST** call `removeElement()` before being destroyed. - -### Constants - -- **`MAX_ELEMENTS`**: Maximum number of registered elements (16). - -### Public Methods - -- **`UIManager()`** - Constructs an empty manager. - -- **`~UIManager()`** - Calls **`clear()`** (unregisters only). - -- **`bool addElement(UITouchElement* element)`** - Registers element for hit testing and events. - -- **`bool removeElement(uint8_t id)`** / **`bool removeElement(UITouchWidget* widget)`** - Unregisters element. Does not destroy the object. - -- **`UITouchElement* getElement(uint8_t id) const`** - Returns registered element for that widget id. - -- **`uint8_t getElementCount() const`** - Number of registered elements. - -- **`void clear()`** - Clears all registrations. - -- **`uint8_t processEvents(TouchEvent* events, uint8_t count)`** - For each event: hit test, then `hit->processEvent(event)`. - -- **`bool processEvent(TouchEvent& event)`** - Convenience wrapper around `processEvents(&event, 1)`. - -- **`void releaseCapture()`** - Clears captured widget. - -- **`void update(unsigned long deltaTime)`** / **`void draw(Renderer& renderer)`** - **No-op** (deprecated). Touch widgets are updated/drawn through Scene. - -### Usage - -```cpp -void MyScene::initUI() { - auto& ui = getUIManager(); - ui.clear(); - - okButton = std::make_unique("OK", 10, 20, 100, 40); - ui.addElement(okButton.get()); - okButton->setOnClick(onOkClicked); - - // Optional: add to layout and register as entity - barLayout = std::make_unique(/* ... */); - barLayout->addElement(okButton.get()); - addEntity(barLayout.get()); -} -``` - ---- - -## UIHitTest - -**Include:** `graphics/ui/UIHitTest.h` - -Helper class for hit testing UITouchElement entities. - -### Public Methods - -- **`static bool hitTest(const UITouchElement& element, int16_t x, int16_t y)`** - Returns true if the point is within the element's bounds. - ---- - -## Related Documentation - -- [API Reference](/api/) - Main index -- [API Core](/api/core/engine) — Engine, Entity, Scene -- [API Graphics](/api/graphics/renderer) — Rendering system -- [API Input](/api/input/input-manager) — Input and touch diff --git a/api/physics.md b/api/physics.md new file mode 100644 index 0000000..2e323d0 --- /dev/null +++ b/api/physics.md @@ -0,0 +1,194 @@ +# API Reference: Physics Module + +> **Source of truth:** +> - `include/core/Actor.h`, `include/core/PhysicsActor.h` +> - `include/physics/CollisionSystem.h` +> - `include/physics/PhysicsScheduler.h` +> - `include/physics/KinematicActor.h`, `include/physics/RigidActor.h` +> - `include/physics/StaticActor.h`, `include/physics/SensorActor.h` +> - `include/physics/TileAttributes.h` +> - `include/physics/TileCollisionBuilder.h`, `include/physics/TileConsumptionHelper.h` + +## Overview + +*(Requires `PIXELROOT32_ENABLE_PHYSICS=1`)* + +The Physics module provides a custom, highly optimized 2D physics engine. It uses a **Flat Solver** architecture optimized for embedded devices without hardware floating-point units, resolving collisions through discrete SAT (Separating Axis Theorem) and iterative impulse resolution. + +## Key Concepts + +### Actor + +`Actor` is the base class for entities that occupy space in the world. An `Actor` is technically an `Entity`, but specifically manages spatial bounds (`width`, `height`, `x`, `y`). + +**Collision Shapes:** +- `RECTANGLE` (Default): AABB (Axis-Aligned Bounding Box) collision. +- `CIRCLE`: Radial collision, great for players or projectiles. + +### PhysicsActor + +The core class of the physics system. It extends `Actor` with physical properties and behaviors. + +**Key Physical Properties:** +- `velocity`: The current speed and direction (units per second). +- `mass` / `invMass`: Determines how the actor responds to impulses (0 mass = infinite mass/static). +- `restitution`: Bounciness (0.0 to 1.0). +- `friction`: Resistance to sliding. +- `drag`: Air resistance or fluid drag. +- `collisionLayer` & `collisionMask`: Bitmasks used to filter which actors collide. + +**Collision Resolution:** +- Handled internally by the `CollisionSystem`. +- Users interact via `moveAndCollide()` (for kinematic bodies) or `applyImpulse()` (for rigid bodies). + +### Default Layers + +| Macro | Value | Description | +|-------|-------|-------------| +| `LAYER_DEFAULT` | `0x0001` | Default layer for actors. | +| `LAYER_PLAYER` | `0x0002` | Typically used for the player character. | +| `LAYER_ENEMY` | `0x0004` | Typically used for enemies. | +| `LAYER_ENVIRONMENT` | `0x0008` | Used for solid world boundaries/tiles. | + +## Actor Types + +### StaticActor + +A `PhysicsActor` with infinite mass that does not move. Used for walls, floors, and platforms. + +```cpp +auto wall = scene.createEntity(); +wall->setSize(100, 20); +wall->setPosition(50, 200); +wall->setCollisionLayer(LAYER_ENVIRONMENT); +``` + +### SensorActor + +An actor that detects overlaps but does not physically collide or stop other actors. + +```cpp +auto trigger = scene.createEntity(); +trigger->setSize(30, 30); +trigger->setOnOverlap([](PhysicsActor* self, PhysicsActor* other, const WorldCollisionInfo& info) { + if (other->getCollisionLayer() == LAYER_PLAYER) { + // Player entered the zone + } +}); +``` + +### KinematicActor + +An actor whose movement is fully controlled by the game logic (not forces/impulses), but it stops when hitting solid objects (like a `StaticActor`). + +```cpp +auto platform = scene.createEntity(); +platform->setSize(40, 10); +// In update loop: +platform->moveAndCollide(Vector2(50.0f * dt, 0)); +``` + +### RigidActor + +An actor entirely driven by the physics simulation (gravity, impulses, velocity). Best for physics objects like crates or a bouncing ball. + +```cpp +auto crate = scene.createEntity(); +crate->setSize(16, 16); +crate->setMass(10.0f); +crate->setRestitution(0.4f); +// In game logic: +crate->applyImpulse(Vector2(0.0f, -200.0f)); // Jump/bounce +``` + +### CircleActor (Pattern) + +While not a specific class, setting the collision shape to `CIRCLE` transforms the actor: +```cpp +auto ball = scene.createEntity(); +ball->setCollisionShape(CollisionShape::CIRCLE); +ball->setSize(16, 16); // Sets radius to 8 +``` + +## Architecture Notes + +### CollisionSystem (The Flat Solver) + +The `CollisionSystem` is attached to a `Scene`. It manages the broadphase (Spatial Grid) and narrowphase collision detection. +- Uses **Discrete Collision Detection**. +- Solves penetration using projection (positional correction). +- Solves velocities using iterative impulses. +- Emits overlap and collision callbacks. + +### PhysicsScheduler + +Ensures the physics simulation runs at a fixed time step regardless of the rendering frame rate. This guarantees deterministic jumps and collision responses. +- Default timestep: `1/60.0f` seconds. +- Cap: `MAX_FRAME_ACCUMULATOR` prevents the "spiral of death" during lag spikes. + +## Configuration & Data Structures + +### WorldCollisionInfo + +Struct passed to collision callbacks. +- `normal`: The collision normal (pointing away from the other object). +- `penetration`: Depth of the overlap. +- `contactPoint`: The estimated point of impact. + +### LimitRect + +A structural boundary used to restrict actor movement (e.g., keeping the player inside the camera view or level bounds). + +### CollisionSystem Constants + +| Constant | Description | +|----------|-------------| +| `PHYSICS_MAX_PAIRS` | Max broadphase collision pairs (default: 128). | +| `PHYSICS_MAX_CONTACTS` | Max simultaneous narrowphase contacts (default: 128). | +| `VELOCITY_ITERATIONS` | Number of passes in the impulse solver (default: 2). | + +## Tile Collision Utilities + +### TileAttributes + +Custom metadata attached to tiles. Managed via `TileConsumptionHelper`. + +> [!IMPORTANT] +> Since attributes are stored in Flash memory on ESP32, use **`PIXELROOT32_STRCMP_P`** or **`PIXELROOT32_MEMCPY_P`** to compare or copy strings from attributes. + +### TileConsumptionHelper + +A utility to consume tilemap arrays and extract custom properties (like solid/water/damage flags). + +### TileCollisionBuilder + +A utility that converts grid-based tilemaps into optimized `StaticActor` collision blocks. It merges adjacent solid tiles into larger rectangles to drastically reduce the broadphase entity count. + +**Example Usage:** + +```cpp +using pixelroot32::physics::TileCollisionBuilder; +using pixelroot32::physics::CollisionBox; + +std::vector boxes; +TileCollisionBuilder::buildOptimizedBoxes( + mapWidth, mapHeight, tileSize, + [&](int x, int y) { + return isTileSolid(x, y); // Your logic + }, + boxes +); + +// Spawn a StaticActor for each resulting box +for (const auto& box : boxes) { + auto actor = scene.createEntity(); + actor->setPosition(box.x, box.y); + actor->setSize(box.width, box.height); +} +``` + +## Related Documentation + +- [API Reference](index.md) - Main index +- [Core Module](core.md) - Scene and Entity +- [Math Module](math.md) - Vector2 and Scalar \ No newline at end of file diff --git a/api/physics/collision-system.md b/api/physics/collision-system.md deleted file mode 100644 index d073362..0000000 --- a/api/physics/collision-system.md +++ /dev/null @@ -1,539 +0,0 @@ -# CollisionSystem & physics core - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Physics Module Overview - -The Physics module provides a high-performance "Flat Solver" optimized for microcontrollers. It handles collision detection, position resolution, and physical integration for different types of bodies. - ---- - -## Actor - -**Include:** `core/Actor.h` - -**Inherits:** [Entity](/api/core/entity) - -The base class for all objects capable of collision. Actors extend Entity with collision layers, masks, and shape definitions. Note: You should typically use a specialized subclass like `RigidActor` or `KinematicActor` instead of this base class. - -### Constants - -- **`enum CollisionShape`** - - `AABB`: Axis-aligned bounding box (Rectangle). - - `CIRCLE`: Circular collision body. - -### Properties - -- **`uint16_t entityId`**: Unique id assigned by `CollisionSystem::addEntity` (used for pair deduplication). `0` = unregistered. -- **`int queryId`**: Used internally by the spatial grid for deduplication in `getPotentialColliders`. -- **`CollisionLayer layer`**: Bitmask representing the layers this actor belongs to. -- **`CollisionLayer mask`**: Bitmask representing the layers this actor scans for collisions. - -### Public Methods - -- **`Actor(Scalar x, Scalar y, int w, int h)`** - Constructs a new Actor. - -- **`void setCollisionLayer(CollisionLayer l)`** - Sets the collision layer this actor belongs to. - -- **`void setCollisionMask(CollisionLayer m)`** - Sets the collision layers this actor interacts with. - -- **`bool isInLayer(uint16_t targetLayer) const`** - Checks if the Actor belongs to a specific collision layer. - -- **`virtual Rect getHitBox()`** - Returns the bounding rectangle for AABB detection or the bounding box of the circle. - -- **`virtual void onCollision(Actor* other)`** - Callback invoked when a collision is detected. **Note:** All collision responses (velocity/position changes) are handled by the `CollisionSystem`. This method is for gameplay notifications only. - ---- - - -## PhysicsActor - -**Include:** `core/PhysicsActor.h` - -**Inherits:** [Actor](#actor) - -Base class for all physics-enabled bodies. It provides the core integration and response logic used by the `CollisionSystem`. - -### Properties - -- **`Vector2 velocity`**: Current movement speed in pixels/second. -- **`Vector2 previousPosition`**: Position from the previous physics frame (used for spatial crossing detection). -- **`Scalar mass`**: Mass of the body (Default: `1.0`). -- **`Scalar restitution`**: Bounciness factor (0.0 = no bounce, 1.0 = perfect bounce). -- **`Scalar friction`**: Friction coefficient (not yet fully implemented in solver). -- **`Scalar gravityScale`**: Multiplier for global gravity (Default: `1.0`). -- **`CollisionShape shape`**: The geometric shape used for detection (Default: `AABB`). -- **`Scalar radius`**: Radius used if `shape` is `CIRCLE` (Default: `0`). -- **`bool bounce`** (property accessor): If `true`, the actor will bounce off surfaces based on its restitution (Default: `true`). Supports both `actor->bounce = false` (property syntax) and explicit `actor->setBounce(false)` / `actor->isBounce()` methods. Internally stored in packed flags. -- **`bool sensor`** (property accessor): When true, the body generates collision events but does not produce physical response (no impulse, no penetration correction). Use for triggers, collectibles. Supports both `actor->sensor = true` (property syntax) and explicit `actor->setSensor(true)` / `actor->isSensor()` methods. Internally stored in packed flags. -- **`bool oneWay`** (property accessor): When true, the body only blocks from one side (e.g. one-way platform: land from above, pass through from below). Supports both `actor->oneWay = true` (property syntax) and explicit `actor->setOneWay(true)` / `actor->isOneWay()` methods. Internally stored in packed flags. -- **`void* userData`**: Optional pointer or packed value (e.g. tile coordinates) for game logic. Use `physics::packTileData` / `unpackTileData` from `physics/TileAttributes.h` for tile metadata. - -### Constructors - -- **`PhysicsActor(Scalar x, Scalar y, int w, int h)`** - Constructs a new PhysicsActor. - -- **`PhysicsActor(Vector2 position, int w, int h)`** - Constructs a new PhysicsActor using a position vector. - -### Public Methods - -- **`void update(unsigned long deltaTime)`** - Updates the actor state, applying physics integration and checking world boundary collisions. - -- **`void setVelocity(const Vector2& v)`** - Sets the linear velocity of the actor. Also supports `(Scalar x, Scalar y)` and `(float x, float y)`. - -- **`const Vector2& getVelocity() const`** - Gets the current velocity vector. - -- **`void updatePreviousPosition()`** - Updates the previous position to the current position. Should be called at the start of each physics frame to track position history for spatial crossing detection (e.g., one-way platforms). This is automatically called by `CollisionSystem::update()`. - -- **`Vector2 getPreviousPosition() const`** - Gets the position from the previous physics frame. Used internally for one-way platform validation. - -- **`void setPosition(Vector2 pos)`** - Sets the position and syncs previous position. When position is set directly (not via physics integration), the previous position is also updated to prevent false crossing detection. - -- **`void setRestitution(Scalar r)`** - Sets the restitution (bounciness). 1.0 means perfect bounce, < 1.0 means energy loss. - -- **`void setFriction(Scalar f)`** - Sets the friction coefficient (0.0 means no friction). - -- **`void setSensor(bool s)`** - Sets whether this body is a sensor (trigger). Sensors fire `onCollision` but do not receive impulse or penetration correction. - -- **`bool isSensor() const`** - Returns true if this body is a sensor. - -- **`void setOneWay(bool w)`** - Sets whether this body is a one-way platform (blocks only from one side, e.g. from above). - -- **`bool isOneWay() const`** - Returns true if this body is a one-way platform. - -- **`void setBounce(bool b)`** - Sets whether this body bounces off surfaces (uses restitution). When false, velocity is zeroed on contact instead of reflected. - -- **`bool isBounce() const`** - Returns true if this body has bounce enabled. - -- **`void setUserData(void* ptr)`** - Sets optional user data (e.g. tile coordinates or game-specific pointer). - -- **`void* getUserData() const`** - Gets the current user data. - -- **`void setLimits(const LimitRect& limits)`** - Sets custom movement limits for the actor. - -- **`void setWorldBounds(int w, int h)`** - Defines the world size for boundary checking, used as default limits. - -- **`WorldCollisionInfo getWorldCollisionInfo() const`** - Gets information about collisions with the world boundaries. - -- **`void resetWorldCollisionInfo()`** - Resets the world collision flags for the current frame. - -- **`PhysicsBodyType getBodyType() const`** - Gets the simulation body type (STATIC, KINEMATIC, or RIGID). - -- **`void setBodyType(PhysicsBodyType type)`** - Sets the simulation body type. - -- **`void setMass(float m)`** - Sets the mass of the actor. - -- **`pixelroot32::math::Scalar getMass() const`** - Gets the mass of the actor. - -- **`void setGravityScale(pixelroot32::math::Scalar scale)`** - Sets the gravity scale multiplier. - -- **`pixelroot32::math::Scalar getGravityScale() const`** - Gets the gravity scale multiplier. - -- **`CollisionShape getShape() const`** - Gets the collision shape type (AABB or CIRCLE). - -- **`void setShape(CollisionShape s)`** - Sets the collision shape type. - -- **`pixelroot32::math::Scalar getRadius() const`** - Gets the radius (only meaningful for CIRCLE shape). - -- **`void setRadius(pixelroot32::math::Scalar r)`** - Sets the radius and updates width/height to match diameter. - -- **`void integrate(pixelroot32::math::Scalar dt)`** - Integrates velocity to update position. - -- **`void resolveWorldBounds()`** - Resolves collisions with the defined world or custom bounds. - -- **`virtual void onWorldCollision()`** - Callback triggered when this actor collides with world boundaries. Override to implement custom behavior. - -- **`void setWorldSize(int w, int h)`** - Alias for `setWorldBounds()`. - -- **`pixelroot32::math::Scalar getVelocityX() const`** - Gets the horizontal velocity component. - -- **`pixelroot32::math::Scalar getVelocityY() const`** - Gets the vertical velocity component. - ---- - - -## LimitRect - -**Inherits:** None - -Bounding rectangle for world-collision resolution. Defines the limits of the play area. - -### Properties - -- **`left`**: Left boundary (-1 for no limit). -- **`top`**: Top boundary (-1 for no limit). -- **`right`**: Right boundary (-1 for no limit). -- **`bottom`**: Bottom boundary (-1 for no limit). - -### Constructors - -- **`LimitRect(int l, int t, int r, int b)`** - Constructs a LimitRect with specific bounds. - ---- - - -## WorldCollisionInfo - -**Inherits:** None - -Information about world collisions in the current frame. Holds flags indicating which sides of the play area the actor collided with. - -### Properties - -- **`left`**: True if collided with the left boundary. -- **`right`**: True if collided with the right boundary. -- **`top`**: True if collided with the top boundary. -- **`bottom`**: True if collided with the bottom boundary. - ---- - - -## CircleActor (Pattern) - -While the engine defines `RigidActor` and `StaticActor`, creating a circular object is done by setting the `shape` property. - -**Structure:** - -```cpp -class MyCircle : public RigidActor { -public: - MyCircle(Scalar x, Scalar y, Scalar r) : RigidActor(x, y, r*2, r*2) { - shape = CollisionShape::CIRCLE; - radius = r; - } -}; -``` - ---- - - -## Collision Primitives - -**Inherits:** None - -Lightweight geometric primitives and helpers used by the physics and collision systems. - -### Types - -- **`struct Circle`** - Represents a circle in 2D space. - - - `Scalar x, y` – center position. - - `Scalar radius` – circle radius. - -- **`struct Segment`** - Represents a line segment between two points. - - - `Scalar x1, y1` – start point. - - `Scalar x2, y2` – end point. - -### Helper Functions - -- **`bool intersects(const Circle& a, const Circle& b)`** - Returns true if two circles overlap. - -- **`bool intersects(const Circle& c, const Rect& r)`** - Returns true if a circle overlaps an axis-aligned rectangle. - -- **`bool intersects(const Segment& s, const Rect& r)`** - Returns true if a line segment intersects an axis-aligned rectangle. - -- **`bool sweepCircleVsRect(const Circle& start, const Circle& end, const Rect& rect, Scalar& tHit)`** - Performs a simple sweep test between two circle positions against a rectangle. - Returns true if a collision occurs between `start` and `end`, writing the normalized hit time in `tHit` (`0.0f` = at `start`, `1.0f` = at `end`). - ---- - - -## DefaultLayers - -**Inherits:** None - -Namespace with common collision layer constants: - -- `kNone`: 0 (No collision) -- `kAll`: 0xFFFF (Collides with everything) - ---- - - -## CollisionSystem - -**Inherits:** None - -The central physics system implementing **Flat Solver**. Manages collision detection and resolution with fixed timestep for deterministic behavior. Uses a **dual-layer spatial grid** (static + dynamic) to minimize per-frame work when many static tiles are present, and a **fixed-size contact pool** (`PHYSICS_MAX_CONTACTS`, default 128; overridable via build flags) to avoid heap allocations in the hot path. - -### Key Logic: "The Flat Solver" - -The solver executes in strict order: - -1. **Detect Collisions**: Rebuilds static grid if dirty, clears dynamic layer, inserts RIGID/KINEMATIC into dynamic layer; queries grid for potential pairs; narrowphase and contact generation. Contacts are stored in a fixed array; excess contacts are dropped when the pool is full. -2. **Solve Velocity**: Impulse-based collision response (2 iterations by default); sensor contacts are skipped. -3. **Integrate Positions**: Updates positions: `p = p + v * dt` (RIGID only). -4. **Solve Penetration**: Baumgarte stabilization with slop threshold; sensor contacts skipped. -5. **Trigger Callbacks**: Calls `onCollision()` for all contacts. - -### Public Constants - -- **`FIXED_DT`**: Fixed timestep (`1/60s`) -- **`SLOP`**: Minimum penetration to correct (`0.02f`) -- **`BIAS`**: Position correction factor (`0.2f`) -- **`VELOCITY_ITERATIONS`**: Impulse solver iterations (`2`) -- **`VELOCITY_THRESHOLD`**: Zero restitution below this speed (`0.5f`) -- **`CCD_THRESHOLD`**: CCD activation threshold (`3.0f`) - -### Public Methods - -- **`void update()`** - Executes the full physics pipeline. Called automatically by `Scene::update()`. - -- **`void detectCollisions()`** - Broadphase and narrowphase detection. Populates contact list. - -- **`void solveVelocity()`** - Impulse-based velocity solver. Applies collision response. - -- **`void integratePositions()`** - Updates positions using velocity. Only affects `RigidActor`. - -- **`void solvePenetration()`** - Position correction using Baumgarte stabilization. - -- **`void triggerCallbacks()`** - Invokes `onCollision()` for all contacts. - -- **`bool needsCCD(PhysicsActor* body)`** - Returns true if body needs Continuous Collision Detection (fast-moving circles). - -- **`bool sweptCircleVsAABB(PhysicsActor* circle, PhysicsActor* box, Scalar& outTime, Vector2& outNormal)`** - Performs swept test for CCD. Returns collision time (0.0-1.0) and normal. - -- **`bool validateOneWayPlatform(PhysicsActor* actor, PhysicsActor* platform, const Vector2& collisionNormal)`** - Validates whether a one-way platform collision should be resolved based on spatial crossing detection. Returns `true` if the collision should be resolved (actor crossed from above), `false` otherwise. This method checks: - - If the platform is a one-way platform - - If the collision normal points upward (actor above platform) - - If the actor crossed the platform surface from above (using previous position) - - If the actor is moving downward or stationary - - Rejects horizontal collisions (side collisions with one-way platforms) - -- **`size_t getEntityCount() const`** - Returns number of entities in the system. - -- **`void clear()`** - Removes all entities, resets the contact count, and clears the spatial grid (both static and dynamic layers). - ---- - - -## TileAttributes (Physics) - -**Include:** `physics/TileAttributes.h` -**Namespace:** `pixelroot32::physics` - -Helpers for encoding tile metadata in `PhysicsActor::userData`, used by tilemap collision builders and game logic. Supports both a **flags-based** API (recommended for new code) and a legacy **behavior enum** API. - -### TileFlags (recommended) - -- **`enum TileFlags : uint8_t`**: Bit flags for tile behavior (1 byte per tile, no strings at runtime). Values: `TILE_NONE`, `TILE_SOLID`, `TILE_SENSOR`, `TILE_DAMAGE`, `TILE_COLLECTIBLE`, `TILE_ONEWAY`, `TILE_TRIGGER` (bits 6–7 reserved). -- **`packTileData(uint16_t x, uint16_t y, TileFlags flags)`**: Packs coords (10+10 bits) and flags (8 bits) into `uintptr_t` for `setUserData()`. -- **`unpackTileData(uintptr_t packed, uint16_t& x, uint16_t& y, TileFlags& flags)`**: Unpacks for use in `onCollision`. -- **`getTileFlags(const TileBehaviorLayer& layer, int x, int y)`**: O(1) lookup with bounds check; returns `TILE_NONE` (0) when out of bounds. -- **`isSensorTile(TileFlags flags)`** / **`isOneWayTile(TileFlags flags)`** / **`isSolidTile(TileFlags flags)`**: Derive physics config from flags for the collision builder. - -### TileBehaviorLayer - -- **`struct TileBehaviorLayer`**: `const uint8_t* data`, `uint16_t width`, `uint16_t height`. Points to a dense array (1 byte per tile) exported by the Tilemap Editor. Use with `getTileFlags()` for O(1) lookups. - -### Legacy (deprecated for new code) - -- **`enum class TileCollisionBehavior`**: `SOLID`, `SENSOR`, `ONE_WAY_UP`, `DAMAGE`, `DESTRUCTIBLE`. -- **`packTileData(x, y, TileCollisionBehavior)`** / **`unpackTileData(..., TileCollisionBehavior&)`**: Same encoding with 4-bit behavior. -- **`packCoord(x, y)`** / **`unpackCoord(packed, x, y)`**: Legacy 16+16 bit encoding for coords only. - ---- - - -## TileConsumptionHelper - -**Include:** `physics/TileConsumptionHelper.h` -**Namespace:** `pixelroot32::physics` - -Helper for **consumible tiles** (e.g. coins, pickups): removes the tile's physics body from the scene and updates the tilemap's `runtimeMask` so the tile is no longer drawn. Reuses `TileMapGeneric::runtimeMask` (no separate consumed mask). - -- **`struct TileConsumptionConfig`**: Optional config: `updateTilemap`, `logConsumption`, `validateCoordinates`. -- **`TileConsumptionHelper(Scene& scene, void* tilemap, const TileConsumptionConfig& config)`**: Constructor. `tilemap` is `TileMapGeneric*` (any of `Sprite`, `Sprite2bpp`, `Sprite4bpp`). -- **`bool consumeTile(Actor* tileActor, uint16_t tileX, uint16_t tileY)`**: Removes `tileActor` from the scene and sets `setTileActive(tileX, tileY, false)` on the tilemap. If `tileActor == nullptr`, only updates the tilemap mask. -- **`bool consumeTileFromUserData(Actor* tileActor, uintptr_t packedUserData)`**: Unpacks coords/flags from userData and consumes only if `TILE_COLLECTIBLE` is set. -- **`bool isTileConsumed(uint16_t tileX, uint16_t tileY) const`**: Returns whether the tile is inactive in the tilemap. -- **`bool restoreTile(uint16_t tileX, uint16_t tileY)`**: Sets the tile active again (visual only; does not re-add a physics body). - -**Convenience functions:** - -- **`consumeTileFromCollision(tileActor, packedUserData, scene, tilemap, config)`**: One-shot consumption from an `onCollision` callback. -- **`consumeTilesBatch(scene, tilemap, tiles[][2], count, config)`**: Updates `runtimeMask` for multiple tiles (no entity removal; use for clearing areas or reset). - ---- - - -## TileCollisionBuilder - -**Include:** `physics/TileCollisionBuilder.h` -**Namespace:** `pixelroot32::physics` - -High-level builder that generates `StaticActor` or `SensorActor` bodies from a `TileBehaviorLayer`. Iterates all tiles with non-zero flags, creates the appropriate physics body, configures it (sensor, one-way), packs coords and flags into `userData`, and adds it to the scene. This is the recommended way to populate physics for tilemap-based levels. - -### TileCollisionBuilderConfig - -```cpp -struct TileCollisionBuilderConfig { - uint8_t tileWidth; // Width of each tile in world units (e.g., 16) - uint8_t tileHeight; // Height of each tile in world units (e.g., 16) - uint16_t maxEntities; // Maximum entities to create (safety limit) - - TileCollisionBuilderConfig(uint8_t w = 16, uint8_t h = 16, uint16_t max = 0xFFFF); -}; -``` - -### Class Definition - -```cpp -class TileCollisionBuilder { -public: - TileCollisionBuilder(pixelroot32::core::Scene& scene, - const TileCollisionBuilderConfig& config = TileCollisionBuilderConfig()); - - int buildFromBehaviorLayer(const TileBehaviorLayer& layer, uint8_t layerIndex = 0); - int getEntitiesCreated() const; - void reset(); -}; -``` - -### Public Methods - -- **`TileCollisionBuilder(Scene& scene, const TileCollisionBuilderConfig& config)`** - Constructs the builder bound to a scene. - -- **`int buildFromBehaviorLayer(const TileBehaviorLayer& layer, uint8_t layerIndex = 0)`** - Iterates all tiles in the layer. For each tile with `flags != TILE_NONE`: - - Creates `StaticActor` (solid, one-way) or `SensorActor` (sensor, damage, collectible) - - Configures via `setSensor()` / `setOneWay()` from flags - - Sets `setCollisionLayer(kDefaultItemCollisionLayer)` and `setCollisionMask(kDefaultItemCollisionMask)` - - Calls `setUserData(reinterpret_cast(packTileData(x, y, flags)))` - - Adds to scene via `scene.addEntity()` - - Returns the total number of entities created. - -- **`int getEntitiesCreated() const`** - Returns the count from the last `buildFromBehaviorLayer()` call. - -- **`void reset()`** - Resets `entitiesCreated` to 0. Does not clear the scene. - -### Convenience Helper - -```cpp -inline int buildTileCollisions( - pixelroot32::core::Scene& scene, - const TileBehaviorLayer& layer, - uint8_t tileWidth = 16, - uint8_t tileHeight = 16, - uint8_t layerIndex = 0 -); -``` - -One-liner that creates a builder, calls `buildFromBehaviorLayer()`, and returns the count. - -### Usage Example - -```cpp -#include "physics/TileCollisionBuilder.h" - -void GameScene::init() override { - // Behavior layer exported by Tilemap Editor (dense uint8_t[] array) - TileBehaviorLayer layer = { behaviorData, 32, 32 }; - - // Basic usage (one-liner) - int count = buildTileCollisions(*this, layer, 16, 16, 0); - - // Or with explicit config - TileCollisionBuilderConfig config(16, 16, 2048); // 16x16 tiles, max 2048 bodies - TileCollisionBuilder builder(*this, config); - int entities = builder.buildFromBehaviorLayer(layer, 0); -} -``` - -### Integration with onCollision - -After collision bodies are created, use `userData` in callbacks to identify the tile: - -```cpp -void PlayerActor::onCollision(Actor* other) override { - if (other->getUserData()) { - uintptr_t packed = reinterpret_cast(other->getUserData()); - uint16_t tx, ty; - TileFlags flags; - unpackTileData(packed, tx, ty, flags); - - if (flags & TILE_COLLECTIBLE) { - TileConsumptionHelper helper(*scene, tilemap); - helper.consumeTileFromUserData(other, packed); - } - if (flags & TILE_DAMAGE) { - takeDamage(); - } - } -} -``` - -### Memory Considerations - -- Each created actor is a heap allocation (`new StaticActor` / `new SensorActor`). -- Call `scene.clearEntities()` before rebuilding to avoid duplicates. -- On ESP32, keep `maxEntities` reasonable; 32×32 tiles with every tile solid = 1024 bodies. - ---- diff --git a/api/physics/kinematic-actor.md b/api/physics/kinematic-actor.md deleted file mode 100644 index 10e2fa6..0000000 --- a/api/physics/kinematic-actor.md +++ /dev/null @@ -1,47 +0,0 @@ -# KinematicActor - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## KinematicActor - -**Inherits:** [PhysicsActor](#physicsactor) - -A body that is moved manually via code but still interacts with the physics world (stops at walls, pushes objects). Ideal for players and moving platforms. - -### Constructors - -- **`KinematicActor(Scalar x, Scalar y, int w, int h)`** - Constructs a new KinematicActor. - -- **`KinematicActor(Vector2 position, int w, int h)`** - Constructs a new KinematicActor using a position vector. - -### Public Methods - -- **`bool moveAndCollide(Vector2 relativeMove)`** - Moves the actor by `relativeMove`. If a collision occurs, it stops at the point of contact and returns `true`. -- **`Vector2 moveAndSlide(Vector2 velocity)`** - Moves the actor, sliding along surfaces if it hits a wall or floor. Returns the remaining velocity. - -- **`bool is_on_ceiling() const`** - Returns true if the body collided with the ceiling during the last `moveAndSlide` call. - -- **`bool is_on_floor() const`** - Returns true if the body collided with the floor during the last `moveAndSlide` call. - -- **`bool is_on_wall() const`** - Returns true if the body collided with a wall during the last `moveAndSlide` call. - -**Example:** - -```cpp -void Player::update(unsigned long dt) { - Vector2 motion(0, 0); - if (input.isButtonDown(0)) motion.x += 100 * dt / 1000.0f; - - // Automatic sliding against walls - moveAndSlide(motion); -} -``` - ---- diff --git a/api/physics/rigid-actor.md b/api/physics/rigid-actor.md deleted file mode 100644 index e32f30b..0000000 --- a/api/physics/rigid-actor.md +++ /dev/null @@ -1,32 +0,0 @@ -# RigidActor - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## RigidActor - -**Inherits:** [PhysicsActor](#physicsactor) - -A body fully simulated by the physics engine. It is affected by gravity, forces, and collisions with other bodies. Ideal for debris, boxes, and physical props. - -### Constructors - -- **`RigidActor(Scalar x, Scalar y, int w, int h)`** - Constructs a new RigidActor. - -- **`RigidActor(Vector2 position, int w, int h)`** - Constructs a new RigidActor using a position vector. - -### Properties - -- **`bool bounce`** (property accessor): Whether the object should use restitution for bounces. Supports `actor->bounce = true` (property syntax) or explicit `actor->setBounce(true)` / `actor->isBounce()` methods. Internally stored in packed flags. - -**Example:** - -```cpp -auto box = std::make_unique(100, 0, 16, 16); -box->setCollisionLayer(Layers::kProps); -box->bounce = true; // Make it bouncy -scene->addEntity(box.get()); -``` - ---- diff --git a/api/physics/sensor-actor.md b/api/physics/sensor-actor.md deleted file mode 100644 index c7098fd..0000000 --- a/api/physics/sensor-actor.md +++ /dev/null @@ -1,22 +0,0 @@ -# SensorActor - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## SensorActor - -**Inherits:** [StaticActor](#staticactor) - -A static body that acts as a **trigger**: it generates `onCollision` callbacks but does not produce any physical response (no impulse, no penetration correction). Use for collectibles, checkpoints, damage zones, or area triggers. - -**Include:** `physics/SensorActor.h` - -**Constructors:** Same as `StaticActor`; internally calls `setSensor(true)`. - -```cpp -SensorActor coin(x, y, 16, 16); -coin.setCollisionLayer(Layers::kCollectible); -scene->addEntity(&coin); -// In player's onCollision: if (other->isSensor()) { collectCoin(other); } -``` - ---- diff --git a/api/physics/static-actor.md b/api/physics/static-actor.md deleted file mode 100644 index ad6e873..0000000 --- a/api/physics/static-actor.md +++ /dev/null @@ -1,27 +0,0 @@ -# StaticActor - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## StaticActor - -**Inherits:** [PhysicsActor](#physicsactor) - -An immovable body that other objects can collide with. Ideal for floors, walls, and level geometry. Static bodies are placed in the **static layer** of the spatial grid (rebuilt only when entities are added or removed), reducing per-frame cost in levels with many tiles. - -### Constructors - -- **`StaticActor(Scalar x, Scalar y, int w, int h)`** - Constructs a new StaticActor. - -- **`StaticActor(Vector2 position, int w, int h)`** - Constructs a new StaticActor using a position vector. - -**Example:** - -```cpp -auto floor = std::make_unique(0, 230, 240, 10); -floor->setCollisionLayer(Layers::kWall); -scene->addEntity(floor.get()); -``` - ---- diff --git a/api/platform.md b/api/platform.md new file mode 100644 index 0000000..3279c20 --- /dev/null +++ b/api/platform.md @@ -0,0 +1,85 @@ +# API Reference: Platform Abstractions + +> **Source of truth:** +> - `include/core/Log.h` +> - `include/platforms/PlatformMemory.h` +> - `include/platforms/PlatformCapabilities.h` + +## Overview + +The Platform module provides a hardware abstraction layer (HAL) for the engine. It ensures that platform-specific features (like memory allocation on the ESP32) can be used optimally, while falling back to standard implementations on other platforms (like Windows/Linux via SDL2). + +## Key Concepts + +### Logging System + +A unified logging system that outputs to the Serial console on ESP32 or standard output on PC. It is enabled by defining `PIXELROOT32_DEBUG_MODE=1` in your build flags. If debug mode is disabled, all log macros are stripped by the preprocessor, resulting in zero overhead. + +**Log Levels:** +- `PR32_LOG_ERROR(fmt, ...)`: Critical errors. +- `PR32_LOG_WARN(fmt, ...)`: Warnings. +- `PR32_LOG_INFO(fmt, ...)`: General information. +- `PR32_LOG_DEBUG(fmt, ...)`: Verbose debug info. + +**Usage Example:** +```cpp +#include "core/Log.h" + +void init() { + PR32_LOG_INFO("Initializing game... Width: %d", 240); +} +``` + +### Conditional Compilation + +The engine uses macros to detect the current platform. You can use these in your own game code if you need platform-specific behavior. + +- `PIXELROOT32_PLATFORM_ESP32`: Defined when compiling for any ESP32 variant. +- `PIXELROOT32_PLATFORM_NATIVE`: Defined when compiling for PC (SDL2). + +**Usage Example:** +```cpp +#ifdef PIXELROOT32_PLATFORM_ESP32 + // Setup ESP32-specific hardware pins + pinMode(2, OUTPUT); +#else + // Setup PC equivalent or mock + PR32_LOG_INFO("Running on PC emulator"); +#endif +``` + +### Platform Memory Allocation + +On the ESP32, standard `malloc` and `new` allocate from the default internal RAM. However, the ESP32 also has external PSRAM and faster internal IRAM. The `PlatformMemory` macros abstract these platform-specific allocations. On PC, these macros safely fall back to standard `malloc`/`free`. + +| Macro | Description | +|-------|-------------| +| `PR32_MALLOC(size)` | Standard allocation (internal RAM). | +| `PR32_MALLOC_PSRAM(size)` | Allocates in external PSRAM (if available). Great for large tilemaps or audio buffers. | +| `PR32_MALLOC_DMA(size)` | Allocates DMA-capable memory. Required for SPI display buffers. | +| `PR32_FREE(ptr)` | Safely frees memory allocated by any of the above macros. | + +**Usage Example:** +```cpp +#include "platforms/PlatformMemory.h" + +// Allocate a large buffer in PSRAM to save precious internal RAM +uint8_t* largeBuffer = (uint8_t*)PR32_MALLOC_PSRAM(1024 * 1024); + +if (largeBuffer != nullptr) { + // Use buffer... + PR32_FREE(largeBuffer); +} +``` + +### Platform Capabilities + +Detected hardware capabilities, such as the number of CPU cores and recommended audio task pinning. + +> **Note**: For detailed information on the `PlatformCapabilities` struct, refer to the [Core Module](core.md#platformcapabilities). + +## Related Documentation + +- [API Reference](index.md) - Main index +- [Core Module](core.md) - PlatformCapabilities +- [Configuration](config.md) - Build flags \ No newline at end of file diff --git a/api/platform/platform-capabilities.md b/api/platform/platform-capabilities.md deleted file mode 100644 index 631eaab..0000000 --- a/api/platform/platform-capabilities.md +++ /dev/null @@ -1,88 +0,0 @@ -# Platform capabilities & logging - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Platform Abstractions Overview - -Version 1.1.0 introduces unified abstractions for cross-platform operations, eliminating the need for manual `#ifdef` blocks in user code. - ---- - - -## Logging System - -**Namespace:** `pixelroot32::core::logging` - -The unified logging system provides platform-agnostic logging with different log levels, automatically routing to the appropriate output (Serial for ESP32, stdout for native). Enable with `-DPIXELROOT32_DEBUG_MODE` in build flags. - -### Log Levels - -| LogLevel Enum | Output Prefix | Use Case | -|--------------|---------------|----------| -| `LogLevel::Info` | `[INFO]` | General information, debug messages | -| `LogLevel::Profiling` | `[PROF]` | Performance timing markers | -| `LogLevel::Warning` | `[WARN]` | Warnings, non-critical issues | -| `LogLevel::Error` | `[ERROR]` | Errors, critical failures | - -### Functions - -- **`void log(LogLevel level, const char* format, ...)`** - Logs a message with the specified level and printf-style formatting. - -- **`void log(const char* format, ...)`** - Logs a message with Info level (shorthand). - -### Conditional Compilation - -When `PIXELROOT32_DEBUG_MODE` is **not defined**, all `log()` calls become no-ops at compile time. The engine uses a double-layer conditional: - -1. **`#ifdef PIXELROOT32_DEBUG_MODE`** in the header makes `log()` calls emit formatting code -2. **`if constexpr (EnableLogging)`** in the implementation skips runtime formatting - -This means zero runtime cost in production builds (no string formatting, no branching). - -### Usage Example - -```cpp -// Enable in platformio.ini: -// build_flags = -D PIXELROOT32_DEBUG_MODE - -#include "core/Log.h" - -using namespace pixelroot32::core::logging; - -// Log with explicit level -log(LogLevel::Info, "Player position: %d", playerX); - -// Log warning -log(LogLevel::Warning, "Low memory: %d bytes free", freeRAM); - -// Log error -log(LogLevel::Error, "Failed to load sprite: %s", filename); - -// Log with default Info level -log("Player position: %d", playerX); -``` - ---- - - -## PlatformCapabilities - -**Namespace:** `pixelroot32::platforms` - -A structure that holds detected hardware capabilities, used to optimize task pinning and threading. - -### Properties - -- **`bool hasDualCore`**: True if the hardware has more than one CPU core. -- **`int coreCount`**: Total number of CPU cores detected. -- **`int audioCoreId`**: Recommended CPU core for audio tasks. -- **`int mainCoreId`**: Recommended CPU core for the main game loop. -- **`int audioPriority`**: Recommended priority for audio tasks. - -### Static Methods - -- **`static PlatformCapabilities detect()`**: Automatically detects hardware capabilities based on the platform and configuration. It respects the defaults defined in `platforms/PlatformDefaults.h` and any compile-time overrides. - ---- diff --git a/api/platform/platform-memory.md b/api/platform/platform-memory.md deleted file mode 100644 index 103ab3b..0000000 --- a/api/platform/platform-memory.md +++ /dev/null @@ -1,48 +0,0 @@ -# Platform memory - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Platform Memory Abstraction - -**Include:** `platforms/PlatformMemory.h` - -Provides a unified API for memory operations that differ between ESP32 (Flash/PROGMEM) and Native (RAM) platforms. - -### Macros - -- **`PIXELROOT32_FLASH_ATTR`** - Attribute for data stored in Flash memory. - -- **`PIXELROOT32_STRCMP_P(dest, src)`** - Compare a RAM string with a Flash string. - -- **`PIXELROOT32_MEMCPY_P(dest, src, size)`** - Copy data from Flash to RAM. - -- **`PIXELROOT32_READ_BYTE_P(addr)`** - Read an 8-bit value from Flash. - -- **`PIXELROOT32_READ_WORD_P(addr)`** - Read a 16-bit value from Flash. - -- **`PIXELROOT32_READ_DWORD_P(addr)`** - Read a 32-bit value from Flash. - -- **`PIXELROOT32_READ_FLOAT_P(addr)`** - Read a float value from Flash. - -- **`PIXELROOT32_READ_PTR_P(addr)`** - Read a pointer from Flash. - -### Usage Example - -```cpp -#include "platforms/PlatformMemory.h" - -const char MY_STRING[] PIXELROOT32_FLASH_ATTR = "Hello"; -char buffer[10]; -PIXELROOT32_STRCMP_P(buffer, MY_STRING); -uint8_t val = PIXELROOT32_READ_BYTE_P(&my_array[i]); -``` - ---- diff --git a/api/ui.md b/api/ui.md new file mode 100644 index 0000000..0a2c7f6 --- /dev/null +++ b/api/ui.md @@ -0,0 +1,134 @@ +# API Reference: UI Module + +> **Source of truth:** +> - `include/graphics/ui/UIElement.h`, `UIButton.h`, `UILabel.h`, `UIPanel.h`, `UICheckbox.h` +> - `include/graphics/ui/UILayout.h`, `UIGridLayout.h`, `UIVerticalLayout.h`, `UIHorizontalLayout.h`, `UIAnchorLayout.h`, `UIPaddingContainer.h` +> - `include/graphics/ui/UITouchWidget.h`, `UITouchElement.h`, `UITouchButton.h`, `UITouchSlider.h`, `UITouchCheckbox.h` +> - `include/graphics/ui/UIManager.h`, `UIHitTest.h` + +## Overview + +*(Requires `PIXELROOT32_ENABLE_UI_SYSTEM=1`)* + +The UI module provides a hierarchical, object-oriented user interface system. It is designed to be lightweight, resolution-independent, and fully integrated with the engine's rendering and input systems. + +## Core Elements + +### UIElement + +The abstract base class for all UI nodes. It provides the core hierarchical structure (parent/children relationships), bounds management (`x`, `y`, `width`, `height`), rendering phases (`draw`, `drawChildren`), and visibility toggles. + +### Standard Widgets + +These are primarily non-interactive or controller-driven visual elements. + +- **`UIButton`**: A simple button that renders differently based on its state (Normal, Hover, Pressed, Disabled) and displays text. +- **`UILabel`**: Displays a single line or multiline string of text using a specified `Font`. +- **`UIPanel`**: A generic container with a background color and an optional border. Great for grouping other elements. +- **`UICheckbox`**: A simple binary state widget (checked/unchecked). + +## Layouts + +Layouts are specialized `UIElement`s that automatically position and size their children according to specific rules. + +### UILayout (Base) & ScrollBehavior + +`UILayout` extends `UIElement` to add children management, spacing, and scrolling logic. + +**Scroll Behaviors:** +- `NO_SCROLL`: Content is clipped. +- `AUTO_SCROLL`: Shows scrollbars only when content exceeds bounds. +- `ALWAYS_SCROLL`: Always displays scrollbars. + +### Available Layouts + +- **`UIVerticalLayout`**: Stacks children vertically. +- **`UIHorizontalLayout`**: Stacks children horizontally. +- **`UIGridLayout`**: Arranges children in a 2D grid with defined cell sizes. +- **`UIPaddingContainer`**: A container that adds fixed padding around a single child element. +- **`UIAnchorLayout`**: Positions children based on anchor points relative to the parent's bounds. + - **Anchors**: `TOP_LEFT`, `TOP_CENTER`, `TOP_RIGHT`, `CENTER_LEFT`, `CENTER`, `CENTER_RIGHT`, `BOTTOM_LEFT`, `BOTTOM_CENTER`, `BOTTOM_RIGHT`. + +## Touch Widgets Architecture + +*(Requires `PIXELROOT32_ENABLE_TOUCH=1`)* + +The touch UI system builds on top of the standard UI system but introduces complex state machines for handling direct screen interactions (Press, Drag, Release, Click). + +- **`UITouchWidget`**: The base interface for any UI component that wants to receive raw touch events. +- **`UITouchElement`**: Extends `UIElement` and implements `UITouchWidget`, bridging the visual hierarchy with the touch event system. +- **`UIHitTest`**: A utility class used by the `UIManager` to perform deep hit-testing across the UI tree, respecting `clipToBounds` flags. + +### UITouchButton + +A button fully driven by touch events. + +**Example:** +```cpp +auto touchBtn = std::make_shared(); +touchBtn->setBounds(10, 10, 100, 30); +touchBtn->setText("Tap Me!"); +touchBtn->setOnClick([](UITouchButton* btn) { + PR32_LOG_INFO("Button Tapped!"); +}); +``` + +### UITouchSlider + +A horizontal or vertical slider for selecting a numeric value. Handles dragging and bounds-checking. + +**Example:** +```cpp +auto slider = std::make_shared(); +slider->setBounds(10, 50, 150, 20); +slider->setRange(0.0f, 100.0f); +slider->setValue(50.0f); +slider->setOnValueChanged([](UITouchSlider* s, float val) { + PR32_LOG_INFO("Volume: %f", val); +}); +``` + +### UITouchCheckbox + +A touch-driven toggle switch. + +**Example:** +```cpp +auto checkbox = std::make_shared(); +checkbox->setBounds(10, 90, 20, 20); +checkbox->setChecked(true); +checkbox->setOnToggled([](UITouchCheckbox* cb, bool state) { + PR32_LOG_INFO("Music: %s", state ? "ON" : "OFF"); +}); +``` + +## UIManager + +The `UIManager` is the root of the UI tree for a `Scene`. It manages the top-level element, dispatches touch events via `processTouchEvents()`, and orchestrates rendering. + +**Example:** +```cpp +void MyMenuScene::init() { + auto& ui = getUIManager(); + + // Create root panel + auto panel = std::make_shared(); + panel->setBounds(0, 0, 240, 240); + panel->setBackgroundColor(Color::PR32_BLUE); + + // Add child + auto label = std::make_shared(); + label->setBounds(10, 10, 100, 20); + label->setText("Main Menu"); + panel->addChild(label); + + // Set as root + ui.setRoot(panel); +} +``` + +## Related Documentation + +- [API Reference](index.md) - Main index +- [Input Module](input.md) - Touch event generation +- [Graphics Module](graphics.md) - Renderer and Color \ No newline at end of file diff --git a/architecture/architecture-index.md b/architecture/architecture-index.md new file mode 100644 index 0000000..a0a5322 --- /dev/null +++ b/architecture/architecture-index.md @@ -0,0 +1,141 @@ +# Architecture Index - PixelRoot32 Game Engine + +> **NOTE:** This is the main entry point for architecture documentation. For detailed narratives and design philosophy, see the files in this folder. For ESP32 rendering pipeline details, see the ESP32 section below. + +--- + +## Quick Navigation + +### Layer Architecture + +| Layer | Document | Description | +|-------|----------|-------------| +| **Layer 0** | [Hardware Layer](./layer-hardware.md) | ESP32, displays, audio hardware, PC simulation | +| **Layer 1** | [Driver Layer](./layer-drivers.md) | TFT_eSPI, U8G2, SDL2, AudioBackends | +| **Layer 2** | [Abstraction Layer](./layer-abstraction.md) | DrawSurface, PlatformMemory, Logging, Math | +| **Layer 3** | [System Layer](./layer-systems.md) | Renderer, Audio, Physics, UI subsystems | +| **Layer 4** | [Scene Layer](./layer-scene.md) | Engine, SceneManager, Entity, Actor hierarchy | + +### Subsystem Deep Dives + +| Subsystem | Document | Description | +|-----------|----------|-------------| +| **Audio NES** | [Audio Subsystem](./audio-subsystem.md) | 4-channel NES-style: shared `ApuCore`, `AudioScheduler`, backends | +| **Physics** | [Physics Subsystem](./physics-subsystem.md) | Flat Solver, collisions, CCD | +| **Memory** | [Memory System](./memory-system.md) | Smart pointers, RAII, ESP32 DRAM | +| **Resolution Scaling** | [Resolution Scaling](./resolution-scaling.md) | Logical vs physical resolution | +| **Tile Animation** | [Tile Animation](./tile-animation.md) | Lookup tables, O(1) resolve | +| **Touch Input** | [Touch Input](./touch-input.md) | Pipeline, XPT2046, calibration | +| **Extensibility** | [Extending PixelRoot32](../guide/extending-pixelroot32.md) | Custom drivers, configuration | + +### API Reference + +| Module | Document | +|--------|----------| +| Configuration | [config.md](../api/config.md) | +| Math | [math.md](../api/math.md) | +| Core | [core.md](../api/core.md) | +| Physics | [physics.md](../api/physics.md) | +| Graphics | [graphics.md](../api/graphics.md) | +| UI | [ui.md](../api/ui.md) | +| Audio | [audio.md](../api/audio.md) | +| Input | [input.md](../api/input.md) | +| Platform | [platform.md](../api/platform.md) | + +--- + +## Core Class Hierarchy + +```mermaid +graph TD + Entity --> Actor + Actor --> PhysicsActor + PhysicsActor --> StaticActor + PhysicsActor --> KinematicActor + PhysicsActor --> RigidActor + StaticActor --> SensorActor + Entity --> UIElement + UIElement --> UILayout + UILayout --> UIAnchorLayout + UILayout --> UIGridLayout + UILayout --> UIHorizontalLayout + UILayout --> UIVerticalLayout + UIElement --> UIButton + UIElement --> UILabel + UIElement --> UICheckBox + UIElement --> UIPanel + UIElement --> UIPaddingContainer + UIElement --> UITouchElement + UITouchElement --> UITouchButton + UITouchElement --> UITouchCheckbox + UITouchElement --> UITouchSlider + Entity --> ParticleEmitter + DrawSurface --> BaseDrawSurface +``` + +--- + +## Subsystem Modular Compilation + +| Subsystem | Enable Flag | Default | +|-----------|-------------|---------| +| Audio | `PIXELROOT32_ENABLE_AUDIO` | Enabled | +| Physics | `PIXELROOT32_ENABLE_PHYSICS` | Enabled | +| UI System | `PIXELROOT32_ENABLE_UI_SYSTEM` | Enabled | +| Particles | `PIXELROOT32_ENABLE_PARTICLES` | Enabled | +| Touch Input | `PIXELROOT32_ENABLE_TOUCH` | Disabled | +| Tile Animations | `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enabled | +| Static tilemap FB snapshot (4bpp) | `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enabled (`PlatformDefaults.h`) | +| Debug Overlay | `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Disabled | + +--- + +## ESP32 Rendering Pipeline and Tilemap Caching + +On ESP32 with **TFT_eSPI** (`TFT_eSPI_Drawer`), the logical framebuffer is typically an **8-bit color-depth sprite** (`TFT_eSprite`). Each frame: + +1. **`Renderer::beginFrame()`** obtains a pointer to that buffer via **`DrawSurface::getSpriteBuffer()`** (when the driver supports it), clears the buffer, then draws the scene. +2. **2bpp / 4bpp tilemaps and sprites** can write **directly into that buffer** (matching TFT_eSPI's 8bpp packing for RGB565), avoiding a virtual `drawPixel` per pixel where possible. +3. **`present()` / `sendBuffer()`** converts logical 8bpp rows to **RGB565** using a LUT and pushes pixels to the panel via **DMA**. + +### Static Tilemap Layer Cache + +The engine provides **`pixelroot32::graphics::StaticTilemapLayerCache`** (`include/graphics/StaticTilemapLayerCache.h`): a **4bpp tilemap** helper that can snapshot the logical framebuffer after drawing a **static** group of `TileMap4bppDrawSpec` entries, then on subsequent frames **`memcpy`** that snapshot back and redraw only the **dynamic** group. + +- **Allocation:** `allocateForLogicalSize` / `allocateForRenderer` in `Scene::init()` +- **Opt-out:** build flag `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE=0`, or `setFramebufferCacheEnabled(false)` +- **Example:** `examples/animated_tilemap` — `AnimatedTilemapScene` + +**Game / scene developer contract:** + +- Call **`invalidate()`** when something inside the **static** group changes visually +- **Dynamic** layers are drawn every frame on the fast path—no invalidation needed +- **Scroll:** cache rebuilds when the camera sample changes; no extra invalidation solely for scroll + +--- + +## Related Documentation + +| Document | Description | +|----------|-------------| +| [API Reference](../api/index.md) | Complete API documentation index | +| [Getting Started](../guide/getting-started.md) | First steps with the engine | +| [Style Guide](../guide/coding-style.md) | Coding conventions | +| [Platform Compatibility](../guide/platform-compatibility.md) | Supported hardware matrix | +| [Testing](../guide/testing.md) | Unit and integration testing | +| [MusicPlayer Guide](../guide/music-player-guide.md) | Background music, multi-track, tempo/BPM | + +--- + +## Detailed Architecture + +For comprehensive narrative documentation including: + +- Executive summary and design philosophy +- Design philosophy and modularity explanation +- Layer hierarchy in depth +- Module dependencies diagram +- Performance optimizations detail +- Configuration and compilation flags + +**See:** [Layer Abstraction](./layer-abstraction.md) - Design philosophy and layer details \ No newline at end of file diff --git a/architecture/audio-architecture.md b/architecture/audio-architecture.md deleted file mode 100644 index cac3bf9..0000000 --- a/architecture/audio-architecture.md +++ /dev/null @@ -1,638 +0,0 @@ -# Audio subsystem - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -# PixelRoot32 / PR32: NES-like Audio System – Implementation and Usage - -This document describes how the NES-style audio subsystem is implemented in the -PixelRoot32 (PR32) engine and how to use it from your games. It covers both the -high-level architecture and the concrete implementation details. - -> **Current engine:** **`ApuCore`** owns the **four channels**, the **SPSC `AudioCommandQueue`**, **mixing** (FPU compressor + optional HPF, or **LUT** on no-FPU ESP32), **music sequencing**, and **LFSR noise** on all platforms. **`AudioEngine`** is a facade; **`DefaultAudioScheduler`**, **`ESP32AudioScheduler`**, and **`NativeAudioScheduler`** only choose **execution context**. Authoritative mirror: engine [`ARCH_AUDIO_SUBSYSTEM.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/architecture/ARCH_AUDIO_SUBSYSTEM.md). - -- The system focuses on: - - Being **deterministic** and low-cost in terms of CPU and RAM. - - Respecting the existing engine architecture (`core`, `drivers`, `examples`) and the style guide. - - Providing a **platform-agnostic** audio API: - - Consistent with `Engine`, `Renderer`, and `InputManager`. - - With implementations for ESP32 (I2S and DAC) and SDL2 (desktop). - - Avoiding unnecessary complexity: - - No exact emulation of the NES APU. - - No heavy external audio libraries. - - No breaking changes for existing games in `examples`. - ---- - -## 1. Overview - -- 4 fixed channels: - - 2 `PULSE` channels (square wave with configurable duty cycle). - - 1 `TRIANGLE` channel. - - 1 `NOISE` channel. -- Software mixing into a **mono** 16-bit (`int16_t`) stream. -- **Event-driven** model: games fire short-lived `AudioEvent` instances (SFX, notes). -- **Conditionally compiled**: Entire subsystem can be excluded with `PIXELROOT32_ENABLE_AUDIO=0` to save firmware size and RAM. -- Fully **platform-agnostic** core: - - Wave, mixing, music timing, and the command queue live in **`ApuCore`**. - - Backends (SDL2, ESP32 I2S/DAC) only pull PCM via **`AudioEngine::generateSamples`**. - -Main files: - -- Facade: [`audio/AudioEngine.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioEngine.h) -- Shared core: [`audio/ApuCore.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/ApuCore.h), [`src/audio/ApuCore.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/src/audio/ApuCore.cpp) -- Audio types: [`audio/AudioTypes.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioTypes.h) -- SDL2 backend: [`SDL2_AudioBackend`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/drivers/native/SDL2_AudioBackend.h) -- ESP32 I2S backend: [`ESP32_I2S_AudioBackend`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/drivers/esp32/ESP32_I2S_AudioBackend.h) -- ESP32 DAC backend: [`ESP32_DAC_AudioBackend`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/drivers/esp32/ESP32_DAC_AudioBackend.h) - ---- - -## 2. Internal data model - -### 2.1 Basic types - -Defined in [`AudioTypes.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioTypes.h): - -```cpp -enum class WaveType { - PULSE, - TRIANGLE, - NOISE -}; -``` - -`WaveType` defines the waveform type for each channel. - -### 2.2 AudioChannel - -Each channel is an **`AudioChannel`** owned by **`ApuCore`** (fixed array of four). See **[`AudioTypes.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioTypes.h)** for the full struct: float oscillator state (**`phase`**, **`phaseIncrement`**, volumes, **`dutyCycle`**), **fixed-point mirrors** (**`phaseQ32`**, **`phaseIncQ32`**, **`dutyCycleQ32`**) for the no-FPU mixing path, **NES-style LFSR** fields (**`lfsrState`**, **`noisePeriodSamples`**, **`noiseCountdown`**), and **`remainingSamples`** for sample-accurate note/SFX lifetime. - -Key characteristics: - -- **No dynamic allocation**: four channels in **`ApuCore`**. -- **Noise** is **deterministic** on every platform (same LFSR polynomial); no `rand()` in the audio path. -- **Retrigger**: short **fade-in** on new events reduces clicks; FPU path adds a **one-pole HPF** after the mix. - -### 2.3 AudioEvent - -Also defined in `AudioTypes.h`: - -```cpp -struct AudioEvent { - WaveType type; - float frequency; - float duration; // seconds - float volume; // 0.0 - 1.0 - float duty; // only for PULSE - uint8_t noisePeriod = 0; // NOISE: 0 = from frequency, >0 = fixed LFSR period - const struct InstrumentPreset* preset = nullptr; // static/constexpr/global; nullptr = legacy ADSR -}; -``` - -- It is the basic unit used to trigger a sound. -- It is passed as a parameter to `AudioEngine::playEvent`. -- **Note**: Only available when `PIXELROOT32_ENABLE_AUDIO=1` - ---- - -## 3. AudioEngine and ApuCore: mixing core - -[`AudioEngine`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioEngine.h) forwards **`generateSamples`** to the active scheduler, which delegates to **[`ApuCore`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/ApuCore.h)**. - -### 3.1 Channel initialization - -**`ApuCore`** constructs: - -- `channels[0]` and `channels[1]` → `WaveType::PULSE` -- `channels[2]` → `WaveType::TRIANGLE` -- `channels[3]` → `WaveType::NOISE` - -Each channel is reset via **`AudioChannel::reset()`**. - -**Note**: This entire subsystem is only compiled when `PIXELROOT32_ENABLE_AUDIO=1`. - -### 3.2 Lifetime and time model (Sample-Based) - -The audio system no longer uses `deltaTime` or frame-based updates. Instead, it uses **sample-accurate timing** managed by an `AudioScheduler`: - -- **Audio Time**: Internal unit is samples (e.g., 1 second = 44100 samples at 44.1kHz). -- **Decoupled Logic**: The `AudioScheduler` runs in a separate thread (SDL2) or core (ESP32). -- **Lifetime**: For each active `AudioChannel`, **`ApuCore::generateSamples`** subtracts 1 from `remainingSamples` for every PCM sample produced (whichever thread/task invokes **`AudioEngine::generateSamples`** through the scheduler). -- When `remainingSamples` reaches 0, the channel is automatically disabled. - -Important: - -- **Game logic** runs at its own frame rate (e.g., 60 FPS). -- **Audio generation** runs at the hardware sample rate (e.g., 22050 Hz). -- Render stalls or frame drops **do not affect** audio pitch or tempo. - -### 3.3 Per-channel sample generation - -Oscillators are implemented in **`ApuCore::generateSampleForChannel`** (float path) and in an **integer inner loop** inside **`ApuCore::generateSamples`** on no-FPU ESP32 builds. After the raw waveform sample, the **float path** applies an **ADSR envelope** (from **`InstrumentPreset`** when **`AudioEvent::preset`** is set, else short legacy attack/release), then **LFO** modulation on pitch or volume when enabled. **`NOISE`** uses the same **15-bit LFSR** everywhere, clocked with **`noisePeriodSamples`** / **`noiseCountdown`**. - -Per-channel samples are scaled by **`MIXER_SCALE` (0.4)** before the global **compressor** `mixed = sum / (1 + |sum| * 0.5)` (FPU) or the equivalent **`audio_mixer_lut`** path (C3). **Master volume** applies after that. The old **`12000.0f`** scaling is **not** used in current code. - -### 3.4 Mixing all channels (Non-Linear Mixer) - -`void ApuCore::generateSamples(int16_t* stream, int length)` (invoked via **`AudioEngine::generateSamples`**): - -The system uses a **non-linear mixing strategy** that adapts to the underlying hardware to maximize volume and quality while preventing digital clipping. - -#### Strategy A: Floating-Point Soft Clipping (FPU-enabled) - -Used on ESP32, ESP32-S3, and Native (SDL2). It applies a compression curve: -`Output = Sum / (1.0 + |Sum| * 0.5)` - -- Each channel is scaled by **0.4x**. -- Peak volume reaches ~88% of the dynamic range. -- Provides a natural "analog" saturation. - -#### Strategy B: Look-Up Table (LUT) Mixing (No-FPU / ESP32-C3) - -Used on architectures without floating-point units. - -- Channels are summed using `int32_t`. -- A precomputed **1025-entry LUT** (`AudioMixerLUT.h`) maps the 32-bit sum to a 16-bit compressed value. -- Calculated as: `index = (sum + 131072) >> 8`. -- Zero CPU overhead for floating-point math. - -#### Clipping Prevention - -The asymptotic nature of the curve ensures that the output **never** exceeds the `int16_t` limits, eliminating the need for hard clipping and reducing harmonic distortion. - -#### Volume Optimization (v1.2.2+) - -- **Q16 Fixed-Point Volume**: Master volume is pre-computed as Q16 fixed-point for faster LUT mixing path on no-FPU architectures (ESP32-C3). This eliminates floating-point operations during the mixing hot path. -- **ESP32 I2S Buffer**: The I2S DMA buffer size is increased to 1024 samples to match native configuration and improve audio stability on ESP32. - -### 3.5 Event playback: playEvent - -`void AudioEngine::playEvent(const AudioEvent& event)`: - -- Now acts as a **Command Producer**. -- It enqueues an `AudioCommand` into a lock-free **Single Producer / Single Consumer (SPSC)** queue. -- **`ApuCore`** (running on Core 0 / audio thread / test thread) consumes this command and: - - Looks for a free channel of the requested type (`WaveType`). - - Applies **voice stealing** if necessary (using the channel with the smallest `remainingSamples`). - - Converts the event's duration (seconds) into `remainingSamples` based on the current sample rate. - - Initializes the channel state (`enabled`, `frequency`, `phase`, `volume`, etc.). - ---- - -## 4. Audio Schedulers and Backends - -The system uses a decoupled architecture: **`ApuCore`** owns audio state and timing logic; an **`AudioScheduler`** selects **when** `ApuCore::generateSamples` runs; an **`AudioBackend`** outputs PCM. - -### 4.1 AudioScheduler - -The **`AudioScheduler`** interface wires **`submitCommand`** / **`generateSamples`** to **`ApuCore`** and exposes **`isMusicPlaying()`** / **`isMusicPaused()`** on stock implementations. - -There are **three** main implementations: - -- **`NativeAudioScheduler`**: SDL2 / `PLATFORM_NATIVE`. Dedicated **`std::thread`** + ring buffer. -- **`ESP32AudioScheduler`**: ESP32. Same context as the backend audio task. -- **`DefaultAudioScheduler`**: Unit tests and callback-driven hosts. - -#### 4.1.1 Platform-Agnostic Core Management - -The system no longer uses hardcoded core IDs for ESP32. Instead, it uses a `PlatformCapabilities` (`pixelroot32::platforms`) structure to detect hardware features at startup: - -- **Dual-Core ESP32**: Audio task is pinned to **Core 0** (leaving Core 1 for the game loop). -- **Single-Core ESP32**: Audio task runs on **Core 0** with high priority, allowing the FreeRTOS scheduler to manage time-slicing. -- **Native (SDL2)**: Uses a standard system thread. - -### 4.2 Platform Configuration and Build Flags - -The audio system behavior can be customized via `platforms/PlatformDefaults.h` or compile-time flags. - -#### 4.2.1 Core Affinity - -- `PR32_DEFAULT_AUDIO_CORE`: Defines the default core for audio processing (Default: `0` on ESP32). -- `PR32_DEFAULT_MAIN_CORE`: Defines the default core for the main engine loop (Default: `1` on ESP32). - -#### 4.2.2 Build Flags - -| Flag | Default | Description | -|------|---------|-------------| -| `PIXELROOT32_ENABLE_AUDIO` | `1` | Master switch: when `0`, the whole audio subsystem (engine APIs, **`ApuCore`**, backends used by audio) is excluded from the build. | -| `PIXELROOT32_NO_DAC_AUDIO` | — | Disables the internal DAC backend on classic ESP32. | -| `PIXELROOT32_NO_I2S_AUDIO` | — | Disables the I2S audio backend. | - -Display-related compile flags (e.g. U8G2, TFT_eSPI) live with platform docs: [Platform configuration](../guide/platform-config.md). - -### 4.3 Audio Backends (interface) - -Backends implement the abstract `AudioBackend` interface: - -```cpp -class AudioBackend { -public: - virtual ~AudioBackend() = default; - virtual void init(AudioEngine* engine, const pixelroot32::platforms::PlatformCapabilities& caps) = 0; - virtual int getSampleRate() const = 0; -}; -``` - -**Note**: Audio backends are only compiled and available when `PIXELROOT32_ENABLE_AUDIO=1`. - -### 4.4 SDL2 backend (Windows / Linux / Mac) - -Implemented in: - -- Header: [`include/drivers/native/SDL2_AudioBackend.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/drivers/native/SDL2_AudioBackend.h) -- Source: [`src/drivers/native/SDL2_AudioBackend.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/src/drivers/native/SDL2_AudioBackend.cpp) - -Key points: - -- Uses `SDL_OpenAudioDevice` to open a mono device (`AUDIO_S16SYS`, 1 channel). -- Sets up a C callback (`SDLAudioCallbackWrapper`) that calls the member function - `SDL2_AudioBackend::audioCallback`. -- In `audioCallback`: - - Computes how many 16-bit samples are required from `len` (bytes). - - Calls `engineInstance->generateSamples(...)` to fill the buffer directly. - -This completely decouples **audio timing** from the SDL2 game loop. - -### 4.5 ESP32 Backends - -The engine provides two distinct backends for ESP32, allowing developers to choose between high-quality I2S (external DAC) or retro-style internal DAC. - -#### A) ESP32 I2S Backend (External DAC) - -- **Class**: `ESP32_I2S_AudioBackend` -- **Header**: [`include/drivers/esp32/ESP32_I2S_AudioBackend.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/drivers/esp32/ESP32_I2S_AudioBackend.h) -- **Use case**: High-quality audio using external DACs like **MAX98357A** or **PCM5102**. -- **Key points**: - - Uses ESP32 **I2S** peripheral with DMA (`I2S_NUM_0`). - - Output is digital I2S (BCLK, LRCK, DOUT). - - Runs in a dedicated FreeRTOS task to ensure smooth playback. - - Supports standard sample rates (e.g., 22050Hz, 44100Hz). - -#### B) ESP32 DAC Backend (Internal DAC) - -- **Class**: `ESP32_DAC_AudioBackend` -- **Header**: [`include/drivers/esp32/ESP32_DAC_AudioBackend.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/drivers/esp32/ESP32_DAC_AudioBackend.h) -- **Use case**: Retro audio using the ESP32's **internal 8-bit DAC** (GPIO 25 or 26), either - driving a small speaker directly or feeding a simple amplifier like **PAM8302A**. -- **Key points**: - - Uses the legacy **I2S driver** in **`I2S_MODE_DAC_BUILT_IN`**: **`i2s_write`** pushes blocks to the internal DAC via **DMA** (not per-sample **`dacWrite()`**). - - A FreeRTOS task (created by the backend) calls **`AudioEngine::generateSamples`**, converts signed PCM to **offset-binary** for the DAC path, and applies **~0.7×** headroom before **`i2s_write`** (PAM8302A-friendly). - - **Hardware**: internal DAC exists on **classic ESP32** / **ESP32-S2** class chips, not **S3** / **C3**. -- **Limitations**: 8-bit effective resolution; more noise than external I2S. -- **Recommendation**: For higher fidelity, use **`ESP32_I2S_AudioBackend`** + external codec. - -#### C) Reference Pinout (ESP32) - -| Backend | Peripheral | ESP32 Pin | Module Connection | -|---------|------------|-----------|-----------------| -| **DAC** | DAC1 | GPIO 25 | **PAM8302A**: A+ (IN+) | -| **DAC** | GND | GND | **PAM8302A**: A- (IN-) | -| **I2S** | BCLK | GPIO 26 | **MAX98357A**: BCLK | -| **I2S** | LRCK | GPIO 25 | **MAX98357A**: LRC (WS) | -| **I2S** | DOUT | GPIO 22 | **MAX98357A**: DIN | - -### 4.6 Backend Configuration (in `main.cpp`) - -To select a backend, simply instantiate the desired class and pass it to the `AudioConfig` struct. - -**Example for Internal DAC (PAM8302A):** - -```cpp -// 1. Instantiate the backend (GPIO 25, 11025Hz for retro feel) -pr32::drivers::esp32::ESP32_DAC_AudioBackend audioBackend(25, 11025); - -// 2. Configure the engine -pr32::audio::AudioConfig audioConfig; -audioConfig.backend = &audioBackend; - -// 3. Initialize engine -pr32::core::Engine engine(displayConfig, inputConfig, audioConfig); -``` - -**Example for I2S (MAX98357A):** - -```cpp -// 1. Instantiate the backend (BCLK=26, LRCK=25, DOUT=22) -pr32::drivers::esp32::ESP32_I2S_AudioBackend audioBackend(26, 25, 22, 22050); - -// 2. Configure the engine -pr32::audio::AudioConfig audioConfig; -audioConfig.backend = &audioBackend; - -// 3. Initialize engine -pr32::core::Engine engine(displayConfig, inputConfig, audioConfig); -``` - -Advantages: - -- Audio is generated in a dedicated task, independent from the game frame rate. -- The game loop can run at 60 FPS even while audio is streamed at 22050 Hz. - ---- - -## 5. Integration with Engine and the game loop - -The central `Engine` is responsible for: - -- Creating the `AudioEngine` instance. -- Providing an `AudioConfig` with the appropriate backend and scheduler. -- Managing the lifecycle of the audio subsystem. - -See [`core/Engine.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/core/Engine.h) and -[`core/Engine.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/src/core/Engine.cpp). - -**Decoupled flow:** - -1. The game creates `DisplayConfig`, `InputConfig`, and `AudioConfig`. -2. It constructs `Engine(displayConfig, inputConfig, audioConfig)`. -3. It calls `engine.init()`: - - Initializes renderer, input, and audio. - - The audio scheduler starts its dedicated thread/task. -4. On each frame: - - `Engine::update`: - - Computes `deltaTime`. - - Updates input. - - Calls `sceneManager.update(deltaTime)`. - - **Note**: `AudioEngine` and `MusicPlayer` no longer need frame updates. - - `Engine::draw`: - - Renders the scene. - -Meanwhile, the **Audio Scheduler** (Core 0 / Thread) runs independently at the target sample rate. - ---- - -## 6. Using audio from a game - -### 6.1 Accessing the AudioEngine - -From any scene or actor that has access to `Engine`: - -```cpp -auto& audio = engine.getAudioEngine(); -``` - -### 6.2 Triggering a simple sound - -Example of a “coin” sound when the player passes an obstacle in GeometryJump -([`GeometryJumpScene.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples)): - -```cpp -pr32::audio::AudioEvent coinEvent{}; -coinEvent.type = pr32::audio::WaveType::PULSE; -coinEvent.frequency = 1500.0f; -coinEvent.duration = 0.12f; -coinEvent.volume = 0.8f; -coinEvent.duty = 0.5f; -engine.getAudioEngine().playEvent(coinEvent); -``` - -Recommended patterns: - -- Use `PULSE` for “blip”, “coin”, jumps, and UI sounds. -- Use `TRIANGLE` for bass lines or softer tones. -- Use `NOISE` for hits, explosions, and collisions. - -### 6.2.1 Global master volume - -Games can control a global volume multiplier without changing individual events: - -```cpp -auto& audio = engine.getAudioEngine(); - -audio.setMasterVolume(0.5f); // 50% of full volume -// ... -float current = audio.getMasterVolume(); // Query current setting -``` - -- `setMasterVolume` clamps the value to `[0.0f, 1.0f]`. -- It scales all channels uniformly on top of each event’s own `volume`. - -### 6.3 Designing NES-like effects - -Effects are built by combining basic parameters and optional ADSR envelopes: - -**Basic Parameters** -- `frequency`: lower or higher pitch. -- `duration`: effect length (seconds). -- `volume`: 0.0–1.0. -- `duty` (pulse only): - - 0.125: thinner, sharper timbre. - - 0.25: classic "NES lead". - - 0.5: symmetric square, "fatter" sound. - -**ADSR Envelope (via `InstrumentPreset`)** -- `attackTime`: how quickly the sound reaches peak volume (0.0 = instant). -- `decayTime`: how quickly it drops to sustain level after attack. -- `sustainLevel`: volume maintained during the sustain phase (0.0-1.0). -- `releaseTime`: how quickly the sound fades after duration ends. - -Use an `InstrumentPreset` with an `AudioEvent` to apply envelopes: -```cpp -AudioEvent evt{}; -evt.type = WaveType::PULSE; -evt.frequency = 1500.0f; -evt.duration = 0.12f; -evt.preset = &INSTR_PULSE_LEAD; // Uses built-in ADSR envelope -audio.playEvent(evt); -``` - ---- - -## 7. Melody subsystem (tracks and songs) - -The audio system also includes a lightweight melody/music layer built on top of -`AudioEngine`. It is designed to stay simple and deterministic, while being easy -to use from games. - -### 7.1 Data model (`AudioMusicTypes.h`) - -Defined in [`AudioMusicTypes.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioMusicTypes.h): - -```cpp -enum class Note : uint8_t { - C = 0, Cs, D, Ds, E, F, Fs, G, Gs, A, As, B, - Rest, - COUNT -}; -``` - -- `Note::Rest` represents a silence. -- Frequencies are derived from an internal table for octave 4 combined with - power-of-two shifts: - -```cpp -inline float noteToFrequency(Note note, int octave); -``` - -Melodies are sequences of `MusicNote` elements: - -```cpp -struct MusicNote { - Note note; - uint8_t octave; // 0-8 (for percussion: 1=Kick, 2=Snare, 3+=Hi-HAT) - float duration; // seconds - float volume; // 0.0 - 1.0 - const InstrumentPreset* preset = nullptr; // Optional preset for ADSR/LFO per-note -}; -``` - -A `MusicTrack` groups notes and defines how they are played. **Multi-track:** optional pointers add up to **three** parallel layers (same **`MAX_MUSIC_TRACKS` = 4** total including the root): - -```cpp -struct MusicTrack { - const MusicNote* notes; - size_t count; - bool loop; - WaveType channelType; - float duty; - const MusicTrack* secondVoice = nullptr; - const MusicTrack* thirdVoice = nullptr; - const MusicTrack* percussion = nullptr; -}; -``` - -For convenience there are **instrument** presets (melodic and **percussion**) and helpers: - -```cpp -struct InstrumentPreset { - // Basic parameters - float baseVolume; - float duty; // 0.0 = NOISE (percussion), >0 = PULSE/TRIANGLE - uint8_t defaultOctave; - float defaultDuration = 0.0f; // 0.0 = use note.duration, >0 = fixed (percussion) - uint8_t noisePeriod = 0; // 0 = calc from freq, >0 = direct LFSR period - - // ADSR Envelope - float attackTime = 0.002f; // Attack time in seconds - float decayTime = 0.0f; // Decay time in seconds - float sustainLevel = 1.0f; // Sustain level (0.0-1.0) - float releaseTime = 0.005f; // Release time in seconds - - // LFO Modulation - LfoTarget lfoTarget = LfoTarget::NONE; // NONE, PITCH, or VOLUME - float lfoFrequency = 0.0f; // LFO frequency in Hz - float lfoDepth = 0.0f; // Modulation depth - float lfoDelay = 0.0f; // Delay before LFO starts - - // Waveform refinements - bool noiseShortMode = false; // Metallic 93-step LFSR for NOISE - float dutySweep = 0.0f; // Duty cycle change per second (PWM) -}; - -inline MusicNote makeNote(const InstrumentPreset& preset, Note note, float duration); -inline MusicNote makeNote(const InstrumentPreset& preset, Note note, uint8_t octave, float duration); -inline MusicNote makeRest(float duration); -``` - -**Built-in `constexpr` presets** — full initializer lists live only in the header (do not rely on abbreviated copies). Melodic: **`INSTR_PULSE_LEAD`**, **`INSTR_PULSE_HARMONY`**, **`INSTR_PULSE_PAD`**, **`INSTR_PULSE_BASS`**, **`INSTR_TRIANGLE_LEAD`**, **`INSTR_TRIANGLE_PAD`**, **`INSTR_TRIANGLE_BASS`**. Percussion (use with **`WaveType::NOISE`**, **`duty == 0`**): **`INSTR_KICK`**, **`INSTR_SNARE`**, **`INSTR_HIHAT`**. See [`AudioMusicTypes.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/AudioMusicTypes.h) and the engine [API audio reference](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/api/API_AUDIO.md#predefined-presets) for roles and parameters. - -These helpers reduce boilerplate when defining tracks and keep note volumes and octaves consistent per instrument. - -#### 7.1.1 NES-style tick timing - -Inside **`ApuCore`**, the music sequencer advances in **ticks** derived from **audio sample time** and the current **BPM** (default **150**, **4 ticks per beat**), scaled by **`MUSIC_SET_TEMPO`**. Ticks are **not** tied to the game’s render frame rate: tempo stays stable when the main loop stalls, as long as **`generateSamples`** keeps being called on the audio side. - -### 7.2 MusicPlayer (`MusicPlayer.h`) - -**For a full MusicPlayer integration walkthrough, see the [Music player guide](/guide/music-player).** - -Defined in [`MusicPlayer.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/include/audio/MusicPlayer.h) and -[`MusicPlayer.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/src/audio/MusicPlayer.cpp). - -**Responsibilities (thin client):** - -- Acts as a **command producer** for the music system. -- Provides **`play` / `stop` / `pause` / `resume`**, **`setTempoFactor`**, **`setBPM` / `getBPM`**, and **`getActiveTrackCount`** (layers requested on the last **`play()`**). -- Enqueues **`AudioCommand`**s through **`AudioEngine::submitCommand`**; schedulers forward them to **`ApuCore`**. - -**Sequencing (audio consumer):** - -- Tick-based sequencing runs in **`ApuCore::updateMusicSequencer`** (same code path on every platform). -- Uses **sample-accurate** wall time instead of `deltaTime`. -- Emits internal **`AudioEvent`s** via **`executePlayEvent`** on the audio consumer thread/context. - -### 7.3 Integration with Engine - -`MusicPlayer` is owned by `Engine` alongside `AudioEngine`: - -- The `Engine` constructor creates `audioEngine` and `musicPlayer`. -- Games use: - -```cpp -auto& music = engine.getMusicPlayer(); -music.play(myTrack); -``` - -This keeps music sequencing **sample-accurate** and completely independent of the game frame rate. Render stalls or logic spikes will not cause music to jitter or slow down. - -### 7.4 Example: looping lead + reference sample - -A minimal looping lead (additional layers optional via **`secondVoice`** / **`percussion`**): - -```cpp -using namespace pixelroot32::audio; - -static const MusicNote MELODY_NOTES[] = { - makeNote(INSTR_PULSE_LEAD, Note::C, 0.20f), - makeNote(INSTR_PULSE_LEAD, Note::E, 0.20f), - makeNote(INSTR_PULSE_LEAD, Note::G, 0.25f), - makeRest(0.10f), -}; - -static const MusicTrack GAME_MUSIC = { - MELODY_NOTES, - sizeof(MELODY_NOTES) / sizeof(MusicNote), - true, - WaveType::PULSE, - 0.5f, -}; - -void MyScene::init(pr32::core::Engine& engine) { - engine.getMusicPlayer().play(GAME_MUSIC); -} -``` - -- Each **active** music layer prefers a matching **`WaveType`**; voice allocation and SFX **stealing** still apply across the **four** hardware channels. -- For **multi-track** arrangements, instrument presets, and drum patterns, use the engine sample **[`music_demo`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo)**. - ---- - -## 8. Current limitations and future extensions - -With the **Multi-Core Architecture (v0.7.0-dev)**, many previous limitations were addressed, particularly regarding timing and stability. - -### 8.1 Resolved / Improved - -- **Sample-Accurate Timing**: The system now uses samples instead of `deltaTime` for all internal logic, eliminating jitter and drift. -- **Decoupled Execution**: Audio logic is completely isolated from the game's frame rate, preventing audio stuttering during heavy CPU load. -- **Music tempo control**: real-time changes via **`MUSIC_SET_TEMPO`** and absolute **`MUSIC_SET_BPM`**. -- **Multi-track music**: up to **four** parallel layers (main + three pointers), carried in **`AudioCommand::subTracks`**. -- **ADSR Envelopes**: Full Attack-Decay-Sustain-Release envelopes implemented via `InstrumentPreset` for expressive note articulation and click-free playback. -- **LFO Modulation**: Low-frequency oscillators for vibrato (pitch) and tremolo (volume) effects. - -### 8.2 Remaining Limitations - -- No exact cycle-accurate emulation of the NES APU. -- **Pitch Sweeps**: Frequency slides (pitch slides) are not yet implemented. -- **Complex Envelopes**: ADSR or complex multi-point envelopes are not supported (only linear interpolation). -- **Music catch-up**: if **`generateSamples`** is not called for a long time, **`updateMusicSequencer`** may process many ticks in one step (CPU scales with backlog; no **`MAX_NOTES_PER_FRAME`** cap in current code). - -### 8.3 Future Extensions - -- ~~**Deterministic LFSR everywhere**~~: ✅ Implemented in **`ApuCore`** (including native builds). -- **Frequency Sweeps**: Add `frequencyDelta` (or NES-style sweep unit) to **`ApuCore`** for pitch slides. -- **High-Level SFX Helpers**: Add methods like `playJumpSfx()`, `playExplosionSfx()` to `AudioEngine` for easier use. -- **Advanced music tooling**: pattern tables, FMS-style expansion, or streaming from flash without large static arrays. -- **SIMD Optimizations**: Investigate SSE/AVX for native platforms and DSP instructions for ESP32-S3 (see research document). - ---- - -## 9. Summary - -- The NES-like audio system in PixelRoot32: - - Uses 4 static channels (2 Pulse, 1 Triangle, 1 Noise). - - Produces mono 16-bit audio via software mixing. - - Is platform-agnostic thanks to `AudioBackend`, `AudioScheduler`, and shared **`ApuCore`** logic. - - Is **decoupled** from the game loop, running on Core 0 (ESP32) or a separate thread (SDL2). - - Uses **sample-accurate timing** for both SFX and music inside **`ApuCore`**. - - Is controlled from games through `AudioEngine` (SFX) and `MusicPlayer` (Music) via a lock-free **SPSC** command queue (**128** slots; overflow drops newest). diff --git a/architecture/audio-subsystem.md b/architecture/audio-subsystem.md new file mode 100644 index 0000000..4f8000d --- /dev/null +++ b/architecture/audio-subsystem.md @@ -0,0 +1,757 @@ +# PixelRoot32 / PR32: NES-inspired Audio System – Implementation and Usage + +> **Note:** For the complete audio system documentation with interactive examples and API reference, visit the [official documentation](https://docs.pixelroot32.org/manual/game_development/audio/). + +This document describes how the NES-style audio subsystem is implemented in the +PixelRoot32 (PR32) engine and how to use it from your games. It covers both the +high-level architecture and the concrete implementation details. + +- The system focuses on: + - Being **deterministic** and low-cost in terms of CPU and RAM. + - Respecting the existing engine architecture (`core`, `drivers`, `examples`) and the style guide. + - Providing a **platform-agnostic** audio API: + - Consistent with `Engine`, `Renderer`, and `InputManager`. + - With implementations for ESP32 (I2S and DAC) and SDL2 (desktop). + - Avoiding unnecessary complexity: + - No exact emulation of the NES APU. + - No heavy external audio libraries. + - No breaking changes for existing games in `examples`. + +--- + +## 1. Overview + +- **Dynamic voice pool** inside **`ApuCore`** (default **`MAX_VOICES = 8`**). Each voice holds the same oscillator/envelope state as before (`AudioChannel`, exposed as alias **`Voice`** in [`AudioTypes.h`](include/audio/AudioTypes.h)). +- **Public API unchanged**: games and music still describe sounds with **`WaveType`** on **`AudioEvent`** / **`MusicTrack::channelType`**. Internally, `ApuCore` maps `WaveType` ↔ **`VoiceType`** (`PULSE`, `TRIANGLE`, `NOISE`, `SINE`, `SAW`) for allocation policy; the active waveform on a voice is still a `WaveType` on that voice’s state. +- **Voice allocation**: on `PLAY_EVENT`, `ApuCore` prefers an **inactive** voice whose stored type matches the requested `WaveType`; if none, it may **steal** the active voice with the **smallest `remainingSamples`**. When all eight voices are busy, the newest event replaces the “shortest remaining” note (same steal metric as under the old 2-voice melodic pool, but generalized across the whole pool). +- Software mixing into a **mono** 16-bit (`int16_t`) stream. +- **Event-driven** model: games fire short-lived `AudioEvent` instances (SFX, notes). +- **Conditionally compiled**: Entire subsystem can be excluded with `PIXELROOT32_ENABLE_AUDIO=0` to save firmware size and RAM. +- **Platform-agnostic API** with concrete work split as follows: + - [`AudioEngine`](include/audio/AudioEngine.h) is a thin facade: it forwards `playEvent` / `setMasterVolume` as commands and delegates `generateSamples` to an `AudioScheduler`. + - **Mixing, voice pool state, music sequencing, and the SPSC command queue** live in **[`ApuCore`](include/audio/ApuCore.h)** (`src/audio/ApuCore.cpp`). The three schedulers only decide **execution context** (same-thread vs FreeRTOS task vs `std::thread` + ring buffer). + - Backends (SDL2, ESP32 I2S/DAC) pull PCM by calling `AudioEngine::generateSamples`, which reaches `ApuCore::generateSamples`. + +### Command path (high level) + +```mermaid +flowchart TB + subgraph Game["Game thread (e.g. Core 1 on ESP32)"] + A[Game code] -->|playEvent / submitCommand| B[AudioEngine facade] + B -->|music commands| C[MusicPlayer] + C -->|submitCommand| B + end + + subgraph Queue["SPSC command queue"] + B -->|single producer| D[AudioCommandQueue 128 slots] + end + + subgraph Audio["Audio consumer context"] + D -->|dequeue| E[AudioScheduler thin wrapper] + E -->|ApuCore::generateSamples| F[Pulse x2 + Triangle + Noise + music] + F --> G[Non-linear mixer FPU or LUT + optional HPF] + end + + subgraph Output["Hardware"] + G -->|PCM| H[Backend: I2S / DAC / SDL2] + H --> I[Speaker] + end +``` + +- **`AudioEngine`** forwards commands and **`generateSamples`** to the active **`AudioScheduler`**, which delegates to **`ApuCore`**: SPSC queue, four channels, music sequencer, mixing, and (on the FPU path) a one-pole output HPF. +- On **ESP32**, core affinity and task priority are applied when the **backend** creates its FreeRTOS task (`PlatformCapabilities`), not inside `ESP32AudioScheduler` construction arguments (those parameters are reserved for API stability). + +Main files: + +- Facade: [`audio/AudioEngine.h`](include/audio/AudioEngine.h) +- Shared core: [`audio/ApuCore.h`](include/audio/ApuCore.h), [`src/audio/ApuCore.cpp`](src/audio/ApuCore.cpp) +- Schedulers: [`audio/DefaultAudioScheduler.h`](include/audio/DefaultAudioScheduler.h), [`drivers/esp32/ESP32AudioScheduler.h`](include/drivers/esp32/ESP32AudioScheduler.h), [`drivers/native/NativeAudioScheduler.h`](include/drivers/native/NativeAudioScheduler.h) +- Audio types & commands: [`audio/AudioTypes.h`](include/audio/AudioTypes.h), [`audio/AudioCommandQueue.h`](include/audio/AudioCommandQueue.h) +- Mixer LUT: [`audio/AudioMixerLUT.h`](include/audio/AudioMixerLUT.h) (`inline constexpr` table, single rodata copy per link) +- SDL2 backend: [`SDL2_AudioBackend`](include/drivers/native/SDL2_AudioBackend.h) +- ESP32 I2S backend: [`ESP32_I2S_AudioBackend`](include/drivers/esp32/ESP32_I2S_AudioBackend.h) +- ESP32 DAC backend: [`ESP32_DAC_AudioBackend`](include/drivers/esp32/ESP32_DAC_AudioBackend.h) (I2S **DAC-built-in** + DMA) + +--- + +## 2. Internal data model + +### 2.1 Basic types + +Defined in [`AudioTypes.h`](include/audio/AudioTypes.h): + +```cpp +enum class WaveType : uint8_t { + PULSE, + TRIANGLE, + NOISE, + SINE, + SAW +}; +``` + +`WaveType` is the **authoring / API** waveform tag on `AudioEvent` and on `MusicTrack::channelType`. + +For internal bookkeeping (optional reading for engine contributors), [`AudioTypes.h`](include/audio/AudioTypes.h) also defines **`VoiceType`** (same set of synthesis kinds) and **`constexpr`** helpers **`toVoiceType(WaveType)`** / **`toWaveType(VoiceType)`** so `ApuCore` can treat allocation consistently while keeping the public surface on `WaveType`. + +### 2.2 AudioChannel (voice state) + +Each **voice** is an `AudioChannel` instance owned by **`ApuCore`** (fixed array **`voices[MAX_VOICES]`**, default size **8**). The type alias **`using Voice = AudioChannel`** documents the intent: one entry in the pool is one polyphonic voice. + +Highlights (see [`AudioTypes.h`](include/audio/AudioTypes.h) for the full struct): + +- **Float path** (FPU ESP32, native): `phase` in `[0,1)`, `phaseIncrement = frequency / sampleRate`, linear volume ramp (`volume`, `targetVolume`, `volumeDelta`). +- **Integer mirror** (ESP32-C3, no FPU): `phaseQ32`, `phaseIncQ32`, `dutyCycleQ32` — updated on each `PLAY_EVENT` so the hot inner loop in `ApuCore::generateSamples` avoids per-sample soft-float. +- **Noise**: `lfsrState` (15-bit LFSR), `noisePeriodSamples`, `noiseCountdown` — same deterministic polynomial on **all** platforms. +- **Lifetime**: `remainingSamples` counts down each rendered sample until the voice is disabled (then it can be reused or stolen per §3.5). + +On **note on**, `ApuCore` initializes an ADSR envelope from the `InstrumentPreset` (or legacy defaults: 2ms attack, no decay, full sustain, 5ms release) to shape the note amplitude over time, reducing clicks and enabling expressive articulation. + +### 2.3 AudioEvent + +Also defined in `AudioTypes.h`: + +```cpp +struct AudioEvent { + WaveType type; + float frequency; + float duration; // seconds + float volume; // 0.0 - 1.0 + float duty; // only for PULSE + uint8_t noisePeriod = 0; // for NOISE: 0=calc from frequency, >0=direct LFSR period + const InstrumentPreset* preset = nullptr; + float sweepEndHz = 0.0f; // linear sweep target (PULSE/TRIANGLE); 0 = off + float sweepDurationSec = 0.0f; // sweep length; 0 = off +}; +``` + +- **Linear sweep** (optional): when `sweepDurationSec > 0` and `sweepEndHz > 0`, and `type` is `PULSE` or `TRIANGLE`, frequency moves linearly from `frequency` toward `sweepEndHz` over `min(ceil(sweepDurationSec * sampleRate), note samples)`; `NOISE` ignores these fields. +- It is the basic unit used to trigger a sound. +- It is passed as a parameter to `AudioEngine::playEvent`. +- **Note**: Only available when `PIXELROOT32_ENABLE_AUDIO=1` + +--- + +## 3. AudioEngine and ApuCore: mixing core + +[`AudioEngine`](include/audio/AudioEngine.h) forwards `generateSamples` to the active [`AudioScheduler`](include/audio/AudioScheduler.h), which in all stock implementations delegates to **[`ApuCore::generateSamples`](include/audio/ApuCore.h)**. + +### 3.1 Voice pool initialization + +`ApuCore` constructs **`MAX_VOICES`** entries (default **8**). After `reset()`, each slot’s waveform tag defaults to `WaveType::PULSE` until the next `PLAY_EVENT` retriggers it; **`executePlayEvent`** sets `type` from the incoming event (via the `VoiceType` mapping for clarity in code paths). + +**`STOP_CHANNEL`**: `AudioCommand` still carries a **`channelIndex`** byte; valid indices are **`0` … `MAX_VOICES - 1`** (today **0–7**). Older docs referred to “channels 0–3”; callers that assumed exactly four hardware slots should treat the index as a **voice slot** in the pool. + +**Note**: This entire subsystem is only compiled when `PIXELROOT32_ENABLE_AUDIO=1`. + +### 3.2 Lifetime and time model (Sample-Based) + +The audio system no longer uses `deltaTime` or frame-based updates. Instead, it uses **sample-accurate timing** managed by an `AudioScheduler`: + +- **Audio Time**: Internal unit is samples (e.g., 1 second = 44100 samples at 44.1kHz). +- **Decoupled Logic**: The `AudioScheduler` runs in a separate thread (SDL2) or core (ESP32). +- **Lifetime**: For each active **voice** (`AudioChannel` / `Voice`), `ApuCore` subtracts 1 from `remainingSamples` for every sample generated. +- When `remainingSamples` reaches 0, the voice is automatically disabled (unless extended by the ADSR **RELEASE** phase per existing rules). + +Important: + +- **Game logic** runs at its own frame rate (e.g., 60 FPS). +- **Audio generation** runs at the hardware sample rate (e.g., 22050 Hz). +- Render stalls or frame drops **do not affect** audio pitch or tempo. + +### 3.3 Per-voice sample generation + +Oscillator work is implemented in **`ApuCore::generateSampleForVoice`** (float path) and in the **integer inner loop** of `ApuCore::generateSamples` on no-FPU ESP32 builds. If the voice is disabled, the contribution is `0`. + +1. **`PULSE`**: square with configurable duty (`dutyCycle` / `dutyCycleQ32`). +2. **`TRIANGLE`**: symmetric triangle in `[-1, 1]` (float) or quantized from `phaseQ32` high bits (integer path). +3. **`NOISE`**: **NES-style 15-bit LFSR** on **all** targets — same bit taps, advanced when `noiseCountdown` reaches 0, then reloaded from `noisePeriodSamples`. `AudioEvent::frequency` for noise sets the default clock when `noisePeriod == 0`; otherwise a fixed period can be supplied for percussion. + +After the per-voice sample (float path): + +- Phase advances for non-noise waves; noise uses only the countdown/LFSR. +- **ADSR envelope** is applied via per-sample state machine (ATTACK→DECAY→SUSTAIN→RELEASE→OFF) replacing the simple volume ramp. +- **LFO modulation** is applied when enabled: pitch modulation alters `phaseIncrement` (frequency), volume modulation alters envelope output amplitude. +- **Per-channel `MIXER_SCALE` (0.4)**, **master volume**, and the **non-linear compressor** are applied in `ApuCore::generateSamples` (see §3.4). On FPU/native builds a **single-pole HPF** runs on the mixed float signal before scaling to `int16_t` to tame DC and retrigger clicks. The no-FPU LUT path omits that float HPF for CPU reasons; the LUT output is already centred. + +### 3.4 Mixing all voices (Non-Linear Mixer) + +`AudioEngine::generateSamples` → scheduler → **`ApuCore::generateSamples`**. + +The system uses a **non-linear mixing strategy** aligned across FPU and LUT paths: + +- Per voice: `s = wave(v) * volume(v) * MIXER_SCALE` (`MIXER_SCALE = 0.4`). +- Sum `S = Σ s`, then **`mixed = S / (1 + |S| * MIXER_K)`** with `MIXER_K = 0.5`. +- Scale to PCM and apply **master volume**. + +#### Strategy A: Floating-Point (FPU ESP32, ESP32-S3, native) + +- Compressor as above, then **HPF** on the mixed float, then clamp to `int16_t`. +- `std::fabs` / float path. + +#### Strategy B: Integer + LUT (ESP32-C3, no FPU) + +- Inner loop uses **fixed-point phase** and **Q15 volume** so the oscillator does not touch soft-float every sample. +- **ADSR Envelope** is also implemented completely in integer Q15 fixed-point math (`tickEnvelopeQ15`) to eliminate heavy soft-float emulation during the fast 22kHz inner loop. +- Per-voice samples are scaled with the same **0.4** intent (`≈ 13107/32768` in Q15) before summation. +- **`audio_mixer_lut`**: `index = (sum + 131072) >> 8` into 1025 entries; table documented in `AudioMixerLUT.h` to match the FPU curve. +- **Master volume** via precomputed **Q16** `masterVolumeScale` when ≠ 1.0. + +#### Clipping prevention + +The compressor / LUT asymptote keeps peaks bounded; final clamp remains as a safety net on the FPU path. + +#### Profiling peaks + +When **`PIXELROOT32_ENABLE_PROFILING`** is defined (`platforms/EngineConfig.h` → `pixelroot32::platforms::config::EnableProfiling`), `ApuCore` may log peak statistics roughly once per second of audio. Peak tracking uses **per-instance** members (no `static` locals inside `generateSamples`). + +### 3.5 Event playback: playEvent + +`void AudioEngine::playEvent(const AudioEvent& event)`: + +- Acts as a **Command Producer**. +- It enqueues an `AudioCommand` into a lock-free **Single Producer / Single Consumer (SPSC)** ring buffer ([`AudioCommandQueue`](include/audio/AudioCommandQueue.h), capacity **128** entries). +- The **audio consumer** (scheduler thread / ESP32 audio task / SDL callback path) dequeues and applies commands. + +**Queue contract and overflow** + +- The implementation is only safe with **one producer** and **one consumer**. Do not call `playEvent` / `submitCommand` from multiple threads unless you replace the queue with an MPMC-safe structure. +- If the queue is **full**, `enqueue` **drops the newest command** and returns `false`. **`ApuCore::submitCommand`** increments an atomic **`droppedCommands`** counter and may emit a **throttled** warning when `PIXELROOT32_DEBUG_MODE` is defined. Avoid flooding more than ~127 outstanding commands between consumer passes. + +`ApuCore` then: + +- Selects a **voice slot** via **`findVoiceForEvent(WaveType)`**: prefers an **inactive** voice whose `type` already matches the requested `WaveType`; otherwise picks any inactive slot; if **all** voices are active, **steals** the voice with the **minimum `remainingSamples`** (breaking ties by scan order). +- Converts the event's duration (seconds) into `remainingSamples` based on the current sample rate. +- Initializes the voice state (`enabled`, `frequency`, `phase`, fixed-point mirrors, ADSR envelope, LFSR for noise, etc.) and sets `type` from the event. + +--- + +## 4. Audio Schedulers and Backends + +The system uses a decoupled architecture: **`ApuCore`** owns the **voice pool** (synthesis state), the command queue, music sequencing, and mixing. An **`AudioScheduler`** selects **when** `ApuCore::generateSamples` runs; an **`AudioBackend`** pushes PCM to hardware. + +### 4.1 AudioScheduler + +The `AudioScheduler` interface provides `init`, `submitCommand` (forwards to `ApuCore`), `start` / `stop`, `isIndependent`, `generateSamples`, and optional **`isMusicPlaying` / `isMusicPaused`** (all stock implementations delegate to the same `ApuCore` atomics). + +| Scheduler | Typical use | Where `ApuCore::generateSamples` runs | +|-----------|-------------|----------------------------------------| +| **`NativeAudioScheduler`** | `PLATFORM_NATIVE` && !unit tests | Dedicated **`std::thread`**; PCM pushed to a lock-free ring; SDL callback drains via `AudioEngine::generateSamples`. | +| **`ESP32AudioScheduler`** | ESP32 firmware | Same **CPU context** as the backend audio task (I2S/DAC backend creates the FreeRTOS task and calls `engine->generateSamples`). | +| **`DefaultAudioScheduler`** | Unit tests, minimal hosts | Whatever thread invokes `generateSamples` (no extra audio thread). | + +**Task creation** for ESP32 still lives in the **backends** ([`ESP32_I2S_AudioBackend`](src/drivers/esp32/ESP32_I2S_AudioBackend.cpp), [`ESP32_DAC_AudioBackend`](src/drivers/esp32/ESP32_DAC_AudioBackend.cpp)), not inside `ESP32AudioScheduler::start()`. + +#### 4.1.1 Platform-Agnostic Core Management + +The system no longer uses hardcoded core IDs for ESP32. Instead, it uses a `PlatformCapabilities` (`pixelroot32::platforms`) structure to detect hardware features at startup: + +- **Dual-Core ESP32**: Audio task is pinned to **Core 0** (leaving Core 1 for the game loop). Task priority defaults to `5` and internal DMA buffer block size is `512` samples. +- **Single-Core ESP32**: Audio task runs on **Core 0**, sharing it with the game loop and display drivers. To prevent audio starvation against heavy display transfers (e.g. U8G2), priority is dynamically elevated to `18`. To prevent this high-priority task from aggressively fragmenting display transfers via priority inversion, the internal buffer block size is reduced to `128` samples, and `taskYIELD()` is used cooperatively. +- **Native (SDL2)**: Uses a standard system thread. + +### 4.2 Platform Configuration and Build Flags + +The audio system behavior can be customized via `platforms/PlatformDefaults.h` or compile-time flags. + +#### 4.2.1 Core Affinity + +- `PR32_DEFAULT_AUDIO_CORE`: Defines the default core for audio processing (Default: `0` on ESP32). +- `PR32_DEFAULT_MAIN_CORE`: Defines the default core for the main engine loop (Default: `1` on ESP32). + +#### 4.2.2 Build Flags + +| Flag | Description | +|------|-------------| +| `PIXELROOT32_NO_DAC_AUDIO` | Disables the Internal DAC backend on classic ESP32. | +| `PIXELROOT32_NO_I2S_AUDIO` | Disables the I2S audio backend. | +| `PIXELROOT32_USE_U8G2` | Enables support for the U8G2 display driver (future support). | +| `PIXELROOT32_NO_TFT_ESPI` | Disables the default TFT_eSPI display driver. | + +### 4.3 Audio Backends (interface) + +Backends implement the abstract `AudioBackend` interface: + +```cpp +class AudioBackend { +public: + virtual ~AudioBackend() = default; + virtual void init(AudioEngine* engine, const pixelroot32::platforms::PlatformCapabilities& caps) = 0; + virtual int getSampleRate() const = 0; +}; +``` + +**Note**: Audio backends are only compiled and available when `PIXELROOT32_ENABLE_AUDIO=1`. + +### 4.4 SDL2 backend (Windows / Linux / Mac) + +Implemented in: + +- Header: [`include/drivers/native/SDL2_AudioBackend.h`](include/drivers/native/SDL2_AudioBackend.h) +- Source: [`src/drivers/native/SDL2_AudioBackend.cpp`](src/drivers/native/SDL2_AudioBackend.cpp) + +Key points: + +- Uses `SDL_OpenAudioDevice` to open a mono device (`AUDIO_S16SYS`, 1 channel). +- Sets up a C callback (`SDLAudioCallbackWrapper`) that calls the member function + `SDL2_AudioBackend::audioCallback`. +- In `audioCallback`: + - Computes how many 16-bit samples are required from `len` (bytes). + - Calls `engineInstance->generateSamples(...)` to fill the buffer directly. + +This completely decouples **audio timing** from the SDL2 game loop. + +### 4.5 ESP32 Backends + +The engine provides two distinct backends for ESP32, allowing developers to choose between high-quality I2S (external DAC) or retro-style internal DAC. + +#### A) ESP32 I2S Backend (External DAC) + +- **Class**: `ESP32_I2S_AudioBackend` +- **Header**: [`include/drivers/esp32/ESP32_I2S_AudioBackend.h`](include/drivers/esp32/ESP32_I2S_AudioBackend.h) +- **Use case**: High-quality audio using external DACs like **MAX98357A** or **PCM5102**. +- **Key points**: + - Uses ESP32 **I2S** peripheral with DMA (`I2S_NUM_0`). + - Output is digital I2S (BCLK, LRCK, DOUT). + - Runs in a dedicated FreeRTOS task to ensure smooth playback. + - Supports standard sample rates (e.g., 22050Hz, 44100Hz). + +#### B) ESP32 DAC Backend (Internal DAC) + +- **Class**: `ESP32_DAC_AudioBackend` +- **Header**: [`include/drivers/esp32/ESP32_DAC_AudioBackend.h`](include/drivers/esp32/ESP32_DAC_AudioBackend.h) +- **Use case**: Retro audio using the ESP32 **internal 8-bit DAC** (GPIO **25** = DAC1 or **26** = DAC2), e.g. with **PAM8302A**. +- **Key points**: + - Uses the legacy **I2S driver** in **`I2S_MODE_DAC_BUILT_IN`**: samples are written with **`i2s_write`** in blocks (DMA), **not** per-sample `dacWrite()`. + - A FreeRTOS task fills a buffer from `AudioEngine::generateSamples`, converts signed `int16` to **offset-binary** for the DAC path, and applies **~0.7×** headroom before `i2s_write` (same intent as the old PAM8302A scaling). + - **Hardware note**: internal DAC exists on **classic ESP32** / **ESP32-S2** class chips, not on **S3** / **C3**. +- **Limitations**: 8-bit effective resolution, mono, more noise than external I2S DAC. +- **Recommendation**: For higher fidelity, prefer **`ESP32_I2S_AudioBackend`** + external codec. + +#### C) Reference Pinout (ESP32) + +| Backend | Peripheral | ESP32 Pin | Module Connection | +|---------|------------|-----------|-----------------| +| **DAC** | DAC1 | GPIO 25 | **PAM8302A**: A+ (IN+) | +| **DAC** | GND | GND | **PAM8302A**: A- (IN-) | +| **I2S** | BCLK | GPIO 26 | **MAX98357A**: BCLK | +| **I2S** | LRCK | GPIO 25 | **MAX98357A**: LRC (WS) | +| **I2S** | DOUT | GPIO 22 | **MAX98357A**: DIN | + +### 4.6 Backend Configuration (in `main.cpp`) + +To select a backend, simply instantiate the desired class and pass it to the `AudioConfig` struct. + +**Example for Internal DAC (PAM8302A):** + +```cpp +// 1. Instantiate the backend (GPIO 25, 11025Hz for retro feel) +pr32::drivers::esp32::ESP32_DAC_AudioBackend audioBackend(25, 11025); + +// 2. Configure the engine +pr32::audio::AudioConfig audioConfig; +audioConfig.backend = &audioBackend; + +// 3. Initialize engine +pr32::core::Engine engine(displayConfig, inputConfig, audioConfig); +``` + +**Example for I2S (MAX98357A):** + +```cpp +// 1. Instantiate the backend (BCLK=26, LRCK=25, DOUT=22) +pr32::drivers::esp32::ESP32_I2S_AudioBackend audioBackend(26, 25, 22, 22050); + +// 2. Configure the engine +pr32::audio::AudioConfig audioConfig; +audioConfig.backend = &audioBackend; + +// 3. Initialize engine +pr32::core::Engine engine(displayConfig, inputConfig, audioConfig); +``` + +Advantages: + +- Audio is generated in a dedicated task, independent from the game frame rate. +- The game loop can run at 60 FPS even while audio is streamed at 22050 Hz. + +--- + +## 5. Integration with Engine and the game loop + +The central `Engine` is responsible for: + +- Creating the `AudioEngine` instance. +- Providing an `AudioConfig` with the appropriate backend and scheduler. +- Managing the lifecycle of the audio subsystem. + +See [`core/Engine.h`](include/core/Engine.h) and +[`core/Engine.cpp`](src/core/Engine.cpp). + +**Decoupled flow:** + +1. The game creates `DisplayConfig`, `InputConfig`, and `AudioConfig`. +2. It constructs `Engine(displayConfig, inputConfig, audioConfig)`. +3. It calls `engine.init()`: + - Initializes renderer, input, and audio. + - The audio scheduler starts its dedicated thread/task. +4. On each frame: + - `Engine::update`: + - Computes `deltaTime`. + - Updates input. + - Calls `sceneManager.update(deltaTime)`. + - **Note**: `AudioEngine` and `MusicPlayer` no longer need frame updates. + - `Engine::draw`: + - Renders the scene. + +Meanwhile, the **Audio Scheduler** (Core 0 / Thread) runs independently at the target sample rate. + +--- + +## 6. Using audio from a game + +### 6.1 Accessing the AudioEngine + +From any scene or actor that has access to `Engine`: + +```cpp +auto& audio = engine.getAudioEngine(); +``` + +### 6.2 Triggering a simple sound + +Example of a “coin” sound when the player passes an obstacle in GeometryJump +([`GeometryJumpScene.cpp`](examples/GeometryJump/GeometryJumpScene.cpp)): + +```cpp +pr32::audio::AudioEvent coinEvent{}; +coinEvent.type = pr32::audio::WaveType::PULSE; +coinEvent.frequency = 1500.0f; +coinEvent.duration = 0.12f; +coinEvent.volume = 0.8f; +coinEvent.duty = 0.5f; +engine.getAudioEngine().playEvent(coinEvent); +``` + +Recommended patterns: + +- Use `PULSE` for “blip”, “coin”, jumps, and UI sounds. +- Use `TRIANGLE` for bass lines or softer tones. +- Use `NOISE` for hits, explosions, and collisions. + +### 6.2.1 Global master volume + +Games can control a global volume multiplier without changing individual events: + +```cpp +auto& audio = engine.getAudioEngine(); + +audio.setMasterVolume(0.5f); // 50% of full volume +// ... +float current = audio.getMasterVolume(); // Query current setting +``` + +- `setMasterVolume` clamps the value to `[0.0f, 1.0f]`. +- It scales the mixed bus uniformly on top of each event’s own `volume`. + +### 6.2.2 Optional master bitcrush + +After the non-linear mixer (and HPF on FPU paths), the bus may be re-quantized for a lo-fi effect: + +```cpp +audio.setMasterBitcrush(6); // 1–15 effective bits; 0 = off +uint8_t bits = audio.getMasterBitcrush(); +``` + +Internally this uses `AudioCommandType::SET_MASTER_BITCRUSH` on the same SPSC queue as other audio commands. + +### 6.3 Designing NES-like effects + +Effects are built by combining basic parameters and optional ADSR envelopes: + +**Basic Parameters** +- `frequency`: lower or higher pitch (sweep start when using `sweepEndHz` / `sweepDurationSec`). +- `duration`: effect length (seconds). +- `volume`: 0.0–1.0. +- `duty` (pulse only): + - 0.125: thinner, sharper timbre. + - 0.25: classic "NES lead". + - 0.5: symmetric square, "fatter" sound. + +**ADSR Envelope (via `InstrumentPreset`)** +- `attackTime`: how quickly the sound reaches peak volume (0.0 = instant). +- `decayTime`: how quickly it drops to sustain level after attack. +- `sustainLevel`: volume maintained during the sustain phase (0.0-1.0). +- `releaseTime`: how quickly the sound fades after duration ends. + +Use an `InstrumentPreset` with an `AudioEvent` to apply envelopes: +```cpp +AudioEvent evt{}; +evt.type = WaveType::PULSE; +evt.frequency = 1500.0f; +evt.duration = 0.12f; +evt.preset = &INSTR_PULSE_LEAD; // Uses built-in ADSR envelope +audio.playEvent(evt); +``` + +--- + +## 7. Melody subsystem (tracks and songs) + +The audio system also includes a lightweight melody/music layer built on top of +`AudioEngine`. It is designed to stay simple and deterministic, while being easy +to use from games. + +### 7.1 Data model (`AudioMusicTypes.h`) + +Defined in [`AudioMusicTypes.h`](include/audio/AudioMusicTypes.h): + +```cpp +enum class Note : uint8_t { + C = 0, Cs, D, Ds, E, F, Fs, G, Gs, A, As, B, + Rest, + COUNT +}; +``` + +- `Note::Rest` represents a silence. +- Frequencies are derived from an internal table for octave 4 combined with + power-of-two shifts: + +```cpp +inline float noteToFrequency(Note note, int octave); +``` + +Melodies are sequences of `MusicNote` elements: + +```cpp +struct MusicNote { + Note note; + uint8_t octave; + float duration; // seconds + float volume; // 0.0 - 1.0 +}; +``` + +A `MusicTrack` groups notes and defines how they are played: + +```cpp +struct MusicTrack { + const MusicNote* notes; + size_t count; + bool loop; + WaveType channelType; + float duty; + + // Multi-track support (optional - nullptr = disabled) + const MusicTrack* secondVoice = nullptr; // Second melody voice + const MusicTrack* thirdVoice = nullptr; // Third melody voice + const MusicTrack* percussion = nullptr; // Drum/percussion track +}; + +// Maximum simultaneous tracks: main + 3 sub-tracks +constexpr size_t MAX_MUSIC_TRACKS = 4; +``` + +For convenience there are simple “instrument” presets and helpers: + +```cpp +struct InstrumentPreset { + // Basic parameters + float baseVolume; + float duty; // 0.0 = NOISE (percussion), >0 = PULSE/TRIANGLE + uint8_t defaultOctave; + float defaultDuration = 0.0f; // 0.0 = use note.duration, >0 = fixed (percussion) + uint8_t noisePeriod = 0; // 0 = calc from freq, >0 = direct LFSR period + + // ADSR Envelope + float attackTime = 0.002f; // Attack time in seconds + float decayTime = 0.0f; // Decay time in seconds + float sustainLevel = 1.0f; // Sustain level (0.0-1.0) + float releaseTime = 0.005f; // Release time in seconds + + // LFO Modulation + LfoTarget lfoTarget = LfoTarget::NONE; // NONE, PITCH, or VOLUME + float lfoFrequency = 0.0f; // LFO frequency in Hz + float lfoDepth = 0.0f; // Modulation depth + float lfoDelay = 0.0f; // Delay before LFO starts + + // Waveform refinements + bool noiseShortMode = false; // Metallic 93-step LFSR for NOISE + float dutySweep = 0.0f; // Duty cycle change per second (PWM) +}; + +// Melodic instruments +inline constexpr InstrumentPreset INSTR_PULSE_LEAD{0.35f, 0.5f, 4}; +inline constexpr InstrumentPreset INSTR_PULSE_HARMONY{0.22f, 0.125f, 5}; +inline constexpr InstrumentPreset INSTR_TRIANGLE_BASS{0.30f, 0.5f, 3}; + +// Percussion instruments (duty=0, use with WaveType::NOISE) +inline constexpr InstrumentPreset INSTR_KICK{0.40f, 0.0f, 1, 0.12f, 25}; // Kick: octave=1, dur=0.12s, period=25 +inline constexpr InstrumentPreset INSTR_SNARE{0.30f, 0.0f, 2, 0.15f, 50}; // Snare: octave=2, dur=0.15s, period=50 +inline constexpr InstrumentPreset INSTR_HIHAT{0.20f, 0.0f, 3, 0.05f, 12}; // Hi-HAT: octave=3, dur=0.05s, period=12 + +inline MusicNote makeNote(const InstrumentPreset& preset, Note note, float duration); +inline MusicNote makeNote(const InstrumentPreset& preset, Note note, uint8_t octave, float duration); +inline MusicNote makeRest(float duration); +inline float instrumentToFrequency(const InstrumentPreset& preset, Note note, uint8_t octave); +``` + +> **Note:** For percussion, use `duty == 0.0f` to signal NOISE channel. The `defaultOctave` field determines drum type: 1=Kick, 2=Snare, 3+=Hi-HAT. The `instrumentToFrequency()` helper returns **fixed LFSR clock rates** for the NOISE channel (Kick=80Hz, Snare=150Hz, Hi-HAT=3000Hz). Unlike `noteToFrequency()` which produces musical pitch, these values control noise density/brightness (higher = more chaotic/bright, lower = slower/deeper). + +These helpers reduce boilerplate when defining tracks and keep note volumes and +octaves consistent per instrument. + +### 7.2 MusicPlayer (`MusicPlayer.h`) + +**📖 Detailed guide:** [Music player guide](../guide/music-player-guide.md) + +Defined in [`MusicPlayer.h`](include/audio/MusicPlayer.h) and +[`MusicPlayer.cpp`](src/audio/MusicPlayer.cpp). + +**Responsibilities (thin client):** + +- **`play` / `stop` / `pause` / `resume` / `setTempoFactor` / `setBPM` / `setMasterVolume`**: build `AudioCommand`s or delegate to `AudioEngine::setMasterVolume`. +- **`isPlaying()`**: combines **`AudioEngine::isMusicPlaying()`** and **`isMusicPaused()`** (fed by `ApuCore` atomics) with a short grace window after `play()` so the game thread does not observe a false “stopped” before `MUSIC_PLAY` is consumed. +- **`getActiveTrackCount()`**: derives from the last `play()` request and local flags (1 + non-null sub-tracks). + +**Sequencing (audio consumer):** + +- Tick-based sequencer lives in **`ApuCore::updateMusicSequencer`**: `globalTickCounter` is derived from **`audioTimeSamples / tickDurationSamples`** each block; when a tick elapses, pending `MusicNote`s emit internal **`executePlayEvent`** calls (same path as SFX). +- **All platforms share identical sequencing math** because there is a single implementation in `ApuCore`. + +### 7.3 Integration with Engine + +`MusicPlayer` is owned by `Engine` alongside `AudioEngine`: + +- The `Engine` constructor creates `audioEngine` and `musicPlayer`. +- Games use: + +```cpp +auto& music = engine.getMusicPlayer(); +music.play(myTrack); +``` + +This keeps music sequencing **sample-accurate** and completely independent of the game frame rate. Render stalls or logic spikes will not cause music to jitter or slow down. + +### 7.4 Example: GeometryJump background music + +GeometryJump defines a simple looping melody using the helpers in +`AudioMusicTypes.h` +(see [`GeometryJumpScene.cpp`](examples/GeometryJump/GeometryJumpScene.cpp)): + +```cpp +using namespace pixelroot32::audio; + +static const MusicNote MELODY_NOTES[] = { + makeNote(INSTR_PULSE_LEAD, Note::C, 0.20f), + makeNote(INSTR_PULSE_LEAD, Note::E, 0.20f), + makeNote(INSTR_PULSE_LEAD, Note::G, 0.25f), + makeRest(0.10f), + // ... +}; + +static const MusicTrack GAME_MUSIC = { + MELODY_NOTES, + sizeof(MELODY_NOTES) / sizeof(MusicNote), + true, // loop + WaveType::PULSE, // use one PULSE channel + 0.5f // duty cycle +}; + +void GeometryJumpScene::init() { + // ... + engine.getMusicPlayer().play(GAME_MUSIC); +} +``` + +- Music uses the same **`executePlayEvent`** path as SFX, so melody notes **consume slots in the voice pool** alongside sound effects. With **8** voices, simple lead lines still leave headroom for SFX; dense multi-track + rapid SFX can trigger **voice stealing** (§3.5). +- Timing is **sample-accurate** in `ApuCore`, so melody playback does not depend on render FPS; game logic should still avoid assuming instant delivery of enqueued commands if the audio queue overflows (§3.5). + +### 7.5 Multi-track music playback + +The MusicPlayer supports up to **4** simultaneous tracks (main + 3 sub-tracks): + +```cpp +// Define separate tracks for different layers +static const MusicNote MELODY_NOTES[] = { /* ... */ }; +static const MusicNote BASS_NOTES[] = { /* ... */ }; +static const MusicNote DRUM_NOTES[] = { /* ... */ }; + +static const MusicTrack MELODY_TRACK = { MELODY_NOTES, 3, true, WaveType::PULSE, 0.5f }; +static const MusicTrack BASS_TRACK = { BASS_NOTES, 2, true, WaveType::PULSE, 0.25f }; +static const MusicTrack DRUM_TRACK = { DRUM_NOTES, 4, true, WaveType::NOISE, 0.0f }; + +// Combine into main track +static const MusicTrack FULL_MUSIC = { + MELODY_NOTES, 3, true, WaveType::PULSE, 0.5f, + &BASS_TRACK, // secondVoice - bass line + nullptr, // thirdVoice + &DRUM_TRACK // percussion - drums +}; + +// Play all 3 tracks simultaneously +musicPlayer.play(FULL_MUSIC); + +// Query active track count +size_t count = musicPlayer.getActiveTrackCount(); // Returns 3 +``` + +Key behaviors: + +- Sub-track pointers default to `nullptr` for single-layer music. +- `stop()`, `pause()`, `resume()` affect the whole arrangement enqueued by the last `MUSIC_PLAY`. +- Tempo factor / BPM commands apply to the shared sequencer in `ApuCore`. + +`AudioCommand` carries **`subTracks[MAX_SUB_TRACKS]`** and **`subTrackCount`** for `MUSIC_PLAY`; `ApuCore::processCommands` copies them into its internal `tracks[]` array. + +--- + +## 8. Current limitations and future extensions + +With the **Multi-Core Architecture (v0.7.0-dev)**, many previous limitations were addressed, particularly regarding timing and stability. + +### 8.1 Resolved / Improved + +- **Sample-Accurate Timing**: The system now uses samples instead of `deltaTime` for all internal logic, eliminating jitter and drift. +- **Decoupled Execution**: Audio logic is completely isolated from the game's frame rate, preventing audio stuttering during heavy CPU load. +- **Music Tempo Control**: Added support for real-time tempo changes via `MUSIC_SET_TEMPO`. +- **ADSR Envelopes**: Full Attack-Decay-Sustain-Release envelopes implemented via `InstrumentPreset` for expressive note articulation and click-free playback. +- **LFO Modulation**: Low-frequency oscillators for vibrato (pitch) and tremolo (volume) effects. +- **Multi-track Music**: Support for up to 4 simultaneous tracks (main + 3 sub-tracks) with independent voices and percussion. +- **Linear frequency sweep**: optional portamento on `PULSE` / `TRIANGLE` via `AudioEvent::sweepEndHz` and `sweepDurationSec` (sample-accurate linear interpolation; applied before LFO pitch modulation each sample). +- **Master bitcrush**: optional `AudioEngine::setMasterBitcrush` (0–15) on the final `int16_t` bus. +- **extra waveforms**: `WaveType::SINE` (256-point LUT in [`AudioOscLUT.h`](include/audio/AudioOscLUT.h)) and `WaveType::SAW` (linear ramp); both are first-class `WaveType` values allocated from the **same `MAX_VOICES` pool** as pulse/triangle/noise. `executePlayEvent` sets each voice’s `type` per note; linear sweep applies to SINE/SAW as well. +- **— post-mix hook**: optional `AudioConfig::postMixMono` / `postMixUser`, applied in `ApuCore::generateSamples` after bitcrush on the full buffer. **RT-safe contract:** no heap allocation, no mutexes, bounded work. `AudioEngine::init` forwards the pointer to `ApuCore::setPostMixMono`. + +### 8.2 Remaining Limitations + +- No exact cycle-accurate emulation of the NES APU. +- **NES hardware-style sweep registers** (automatic decreasing pitch per APU frame, etc.) are not emulated; only **linear** frequency sweeps on `PLAY_EVENT` are supported. +- **Complex envelopes**: ADSR is implemented; however, advanced features like delayed attack, logarithmic curves, or multiple envelope stages are not available. +- **Music catch-up / backlog**: if `generateSamples` is not called for a long wall-clock gap (host suspended, debugger break), `updateMusicSequencer` may need to process many ticks in one call. **`ApuCore`** bounds how many note starts are handled per `generateSamples` invocation via **`setSequencerNoteLimit` / `getSequencerNoteLimit`** (default from `MAX_NOTES_PER_FRAME`, overridable 1–1000; compile-time cap **`AUDIO_SEQUENCER_MAX_NOTES`** in [`ApuCore.h`](include/audio/ApuCore.h)). Excess notes are deferred to the next audio block; **`getDeferredNotes`** reflects how many were deferred last pass (diagnostic). If the host is suspended for a very long time, work still grows with backlog, but each block does not fire an unbounded burst of `executePlayEvent` calls in a single inner loop iteration. +- **`MUSIC_PLAY` start anchor** (distinct from host-suspend catch-up): On `MUSIC_PLAY`, `ApuCore` aligns `globalTickCounter` and each track’s `nextNoteTicks` to **`audioTimeSamples / tickDurationSamples`** at command processing time so elapsed time since engine init is **not** mistaken for a tick backlog. Without this, the first `updateMusicSequencer` after play could fire up to **`MAX_NOTES_PER_FRAME`** note starts in one block (voice stealing → dissonant cluster) and skip the start of the phrase. A one-shot **`firstSequencerCallAfterPlay_`** allows the first scheduled note to run in the same audio block when **`currentTick == startTick`** (the usual guard would otherwise wait one tick). +- **Code references (maintainers)** — approximate line numbers; re-check after refactors: + - [`ApuCore.h`](include/audio/ApuCore.h): sequencer limits `MAX_NOTES_PER_FRAME` / `AUDIO_SEQUENCER_MAX_NOTES` (~L44–L52); API `setSequencerNoteLimit`, `getSequencerNoteLimit`, `getDeferredNotes` (~L74–L82). + - [`ApuCore.cpp`](src/audio/ApuCore.cpp): `executePlayEvent` — linear sweep clamped to note duration (`sweepLen > noteLen` → truncate, ~L433–L451); `apply_linear_frequency_sweep_float` (~L32–L45); no-FPU path, lerp of `phaseIncQ32` during sweep (~L795–L810). +- **Command queue**: fixed depth **128**; overflow **drops** the newest command and increments **`ApuCore::droppedCommands`**. Contract: **single producer, single consumer** — do not call `playEvent` / `submitCommand` from multiple threads without external serialization. +- **`MusicPlayer` vs. drops**: local `playing` / `paused` flags can briefly disagree with `ApuCore` if a command is dropped. For diagnostics, **`ApuCore::getDroppedCommands()`** is public (e.g. tests or a custom scheduler wrapper); stock `AudioEngine` does not expose it—avoid saturating the queue instead. + +### 8.3 Future Extensions + +- **NES-style hardware sweep units** (per-frame decrement registers) and exponential / logarithmic glide curves. +- **High-Level SFX Helpers**: Add methods like `playJumpSfx()`, `playExplosionSfx()` to `AudioEngine` for easier use. +- **Advanced Music Tooling**: Better support for patterns and multi-track sequencing in the `MusicPlayer`. +- **SIMD Optimizations**: Investigate SSE/AVX for native platforms and DSP instructions for ESP32-S3 (see research document). + +--- + +## 9. Summary + +- The NES-inspired audio system in PixelRoot32: + - Uses a **dynamic voice pool** inside **`ApuCore`** (default **8** voices) with **voice stealing** under load, while keeping **`WaveType`** on the public `AudioEvent` / track metadata. + - Produces mono 16-bit audio via software mixing. + - Is platform-agnostic thanks to `AudioBackend`, `AudioScheduler`, and the shared **`ApuCore`** implementation. + - Is **decoupled** from the game loop, running on Core 0 (ESP32) or a separate thread (native/SDL2). + - Uses **sample-accurate timing** for both SFX and music inside **`ApuCore`**. + - Is controlled from games through `AudioEngine` (SFX) and `MusicPlayer` (Music) via a **lock-free SPSC command queue** (bounded capacity; **overflow drops** the latest command; see §3.5). diff --git a/architecture/index.md b/architecture/index.md new file mode 100644 index 0000000..a0a5322 --- /dev/null +++ b/architecture/index.md @@ -0,0 +1,141 @@ +# Architecture Index - PixelRoot32 Game Engine + +> **NOTE:** This is the main entry point for architecture documentation. For detailed narratives and design philosophy, see the files in this folder. For ESP32 rendering pipeline details, see the ESP32 section below. + +--- + +## Quick Navigation + +### Layer Architecture + +| Layer | Document | Description | +|-------|----------|-------------| +| **Layer 0** | [Hardware Layer](./layer-hardware.md) | ESP32, displays, audio hardware, PC simulation | +| **Layer 1** | [Driver Layer](./layer-drivers.md) | TFT_eSPI, U8G2, SDL2, AudioBackends | +| **Layer 2** | [Abstraction Layer](./layer-abstraction.md) | DrawSurface, PlatformMemory, Logging, Math | +| **Layer 3** | [System Layer](./layer-systems.md) | Renderer, Audio, Physics, UI subsystems | +| **Layer 4** | [Scene Layer](./layer-scene.md) | Engine, SceneManager, Entity, Actor hierarchy | + +### Subsystem Deep Dives + +| Subsystem | Document | Description | +|-----------|----------|-------------| +| **Audio NES** | [Audio Subsystem](./audio-subsystem.md) | 4-channel NES-style: shared `ApuCore`, `AudioScheduler`, backends | +| **Physics** | [Physics Subsystem](./physics-subsystem.md) | Flat Solver, collisions, CCD | +| **Memory** | [Memory System](./memory-system.md) | Smart pointers, RAII, ESP32 DRAM | +| **Resolution Scaling** | [Resolution Scaling](./resolution-scaling.md) | Logical vs physical resolution | +| **Tile Animation** | [Tile Animation](./tile-animation.md) | Lookup tables, O(1) resolve | +| **Touch Input** | [Touch Input](./touch-input.md) | Pipeline, XPT2046, calibration | +| **Extensibility** | [Extending PixelRoot32](../guide/extending-pixelroot32.md) | Custom drivers, configuration | + +### API Reference + +| Module | Document | +|--------|----------| +| Configuration | [config.md](../api/config.md) | +| Math | [math.md](../api/math.md) | +| Core | [core.md](../api/core.md) | +| Physics | [physics.md](../api/physics.md) | +| Graphics | [graphics.md](../api/graphics.md) | +| UI | [ui.md](../api/ui.md) | +| Audio | [audio.md](../api/audio.md) | +| Input | [input.md](../api/input.md) | +| Platform | [platform.md](../api/platform.md) | + +--- + +## Core Class Hierarchy + +```mermaid +graph TD + Entity --> Actor + Actor --> PhysicsActor + PhysicsActor --> StaticActor + PhysicsActor --> KinematicActor + PhysicsActor --> RigidActor + StaticActor --> SensorActor + Entity --> UIElement + UIElement --> UILayout + UILayout --> UIAnchorLayout + UILayout --> UIGridLayout + UILayout --> UIHorizontalLayout + UILayout --> UIVerticalLayout + UIElement --> UIButton + UIElement --> UILabel + UIElement --> UICheckBox + UIElement --> UIPanel + UIElement --> UIPaddingContainer + UIElement --> UITouchElement + UITouchElement --> UITouchButton + UITouchElement --> UITouchCheckbox + UITouchElement --> UITouchSlider + Entity --> ParticleEmitter + DrawSurface --> BaseDrawSurface +``` + +--- + +## Subsystem Modular Compilation + +| Subsystem | Enable Flag | Default | +|-----------|-------------|---------| +| Audio | `PIXELROOT32_ENABLE_AUDIO` | Enabled | +| Physics | `PIXELROOT32_ENABLE_PHYSICS` | Enabled | +| UI System | `PIXELROOT32_ENABLE_UI_SYSTEM` | Enabled | +| Particles | `PIXELROOT32_ENABLE_PARTICLES` | Enabled | +| Touch Input | `PIXELROOT32_ENABLE_TOUCH` | Disabled | +| Tile Animations | `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enabled | +| Static tilemap FB snapshot (4bpp) | `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enabled (`PlatformDefaults.h`) | +| Debug Overlay | `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Disabled | + +--- + +## ESP32 Rendering Pipeline and Tilemap Caching + +On ESP32 with **TFT_eSPI** (`TFT_eSPI_Drawer`), the logical framebuffer is typically an **8-bit color-depth sprite** (`TFT_eSprite`). Each frame: + +1. **`Renderer::beginFrame()`** obtains a pointer to that buffer via **`DrawSurface::getSpriteBuffer()`** (when the driver supports it), clears the buffer, then draws the scene. +2. **2bpp / 4bpp tilemaps and sprites** can write **directly into that buffer** (matching TFT_eSPI's 8bpp packing for RGB565), avoiding a virtual `drawPixel` per pixel where possible. +3. **`present()` / `sendBuffer()`** converts logical 8bpp rows to **RGB565** using a LUT and pushes pixels to the panel via **DMA**. + +### Static Tilemap Layer Cache + +The engine provides **`pixelroot32::graphics::StaticTilemapLayerCache`** (`include/graphics/StaticTilemapLayerCache.h`): a **4bpp tilemap** helper that can snapshot the logical framebuffer after drawing a **static** group of `TileMap4bppDrawSpec` entries, then on subsequent frames **`memcpy`** that snapshot back and redraw only the **dynamic** group. + +- **Allocation:** `allocateForLogicalSize` / `allocateForRenderer` in `Scene::init()` +- **Opt-out:** build flag `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE=0`, or `setFramebufferCacheEnabled(false)` +- **Example:** `examples/animated_tilemap` — `AnimatedTilemapScene` + +**Game / scene developer contract:** + +- Call **`invalidate()`** when something inside the **static** group changes visually +- **Dynamic** layers are drawn every frame on the fast path—no invalidation needed +- **Scroll:** cache rebuilds when the camera sample changes; no extra invalidation solely for scroll + +--- + +## Related Documentation + +| Document | Description | +|----------|-------------| +| [API Reference](../api/index.md) | Complete API documentation index | +| [Getting Started](../guide/getting-started.md) | First steps with the engine | +| [Style Guide](../guide/coding-style.md) | Coding conventions | +| [Platform Compatibility](../guide/platform-compatibility.md) | Supported hardware matrix | +| [Testing](../guide/testing.md) | Unit and integration testing | +| [MusicPlayer Guide](../guide/music-player-guide.md) | Background music, multi-track, tempo/BPM | + +--- + +## Detailed Architecture + +For comprehensive narrative documentation including: + +- Executive summary and design philosophy +- Design philosophy and modularity explanation +- Layer hierarchy in depth +- Module dependencies diagram +- Performance optimizations detail +- Configuration and compilation flags + +**See:** [Layer Abstraction](./layer-abstraction.md) - Design philosophy and layer details \ No newline at end of file diff --git a/architecture/layer-abstraction.md b/architecture/layer-abstraction.md new file mode 100644 index 0000000..40b9d7e --- /dev/null +++ b/architecture/layer-abstraction.md @@ -0,0 +1,199 @@ +# Layer 2: Abstraction Layer + +## Responsibility + +Abstract interfaces that decouple subsystems from concrete implementations, enabling portability and testability. + +**Design Patterns**: +- **Bridge Pattern**: `DrawSurface` decouples Renderer from specific drivers +- **Strategy Pattern**: `AudioScheduler` allows different scheduling implementations + +--- + +## Main Components + +### PlatformMemory.h (Macro Abstraction) + +Provides a unified API for memory operations that differ between ESP32 (Flash/PROGMEM) and Native (RAM) platforms. + +| Macro | Description | ESP32 Mapping | Native Mapping | +|-------|-------------|---------------|----------------| +| `PIXELROOT32_FLASH_ATTR` | Store data in Flash | `PROGMEM` | (empty) | +| `PIXELROOT32_STRCMP_P` | Compare with Flash string | `strcmp_P` | `strcmp` | +| `PIXELROOT32_MEMCPY_P` | Copy from Flash | `memcpy_P` | `memcpy` | +| `PIXELROOT32_READ_BYTE_P` | Read 8-bit from Flash | `pgm_read_byte` | direct access | +| `PIXELROOT32_READ_WORD_P` | Read 16-bit from Flash | `pgm_read_word` | direct access | +| `PIXELROOT32_READ_DWORD_P` | Read 32-bit from Flash | `pgm_read_dword` | direct access | +| `PIXELROOT32_READ_FLOAT_P` | Read float from Flash | `pgm_read_float` | direct access | + +**Usage Example**: + +```cpp +#include "platforms/PlatformMemory.h" + +// Store data in Flash +const char message[] PIXELROOT32_FLASH_ATTR = "Hello"; + +// Compare with Flash string +if (PIXELROOT32_STRCMP_P("lava", type) == 0) { + // Handle lava tile +} +``` + +--- + +### DrawSurface (Bridge Pattern) + +Abstract base class that decouples the Renderer from specific display drivers. + +```cpp +class DrawSurface { +public: + virtual void init() = 0; + virtual void drawPixel(int x, int y, uint16_t color) = 0; + virtual void drawLine(int x1, int y1, int x2, int y2, uint16_t color) = 0; + virtual void drawFilledRectangle(int x, int y, int w, int h, uint16_t color) = 0; + virtual void clearBuffer() = 0; + virtual void sendBuffer() = 0; + virtual void setOffset(int x, int y) {} + virtual void setRotation(uint8_t rotation) {} +}; +``` + +**Implementations**: +- `TFT_eSPI_Drawer` - ESP32 color displays +- `U8G2_Drawer` - ESP32 monochrome displays +- `SDL2_Drawer` - PC simulation + +--- + +### AudioScheduler (Strategy Pattern) + +Abstract scheduler for platform-specific audio timing. + +```cpp +class AudioScheduler { +public: + virtual void init() = 0; + virtual void submitCommand(const AudioCommand& cmd) = 0; + virtual void generateSamples(int16_t* stream, int length) = 0; + virtual void start() = 0; + virtual void stop() = 0; +}; +``` + +**Implementations**: +- `ESP32AudioScheduler` - FreeRTOS task on Core 0 +- `NativeAudioScheduler` - POSIX thread (PC) + +--- + +### PlatformCapabilities + +Structure that detects and exposes hardware capabilities at runtime. + +```cpp +namespace pixelroot32::platforms { + struct PlatformCapabilities { + bool hasDualCore; // Multi-core support (ESP32, ESP32-S3) + int audioCoreId; // Recommended core for audio (0 on ESP32) + int mainCoreId; // Recommended core for game loop (1 on ESP32) + bool hasFPU; // Floating-point unit available + size_t totalSRAM; // Total SRAM available + bool hasPSRAM; // External SPI RAM available (S3) + }; +} +``` + +**Usage**: + +```cpp +auto caps = pixelroot32::platforms::detectCapabilities(); +if (caps.hasDualCore) { + // Use dual-core audio scheduling +} +``` + +--- + +### Math System (Scalar Abstraction) + +**Files**: `include/math/Scalar.h`, `include/math/Fixed16.h`, `include/math/MathUtil.h` + +Provides deterministic, platform-optimized numerical operations. + +**Features**: + +- **Hardware Adaptation**: Automatically switches between `float` and `Fixed16` based on FPU presence (ESP32-S3 vs ESP32-C3) +- **16.16 Fixed Point**: Optimized `Fixed16` implementation for RISC-V targets (C3/C6) +- **Generic Math API**: Single API for `sin`, `cos`, `sqrt`, `atan2` that resolves to most efficient implementation per platform +- **Stable Rounding**: Explicit `roundToInt`, `floorToInt`, `ceilToInt` to avoid floating-point truncation artifacts + +```cpp +using Scalar = pixelroot32::math::Scalar; // float or Fixed16 + +Scalar angle = MathUtil::atan2(dy, dx); +Scalar distance = MathUtil::sqrt(dx*dx + dy*dy); +int pixelX = MathUtil::roundToInt(cameraX + offset); +``` + +--- + +### Unified Logging System + +**Files**: `include/core/Log.h`, `src/platforms/PlatformLog.cpp` + +Cross-platform logging abstraction that eliminates `#ifdef` blocks in user code. + +**Features**: +- Unified API for ESP32 (Serial) and Native (stdout) +- Log levels: Info, Profiling, Warning, Error +- printf-style formatting +- Automatic platform routing +- **Zero overhead when disabled**: Double-layer conditional compilation + +**Architecture - Double-Layer Conditional Compilation**: + +``` +PIXELROOT32_DEBUG_MODE defined: + log() → format with va_list → platformPrint() → Serial/stdout + +PIXELROOT32_DEBUG_MODE not defined: + log() → (void)level; (void)fmt; → no-op +``` + +**Main API**: + +```cpp +namespace pixelroot32::core::logging { + enum class LogLevel { Info, Profiling, Warning, Error }; + void log(LogLevel level, const char* format, ...); + void log(const char* format, ...); // Info level shorthand +} + +// Enable in platformio.ini: +// build_flags = -DPIXELROOT32_DEBUG_MODE +``` + +**Platform Output**: +- ESP32: Routes to `Serial.print()` +- Native: Routes to `printf()` with `fflush(stdout)` + +--- + +## Abstraction Benefits + +1. **Portability**: Same game code runs on ESP32 and PC +2. **Testability**: Mock implementations for unit testing +3. **Flexibility**: Swap implementations without changing game code +4. **Maintainability**: Changes isolated to specific layers + +--- + +## Related Documentation + +- [API Reference - Math Module](../api/math.md) - Scalar and math utilities +- [API Reference - Platform](../api/platform.md) - Platform abstractions +- [Memory System](memory-system.md) - Memory management details +- [Hardware Layer](layer-hardware.md) - Physical hardware +- [Driver Layer](layer-drivers.md) - Concrete implementations diff --git a/architecture/layer-drivers.md b/architecture/layer-drivers.md new file mode 100644 index 0000000..83e60ca --- /dev/null +++ b/architecture/layer-drivers.md @@ -0,0 +1,166 @@ +# Layer 1: Driver Layer + +## Responsibility + +Platform-specific hardware abstraction that bridges the gap between hardware and the engine's abstract interfaces. + +**Design Pattern**: Concrete implementation of abstractions defined in Layer 2. + +--- + +## ESP32 Drivers + +| Driver | File | Description | +|--------|------|-------------| +| `TFT_eSPI_Drawer` | `drivers/esp32/TFT_eSPI_Drawer.cpp` | TFT display driver (ST7789, ST7735, ILI9341) | +| `U8G2_Drawer` | `drivers/esp32/U8G2_Drawer.cpp` | Monochrome OLED driver (SSD1306, SH1106) | +| `ESP32_I2S_AudioBackend` | `drivers/esp32/ESP32_I2S_AudioBackend.cpp` | I2S audio backend for external DACs | +| `ESP32_DAC_AudioBackend` | `drivers/esp32/ESP32_DAC_AudioBackend.cpp` | Internal DAC audio backend | +| `ESP32AudioScheduler` | `audio/ESP32AudioScheduler.cpp` | Multi-core audio scheduler (FreeRTOS task) | + +### TFT_eSPI Driver + +The primary color display driver using the popular TFT_eSPI library. + +**Features**: +- Hardware SPI communication +- DMA support for fast transfers +- Resolution scaling (nearest-neighbor) +- Double-buffering for smooth rendering + +**Future optimization (Opción B — diseño, no implementado):** `sendBufferScaled()` hoy envía el rectángulo completo (**`setAddrWindow`** + bloques DMA). Una variante sería **comparar** el sprite 8 bpp contra una **copia del frame anterior** (o un diff por bandas) y emitir **varias ventanas** SPI solo donde cambiaron píxeles. Mejora el techo SPI cuando el área sucia es pequeña; coste típico **~W×H bytes** RAM y más llamadas a **`setAddrWindow`**. La **Opción A** (omitir `draw`+`present` en el **`Engine`** cuando la escena lo indica) está descrita en [ESP32 rendering](../ARCHITECTURE.md#esp32-rendering-pipeline-and-tilemap-caching). + +**Supported Displays**: +- ST7789 (240x240, 320x240) +- ST7735 (128x128, 160x128) +- ILI9341 (320x240) + +### U8G2 Driver + +Driver for monochrome OLED displays using the U8G2 library. + +**Features**: +- I2C and SPI support +- 1MHz I2C bus overclocking for 60 FPS +- Page buffer mode for memory efficiency + +**Supported Displays**: +- SSD1306 (128x64, 128x32) +- SH1106 (128x64) + +### Audio Drivers + +#### ESP32_I2S_AudioBackend + +For high-quality audio with external DACs (MAX98357A, PCM5102). + +```cpp +ESP32_I2S_AudioBackend audioBackend(26, 25, 22, 22050); +// BCLK=26, LRCK=25, DOUT=22, 22050Hz sample rate +``` + +**Features**: +- I2S peripheral with DMA +- Standard sample rates (11025, 22050, 44100 Hz) +- Pinned to Core 0 (separate from game loop) + +#### ESP32_DAC_AudioBackend + +For retro-style audio using the internal 8-bit DAC. + +```cpp +ESP32_DAC_AudioBackend audioBackend(25, 11025); +// GPIO 25, 11025Hz for retro feel +``` + +**Features**: +- 8-bit resolution +- Software-based sample pushing +- 0.7x attenuation to prevent saturation + +--- + +## Native (PC) Drivers + +| Driver | File | Description | +|--------|------|-------------| +| `SDL2_Drawer` | `drivers/native/SDL2_Drawer.cpp` | SDL2 graphics simulation | +| `SDL2_AudioBackend` | `drivers/native/SDL2_AudioBackend.cpp` | SDL2 audio backend | +| `NativeAudioScheduler` | `audio/NativeAudioScheduler.cpp` | Native thread-based scheduler | +| `MockArduino` | `platforms/mock/MockArduino.cpp` | Arduino API emulation | + +### SDL2_Drawer + +Graphics driver for PC development using SDL2. + +**Features**: +- Windowed and fullscreen modes +- Hardware acceleration via SDL2 +- Mouse-to-touch event conversion (when touch enabled) +- Pixel-perfect scaling options + +### SDL2_AudioBackend + +Audio driver using SDL2's audio subsystem. + +**Features**: +- Standard audio device access +- Callback-based sample generation +- Thread-safe command queue + +### NativeAudioScheduler + +Thread-based audio scheduling for PC platforms. + +**Features**: +- Dedicated high-priority thread +- Sample-accurate timing +- Lock-free command queue + +--- + +## Driver Selection + +Drivers are selected at compile-time via build flags: + +```ini +# platformio.ini + +# Use TFT_eSPI (default) +build_flags = -D PIXELROOT32_USE_TFT_ESPI_DRIVER + +# Use U8G2 for OLED +build_flags = -D PIXELROOT32_USE_U8G2 + +# Custom display +custom_display = new MyCustomDriver() +``` + +--- + +## Creating Custom Drivers + +See [Extending PixelRoot32](../guide/extending-pixelroot32.md) for detailed instructions on creating custom display and audio drivers. + +Quick overview: + +```cpp +#include + +class MyCustomDriver : public pixelroot32::graphics::BaseDrawSurface { +public: + void init() override; + void drawPixel(int x, int y, uint16_t color) override; + void clearBuffer() override; + void sendBuffer() override; +}; +``` + +--- + +## Related Documentation + +- [Abstraction Layer](layer-abstraction.md) - Interfaces these drivers implement +- [Hardware Layer](layer-hardware.md) - Physical hardware details +- [System Layer](layer-systems.md) - High-level systems that use these drivers +- [Extending PixelRoot32](../guide/extending-pixelroot32.md) - How to create custom drivers diff --git a/architecture/layer-hardware.md b/architecture/layer-hardware.md new file mode 100644 index 0000000..0e99509 --- /dev/null +++ b/architecture/layer-hardware.md @@ -0,0 +1,105 @@ +# Layer 0: Hardware Layer + +## Responsibility + +Underlying physical hardware that the engine runs on. + +--- + +## Components + +### Microcontrollers + +- **ESP32/ESP32-S3**: Main target microcontrollers + - Dual-core Xtensa LX6 processors + - Wi-Fi and Bluetooth connectivity + - Various memory configurations (520KB SRAM on classic ESP32, 512KB on S3) + +- **ESP32-C3**: RISC-V variant + - Single-core RISC-V processor + - No FPU (uses Fixed16 math) + - 400KB SRAM + +### Displays + +| Display | Type | Resolution | Interface | Use Case | +|---------|------|------------|-----------|----------| +| ST7789 | TFT LCD | 240x240, 320x240 | SPI | Color games, high resolution | +| ST7735 | TFT LCD | 128x128, 160x128 | SPI | Smaller color displays | +| SSD1306 | OLED | 128x64, 128x32 | I2C/SPI | Monochrome, low power | +| SH1106 | OLED | 128x64 | I2C/SPI | Alternative monochrome | + +### Audio Hardware + +| Component | Type | Description | +|-----------|------|-------------| +| Internal DAC | 8-bit | ESP32 GPIO 25/26, PAM8302A amplifier | +| I2S + MAX98357A | Digital | High-quality audio, class D amp | +| I2S + PCM5102 | Digital | DAC for headphones/line out | + +### Input + +- **Physical Buttons**: Connected to GPIOs + - Typical configurations: 4-directional + 2 action buttons + - Direct GPIO polling with debouncing + +- **Touch Controllers**: + - XPT2046: Resistive touch (SPI) + - GT911: Capacitive touch (I2C) + +### PC/Native Platform + +- **Simulation**: SDL2 on Windows/Linux/macOS +- **Purpose**: Rapid development without hardware +- **Features**: Full API compatibility, faster iteration + +--- + +## Hardware Capabilities Detection + +The engine uses `PlatformCapabilities` structure to detect hardware at runtime: + +```cpp +struct PlatformCapabilities { + bool hasDualCore; // Multi-core support + int audioCoreId; // Recommended core for audio + int mainCoreId; // Recommended core for game loop + bool hasFPU; // Floating-point unit available + size_t totalSRAM; // Total SRAM available +}; +``` + +--- + +## Memory Layout (ESP32) + +```mermaid +flowchart TB + subgraph DRAM["DRAM (520KB)"] + direction TB + D1[".dram0.bss (static data)"] + D2["Heap (dynamic allocations)"] + D3["Stack"] + end + + subgraph IRAM["IRAM (128KB)"] + direction TB + I1["Instruction RAM (cached code)"] + end + + subgraph FLASH["Flash (4MB+)"] + direction TB + F1["Program code and PROGMEM data"] + end + + DRAM --> IRAM --> FLASH +``` + +--- + +## Related Documentation + +- [Driver Layer](layer-drivers.md) - Hardware abstraction drivers +- [Abstraction Layer](layer-abstraction.md) - PlatformMemory and cross-platform abstractions +- [Memory System](memory-system.md) - Memory management strategies +- [Platform Compatibility](../guide/platform-compatibility.md) - Supported hardware matrix diff --git a/architecture/layer-scene.md b/architecture/layer-scene.md new file mode 100644 index 0000000..a94f408 --- /dev/null +++ b/architecture/layer-scene.md @@ -0,0 +1,452 @@ +# Layer 4: Scene Layer + +## Responsibility + +Game scene and entity management. This layer provides the organizational structure for game objects and their lifecycle. + +--- + +## Components + +### Engine + +**Files**: `include/core/Engine.h`, `src/core/Engine.cpp` + +Central class that orchestrates all subsystems. + +**Responsibilities**: + +- Manages Renderer, SceneManager, InputManager, AudioEngine, MusicPlayer +- Runs the main game loop +- Provides automatic touch processing (when enabled) + +**Game Loop**: + +```cpp +void Engine::run() { + while (true) { + // 1. Calculate delta time + deltaTime = currentMillis - previousMillis; + + // 2. Update + update(); + + // 3. Draw + draw(); + } +} + +void Engine::update() { + inputManager.update(deltaTime); + sceneManager.update(deltaTime); + // Note: AudioEngine runs on separate thread/core +} + +void Engine::draw() { + renderer.beginFrame(); + sceneManager.draw(renderer); + renderer.endFrame(); +} +``` + +**Touch Integration** (`PIXELROOT32_ENABLE_TOUCH=1`): + +- `getTouchDispatcher()`: Access touch event dispatcher +- `hasTouchEvents()`: Check for pending events +- `setTouchManager()`: Register external TouchManager for auto-processing + +When `setTouchManager()` is called, Engine automatically: + +1. Polls `touchManager->getTouchPoints()` each frame +2. Detects touch releases (count >0 → 0) +3. Processes through `TouchEventDispatcher` +4. Dispatches to `Scene::processTouchEvents()` + +See [Touch Input Architecture](touch-input.md) for details. + +--- + +### SceneManager + +**Files**: `include/core/SceneManager.h`, `src/core/SceneManager.cpp` + +Scene stack management (push/pop operations). + +**Operations**: + +| Method | Description | +|--------|-------------| +| `setCurrentScene()` | Replace current scene | +| `pushScene()` | Push new scene (pauses previous) | +| `popScene()` | Pop scene (resumes previous) | + +**Scene Stack**: + +```cpp +Scene* sceneStack[MaxScenes]; // Default: 8 scenes +int sceneCount; +``` + +Useful for: + +- Pause menus (push pause scene over game scene) +- Settings screens +- Dialog overlays + +--- + +### Scene + +**Files**: `include/core/Scene.h`, `src/core/Scene.cpp` + +Entity container representing a level or screen. + +**Memory Model**: Non-owning + +The Scene follows a **non-owning** model for entities. When you call `addEntity(Entity*)`, the scene stores a reference but **does not take ownership**. + +```cpp +// Scene does NOT delete entities +// You are responsible for lifetime (typically std::unique_ptr) + +class GameScene : public Scene { + std::unique_ptr player; // You own it + +public: + void init() override { + player = std::make_unique(100, 100, 16, 16); + addEntity(player.get()); // Scene gets non-owning pointer + } +}; +``` + +**Features**: + +- Entity array (`MAX_ENTITIES = 32` default) +- Render layer system (`MAX_LAYERS = 3` default) +- Integrated `CollisionSystem` +- Viewport culling +- Optional `SceneArena` for custom allocators + +**Lifecycle**: + +```cpp +virtual void init(); // Called when entering scene +virtual void update(unsigned long dt); // Every frame +virtual void draw(Renderer& r); // Every frame +virtual void processTouchEvents(const TouchEvent* events, uint8_t count); +virtual void onUnconsumedTouchEvent(const TouchEvent& event); +``` + +--- + +### Entity + +**Files**: `include/core/Entity.h` + +Abstract base class for all game objects. + +**Properties**: + +| Property | Type | Description | +|----------|------|-------------| +| `x`, `y` | `float` | Position | +| `width`, `height` | `int` | Dimensions | +| `type` | `EntityType` | GENERIC, ACTOR, UI_ELEMENT | +| `renderLayer` | `uint8_t` | Render layer (0-255) | +| `isVisible` | `bool` | Visibility control | +| `isEnabled` | `bool` | Update control | + +**Virtual Methods**: + +```cpp +virtual void update(unsigned long deltaTime) = 0; +virtual void draw(Renderer& renderer) = 0; +``` + +--- + +## Actor / PhysicsActor Hierarchy + +Following the Godot Engine philosophy, physical actors are specialized into distinct types based on their movement requirements. + +### Hierarchy Diagram + +``` +Entity +└── Actor + └── PhysicsActor (Base) + ├── StaticActor (Immovable walls/floors) + │ └── SensorActor (Trigger zones) + ├── KinematicActor (Player-controlled) + └── RigidActor (Physics-simulated props) +``` + +### Actor Types + +| Type | Movement | Collision Layer | Use Case | +|------|----------|-----------------|----------| +| **StaticActor** | None | Static grid | Walls, floors, platforms | +| **SensorActor** | None | Static grid | Collectibles, triggers | +| **KinematicActor** | Code-driven | Dynamic grid | Player, enemies, moving platforms | +| **RigidActor** | Physics-simulated | Dynamic grid | Props, debris, projectiles | + +### Common Features + +All PhysicsActor types support: + +- `setShape(CollisionShape::AABB/CIRCLE)` - Hitbox shape +- `setCollisionLayer(mask)` - Layer membership +- `setCollisionMask(mask)` - Layers to collide with +- `setSensor(true/false)` - Trigger mode (no collision response) +- `setOneWay(true/false)` - One-way platform mode +- `onCollision(Actor* other)` - Notification callback + +### Actor Example + +```cpp +class Player : public KinematicActor { +public: + void update(unsigned long dt) override { + // Movement logic + if (engine.getInputManager().isButtonPressed(BTN_A)) { + velocity.y = -jumpForce; + } + + // Move with collision + moveAndSlide(); + } + + void draw(Renderer& r) override { + r.drawSprite(playerSprite, x, y, Color::White); + } + + void onCollision(Actor* other) override { + if (other->isInLayer(Layers::kEnemy)) { + takeDamage(); + } + } +}; +``` + +--- + +## Game Layer + +**Responsibility**: Game-specific code implemented by the user. + +This is where you implement your game logic using the engine's architecture. + +### Typical Implementation + +```cpp +class GameScene : public Scene { + std::unique_ptr player; + std::vector> enemies; + std::unique_ptr background; + +public: + void init() override { + // Create player + player = std::make_unique(100, 100, 16, 16); + addEntity(player.get()); + + // Create enemies + for (int i = 0; i < 5; i++) { + auto enemy = std::make_unique(...); + enemies.push_back(std::move(enemy)); + addEntity(enemies.back().get()); + } + + // Start music + engine.getMusicPlayer().play(backgroundMusic); + } + + void update(unsigned long dt) override { + // Scene-level update logic + Scene::update(dt); // Updates all entities + } + + void draw(Renderer& r) override { + // Draw background + r.drawTileMap(*background, 0, 0); + + // Draw entities + Scene::draw(r); + } +}; +``` + +--- + +## Diagrams (scene graph and entities) + +### Scene lifecycle + +```mermaid +flowchart TB + subgraph SceneLifecycle["Scene Lifecycle"] + direction LR + A[Created] --> B[Initialized] + B --> C[Active] + C --> D[Cleanup] + D --> E[Destroyed] + end + + subgraph ActiveState["Active State"] + direction TB + U[update] + D2[draw] + P[processTouchEvents] + + U --> D2 + D2 --> U + end + + C -.-> ActiveState +``` + +### SceneManager transitions + +```mermaid +flowchart LR + E[Engine] --> SM[SceneManager] + SM --> CS[Current Scene] + SM --> NS[Next Scene] + + CS -->|"changeScene()"| NS + NS -->|"init()"| CS2[New Current Scene] +``` + +### Entity / Actor relationships + +```mermaid +classDiagram + class Entity { + +Vector2 position + +int width, height + +EntityType type + +bool isVisible + +bool isEnabled + +unsigned char renderLayer + +update(deltaTime)* + +draw(Renderer&)* + +setRenderLayer(layer) + } + + class Actor { + +uint16_t entityId + +CollisionLayer layer + +CollisionLayer mask + +CollisionSystem* collisionSystem + +setCollisionLayer(l) + +setCollisionMask(m) + +onCollision(Actor* other)* + } + + class KinematicActor { + +moveAndSlide(velocity, dt) + +moveAndCollide(velocity, dt) + +isOnFloor() + +isOnWall() + } + + class RigidActor { + +Vector2 velocity + +applyForce(force) + +applyImpulse(impulse) + } + + class StaticActor { + +StaticActor(x, y, w, h) + } + + class SensorActor { + +SensorActor(x, y, w, h) + } + + Entity <|-- Actor + Actor <|-- KinematicActor + Actor <|-- RigidActor + Actor <|-- StaticActor + Actor <|-- SensorActor +``` + +### Collision layer vs mask + +```mermaid +flowchart LR + A[Actor A] -->|"layer: PLAYER"| C{Collides?} + B[Actor B] -->|"mask: ENEMY | ITEM"| C + + C -->|"PLAYER & mask != 0"| D[Yes] + C -->|"PLAYER & mask == 0"| E[No] +``` + +### Entity lifecycle (ownership) + +```mermaid +stateDiagram-v2 + [*] --> Created: new Entity() + Created --> Active: addEntity() + Active --> Updating: isEnabled = true + Active --> Frozen: isEnabled = false + Updating --> Visible: isVisible = true + Updating --> Hidden: isVisible = false + Active --> Removed: removeEntity() + Removed --> [*]: destructor +``` + +### Scene-based game structure + +High-level view of how `SceneManager` binds multiple scenes (from core concepts): + +```mermaid +flowchart TD + subgraph Engine["Engine"] + SM[SceneManager] + end + + subgraph Scene1["Scene: MainMenu"] + E1[Entity: Background] + E2[Entity: Logo] + E3[Entity: ButtonContainer] + B1[UIElement: StartButton] + B2[UIElement: OptionsButton] + end + + subgraph Scene2["Scene: GameLevel"] + E4[Entity: Tilemap] + E5[Actor: Player] + E6[Actor: Enemy] + E7[Actor: Coin] + E8[Entity: HUD] + end + + SM --> Scene1 + SM --> Scene2 + E3 --> B1 + E3 --> B2 +``` + +--- + +## Memory Considerations + +| Component | Default Size | Configurable | +|-----------|--------------|--------------| +| Max Entities per Scene | 32 | `MAX_ENTITIES` | +| Max Render Layers | 3 | `MAX_LAYERS` | +| Max Scene Stack | 8 | `MaxScenes` | +| Physics Contacts | 128 | `PHYSICS_MAX_CONTACTS` | + +See [Memory System](memory-system.md) for optimization strategies. + +--- + +## Related Documentation + +- [Physics Subsystem](physics-subsystem.md) - Actor physics details +- [Touch Input](touch-input.md) - Scene touch handling +- [Memory System](memory-system.md) - Entity memory management +- [API Reference - Core](../api/core.md) - Class-level API diff --git a/architecture/layer-systems.md b/architecture/layer-systems.md new file mode 100644 index 0000000..d7874c1 --- /dev/null +++ b/architecture/layer-systems.md @@ -0,0 +1,226 @@ +# Layer 3: System Layer + +## Responsibility + +Game engine subsystems that implement high-level functionality. These systems provide the core capabilities that game code builds upon. + +--- + +## Subsystem Overview + +| Subsystem | Document | +|-----------|----------| +| **Audio** | [Audio Subsystem](./audio-subsystem.md) | +| **Physics** | [Physics Subsystem](./physics-subsystem.md) | +| **Touch Input** | [Touch Input](./touch-input.md) | +| **Tile Animation** | [Tile Animation](./tile-animation.md) | +| **Resolution Scaling** | [Resolution Scaling](./resolution-scaling.md) | + +--- + +## Architecture Diagrams + +### Rendering Pipeline (Game Code → Display) + +```mermaid +flowchart TB + subgraph Game["Game Code"] + A[Actor::draw] -->|"drawSprite()"| B[Renderer] + end + + subgraph RendererLayer["Renderer"] + B -->|"Clip"| C[Viewport Culling] + C -->|"Transform"| D[World to Screen] + D -->|"Scale"| E[Logical to Physical] + end + + subgraph Surface["DrawSurface"] + E --> F[DrawSurface Interface] + end + + subgraph Driver["Driver Layer"] + F --> G[TFT_eSPI] + F --> H[U8G2] + F --> I[SDL2] + end + + subgraph Display["Display"] + G --> J[LCD Panel] + H --> K[OLED] + I --> L[PC Monitor] + end +``` + +### Logical vs Physical Resolution + +```mermaid +flowchart LR + subgraph Logical["Logical (128x128)"] + L1["Pixel at (64, 64)"] + end + + subgraph Physical["Physical (240x240)"] + P1["Pixel at (120, 120)"] + end + + L1 -->|"Scale 1.875x"| P1 +``` + +### Indexed Color → RGB565 + +```mermaid +flowchart LR + A[Indexed Color] -->|"Background Palette"| B[RGB565] + C[Indexed Color] -->|"Sprite Palette"| B +``` + +### PC Keyboard/Mouse Mapping + +```mermaid +flowchart LR + subgraph Keyboard["Keyboard"] + A[Arrow Keys] + B[Z/X/C/V] + C[Enter/Space] + end + + subgraph Mapping["Engine Mapping"] + A -->|maps to| D[UP/DOWN/LEFT/RIGHT] + B -->|maps to| E[B/A/X/Y] + C -->|maps to| F[START] + end + + subgraph Mouse["Mouse"] + G[Left Click] -->|maps to| H[Touch PRESS/CLICK] + I[Movement] -->|maps to| J[Touch DRAG] + end +``` + +### UI Composition + +```mermaid +flowchart TB + subgraph Scene["Scene"] + E[Entity list addEntity] + U[UIManager touch registry] + end + + subgraph Layouts["Layout containers"] + V[UIVerticalLayout] + H[UIHorizontalLayout] + G[UIGridLayout] + A[UIAnchorLayout] + P[UIPanel] + end + + subgraph ClassicUI["Classic UI entities"] + L[UILabel] + B[UIButton] + C[UICheckBox] + end + + subgraph TouchUI["Touch widgets"] + TB[UITouchButton] + TC[UITouchCheckbox] + TS[UITouchSlider] + end + + E --> Layouts + Layouts --> ClassicUI + E --> TouchUI + U --> TouchUI +``` + +### System Architecture + +```mermaid +flowchart TB + subgraph SystemLayer["System Layer"] + R[Renderer] + I[Input Manager] + A[Audio Engine] + P["Physics
(Flat Solver)"] + Merge(( )) + R --> Merge + I --> Merge + A --> Merge + P --> Merge + Merge --> UIS[UI System] + Merge --> PS[Particle System] + Merge --> C2[Camera 2D] + Merge --> TA[Tile Animation] + end + Scene["Scene Layer
coordinates game objects"] + SystemLayer --> Scene +``` + +--- + +## Data Flow + +### Game Loop Flow + +```mermaid +flowchart TB + Init[Init] --> GameLoop[Game Loop] --> Exit[Exit] + GameLoop --> InputPoll["Input
Poll"] + GameLoop --> UpdateLogic["Update
Logic"] + GameLoop --> DrawRender["Draw
Render"] + UpdateLogic --> AudioGen["Audio
Generate"] + UpdateLogic --> PhysicsUpd["Physics
Update"] + UpdateLogic --> UIDraw["UI
Draw"] +``` + +### Audio Pipeline + +``` +Game Code + │ + ▼ (submitCommand) +AudioCommandQueue (Thread-Safe) + │ + ▼ (processCommands) +AudioScheduler + │ + ├──▶ Pulse Channel + ├──▶ Triangle Channel + ├──▶ Noise Channel + └──▶ Music Sequencer + │ + ▼ (generateSamples) +Mixer (with LUT) + │ + ▼ +AudioBackend + ├──▶ ESP32_I2S_AudioBackend + ├──▶ ESP32_DAC_AudioBackend + └──▶ SDL2_AudioBackend +``` + +--- + +## Modular Compilation Flags + +| Subsystem | Flag | Default | +|-----------|------|---------| +| Audio | `PIXELROOT32_ENABLE_AUDIO` | Enabled | +| Physics | `PIXELROOT32_ENABLE_PHYSICS` | Enabled | +| UI System | `PIXELROOT32_ENABLE_UI_SYSTEM` | Enabled | +| Particles | `PIXELROOT32_ENABLE_PARTICLES` | Enabled | +| Touch Input | `PIXELROOT32_ENABLE_TOUCH` | Disabled | +| Tile Animations | `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enabled | +| Static tilemap framebuffer cache (4bpp) | `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enabled | + +--- + +## Related Documentation + +| Subsystem | Document | +|-----------|----------| +| Audio | [Audio Subsystem](./audio-subsystem.md) | +| Physics | [Physics Subsystem](./physics-subsystem.md) | +| Touch Input | [Touch Input](./touch-input.md) | +| Tile Animation | [Tile Animation](./tile-animation.md) | +| Resolution Scaling | [Resolution Scaling](./resolution-scaling.md) | +| Memory | [Memory System](./memory-system.md) | +| API Reference | [API Index](../api/index.md) | \ No newline at end of file diff --git a/architecture/layers.md b/architecture/layers.md deleted file mode 100644 index 64f5921..0000000 --- a/architecture/layers.md +++ /dev/null @@ -1,1088 +0,0 @@ -# Layer hierarchy - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -## Responsibility - -Underlying physical hardware that the engine runs on. - ---- - -## Components - -### Microcontrollers - -- **ESP32/ESP32-S3**: Main target microcontrollers - - Dual-core Xtensa LX6 processors - - Wi-Fi and Bluetooth connectivity - - Various memory configurations (520KB SRAM on classic ESP32, 512KB on S3) - -- **ESP32-C3**: RISC-V variant - - Single-core RISC-V processor - - No FPU (uses Fixed16 math) - - 400KB SRAM - -### Displays - -| Display | Type | Resolution | Interface | Use Case | -|---------|------|------------|-----------|----------| -| ST7789 | TFT LCD | 240x240, 320x240 | SPI | Color games, high resolution | -| ST7735 | TFT LCD | 128x128, 160x128 | SPI | Smaller color displays | -| SSD1306 | OLED | 128x64, 128x32 | I2C/SPI | Monochrome, low power | -| SH1106 | OLED | 128x64 | I2C/SPI | Alternative monochrome | - -### Audio Hardware - -| Component | Type | Description | -|-----------|------|-------------| -| Internal DAC | 8-bit | ESP32 GPIO 25/26, PAM8302A amplifier | -| I2S + MAX98357A | Digital | High-quality audio, class D amp | -| I2S + PCM5102 | Digital | DAC for headphones/line out | - -### Input - -- **Physical Buttons**: Connected to GPIOs - - Typical configurations: 4-directional + 2 action buttons - - Direct GPIO polling with debouncing - -- **Touch Controllers**: - - XPT2046: Resistive touch (SPI) - - GT911: Capacitive touch (I2C) - -### PC/Native Platform - -- **Simulation**: SDL2 on Windows/Linux/macOS -- **Purpose**: Rapid development without hardware -- **Features**: Full API compatibility, faster iteration - ---- - -## Hardware Capabilities Detection - -The engine uses `PlatformCapabilities` structure to detect hardware at runtime: - -```cpp -struct PlatformCapabilities { - bool hasDualCore; // Multi-core support - int audioCoreId; // Recommended core for audio - int mainCoreId; // Recommended core for game loop - bool hasFPU; // Floating-point unit available - size_t totalSRAM; // Total SRAM available -}; -``` - ---- - -## Memory Layout (ESP32) - -``` -────────────────────────────────────── - DRAM (520KB) -├─ .dram0.bss (static data) -├─ Heap (dynamic allocations) -└─ Stack -────────────────────────────────────── - IRAM (128KB) -└─ Instruction RAM (cached code) -────────────────────────────────────── - Flash (4MB+) -└─ Program code and PROGMEM data -────────────────────────────────────── -``` - ---- - -## Related Documentation - -- [Driver Layer](/architecture/layers) - Hardware abstraction drivers -- [Abstraction Layer](/architecture/layers) - PlatformMemory and cross-platform abstractions -- [Memory System](/architecture/memory-system) - Memory management strategies -- [Platform Compatibility](/guide/platform-config) - Supported hardware matrix - - ---- - -## Responsibility - -Platform-specific hardware abstraction that bridges the gap between hardware and the engine's abstract interfaces. - -**Design Pattern**: Concrete implementation of abstractions defined in Layer 2. - ---- - -## ESP32 Drivers - -| Driver | File | Description | -|--------|------|-------------| -| `TFT_eSPI_Drawer` | `drivers/esp32/TFT_eSPI_Drawer.cpp` | TFT display driver (ST7789, ST7735, ILI9341) | -| `U8G2_Drawer` | `drivers/esp32/U8G2_Drawer.cpp` | Monochrome OLED driver (SSD1306, SH1106) | -| `ESP32_I2S_AudioBackend` | `drivers/esp32/ESP32_I2S_AudioBackend.cpp` | I2S audio backend for external DACs | -| `ESP32_DAC_AudioBackend` | `drivers/esp32/ESP32_DAC_AudioBackend.cpp` | Internal DAC audio backend | -| `ESP32AudioScheduler` | `src/audio/ESP32AudioScheduler.cpp` | ESP32 scheduler (delegates to **`ApuCore`**; FreeRTOS task lives in backend) | - -### TFT_eSPI Driver - -The primary color display driver using the popular TFT_eSPI library. - -**Features**: -- Hardware SPI communication -- DMA support for fast transfers -- Resolution scaling (nearest-neighbor) -- Double-buffering for smooth rendering - -**Supported Displays**: -- ST7789 (240x240, 320x240) -- ST7735 (128x128, 160x128) -- ILI9341 (320x240) - -### U8G2 Driver - -Driver for monochrome OLED displays using the U8G2 library. - -**Features**: -- I2C and SPI support -- 1MHz I2C bus overclocking for 60 FPS -- Page buffer mode for memory efficiency - -**Supported Displays**: -- SSD1306 (128x64, 128x32) -- SH1106 (128x64) - -### Audio Drivers - -#### ESP32_I2S_AudioBackend - -For high-quality audio with external DACs (MAX98357A, PCM5102). - -```cpp -ESP32_I2S_AudioBackend audioBackend(26, 25, 22, 22050); -// BCLK=26, LRCK=25, DOUT=22, 22050Hz sample rate -``` - -**Features**: -- I2S peripheral with DMA -- Standard sample rates (11025, 22050, 44100 Hz) -- Pinned to Core 0 (separate from game loop) - -#### ESP32_DAC_AudioBackend - -For retro-style audio using the internal 8-bit DAC. - -```cpp -ESP32_DAC_AudioBackend audioBackend(25, 11025); -// GPIO 25, 11025Hz for retro feel -``` - -**Features**: -- 8-bit effective resolution (internal DAC) -- **I2S `DAC_BUILT_IN`** + **`i2s_write`** (DMA), not per-sample **`dacWrite()`** -- ~0.7× headroom before DAC write (e.g. PAM8302A) - ---- - -## Native (PC) Drivers - -| Driver | File | Description | -|--------|------|-------------| -| `SDL2_Drawer` | `drivers/native/SDL2_Drawer.cpp` | SDL2 graphics simulation | -| `SDL2_AudioBackend` | `drivers/native/SDL2_AudioBackend.cpp` | SDL2 audio backend | -| `NativeAudioScheduler` | `audio/NativeAudioScheduler.cpp` | Native thread-based scheduler | -| `MockArduino` | `platforms/mock/MockArduino.cpp` | Arduino API emulation | - -### SDL2_Drawer - -Graphics driver for PC development using SDL2. - -**Features**: -- Windowed and fullscreen modes -- Hardware acceleration via SDL2 -- Mouse-to-touch event conversion (when touch enabled) -- Pixel-perfect scaling options - -### SDL2_AudioBackend - -Audio driver using SDL2's audio subsystem. - -**Features**: -- Standard audio device access -- Callback-based sample generation -- Thread-safe command queue - -### NativeAudioScheduler - -Thread-based audio scheduling for PC platforms. - -**Features**: -- Dedicated high-priority thread -- Sample-accurate timing -- Lock-free command queue - ---- - -## Driver Selection - -Drivers are selected at compile-time via build flags: - -```ini -# platformio.ini - -# Use TFT_eSPI (default) -build_flags = -D PIXELROOT32_USE_TFT_ESPI_DRIVER - -# Use U8G2 for OLED -build_flags = -D PIXELROOT32_USE_U8G2 - -# Custom display -custom_display = new MyCustomDriver() -``` - ---- - -## Creating Custom Drivers - -See [Extending PixelRoot32](/guide/extending) for detailed instructions on creating custom display and audio drivers. - -Quick overview: - -```cpp -#include - -class MyCustomDriver : public pixelroot32::graphics::BaseDrawSurface { -public: - void init() override; - void drawPixel(int x, int y, uint16_t color) override; - void clearBuffer() override; - void sendBuffer() override; -}; -``` - ---- - -## Related Documentation - -- [Abstraction Layer](/architecture/layers) - Interfaces these drivers implement -- [Hardware Layer](/architecture/layers) - Physical hardware details -- [System Layer](/architecture/layers) - High-level systems that use these drivers -- [Extending PixelRoot32](/guide/extending) - How to create custom drivers - - ---- - -## Responsibility - -Abstract interfaces that decouple subsystems from concrete implementations, enabling portability and testability. - -**Design Patterns**: -- **Bridge Pattern**: `DrawSurface` decouples Renderer from specific drivers -- **Strategy Pattern**: `AudioScheduler` allows different scheduling implementations - ---- - -## Main Components - -### PlatformMemory.h (Macro Abstraction) - -Provides a unified API for memory operations that differ between ESP32 (Flash/PROGMEM) and Native (RAM) platforms. - -| Macro | Description | ESP32 Mapping | Native Mapping | -|-------|-------------|---------------|----------------| -| `PIXELROOT32_FLASH_ATTR` | Store data in Flash | `PROGMEM` | (empty) | -| `PIXELROOT32_STRCMP_P` | Compare with Flash string | `strcmp_P` | `strcmp` | -| `PIXELROOT32_MEMCPY_P` | Copy from Flash | `memcpy_P` | `memcpy` | -| `PIXELROOT32_READ_BYTE_P` | Read 8-bit from Flash | `pgm_read_byte` | direct access | -| `PIXELROOT32_READ_WORD_P` | Read 16-bit from Flash | `pgm_read_word` | direct access | -| `PIXELROOT32_READ_DWORD_P` | Read 32-bit from Flash | `pgm_read_dword` | direct access | -| `PIXELROOT32_READ_FLOAT_P` | Read float from Flash | `pgm_read_float` | direct access | - -**Usage Example**: - -```cpp -#include "platforms/PlatformMemory.h" - -// Store data in Flash -const char message[] PIXELROOT32_FLASH_ATTR = "Hello"; - -// Compare with Flash string -if (PIXELROOT32_STRCMP_P("lava", type) == 0) { - // Handle lava tile -} -``` - ---- - -### DrawSurface (Bridge Pattern) - -Abstract base class that decouples the Renderer from specific display drivers. - -```cpp -class DrawSurface { -public: - virtual void init() = 0; - virtual void drawPixel(int x, int y, uint16_t color) = 0; - virtual void drawLine(int x1, int y1, int x2, int y2, uint16_t color) = 0; - virtual void drawFilledRectangle(int x, int y, int w, int h, uint16_t color) = 0; - virtual void clearBuffer() = 0; - virtual void sendBuffer() = 0; - virtual void setOffset(int x, int y) {} - virtual void setRotation(uint8_t rotation) {} -}; -``` - -**Implementations**: -- `TFT_eSPI_Drawer` - ESP32 color displays -- `U8G2_Drawer` - ESP32 monochrome displays -- `SDL2_Drawer` - PC simulation - ---- - -### AudioScheduler (Strategy Pattern) - -Abstract scheduler for platform-specific audio timing. - -```cpp -class AudioScheduler { -public: - virtual void init() = 0; - virtual void submitCommand(const AudioCommand& cmd) = 0; - virtual void generateSamples(int16_t* stream, int length) = 0; - virtual void start() = 0; - virtual void stop() = 0; -}; -``` - -**Implementations**: -- `ESP32AudioScheduler` - delegates to **`ApuCore`** (backend owns FreeRTOS task) -- `NativeAudioScheduler` - **`std::thread`** + ring buffer → SDL2 (delegates to **`ApuCore`**) -- `DefaultAudioScheduler` - tests / same-thread **`ApuCore`** - ---- - -### PlatformCapabilities - -Structure that detects and exposes hardware capabilities at runtime. - -```cpp -namespace pixelroot32::platforms { - struct PlatformCapabilities { - bool hasDualCore; // Multi-core support (ESP32, ESP32-S3) - int audioCoreId; // Recommended core for audio (0 on ESP32) - int mainCoreId; // Recommended core for game loop (1 on ESP32) - bool hasFPU; // Floating-point unit available - size_t totalSRAM; // Total SRAM available - bool hasPSRAM; // External SPI RAM available (S3) - }; -} -``` - -**Usage**: - -```cpp -auto caps = pixelroot32::platforms::detectCapabilities(); -if (caps.hasDualCore) { - // Use dual-core audio scheduling -} -``` - ---- - -### Math System (Scalar Abstraction) - -**Files**: `include/math/Scalar.h`, `include/math/Fixed16.h`, `include/math/MathUtil.h` - -Provides deterministic, platform-optimized numerical operations. - -**Features**: - -- **Hardware Adaptation**: Automatically switches between `float` and `Fixed16` based on FPU presence (ESP32-S3 vs ESP32-C3) -- **16.16 Fixed Point**: Optimized `Fixed16` implementation for RISC-V targets (C3/C6) -- **Generic Math API**: Single API for `sin`, `cos`, `sqrt`, `atan2` that resolves to most efficient implementation per platform -- **Stable Rounding**: Explicit `roundToInt`, `floorToInt`, `ceilToInt` to avoid floating-point truncation artifacts - -```cpp -using Scalar = pixelroot32::math::Scalar; // float or Fixed16 - -Scalar angle = MathUtil::atan2(dy, dx); -Scalar distance = MathUtil::sqrt(dx*dx + dy*dy); -int pixelX = MathUtil::roundToInt(cameraX + offset); -``` - ---- - -### Unified Logging System - -**Files**: `include/core/Log.h`, `src/platforms/PlatformLog.cpp` - -Cross-platform logging abstraction that eliminates `#ifdef` blocks in user code. - -**Features**: -- Unified API for ESP32 (Serial) and Native (stdout) -- Log levels: Info, Profiling, Warning, Error -- printf-style formatting -- Automatic platform routing -- **Zero overhead when disabled**: Double-layer conditional compilation - -**Architecture - Double-Layer Conditional Compilation**: - -``` -PIXELROOT32_DEBUG_MODE defined: - log() → format with va_list → platformPrint() → Serial/stdout - -PIXELROOT32_DEBUG_MODE not defined: - log() → (void)level; (void)fmt; → no-op -``` - -**Main API**: - -```cpp -namespace pixelroot32::core::logging { - enum class LogLevel { Info, Profiling, Warning, Error }; - void log(LogLevel level, const char* format, ...); - void log(const char* format, ...); // Info level shorthand -} - -// Enable in platformio.ini: -// build_flags = -DPIXELROOT32_DEBUG_MODE -``` - -**Platform Output**: -- ESP32: Routes to `Serial.print()` -- Native: Routes to `printf()` with `fflush(stdout)` - ---- - -## Abstraction Benefits - -1. **Portability**: Same game code runs on ESP32 and PC -2. **Testability**: Mock implementations for unit testing -3. **Flexibility**: Swap implementations without changing game code -4. **Maintainability**: Changes isolated to specific layers - ---- - -## Related Documentation - -- [API Reference - Math Module](/api/math/scalar) - Scalar and math utilities -- [API Reference - Platform](/api/platform/platform-capabilities) - Platform abstractions -- [Memory System](/architecture/memory-system) - Memory management details -- [Hardware Layer](/architecture/layers) - Physical hardware -- [Driver Layer](/architecture/layers) - Concrete implementations - - ---- - -## Responsibility - -Game engine subsystems that implement high-level functionality. These systems provide the core capabilities that game code builds upon. - ---- - -## Subsystem Overview - -The System Layer contains the following major subsystems: - -| Subsystem | Responsibility | Detailed Document | -|-----------|--------------|-------------------| -| **Renderer** | Graphics rendering, sprites, tilemaps | See API Reference | -| **InputManager** | Button and touch input handling | [Touch Input](/architecture/ARCH_TOUCH_INPUT) | -| **AudioEngine** | NES-style 4-channel audio | [Audio Subsystem](/architecture/audio-architecture) | -| **CollisionSystem** | Physics simulation, collisions | [Physics Subsystem](/architecture/physics-system) | -| **UI System** | User interface and layouts | See API Reference | -| **Particle System** | Visual effects and particles | See API Reference | -| **Camera2D** | Viewport transformations | See API Reference | -| **Tile Animation** | Animated tilemaps | [Tile Animation](/architecture/ARCH_TILE_ANIMATION) | -| **Resolution Scaling** | Logical vs physical resolution | [Resolution Scaling](/architecture/ARCH_RESOLUTION_SCALING) | - ---- - -## System Architecture Diagram - -```mermaid -flowchart TB - subgraph systemLayer ["System Layer"] - direction TB - subgraph primary [Primary systems] - direction LR - Ren[Renderer] - InMgr[Input Manager] - Aud[Audio Engine] - Phy["Physics (Flat Solver)"] - end - Ren --> Hub(( )) - InMgr --> Hub - Aud --> Hub - Phy --> Hub - Hub --> UIS[UI System] - Hub --> Part[Particle System] - Hub --> Cam[Camera 2D] - Hub --> TileAnim[Tile Animation] - end - Hub --> SceneLay["Scene Layer (coordinates game objects)"] -``` - ---- - -## Renderer - -**Files**: `include/graphics/Renderer.h`, `src/graphics/Renderer.cpp` - -High-level rendering system that abstracts graphics operations. - -### Features - -- Logical resolution independent of physical resolution -- Support for 1bpp, 2bpp, 4bpp sprites -- Sprite animation system -- Tilemaps with viewport culling -- Multi-palette tilemaps (2bpp/4bpp) -- Multi-palette sprites (2bpp/4bpp) -- Native bitmap font system -- Render contexts for dual palettes - -### Multi-Palette Sprites Architecture - -The engine supports multiple palettes for 2bpp/4bpp sprites through a sprite palette slot bank. - -**Data Flow**: -``` -sprite.paletteSlot → getSpritePaletteSlot() → resolveColorWithPalette() → drawSpriteInternal -``` - -**API Example**: -```cpp -class Renderer { - void beginFrame(); - void endFrame(); - void drawSprite(const Sprite& sprite, int x, int y, Color color); - void drawText(std::string_view text, int x, int y, Color color, uint8_t size); - void drawTileMap(const TileMap& map, int originX, int originY); -}; -``` - ---- - -## InputManager - -**Files**: `include/input/InputManager.h`, `src/input/InputManager.cpp` - -Input management from physical buttons or keyboard (PC), plus optional touch event routing. - -### Features - -- Debouncing support -- States: Pressed, Released, Down, Clicked -- Configurable via `InputConfig` -- Hardware abstraction through polling -- **Touch event dispatcher** (when `PIXELROOT32_ENABLE_TOUCH=1`) - -### Button States - -| Method | Description | -|--------|-------------| -| `isButtonPressed()` | UP → DOWN transition | -| `isButtonReleased()` | DOWN → UP transition | -| `isButtonDown()` | Current DOWN state | -| `isButtonClicked()` | Complete click detected | - -**Touch input** is covered in detail in [Touch Input Architecture](/architecture/ARCH_TOUCH_INPUT). - ---- - -## AudioEngine - -**Files**: `include/audio/AudioEngine.h`, `src/audio/AudioEngine.cpp` - -NES-style 4-channel audio system; synthesis and mixing live in **`ApuCore`**. See [Audio Subsystem](/architecture/audio-architecture). - -**Quick Overview**: -- 2 PULSE channels (square wave) -- 1 TRIANGLE channel -- 1 NOISE channel -- Sample-accurate timing via **`ApuCore`** (invoked from the active **`AudioScheduler`**) -- Modular compilation: `PIXELROOT32_ENABLE_AUDIO` - ---- - -## CollisionSystem (Flat Solver) - -**Files**: `include/physics/CollisionSystem.h`, `src/physics/CollisionSystem.cpp` - -High-performance physics solver optimized for ESP32 microcontrollers. - -**Simulation Pipeline**: -``` -1. Detect Collisions → Dual-layer spatial grid -2. Solve Velocity → Impulse-based response -3. Integrate Positions → p = p + v * dt -4. Solve Penetration → Baumgarte stabilization -5. Trigger Callbacks → onCollision notifications -``` - -See [Physics System Reference](/architecture/physics-system) for complete details. - ---- - -## UI System - -**Files**: `include/graphics/ui/*.h`, `src/graphics/ui/*.cpp` - -User interface system with automatic layouts. - -### Class Hierarchy - -``` -Entity -├── UIElement -│ ├── UILabel -│ ├── UIButton -│ ├── UICheckbox -│ └── UIPanel -│ └── UILayout -│ ├── UIHorizontalLayout -│ ├── UIVerticalLayout -│ ├── UIGridLayout -│ ├── UIAnchorLayout -│ └── UIPaddingContainer -└── UITouchElement - ├── UITouchButton - ├── UITouchSlider - └── UITouchCheckbox -``` - -### Touch Widget Architecture - -- **UITouchWidget**: Lightweight widget data struct -- **UITouchElement**: Abstract base with widget data -- **UIManager**: Non-owning registry (max 16 elements) - -Scene owns widgets; UIManager only routes events. - ---- - -## Particle System - -**Files**: `include/graphics/particles/*.h`, `src/graphics/particles/*.cpp` - -Visual effects system with configurable emitters. - -**Components**: -- `Particle`: Individual particle with position, velocity, life -- `ParticleEmitter`: Configurable emitter with presets -- `ParticleConfig`: Emission configuration - -Modular compilation: `PIXELROOT32_ENABLE_PARTICLES` - ---- - -## Camera2D - -**Files**: `include/graphics/Camera2D.h`, `src/graphics/Camera2D.cpp` - -2D camera with viewport transformations. - -**Features**: -- Position and zoom control -- Automatic offset for Renderer -- Support for fixed-position UI elements -- Stable rounding to prevent jitter - ---- - -## Tilemap rendering - -**Files**: `include/graphics/Renderer.h`, `src/graphics/Renderer.cpp`, `include/graphics/TileAnimation.h` - -`Renderer::drawTileMap` performs **viewport culling** (only tiles that can intersect the logical framebuffer), optional **`TileAnimationManager::resolveFrame`**, optional **runtime tile masks** and **per-cell background palettes** on 2bpp/4bpp maps, then rasterizes each visible tile (ESP32: hot paths use `IRAM_ATTR` where applicable). - -For largely static **4bpp** layers when **`DrawSurface::getSpriteBuffer()`** is available, use **`StaticTilemapLayerCache`** and **`PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE`** (see Graphics API and architecture docs). - -See [Tile Animation](/architecture/ARCH_TILE_ANIMATION) for the animation system. - ---- - -## Subsystem Modular Compilation - -| Subsystem | Flag | Default | -|-----------|------|---------| -| Audio | `PIXELROOT32_ENABLE_AUDIO` | Enabled | -| Physics | `PIXELROOT32_ENABLE_PHYSICS` | Enabled | -| UI System | `PIXELROOT32_ENABLE_UI_SYSTEM` | Enabled | -| Particles | `PIXELROOT32_ENABLE_PARTICLES` | Enabled | -| Touch Input | `PIXELROOT32_ENABLE_TOUCH` | Disabled | -| Tile Animations | `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enabled | -| Static tilemap framebuffer cache (4bpp) | `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enabled (`PlatformDefaults.h`) | - ---- - -## Data Flow - -### Game Loop Flow - -```mermaid -flowchart TB - Init[Init] --> GL[Game Loop] --> Exit[Exit] - GL --> InPoll["Input Poll"] - GL --> Upd["Update Logic"] - GL --> Drw["Draw Render"] - Upd --> Aud["Audio Generate"] - Upd --> Phy["Physics Update"] - Upd --> Ui["UI Draw"] -``` - -### Audio Flow - -``` -Game Code - │ - ▼ (submitCommand) -AudioCommandQueue (SPSC, in ApuCore) - │ - ▼ (processCommands) -AudioScheduler → ApuCore - │ - ├──▶ Pulse Channel - ├──▶ Triangle Channel - ├──▶ Noise Channel - └──▶ Music Sequencer - │ - ▼ (generateSamples) -Mixer (with LUT) - │ - ▼ -AudioBackend - ├──▶ ESP32_I2S_AudioBackend - ├──▶ ESP32_DAC_AudioBackend - └──▶ SDL2_AudioBackend -``` - ---- - -## Related Documentation - -| Subsystem | Document | -|-----------|----------| -| Audio | [Audio Subsystem](/architecture/audio-architecture) | -| Physics | [Physics Subsystem](/architecture/physics-system) | -| Touch Input | [Touch Input](/architecture/ARCH_TOUCH_INPUT) | -| Tile Animation | [Tile Animation](/architecture/ARCH_TILE_ANIMATION) | -| Resolution Scaling | [Resolution Scaling](/architecture/ARCH_RESOLUTION_SCALING) | -| Memory | [Memory System](/architecture/memory-system) | - -**API Reference**: See `docs/api/API_*.md` for class-level documentation. - - ---- - -## Responsibility - -Game scene and entity management. This layer provides the organizational structure for game objects and their lifecycle. - ---- - -## Components - -### Engine - -**Files**: `include/core/Engine.h`, `src/core/Engine.cpp` - -Central class that orchestrates all subsystems. - -**Responsibilities**: -- Manages Renderer, SceneManager, InputManager, AudioEngine, MusicPlayer -- Runs the main game loop -- Provides automatic touch processing (when enabled) - -**Game Loop**: - -```cpp -void Engine::run() { - while (true) { - // 1. Calculate delta time - deltaTime = currentMillis - previousMillis; - - // 2. Update - update(); - - // 3. Draw - draw(); - } -} - -void Engine::update() { - inputManager.update(deltaTime); - sceneManager.update(deltaTime); - // Note: AudioEngine runs on separate thread/core -} - -void Engine::draw() { - renderer.beginFrame(); - sceneManager.draw(renderer); - renderer.endFrame(); -} -``` - -**Touch Integration** (`PIXELROOT32_ENABLE_TOUCH=1`): -- `getTouchDispatcher()`: Access touch event dispatcher -- `hasTouchEvents()`: Check for pending events -- `setTouchManager()`: Register external TouchManager for auto-processing - -When `setTouchManager()` is called, Engine automatically: -1. Polls `touchManager->getTouchPoints()` each frame -2. Detects touch releases (count >0 → 0) -3. Processes through `TouchEventDispatcher` -4. Dispatches to `Scene::processTouchEvents()` - -See [Touch Input Architecture](/architecture/ARCH_TOUCH_INPUT) for details. - ---- - -### SceneManager - -**Files**: `include/core/SceneManager.h`, `src/core/SceneManager.cpp` - -Scene stack management (push/pop operations). - -**Operations**: - -| Method | Description | -|--------|-------------| -| `setCurrentScene()` | Replace current scene | -| `pushScene()` | Push new scene (pauses previous) | -| `popScene()` | Pop scene (resumes previous) | - -**Scene Stack**: - -```cpp -Scene* sceneStack[MaxScenes]; // Default: 8 scenes -int sceneCount; -``` - -Useful for: -- Pause menus (push pause scene over game scene) -- Settings screens -- Dialog overlays - ---- - -### Scene - -**Files**: `include/core/Scene.h`, `src/core/Scene.cpp` - -Entity container representing a level or screen. - -**Memory Model**: Non-owning - -The Scene follows a **non-owning** model for entities. When you call `addEntity(Entity*)`, the scene stores a reference but **does not take ownership**. - -```cpp -// Scene does NOT delete entities -// You are responsible for lifetime (typically std::unique_ptr) - -class GameScene : public Scene { - std::unique_ptr player; // You own it - -public: - void init() override { - player = std::make_unique(100, 100, 16, 16); - addEntity(player.get()); // Scene gets non-owning pointer - } -}; -``` - -**Features**: -- Entity array (`MAX_ENTITIES = 32` default) -- Render layer system (`MAX_LAYERS = 3` default) -- Integrated `CollisionSystem` -- Viewport culling -- Optional `SceneArena` for custom allocators - -**Lifecycle**: - -```cpp -virtual void init(); // Called when entering scene -virtual void update(unsigned long dt); // Every frame -virtual void draw(Renderer& r); // Every frame -virtual void processTouchEvents(const TouchEvent* events, uint8_t count); -virtual void onUnconsumedTouchEvent(const TouchEvent& event); -``` - ---- - -### Entity - -**Files**: `include/core/Entity.h` - -Abstract base class for all game objects. - -**Properties**: - -| Property | Type | Description | -|----------|------|-------------| -| `x`, `y` | `float` | Position | -| `width`, `height` | `int` | Dimensions | -| `type` | `EntityType` | GENERIC, ACTOR, UI_ELEMENT | -| `renderLayer` | `uint8_t` | Render layer (0-255) | -| `isVisible` | `bool` | Visibility control | -| `isEnabled` | `bool` | Update control | - -**Virtual Methods**: - -```cpp -virtual void update(unsigned long deltaTime) = 0; -virtual void draw(Renderer& renderer) = 0; -``` - ---- - -## Actor / PhysicsActor Hierarchy - -Following the Godot Engine philosophy, physical actors are specialized into distinct types based on their movement requirements. - -### Hierarchy Diagram - -``` -Entity -└── Actor - └── PhysicsActor (Base) - ├── StaticActor (Immovable walls/floors) - │ └── SensorActor (Trigger zones) - ├── KinematicActor (Player-controlled) - └── RigidActor (Physics-simulated props) -``` - -### Actor Types - -| Type | Movement | Collision Layer | Use Case | -|------|----------|-----------------|----------| -| **StaticActor** | None | Static grid | Walls, floors, platforms | -| **SensorActor** | None | Static grid | Collectibles, triggers | -| **KinematicActor** | Code-driven | Dynamic grid | Player, enemies, moving platforms | -| **RigidActor** | Physics-simulated | Dynamic grid | Props, debris, projectiles | - -### Common Features - -All PhysicsActor types support: - -- `setShape(CollisionShape::AABB/CIRCLE)` - Hitbox shape -- `setCollisionLayer(mask)` - Layer membership -- `setCollisionMask(mask)` - Layers to collide with -- `setSensor(true/false)` - Trigger mode (no collision response) -- `setOneWay(true/false)` - One-way platform mode -- `onCollision(Actor* other)` - Notification callback - -### Actor Example - -```cpp -class Player : public KinematicActor { -public: - void update(unsigned long dt) override { - // Movement logic - if (engine.getInputManager().isButtonPressed(BTN_A)) { - velocity.y = -jumpForce; - } - - // Move with collision - moveAndSlide(); - } - - void draw(Renderer& r) override { - r.drawSprite(playerSprite, x, y, Color::White); - } - - void onCollision(Actor* other) override { - if (other->isInLayer(Layers::kEnemy)) { - takeDamage(); - } - } -}; -``` - ---- - -## Game Layer - -![Architecture Layers](../public/architecture.png) - -**Responsibility**: Game-specific code implemented by the user. - -This is where you implement your game logic using the engine's architecture. - -### Typical Implementation - -```cpp -class GameScene : public Scene { - std::unique_ptr player; - std::vector> enemies; - std::unique_ptr background; - -public: - void init() override { - // Create player - player = std::make_unique(100, 100, 16, 16); - addEntity(player.get()); - - // Create enemies - for (int i = 0; i < 5; i++) { - auto enemy = std::make_unique(...); - enemies.push_back(std::move(enemy)); - addEntity(enemies.back().get()); - } - - // Start music - engine.getMusicPlayer().play(backgroundMusic); - } - - void update(unsigned long dt) override { - // Scene-level update logic - Scene::update(dt); // Updates all entities - } - - void draw(Renderer& r) override { - // Draw background - r.drawTileMap(*background, 0, 0); - - // Draw entities - Scene::draw(r); - } -}; -``` - ---- - -## Entity Lifecycle - -```mermaid -stateDiagram-v2 - [*] --> Created - Created --> init - state active { - [*] --> update - update --> draw - draw --> update - } - init --> active: Scene init - active --> sceneEnd: scene ends - sceneEnd --> Destroyed - Destroyed --> [*] -``` - ---- - -## Memory Considerations - -| Component | Default Size | Configurable | -|-----------|--------------|--------------| -| Max Entities per Scene | 32 | `MAX_ENTITIES` | -| Max Render Layers | 3 | `MAX_LAYERS` | -| Max Scene Stack | 8 | `MaxScenes` | -| Physics Contacts | 128 | `PHYSICS_MAX_CONTACTS` | - -See [Memory System](/architecture/memory-system) for optimization strategies. - ---- - -## Related Documentation - -- [Physics Subsystem](/architecture/physics-system) - Actor physics details -- [Touch Input](/architecture/ARCH_TOUCH_INPUT) - Scene touch handling -- [Memory System](/architecture/memory-system) - Entity memory management -- [API Reference - Core](/api/core/engine) - Class-level API diff --git a/architecture/memory-system.md b/architecture/memory-system.md index c31cb3d..452288d 100644 --- a/architecture/memory-system.md +++ b/architecture/memory-system.md @@ -1,17 +1,37 @@ -# Memory system - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - # Memory Management Guide - PixelRoot32 C++17 -**Document Version:** 1.1 -**Last Updated:** March 2026 -**Engine Version:** v1.1.0 - ## Overview This guide covers modern memory management practices in PixelRoot32 using C++17 features. The engine has transitioned from manual memory management to smart pointers and RAII (Resource Acquisition Is Initialization) patterns for improved safety and maintainability. +### Memory regions (ESP32-oriented overview) + +```mermaid +flowchart TB + subgraph Flash["Flash / PROGMEM (4MB+)"] + F1[Program Code] + F2[Sprite Data] + F3[Tilemaps] + F4[Constant Data] + end + + subgraph DRAM["DRAM (520KB total)"] + D1[Heap ~300KB] + D2[Stack] + D3[Static Variables] + end + + subgraph IRAM["IRAM (128KB)"] + I1[Critical Functions] + I2[ISR Handlers] + end + + subgraph DMA["DMA-Capable (160KB)"] + M1[Frame Buffers] + M2[Audio Buffers] + end +``` + --- ## Engine Memory Limits diff --git a/architecture/modules.md b/architecture/modules.md deleted file mode 100644 index 353361a..0000000 --- a/architecture/modules.md +++ /dev/null @@ -1,384 +0,0 @@ -# Modules & compilation - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -# API Reference: Configuration - -This document covers global configuration options, build flags, and compile-time constants for the PixelRoot32 Game Engine. - -> **Note:** This is part of the [API Reference](/api/). See the main index for complete documentation. - ---- - -## Platform Macros (Build Flags) - -| Macro | Description | Default (ESP32) | -|-------|-------------|-----------------| -| `PR32_DEFAULT_AUDIO_CORE` | CPU core assigned to audio tasks. | `0` | -| `PR32_DEFAULT_MAIN_CORE` | CPU core assigned to the main game loop. | `1` | -| `PIXELROOT32_NO_DAC_AUDIO` | Disable Internal DAC support on classic ESP32. | Enabled | -| `PIXELROOT32_NO_I2S_AUDIO` | Disable I2S audio support. | Enabled | -| `PIXELROOT32_USE_U8G2_DRIVER` | Enable U8G2 display driver support for monochromatic OLEDs. | Disabled | -| `PIXELROOT32_NO_TFT_ESPI` | Disable default TFT_eSPI driver support. | Enabled | - ---- - -## Modular Compilation Flags - -| Macro | Description | Default | -|-------|-------------|---------| -| `PIXELROOT32_ENABLE_AUDIO` | Enable audio subsystem (AudioEngine + MusicPlayer). | `1` | -| `PIXELROOT32_ENABLE_PHYSICS` | Enable physics system (CollisionSystem). | `1` | -| `PIXELROOT32_ENABLE_UI_SYSTEM` | Enable UI system (UIButton, UILabel, etc.). | `1` | -| `PIXELROOT32_ENABLE_PARTICLES` | Enable particle system. | `1` | -| `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Enable FPS/RAM/CPU debug overlay. | Disabled | -| `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enable tile animation system. | `1` | -| `PIXELROOT32_ENABLE_2BPP_SPRITES` | Enable 2bpp sprite support. | Disabled | -| `PIXELROOT32_ENABLE_4BPP_SPRITES` | Enable 4bpp sprite support. | Disabled | -| `PIXELROOT32_ENABLE_SCENE_ARENA` | Enable scene memory arena. | Disabled | -| `PIXELROOT32_ENABLE_PROFILING` | Enable profiling hooks in physics pipeline. | Disabled | -| `PIXELROOT32_ENABLE_TOUCH` | Enable automatic touch processing in Engine (mouse-to-touch on Native, touch point injection on ESP32). | `0` (disabled) | -| `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enable **`StaticTilemapLayerCache`** (4bpp direct logical framebuffer snapshot). Set `0` to save ~W×H RAM or force full redraw. | `1` (`PlatformDefaults.h`) | -| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK` | TFT_eSPI `sendBufferScaled`: RGB565 lines per DMA batch (when `PIXELROOT32_USE_TFT_ESPI_DRIVER`). Higher = fewer DMA rounds, more RAM for line buffers. | `60` (`PlatformDefaults.h`) | -| `PIXELROOT32_TFT_ESPI_LINES_PER_BLOCK_FALLBACK` | Line batch size retried if primary DMA line buffers cannot be allocated. | `30` (`PlatformDefaults.h`) | -| `PIXELROOT32_DEBUG_MODE` | Enable unified logging system. | Disabled | - ---- - -## Memory Savings by Subsystem - -| Subsystem Disabled | RAM Savings | Flash Savings | -|-------------------|-------------|--------------| -| `PIXELROOT32_ENABLE_AUDIO=0` | ~8 KB | ~15 KB | -| `PIXELROOT32_ENABLE_PHYSICS=0` | ~12 KB | ~25 KB | -| `PIXELROOT32_ENABLE_UI_SYSTEM=0` | ~4 KB | ~20 KB | -| `PIXELROOT32_ENABLE_PARTICLES=0` | ~6 KB | ~10 KB | -| `PIXELROOT32_ENABLE_TOUCH=0` | ~200 bytes | ~2 KB | - ---- - -## Build Profiles (platformio.ini) - -```ini -[profile_full] ; All features enabled -build_flags = - -D PIXELROOT32_ENABLE_AUDIO=1 - -D PIXELROOT32_ENABLE_PHYSICS=1 - -D PIXELROOT32_ENABLE_PARTICLES=1 - -D PIXELROOT32_ENABLE_UI_SYSTEM=1 - -[profile_arcade] ; Audio + Physics + Particles, no UI -build_flags = - -D PIXELROOT32_ENABLE_AUDIO=1 - -D PIXELROOT32_ENABLE_PHYSICS=1 - -D PIXELROOT32_ENABLE_PARTICLES=1 - -D PIXELROOT32_ENABLE_UI_SYSTEM=0 - -[profile_puzzle] ; Audio + UI only, no physics/particles -build_flags = - -D PIXELROOT32_ENABLE_AUDIO=1 - -D PIXELROOT32_ENABLE_PHYSICS=0 - -D PIXELROOT32_ENABLE_PARTICLES=0 - -D PIXELROOT32_ENABLE_UI_SYSTEM=1 - -[profile_retro] ; Minimal: no subsystems -build_flags = - -D PIXELROOT32_ENABLE_AUDIO=0 - -D PIXELROOT32_ENABLE_PHYSICS=0 - -D PIXELROOT32_ENABLE_PARTICLES=0 - -D PIXELROOT32_ENABLE_UI_SYSTEM=0 -``` - ---- - -## Recommended Profiles by Game Type - -| Game Type | Recommended Profile | Rationale | -|-----------|-------------------|-----------| -| Arcade (shooters, platformers) | `arcade` or `full` | Physics + particles + optional UI | -| Puzzle / Casual | `puzzle` | UI for menus, simple collision logic | -| Retro / Minimal | `retro` | Minimal footprint, custom collision | -| Educational / Tool | `puzzle` or custom | UI for menus | - ---- - -## Constants - -- **`DISPLAY_WIDTH`** - The width of the display in pixels. Default is `240`. - -- **`DISPLAY_HEIGHT`** - The height of the display in pixels. Default is `240`. - -- **`int xOffset`** - The horizontal offset for the display alignment. Default is `0`. - -- **`int yOffset`** - The vertical offset for the display alignment. Default is `0`. - -- **`PHYSICS_MAX_PAIRS`** - Maximum number of collision pairs considered in broadphase. Default is `128`. - -- **`PHYSICS_MAX_CONTACTS`** - Maximum number of simultaneous contacts in the solver (fixed pool, no heap per frame). Default is `128`. When exceeded, additional contacts are dropped. - -- **`VELOCITY_ITERATIONS`** - Number of impulse solver passes per frame. Higher values improve stacking stability but increase CPU load. Default is `2`. - -- **`SPATIAL_GRID_CELL_SIZE`** - Size of each cell in the broadphase grid (in pixels). Default is `32`. - -- **`SPATIAL_GRID_MAX_ENTITIES_PER_CELL`** - Legacy: maximum entities per cell when using a single grid. Default is `24`. - -- **`SPATIAL_GRID_MAX_STATIC_PER_CELL`** - Maximum static (immovable) actors per grid cell. Default is `12`. Used by the static layer of the spatial grid. - -- **`SPATIAL_GRID_MAX_DYNAMIC_PER_CELL`** - Maximum dynamic (RIGID/KINEMATIC) actors per grid cell. Default is `12`. Used by the dynamic layer of the spatial grid. - ---- - -## Custom Scene Limits - -The engine defines default limits in `platforms/EngineConfig.h`: `MAX_LAYERS` (default 3) and `MAX_ENTITIES` (default 32). These are guarded with `#ifndef`, so you can override them from your project without modifying the engine. - -**Compiler flags (recommended)** - -In your project (e.g. in `platformio.ini`), add the defines to `build_flags`: - -```ini -build_flags = - -DMAX_LAYERS=5 - -DMAX_ENTITIES=64 -``` - ---- - -## Related Documentation - -- [API Reference](/api/) - Main index -- [Platform Compatibility Guide](/guide/platform-config) -- [Extending PixelRoot32](/guide/extending) - ---- - -# Architecture Overview - PixelRoot32 Game Engine - -## Executive Summary - -PixelRoot32 is a lightweight, modular 2D game engine written in C++17, designed primarily for ESP32 microcontrollers, with a native simulation layer for PC (SDL2) that enables rapid development without hardware. - -The engine follows a scene-based architecture inspired by Godot Engine, making it intuitive for developers familiar with modern game development workflows. - ---- - -## Design Philosophy - -- **Modularity**: Each subsystem can be used independently and compiled conditionally -- **Selective Compilation**: Subsystems can be excluded at compile time to reduce firmware size and RAM usage -- **Portability**: Same code for ESP32 and PC (SDL2) -- **Performance**: Optimized for resource-constrained hardware with aggressive dead code elimination -- **Extensibility**: Plugin architecture for drivers and backends -- **Modern C++**: Leverages C++17 features (smart pointers, string_view) for safety and efficiency - -### What Does "Modularity" Mean in PixelRoot32? - -**Modularity** means that each main subsystem has **low coupling** and can be instantiated, tested, and used in isolation, without depending on other subsystems. This allows: - -- **Independent testing**: Each module can be unit tested -- **Selective usage**: Use only the modules you need -- **Easy replacement**: Change implementations without affecting the rest of the code -- **Conditional compilation**: Exclude entire subsystems at compile time to save firmware size and RAM - -**Concrete examples of independence:** - -```cpp -// 1. AudioEngine works without Renderer or SceneManager (if enabled) -#if PIXELROOT32_ENABLE_AUDIO -AudioConfig audioConfig; -AudioEngine audio(audioConfig); -audio.init(); -audio.playEvent({WaveType::PULSE, 440.0f, 0.5f, 0.8f}); -#endif - -// 2. Renderer can be used without Audio or Input -DisplayConfig displayConfig; -Renderer renderer(displayConfig); -renderer.init(); -renderer.beginFrame(); -renderer.drawSprite(sprite, 10, 10, Color::White); -renderer.endFrame(); - -// 3. InputManager is autonomous -InputConfig inputConfig; -InputManager input(inputConfig); -input.init(); -input.update(deltaTime); -if (input.isButtonPressed(0)) { /* ... */ } - -// 4. CollisionSystem is optional per scene (if enabled) -#if PIXELROOT32_ENABLE_PHYSICS -Scene scene; -// You can update physics only if you need it -scene.collisionSystem.update(); -#endif - -// 5. Interchangeable drivers without changing game code -// Same code works with TFT_eSPI_Drawer, U8G2_Drawer, or SDL2_Drawer -``` - -**Note**: `Engine` is the only component with tight coupling (orchestrates everything), but each subsystem can exist and function independently. The modular compilation system uses `PIXELROOT32_ENABLE_*` flags to conditionally compile subsystems, dramatically reducing firmware size and RAM usage on embedded targets. - ---- - -## Main Architectural Features - -- Stack-based Scene-Entity system -- Rendering with logical resolution independent of physical resolution -- NES-style 4-channel audio subsystem (conditionally compiled) -- UI system with automatic layouts (conditionally compiled) -- "Flat Solver" physics with specialized Actor types (conditionally compiled) -- Circular and AABB collision support -- Multi-platform support through driver abstraction -- **Modular compilation** for selective subsystem inclusion - ---- - -## Layer Hierarchy - -The engine is organized into 5 architectural layers: - -| Layer | Name | Description | Document | -|-------|------|-------------|----------| -| Layer 0 | Hardware | Physical hardware (ESP32, displays, audio) | [Hardware Layer](/architecture/layers) | -| Layer 1 | Drivers | Platform-specific drivers (TFT_eSPI, U8G2, SDL2) | [Driver Layer](/architecture/layers) | -| Layer 2 | Abstraction | Abstract interfaces (DrawSurface, PlatformMemory) | [Abstraction Layer](/architecture/layers) | -| Layer 3 | Systems | High-level subsystems (Renderer, Audio, Physics, UI) | [System Layer](/architecture/layers) | -| Layer 4 | Scene | Scene and entity management | [Scene Layer](/architecture/layers) | -| Layer 5 | Game | User game code | (Implemented by user) | - -Layer diagram: see [Architecture overview](/architecture/overview). - ---- - -## Subsystem Deep Dives - -For detailed documentation on specific subsystems, see: - -| Subsystem | Document | -|-----------|----------| -| Audio NES | [Audio Subsystem](/architecture/audio-architecture) | -| Physics | [Physics Subsystem](/architecture/physics-system) | -| Memory Management | [Memory System](/architecture/memory-system) | -| Resolution Scaling | [Resolution Scaling](/architecture/ARCH_RESOLUTION_SCALING) | -| Tile Animation | [Tile Animation](/architecture/ARCH_TILE_ANIMATION) | -| Touch Input | [Touch Input](/architecture/ARCH_TOUCH_INPUT) | -| Extending | [Extending PixelRoot32](/guide/extending) | - ---- - -## Quick Reference: Module Dependencies - -``` -Engine -├── SceneManager -│ └── Scene -│ ├── Entity -│ │ ├── Actor -│ │ └── UIElement -│ └── CollisionSystem -├── Renderer -│ ├── DrawSurface (abstract) -│ │ ├── TFT_eSPI_Drawer -│ │ ├── U8G2_Drawer -│ │ └── SDL2_Drawer -│ ├── Font (abstract) -│ │ └── Font5x7 -│ └── Camera2D -├── InputManager -│ └── InputConfig -├── AudioEngine -│ ├── AudioScheduler (abstract) -│ │ ├── ESP32AudioScheduler -│ │ └── NativeAudioScheduler -│ └── MusicPlayer -└── PlatformCapabilities -``` - ---- - -## Performance Optimizations - -### Implemented Strategies - -1. **Logical vs Physical Resolution**: Rendering at low resolution (e.g., 128x128) with high-performance scaling to physical display (e.g., 240x240). - -2. **Scaling Pipeline (v1.0.0)**: - - **Fast-Path Switching**: Specialized routines for 1:1 and 2x integer scaling - - **Bit-Expansion LUTs**: OLED horizontal expansion via lookup tables - - **32-bit Register Writes**: TFT vertical duplication via optimized memcpy - -3. **Multi-Core Audio (ESP32)**: - - Core 0: Audio scheduling and generation - - Core 1: Main game loop - -4. **Mixer LUT**: Lookup tables for mixing without FPU - -5. **DMA Pipelining (TFT)**: Double buffering with large block sizes - -6. **IRAM-Cached Rendering**: Critical functions in internal RAM - -7. **Viewport Culling**: Only render visible entities - -### Performance Metrics - -- **FPS Target**: 30-60 FPS on ESP32 -- **Audio Latency**: < 50ms -- **Memory Footprint**: < 100KB RAM for complete engine -- **Sprite Capacity**: 100+ sprites @ 60fps (logical resolution 128x128) - ---- - -## Configuration and Compilation - -### Key Configuration Files - -| File | Description | -|------|-------------| -| `platforms/EngineConfig.h` | Global engine configuration | -| `platforms/PlatformDefaults.h` | Platform-specific defaults | -| `platforms/PlatformCapabilities.h` | Hardware detection | -| `graphics/DisplayConfig.h` | Display configuration | -| `input/InputConfig.h` | Input configuration | -| `audio/AudioConfig.h` | Audio configuration | - -### Common Compilation Flags - -| Flag | Description | -|------|-------------| -| `PLATFORM_ESP32` | Compilation for ESP32 | -| `PLATFORM_NATIVE` | Compilation for PC | -| `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Enable debug overlay | -| `PIXELROOT32_ENABLE_2BPP_SPRITES` | 2bpp sprite support | -| `PIXELROOT32_ENABLE_4BPP_SPRITES` | 4bpp sprite support | -| `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Static 4bpp tilemap layer framebuffer snapshot (`StaticTilemapLayerCache`) | - ---- - -## Conclusion - -PixelRoot32 implements a well-defined layered architecture that enables: - -1. **Portability**: 100% portable game code between ESP32 and PC -2. **Modularity**: Independent and replaceable subsystems -3. **Performance**: Specific optimizations for embedded hardware -4. **Extensibility**: Easy addition of new drivers and features -5. **Simplicity**: Intuitive API inspired by Godot Engine - -The Scene-Entity architecture provides a familiar programming model for game developers, while the driver abstraction layer enables multi-platform support without sacrificing performance. - ---- - -**Document Generated**: March 2026 -**Engine Version**: v1.1.0 diff --git a/architecture/overview.md b/architecture/overview.md deleted file mode 100644 index dde7568..0000000 --- a/architecture/overview.md +++ /dev/null @@ -1,449 +0,0 @@ -# Architecture Overview - -> **See also:** [Layer hierarchy](/architecture/layers), [Modules](/architecture/modules), [Memory system](/architecture/memory-system) - -PixelRoot32 is architected as a layered system, abstracting hardware specifics while providing high-level game development patterns. Understanding these layers helps you extend the engine and debug issues effectively. - -## Layer Hierarchy - -The engine is organized into five distinct layers, from hardware to game code. A visual stack diagram lives in the [Layer hierarchy](/architecture/layers) page and in the Mermaid diagram below. - -## Design Philosophy - -### Modularity Through Compilation - -Unlike monolithic engines, PixelRoot32 uses **compile-time modularity**: - -```cpp -// Only include what you need -#define PIXELROOT32_ENABLE_AUDIO 1 -#define PIXELROOT32_ENABLE_PHYSICS 1 -#define PIXELROOT32_ENABLE_UI_SYSTEM 1 -#define PIXELROOT32_ENABLE_TOUCH 0 // Disabled - saves ~200 bytes -``` - -Unused subsystems are completely excluded from the binary, not just disabled at runtime. - -### Abstraction Without Overhead - -The engine uses template-based abstraction and the **Bridge Pattern** to eliminate virtual call overhead on hot paths: - -```cpp -// DrawSurface is a pure interface -template -class DrawSurfaceImpl : public DrawSurface { - Implementation impl; -public: - void drawPixel(int x, int y, Color c) override { - impl.drawPixel(x, y, c); // Inlined, no vtable lookup - } -}; -``` - -### Embedded-First Design - -Every decision considers ESP32 constraints: - -| Resource | Constraint | Solution | -|----------|-----------|----------| -| RAM (520KB) | Limited | Logical/physical resolution decoupling | -| Flash (4MB+) | Slow access | `IRAM_ATTR` for critical code | -| CPU (240MHz) | Single-core game loop | DMA for rendering, multi-core audio | -| No FPU (C3) | Software float slow | Fixed-point `Scalar` abstraction | - -## Layer Deep Dive - -### Layer 0: Hardware - -The foundation—ESP32 variants and peripherals. - -| Component | ESP32 | ESP32-S3 | ESP32-C3 | ESP32-C6 | -|-----------|-------|----------|----------|----------| -| CPU | Xtensa LX6 | Xtensa LX7 | RISC-V | RISC-V | -| FPU | Yes | Yes | No | No | -| Recommended Math | float | float | Fixed16 | Fixed16 | -| Audio DAC | ✅ GPIO 25/26 | ❌ | ❌ | ❌ | -| Audio I2S | ✅ | ✅ | ✅ | ✅ | - -### Layer 1: Drivers - -Hardware-specific implementations: - -```cpp -// TFT_eSPI_Drawer for color LCDs -class TFT_eSPI_Drawer : public DrawSurface { - TFT_eSPI* tft; - uint8_t* dmaBuffer; - - void present() override { - // DMA-accelerated transfer - tft->pushImageDMA(...); - } -}; - -// U8G2_Drawer for monochrome OLEDs -class U8G2_Drawer : public DrawSurface { - U8G2* u8g2; - - void drawSprite(const Sprite& s, int x, int y, Color c) override { - // XBM format for zero-copy rendering - u8g2->drawXBM(x, y, s.width, s.height, - reinterpret_cast(s.data)); - } -}; - -// SDL2_Drawer for PC simulation -class SDL2_Drawer : public DrawSurface { - SDL_Renderer* renderer; - SDL_Texture* frameTexture; - - void present() override { - SDL_UpdateTexture(frameTexture, ...); - SDL_RenderCopy(renderer, frameTexture, NULL, NULL); - SDL_RenderPresent(renderer); - } -}; -``` - -### Layer 2: Abstraction - -Platform-agnostic interfaces: - -#### DrawSurface (Bridge Pattern) - -```cpp -class DrawSurface { -public: - virtual void drawPixel(int x, int y, Color c) = 0; - virtual void drawSprite(const Sprite& s, int x, int y, Color c) = 0; - virtual void present() = 0; - - // Resolution scaling support - virtual void setLogicalSize(int w, int h) = 0; - virtual void* getSpriteBuffer() = 0; // For direct buffer access -}; -``` - -#### AudioScheduler (Strategy Pattern) - -```cpp -class AudioScheduler { -public: - virtual void start() = 0; - virtual void stop() = 0; - virtual void submitCommand(const AudioCommand& cmd) = 0; - - // Platform-specific implementations: - // - ESP32: FreeRTOS task on Core 0 - // - Native: SDL audio callback thread -}; -``` - -#### Math System (Scalar Abstraction) - -```cpp -// Automatically selects float or Fixed16 -#if defined(SOC_CPU_HAS_FPU) - using Scalar = float; -#else - using Scalar = Fixed16; // Q16.16 fixed-point -#endif - -template -T toScalar(T value) { return value; } -``` - -### Layer 3: Systems - -High-level subsystems that form the engine's capabilities. - -#### Rendering Pipeline - -```mermaid -flowchart LR - A[Game Code] -->|draw calls| B[Renderer] - B -->|clipping| C[DrawSurface] - C -->|transform| D[Driver] - D -->|DMA| E[Display] -``` - -**Renderer responsibilities:** -- Viewport and camera management -- Coordinate transformation (world → screen) -- Batch drawing primitives -- Resolution scaling (logical → physical) - -#### Physics System (Flat Solver) - -```mermaid -flowchart TB - subgraph Broadphase["Broadphase (Spatial Grid)"] - A[Actor insertion] - B[Grid cell lookup] - C[Potential pairs] - end - - subgraph Narrowphase["Narrowphase (AABB)"] - D[Exact intersection] - E[Contact generation] - end - - subgraph Solver["Solver"] - F[Position correction] - G[Velocity updates] - end - - A --> B --> C --> D --> E --> F --> G -``` - -**CollisionSystem features:** -- Uniform grid broadphase (32px cells) -- AABB narrowphase -- Iterative solver for stability -- Layer/mask filtering - -#### Audio System - -```mermaid -flowchart LR - A[Game Code] -->|Commands| B[AudioCommandQueue in ApuCore] - B -->|SPSC| C[AudioScheduler -> ApuCore] - C -->|Mix| D[Channels] - D -->|Output| E[Hardware] -``` - -**Architecture highlights:** -- Lock-free SPSC queue between game and audio threads -- Sample-accurate timing (not frame-based) -- Non-linear mixer with soft clipping -- LUT-based mixing for no-FPU targets - -### Layer 4: Scene Layer - -The object hierarchy: - -```mermaid -classDiagram - class Engine { - +run() - +setScene(Scene*) - +getRenderer() - +getInputManager() - } - - class SceneManager { - +getCurrentScene() - +changeScene(Scene*) - } - - class Scene { - +init() - +update(deltaTime) - +draw(Renderer&) - +addEntity(Entity*) - #entities[] - #collisionSystem - } - - class Entity { - +position: Vector2 - +update(deltaTime)* - +draw(Renderer&)* - #renderLayer - } - - class Actor { - +layer: CollisionLayer - +mask: CollisionLayer - +onCollision(Actor*) - } - - Engine --> SceneManager - SceneManager --> Scene - Scene --> Entity - Entity <|-- Actor -``` - -### Layer 5: Game Layer - -Your code lives here, extending engine classes: - -```cpp -// Your scene -class Level1 : public Scene { - void init() override { - // Create entities specific to this level - } -}; - -// Your actor -class Player : public KinematicActor { - void update(unsigned long dt) override { - // Player-specific logic - } -}; -``` - -## Communication Patterns - -### Engine → Subsystems - -```cpp -class Engine { - Renderer renderer; // Direct ownership - AudioEngine audio; // Direct ownership - InputManager input; // Direct ownership - SceneManager scenes; // Direct ownership - -public: - Renderer& getRenderer() { return renderer; } - AudioEngine& getAudioEngine() { return audio; } -}; -``` - -### Scene → Entities - -```cpp -class Scene { - Entity* entities[MAX_ENTITIES]; // Fixed array, O(1) access - - void update(unsigned long dt) { - for (int i = 0; i < entityCount; ++i) { - entities[i]->update(dt); // Virtual dispatch - } - } -}; -``` - -### Physics Callbacks - -```cpp -class Actor { - virtual void onCollision(Actor* other) = 0; // Notification only -}; - -// System calls this after resolving collision -void CollisionSystem::notifyCollision(Actor* a, Actor* b) { - a->onCollision(b); - b->onCollision(a); -} -``` - -## Data Flow - -### Render Frame Data Flow - -```mermaid -sequenceDiagram - participant Game - participant Scene - participant Entity - participant Renderer - participant DrawSurface - participant Driver - - Game->>Scene: draw(renderer) - Scene->>Entity: draw(renderer) - Entity->>Renderer: drawSprite() - Renderer->>Renderer: Apply camera offset - Renderer->>Renderer: Clip to viewport - Renderer->>DrawSurface: drawSprite() - DrawSurface->>Driver: Platform-specific render - Driver->>Driver: DMA transfer (async) -``` - -### Audio Frame Data Flow - -```mermaid -sequenceDiagram - participant Game - participant AudioEngine - participant CommandQueue - participant AudioScheduler - participant Mixer - participant Hardware - - Game->>AudioEngine: playSFX(sfx) - AudioEngine->>CommandQueue: push(PLAY_CMD) - - Note over AudioScheduler: Audio thread (Core 0) - AudioScheduler->>CommandQueue: pop() - AudioScheduler->>Mixer: Add channel - Mixer->>Mixer: Mix samples - Mixer->>Hardware: Write buffer -``` - -## Extension Points - -The architecture provides several clean extension points: - -### Custom DrawSurface - -```cpp -class MyCustomDrawer : public DrawSurface { - void drawPixel(int x, int y, Color c) override; - void present() override; -}; - -// Use it -DisplayConfig config(...); -config.setCustomDrawer(std::make_unique()); -Engine engine(std::move(config)); -``` - -### Custom Actor Types - -```cpp -class Projectile : public KinematicActor { - void update(unsigned long dt) override { - // Custom movement - moveAndSlide(velocity * dt, dt); - - // Destroy if out of bounds - if (position.y < 0) { - markForDeletion(); - } - } -}; -``` - -### Custom Scene Types - -```cpp -class NetworkedScene : public Scene { - void update(unsigned long dt) override { - // Custom networked update - receiveNetworkUpdates(); - Scene::update(dt); - sendNetworkUpdates(); - } -}; -``` - -## Performance Considerations - -| Layer | Hot Path Optimizations | -|-------|----------------------| -| Driver | DMA, IRAM_ATTR, LUT-based operations | -| Abstraction | Template inlining, no virtuals on hot paths | -| Systems | Spatial partitioning, viewport culling | -| Scene | Fixed arrays, lazy sorting by layer | -| Game | Zero-allocation policy, object pooling | - -## Design Patterns Used - -| Pattern | Usage | Benefit | -|---------|-------|---------| -| **Bridge** | `DrawSurface` → Drivers | Platform independence | -| **Strategy** | `AudioScheduler` implementations | Platform-specific optimization | -| **Component** | Entity → Actor | Optional physics per object | -| **Observer** | `onCollision` callbacks | Decoupled collision response | -| **Object Pool** | Entity arrays, particle pools | Zero allocation in loop | -| **Command** | Audio command queue | Thread-safe communication | - -## Next Steps - -- **[Layer Hierarchy](/architecture/layers)** — Detailed layer documentation -- **[Modules](/architecture/modules)** — Subsystem reference -- **[Design Patterns](/architecture/patterns)** — Pattern implementation details -- **[Memory System](/architecture/memory-system)** — Memory management architecture diff --git a/architecture/patterns.md b/architecture/patterns.md deleted file mode 100644 index d658d93..0000000 --- a/architecture/patterns.md +++ /dev/null @@ -1,223 +0,0 @@ -# Design patterns & philosophy - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -# Architecture Overview - PixelRoot32 Game Engine - -## Executive Summary - -PixelRoot32 is a lightweight, modular 2D game engine written in C++17, designed primarily for ESP32 microcontrollers, with a native simulation layer for PC (SDL2) that enables rapid development without hardware. - -The engine follows a scene-based architecture inspired by Godot Engine, making it intuitive for developers familiar with modern game development workflows. - ---- - -## Design Philosophy - -- **Modularity**: Each subsystem can be used independently and compiled conditionally -- **Selective Compilation**: Subsystems can be excluded at compile time to reduce firmware size and RAM usage -- **Portability**: Same code for ESP32 and PC (SDL2) -- **Performance**: Optimized for resource-constrained hardware with aggressive dead code elimination -- **Extensibility**: Plugin architecture for drivers and backends -- **Modern C++**: Leverages C++17 features (smart pointers, string_view) for safety and efficiency - -### What Does "Modularity" Mean in PixelRoot32? - -**Modularity** means that each main subsystem has **low coupling** and can be instantiated, tested, and used in isolation, without depending on other subsystems. This allows: - -- **Independent testing**: Each module can be unit tested -- **Selective usage**: Use only the modules you need -- **Easy replacement**: Change implementations without affecting the rest of the code -- **Conditional compilation**: Exclude entire subsystems at compile time to save firmware size and RAM - -**Concrete examples of independence:** - -```cpp -// 1. AudioEngine works without Renderer or SceneManager (if enabled) -#if PIXELROOT32_ENABLE_AUDIO -AudioConfig audioConfig; -AudioEngine audio(audioConfig); -audio.init(); -audio.playEvent({WaveType::PULSE, 440.0f, 0.5f, 0.8f}); -#endif - -// 2. Renderer can be used without Audio or Input -DisplayConfig displayConfig; -Renderer renderer(displayConfig); -renderer.init(); -renderer.beginFrame(); -renderer.drawSprite(sprite, 10, 10, Color::White); -renderer.endFrame(); - -// 3. InputManager is autonomous -InputConfig inputConfig; -InputManager input(inputConfig); -input.init(); -input.update(deltaTime); -if (input.isButtonPressed(0)) { /* ... */ } - -// 4. CollisionSystem is optional per scene (if enabled) -#if PIXELROOT32_ENABLE_PHYSICS -Scene scene; -// You can update physics only if you need it -scene.collisionSystem.update(); -#endif - -// 5. Interchangeable drivers without changing game code -// Same code works with TFT_eSPI_Drawer, U8G2_Drawer, or SDL2_Drawer -``` - -**Note**: `Engine` is the only component with tight coupling (orchestrates everything), but each subsystem can exist and function independently. The modular compilation system uses `PIXELROOT32_ENABLE_*` flags to conditionally compile subsystems, dramatically reducing firmware size and RAM usage on embedded targets. - ---- - -## Main Architectural Features - -- Stack-based Scene-Entity system -- Rendering with logical resolution independent of physical resolution -- NES-style 4-channel audio subsystem (conditionally compiled) -- UI system with automatic layouts (conditionally compiled) -- "Flat Solver" physics with specialized Actor types (conditionally compiled) -- Circular and AABB collision support -- Multi-platform support through driver abstraction -- **Modular compilation** for selective subsystem inclusion - ---- - -## Layer Hierarchy - -The engine is organized into 5 architectural layers: - -| Layer | Name | Description | Document | -|-------|------|-------------|----------| -| Layer 0 | Hardware | Physical hardware (ESP32, displays, audio) | [Hardware Layer](/architecture/layers) | -| Layer 1 | Drivers | Platform-specific drivers (TFT_eSPI, U8G2, SDL2) | [Driver Layer](/architecture/layers) | -| Layer 2 | Abstraction | Abstract interfaces (DrawSurface, PlatformMemory) | [Abstraction Layer](/architecture/layers) | -| Layer 3 | Systems | High-level subsystems (Renderer, Audio, Physics, UI) | [System Layer](/architecture/layers) | -| Layer 4 | Scene | Scene and entity management | [Scene Layer](/architecture/layers) | -| Layer 5 | Game | User game code | (Implemented by user) | - -Layer diagram: see [Architecture overview](/architecture/overview). - ---- - -## Subsystem Deep Dives - -For detailed documentation on specific subsystems, see: - -| Subsystem | Document | -|-----------|----------| -| Audio NES | [Audio Subsystem](/architecture/audio-architecture) | -| Physics | [Physics Subsystem](/architecture/physics-system) | -| Memory Management | [Memory System](/architecture/memory-system) | -| Resolution Scaling | [Resolution Scaling](/architecture/ARCH_RESOLUTION_SCALING) | -| Tile Animation | [Tile Animation](/architecture/ARCH_TILE_ANIMATION) | -| Touch Input | [Touch Input](/architecture/ARCH_TOUCH_INPUT) | -| Extending | [Extending PixelRoot32](/guide/extending) | - ---- - -## Quick Reference: Module Dependencies - -``` -Engine -├── SceneManager -│ └── Scene -│ ├── Entity -│ │ ├── Actor -│ │ └── UIElement -│ └── CollisionSystem -├── Renderer -│ ├── DrawSurface (abstract) -│ │ ├── TFT_eSPI_Drawer -│ │ ├── U8G2_Drawer -│ │ └── SDL2_Drawer -│ ├── Font (abstract) -│ │ └── Font5x7 -│ └── Camera2D -├── InputManager -│ └── InputConfig -├── AudioEngine -│ ├── AudioScheduler (abstract) -│ │ ├── ESP32AudioScheduler -│ │ └── NativeAudioScheduler -│ └── MusicPlayer -└── PlatformCapabilities -``` - ---- - -## Performance Optimizations - -### Implemented Strategies - -1. **Logical vs Physical Resolution**: Rendering at low resolution (e.g., 128x128) with high-performance scaling to physical display (e.g., 240x240). - -2. **Scaling Pipeline (v1.0.0)**: - - **Fast-Path Switching**: Specialized routines for 1:1 and 2x integer scaling - - **Bit-Expansion LUTs**: OLED horizontal expansion via lookup tables - - **32-bit Register Writes**: TFT vertical duplication via optimized memcpy - -3. **Multi-Core Audio (ESP32)**: - - Core 0: Audio scheduling and generation - - Core 1: Main game loop - -4. **Mixer LUT**: Lookup tables for mixing without FPU - -5. **DMA Pipelining (TFT)**: Double buffering with large block sizes - -6. **IRAM-Cached Rendering**: Critical functions in internal RAM - -7. **Viewport Culling**: Only render visible entities - -### Performance Metrics - -- **FPS Target**: 30-60 FPS on ESP32 -- **Audio Latency**: < 50ms -- **Memory Footprint**: < 100KB RAM for complete engine -- **Sprite Capacity**: 100+ sprites @ 60fps (logical resolution 128x128) - ---- - -## Configuration and Compilation - -### Key Configuration Files - -| File | Description | -|------|-------------| -| `platforms/EngineConfig.h` | Global engine configuration | -| `platforms/PlatformDefaults.h` | Platform-specific defaults | -| `platforms/PlatformCapabilities.h` | Hardware detection | -| `graphics/DisplayConfig.h` | Display configuration | -| `input/InputConfig.h` | Input configuration | -| `audio/AudioConfig.h` | Audio configuration | - -### Common Compilation Flags - -| Flag | Description | -|------|-------------| -| `PLATFORM_ESP32` | Compilation for ESP32 | -| `PLATFORM_NATIVE` | Compilation for PC | -| `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Enable debug overlay | -| `PIXELROOT32_ENABLE_2BPP_SPRITES` | 2bpp sprite support | -| `PIXELROOT32_ENABLE_4BPP_SPRITES` | 4bpp sprite support | -| `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Static 4bpp tilemap layer framebuffer snapshot (`StaticTilemapLayerCache`) | - ---- - -## Conclusion - -PixelRoot32 implements a well-defined layered architecture that enables: - -1. **Portability**: 100% portable game code between ESP32 and PC -2. **Modularity**: Independent and replaceable subsystems -3. **Performance**: Specific optimizations for embedded hardware -4. **Extensibility**: Easy addition of new drivers and features -5. **Simplicity**: Intuitive API inspired by Godot Engine - -The Scene-Entity architecture provides a familiar programming model for game developers, while the driver abstraction layer enables multi-platform support without sacrificing performance. - ---- - -**Document Generated**: March 2026 -**Engine Version**: v1.1.0 diff --git a/architecture/physics-system.md b/architecture/physics-subsystem.md similarity index 88% rename from architecture/physics-system.md rename to architecture/physics-subsystem.md index 411a8da..5d1208c 100644 --- a/architecture/physics-system.md +++ b/architecture/physics-subsystem.md @@ -1,9 +1,7 @@ -# Physics subsystem - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - # PixelRoot32 Physics System Reference – Flat Solver +> **Note:** For the complete physics system documentation with examples and API reference, visit the [official documentation](https://docs.pixelroot32.org/manual/game_development/physics_and_collisions/). + This document describes the **Flat Solver**, the current physics system in PixelRoot32. This version represents a major architectural overhaul from previous versions, focusing on stability, determinism, and microcontroller-friendly performance. **Modular Compilation:** The entire physics system is only compiled when `PIXELROOT32_ENABLE_PHYSICS=1`. When disabled, all physics-related classes, collision detection, and solver components are excluded from the build, significantly reducing firmware size and RAM usage. @@ -26,18 +24,59 @@ Every frame, the `CollisionSystem` executes physics in strict order: ``` 1. Detect Collisions → Identify all overlapping pairs -2. Solve Velocity → Apply impulse-based collision response -3. Integrate Positions → Update positions: p = p + v * FIXED_DT -4. Solve Penetration → Baumgarte stabilization + Slop -5. Trigger Callbacks → Notify gameplay code (onCollision) +2. Integrate Velocity → Update velocities (v = v + a * dt) in CollisionSystem +3. Solve Velocity → Apply impulse-based collision response +4. Integrate Positions → Update positions: p = p + v * FIXED_DT +5. Solve Penetration → Baumgarte stabilization + Slop +6. Trigger Callbacks → Notify gameplay code (onCollision) ``` +> **Note (v1.2.2+):** Velocity integration has been centralized in `CollisionSystem::update()`. The `Actor::update()` no longer handles velocity integration — this ensures a single integration path where velocities are updated before positions, improving determinism and consistency. + This order is critical: - **Velocity is solved before position integration** (prevents energy loss) - **Position integration happens before penetration correction** (allows proper separation) - **Callbacks happen last** (gameplay can inspect final state) +### 1.3 Physics Pipeline Overview + +``` +CollisionSystem::update() + │ + ▼ +Broadphase (Spatial Grid) + │ + ▼ (candidate pairs) +Narrowphase (AABB / Circle tests) + │ + ▼ (contacts) +Contact Solver + │ + ├──▶ Velocity Solve (Impulse response) + │ └─ Bounce & friction + │ + ├──▶ Position Integrate (p += v·dt) + │ + └──▶ Penetration Correct (Baumgarte) + │ + ▼ +Collision Events + │ + ▼ (dispatch) +onCollision Callbacks +``` + +> **Key insight:** Steps 1-2 run per-frame. Steps 3-6 run inside the fixed timestep loop (1/60s). + +> **Quick Reference:** The simulation runs in `CollisionSystem::update()`: +> 1. **Detect** → find overlapping pairs (grid → AABB/circle checks) +> 2. **Integrate Velocity** → `v = v + a·dt` +> 3. **Solve Velocity** → impulse-based response +> 4. **Integrate Position** → `p = p + v·FIXED_DT` +> 5. **Solve Penetration** → Baumgarte stabilization +> 6. **Callbacks** → `onCollision()` events + --- ## 2. Key Constants @@ -49,6 +88,10 @@ static constexpr Scalar BIAS = toScalar(0.2f); // 20% correction p static constexpr Scalar VELOCITY_THRESHOLD = toScalar(0.5f); // Zero restitution below this static constexpr int VELOCITY_ITERATIONS = 2; // Impulse solver iterations static constexpr Scalar CCD_THRESHOLD = toScalar(3.0f); // CCD activation threshold + +// vPhysics Scheduler constants +static constexpr Scalar VELOCITY_DAMPING = toScalar(0.999f); // Per-frame velocity damping +static constexpr Scalar MAX_VELOCITY = toScalar(500.0f); // Maximum velocity cap (units/s) ``` --- @@ -80,9 +123,12 @@ Frame 4 (16ms): accumulator = 30666µs → 1 step, accumulator = 14000µs | Feature | Description | |---------|-------------| | **Time Accumulator** | Accumulates real microseconds, never discards time | -| **Adaptive Steps** | 2 steps normal, up to 4 when behind (catch-up mode) | +| **Adaptive Steps** | 1 step normal (2 on desktop), up to 4 when behind (catch-up mode) | | **No Clamping** | Preserves real time for catch-up, avoids "slow motion" | -| **ESP32 Optimized** | Early skip for stationary bodies, IRAM_ATTR | +| **ESP32 Optimized** | Early skip for stationary bodies, skip invisible entities, IRAM_ATTR | +| **Skip Invisible** | Collision detection skips entities with `isVisible() == false` for performance | + +> **Note (v1.2.2+):** `MAX_STEPS_NORMAL` was reduced from 2 to 1 for ESP32-C3 stability while maintaining simulation accuracy. Desktop builds may use 2 steps. ### 2.1.4 Integration @@ -110,7 +156,7 @@ void Scene::update(unsigned long deltaTime) { # platformio.ini -D PIXELROOT32_ENABLE_PHYSICS_FIXED_TIMESTEP=1 ; Enable scheduler (profile_full/arcade) -D PIXELROOT32_VELOCITY_DAMPING=0.999 ; Per-frame damping (default) --D PIXELROOT32_MAX_VELOCITY=300 ; Max velocity (units/s, default) +-D PIXELROOT32_MAX_VELOCITY=500 ; Max velocity (units/s, default) -D PIXELROOT32_HAS_FAST_RSQRT=1 ; Enable fast reciprocal sqrt ``` @@ -264,6 +310,7 @@ bool validateOneWayPlatform(PhysicsActor* actor, PhysicsActor* platform, ``` **Test Coverage**: `test/unit/test_collision_system/test_collision_system.cpp` includes: + - `test_one_way_platform_crossing_from_above` - `test_one_way_platform_crossing_from_below` - `test_one_way_platform_wrong_normal_direction` @@ -483,7 +530,7 @@ Tune in `CollisionSystem.h` or override via `platforms/EngineConfig.h` / build f #define SPATIAL_GRID_MAX_DYNAMIC_PER_CELL 12 ``` -**ESP32 DRAM:** On boards with limited internal RAM, reducing `PHYSICS_MAX_CONTACTS` and `PHYSICS_MAX_PAIRS` (e.g. to 64) and/or `SPATIAL_GRID_MAX_STATIC_PER_CELL` and `SPATIAL_GRID_MAX_DYNAMIC_PER_CELL` (e.g. to 4) lowers `.dram0.bss` usage. See [Memory Management Guide](/architecture/memory-system#esp32-dram-and-build-configuration). +**ESP32 DRAM:** On boards with limited internal RAM, reducing `PHYSICS_MAX_CONTACTS` and `PHYSICS_MAX_PAIRS` (e.g. to 64) and/or `SPATIAL_GRID_MAX_STATIC_PER_CELL` and `SPATIAL_GRID_MAX_DYNAMIC_PER_CELL` (e.g. to 4) lowers `.dram0.bss` usage. See [Memory Management Guide](memory-system.md#esp32-dram-and-build-configuration). Solver tuning (in code): @@ -596,12 +643,6 @@ void RigidActor::update(unsigned long deltaTime) { ## References -- [API Reference](/api/) - Class documentation -- [Architecture](/architecture/overview) — System design -- [Migration guide v1.2.0](/guide/migrations/v1.2.0) — PhysicsActor flags packing changes - ---- - -**Document Version**: Flat Solver -**Last Updated**: April 2026 -**Engine Version**: v1.2.0 +- [API Reference](../api/physics.md) - Class documentation +- [Architecture Index](architecture-index.md) - System design +- [Migration Guide v1.2.0](../migration/migration-v1-2-0.md) - PhysicsActor flags packing changes diff --git a/architecture/rendering-pipeline.md b/architecture/rendering-pipeline.md deleted file mode 100644 index 8fea0dd..0000000 --- a/architecture/rendering-pipeline.md +++ /dev/null @@ -1,358 +0,0 @@ -# Rendering pipeline (systems layer) - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - -# Layer 3: System Layer - -## Responsibility - -Game engine subsystems that implement high-level functionality. These systems provide the core capabilities that game code builds upon. - ---- - -## Subsystem Overview - -The System Layer contains the following major subsystems: - -| Subsystem | Responsibility | Detailed Document | -|-----------|--------------|-------------------| -| **Renderer** | Graphics rendering, sprites, tilemaps | See API Reference | -| **InputManager** | Button and touch input handling | [Touch Input](/architecture/ARCH_TOUCH_INPUT) | -| **AudioEngine** | NES-style 4-channel audio | [Audio Subsystem](/architecture/audio-architecture) | -| **CollisionSystem** | Physics simulation, collisions | [Physics Subsystem](/architecture/physics-system) | -| **UI System** | User interface and layouts | See API Reference | -| **Particle System** | Visual effects and particles | See API Reference | -| **Camera2D** | Viewport transformations | See API Reference | -| **Tile Animation** | Animated tilemaps | [Tile Animation](/architecture/ARCH_TILE_ANIMATION) | -| **Resolution Scaling** | Logical vs physical resolution | [Resolution Scaling](/architecture/ARCH_RESOLUTION_SCALING) | - ---- - -## System Architecture Diagram - -```mermaid -flowchart TB - subgraph systemLayer ["System Layer"] - direction TB - subgraph primary [Primary systems] - direction LR - Ren[Renderer] - InMgr[Input Manager] - Aud[Audio Engine] - Phy["Physics (Flat Solver)"] - end - Ren --> Hub(( )) - InMgr --> Hub - Aud --> Hub - Phy --> Hub - Hub --> UIS[UI System] - Hub --> Part[Particle System] - Hub --> Cam[Camera 2D] - Hub --> TileAnim[Tile Animation] - end - Hub --> SceneLay["Scene Layer (coordinates game objects)"] -``` - ---- - -## Renderer - -**Files**: `include/graphics/Renderer.h`, `src/graphics/Renderer.cpp` - -High-level rendering system that abstracts graphics operations. - -### Features - -- Logical resolution independent of physical resolution -- Support for 1bpp, 2bpp, 4bpp sprites -- Sprite animation system -- Tilemaps with viewport culling -- Multi-palette tilemaps (2bpp/4bpp) -- Multi-palette sprites (2bpp/4bpp) -- Native bitmap font system -- Render contexts for dual palettes - -### Multi-Palette Sprites Architecture - -The engine supports multiple palettes for 2bpp/4bpp sprites through a sprite palette slot bank. - -**Data Flow**: - -``` -sprite.paletteSlot → getSpritePaletteSlot() → resolveColorWithPalette() → drawSpriteInternal -``` - -**API Example**: - -```cpp -class Renderer { - void beginFrame(); - void endFrame(); - void drawSprite(const Sprite& sprite, int x, int y, Color color); - void drawText(std::string_view text, int x, int y, Color color, uint8_t size); - void drawTileMap(const TileMap& map, int originX, int originY); -}; -``` - ---- - -## InputManager - -**Files**: `include/input/InputManager.h`, `src/input/InputManager.cpp` - -Input management from physical buttons or keyboard (PC), plus optional touch event routing. - -### Features - -- Debouncing support -- States: Pressed, Released, Down, Clicked -- Configurable via `InputConfig` -- Hardware abstraction through polling -- **Touch event dispatcher** (when `PIXELROOT32_ENABLE_TOUCH=1`) - -### Button States - -| Method | Description | -|--------|-------------| -| `isButtonPressed()` | UP → DOWN transition | -| `isButtonReleased()` | DOWN → UP transition | -| `isButtonDown()` | Current DOWN state | -| `isButtonClicked()` | Complete click detected | - -**Touch input** is covered in detail in [Touch Input Architecture](/architecture/ARCH_TOUCH_INPUT). - ---- - -## AudioEngine - -**Files**: `include/audio/AudioEngine.h`, `src/audio/AudioEngine.cpp` - -NES-style 4-channel audio system. See [Audio Subsystem Reference](/architecture/audio-architecture) for complete details. - -**Quick Overview**: - -- 2 PULSE channels (square wave) -- 1 TRIANGLE channel -- 1 NOISE channel -- Sample-accurate timing via AudioScheduler -- Modular compilation: `PIXELROOT32_ENABLE_AUDIO` - ---- - -## CollisionSystem (Flat Solver) - -**Files**: `include/physics/CollisionSystem.h`, `src/physics/CollisionSystem.cpp` - -High-performance physics solver optimized for ESP32 microcontrollers. - -**Simulation Pipeline**: - -``` -1. Detect Collisions → Dual-layer spatial grid -2. Solve Velocity → Impulse-based response -3. Integrate Positions → p = p + v * dt -4. Solve Penetration → Baumgarte stabilization -5. Trigger Callbacks → onCollision notifications -``` - -See [Physics System Reference](/architecture/physics-system) for complete details. - ---- - -## UI System - -**Files**: `include/graphics/ui/*.h`, `src/graphics/ui/*.cpp` - -User interface system with automatic layouts. - -### Class Hierarchy - -``` -Entity -├── UIElement -│ ├── UILabel -│ ├── UIButton -│ ├── UICheckbox -│ └── UIPanel -│ └── UILayout -│ ├── UIHorizontalLayout -│ ├── UIVerticalLayout -│ ├── UIGridLayout -│ ├── UIAnchorLayout -│ └── UIPaddingContainer -└── UITouchElement - ├── UITouchButton - ├── UITouchSlider - └── UITouchCheckbox -``` - -### Touch Widget Architecture - -- **UITouchWidget**: Lightweight widget data struct -- **UITouchElement**: Abstract base with widget data -- **UIManager**: Non-owning registry (max 16 elements) - -Scene owns widgets; UIManager only routes events. - ---- - -## Particle System - -**Files**: `include/graphics/particles/*.h`, `src/graphics/particles/*.cpp` - -Visual effects system with configurable emitters. - -**Components**: - -- `Particle`: Individual particle with position, velocity, life -- `ParticleEmitter`: Configurable emitter with presets -- `ParticleConfig`: Emission configuration - -Modular compilation: `PIXELROOT32_ENABLE_PARTICLES` - ---- - -## Camera2D - -**Files**: `include/graphics/Camera2D.h`, `src/graphics/Camera2D.cpp` - -2D camera with viewport transformations. - -**Features**: - -- Position and zoom control -- Automatic offset for Renderer -- Support for fixed-position UI elements -- Stable rounding to prevent jitter - ---- - -## Tilemap rendering - -**Files**: `include/graphics/Renderer.h`, `src/graphics/Renderer.cpp`, `include/graphics/TileAnimation.h` - -`Renderer::drawTileMap` performs **viewport culling** (only tiles that can intersect the logical framebuffer), optional **`TileAnimationManager::resolveFrame`**, optional **runtime tile masks** and **per-cell background palettes** on 2bpp/4bpp maps, then rasterizes each visible tile (ESP32: hot paths use `IRAM_ATTR` where applicable). - -For largely static **4bpp** layers when **`DrawSurface::getSpriteBuffer()`** is available, use **`StaticTilemapLayerCache`** and **`PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE`** (see Graphics API and architecture docs). - -See [Tile Animation](/architecture/ARCH_TILE_ANIMATION) for the animation system. - ---- - -## Subsystem Modular Compilation - -| Subsystem | Flag | Default | -|-----------|------|---------| -| Audio | `PIXELROOT32_ENABLE_AUDIO` | Enabled | -| Physics | `PIXELROOT32_ENABLE_PHYSICS` | Enabled | -| UI System | `PIXELROOT32_ENABLE_UI_SYSTEM` | Enabled | -| Particles | `PIXELROOT32_ENABLE_PARTICLES` | Enabled | -| Touch Input | `PIXELROOT32_ENABLE_TOUCH` | Disabled | -| Tile Animations | `PIXELROOT32_ENABLE_TILE_ANIMATIONS` | Enabled | -| Static tilemap framebuffer cache (4bpp) | `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enabled (`PlatformDefaults.h`) | - ---- - -## Data Flow - -### Game Loop Flow - -```mermaid -flowchart TB - Init[Init] --> GL[Game Loop] --> Exit[Exit] - GL --> InPoll["Input Poll"] - GL --> Upd["Update Logic"] - GL --> Drw["Draw Render"] - Upd --> Aud["Audio Generate"] - Upd --> Phy["Physics Update"] - Upd --> Ui["UI Draw"] -``` - -### Audio Flow - -``` -Game Code - │ - ▼ (submitCommand) -AudioCommandQueue (Thread-Safe) - │ - ▼ (processCommands) -AudioScheduler - │ - ├──▶ Pulse Channel - ├──▶ Triangle Channel - ├──▶ Noise Channel - └──▶ Music Sequencer - │ - ▼ (generateSamples) -Mixer (with LUT) - │ - ▼ -AudioBackend - ├──▶ ESP32_I2S_AudioBackend - ├──▶ ESP32_DAC_AudioBackend - └──▶ SDL2_AudioBackend -``` - ---- - -## Related Documentation - -| Subsystem | Document | -|-----------|----------| -| Audio | [Audio Subsystem](/architecture/audio-architecture) | -| Physics | [Physics Subsystem](/architecture/physics-system) | -| Touch Input | [Touch Input](/architecture/ARCH_TOUCH_INPUT) | - ---- - -## Framebuffer Optimization (v1.2.2+) - -The engine includes rendering optimizations to reduce unnecessary framebuffer updates: - -### `shouldRedrawFramebuffer()` in Scene - -**Purpose:** Conditionally skips `draw()` and `present()` calls when visual state hasn't changed, reducing CPU and memory bandwidth usage. - -**Behavior:** - -- Returns `true` if the visual state has changed (entities moved, animations updated, etc.) -- Returns `false` if the scene is visually identical to the previous frame -- Use in the game loop to determine if rendering should occur: - -```cpp -void GameScene::update(unsigned long deltaTime) { - // Update logic - // ... - - // Only render if visual state changed - if (shouldRedrawFramebuffer()) { - engine.draw(); - engine.present(); - } -} -``` - -**Use Case:** Particularly useful on ESP32 when static backgrounds are cached and only dynamic elements change. - -### `getVisualSignature()` Visual Computation - -**Purpose:** Efficient hash-based computation that represents the current visual state of the scene. - -**Implementation Details:** - -- Computes a signature based on entity positions, animation frames, camera position, and other visual state -- Uses efficient integer operations (no floating-point) -- Comparable in O(1) time to detect framebuffer changes - -**Integration:** Used internally by `shouldRedrawFramebuffer()` to determine if a redraw is necessary. The signature changes when: - -- Entities move or change state -- Tile animations advance -- Camera position changes -- UI elements update -| Tile Animation | [Tile Animation](/architecture/ARCH_TILE_ANIMATION) | -| Resolution Scaling | [Resolution Scaling](/architecture/ARCH_RESOLUTION_SCALING) | -| Memory | [Memory System](/architecture/memory-system) | - -**API Reference**: See `docs/api/API_*.md` for class-level documentation. diff --git a/architecture/ARCH_RESOLUTION_SCALING.md b/architecture/resolution-scaling.md similarity index 91% rename from architecture/ARCH_RESOLUTION_SCALING.md rename to architecture/resolution-scaling.md index 5a8f901..6f10f06 100644 --- a/architecture/ARCH_RESOLUTION_SCALING.md +++ b/architecture/resolution-scaling.md @@ -1,7 +1,3 @@ -# Resolution scaling - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - # Technical Specification: Independent Resolution Scaling System for PixelRoot32 ## Executive Summary @@ -39,6 +35,30 @@ flowchart LR end ``` +### World space vs screen space (camera) + +```mermaid +flowchart TB + subgraph World["World Space"] + direction TB + W1["Player at (100, 200)"] + W2["Enemy at (300, 150)"] + W3["Tilemap origin (0, 0)"] + end + + subgraph Screen["Screen Space (Camera)"] + direction TB + C1["Viewport offset (-50, -100)"] + S1["Player renders at (50, 100)"] + S2["Enemy renders at (250, 50)"] + end + + W1 -->|"position - cameraOffset"| S1 + W2 -->|"position - cameraOffset"| S2 + C1 --> S1 + C1 --> S2 +``` + --- ## Core Components diff --git a/architecture/ARCH_TILE_ANIMATION.md b/architecture/tile-animation.md similarity index 64% rename from architecture/ARCH_TILE_ANIMATION.md rename to architecture/tile-animation.md index 39a846d..d6618b5 100644 --- a/architecture/ARCH_TILE_ANIMATION.md +++ b/architecture/tile-animation.md @@ -1,20 +1,36 @@ -# Tile animation - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - # Tile Animation System — Architecture Diagram ## System Overview ```mermaid flowchart TB - subgraph engine ["PIXELROOT32 ENGINE"] - S[Scene] --> R[Renderer] --> D[Display] - S --> AM["Animation Manager"] - R --> TM["TileMap Generic"] - TM --> AM - AM --> LT["Lookup Table"] - TM --> TS["Tileset (PROGMEM)"] + subgraph E["PIXELROOT32 ENGINE"] + + subgraph L1["High Level"] + S["Scene"] + end + + subgraph L2["Rendering"] + R["Renderer"] + T["TileMap"] + end + + subgraph L3["Data / Systems"] + A["Animation Manager"] + TS["Tileset (PROGMEM)"] + L["Lookup Table"] + end + + subgraph L4["Output"] + D["Display"] + end + + S --> R --> D + R --> T + T <--> A + A --> L + T --> TS + end ``` @@ -55,39 +71,28 @@ Tilemap indices → AnimationManager → Tileset # Update Loop (Scene::update) -``` -───────────────────────────────────────────── -Scene::update(deltaTime) - - └─▶ animManager.step(deltaTime) - │ - ├─ Wall pacing (~60 Hz max): - │ accumulate micros between calls - │ (fallback: deltaTime when 0) - │ → emit 0..N logical ticks - │ - ├─ For each tick: globalFrameCounter++ - │ - └─ For each animation: - │ - ├─ currentFrame = - │ (counter / duration) % frames - │ - ├─ currentTile = - │ baseTile + currentFrame - │ - └─ Update lookup table: - for f in 0..frameCount-1 - lookupTable[base+f] = - currentTile - - Return O(1) when no tick; else O(A×F) -───────────────────────────────────────────── +```mermaid +flowchart TB + A["Renderer::drawTileMap(map, x, y)"] + + A --> B["Viewport culling
(compute visible tiles)"] + A --> C["For each visible tile"] + + C --> D["index = map.indices[i]"] + + D --> E{"map.animManager ?"} + E -- Yes --> F["index = map.animManager->resolveFrame(index)"] + E -- No --> G["index unchanged"] + + F --> H["Resolve palette
(2bpp / 4bpp)"] + G --> H + + H --> I["drawSprite(map.tiles[index], x, y)"] ``` Key properties: -• deterministic update cost when the lookup rebuilds +• deterministic update cost when the lookup rebuilds (same as before) • no dynamic allocations • small constant runtime; pacing decoupled from main-loop iteration rate @@ -95,28 +100,29 @@ Key properties: # Render Loop (Scene::draw) -``` -───────────────────────────────────────────── - Renderer::drawTileMap(map, x, y) - - ├─ Viewport culling - │ (compute visible tiles) - │ - └─ For each visible tile: - │ - ├─ index = map.indices[i] - │ - ├─ if (map.animManager) - │ index = - │ map.animManager - │ ->resolveFrame(index) - │ - ├─ Resolve palette - │ (for 2bpp / 4bpp modes) - │ - └─ drawSprite( - map.tiles[index], x, y) -───────────────────────────────────────────── +```mermaid +flowchart TB + A["Renderer::drawTileMap(map, x, y)"] + + %% Paso inicial + A --> B["Viewport culling
(compute visible tiles)"] + + %% Loop como subgraph vertical + B --> C + + subgraph LOOP["For each visible tile"] + direction TB + + C["index = map.indices[i]"] --> D{"map.animManager?"} + + D -- Yes --> E["index = resolveFrame(index)"] + D -- No --> F["index unchanged"] + + E --> G["Resolve palette
(2bpp / 4bpp)"] + F --> G + + G --> H["drawSprite(map.tiles[index], x, y)"] + end ``` Animation integration cost: @@ -176,6 +182,7 @@ N + 9 bytes ``` **Validación:** + - Compile-time: `static_assert(MAX_TILESET_SIZE >= 64)` - Runtime (debug): Verifica `tileCount <= MAX_TILESET_SIZE` @@ -298,7 +305,7 @@ lookupTable[45] = 45 ### Loop -After logical tick 31: +After frame 31: ``` currentFrame = 0 @@ -310,39 +317,14 @@ The animation repeats. # Rendering Pipeline -## Without Animation - -``` -Tilemap index - │ - ▼ -Tileset lookup - │ - ▼ -Renderer - │ - ▼ -Display -``` - ---- +```mermaid +flowchart TB + A["Tilemap index"] --> B{"Animation enabled?"} -## With Animation + B -- No --> C["Tileset lookup"] + B -- Yes --> D["resolveFrame()"] --> C -``` -Tilemap index - │ - ▼ -AnimationManager.resolveFrame() - │ - ▼ -Tileset lookup - │ - ▼ -Renderer - │ - ▼ -Display + C --> E["Renderer"] --> F["Display"] ``` --- @@ -484,3 +466,16 @@ Result: Production-ready tile animation system optimized for ESP32-class hardware ``` + +--- + +## Related Documentation + +### Framebuffer Optimization (v1.2.2+) + +For additional rendering optimizations, see [Architecture Index](./architecture-index.md): + +- **`shouldRedrawFramebuffer()`**: Scene method that conditionally skips `draw()` and `present()` calls when visual state hasn't changed, reducing unnecessary rendering. +- **`getVisualSignature()`**: Visual signature computation to efficiently detect framebuffer changes and avoid redundant redraws. + +See [ARCHITECTURE.md - Rendering Pipeline](../ARCHITECTURE.md#rendering-pipeline) for details. diff --git a/architecture/ARCH_TOUCH_INPUT.md b/architecture/touch-input.md similarity index 93% rename from architecture/ARCH_TOUCH_INPUT.md rename to architecture/touch-input.md index e457ed6..d6090aa 100644 --- a/architecture/ARCH_TOUCH_INPUT.md +++ b/architecture/touch-input.md @@ -1,12 +1,8 @@ -# Touch input architecture - -> **Source:** Ported from the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository. Cross-links were adjusted for this site. - # Touch Input Architecture This document describes how resistive and capacitive touch integrate with PixelRoot32: data flow, calibration, and platform responsibilities. -For public API details (methods, parameters), see [Touch system API](/api/input/touch-system). +For public API details (methods, parameters), see [API Reference — Input Module](API_REFERENCE.md#touch-input-overview). ## 1. Design principles @@ -38,6 +34,32 @@ Hardware (XPT2046, GT911, …) → onUnconsumedTouchEvent() ``` +### Input abstraction (buttons + touch + PC) + +```mermaid +flowchart TB + subgraph Hardware["Hardware Layer"] + A[GPIO Buttons] + B[XPT2046 Touch] + C[PC Keyboard] + D[PC Mouse] + end + + subgraph Abstraction["Input Abstraction"] + A --> E[InputManager] + B --> F[TouchManager] + C --> E + D --> F + end + + subgraph Game["Game Layer"] + E -->|Button State| G[Game Code] + F -->|Touch Events| H[Scene] + H -->|UI| I[UIManager] + H -->|Unconsumed| J[onUnconsumedTouchEvent] + end +``` + ### Without Engine Integration (`PIXELROOT32_ENABLE_TOUCH=0`) ```text @@ -239,6 +261,6 @@ On Native (PC), `SDL2_Drawer` automatically converts mouse events to touch event ## 11. Related documentation -- [Touch system API](/api/input/touch-system) -- [Architecture overview](/architecture/overview) (layer diagram and subsystem context) +- [API Reference — Touch Input](../api/input.md) +- [Architecture Index](architecture-index.md) (system layer documentation) - `include/core/Engine.h` — documented touch loop contract in class comment diff --git a/examples/animated-tilemap.md b/examples/animated-tilemap.md index f52824b..7153765 100644 --- a/examples/animated-tilemap.md +++ b/examples/animated-tilemap.md @@ -1,11 +1,71 @@ -# Animated tilemap (`animated_tilemap`) +# Animated Tilemap Example -Sample project: **[`examples/animated_tilemap/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap)** — tile animation, palettes, static tilemap framebuffer cache (**reference depth** for README structure in other samples). +Demonstrates **4bpp** animated tilemaps, multi-palette setup (`ColorPaletteManager`), optional **scene arena**, and the engine **`StaticTilemapLayerCache`** when the driver exposes a direct logical 8bpp sprite buffer (typical **ESP32 + TFT_eSPI**). -Environments: `native`, `esp32dev`, `esp32cyd`. +## Requirements (build flags) -Authoritative details: [`examples/animated_tilemap/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/animated_tilemap/README.md). +- **`PIXELROOT32_ENABLE_TILE_ANIMATIONS`** +- **`PIXELROOT32_ENABLE_4BPP_SPRITES`** (this scene’s tilemaps are 4bpp) +- **`PIXELROOT32_ENABLE_2BPP_SPRITES`** — also enabled in **`platformio.ini`** so the packed sprite/tile driver configuration matches other multi-bpp samples. -- Guide: [Tilemaps](/guide/tilemaps), [ARCH tile animation](/architecture/ARCH_TILE_ANIMATION) +See **`platformio.ini`** in this folder for **`native`**, **`esp32dev`**, and **`esp32cyd`** presets. **`esp32dev`** and **`esp32cyd`** enable **`PIXELROOT32_ENABLE_PROFILING`** (serial heartbeat: Update / Draw / Present); **`esp32dev`** omits the debug overlay so FPS numbers are closer to real draw cost. Touch remains **disabled** for this example (`TOUCH_CS=-1`). -See also [`metroidvania`](./metroidvania) and the [tilemap overview](./tilemap-scene). +## How this scene uses the engine cache + +Snapshot logic is **`pixelroot32::graphics::StaticTilemapLayerCache`** (`graphics/StaticTilemapLayerCache.h`), not hand-rolled `memcpy` in the scene. + +1. **`init()`**: `tilemapLayerCache.clear()`, then after level/palette setup **`invalidate()`** and **`allocateForRenderer(engine.getRenderer())`** (reserves **W×H** bytes once; `false` → full-draw fallback, no crash). +2. **`draw()`**: builds **`TileMap4bppDrawSpec`** arrays — **background** = **static** (snapshot), **ground + details** = **dynamic** (both use **`animManager`**) — and passes camera samples **`-renderer.getXOffset()`** / **`-renderer.getYOffset()`**, then **`Scene::draw(renderer)`**. +3. **`invalidateStaticLayerCache()`** forwards to **`tilemapLayerCache.invalidate()`**. + +**Global `Engine`:** `AnimatedTilemapScene.cpp` expects **`extern pixelroot32::core::Engine engine`** (provided by your platform file, e.g. **`platforms/esp32_dev.h`** / **`native.h`** via **`main.cpp`**). + +## Invalidation and performance (important) + +| Situation | What to do | +|-----------|------------| +| Something in the **static** group must change visually (tiles, palette, mask, or **`step(deltaTime)`** on an animator bound to **background** only in this split) | Call **`invalidateStaticLayerCache()`** (or the fast path can show stale pixels). | +| **Ground** and **details** animate (dynamic group) | No per-frame **`invalidate()`** needed; they are redrawn every frame after **`memcpy`** restore. | +| **Ground** is in the **static** group **and** you **`step(deltaTime)`** its animator every frame | Either **invalidate every frame** or move **ground** to **dynamic** — this example uses the latter. | +| Camera / scroll only | Rebuild follows offset samples; no invalidation needed **only** for scroll. | +| **`getSpriteBuffer()`** is **`nullptr`** (e.g. some native paths) | All layers drawn every frame; snapshot unused. | + +**Current example code:** `update()` steps both animators; **`draw()`** keeps **background** in the static group and **ground + details** in the dynamic group so the ESP32 fast path can **`memcpy`** restore the snapshot and only raster the animated layers (until the camera offset changes, which forces a rebuild). + +### Opción A: omitir `draw` + `present` sin cambio visual + +La escena implementa **`Scene::shouldRedrawFramebuffer()`**: combina **`TileAnimationManager::getVisualSignature()`** (ground + details) y el offset de cámara del **`Renderer`**. Si coincide con el último frame presentado **y** no hay entidades en la escena, el **`Engine`** no llama a **`draw()`** ni **`present()`** ese tick (ahorra casi todo el coste SPI en los ticks entre avances de frame de animación). Con **`PIXELROOT32_ENABLE_DEBUG_OVERLAY`** el motor siempre redibuja. + +## Disabling the snapshot + +- **Compile-time:** `-DPIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE=0` (default is **`1`** in **`PlatformDefaults.h`**). Saves ~**W×H** bytes RAM. +- **Runtime:** call **`setFramebufferCacheEnabled(false)`** on your **`StaticTilemapLayerCache`** instance. This example keeps the cache **private**; add a small public wrapper on **`AnimatedTilemapScene`** if you need toggling from game code. + +## Documentation links + +- API detail: [Graphics module — `StaticTilemapLayerCache`](/api/graphics#multi-layer-4bpp-tilemap-framebuffer-snapshot-statictilemaplayercache) +- Pipeline / layering: [Architecture — ESP32 rendering and tilemap caching](/architecture/architecture-index.md#esp32-rendering-pipeline-and-tilemap-caching) +- Static cache concept: [Architecture — Static tilemap layer cache](/architecture/architecture-index.md#static-tilemap-layer-cache-engine--scenes) + +## Features + +- Animated tilemaps + **`TileAnimationManager`** +- Multi-palette / **`ColorPaletteManager`** +- Optional **`PIXELROOT32_ENABLE_SCENE_ARENA`** in this example’s **`platformio.ini`** +- 2bpp/4bpp build flags enabled for the driver stack; layer data here is **4bpp** +- Engine **`StaticTilemapLayerCache`** (~**W×H** RAM when allocated and flag enabled) + +## Build + +Run from **`examples/animated_tilemap`** (this directory): + +```bash +# Native (SDL2) — paths in platformio.ini may need local SDL include/lib on Windows +pio run -e native + +# ESP32 (ST7789 240×240) +pio run -e esp32dev + +# ESP32 CYD-style (ILI9341 240×320) +pio run -e esp32cyd +``` diff --git a/examples/audio-playback.md b/examples/audio-playback.md index 9369f55..58e9ab1 100644 --- a/examples/audio-playback.md +++ b/examples/audio-playback.md @@ -1,15 +1,84 @@ -# Audio samples (`snake`, `tic_tac_toe`, `music_demo`) +> **Note:** Multi-track / `MusicPlayer` focus; for `AudioEngine` SFX see [Snake](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/snake) and [Tic Tac Toe](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe). -**`flappy_bird` does not use the audio stack** — it is a physics + small-OLED rendering sample only. For **`AudioEngine`**, procedural events, and platform backends, use **`snake`**. For **single-track** music (`MusicPlayer` / note data), use **`tic_tac_toe`** (see `src/assets/music.h` in that project). For **multi-track** arrangements, **BPM**, and **percussion presets** (`INSTR_KICK` / `SNARE` / `HIHAT`), use **`music_demo`**. +# Music Demo Example -| Sample | Audio focus | -|--------|-------------| -| [`snake`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/snake) | **`AudioEvent`** / `playEvent`, SDL2 vs ESP32 I2S (or DAC) backends — [Snake doc](./snake) | -| [`tic_tac_toe`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe) | **`MusicPlayer`**, melody data, engine constructed with **`AudioConfig`** — [UI / tic-tac-toe doc](./ui-layout) | -| [`music_demo`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo) | **`MusicPlayer`** **multi-track** (`secondVoice` / `thirdVoice` / `percussion`), **`setBPM`**, instrument + drum presets | +Interactive **audio** demo showcasing the **PixelRoot32 audio subsystem** with **instrument presets** and **pre-composed multi-part music**. Features a **UI-based interface** for testing sound presets and playing layered `MusicTrack` arrangements. -Guide: [Music player](/guide/music-player). API: [AudioEngine](/api/audio/audio-engine), [MusicPlayer](/api/audio/music-player). +When **`PIXELROOT32_ENABLE_UI_SYSTEM`** is on (default in [`PlatformDefaults.h`](../../include/platforms/PlatformDefaults.h)), the demo exposes **touch UI**: **`UIButton`** for instrument preset selection and melody playback, **`UILabel`** for navigation hints, and **`UIVerticalLayout`** for organized button placement — see [`MusicDemoScene.h`](src/MusicDemoScene.h). -Physics on OLED without audio: [`flappy_bird`](./flappy-bird). +## Requirements (build flags) -See the [samples index](./demos). +- **`PIXELROOT32_ENABLE_AUDIO`** — required for audio subsystem and music playback +- **`PIXELROOT32_ENABLE_UI_SYSTEM`** — required for UI widgets (buttons, labels, layouts) +- **`PIXELROOT32_ENABLE_TOUCH=1`** — set for **`native`** in `platformio.ini` so touch APIs compile and mouse/touch can drive the demo + +See **`platformio.ini`** for **`native`** and **`esp32dev`**. + +## Platforms + +| Environment | Display / input | +|-------------|-----------------| +| **`native`** | SDL2, 240×240; touch flag enables the same code paths with **simulated** touch | +| **`esp32dev`** | **ST7789** 240×240 (use keyboard/GPIO per your `platforms/esp32_dev.h`) | + +## Controls / interaction + +- **Navigate menus** — use your **`InputManager`** layout to move between UI buttons (see scene `update` / button handling) +- **Select / Play** — trigger button callbacks to play instrument sounds or melodies +- **Same melody again** — pressing the **currently playing** melody button **stops** playback (toggle) +- **Back** — **`B`** stops music if playing, then returns to the previous menu level + +## Melody assets (`src/assets/`) + +Tracks are split per theme; shared beat constants and demo-only **`InstrumentPreset`** overrides live in **`common_melodies.h`**: + +| File | Role | +|------|------| +| [`common_melodies.h`](src/assets/common_melodies.h) | Beat fractions (`S`/`E`/`Q`/…), `kDemoArcadeLeadWave` / `kDemoAdventureLeadWave`, **`DEMO_SNES_LEAD_TIGHT`** / **`DEMO_SNES_BASS_STAC`** (tighter ADSR for SNES-style arranging), **`ARP_STEP`** | +| [`classic_arcade_melody.h`](src/assets/classic_arcade_melody.h) | **Melody 1** — Classic Arcade (`sClassicArcadeTrack`) | +| [`adventure_melody.h`](src/assets/adventure_melody.h) | **Melody 2** — Adventure (`sAdventureTrack`) | +| [`action_melody.h`](src/assets/action_melody.h) | **Melody 3** — Action (`sActionTrack`) | +| [`arpeggio_melody.h`](src/assets/arpeggio_melody.h) | **Melody 4** — Em arpeggio demo (`sArpDemoTrack`) | + +Each full arrangement uses **`MusicTrack`** layering: **main** + optional **`secondVoice`**, **`thirdVoice`**, and **`percussion`**, flattened by `MusicPlayer` into the global voice pool (**`ApuCore::MAX_VOICES`** = 8). The headers comment on keeping harmony/percussion notes relatively short so **SFX** can share the pool without constant stealing. + +## Melodies (UI labels vs. engine) + +| UI button | BPM (see `MusicDemoScene::playMelody`) | Loop length (beats) | Layers (summary) | +|-----------|----------------------------------------|---------------------|------------------| +| **Melody 1** | 140 | 32 | **SAW** lead (`kDemoArcadeLeadWave`), bass (`DEMO_SNES_BASS_STAC`), pulse harmony stabs, noise drums (two groove blocks + fill) | +| **Melody 2** | 125 | 64 | **SINE** lead (`kDemoAdventureLeadWave`), bass, harmony, drums (extended **A \| B \| A′ \| C**-style material) | +| **Melody 3** | 160 | 32 | **PULSE** lead via **`DEMO_SNES_LEAD_TIGHT`** (16th-style arpeggio macros), matching bass, sparse harmony hits, dense 16th-hat drums + break | +| **Melody 4 + ARP voice** | 145 | 32 | **SAW** lead (`kDemoArcadeLeadWave`), **`secondVoice`**: fast Em arpeggio (`INSTR_TRIANGLE_LEAD`, **`WaveType::SINE`** on the sub-track), **`thirdVoice`**: bass, **same drum grid as Melody 1** for a stable loop | + +## Features + +- **10 engine instrument presets** (one-shot tests) — Lead Square, Harmony Square, Bass Triangle, Kick, Snare, Hi-hat, Triangle Lead, Triangle Pad, Pulse Pad, Pulse Bass +- **4 multi-part demo tracks** — layered loops with distinct BPM and form (see table above) +- **Melody 4** — full **four-part** demo: lead + arpeggiated **`secondVoice`** + bass + percussion (not just lead + arp) +- **Audio Lab menu** — **pulse frequency sweep** (Phase A), **SINE / SAW chord** one-shots (Phase B), and **master bitcrush** cycling via `AudioEngine::setMasterBitcrush` +- **UI-based sound testing** — play individual instrument sounds on demand +- **Modular audio architecture** — demonstrates `InstrumentPreset`, per-demo preset tweaks, melody sequencing, `AudioEvent` sweep fields, and audio scheduling + +## Documentation links + +- [Audio API](/api/audio) +- [Music player guide](/guide/music-player-guide.md) +- [Input API](/api/input) +- [UI API](/api/ui) +- [Core — Scene](/api/core) + +## Build + +From **`examples/music_demo`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/basic-usage.md b/examples/basic-usage.md index 5255bdc..34b7fb7 100644 --- a/examples/basic-usage.md +++ b/examples/basic-usage.md @@ -1,8 +1,6 @@ # Entities & scene tutorial -::: danger Not an `examples/` project -This page is a **didactic walkthrough** (bouncing entities, `Entity` subclass, scene wiring). There is **no** matching folder under [`examples/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples) with this code. For a **real, minimal** PlatformIO project, start with **[`hello_world`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/hello_world)** and the [Hello World doc](./hello-world). -::: +> **Not an `examples/` project** — This page is a **didactic walkthrough** (bouncing entities, `Entity` subclass, scene wiring). There is **no** matching folder under [`examples/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples) with this code. For a **real, minimal** PlatformIO project, start with [`hello_world`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/hello_world) and its README. The snippets below illustrate fundamentals: scene lifecycle, custom **`Entity`** subclasses, input, and drawing — useful once you have already opened **`hello_world`** in the repo. @@ -350,7 +348,7 @@ void onUnconsumedTouchEvent(const input::TouchEvent& event) override { ## Next Steps -- **[Samples index](./demos)** — Real folders under `examples/` -- **[Physics (`physics`)](./physics-demo)** — Collision and actors -- **[Sprites (`sprites`)](./sprite-animation)** — Sprite graphics -- **[Audio (`snake`, `tic_tac_toe`)](./audio-playback)** — `AudioEngine` events vs `MusicPlayer` melody +- **[Samples index](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples)** — Real folders under `examples/` +- **[Physics (`physics`)](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/physics)** — Collision and actors +- **[Sprites (`sprites`)](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/sprites)** — Sprite graphics +- **Audio** — See [`snake`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/snake), [`tic_tac_toe`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe), [`music_demo`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo) diff --git a/examples/brick-breaker.md b/examples/brick-breaker.md index c5dfec5..49edf40 100644 --- a/examples/brick-breaker.md +++ b/examples/brick-breaker.md @@ -1,11 +1,64 @@ -# Brick Breaker (`brick_breaker`) +# Brick Breaker Example -Sample project: **[`examples/brick_breaker/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/brick_breaker)** — classic Breakout: paddle, ball physics, destructible brick grid, particles, and **`AudioEngine`** + **`MusicPlayer`**. +Classic **Breakout** arcade game: paddle with ball physics, destructible brick grid, particle effects on destruction, multiple lives, progressive levels, and **procedural audio** through the engine **`AudioEngine`** and **`MusicPlayer`**. -Environments: `native`, `esp32dev`. +## Requirements (build flags) -Authoritative details: [`examples/brick_breaker/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/brick_breaker/README.md). +- **`PIXELROOT32_ENABLE_AUDIO=1`** — set in [`lib/platformio.ini`](lib/platformio.ini) `base` template so all environments inherit it. +- **`PIXELROOT32_ENABLE_PHYSICS=1`** — required for ball collision detection. +- **`PIXELROOT32_ENABLE_PARTICLES=1`** — required for brick destruction effects. +- **`PIXELROOT32_ENABLE_UI_SYSTEM=1`** — required for score/lives display. -- API: [AudioEngine](/api/audio/audio-engine), [Physics](/api/physics/collision-system), [Particles](/api/modules/configuration) +Display size is **240×240** in the project `platformio.ini` (see **`PHYSICAL_DISPLAY_*`**). -See the [samples index](./demos). +## Platforms + +| Environment | Display | Audio backend | +|-------------|---------|---------------| +| **`native`** | SDL2, 240×240 | **`SDL2_AudioBackend`** in [`src/platforms/native.h`](src/platforms/native.h) | +| **`esp32dev`** | **ST7789** 240×240 | Default: **`ESP32_I2S_AudioBackend`** (comment in `esp32_dev.h` documents optional internal **DAC** backend instead) | + +Pin choices for I2S / DAC are in **`src/platforms/esp32_dev.h`** (edit there if your wiring differs). + +## Controls + +- **Arrow keys** (or GPIO D-pad mapped in your platform input config) to move paddle left/right. +- **Start/Enter** to launch ball (from attached position) and start game. +- **Start/Enter** to restart after game over. + +## How audio is triggered + +- **SFX**: [`GameConstants.h`](src/GameConstants.h) defines `sfx::` namespace with `AudioEvent` values (paddle hit, wall hit, brick hit, life lost, start game) using pulse wave tones. +- **BGM**: [`BrickBreakerScene.cpp`](src/BrickBreakerScene.cpp) sets up an Atari-style 4-note melody loop using **`MusicPlayer`** with triangle wave. + +## Features + +- **Scene** with pooled **`PaddleActor`**, **`BallActor`**, **`BrickActor`** +- **Ball attaches to paddle** until player launches (Breakout-style) +- **3 lives** system with visual indicators +- **Progressive levels** — more rows and HP per brick as level increases +- **Particle effects** via `ParticleEmitter` (explosion preset) +- **Audio** — SFX synthesis + BGM loop via `MusicPlayer` +- **Score** system displayed on screen + +## Documentation links + +- [Audio API](/api/audio) +- [Core API](/api/core) +- [Input API](/api/input) +- [Particles API](/api/graphics#particle-system) + +## Build + +From **`examples/brick_breaker`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/camera.md b/examples/camera.md index 81dbc67..86a787f 100644 --- a/examples/camera.md +++ b/examples/camera.md @@ -1,12 +1,54 @@ -# Camera (`camera`) +# Camera Demo Example -Sample project: **[`examples/camera/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/camera)** — **`Camera2D`**, parallax, tile platforms, **`KinematicActor`**. +Side-scrolling platformer-style demo that showcases **`Camera2D`** (smoothing and horizontal bounds), **parallax background layers**, and **`KinematicActor`** movement with tile-based ground and one-way platforms (`StaticActor`, collision layers). The world is wider than the screen so the camera follows the player. -Environments: `native`, `esp32dev`. +## Requirements (build flags) -Authoritative details: [`examples/camera/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/camera/README.md). +- **`PIXELROOT32_ENABLE_SCENE_ARENA`** -- Guides: [Rendering](/guide/rendering) (camera usage is exercised in this sample’s sources) -- API: [Renderer](/api/graphics/renderer), [KinematicActor](/api/physics/kinematic-actor) +Additional engine features (physics actors, tilemaps) follow defaults from [`PlatformDefaults.h`](../../include/platforms/PlatformDefaults.h). See **`platformio.ini`** in this folder for `native` and **`esp32dev`** presets. -See the [samples index](./demos). +The scene expects **`extern pixelroot32::core::Engine engine`** (see `src/platforms/native.h` / `esp32_dev.h` and `main.cpp`). + +## Platforms + +| Environment | Notes | +|-------------|--------| +| **`native`** | SDL2 window, 240×240 logical size (paths to SDL on Windows may need adjustment in `platformio.ini`). | +| **`esp32dev`** | **ST7789** TFT 240×240, TFT_eSPI-style pin defines in `platformio.ini`. | + +The engine version or Git branch is set in **`lib_deps`** in `platformio.ini`. + +## Controls + +- **Left / Right** — move (buttons **2** and **3** in `InputManager` order). +- **Jump** — button **4** (edge-triggered after release so hold does not spam jump). + +## Features + +- **`Camera2D`**: follow target, bounds, locked vertical scroll in this demo +- **Parallax** layers (`GameLayers`) + tilemap strip for ground/platforms +- **`KinematicActor`** player cube (`PlayerCube`), gravity and one-way platform collision masks +- **Scene arena** for stable entity storage + +## Documentation links + +- [Graphics — Camera2D](/api/graphics#camera2d) +- [Core — Scene / entities](/api/core) +- [Physics — kinematic & static actors](/api/physics) +- [Architecture](/architecture/architecture-index.md) + +## Build + +Run from **`examples/camera`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/demos.md b/examples/demos.md index dc86cf5..3f2ca94 100644 --- a/examples/demos.md +++ b/examples/demos.md @@ -1,58 +1,53 @@ # Engine sample projects -Official examples live in the [PixelRoot32 Game Engine](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine) repository under [`examples/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples). Each folder is a **self-contained [PlatformIO](https://platformio.org/)** project: its own `platformio.ini`, `src/` entry point, and `README.md` (build flags, environments, doc links). +# PixelRoot32 — Examples -**Typical workflow:** open the project folder in PlatformIO (or run CLI from that folder), pick an environment (`native`, `esp32dev`, etc.): +Self-contained **[PlatformIO](https://platformio.org/)** projects that show how to use the engine on **PC (SDL2)** and **ESP32-class boards**. Each folder has its own **`platformio.ini`**, **`src/`** entry point, and **`README.md`** with build flags, supported environments, and documentation links. + +**Typical workflow:** open a project folder in PlatformIO (or run CLI from that folder), pick an environment (`native`, `esp32dev`, etc.), then: ```bash cd pio run -e ``` -On Windows, **`native`** builds may need local **SDL2** include/lib paths in `platformio.ini` (see comments in [`animated_tilemap`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap)). +On Windows, **`native`** examples may need local **SDL2** include/lib paths in `platformio.ini` (see comments in [animated_tilemap](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap)). -The engine revision for each sample is pinned in **`lib_deps`** in that folder’s `platformio.ini` (registry tag vs Git branch). +The engine revision for each example is defined in **`lib_deps`** inside that example’s `platformio.ini` (registry tag vs Git branch). -## Catalogue (same as `examples/README.md` in the engine repo) +## Catalogue | Example | What it demonstrates | PlatformIO environments | |--------|----------------------|-------------------------| -| [`hello_world`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/hello_world) | Minimal `Scene`, `UILabel`, button input, background color cycle | `native`, `esp32dev` | -| [`camera`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/camera) | `Camera2D`, parallax, tile platforms, `KinematicActor` | `native`, `esp32dev` | -| [`dual_palette`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/dual_palette) | Dual palette mode (background vs sprite color tables) | `native`, `esp32dev` | -| [`sprites`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/sprites) | 2bpp / 4bpp sprites and animation | `native`, `esp32dev` | -| [`snake`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/snake) | Grid game, segment pool, `AudioEngine` + platform audio backends | `native`, `esp32dev` | -| [`brick_breaker`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/brick_breaker) | Classic Breakout: paddle, ball physics, bricks, particles, `AudioEngine` + `MusicPlayer` | `native`, `esp32dev` | -| [`music_demo`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo) | **`MusicPlayer`** **multi-track** (main + sub-tracks), **tick / BPM** timing, **`InstrumentPreset`** melodies + **percussion** presets | `native`, `esp32dev` | -| [`space_invaders`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/space_invaders) | Classic Space Invaders: alien formation, shooting, bunkers, score system, `AudioEngine` | `native`, `esp32dev` | -| [`physics`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/physics) | `RigidActor` / `KinematicActor` / `StaticActor`, touch, optional touch UI (CYD) | `native`, `esp32dev`, `esp32cyd` | -| [`metroidvania`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/metroidvania) | 4bpp tilemaps, `StaticTilemapLayerCache`, platformer player | `native`, `esp32dev` | -| [`animated_tilemap`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap) | Tile animation, palettes, static tilemap framebuffer cache (reference depth) | `native`, `esp32dev`, `esp32cyd` | -| [`tic_tac_toe`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe) | UI, GPIO vs touch, minimax AI, vector board, **`MusicPlayer`** / melody data | `native`, `esp32dev`, `esp32cyd` | -| [`flappy_bird`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/flappy_bird) | Physics flappy clone, U8g2 OLED, ESP32-C3 (**no audio** in this sample) | `native`, `esp32c3` | - -## Suggested learning order (from the engine repo) - -1. **`hello_world`** — engine init, one scene, text and input. -2. **`sprites`** or **`dual_palette`** — graphics and color models. -3. **`camera`** or **`metroidvania`** / **`animated_tilemap`** — scrolling, tilemaps, caching (read **`animated_tilemap`** for the fullest tilemap write-up). -4. **`physics`** — bodies, sensors, touch. -5. **`snake`** / **`tic_tac_toe`** / **`brick_breaker`** / **`music_demo`** / **`space_invaders`** — **audio** (events, single-track music, or **multi-track** reference). **`flappy_bird`** — physics + OLED, no audio subsystem. - -## README template in the repo - -Per-example README depth follows **[`animated_tilemap` / README.md](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/animated_tilemap/README.md)** as reference: summary, **Requirements (build flags)**, optional technical notes, **Documentation links**, **Features**, **Build** commands. Scene intent is also described in each `src/*Scene.h` when present. - -## Site pages (walkthroughs, not copies of `examples/`) - -These pages explain topics and point at real folders above; they are **not** guaranteed to be line-for-line copies of `src/` (except where noted): - -- [Entities & scene tutorial](./basic-usage) — didactic composite; **not** a folder under `examples/`. -- [Hello World](./hello-world) — aligns with **`hello_world`** (minimal sample). -- Topic pointers: [Sprites](./sprite-animation), [Physics](./physics-demo), [Tilemaps](./tilemap-scene), [Audio](./audio-playback) (`snake`, `tic_tac_toe`, `brick_breaker`, **`music_demo`**), [UI](./ui-layout) (`tic_tac_toe`), [Flappy Bird](./flappy-bird) (physics, no audio). -- Full Game walkthroughs: [Brick Breaker](./brick-breaker), [Space Invaders](./space-invaders), [Tic Tac Toe](./tic-tac-toe). - -## Engine documentation on this site - -- [API home](/api/) -- [Architecture overview](/architecture/overview) +| [hello_world](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/hello_world) | Minimal `Scene`, `UILabel`, button input, background color cycle | `native`, `esp32dev` , `esp32s3` | +| [camera](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/camera) | `Camera2D`, parallax, tile platforms, `KinematicActor` | `native`, `esp32dev` | +| [dual_palette](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/dual_palette) | Dual palette mode (background vs sprite color tables) | `native`, `esp32dev` | +| [sprites](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/sprites) | 2bpp / 4bpp sprites and animation | `native`, `esp32dev` | +| [snake](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/snake) | Grid game, segment pool, `AudioEngine` + platform audio backends | `native`, `esp32dev` | +| [brick_breaker](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/brick_breaker) | Classic Breakout: paddle, ball physics, bricks, particles, `AudioEngine` + `MusicPlayer` | `native`, `esp32dev` | +| [music_demo](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo) | **`MusicPlayer`** **multi-track** (main + sub-tracks), **tick / BPM** timing, **`InstrumentPreset`** melodies + **percussion** presets; UI-based sound testing | `native`, `esp32dev` | +| [space_invaders](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/space_invaders) | Classic Space Invaders: alien formation, shooting, bunkers, score system, `AudioEngine` | `native`, `esp32dev` | +| [physics](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/physics) | `RigidActor` / `KinematicActor` / `StaticActor`, touch, optional touch UI (CYD) | `native`, `esp32dev`, `esp32cyd` | +| [metroidvania](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/metroidvania) | 4bpp tilemaps, `StaticTilemapLayerCache`, platformer player | `native`, `esp32dev` | +| [animated_tilemap](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap) | Tile animation, palettes, static tilemap framebuffer cache (reference depth) | `native`, `esp32dev`, `esp32cyd` | +| [tic_tac_toe](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe) | UI, GPIO vs touch, minimax AI, vector-drawn board, **`MusicPlayer`** / melody data | `native`, `esp32dev`, `esp32cyd` | +| [flappy_bird](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/flappy_bird) | Physics flappy clone, U8g2 OLED, ESP32-C3 (**no audio** in this sample) | `native`, `esp32c3` | + + +## Suggested learning order + +1. **hello_world** — engine init, one scene, text and input. +2. **sprites** or **dual_palette** — graphics and color models. +3. **camera** or **metroidvania** / **animated_tilemap** — scrolling, tilemaps, caching (read **animated_tilemap** for the fullest tilemap write-up). +4. **physics** — bodies, sensors, touch. +5. **snake** / **tic_tac_toe** / **brick_breaker** / **music_demo** / **space_invaders** — **audio** (events, single-track music, or **multi-track** reference). **flappy_bird** — physics + OLED, no audio subsystem. + +## Engine documentation + +- [API reference index](/api/) +- [Architecture](/architecture/architecture-index.md) +- Module docs under [`docs/api/`](/api/) (Graphics, Physics, UI, Input, Audio, Core, …) + +## Format reference for per-example READMEs + +The **[animated_tilemap](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap)** example is the template for depth: opening summary, **Requirements (build flags)**, optional technical subsection, **Documentation links**, **Features**, and **Build** commands. Scene intent is also described in each `src/*Scene.h` file. diff --git a/examples/dual-palette.md b/examples/dual-palette.md index 739cc88..2965a57 100644 --- a/examples/dual-palette.md +++ b/examples/dual-palette.md @@ -1,11 +1,46 @@ -# Dual palette (`dual_palette`) +# Dual Palette Example -Sample project: **[`examples/dual_palette/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/dual_palette)** — separate color tables for background vs sprites. +Demonstrates **dual palette mode**: the **background** and **sprite** paths can use **different color RAM tables**, so the same color **index** maps to **different RGB** on each layer (in this demo, NES-style colors on the background and Game Boy–style colors on sprites, per the scene comments). -Environments: `native`, `esp32dev`. +Runtime entry point calls **`pixelroot32::graphics::enableDualPaletteMode(true)`** in `DualPaletteTestScene::init()` (see [`src/DualPaletteTestScene.cpp`](src/DualPaletteTestScene.cpp)). -Authoritative details: [`examples/dual_palette/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/dual_palette/README.md). +## Requirements (build flags) -- Guide: [Multi-palette](/guide/multi-palette) +- **`PIXELROOT32_ENABLE_SCENE_ARENA`** -See the [samples index](./demos). +See **`platformio.ini`** for **`native`** and **`esp32dev`**. Dual palette itself is toggled in code, not an extra `-D` in this project. + +**`extern pixelroot32::core::Engine engine`** is provided by `src/platforms/native.h` or `esp32_dev.h`. + +## Platforms + +| Environment | Display | +|-------------|---------| +| **`native`** | SDL2, 240×240 | +| **`esp32dev`** | **ST7789** 240×240 (pins in `platformio.ini`) | + +## Features + +- **`Scene`** with layered entities: full-screen **background** shapes + **8×8 1bpp-style** test sprites +- **Dual palette** API (`enableDualPaletteMode`) +- **Scene arena** enabled for allocation patterns consistent with other advanced examples + +## Documentation links + +- [Graphics API](/api/graphics) +- [Architecture](/architecture/architecture-index.md) + +## Build + +From **`examples/dual_palette`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/flappy-bird.md b/examples/flappy-bird.md index 02a614a..d2aec89 100644 --- a/examples/flappy-bird.md +++ b/examples/flappy-bird.md @@ -1,13 +1,54 @@ -# Flappy Bird (`flappy_bird`) +# Flappy Bird Example -Sample project: **[`examples/flappy_bird/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/flappy_bird)** — **`RigidActor`** bird, **`KinematicActor`** pipes, recycling pool, **U8g2** on **128×64** OLED, **ESP32-C3** preset. +A **Flappy Bird**–style game: bird is a **`RigidActor`** (gravity + flap impulse), pipes are **`KinematicActor`** pairs that scroll and **recycle** when off-screen. Score and game states (**waiting / playing / game over**) are handled in [`FlappyBirdScene`](src/FlappyBirdScene.h). -**No `AudioEngine` / music in this sample** — for audio, see [`snake`](./snake) and [`tic_tac_toe`](./ui-layout); overview: [Audio samples](./audio-playback). +## Requirements (build flags) -Environments: `native`, `esp32c3`. +- **`PIXELROOT32_ENABLE_PHYSICS=1`** — required on **`esp32c3`** in `platformio.ini`. +- **`PIXELROOT32_ENABLE_PROFILING`** — enabled on the **`esp32c3`** environment in this project (optional for learning builds). +- **U8g2 path (hardware)**: **`PIXELROOT32_USE_U8G2`**, **`PIXELROOT32_NO_TFT_ESPI`** on **`esp32c3`**. -Authoritative details: [`examples/flappy_bird/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/flappy_bird/README.md). +The **logical framebuffer** is **72×40** pixels, centered in a **128×64** physical OLED via **`X_OFF_SET`**, **`Y_OFF_SET`**, **`LOGICAL_WIDTH`**, **`LOGICAL_HEIGHT`** in `platformio.ini`. -- API: [RigidActor](/api/physics/rigid-actor), [KinematicActor](/api/physics/kinematic-actor) +## Platforms -See the [samples index](./demos). +| Environment | Target | +|-------------|--------| +| **`native`** | SDL2 window sized for the same logical resolution (offsets in `platformio.ini`) | +| **`esp32c3`** | **DFRobot Beetle ESP32-C3** (`board = dfrobot_beetle_esp32c3`) with **U8g2** display (no TFT_eSPI on this preset) | + +This example does **not** ship an `esp32dev` TFT environment — only **`native`** + **`esp32c3`**. + +## Controls + +- **Action / Jump** — button **0** (`FlappyBirdConstants` / scene input) to flap when running. +- Avoid pipes and the top/bottom bounds; pass gaps to increase score. + +## Features + +- **Physics** actors for bird and pipes +- **Object pool–style** pipe reuse +- **Small-resolution** rendering path suited for **128×64 OLED** via U8g2 + +## Documentation links + +- [Physics API](/api/physics) +- [Core API](/api/core) +- [Platform / drivers](/api/platform) + +## Build + +From **`examples/flappy_bird`**: + +```bash +pio run -e native +pio run -e esp32c3 +``` + +## Upload (ESP32-C3) + +```bash +pio run -e esp32c3 --target upload +``` + +Wire your OLED according to the U8g2 configuration used in this project’s platform header / driver setup. diff --git a/examples/hello-world.md b/examples/hello-world.md index 1f82929..9d3e461 100644 --- a/examples/hello-world.md +++ b/examples/hello-world.md @@ -1,29 +1,54 @@ -# Hello World (`hello_world`) +# Hello World Example -This page tracks the **`examples/hello_world/`** sample in the engine repository — a **minimal** boot check: `UILabel`, button polling via **`InputManager`**, and a **background color** cycle. Display is **128×128** (see that folder’s `platformio.ini`). +Minimal PixelRoot32 project: **`UILabel`** text, **button polling** through **`InputManager`**, and a **background color** that cycles every few frames. Intended as the smallest “engine boots → scene draws → input works” check. -::: warning Not the “full UI demo” tutorial -If you saw a longer walkthrough with `UIButton`, `UIVerticalLayout`, audio, and touch, that was a **composite tutorial** and does **not** match this repo folder. For rich UI patterns see [UI system](/guide/ui-system) and [`tic_tac_toe`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe). -::: +## Requirements (build flags) -::: warning ESP32-S3 builds -If you target **ESP32-S3**, DMA display output can require pinning **Arduino Core 2.0.14** in `platformio.ini` (the sample repo already sets this for S3). If the display freezes after the first frame or you see **`pins_arduino.h` missing** after toolchain changes, see [ESP32-S3 DMA and Arduino Core](/guide/platform-config#esp32-s3-dma-arduino-core) and [Framework cache and pins_arduino.h](/guide/platform-config#framework-cache-pins-arduino). -::: +No special `PIXELROOT32_ENABLE_*` flags beyond what **`lib/platformio.ini`** / defaults pull in. Resolution is set with **`PHYSICAL_DISPLAY_WIDTH`**, **`PHYSICAL_DISPLAY_HEIGHT`** (**128×128**) in **`platformio.ini`**. -## Source layout (repository) +The scene uses **`extern pixelroot32::core::Engine engine`** from your platform file (`src/platforms/native.h` or `esp32_dev.h`) — same pattern as other examples. -- [`src/main.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/hello_world/src/main.cpp) — includes the platform header (`native` / `esp32_dev`). -- [`src/HelloWorldScene.h`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/hello_world/src/HelloWorldScene.h) / [`.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/hello_world/src/HelloWorldScene.cpp) — scene logic and labels. +## Platforms -Authoritative build flags, pinout, and **`pio run`** commands are in **[`examples/hello_world/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/hello_world/README.md)** on GitHub. +| Environment | Display | +|-------------|---------| +| **`native`** | SDL2, 128×128 logical size | +| **`esp32dev`** | **ST7735** 128×128 (GreenTab3 profile), SPI pins in **`platformio.ini`**: MOSI **23**, SCLK **18**, DC **2**, RST **4**, CS **-1** | +| **`esp32s3`** | **ST7735** 128×128 (GreenTab3 profile), SPI pins in **`platformio.ini`**: MOSI **12**, MISO **14**, SCLK **13**, DC **10**, RST **11**, CS **9** | -## APIs to read next +> ⚠️ **Note for ESP32-S3**: The `env:esp32s3` uses Arduino Core **2.0.14** as a workaround for DMA freeze issues (see [espressif/arduino-esp32#9618](https://github.com/espressif/arduino-esp32/issues/9618)). This is configured in `platformio.ini` via `platform_packages`. -- [Engine](/api/core/engine), [Scene](/api/core/scene) -- [Renderer](/api/graphics/renderer), [InputManager](/api/input/input-manager) -- UI: [UILabel](/api/modules/ui) (via UI module docs) -## See also -- [Samples index](./demos) -- [Entities tutorial](./basic-usage) — patterns with `Entity` (separate from this folder) +## Controls + +- **D-pad / face buttons** — any press is shown in the second label (`checkButtonPress()` in [`HelloWorldScene.cpp`](src/HelloWorldScene.cpp)). +- Background advances on a fixed frame interval (`COLOR_CHANGE_INTERVAL`). + +## Features + +- **`Scene`** lifecycle (`init` / `update` / `draw`) +- **`UILabel`** and `Renderer` text/color APIs +- **InputManager** button bitmask + +## Documentation links + +- [Core API](/api/core) +- [UI API](/api/ui) +- [Graphics / Renderer](/api/graphics) + +## Build + +From **`examples/hello_world`**: + +```bash +pio run -e native +pio run -e esp32dev +pio run -e esp32_s3 +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/metroidvania.md b/examples/metroidvania.md index 8373e30..3bf35e2 100644 --- a/examples/metroidvania.md +++ b/examples/metroidvania.md @@ -1,12 +1,59 @@ -# Metroidvania-style map (`metroidvania`) +# Metroidvania-Style Example -Sample project: **[`examples/metroidvania/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/metroidvania)** — 4bpp tilemaps, **`StaticTilemapLayerCache`**, platformer-style player. +A compact **platformer** sample with **4bpp tilemap layers** (background, platforms, decorative tiles), **`StaticTilemapLayerCache`** for the ESP32 fast path when available, and a **`KinematicActor`**-based player with climbing and jump rules tailored to the sample map. -Environments: `native`, `esp32dev`. +**Requires `PIXELROOT32_ENABLE_4BPP_SPRITES`** — the scene is guarded in [`src/MetroidvaniaScene.h`](src/MetroidvaniaScene.h). -Authoritative details: [`examples/metroidvania/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/metroidvania/README.md). +## Requirements (build flags) -- Guide: [Tilemaps](/guide/tilemaps) -- API: [TileMap](/api/graphics/tilemap) +- **`PIXELROOT32_ENABLE_4BPP_SPRITES`** +- **`PIXELROOT32_ENABLE_2BPP_SPRITES`** (enabled alongside 4bpp in this example’s `platformio.ini`) +- **`PIXELROOT32_ENABLE_SCENE_ARENA`** -See also [animated_tilemap](./animated-tilemap) and the [tilemap overview](./tilemap-scene). +See **`platformio.ini`** for **`native`** and **`esp32dev`** presets (no `esp32cyd` environment in this project). + +**`extern pixelroot32::core::Engine engine`** is wired in the platform headers under `src/platforms/`. + +## Platforms + +| Environment | Display | +|-------------|---------| +| **`native`** | SDL2, 240×240 | +| **`esp32dev`** | **ST7789** 240×240 | + +## Controls + +Uses **`GameConstants.h`** button IDs: **Up / Down / Left / Right** and **Jump** (`BTN_UP` … `BTN_JUMP`). Map these to your `InputManager` / GPIO / keyboard mapping for the platform file you use. + +## How this scene uses the tilemap cache + +Like the animated tilemap sample, drawing goes through **`StaticTilemapLayerCache`**: allocate for the renderer when layers are ready, draw static groups with camera offsets, and **`invalidate()`** when static tile data or relevant animators change. See [Animated Tilemap README](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap) for the detailed invalidation table and [Architecture — static tilemap cache](/architecture/architecture-index). + +## Features + +- **4bpp tilemaps** and layered level data +- **`StaticTilemapLayerCache`** snapshot path when the driver exposes a logical framebuffer +- **Player actor** with gravity, stairs/climb behavior, and map collision +- **Scene arena** + owned layer entities + +## Documentation links + +- [Graphics — tilemaps & `StaticTilemapLayerCache`](/api/graphics#multi-layer-4bpp-tilemap-framebuffer-snapshot-statictilemaplayercache) +- [Architecture — ESP32 rendering / tilemap caching](/architecture/architecture-index.md#esp32-rendering-pipeline-and-tilemap-caching) +- [Physics API](/api/physics) +- [Core API](/api/core) + +## Build + +From **`examples/metroidvania`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/music-demo.md b/examples/music-demo.md new file mode 100644 index 0000000..494b56e --- /dev/null +++ b/examples/music-demo.md @@ -0,0 +1,82 @@ +# Music Demo Example + +Interactive **audio** demo showcasing the **PixelRoot32 audio subsystem** with **instrument presets** and **pre-composed multi-part music**. Features a **UI-based interface** for testing sound presets and playing layered `MusicTrack` arrangements. + +When **`PIXELROOT32_ENABLE_UI_SYSTEM`** is on (default in [`PlatformDefaults.h`](../../include/platforms/PlatformDefaults.h)), the demo exposes **touch UI**: **`UIButton`** for instrument preset selection and melody playback, **`UILabel`** for navigation hints, and **`UIVerticalLayout`** for organized button placement — see [`MusicDemoScene.h`](src/MusicDemoScene.h). + +## Requirements (build flags) + +- **`PIXELROOT32_ENABLE_AUDIO`** — required for audio subsystem and music playback +- **`PIXELROOT32_ENABLE_UI_SYSTEM`** — required for UI widgets (buttons, labels, layouts) +- **`PIXELROOT32_ENABLE_TOUCH=1`** — set for **`native`** in `platformio.ini` so touch APIs compile and mouse/touch can drive the demo + +See **`platformio.ini`** for **`native`** and **`esp32dev`**. + +## Platforms + +| Environment | Display / input | +|-------------|-----------------| +| **`native`** | SDL2, 240×240; touch flag enables the same code paths with **simulated** touch | +| **`esp32dev`** | **ST7789** 240×240 (use keyboard/GPIO per your `platforms/esp32_dev.h`) | + +## Controls / interaction + +- **Navigate menus** — use your **`InputManager`** layout to move between UI buttons (see scene `update` / button handling) +- **Select / Play** — trigger button callbacks to play instrument sounds or melodies +- **Same melody again** — pressing the **currently playing** melody button **stops** playback (toggle) +- **Back** — **`B`** stops music if playing, then returns to the previous menu level + +## Melody assets (`src/assets/`) + +Tracks are split per theme; shared beat constants and demo-only **`InstrumentPreset`** overrides live in **`common_melodies.h`**: + +| File | Role | +|------|------| +| [`common_melodies.h`](src/assets/common_melodies.h) | Beat fractions (`S`/`E`/`Q`/…), `kDemoArcadeLeadWave` / `kDemoAdventureLeadWave`, **`DEMO_SNES_LEAD_TIGHT`** / **`DEMO_SNES_BASS_STAC`** (tighter ADSR for SNES-style arranging), **`ARP_STEP`** | +| [`classic_arcade_melody.h`](src/assets/classic_arcade_melody.h) | **Melody 1** — Classic Arcade (`sClassicArcadeTrack`) | +| [`adventure_melody.h`](src/assets/adventure_melody.h) | **Melody 2** — Adventure (`sAdventureTrack`) | +| [`action_melody.h`](src/assets/action_melody.h) | **Melody 3** — Action (`sActionTrack`) | +| [`arpeggio_melody.h`](src/assets/arpeggio_melody.h) | **Melody 4** — Em arpeggio demo (`sArpDemoTrack`) | + +Each full arrangement uses **`MusicTrack`** layering: **main** + optional **`secondVoice`**, **`thirdVoice`**, and **`percussion`**, flattened by `MusicPlayer` into the global voice pool (**`ApuCore::MAX_VOICES`** = 8). The headers comment on keeping harmony/percussion notes relatively short so **SFX** can share the pool without constant stealing. + +## Melodies (UI labels vs. engine) + +| UI button | BPM (see `MusicDemoScene::playMelody`) | Loop length (beats) | Layers (summary) | +|-----------|----------------------------------------|---------------------|------------------| +| **Melody 1** | 140 | 32 | **SAW** lead (`kDemoArcadeLeadWave`), bass (`DEMO_SNES_BASS_STAC`), pulse harmony stabs, noise drums (two groove blocks + fill) | +| **Melody 2** | 125 | 64 | **SINE** lead (`kDemoAdventureLeadWave`), bass, harmony, drums (extended **A \| B \| A′ \| C**-style material) | +| **Melody 3** | 160 | 32 | **PULSE** lead via **`DEMO_SNES_LEAD_TIGHT`** (16th-style arpeggio macros), matching bass, sparse harmony hits, dense 16th-hat drums + break | +| **Melody 4 + ARP voice** | 145 | 32 | **SAW** lead (`kDemoArcadeLeadWave`), **`secondVoice`**: fast Em arpeggio (`INSTR_TRIANGLE_LEAD`, **`WaveType::SINE`** on the sub-track), **`thirdVoice`**: bass, **same drum grid as Melody 1** for a stable loop | + +## Features + +- **10 engine instrument presets** (one-shot tests) — Lead Square, Harmony Square, Bass Triangle, Kick, Snare, Hi-hat, Triangle Lead, Triangle Pad, Pulse Pad, Pulse Bass +- **4 multi-part demo tracks** — layered loops with distinct BPM and form (see table above) +- **Melody 4** — full **four-part** demo: lead + arpeggiated **`secondVoice`** + bass + percussion (not just lead + arp) +- **Audio Lab menu** — **pulse frequency sweep** (Phase A), **SINE / SAW chord** one-shots (Phase B), and **master bitcrush** cycling via `AudioEngine::setMasterBitcrush` +- **UI-based sound testing** — play individual instrument sounds on demand +- **Modular audio architecture** — demonstrates `InstrumentPreset`, per-demo preset tweaks, melody sequencing, `AudioEvent` sweep fields, and audio scheduling + +## Documentation links + +- [Audio API](/api/audio) +- [Music player guide](/guide/music-player-guide.md) +- [Input API](/api/input) +- [UI API](/api/ui) +- [Core — Scene](/api/core) + +## Build + +From **`examples/music_demo`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/physics-demo.md b/examples/physics-demo.md index 1ad1f50..2875db1 100644 --- a/examples/physics-demo.md +++ b/examples/physics-demo.md @@ -1,11 +1,57 @@ -# Physics (`physics`) +# Physics Demo Example -Sample project: **[`examples/physics/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/physics)** — **`RigidActor`**, **`KinematicActor`**, **`StaticActor`**, sensors, touch, optional CYD touch UI. +Interactive **physics** sandbox: **`KinematicActor`** player, **`RigidActor`** boxes and circles (AABB and circle shape), **`StaticActor`** floors/walls, **sensors**, and **`ActorTouchController`** for dragging/spawning on touch-capable builds. -Environments: `native`, `esp32dev`, `esp32cyd`. +When **`PIXELROOT32_ENABLE_UI_SYSTEM`** is on (default in [`PlatformDefaults.h`](../../include/platforms/PlatformDefaults.h)), **`esp32cyd`** builds also expose **touch UI**: **`UITouchButton`** (full reset), **`UITouchSlider`** (dynamic spawn count), **`UITouchCheckbox`**, and horizontal/vertical **layouts** — see [`PhysicsDemoScene.h`](src/PhysicsDemoScene.h). -Authoritative details: [`examples/physics/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/physics/README.md). +## Requirements (build flags) -- API: [CollisionSystem](/api/physics/collision-system), [KinematicActor](/api/physics/kinematic-actor) +- **`PIXELROOT32_ENABLE_SCENE_ARENA`** — pre-allocated box/circle pools and arena-safe add/remove when the slider changes. +- **`PIXELROOT32_ENABLE_TOUCH=1`** — set for **`native`** and **`esp32cyd`** in `platformio.ini` so touch APIs compile and mouse/touch can drive the demo. +- **`esp32cyd`** additionally enables **`PIXELROOT32_ENABLE_DEBUG_OVERLAY`**, **`PIXELROOT32_DEBUG_MODE`**, **ILI9341** 240×320, and **XPT2046** touch (many tuning `-D`s in `platformio.ini`). -See the [samples index](./demos). \ No newline at end of file +See **`platformio.ini`** for **`native`**, **`esp32dev`**, **`esp32cyd`**. + +## Platforms + +| Environment | Display / input | +|-------------|-----------------| +| **`native`** | SDL2, 240×240; touch flag enables the same code paths with **simulated** touch | +| **`esp32dev`** | **ST7789** 240×240 (no `PIXELROOT32_ENABLE_TOUCH` in this preset — use keyboard/GPIO per your `platforms/esp32_dev.h`) | +| **`esp32cyd`** | **ILI9341** 240×320 + **XPT2046** resistive touch | + +## Controls / interaction + +- **Move player** — bind your **`InputManager`** layout to the kinematic actor (see scene `update` / player handling). +- **Touch (native + CYD)** — `processTouchEvents` / `onUnconsumedTouchEvent` and **`ActorTouchController`** for world interaction; CYD also gets on-screen **UI widgets** when `PIXELROOT32_ENABLE_UI_SYSTEM` is true. + +## Features + +- **Rigid** dynamics (restitution, friction), **circle vs AABB** collision shape +- **Static** scenery with bounce flag on walls +- **Sensor-style** regions (as wired in the demo scene) +- **Optional touch HUD** (slider adjusts how many boxes/circles are registered without re-running `init()` from scratch) + +## Documentation links + +- [Physics API](/api/physics) +- [Input API](/api/input) +- [UI API](/api/ui) +- [Core — Scene](/api/core) + +## Build + +From **`examples/physics`**: + +```bash +pio run -e native +pio run -e esp32dev +pio run -e esp32cyd +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +pio run -e esp32cyd --target upload +``` diff --git a/examples/snake.md b/examples/snake.md index 7edd31a..ec753b2 100644 --- a/examples/snake.md +++ b/examples/snake.md @@ -1,13 +1,55 @@ -# Snake (`snake`) +# Snake Game Example -Sample project: **[`examples/snake/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/snake)** — grid movement, segment pool, **`AudioEngine`** with platform audio backends. +Classic **Snake** on a grid: discrete movement (no physics engine), **pre-allocated segment pool** to avoid runtime allocations, food spawning, wall/self collision, score, and **procedural audio** through the engine **`AudioEngine`**. -Environments: `native`, `esp32dev`. +## Requirements (build flags) -Authoritative details: [`examples/snake/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/snake/README.md). +- **`PIXELROOT32_ENABLE_AUDIO=1`** — set in [`lib/platformio.ini`](lib/platformio.ini) `base` template so all environments inherit it. -- API: [AudioEngine](/api/audio/audio-engine) +Display size is **240×240** in the project `platformio.ini` (see **`PHYSICAL_DISPLAY_*`**). -For **`MusicPlayer`** in another sample, see [tic_tac_toe](./ui-layout) and the [Audio samples](./audio-playback) overview. +## Platforms -See the [samples index](./demos). +| Environment | Display | Audio backend | +|-------------|---------|----------------| +| **`native`** | SDL2, 240×240 | **`SDL2_AudioBackend`** in [`src/platforms/native.h`](src/platforms/native.h) | +| **`esp32dev`** | **ST7789** 240×240 | Default: **`ESP32_I2S_AudioBackend`** (comment in `esp32_dev.h` documents optional internal **DAC** backend instead) | + +Pin choices for I2S / DAC are in **`src/platforms/esp32_dev.h`** (edit there if your wiring differs). + +## Controls + +- **Arrow keys** (or GPIO D-pad mapped in your platform input config) to steer. +- **180° reverse** on the same frame is blocked via `nextDir` (see [`SnakeScene.h`](src/SnakeScene.h)). +- Eat food to grow and add score; hitting walls or yourself ends the run. + +## How audio is triggered + +[`SnakeScene.cpp`](src/SnakeScene.cpp) builds **`pixelroot32::audio::AudioEvent`** values (move, eat, crash) and calls **`engine.getAudioEngine().playEvent(...)`**. Wave types (triangle, pulse, noise) are lightweight beeps suited for embedded output. + +## Features + +- **Scene** + **Entity** background + pooled **`SnakeSegmentActor`** +- **Grid logic** and timers (`moveInterval`, `lastMoveTime`) +- **Audio** subsystem integration (`AudioEngine`, platform backends) + +## Documentation links + +- [Audio API](/api/audio) +- [Core API](/api/core) +- [Input API](/api/input) + +## Build + +From **`examples/snake`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/space-invaders.md b/examples/space-invaders.md index 7ec8d61..092cab3 100644 --- a/examples/space-invaders.md +++ b/examples/space-invaders.md @@ -1,11 +1,57 @@ -# Space Invaders (`space_invaders`) +# Space Invaders Example -Sample project: **[`examples/space_invaders/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/space_invaders)** — classic Space Invaders: alien formation, shooting mechanics, bunkers, score system, and **`AudioEngine`**. +Classic **Space Invaders** arcade game: alien formation with synchronized movement, player ship with shooting mechanics, bunkers for protection, enemy projectiles, score system, and **procedural audio** through the engine **`AudioEngine`**. -Environments: `native`, `esp32dev`. +## Requirements (build flags) -Authoritative details: [`examples/space_invaders/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/space_invaders/README.md). +- **`PIXELROOT32_ENABLE_AUDIO=1`** — set in [`lib/platformio.ini`](lib/platformio.ini) `base` template so all environments inherit it. +- **`PIXELROOT32_ENABLE_PHYSICS=1`** — required for projectile collision detection. -- API: [AudioEngine](/api/audio/audio-engine), [Physics](/api/physics/collision-system) +Display size is **240×240** in the project `platformio.ini` (see **`PHYSICAL_DISPLAY_*`**). -See the [samples index](./demos). +## Platforms + +| Environment | Display | Audio backend | +|-------------|---------|----------------| +| **`native`** | SDL2, 240×240 | **`SDL2_AudioBackend`** in [`src/platforms/native.h`](src/platforms/native.h) | +| **`esp32dev`** | **ST7789** 240×240 | Default: **`ESP32_I2S_AudioBackend`** (comment in `esp32_dev.h` documents optional internal **DAC** backend instead) | + +Pin choices for I2S / DAC are in **`src/platforms/esp32_dev.h`** (edit there if your wiring differs). + +## Controls + +- **Arrow keys** (or GPIO D-pad mapped in your platform input config) to move left/right. +- **Space** to fire (max 4 simultaneous bullets with 150ms cooldown). +- Destroy all aliens to win; if aliens reach the player's level, game over. + +## How audio is triggered + +[`SpaceInvadersScene.cpp`](src/SpaceInvadersScene.cpp) builds **`pixelroot32::audio::AudioEvent`** values (player shoot, enemy shoot, explosion, BGM tempo changes) and calls **`engine.getAudioEngine().playEvent(...)`**. Wave types (pulse, triangle) are lightweight beeps suited for embedded output. + +## Features + +- **Scene** with pooled **`ProjectileActor`**, **`AlienActor`**, **`BunkerActor`**, **`PlayerActor`** +- **4-player bullet limit** with fire rate cooldown (150ms) +- **Alien formation** with step-based movement and tempo-based BGM +- **Audio** subsystem integration (`AudioEngine`, platform backends) + +## Documentation links + +- [Audio API](/api/audio) +- [Core API](/api/core) +- [Input API](/api/input) + +## Build + +From **`examples/space_invaders`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` \ No newline at end of file diff --git a/examples/sprite-animation.md b/examples/sprite-animation.md index f675f4e..4c1dfad 100644 --- a/examples/sprite-animation.md +++ b/examples/sprite-animation.md @@ -1,11 +1,45 @@ -# Sprites (`sprites`) +# Sprites Demo Example -Sample project: **[`examples/sprites/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/sprites)** — 2bpp / 4bpp sprites, animation timing, flipping, palette use. +Demonstrates **2BPP** (4 colors) and **4BPP** (16 colors) **sprites** on the same scene, including **animation** and popup assets under `src/assets/`. Entities are owned by the scene and updated/drawn through the usual **`Scene`** pipeline. -Environments: `native`, `esp32dev`. +## Requirements (build flags) -Authoritative details: [`examples/sprites/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/sprites/README.md). +- **`PIXELROOT32_ENABLE_2BPP_SPRITES`** +- **`PIXELROOT32_ENABLE_4BPP_SPRITES`** -- API: [Sprite](/api/graphics/sprite), [Renderer](/api/graphics/renderer) +[`SpritesDemoScene.h`](src/SpritesDemoScene.h) is compiled only when at least one of these is defined (see `#if` guard in the header). -See the [samples index](./demos). \ No newline at end of file +See **`platformio.ini`** for **`native`** and **`esp32dev`**. + +## Platforms + +| Environment | Display | +|-------------|---------| +| **`native`** | SDL2, 240×240 | +| **`esp32dev`** | **ST7789** 240×240 (TFT_eSPI defines in `platformio.ini`) | + +## Features + +- **2bpp and 4bpp** sprite drawing on one screen +- **Sprite animation** via demo entities +- **Asset headers** (`Sprites.h`, `SpritesPopup.h`) as reference for embedding bitmaps + +## Documentation links + +- [Graphics API — sprites](/api/graphics) +- [Core — Scene / Entity](/api/core) + +## Build + +From **`examples/sprites`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/tic-tac-toe.md b/examples/tic-tac-toe.md index 8f3b3c0..af2068d 100644 --- a/examples/tic-tac-toe.md +++ b/examples/tic-tac-toe.md @@ -1,11 +1,61 @@ -# Tic Tac Toe (`tic_tac_toe`) +# Tic-Tac-Toe Example -Sample project: **[`examples/tic_tac_toe/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe)** — UI, GPIO vs touch input, minimax AI, vector-drawn board, and **`MusicPlayer`**. +A **tic-tac-toe** game against an **AI** (minimax-style move search with configurable error chance). The board is drawn with **primitive rendering** (grid, X/O, cursor). **UI** uses **`UILabel`** for status text and either **`UIButton`** or **`UITouchButton`** depending on touch support. -Environments: `native`, `esp32dev`, `esp32cyd`. +On **`esp32cyd`**, **`PIXELROOT32_ENABLE_TOUCH`** and **`onUnconsumedTouchEvent`** map taps to cells; **`native`** / **`esp32dev`** use cursor + confirm-style input per `GameConstants.h` button IDs. -Authoritative details: [`examples/tic_tac_toe/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/tic_tac_toe/README.md). +## Requirements (build flags) -- API: [UI System](/api/modules/ui), [AudioEngine](/api/audio/audio-engine) +- **`PIXELROOT32_ENABLE_TOUCH=1`** on **`native`** and **`esp32cyd`** (see `platformio.ini`) so touch code paths compile where used. +- ESP32 Dev preset does **not** set touch in `platformio.ini` — use GPIO **DPAD + A** (or equivalent) as in `GameConstants.h` (`BTN_UP`, `BTN_DOWN`, `BTN_PREV`, `BTN_NEXT`, `BTN_SELECT`). -See the [samples index](./demos). +`PIXELROOT32_ENABLE_UI_SYSTEM` defaults to **on** in the engine ([`PlatformDefaults.h`](../../include/platforms/PlatformDefaults.h)). + +See **`platformio.ini`** for **`native`**, **`esp32dev`**, and **`esp32cyd`** (CYD uses **ILI9341** 240×320 + **XPT2046** touch — many calibration defines are in the INI). + +## Platforms + +| Environment | Display / input | +|-------------|----------------| +| **`native`** | SDL2, 240×240, simulated touch enabled | +| **`esp32dev`** | **ST7789** 240×240, no touch flags in INI | +| **`esp32cyd`** | **ILI9341** 240×320 + resistive touch (**XPT2046** GPIO SPI) | + +## Controls (GPIO / keyboard) + +- Navigate / change cell / confirm per `src/GameConstants.h` (`BTN_*` indices). +- **Play Again**: reset control wired in `TicTacToeScene::createResetButton()` (GPIO or touch widget depending on build). + +## Touch (CYD) + +Touches that the UI does not consume are handled in **`onUnconsumedTouchEvent`**, with hit slop around each cell (`kTouchHitSlop` in [`TicTacToeScene.h`](src/TicTacToeScene.h)). + +## Features + +- **Scene** lifecycle + **UI labels** and conditional **touch / GPIO buttons** +- **`TouchEvent`** pipeline for board placement +- **AI**: `computeAIMove`, win detection, draw state +- **Custom palette** and vector draw for marks (no tilemap required for the board) + +## Documentation links + +- [UI API](/api/ui) +- [Input API](/api/input) +- [Core API](/api/core) + +## Build + +From **`examples/tic_tac_toe`**: + +```bash +pio run -e native +pio run -e esp32dev +pio run -e esp32cyd +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +pio run -e esp32cyd --target upload +``` diff --git a/examples/tilemap-scene.md b/examples/tilemap-scene.md index 2e191b7..bd1680d 100644 --- a/examples/tilemap-scene.md +++ b/examples/tilemap-scene.md @@ -1,16 +1,61 @@ -# Tilemap samples +> **Note:** Level-style tilemaps; for tile animation and cache details see [Animated tilemap](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap). -Two real folders in the engine repo cover most tilemap learning paths: +# Metroidvania-Style Example -| Folder | Focus | -|--------|--------| -| [`metroidvania`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/metroidvania) | 4bpp tilemaps, **`StaticTilemapLayerCache`**, platformer player | -| [`animated_tilemap`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap) | Tile **animation**, palettes, framebuffer cache (deepest README) | +A compact **platformer** sample with **4bpp tilemap layers** (background, platforms, decorative tiles), **`StaticTilemapLayerCache`** for the ESP32 fast path when available, and a **`KinematicActor`**-based player with climbing and jump rules tailored to the sample map. -Dedicated site stubs: [metroidvania](./metroidvania), [animated_tilemap](./animated-tilemap). +**Requires `PIXELROOT32_ENABLE_4BPP_SPRITES`** — the scene is guarded in [`src/MetroidvaniaScene.h`](src/MetroidvaniaScene.h). -For **camera / parallax** as a separate concern, use [`camera`](./camera), not `metroidvania` alone. +## Requirements (build flags) -Guides: [Tilemaps](/guide/tilemaps), [ARCH tile animation](/architecture/ARCH_TILE_ANIMATION). API: [TileMap](/api/graphics/tilemap). +- **`PIXELROOT32_ENABLE_4BPP_SPRITES`** +- **`PIXELROOT32_ENABLE_2BPP_SPRITES`** (enabled alongside 4bpp in this example’s `platformio.ini`) +- **`PIXELROOT32_ENABLE_SCENE_ARENA`** -See the [samples index](./demos). +See **`platformio.ini`** for **`native`** and **`esp32dev`** presets (no `esp32cyd` environment in this project). + +**`extern pixelroot32::core::Engine engine`** is wired in the platform headers under `src/platforms/`. + +## Platforms + +| Environment | Display | +|-------------|---------| +| **`native`** | SDL2, 240×240 | +| **`esp32dev`** | **ST7789** 240×240 | + +## Controls + +Uses **`GameConstants.h`** button IDs: **Up / Down / Left / Right** and **Jump** (`BTN_UP` … `BTN_JUMP`). Map these to your `InputManager` / GPIO / keyboard mapping for the platform file you use. + +## How this scene uses the tilemap cache + +Like the animated tilemap sample, drawing goes through **`StaticTilemapLayerCache`**: allocate for the renderer when layers are ready, draw static groups with camera offsets, and **`invalidate()`** when static tile data or relevant animators change. See [Animated Tilemap README](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap) for the detailed invalidation table and [Architecture — static tilemap cache](/architecture/architecture-index). + +## Features + +- **4bpp tilemaps** and layered level data +- **`StaticTilemapLayerCache`** snapshot path when the driver exposes a logical framebuffer +- **Player actor** with gravity, stairs/climb behavior, and map collision +- **Scene arena** + owned layer entities + +## Documentation links + +- [Graphics — tilemaps & `StaticTilemapLayerCache`](/api/graphics#multi-layer-4bpp-tilemap-framebuffer-snapshot-statictilemaplayercache) +- [Architecture — ESP32 rendering / tilemap caching](/architecture/architecture-index.md#esp32-rendering-pipeline-and-tilemap-caching) +- [Physics API](/api/physics) +- [Core API](/api/core) + +## Build + +From **`examples/metroidvania`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` diff --git a/examples/ui-layout.md b/examples/ui-layout.md index f3a5ce5..af2068d 100644 --- a/examples/ui-layout.md +++ b/examples/ui-layout.md @@ -1,12 +1,61 @@ -# UI (`tic_tac_toe`) +# Tic-Tac-Toe Example -Sample project: **[`examples/tic_tac_toe/`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe)** — UI widgets, GPIO vs touch, minimax AI, vector-drawn board, plus **`MusicPlayer`** / background melody (`src/assets/music.h`, SDL2 or I2S/DAC backends in platform headers). +A **tic-tac-toe** game against an **AI** (minimax-style move search with configurable error chance). The board is drawn with **primitive rendering** (grid, X/O, cursor). **UI** uses **`UILabel`** for status text and either **`UIButton`** or **`UITouchButton`** depending on touch support. -Environments: `native`, `esp32dev`, `esp32cyd`. +On **`esp32cyd`**, **`PIXELROOT32_ENABLE_TOUCH`** and **`onUnconsumedTouchEvent`** map taps to cells; **`native`** / **`esp32dev`** use cursor + confirm-style input per `GameConstants.h` button IDs. -Authoritative details: [`examples/tic_tac_toe/README.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/examples/tic_tac_toe/README.md). +## Requirements (build flags) -- Guide: [UI system](/guide/ui-system), [Music player](/guide/music-player) -- API: [UI module](/api/modules/ui), [MusicPlayer](/api/audio/music-player) +- **`PIXELROOT32_ENABLE_TOUCH=1`** on **`native`** and **`esp32cyd`** (see `platformio.ini`) so touch code paths compile where used. +- ESP32 Dev preset does **not** set touch in `platformio.ini` — use GPIO **DPAD + A** (or equivalent) as in `GameConstants.h` (`BTN_UP`, `BTN_DOWN`, `BTN_PREV`, `BTN_NEXT`, `BTN_SELECT`). -See also [Audio samples](./audio-playback). [Samples index](./demos). \ No newline at end of file +`PIXELROOT32_ENABLE_UI_SYSTEM` defaults to **on** in the engine ([`PlatformDefaults.h`](../../include/platforms/PlatformDefaults.h)). + +See **`platformio.ini`** for **`native`**, **`esp32dev`**, and **`esp32cyd`** (CYD uses **ILI9341** 240×320 + **XPT2046** touch — many calibration defines are in the INI). + +## Platforms + +| Environment | Display / input | +|-------------|----------------| +| **`native`** | SDL2, 240×240, simulated touch enabled | +| **`esp32dev`** | **ST7789** 240×240, no touch flags in INI | +| **`esp32cyd`** | **ILI9341** 240×320 + resistive touch (**XPT2046** GPIO SPI) | + +## Controls (GPIO / keyboard) + +- Navigate / change cell / confirm per `src/GameConstants.h` (`BTN_*` indices). +- **Play Again**: reset control wired in `TicTacToeScene::createResetButton()` (GPIO or touch widget depending on build). + +## Touch (CYD) + +Touches that the UI does not consume are handled in **`onUnconsumedTouchEvent`**, with hit slop around each cell (`kTouchHitSlop` in [`TicTacToeScene.h`](src/TicTacToeScene.h)). + +## Features + +- **Scene** lifecycle + **UI labels** and conditional **touch / GPIO buttons** +- **`TouchEvent`** pipeline for board placement +- **AI**: `computeAIMove`, win detection, draw state +- **Custom palette** and vector draw for marks (no tilemap required for the board) + +## Documentation links + +- [UI API](/api/ui) +- [Input API](/api/input) +- [Core API](/api/core) + +## Build + +From **`examples/tic_tac_toe`**: + +```bash +pio run -e native +pio run -e esp32dev +pio run -e esp32cyd +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +pio run -e esp32cyd --target upload +``` diff --git a/guide/audio.md b/guide/audio.md index 877a3a3..b48d952 100644 --- a/guide/audio.md +++ b/guide/audio.md @@ -2,7 +2,7 @@ PixelRoot32 provides a **NES-like** audio subsystem: **four fixed channels** (two pulse, one triangle, one noise), **mono** 16-bit output, **event-driven** playback (`AudioEvent`), and **sample-accurate** timing decoupled from the game frame rate. There is **no DMC/sample channel** in the current engine. -For implementation details, see the engine source: [`ARCH_AUDIO_SUBSYSTEM.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/architecture/ARCH_AUDIO_SUBSYSTEM.md) (authoritative). +For implementation details, see [Audio subsystem](../architecture/audio-subsystem.md) (authoritative). ## Architecture Overview @@ -92,7 +92,7 @@ Per-channel volume is set per **`AudioEvent::volume`**; there are no separate `s ## Music (`MusicPlayer`) -Sequencing is **sample-accurate** and **tick-based** inside **`ApuCore`**. **`MusicPlayer`** only **enqueues** `AudioCommand`s (`MUSIC_PLAY`, tempo/BPM, pause/resume, stop). See **[Music Player API](/api/audio/music-player)** and the long-form **[Music player guide](/guide/music-player)**. +Sequencing is **sample-accurate** and **tick-based** inside **`ApuCore`**. **`MusicPlayer`** only **enqueues** `AudioCommand`s (`MUSIC_PLAY`, tempo/BPM, pause/resume, stop). See **[Music Player API](../api/audio.md#playing-music)** and the long-form **[Music player guide](./music-player-guide.md)**. ### Multi-track layout @@ -150,7 +150,7 @@ audioConfig.sampleRate = 22050; pr32::core::Engine engine(displayConfig, inputConfig, audioConfig); ``` -See **[AudioEngine](/api/audio/audio-engine)** for **`AudioConfig`** fields and architecture links. +See **[AudioEngine](../api/audio.md)** for **`AudioConfig`** fields and architecture links. ## Platform differences @@ -169,8 +169,8 @@ See **[AudioEngine](/api/audio/audio-engine)** for **`AudioConfig`** fields and ## Next steps -- **[Audio architecture](/architecture/audio-architecture)** — Subsystem narrative (kept in sync with the engine; see also engine [`ARCH_AUDIO_SUBSYSTEM.md`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/architecture/ARCH_AUDIO_SUBSYSTEM.md)) -- **[AudioEngine & types](/api/audio/audio-engine)** — Methods, `AudioEvent`, `AudioCommand`, `AudioConfig`, music transport queries -- **[AudioScheduler](/api/audio/audio-scheduler)** — Schedulers vs **`ApuCore`** -- **[MusicPlayer](/api/audio/music-player)** — Tracks, presets, tempo/BPM +- **[Audio architecture](../architecture/audio-subsystem.md)** — Subsystem narrative (kept in sync with the engine) +- **[AudioEngine & types](../api/audio.md)** — Methods, `AudioEvent`, `AudioCommand`, `AudioConfig`, music transport queries +- **[AudioScheduler](../api/audio.md#architecture-notes)** — Schedulers vs **`ApuCore`** +- **[MusicPlayer](../api/audio.md#playing-music)** — Tracks, presets, tempo/BPM - **Engine source:** [`ApuCore.h` / `ApuCore.cpp`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/include/audio) — authoritative implementation diff --git a/guide/coding-style.md b/guide/coding-style.md new file mode 100644 index 0000000..6ca045b --- /dev/null +++ b/guide/coding-style.md @@ -0,0 +1,210 @@ +# Coding Style Guide - PixelRoot32 + +> **Note:** This document supersedes `style-guide.md`. The old file is retained for backward compatibility but now redirects here. + +**Scope:** C++ coding conventions and style rules. +**For other topics:** See [Memory](./memory.md), [Gameplay Guidelines](./gameplay-guidelines.md), [UI Guidelines](./ui-guidelines.md), [Graphics Guidelines](./graphics-guidelines.md), [ESP32 Performance](./performance/esp32-performance.md). + +--- + +## 🎯 Design Principles + +These principles guide all implementation decisions: + +- **Deterministic over convenient**: Explicit control flow, no hidden allocations +- **Zero allocation at runtime**: Pre-allocate during init, pool objects +- **Data-oriented where possible**: Prefer contiguous arrays, cache-friendly layouts +- **No exceptions, no RTTI**: Error handling via return codes +- **Embedded-first**: Optimize for ESP32, portable to native + +--- + +## 📐 Language & Standards + +- **C++17** minimum +- **No exceptions** - compile with `-fno-exceptions` +- **No RTTI** - compile with `-fno-rtti` +- Prefer deterministic and explicit control flow + +--- + +## 🔧 Modern C++ Features (C++17) + +| Feature | Usage | Avoid In | +|---------|-------|----------| +| `std::unique_ptr` | Init-time ownership | Hot paths | +| `std::string_view` | Non-owning strings | - | +| `std::optional` | May-not-exist values | Hot paths | +| `[[nodiscard]]` | Must-check returns | - | +| `constexpr` | Compile-time constants | - | + +**Example:** +```cpp +// ✅ Smart pointer for ownership +auto player = std::make_unique(x, y); +scene.addEntity(player.get()); // Non-owning access + +// ✅ String view for parameters +void setName(std::string_view name); // No copy + +// ⚠️ Optional OK for init, avoid in update() +std::optional loadConfig(const char* path); +``` + +--- + +## 📁 File Structure + +| Extension | Purpose | +|-----------|---------| +| `.h` | Interfaces, public types, trivial inline code | +| `.cpp` | Implementations, heavy logic | + +### Include Rules + +``` +src/ → Can include anything +include/ → Can include from src/ (implementation details) + → Must NOT expose internal headers +``` + +**Critical rule:** Source files in `src/` must **never** include headers from `include/`. + +--- + +## 🏷️ Naming Conventions + +| Element | Convention | Example | +|---------|------------|---------| +| Classes/Structs | PascalCase | `PhysicsActor`, `SpriteLayer` | +| Methods/Functions | camelCase | `update()`, `drawSprite()` | +| Variables/Members | camelCase | `position`, `velocity` | +| Constants | UPPER_SNAKE | `MAX_ENTITIES` | +| Namespaces | lowercase | `pixelroot32::graphics` | + +### Member Prefix (Optional) + +Use `m_` or `this->` when parameter shadows member: + +```cpp +// ✅ Option A: m_ prefix +void setX(int x) { m_x = x; } + +// ✅ Option B: this-> explicit +void setX(int x) { this->x = x; } + +// ❌ Avoid: shadowing bug +void setX(int x) { x = x; } // Bug! +``` + +**No Hungarian notation** (e.g., `strName`, `nCount`). + +--- + +## 🧩 Namespace Design + +### Root Namespace + +All symbols live under `pixelroot32`. + +### Public API Namespaces + +```cpp +pixelroot32::core // Engine, Scene, Actor +pixelroot32::graphics // Renderer, sprites +pixelroot32::input // Input handling +pixelroot32::physics // Collision system +pixelroot32::math // Scalar, Vector2 +``` + +### Internal Namespaces (Non-API) + +```cpp +pixelroot32::platform // Hardware abstraction +pixelroot32::internal // Engine internals +pixelroot32::detail // Implementation details +``` + +**Rules for internal:** +- May change without notice +- Must not be included by user projects +- Must not be exposed through `include/` headers + +--- + +## 📦 Namespace Usage Rules + +**Golden Rule:** *Never in headers. Rarely in .cpp. Only in small scopes.* + +### Headers (`.h`) + +```cpp +// ✅ Fully qualified +pixelroot32::graphics::Renderer renderer; + +// ✅ Alias inside function only +void MyClass::draw() { + namespace gfx = pixelroot32::graphics; + gfx::Renderer renderer; +} + +// ❌ NEVER in headers +using namespace pixelroot32::graphics; +``` + +### Implementation (`.cpp`) + +| Approach | Use Case | Example | +|----------|----------|---------| +| Subsystem alias | Large modules | `namespace gfx = pixelroot32::graphics;` | +| Root alias | Multiple refs | `namespace pr32 = pixelroot32;` | +| Selective using | Specific symbols | `using pixelroot32::graphics::Renderer;` | +| `using namespace` | Tests only, small scopes (<20 lines) | Inside functions | + +**Acceptable only in:** +- Unit tests (`test_*.cpp`) +- Function scopes (not file/namespace scope) +- Prototypes (<50 lines) + +**Prohibited in:** +- Engine core `.cpp` files +- Namespace scope +- Public headers + +--- + +## 📚 Class Layout + +Order inside classes: + +```cpp +class Example { +public: // 1. Public interface first + void publicMethod(); + int publicMember; + +protected: // 2. Protected for inheritance + void protectedMethod(); + +private: // 3. Implementation last + void privateMethod(); + int privateMember; +}; +``` + +--- + +## 🔗 Related Documentation + +| Document | Topic | +|----------|-------| +| [memory.md](./memory.md) | Object pooling, zero allocation | +| [gameplay-guidelines.md](./gameplay-guidelines.md) | Game feel, deltaTime, slopes | +| [ui-guidelines.md](./ui-guidelines.md) | Layouts, panels, navigation | +| [graphics-guidelines.md](./graphics-guidelines.md) | Sprites, tilemaps, palettes | +| [performance/esp32-performance.md](./performance/esp32-performance.md) | Hot paths, optimization | +| [Memory system](../architecture/memory-system.md) | C++17 memory deep dive | + +--- + +*PixelRoot32 aims for clarity over abstraction, control over convenience.* diff --git a/guide/core-concepts.md b/guide/core-concepts.md index b42c699..f0252e8 100644 --- a/guide/core-concepts.md +++ b/guide/core-concepts.md @@ -277,7 +277,7 @@ build_flags = ## Next Steps -- **[Game Loop](/guide/game-loop)** — Deep dive into the update/render cycle -- **[Scenes](/guide/scenes)** — Scene management and transitions -- **[Entities & Actors](/guide/entities-actors)** — Creating game objects -- **[Architecture Overview](/architecture/overview)** — Engine design patterns +- **[Game Loop](./game-loop.md)** — Deep dive into the update/render cycle +- **[Scenes](./scenes.md)** — Scene management and transitions +- **[Entities & Actors](./entities-actors.md)** — Creating game objects +- **[Architecture Index](../architecture/architecture-index.md)** — Engine design patterns diff --git a/guide/entities-actors.md b/guide/entities-actors.md index 2e50746..a4b6a54 100644 --- a/guide/entities-actors.md +++ b/guide/entities-actors.md @@ -544,6 +544,6 @@ stateDiagram-v2 ## Next Steps -- **[Scenes](/guide/scenes)** — How entities live in scenes -- **[Physics](/guide/physics)** — Deep dive into collision and movement -- **[Rendering](/guide/rendering)** — Drawing techniques and optimization +- **[Scenes](./scenes.md)** — How entities live in scenes +- **[Physics](./physics.md)** — Deep dive into collision and movement +- **[Rendering](./rendering.md)** — Drawing techniques and optimization diff --git a/guide/entities-scene-tutorial.md b/guide/entities-scene-tutorial.md new file mode 100644 index 0000000..2fcd56d --- /dev/null +++ b/guide/entities-scene-tutorial.md @@ -0,0 +1,354 @@ +# Entities & scene tutorial + +> **Not an `examples/` project** — This page is a **didactic walkthrough** (bouncing entities, `Entity` subclass, scene wiring). There is **no** matching folder under [`examples/`](../../examples/README.md) with this code. For a **real, minimal** PlatformIO project, start with [`hello_world`](../../examples/hello_world/) and its README. + +The snippets below illustrate fundamentals: scene lifecycle, custom **`Entity`** subclasses, input, and drawing — useful once you have already opened **`hello_world`** in the repo. + +## Complete Source + +```cpp +#include +#include +#include +#include +#include +#include + +using namespace pixelroot32; + +// ============================================================================ +// Custom Entity: A bouncing ball +// ============================================================================ +class Ball : public core::Entity { + math::Scalar velocityX; + math::Scalar velocityY; + graphics::Color color; + int screenWidth; + int screenHeight; + +public: + Ball(int x, int y, int screenW, int screenH) + : Entity(math::toScalar(x), math::toScalar(y), 8, 8, core::EntityType::GENERIC), + screenWidth(screenW), + screenHeight(screenH), + color(graphics::Color::RED) { + + // Random velocity using math::Scalar + velocityX = math::toScalar(random(50, 150)); + velocityY = math::toScalar(random(50, 150)); + + if (random(2) == 0) velocityX = -velocityX; + if (random(2) == 0) velocityY = -velocityY; + } + + void update(unsigned long deltaTime) override { + using namespace math; + + // Convert deltaTime to seconds as Scalar + Scalar dt = toScalar(deltaTime) / toScalar(1000); + + // Update position + position.x += velocityX * dt; + position.y += velocityY * dt; + + // Bounce off walls + if (position.x <= toScalar(0) || + position.x >= toScalar(screenWidth - width)) { + velocityX = -velocityX; + color = static_cast(random(1, 16)); + } + + if (position.y <= toScalar(0) || + position.y >= toScalar(screenHeight - height)) { + velocityY = -velocityY; + color = static_cast(random(1, 16)); + } + + // Clamp to screen + if (position.x < toScalar(0)) position.x = toScalar(0); + if (position.y < toScalar(0)) position.y = toScalar(0); + if (position.x > toScalar(screenWidth - width)) + position.x = toScalar(screenWidth - width); + if (position.y > toScalar(screenHeight - height)) + position.y = toScalar(screenHeight - height); + } + + void draw(graphics::Renderer& r) override { + r.drawFilledRectangle( + static_cast(position.x), + static_cast(position.y), + width, + height, + color + ); + } +}; + +// ============================================================================ +// Game Scene +// ============================================================================ +class BouncingBallsScene : public core::Scene { + static constexpr int NUM_BALLS = 10; + std::unique_ptr balls[NUM_BALLS]; + unsigned long spawnTimer = 0; + int ballsActive = 0; + int screenWidth; + int screenHeight; + +public: + void init() override { + // Nothing to do - balls spawned in first update + screenWidth = engine->getRenderer().getLogicalWidth(); + screenHeight = engine->getRenderer().getLogicalHeight(); + } + + void update(unsigned long deltaTime) override { + auto& input = engine->getInputManager(); + + // Spawn balls gradually + spawnTimer += deltaTime; + if (spawnTimer > 500 && ballsActive < NUM_BALLS) { // Every 500ms + spawnTimer = 0; + + balls[ballsActive] = std::make_unique( + screenWidth / 2, + screenHeight / 2, + screenWidth, + screenHeight + ); + addEntity(balls[ballsActive].get()); + ballsActive++; + } + + // Reset with button A + if (input.isButtonJustPressed(input::ButtonName::A)) { + resetBalls(); + } + + // Update all entities (calls Ball::update) + Scene::update(deltaTime); + } + + void draw(graphics::Renderer& r) override { + // Clear handled by beginFrame, draw background + r.drawFilledRectangle(0, 0, screenWidth, screenHeight, graphics::Color::BLACK); + + // Draw all entities (balls) + Scene::draw(r); + + // Draw UI + r.drawText("Balls: " + std::to_string(ballsActive), 5, 5, graphics::Color::WHITE, 1); + r.drawText("Press A to reset", 5, 15, graphics::Color::GRAY, 1); + } + +private: + void resetBalls() { + // Remove existing balls + for (int i = 0; i < ballsActive; ++i) { + removeEntity(balls[i].get()); + balls[i].reset(); + } + ballsActive = 0; + spawnTimer = 0; + } +}; + +// ============================================================================ +// Main Setup +// ============================================================================ +void setup() { + // Seed random + randomSeed(analogRead(0)); + + // Display configuration + graphics::DisplayConfig displayConfig(240, 240); + + // Input configuration + input::InputConfig inputConfig; + inputConfig.addButton(input::ButtonName::A, 0); // BOOT button + + // Create engine + core::Engine engine(std::move(displayConfig), inputConfig); + + // Create and set scene + BouncingBallsScene scene; + engine.setScene(&scene); + + // Initialize and run + engine.init(); + engine.run(); +} + +void loop() { + // Empty - engine.run() contains the game loop +} +``` + +## Key Concepts Demonstrated + +### 1. Entity Creation + +```cpp +class Ball : public core::Entity { +public: + Ball(int x, int y, int w, int h) + : Entity(x, y, w, h, EntityType::GENERIC) { + // Initialize + } + + void update(unsigned long deltaTime) override; + void draw(graphics::Renderer& r) override; +}; +``` + +- Extend `Entity` for game objects +- Implement `update()` for logic +- Implement `draw()` for rendering + +### 2. Frame-Rate Independent Movement + +```cpp +void update(unsigned long deltaTime) override { + // Convert to seconds + math::Scalar dt = math::toScalar(deltaTime) / math::toScalar(1000); + + // Move at constant speed regardless of FPS + position.x += velocityX * dt; +} +``` + +Always use `deltaTime` for time-based calculations. + +### 3. Entity Management + +```cpp +// Create +balls[i] = std::make_unique(...); +addEntity(balls[i].get()); // Scene manages the pointer + +// Remove +removeEntity(balls[i].get()); // Remove from scene +balls[i].reset(); // Free memory +``` + +Use smart pointers for automatic memory management. + +### 4. Input Handling + +```cpp +auto& input = engine->getInputManager(); + +// Button pressed this frame (edge trigger) +if (input.isButtonJustPressed(ButtonName::A)) { + // Trigger action once +} + +// Button held (continuous) +if (input.isButtonPressed(ButtonName::LEFT)) { + // Continuous action +} +``` + +### 5. Scene Structure + +```cpp +class MyScene : public core::Scene { +public: + void init() override; // Setup + void update(unsigned long deltaTime) override; // Logic + void draw(graphics::Renderer& r) override; // Rendering +}; +``` + +## Build Configuration + +```ini +; platformio.ini +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino + +; C++17 required +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++17 + -fno-exceptions + +; Library dependencies +lib_deps = + https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine.git +``` + +## Running the Example + +1. **Create project**: + ```bash + mkdir bouncing_balls + cd bouncing_balls + ``` + +2. **Copy code** to `src/main.cpp` + +3. **Create** `platformio.ini` with configuration above + +4. **Build and upload**: + - Open in VS Code with PlatformIO + - Select environment (ESP32 or Native) + - Build and upload + +## Expected Behavior + +- Balls spawn one at a time every 500ms +- Balls bounce off screen edges +- Ball color changes on each bounce +- Press button A to reset +- Counter shows active ball count + +## Variations + +### Add Physics + +```cpp +// Change to physics-enabled actor +class Ball : public physics::KinematicActor { +public: + Ball(...) : KinematicActor(x, y, w, h) { + setCollisionLayer(DefaultLayers::kEnvironment); + } + + void onCollision(Actor* other) override { + // Bounce with physics response + velocityX = -velocityX; + } +}; +``` + +### Add Audio + +```cpp +#if PIXELROOT32_ENABLE_AUDIO +void onCollision(Actor* other) override { + engine->getAudioEngine().playSFX(sound_bounce); +} +#endif +``` + +### Touch Control + +```cpp +#if PIXELROOT32_ENABLE_TOUCH +void onUnconsumedTouchEvent(const input::TouchEvent& event) override { + if (event.type == input::TouchEventType::CLICK) { + spawnBallAt(event.x, event.y); + } +} +#endif +``` + +## Next Steps + +- **[Samples index](../../examples/README.md)** — Real folders under `examples/` +- **[Physics (`physics`)](../../examples/physics/README.md)** — Collision and actors +- **[Sprites (`sprites`)](../../examples/sprites/README.md)** — Sprite graphics +- **Audio** — See [`snake`](../../examples/snake/README.md), [`tic_tac_toe`](../../examples/tic_tac_toe/README.md), [`music_demo`](../../examples/music_demo/README.md) diff --git a/guide/extending.md b/guide/extending-pixelroot32.md similarity index 97% rename from guide/extending.md rename to guide/extending-pixelroot32.md index 3b642e7..e750e8e 100644 --- a/guide/extending.md +++ b/guide/extending-pixelroot32.md @@ -1,5 +1,7 @@ # Extensibility Guide: Creating Custom Drivers +> **Note:** For the complete extensibility guide with advanced examples, visit the [official documentation](https://docs.pixelroot32.org/manual/optimization/extensibility/). + This guide explains how to implement a custom display driver (`DrawSurface`) to support hardware not included by default in the PixelRoot32 engine (e.g., monochromatic OLED displays, e-Ink screens, or non-standard SPI displays). ## 1. Inherit from `BaseDrawSurface` diff --git a/guide/game-loop.md b/guide/game-loop.md index 7894199..26d8b1d 100644 --- a/guide/game-loop.md +++ b/guide/game-loop.md @@ -21,6 +21,26 @@ flowchart LR Each phase has specific responsibilities and timing constraints. +### Simplified per-frame sequence + +```mermaid +sequenceDiagram + participant Engine + participant Input + participant Scene + participant Physics + participant Renderer + + loop Every Frame + Engine->>Input: Poll buttons/touch + Engine->>Scene: update(deltaTime) + Scene->>Physics: Process collisions + Engine->>Scene: draw(renderer) + Scene->>Renderer: Render entities + Engine->>Renderer: endFrame() / present() + end +``` + ## Phase Breakdown ### 1. Input Phase @@ -64,17 +84,15 @@ The update phase runs game logic: - **Game state**: Score tracking, timer updates - **Movement preparation**: Calculate desired velocity -::: warning Frame Rate Independence -Always use `deltaTime` for time-based calculations: - -```cpp -// Bad: Frame-rate dependent -position.x += 5; // Moves 5px/frame (300px/s at 60fps, 150px/s at 30fps) - -// Good: Frame-rate independent -position.x += speed * deltaTime / 1000.0f; // Consistent speed in px/sec -``` -::: +> **Frame rate independence** — Always use `deltaTime` for time-based calculations: +> +> ```cpp +> // Bad: Frame-rate dependent +> position.x += 5; // Moves 5px/frame (300px/s at 60fps, 150px/s at 30fps) +> +> // Good: Frame-rate independent +> position.x += speed * deltaTime / 1000.0f; // Consistent speed in px/sec +> ``` ### 3. Physics Phase @@ -332,6 +350,6 @@ This displays: ## Next Steps -- **[Scenes](/guide/scenes)** — Scene management and transitions -- **[Rendering](/guide/rendering)** — Understanding the graphics pipeline -- **[Physics](/guide/physics)** — Collision and movement systems +- [Layer 4 — Scene](../architecture/layer-scene.md) — Scene management and entity hierarchy +- [Layer 3 — Systems](../architecture/layer-systems.md) — Renderer, physics, UI +- [Physics subsystem](../architecture/physics-subsystem.md) — Collision and movement diff --git a/guide/gameplay-guidelines.md b/guide/gameplay-guidelines.md new file mode 100644 index 0000000..92a09bd --- /dev/null +++ b/guide/gameplay-guidelines.md @@ -0,0 +1,276 @@ +# Gameplay Guidelines - PixelRoot32 + +Patterns and best practices for game feel, mechanics, and common implementations. + +--- + +## 🎮 Core Principles + +### Frame-Rate Independence + +Always multiply movement by `deltaTime`: + +```cpp +// ✅ Good: Frame-rate independent +void update(unsigned long dt) { + x += speed * math::toScalar(dt * 0.001f); +} + +// ❌ Bad: Frame-rate dependent (different speeds at different FPS) +void update(unsigned long dt) { + x += speed; // Bug! +} +``` + +### Logic/Visual Decoupling + +For infinite runners and auto-scrollers: + +- **Logic progression** (obstacle spacing, spawn timing): Constant in real time +- **Visual speed**: Can increase for difficulty without affecting game logic + +```cpp +// ✅ Decoupled: Logic constant, visual varies +void update(unsigned long dt) { + // Game logic: constant spacing + spawnTimer += dt; + if (spawnTimer > SPAWN_INTERVAL) { + spawnObstacle(); + spawnTimer = 0; + } + + // Visual: can speed up for effect + scrollX += visualSpeed * dt; +} +``` + +--- + +## 🕹️ Game Feel + +### Snappy Controls + +For fast-paced games, prefer higher values to reduce "floatiness": + +```cpp +// ❌ Floaty +constexpr Scalar GRAVITY = math::toScalar(0.3f); +constexpr Scalar JUMP_FORCE = math::toScalar(8.0f); + +// ✅ Snappy +constexpr Scalar GRAVITY = math::toScalar(0.6f); +constexpr Scalar JUMP_FORCE = math::toScalar(12.0f); +``` + +### Slopes & Ramps on Tilemaps + +Treat contiguous ramp tiles as a single logical slope: + +```cpp +// ✅ Linear interpolation over world X +Scalar getRampHeight(int worldX) { + // Ramp from y=80 to y=48 across 4 tiles (64 pixels) + Scalar t = math::toScalar((worldX - rampStartX) / 64.0f); + return math::lerp(math::toScalar(80.0f), math::toScalar(48.0f), t); +} +``` + +Keep gravity and jump parameters identical between flat ground and ramps for consistent jump timing. + +--- + +## 🏗️ Architecture Patterns + +### Tuning Constants + +Extract gameplay values to a dedicated header: + +```cpp +// GameConstants.h +namespace GameConstants { + constexpr Scalar PLAYER_SPEED = math::toScalar(120.0f); // px/sec + constexpr Scalar GRAVITY = math::toScalar(0.6f); + constexpr Scalar JUMP_FORCE = math::toScalar(12.0f); + constexpr int MAX_BULLETS = 50; +} +``` + +Benefits: +- Designers can tweak without touching logic +- Single source of truth +- Easy balance testing + +### State Management with `reset()` + +Reuse actors across game sessions instead of destroying/recreating: + +```cpp +class PlayerActor : public PhysicsActor { +public: + void reset(Vector2 startPos) { + position = startPos; + velocity = Vector2::zero(); + health = MAX_HEALTH; + isActive = true; + } +}; + +// In scene +void onGameOver() { + player->reset(START_POSITION); // ✅ Reuse + // NOT: player = new PlayerActor(); // ❌ Allocates +} +``` + +### Component Pattern + +| Actor Type | Use For | Example | +|------------|---------|---------| +| `Actor` | Static objects | Walls, platforms | +| `PhysicsActor` | Moving objects | Player, enemies | +| `KinematicActor` | Controlled movement | Player with input | +| `SensorActor` | Triggers | Goal zones, hazards | + +--- + +## 🐛 Anti-Patterns (Common Mistakes) + +### 1. No Delta Time + +```cpp +// ❌ WRONG: Different behavior at different FPS +void update(unsigned long dt) { + x += speed; + y += velocity.y; +} + +// ✅ CORRECT: Consistent regardless of FPS +void update(unsigned long dt) { + Scalar dtSec = math::toScalar(dt * 0.001f); + x += speed * dtSec; + y += velocity.y * dtSec; +} +``` + +### 2. Logic in draw() + +```cpp +// ❌ WRONG: Game logic in render +void draw(Renderer& r) { + if (player->x > 100) { // Logic! + spawnEnemy(); + } + player->draw(r); +} + +// ✅ CORRECT: Logic in update, render in draw +void update(unsigned long dt) { + if (player->position.x > ENEMY_SPAWN_X) { + spawnEnemy(); + } +} + +void draw(Renderer& r) { + player->draw(r); // Pure rendering +} +``` + +### 3. Runtime Allocation in Game Loop + +```cpp +// ❌ WRONG: Allocates every frame +void update(unsigned long dt) { + if (shootPressed) { + auto bullet = std::make_unique(x, y); // BAD! + scene.addEntity(bullet.get()); + } +} + +// ✅ CORRECT: Pool pattern +class BulletPool { + std::array bullets; + std::bitset active; + +public: + void spawn(Vector2 pos) { + for (size_t i = 0; i < MAX_BULLETS; ++i) { + if (!active[i]) { + active[i] = true; + bullets[i].reset(pos); + return; + } + } + } +}; +``` + +### 4. std::rand() in Hot Paths + +```cpp +// ❌ WRONG: Slow, uses division +void update(unsigned long dt) { + if (std::rand() % 100 < 5) { // Expensive! + spawnParticle(); + } +} + +// ✅ CORRECT: Fast Xorshift +void update(unsigned long dt) { + if (math::randomRange(0, 100) < 5) { // Optimized + spawnParticle(); + } +} +``` + +### 5. Magic Numbers + +```cpp +// ❌ WRONG: What do these mean? +if (player.y > 200) { ... } +if (enemy.hp < 25) { ... } + +// ✅ CORRECT: Named constants +constexpr Scalar GROUND_Y = math::toScalar(200.0f); +constexpr int CRITICAL_HEALTH = 25; + +if (player.position.y > GROUND_Y) { ... } +if (enemy.health < CRITICAL_HEALTH) { ... } +``` + +### 6. Using std::vector in Game Loop + +```cpp +// ❌ WRONG: Potential reallocation +void update(unsigned long dt) { + enemies.push_back(new Enemy()); // May allocate! +} + +// ✅ CORRECT: Fixed-size pool +std::array enemies; +std::bitset enemyActive; + +void spawnEnemy() { + for (size_t i = 0; i < MAX_ENEMIES; ++i) { + if (!enemyActive[i]) { + enemyActive[i] = true; + enemies[i].reset(); + return; + } + } +} +``` + +--- + +## 📚 Related Documentation + +| Document | Topic | +|----------|-------| +| [Coding Style](coding-style.md) | C++ conventions | +| [Memory](memory.md) | Pool patterns, allocation | +| [UI Guidelines](ui-guidelines.md) | UI layouts, HUDs | +| [Performance](performance/esp32-performance.md) | Hot paths, optimization | + +--- + +*Good games feel responsive, consistent, and intentional.* diff --git a/guide/getting-started.md b/guide/getting-started.md index 3e10cbc..70d6440 100644 --- a/guide/getting-started.md +++ b/guide/getting-started.md @@ -6,83 +6,52 @@ PixelRoot32 follows a **scene-based architecture inspired by Godot Engine**, making it intuitive for developers familiar with modern game development workflows. -**Key Features:** - -- **Cross-Platform** — Develop on PC (Windows/Linux/macOS) and deploy on ESP32 -- **Scene-Entity System** — Intuitive management of Scenes, Entities, and Actors -- **High Performance** — Optimized for ESP32 with DMA transfers and IRAM-cached rendering -- **Sprite System** — 1bpp/2bpp/4bpp sprites with multi-palette, flipping, rotation, and animation -- **Tilemap Support** — Optimized rendering with viewport culling, multi-palette, and tile animations -- **NES-Style Audio** — Built-in 4-channel audio subsystem (Pulse, Triangle, Noise) -- **AABB Physics** — Godot-style physics with Kinematic/Rigid actors and sensors -- **Lightweight UI** — Label, Button, Checkbox with automatic layouts -- **Modular Architecture** — Compile only needed subsystems via `PIXELROOT32_ENABLE_*` flags +**Key features** + +- **Cross-platform** — Develop on PC (Windows/Linux/macOS) and deploy on ESP32 +- **Scene–entity system** — Scenes, entities, and actors +- **High performance** — DMA transfers and IRAM-friendly paths on ESP32 +- **Sprites** — 1bpp/2bpp/4bpp, palettes, animation +- **Tilemaps** — Viewport culling, multi-palette, tile animations +- **NES-style audio** — 4-channel subsystem +- **AABB physics** — Kinematic / rigid / static / sensor actors +- **UI** — Labels, buttons, layouts; optional touch widgets +- **Modular builds** — `PIXELROOT32_ENABLE_*` compile-time flags ## Prerequisites -Before you begin, ensure you have: - -- **VS Code** with the **PlatformIO IDE** extension installed -- **ESP32 DevKit** (ESP32-S3, ESP32-C3, or classic ESP32) or a PC for simulation -- **USB cable** for programming -- For PC development: **SDL2** libraries (see [Platform Configuration](/guide/platform-config)) +- **VS Code** with **PlatformIO IDE** +- **ESP32** board or PC for `native` builds +- **USB cable** for flashing (ESP32) +- For PC: **SDL2** dev libraries (see [Platform compatibility](./platform-compatibility.md)) ## Installation -### Option 1: Add to Existing Project (PlatformIO Registry) - -Add the library to your `platformio.ini`: +### Option 1: PlatformIO Registry ```ini lib_deps = gperez88/PixelRoot32-Game-Engine@^1.2.1 ``` -PlatformIO will automatically download and install the library during the next build. - -### Option 2: Clone the Repository - -For exploring examples and contributing: +### Option 2: Clone the repository ```bash git clone https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine.git cd PixelRoot32-Game-Engine ``` -### Open an Example Project - -The engine includes several self-contained examples, each with its own `platformio.ini`: +### Open an example -| Example | Description | -|---------|-------------| -| `hello_world` | Minimal setup with basic scene | -| `snake` | Classic Snake game with audio | -| `flappy_bird` | Physics and input demo | -| `metroidvania` | Platformer with tilemaps | -| `physics` | AABB physics showcase | -| `animated_tilemap` | Tile animations and caching | -| `sprites` | Sprite rendering and animation | -| `camera` | Camera follow and scrolling | -| `tic_tac_toe` | UI layout system demo | +Each example is a self-contained PlatformIO project under [`examples/`](../../examples/README.md). ```bash cd examples/hello_world ``` -### 3. Configure PlatformIO - -Open the example folder in VS Code (File → Open Folder) and select your environment: +## Configure PlatformIO -| Environment | Target | Use Case | -|-------------|--------|----------| -| `esp32dev` | Generic ESP32 | Most ESP32 development boards | -| `esp32cyd` | Cheap Yellow Display | Popular CYD modules | -| `esp32c3` | ESP32-C3 | Cost-optimized variants | -| `native` | PC (SDL2) | Development without hardware | - -::: warning Required Configuration - -To compile PixelRoot32, you **must** configure C++17 and disable exceptions in `platformio.ini`: +> **Required** — C++17 and no exceptions: ```ini build_unflags = -std=gnu++11 @@ -91,24 +60,9 @@ build_flags = -fno-exceptions ``` -::: - -## Creating Your First Project +Typical environments: `native`, `esp32dev`, `esp32cyd`, `esp32c3`, `esp32s3` (see each example’s `platformio.ini`). -### Project Structure - -A minimal PixelRoot32 project contains: - -``` -my_game/ -├── platformio.ini # PlatformIO configuration -├── src/ -│ └── main.cpp # Your game code -└── lib/ - └── PixelRoot32-Game-Engine/ # Engine library -``` - -### Minimal Main File +## Minimal main file ```cpp #include @@ -117,74 +71,34 @@ my_game/ using namespace pixelroot32; -// Your game scene class GameScene : public core::Scene { public: - void init() override { - // Called once when entering the scene - } - - void update(unsigned long deltaTime) override { - // Called every frame for game logic - (void)deltaTime; // Unused in this example - } - + void init() override {} + void update(unsigned long deltaTime) override { (void)deltaTime; } void draw(graphics::Renderer& renderer) override { - // Called every frame for rendering renderer.drawText("Hello World!", 10, 10, graphics::Color::WHITE, 2); } }; -// Global engine and scene core::Engine* engine; GameScene scene; void setup() { - // Configure display (240x240 logical resolution) graphics::DisplayConfig displayConfig(240, 240); - - // Configure input buttons input::InputConfig inputConfig; - inputConfig.addButton(input::ButtonName::A, 0); // GPIO 0 - - // Create engine + inputConfig.addButton(input::ButtonName::A, 0); engine = new core::Engine(std::move(displayConfig), inputConfig); - - // Set the initial scene engine->setScene(&scene); - - // Initialize and run engine->init(); - engine->run(); // Contains the infinite game loop + engine->run(); } -void loop() { - // Empty - engine.run() never returns -} +void loop() {} ``` -## Building and Running - -### For ESP32 - -1. **Select Environment**: Click the PlatformIO environment selector (bottom-left) and choose your board -2. **Build**: Click the checkmark icon or press `Ctrl+Alt+B` -3. **Upload**: Click the arrow icon or press `Ctrl+Alt+U` -4. **Monitor**: Click the plug icon to open serial monitor for debugging - -### For PC (Native) - -1. **Select Environment**: Choose `env:native` -2. **Build**: Build the project (automatically downloads SDL2 if needed) -3. **Run**: The executable runs directly on your PC +## Understanding the game loop -::: tip -Native development is ideal for rapid iteration. Test game logic and UI without flashing hardware. -::: - -## Understanding the Game Loop - -PixelRoot32 follows a classic game loop pattern: +PixelRoot32 follows a classic loop: ```mermaid flowchart LR @@ -195,173 +109,28 @@ flowchart LR E --> B ``` -| Phase | Description | Frequency | -|-------|-------------|-----------| -| **Input** | Poll buttons, touch, etc. | Every frame | -| **Update** | Game logic, physics, AI | Every frame | -| **Draw** | Render to framebuffer | Every frame | -| **Present** | Send to display (DMA) | Every frame | - -## Best Practices - -For optimal performance on ESP32: - -1. **Use Fixed-Point Math** — Always use `Scalar` instead of `float`. Convert literals with `math::toScalar()`. -2. **Zero Allocation Policy** — Avoid `new`/`malloc` in the game loop. Use Object Pooling and `std::unique_ptr`. -3. **Organize by Render Layers** — Use `renderLayer` (0=Bg, 1=Game, 2=UI) to optimize draw order. -4. **Platform Memory Macros** — Use `PIXELROOT32_FLASH_ATTR` and `PIXELROOT32_READ_*_P` for cross-platform Flash/RAM access. -5. **Centralized Logging** — Use `log()` from `core/Log.h` instead of `Serial.print`. - -::: tip -See the [Style & Best Practices Guide](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/STYLE_GUIDE.md) for detailed rules. -::: - -## Next Steps - -- **[Core Concepts](/guide/core-concepts)** — Learn about scenes, entities, and actors -- **[Rendering](/guide/rendering)** — Understand the graphics system -- **[Physics](/guide/physics)** — Add collision and movement -- **[Examples](/examples/basic-usage)** — Browse complete working examples - -## Troubleshooting - -### Known Issues - -#### ESP32-S3 DMA Freeze (Arduino Core > 2.0.14) - -**Problem**: DMA-based transfers may freeze after the first frame when using ESP32-S3 with Arduino Core versions newer than 2.0.14. - -**Workaround**: Pin Arduino Core to 2.0.14 in `platformio.ini`: - -```ini -[env:esp32s3] -platform_packages = - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#2.0.14 -``` - -> This is already configured in the `hello_world` example for ESP32-S3. - -#### Framework Cache Corruption (`pins_arduino.h` not found) - -**Problem**: Build fails with `pins_arduino.h: No such file or directory` after changing Arduino Core versions. - -**Solution**: - -1. Clean build cache: `pio run --target clean` -2. Remove corrupted package: `rmdir /s /q %USERPROFILE%\.platformio\packages\framework-arduinoespressif32` -3. Rebuild: `pio run` — PlatformIO will reinstall the framework. - -### Build Errors - -**Error**: `unknown type name 'std::optional'` - -- **Solution**: Ensure C++17 is enabled in `platformio.ini` - -**Error**: `undefined reference to 'SDL_Init'` - -- **Solution**: For native builds, ensure SDL2 development libraries are installed - -### Runtime Issues - -**Issue**: Blank screen on ESP32 - -- Check display pins match your board configuration -- Verify `TFT_eSPI` setup for your specific display - -**Issue**: Display freezes after the first frame on **ESP32-S3**, or build fails with **`pins_arduino.h` not found** after changing Arduino Core versions - -- See [ESP32-S3 DMA and Arduino Core](/guide/platform-config#esp32-s3-dma-arduino-core) and [Framework cache and pins_arduino.h](/guide/platform-config#framework-cache-pins-arduino) on the [Platform Configuration](/guide/platform-config) page - -**Issue**: Low FPS on ESP32 - -- Reduce logical resolution: `DisplayConfig(128, 128)` -- Prefer lower logical size and profile **draw** vs **present**; for static **4bpp** layers use `StaticTilemapLayerCache` (see Graphics API) and tune `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` if you need to trade RAM for full redraws. -- Check SPI speed settings - -## Platform-Specific Notes - -### ESP32-S3 - -- Optimal performance with FPU support -- Use float-based `Scalar` for math -- Full audio capabilities -- DMA display output may require pinning Arduino Core 2.0.14; see [ESP32-S3 DMA and Arduino Core](/guide/platform-config#esp32-s3-dma-arduino-core) - -### ESP32-C3 / ESP32-C6 - -- Fixed-point math automatically selected -- No hardware FPU (emulated in software) -- I2S audio only (no DAC) - -### ESP32 (Classic) - -- DAC audio available on GPIO 25/26 -- Fixed-point math recommended -- Original ESP32 support - -## Resources - -### Documentation - -- **[API Reference](/api/)** — Complete class and function documentation -- **[Architecture](/architecture/overview)** — System design and patterns -- **[Platform Configuration](/guide/platform-config)** — Board-specific setup - -### External Links - -- **[GitHub Repository](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine)** — Source code and issues -- **[PlatformIO Registry](https://registry.platformio.org/libraries/gperez88/PixelRoot32-Game-Engine)** — Library releases -- **[Style Guide](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/STYLE_GUIDE.md)** — Coding standards and best practices - -## Changelog - -## 1.2.1 (Latest) - -### 🏀 Physics - -- **Fixed Timestep Scheduler**: New `PhysicsScheduler` with accumulator-based 60Hz simulation for stable physics across variable frame rates, especially on ESP32 under WiFi/BT interrupt load. -- **Scene Integration**: `Scene` now uses the scheduler instead of direct `CollisionSystem::update()` calls. -- **Physics Optimizations**: Added adaptive step limiting, velocity clamping, damping, and fast reciprocal square root optimizations. - -### 🎮 Examples - -- **Space Invaders**: Complete sample game with grid-based movement, alien formations, projectile pooling, bunker defenses, swept collision, procedural audio, and native/ESP32 support. -- **Brick Breaker**: New breakout-style sample with paddle/ball physics, destructible bricks, collision layers, particles, audio, starfield effects, and HUD. - -### ⚡ Architecture & QA - -- **Build Profiles**: Fixed timestep physics is now enabled by default across build profiles. -- **Docs & Tests**: Expanded documentation and added comprehensive unit tests for the scheduler and physics behavior. - -### v1.2.0 - -**Architecture** - -- Physics conditionals refactored to preprocessor macros -- Namespace cleanup with aliases and selective `using` - -**Graphics** - -- ILI9341 display support -- Static tilemap layer cache for ESP32 fast-path rendering -- Tile animation fixes - -**Math** - -- Deterministic PRNG (Xorshift32) with thread-safe `Random` struct +| Phase | Description | +|-------|-------------| +| **Input** | Buttons, touch, etc. | +| **Update** | Logic, physics, AI | +| **Draw** | Framebuffer | +| **Present** | DMA / display output | -**Input** +See [Game loop](./game-loop.md) for detail. -- Touch pipeline abstraction (XPT2046/GT911) -- ESP32 CYD gesture system with consume/propagate semantics +## Best practices (ESP32) -**UI** +1. Use **`math::Scalar`** / fixed-point patterns from the style guide; avoid raw `float` literals where the project uses fixed math. +2. **No heap churn** in `update()`/`draw()` — pool or pre-allocate in `init()`. +3. **Render layers** — background / world / UI ordering. +4. Use **`log()`** from `core/Log.h` instead of ad-hoc `Serial` spam. -- Function pointer callbacks replacing `std::function` -- Touch UI components: `UITouchButton`, `UITouchCheckbox`, `UITouchSlider` +See [Coding style](./coding-style.md) and [Memory system](../architecture/memory-system.md). -::: tip Migration Guide -Upgrading from v1.1.0? See [MIGRATION_v1.2.0](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/docs/MIGRATION_v1.2.0.md) -::: +## Next steps -**Full changelog:** [CHANGELOG.md](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/blob/main/CHANGELOG.md) +- [Core diagrams & scenes](../architecture/layer-scene.md) — entities and scene layer +- [Game loop](./game-loop.md) +- [Entities tutorial](./entities-scene-tutorial.md) — didactic `Entity` patterns +- [API index](../api/index.md) +- [Changelog](../../CHANGELOG.md) diff --git a/guide/graphics-guidelines.md b/guide/graphics-guidelines.md new file mode 100644 index 0000000..7c3a731 --- /dev/null +++ b/guide/graphics-guidelines.md @@ -0,0 +1,329 @@ +# Graphics Guidelines - PixelRoot32 + +Patterns and best practices for rendering, sprites, tilemaps, and palette systems. + +**Pipeline first:** read **[Rendering](./rendering.md)** for `Renderer`, layers, camera, and draw flow. This page focuses on **asset layout**, bitmap conventions, and tile/palette usage. + +--- + +## 🎨 Sprite Guidelines + +### 1bpp Sprite Definition + +Define sprites as `static const uint16_t` arrays, one row per element: + +```cpp +// ✅ 1bpp sprite: bit 0 = leftmost, bit (width-1) = rightmost +static const uint16_t playerBitmap[] = { + 0b0000001111000000, // Row 0 + 0b0000011111100000, // Row 1 + 0b0000111111110000, // Row 2 + 0b0000111111110000, // Row 3 + 0b0000010101000000, // Row 4 +}; + +pixelroot32::graphics::Sprite playerSprite = { + .width = 16, + .height = 5, + .data = playerBitmap +}; +``` + +### Sprite Descriptors + +Always wrap bitmaps in descriptors: + +```cpp +// ✅ Descriptor approach +void MyActor::draw(Renderer& r) { + r.drawSprite(playerSprite, static_cast(x), static_cast(y)); +} + +// ❌ Never pass raw bitmaps +void MyActor::draw(Renderer& r) { + r.drawBitmap(playerBitmap, ...); // Wrong API +} +``` + +### Layered Sprites (Multi-Color) + +Compose from multiple 1bpp layers: + +```cpp +// ✅ Layered multi-color sprite +static const uint16_t shipOutline[] = { ... }; +static const uint16_t shipCockpit[] = { ... }; +static const uint16_t shipEngine[] = { ... }; + +pixelroot32::graphics::MultiSprite ship = { + .layers = { + {shipOutline, PaletteType::PR32}, + {shipCockpit, PaletteType::NES}, + {shipEngine, PaletteType::GB} + }, + .layerCount = 3 +}; + +renderer.drawMultiSprite(ship, x, y); +``` + +**Keep layer data `static const`** for flash storage. + +### Higher Bit Depth (Optional) + +Enable for specific use cases: + +```cpp +// platformio.ini +-DPIXELROOT32_ENABLE_2BPP_SPRITES=1 // 2x memory +-DPIXELROOT32_ENABLE_4BPP_SPRITES=1 // 4x memory +``` + +| Format | Use For | Cost | +|--------|---------|------| +| 1bpp (default) | Gameplay sprites, tiles | 1x | +| 2bpp | Logos, detailed UI | 2x | +| 4bpp | Photos, title screens | 4x | + +**Default to 1bpp** for gameplay-critical assets. + +--- + +## 🗺️ Tilemap System + +### Basic Tilemap + +```cpp +// Tile indices (compact uint8_t) +static const uint8_t tileIndices[] = { + 0, 0, 0, 0, // Row 0 + 0, 1, 1, 0, // Row 1 (tile 1 = ground) + 0, 1, 1, 0, // Row 2 + 0, 0, 0, 0, // Row 3 +}; + +pixelroot32::graphics::TileMap level = { + .width = 4, + .height = 4, + .tileWidth = 16, + .tileHeight = 16, + .tiles = tileIndices, + .tileset = groundTiles, + .tilesetSize = 2 // 2 unique tiles +}; + +// Draw +renderer.drawTileMap(level, 0, 0); // x, y offset +``` + +### Tile Reuse + +Reuse tiles across the map to minimize flash: + +```cpp +// ✅ Reuse: indices point to same tile data +// Tile 0 = sky (used 100 times) +// Tile 1 = ground (used 20 times) +// Total unique tiles: 2, not 120 +``` + +### Scrolling with Camera + +```cpp +// ✅ Centralized camera logic +class GameScene : public Scene { + Camera2D camera; + +public: + void update(unsigned long dt) { + camera.setTarget(player->position); + camera.update(dt); + } + + void draw(Renderer& r) { + r.setDisplayOffset(-camera.getX(), -camera.getY()); + r.drawTileMap(backgroundLayer, 0, 0); + Scene::draw(r); // Actors + } +}; +``` + +--- + +## 🎬 Tile Animation + +### Memory Budget + +| Tileset Size | RAM Usage | % ESP32 DRAM | +|--------------|-----------|--------------| +| 64 tiles | 73 bytes | 0.02% | +| 128 tiles | 137 bytes | 0.04% | +| 256 tiles | 265 bytes | 0.08% | + +**Start with 64-128 tiles.** + +### Initialization + +```cpp +// In PROGMEM +PIXELROOT32_SCENE_FLASH_ATTR const TileAnimation animations[] = { + { 2, 4, 8, 0 }, // Water: tiles 2-5, 4 frames, 8× (1/60 s) ticks per cell + { 6, 2, 6, 0 }, // Lava: tiles 6-7, 2 frames, 6× (1/60 s) ticks per cell +}; + +TileAnimationManager animManager(animations, 2, 64); + +// Link to tilemap +TileMap2bpp backgroundLayer = { + // ... other fields ... + .animManager = &animManager // Enables animations +}; +``` + +### Game Loop Integration + +```cpp +void MyScene::update(unsigned long dt) { + animManager.step(dt); // Wall-time pacing (see TileAnimationManager API) + Scene::update(dt); +} + +void MyScene::draw(Renderer& r) { + r.drawTileMap(backgroundLayer, 0, 0); + Scene::draw(r); +} +``` + +### Speed Control + +Animation speed is driven by **`frameDuration`** in **`TileAnimation`** data (larger value → each sprite frame held longer). Do not gate **`step(dt)`** on engine loop count to change speed; that breaks wall-clock pacing when the loop runs faster than the display. + +```cpp +// Pause when game paused (do not call step while frozen) +if (!isPaused) { + animManager.step(dt); +} +``` + +### Common Pitfalls + +1. **Sequential frames only**: Tiles 2,3,4,5 - not 2,5,9,12 +2. **Shared state**: All instances of a tile share the same frame +3. **StaticTilemapLayerCache**: If tilemap is in **static** group, advancing tile animation requires `invalidate()` on that cache when applicable + +--- + +## 🎨 Multi-Palette Systems + +### Slot-Based Palettes + +Separate palettes for sprites and backgrounds: + +```cpp +// Initialize during scene init +void MyScene::init() { + pixelroot32::graphics::Color::enableDualPaletteMode(true); + + // Background slots (for tilemaps) + pixelroot32::graphics::initBackgroundPaletteSlots(); + setBackgroundPaletteSlot(0, PaletteType::PR32); // Ground + setBackgroundPaletteSlot(1, PaletteType::NES); // Water + setBackgroundPaletteSlot(2, PaletteType::GB); // Underground + + // Sprite slots + pixelroot32::graphics::initSpritePaletteSlots(); + setSpritePaletteSlot(0, PaletteType::PR32); // Player + setSpritePaletteSlot(1, PaletteType::NES); // Fire enemies + setSpritePaletteSlot(2, PaletteType::GBC); // Ice enemies +} +``` + +### Custom Palettes + +```cpp +// In PROGMEM +static const uint16_t CUSTOM_FIRE[] = { + 0x0000, 0xFFFF, 0xF800, 0xFC00, // Colors 0-3 + 0xFA00, 0xF800, 0xF600, 0xF400, // Colors 4-7 + // ... 16 colors total +}; + +// Apply to slot +setSpriteCustomPaletteSlot(5, CUSTOM_FIRE); +``` + +### Batching with Context + +```cpp +// ✅ Set context once for many sprites +void BulletManager::drawAll(Renderer& r) { + r.setSpritePaletteSlotContext(1); // Fire palette + + for (auto& bullet : bullets) { + r.drawSprite(bulletSprite, bullet.x, bullet.y); + } + + r.setSpritePaletteSlotContext(0xFF); // Reset +} +``` + +### Slot Documentation + +```cpp +// Background slots: +// 0: Default ground (PR32) +// 1: Water areas (NES - blue) +// 2: Underground (GB - green) +// 3: Lava (custom red) +// 4-7: Reserved + +// Sprite slots: +// 0: Player (PR32) +// 1: Fire enemies (NES) +// 2: Ice enemies (GBC) +// 3: Boss (PICO8) +// 4-7: Reserved +``` + +--- + +## 🧱 Render Layers + +Standard layer assignment: + +| Layer | Content | +|-------|---------| +| 0 | Background (tilemaps, fills) | +| 1 | Gameplay (player, enemies, bullets) | +| 2 | UI (HUD, menus, text) | + +```cpp +class MyActor : public Actor { +public: + MyActor() { + renderLayer = 1; // Gameplay layer + } +}; + +class HUD : public Actor { +public: + HUD() { + renderLayer = 2; // UI layer (top) + } +}; +``` + +--- + +## 📚 Related Documentation + +| Document | Topic | +|----------|-------| +| [UI Guidelines](ui-guidelines.md) | Layouts, panels, HUDs | +| [Performance](performance/esp32-performance.md) | Hot paths, optimization | +| [Tile animation](../architecture/tile-animation.md) | Animation system deep dive | +| [API Graphics](../api/graphics.md) | Complete graphics API | + +--- + +*Graphics should be efficient, authentic, and layer-friendly.* diff --git a/guide/graphics-techniques.md b/guide/graphics-techniques.md new file mode 100644 index 0000000..bb50fb1 --- /dev/null +++ b/guide/graphics-techniques.md @@ -0,0 +1,63 @@ +# Graphics Techniques - PixelRoot32 + +> **Note:** This document combines `tilemaps.md` and `multi-palette.md` for unified reference. + +--- + +## Tilemaps + +Tilemaps are efficient for backgrounds and level geometry: the engine stores compact tile indices, applies palettes, and can skip off-screen regions. + +### Features + +- Generic `TileMap` templates for different BPP modes (see [TileMap API](../api/graphics.md#tilemaps)). +- Viewport culling so only visible tiles hit the draw surface. +- Optional **tile animations** (water, lava, etc.) with O(1) frame lookup when enabled — see [Tile animation architecture](../architecture/tile-animation.md). +- Optional static layer cache on ESP32 for heavy 4bpp multi-layer scenes (described in the graphics API). + +### Compile-time Flags + +`PIXELROOT32_ENABLE_TILE_ANIMATIONS`, `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE`, and related switches are documented in [Configuration flags](../api/config.md) and the [API overview](../api/index.md). + +### Examples in Engine Repo + +- `examples/animated_tilemap` — animated tiles +- `examples/metroidvania` — larger scrollable maps +- `examples/snake` — minimal grid usage + +--- + +## Multi-Palette and Indexed Color + +Sprites and tile layers use **indexed** pixel data plus **palette** tables. You can switch palettes per draw call or layer to get more visual variety without wider BPP. + +### Color Modes + +| Mode | Footprint | Colors | Enable Flag | +|------|-----------|--------|-------------| +| **1bpp** | 1 bit/pixel | 2 colors | (always on) | +| **2bpp** | 2 bits/pixel | 4 colors | `PIXELROOT32_ENABLE_2BPP_SPRITES` | +| **4bpp** | 4 bits/pixel | 16 colors | `PIXELROOT32_ENABLE_4BPP_SPRITES` | + +Built-in palette presets (PR32, NES, Game Boy, PICO-8, etc.) are described in the [Color / graphics API](../api/graphics.md#color-and-palettes). + +### Workflow + +1. Store art as indices (and optional per-tile attributes). +2. Assign a `PaletteType` or custom colors when drawing sprites or tile layers. +3. Use multi-palette tile attributes where supported (see tile attribute section in [TileMap API](../api/graphics.md#tilemaps)). + +### Example Project + +The `examples/dual_palette` sample in the engine repository demonstrates switching palettes in a real scene. + +--- + +## Related Documentation + +| Document | Topic | +|----------|-------| +| [Rendering](./rendering.md) | Core rendering pipeline | +| [Graphics Guidelines](./graphics-guidelines.md) | Best practices | +| [Tile Animation](../architecture/tile-animation.md) | Animation system deep dive | +| [Physics tile collision](../api/physics.md#collision-system-the-flat-solver) | Tilemap collision | \ No newline at end of file diff --git a/guide/index.md b/guide/index.md new file mode 100644 index 0000000..fe30138 --- /dev/null +++ b/guide/index.md @@ -0,0 +1,56 @@ +# Guide + +User guides and how-to documentation for PixelRoot32 Game Engine. + +## Getting Started + +- [Getting Started](./getting-started.md) — Install, first project, game loop overview +- [Game Loop](./game-loop.md) — Phases, delta time, lifecycle diagrams +- [Music Player Guide](./music-player-guide.md) - Complete guide for the NES-style audio system +- [Extending PixelRoot32](./extending-pixelroot32.md) - Creating custom drivers and extending the engine +- [Gameplay Guidelines](./gameplay-guidelines.md) - Best practices for game development +- [Entities & scene tutorial](./entities-scene-tutorial.md) - Didactic `Entity` walkthrough (not a bundled `examples/` folder) + +## Game development topics + +- [Core concepts](./core-concepts.md) — Terminology and mental model +- [Scenes](./scenes.md) — Scene lifecycle, stacking, transitions +- [Entities & actors](./entities-actors.md) — Patterns for gameplay objects +- [Rendering](./rendering.md) — Sprites, layers, camera-oriented drawing +- [Input](./input.md) — `InputManager`, touch pipeline, calibration +- [Physics](./physics.md) — Collisions, actors, tile helpers +- [Audio](./audio.md) — Channels, SFX, and music overview +- [UI system](./ui-system.md) — Layouts, widgets, HUDs + +## Advanced + +- [Memory](./memory.md) — Pools, arenas, embedded constraints +- [Performance](./performance/esp32-performance.md) — Hot paths, memory, build profiles, resolution scaling +- [Graphics Techniques](./graphics-techniques.md) — Tilemaps, palettes, indexed color +- [Platform Compatibility](./platform-compatibility.md) — `platformio.ini`, feature flags + +## Contributing & tooling + +- [Testing](./testing.md) — Unity, `native_test`, coverage, CI + +## Standards & compatibility + +- [Coding style](./coding-style.md) — C++ conventions, namespaces, naming +- [Coding Style](./coding-style.md) — C++ conventions, namespaces, naming +- [Graphics guidelines](./graphics-guidelines.md) — Sprites, tilemaps, palettes (pipeline: [Rendering](./rendering.md)) +- [UI guidelines](./ui-guidelines.md) — Layout patterns (architecture: [UI system](./ui-system.md)) +- [Platform compatibility](./platform-compatibility.md) — Hardware matrix, ESP32 variants +- [Performance](./performance/) — ESP32 hot paths, build profiles + +## Tools & samples + +- [Tools (sprite compiler, tilemap editor docs)](../tools/index.md) - Workflow documentation shipped with the engine +- [Example projects catalog](../../examples/README.md) - PlatformIO samples under `examples/` + +## Navigation + +- [Architecture](../architecture/) - System architecture and design +- [API Reference](../api/) - Complete API documentation +- [Workflow tools](../tools/index.md) - Sprite compiler and tilemap editor documentation +- [Migration](../migration/) - Version upgrade guides +- [Philosophy](../philosophy/) - Engine design philosophy diff --git a/guide/input.md b/guide/input.md index a71b76c..7fbc621 100644 --- a/guide/input.md +++ b/guide/input.md @@ -259,7 +259,7 @@ int virtualDirFromTouch(const pixelroot32::input::TouchEvent& event, ### Gestures -Higher-level gestures (swipe, long-press) are usually derived from `TouchEvent` streams in `onUnconsumedTouchEvent` or custom widgets—see [Touch system API](/api/input/touch-system). +Higher-level gestures (swipe, long-press) are usually derived from `TouchEvent` streams in `onUnconsumedTouchEvent` or custom widgets—see [Touch system API](../api/input.md#touch-input-system). ### Buffered combos @@ -303,6 +303,6 @@ Wire your scene’s `update` method so it receives `engine.getInputManager()` (o ## Next steps -- [Touch input architecture](/architecture/ARCH_TOUCH_INPUT) -- [UI system](/guide/ui-system) -- [InputManager API](/api/input/input-manager) +- [Touch input architecture](../architecture/touch-input.md) +- [UI system](./ui-system.md) +- [InputManager API](../api/input.md#inputmanager) diff --git a/guide/memory.md b/guide/memory.md index 6140ae0..0d9d538 100644 --- a/guide/memory.md +++ b/guide/memory.md @@ -1,8 +1,8 @@ -# Memory Management +# Memory -PixelRoot32 is designed for memory-constrained embedded systems. Understanding the memory model is crucial for building stable, performant games. +PixelRoot32 is designed for memory-constrained embedded systems. This page is the **single developer guide** for pools, heaps, and zero-allocation rules in gameplay. For the full **architecture** treatment (arenas, ownership, IRAM/PSRAM policy), see [Memory system](../architecture/memory-system.md). -## Memory Model Overview +## Memory model overview ```mermaid flowchart TB @@ -397,5 +397,5 @@ log("Stack free: %d words", watermark); ## Next Steps -- **[Platform Configuration](/guide/platform-config)** — Build flags and optimization -- **[Resolution scaling](/guide/resolution-scaling)** — Logical vs physical framebuffer trade-offs +- **[Platform Compatibility](./platform-compatibility.md)** — Build flags and optimization +- **[Performance Guide](./performance/esp32-performance.md)** — Logical vs physical framebuffer trade-offs diff --git a/guide/migrations/overview.md b/guide/migrations/overview.md deleted file mode 100644 index 8995a17..0000000 --- a/guide/migrations/overview.md +++ /dev/null @@ -1,11 +0,0 @@ -# Migration guides - -Release notes and upgrade steps when the public API or build flags change between versions. - -| Version | Summary | -|---------|---------| -| [v1.0.0](./v1.0.0) | Baseline migration topics for the 1.0 line. | -| [v1.1.0](./v1.1.0) | Changes introduced in the 1.1 release. | -| [v1.2.0](./v1.2.0) | Physics actor flag packing and related API adjustments. | - -The engine `library.json` version tracks the latest tagged release; always check the migration page for your target version before upgrading a game project. diff --git a/guide/multi-palette.md b/guide/multi-palette.md deleted file mode 100644 index faeb052..0000000 --- a/guide/multi-palette.md +++ /dev/null @@ -1,25 +0,0 @@ -# Multi-palette and indexed color - -Sprites and tile layers use **indexed** pixel data plus **palette** tables. You can switch palettes per draw call or layer to get more visual variety without wider BPP. - -## Modes - -- **1bpp** — smallest footprint; one bit per pixel selects between two colors from a palette. -- **2bpp / 4bpp** — optional; enable with `PIXELROOT32_ENABLE_2BPP_SPRITES` / `PIXELROOT32_ENABLE_4BPP_SPRITES` (see [Configuration](/api/modules/configuration)). - -Built-in palette presets (PR32, NES, Game Boy, PICO-8, etc.) are described in the [Color / graphics API](/api/graphics/color). - -## Workflow - -1. Store art as indices (and optional per-tile attributes). -2. Assign a `PaletteType` or custom colors when drawing sprites or tile layers. -3. Use multi-palette tile attributes where supported (see tile attribute section in [TileMap API](/api/graphics/tilemap)). - -## Example project - -The `examples/dual_palette` sample in the engine repository demonstrates switching palettes in a real scene. - -## Related - -- [Rendering](/guide/rendering) -- [Tilemaps](/guide/tilemaps) diff --git a/guide/music-player.md b/guide/music-player-guide.md similarity index 61% rename from guide/music-player.md rename to guide/music-player-guide.md index 536cfb5..df733a0 100644 --- a/guide/music-player.md +++ b/guide/music-player-guide.md @@ -1,14 +1,12 @@ # MusicPlayer Integration Guide - PixelRoot32 -**Document Version:** 1.0 -**Last Updated:** February 2026 -**Engine Version:** v1.0.0 - ## Overview -The `MusicPlayer` class provides a simple way to add background music and melodies. It integrates with the NES-style audio stack (**`ApuCore`** tick sequencer), supports **tempo** and **BPM**, **looping**, and **multi-track** playback (**main** `MusicTrack` plus optional **`secondVoice`**, **`thirdVoice`**, **`percussion`**). For a complete reference sketch, open the engine’s **[`music_demo`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo)** sample. +The `MusicPlayer` class provides a simple yet powerful way to add background music and melodies to your PixelRoot32 games. It integrates seamlessly with the **NES-inspired** audio stack and supports tempo control, looping, dynamic music switching, and **multi-track layering** (`secondVoice`, `thirdVoice`, `percussion`) so you can spell out arpeggiated figures with ordinary **`MusicNote`** data. + +**Modular Compilation:** The MusicPlayer is only compiled when `PIXELROOT32_ENABLE_AUDIO=1`. When disabled, all music-related functionality is excluded from the build, saving both firmware size and RAM usage. `MusicTrack::channelType` supports `PULSE`, `TRIANGLE`, `NOISE`, `SINE`, and `SAW`. -**Modular Compilation:** The MusicPlayer is only compiled when `PIXELROOT32_ENABLE_AUDIO=1`. When disabled, all music-related functionality is excluded from the build, saving both firmware size and RAM usage. +**Voice pool vs. sequencer tracks:** `MusicPlayer` can arrange up to **`MAX_MUSIC_TRACKS` (4)** logical layers (main + sub-tracks), but every note still becomes a `PLAY_EVENT` inside **`ApuCore`**, which mixes at most **`ApuCore::MAX_VOICES` (8)** simultaneous **voices**. Dense chords, fast arps, **plus** heavy SFX can exceed eight concurrent notes and trigger **voice stealing** (shortest remaining note is replaced). Author shorter note lengths or fewer simultaneous layers if you need deterministic timbres on hardware. This guide covers everything from basic music playback to advanced patterns like adaptive soundtracks and smooth transitions. @@ -21,6 +19,7 @@ This guide covers everything from basic music playback to advanced patterns like ```cpp #include "audio/MusicPlayer.h" #include "audio/AudioMusicTypes.h" +#include "audio/AudioTypes.h" using namespace pixelroot32::audio; @@ -37,10 +36,7 @@ static const MusicTrack SIMPLE_TRACK = { sizeof(SIMPLE_MELODY) / sizeof(MusicNote), true, // Loop enabled WaveType::PULSE, // Use pulse wave - 0.5f, // 50% duty cycle - nullptr, // secondVoice (optional) - nullptr, // thirdVoice (optional) - nullptr, // percussion (optional) + 0.5f // 50% duty cycle }; // In your scene's init() method @@ -66,33 +62,87 @@ struct MusicTrack { const MusicNote* notes; // Array of notes size_t count; // Number of notes bool loop; // Whether to loop the track - WaveType channelType; // Wave type (PULSE, TRIANGLE, NOISE) + WaveType channelType; // PULSE, TRIANGLE, NOISE, SINE, SAW float duty; // Duty cycle for pulse waves (0.0-1.0) - const MusicTrack* secondVoice = nullptr; - const MusicTrack* thirdVoice = nullptr; - const MusicTrack* percussion = nullptr; + + // Multi-track support (optional) + const MusicTrack* secondVoice = nullptr; // Second melody voice + const MusicTrack* thirdVoice = nullptr; // Third melody voice + const MusicTrack* percussion = nullptr; // Drum/percussion track }; ``` -Up to **`MAX_MUSIC_TRACKS` (4)** layers run together: one **root** track plus any non-null pointers. `MusicPlayer::getActiveTrackCount()` returns how many layers were enabled on the last `play()` (1–4). +### Multi-Track Music Playback -### MusicNote Definition +The MusicPlayer supports up to 4 simultaneous tracks playing in parallel: ```cpp -struct MusicNote { - Note note; // Musical note (C, D, E, etc.) or Rest - uint8_t octave; // Octave number (0-8) - float duration; // Duration in seconds - float volume; // Volume (0.0-1.0) - const InstrumentPreset* preset = nullptr; // from makeNote(), or nullptr +// Define three separate tracks +static const MusicNote MELODY_NOTES[] = { + makeNote(INSTR_PULSE_LEAD, Note::C, 4, 0.25f), + makeNote(INSTR_PULSE_LEAD, Note::E, 4, 0.25f), + makeNote(INSTR_PULSE_LEAD, Note::G, 4, 0.25f), }; + +static const MusicNote BASS_NOTES[] = { + makeNote(INSTR_TRIANGLE_BASS, Note::C, 2, 0.5f), + makeNote(INSTR_TRIANGLE_BASS, Note::G, 2, 0.5f), +}; + +static const MusicNote DRUM_NOTES[] = { + makeNote(INSTR_KICK, Note::Rest, 0.25f), + makeRest(0.25f), + makeNote(INSTR_SNARE, Note::Rest, 0.25f), + makeRest(0.25f), +}; + +// Create individual tracks +static const MusicTrack MELODY_TRACK = { + MELODY_NOTES, 3, true, WaveType::PULSE, 0.5f +}; + +static const MusicTrack BASS_TRACK = { + BASS_NOTES, 2, true, WaveType::TRIANGLE, 0.5f +}; + +static const MusicTrack DRUM_TRACK = { + DRUM_NOTES, 4, true, WaveType::NOISE, 0.0f +}; + +// Combine into main track with sub-tracks +static const MusicTrack FULL_MUSIC = { + MELODY_NOTES, 3, true, WaveType::PULSE, 0.5f, + &BASS_TRACK, // secondVoice - bass line + nullptr, // thirdVoice - not used + &DRUM_TRACK // percussion - drums +}; + +// Play all 3 tracks simultaneously +musicPlayer.play(FULL_MUSIC); + +// Query active track count +size_t count = musicPlayer.getActiveTrackCount(); // Returns 3 ``` -### InstrumentPreset (header) +> **Note:** All sub-track pointers default to `nullptr` for backward compatibility with existing single-track code. + +--- + +## Manual arpeggios (extra voice) -Authoritative definitions live in **`AudioMusicTypes.h`** (`constexpr` presets such as **`INSTR_PULSE_LEAD`**, **`INSTR_TRIANGLE_BASS`**, **`INSTR_KICK`**, **`INSTR_SNARE`**, **`INSTR_HIHAT`**). Percussion presets use **`duty == 0`** and optional **`defaultDuration`** / **`noisePeriod`** for noise hits—do not redefine them locally unless you are experimenting. +There is **no separate arpeggiator API**. To get a rapid broken-chord line under a lead, add a **`MusicTrack`** hooked via **`secondVoice`** or **`thirdVoice`** whose **`MusicNote`** entries use **short `duration` values** (in **beats**, same grid as the rest of the sequencer). You can use **`WaveType::SINE`** / **`SAW`** directly on that layer. See **Melody 4** in `examples/music_demo/src/assets/melodies.h`. -For **drum rows** authored with those percussion presets, **`Note::C`** (as in examples below) and **`Note::Rest`** (as in the engine’s **`MUSIC_PLAYER_GUIDE.md`**) are both valid placeholders: pitch is driven by **`instrumentToFrequency`** and the preset, not by the nominal note name. +### MusicNote Definition + +```cpp +struct MusicNote { + Note note; // Musical note (C, D, E, etc.) + uint8_t octave; // Octave number (0-8). For percussion: 1=Kick, 2=Snare, 3+=Hi-HAT + float duration; // Duration in seconds + float volume; // Volume (0.0-1.0) + const InstrumentPreset* preset; // Optional: pointer to instrument preset for percussion +}; +``` --- @@ -111,6 +161,12 @@ makeNote(INSTR_PULSE_LEAD, Note::C, 5, 0.25f); // Rest (silence) makeRest(0.5f); + +// Using predefined instruments +static const InstrumentPreset INSTR_PULSE_LEAD = { + 0.8f, // Base volume + 0.5f // Duty cycle +}; ``` ### Musical Notes and Octaves @@ -141,9 +197,7 @@ enum class Note : uint8_t { ## Advanced Music Patterns -### 1. Tempo control (factor + BPM) - -**`setTempoFactor` / `getTempoFactor`** scale playback speed (relative). **`setBPM` / `getBPM`** set absolute beats per minute (engine default **150**; clamped in **`MusicPlayer`**). Tick scheduling in **`ApuCore`** uses BPM together with the tempo factor. +### 1. Tempo Control ```cpp #if PIXELROOT32_ENABLE_AUDIO @@ -166,9 +220,6 @@ public: currentTempo -= 0.02f; musicPlayer.setTempoFactor(currentTempo); } - - // Example: hard switch to 128 BPM for a calmer section - // musicPlayer.setBPM(128.0f); } }; #endif @@ -194,52 +245,33 @@ void GameScene::switchToBattleMusic() { } ``` -### 3. Layered music (multi-track) - -Wire extra `MusicTrack` instances from the **root** track. `MusicPlayer::play` copies non-null pointers into `AudioCommand::subTracks` in order: **secondVoice → thirdVoice → percussion**. +### 3. Layered Music System ```cpp -// Bass on triangle -static const MusicNote BASS_NOTES[] = { +// Background layer (bass line) +static const MusicNote BASS_LINE[] = { makeNote(INSTR_TRIANGLE_BASS, Note::C, 2, 0.5f), makeNote(INSTR_TRIANGLE_BASS, Note::G, 2, 0.5f), makeNote(INSTR_TRIANGLE_BASS, Note::A, 2, 0.5f), makeNote(INSTR_TRIANGLE_BASS, Note::F, 2, 0.5f), }; -static const MusicTrack BASS_TRACK = { - BASS_NOTES, sizeof(BASS_NOTES)/sizeof(MusicNote), true, WaveType::TRIANGLE, 0.5f, -}; -// Lead on pulse +// Melody layer (lead) static const MusicNote MELODY[] = { makeNote(INSTR_PULSE_LEAD, Note::C, 4, 0.25f), makeNote(INSTR_PULSE_LEAD, Note::E, 4, 0.25f), makeNote(INSTR_PULSE_LEAD, Note::G, 4, 0.25f), makeNote(INSTR_PULSE_LEAD, Note::C, 5, 0.25f), }; -static const MusicTrack MELODY_TRACK = { - MELODY, sizeof(MELODY)/sizeof(MusicNote), true, WaveType::PULSE, 0.5f, -}; -// Optional: simple drum loop on NOISE (see INSTR_KICK / SNARE / HIHAT) -static const MusicNote DRUM_LINE[] = { - makeNote(INSTR_KICK, Note::C, 0.12f), - makeRest(0.12f), - makeNote(INSTR_SNARE, Note::C, 0.15f), - makeRest(0.13f), -}; -static const MusicTrack DRUM_TRACK = { - DRUM_LINE, sizeof(DRUM_LINE)/sizeof(MusicNote), true, WaveType::NOISE, 0.0f, +// Use different wave types for variety +static const MusicTrack BASS_TRACK = { + BASS_LINE, sizeof(BASS_LINE)/sizeof(MusicNote), true, WaveType::TRIANGLE, 0.5f }; -// Root: lead + bass + drums → getActiveTrackCount() == 3 (add thirdVoice for four) -static const MusicTrack LAYERED = { - MELODY, sizeof(MELODY)/sizeof(MusicNote), true, WaveType::PULSE, 0.5f, - &BASS_TRACK, - nullptr, - &DRUM_TRACK, +static const MusicTrack MELODY_TRACK = { + MELODY, sizeof(MELODY)/sizeof(MusicNote), true, WaveType::PULSE, 0.75f }; -// musicPlayer.play(LAYERED); ``` ### 4. Adaptive Soundtrack @@ -350,13 +382,14 @@ void GameScene::playAttackSound() { // Slight tempo reduction for dramatic effect musicPlayer.setTempoFactor(originalTempo * 0.95f); - // Play attack sound effect - engine.getAudioEngine().playEvent({ - WaveType::NOISE, - 200.0f, // Low frequency for impact - 0.8f, // High volume - 0.1f // Short duration - }); + // Play attack sound effect (AudioEvent: type, frequency, duration, volume, duty, ...) + AudioEvent hit{}; + hit.type = WaveType::NOISE; + hit.frequency = 200.0f; // noise clock / density + hit.duration = 0.1f; + hit.volume = 0.8f; + hit.duty = 0.5f; + engine.getAudioEngine().playEvent(hit); // Restore tempo after delay delay(100); @@ -415,10 +448,10 @@ static const MusicNote BLUES_PROGRESSION[] = { }; ``` -### Arpeggio Pattern +### Broken chord / arpeggiated bass (manual) ```cpp -static const MusicNote ARPEGGIO[] = { +static const MusicNote ARPEGGIATED_BASS[] = { makeNote(INSTR_TRIANGLE_BASS, Note::C, 4, 0.25f), makeNote(INSTR_TRIANGLE_BASS, Note::E, 4, 0.25f), makeNote(INSTR_TRIANGLE_BASS, Note::G, 4, 0.25f), @@ -430,20 +463,21 @@ static const MusicNote ARPEGGIO[] = { }; ``` -### Percussive rhythm - -Use **`INSTR_KICK`**, **`INSTR_SNARE`**, **`INSTR_HIHAT`** (or your own **`InstrumentPreset`** with **`duty == 0`**) on a track with **`WaveType::NOISE`**, often linked as **`percussion`** from the main `MusicTrack`: +### Percussive Rhythm ```cpp +// Use INSTR_KICK, INSTR_SNARE, INSTR_HIHAT presets with WaveType::NOISE static const MusicNote DRUM_PATTERN[] = { - makeNote(INSTR_KICK, Note::C, 0.12f), - makeRest(0.125f), - makeNote(INSTR_KICK, Note::C, 0.12f), - makeRest(0.125f), - makeNote(INSTR_SNARE, Note::C, 0.15f), - makeRest(0.125f), - makeNote(INSTR_KICK, Note::C, 0.12f), + makeNote(INSTR_KICK, Note::Rest, 0.25f), // Kick on beat 1 + makeRest(0.25f), + makeNote(INSTR_SNARE, Note::Rest, 0.25f), // Snare on beat 2 + makeRest(0.25f), + makeNote(INSTR_KICK, Note::Rest, 0.125f), // Kick (eighth note) + makeNote(INSTR_HIHAT, Note::Rest, 0.125f), // Hi-HAT (eighth note) + makeRest(0.25f), + makeNote(INSTR_SNARE, Note::Rest, 0.25f), // Snare on beat 4 makeRest(0.125f), + makeNote(INSTR_HIHAT, Note::Rest, 0.125f), // Hi-HAT }; ``` @@ -462,6 +496,7 @@ static const MusicNote DRUM_PATTERN[] = { - Keep music tracks reasonably short (under 100 notes) - Use looping instead of very long sequences - Consider using different wave types for variety vs. complexity +- Remember the **`ApuCore::MAX_VOICES` (8)** cap: overlapping long notes across **multi-track** layers **and** SFX can cause **voice stealing**; shorten releases or stagger hits if you hear notes cutting off unexpectedly ### 3. User Experience @@ -474,15 +509,60 @@ static const MusicNote DRUM_PATTERN[] = { **ESP32:** -- Music timing is sample-accurate and runs on Core 0 -- Limited memory - keep tracks short and efficient -- Use 1bpp sprites and simple music for best performance +- Music timing is **sample-accurate** inside `ApuCore` (shared by all schedulers). The backend’s audio task or I2S callback calls `AudioEngine::generateSamples`, which advances the sequencer and mixes PCM. +- Limited memory: keep tracks short and efficient; prefer `static const` data in flash. +- Subsystem is compiled only when `PIXELROOT32_ENABLE_AUDIO=1`. **Native (PC/Mac/Linux):** -- Full SDL2 audio backend -- More memory available for longer/complex tracks -- Higher quality mixing and effects +- `NativeAudioScheduler` runs `ApuCore` in a dedicated `std::thread` and double-buffers PCM for the SDL2 callback—same synthesis and music logic as ESP32. +- More headroom for longer tracks; mixing path uses the same non-linear curve as ESP32 (FPU). + +### `isPlaying()` and transport state + +`MusicPlayer::isPlaying()` reflects **whether music is actively being sequenced**, not only a client-side flag: + +- The authoritative signal is `AudioEngine::isMusicPlaying()` / `isMusicPaused()`, which read atomics updated by **`ApuCore`** when `MUSIC_PLAY` / `MUSIC_STOP` / end-of-non-looping-track / pause / resume are processed. +- **While paused**, `isPlaying()` returns **false** (playback is suspended). +- **Right after `play()`**, before the audio thread has dequeued `MUSIC_PLAY`, `isPlaying()` may still return **true** briefly so game code does not see a spurious “stopped” window (command in flight). +- **Non-looping** tracks: when the last note finishes, `ApuCore` clears the music-playing flag; `isPlaying()` becomes **false** without an explicit `stop()`. + +For raw transport without the MusicPlayer wrapper, call `engine.getAudioEngine().isMusicPlaying()` / `isMusicPaused()`. + +### BPM API + +Besides `setTempoFactor` / `getTempoFactor`, you can drive absolute tempo with **`setBPM`** / **`getBPM`** (default 150 BPM, 4 ticks per beat in the sequencer). + +### Master Volume Control + +Control the global master volume directly from `MusicPlayer` (delegates to `AudioEngine`): + +```cpp +// Set master volume to 50% +engine.getMusicPlayer().setMasterVolume(0.5f); + +// Get current volume +float currentVolume = engine.getMusicPlayer().getMasterVolume(); + +// Fade music out gradually +for (float v = 1.0f; v >= 0.0f; v -= 0.05f) { + engine.getMusicPlayer().setMasterVolume(v); + delay(50); +} +``` + +- `setMasterVolume(float volume)`: Sets global volume (0.0 = silent, 1.0 = full) +- `getMasterVolume() const`: Returns current global volume + +This affects both music and sound effects globally (same as calling `AudioEngine::setMasterVolume` directly). + +### Master bitcrush and post-mix hook + +For global lo-fi degradation or analysis, use **`AudioEngine::setMasterBitcrush`** / **`getMasterBitcrush`** (0–15; 0 = off). For custom processing on the final mono buffer (after bitcrush), configure **`AudioConfig::postMixMono`** / **`postMixUser`** when constructing the engine—see [audio.md](../api/audio.md). + +### Related API (sweeps and extra waves) + +One-shot **frequency sweeps** on `AudioEvent` (`sweepEndHz`, `sweepDurationSec`) apply to **`PULSE`** and **`TRIANGLE`** (and to **`SINE`** / **`SAW`** when extra waves are enabled). **`NOISE`** ignores sweep fields. Full detail: [audio.md](../api/audio.md). --- @@ -541,6 +621,7 @@ Serial.println(sizeof(MY_TRACK) / sizeof(MusicNote)); ```cpp #include "audio/MusicPlayer.h" #include "audio/AudioMusicTypes.h" +#include "audio/AudioTypes.h" using namespace pixelroot32::audio; @@ -648,13 +729,15 @@ private: ## References -- **Audio Types:** See `AudioMusicTypes.h` for complete type definitions -- **MusicPlayer API:** See `MusicPlayer.h` for full method documentation -- **Audio Engine:** See `AudioEngine.h` for sound effects integration -- **Examples:** Check `examples/Games/SpaceInvaders/` and `examples/Games/BrickBreaker/` for real-world usage +- **Audio types & presets:** `include/audio/AudioMusicTypes.h` +- **Wave types, `AudioEvent`, `AudioCommand`:** `include/audio/AudioTypes.h` +- **MusicPlayer API:** `include/audio/MusicPlayer.h` (`play`, `stop`, tempo/BPM, …) +- **Audio facade & music transport:** `include/audio/AudioEngine.h` (`isMusicPlaying`, `isMusicPaused`, `setMasterBitcrush`, …) +- **Engine config & post-mix:** `include/audio/AudioConfig.h` +- **Shared synthesis, voice pool & sequencer:** `include/audio/ApuCore.h` (`MAX_VOICES`, `NUM_CHANNELS` alias) +- **API reference (sweep, bitcrush, SINE/SAW, hooks):** [API Audio](../api/audio.md) +- **Examples:** See game samples under `examples/` for real-world usage. --- -**Note:** Music timing is sample-accurate and independent of frame rate, ensuring consistent playback across different hardware platforms and performance conditions. - -**Performance note:** The music sequencer runs inside **`ApuCore`**. If the audio consumer does not run for a long time (e.g. debugger break), the next **`generateSamples`** may advance **many musical ticks in one block**, which can cost more CPU in that step. There is **no** fixed per-quantum cap such as `MAX_NOTES_PER_FRAME` in the current engine. +**Note:** Music timing is sample-accurate inside `ApuCore` and is independent of render frame rate, so melody tempo does not slow down when the main loop stalls (within the limits of the audio backend’s buffer). diff --git a/guide/performance/esp32-performance.md b/guide/performance/esp32-performance.md new file mode 100644 index 0000000..e49aa0d --- /dev/null +++ b/guide/performance/esp32-performance.md @@ -0,0 +1,173 @@ +# ESP32 Performance Guide - PixelRoot32 + +## 🔥 Hot Path Rules + +**Hot paths** = `update()`, `draw()`, collision detection loops, audio callbacks. + +**Prohibited in hot paths:** + +| Feature | Why | Alternative | +|---------|-----|-------------| +| `std::optional` | Extra branching/code size | Raw pointers or sentinel values | +| Virtual calls | Vtable indirection | Templates or function pointers | +| `new` / `malloc` | Heap fragmentation | Pre-allocated pools | +| `std::vector::push_back` | Potential reallocation | Fixed-size arrays with flags | +| Logging / `log()` | String formatting overhead | Debug-only counters or periodic logging | +| `std::rand()` | Slow, uses division | `math::randomScalar()` (Xorshift) | + +--- + +## ⚡ Performance (ESP32 Focus) + +### Inlining + +- Define trivial accessors (e.g., `getHitBox`, `getX`) in the header (`.h`) to allow compiler inlining. +- Keep heavy implementation logic in `.cpp`. + +### Fast Randomness + +`std::rand()` is slow and uses division. Use `math::randomScalar()` or `math::randomRange()` (which use optimized Xorshift algorithms compatible with `Fixed16`) for visual effects. + +### Collision Detection + +- Use simple AABB (Axis-Aligned Bounding Box) checks first. Use Collision Layers (`GameLayers.h`) to avoid checking unnecessary pairs. +- For very fast projectiles (bullets, lasers), prefer lightweight sweep tests: + - Represent the projectile as a small `physics::Circle` and call `physics::sweepCircleVsRect(startCircle, endCircle, targetRect, tHit)` against potential targets. + - Use sweep tests only for the few entities that need them; keep everything else on basic AABB to avoid unnecessary CPU cost. + +### Single-Core Resource Contention (ESP32-C3) + +Single-core architectures (like the ESP32-C3) run the game logic, display transfers, and audio synthesis on a single core. + +- **Priority Inversion**: Heavy display transfers (like full-screen U8G2 refreshes) can block the audio task, causing buffer underruns and audio glitches. The engine dynamically detects single-core platforms and elevates the audio task priority (e.g., to `18`) to protect audio streams. +- **Context Thrashing**: An audio priority that is *too* high (e.g., `24`) will preempt the display transfer constantly to synthesize audio, fragmenting the hardware SPI transaction and ballooning draw times (up to 4x). The engine mitigates this by balancing priority, reducing audio buffer block sizes to `128` samples, and using `taskYIELD()` for cooperative multitasking. +- **Float Operations**: Soft-float emulation on the ESP32-C3 is extremely slow. The engine provides integer Q15 implementations for performance-critical inner loops (like `tickEnvelopeQ15` and audio mixer LUTs). Avoid introducing new float-based calculations inside per-sample audio loops or per-pixel drawing loops. + +--- + +## 💾 Memory & Resources + +**📖 For comprehensive C++17 memory management guide, see [Memory Management Guide](../../architecture/memory-system.md)** + +### Smart Pointers (C++17) + +Use `std::unique_ptr` for **init-time ownership** (Scenes, Actors, UI elements) to automate memory management and document ownership. + +- Use `std::make_unique(...)` to create objects during initialization only. +- Pass raw pointers (via `.get()`) to functions that do *not* take ownership (like `addEntity`). +- Use `std::move` only when transferring ownership explicitly. +- ⚠️ **Do not use in hot paths**: `unique_ptr` is for init-time, not runtime game loop. + +### Object Pooling + +Pre-allocate all game objects (obstacles, particles, enemies) during `init()`. + +- Pools are for **runtime** zero-allocation recycling; `unique_ptr` is for **init-time** ownership semantics. +- Pattern: Use fixed-size arrays (e.g., `Particle particles[50]`) and flags (`isActive`) instead of `std::vector` with `push_back`/`erase`. +- Trade-off: Eliminates runtime allocations and fragmentation at the cost of a slightly higher fixed RAM footprint; dimension pools to realistic worst-case usage. + +### Zero Runtime Allocation + +Never use `new` or `malloc` inside the game loop (`update` or `draw`). + +### String Handling + +Avoid `std::string` copies. Use `std::string_view` for passing strings. For formatting, use `snprintf` with stack-allocated `char` buffers. + +### Scene Arenas (`PIXELROOT32_ENABLE_SCENE_ARENA`) + +Use a single pre-allocated buffer per scene for temporary entities or scratch data when you need strict zero-allocation guarantees. + +- Trade-off: Very cache-friendly and fragmentation-proof, but the buffer cannot grow at runtime; oversizing wastes RAM, undersizing returns `nullptr` and requires graceful fallback logic. + +--- + +## 🏗️ Build Profiles + +PixelRoot32 supports two build profiles for different use cases: + +### Embedded Profile (Default) + +For ESP32 and resource-constrained hardware: + +- **Zero allocation** at runtime +- **No exceptions**, `-fno-exceptions` flag +- **Deterministic** behavior prioritized +- **Modular compilation** to reduce binary size +- **All Hot Path Rules** enforced + +### Native Profile (Optional) + +For PC simulation and development: + +- **Relaxed constraints** for faster iteration +- **Exceptions permitted** if needed for tooling +- **Debug-friendly** features enabled +- **All subsystems** can be compiled in + +Use `PLATFORM_NATIVE` flag to switch profiles. + +--- + +## 📊 Recommended Build Profiles + +Choose a profile based on your game type to optimize memory usage: + +| Game Type | Profile | Enabled | Disabled | +|-----------|---------|---------|----------| +| Arcade shooters/platformers | `arcade` | Audio, Physics, Particles | UI System | +| Puzzle/casual games | `puzzle` | Audio, UI System | Physics, Particles | +| Retro/minimal | `retro` | None | All | +| Educational/tools | `puzzle` or custom | Audio, UI System | Physics, Particles | + +**Example platformio.ini configuration:** + +```ini +[env:esp32_arcade] +extends = base_esp32, profile_arcade +build_flags = + ${base_esp32.build_flags} + ${profile_arcade.build_flags} + +[env:esp32_puzzle] +extends = base_esp32, profile_puzzle +build_flags = + ${base_esp32.build_flags} + ${profile_puzzle.build_flags} + +[env:native_retro] +build_flags = + -DPLATFORM_NATIVE=1 + -DPIXELROOT32_ENABLE_AUDIO=0 + -DPIXELROOT32_ENABLE_PHYSICS=0 + -DPIXELROOT32_ENABLE_PARTICLES=0 + -DPIXELROOT32_ENABLE_UI_SYSTEM=0 +``` + +--- + +## 📐 Resolution Scaling + +PixelRoot32 separates **logical** resolution (what your game draws at) from **physical** resolution (the actual display), so you can target low pixel counts for performance while filling modern panels. + +### When to Use + +- Ship gameplay at 128×128 or 160×144 but drive a 240×240 TFT +- Keep UI and physics in logical space; only the final blit scales up + +### Configure DisplayConfig + +Set `logicalWidth` / `logicalHeight` for the render buffer and `physicalWidth` / `physicalHeight` for the panel. The renderer and input pipeline map between the two. + +See [DisplayConfig / Engine](../../api/core.md#engine) and the architecture deep dive [Resolution Scaling](../../architecture/resolution-scaling.md) for implementation details, ESP32 considerations, and coordinate mapping. + +--- + +## 📚 Related Documentation + +| Document | Description | +|----------|-------------| +| [Memory Management Guide](../../architecture/memory-system.md) | Complete C++17 memory guide with smart pointers | +| [Platform Compatibility](../platform-compatibility.md) | Hardware matrix and feature support | +| [Architecture Index](../../architecture/architecture-index.md) | Layer architecture and subsystem navigation | +| [Rendering Guide](../rendering.md) | Core rendering pipeline | diff --git a/guide/physics.md b/guide/physics.md index 78bc783..bdeb4e2 100644 --- a/guide/physics.md +++ b/guide/physics.md @@ -605,6 +605,6 @@ public: ## Next Steps -- **[Entities & Actors](/guide/entities-actors)** — Actor types reference -- **[CollisionSystem & tile helpers](/api/physics/collision-system)** — `TileCollisionBuilder`, layers, and static colliders -- **[Architecture](/architecture/physics-system)** — Physics architecture deep dive +- **[Entities & Actors](./entities-actors.md)** — Actor types reference +- **[CollisionSystem & tile helpers](../api/physics.md#collision-system-the-flat-solver)** — `TileCollisionBuilder`, layers, and static colliders +- **[Architecture](../architecture/physics-subsystem.md)** — Physics architecture deep dive diff --git a/guide/platform-config.md b/guide/platform-compatibility.md similarity index 80% rename from guide/platform-config.md rename to guide/platform-compatibility.md index a9c640d..ae2da15 100644 --- a/guide/platform-config.md +++ b/guide/platform-compatibility.md @@ -1,13 +1,13 @@ # PixelRoot32 Platform Compatibility Guide -**Document Version:** 1.1 -**Last Updated:** March 2026 -**Engine Version:** v1.0.0 +**Canonical doc:** this file is the single source for the platform matrix, `build_flags`, and PlatformIO examples. ## Overview This document provides detailed information about PixelRoot32 Game Engine compatibility across different ESP32 variants and platforms. It helps developers understand which features are available on their target hardware. +For the published site (navigation and anchors), see [Platform compatibility](https://docs.pixelroot32.org/guide/platform-compatibility.html) on the official documentation. + --- ## Platform Feature Matrix @@ -88,10 +88,6 @@ build_flags = **Audio:** I2S only (external amplifier required) -::: warning ESP32-S3 display DMA and Arduino Core -SPI/DMA display paths can require pinning **Arduino Core 2.0.14** on ESP32-S3 depending on your toolchain. See [ESP32-S3 DMA and Arduino Core](#esp32-s3-dma-arduino-core) in Troubleshooting below. -::: - --- ### ESP32-C3 @@ -293,6 +289,8 @@ The modular compilation system allows significant memory savings by disabling un **Example:** ESP32-C3 minimal build (audio+physics disabled) saves ~20KB RAM compared to full build. +For detailed configuration examples, see [Global Configuration](https://docs.pixelroot32.org/api_reference/core/global_config/). + ### Audio Capabilities - **DAC Output:** 8-bit, direct GPIO drive (PAM8302A recommended) @@ -328,81 +326,6 @@ The modular compilation system allows significant memory savings by disabling un **Likely Cause:** Running out of SRAM on constrained platforms **Solution:** Use lower logical resolution, reduce entity count -### ESP32-S3 DMA and Arduino Core {#esp32-s3-dma-arduino-core} - -**Problem:** On ESP32-S3, Arduino Core versions newer than **2.0.14** can cause DMA-based display transfers to freeze after the first frame. This is a known interaction with the ESP32-S3 GDMA subsystem in ESP-IDF 4.4.7+ (bundled with Arduino Core 2.0.15+). - -**Symptoms:** - -- Display freezes after the first rendered frame -- DMA transfer does not complete -- Crashes during display initialization - -**Workaround:** Pin **Arduino Core 2.0.14** (the last release widely used before the relevant GDMA changes). - -In PlatformIO, use the `platform_packages` directive: - -```ini -[env:esp32s3] -platform_packages = - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#2.0.14 -``` - -> The [`hello_world`](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/hello_world) example already includes this for its ESP32-S3 environment. For new projects targeting ESP32-S3 with DMA display output, apply the same when needed. - -**Future direction:** A definitive fix may come from migrating display integration from `TFT_eSPI` toward `LovyanGFX` for better alignment with current drivers and improved stability on ESP32-S3. - -**Related issues:** - -- [espressif/arduino-esp32 #9618](https://github.com/espressif/arduino-esp32/issues/9618) — ESP32-S3 DMA issues with Core newer than 2.0.14 -- [TFT_eSPI #3329](https://github.com/Bodmer/TFT_eSPI/issues/3329) -- [TFT_eSPI #3367](https://github.com/Bodmer/TFT_eSPI/issues/3367) -- [ESP32-HUB75-MatrixPanel-DMA #775](https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/issues/775) - -### Framework cache and pins_arduino.h {#framework-cache-pins-arduino} - -**Problem:** After changing Arduino Core versions (including the [DMA workaround above](#esp32-s3-dma-arduino-core)), the cached `framework-arduinoespressif32` package can become inconsistent. Builds may fail with: - -```text -fatal error: pins_arduino.h: No such file or directory -``` - -**Symptoms:** - -- Build fails: `pins_arduino.h` not found -- A project that previously built suddenly fails -- Often occurs right after changing `platform_packages` - -**Solution:** - -1. Clean the project build: - - ```bash - pio run --target clean - ``` - -2. Remove the framework package directory so PlatformIO reinstalls a clean copy. - - **Windows (Command Prompt):** - - ```bat - rmdir /s /q %USERPROFILE%\.platformio\packages\framework-arduinoespressif32 - ``` - - **macOS / Linux:** - - ```bash - rm -rf ~/.platformio/packages/framework-arduinoespressif32 - ``` - -3. Rebuild (PlatformIO will download the framework again): - - ```bash - pio run - ``` - -**Prevention:** After any change to `platform_packages` for the Arduino core, run a clean build so the correct framework is installed. - --- ## Migration Between Platforms @@ -472,8 +395,8 @@ if (!caps.hasFPU) { ## References - **Official Documentation:** -- **Platform Compatibility Guide:** -- **API Reference:** +- **Platform Compatibility Guide:** +- **API Reference:** - **ESP32 Arduino Core Documentation:** - **PlatformIO ESP32 Platforms:** - **PixelRoot32 Engine Configuration:** See `platforms/PlatformDefaults.h` @@ -481,8 +404,6 @@ if (!caps.hasFPU) { --- ---- - ## Hardware Limits and Constraints ### Tilemap System Limits diff --git a/guide/rendering.md b/guide/rendering.md index 638f88f..5bce7bc 100644 --- a/guide/rendering.md +++ b/guide/rendering.md @@ -506,6 +506,5 @@ for (int i = 0; i < 100; ++i) { ## Next Steps -- **[Resolution Scaling](/guide/resolution-scaling)** — Deep dive into logical/physical resolution -- **[Tilemaps](/guide/tilemaps)** — Advanced tilemap techniques -- **[Multi-Palette](/guide/multi-palette)** — Palette management +- **[Performance Guide](./performance/esp32-performance.md)** — Logical vs physical resolution, hot paths +- **[Graphics Techniques](./graphics-techniques.md)** — Tilemaps, palettes, indexed color diff --git a/guide/resolution-scaling.md b/guide/resolution-scaling.md deleted file mode 100644 index ae33578..0000000 --- a/guide/resolution-scaling.md +++ /dev/null @@ -1,19 +0,0 @@ -# Resolution scaling - -PixelRoot32 separates **logical** resolution (what your game draws at) from **physical** resolution (the actual display), so you can target low pixel counts for performance while filling modern panels. - -## When to use it - -- Ship gameplay at 128×128 or 160×144 but drive a 240×240 TFT. -- Keep UI and physics in logical space; only the final blit scales up. - -## Configure `DisplayConfig` - -Set `logicalWidth` / `logicalHeight` for the render buffer and `physicalWidth` / `physicalHeight` for the panel. The renderer and input pipeline map between the two according to your driver setup. - -See [DisplayConfig / Engine](/api/core/engine) and the architecture deep dive [Resolution scaling](/architecture/ARCH_RESOLUTION_SCALING) for implementation details, ESP32 considerations, and coordinate mapping. - -## Related - -- [Rendering guide](/guide/rendering) -- [Platform configuration](/guide/platform-config) diff --git a/guide/scenes.md b/guide/scenes.md index 7ed7de6..5dada42 100644 --- a/guide/scenes.md +++ b/guide/scenes.md @@ -458,6 +458,6 @@ void setup() { ## Next Steps -- **[Entities & Actors](/guide/entities-actors)** — Create game objects that live in scenes -- **[Game Loop](/guide/game-loop)** — Understand the update/draw cycle -- **[UI System](/guide/ui-system)** — Add interactive elements to scenes +- **[Entities & Actors](./entities-actors.md)** — Create game objects that live in scenes +- **[Game Loop](./game-loop.md)** — Understand the update/draw cycle +- **[UI System](./ui-system.md)** — Add interactive elements to scenes diff --git a/guide/testing.md b/guide/testing.md index 41fb3dc..a484892 100644 --- a/guide/testing.md +++ b/guide/testing.md @@ -1,8 +1,6 @@ -# Testing Guide - PixelRoot32 Game Engine +# Testing -**Document Version:** 1.1 -**Last Updated:** February 2026 -**Engine Version:** v1.0.0 +Unity-based unit and integration tests, PlatformIO environments (`native_test`), coverage scripts, and CI notes — single guide for the engine test tree. ## Overview diff --git a/guide/tilemaps.md b/guide/tilemaps.md deleted file mode 100644 index 33207a9..0000000 --- a/guide/tilemaps.md +++ /dev/null @@ -1,25 +0,0 @@ -# Tilemaps - -Tilemaps are efficient for backgrounds and level geometry: the engine stores compact tile indices, applies palettes, and can skip off-screen regions. - -## Features - -- Generic `TileMap` templates for different BPP modes (see [TileMap API](/api/graphics/tilemap)). -- Viewport culling so only visible tiles hit the draw surface. -- Optional **tile animations** (water, lava, etc.) with O(1) frame lookup when enabled — see [Tile animation architecture](/architecture/ARCH_TILE_ANIMATION). -- Optional static layer cache on ESP32 for heavy 4bpp multi-layer scenes (described in the graphics API). - -## Compile-time flags - -`PIXELROOT32_ENABLE_TILE_ANIMATIONS`, `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE`, and related switches are documented in [Configuration flags](/api/modules/configuration) and the [API overview](/api/). - -## Samples in the engine repo - -- `examples/animated_tilemap` — animated tiles -- `examples/metroidvania` — larger scrollable maps -- `examples/snake` — minimal grid usage - -## Related - -- [Rendering](/guide/rendering) -- [Physics tile collision](/api/physics/collision-system) diff --git a/guide/ui-guidelines.md b/guide/ui-guidelines.md new file mode 100644 index 0000000..b0741c8 --- /dev/null +++ b/guide/ui-guidelines.md @@ -0,0 +1,255 @@ +# UI Guidelines - PixelRoot32 + +Patterns and best practices for building user interfaces with the UI system. + +**Architecture first:** read **[UI system](./ui-system.md)** for `UIManager`, entity vs touch registration, and lifecycle. Authoritative API surface: `include/graphics/ui/` and [UI module](../api/ui.md). + +--- + +## Layout system + +### Choosing the right layout + +| Layout | Use case | Navigation | +|--------|----------|------------| +| `UIVerticalLayout` | Lists, menus | UP/DOWN (via `handleInput`) | +| `UIHorizontalLayout` | Toolbars, option rows | LEFT/RIGHT | +| `UIGridLayout` | Inventories, galleries | Four-way | +| `UIAnchorLayout` | HUD elements, fixed positions | None at layout level | + +Layouts take a **viewport rectangle** `(x, y, width, height)` and use **`addElement` / `removeElement`**, not `addChild`. + +### Vertical layout (lists) + +```cpp +using namespace pixelroot32; + +void onStart() { /* ... */ } + +auto* menu = new graphics::ui::UIVerticalLayout( + math::toScalar(10), math::toScalar(20), + 200, 120); +menu->setSpacing(4); + +auto* start = new graphics::ui::UIButton( + "Start", 0, + math::Vector2(math::toScalar(0), math::toScalar(0)), + math::Vector2(math::toScalar(80), math::toScalar(24)), + onStart); +menu->addElement(start); +// ... more buttons + +// From scene update: drive D-pad / focus navigation +menu->handleInput(engine.getInputManager()); +``` + +### Horizontal layout (bars) + +```cpp +auto* toolbar = new graphics::ui::UIHorizontalLayout( + math::toScalar(10), math::toScalar(200), + 240, 32); +toolbar->setSpacing(8); +// toolbar->addElement(... UIButton with full constructor ...) +``` + +### Grid layout (inventories) + +Grid **cell size is computed** from the layout’s width/height, `setColumns`, padding, and `setSpacing`. There is **no** `setCellSize`. + +```cpp +auto* inventory = new graphics::ui::UIGridLayout( + math::toScalar(10), math::toScalar(50), + 200, 160); +inventory->setColumns(4); +inventory->setSpacing(4); + +for (int i = 0; i < 12; ++i) { + // new UIButton(...) with index i, callback, etc. + // inventory->addElement(btn); +} +``` + +### Anchor layout (HUDs) + +`addElement` takes **`(UIElement*, Anchor)`** only. There are **no** extra `(ox, oy)` parameters; use a full-screen layout and `setScreenSize`, or adjust the layout’s position/size for margins. + +```cpp +constexpr int SW = 320; +constexpr int SH = 240; + +auto* hud = new graphics::ui::UIAnchorLayout( + math::toScalar(0), math::toScalar(0), SW, SH); +hud->setFixedPosition(true); +hud->setScreenSize(SW, SH); + +auto* score = new graphics::ui::UILabel( + "Score: 0", + math::Vector2(math::toScalar(0), math::toScalar(0)), + graphics::Color::White, + 1); +hud->addElement(score, graphics::ui::Anchor::TOP_LEFT); +``` + +Enum literals use **`Anchor::TOP_LEFT`**, `TOP_RIGHT`, `BOTTOM_RIGHT`, etc. + +--- + +## Container patterns + +### Padding container + +`UIPaddingContainer` wraps **one** child (`setChild`) and uses a normal **bounding box** `(x, y, w, h)`. + +```cpp +auto* padded = new graphics::ui::UIPaddingContainer( + math::toScalar(0), math::toScalar(0), 120, 40); +// padded->setPadding(...); +// padded->setChild(button); +``` + +### Panel (visual containers) + +`UIPanel` also wraps **one** child. Nest a `UILayout` for multiple rows. + +```cpp +auto* dialog = new graphics::ui::UIPanel( + math::toScalar(50), math::toScalar(50), 140, 100); +dialog->setBackgroundColor(graphics::Color::Black); +dialog->setBorderColor(graphics::Color::White); + +auto* layout = new graphics::ui::UIVerticalLayout( + math::toScalar(0), math::toScalar(0), 130, 90); +// layout->addElement(...); +dialog->setChild(layout); +``` + +--- + +## Navigation + +### D-pad and `handleInput` + +`Scene::update` does **not** call `UILayout::handleInput` for you. Call it from your scene when you want list/grid/toolbar navigation: + +```cpp +void MyScene::update(unsigned long dt) { + if (mainMenu) { + mainMenu->handleInput(engine.getInputManager()); + } + Scene::update(dt); +} +``` + +### Selection helpers + +```cpp +layout->setSelectedIndex(2); +int idx = layout->getSelectedIndex(); +graphics::ui::UIElement* sel = layout->getSelectedElement(); // not getSelectedChild +``` + +--- + +## ESP32 performance + +### Viewport culling + +`UIVerticalLayout` and `UIGridLayout` skip drawing children outside the visible viewport when scrolling is relevant. Still prefer **fewer nodes** and **less frequent text changes**. + +### Scrolling + +Use **`setScrollEnabled`** / **`setScrollingEnabled`** (see `UIVerticalLayout` and `UILayout`) and **`setScrollOffset`** where applicable. There is **no** `UIScrollMode` enum in the current API. + +--- + +## Sprite and graphics + +### Sprite descriptors + +Wrap bitmaps in descriptors before rendering: + +```cpp +pixelroot32::graphics::Sprite playerSprite = { + .width = 16, + .height = 16, + .data = playerBitmap +}; + +renderer.drawSprite(playerSprite, x, y, paletteSlot, flipX); +``` + +### Multi-sprite (layered) + +Compose multi-color sprites from 1bpp layers: + +```cpp +pixelroot32::graphics::MultiSprite ship = { + .layers = { + {shipOutline, PaletteType::PR32}, + {shipCockpit, PaletteType::NES}, + {shipEngine, PaletteType::GB} + }, + .layerCount = 3 +}; + +renderer.drawMultiSprite(ship, x, y); +``` + +**Keep layer data `static const`** for flash storage. + +### Bit depth guidelines + +| Format | Use for | Memory cost | +|--------|---------|-------------| +| 1bpp | Game sprites, tiles | 1x | +| 2bpp | Logos, detailed UI | 2x | +| 4bpp | Photos, rich graphics | 4x | + +**Default to 1bpp** for gameplay-critical sprites. + +--- + +## What actors should not do + +```cpp +// Wrong: per-pixel loops in actors +void MyActor::draw(Renderer& r) { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (bitmap[y] & (1 << x)) { + r.drawPixel(x, y, color); + } + } + } +} + +// Correct: batch drawing via renderer +void MyActor::draw(Renderer& r) { + r.drawSprite(mySprite, position.x, position.y); +} +``` + +--- + +## Touch vs classic widgets + +- **`UITouchButton` / `UITouchCheckbox` / `UITouchSlider`**: register with `UIManager::addElement` and implement `Scene::processTouchEvents`. Also `addEntity` so widgets draw. +- **`UIButton` / `UICheckBox`**: use **`handleInput`** and focus/selection; they do not use the touch event dispatcher for hit testing. + +See [architecture/touch-input.md](../architecture/touch-input.md). + +--- + +## Related documentation + +| Document | Topic | +|----------|-------| +| [graphics-guidelines.md](./graphics-guidelines.md) | Sprites, tilemaps, palettes | +| [coding-style.md](./coding-style.md) | C++ conventions | +| [performance/esp32-performance.md](./performance/esp32-performance.md) | Optimization | +| [api/ui.md](../api/ui.md) | UI API reference | + +--- + +UIs should be responsive, efficient, and consistent with the real `UIElement` / `UITouchElement` split. diff --git a/guide/ui-system.md b/guide/ui-system.md index 6629066..0bf4da1 100644 --- a/guide/ui-system.md +++ b/guide/ui-system.md @@ -425,5 +425,5 @@ checkbox->setStyle(graphics::Color::White, graphics::Color::Black, false); ## Next steps -- **[Input](/guide/input)** — Touch and button handling +- **[Input](./input.md)** — Touch and button handling - **[Examples/Hello World](/examples/hello-world)** — Sample projects diff --git a/migration/index.md b/migration/index.md new file mode 100644 index 0000000..da13211 --- /dev/null +++ b/migration/index.md @@ -0,0 +1,22 @@ +# Migration Guides + +Version upgrade guides for PixelRoot32 Game Engine. + +## Available Migrations + +- [v1.0.0 Migration](./migration-v1-0-0.md) - Initial stable release migration +- [v1.1.0 Migration](./migration-v1-1-0.md) - Point release updates +- [v1.2.0 Migration](./migration-v1-2-0.md) - Latest feature additions + +## Migration Policy + +Each migration guide includes: +- Breaking changes +- New features overview +- Code modification examples +- Deprecation notices + +## Navigation + +- [Guide](../guide/) - User guides and how-to documentation +- [API Reference](../api/) - Complete API documentation diff --git a/guide/migrations/v1.0.0.md b/migration/migration-v1-0-0.md similarity index 99% rename from guide/migrations/v1.0.0.md rename to migration/migration-v1-0-0.md index 5f997cc..ba68cfc 100644 --- a/guide/migrations/v1.0.0.md +++ b/migration/migration-v1-0-0.md @@ -808,5 +808,5 @@ actor->setRadius(toScalar(radius)); // Critical for circles! - [PlatformIO Build Flags](https://docs.platformio.org/en/latest/projectconf/sections/env/options/build/build_flags.html) - [Fixed-Point Arithmetic (Wikipedia)](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) - Theory behind Q format and integer math. - [Q (number format)](https://en.wikipedia.org/wiki/Q_(number_format)) - Understanding the Q16.16 format used in PixelRoot32. -- [Physics system](/architecture/physics-system) — Flat Solver and collision pipeline -- [API Reference](/api/) - CollisionSystem API +- [Physics System Reference](../architecture/physics-subsystem.md) - Complete Flat Solver documentation +- [API Reference](../api/physics.md) - CollisionSystem API diff --git a/guide/migrations/v1.1.0.md b/migration/migration-v1-1-0.md similarity index 100% rename from guide/migrations/v1.1.0.md rename to migration/migration-v1-1-0.md diff --git a/guide/migrations/v1.2.0.md b/migration/migration-v1-2-0.md similarity index 98% rename from guide/migrations/v1.2.0.md rename to migration/migration-v1-2-0.md index b2fbe4f..521599d 100644 --- a/guide/migrations/v1.2.0.md +++ b/migration/migration-v1-2-0.md @@ -280,5 +280,5 @@ After migration, verify: ## References -- [Physics system](/architecture/physics-system) -- [API Reference](/api/) +- [Physics System Reference](../architecture/physics-subsystem.md) +- [API Reference](../api/physics.md) diff --git a/package.json b/package.json index a68ded8..46c502b 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "type": "module", "description": "PixelRoot32 Game Engine Documentation", "scripts": { + "sync-docs": "node scripts/sync-docs-from-engine.mjs", "docs:dev": "vitepress dev", "docs:build": "vitepress build", "docs:preview": "vitepress preview" diff --git a/philosophy/engine-philosophy.md b/philosophy/engine-philosophy.md new file mode 100644 index 0000000..8a7a2b5 --- /dev/null +++ b/philosophy/engine-philosophy.md @@ -0,0 +1,43 @@ +# Engine Philosophy + +PixelRoot32 is not the product of a traditional electronics expert or a large engineering team. + +It was born from curiosity, experimentation, and a deep love for retro games from the 90s. + +The journey started with a simple Raspberry Pi Zero project (PiGRRL Zero), exploring how far limited hardware could go. From there, the challenge evolved: rendering small games, experimenting with engines like Godot under constraints, and eventually discovering the ESP32 as a surprisingly capable platform for game development. + +Coming from a mobile development background with limited experience in C++, this project represents a leap into a different domain. That leap was made possible by how accessible knowledge has become today—especially through AI-assisted tools that lower the barrier to learning and building complex systems. PixelRoot32, in many ways, reflects that shift: the ability to explore new fields driven by curiosity rather than formal specialization. + +PixelRoot32 exists as a personal exploration of what is possible when passion meets constraints. + +--- + +## Core Principles + +* **Constraints drive design**Limited hardware is not a limitation, but a creative constraint that shapes better, simpler systems. +* **Determinism over convenience**The engine favors predictable behavior over abstractions that hide complexity. +* **No hidden costs**No runtime allocations, no implicit work — everything is explicit and under control. +* **Simplicity over complexity**Systems are designed to be understandable, hackable, and transparent. +* **Learning by building**PixelRoot32 is as much a learning journey as it is a game engine. +* **Retro-inspired, not retro-limited**Inspired by 90s games, but built with modern tools and ideas. + +--- + +## Philosophy in Practice + +This philosophy translates into concrete decisions: + +* No exceptions or RTTI +* Fixed memory strategies and object pooling +* Explicit ownership (`std::unique_ptr`) +* Integer-friendly rendering pipelines +* Compile-time modular systems +* Minimal dependencies + +--- + +## Acknowledgements + +PixelRoot32 was heavily inspired by the work of nbourre and the ESP32 Game Engine project:[GitHub - nbourre/ESP32-Game-Engine · GitHub](https://github.com/nbourre/ESP32-Game-Engine) + +This project builds upon that inspiration with a focus on structure, determinism, and long-term maintainability. diff --git a/philosophy/index.md b/philosophy/index.md new file mode 100644 index 0000000..1546d09 --- /dev/null +++ b/philosophy/index.md @@ -0,0 +1,19 @@ +# Philosophy + +Design philosophy and vision behind PixelRoot32 Game Engine. + +## Documents + +- [Engine Philosophy](./engine-philosophy.md) - Why this engine exists, design principles, goals + +## Core Principles + +- **Modularity**: Subsystems can be used independently +- **Selective Compilation**: Exclude unused subsystems to save RAM/flash +- **Portability**: Same code for ESP32 and PC +- **Performance**: Optimized for resource-constrained hardware + +## Navigation + +- [Guide](../guide/) - User guides and how-to documentation +- [Architecture](../architecture/) - System architecture and design diff --git a/scripts/sync-docs-from-engine.mjs b/scripts/sync-docs-from-engine.mjs new file mode 100644 index 0000000..5a4618c --- /dev/null +++ b/scripts/sync-docs-from-engine.mjs @@ -0,0 +1,63 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const engineArgIndex = process.argv.indexOf('--engine'); +const enginePath = engineArgIndex !== -1 && process.argv[engineArgIndex + 1] ? process.argv[engineArgIndex + 1] : './engine'; +const docsSource = path.join(enginePath, 'docs'); +const docsTarget = path.join(__dirname, '..'); + +const sections = ['guide', 'api', 'architecture', 'philosophy', 'migration', 'examples', 'tools']; + +const excludedFiles = ['README.md', 'CHANGELOG.md', 'LICENSE', 'assets', 'img', 'images', '.vitepress', '.github']; + +function syncDocs() { + console.log(`Syncing docs from ${enginePath}...`); + + if (!fs.existsSync(docsSource)) { + console.error(`Error: Engine docs not found at ${docsSource}`); + process.exit(1); + } + + for (const section of sections) { + const srcDir = path.join(docsSource, section); + const destDir = path.join(docsTarget, section); + const destLegacy = path.join(docsTarget, '_legacy_vitepress', section); + + if (fs.existsSync(srcDir)) { + copyDirRecursive(srcDir, destDir, excludedFiles); + console.log(`Synced ${section}/`); + } + + if (fs.existsSync(destDir)) { + fs.cpSync(destDir, destLegacy, { recursive: true, force: true }); + } + } + + console.log('Docs sync complete.'); +} + +function copyDirRecursive(src, dest, excluded) { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest, { recursive: true }); + } + + const entries = fs.readdirSync(src, { withFileTypes: true }); + + for (const entry of entries) { + if (excluded.includes(entry.name)) { + continue; + } + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + + if (entry.isDirectory()) { + copyDirRecursive(srcPath, destPath, excluded); + } else { + fs.copyFileSync(srcPath, destPath); + } + } +} + +syncDocs(); \ No newline at end of file diff --git a/tools/index.md b/tools/index.md index 50d41c3..de2b5fd 100644 --- a/tools/index.md +++ b/tools/index.md @@ -28,8 +28,11 @@ The **Tool Suite** adds advanced editor modules. Licensing and downloads are han Coming soon - visual editor for multi-layer tilemaps, tilesets, animations, attributes, and **C++ export** aligned with the engine. - [Overview](/tools/tilemap-editor/overview) +- [Quick start](/tools/tilemap-editor/quick-start) - [Installation](/tools/tilemap-editor/installation) - [Usage guide](/tools/tilemap-editor/usage-guide) +- [Advanced guide](/tools/tilemap-editor/advanced-guide) +- [Technical reference](/tools/tilemap-editor/technical-reference) ### Music Editor (module 2) @@ -45,4 +48,4 @@ Upcoming - tracker-style music authoring for PixelRoot32. | Tilemap Editor | Premium (suite) | Coming soon | | Music Editor | Premium (suite) | Upcoming | -**Engine docs:** [Tilemaps](/guide/tilemaps) · [Multi-palette](/guide/multi-palette) · [Rendering](/guide/rendering) · [Tile animation (architecture)](/architecture/ARCH_TILE_ANIMATION) +**Engine docs:** [Graphics Techniques](../guide/graphics-techniques.md) · [Rendering](../guide/rendering.md) · [Tile animation (architecture)](../architecture/tile-animation.md) diff --git a/tools/sprite-compiler/advanced-features.md b/tools/sprite-compiler/advanced-features.md index 8868945..c9eadb1 100644 --- a/tools/sprite-compiler/advanced-features.md +++ b/tools/sprite-compiler/advanced-features.md @@ -63,4 +63,4 @@ Packed modes can reduce draw setup when a sprite uses several colors in one arra ## See also - [Rendering](/guide/rendering) -- [Multi-palette](/guide/multi-palette) +- [Graphics Techniques](/guide/graphics-techniques.md) diff --git a/tools/sprite-compiler/installation.md b/tools/sprite-compiler/installation.md index aa19aca..7487b05 100644 --- a/tools/sprite-compiler/installation.md +++ b/tools/sprite-compiler/installation.md @@ -24,7 +24,7 @@ If Python is missing, install it from [python.org](https://www.python.org/). ## Installation methods -### Method 1: From source (recommended) +### Method 1: From source Run the tool from a clone of the repository; the CLI entry point is **`python main.py`**. @@ -51,7 +51,7 @@ Work from this directory (or call `python` with the full path to `main.py`) when ### Method 2: Pre-built binaries (no Python) -The **[Releases](ttps://github.com/PixelRoot32-Game-Engine/PixelRoot32-Sprite-Sheet-Compiler/releases)** page may ship standalone builds so you do **not** need to install Python. Exact file names change per release; pick the asset for your OS. +The **[Releases](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Sprite-Sheet-Compiler/releases)** page may ship standalone builds so you do **not** need to install Python. Exact file names change per release; pick the asset for your OS. #### Windows diff --git a/tools/tilemap-editor/overview.md b/tools/tilemap-editor/overview.md index bf1d2a3..710681d 100644 --- a/tools/tilemap-editor/overview.md +++ b/tools/tilemap-editor/overview.md @@ -79,5 +79,5 @@ For detailed walkthrough, see the [Quick Start Guide](/tools/tilemap-editor/quic ## See also - [Tools overview](/tools/) -- [Tilemaps](/guide/tilemaps) -- [Tile animation](/architecture/ARCH_TILE_ANIMATION) +- [Graphics Techniques](/guide/graphics-techniques.md) +- [Tile animation](../../architecture/tile-animation.md) diff --git a/verify-links.ps1 b/verify-links.ps1 new file mode 100644 index 0000000..f6c2f0c --- /dev/null +++ b/verify-links.ps1 @@ -0,0 +1,71 @@ +# Link verification script for PixelRoot32-Docs +$docsRoot = "C:\Users\gperez88\Documents\Proyects\Games\pixelroot32 workspace\PixelRoot32-Docs" + +$mdFiles = Get-ChildItem -Path $docsRoot -Recurse -Filter "*.md" -Exclude "node_modules*" +$brokenLinks = @() +$totalLinks = 0 + +foreach ($file in $mdFiles) { + $lines = Get-Content $file.FullName + for ($i = 0; $i -lt $lines.Count; $i++) { + if ($lines[$i] -match '\]\(([^)]+\.md)\)') { + $links = [regex]::Matches($lines[$i], '\]\(([^)]+\.md)\)') + foreach ($link in $links) { + $totalLinks++ + $target = $link.Groups[1].Value + + # Skip external links + if ($target -match '^https?://') { + continue + } + + # Resolve relative path + $fileDir = Split-Path $file.FullName -Parent + if ($target -match '^[^/]') { + $fullTarget = Join-Path $fileDir $target + } else { + $fullTarget = Join-Path $docsRoot $target + } + $fullTarget = $fullTarget.Replace('/', '\') + + if (-not (Test-Path $fullTarget)) { + $brokenLinks += [PSCustomObject]@{ + File = $file.Name + Line = $i + 1 + Link = $target + Note = "Target not found" + } + } + } + } + } +} + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Link Verification Report - PixelRoot32-Docs" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" +Write-Host "Total links checked: $totalLinks" -ForegroundColor Gray +Write-Host "Broken links found: $($brokenLinks.Count)" -ForegroundColor $(if ($brokenLinks.Count -eq 0) { "Green" } else { "Red" }) +Write-Host "" + +if ($brokenLinks.Count -gt 0) { + Write-Host "Broken Links:" -ForegroundColor Red + Write-Host "-" * 80 + $brokenLinks | ForEach-Object { + Write-Host " $($_.File):$($_.Line) -> $($_.Link)" -ForegroundColor Yellow + } +} else { + Write-Host "No broken links found!" -ForegroundColor Green +} + +# Count files by folder +Write-Host "" +Write-Host "Files by folder:" -ForegroundColor Cyan +$folders = @('guide', 'api', 'architecture', 'philosophy', 'migration', 'examples', 'tools') +foreach ($folder in $folders) { + $count = (Get-ChildItem -Path (Join-Path $docsRoot $folder) -Filter "*.md" -Recurse -ErrorAction SilentlyContinue).Count + if ($count -gt 0) { + Write-Host " $folder : $count files" -ForegroundColor Gray + } +} \ No newline at end of file