No description
  • C++ 99.1%
  • Python 0.6%
  • C 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-13 14:02:21 +08:00
.vscode chore: add project configuration files 2026-05-05 20:23:50 +08:00
assets feat(presentation/assets): animation queue + embedded art pipeline, HP/SR-tiered idle, gameplay fixes 2026-05-11 19:27:32 +08:00
reports/original-repair fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
SaveManager fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
src fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
tests fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
tools fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
.gitignore feat(presentation/assets): animation queue + embedded art pipeline, HP/SR-tiered idle, gameplay fixes 2026-05-11 19:27:32 +08:00
diagram.json feat(hardware): add three buttons to the Wokwi diagram 2026-05-07 13:37:43 +08:00
LICENSE chore: add MIT license 2026-05-05 18:02:05 +08:00
platformio.ini fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
README.md fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
README_ja.md fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
README_zh.md fix: harden original Artoria v9 save workflows and correct trilingual docs 2026-10-13 14:02:21 +08:00
wokwi.toml feat: add initial game core, pet systems, and platform setup 2026-05-05 17:26:14 +08:00

English | 简体中文 | 日本語

👑 Artoria Tamagotchi (Fate/Grand Order Virtual Pet)

This repository maintains the original Artoria game and v9 save format. The reusable C++17 engine with Artoria and Pocket Sprout lives in embedded-tamagotchi-engine.

A Tamagotchi-like virtual pet project powered by the ESP32-S3 (N16R8).

Raise your own Artoria! Start your journey with Saber Lily and guide her growth. Depending on your care and interactions, she can evolve into various classes including Archer, Lancer, Rider, and more.

✨ Features

  • Evolution System: Multiple growth routes based on pet stats (Health, Seriousness, etc.).
  • Time & Day Management: Built-in internal clock to handle daily resets and penalties for missing meals.
  • Persistent Storage: 3-slot save system with checksum validation, serial full or pet-only export, slot import with full-restore or visit (trade-in) mode, device-bound rounds/clock state, and import-time safeguards.
  • Interactive Actions: Feed, Poke, and monitor status.
  • Presentation & Display: Cutscenes for feed, combo chains, Poke, evolution, and similar flows are driven by an ordered animation queue; idle sprites are tiered from pet state (Health, Seriousness, etc.), including Rhongomyniad. SSD1351 128×128 65K color OLED via Adafruit GFX / Adafruit SSD1351, plus a serial placeholder backend for headless testing. Optional: bake PNGs under assets/ into flash-resident binaries (missing assets fall back to placeholders).

🚧 Current Project Status

  • Gameplay & systems: Core loops are in place end-to-end—feeding and combos, special foods, Poke, evolution, day roll-up, save import/export, gallery, destroy confirmation, and related flows all run. Recent work routes on-screen cutscenes through a single “queue frames, play them on time” path; idle poses are picked from pet state and loaded from flash (placeholders remain when art is missing).
  • Art & assets: Final sprite / keyframe art is not all finished or wired in yet; the device still uses stick figures plus simple shapes and text so layouts and timing can be validated. Once assets are built through the assets/ pipeline, the same logic can show real PNG sequences.
  • Display: SSD1351 backend (Adafruit libraries) and Serial placeholder backend both compile. Physical display and button checks for this legacy repair edition remain unverified.
  • Input: 3-button physical input (L/M/R) with serial simulation (btn l|m|r, btnl l|m|r, btnr l|m|r).

🛠️ Hardware Requirements

  • ESP32-S3 Development Board (N16R8 recommended)
  • SSD1351 1.5" 128x128 65K Color OLED (SPI)
  • 3 tactile buttons (GPIO 4, 5, 6)

Pin Mapping

Function GPIO
BTN_L 4
BTN_M 5
BTN_R 6
TFT_DC 8
TFT_RST 9
TFT_CS 10
TFT_MOSI 11
TFT_SCLK 12

Build

pio run -e esp32-s3-devkitc-1            # SSD1351 build
pio run -e original-serial               # Serial placeholder build
pio run -e esp32-s3-devkitc-1 -t upload  # Upload selected environment
pio device monitor -b 115200            # Serial monitor

To regenerate embedded art from PNGs under assets/, run python tools/convert_assets.py in a Python environment with Pillow installed (naming and folders are documented in assets/README.md). The PlatformIO pre-build hook stays commented by default so builds do not break when PlatformIO’s bundled Python lacks Pillow; if you skip conversion, the firmware still runs using stick-figure / placeholder graphics.

Select original-serial at build time; no header edits are needed. This backend still runs on ESP32-S3. Host tests use separate peripheral adapters. Install Pillow for PNG conversion and pyserial for the save tool.

Button Mapping

Idle Screen

Button Action
L Feed (start draw)
M (short) Status panel
M (long) Open Gallery
R Poke

Status Panel

Button Action
M Close

Feed Pick (select 3 of 4)

Button Action
L Move cursor left
R Move cursor right
M Toggle selection (auto-submit at 3)

Special Food Selection

Button Action
L Move cursor up
R Move cursor down
M Select
Button Action
L Navigate left (with page wrap)
R Navigate right (with page wrap)
M Close gallery (back to idle)

Destroy Confirm

Button Action
L Move to YES
R Move to NO
M Confirm selection

Destroy Combo

Hold all three buttons (L+M+R) for 5 seconds to trigger destroy confirmation (default cursor on NO).

Confirm the date/time after first boot or power loss. In setup, L increases the current field, M moves to the next field or confirms, R moves back, and long L restores the editable default date.

Smoke Test Procedure (Serial / Buttons)

Use serial commands to simulate button presses. Connect at 115200 baud.

1. Boot to Idle

Power on and wait for the boot screen (~1.5s). First boot or power loss requires date/time confirmation with the buttons or SET_TIME <unix_seconds>; enter Idle only after setup succeeds. A recovery lock blocks normal play.

2. Status Panel

btn m          # Open status panel
btn m          # Close status panel (back to idle)

3. Feed Flow

btn l          # Start feed draw (shows 4 food cards with animation)
               # Wait ~800ms for draw animation to complete
               # Automatically enters Feed Pick screen
btn l          # Move cursor left
btn r          # Move cursor right
btn m          # Select food at cursor (repeat to select 3)
btn r          # Move to a different food
btn m          # Second selection
btn r          # Move to a third food
btn m          # Third selection -> auto-submit, shows Feed Result
               # Feed Result stays for at least 1.5 seconds
               # Then returns to idle automatically

4. Non-combo Feed Result Hold

After selecting 3 foods (no combo triggered):

  • PAGE_FEED_RESULT displays for at least 1500ms
  • Input is blocked during this hold period
  • Page returns to idle automatically after hold expires

5. Combo Feed Result to Special Food

If a combo is triggered:

  • PAGE_FEED_RESULT shows with "COMBO" text
  • ANIM_COMBO plays (~1000ms)
  • After combo animation, enters Special Food selection
btn l          # Move cursor in special food list
btn r          # Move cursor in special food list
btn m          # Select special food
               # Confirmation displays for at least 1 second
               # Then returns to idle

6. Mapo Tofu Trigger

If mapo tofu is triggered during special food selection:

  • ANIM_MAPO_TOFU plays (~1500ms)
  • Input blocked during animation
  • Returns to idle after animation

7. Poke

btn r          # Poke animation plays (~500ms)
               # Returns to idle after animation completes

8. Destroy — Three-Button Hold

ctx            # Check current context
# Physically hold L+M+R for 5 seconds to test destroy confirmation.
# 'btnl l' simulates only L; it does not simulate a simultaneous three-key hold.
# 'reset' directly resets the pet / restores the owner, bypassing confirmation.

Destroy confirm: default cursor on NO (safe). L/R to move, M to confirm.

9. Auto-save

Auto-save triggers periodically. It shows a brief toast "Autosaved" at the bottom of the screen without interrupting the current page or animation.

10. Debug Commands

s              # Print full status
h              # Help
fl             # Food list
t <min>        # Advance N minutes
d              # Advance 1 day (triggers day-end)
save           # Manual save
load           # Load save
erase          # Erase save
reset          # Destroy and reset pet (ends visit if visiting)
stime Y M D H m  # Set simulated time
SET_TIME <epoch> # Confirm/set time; offline compensation applies during setup wait
hp <val>       # Debug: set health (0-100)
sr <val>       # Debug: set seriousness (0-100)
age <val>      # Debug: set age days
grad           # Debug: force child graduation
mapo           # Debug: +1 mapo tofu count
FORCE_NOBU     # Debug: force Nobbu / Oda Nobunaga branch
UNLOCK_ALL     # Debug: unlock all gallery forms
RESET_GALLERY  # Debug: reset gallery (lock all)
SAVE_SLOT_STATUS   # Print slot0/1/2 status (seq/time/ver/size/crc)
SAVE_EXPORT <slot> # Export full backup (pet + gallery) over serial (hex stream)
SAVE_EXPORT_PET <slot> # Export pet-only snapshot (visit/trade; hex stream)
SAVE_IMPORT_BEGIN <slot> # Start slot import session
SAVE_IMPORT_DATA <hex>   # Append one hex payload chunk
SAVE_IMPORT_COMMIT       # Commit import payload
SAVE_IMPORT_ABORT        # Abort import session
s0 / s1 / s2   # Print per-slot snapshot status
devinfo        # Show device state (rounds, visit mode, clock epoch)
bright <0-15>  # Set screen brightness
dim <0-15>     # Set dim brightness
dim_t <sec>    # Set dim timeout
off_t <sec>    # Set off timeout
pwrsave        # Save power config to NVS (optional; values also persist when set)
pwrinfo        # Print power config
btn l|m|r      # Simulate short press
btnl l|m|r     # Simulate long press
btnr l|m|r     # Simulate repeat
ctx            # Show current UI context

Serial Save Import/Export

The project supports per-slot save import/export over serial for backup, migration, and pet-only exchange (“visit”) workflows.

Device state, exports, and visit mode

Some fields are stored per device (not inside a slot): rounds (incremented on successful full import and on visit import), device_clock_epoch (baseline used with SET_TIME for offline catch-up), and visit mode flags (is_visiting, frozen owner slot, visit start time). Inspect them with the devinfo serial command.

  • SAVE_EXPORT <slot> — Full backup: pet plus bundled gallery data for that workflow (larger payload).
  • SAVE_EXPORT_PET <slot> — Pet-only snapshot for trading or visit mode (smaller payload; no gallery block).
  • SAVE_IMPORT_BEGIN prints [SaveImport] READY ... target_bytes=... (legacy=...), announcing the expected full and pet-only payload sizes; the host should send a file whose length matches one of these before SAVE_IMPORT_COMMIT.

Full import (payload length = full backup): restores gallery from the bundle, rebuilds the active slot pair around the target slot, aligns pet timestamps to the local clock, clears visit mode, bumps rounds, updates device_clock_epoch, and persists.

Pet-only import (payload length = pet-only): Visit mode — among the two active slots, the newer pet is treated as the owner and that slot stays frozen on disk; the imported slot holds the guest, which is loaded into RAM. Local gallery data stays in place, but the guest’s current form is unlocked if needed. Import into the owner’s latest slot is rejected (pick the other active slot). rounds and device_clock_epoch are updated similarly to a full import.

While visiting: new imports and stime, t <min>, and d are rejected. SET_TIME is rejected during normal visitor play; after power loss it can confirm the time and compensate the visitor while the owner remains frozen. reset destroys the guest and restores the owner (visit ends). Invalid payloads (e.g. save_time == 0) print [SaveImport] REJECTED: and abort.

Firmware commands

  • SAVE_SLOT_STATUS: Query slot status for all slots (slot0, slot1, slot2).
  • SAVE_EXPORT <slot>: Export full backup (pet + gallery) as a hex stream block.
  • SAVE_EXPORT_PET <slot>: Export pet-only snapshot as a hex stream block.
  • SAVE_IMPORT_BEGIN <slot> -> SAVE_IMPORT_DATA <hex> -> SAVE_IMPORT_COMMIT: Import one slot payload (length selects full restore vs visit).
  • SAVE_IMPORT_ABORT: Abort the current import session.
  • s0 / s1 / s2: Print one slot snapshot in status format.
  • devinfo: Print device-bound state (rounds, visit mode, clock epoch).

Host-side helper (Python)

From the SaveManager directory:

py save_manager.py

Or direct CLI usage:

py save_manager.py --port COM5 status
py save_manager.py --port COM5 export --slot 0 --out slot0_full.bin
py save_manager.py --port COM5 export --slot 0 --out slot0_pet.bin --pet-only
py save_manager.py --port COM5 import --slot 1 --in slot0_full.bin

Import behavior (important)

  • Full backup: The imported slot joins the active pair; from the other two slots, the older one becomes the second active slot (by sequence, then save time; empty slots count as older). The remaining slot is frozen and skipped by auto/manual rotation. Success line: [SaveImport] Full backup restored ....
  • Pet-only (visit): Owner slot is frozen; guest lives in the imported slot; [SaveImport] Visit started ... on success. reset ends the visit and restores the owner.
  • If the device was waiting for first-time clock setup when import completes, it leaves that wait using the stored device clock anchor when possible; normal boots are unchanged. Offline “catch-up from old save time” is not applied in the import paths described above.
  • Import, owner restoration and time confirmation use a durable recovery guard. A partial failure blocks gameplay and automatic writes, including after restart; it does not report full success or silently erase data. Ordinary v9 rotation still writes separate keys and is not a cross-key transaction.

Page Hold Behavior

The display system uses a "page hold" mechanism to ensure result/confirmation pages are visible:

  • Feed Result (non-combo): Held for 1500ms, input blocked, then auto-returns to idle.
  • Special Food Confirm (non-mapo): Held for 1000ms, input blocked, then auto-returns to idle.
  • Mapo Tofu: Animation plays for 1500ms (managed by animation system, not hold).
  • Boot: After 1500ms, continues to Idle or the required time-setup screen. Recovery prevents normal operation.
  • Day End: Animation plays for 1500ms, input blocked.
  • Evolution: Animation plays for 2000ms, input blocked.
  • Destroy animation: Animation plays for 2000ms, input blocked.

During any hold or animation, isPageBlockingInput() returns true and the MenuController skips input processing.

Verification and Recovery

The offline repair suite runs actual production save, menu, time, display and serial paths with peripheral adapters:

python tests/host/run_all.py

Run from a Git checkout retaining the original baseline history. Build with PlatformIO first to install the ESP32-S3 target compiler used by ABI checks. See the repair report for commands, failure/interruption tests and remaining limits. This legacy repair edition has host regression and two-backend build evidence; its physical hardware tests have not been executed. Engine hardware results do not apply to this edition.

The original v9 format remains 72 bytes pet-only / 84 bytes full. This firmware does not read engine v10 backups. Normal v9 saves use multiple NVS keys; the recovery guard covers import, owner restoration and time confirmation, not all ordinary saves.

If recovery is required, export a readable full slot before an explicit erase. Choose the actual retained owner slot from slot status / device state:

python SaveManager/save_manager.py --port PORT export --slot 0 --out owner.bin
# After checking the backup, send erase in the serial console: it deletes game saves.
python SaveManager/save_manager.py --port PORT import --slot 1 --in owner.bin

Normal boot does not erase data or automatically finish an interrupted protected operation. Offline catch-up supports at most 366 days, subject to age/epoch capacity; it preserves the original minute/day rules.