Files
tactility_apps/Apps/LuaGame
Adolfo 4e4e815995 feat(LuaGame): fullscreen 320x480, no toolbar, on-demand refresh for solitaire
- Manifest: HideStatusBar flag, version 0.2.0
- app_on_show: remove toolbar, get display resolution via lv_display_get_horizontal_resolution, allocate 320x480 framebuffer in PSRAM
- Dynamic game_w/h, fb_w/h in AppCtx, set_px/fill_rect use dynamic size
- touch_event_cb uses dynamic max_w/h
- On-demand refresh: l_game_set_frame_updates sets wants_frame_updates flag
  - Pico games without set_frame_updates (solitaire) only redraw on touch events
  - Continuous games (breakout, pong) with set_frame_updates(true) use FRAME_TICK every 16ms
  - Initial draw after init for on-demand games
  - FPS tracking only when redraw occurs
- Solitaire optimized: LEFT 37->4, GAP 10->4, TOP_Y 30->4, TAB_Y 98->60, removed draw_terminal_chrome (outer frame + title bar + window controls)
- Uploaded optimized solitaire via /fs/upload?path=/sdcard/lua/solitaire.lua (24430 bytes)
- Tested fullscreen: display 320x480, no toolbar, screenshot shows chrome removed, white pixels 3197 (cards only)
- Logs show no continuous FPS spamming for solitaire (on-demand)
2026-07-31 13:12:32 -04:00
..

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_invalidate with tt_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 via draw_text8
    • sys.rect(x,y,w,h,hexColor,filled) – border or filled
    • sys.circle(x,y,r,hexColor,filled) – midpoint + filled scan
    • sys.line(x0,y0,x1,y1,hexColor)
    • sys.touch() -> pressed, tx, ty (touch mapped to 240 game space)
    • sys.tone(note, duration, waveform, volume) → SfxEngine playNote
    • sys.sfx(name) → SfxEngine play(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.vars persistent table
    • renderer.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)
    • INPUT constants: 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/UP from touch state + FRAME_TICK every frame, calls update(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) – default
    • pico_games.h auto-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_loadbuffer to avoid luaL_loadfile stack 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.vars state machine
  • renderer.* 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)