diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 1cd91a5..a465018 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -296,6 +296,7 @@ export default defineConfig({ { text: 'Animated Tilemap', link: '/examples/animated-tilemap' }, { text: 'Tilemaps (overview)', link: '/examples/tilemap-scene' }, { text: 'Tic Tac Toe', link: '/examples/tic-tac-toe' }, + { text: '2048', link: '/examples/2048' }, { 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' }, diff --git a/api/audio.md b/api/audio.md index d5a419e..cfb6ab9 100644 --- a/api/audio.md +++ b/api/audio.md @@ -91,8 +91,8 @@ void MyScene::init() { ## 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. +- **ESP32 buffer notes**: I2S backends use configurable block size (default 512 samples on dual-core, 128 on single-core). Internal DAC output uses I2S in `I2S_MODE_DAC_BUILT_IN`. +- On **no-FPU** ESP32 (e.g. ESP32-C3), `ApuCore` uses an integer oscillator mirror, fixed-point HPF, and integer LFO to avoid soft-float in the inner loop. - **Noise / LFSR**: Deterministic everywhere (no `rand()`). ## Configuration @@ -104,6 +104,7 @@ void MyScene::init() { | `PIXELROOT32_NO_I2S_AUDIO` | - | Disable I2S audio backend | | `ApuCore::MAX_VOICES` | `8` | Synthesis voice pool size | | `AudioCommandQueue::CAPACITY` | `128` | SPSC ring capacity | +| `ESP32_I2S_AudioBackend::blockSize` | `512` / `128` (single-core) | DMA buffer block size in samples | ## Related Types diff --git a/api/config.md b/api/config.md index 2c94223..e6a2558 100644 --- a/api/config.md +++ b/api/config.md @@ -35,6 +35,8 @@ This document covers global configuration options, build flags, and compile-time | `PIXELROOT32_ENABLE_PROFILING` | Enable profiling hooks in physics pipeline. | Disabled | | `PIXELROOT32_ENABLE_TOUCH` | Enable automatic touch processing. | `0` (disabled) | | `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE` | Enable **`StaticTilemapLayerCache`** (4bpp FB snapshot). | `1` | +| `PIXELROOT32_ENABLE_DIRTY_REGIONS` | Enable dirty-cell selective framebuffer clear (`DirtyGrid`). Requires 64–226 B RAM. | `0` | +| `PIXELROOT32_ENABLE_DIRTY_REGION_PROFILING` | Enable dirty region profiling metrics. | `0` | | `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 | @@ -52,9 +54,12 @@ This document covers global configuration options, build flags, and compile-time | `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 | +| `PIXELROOT32_ENABLE_DIRTY_REGIONS=0` | -64 to -226 bytes | ~1 KB | ## Build Profiles (platformio.ini) +> **Note:** `PIXELROOT32_ENABLE_DIRTY_REGIONS` can be added to any profile for selective framebuffer clearing. It is not enabled by default in any profile. + ```ini [profile_full] ; All features enabled build_flags = @@ -62,6 +67,7 @@ build_flags = -D PIXELROOT32_ENABLE_PHYSICS=1 -D PIXELROOT32_ENABLE_PARTICLES=1 -D PIXELROOT32_ENABLE_UI_SYSTEM=1 + -D PIXELROOT32_ENABLE_DIRTY_REGIONS=1 [profile_arcade] ; Audio + Physics + Particles, no UI build_flags = diff --git a/api/generated/audio/ApuCore.md b/api/generated/audio/ApuCore.md index f150c97..db9f0c2 100644 --- a/api/generated/audio/ApuCore.md +++ b/api/generated/audio/ApuCore.md @@ -25,32 +25,144 @@ Mixing curve: On cores without an FPU (ESP32-C3) the integer-optimised path uses `audio_mixer_lut` which is pre-fitted to the same curve. +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `audioTimeSamples` | `uint64_t` | Global sample counter at capture. | +| `peak` | `float` | Peak sample magnitude [0.0 - 1.0]. | +| `clipped` | `bool` | Whether any sample exceeded ±32767. | + ## Methods ### `void init(int sampleRate)` +**Description:** + +Configures the output sample rate. + +**Parameters:** + +- `sampleRate`: Sample rate in Hz (e.g., 22050, 44100). + +Safe to call before start(). Pre-calculates internal timing values. + ### `bool submitCommand(const AudioCommand& cmd)` -**Returns:** false if the SPSC queue was full. +**Description:** + +Enqueues a command for processing. + +**Parameters:** + +- `cmd`: The audio command to enqueue. + +**Returns:** true if the command was enqueued, false if the queue was full. ### `void generateSamples(int16_t* stream, int length)` +**Description:** + +Generates audio samples. + +**Parameters:** + +- `stream`: Output buffer (mono, int16 samples). +- `length`: Number of samples to generate. + +Processes all pending commands, updates music sequencer, and synthesizes +voices into the output buffer. Applies master volume, bitcrush, and DC blocking. + ### `uint32_t getDroppedCommands() const` +**Description:** + +Returns the total number of commands dropped since construction. @return Monotonic count. + +**Returns:** Monotonic count. + ### `void setSequencerNoteLimit(size_t limit)` +**Description:** + +Sets the maximum notes processed per audio frame. + +**Parameters:** + +- `limit`: Max notes [1-1000], clamped to safe bounds internally. + +Bounded processing prevents audio starvation when many notes queue up. + ### `size_t getSequencerNoteLimit() const` +**Description:** + +Gets current max notes per frame setting. @return Current limit. + +**Returns:** Current limit. + ### `size_t getDeferredNotes() const` +**Description:** + +Returns notes deferred to next frame due to note limit. @return Deferred count. + +**Returns:** Deferred count. + ### `bool isMusicPlaying() const` +**Description:** + +Reports if music is currently playing. @return true if playing. + +**Returns:** true if playing. + ### `bool isMusicPaused() const` +**Description:** + +Reports if music is paused. @return true if paused. + +**Returns:** true if paused. + ### `int getSampleRate() const` +**Description:** + +Gets the configured sample rate. @return Sample rate in Hz. + +**Returns:** Sample rate in Hz. + ### `void reset()` +**Description:** + +Resets all state to initial values. + ### `void setPostMixMono(void (*fn)(int16_t* mono, int length, void* user), void* user)` +**Description:** + +Sets an optional post-mix callback. + +**Parameters:** + +- `fn`: Function pointer: void(int16_t* mono, int length, void* user). +- `user`: User data passed to the callback. + +Called after bitcrush on the final mono buffer. Runs in audio thread context. + ### `void getAndResetProfileStats(ProfileEntry* out, uint8_t& count)` + +**Description:** + +Reads and clears all profile entries. + +**Parameters:** + +- `out`: Array of ProfileEntry to fill. +- `count`: On input: max entries. On output: actual count written. + +### `size_t countEnabledVoicesForTesting() const` + +### `size_t getSequencerMainNoteIndexForTesting() const` diff --git a/api/generated/audio/AudioBackend.md b/api/generated/audio/AudioBackend.md index 868b236..280f2d0 100644 --- a/api/generated/audio/AudioBackend.md +++ b/api/generated/audio/AudioBackend.md @@ -8,16 +8,36 @@ 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. +This class abstracts the underlying audio hardware or API (e.g., SDL2 on native, +I2S on ESP32). It is responsible for requesting audio samples from the AudioEngine +and pushing them to the output device. Platforms implement this interface to +bridge between the engine's sample generation and the actual hardware. + +Typical lifecycle: +1. init() is called with an AudioEngine pointer. +2. The backend sets up the audio output (I2S, SDL audio, etc.). +3. In its output callback, the backend calls engine->generateSamples(). +4. On destruction, resources are cleaned up. ## Methods +### `virtual void init(AudioEngine* engine, const pixelroot32::platforms::PlatformCapabilities& caps = pixelroot32::platforms::PlatformCapabilities())` + +**Description:** + +Initializes the audio backend. + +**Parameters:** + +- `engine`: Pointer to the AudioEngine instance to request samples from. + Must not be nullptr. +- `caps`: Platform capabilities to guide backend initialization + (e.g., core pinning on ESP32, thread priorities). + ### `virtual int getSampleRate() const` **Description:** Returns the configured sample rate of the backend. -**Returns:** Sample rate in Hz (e.g., 22050, 44100). +**Returns:** Sample rate in Hz (e.g., 22050, 44100, 48000). diff --git a/api/generated/audio/AudioChannel.md b/api/generated/audio/AudioChannel.md index fc92e6b..bf184fb 100644 --- a/api/generated/audio/AudioChannel.md +++ b/api/generated/audio/AudioChannel.md @@ -10,6 +10,21 @@ Represents the internal state of a single audio channel. Designed to be static and memory-efficient. +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `sweepSamplesTotal` | `uint32_t` | Total samples for the sweep. | +| `sweepSamplesRemaining` | `uint32_t` | Samples remaining in the sweep. | +| `sweepStartHz` | `float` | Starting frequency in Hz. | +| `sweepEndHz` | `float` | Ending frequency in Hz. | +| `sweepStartIncQ32` | `uint32_t` | Q32 phase increment at sweep start. | +| `sweepEndIncQ32` | `uint32_t` | Q32 phase increment at sweep end. | + ## Methods ### `void reset()` + +**Description:** + +Resets the channel to a clean disabled state. diff --git a/api/generated/audio/AudioCommandQueue.md b/api/generated/audio/AudioCommandQueue.md index 60ddedb..6d7e17a 100644 --- a/api/generated/audio/AudioCommandQueue.md +++ b/api/generated/audio/AudioCommandQueue.md @@ -6,11 +6,18 @@ ## Description -Multi-Producer Single-Consumer lock-free ring buffer for AudioCommands. +Multi-Producer Single-Consumer (MPSC) 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. +Fixed-size, zero-allocation queue designed for real-time audio thread communication. +Supports multiple concurrent producer threads (e.g., game logic, music sequencer) +and a single consumer thread (the audio thread). + +Drop policy: When the queue is full, the newest command is silently dropped and +the droppedCommands counter is incremented. Callers can monitor this via +getDroppedCommands() for diagnostics. + +Thread-safety: Uses compare-and-swap (CAS) for atomic ring index advancement. +The producer path is wait-free; the consumer path is lock-free. ## Methods diff --git a/api/generated/audio/AudioConfig.md b/api/generated/audio/AudioConfig.md index 04b06c7..e328596 100644 --- a/api/generated/audio/AudioConfig.md +++ b/api/generated/audio/AudioConfig.md @@ -14,3 +14,19 @@ Configuration for the Audio subsystem. |------|------|-------------| | `backend` | `AudioBackend*` | Pointer to the platform-specific audio backend. | | `sampleRate` | `int` | Desired sample rate in Hz. | +| `blockSize` | `int` | Audio block size (samples). Must be multiple of 128. | + +## Methods + +### `: backend(backend), sampleRate(sampleRate), blockSize(blockSize)` + +**Description:** + +Constructs an AudioConfig with platform-adaptive block size. + +**Parameters:** + +- `backend`: Pointer to the audio backend implementation. May be nullptr for headless configs. +- `sampleRate`: Desired sample rate in Hz (default 22050 for retro feel). +- `blockSize`: Audio block size in samples. Defaults to 256 on FPU platforms, 128 on no-FPU platforms. + Must be a multiple of 128 for I2S DMA alignment. diff --git a/api/generated/audio/AudioEngine.md b/api/generated/audio/AudioEngine.md index 134844f..be792ac 100644 --- a/api/generated/audio/AudioEngine.md +++ b/api/generated/audio/AudioEngine.md @@ -6,26 +6,93 @@ ## Description -Core class for the NES-like audio subsystem. +Facade class for the NES-style audio subsystem. + +Provides a high-level API for playing sound events, controlling volume, +and managing music playback. Internally delegates to an AudioScheduler +(typically DefaultAudioScheduler) which owns the ApuCore for synthesis. + +Usage: +1. Construct with an AudioConfig and PlatformCapabilities. +2. Call init() to set up the scheduler and backend. +3. Call playEvent() to trigger one-shot sounds. +4. Use MusicPlayer for music track sequencing. ## Methods ### `void init()` +**Description:** + +Initializes the engine and its internal scheduler. + ### `void generateSamples(int16_t* stream, int length)` +**Description:** + +Generates audio samples into the output buffer. + +**Parameters:** + +- `stream`: Output buffer (mono, int16 samples). +- `length`: Number of samples to generate. + ### `void playEvent(const AudioEvent& event)` +**Description:** + +Triggers a one-shot sound event. + +**Parameters:** + +- `event`: The event to play (type, frequency, duration, volume). + ### `void setMasterVolume(float volume)` +**Description:** + +Sets the master volume for all audio output. + +**Parameters:** + +- `volume`: Volume level [0.0 = silent, 1.0 = full]. + ### `float getMasterVolume() const` +**Description:** + +Gets the current master volume. @return Volume [0.0 - 1.0]. + +**Returns:** Volume [0.0 - 1.0]. + ### `void setMasterBitcrush(uint8_t bits)` +**Description:** + +Sets the master bitcrusher effect on the final output. + +**Parameters:** + +- `bits`: Bit depth reduction [0 = off, 1-15 = re-quantize to N bits]. + ### `uint8_t getMasterBitcrush() const` +**Description:** + +Gets the current master bitcrush setting. @return Bit depth (0 = off). + +**Returns:** Bit depth (0 = off). + ### `void submitCommand(const AudioCommand& cmd)` +**Description:** + +Submits a raw audio command to the scheduler. + +**Parameters:** + +- `cmd`: The command to execute. + ### `bool isMusicPlaying() const` **Description:** @@ -35,8 +102,20 @@ Reports the real-time music transport state from the ### `bool isMusicPaused() const` +### `void setScheduler(std::unique_ptr scheduler)` + +**Description:** + +Replaces the scheduler with a custom implementation. + +**Parameters:** + +- `scheduler`: Unique pointer to the new scheduler. Takes ownership. + ### `AudioScheduler* getScheduler() const` **Description:** -Gets the underlying scheduler for diagnostics/profiling. +Gets the current scheduler for diagnostics or profiling. + +**Returns:** Pointer to the current AudioScheduler, or nullptr if not set. diff --git a/api/generated/audio/AudioScheduler.md b/api/generated/audio/AudioScheduler.md index 2d76313..f4017fb 100644 --- a/api/generated/audio/AudioScheduler.md +++ b/api/generated/audio/AudioScheduler.md @@ -14,27 +14,41 @@ or in a dedicated audio thread. ## Methods +### `virtual void init(AudioBackend* backend, int sampleRate, const pixelroot32::platforms::PlatformCapabilities& caps = pixelroot32::platforms::PlatformCapabilities(), int blockSize = 256)` + +**Description:** + +Initializes the scheduler. + +**Parameters:** + +- `backend`: The audio backend to use for output. +- `sampleRate`: The output sample rate in Hz. +- `caps`: Platform capabilities to guide core pinning or threading decisions. +- `blockSize`: Audio block size in samples for I2S DMA and ring buffer operations. + Must be a multiple of 128. + ### `virtual void submitCommand(const AudioCommand& cmd)` **Description:** -Submits a command to the scheduler. +Submits a command to the scheduler for execution. **Parameters:** -- `cmd`: The command to execute. +- `cmd`: The command to enqueue (PLAY_EVENT, STOP_CHANNEL, etc.). ### `virtual void start()` **Description:** -Starts the scheduler execution. +Starts the scheduler execution. Enables audio generation. ### `virtual void stop()` **Description:** -Stops the scheduler execution. +Stops the scheduler execution. Silences all voices. ### `virtual bool isIndependent() const` @@ -42,11 +56,21 @@ Stops the scheduler execution. Checks if the scheduler runs in an independent thread. +**Returns:** true if the scheduler owns a dedicated audio thread, false if it + runs synchronously with the backend's callback. + ### `virtual void generateSamples(int16_t* stream, int length)` **Description:** -Generates samples. Should be called by the backend or scheduler thread. +Generates samples into the provided buffer. + +**Parameters:** + +- `stream`: Pointer to the output buffer (mono, int16 samples). +- `length`: Number of samples to generate (must match blockSize). + +Called by the backend (or scheduler thread) to fill the audio buffer. ### `virtual bool isMusicPlaying() const` diff --git a/api/generated/audio/DefaultAudioScheduler.md b/api/generated/audio/DefaultAudioScheduler.md index cb483d3..8cc515f 100644 --- a/api/generated/audio/DefaultAudioScheduler.md +++ b/api/generated/audio/DefaultAudioScheduler.md @@ -8,19 +8,58 @@ ## Description -Backend-driven scheduler used on platforms without a dedicated - audio task. +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.). +context the backend invokes it (tests, simulators without a thread, etc.). +Does not own a thread — audio generation is driven by the backend's callback. + +For platforms with a dedicated audio task (e.g., FreeRTOS on ESP32), + use a scheduler that spawns its own thread instead. ## Inheritance [AudioScheduler](./AudioScheduler.md) → `DefaultAudioScheduler` +::: tip +For platforms with a dedicated audio task (e.g., FreeRTOS on ESP32), + use a scheduler that spawns its own thread instead. +::: + ## Methods +### `bool isMusicPlaying() const` + +**Description:** + +Generates samples via ApuCore. @param stream Output buffer. @param length Sample count. + +**Parameters:** + +- `stream`: Output buffer. + +### `bool isMusicPaused() const` + +### `ApuCore& getApuCore()` + +**Description:** + +Returns reference to the ApuCore for diagnostics. @return ApuCore reference. + +**Returns:** ApuCore reference. + ### `const ApuCore& core() const` +**Description:** + +Exposes the underlying core for tests or higher-level queries. @return Const ApuCore reference. + +**Returns:** Const ApuCore reference. + ### `ApuCore& core()` + +**Description:** + +Exposes the underlying core for tests or higher-level queries. @return ApuCore reference. + +**Returns:** ApuCore reference. diff --git a/api/generated/audio/EnvelopeState.md b/api/generated/audio/EnvelopeState.md new file mode 100644 index 0000000..f97cca0 --- /dev/null +++ b/api/generated/audio/EnvelopeState.md @@ -0,0 +1,40 @@ +# EnvelopeState + + + +**Source:** `AudioTypes.h` + +## Description + +Holds ADSR envelope state for a single voice. + +Tracks the attack/decay/sustain/release stages and provides both +floating-point and Q15 fixed-point variants to support platforms +without an FPU (e.g., ESP32-C3 RISC-V). + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `attackSamples` | `uint32_t` | Samples for attack phase (0 = instantaneous). | +| `decaySamples` | `uint32_t` | Samples for decay phase. | +| `sustainLevel` | `float` | Target volume at sustain phase [0.0 - 1.0]. | +| `releaseSamples` | `uint32_t` | Samples for release phase. | +| `sampleCounter` | `uint32_t` | Counter within current stage. | +| `currentLevel` | `float` | Current envelope amplitude [0.0 - 1.0]. | +| `attackDelta` | `float` | Volume increment per sample during attack: 1.0 / attackSamples. | +| `decayDelta` | `float` | Volume decrement per sample during decay: (1.0 - sustainLevel) / decaySamples. | +| `releaseDelta` | `float` | Volume decrement per sample during release: sustainLevel / releaseSamples. | +| `currentLevelQ15` | `int32_t` | Q15 mirror of currentLevel (-32768 to +32768). | +| `attackDeltaQ15` | `int32_t` | Q15 attack delta: 32768 / attackSamples. | +| `decayDeltaQ15` | `int32_t` | Q15 decay delta: (32768 - sustainLevelQ15) / decaySamples. | +| `sustainLevelQ15` | `int32_t` | Q15 sustain level. | +| `releaseDeltaQ15` | `int32_t` | Q15 release delta: sustainLevelQ15 / releaseSamples. | + +## Methods + +### `void reset()` + +**Description:** + +Resets all envelope state to OFF. diff --git a/api/generated/audio/InstrumentPreset.md b/api/generated/audio/InstrumentPreset.md index 6d7bea0..fa5e39c 100644 --- a/api/generated/audio/InstrumentPreset.md +++ b/api/generated/audio/InstrumentPreset.md @@ -19,6 +19,47 @@ For percussion instruments (duty == 0): ## Methods +### `inline MusicNote makeNote(const InstrumentPreset& preset, Note note, float duration)` + +**Description:** + +Constructs a MusicNote using the preset's default octave. + +**Parameters:** + +- `preset`: The instrument preset defining default volume and octave. +- `note`: The musical note to play. +- `duration`: Note duration in seconds. + +**Returns:** A MusicNote with the preset's base volume and default octave. + +### `inline MusicNote makeNote(const InstrumentPreset& preset, Note note, uint8_t octave, float duration)` + +**Description:** + +Constructs a MusicNote with a specific octave override. + +**Parameters:** + +- `preset`: The instrument preset defining default volume and other parameters. +- `note`: The musical note to play. +- `octave`: The octave for this note (overrides preset default). +- `duration`: Note duration in seconds. + +**Returns:** A MusicNote with the preset's base volume and specified octave. + +### `inline MusicNote makeRest(float duration)` + +**Description:** + +Constructs a rest (silence) note. + +**Parameters:** + +- `duration`: Duration of the rest in seconds. + +**Returns:** A MusicNote representing silence. + ### `inline float instrumentToFrequency(const InstrumentPreset& preset, Note /*note*/, uint8_t /*octave*/)` **Parameters:** diff --git a/api/generated/audio/LfoState.md b/api/generated/audio/LfoState.md new file mode 100644 index 0000000..fb367d7 --- /dev/null +++ b/api/generated/audio/LfoState.md @@ -0,0 +1,36 @@ +# LfoState + + + +**Source:** `AudioTypes.h` + +## Description + +Holds LFO (Low-Frequency Oscillator) state for pitch or volume modulation. + +Supports both floating-point and Q15 fixed-point variants for no-FPU platforms. +The LFO produces a sine-like waveform that can modulate pitch (vibrato) or +volume (tremolo) of a voice. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `enabled` | `bool` | Whether the LFO is active. | +| `target` | `LfoTarget` | What the LFO modulates: PITCH or VOLUME. | +| `depth` | `float` | Modulation depth (pitch: ratio e.g. 0.05; volume: 0.0-1.0). | +| `periodSamples` | `uint32_t` | LFO period in samples (derived from lfoFrequency). | +| `sampleCounter` | `uint32_t` | Current sample position within the period. | +| `currentValue` | `float` | Current LFO output value [-1.0 to +1.0]. | +| `depthQ15` | `int32_t` | Q15 depth: 0-32768 maps to 0.0-1.0. | +| `currentValueQ15` | `int32_t` | Q15 output: -32768 to +32768 maps to -1.0 to +1.0. | +| `delaySamples` | `uint16_t` | Samples to wait before LFO starts (in seconds * sampleRate). | +| `delayCounter` | `uint16_t` | Countdown for delay phase. | + +## Methods + +### `void reset()` + +**Description:** + +Resets all LFO state to disabled. diff --git a/api/generated/audio/MusicPlayer.md b/api/generated/audio/MusicPlayer.md index ebac94b..d76a11a 100644 --- a/api/generated/audio/MusicPlayer.md +++ b/api/generated/audio/MusicPlayer.md @@ -6,7 +6,11 @@ ## Description -Simple sequencer to play MusicTracks. +Simple sequencer to play MusicTracks using the AudioEngine. + +Provides a high-level interface for music playback with tempo control, +pause/resume, and master volume management. Wraps audio commands to the +engine's scheduler. ## Methods @@ -18,25 +22,25 @@ Starts playing a track. **Parameters:** -- `track`: The track to play. +- `track`: The MusicTrack to play. Must remain in scope for duration. ### `void stop()` **Description:** -Stops playback and silences the channel. +Stops playback and silences all voices. ### `void pause()` **Description:** -Pauses playback. +Pauses playback at the current position. ### `void resume()` **Description:** -Resumes playback. +Resumes playback from the paused position. ### `bool isPlaying() const` @@ -54,7 +58,7 @@ Sets the global tempo scaling factor. **Parameters:** -- `factor`: 1.0f is normal speed, 2.0f is double speed. +- `factor`: 1.0f is normal speed, 2.0f is double speed, 0.5f is half speed. ### `float getTempoFactor() const` @@ -72,7 +76,7 @@ Sets the tempo in BPM (beats per minute). **Parameters:** -- `bpm`: Beats per minute (default 150). +- `bpm`: Beats per minute (affects note timing resolution). ### `float getBPM() const` @@ -94,11 +98,11 @@ Gets the number of currently active tracks. **Description:** -Sets the master volume level. +Sets the master volume level for music playback. **Parameters:** -- `volume`: Volume level (0.0f = silent, 1.0f = full volume). +- `volume`: Volume level [0.0f = silent, 1.0f = full volume]. ### `float getMasterVolume() const` @@ -106,4 +110,4 @@ Sets the master volume level. Gets the current master volume level. -**Returns:** Current volume (0.0f - 1.0f). +**Returns:** Current volume [0.0f - 1.0f]. diff --git a/api/generated/core/Actor.md b/api/generated/core/Actor.md index 57cb517..4ce2ece 100644 --- a/api/generated/core/Actor.md +++ b/api/generated/core/Actor.md @@ -29,6 +29,24 @@ enemies, and projectiles. ## Methods +### `: Entity(x, y, w, h, EntityType::ACTOR)` + +**Description:** + +Constructor using Scalar coordinates. + +### `: Entity(pos, w, h, EntityType::ACTOR)` + +**Description:** + +Constructor using Vector2 position. + +### `void setCollisionLayer(pixelroot32::physics::CollisionLayer l)` + +### `void setCollisionMask(pixelroot32::physics::CollisionLayer m)` + +### `void update(unsigned long deltaTime)` + ### `bool isInLayer(uint16_t targetLayer) const` ### `virtual Rect getHitBox()` diff --git a/api/generated/core/Engine.md b/api/generated/core/Engine.md index e8d531a..393e554 100644 --- a/api/generated/core/Engine.md +++ b/api/generated/core/Engine.md @@ -65,7 +65,50 @@ Sets the current active scene. - `newScene`: Pointer to the new Scene to become active. -### `* Use this to inject touch points on ESP32(via TouchManager)` +### `std::optional getCurrentScene() const` + +**Description:** + +Retrieves the currently active scene. + +**Returns:** Optional pointer to the current Scene, or nullopt if none is set. + +### `void setRenderer(pixelroot32::graphics::Renderer&& newRenderer)` + +**Description:** + +Replaces the current renderer instance. + +**Parameters:** + +- `newRenderer`: R-value reference to the new Renderer to use. + +### `pixelroot32::graphics::Renderer& getRenderer()` + +**Description:** + +Provides access to the Renderer subsystem. + +**Returns:** Reference to the current Renderer. + +### `pixelroot32::input::InputManager& getInputManager()` + +**Description:** + +Provides access to the InputManager subsystem. + +**Returns:** Reference to the InputManager . + +### `pixelroot32::input::TouchEventDispatcher& getTouchDispatcher()` + +**Description:** + +Provides access to the touch event system. + +**Returns:** Reference to the TouchEventDispatcher. + +Use this to inject touch points on ESP32 (via TouchManager): + engine.getTouchDispatcher().processTouch(id, pressed, x, y, timestamp); ### `bool hasTouchEvents() const` @@ -75,7 +118,26 @@ 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 setTouchManager(pixelroot32::input::TouchManager* touchManager)` + +**Description:** + +Set the TouchManager for automatic touch processing. + +**Parameters:** + +- `touchManager`: Pointer to the TouchManager instance. + +On ESP32, call this once in setup() after touchManager.init(): + touchManager.init(); + engine.setTouchManager(&touchManager); + +Then in loop(), just call engine.run() - Engine handles: + - Polling touchManager.getTouchPoints() each frame + - Detecting touch release (when count goes from >0 to 0) + - Sending gesture events to the current scene + +This eliminates the need to manually inject touch points or track release state. ### `void connectInputToDrawer()` @@ -89,6 +151,34 @@ Connect InputManager to Drawer for mouse-to-touch mapping. Called automatically in init() for Native builds. +### `graphics::ui::UIManager& getUIManager()` + +**Description:** + +Provides access to the UI system via the current scene. + +**Returns:** Reference to the current scene's UIManager. + +::: tip +Asserts if no scene is currently active. +::: + +### `pixelroot32::audio::AudioEngine& getAudioEngine()` + +**Description:** + +Provides access to the AudioEngine subsystem. + +**Returns:** Reference to the AudioEngine. + +### `pixelroot32::audio::MusicPlayer& getMusicPlayer()` + +**Description:** + +Provides access to the MusicPlayer subsystem. + +**Returns:** Reference to the MusicPlayer. + ### `const PlatformCapabilities& getPlatformCapabilities() const` **Description:** @@ -108,3 +198,10 @@ Updates the game logic. **Description:** Renders the current frame. + +### `void drawDebugOverlay(pixelroot32::graphics::Renderer& r)` + +**Description:** + +Draws a debug overlay with real-time engine metrics. +Shows FPS, CPU usage (estimated), and RAM usage. diff --git a/api/generated/core/Entity.md b/api/generated/core/Entity.md index 1113600..6c9f6e4 100644 --- a/api/generated/core/Entity.md +++ b/api/generated/core/Entity.md @@ -63,6 +63,40 @@ Sets the render layer. - `layer`: The layer index (0 to MaxLayers-1). Clamped if exceeded. +### `: position(pos), width(w), height(h), type(t)` + +**Description:** + +Constructor. + +**Parameters:** + +- `position`: Initial position. +- `w`: Width. +- `h`: Height. +- `t`: EntityType. + +### `: position(x, y), width(w), height(h), type(t)` + +**Description:** + +Constructor. + +**Parameters:** + +- `x`: Initial X position. +- `y`: Initial Y position. +- `w`: Width. +- `h`: Height. +- `t`: EntityType. + +### `: position(pixelroot32::math::toScalar(x), pixelroot32::math::toScalar(y)), width(w), height(h), type(t)` + +**Description:** + +Constructor with float coordinates for convenience. +Only enabled if Scalar is NOT float to avoid ambiguity. + ### `virtual void update(unsigned long deltaTime)` **Description:** @@ -72,3 +106,13 @@ Updates the entity's logic. **Parameters:** - `deltaTime`: Time elapsed since the last frame in milliseconds. + +### `virtual void draw(pixelroot32::graphics::Renderer& renderer)` + +**Description:** + +Renders the entity. + +**Parameters:** + +- `renderer`: Reference to the renderer to use for drawing. diff --git a/api/generated/core/PhysicsActor.md b/api/generated/core/PhysicsActor.md index 9643765..d5aa502 100644 --- a/api/generated/core/PhysicsActor.md +++ b/api/generated/core/PhysicsActor.md @@ -72,6 +72,14 @@ Gets information about collisions with the world boundaries. **Returns:** A WorldCollisionInfo struct containing collision flags. +### `bool isPhysicsBody() const` + +**Description:** + +Checks if this actor is a physics-enabled body. + +**Returns:** true. + ### `void resetWorldCollisionInfo()` **Description:** @@ -106,6 +114,42 @@ Sets the mass of the actor. - `m`: Mass value. +### `pixelroot32::math::Scalar getMass() const` + +**Description:** + +Gets the mass of the actor. + +**Returns:** Mass as Scalar. + +### `void setGravityScale(pixelroot32::math::Scalar scale)` + +**Description:** + +Sets the gravity scale. + +**Parameters:** + +- `scale`: Multiplier for the world gravity. + +### `pixelroot32::math::Scalar getGravityScale() const` + +**Description:** + +Gets the gravity scale. + +**Returns:** Gravity scale as Scalar. + +### `virtual void integrate(pixelroot32::math::Scalar dt)` + +**Description:** + +Integrates velocity to update position. + +**Parameters:** + +- `dt`: Delta time in seconds (as Scalar). + ### `virtual void resolveWorldBounds()` **Description:** @@ -123,6 +167,79 @@ Sets the linear velocity of the actor using floats. - `x`: Horizontal velocity. - `y`: Vertical velocity. +### `void setVelocity(pixelroot32::math::Scalar x, pixelroot32::math::Scalar y)` + +**Description:** + +Sets the linear velocity of the actor using Scalars. + +**Parameters:** + +- `x`: Horizontal velocity. +- `y`: Vertical velocity. + +### `void setVelocity(const pixelroot32::math::Vector2& v)` + +**Description:** + +Sets the linear velocity of the actor using a Vector2. + +**Parameters:** + +- `v`: Velocity vector. + +### `pixelroot32::math::Scalar getVelocityX() const` + +**Description:** + +Gets the horizontal velocity. + +**Returns:** Horizontal velocity as Scalar. + +### `pixelroot32::math::Scalar getVelocityY() const` + +**Description:** + +Gets the vertical velocity. + +**Returns:** Vertical velocity as Scalar. + +### `const pixelroot32::math::Vector2& getVelocity() const` + +**Description:** + +Gets the velocity vector. + +**Returns:** Reference to the velocity Vector2. + +### `void setRestitution(pixelroot32::math::Scalar r)` + +**Description:** + +Sets the restitution (bounciness) of the actor. + +**Parameters:** + +- `r`: Restitution value (0.0 to 1.0+). 1.0 means no energy is lost on bounce. + +### `pixelroot32::math::Scalar getRestitution() const` + +**Description:** + +Gets the restitution (bounciness) of the actor. + +**Returns:** Restitution as Scalar. + +### `void setFriction(pixelroot32::math::Scalar f)` + +**Description:** + +Sets the friction coefficient. + +**Parameters:** + +- `f`: Friction value (0.0 means no friction). + ### `CollisionShape getShape() const` **Description:** @@ -141,6 +258,24 @@ Sets the collision shape type. - `s`: The new CollisionShape. +### `pixelroot32::math::Scalar getRadius() const` + +**Description:** + +Gets the radius (only for Shape::CIRCLE). + +**Returns:** Radius as Scalar. + +### `void setRadius(pixelroot32::math::Scalar r)` + +**Description:** + +Sets the radius and updates width/height to match diameter. + +**Parameters:** + +- `r`: Radius value. + ### `void setUserData(void* data)` **Description:** @@ -213,6 +348,24 @@ Returns true if this body bounces on collision. Updates the previous position to the current position. +### `pixelroot32::math::Vector2 getPreviousPosition() const` + +**Description:** + +Gets the previous frame position. + +**Returns:** The position from the previous physics frame. + +### `void setPosition(pixelroot32::math::Vector2 pos)` + +**Description:** + +Sets the position and syncs previous position. + +**Parameters:** + +- `pos`: The new position. + ### `virtual void onWorldCollision()` **Description:** diff --git a/api/generated/core/Scene.md b/api/generated/core/Scene.md index eca1760..33f6c6c 100644 --- a/api/generated/core/Scene.md +++ b/api/generated/core/Scene.md @@ -33,7 +33,34 @@ Update the UI system. - `deltaTime`: Time elapsed in ms. -### `* Execution order(deterministic)` +### `pixelroot32::graphics::ui::UIManager& getUIManager()` + +**Description:** + +Get the UI manager for this scene. + +**Returns:** Reference to the UIManager + +### `virtual void processTouchEvents(pixelroot32::input::TouchEvent* events, uint8_t count)` + +**Description:** + +Central touch pipeline entry point (call once per frame). + +**Parameters:** + +- `events`: Mutable buffer — consumed flags are set in-place. +- `count`: Number of events in the buffer. + +### `virtual void onUnconsumedTouchEvent(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Hook for scene-specific handling of unconsumed touch events. + +**Parameters:** + +- `event`: The touch event (not consumed by UI). ### `virtual void update(unsigned long deltaTime)` @@ -45,6 +72,30 @@ Updates all entities in the scene and handles collisions. - `deltaTime`: Time elapsed in ms. +### `virtual void draw(pixelroot32::graphics::Renderer& renderer)` + +**Description:** + +Draws all visible entities in the scene. + +**Parameters:** + +- `renderer`: The renderer to use. + +### `virtual void adviseFramebufferBeforeBeginFrame(pixelroot32::graphics::Renderer& renderer)` + +**Description:** + +Advises the scene that the framebuffer is about to be drawn. + +**Parameters:** + +- `renderer`: The renderer to use. + +Optional hook: run immediately before Renderer::beginFrame(). +Scenes using StaticTilemapLayerCache should call adviseFramebufferBeforeBeginFrame with the same +layers/camera sampling as StaticTilemapLayerCache::draw so dirty-region clears align with framebuffer memcpy restores. + ### `virtual bool shouldRedrawFramebuffer() const` **Description:** diff --git a/api/generated/core/SceneManager.md b/api/generated/core/SceneManager.md index 174fb9c..3170d09 100644 --- a/api/generated/core/SceneManager.md +++ b/api/generated/core/SceneManager.md @@ -49,6 +49,30 @@ Updates the currently active scene. - `dt`: Delta time in ms. +### `void draw(pixelroot32::graphics::Renderer& renderer)` + +**Description:** + +Draws the currently active scene. + +**Parameters:** + +- `renderer`: The renderer to use. + +### `void adviseFramebufferBeforeBeginFrame(pixelroot32::graphics::Renderer& renderer)` + +**Description:** + +Lets stacked scenes advertise framebuffer prep (runs before Renderer::beginFrame). + +### `std::optional getCurrentScene() const` + +**Description:** + +Gets the currently active scene. + +**Returns:** Optional pointer to the top scene on the stack. + ### `bool aggregateShouldRedrawFramebuffer() const` **Description:** diff --git a/api/generated/drivers/ESP32AudioScheduler.md b/api/generated/drivers/ESP32AudioScheduler.md index 618cf3f..1c0409b 100644 --- a/api/generated/drivers/ESP32AudioScheduler.md +++ b/api/generated/drivers/ESP32AudioScheduler.md @@ -21,6 +21,14 @@ Constructor arguments are reserved for API stability. ## Methods +### `bool isIndependent() const` + +### `bool isMusicPlaying() const` + +### `bool isMusicPaused() const` + +### `ApuCore& getApuCore()` + ### `const ApuCore& core() const` ### `ApuCore& core()` diff --git a/api/generated/drivers/ESP32_DAC_AudioBackend.md b/api/generated/drivers/ESP32_DAC_AudioBackend.md index b2263a8..18d928c 100644 --- a/api/generated/drivers/ESP32_DAC_AudioBackend.md +++ b/api/generated/drivers/ESP32_DAC_AudioBackend.md @@ -23,4 +23,6 @@ Target: ESP32 (original), ESP32-S2. The DAC does not exist on S3/C3. ## Methods +### `int getSampleRate() const` + ### `void audioTaskLoop()` diff --git a/api/generated/drivers/ESP32_I2S_AudioBackend.md b/api/generated/drivers/ESP32_I2S_AudioBackend.md index 515dd52..d47063a 100644 --- a/api/generated/drivers/ESP32_I2S_AudioBackend.md +++ b/api/generated/drivers/ESP32_I2S_AudioBackend.md @@ -19,4 +19,6 @@ to ensure smooth playback independent of the game loop frame rate. ## Methods +### `int getSampleRate() const` + ### `void audioTaskLoop()` diff --git a/api/generated/drivers/NativeAudioScheduler.md b/api/generated/drivers/NativeAudioScheduler.md index 51c9f10..73c5e67 100644 --- a/api/generated/drivers/NativeAudioScheduler.md +++ b/api/generated/drivers/NativeAudioScheduler.md @@ -23,6 +23,14 @@ threading and the ring buffer. ### `explicit NativeAudioScheduler(size_t ringBufferSize = 4096)` +### `bool isIndependent() const` + +### `bool isMusicPlaying() const` + +### `bool isMusicPaused() const` + +### `ApuCore& getApuCore()` + ### `const ApuCore& core() const` ### `ApuCore& core()` diff --git a/api/generated/drivers/SDL2_AudioBackend.md b/api/generated/drivers/SDL2_AudioBackend.md index c2b78e4..52fce6b 100644 --- a/api/generated/drivers/SDL2_AudioBackend.md +++ b/api/generated/drivers/SDL2_AudioBackend.md @@ -16,4 +16,6 @@ Audio backend implementation for SDL2 (Windows/Linux/Mac). ## Methods +### `int getSampleRate() const` + ### `void audioCallback(uint8_t* stream, int len)` diff --git a/api/generated/drivers/SDL2_Drawer.md b/api/generated/drivers/SDL2_Drawer.md index d31d9d7..522d05d 100644 --- a/api/generated/drivers/SDL2_Drawer.md +++ b/api/generated/drivers/SDL2_Drawer.md @@ -13,3 +13,36 @@ SDL2-backed draw surface for native desktop builds. ## Inheritance [BaseDrawSurface](../graphics/BaseDrawSurface.md) → `SDL2_Drawer` + +## Methods + +### `uint8_t* getSpriteBuffer()` + +**Description:** + +Get pointer to sprite buffer (not supported in SDL2). + +### `void setTouchDispatcher(pixelroot32::input::TouchEventDispatcher* touchDispatcher)` + +**Description:** + +Set the TouchEventDispatcher to receive mouse events. + +**Parameters:** + +- `touchDispatcher`: Pointer to the Engine's TouchEventDispatcher. + +This is the preferred method when PIXELROOT32_ENABLE_TOUCH is enabled. +Mouse events are mapped directly to touch events. + +### `void setInputManager(pixelroot32::input::InputManager* inputManager)` + +**Description:** + +Set the InputManager for backwards compatibility. + +**Parameters:** + +- `inputManager`: Pointer to the InputManager instance. + +Deprecated: Use setTouchDispatcher() instead. diff --git a/api/generated/drivers/U8G2_Drawer.md b/api/generated/drivers/U8G2_Drawer.md index a5f6ff3..7c7c68b 100644 --- a/api/generated/drivers/U8G2_Drawer.md +++ b/api/generated/drivers/U8G2_Drawer.md @@ -18,6 +18,10 @@ Implementation of DrawSurface using the U8G2 library for monochromatic OLED disp ### `U8G2* getU8g2() const` +### `void drawTileDirect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data)` + +### `uint8_t* getSpriteBuffer()` + ### `bool needsScaling() const` **Description:** diff --git a/api/generated/graphics/BaseDrawSurface.md b/api/generated/graphics/BaseDrawSurface.md index d919b5f..9234faf 100644 --- a/api/generated/graphics/BaseDrawSurface.md +++ b/api/generated/graphics/BaseDrawSurface.md @@ -8,15 +8,208 @@ ## Description -Optional base class for DrawSurface implementations that provides default primitive rendering. +Optional base class for DrawSurface implementations providing 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() +At minimum, a subclass must implement: +- init() — initialize the hardware or window +- drawPixel() — draw a single pixel (all other primitives delegate here) +- sendBuffer() — transfer framebuffer to physical display +- clearBuffer() — fill framebuffer with background color + +The default implementations use Bresenham-style algorithms for lines, +rectangles, circles, and bitmaps, all building on drawPixel(). +These are slow but correct; override in performance-critical drivers. ## Inheritance [DrawSurface](./DrawSurface.md) → `BaseDrawSurface` + +## Methods + +### `void setTextColor(uint16_t color)` + +**Description:** + +Sets the text color. + +**Parameters:** + +- `color`: 16-bit RGB565 text color. + +### `void setTextSize(uint8_t size)` + +**Description:** + +Sets the text size multiplier. + +**Parameters:** + +- `size`: Size multiplier (1 = normal, 2 = double, etc.). + +### `void setCursor(int16_t x, int16_t y)` + +**Description:** + +Sets the text cursor position. + +**Parameters:** + +- `x`: X coordinate in pixels. +- `y`: Y coordinate in pixels. + +### `void setContrast(uint8_t level)` + +**Description:** + +Sets the display brightness. + +**Parameters:** + +- `level`: Contrast level (0–255). + +### `void setRotation(uint16_t rot)` + +**Description:** + +Sets the display rotation. + +**Parameters:** + +- `rot`: Rotation index (0, 1, 2, 3) or degrees (0, 90, 180, 270). + +### `void setDisplaySize(int w, int h)` + +**Description:** + +Sets the logical rendering resolution. + +**Parameters:** + +- `w`: Logical width in pixels. +- `h`: Logical height in pixels. + +### `void setPhysicalSize(int w, int h)` + +**Description:** + +Sets the physical display resolution. + +**Parameters:** + +- `w`: Physical width in pixels. +- `h`: Physical height in pixels. + +### `void setOffset(int x, int y)` + +**Description:** + +Sets the display origin offset. + +**Parameters:** + +- `x`: Horizontal offset in pixels. +- `y`: Vertical offset in pixels. + +### `void present()` + +**Description:** + +Transfers the framebuffer to the physical display. + +### `uint16_t color565(uint8_t r, uint8_t g, uint8_t b)` + +**Description:** + +Converts 24-bit RGB to RGB565. + +**Parameters:** + +- `r`: Red component (0-255). +- `g`: Green component (0-255). +- `b`: Blue component (0-255). + +**Returns:** 16-bit RGB565 color. + +### `void drawLine(int x1, int y1, int x2, int y2, uint16_t color)` + +**Description:** + +Draws a line between two points (Bresenham algorithm). + +**Parameters:** + +- `x1`: Start X coordinate. +- `y1`: Start Y coordinate. +- `x2`: End X coordinate. +- `y2`: End Y coordinate. +- `color`: 16-bit RGB565 line color. + +### `void drawRectangle(int x, int y, int w, int h, uint16_t color)` + +**Description:** + +Draws a rectangle outline. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `w`: Width in pixels. +- `h`: Height in pixels. +- `color`: 16-bit RGB565 outline color. + +### `void drawFilledRectangle(int x, int y, int w, int h, uint16_t color)` + +**Description:** + +Draws a filled rectangle. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `w`: Width in pixels. +- `h`: Height in pixels. +- `color`: 16-bit RGB565 fill color. + +### `void drawCircle(int x0, int y0, int r, uint16_t color)` + +**Description:** + +Draws a circle outline (midpoint algorithm). + +**Parameters:** + +- `x0`: Center X coordinate. +- `y0`: Center Y coordinate. +- `r`: Radius in pixels. +- `color`: 16-bit RGB565 outline color. + +### `void drawFilledCircle(int x0, int y0, int r, uint16_t color)` + +**Description:** + +Draws a filled circle (midpoint algorithm). + +**Parameters:** + +- `x0`: Center X coordinate. +- `y0`: Center Y coordinate. +- `r`: Radius in pixels. +- `color`: 16-bit RGB565 fill color. + +### `void drawBitmap(int x, int y, int w, int h, const uint8_t *bitmap, uint16_t color)` + +**Description:** + +Draws a 1bpp bitmap, rendering only non-zero pixels. + +**Parameters:** + +- `x`: Top-left X coordinate. +- `y`: Top-left Y coordinate. +- `w`: Width in pixels. +- `h`: Height in pixels. +- `bitmap`: Pointer to packed 1bpp row data (byte per row, MSB left). +- `color`: 16-bit RGB565 color for "on" pixels. diff --git a/api/generated/graphics/Camera2D.md b/api/generated/graphics/Camera2D.md index ff590f1..dd3e6a3 100644 --- a/api/generated/graphics/Camera2D.md +++ b/api/generated/graphics/Camera2D.md @@ -6,27 +6,47 @@ ## Description -2D camera system for managing viewports and scrolling. +2D camera for viewport management and smooth scrolling. + +Manages the viewable area of a scene, providing bounds clamping and +smooth follow-target behavior. The camera sits at a fixed position +until instructed to follow a target; its X/Y coordinates are negated +to become the Renderer's X/Y offset (so content scrolls "under" the camera). + +Bounds are enforced per-axis; if no bounds are set the camera is unclamped +on that axis. A target X or Y position outside bounds is clamped before +the camera moves. + +Camera2D does not own a Renderer. Call apply(renderer) to push the + camera transform after updating. + +::: tip +Camera2D does not own a Renderer. Call apply(renderer) to push the + camera transform after updating. +::: ## Methods -### `void apply(Renderer& renderer) const` +### `void setBounds(pixelroot32::math::Scalar minX, pixelroot32::math::Scalar maxX)` -**Description:** +### `void setVerticalBounds(pixelroot32::math::Scalar minY, pixelroot32::math::Scalar maxY)` -Applies the camera transformation to a renderer. +### `void setPosition(pixelroot32::math::Vector2 position)` -**Parameters:** +### `void followTarget(pixelroot32::math::Scalar targetX)` -- `renderer`: The renderer to apply the camera to. +### `void followTarget(pixelroot32::math::Vector2 target)` -### `void setViewportSize(int width, int height)` +### `pixelroot32::math::Scalar getX() const` -**Description:** +### `pixelroot32::math::Scalar getY() const` + +### `pixelroot32::math::Vector2 getPosition() const` + +### `void apply(Renderer& renderer) const` -Sets the viewport size (usually logical resolution). +**Description:** -**Parameters:** +Pushes the camera transform into the renderer (sets display offset). -- `width`: Viewport width. -- `height`: Viewport height. +### `void setViewportSize(int width, int height)` diff --git a/api/generated/graphics/DirtyGrid.md b/api/generated/graphics/DirtyGrid.md new file mode 100644 index 0000000..31e4b7b --- /dev/null +++ b/api/generated/graphics/DirtyGrid.md @@ -0,0 +1,140 @@ +# DirtyGrid + + + +**Source:** `DirtyGrid.h` + +## Description + +Two-buffer dirty cell grid (8×8 px cells) for selective framebuffer clears. + +Bit-packed storage: one bit per cell. `curr` accumulates marks for the current frame; +`swapAndClear()` moves that state into `prev` for the next frame's cleanup pass. + +## Methods + +### `void markCell(uint8_t cx, uint8_t cy)` + +**Description:** + +Marks a specific cell as dirty in the current frame. + +**Parameters:** + +- `cx`: Cell X coordinate. +- `cy`: Cell Y coordinate. + +### `void markRect(int x, int y, int w, int h)` + +**Description:** + +Marks cells intersected by a rectangle as dirty. + +**Parameters:** + +- `x`: Top-left X coordinate in pixels. +- `y`: Top-left Y coordinate in pixels. +- `w`: Width of the rectangle in pixels. +- `h`: Height of the rectangle in pixels. + +### `bool isPrevDirty(uint8_t cx, uint8_t cy) const` + +**Description:** + +Checks if a cell was marked dirty in the previous frame. + +**Parameters:** + +- `cx`: Cell X coordinate. +- `cy`: Cell Y coordinate. + +**Returns:** true if the cell was dirty in the previous frame, false otherwise. + +### `void swapAndClear()` + +**Description:** + +Swaps the current and previous buffers, clearing the new current buffer. + +### `void markAll()` + +**Description:** + +Marks all cells as dirty in the current frame. + +### `bool isFullDirty() const` + +**Description:** + +Checks if the entire grid is marked as fully dirty. + +**Returns:** true if the grid is fully dirty, false otherwise. + +### `void setFullDirty(bool v)` + +**Description:** + +Sets the full dirty state of the grid. + +**Parameters:** + +- `v`: The full dirty state to set. + +### `uint8_t getCols() const` + +**Description:** + +Gets the number of columns in the grid. + +**Returns:** The number of columns. + +### `uint8_t getRows() const` + +**Description:** + +Gets the number of rows in the grid. + +**Returns:** The number of rows. + +### `uint32_t countPrevMarkedCells() const` + +**Description:** + +Gets the number of cells marked in the previous frame. + +**Returns:** The number of cells marked in the previous frame. + +### `uint32_t countCurrMarkedCells() const` + +**Description:** + +Gets the number of cells marked in the current frame. + +**Returns:** The number of cells marked in the current frame. + +### `uint32_t totalCellCount() const` + +**Description:** + +Gets the total number of cells in the grid. + +**Returns:** The total number of cells in the grid. + +### `bool isCurrMarked(uint8_t cx, uint8_t cy) const` + +**Description:** + +True when the curr buffer has this cell marked for the current frame. + +**Parameters:** + +- `cx`: Cell X coordinate. +- `cy`: Cell Y coordinate. + +**Returns:** True when the curr buffer has this cell marked for the current frame, false otherwise. + +### `void clearFramebuffer8FromPrev(uint8_t* fb, int framebufferWidth, int framebufferHeight, uint8_t fillByte) const` + +**Parameters:** + +- `framebufferWidth`: Row stride in bytes (typically logical width). diff --git a/api/generated/graphics/DisplayType.md b/api/generated/graphics/DisplayType.md index 7232e5f..d6f1792 100644 --- a/api/generated/graphics/DisplayType.md +++ b/api/generated/graphics/DisplayType.md @@ -76,3 +76,11 @@ Gets the underlying DrawSurface implementation. **Description:** Initializes the underlying draw surface. + +### `std::unique_ptr releaseDrawSurface()` + +**Description:** + +Transfers ownership of the DrawSurface to the caller. + +**Returns:** A unique_ptr containing the DrawSurface. diff --git a/api/generated/graphics/FontManager.md b/api/generated/graphics/FontManager.md index 1a73180..9703af5 100644 --- a/api/generated/graphics/FontManager.md +++ b/api/generated/graphics/FontManager.md @@ -51,6 +51,20 @@ Calculates the width in pixels of a text string when rendered. **Returns:** Width in pixels, or 0 if font is invalid or text is empty. +### `static int16_t textWidth(const Font* font, std::string_view 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:** diff --git a/api/generated/graphics/LayerAttributes.md b/api/generated/graphics/LayerAttributes.md new file mode 100644 index 0000000..205b686 --- /dev/null +++ b/api/generated/graphics/LayerAttributes.md @@ -0,0 +1,67 @@ +# LayerAttributes + + + +**Source:** `Renderer.h` + +## Description + +All tiles with attributes in a single tilemap layer. + +LayerAttributes organizes all tile metadata for a single layer, providing +efficient lookup of attributes by tile position. Only tiles with non-empty +attributes are included, using a sparse representation to minimize memory. + +Layer Organization: +- Each layer in a scene has its own LayerAttributes structure +- Layers are typically organized as: Background, Midground, Foreground, etc. +- Layer name matches the name defined in the Tilemap Editor + +Memory Efficiency: +- Sparse representation: only tiles with attributes are stored +- All data stored in PROGMEM (flash memory) on ESP32 +- No RAM overhead for attribute storage +- Typical size: ~40 bytes per tile with attributes (depends on key/value lengths) + +Query Workflow: +1. Identify layer by index or name +2. Search tiles array for matching (x, y) position +3. If found, iterate through tile's attributes array +4. Compare keys using strcmp_P() for PROGMEM strings + +Example Usage: +```cpp +// Query attribute for tile at (10, 5) in layer 0 +const char* value = get_tile_attribute(0, 10, 5, "solid"); +if (value && strcmp_P(value, "true") == 0) { + // Tile is solid +} +``` + +Layer name is a PROGMEM string (use strcmp_P() for comparison) +Tiles array is sorted by position for potential binary search optimization +Maximum 65535 tiles with attributes per layer (uint16_t limit) + +TileAttributeEntry for individual tile attributes +TileAttribute for key-value pairs +Generated scene headers for query helper functions + +::: tip +Layer name is a PROGMEM string (use strcmp_P() for comparison) +::: + +::: tip +Tiles array is sorted by position for potential binary search optimization +::: + +::: tip +Maximum 65535 tiles with attributes per layer (uint16_t limit) +::: + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `char` | `const` | Layer name (PROGMEM string, e.g., "Background") | +| `num_tiles_with_attributes` | `uint16_t` | Number of tiles with attributes in this layer | +| `TileAttributeEntry` | `const` | PROGMEM array of tiles with attributes (sparse) | diff --git a/api/generated/graphics/LayerType.md b/api/generated/graphics/LayerType.md new file mode 100644 index 0000000..04bdfdc --- /dev/null +++ b/api/generated/graphics/LayerType.md @@ -0,0 +1,9 @@ +# LayerType + + + +**Source:** `Renderer.h` + +## Description + +Classifies draw layers for dirty-region marking (static backgrounds vs dynamic content). diff --git a/api/generated/graphics/MultiSprite.md b/api/generated/graphics/MultiSprite.md new file mode 100644 index 0000000..55e238a --- /dev/null +++ b/api/generated/graphics/MultiSprite.md @@ -0,0 +1,25 @@ +# MultiSprite + + + +**Source:** `Renderer.h` + +## Description + +Multi-layer, multi-color sprite built from 1bpp layers. + +A MultiSprite combines several SpriteLayer entries that share the same +width and height. Layers are drawn in array order, allowing more complex +visuals (highlights, outlines) while keeping each layer 1bpp. + +This design keeps compatibility with the existing Sprite format while +enabling NES/GameBoy-style layered sprites. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `width` | `uint8_t` | Sprite width in pixels (<= 16). | +| `height` | `uint8_t` | Sprite height in pixels. | +| `SpriteLayer` | `const` | Pointer to array of layers. | +| `layerCount` | `uint8_t` | Number of layers in the array. | diff --git a/api/generated/graphics/ParticleEmitter.md b/api/generated/graphics/ParticleEmitter.md index baa5944..72e6eaa 100644 --- a/api/generated/graphics/ParticleEmitter.md +++ b/api/generated/graphics/ParticleEmitter.md @@ -16,3 +16,30 @@ Uses a fixed-size array for particles to avoid dynamic allocation during runtime ## Inheritance [Entity](../core/Entity.md) → `ParticleEmitter` + +## Methods + +### `void burst(pixelroot32::math::Vector2 position, int count)` + +**Description:** + +Emits a burst of particles from a specific location. + +**Parameters:** + +- `position`: Emission origin. +- `count`: Number of particles to spawn. + +### `inline uint16_t lerpColor(uint16_t c1, uint16_t c2, pixelroot32::math::Scalar t)` + +**Description:** + +Linear interpolation between two 16-bit RGB565 colors. + +**Parameters:** + +- `c1`: Start color. +- `c2`: End color. +- `t`: Interpolation factor (0.0 - 1.0). + +**Returns:** Interpolated RGB565 color. diff --git a/api/generated/graphics/Renderer.md b/api/generated/graphics/Renderer.md index af1b309..e3c3e6d 100644 --- a/api/generated/graphics/Renderer.md +++ b/api/generated/graphics/Renderer.md @@ -26,6 +26,24 @@ Initializes the renderer and the underlying draw surface. Prepares the buffer for a new frame (clears screen). +### `void forceFullRedraw()` + +**Description:** + +Forces a full clear on the next beginFrame when `PIXELROOT32_ENABLE_DIRTY_REGIONS` is on (no-op otherwise). + +### `void resetFramebufferClearSuppressionAdvice()` + +### `void accumulateFramebufferClearSuppressionAdvice(bool skipClearDueToMemcpyRestore)` + +**Description:** + +Accumulate framebuffer clear suppression advice from a scene. + +**Parameters:** + +- `skipClearDueToMemcpyRestore`: True if the scene will restore framebuffer via memcpy + ### `void endFrame()` **Description:** @@ -40,6 +58,62 @@ Gets the underlying DrawSurface implementation. **Returns:** Reference to the DrawSurface. +### `void drawText(std::string_view text, int16_t x, int16_t y, Color color, uint8_t size)` + +**Description:** + +Draws a string of text using the default font. + +**Parameters:** + +- `text`: The text to draw. +- `x`: X coordinate. +- `y`: Y coordinate. +- `color`: Text color. +- `size`: Text size multiplier. + +### `void drawText(std::string_view text, int16_t x, int16_t y, Color color, uint8_t size, const Font* font)` + +**Description:** + +Draws a string of text using a specific font. + +**Parameters:** + +- `text`: The text to draw. +- `x`: X coordinate. +- `y`: Y coordinate. +- `color`: Text color. +- `size`: Text size multiplier. +- `font`: Pointer to the font to use. If nullptr, uses the default font. + +### `void drawTextCentered(std::string_view text, int16_t y, Color color, uint8_t size)` + +**Description:** + +Draws text centered horizontally at a given Y coordinate using the default font. + +**Parameters:** + +- `text`: The text to draw. +- `y`: Y coordinate. +- `color`: Text color. +- `size`: Text size. + +### `void drawTextCentered(std::string_view text, int16_t y, Color color, uint8_t size, const Font* font)` + +**Description:** + +Draws text centered horizontally at a given Y coordinate using a specific font. + +**Parameters:** + +- `text`: The text to draw. +- `y`: Y coordinate. +- `color`: Text color. +- `size`: Text size. +- `font`: Pointer to the font to use. If nullptr, uses the default font. + ### `void drawFilledCircle(int x, int y, int radius, Color color)` **Description:** @@ -160,6 +234,8 @@ Sets the logical display size (rendering resolution). - `w`: Logical width. - `h`: Logical height. +### `if constexpr(pixelroot32::platforms::config::EnableDirtyRegions)` + ### `int getLogicalWidth() const` ### `int getLogicalHeight() const` @@ -360,30 +436,6 @@ Draws a scaled multi-layer sprite. - `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:** @@ -401,3 +453,11 @@ Enables or disables ignoring global offsets for subsequent draw calls. Checks if offset bypass is currently enabled. **Returns:** True if offsets are being ignored. + +### `void setDebugDirtyCellOverlay(bool enabled)` + +**Description:** + +When PIXELROOT32_DEBUG_MODE is defined and enabled, outlines curr dirty cells before present. + +### `bool isDebugDirtyCellOverlayEnabled() const` diff --git a/api/generated/graphics/ResolutionPreset.md b/api/generated/graphics/ResolutionPreset.md new file mode 100644 index 0000000..eb39501 --- /dev/null +++ b/api/generated/graphics/ResolutionPreset.md @@ -0,0 +1,12 @@ +# ResolutionPreset + + + +**Source:** `ResolutionPresets.h` + +## Description + +Logical resolution choices for memory-constrained targets. + +Use create() to turn a preset into a DisplayConfig with the correct +logical and physical dimensions already set. diff --git a/api/generated/graphics/ResolutionPresets.md b/api/generated/graphics/ResolutionPresets.md new file mode 100644 index 0000000..0b0097b --- /dev/null +++ b/api/generated/graphics/ResolutionPresets.md @@ -0,0 +1,13 @@ +# ResolutionPresets + + + +**Source:** `ResolutionPresets.h` + +## Description + +Factory for creating DisplayConfig from resolution presets. + +Simplifies display setup on memory-constrained targets by providing +a single create() call that sets logical dimensions, physical dimensions, +and rotation together. diff --git a/api/generated/graphics/Sprite.md b/api/generated/graphics/Sprite.md new file mode 100644 index 0000000..94e21ac --- /dev/null +++ b/api/generated/graphics/Sprite.md @@ -0,0 +1,29 @@ +# Sprite + + + +**Source:** `Renderer.h` + +## Description + +Compact sprite descriptor for monochrome bitmapped sprites. + +Sprites are stored as an array of 16-bit rows. Each row packs horizontal +pixels into bits, using the following convention: + +- Bit 0 represents the leftmost pixel of the row. +- Bit (width - 1) represents the rightmost pixel of the row. + +Only the lowest (width) bits of each row are used. A bit value of 1 means +"pixel on", 0 means "pixel off". + +This format is optimized for small microcontroller displays (NES/GameBoy +style assets) and keeps data in flash-friendly, constexpr-friendly form. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `uint16_t` | `const` | Pointer to packed row data (size = height). | +| `width` | `uint8_t` | Sprite width in pixels (<= 16). | +| `height` | `uint8_t` | Sprite height in pixels. | diff --git a/api/generated/graphics/SpriteAnimation.md b/api/generated/graphics/SpriteAnimation.md new file mode 100644 index 0000000..7c90588 --- /dev/null +++ b/api/generated/graphics/SpriteAnimation.md @@ -0,0 +1,69 @@ +# SpriteAnimation + + + +**Source:** `Renderer.h` + +## Description + +Lightweight, step-based sprite animation controller. + +SpriteAnimation owns no memory. It references a compile-time array of +SpriteAnimationFrame entries and exposes simple integer-based control: + +- step(): advance to the next frame (wrapping at frameCount) +- reset(): go back to frame 0 +- getCurrentSprite()/getCurrentMultiSprite(): query current frame data + +The animation object never draws anything; Actors remain responsible for +asking which frame to render and calling Renderer accordingly. + +Initially this struct is used for "step-based" animation (advance once per +logical event, such as a horde movement). The design can be extended later +with time-based advancement without changing Renderer. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `SpriteAnimationFrame` | `const` | Pointer to immutable frame table. | +| `frameCount` | `uint8_t` | Number of frames in the table. | +| `current` | `uint8_t` | Current frame index [0, frameCount). | + +## Methods + +### `void reset()` + +**Description:** + +Reset the animation to the first frame. + +### `void step()` + +**Description:** + +Advance to the next frame in a loop (step-based advancement). + +### `const SpriteAnimationFrame& getCurrentFrame() const` + +**Description:** + +Get the current frame descriptor (may contain either type of sprite). + +**Returns:** Reference to the current frame descriptor. + +### `const Sprite* getCurrentSprite() const` + +**Description:** + +Convenience helper: returns the current simple Sprite, if any. + +**Returns:** Pointer to the current simple Sprite, or nullptr if none. + +### `const MultiSprite* getCurrentMultiSprite() const` + +**Description:** + +Convenience helper: returns the current MultiSprite, if any. + +**Returns:** Pointer to the current MultiSprite, or nullptr if none. diff --git a/api/generated/graphics/SpriteAnimationFrame.md b/api/generated/graphics/SpriteAnimationFrame.md new file mode 100644 index 0000000..318d8a0 --- /dev/null +++ b/api/generated/graphics/SpriteAnimationFrame.md @@ -0,0 +1,20 @@ +# SpriteAnimationFrame + + + +**Source:** `Renderer.h` + +## Description + +Single animation frame that can reference either a Sprite or a MultiSprite. + +Exactly one of the pointers is expected to be non-null for a valid frame. +This allows the same animation system to drive both simple and layered +sprites without exposing bit-level details to game code. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `Sprite` | `const` | Optional pointer to a simple 1bpp sprite frame. | +| `MultiSprite` | `const` | Optional pointer to a layered sprite frame. | diff --git a/api/generated/graphics/SpriteLayer.md b/api/generated/graphics/SpriteLayer.md new file mode 100644 index 0000000..61287af --- /dev/null +++ b/api/generated/graphics/SpriteLayer.md @@ -0,0 +1,19 @@ +# SpriteLayer + + + +**Source:** `Renderer.h` + +## Description + +Single monochrome layer used by layered sprites. + +Each layer uses the same width/height as its owning MultiSprite but can +provide its own bitmap and color. + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `uint16_t` | `const` | Pointer to packed row data for this layer. | +| `color` | `Color` | Color used for "on" pixels in this layer. | diff --git a/api/generated/graphics/TileAnimation.md b/api/generated/graphics/TileAnimation.md new file mode 100644 index 0000000..953fd5e --- /dev/null +++ b/api/generated/graphics/TileAnimation.md @@ -0,0 +1,42 @@ +# TileAnimation + + + +**Source:** `TileAnimation.h` + +## Description + +Single tile animation definition (compile-time constant). + +Defines a sequence of tile indices that form an animation loop. +All data stored in PROGMEM/flash to minimize RAM usage. + +Memory Layout: 4 bytes total (POD structure) +- baseTileIndex: First tile in animation sequence (0-255) +- frameCount: Number of frames in animation (1-255) +- frameDuration: Hold each animation cell for this many **60 Hz ticks** (1-255). + Logical tick rate is capped at 60/s (wall clock), independent of Engine loop speed. +- reserved: Padding for alignment (future use) + +Example: Water animation with 4 frames, 8 ticks per frame (~133 ms per cell at 60 Hz) +{ baseTileIndex: 42, frameCount: 4, frameDuration: 8, reserved: 0 } + +This structure is stored in PROGMEM (flash memory) +sizeof(TileAnimation) == 4 bytes + +::: tip +This structure is stored in PROGMEM (flash memory) +::: + +::: tip +sizeof(TileAnimation) == 4 bytes +::: + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `baseTileIndex` | `uint8_t` | First tile in sequence (e.g., 42) | +| `frameCount` | `uint8_t` | Number of frames (e.g., 4) | +| `frameDuration` | `uint8_t` | Ticks per frame (e.g., 8) | +| `reserved` | `uint8_t` | Padding for alignment/future use | diff --git a/api/generated/graphics/TileAnimationManager.md b/api/generated/graphics/TileAnimationManager.md new file mode 100644 index 0000000..e9ee273 --- /dev/null +++ b/api/generated/graphics/TileAnimationManager.md @@ -0,0 +1,77 @@ +# TileAnimationManager + + + +**Source:** `TileAnimation.h` + +## Description + +Manages tile animations for a tilemap. + +Provides O(1) frame resolution via lookup table. All animation +definitions stored in PROGMEM. Zero dynamic allocations. + +CRITICAL: Uses fixed-size arrays (no new/delete) to comply with +PixelRoot32's strict "zero allocation" policy for ESP32. + +## Methods + +### `uint8_t resolveFrame(uint8_t tileIndex)` + +**Description:** + +Resolve tile index to current animated frame. + +**Parameters:** + +- `tileIndex`: Base tile index from tilemap + +**Returns:** Current frame index (may be same as input if not animated) + +PERFORMANCE: O(1) array lookup, IRAM-friendly, no branches in hot path + +### `* Complexity: O(animations × frameCount) when at least one tick elapses; else O(1)` + +### `void step(unsigned long deltaTimeMs)` + +**Description:** + +Advance animations from elapsed wall time (60 Hz logical ticks max). +Uses high-resolution time between calls (micros); deltaTimeMs is a fallback when +the clock does not advance between calls (e.g. unit tests without real time). + +**Parameters:** + +- `deltaTimeMs`: Fallback elapsed time in milliseconds. + +### `void reset()` + +**Description:** + +Reset all animations to frame 0. + +### `uint32_t getVisualSignature() const` + +**Description:** + +Fingerprint of the current resolved animation state (O(animCount)). + +**Returns:** The visual signature of the current resolved animation state. + +Stable across `step(deltaTimeMs)` calls until a visible frame advances (same contract as +`resolveFrame` / lookup table). Used by scenes to skip draw+present when output +would be identical (e.g. ESP32 SPI budget). + +### `bool animatedTileAppearanceChanged(uint8_t storedTileIndex) const` + +**Description:** + +Checks if the resolved graphic for storedTileIndex changed since the snapshot taken at the +start of last step(); invalid indices return true so callers repaint conservatively. + +**Parameters:** + +- `storedTileIndex`: The tile index to check. + +**Returns:** True if the resolved graphic for storedTileIndex changed since the snapshot taken at the +start of last step(); invalid indices return true so callers repaint conservatively. diff --git a/api/generated/graphics/TileAttribute.md b/api/generated/graphics/TileAttribute.md new file mode 100644 index 0000000..95528be --- /dev/null +++ b/api/generated/graphics/TileAttribute.md @@ -0,0 +1,54 @@ +# TileAttribute + + + +**Source:** `Renderer.h` + +## Description + +Single attribute key-value pair for tile metadata. + +TileAttribute represents a single metadata entry attached to a tile, such as +collision properties, interaction types, or game-specific data. Both key and +value are stored as PROGMEM strings to minimize RAM usage on ESP32. + +Attributes are exported from the PixelRoot32 Tilemap Editor with final resolved +values (tileset defaults merged with instance overrides). The editor's two-level +attribute system (default + instance) is collapsed at export time, so runtime +code only sees the final merged result. + +Common Use Cases: +- Collision detection: {"solid", "true"}, {"walkable", "false"} +- Interaction: {"type", "door"}, {"interactable", "true"} +- Game logic: {"damage", "10"}, {"health", "50"} +- Tile behavior: {"animated", "true"}, {"speed", "2"} + +Memory Layout: +- Both pointers reference flash memory (PROGMEM/PIXELROOT32_SCENE_FLASH_ATTR) +- No RAM overhead for string storage +- Suitable for ESP32 with limited RAM + +All strings are null-terminated C strings stored in flash memory +Use strcmp_P() or similar PROGMEM-aware functions to compare keys +Values are always strings; convert to int/bool as needed in game code + +TileAttributeEntry for tile position association +LayerAttributes for layer-level attribute organization + +::: tip +All strings are null-terminated C strings stored in flash memory +::: + +::: tip +Use strcmp_P() or similar PROGMEM-aware functions to compare keys +::: + +::: tip +Values are always strings; convert to int/bool as needed in game code +::: + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `char` | `const` | Attribute key (PROGMEM string, e.g., "type", "solid") | diff --git a/api/generated/graphics/TileAttributeEntry.md b/api/generated/graphics/TileAttributeEntry.md new file mode 100644 index 0000000..d917027 --- /dev/null +++ b/api/generated/graphics/TileAttributeEntry.md @@ -0,0 +1,66 @@ +# TileAttributeEntry + + + +**Source:** `Renderer.h` + +## Description + +All attributes for a single tile at a specific position. + +TileAttributeEntry associates a tile position (x, y) with its metadata attributes. +Only tiles that have attributes are included in the exported data (sparse +representation), minimizing memory usage for large tilemaps. + +Attribute Resolution: +- Editor merges tileset default attributes with instance overrides +- Only final resolved attributes are exported (no inheritance logic at runtime) +- Empty tiles (no attributes) are not included in the exported data + +Position Encoding: +- X and Y are tile coordinates (not pixel coordinates) +- Coordinates are relative to the layer's origin (0, 0) +- Maximum coordinate value: 65535 (uint16_t range) + +Query Pattern: +```cpp +// Find tile at position (10, 5) +for (uint16_t i = 0; i < layer.num_tiles_with_attributes; i++) { + if (layer.tiles[i].x == 10 && layer.tiles[i].y == 5) { + // Found tile, search attributes + for (uint8_t j = 0; j < layer.tiles[i].num_attributes; j++) { + if (strcmp_P(layer.tiles[i].attributes[j].key, "solid") == 0) { + // Found "solid" attribute + } + } + } +} +``` + +Attributes array is stored in PROGMEM (flash memory) +Maximum 255 attributes per tile (uint8_t limit) +Use helper functions like get_tile_attribute() for easier queries + +TileAttribute for individual key-value pairs +LayerAttributes for layer-level organization + +::: tip +Attributes array is stored in PROGMEM (flash memory) +::: + +::: tip +Maximum 255 attributes per tile (uint8_t limit) +::: + +::: tip +Use helper functions like get_tile_attribute() for easier queries +::: + +## Properties + +| Name | Type | Description | +|------|------|-------------| +| `x` | `uint16_t` | Tile X coordinate in layer space | +| `y` | `uint16_t` | Tile Y coordinate in layer space | +| `num_attributes` | `uint8_t` | Number of attributes for this tile | +| `TileAttribute` | `const` | PROGMEM array of attribute key-value pairs | diff --git a/api/generated/graphics/TilemapSpriteDirtyMode.md b/api/generated/graphics/TilemapSpriteDirtyMode.md new file mode 100644 index 0000000..6a138b6 --- /dev/null +++ b/api/generated/graphics/TilemapSpriteDirtyMode.md @@ -0,0 +1,9 @@ +# TilemapSpriteDirtyMode + + + +**Source:** `Renderer.h` + +## Description + +Suppress per-sprite dirty marks while drawing tilemaps (static layer or selective animated marking). diff --git a/api/generated/graphics/TouchConfig.md b/api/generated/graphics/TouchConfig.md index 71249ad..8306f62 100644 --- a/api/generated/graphics/TouchConfig.md +++ b/api/generated/graphics/TouchConfig.md @@ -6,16 +6,17 @@ ## Description -Configuration for touch controller +Configuration for a touch controller (XPT2046 or GT911). -Add to DisplayConfig or use standalone. -Define one of TOUCH_DRIVER_XPT2046 or TOUCH_DRIVER_GT911 in build flags. +Set controller, communication parameters, and calibration transform. +Coordinate mapping: screenX = rawX * scaleX + offsetX (and same for Y). +Raw coordinates outside display bounds are clamped before mapping. ## Properties | Name | Type | Description | |------|------|-------------| -| `controller` | `TouchController` | Active controller | +| `controller` | `TouchController` | Active controller type. | ## Methods @@ -23,10 +24,23 @@ Define one of TOUCH_DRIVER_XPT2046 or TOUCH_DRIVER_GT911 in build flags. **Description:** -Constructor for XPT2046 +Factory: XPT2046 SPI configuration. + +**Parameters:** + +- `cs`: SPI chip-select pin. +- `irq`: Interrupt pin (255 = unused). + +**Returns:** Configured TouchConfig. ### `static TouchConfig createGT911(uint8_t irq = 4)` **Description:** -Constructor for GT911 +Factory: GT911 I2C configuration. + +**Parameters:** + +- `irq`: Interrupt pin (default 4). + +**Returns:** Configured TouchConfig. diff --git a/api/generated/graphics/TouchController.md b/api/generated/graphics/TouchController.md index 8987102..2caa399 100644 --- a/api/generated/graphics/TouchController.md +++ b/api/generated/graphics/TouchController.md @@ -6,4 +6,4 @@ ## Description -Supported touch controller types +Supported touch controller types. diff --git a/api/generated/graphics/UIAnchorLayout.md b/api/generated/graphics/UIAnchorLayout.md index d393784..91bfa28 100644 --- a/api/generated/graphics/UIAnchorLayout.md +++ b/api/generated/graphics/UIAnchorLayout.md @@ -41,3 +41,32 @@ Sets the screen size for anchor calculations. - `screenWidth`: Screen width in pixels. - `screenHeight`: Screen height in pixels. + +### `pixelroot32::math::Scalar getScreenWidth() const` + +**Description:** + +Gets the screen width. + +**Returns:** Screen width in pixels. + +### `pixelroot32::math::Scalar getScreenHeight() const` + +**Description:** + +Gets the screen height. + +**Returns:** Screen height in pixels. + +### `void calculateAnchorPosition(UIElement* element, Anchor anchor, pixelroot32::math::Scalar& outX, pixelroot32::math::Scalar& outY) const` + +**Description:** + +Calculates position for an element based on its anchor. + +**Parameters:** + +- `element`: The element to position. +- `anchor`: The anchor point. +- `outX`: Output parameter for calculated X position. +- `outY`: Output parameter for calculated Y position. diff --git a/api/generated/graphics/UIButton.md b/api/generated/graphics/UIButton.md index e2f5e24..de48508 100644 --- a/api/generated/graphics/UIButton.md +++ b/api/generated/graphics/UIButton.md @@ -49,6 +49,25 @@ Checks if the button is currently selected. **Returns:** true if selected. +### `bool isFocusable() const` + +**Description:** + +Checks if the element is focusable. + +**Returns:** true (Buttons are always focusable). + +### `void handleInput(const pixelroot32::input::InputManager& input)` + +**Description:** + +Handles input events. +Checks for touch events within bounds or confirmation buttons if selected. + +**Parameters:** + +- `input`: The input manager instance. + ### `void press()` **Description:** diff --git a/api/generated/graphics/UICheckBox.md b/api/generated/graphics/UICheckBox.md index 5fa7b2a..dc82468 100644 --- a/api/generated/graphics/UICheckBox.md +++ b/api/generated/graphics/UICheckBox.md @@ -19,6 +19,19 @@ Can trigger a callback function when its state changes. ## Methods +### `bool isPointInside(int px, int py) const` + +**Description:** + +Checks if a point is inside the checkbox's bounds. + +**Parameters:** + +- `px`: Point X coordinate. +- `py`: Point Y coordinate. + +**Returns:** true if point is inside. + ### `void setStyle(Color textCol, Color bgCol, bool drawBg = false)` **Description:** @@ -67,21 +80,27 @@ Checks if the checkbox is currently selected. **Returns:** true if selected. -### `void toggle()` +### `bool isFocusable() const` **Description:** -Toggles the checkbox state. +Checks if the element is focusable. -### `bool isPointInside(int px, int py) const` +**Returns:** true (Checkboxes are always focusable). + +### `void handleInput(const pixelroot32::input::InputManager& input)` **Description:** -Internal helper to check if a point is inside the checkbox's bounds. +Handles input events. +Checks for touch events within bounds or confirmation buttons if selected. **Parameters:** -- `px`: Point X coordinate. -- `py`: Point Y coordinate. +- `input`: The input manager instance. -**Returns:** true if point is inside. +### `void toggle()` + +**Description:** + +Toggles the checkbox state. diff --git a/api/generated/graphics/UIElement.md b/api/generated/graphics/UIElement.md index c426620..73b69d3 100644 --- a/api/generated/graphics/UIElement.md +++ b/api/generated/graphics/UIElement.md @@ -52,3 +52,26 @@ Sets whether the element is in a fixed position (HUD/Overlay). Checks if the element is in a fixed position. **Returns:** True if fixed position is enabled. + +### `virtual void setPosition(pixelroot32::math::Scalar newX, pixelroot32::math::Scalar newY)` + +**Description:** + +Sets the position of the element. + +**Parameters:** + +- `newX`: New X coordinate. +- `newY`: New Y coordinate. + +### `virtual void getPreferredSize(pixelroot32::math::Scalar& preferredWidth, pixelroot32::math::Scalar& preferredHeight) const` + +**Description:** + +Gets the preferred size of the element. +Used by layouts to determine how much space the element needs. + +**Parameters:** + +- `preferredWidth`: Output parameter for preferred width (or -1 if flexible). +- `preferredHeight`: Output parameter for preferred height (or -1 if flexible). diff --git a/api/generated/graphics/UIHorizontalLayout.md b/api/generated/graphics/UIHorizontalLayout.md index 103dee8..2a0435c 100644 --- a/api/generated/graphics/UIHorizontalLayout.md +++ b/api/generated/graphics/UIHorizontalLayout.md @@ -40,6 +40,42 @@ Enables or disables scrolling (alias for setScrollEnabled). - `enable`: True to enable scrolling. +### `void setViewportWidth(pixelroot32::math::Scalar w)` + +**Description:** + +Sets the viewport width (visible area). + +**Parameters:** + +- `w`: Viewport width in pixels. + +### `pixelroot32::math::Scalar getScrollOffset() const` + +**Description:** + +Gets the current scroll offset. + +**Returns:** Scroll offset in pixels. + +### `void setScrollOffset(pixelroot32::math::Scalar offset)` + +**Description:** + +Sets the scroll offset directly. + +**Parameters:** + +- `offset`: Scroll offset in pixels. + +### `pixelroot32::math::Scalar getContentWidth() const` + +**Description:** + +Gets the total content width. + +**Returns:** Content width in pixels. + ### `int getSelectedIndex() const` **Description:** @@ -66,6 +102,16 @@ Gets the selected element. **Returns:** Pointer to selected element, or nullptr if none selected. +### `void setScrollSpeed(pixelroot32::math::Scalar speed)` + +**Description:** + +Sets the scroll speed for smooth scrolling. + +**Parameters:** + +- `speed`: Pixels per millisecond. + ### `void setNavigationButtons(uint8_t leftButton, uint8_t rightButton)` **Description:** diff --git a/api/generated/graphics/UILabel.md b/api/generated/graphics/UILabel.md index f32146b..ec6d004 100644 --- a/api/generated/graphics/UILabel.md +++ b/api/generated/graphics/UILabel.md @@ -18,6 +18,17 @@ Displays a string of text on the screen. Auto-calculates its bounds based on tex ## Methods +### `void setText(std::string_view t)` + +**Description:** + +Updates the label's text. +Recalculates dimensions if text changes. + +**Parameters:** + +- `t`: New text. + ### `void setVisible(bool v)` **Description:** diff --git a/api/generated/graphics/UILayout.md b/api/generated/graphics/UILayout.md index 4ef2315..9b23bda 100644 --- a/api/generated/graphics/UILayout.md +++ b/api/generated/graphics/UILayout.md @@ -20,6 +20,31 @@ that can be added to scenes. ## Methods +### `: UIElement(x, y, w, h, UIElementType::LAYOUT)` + +**Description:** + +Constructs a new UILayout. + +**Parameters:** + +- `x`: X position of the layout container. +- `y`: Y position of the layout container. +- `w`: Width of the layout container. +- `h`: Height of the layout container. + +### `: UIElement(position, w, h, UIElementType::LAYOUT)` + +**Description:** + +Constructs a new UILayout. + +**Parameters:** + +- `position`: Position of the layout container. +- `w`: Width of the layout container. +- `h`: Height of the layout container. + ### `virtual void addElement(UIElement* element)` **Description:** @@ -47,6 +72,52 @@ Removes a UI element from the layout. Recalculates positions of all elements in the layout. Should be called automatically when elements are added/removed. +### `virtual void handleInput(const pixelroot32::input::InputManager& input)` + +**Description:** + +Handles input for layout navigation (scroll, selection, etc.). + +**Parameters:** + +- `input`: Reference to the InputManager. + +### `void setPadding(pixelroot32::math::Scalar p)` + +**Description:** + +Sets the padding (internal spacing) of the layout. + +**Parameters:** + +- `p`: Padding value in pixels. + +### `pixelroot32::math::Scalar getPadding() const` + +**Description:** + +Gets the current padding. + +**Returns:** Padding value in pixels. + +### `void setSpacing(pixelroot32::math::Scalar s)` + +**Description:** + +Sets the spacing between elements. + +**Parameters:** + +- `s`: Spacing value in pixels. + +### `pixelroot32::math::Scalar getSpacing() const` + +**Description:** + +Gets the current spacing. + +**Returns:** Spacing value in pixels. + ### `size_t getElementCount() const` **Description:** diff --git a/api/generated/graphics/UIManager.md b/api/generated/graphics/UIManager.md index b00e300..b4ab9a9 100644 --- a/api/generated/graphics/UIManager.md +++ b/api/generated/graphics/UIManager.md @@ -64,6 +64,10 @@ Register an element for touch hit-testing and processEvents. ### `void clear()` +### `uint8_t processEvents(pixelroot32::input::TouchEvent* events, uint8_t count)` + +### `bool processEvent(pixelroot32::input::TouchEvent& event)` + ### `UITouchWidget* getActiveWidget() const` ### `UITouchWidget* getHoverWidget() const` @@ -82,4 +86,4 @@ Register an element for touch hit-testing and processEvents. ### `void update(unsigned long deltaTime)` -### `int8_t findFreeSlot() const` +### `void draw(pixelroot32::graphics::Renderer& renderer)` diff --git a/api/generated/graphics/UIPaddingContainer.md b/api/generated/graphics/UIPaddingContainer.md index c60305d..d2607e8 100644 --- a/api/generated/graphics/UIPaddingContainer.md +++ b/api/generated/graphics/UIPaddingContainer.md @@ -38,6 +38,61 @@ Gets the child element. **Returns:** Pointer to the child element, or nullptr if none set. +### `void setPadding(pixelroot32::math::Scalar p)` + +**Description:** + +Sets uniform padding on all sides. + +**Parameters:** + +- `p`: Padding value in pixels. + +### `void setPadding(pixelroot32::math::Scalar left, pixelroot32::math::Scalar right, pixelroot32::math::Scalar top, pixelroot32::math::Scalar bottom)` + +**Description:** + +Sets asymmetric padding. + +**Parameters:** + +- `left`: Left padding in pixels. +- `right`: Right padding in pixels. +- `top`: Top padding in pixels. +- `bottom`: Bottom padding in pixels. + +### `pixelroot32::math::Scalar getPaddingLeft() const` + +**Description:** + +Gets the left padding. + +**Returns:** Left padding in pixels. + +### `pixelroot32::math::Scalar getPaddingRight() const` + +**Description:** + +Gets the right padding. + +**Returns:** Right padding in pixels. + +### `pixelroot32::math::Scalar getPaddingTop() const` + +**Description:** + +Gets the top padding. + +**Returns:** Top padding in pixels. + +### `pixelroot32::math::Scalar getPaddingBottom() const` + +**Description:** + +Gets the bottom padding. + +**Returns:** Bottom padding in pixels. + ### `void updateChildPosition()` **Description:** diff --git a/api/generated/graphics/UIPanel.md b/api/generated/graphics/UIPanel.md index f4d7e77..e5ad321 100644 --- a/api/generated/graphics/UIPanel.md +++ b/api/generated/graphics/UIPanel.md @@ -38,6 +38,42 @@ Gets the child element. **Returns:** Pointer to the child element, or nullptr if none set. +### `void setBackgroundColor(pixelroot32::graphics::Color color)` + +**Description:** + +Sets the background color. + +**Parameters:** + +- `color`: Background color. + +### `pixelroot32::graphics::Color getBackgroundColor() const` + +**Description:** + +Gets the background color. + +**Returns:** Background color. + +### `void setBorderColor(pixelroot32::graphics::Color color)` + +**Description:** + +Sets the border color. + +**Parameters:** + +- `color`: Border color. + +### `pixelroot32::graphics::Color getBorderColor() const` + +**Description:** + +Gets the border color. + +**Returns:** Border color. + ### `void setBorderWidth(uint8_t width)` **Description:** diff --git a/api/generated/graphics/UITouchButton.md b/api/generated/graphics/UITouchButton.md index 6f41729..703736f 100644 --- a/api/generated/graphics/UITouchButton.md +++ b/api/generated/graphics/UITouchButton.md @@ -21,6 +21,24 @@ Events: OnDown, OnUp, OnClick ## Methods +### `void setLabel(std::string_view label)` + +**Description:** + +Set the button label + +**Parameters:** + +- `label`: String view to the label (no allocation) + +### `std::string_view getLabel() const` + +**Description:** + +Get the current label + +**Returns:** String view to the label (no allocation) + ### `void setColors(Color normal, Color pressed, Color disabled)` **Description:** @@ -179,6 +197,24 @@ Auto-size button width to fit the current label - `padding`: Extra pixels to add around text (default: 4) +### `void handleTouchDown(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Handle touch down event + +### `void handleTouchUp(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Handle touch up event + +### `void handleClick(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Handle click event + ### `void setActive()` **Description:** diff --git a/api/generated/graphics/UITouchCheckbox.md b/api/generated/graphics/UITouchCheckbox.md index 6addc0a..6571af3 100644 --- a/api/generated/graphics/UITouchCheckbox.md +++ b/api/generated/graphics/UITouchCheckbox.md @@ -39,6 +39,24 @@ Get current font size **Returns:** Font size multiplier +### `void setLabel(std::string_view label)` + +**Description:** + +Set the checkbox label + +**Parameters:** + +- `label`: String view to the label (no allocation) + +### `std::string_view getLabel() const` + +**Description:** + +Get the current label + +**Returns:** String view to the label (no allocation) + ### `void setChecked(bool checked)` **Description:** @@ -139,6 +157,18 @@ Get the OnChanged callback Reset checkbox state +### `void handleTouchDown(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Handle touch down event + +### `void handleTouchUp(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Handle touch up event (triggers toggle if within bounds) + ### `void setActive()` **Description:** diff --git a/api/generated/graphics/UITouchElement.md b/api/generated/graphics/UITouchElement.md index 3e1d52e..fe13f39 100644 --- a/api/generated/graphics/UITouchElement.md +++ b/api/generated/graphics/UITouchElement.md @@ -22,6 +22,14 @@ Memory: Owns widget data inline - no external allocation needed. ## Methods +### `virtual bool processEvent(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Process a touch event (polymorphic dispatch from UIManager). + +**Returns:** true if this element handled the event for UI consumption semantics + ### `virtual uint8_t getWidgetState() const` **Description:** @@ -74,6 +82,17 @@ Set widget enabled state - `enabled`: True to enable +### `void setPosition(pixelroot32::math::Scalar newX, pixelroot32::math::Scalar newY)` + +**Description:** + +Override setPosition to sync widgetData_ with Entity position + +**Parameters:** + +- `newX`: New X coordinate +- `newY`: New Y coordinate + ### `int16_t getX() const` **Description:** diff --git a/api/generated/graphics/UITouchSlider.md b/api/generated/graphics/UITouchSlider.md index abadd47..827696b 100644 --- a/api/generated/graphics/UITouchSlider.md +++ b/api/generated/graphics/UITouchSlider.md @@ -165,6 +165,30 @@ Check if value changed since last frame Reset slider state +### `bool handleTouchDown(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Handle touch down event + +**Returns:** true if handled + +### `bool handleDragMove(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Handle drag move event + +**Returns:** true if handled + +### `bool handleTouchUp(const pixelroot32::input::TouchEvent& event)` + +**Description:** + +Handle touch up event + +**Returns:** true if handled + ### `void updateValueFromPosition(int16_t xPos)` **Description:** diff --git a/api/generated/graphics/UITouchWidget.md b/api/generated/graphics/UITouchWidget.md index 075e7e1..c52c9ee 100644 --- a/api/generated/graphics/UITouchWidget.md +++ b/api/generated/graphics/UITouchWidget.md @@ -23,6 +23,16 @@ Base touch widget structure ## Methods +### `: type(UIWidgetType::Generic)` + +**Description:** + +Default constructor - creates empty widget + +### `, state(UIWidgetState::Idle)` + +### `, flags(UIWidgetFlags::None)` + ### `, id(0)` ### `, x(0)` @@ -33,6 +43,12 @@ Base touch widget structure ### `, height(0)` +### `: type(widgetType)` + +**Description:** + +Construct touch widget with all fields + ### `, id(widgetId)` ### `, x(xPos)` diff --git a/api/generated/graphics/UIVerticalLayout.md b/api/generated/graphics/UIVerticalLayout.md index 2862169..1d2c663 100644 --- a/api/generated/graphics/UIVerticalLayout.md +++ b/api/generated/graphics/UIVerticalLayout.md @@ -40,6 +40,42 @@ Enables or disables scrolling (alias for setScrollEnabled). - `enable`: True to enable scrolling. +### `void setViewportHeight(pixelroot32::math::Scalar h)` + +**Description:** + +Sets the viewport height (visible area). + +**Parameters:** + +- `h`: Viewport height in pixels. + +### `pixelroot32::math::Scalar getScrollOffset() const` + +**Description:** + +Gets the current scroll offset. + +**Returns:** Scroll offset in pixels. + +### `void setScrollOffset(pixelroot32::math::Scalar offset)` + +**Description:** + +Sets the scroll offset directly. + +**Parameters:** + +- `offset`: Scroll offset in pixels. + +### `pixelroot32::math::Scalar getContentHeight() const` + +**Description:** + +Gets the total content height. + +**Returns:** Content height in pixels. + ### `int getSelectedIndex() const` **Description:** @@ -66,6 +102,16 @@ Gets the selected element. **Returns:** Pointer to selected element, or nullptr if none selected. +### `void setScrollSpeed(pixelroot32::math::Scalar speed)` + +**Description:** + +Sets the scroll speed for smooth scrolling. + +**Parameters:** + +- `speed`: Pixels per millisecond. + ### `void setNavigationButtons(uint8_t upButton, uint8_t downButton)` **Description:** diff --git a/api/generated/index.md b/api/generated/index.md index ce0fe11..d1ae12a 100644 --- a/api/generated/index.md +++ b/api/generated/index.md @@ -8,16 +8,17 @@ Auto-generated API documentation from C++ header files. - [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. +- [AudioCommandQueue](./audio/AudioCommandQueue.md) — Multi-Producer Single-Consumer (MPSC) 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. +- [AudioEngine](./audio/AudioEngine.md) — Facade class for the NES-style 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. +- [DefaultAudioScheduler](./audio/DefaultAudioScheduler.md) — Backend-driven scheduler used on platforms without a dedicated audio task. +- [EnvelopeState](./audio/EnvelopeState.md) — Holds ADSR envelope state for a single voice. - [InstrumentPreset](./audio/InstrumentPreset.md) — Defines instrument characteristics for playback. +- [LfoState](./audio/LfoState.md) — Holds LFO (Low-Frequency Oscillator) state for pitch or volume modulation. - [MusicNote](./audio/MusicNote.md) — Represents a single note in a melody. -- [MusicPlayer](./audio/MusicPlayer.md) — Simple sequencer to play MusicTracks. +- [MusicPlayer](./audio/MusicPlayer.md) — Simple sequencer to play MusicTracks using the AudioEngine. ## Core @@ -49,22 +50,37 @@ Auto-generated API documentation from C++ header files. ## 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. +- [BaseDrawSurface](./graphics/BaseDrawSurface.md) — Optional base class for DrawSurface implementations providing default primitive rendering. +- [Camera2D](./graphics/Camera2D.md) — 2D camera for viewport management and smooth scrolling. +- [DirtyGrid](./graphics/DirtyGrid.md) — Two-buffer dirty cell grid (8×8 px cells) for selective framebuffer clears. - [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. +- [LayerAttributes](./graphics/LayerAttributes.md) — All tiles with attributes in a single tilemap layer. +- [LayerType](./graphics/LayerType.md) — Classifies draw layers for dirty-region marking (static backgrounds vs dynamic content). +- [MultiSprite](./graphics/MultiSprite.md) — Multi-layer, multi-color sprite built from 1bpp layers. - [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. +- [ResolutionPreset](./graphics/ResolutionPreset.md) — Logical resolution choices for memory-constrained targets. +- [ResolutionPresets](./graphics/ResolutionPresets.md) — Factory for creating DisplayConfig from resolution presets. - [ScrollBehavior](./graphics/ScrollBehavior.md) — Defines how scrolling behaves in layouts. +- [Sprite](./graphics/Sprite.md) — Compact sprite descriptor for monochrome bitmapped sprites. - [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. +- [SpriteAnimation](./graphics/SpriteAnimation.md) — Lightweight, step-based sprite animation controller. +- [SpriteAnimationFrame](./graphics/SpriteAnimationFrame.md) — Single animation frame that can reference either a Sprite or a MultiSprite. +- [SpriteLayer](./graphics/SpriteLayer.md) — Single monochrome layer used by layered sprites. +- [TileAnimation](./graphics/TileAnimation.md) — Single tile animation definition (compile-time constant). +- [TileAnimationManager](./graphics/TileAnimationManager.md) — Manages tile animations for a tilemap. +- [TileAttribute](./graphics/TileAttribute.md) — Single attribute key-value pair for tile metadata. +- [TileAttributeEntry](./graphics/TileAttributeEntry.md) — All attributes for a single tile at a specific position. - [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 +- [TilemapSpriteDirtyMode](./graphics/TilemapSpriteDirtyMode.md) — Suppress per-sprite dirty marks while drawing tilemaps (static layer or selective animated marking). +- [TouchConfig](./graphics/TouchConfig.md) — Configuration for a touch controller (XPT2046 or GT911). +- [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. diff --git a/api/generated/input/ActorTouchController.md b/api/generated/input/ActorTouchController.md index 814f8a5..7e22cae 100644 --- a/api/generated/input/ActorTouchController.md +++ b/api/generated/input/ActorTouchController.md @@ -34,6 +34,21 @@ for (uint8_t i = 0; i < count; i++) { Clear registered actors and drag state (e.g. scene reset / arena recycle). +### `bool registerActor(pixelroot32::core::Actor* actor)` + +**Description:** + +Register an actor to the drag pool + +**Parameters:** + +- `actor`: Pointer to the actor to register + +**Returns:** true if registration succeeded, false if pool is full + +Note: Does not check for duplicates - caller should ensure +the actor is not already registered. + ### `void setTouchHitSlop(int16_t expandPixels)` **Description:** @@ -47,6 +62,18 @@ Expand hit-test rectangles by this many pixels on each side (0 = exact hitbox on Current hit slop in pixels (per side). +### `bool unregisterActor(pixelroot32::core::Actor* actor)` + +**Description:** + +Unregister an actor from the drag pool + +**Parameters:** + +- `actor`: Pointer to the actor to unregister + +**Returns:** true if actor was found and removed, false if not found + ### `void handleTouch(const TouchEvent& event)` **Description:** @@ -70,6 +97,33 @@ Check if currently dragging an actor **Returns:** true if a drag operation is in progress +### `pixelroot32::core::Actor* getDraggedActor() const` + +**Description:** + +Get the currently dragged actor + +**Returns:** Pointer to the dragged actor, nullptr if not dragging + +### `pixelroot32::core::Actor* hitTest(int16_t x, int16_t y)` + +**Description:** + +Perform hit test to find actor at touch position + +**Parameters:** + +- `x`: X coordinate +- `y`: Y coordinate + +**Returns:** Pointer to hit actor, nullptr if none hit + +### `bool pointInRect(int16_t px, int16_t py, const pixelroot32::core::Rect& rect, int16_t slop = 0)` + +**Description:** + +Check if a point is inside a rectangle, optionally expanded by @a slop pixels per side. + ### `void onTouchDown(const TouchEvent& event)` **Description:** diff --git a/api/generated/input/InputConfig.md b/api/generated/input/InputConfig.md index 3ae042e..684b948 100644 --- a/api/generated/input/InputConfig.md +++ b/api/generated/input/InputConfig.md @@ -8,15 +8,21 @@ Configuration structure for the InputManager. -Defines the mapping between logical inputs and physical pins (ESP32) -or keyboard keys (Native/SDL2). +Maps logical inputs to physical GPIO pins (ESP32) or keyboard scancodes (Native/SDL2). +Uses fixed-size std::array instead of std::vector for zero-allocation and +deterministic memory usage on ESP32. -Uses variadic arguments to allow flexible configuration of input count. +Usage: +// ESP32 +pr32::input::InputConfig config(BTN_UP, BTN_DOWN, BTN_LEFT, BTN_RIGHT); -## Properties +// Native (SDL2) +pr32::input::InputConfig config(SDL_SCANCODE_UP, SDL_SCANCODE_DOWN); -| 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. | +// Empty +pr32::input::InputConfig config{}; +InputManager + +## Methods + +### `if constexpr(sizeof...(args) > 0)` diff --git a/api/generated/input/TouchEvent.md b/api/generated/input/TouchEvent.md index 914397a..9cf8833 100644 --- a/api/generated/input/TouchEvent.md +++ b/api/generated/input/TouchEvent.md @@ -37,12 +37,14 @@ Invariants: ## Methods -### `, x(0)` +### `: timestamp(0)` **Description:** Default constructor - creates empty event +### `, x(0)` + ### `, y(0)` ### `, type(0)` @@ -53,7 +55,7 @@ Default constructor - creates empty event ### `, _padding(0)` -### `, x(xPos)` +### `: timestamp(ts)` **Description:** @@ -68,6 +70,8 @@ Construct touch event with all fields - `ts`: Timestamp in ms - `eventFlags`: Event flags +### `, x(xPos)` + ### `, y(yPos)` ### `, type(static_cast(eventType))` diff --git a/api/generated/input/TouchPoint.md b/api/generated/input/TouchPoint.md index 0024ffc..ba4f792 100644 --- a/api/generated/input/TouchPoint.md +++ b/api/generated/input/TouchPoint.md @@ -29,6 +29,26 @@ Invariants: ## Methods +### `: x(0), y(0), pressed(false), id(0), ts(0)` + +**Description:** + +Default constructor - creates empty/invalid touch point + +### `: x(xPos), y(yPos), pressed(isPressed), id(touchId), ts(timestamp)` + +**Description:** + +Construct a touch point with all fields + +**Parameters:** + +- `xPos`: X coordinate +- `yPos`: Y coordinate +- `isPressed`: Touch state +- `touchId`: Touch identifier +- `timestamp`: Timestamp in ms + ### `bool isValid(int16_t maxX, int16_t maxY) const` **Description:** diff --git a/api/generated/input/TouchStateData.md b/api/generated/input/TouchStateData.md index 7c3a62a..c2a55f4 100644 --- a/api/generated/input/TouchStateData.md +++ b/api/generated/input/TouchStateData.md @@ -23,6 +23,8 @@ Per-touch-ID state tracking ## Methods +### `: state(TouchState::Idle)` + ### `, pressTime(0)` ### `, pressX(0)` diff --git a/api/generated/math/Fixed16.md b/api/generated/math/Fixed16.md index 598c94f..07dfb91 100644 --- a/api/generated/math/Fixed16.md +++ b/api/generated/math/Fixed16.md @@ -13,13 +13,13 @@ Designed for platforms without FPU (ESP32-C3, C2, C6). ## Methods -### `constexpr Fixed16()` +### `constexpr Fixed16() : raw(0)` **Description:** Default constructor (initializes to 0). -### `constexpr explicit Fixed16(int32_t rawValue, bool /*isRaw*/)` +### `constexpr explicit Fixed16(int32_t rawValue, bool /*isRaw*/) : raw(rawValue)` **Description:** @@ -30,7 +30,7 @@ Raw value constructor. - `rawValue`: The raw 32-bit representation. - `isRaw`: Dummy parameter to distinguish from integer constructor. -### `constexpr Fixed16(int v)` +### `constexpr Fixed16(int v) : raw(v << FRACTIONAL_BITS)` **Description:** @@ -40,7 +40,7 @@ Construct from integer. - `v`: The integer value. -### `constexpr Fixed16(float v)` +### `constexpr Fixed16(float v) : raw(static_cast(v * ONE + (v >= 0 ? 0.5f : -0.5f)))` **Description:** @@ -50,7 +50,7 @@ Construct from float. - `v`: The float value. -### `constexpr Fixed16(double v)` +### `constexpr Fixed16(double v) : raw(static_cast(v * ONE + (v >= 0 ? 0.5 : -0.5)))` **Description:** diff --git a/api/generated/math/Vector2.md b/api/generated/math/Vector2.md index 855de00..6525f49 100644 --- a/api/generated/math/Vector2.md +++ b/api/generated/math/Vector2.md @@ -12,13 +12,13 @@ Automatically adapts to the architecture's FPU availability. ## Methods -### `constexpr Vector2()` +### `constexpr Vector2() : x(toScalar(0)), y(toScalar(0))` **Description:** Default constructor, initializes to (0, 0). -### `constexpr Vector2(Scalar _x, Scalar _y)` +### `constexpr Vector2(Scalar _x, Scalar _y) : x(_x), y(_y)` **Description:** @@ -29,7 +29,7 @@ Constructor with given x and y components. - `_x`: X component. - `_y`: Y component. -### `constexpr Vector2(const Vector2& other)` +### `constexpr Vector2(const Vector2& other) : x(other.x), y(other.y)` **Description:** @@ -39,7 +39,7 @@ Copy constructor. - `other`: The other vector to copy from. -### `constexpr Vector2(int _x, int _y)` +### `constexpr Vector2(int _x, int _y) : x(toScalar(_x)), y(toScalar(_y))` **Description:** diff --git a/api/generated/physics/CollisionSystem.md b/api/generated/physics/CollisionSystem.md index 14b9ade..ce9c935 100644 --- a/api/generated/physics/CollisionSystem.md +++ b/api/generated/physics/CollisionSystem.md @@ -10,6 +10,26 @@ Manages physics simulation and collision detection for all actors. ## Methods +### `void addEntity(pixelroot32::core::Entity* e)` + +**Description:** + +Adds an entity to the collision system. + +**Parameters:** + +- `e`: Pointer to the entity to add. + +### `void removeEntity(pixelroot32::core::Entity* e)` + +**Description:** + +Removes an entity from the collision system. + +**Parameters:** + +- `e`: Pointer to the entity to remove. + ### `void update()` **Description:** @@ -59,3 +79,15 @@ Gets the total number of registered entities. **Description:** Clears the collision system state. + +### `bool needsCCD(pixelroot32::core::PhysicsActor* body) const` + +**Description:** + +Checks if a body requires continuous collision detection (CCD). + +**Parameters:** + +- `body`: The physics actor to check. + +**Returns:** True if CCD is required. diff --git a/api/generated/physics/RigidActor.md b/api/generated/physics/RigidActor.md index 0f022c7..51c94f2 100644 --- a/api/generated/physics/RigidActor.md +++ b/api/generated/physics/RigidActor.md @@ -16,3 +16,25 @@ dynamic objects that should behave naturally, like falling crates or debris. ## Inheritance [PhysicsActor](../core/PhysicsActor.md) → `RigidActor` + +## Methods + +### `void applyForce(const pixelroot32::math::Vector2& f)` + +**Description:** + +Applies a force to the center of mass. + +**Parameters:** + +- `f`: Force vector. + +### `void applyImpulse(const pixelroot32::math::Vector2& j)` + +**Description:** + +Applies an instantaneous impulse (velocity change). + +**Parameters:** + +- `j`: Impulse vector. diff --git a/api/generated/physics/Segment.md b/api/generated/physics/Segment.md index f6baf98..4bdeac0 100644 --- a/api/generated/physics/Segment.md +++ b/api/generated/physics/Segment.md @@ -31,3 +31,29 @@ Checks intersection between two circles. - `b`: Second circle. **Returns:** True if circles intersect. + +### `bool intersects(const Circle& c, const pixelroot32::core::Rect& r)` + +**Description:** + +Checks intersection between a circle and a rectangle. + +**Parameters:** + +- `c`: The circle. +- `r`: The rectangle. + +**Returns:** True if they intersect. + +### `bool intersects(const Segment& s, const pixelroot32::core::Rect& r)` + +**Description:** + +Checks intersection between a line segment and a rectangle. + +**Parameters:** + +- `s`: The line segment. +- `r`: The rectangle. + +**Returns:** True if they intersect. diff --git a/api/generated/physics/SpatialGrid.md b/api/generated/physics/SpatialGrid.md index 52016a1..4ee90bd 100644 --- a/api/generated/physics/SpatialGrid.md +++ b/api/generated/physics/SpatialGrid.md @@ -31,3 +31,37 @@ Clears all entities (static and dynamic) from the grid. **Description:** Marks the static layer as dirty, requiring a rebuild. + +### `void rebuildStaticIfNeeded(pixelroot32::core::Entity* const* entities, uint16_t entityCount)` + +**Description:** + +Rebuilds the static layer if marked dirty. + +**Parameters:** + +- `entities`: Pointer to array of entities. +- `entityCount`: Total number of entities. + +### `void insertDynamic(pixelroot32::core::Actor* actor)` + +**Description:** + +Inserts a dynamic actor into the grid. + +**Parameters:** + +- `actor`: The actor to insert. + +### `void getPotentialColliders(pixelroot32::core::Actor* actor, pixelroot32::core::Actor** outArray, int& count, int maxCount)` + +**Description:** + +Gets potential colliders for a given actor from the grid. + +**Parameters:** + +- `actor`: The actor to query for. +- `outArray`: Output array to store potential colliders. +- `count`: Reference to store the number of colliders found. +- `maxCount`: Maximum number of colliders to return. diff --git a/api/generated/physics/TileCollisionBuilder.md b/api/generated/physics/TileCollisionBuilder.md index 1a1eef2..6ea869e 100644 --- a/api/generated/physics/TileCollisionBuilder.md +++ b/api/generated/physics/TileCollisionBuilder.md @@ -50,3 +50,41 @@ Gets the number of entities created by this builder. **Description:** Resets the entity counter. + +### `pixelroot32::core::PhysicsActor* createTileBody(uint16_t x, uint16_t y, TileFlags flags)` + +**Description:** + +Creates a single tile physics body. + +**Parameters:** + +- `x`: Tile X coordinate +- `y`: Tile Y coordinate +- `flags`: TileFlags for this tile + +**Returns:** Pointer to created actor, or nullptr if entity limit reached + +### `void configureTileBody(pixelroot32::core::PhysicsActor* body, TileFlags flags)` + +**Description:** + +Configures a tile body based on its flags. + +**Parameters:** + +- `body`: The physics actor to configure +- `flags`: TileFlags for configuration + +### `pixelroot32::math::Vector2 tileToWorldPosition(uint16_t x, uint16_t y) const` + +**Description:** + +Converts tile coordinates to world position. + +**Parameters:** + +- `x`: Tile X coordinate +- `y`: Tile Y coordinate + +**Returns:** World position vector diff --git a/api/generated/physics/TileCollisionBuilderConfig.md b/api/generated/physics/TileCollisionBuilderConfig.md index c7dcb19..a0cfbf4 100644 --- a/api/generated/physics/TileCollisionBuilderConfig.md +++ b/api/generated/physics/TileCollisionBuilderConfig.md @@ -15,3 +15,7 @@ Configuration for tile collision building. | `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) | + +## Methods + +### `: tileWidth(w), tileHeight(h), maxEntities(pixelroot32::platforms::config::MaxEntities / 2)` diff --git a/api/generated/physics/TileConsumptionHelper.md b/api/generated/physics/TileConsumptionHelper.md index 7f767b9..8ce4a8e 100644 --- a/api/generated/physics/TileConsumptionHelper.md +++ b/api/generated/physics/TileConsumptionHelper.md @@ -21,6 +21,33 @@ bool consumed = helper.consumeTile(tileActor, tileX, tileY); ## Methods +### `bool consumeTile(pixelroot32::core::Actor* tileActor, uint16_t tileX, uint16_t tileY)` + +**Description:** + +Consumes a tile by removing its physics body and updating visuals. + +**Parameters:** + +- `tileActor`: Pointer to the tile physics actor to consume +- `tileX`: Tile X coordinate (from unpacked userData) +- `tileY`: Tile Y coordinate (from unpacked userData) + +**Returns:** true if tile was successfully consumed, false if already consumed or invalid + +### `bool consumeTileFromUserData(pixelroot32::core::Actor* tileActor, uintptr_t packedUserData)` + +**Description:** + +Consumes a tile using packed userData from collision callback. + +**Parameters:** + +- `tileActor`: Pointer to the tile physics actor +- `packedUserData`: Packed userData from tileActor->getUserData() + +**Returns:** true if tile was successfully consumed, false otherwise + ### `bool isTileConsumed(uint16_t tileX, uint16_t tileY) const` **Description:** diff --git a/api/generated/platforms/MockAudioBackend.md b/api/generated/platforms/MockAudioBackend.md index 3011ebe..f94b62d 100644 --- a/api/generated/platforms/MockAudioBackend.md +++ b/api/generated/platforms/MockAudioBackend.md @@ -19,8 +19,14 @@ for verifying AudioScheduler and AudioEngine interactions. ## Methods +### `void init(AudioEngine* engine, const platforms::PlatformCapabilities& caps)` + +### `int getSampleRate() const` + ### `bool wasInitCalled() const` ### `AudioEngine* getEngine() const` +### `const platforms::PlatformCapabilities& getCaps() const` + ### `void reset()` diff --git a/api/graphics.md b/api/graphics.md index c52b416..ee2afe4 100644 --- a/api/graphics.md +++ b/api/graphics.md @@ -6,6 +6,7 @@ > - `include/graphics/Color.h` > - `include/graphics/Font.h`, `include/graphics/FontManager.h` > - `include/graphics/StaticTilemapLayerCache.h` +> - `include/graphics/DirtyGrid.h` > - `include/graphics/TileAnimation.h` > - `include/graphics/DrawSurface.h`, `include/graphics/BaseDrawSurface.h` > - `include/graphics/particles/*.h` @@ -22,11 +23,14 @@ The engine includes several low-level optimizations for the ESP32 platform to ma - **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. +- **Dirty Region Tracking**: Selective framebuffer clear via `DirtyGrid` (double-buffer prev/curr with 8×8 cells), reducing memset overhead. - **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. +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. + +> **Dirty Regions Interaction:** When both the static cache and Dirty Regions are enabled, the cache advises `beginFrame()` to skip its selective or full clear if a cache `memcpy` will entirely overwrite the framebuffer anyway. ## Key Concepts @@ -62,6 +66,26 @@ Generic descriptors for tile-based backgrounds, instantiated as `TileMap` (1bpp) 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. +### Dirty Region System + +The Dirty Region System provides selective framebuffer clearing to reduce unnecessary `memset` operations when most of the screen stays unchanged. + +- **`DirtyGrid`** (requires `PIXELROOT32_ENABLE_DIRTY_REGIONS=1`): Double-buffer design with `prev` and `curr` bit-packed grids of 8×8 pixel cells. Each cell tracks whether it was drawn to in the current frame. + +- **`LayerType`** (in `include/graphics/Renderer.h`): Classifies tilemaps to enable selective tracking: + - `LayerType::Static`: Background layers that rarely change. The system tracks dirty cells, but the layer itself doesn't mark cells as dirty. + - `LayerType::Dynamic`: Sprites, particles, and UI that move every frame. These mark their occupied cells as dirty. + +- **`forceFullRedraw()`**: Call this when a full framebuffer clear is required (scene transitions, pause menus, camera jumps). Resets the dirty grid state and synchronizes the internal bit buffer. + +- **`TilemapSpriteDirtyMode`**: Controls per-sprite dirty marking behavior. Use `SuppressPerSpriteBoundsMark` for static or selectively-animated tilemaps to avoid marking cells as dirty unnecessarily. + +- **Debug overlay** (`setDebugDirtyCellOverlay`): Visualizes dirty cells on screen for debugging. Requires `PIXELROOT32_DEBUG_MODE=1`. + +- **Compile flag**: `PIXELROOT32_ENABLE_DIRTY_REGIONS` (default: disabled). RAM cost: `2 × ceil(cols × rows / 8)` bytes — 60 bytes for 120×120, 226 bytes for 240×240. + +> **Tip:** Use `LayerType::Static` for backgrounds that don't change—avoids unnecessary dirty cell marking. + ### Tile Attribute System Provides runtime access to custom metadata attached to tiles. Attributes are stored in Flash memory on the ESP32. @@ -90,6 +114,8 @@ Abstract interfaces for platform-specific drawing operations (e.g., `SDL2_Drawer - `Color`, `PaletteType` → `include/graphics/Color.h` - `Font`, `FontManager` → `include/graphics/FontManager.h` - `Sprite`, `TileMap` → `include/graphics/Renderer.h` +- `DirtyGrid` → `include/graphics/DirtyGrid.h` +- `LayerType` → `include/graphics/Renderer.h` - `ParticleEmitter`, `ParticleConfig` → `include/graphics/particles/ParticleEmitter.h` - `TileAnimationManager` → `include/graphics/TileAnimation.h` diff --git a/api/index.md b/api/index.md index dcd09f6..2dd53eb 100644 --- a/api/index.md +++ b/api/index.md @@ -95,6 +95,7 @@ Some modules are optional and can be disabled to save memory: | 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`) | +| Dirty Regions | `PIXELROOT32_ENABLE_DIRTY_REGIONS` | Disabled | | Debug Overlay | `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Disabled | --- diff --git a/architecture/audio-subsystem.md b/architecture/audio-subsystem.md index 4f8000d..93ac851 100644 --- a/architecture/audio-subsystem.md +++ b/architecture/audio-subsystem.md @@ -172,7 +172,7 @@ 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. +- **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. On no-FPU platforms (ESP32-C3), a **fixed-point HPF** runs on the integer mix output before scaling to `int16_t`. ### 3.4 Mixing all voices (Non-Linear Mixer) @@ -193,6 +193,7 @@ The system uses a **non-linear mixing strategy** aligned across FPU and LUT path - 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. +- **LFO modulation**: Both pitch and volume LFO are supported on no-FPU builds via integer-only Q16/Q15 phase accumulation (`tickLfoPhase`, `tickLfoDepthQ15`) — same modulation logic as the FPU path, implemented in fixed-point to avoid soft-float. - 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. diff --git a/architecture/index.md b/architecture/index.md index a0a5322..d8ea3b6 100644 --- a/architecture/index.md +++ b/architecture/index.md @@ -20,7 +20,7 @@ | Subsystem | Document | Description | |-----------|----------|-------------| -| **Audio NES** | [Audio Subsystem](./audio-subsystem.md) | 4-channel NES-style: shared `ApuCore`, `AudioScheduler`, backends | +| **Audio NES** | [Audio Subsystem](./audio-subsystem.md) | 8-voices 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 | @@ -86,6 +86,7 @@ graph TD | 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`) | +| Dirty Regions (selective clear) | `PIXELROOT32_ENABLE_DIRTY_REGIONS` | Disabled | | Debug Overlay | `PIXELROOT32_ENABLE_DEBUG_OVERLAY` | Disabled | --- @@ -111,6 +112,7 @@ The engine provides **`pixelroot32::graphics::StaticTilemapLayerCache`** (`inclu - 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 +- **Dirty Regions Interaction:** When both the static cache and Dirty Regions are enabled, the cache advises `beginFrame()` to skip its selective or full clear if a cache `memcpy` will entirely overwrite the framebuffer anyway. --- diff --git a/architecture/layer-systems.md b/architecture/layer-systems.md index d7874c1..e12bcd0 100644 --- a/architecture/layer-systems.md +++ b/architecture/layer-systems.md @@ -29,7 +29,8 @@ flowchart TB end subgraph RendererLayer["Renderer"] - B -->|"Clip"| C[Viewport Culling] + B -->|"Track"| DB[Dirty Grid] + DB -->|"Clip"| C[Viewport Culling] C -->|"Transform"| D[World to Screen] D -->|"Scale"| E[Logical to Physical] end @@ -210,6 +211,7 @@ AudioBackend | 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 | +| Dirty Regions (selective clear) | `PIXELROOT32_ENABLE_DIRTY_REGIONS` | Disabled | --- diff --git a/examples/2048.md b/examples/2048.md new file mode 100644 index 0000000..4226d0e --- /dev/null +++ b/examples/2048.md @@ -0,0 +1,127 @@ +# 2048 Example + +A **2048** puzzle game where you slide tiles to combine them and reach **2048**. The game features **grid rendering**, **tile values**, **score tracking**, and **game over / win states**. + +On **`esp32cyd`**, swipes are detected via **`onUnconsumedTouchEvent`** to slide tiles in the swipe direction. On **`native`**, use D-pad controls per `Game2048Constants.h` button IDs. + +## Requirements (build flags) + +- **`PIXELROOT32_ENABLE_TOUCH=1`** on **`native`** and **`esp32cyd`** (see `platformio.ini`) so touch code paths compile. + +`PIXELROOT32_ENABLE_UI_SYSTEM` defaults to **on** in the engine ([`PlatformDefaults.h`](../../include/platforms/PlatformDefaults.h)). + +See **`platformio.ini`** for **`native`** and **`esp32cyd`** (CYD uses **ILI9341** 240×320 + **XPT2046** touch — many calibration defines are in the INI). + +## Platforms + +| Environment | Display / input | +|-------------|----------------| +| **`native`** | SDL2, 240×320, simulated touch + keyboard enabled | +| **`esp32cyd`** | **ILI9341** 240×320 + resistive touch (**XPT2046** GPIO SPI) | + +## Controls (GPIO / keyboard) + +- **Arrow keys / D-pad** to slide all tiles in the selected direction. +- **Play Again**: reset button wired in `Game2048Scene::createResetButton()` (GPIO or touch widget depending on build). + +## Touch (CYD) + +Swipe gestures are detected in **`onUnconsumedTouchEvent`**: + +- **DragStart / DragMove / DragEnd** events track the swipe direction. +- Minimum swipe distance of **30 pixels** triggers a move. +- Swipe direction is determined by the major axis (horizontal or vertical). + +## Features + +- **Scene** lifecycle + **UI labels** and conditional **touch / GPIO buttons** +- **TouchEvent** pipeline for swipe detection +- **Game logic**: tile sliding, merging, spawning, win/lose detection +- **Custom palette** in `color_palette.h` (PR32-compatible tile colors) +- **Score tracking** displayed in label +- **Audio SFX** (no background music): spawn (Triangle), merge (double Triangle), move (simple Triangle) +- **Fixed merge behavior**: correct merge in all 4 directions (left, right, up, down) +- **AI Auto-play**: expectimax algorithm with depth-3 search and heuristic evaluation + +## Audio + +Audio SFX are defined in **`src/assets/sfx.h`**: + +- `playSpawnSound` - Triangle wave (A4) - plays when new tile spawns +- `playMergeSound` - Double Triangle (C5 + E5) - plays when tiles merge +- `playMoveSound` - Simple Triangle (A3) - plays on move without merge +- `playWinSound` / `playGameOverSound` - win/lose feedback + +No background music (removed to reduce memory footprint). + +## AI Auto-play + +The AI uses an **optimized Expectimax** algorithm with adaptive depth search and intelligent state caching: + +### Algorithm + +| Feature | Implementation | +|---------|---------------| +| **Search** | Expectimax (MAX nodes: player moves, CHANCE nodes: tile spawns) | +| **Depth** | Adaptive 2-5 based on empty cells (deeper when board is tight) | +| **Sampling** | Smart: evaluates all cells when ≤8 empty, samples 6 when >8 empty | +| **Spawn probability** | Spawn-2 (90%) + Spawn-4 (10%) with proper weighting | +| **Transposition Table** | 8192-entry cache with FNV-1a hashing to avoid recomputing states | +| **Probability cutoff** | Stops exploring paths with <0.01% cumulative probability | + +### Heuristic Evaluation + +The AI evaluates board states using 6 weighted factors: + +| Factor | Weight | Description | +|--------|--------|-------------| +| **Empty cells** | 4K-100K (exponential) | Mobility bonus that increases as board fills up | +| **Monotonicity penalty** | -100× | Severe penalty for non-monotonic sequences (larger tiles matter more) | +| **Smoothness** | +100× | Rewards adjacent tiles with similar values (merge potential) | +| **Merge bonus** | +1000× per merge | Strong incentive for creating merge opportunities | +| **Corner bonus** | +100× maxTile | Rewards keeping max tile in any corner | +| **Corner penalty** | -1000× maxTile | **Catastrophic penalty** if max tile is NOT in a corner | +| **Adjacent tiles** | +0.5× value | Bonus for tiles adjacent to max tile (builds L-pattern) | + +### Performance + +- **Native (PC)**: Base depth 3, adaptive up to depth 5 +- **ESP32**: Base depth 2, adaptive up to depth 4 +- Average decision time: <100ms on native, <500ms on ESP32 +- **Win rate**: Consistently reaches 2048 tile + +### Controls + +- AI is enabled by compile flag `-D GAME2048_AI_MODE=1` in `platformio.ini` +- When enabled, AI automatically makes optimal moves every frame +- AI respects game rules from `game_rules.md` + +To disable AI, remove `-D GAME2048_AI_MODE=1` from build_flags in platformio.ini. + +## Documentation links + +- [Game Logic](game_logic.md) +- [Game Rules](game_rules.md) +- [UI API](../../docs/api/ui.md) +- [Input API](../../docs/api/input.md) +- [Core API](../../docs/api/core.md) + +## Build + +From **`examples/2048`**: + +```bash +pio run -e native +pio run -e esp32cyd +``` + +## Upload (ESP32) + +```bash +pio run -e esp32cyd --target upload +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/2048 diff --git a/examples/animated-tilemap.md b/examples/animated-tilemap.md index 7153765..03bc5ff 100644 --- a/examples/animated-tilemap.md +++ b/examples/animated-tilemap.md @@ -69,3 +69,8 @@ pio run -e esp32dev # ESP32 CYD-style (ILI9341 240×320) pio run -e esp32cyd ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap diff --git a/examples/animated_tilemap.md b/examples/animated_tilemap.md new file mode 100644 index 0000000..76b510f --- /dev/null +++ b/examples/animated_tilemap.md @@ -0,0 +1,76 @@ +# Animated Tilemap Example + +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**). + +## Requirements (build flags) + +- **`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. + +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`). + +## 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`](../../docs/api/graphics.md#multi-layer-4bpp-tilemap-framebuffer-snapshot-statictilemaplayercache) +- Pipeline / layering: [Architecture — ESP32 rendering and tilemap caching](../../docs/architecture/architecture-index.md#esp32-rendering-pipeline-and-tilemap-caching) +- Static cache concept: [Architecture — Static tilemap layer cache](../../docs/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 +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap diff --git a/examples/audio-playback.md b/examples/audio-playback.md index 58e9ab1..d56c0ad 100644 --- a/examples/audio-playback.md +++ b/examples/audio-playback.md @@ -82,3 +82,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/audio_playback diff --git a/examples/basic-usage.md b/examples/basic-usage.md index 34b7fb7..8722896 100644 --- a/examples/basic-usage.md +++ b/examples/basic-usage.md @@ -352,3 +352,8 @@ void onUnconsumedTouchEvent(const input::TouchEvent& event) override { - **[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) + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/basic_usage diff --git a/examples/brick-breaker.md b/examples/brick-breaker.md index 49edf40..1ac8100 100644 --- a/examples/brick-breaker.md +++ b/examples/brick-breaker.md @@ -62,3 +62,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/brick_breaker diff --git a/examples/brick_breaker.md b/examples/brick_breaker.md new file mode 100644 index 0000000..746e9fc --- /dev/null +++ b/examples/brick_breaker.md @@ -0,0 +1,69 @@ +# Brick Breaker Example + +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`**. + +## Requirements (build flags) + +- **`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. + +Display size is **240×240** in the project `platformio.ini` (see **`PHYSICAL_DISPLAY_*`**). + +## 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](../../docs/api/audio.md) +- [Core API](../../docs/api/core.md) +- [Input API](../../docs/api/input.md) +- [Particles API](../../docs/api/particles.md) + +## Build + +From **`examples/brick_breaker`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/brick_breaker diff --git a/examples/camera.md b/examples/camera.md index 86a787f..c813f7c 100644 --- a/examples/camera.md +++ b/examples/camera.md @@ -33,10 +33,10 @@ The engine version or Git branch is set in **`lib_deps`** in `platformio.ini`. ## Documentation links -- [Graphics — Camera2D](/api/graphics#camera2d) -- [Core — Scene / entities](/api/core) -- [Physics — kinematic & static actors](/api/physics) -- [Architecture](/architecture/architecture-index.md) +- [Graphics — Camera2D](../../docs/api/graphics.md#camera2d) +- [Core — Scene / entities](../../docs/api/core.md) +- [Physics — kinematic & static actors](../../docs/api/physics.md) +- [Architecture](../../docs/architecture/architecture-index.md) ## Build @@ -52,3 +52,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/camera diff --git a/examples/demos.md b/examples/demos.md index 3f2ca94..f2e5d3e 100644 --- a/examples/demos.md +++ b/examples/demos.md @@ -2,7 +2,7 @@ # PixelRoot32 — Examples -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. +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: @@ -11,43 +11,23 @@ cd pio run -e ``` -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)). +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 example is defined in **`lib_deps`** inside that example’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 -| 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` , `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. +- [2048](./2048) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/2048) +- [animated_tilemap](./animated_tilemap) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/animated_tilemap) +- [brick_breaker](./brick_breaker) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/brick_breaker) +- [camera](./camera) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/camera) +- [dual_palette](./dual_palette) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/dual_palette) +- [flappy_bird](./flappy_bird) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/flappy_bird) +- [hello_world](./hello_world) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/hello_world) +- [metroidvania](./metroidvania) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/metroidvania) +- [music_demo](./music_demo) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo) +- [physics](./physics) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/physics) +- [snake](./snake) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/snake) +- [space_invaders](./space_invaders) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/space_invaders) +- [sprites](./sprites) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/sprites) +- [tic_tac_toe](./tic_tac_toe) — [source code](https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe) \ No newline at end of file diff --git a/examples/dual-palette.md b/examples/dual-palette.md index 2965a57..6fae289 100644 --- a/examples/dual-palette.md +++ b/examples/dual-palette.md @@ -44,3 +44,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/dual_palette diff --git a/examples/dual_palette.md b/examples/dual_palette.md new file mode 100644 index 0000000..116fce7 --- /dev/null +++ b/examples/dual_palette.md @@ -0,0 +1,51 @@ +# Dual Palette Example + +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). + +Runtime entry point calls **`pixelroot32::graphics::enableDualPaletteMode(true)`** in `DualPaletteTestScene::init()` (see [`src/DualPaletteTestScene.cpp`](src/DualPaletteTestScene.cpp)). + +## Requirements (build flags) + +- **`PIXELROOT32_ENABLE_SCENE_ARENA`** + +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](../../docs/api/graphics.md) +- [Architecture](../../docs/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 +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/dual_palette diff --git a/examples/flappy-bird.md b/examples/flappy-bird.md index d2aec89..33e2417 100644 --- a/examples/flappy-bird.md +++ b/examples/flappy-bird.md @@ -52,3 +52,8 @@ pio run -e esp32c3 --target upload ``` Wire your OLED according to the U8g2 configuration used in this project’s platform header / driver setup. + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/flappy_bird diff --git a/examples/flappy_bird.md b/examples/flappy_bird.md new file mode 100644 index 0000000..2d60b81 --- /dev/null +++ b/examples/flappy_bird.md @@ -0,0 +1,59 @@ +# Flappy Bird Example + +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). + +## Requirements (build flags) + +- **`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`**. + +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`. + +## Platforms + +| 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](../../docs/api/physics.md) +- [Core API](../../docs/api/core.md) +- [Platform / drivers](../../docs/api/platform.md) + +## 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. + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/flappy_bird diff --git a/examples/hello-world.md b/examples/hello-world.md index 9d3e461..e71bf34 100644 --- a/examples/hello-world.md +++ b/examples/hello-world.md @@ -52,3 +52,8 @@ pio run -e esp32_s3 ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/hello_world diff --git a/examples/hello_world.md b/examples/hello_world.md new file mode 100644 index 0000000..b314baa --- /dev/null +++ b/examples/hello_world.md @@ -0,0 +1,59 @@ +# Hello World Example + +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. + +## Requirements (build flags) + +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`**. + +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. + +## Platforms + +| 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** | + +> ⚠️ **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`. + + + +## 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](../../docs/api/core.md) +- [UI API](../../docs/api/ui.md) +- [Graphics / Renderer](../../docs/api/graphics.md) + +## 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 +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/hello_world diff --git a/examples/metroidvania.md b/examples/metroidvania.md index 3bf35e2..e817246 100644 --- a/examples/metroidvania.md +++ b/examples/metroidvania.md @@ -1,14 +1,15 @@ # Metroidvania-Style Example -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. +A compact **platformer** sample with **4bpp tilemap layers**, **`StaticTilemapLayerCache`** for the ESP32 fast path when available, and a **`KinematicActor`**-based player with gravity, climbing, and jump rules tailored to the sample map. **Requires `PIXELROOT32_ENABLE_4BPP_SPRITES`** — the scene is guarded in [`src/MetroidvaniaScene.h`](src/MetroidvaniaScene.h). ## Requirements (build flags) - **`PIXELROOT32_ENABLE_4BPP_SPRITES`** -- **`PIXELROOT32_ENABLE_2BPP_SPRITES`** (enabled alongside 4bpp in this example’s `platformio.ini`) +- **`PIXELROOT32_ENABLE_2BPP_SPRITES`** (enabled alongside 4bpp in this example's `platformio.ini`) - **`PIXELROOT32_ENABLE_SCENE_ARENA`** +- **`PIXELROOT32_ENABLE_DIRTY_REGIONS`** See **`platformio.ini`** for **`native`** and **`esp32dev`** presets (no `esp32cyd` environment in this project). @@ -18,30 +19,47 @@ See **`platformio.ini`** for **`native`** and **`esp32dev`** presets (no `esp32c | Environment | Display | |-------------|---------| -| **`native`** | SDL2, 240×240 | -| **`esp32dev`** | **ST7789** 240×240 | +| **`native`** | SDL2, 240x240 | +| **`esp32dev`** | **ST7789** 240x240 | ## 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. +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. + +## Player Actor + +The **`PlayerActor`** extends **`KinematicActor`** and implements: + +- **Gravity + horizontal movement** with configurable `PLAYER_GRAVITY`, `PLAYER_MOVE_SPEED`, `PLAYER_JUMP_VELOCITY` +- **Ladder climbing** via `setStairs()` / `buildStairsCache()` — a bitmask RAM cache of climbable tiles +- **State machine**: `IDLE`, `RUN`, `JUMP`, `CLIMBING` with sprite animation per state +- **Collision layers**: `PLAYER`, `PLATFORM`, `GROUND`, `ENEMY` (for future extension) ## 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). +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](../animated_tilemap/README.md) for the detailed invalidation table and [Architecture — static tilemap cache](../../docs/architecture/architecture-index.md#static-tilemap-layer-cache-engine--scenes). + +## Dirty Regions + +This example enables **`PIXELROOT32_ENABLE_DIRTY_REGIONS`** for targeted clearing on ESP32. The rendering pipeline uses a dirty-grid approach to selectively clear only the regions that changed between frames, reducing SPI transfer overhead on the display. ## Features -- **4bpp tilemaps** and layered level data +- **4bpp tilemaps** and layered level data (background, platforms, stairs) - **`StaticTilemapLayerCache`** snapshot path when the driver exposes a logical framebuffer -- **Player actor** with gravity, stairs/climb behavior, and map collision +- **Dirty Regions** — targeted clearing via dirty-grid pipeline on ESP32 +- **Player actor** with gravity, stairs/climb behavior, and manual tilemap collision +- **Stairs mask cache** — bitmask RAM cache built once from tile indices for fast overlap checks +- **State-driven sprite animation** — `IDLE`, `RUN`, `JUMP`, `CLIMBING` states - **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) +- [Graphics — tilemaps & `StaticTilemapLayerCache`](../../docs/api/graphics.md#multi-layer-4bpp-tilemap-framebuffer-snapshot-statictilemaplayercache) +- [Architecture — static tilemap layer cache](../../docs/architecture/architecture-index.md#static-tilemap-layer-cache-engine--scenes) +- [Architecture — ESP32 rendering / tilemap caching](../../docs/architecture/architecture-index.md#esp32-rendering-pipeline-and-tilemap-caching) +- [Physics API](../../docs/api/physics.md) +- [Core API](../../docs/api/core.md) ## Build @@ -57,3 +75,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/metroidvania diff --git a/examples/music-demo.md b/examples/music-demo.md index 494b56e..427cf95 100644 --- a/examples/music-demo.md +++ b/examples/music-demo.md @@ -80,3 +80,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo diff --git a/examples/music_demo.md b/examples/music_demo.md new file mode 100644 index 0000000..b075b95 --- /dev/null +++ b/examples/music_demo.md @@ -0,0 +1,87 @@ +# 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](../../docs/api/audio.md) +- [Music player guide](../../docs/guide/music-player-guide.md) +- [Input API](../../docs/api/input.md) +- [UI API](../../docs/api/ui.md) +- [Core — Scene](../../docs/api/core.md) + +## Build + +From **`examples/music_demo`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/music_demo diff --git a/examples/physics-demo.md b/examples/physics-demo.md index 2875db1..7bac9f5 100644 --- a/examples/physics-demo.md +++ b/examples/physics-demo.md @@ -55,3 +55,8 @@ pio run -e esp32cyd pio run -e esp32dev --target upload pio run -e esp32cyd --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/physics_demo diff --git a/examples/physics.md b/examples/physics.md new file mode 100644 index 0000000..dffd261 --- /dev/null +++ b/examples/physics.md @@ -0,0 +1,62 @@ +# Physics Demo Example + +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. + +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). + +## Requirements (build flags) + +- **`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 **`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](../../docs/api/physics.md) +- [Input API](../../docs/api/input.md) +- [UI API](../../docs/api/ui.md) +- [Core — Scene](../../docs/api/core.md) + +## 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 +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/physics diff --git a/examples/snake.md b/examples/snake.md index ec753b2..1a9036e 100644 --- a/examples/snake.md +++ b/examples/snake.md @@ -35,9 +35,9 @@ Pin choices for I2S / DAC are in **`src/platforms/esp32_dev.h`** (edit there if ## Documentation links -- [Audio API](/api/audio) -- [Core API](/api/core) -- [Input API](/api/input) +- [Audio API](../../docs/api/audio.md) +- [Core API](../../docs/api/core.md) +- [Input API](../../docs/api/input.md) ## Build @@ -53,3 +53,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/snake diff --git a/examples/space-invaders.md b/examples/space-invaders.md index 092cab3..a79b4dd 100644 --- a/examples/space-invaders.md +++ b/examples/space-invaders.md @@ -54,4 +54,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload -``` \ No newline at end of file +``` + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/space_invaders diff --git a/examples/space_invaders.md b/examples/space_invaders.md new file mode 100644 index 0000000..8e1a861 --- /dev/null +++ b/examples/space_invaders.md @@ -0,0 +1,61 @@ +# Space Invaders Example + +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`**. + +## Requirements (build flags) + +- **`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. + +Display size is **240×240** in the project `platformio.ini` (see **`PHYSICAL_DISPLAY_*`**). + +## 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](../../docs/api/audio.md) +- [Core API](../../docs/api/core.md) +- [Input API](../../docs/api/input.md) + +## Build + +From **`examples/space_invaders`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/space_invaders diff --git a/examples/sprite-animation.md b/examples/sprite-animation.md index 4c1dfad..1b7c256 100644 --- a/examples/sprite-animation.md +++ b/examples/sprite-animation.md @@ -43,3 +43,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/sprite_animation diff --git a/examples/sprites.md b/examples/sprites.md new file mode 100644 index 0000000..3489b9a --- /dev/null +++ b/examples/sprites.md @@ -0,0 +1,50 @@ +# Sprites Demo Example + +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. + +## Requirements (build flags) + +- **`PIXELROOT32_ENABLE_2BPP_SPRITES`** +- **`PIXELROOT32_ENABLE_4BPP_SPRITES`** + +[`SpritesDemoScene.h`](src/SpritesDemoScene.h) is compiled only when at least one of these is defined (see `#if` guard in the header). + +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](../../docs/api/graphics.md) +- [Core — Scene / Entity](../../docs/api/core.md) + +## Build + +From **`examples/sprites`**: + +```bash +pio run -e native +pio run -e esp32dev +``` + +## Upload (ESP32) + +```bash +pio run -e esp32dev --target upload +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/sprites diff --git a/examples/tic-tac-toe.md b/examples/tic-tac-toe.md index af2068d..7db3f5e 100644 --- a/examples/tic-tac-toe.md +++ b/examples/tic-tac-toe.md @@ -59,3 +59,8 @@ pio run -e esp32cyd pio run -e esp32dev --target upload pio run -e esp32cyd --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe diff --git a/examples/tic_tac_toe.md b/examples/tic_tac_toe.md new file mode 100644 index 0000000..cfa33f9 --- /dev/null +++ b/examples/tic_tac_toe.md @@ -0,0 +1,66 @@ +# Tic-Tac-Toe Example + +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. + +On **`esp32cyd`**, **`PIXELROOT32_ENABLE_TOUCH`** and **`onUnconsumedTouchEvent`** map taps to cells; **`native`** / **`esp32dev`** use cursor + confirm-style input per `GameConstants.h` button IDs. + +## Requirements (build flags) + +- **`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`). + +`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](../../docs/api/ui.md) +- [Input API](../../docs/api/input.md) +- [Core API](../../docs/api/core.md) + +## 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 +``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tic_tac_toe diff --git a/examples/tilemap-scene.md b/examples/tilemap-scene.md index bd1680d..eea6812 100644 --- a/examples/tilemap-scene.md +++ b/examples/tilemap-scene.md @@ -59,3 +59,8 @@ pio run -e esp32dev ```bash pio run -e esp32dev --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/tilemap_scene diff --git a/examples/ui-layout.md b/examples/ui-layout.md index af2068d..6977c0c 100644 --- a/examples/ui-layout.md +++ b/examples/ui-layout.md @@ -59,3 +59,8 @@ pio run -e esp32cyd pio run -e esp32dev --target upload pio run -e esp32cyd --target upload ``` + + +--- + +**Source code:** https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/ui_layout diff --git a/guide/audio.md b/guide/audio.md index b48d952..3ad934c 100644 --- a/guide/audio.md +++ b/guide/audio.md @@ -1,6 +1,6 @@ # Audio System -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. +PixelRoot32 provides a **NES-like** audio subsystem: a **dynamic 8-voice pooling system** (supporting Pulse, Triangle, Noise, Sine, and Saw waveforms), **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 [Audio subsystem](../architecture/audio-subsystem.md) (authoritative). @@ -20,7 +20,7 @@ flowchart TB subgraph Audio["Audio consumer context"] D -->|dequeue| E[AudioScheduler thin wrapper] - E -->|ApuCore::generateSamples| F[Pulse x2 + Triangle + Noise + music] + E -->|ApuCore::generateSamples| F[Dynamic 8-Voice Pool + music] F --> G[Non-linear mixer FPU or LUT + optional HPF] end @@ -30,14 +30,14 @@ flowchart TB 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. Synthesis is not duplicated across three scheduler copies. +- **`AudioEngine`** forwards commands and **`generateSamples`** to the active **`AudioScheduler`**, which delegates to **`ApuCore`**: SPSC queue, dynamic 8-voice pool, music sequencer, mixing, and (on the FPU path) a one-pole output HPF. Synthesis is not duplicated across three scheduler copies. - 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). ## Key Features | Feature | Description | |--------|-------------| -| **4 channels** | 2× pulse (duty configurable), 1× triangle, 1× noise | +| **8 voices** | Dynamic voice pooling with stealing logic. Supports Pulse, Triangle, Noise, Sine, and Saw. | | **Sample-accurate** | Channel lifetime in samples; independent of FPS | | **Schedulers** | `NativeAudioScheduler` (PC), `ESP32AudioScheduler` (firmware), `DefaultAudioScheduler` (tests / callback-driven) | | **Command path** | Lock-free **SPSC** ring buffer, **128** entries; one producer / one consumer | @@ -146,6 +146,7 @@ pr32::drivers::esp32::ESP32_I2S_AudioBackend audioBackend(26, 25, 22, 22050); pr32::audio::AudioConfig audioConfig; audioConfig.backend = &audioBackend; audioConfig.sampleRate = 22050; +audioConfig.blockSize = 128; // Optional: tune for platform latency (e.g., 128 for ESP32-C3, 256 for FPU) pr32::core::Engine engine(displayConfig, inputConfig, audioConfig); ``` @@ -156,8 +157,8 @@ See **[AudioEngine](../api/audio.md)** for **`AudioConfig`** fields and architec | Platform | Mixer | Noise (typical) | Audio execution | |----------|--------|-----------------|-----------------| -| **ESP32 (FPU)** | Float + soft clip | Clocked LFSR | Backend task; core from `PlatformCapabilities` | -| **ESP32-C3 (no FPU)** | LUT | Clocked LFSR | Same; integer LUT mix | +| **ESP32 (FPU)** | Float + soft clip + HPF | Clocked LFSR | Backend task; core from `PlatformCapabilities` | +| **ESP32-C3 (no FPU)** | LUT + Q15 Fixed-Point HPF | Clocked LFSR + Q15 LFO | Same; integer path with Q15 LFO and HPF, static function pointer dispatch for wave generation | | **PC (native)** | Float + soft clip + HPF | Same LFSR as firmware | `std::thread` + ring buffer → SDL2 callback | ## Best practices diff --git a/guide/game-loop.md b/guide/game-loop.md index 26d8b1d..7c39478 100644 --- a/guide/game-loop.md +++ b/guide/game-loop.md @@ -127,7 +127,9 @@ sequenceDiagram ```cpp void Renderer::beginFrame() { - drawer->clear(); // Clear framebuffer + // Clear framebuffer (full or selective with dirty regions if enabled) + // drawer->clear(); // Full clear + // drawer->selectiveClear(); // Dirty region selective clear } void Scene::draw(Renderer& renderer) { @@ -160,6 +162,9 @@ flowchart TB ```cpp void Renderer::endFrame() { + // Draw dirty cell debug overlay if enabled + // drawer->drawDebugOverlay(); + drawer->present(); // Send to display } ``` diff --git a/guide/getting-started.md b/guide/getting-started.md index 70d6440..4123580 100644 --- a/guide/getting-started.md +++ b/guide/getting-started.md @@ -8,15 +8,18 @@ PixelRoot32 follows a **scene-based architecture inspired by Godot Engine**, mak **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 +- **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, IRAM-cached rendering, and a Dirty Regions pipeline. +- **Sprite System**: Support for 1bpp/2bpp/4bpp sprites with multi-palette selection, flipping, rotation, and animation. +- **Tilemap Support**: Optimized rendering with viewport culling, static layer caching, multi-palette, and tile animations. +- **Tile Animation System**: Frame-based animations (water, lava) with O(1) frame resolution and zero-allocation policy. +- **Independent Resolution Scaling**: Render at low logical resolutions (e.g., 128x128) and scale to physical displays (e.g., 240x240). +- **NES-Style Audio**: Built-in dynamic 8-voice audio subsystem with fixed-point No-FPU optimizations (Pulse, Triangle, Noise, Sine, Saw). +- **Lightweight UI**: Label, Button, and Checkbox with automatic layouts. +- **AABB Physics**: Godot-style physics with Kinematic/Rigid actors, sensors, and one-way platforms. +- **Indexed Color Palettes**: Optimized palettes (PR32, NES, GameBoy, PICO-8) with multi-palette support. +- **Modular Architecture**: Compile only needed subsystems via `PIXELROOT32_ENABLE_*` flags to reduce firmware size. ## Prerequisites diff --git a/guide/graphics-techniques.md b/guide/graphics-techniques.md index bb50fb1..6df6ad0 100644 --- a/guide/graphics-techniques.md +++ b/guide/graphics-techniques.md @@ -14,10 +14,12 @@ Tilemaps are efficient for backgrounds and level geometry: the engine stores com - 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). +- Optional **dirty region tracking** for selective framebuffer clearing — see [Dirty Region System](../api/graphics.md#dirty-region-system). +- **`LayerType`** classification for dirty region optimization (Static vs Dynamic layers). ### 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). +`PIXELROOT32_ENABLE_TILE_ANIMATIONS`, `PIXELROOT32_ENABLE_STATIC_TILEMAP_FB_CACHE`, `PIXELROOT32_ENABLE_DIRTY_REGIONS`, and related switches are documented in [Configuration flags](../api/config.md) and the [API overview](../api/index.md). ### Examples in Engine Repo diff --git a/guide/input.md b/guide/input.md index 7fbc621..08e0714 100644 --- a/guide/input.md +++ b/guide/input.md @@ -74,22 +74,25 @@ You choose the meaning of each index in your game (jump, fire, menu, …). Keep ### Configuration -`InputConfig` stores parallel arrays of pins (ESP32) or scancodes (native). Use the variadic constructor: first argument is **count**, then one value per button. +`InputConfig` stores parallel arrays of pins (ESP32) or scancodes (native). The number of inputs is automatically deduced from the template variadic arguments—no explicit `count` parameter needed. ```cpp #include #if defined(PLATFORM_NATIVE) // Example: 4 keyboard keys (SDL scancodes) -input::InputConfig inputConfig(4, 80, 79, 81, 82); // arbitrary example keys +// Count is auto-deduced: 4 buttons = indices 0, 1, 2, 3 +input::InputConfig inputConfig(80, 79, 81, 82); // arbitrary example keys #else -// ESP32: count + GPIO pin numbers -input::InputConfig inputConfig(4, 0, 32, 33, 25); +// ESP32: GPIO pin numbers - count inferred automatically +input::InputConfig inputConfig(0, 32, 33, 25); #endif core::Engine engine(std::move(displayConfig), inputConfig); ``` +> **Note:** The `count` parameter was removed in v1.5.0. The number of buttons is now deduced from the number of arguments passed. + ### Button state patterns ```cpp @@ -229,7 +232,7 @@ flowchart LR ### Default PC mappings (illustrative) -Your `InputConfig` on native builds lists **SDL scancodes** in the order of logical indices `0…n-1`. A typical layout might map arrow keys to indices `2–5` and action keys to `0–1`, but the exact table is entirely project-specific—mirror the order you passed into `InputConfig(count, …)`. +Your `InputConfig` on native builds lists **SDL scancodes** in the order of logical indices `0…n-1`. A typical layout might map arrow keys to indices `2–5` and action keys to `0–1`, but the exact table is entirely project-specific—mirror the order you passed into `InputConfig(...)` (the count is now automatic since v1.5.0). ## Input patterns @@ -288,9 +291,11 @@ Store recent **logical indices** (`uint8_t`) in a ring buffer if you need fighti void setup() { pixelroot32::graphics::DisplayConfig display(240, 240); #if defined(PLATFORM_NATIVE) - pixelroot32::input::InputConfig input(4, /* scancodes */ 0, 0, 0, 0); + // Count is auto-deduced - no explicit count needed + pixelroot32::input::InputConfig input(0, 0, 0, 0); // 4 scancodes #else - pixelroot32::input::InputConfig input(4, 0, 32, 33, 25); + // ESP32: 4 GPIO pins - count inferred from arguments + pixelroot32::input::InputConfig input(0, 32, 33, 25); #endif pixelroot32::core::Engine engine(std::move(display), input); // GameScene scene(engine); engine.setScene(&scene); diff --git a/guide/performance/esp32-performance.md b/guide/performance/esp32-performance.md index e49aa0d..efa2d4e 100644 --- a/guide/performance/esp32-performance.md +++ b/guide/performance/esp32-performance.md @@ -35,13 +35,32 @@ - 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. +### Dirty Region Selective Clear + +Reduces framebuffer clearing overhead by tracking which 8×8 pixel cells were actually drawn to in the previous frame, utilizing a **double dirty grid** pipeline. + +- **Benefit**: Replaces full-screen `memset` with targeted **selective row-run 8bpp clearing**. It skips untouched rows entirely and uses `__builtin_popcount` optimizations to quickly identify blocks of dirty cells. +- **RAM cost**: 64–226 bytes (depends on resolution and cell size). +- **When it pays off**: Games with mostly static backgrounds and small moving sprites. +- **Profiling flag**: `PIXELROOT32_ENABLE_DIRTY_REGION_PROFILING=1` +- **Metric**: `dirty_ratio` — fraction of cells marked dirty. Good values are <0.5; >0.8 suggests full clear is cheaper. + +```ini +; Enable in platformio.ini +build_flags = + -DPIXELROOT32_ENABLE_DIRTY_REGIONS=1 + -DPIXELROOT32_ENABLE_DIRTY_REGION_PROFILING=1 +``` + +> **Tip:** If `dirty_ratio` > 0.8, disable dirty regions and use full clear—it avoids the tracking overhead. + ### 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. +- **Float Operations**: Soft-float emulation on the ESP32-C3 is extremely slow. The engine provides fixed-point Q15 implementations for performance-critical inner loops (like `tickEnvelopeQ15`, LFO generation for vibrato/tremolo, HPF filtering, and audio mixer LUTs). Avoid introducing new float-based calculations inside per-sample audio loops or per-pixel drawing loops. --- diff --git a/guide/rendering.md b/guide/rendering.md index 5bce7bc..f4c7b07 100644 --- a/guide/rendering.md +++ b/guide/rendering.md @@ -48,7 +48,7 @@ graphics::Renderer renderer(config); renderer.init(); // Frame structure -renderer.beginFrame(); // Clear framebuffer +renderer.beginFrame(); // Clear framebuffer (full or selective with dirty regions) // ... draw game content ... @@ -78,9 +78,9 @@ class Renderer { void drawMultiSprite(const MultiSprite& sprite, int x, int y); // Tilemaps - void drawTileMap(const TileMap& map, int x, int y, Color color); - void drawTileMap2bpp(const TileMap2bpp& map, int x, int y); - void drawTileMap4bpp(const TileMap4bpp& map, int x, int y); + void drawTileMap(const TileMap& map, int x, int y, Color color, LayerType layerType = LayerType::Dynamic); + void drawTileMap2bpp(const TileMap2bpp& map, int x, int y, LayerType layerType = LayerType::Dynamic); + void drawTileMap4bpp(const TileMap4bpp& map, int x, int y, LayerType layerType = LayerType::Dynamic); }; ``` @@ -465,6 +465,123 @@ int width = graphics::FontManager::textWidth("Text", &customFont); renderer.drawText("Text", (240 - width) / 2, 100, Color::WHITE, 1); ``` +## Dirty Region Optimization + +The Dirty Region System reduces framebuffer clearing overhead by tracking which 8×8 pixel cells were drawn to in each frame. + +### When to Enable + +- Games with mostly static backgrounds (platformers, top-down RPGs) +- Scenes where only a small portion of the screen changes per frame +- When `dirty_ratio` < 0.5 (most of the screen stays clean) + +### Enable in platformio.ini + +```ini +build_flags = + -DPIXELROOT32_ENABLE_DIRTY_REGIONS=1 + -DPIXELROOT32_ENABLE_DIRTY_REGION_PROFILING=1 ; Optional: runtime profiling + -DPIXELROOT32_DEBUG_MODE=1 ; Required for debug overlay +``` + +### Using LayerType (Static vs. Dynamic) + +When using the Dirty Region pipeline, you must classify elements via the `LayerType` enum when drawing to optimize tracking: + +- **`LayerType::Static`**: For backgrounds or static HUD elements. They are drawn to the framebuffer but do **not** mark their corresponding cells as dirty, minimizing tracking overhead. +- **`LayerType::Dynamic`**: For moving sprites or animations. Their bounding boxes automatically mark intersecting 8x8 cells as dirty, ensuring they are redrawn next frame. + +Classify tilemaps when drawing to optimize tracking: + +```cpp +// Static background - rarely changes, doesn't mark dirty cells +renderer.drawTileMap(backgroundMap, 0, 0, Color::WHITE, LayerType::Static); + +// Dynamic sprites - move every frame, mark their cells as dirty +renderer.drawTileMap(playerSprite, x, y, Color::WHITE, LayerType::Dynamic); +``` + +### When to Call forceFullRedraw() + +Call this to force a full framebuffer clear when needed: + +```cpp +// Scene transitions +void GameScene::onSceneEnter() { + renderer.forceFullRedraw(); +} + +// Pause menus +void pauseGame() { + renderer.forceFullRedraw(); + // Draw pause UI +} + +// Camera jump / teleport +void teleportPlayer(Vector2 newPos) { + camera.setPosition(newPos); + renderer.forceFullRedraw(); +} +``` + +### Example: Tilemap + Sprites with Layer Types + +```cpp +void GameScene::draw(Renderer& r) { + // Background (static - won't mark dirty cells) + r.drawTileMap(stageMap, 0, 0, Color::WHITE, LayerType::Static); + + // Midground objects + r.drawTileMap(wallsMap, 0, 0, Color::WHITE, LayerType::Dynamic); + + // Entities (dynamic sprites) + for (auto* enemy : enemies) { + enemy->draw(r); // Automatically marks dirty + } + + // Player + player->draw(r); // Automatically marks dirty + + // UI (drawn last, on top) + r.setOffsetBypass(true); + drawUI(r); + r.setOffsetBypass(false); +} +``` + +### Debug Overlay + +Enable to visualize which cells are dirty: + +```cpp +// In setup (requires PIXELROOT32_DEBUG_MODE=1) +renderer.setDebugDirtyCellOverlay(true); +``` + +This draws a colored overlay showing dirty cells in real-time. When `forceFullRedraw()` has been called, all cells are highlighted. + +### StaticTilemapLayerCache Integration + +The Dirty Region system integrates tightly with `StaticTilemapLayerCache` to provide an ultra-fast rendering path on ESP32. Instead of redrawing the background tilemap pixel-by-pixel, the cache takes a snapshot of the logical framebuffer containing only `LayerType::Static` elements. On subsequent frames, `renderer.beginFrame()` uses a fast `memcpy` to restore the background, and only the cells marked by `LayerType::Dynamic` elements are selectively cleared and redrawn. + +#### Scene Stacking Contract + +When using `StaticTilemapLayerCache` with stacked scenes: + +> **Important:** At least one scene must NOT use the static cache (i.e., must not advise suppression), or all stacked scenes must collectively cover the entire framebuffer. Failure to meet this contract may result in stale pixels in uncovered regions. + +```cpp +// Scene A: uses static cache (advises suppression) +void SceneA::beginFrame() { + renderer.accumulateFramebufferClearSuppressionAdvice(true); +} + +// Scene B: does NOT use cache (does NOT advise) +void SceneB::beginFrame() { + renderer.accumulateFramebufferClearSuppressionAdvice(false); // Required! +} +``` + ## Best Practices ### Batch Similar Draw Calls diff --git a/guide/testing.md b/guide/testing.md index a484892..80c5131 100644 --- a/guide/testing.md +++ b/guide/testing.md @@ -81,6 +81,8 @@ test/ │ ├── test_rect/ # Rect type │ ├── test_scene/ # Scene │ ├── test_scene_manager/ # Scene manager +│ ├── test_tile_animation_manager/ # Tile animations +│ ├── test_touch_state_machine/ # Touch gesture state machine │ ├── test_ui/ # UI elements and layouts │ └── ... ├── test_engine_integration/ # Engine integration tests @@ -198,6 +200,31 @@ void test_audio_engine_play_event(void) { } ``` +### Testing Collision Systems + +The engine includes comprehensive unit tests for the collision system, ensuring physical interactions behave accurately. These tests cover: + +- **Velocity Integration**: Verifying actors move correctly based on velocity and `deltaTime`. +- **Penetration Correction**: Ensuring intersecting actors are pushed apart cleanly without overshooting. +- **Sensor Contacts**: Checking that `isSensor` actors trigger `onCollision` callbacks but do not block physical movement. +- **Boundary Scenarios**: Verifying behavior at the edges of the simulation or with one-way platforms. + +```cpp +void test_physics_actor_penetration_correction(void) { + // Arrange: two overlapping actors + PhysicsActor p1(0, 0, 10, 10); + PhysicsActor p2(5, 0, 10, 10); + p1.setBodyType(PhysicsActor::BodyType::KINEMATIC); + p2.setBodyType(PhysicsActor::BodyType::STATIC); + + // Act + // (Internal resolution logic invoked during step) + + // Assert: p1 should be pushed left + TEST_ASSERT_FLOAT_EQUAL(-5.0f, toFloat(p1.position.x)); +} +``` + --- ## Integration Testing diff --git a/migration/index.md b/migration/index.md index da13211..0f11779 100644 --- a/migration/index.md +++ b/migration/index.md @@ -6,7 +6,8 @@ Version upgrade guides for PixelRoot32 Game Engine. - [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 +- [v1.2.0 Migration](./migration-v1-2-0.md) - Physics actor flags, UI callbacks refactor +- [v1.5.0 Migration](./migration-v1-5-0.md) - InputConfig API simplification (std::vector to std::array) ## Migration Policy diff --git a/migration/migration-v1-5-0.md b/migration/migration-v1-5-0.md new file mode 100644 index 0000000..24ad62a --- /dev/null +++ b/migration/migration-v1-5-0.md @@ -0,0 +1,247 @@ +# Migration Guide: v1.4.0 → v1.5.0 + +## InputConfig API Simplification + +Version 1.5.0 simplifies the `InputConfig` constructor by eliminating the redundant `count` parameter. The number of inputs is now automatically deduced from the template arguments, making the API cleaner and less error-prone. + +--- + +## Breaking Change: `count` Parameter Removed + +### What Changed + +The `InputConfig` constructor no longer requires the explicit `count` argument. The number of inputs is automatically deduced from the template variadic arguments. + +### Before + +```cpp +pr32::input::InputConfig inputConfig( + 6, // count - now auto-deduced + BTN_UP, + BTN_DOWN, + BTN_LEFT, + BTN_RIGHT, + BTN_A, + BTN_B +); +``` + +### After + +```cpp +pr32::input::InputConfig inputConfig( + BTN_UP, + BTN_DOWN, + BTN_LEFT, + BTN_RIGHT, + BTN_A, + BTN_B +); +``` + +--- + +## Memory Optimization: `std::vector` → `std::array` + +### Why This Change + +Version 1.5.0 replaces `std::vector` with `std::array` in `InputConfig`: + +| Aspect | Before (`std::vector`) | After (`std::array`) | +|--------|------------------------|----------------------| +| Heap allocation | Yes (dynamic) | No (stack/global) | +| Fragmentation | Possible | None | +| Determinism | Non-deterministic | O(1) access | +| Max inputs | Dynamic (runtime) | Fixed (16) | +| Zero-init | Required manually | Automatic via `{}` | + +### Memory Impact + +| Metric | Before | After | +|--------|--------|-------| +| `InputConfig` size | ~24 bytes (heap) | 72 bytes (stack, fixed) | +| ESP32 heap usage | Variable | 0 bytes | +| Allocation at runtime | Yes | None | + +--- + +## API Changes + +### Constructor Signature + +**Before:** + +```cpp +InputConfig(int count, ...); // variadic with explicit count +``` + +**After:** + +```cpp +template +InputConfig(Args... args); // count auto-deduced +``` + +### Empty Configuration + +**Before:** + +```cpp +InputConfig config(0); // empty with count +``` + +**After:** + +```cpp +InputConfig config{}; // default constructor or empty init list +InputConfig config(); // also valid +``` + +--- + +## Migration Patterns + +### Pattern 1: Remove the Count Parameter + +```cpp +// Before +pr32::input::InputConfig config(4, PIN_1, PIN_2, PIN_3, PIN_4); + +// After +pr32::input::InputConfig config(PIN_1, PIN_2, PIN_3, PIN_4); +``` + +### Pattern 2: Empty Configuration + +```cpp +// Before +pr32::input::InputConfig config(0); + +// After +pr32::input::InputConfig config{}; +// or +pr32::input::InputConfig config; +``` + +### Pattern 3: Single Input + +```cpp +// Before +pr32::input::InputConfig config(1, BTN_UP); + +// After +pr32::input::InputConfig config(BTN_UP); +``` + +### Pattern 4: Native Platform (SDL2) + +```cpp +// Before +pr32::input::InputConfig config(2, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN); + +// After +pr32::input::InputConfig config(SDL_SCANCODE_UP, SDL_SCANCODE_DOWN); +``` + +--- + +## Compile-Time Validation + +The new implementation includes compile-time validation: + +### Overflow Detection + +```cpp +// If more than 16 arguments are passed, compilation fails: +InputConfig config(1, 2, 3, ..., 17); // static_assert error at compile time +``` + +### Error Message + +``` +error: static_assert failed due to requirement 'sizeof...(Args) <= MAX_INPUT_COUNT' +"Too many arguments for InputConfig" +``` + +--- + +## Runtime Validation + +Invalid configurations are handled at runtime: + +| Scenario | Behavior | +|----------|----------| +| Empty config | `count = 0`, arrays zero-initialized | +| 1-16 inputs | `count = N`, valid | +| >16 inputs | `count = 16` (clamped) | + +--- + +## Migration Checklist + +- [ ] **Search for `InputConfig(` calls**: Find all usages with explicit count +- [ ] **Remove the first integer argument**: The count is now automatic +- [ ] **Verify count deduction**: Ensure the new count matches expected +- [ ] **Check empty configurations**: Replace `InputConfig(0)` with `InputConfig{}` +- [ ] **Build and test**: Ensure all scenes compile and run correctly + +### Search Patterns + +```bash +# Find all InputConfig usages +grep -rn "InputConfig(" --include="*.cpp" --include="*.h" + +# Specific pattern for explicit count (regex) +InputConfig\([0-9]+, +``` + +--- + +## Backward Compatibility + +**This is a breaking change.** The old constructor signature `InputConfig(int count, ...)` no longer works. + +If you have code like this: + +```cpp +InputConfig config(3, A, B, C); // OLD API - WILL NOT COMPILE +``` + +You must update to: + +```cpp +InputConfig config(A, B, C); // NEW API +``` + +--- + +## Verification + +After migration, verify: + +- [ ] All `InputConfig` calls compile without errors +- [ ] Input count matches the number of arguments provided +- [ ] ESP32 builds succeed with no heap allocation warnings +- [ ] Native (SDL2) builds work correctly +- [ ] All unit tests pass (`pio test -e native_test`) + +--- + +## File Changes + +This change affects the following files: + +| File | Change | +|------|--------| +| `include/input/InputConfig.h` | `std::vector` → `std::array`, template variadic constructor | +| `src/core/Engine.cpp` | Updated `InputConfig(0)` → `InputConfig{}` | +| All game scenes | Remove `count` parameter from `InputConfig` calls | + +--- + +## References + +- [Input System Guide](../guide/input.md) +- [Memory Optimization](../guide/memory.md) +- [ESP32 Performance](../guide/performance/esp32-performance.md) +- [API Reference: Input](../api/input.md) \ No newline at end of file diff --git a/scripts/sync-docs-from-engine.mjs b/scripts/sync-docs-from-engine.mjs index 5a4618c..a930108 100644 --- a/scripts/sync-docs-from-engine.mjs +++ b/scripts/sync-docs-from-engine.mjs @@ -1,3 +1,9 @@ +// Syncs documentation from engine repository to docs repo +// Usage: +// node scripts/sync-docs-from-engine.mjs --engine ../PixelRoot32-Game-Engine +// Syncs documentation from engine repository to docs repo +// Usage: +// node scripts/sync-docs-from-engine.mjs --engine ../PixelRoot32-Game-Engine import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -8,7 +14,8 @@ const enginePath = engineArgIndex !== -1 && process.argv[engineArgIndex + 1] ? p const docsSource = path.join(enginePath, 'docs'); const docsTarget = path.join(__dirname, '..'); -const sections = ['guide', 'api', 'architecture', 'philosophy', 'migration', 'examples', 'tools']; +const sections = ['guide', 'api', 'architecture', 'philosophy', 'migration', 'tools']; +const externalSections = ['examples']; const excludedFiles = ['README.md', 'CHANGELOG.md', 'LICENSE', 'assets', 'img', 'images', '.vitepress', '.github']; @@ -26,7 +33,7 @@ function syncDocs() { const destLegacy = path.join(docsTarget, '_legacy_vitepress', section); if (fs.existsSync(srcDir)) { - copyDirRecursive(srcDir, destDir, excludedFiles); + copyDirRecursive(srcDir, destDir, excludedFiles, false); console.log(`Synced ${section}/`); } @@ -35,10 +42,118 @@ function syncDocs() { } } + for (const section of externalSections) { + const srcDir = path.join(enginePath, section); + const destDir = path.join(docsTarget, section); + const destLegacy = path.join(docsTarget, '_legacy_vitepress', section); + + if (fs.existsSync(srcDir)) { + syncExamplesFromSubdirs(srcDir, destDir); + console.log(`Synced ${section}/ from engine root`); + } + + if (fs.existsSync(destDir)) { + fs.cpSync(destDir, destLegacy, { recursive: true, force: true }); + } + } + + addGitHubLinksToExamples(); + console.log('Docs sync complete.'); } -function copyDirRecursive(src, dest, excluded) { +function addGitHubLinksToExamples() { + const examplesDir = path.join(docsTarget, 'examples'); + + if (!fs.existsSync(examplesDir)) { + console.log('Examples directory not found, skipping link addition.'); + return; + } + + const entries = fs.readdirSync(examplesDir, { withFileTypes: true }); + + for (const entry of entries) { + if (!entry.isFile() || !entry.name.endsWith('.md')) { + continue; + } + + if (entry.name === 'demos.md') { + continue; + } + + const exampleNameKebab = path.basename(entry.name, '.md'); + const exampleNameSnake = exampleNameKebab.replace(/-/g, '_'); + const githubLink = `https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/${exampleNameSnake}`; + const filePath = path.join(examplesDir, entry.name); + const content = fs.readFileSync(filePath, 'utf8'); + + if (content.includes(githubLink)) { + console.log(`Link already present in ${entry.name}, skipping.`); + continue; + } + + const linkToAdd = `\n\n---\n\n**Source code:** ${githubLink}\n`; + fs.appendFileSync(filePath, linkToAdd, 'utf8'); + console.log(`Added GitHub link to ${entry.name}`); + } +} + +function syncExamplesFromSubdirs(srcDir, destDir) { + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + } + + const entries = fs.readdirSync(srcDir, { withFileTypes: true }); + const examples = []; + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + + const readmePath = path.join(srcDir, entry.name, 'README.md'); + if (fs.existsSync(readmePath)) { + const destPath = path.join(destDir, `${entry.name}.md`); + fs.copyFileSync(readmePath, destPath); + examples.push(entry.name); + console.log(` Synced ${entry.name}.md`); + } + } + + const demosContent = generateDemosIndex(examples); + fs.writeFileSync(path.join(destDir, 'demos.md'), demosContent, 'utf8'); + console.log(` Generated demos.md`); +} + +function generateDemosIndex(examples) { + const template = `# Engine sample projects + +# PixelRoot32 — Examples + +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**\` 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 example is defined in \`**lib_deps**\` inside that example's \`platformio.ini\` (registry tag vs Git branch). + +## Catalogue + +`; + + const sorted = examples.sort(); + const listItems = sorted.map(name => { + const githubLink = `https://github.com/PixelRoot32-Game-Engine/PixelRoot32-Game-Engine/tree/main/examples/${name}`; + return `- [${name}](./${name}) — [source code](${githubLink})`; + }).join('\n'); + return template + listItems; +} + +function copyDirRecursive(src, dest, excluded, onlyMd = false) { if (!fs.existsSync(dest)) { fs.mkdirSync(dest, { recursive: true }); } @@ -53,8 +168,10 @@ function copyDirRecursive(src, dest, excluded) { const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { - copyDirRecursive(srcPath, destPath, excluded); - } else { + if (!onlyMd) { + copyDirRecursive(srcPath, destPath, excluded, onlyMd); + } + } else if (!onlyMd || entry.name.endsWith('.md')) { fs.copyFileSync(srcPath, destPath); } }