- Custom lua_psram_alloc uses heap_caps_malloc(SPIRAM|8BIT) fallback to reduce internal heap pressure - Fixes sdmmc_read_sectors not enough mem (0x101) and MemoryChecker low 359 bytes - Audio skipped for solitaire (no sfx/tone in source) to save RAM: 'audio not needed, skipping SfxEngine' - Exit: game.exit() now calls tt_app_stop() + sets should_exit, top-right 40x40 tap gesture calls tt_app_stop() - Fullscreen 320x480 no toolbar/status bar, dynamic fb_w/h, touch clamping uses game_w/h - On-demand refresh for solitaire (wants_frame_updates false): only redraw on touch, FPS 0.1-1.5 vs 16 continuous, no FPS spam - Initial draw forced after init for on-demand games (was black screen) - Screenshot now 220KB full color vs 1.2K 4-bit, shows solitaire cards with optimized LEFT=4 GAP=4 TOP_Y=4 TAB_Y=60 (was 37/10/30/98) - no window chrome - Tested solitaire loads external 24430 bytes, pico=1, no Lua errors
LuaGame - Lua Interpreter Game Engine
External ELF app that runs Lua games on Tactility OS (ES3C35P 320x480 tested on 192.168.68.107).
How it works
- Embeds Lua 5.4.7 source (no external dependency, avoids missing symbol issues)
- Lua init + game loop run in dedicated 16K stack FreeRTOS task to avoid GUI task (4096 bytes) stack overflow
- Game framebuffer: 240x240 RGB565 in PSRAM, rendered via LVGL canvas (
lv_canvas_set_buffer+lv_obj_invalidatewithtt_lvgl_lock) - Direct framebuffer text using 8x8 bitmap font (
font8x8.h) – 17+ FPS vs 15 FPS with LVGL labels - Sys API (original Tactility breakout.lua):
sys.log(msg)sys.clear(hexColor)sys.print(x,y,text,hexColor)– direct FB viadraw_text8sys.rect(x,y,w,h,hexColor,filled)– border or filledsys.circle(x,y,r,hexColor,filled)– midpoint + filled scansys.line(x0,y0,x1,y1,hexColor)sys.touch() -> pressed, tx, ty(touch mapped to 240 game space)sys.tone(note, duration, waveform, volume)→ SfxEngineplayNotesys.sfx(name)→ SfxEngineplay(SfxId)mapping: confirm, coin, hurt, gameover, levelup, brickhit, etc.
- Pico API compatibility (from
/Users/adolforeyna/Projects/pico-bare-metal/Adolfo/basic1/games/lua_examples):game.width(),height(),set_frame_updates(bool),exit(),game.varspersistent tablerenderer.clear(white),pixel(x,y,on),line(x0,y0,x1,y1,on,width),rect(x,y,w,h,on,filled),circle(x,y,r,on,filled),triangle(x0,y0,x1,y1,x2,y2,on,filled),text(x,y,txt,on),text_scaled(x,y,txt,on,scale)(scale 1-4)INPUTconstants:NONE=0, TOUCH_DOWN=1, TOUCH_MOVE=2, TOUCH_UP=3, BUTTON_0=4, BUTTON_1=5, FRAME_TICK=6, GESTURE=7- Event system: generates
TOUCH_DOWN/MOVE/UPfrom touch state +FRAME_TICKevery frame, callsupdate(event)returning bool redraw - Detection: if source contains
renderer.→ Pico mode, else sys mode
- Embedded games:
embedded_breakout_lua_src– fast breakout (ball 4.5 vs 2, paddle 0.25 spin) – defaultpico_games.hauto-generated with 17 games: 2048, air_hockey, asteroids, ball, breakout, counter, flappy_bird, lunar_lander, memory_match, pacman, pong, simon_says, snake, solitaire, tetris, tic_tac_toe – tested Pong runs at 17.5 FPS
- File loading: PSRAM buffer +
luaL_loadbufferto avoidluaL_loadfilestack issues, supports SD paths/sdcard/lua/*.lua
Symbol fixes
Previous missing: clearerr, clock, getenv, gmtime, setlocale, setvbuf, tmpfile, tmpnam, ungetc, lv_canvas_get_draw_buf, lv_draw_buf_invalidate_cache, lv_image_cache_drop.
Fixed by local stubs + avoiding unexported APIs, only lv_canvas_create, lv_canvas_set_buffer, lv_obj_invalidate, tt_lvgl_lock/unlock.
Performance
- Ball speed: 2 → 4.5 (2.25x faster), configurable in embedded string
- FPS: 15 → 17.5 FPS after direct FB text + dedicated game loop task
- Memory: internal low ~2-3KB (PSRAM used for FB and Lua heap), stable, no crash 20s+
Build / Install
cd /Users/adolforeyna/Projects/Tactility/apps
unset PYTHONPATH; unset PYTHONHOME
export IDF_PYTHON_ENV_PATH=/Users/adolforeyna/.espressif/python_env/idf5.3_py3.9_env
source /Users/adolforeyna/esp/esp-idf/export.sh
export TACTILITY_SDK_PATH=/Users/adolforeyna/Projects/Tactility/firmware/release/TactilitySDK
export ESP_IDF_VERSION=5.5
python tactility.py Apps/LuaGame build esp32s3 --local-sdk
curl -X PUT http://192.168.68.107/api/apps/install -F "file=@Apps/LuaGame/build/LuaGame.app"
curl -X POST "http://192.168.68.107/api/apps/run?id=one.tactility.luagame"
Porting Pico Games
Yes – all 17 examples from pico-bare-metal/Adolfo/basic1/games/lua_examples use:
game.varsstate machinerenderer.*monochrome API (boolean on/off → mapped to white/black RGB565)INPUT.TOUCH_*+FRAME_TICK
Our engine now implements compatibility shim in C (see l_renderer_*, l_game_*, INPUT table). Pong tested:
[LuaGame] using Pico test game: Pong (7538 bytes)
Pong initialized
FPS 17.5
Other games (snake, tetris, 2048, etc.) should work same – they are ~50-880 lines, no file I/O, pure math/table. To add selector UI, list pico_games[] via LVGL list and load selected src.
TODO
- Game selector UI (menu_wrapper + game_wrapper, hide/show)
- Copy pico examples to SD via USB MSC or bundle selector
- Audio mapping for Pico games (currently sys.sfx only, could map to renderer events)
- Scale canvas to full 320x480 or dynamic
game.width/height - Fix internal memory low warnings (reduce SfxEngine voices or Lua heap)