- Enable use_external=true with priority: solitaire, breakout, game, pong, snake, tetris - Fix is_pico detection to check source content before load (renderer./INPUT.) instead of game.vars heuristic that always true - Successfully uploaded solitaire.lua (24824 bytes) via /fs/upload?path=/sdcard/lua/solitaire.lua - Tested solitaire loads: 'loaded external /sdcard/lua/solitaire.lua 24824 bytes, pico=1, FPS 13.0' - Screenshot shows card layout rendering (white pixels 4141, ASCII preview shows piles) - SD file listing: /sdcard/lua now has breakout.lua and solitaire.lua - Ready for further pico games via same upload API
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)