- Migrates tactility-app-development from ~/.hermes/skills/ into .claude/skills/ for repo co-location - Adds AGENTS.md with local workstation context, hardware table, board-direct build/env wrapper (PYTHONPATH fix), ELF missing-symbol triage, app patterns (immersive, QVGA rail, tutorial 2-row, GameKit bubbling, screenshot rate-limit), and skill index Refs: personal Gitea mirror
8.3 KiB
AGENTS.md — Tactility Apps
This file is for AI agents (and humans) working in this repo. It documents local workflow, owned hardware, and in-repo skills.
Repo
Tactility external ELF apps — loaded at runtime via firmware symbol table. Each app: Apps/<Name>/manifest.properties + main/CMakeLists.txt + Source/*.c.
Upstream: https://github.com/TactilityProject/TactilityApps
Personal Gitea mirror: https://git.reynafamily.com/adolforeyna/tactility_apps (personal remote)
Dev board IP: 192.168.68.112 (S3, OS 0.8.0-dev, SDK 0.8.0-dev) — main deploy target. .111 also online. OS image is 0.8.0-dev so manifest should use sdk=0.8.0-dev or loader warns.
Local Workstation
- Host: M2 Air (14,2) macOS 26.5.2
- Project:
/Users/adolforeyna/Projects/Tactility/apps - ESP-IDF:
/Users/adolforeyna/esp/esp-idf, Python envidf5.3_py3.9_envat~/.espressif/python_env/idf5.3_py3.9_env - Tactility SDK:
/Users/adolforeyna/Projects/Tactility/firmware/release/TactilitySDK(built locally,--local-sdk)
Hardware
| Board | IP | Port | Notes |
|---|---|---|---|
| ES3C28P 2.8" color 240x320 | 192.168.68.112 |
/dev/cu.usbmodem101 |
Primary deploy target, 16MB flash, OCT PSRAM, SD for ELF assets |
| Waveshare RLCD 4.2" mono 400x300 ST7305 | 192.168.68.111? .1101 USB |
/dev/cu.usbmodem1101 |
Secondary, compact density, mono threshold 60, font 18 |
User shorthand: "ending in 112" → 192.168.68.112.
Build, Deploy, Run — Correct Env Wrapper
Hermes desktop Python 3.11 venv pollutes PYTHONPATH → tactility.py crashes with TypeError: unsupported operand type(s) for | (urllib3 uses X | Y 3.10+). Also idf.py crashes with pydantic_core mismatch.
Always:
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
# Build
$IDF_PYTHON_ENV_PATH/bin/python tactility.py Apps/MyApp clean
$IDF_PYTHON_ENV_PATH/bin/python tactility.py Apps/MyApp build esp32s3 --local-sdk --verbose
# → build/MyApp.app (tar), build/cmake-build-esp32s3/MyApp.app.elf
# Symbol check (0 missing = good, any = "Application failed to start: missing symbol" blue OK dialog)
xtensa-esp32s3-elf-nm -D Apps/MyApp/build/cmake-build-esp32s3/MyApp.app.elf | grep " U "
# or automated:
python .claude/skills/tactility-app-development/scripts/verify_symbols.py Apps/MyApp
# Install to device (dashboard API port 80, not dev port 6666)
$IDF_PYTHON_ENV_PATH/bin/python tactility.py Apps/MyApp install 192.168.68.112 esp32s3
# or dashboard directly:
curl -X PUT http://192.168.68.112/api/apps/install -F "file=@build/MyApp.app"
# Run
curl -X POST "http://192.168.68.112/api/apps/run?id=one.tactility.myapp"
# screenshot QA
curl -s http://192.168.68.112/api/screenshot -o /tmp/fb.png && open /tmp/fb.png
curl http://192.168.68.112/api/apps # list installed
# Dev server fallback (needs Developer mode enabled in Settings, port 6666)
# If 6666 refused → use dashboard PUT above
manifest.properties
[manifest]
version=0.1
[target]
sdk=0.8.0-dev
platforms=esp32s3,esp32p4
[app]
id=one.tactility.myapp
versionName=1.0.0
versionCode=1
name=My App
Always pass esp32s3 platform arg to install when only that ELF is built.
CMake Component Template
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
# optionally include shared libs:
# file(GLOB_RECURSE GAMEKIT Source/*.c* ../../Libraries/GameKit/Source/*.c* ...)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" "../../Libraries/GameKit/Include" ...
REQUIRES TactilitySDK esp_driver_i2s ...
)
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-format-truncation -Wno-unused-but-set-variable)
Keep app source as pure C (no auto, no lambdas in .c) or rename to .cpp.
ELF Loader — Missing Symbol Pattern
Tactility firmware exports symbols via ESP_ELFSYM_EXPORT / DEFINE_MODULE_SYMBOL in firmware/Modules/lvgl-module/source/symbols.c. If ELF references an unexported symbol → device modal Error / Application failed to start: missing symbol, heap still healthy (~31KB free) → NOT OOM.
Known missing on 0.8.0-dev:
lv_font_montserrat_14/18→ uselvgl_get_text_font(SMALL/DEFAULT/LARGE)viatactility/lvgl_fonts.hlv_obj_set_style_bg_grad_color/dir/stop,LV_OPA_95/5,LV_SYMBOL_STAR→ use solid colors, COVER/90lv_obj_set_style_max_width→ useLV_PCT(92)fixed percents for gutterslv_obj_set_scrollbar_mode,lv_arc_create(pre PR #496 on 0.7),lv_roller_createlv_anim_*→ exported since 0.6, safe for 0.7/0.8lv_binfont_create→ exported on 0.8.0-dev but VFS fragile, prefer embedded C fonts vialv_font_conv --format lvgl
If OOM not missing-symbol: heap total 310KB free 275B min_free 23 → 66 buttons case = too many LVGL objects, not PSRAM (ELF lives in PSRAM, LVGL objs internal heap only). Fix: reduce obj count, horizontal slide pattern.
App Patterns in This Fleet
| Pattern | Summary |
|---|---|
| Immersive full-screen | Single screen, header+toolbar hidden default, tap toggles chrome, LV_OBJ_FLAG_HIDDEN. No tt_lvgl_toolbar_create_for_app → saves 40px but need custom exit. |
| QVGA intro layout | Header + flex row = left scrollable column (GOAL+CONTROLS cards 90% width) + right 44px rail (play 38px accent + close 30px muted icons). Fixes clipped bottom on 320x198 usable. |
| Tutorial teach-by-play | Dungeon 2-row TUTORIAL_ROWS=2, floor 0→@+>, 1→@+b+$+>, real from 2. isTutorial() skip monster moves, renderRows + HIDDEN flag, re-center board. |
| Adaptive scaling | apply_verse_scaling() strlen→ font LARGE/DEFAULT/SMALL + line_space + pad + letter_spacing. Long (>350c) → compact small. |
| Book browser | Clean slide ~10 objs: [X left][slider flex][N/66 right], center LARGE 50% bigger tappable, swipe 28px threshold, anim 220-300ms via lv_anim_*, fade ref name. Avoid 66 buttons → OOM. |
| Asset splitting | 4.8MB XML bible → per-book .bin (NUL-joined verses) + .idx (BIBK header u16+u32+offsets) + books.json. tt_app_get_assets_path() + fallback /sdcard/bibles/. |
| Persistence | tt_app_get_user_data_child_path(app, "progress.txt", buf, len) → snprintf. Atomic write on change. |
| Exit paths | (1) InputKind::Cancel/Menu Q/ESC → tt_app_stop(), (2) X button 36×28, (3) long-press LV_EVENT_LONG_PRESSED → pause overlay Resume/Exit. |
| GameKit Input | attachInputToCurrent() — re-attach GameKit::attachInput(root, scene) after container changes, non-clickable info boxes so taps bubble, `CLICKABLE |
| Screenshot rate-limit | /api/screenshot 2s+ between calls or ConnectionResetError 54 heap spike. |
In-Repo Skill
| Skill | When to use |
|---|---|
tactility-app-development |
ELF app build/env wrapper, missing-symbol loader triage, asset packaging (per-book binary), immersive LVGL UI, toolbar-less app exit, GameKit/GameInput bubbling pitfall, screenshot rate-limit, QVGA intro rail vs wide buttons, tutorial level pattern, audio apps RLCD pin conflict |
Key references
tactility-app-development/references/build-env.md— Hermes venv PYTHONPATH error transcript + fixtactility-app-development/references/symbols.md— exported vs missing list, OOM vs missing-symbol diagnosis, heap sizestactility-app-development/references/deploy.md— dashboard vs :6666 install, PUT vs POST, platform argtactility-app-development/references/immersive-ui.md— editorial v4 warm #F2F0E8, gap 10, fade 220/300mstactility-app-development/references/intro-fullscreen.md— fullscreen no-toolbar, GameKit bubbling fix, QVGA rail, v7 GOAL+CONTROLS + iconstactility-app-development/references/pocket-dungeon.md— GameKit+SfxEngine GLOB_RECURSE, 112 discovery, 9×7 grid, tutorial 2-rowtactility-app-development/references/black-bar-crop.md— header 44→36 TRANSP, Georgia down-tier LARGE→22, 15px lifttactility-app-development/references/sdk-mismatch-missing-symbol.md— sdk bump +max_widthremovaltactility-app-development/scripts/verify_symbols.py— undefined symbol checker
Load via skill_view(name='tactility-app-development') in agent sessions.