Files
basic1/SAVE_SYSTEM_PLAN.md
Adolfo Reyna e6e4eca188 Add Lua 5.4 scripting integration for dynamic game loading
- Integrated Lua 5.4 engine (32-bit mode for embedded ARM)
- Created LuaGame wrapper class implementing Game interface
- Added C++ bindings exposing renderer, game state, and input to Lua
- Implemented SD card loader for automatic .lua game discovery
- Updated GameLauncher to support std::function for lambda captures
- Made Game class members public for Lua bindings access
- Added example Lua games: counter, snake, bouncing ball
- Included comprehensive API documentation

Games can now be written as .lua text files on SD card and loaded
without recompilation. Build size: 747KB UF2, Lua VM uses ~50-80KB RAM.
2026-02-07 11:56:03 -05:00

3.3 KiB

Game Save System Implementation Plan (RP2350/RP2040)

This document outlines the strategy for implementing non-volatile game save states using the onboard Flash memory.

1. Objectives

  • Persistence: Save game state so progress is kept after power-off.
  • Multi-game Support: Allow multiple games to have independent saves without overwriting each other.
  • Safety: Ensure flash writes don't interfere with program execution or interrupt handlers.
  • Wear Leveling: Minimize unnecessary writes to prolong flash lifespan.

2. Flash Memory Mapping

The RP2350 on this board has 8MB of Flash memory mapped starting at XIP_BASE (0x10000000). We will reserve the top section for data.

Memory Range Purpose Size
0x10000000 - 0x106FFFFF Program Code, Fonts, Assets 7MB
0x10700000 - 0x107EFFFF Reserved/Future 960KB
0x107F0000 - 0x107F0FFF Save Registry 4KB (1 Sector)
0x107F1000 - 0x107FFFFF Game Save Slots 60KB (15 Slots)

3. Architecture Components

A. PersistentStorage Manager (lib/persistent_storage.h)

A singleton or static helper class that handles raw flash operations.

  • bool save_exists(const char* game_id)
  • bool save_data(const char* game_id, void* data, size_t size)
  • bool load_data(const char* game_id, void* buffer, size_t size)

B. Updated Game Interface (lib/game.h)

Add methods to the base class to standardize how state is handled.

virtual const char* get_game_id() const = 0;
virtual size_t get_save_data(void* buffer, size_t max_size) = 0;
virtual void load_save_data(const void* buffer, size_t size) = 0;

C. Save Registry

To avoid collisions, the first sector (0x107F0000) will store a lookup table:

struct RegistryEntry {
    char game_id[16];
    uint32_t sector_index;
    uint32_t data_size;
    uint32_t checksum;
};

4. Implementation Steps

  1. Create PersistentStorage:
    • Implement flash sector erasing and programming using pico/flash.h.
    • Include interrupt protection using save_and_disable_interrupts().
  2. Update Game Base Class:
    • Modify lib/game.h to include the virtual persistence methods.
  3. Implement in Monopoly:
    • Create a MonopolySaveState struct (POD - Plain Old Data).
    • Populate it from game variables in get_save_data.
  4. Integrate with Launcher:
    • Modify GameLauncher to check for existing saves when a game is selected.
    • Modify the main loop to trigger save_data at logical points (e.g., end of turn/exit).

5. Collision Avoidance Logic

  • When a game requests a save for the first time, the PersistentStorage manager scans the Registry.
  • If the game_id isn't found, it allocates the next available 4KB sector.
  • It writes the specific game_id and its assigned sector_index back to the Registry sector.
  • Sequential saves for that ID will always use that specific sector.

6. Important Notes

  • Flash Wear: Each sector supports ~100k erase cycles. Total lifespan depends on frequency of saves.
  • Alignment: Data must be programmed in multiples of FLASH_PAGE_SIZE (256 bytes).
  • Recursion: Never call flash functions from within an interrupt or callback that might be triggered during a write.