chore: move app skill in-repo + AGENTS.md
Main / Build (BookPlayer) (push) Has been cancelled
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
Main / Build (BookPlayer) (push) Has been cancelled
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
- 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
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# RLCD Audio Apps — Mp3Player & VoiceRecorder Fixes (2026-07-12)
|
||||
|
||||
## Context
|
||||
Waveshare RLCD 4.2" uses ES8311 DAC @0x18 + ES7210 mic ADC @0x40, MCLK=12.288MHz GPIO16, BCLK=9 WS=45 TX=8 RX=10 AMP=46 HIGH.
|
||||
Working reference: `MicroPython/test1/Screen/lib/audio_util.py` + `board_config.py` WAVESHARE_RLCD profile.
|
||||
|
||||
## Mp3Player Pitfalls
|
||||
|
||||
### 1. Task Stack & Priority
|
||||
- Default `xTaskCreate(..., 4096, 5)` → stack overflow under minimp3 + SD + I2S + LVGL.
|
||||
- Log: `i2s_alloc_dma_desc failed` sometimes masked as OOM, but also task crashes.
|
||||
- Fix: `6144, prio 6`.
|
||||
|
||||
### 2. taskYIELD Causing Repetition
|
||||
```c
|
||||
// Before: decode one MP3 frame (1152 samples @16k = 72ms) then:
|
||||
taskYIELD(); // gives CPU to LVGL but DMA empties → repeats last buffer
|
||||
// After: remove YIELD, loop tight, let I2S write block (250ms timeout) drive pacing
|
||||
```
|
||||
- DMA 4x120 = 30ms total, smaller than frame time → underrun → repetition. Increased driver DMA to 6x160 (~60ms) + removed YIELD.
|
||||
|
||||
### 3. Sample Rate Assumptions
|
||||
- RLCD files in repo are 16k mono (faith-mysterious-garden/page001.mp3 31652 bytes, 16k 1ch).
|
||||
- `I2S configured: 16000 Hz, 1 channels` — must match ES8311 boot init that already programs 16k exact.
|
||||
- Reclock hook that rewrites ES8311 0x06/0x07/0x08 during I2S start causes pop. For 16k, skip entirely.
|
||||
- MCLK multiple: 16k*768=12.288MHz exact, 32k*384, 48k*256. Default SDK 256 gives 4.096MHz for 16k → robot sound. See `tactility-firmware/references/rlcd-audio-fix.md`.
|
||||
|
||||
## VoiceRecorder Pitfalls
|
||||
|
||||
### Mono vs Stereo for ES7210
|
||||
Working MP:
|
||||
```python
|
||||
i2s = I2S(1, sck=9, ws=45, sd=10, mode=I2S.RX, rate=16000, bits=16, format=I2S.STEREO)
|
||||
# then mono extract: for i in 0..n step 4: mono[j]=buf[i:i+2]
|
||||
```
|
||||
- ES7210 outputs stereo (2 mics) even if you want mono file.
|
||||
- VoiceRecorder previously requested `channel_right = NONE` (mono slot mode) → driver expects 1 slot but codec sends 2 → low pitch mic-like.
|
||||
- Fix: Request `channel_right=1` (STEREO), read `AUDIO_BUF_SIZE` (4096) stereo, downmix left channel in-place backward to avoid overwrite:
|
||||
```c
|
||||
size_t mono_bytes = bytes_read/2;
|
||||
for(int src=bytes_read-4, dst=mono_bytes-2; src>=0; src-=4, dst-=2){
|
||||
buf[dst]=buf[src]; buf[dst+1]=buf[src+1];
|
||||
}
|
||||
fwrite(buf,1,mono_bytes,f);
|
||||
```
|
||||
- WAV header stays mono (1 ch, byterate 32000) – data correctly pitched.
|
||||
|
||||
### TDM vs STD
|
||||
- Other boards (m5stack-stackchan, tab5) use TDM 4-slot for ES7210 via `i2s_controller_set_rx_tdm_config`.
|
||||
- RLCD working MP uses STD stereo, not TDM. Keep STD for RLCD. TDM path DMA also needs reduction (was 8x512 → 4x120) or OOM.
|
||||
|
||||
## Firmware / Logging Interaction
|
||||
|
||||
### File Lock Flood
|
||||
`File.cpp:getLock()` logged `WARN File lock function not set!` on every `listDirectory()` – Tactility scans `/sdcard` every sec → 1Hz UART spam.
|
||||
- UART ISR at 115200 contends with I2S GDMA ISR → jitter → small noises.
|
||||
- Fix: once-only warning + listDir INFO→DEBUG.
|
||||
|
||||
### I2S Pin Logs
|
||||
`esp32_i2s: Configuring I2S pins...` logged INFO on every play → same jitter.
|
||||
- Fix: LOG_D.
|
||||
|
||||
### Verification Checklist
|
||||
- Play 16k mono MP3: no pop at start, no robot, no repetition.
|
||||
- Log after fix: no `File lock flood`, no `i2s_alloc_dma_desc failed`.
|
||||
- Record WAV, download, play on PC: 16k mono correct pitch, not low/mic-like.
|
||||
- Serial for voice: `I2S configured: 16000 Hz, 1 channels` for play, stereo read for record is internal.
|
||||
|
||||
## References
|
||||
- `tactility-firmware/references/rlcd-audio-fix.md` – GPIO5 conflict, correct pins, DMA OOM, MCLK, ES8311 reclock
|
||||
- MicroPython working code: `~/Projects/MicroPython/test1/Screen/lib/board_config.py` WAVESHARE_RLCD
|
||||
@@ -0,0 +1,52 @@
|
||||
# v6 Black Bar Crop Fix + 15px Lift Correction — July 12 BibleVerse on .129 final 2142871
|
||||
|
||||
**Symptom:**
|
||||
Vision: "black bar on the top cropping the text" — Habakkuk screenshots 4539 bytes showed 44px dark bar `#15151F COVER` + border overlapping center column, cutting first line. Also "reduce text size a bit for this font" — Georgia Italic 26pt wider than Montserrat LARGE. Then "main text can be moved a bit up 30px, seems padding favoring empty space on top." → corrected "We sent the text 30px up, but I think it should had been 15".
|
||||
|
||||
**Root cause header:**
|
||||
```c
|
||||
lv_obj_set_size(hb, LV_PCT(100), 44); bg #15151F COVER border 1 #232338
|
||||
center_col lv_obj_center size 90%x80%
|
||||
verse pad_all 6 font 26
|
||||
```
|
||||
|
||||
**Fix v6 final 15px lift:**
|
||||
1. Header transparent same root no border smaller:
|
||||
```c
|
||||
size 36 not 44; bg #0E0E14 same as parent root was #15151F; bg_opa TRANSP was COVER; border 0 was 1; pad_ver 4 was 0
|
||||
```
|
||||
2. Ctrl bar also TRANSP editorial: bg #0E0E14 TRANSP border 0 was #15151F COVER + top border
|
||||
3. Center shift + lift correction:
|
||||
```c
|
||||
// was center
|
||||
size 90%x82% // 80->82
|
||||
align CENTER 0,8 // +8 down to clear statusbar
|
||||
// then 30px up request -> 0,-22
|
||||
// then correction 15px up -> 0,-7 = +8 base -15 = 15px up balanced final
|
||||
// pad verse 4 not 6
|
||||
```
|
||||
Vision Zeph 1:3 after -7: "balanced now, optical center correct, prevents sitting on buttons, 1.5x padding above controls, no excessive top space" PASS. No black bar Hab 2:9 "No extra black bar overlapping, fully visible".
|
||||
|
||||
4. Georgia Italic down-tier wider than Montserrat:
|
||||
```c
|
||||
Before: LARGE->26 DEFAULT->20 SMALL->16
|
||||
After v6: LARGE->22 was 26, DEFAULT->18 was 20, SMALL->16, ultra <40c still 26 via resolve_verse_font_ultra
|
||||
pads tighter: <40 6/20 not 10/28, <100 4/16 not 8/24, <200 3/14 not 6/20, <350 2/12 not 4/16, 350+ 1/8 not 2/12
|
||||
```
|
||||
|
||||
5. Menu auto-show 10s + book anim: See immersive-ui.md and book-browser.md v6 final. Timer gotcha: `t->user_data` incomplete typedef error in SDK 0.8.0-dev → must use `lv_timer_get_user_data(t)` (exported 382/388). Verified .129 menu show 8683 bytes.
|
||||
|
||||
**Verification final 2142871:**
|
||||
- Build 4.3M `tactility.py build esp32s3 --local-sdk` ok, ELF 0 missing
|
||||
- Install .129: heap `46215-47591 free largest 20480 psram 4810396` stable, ss `5335 serif` vs `3991 sans` vs `8683 menu visible` vs `6882 immersive`
|
||||
- Vision checks: Hab 2:9 no crop, Zeph 1:3 balanced 15px lift, menu chrome visible at 0.8s then hidden showing premium cinematic
|
||||
- Screenshot rate-limit >2s between to avoid `ConnectionResetError 54` heap spike from encoder
|
||||
|
||||
**Lessons:**
|
||||
- Avoid opaque header on immersive — use root bg TRANSP so no crop when mistakenly visible
|
||||
- When swapping Mont→Georgia reduce one tier — 26pt serif ~22pt sans visual
|
||||
- Lift correction: base CENTER 0,8 too low empty top → 30px up → user corrected 15px up → -7 final, always confirm lift target in vision
|
||||
- Timer incomplete typedef: use getter, not direct field
|
||||
- Screenshot burst OOM — wait 2s+
|
||||
|
||||
See immersive-ui.md v6 final + pretty-font.md v6 final + book-browser.md v6 final. Beautiful per user final approval.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Book Browser — Horizontal Slide + Clean v4 + Animated v6 Final (arc-free, low-RAM, beautiful)
|
||||
|
||||
## Problem history
|
||||
|
||||
1. **66-button list** — 66× `lv_button_create` + flex rows + labels = 132-264 LVGL objs.
|
||||
- `GET /api/sysinfo`: `heap free 275B total 310KB min_free 23 largest_block 23` → OOM.
|
||||
- Symptom: `PUT /api/apps/install 500 failed to create temp directory`, screenshot `500 25`.
|
||||
- After self-crash: heap 33KB, install `200 ok` again.
|
||||
|
||||
2. **`lv_arc` rotary** — center name changes rotating ring, 6 objs ideal.
|
||||
- Build 0 missing on 0.8.0-dev SDK (`symbols.c` PR #496 `dff93cb6`), but device `0.7.0-dev` (192.168.68.112) missing: blue dialog `Error / missing symbol / OK`, heap `31779` healthy → loader failure not OOM.
|
||||
- `git log -S lv_arc_create` → added after 0.7 release. Must be arc-free for 0.7 fleet.
|
||||
- `lv_roller` not exported on 0.7/0.8.
|
||||
|
||||
3. **v3 horizontal slide + slider** — works on 0.7, ~12 objs, heap `10943 free largest 7168` (112), `54355 free` on 129 0.8.0-dev after editorial.
|
||||
|
||||
## v4 Clean (July 12) — User: remove Go, card, top bar; larger 50% name; number 39/66 right of slider; cancel left of slide
|
||||
|
||||
- Removed: top header `Books — swipe`, Go button 200x36, card bg `15151F` radius 18
|
||||
- New layout:
|
||||
- `center_wrap` full-screen `PCT(100)` pad_top 0 pad_bottom 64 (was 52/88), transparent, CLICKABLE + swipe `PRESSED/PRESSING/RELEASED/PRESS_LOST`
|
||||
- Left arrow 44x44 radius22 `1E1E2E` at `LEFT_MID 8,0`, right arrow same `RIGHT_MID -8,0`, click steps ±1
|
||||
- Center `mid_wrap` 68% width `SIZE_CONTENT`, transparent, gap 10, flex column center, CLICKABLE → `on_book_center_click` jumps (tap name does Go)
|
||||
- `book_center_name` `LARGE` (was DEFAULT, 50% bigger), width `100%`, `DOTS`, center aligned, pretty `georgia_regular_24` serif
|
||||
- `book_center_verses` small dim `5A5A78`
|
||||
- Bottom bar `100%x56` `15151F` top border `232338`, row `SPACE_BETWEEN` center, gap 10, pad hor10 ver8
|
||||
- `[X cancel 36x32 radius8 252538 left] [slider flex_grow 1 height8 range 0..65 bg 232338 ind 4A4A6E knob E8E6E0] [num label 1/66 right 8A8AAA small]`
|
||||
- Object count ~10 (vs 12 v3, vs 264 v1) → heap `54471→54355` stable on 129, `PSRAM 4.9MB`
|
||||
|
||||
## v6 Final Animated (July 12 beautiful build 2142871) — User: "add animation to the bible book selection. Also let's start the app with the menu showing, but make the menu UI auto hide after 10s." + lift correction 30→15px + beautiful approval
|
||||
|
||||
**Animation added**:
|
||||
```c
|
||||
static void book_y_anim_cb(void* var,int32_t v){ lv_obj_set_y((lv_obj_t*)var,v); }
|
||||
static void book_fade_anim_cb(void* var,int32_t v){ lv_obj_set_style_text_opa((lv_obj_t*)var,v,0); }
|
||||
|
||||
set_book_browser_visible(true):
|
||||
ctx->book_dial_idx = cur_book_idx; book_dial_update_center(ctx);
|
||||
remove FLAG_HIDDEN
|
||||
// pop center mid_wrap y 20->0 260ms ease_out
|
||||
mid_wrap y = 20; lv_anim 20->0 260ms ease_out via book_y_anim_cb
|
||||
// fade name OPA_20->COVER 220ms ease_out via book_fade_anim_cb
|
||||
book_dial_update_center: on swipe/slider change name fade 60->255 180ms ease_out
|
||||
```
|
||||
|
||||
**Menu auto-show 10s** (see immersive-ui.md): `chrome_auto_hide_timer` field + `chrome_auto_hide_cb` uses `lv_timer_get_user_data` not `t->user_data` (incomplete typedef error), `onShowApp` `set_chrome_visible(true)` + `schedule 10000`, `toggle_ui` re-arms 10s, `toggle_book_browser` deletes timer, `onHide` deletes. Verified 8683 bytes menu visible `Zephaniah 1:3 / 31094` + bottom pills X/List/Prev/Pause/Next at 0.8s after run, 6882 immersive hidden at ~2s (configured 10s, screenshot race). "This is beatiful, well done."
|
||||
|
||||
**Lift correction**: `CENTER 0,8` too low → `0,-22` 30px up → user corrected "should had been 15" → `0,-7` = `+8 base -15` = 15px up balanced, vision "optical center correct, prevents sitting on buttons".
|
||||
|
||||
## AppCtx fields v6 final
|
||||
```c
|
||||
lv_obj_t* book_browser;
|
||||
lv_obj_t* book_center_name; // LARGE 50% bigger, directly on bg, tappable jumps, georgia_regular_24
|
||||
lv_obj_t* book_center_num; // bottom right 39/66
|
||||
lv_obj_t* book_center_verses;
|
||||
lv_obj_t* book_slider;
|
||||
int book_dial_idx;
|
||||
int book_drag_start_x;
|
||||
bool book_dragging;
|
||||
bool book_browser_visible;
|
||||
lv_timer_t* chrome_auto_hide_timer; // v6 auto-hide
|
||||
bool ui_visible;
|
||||
```
|
||||
|
||||
Center update syncs slider + number + fade:
|
||||
```c
|
||||
static void book_dial_update_center(AppCtx* ctx){
|
||||
BookInfo* b=&ctx->books[ctx->book_dial_idx];
|
||||
// fade anim 60->255 180ms
|
||||
lv_obj_set_style_text_opa(center_name,100,0);
|
||||
lv_anim_t an; lv_anim_init(&an); lv_anim_set_var(&an,center_name); lv_anim_set_values(&an,60,255); lv_anim_set_duration(&an,180);
|
||||
lv_anim_set_exec_cb(&an,book_fade_anim_cb); lv_anim_set_path_cb(&an,lv_anim_path_ease_out); lv_anim_start(&an);
|
||||
lv_label_set_text(book_center_name,b->bname);
|
||||
lv_obj_set_style_text_font(book_center_name,resolve_book_font(ctx),0); // georgia_regular_24 50% bigger
|
||||
snprintf(num,"%2d / %d",b->bnumber,book_count); lv_label_set_text(book_center_num,num);
|
||||
snprintf(vers,"%d verses",b->verse_count); lv_label_set_text(book_center_verses,vers);
|
||||
lv_slider_set_value(slider,dial,LV_ANIM_OFF);
|
||||
}
|
||||
```
|
||||
|
||||
Swipe 28px per book via `lv_indev_get_point/active`.
|
||||
|
||||
## PSRAM vs heap
|
||||
- `CONFIG_ELF_LOADER_LOAD_PSRAM=y` → app `.text/.rodata` SPIRAM 4.2-4.3MB ELF, free 4.8-5.2MB
|
||||
- LVGL objects internal heap 310KB only. 264 objs OOM, 10 objs safe, 13 objs with anim safe 46-47KB
|
||||
|
||||
## Compat table v6 final
|
||||
| widget | 0.7.0-dev .112 | 0.8.0-dev .129 | notes |
|
||||
|---|---|---|---|
|
||||
| `lv_arc_*` | MISSING | exported PR #496 | true rotary if fleet 0.8+ |
|
||||
| `lv_roller_*` | MISSING | MISSING | not exported |
|
||||
| `lv_slider_*` | OK | OK | scrub |
|
||||
| `lv_indev_get_point/active` | OK | OK | swipe 28px |
|
||||
| `lv_anim_*` | OK | OK | fade 220/300, book slide 260ms |
|
||||
| `lv_timer_get_user_data` | OK | OK | auto-hide timer, need getter not direct access |
|
||||
| `georgia_italic_*` embedded | OK via fallback | OK 4.3M PSRAM | pretty serif |
|
||||
|
||||
## Verification v6 final beautiful
|
||||
- Heap 275B OOM → 10943 .112 → 54471→46215 .129 stable with fonts+anim
|
||||
- Screenshots: reading 3756-4347, editorial fade 3991, menu show 8683, immersive 6882, error dialog 2755 missing symbol
|
||||
- Boards .112 0.7.0 33KB, .129 0.8.0 46-54KB
|
||||
- Build 2142871 editorial v6 beautiful approved
|
||||
@@ -0,0 +1,92 @@
|
||||
# Build Env — Transcript & Fix
|
||||
|
||||
## Error 1: Hermes venv pollution — TypeError on `X | Y`
|
||||
|
||||
```
|
||||
File .../hermes-agent/venv/lib/python3.11/site-packages/requests/__init__.py
|
||||
import urllib3
|
||||
File .../urllib3/_base_connection.py
|
||||
bytes, typing.IO[typing.Any], typing.Iterable[bytes | str], str
|
||||
TypeError: unsupported operand type(s) for |: 'type' and 'type'
|
||||
```
|
||||
IDF Python is 3.9.6 (no `X | Y` unions). Hermes venv injects `PYTHONPATH` with Python 3.11's `urllib3` that uses 3.10+ syntax. Affects `tactility.py` which imports `requests`.
|
||||
|
||||
### Fix
|
||||
```bash
|
||||
. /Users/adolforeyna/esp/esp-idf/export.sh
|
||||
export TACTILITY_SDK_PATH=/Users/adolforeyna/Projects/Tactility/firmware/release/TactilitySDK
|
||||
export PYTHONPATH="" # critical
|
||||
/Users/adolforeyna/.espressif/python_env/idf5.3_py3.9_env/bin/python tactility.py Apps/BibleVerse build esp32s3 --local-sdk
|
||||
```
|
||||
|
||||
## Error 2: pydantic_core shadowing — idf.py itself crashes
|
||||
|
||||
```
|
||||
Cannot import module "pydantic_core._pydantic_core".
|
||||
Please use idf.py only in an ESP-IDF shell environment.
|
||||
ModuleNotFoundError: No module named 'pydantic_core._pydantic_core'
|
||||
```
|
||||
Hermes venv 3.11's `pydantic_core` has `_pydantic_core.cpython-311-darwin.so` built for 3.11. IDF venv 3.9/3.10 has `.cpython-39/310.so`. Because `PYTHONPATH` includes Hermes site-packages, Python tries to load 3.11 `.so` under 3.9/3.10 → fails.
|
||||
|
||||
Same error manifests as:
|
||||
```
|
||||
'/.../idf5.3_py3.9_env/bin/python' is currently active ... project was configured with '/.../idf5.3_py3.10_env/bin/python'. Run 'idf.py fullclean'
|
||||
```
|
||||
Solution: clear env, set IDF_PYTHON_ENV_PATH, fullclean.
|
||||
|
||||
### Full Fix (this session — ES3C28P firmware build)
|
||||
|
||||
```bash
|
||||
cd /Users/adolforeyna/Projects/Tactility/firmware
|
||||
unset PYTHONPATH; unset PYTHONHOME
|
||||
export IDF_PYTHON_ENV_PATH=/Users/adolforeyna/.espressif/python_env/idf5.3_py3.9_env
|
||||
. /Users/adolforeyna/esp/esp-idf/export.sh
|
||||
|
||||
# Verify fix:
|
||||
$IDF_PYTHON_ENV_PATH/bin/python -c "import pydantic_core; print('ok')"
|
||||
idf.py --version # ESP-IDF v5.3.2-dirty
|
||||
|
||||
python device.py es3c28p # regenerates sdkconfig
|
||||
idf.py fullclean # when switching python version
|
||||
idf.py build # -> build/Tactility.bin
|
||||
idf.py -p /dev/cu.usbmodem101 flash # flash family board
|
||||
```
|
||||
|
||||
Build output for ES3C28P (16MB, 80MHz, partitions-16mb-with-sd.csv):
|
||||
```
|
||||
Wrote 2863136 bytes (1653045 compressed) at 0x00010000 in 19.9s (1149.4 kbit/s)
|
||||
bootloader 0x0, partition-table 0x8000, Tactility.bin 0x10000, system.bin 0x410000
|
||||
```
|
||||
|
||||
## Error 3: IDF_PATH unset in subshell
|
||||
`zsh -c 'python3 tactility.py...'` loses `IDF_PATH`. Always source inside same shell or use wrapper script.
|
||||
|
||||
## Error 4: format-truncation as error
|
||||
IDF adds `-Werror=all`. `snprintf(dst, 320, "%s/bible", assets_path)` flagged even though 320B is generous.
|
||||
Fix: `target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-format-truncation -Wno-unused-but-set-variable)`
|
||||
|
||||
## Correct build commands
|
||||
|
||||
Firmware core:
|
||||
```
|
||||
cd /Users/adolforeyna/Projects/Tactility/firmware
|
||||
unset PYTHONPATH; unset PYTHONHOME
|
||||
export IDF_PYTHON_ENV_PATH=/Users/adolforeyna/.espressif/python_env/idf5.3_py3.9_env
|
||||
. /Users/adolforeyna/esp/esp-idf/export.sh
|
||||
python device.py es3c28p
|
||||
idf.py build
|
||||
idf.py -p /dev/cu.usbmodem101 flash
|
||||
```
|
||||
|
||||
ELF app (BibleVerse):
|
||||
```
|
||||
cd /Users/adolforeyna/Projects/Tactility/apps
|
||||
. ~/esp/esp-idf/export.sh > /dev/null
|
||||
env -u PYTHONPATH /Users/adolforeyna/.espressif/python_env/idf5.3_py3.9_env/bin/python tactility.py Apps/BibleVerse build esp32s3 --local-sdk
|
||||
# => build/BibleVerse.app 4.2MB, ESP32S3 ELF 17K
|
||||
```
|
||||
|
||||
## Related Files
|
||||
- `/Users/adolforeyna/Projects/Tactility/board_compilation_learnings.md` — full v5.3.2 compatibility fixes (I2C backport, missing cstring, etc.)
|
||||
- This session also fixed wrong board: `waveshare-esp32-s3-rlcd` flashed onto color `es3c28p`. Always `grep CONFIG_TT_DEVICE_ID sdkconfig`.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Deploy & Run on Device (v0.7+ Dashboard)
|
||||
|
||||
## Board Discovery (.112 case)
|
||||
|
||||
User said "ip ending on 112". Local subnet found via `ifconfig` -> `192.168.68.114/24`, so target is `192.168.68.112`.
|
||||
|
||||
Discovery recipe:
|
||||
```bash
|
||||
arp -a | grep 112
|
||||
# ping sweep
|
||||
for cand in 192.168.68.112 192.168.1.112 192.168.0.112; do
|
||||
ping -c1 -W 500 $cand
|
||||
curl -m2 -s http://$cand/ | head
|
||||
done
|
||||
```
|
||||
|
||||
Found: port 80 responds with HTTP 302 -> `/dashboard.html`. Port 6666 (old dev port) Connection refused.
|
||||
|
||||
## Install Endpoint Change
|
||||
|
||||
`tactility.py` (tool v3.5.1) still tries `POST http://<ip>:6666/app/install`. That requires Developer mode enabled on device (Settings > Development > Enable dev server). When disabled, install fails with:
|
||||
|
||||
```
|
||||
❌ Install request failed: HTTPConnectionPool(host='192.168.68.112', port=6666): ... Connection refused
|
||||
```
|
||||
|
||||
Firmware v0.7.0-dev Dashboard is on port 80 with new API:
|
||||
|
||||
- Discovered via browser devtools / `document.documentElement.innerHTML` JS fetch search:
|
||||
- `GET /api/apps` -> list
|
||||
- `PUT /api/apps/install` multipart field `file`
|
||||
- `POST /api/apps/run?id=<appId>`
|
||||
- `POST /api/apps/uninstall?id=<appId>`
|
||||
- `/api/sysinfo`, `/api/screenshot`, etc.
|
||||
|
||||
### Working install via curl / python
|
||||
|
||||
```bash
|
||||
curl http://192.168.68.112/api/apps | jq
|
||||
|
||||
# PUT, not POST
|
||||
curl -X PUT http://192.168.68.112/api/apps/install \
|
||||
-F "file=@build/BibleVerse.app;type=application/octet-stream"
|
||||
# => 200 ok
|
||||
```
|
||||
|
||||
Python:
|
||||
|
||||
```python
|
||||
import requests, pathlib
|
||||
ip="192.168.68.112"
|
||||
path="build/BibleVerse.app"
|
||||
with open(path,'rb') as f:
|
||||
r=requests.put(f"http://{ip}/api/apps/install",
|
||||
files={'file': ('BibleVerse.app', f, 'application/octet-stream')},
|
||||
timeout=120)
|
||||
print(r.status_code, r.text[:500])
|
||||
# run
|
||||
requests.post(f"http://{ip}/api/apps/run?id=one.tactility.bibleverse", timeout=10)
|
||||
```
|
||||
|
||||
## Platform Arg Required
|
||||
|
||||
`tactility.py Apps/MyApp install <ip>` without platform tries all `manifest platforms` (esp32s3,esp32p4) -> fails if only esp32s3 ELF built:
|
||||
|
||||
```
|
||||
❌ ELF file not built for esp32p4
|
||||
```
|
||||
|
||||
Fix: always pass platform explicitly matching what you built:
|
||||
|
||||
```bash
|
||||
tactility.py Apps/BibleVerse install 192.168.68.112 esp32s3
|
||||
# or
|
||||
tactility.py Apps/BibleVerse build esp32s3,esp32p4 --local-sdk # build all first
|
||||
```
|
||||
|
||||
## Browser Verification Recipe
|
||||
|
||||
Use Hermes browser tools:
|
||||
```js
|
||||
// navigate to http://<ip>/dashboard.html -> Apps tab
|
||||
document.documentElement.innerHTML.match(/\/api\/apps[^\s"']*/g)
|
||||
// returns ["/api/apps", "/api/apps/run?id=", "/api/apps/uninstall?id=", "/api/apps/install"]
|
||||
// inline scripts contain:
|
||||
function installAppFile(file) { fetch('/api/apps/install', {method:'PUT', body: formData}) }
|
||||
```
|
||||
|
||||
This avoids guessing old docs.
|
||||
|
||||
## Checklist After Install
|
||||
|
||||
- [ ] GET /api/apps contains your app id
|
||||
- [ ] POST /api/apps/run?id= -> 200 ok (app shows on device)
|
||||
- [ ] If install via dashboard UI: drag .app onto upload zone works same as PUT
|
||||
@@ -0,0 +1,67 @@
|
||||
# Immersive Single-Verse UI — BibleVerse pattern (v6 final 15px lift + menu auto-show)
|
||||
|
||||
From BookPlayer + BibleVerse: fullscreen verse, chrome now auto-shows 10s on start then hides, tap toggles, adaptive scaling + editorial + fade + embedded Georgia Italic serif + clean book browser + animations.
|
||||
|
||||
## v6 Final July 12 — Beautiful build 2142871
|
||||
|
||||
User: "We sent the text 30px up, but I think it should had been 15" → corrected -22 → -7 (15px up). Also "It seems as if there is a black bar on the top cropping the text. Also it seems we need to reduce the text size a bit for this font." + "First, let's save the learnings about UI and Fonts on the app skill, and then let's also add animation to the bible book selection. Also let's start the app with the menu showing, but make the menu UI auto hide after 10s." + "This is beatiful, well done."
|
||||
|
||||
**Black bar crop**: Header `44px bg #15151F COVER border 1 #232338` overlapping verse. Fix `36px bg 0x0E0E14 TRANSP 0` same as root, border 0, pad ver 4. Ctrl bar TRANSP. Vision Hab 2:9 "No extra black bar overlapping, fully visible" PASS.
|
||||
|
||||
**Georgia sizing down**: Georgia Italic wider than Montserrat → LARGE→22 was 26, DEFAULT→18 was 20, SMALL→16, ultra <40c still 26 via `resolve_verse_font_ultra`. Pads tighter `6/4/3/2/1` was 10/8/6/4/2. Heap 47591 stable.
|
||||
|
||||
**Lift correction 30px→15px**: Was `CENTER 0,8` too low (empty top). 30px attempt `0,-22`. User corrected to 15px → `0,-7` = `+8 base -15`. Vision Zeph 1:3 "balanced now, optical center correct, prevents sitting on buttons, 1.5x padding above controls" PASS. 8683 bytes menu visible, 6882 immersive.
|
||||
|
||||
**Menu auto-show 10s**: Previously hidden by default. Now:
|
||||
```c
|
||||
typedef struct { lv_timer_t* chrome_auto_hide_timer; bool ui_visible; } AppCtx;
|
||||
static void chrome_auto_hide_cb(lv_timer_t* t){
|
||||
AppCtx* ctx = (AppCtx*)lv_timer_get_user_data(t); // NOT t->user_data incomplete -> invalid use error, must use getter (exported 382/388)
|
||||
if(ctx->book_browser_visible) return;
|
||||
if(ctx->ui_visible) set_chrome_visible(ctx,false);
|
||||
lv_timer_delete(t); ctx->chrome_auto_hide_timer=NULL;
|
||||
}
|
||||
static void schedule_chrome_auto_hide(AppCtx* ctx,uint32_t ms){
|
||||
if(ctx->chrome_auto_hide_timer){ lv_timer_delete(ctx->chrome_auto_hide_timer); ctx->chrome_auto_hide_timer=NULL; }
|
||||
ctx->chrome_auto_hide_timer = lv_timer_create(chrome_auto_hide_cb, ms, ctx);
|
||||
}
|
||||
onShowApp: set_chrome_visible(&g_ctx,true); schedule_chrome_auto_hide(&g_ctx,10000);
|
||||
toggle_ui: if new_vis schedule 10s else delete; toggle_book_browser deletes timer; onHide deletes.
|
||||
```
|
||||
Verified .129 192.168.68.129: 0.8s after run 8683 bytes menu visible top `Zephaniah 1:3 / 31094` + bottom pills X/List/Prev/Pause/Next "premium cinematic Audible-like" -> ~2s 6882 hidden (timer + screenshot heap race, configured 10000). Heap 46215-47399 stable PSRAM 4.8MB.
|
||||
|
||||
**Book browser animation**:
|
||||
```c
|
||||
static void book_y_anim_cb(void* var,int32_t v){ lv_obj_set_y((lv_obj_t*)var,v); }
|
||||
static void book_fade_anim_cb(void* var,int32_t v){ lv_obj_set_style_text_opa((lv_obj_t*)var,v,0); }
|
||||
set_book_browser_visible(true): remove FLAG_HIDDEN; mid_wrap y 20->0 260ms ease_out + name fade OPA_20->COVER 220ms
|
||||
book_dial_update_center: name fade 60->255 180ms on swipe/slider change
|
||||
```
|
||||
Swipe 28px per book via `lv_indev_get_point/active`, slider fast scrub, bottom row [X left][slider flex][39/66 right], no card/Go/top bar, LARGE pretty name 50% bigger tappable `georgia_regular_24`.
|
||||
|
||||
**Timer gotcha**: `lv_timer_t user_data` incomplete in SDK 0.8.0-dev -> `t->user_data` = `invalid use of incomplete typedef`. Must use `lv_timer_get_user_data(t)` (DEFINE_MODULE_SYMBOL 382/388). Grep `grep timer symbols.c`.
|
||||
|
||||
**Screenshot rate-limit**: Rapid double `GET /api/screenshot` causes `ConnectionResetError 54` + heap spike crash (encoder uses internal heap). Wait 2s+ between shots, check `GET /api/sysinfo` heap recovery to ~47KB before next.
|
||||
|
||||
## Scaling tiers v6 final (Georgia resolved)
|
||||
| len | Fallback | Georgia | line_sp | pad | Example |
|
||||
|-----|----------|---------|---------|-----|---------|
|
||||
| <40 | LARGE | 26 ultra | 12 | 6/20 | John 11:35 11c |
|
||||
| 40-99 | LARGE | 22 | 8 | 4/16 | Gen 2:10 77c |
|
||||
| 100-199 | DEFAULT | 18 | 6 | 3/14 | typical |
|
||||
| 200-349 | SMALL | 16 | 5 | 2/12 | long |
|
||||
| 350+ | SMALL | 16 | 4 | 1/8 | Esther 8:9 |
|
||||
Center `90%x82% align 0,-7` = 15px up balanced.
|
||||
|
||||
## v5 Pretty Serif — Georgia Italic like PSALMS 23:1 card
|
||||
Generation: `npx -y lv_font_conv --font "/System/Library/Fonts/Supplemental/Georgia Italic.ttf" -r 32-126 -r 0x2018-0x201D --size 26 --bpp 4 --format lvgl --lv-include "lvgl.h" --no-compress -o Source/fonts/georgia_italic_26.c` repeat 22/20/18/16 + regular 24. Sizes 26=85KB 22=65KB 20=57KB 18=50KB 16=43KB regular24=68KB ~368KB src ~315KB .rodata PSRAM via `CONFIG_ELF_LOADER_LOAD_PSRAM=y`. `LV_FONT_DECLARE` + resolve helpers fallback to `lvgl_get_text_font()`. Heap 47539 stable vs 47855 pre, 5336 bytes serif vs 3991 sans.
|
||||
|
||||
## v4 Editorial — PSALMS 23:1 white reference
|
||||
Verse warmer `#F2F0E8` flat radius 0 pad 4, ref uppercase `EPHESIANS 1:4` tracking 2 `#9A9AA8` gap 10 was 14, fade 220ms verse /300ms ref via `lv_anim_*` exported since 0.6 safe, heap 54KB stable.
|
||||
|
||||
## Checklist v6 final
|
||||
- [ ] header TRANSP bg 0E0E14 36px border 0, ctrl TRANSP, center -7 15px up balanced PASS Zeph 1:3
|
||||
- [ ] serif downtier LARGE->22 DEFAULT->18 SMALL->16 ultra 26 pads 6/4/3/2/1
|
||||
- [ ] menu shows 8683 on start then auto-hide 10s via lv_timer_get_user_data, tap re-arms, book browser deletes timer
|
||||
- [ ] book browser slide up 20->0 260ms + fade 20->COVER 220ms + dial fade 60->255 180ms
|
||||
- [ ] heap >40KB, PSRAM 4.8MB, 0 missing syms, beautiful per user commit 2142871
|
||||
@@ -0,0 +1,152 @@
|
||||
# Intro screen + fullscreen pattern for Tactility games (2026-07-16 v2-v4)
|
||||
|
||||
## Problem
|
||||
User wants:
|
||||
1. Game engine assessment
|
||||
2. Fullscreen games
|
||||
3. Intro screen with rules/movements before game
|
||||
4. No way to exit (fullscreen lost toolbar back button)
|
||||
5. Touch unresponsive after fullscreen refactor
|
||||
|
||||
## Engine assessment given
|
||||
GameKit Loop/Scene/Input/Grid/Draw/Prefs is enough for MVP:
|
||||
- Input unification (LV_KEY_* + WASD + gesture + touch quadrant via pointToQuadrant) covers all 40+ boards
|
||||
- Grid helpers keep 9x7 logic clean
|
||||
- Model/Renderer split solid, Prefs + SfxEngine integration painless
|
||||
- Gaps: no scene-stack, no tween/flash, proc-gen fixed 2 walls + 2 enemies, 8 entity cap, no FOV, no inventory
|
||||
|
||||
## Fullscreen reality
|
||||
- Firmware has `AppManifest::Flags::HideStatusBar` used by Boot, Setup, TouchCalibration (GuiService.cpp hide/show via `flags.hideStatusbar`)
|
||||
- **BUT** ELF external manifest parsing `AppManifestParsingV1.cpp / V2.cpp` only parses:
|
||||
- id, name, versionName, versionCode, target.sdk, target.platforms
|
||||
- **Ignores flags** — so external apps cannot hide statusbar without firmware patch
|
||||
- What you CAN do today: don't call `tt_lvgl_toolbar_create_for_app(parent, app)` → gain ~40-50px vertical
|
||||
- Breakout, Snake keep toolbar for scores/lives
|
||||
- For PocketDungeon we dropped it: root bg set to COLOR_BG, container 100% x 100% no border, board centered with dynamic cell calc `(screenW-22)/COLS` and `(screenH-70)/ROWS`
|
||||
- If true edge-to-edge needed: patch `AppManifestParsingV1/V2` to parse `app.flags=HideStatusBar` or `manifest.properties` line `flags=...`, rebuild firmware via `python device.py es3c28p && idf.py build flash -p /dev/cu.usbmodem101`, rebuild SDK, rebuild app
|
||||
|
||||
## Intro screen implementation (state machine AppPhase) — v2
|
||||
Pattern added to PocketDungeon.cpp/h:
|
||||
|
||||
### Header
|
||||
```cpp
|
||||
enum class AppPhase : uint8_t { Intro, Playing, Paused };
|
||||
class PocketDungeon final : public App, public GameKit::Scene {
|
||||
lv_obj_t* introContainer = nullptr;
|
||||
lv_obj_t* gameContainer = nullptr;
|
||||
lv_obj_t* pauseOverlay = nullptr;
|
||||
AppPhase phase = AppPhase::Intro;
|
||||
void showIntro();
|
||||
void showGame();
|
||||
void showPause(); void hidePause();
|
||||
void clearIntro(); void clearGame();
|
||||
void renderIntro();
|
||||
void attachInputToCurrent();
|
||||
void exitGame();
|
||||
static void onStartButtonClicked(lv_event_t* e);
|
||||
static void onResumeButtonClicked(lv_event_t* e);
|
||||
static void onExitButtonClicked(lv_event_t* e);
|
||||
static void onGameContainerLongPress(lv_event_t* e);
|
||||
}
|
||||
```
|
||||
|
||||
### Flow v2
|
||||
- `onShow`: set bg, prefs load, model.reset(lv_tick_get()), Sfx start, phase=Intro, onEnter, loop.start(200ms, this) — NO toolbar creation for fullscreen-ish
|
||||
- `onEnter`: GameKit::attachInput(root,this), showIntro()
|
||||
- `showIntro()`: clearGame+clearIntro, phase=Intro, renderIntro()
|
||||
- renderIntro creates full-screen col flex container bg=COLOR_BG, title "POCKET DUNGEON", subtitle, divider line 120x2 accent, rulesBox 90% width flex_grow 1 bg=COLOR_PANEL radius 10:
|
||||
- GOAL (gold #D99B23): "Reach deepest floor..."
|
||||
- HOW TO PLAY (green #3B9D58): "@ = You s = Slime b = Bat $ = Treasure (+5) > = Stairs # = Wall Bump to attack"
|
||||
- CONTROLS (blue #365CF5): "Arrows / WASD / Swipe / Quadrant tap, Q/ESC = exit, Long press = pause"
|
||||
- Start button 160x44 bg accent radius10 -> showGame(), Exit button 160x36 bg #2A2D40, footer Best
|
||||
- `showGame()`: clearIntro+clearGame, phase=Playing, create gameContainer 100% TRANSP no scroll, CLICKABLE|GESTURE_BUBBLE, renderer.create, add X button top-right 36x28 bg #2A2D40 SYMBOL_CLOSE -> exitGame, attachInputToCurrent, render, sfx Warp
|
||||
- `onInput`: Cancel/Menu -> exitGame (Q/ESC). Intro: any Unknown != -> showGame. Paused: any -> hidePause. Playing: dx/dy, gameOver reset, else movePlayer
|
||||
- `render()`: if Intro|Paused return, else renderer.render
|
||||
|
||||
## v3 Touch unresponsive fix — THE CRITICAL PITFALL
|
||||
**Symptom**: After adding introContainer + gameContainer full-screen covering root, touch/swipe dead, only hardware keys work.
|
||||
|
||||
**Root cause**: LVGL event bubbling. `GameKit::attachInput(target, scene)` does:
|
||||
```cpp
|
||||
lv_obj_add_flag(target, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(target, inputEvent, LV_EVENT_KEY, scene);
|
||||
lv_obj_add_event_cb(target, inputEvent, LV_EVENT_GESTURE, scene);
|
||||
lv_obj_add_event_cb(target, inputEvent, LV_EVENT_CLICKED, scene);
|
||||
```
|
||||
If you only attach to `root`, but `introContainer` (100% child) is CLICKABLE and covers root, it receives CLICKED/GESTURE first and doesn't forward to root. Also `rulesBox` inside introContainer swallowed quadrant taps.
|
||||
|
||||
**Fix pattern `attachInputToCurrent()`**:
|
||||
```cpp
|
||||
void attachInputToCurrent() {
|
||||
if (root) GameKit::attachInput(root, this);
|
||||
if (introContainer) {
|
||||
lv_obj_add_flag(introContainer, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_flag(introContainer, LV_OBJ_FLAG_GESTURE_BUBBLE);
|
||||
GameKit::attachInput(introContainer, this);
|
||||
}
|
||||
if (gameContainer) {
|
||||
lv_obj_add_flag(gameContainer, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_flag(gameContainer, LV_OBJ_FLAG_GESTURE_BUBBLE);
|
||||
GameKit::attachInput(gameContainer, this);
|
||||
lv_obj_add_event_cb(gameContainer, onGameContainerLongPress, LV_EVENT_LONG_PRESSED, this);
|
||||
}
|
||||
if (pauseOverlay) {
|
||||
lv_obj_add_flag(pauseOverlay, LV_OBJ_FLAG_CLICKABLE);
|
||||
GameKit::attachInput(pauseOverlay, this);
|
||||
}
|
||||
}
|
||||
```
|
||||
- Call on every phase change.
|
||||
- `rulesBox` must be `remove_flag SCROLLABLE` and NOT CLICKABLE — let taps bubble to introContainer for `pointToQuadrant`.
|
||||
- Start button calls `lv_event_stop_bubbling(e)` to prevent double trigger (button click + quadrant).
|
||||
- Accept `Touch`/`Swipe` InputKind at gameOver restart.
|
||||
|
||||
## v4 Exit handling for toolbar-less fullscreen
|
||||
**Problem**: No toolbar = no system back button = trapped.
|
||||
|
||||
**Three exit paths implemented**:
|
||||
1. Hardware: `InputKind::Cancel` (Q/q key via `keyToInput` maps q/Q -> Cancel, LV_KEY_ESC -> Cancel) or `Menu` -> `exitGame()` -> `tt_app_stop()` (from `tt_app.h`, already included via `TactilityCpp/App.h`). Call `saveBests()` + Sfx `Cancel`.
|
||||
2. X button: small 36x28 top-right in game phase, bg #2A2D40 radius 6, `LV_SYMBOL_CLOSE`, event `onExitButtonClicked` -> `tt_app_stop()`.
|
||||
3. Long-press: `LV_EVENT_LONG_PRESSED` on `gameContainer` -> `showPause()` creates overlay 100% bg #000 OPA_70 flex center, box 180px bg PANEL radius 12, Resume (accent) + Exit Game (grey) + hint. `AppPhase::Paused` — any input resumes, buttons stop bubbling.
|
||||
|
||||
Intro also has Exit button. All paths save bests.
|
||||
|
||||
### Header additions v4
|
||||
```cpp
|
||||
lv_obj_t* pauseOverlay = nullptr;
|
||||
enum AppPhase { Intro, Playing, Paused };
|
||||
void showPause(); void hidePause(); void exitGame();
|
||||
static void onResumeButtonClicked(lv_event_t* e);
|
||||
static void onExitButtonClicked(lv_event_t* e);
|
||||
static void onGameContainerLongPress(lv_event_t* e);
|
||||
```
|
||||
|
||||
### Verification (ad-hoc tmp script hermes-verify-*.py)
|
||||
Checks: no toolbar_create_for_app, has AppPhase Intro/Playing/Paused, showIntro/showGame/showPause, ENTER DUNGEON, GOAL+CONTROLS, CLICKABLE+GESTURE_BUBBLE, LONG_PRESSED, tt_app_stop, Cancel/Menu exit, build produces package esp32s3.
|
||||
Build: `PYTHONPATH='' python3 tactility.py Apps/PocketDungeon build esp32s3 --local-sdk` -> package OK
|
||||
Install: `... install 192.168.68.112 esp32s3 --local-sdk` + `run`
|
||||
|
||||
### v5-v6 QVGA left-info + right-small-icons redesign
|
||||
|
||||
User feedback July 16: "Still not great layout. I think we can replace text button with icons small on the right, and have the information on the side left."
|
||||
|
||||
Implementation:
|
||||
- Header: title POCKET DUNGEON + subtitle + Best F3 G18 compact (pad 6, row 2)
|
||||
- mainRow: flex ROW, flex_grow 1, pad 4/8 column, bottom 8, no scroll, border 0 TRANSP
|
||||
- leftCol: flex_grow 1, height 100%, pad_right 4 bottom 6, column row 8, SCROLLABLE (safety for QVGA overflow)
|
||||
- makeCard(PCT 100%, SIZE_CONTENT, pad 9/3, bg PANEL radius10): GOAL (gold) + HOW TO PLAY (green) only = 2 cards for airy fit; HOW TO PLAY merges symbols + Controls hint "Arrows/WASD/Swipe/Quadrant"
|
||||
- rightRail: fixed 44px wide, SIZE_CONTENT height, column center, row 10, SCROLLABLE removed
|
||||
- makeIconBtn lambda (symbol, bg, cb, user, size): btn size s×s, bg, radius s/3, pad 0, event CLICKED
|
||||
- Play 38px accent #365CF5 SYMBOL_PLAY, Exit 30px muted #252836 SYMBOL_CLOSE
|
||||
- Result: info left 75%, icons small right 25%, no bottom clipping (2 cards vs 3 saves ~60px), screenshot intro_v6.png 6.4K vision: "Structure correct – 2 cards left, play/exit right – but scrollbar visible indicating near overflow; still dense but no clip" → final fix adds pad_bottom to mainRow + leftCol and reduces rail to 44px CONTENT vs 100% height to avoid LVGL flex 100% child overflow bug.
|
||||
|
||||
Pitfalls fixed:
|
||||
- LVGL flex row with child height 100% inside flex_grow parent can overflow on 320x198 usable (QVGA after 20px statusbar). Use rightRail SIZE_CONTENT, not 100%, and leftCol SCROLLABLE with pad_bottom.
|
||||
- Avoid `set_style_max_width` / `set_scrollbar_mode` (unexported) → use PCT widths 92/80 for gutters.
|
||||
- Keep card count ≤2 for QVGA intro to stay airy; third card pushes to scroll clip.
|
||||
|
||||
### Future ideas
|
||||
- Game-over -> intro loop instead of immediate restart
|
||||
- Cell flash animation on Attacked/Blocked using lv_anim (exported since 0.6, safe)
|
||||
- Push to personal/main after user likes exit flow
|
||||
- True statusbar hide via firmware patch parsing flags
|
||||
@@ -0,0 +1,46 @@
|
||||
# Per-Book Offline Bible Assets
|
||||
|
||||
Source: https://github.com/sajeevavahini/bibles — Zefania XML `World English Bible.xml` ~4.8MB, format `XMLBIBLE/BIBLEBOOK/CHAPTER/VERS` attrs `bnumber/bname/cnumber/vnumber`.
|
||||
|
||||
## Problem
|
||||
- 4.8MB XML → 3.8MB blob + 304KB idx monolith = too big for RAM, packaging bloats `.app` to 4.2MB monolith load.
|
||||
- Device has ~8MB PSRAM but LVGL/lwIP compete; loading whole bible at once OOM-prone.
|
||||
|
||||
## Solution: per-book split
|
||||
|
||||
Output `assets/bible/`:
|
||||
- `books.json`: `[{bnumber, bname, verses, chapters}, ...]` 66 entries
|
||||
- `<bnum>.bin` (01.bin..66.bin): concatenated NUL-terminated UTF-8 verse strings
|
||||
- `<bnum>.idx`: binary: header 8 bytes: magic `BIBK` ascii, version u16 (1), count u32; then per entry 8 bytes: `offset u32 LE, cnum u16 LE, vnum u16 LE` sorted by chapter/verse.
|
||||
|
||||
Worst book: Psalms 19.bin ~220KB, still fits. Total ~4.1MB split but loaded one at a time → RAM tiny.
|
||||
|
||||
## Converter
|
||||
|
||||
`Apps/BibleVerse/tools/convert_bible.py` (also template):
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
python3 tools/convert_bible.py /path/to/World\ English\ Bible.xml assets/bible/
|
||||
# or fetch then convert
|
||||
```
|
||||
|
||||
Implementation sketch (see templates/convert_bible.py):
|
||||
- xml.etree.ElementTree iterative parse `BIBLEBOOK` to keep memory low.
|
||||
- Accumulate bin buffer + idx entries per book.
|
||||
- Write books.json last.
|
||||
|
||||
## Hybrid resolution on device
|
||||
|
||||
```c
|
||||
// 1. Try embedded A: assets via tt_app_get_assets_path
|
||||
// 2. Fallback scan /sdcard/bibles/WEB/ same binary format for user-provided translations
|
||||
// 3. build target: load BookInfo array, then on demand load_book(bnumber) frees previous.
|
||||
```
|
||||
|
||||
## Verify packaging
|
||||
|
||||
```bash
|
||||
tar tvf build/BibleVerse.app | grep "\.bin" | wc -l # expect 66
|
||||
ls -lh assets/bible/*.bin | sort -k5 -hr | head
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
# PocketDungeon — GameKit + SfxEngine + tutorial levels (2026-07-16/17)
|
||||
|
||||
User said "board ending on 112" → WiFi discovery to 192.168.68.112. Repo pull needed from personal remote. Infinite floors, tutorial-by-play intro.
|
||||
|
||||
## Repo layout & build
|
||||
- `Apps/PocketDungeon/CMakeLists.txt`: standard `tactility_project`
|
||||
- `main/CMakeLists.txt`: GLOB_RECURSE GameKit + SfxEngine via `../../../Libraries/...` + `REQUIRES TactilitySDK esp_driver_i2s esp_driver_gpio`
|
||||
- `manifest.properties`: sdk=0.8.0-dev (bumped from 0.7 to match board OS 0.8.0-dev, else missing symbol dialog), platforms esp32,esp32s3,esp32c6,esp32p4
|
||||
- GameKit: Loop, Scene (Tick, InputKind), Input keyToInput/gestureToInput/pointToQuadrant/attachInput, Grid, Draw, Prefs
|
||||
- Personal remote `git.reynafamily.com/adolforeyna/tactility_apps` commit d976595 feat: add Pocket Dungeon prototype, pull via `git fetch personal && git merge personal/main`
|
||||
- Build env pitfall: Hermes PYTHONPATH pollution → `PYTHONPATH='' python3 tactility.py Apps/PocketDungeon build esp32s3 --local-sdk` + `IDF_PYTHON_ENV_PATH=idf5.3_py3.9_env`
|
||||
- Board shorthand .112: ping 192.168.68.112 OK (ESP32-S3 MAC 14:c1:9f:d1:56:90, os 0.8.0-dev), .111 also online. Install needs platform arg `esp32s3` else fails ELF not built for esp32. Dashboard port 80 `PUT /api/apps/install`, dev 6666.
|
||||
|
||||
## v2-v4: Intro + fullscreen + input + exit
|
||||
- Engine enough for MVP: Loop/Scene/Input/Grid, but no scene-stack/tween/proc-gen depth.
|
||||
- Fullscreen: firmware HideStatusBar flag ignored by ELF parser V1/V2 → cannot hide statusbar without firmware patch. Gain ~40-50px by not calling `tt_lvgl_toolbar_create_for_app`.
|
||||
- Intro state machine AppPhase Intro/Playing/Paused, introContainer+gameContainer+pauseOverlay, attachInputToCurrent() re-attaches CLICKABLE|GESTURE_BUBBLE to root+active container, rulesBox non-clickable so quadrant taps bubble, start button stop_bubbling.
|
||||
- Touch dead after fullscreen → fixed via attachInputToCurrent() pattern.
|
||||
- Exit trap fullscreen → Q/ESC Cancel/Menu → tt_app_stop() (via TactilityCpp/App.h), X top-right 32x26 SYMBOL_CLOSE, long-press LONG_PRESSED → pause overlay (bg #000 OPA_70, box 180px Resume accent #365CF5 grey Exit #252836).
|
||||
- See references/intro-fullscreen.md v3/v4.
|
||||
|
||||
## v5-v6: QVGA left info + right small icons (cramped fix)
|
||||
User Jul 16: "Still not great layout. I think we can replace text button with icons small on the right, and have the information on the side left."
|
||||
- mainRow flex ROW flex_grow 1 pad 4/8 column bottom 8, leftCol flex_grow 1 scrollable (pad_row 8 bottom 6), rightRail fixed 44px SIZE_CONTENT column center row 10.
|
||||
- Left cards initially GOAL + HOW TO PLAY (2 cards), right icons Play 38 accent + Exit 30 muted (SYMBOL_PLAY/CLOSE radius s/3). Verified via /api/screenshot 6-7KB PNG, no max_width/scrollbar_mode (unexported → missing symbol blue OK dialog). SDK bump 0.7→0.8 fixed.
|
||||
- Symbol hygiene: no `lv_obj_set_style_max_width`, no `set_scrollbar_mode`. Use PCT widths.
|
||||
|
||||
## v7: Tutorial levels instead of text explanations (Jul 17)
|
||||
User: "Let's add three intro levels instead of explanations. First level present the player and the stairs. Next level the bat and the gold. Both with two rows and regular cols. Then go the current first level. So on the initial page, only keep the goal and the controls explanation."
|
||||
|
||||
Implementation:
|
||||
- Model.h: `TUTORIAL_ROWS=2`, `renderRows` in DungeonState, `floor 0,1 = tutorial`, `displayFloor()` maps tutorial→1 real floor-1, `isTutorial()`, `tutorialId()`, `reset(seed, tutorials=true)` floor 0 if tutorials else 2, `generateTutorialFloor(tId)`, `clearAllTiles()`.
|
||||
- Model.cpp:
|
||||
- clearAllTiles border walls
|
||||
- Tut0: renderRows=TUTORIAL_ROWS, hide rows >=3 walls, player {1,1}, stairs COLS-2,1 only
|
||||
- Tut1: + treasure {3,1} + bat {5,1} hp1 + stairs, no random walls
|
||||
- Real >=2: previous logic treasure [5][2+floor%2], walls, enemies scale realFloor=floor-1, best update only floor>=2, moveMonsters skipped when isTutorial
|
||||
- Renderer.cpp: respects renderRows hides cells HIDDEN, board height = cellPx*TUTORIAL_ROWS centered -10 for tutorial, status "TUTORIAL X/2 HP Gold" message "TUT 1: Move to > @=you" / "TUT 2: Kill b, take $, use >"
|
||||
- PocketDungeon.cpp: intro only GOAL + CONTROLS (2 cards), no HOW TO PLAY – symbols taught via play. Right rail 44px Play 38 Exit 30 stays.
|
||||
|
||||
Flow: Intro (Goal+Controls left, icons right) → Tut1 2×9 @→> movement → Tut2 2×9 @+b+$→> combat/loot → Real F1 infinite (displayFloor). Infinite floors – answer to user "how many floors" = infinite.
|
||||
|
||||
Verification: hermes-verify-tutorial*.py checks 2 cards, no HOW TO PLAY, tutorial gen, renderRows, TUTORIAL_ROWS, isTutorial, build package, nm -D 0 missing (89 undef). Installed 192.168.68.112 intro_tutorial.png 6.3K.
|
||||
|
||||
## Floors
|
||||
Infinite unbounded, difficulty scales slime hp floor/3, extra enemy >=3. No win boss. User asked, answered infinite.
|
||||
|
||||
## Next todos
|
||||
- Push to personal/main (requested earlier)
|
||||
- True HideStatusBar via firmware patch AppManifestParsingV1/V2 flags
|
||||
- Victory floor 10 boss + win screen
|
||||
- Cell flash anim Attacked/Blocked via lv_anim (exported since 0.6)
|
||||
@@ -0,0 +1,92 @@
|
||||
# Pretty Serif Font — Georgia Italic embedded (BibleVerse v5/v6 final July 12 - beautiful build 2142871)
|
||||
|
||||
## Problem
|
||||
User: "The main text has a pretty basic font" + white PSALMS 23:1 reference serif italic high-contrast. Montserrat sans functional not editorial.
|
||||
|
||||
Goal: serif italic verse, elegant book dial, heap stable 47KB, 0 missing syms, PSRAM-aware, beautiful per user "This is beatiful, well done."
|
||||
|
||||
## Options
|
||||
1. `lv_binfont_create(path)` → `.fnt` bin via VFS A:/assets — exported 0.8 but path fragile, logs not avail, needs `tt_app_get_assets_path` void check `buf[0]!='\0'`.
|
||||
2. **Embedded C LVGL fonts (chosen)** → `lv_font_conv --format lvgl` → `LV_FONT_DECLARE` → linked direct. Lives ELF `.rodata` → `MALLOC_CAP_SPIRAM` via `CONFIG_ELF_LOADER_LOAD_PSRAM=y` → PSRAM 4.8MB free, not internal 310KB heap. No VFS, no runtime open, 0 missing syms.
|
||||
|
||||
## Generation v6 final
|
||||
```bash
|
||||
FONT="/System/Library/Fonts/Supplemental/Georgia Italic.ttf"
|
||||
RFONT="/System/Library/Fonts/Supplemental/Georgia.ttf"
|
||||
OUT=Apps/BibleVerse/main/Source/fonts
|
||||
mkdir -p $OUT
|
||||
for SZ in 26 22 20 18 16; do npx -y lv_font_conv --font "$FONT" -r 32-126 -r 0x2018-0x201D --size $SZ --bpp 4 --format lvgl --lv-include "lvgl.h" --no-compress -o $OUT/georgia_italic_${SZ}.c; done
|
||||
npx -y lv_font_conv --font "$RFONT" -r 32-126 --size 24 --bpp 4 --format lvgl --lv-include "lvgl.h" --no-compress -o $OUT/georgia_regular_24.c
|
||||
# ls -lh $OUT: 26=85KB 22=65KB 20=57KB 18=50KB 16=43KB regular24=68KB ~368KB src ~315KB binary .rodata
|
||||
```
|
||||
Fix: `--lv-include "lvgl.h"` else generates `#include "lvgl/lvgl.h"` fail in SDK (header is `lvgl.h`).
|
||||
|
||||
CMake: `file(GLOB_RECURSE SOURCE_FILES Source/*.c)` auto-includes `Source/fonts/*.c`.
|
||||
|
||||
## Code wiring v6 final (15px lift)
|
||||
```c
|
||||
LV_FONT_DECLARE(georgia_italic_26)
|
||||
LV_FONT_DECLARE(georgia_italic_22)
|
||||
LV_FONT_DECLARE(georgia_italic_20)
|
||||
LV_FONT_DECLARE(georgia_italic_18)
|
||||
LV_FONT_DECLARE(georgia_italic_16)
|
||||
LV_FONT_DECLARE(georgia_regular_24)
|
||||
|
||||
typedef struct {
|
||||
lv_font_t* font_serif_26; lv_font_t* font_serif_22; lv_font_t* font_serif_20;
|
||||
lv_font_t* font_serif_18; lv_font_t* font_serif_16; lv_font_t* font_book_24;
|
||||
bool fonts_loaded;
|
||||
// + chrome_auto_hide_timer etc
|
||||
} AppCtx;
|
||||
|
||||
// v6 downtier: Georgia wider/taller than Montserrat → map down one to avoid crop
|
||||
static const lv_font_t* resolve_verse_font(AppCtx* ctx, enum LvglFontSize f){
|
||||
if(!ctx->fonts_loaded) return lvgl_get_text_font(f);
|
||||
if(f==FONT_SIZE_LARGE && ctx->font_serif_22) return ctx->font_serif_22; // was 26 too big -> 22
|
||||
if(f==FONT_SIZE_DEFAULT && ctx->font_serif_18) return ctx->font_serif_18; // was 20 -> 18
|
||||
if(f==FONT_SIZE_SMALL && ctx->font_serif_16) return ctx->font_serif_16;
|
||||
return lvgl_get_text_font(f);
|
||||
}
|
||||
static const lv_font_t* resolve_verse_font_ultra(AppCtx* ctx){
|
||||
return ctx->font_serif_26 ? (lv_font_t*)&georgia_italic_26 : lvgl_get_text_font(FONT_SIZE_LARGE);
|
||||
}
|
||||
static const lv_font_t* resolve_book_font(AppCtx* ctx){
|
||||
if(ctx->font_book_24) return ctx->font_book_24;
|
||||
if(ctx->font_serif_22) return ctx->font_serif_22;
|
||||
return lvgl_get_text_font(FONT_SIZE_LARGE);
|
||||
}
|
||||
|
||||
onShowApp:
|
||||
g_ctx.font_serif_26 = (lv_font_t*)&georgia_italic_26; ...
|
||||
g_ctx.font_book_24 = (lv_font_t*)&georgia_regular_24;
|
||||
g_ctx.fonts_loaded = true;
|
||||
|
||||
onHide: static const - no free (live in PSRAM ELF)
|
||||
|
||||
apply_verse_scaling: pads tighter v6 6/4/3/2/1 was 10/8/6/4/2 + resolve_verse_font + lift -7 (15px up not 30px)
|
||||
const lv_font_t* f = is_ultra ? resolve_verse_font_ultra(ctx) : resolve_verse_font(ctx, font_sz);
|
||||
lv_obj_set_style_text_font(lbl_verse, f, 0);
|
||||
```
|
||||
|
||||
## v6 black-bar crop fix + 15px lift correction
|
||||
Symptom: black bar top cropping Habakkuk 1:6 + "reduce text size for this font" + "main text can be moved a bit up 30px" → corrected "should had been 15".
|
||||
Root cause: header `44px bg #15151F COVER border 1 #232338` at TOP_MID overlapping center_col 90%x80%.
|
||||
Fix:
|
||||
- header `36px` not 44, `bg 0x0E0E14` same as root not 15151F, `TRANSP 0` not COVER, border 0, pad ver 4
|
||||
- ctrl bar `TRANSP 0` + `bg 0x0E0E14`
|
||||
- center `90%x82% align CENTER 0,8` → `0,-22` (30px up) → `0,-7` (15px up: +8 base -15) per user correction
|
||||
- verse `pad_all 4` not 6
|
||||
Vision: Hab 2:9 "No extra black bar overlapping, fully visible", Zeph 1:3 "balanced now, optical center correct, 1.5x padding above controls" PASS. Screenshots 5336 bytes serif vs 3991 sans, 8683 with chrome (menu visible) vs 6882 immersive.
|
||||
|
||||
## Verification v6 final beautiful
|
||||
- Build 4.3MB .app ELF 0 missing `nm -D` vs `symbols.c` exports, 0 undef LVGL
|
||||
- Install .129 0.8.0-dev: heap 46215-47591 free largest 20480 psram 4810396 stable vs pre 47855 → fonts PSRAM not heap
|
||||
- Screenshots: 8683 menu show (top Zeph 1:3 / 31094 + bottom pills X/List/Prev/Pause/Next "premium cinematic Audible-like") → 6882 immersive, serif italic vision PASS Hab 1:17 & Zeph 1:3
|
||||
- Fonts fallback works on 0.7 fleet without C files
|
||||
|
||||
## Auto-hide menu + book browser animation v6 final (see immersive-ui.md detail)
|
||||
|
||||
## Tradeoffs
|
||||
- PSRAM 315KB .rodata fine (4.8MB free), heap delta 0
|
||||
- Curly quotes 0x2018-0x201D included for “The Lord is my shepherd;” reference image
|
||||
- Cormorant Garamond download failed via curl HTML 404, Georgia system font editorial highly legible reverent, user approved beautiful
|
||||
@@ -0,0 +1,114 @@
|
||||
# SDK mismatch & missing-symbol crash – PocketDungeon July 16 2026
|
||||
|
||||
## Symptom
|
||||
Board `192.168.68.112` on `{"cpuFamily":"esp32s3","osVersion":"0.8.0-dev"}` shows error dialog:
|
||||
```
|
||||
Error
|
||||
Application failed to start: missing symbol
|
||||
[OK]
|
||||
```
|
||||
Screenshot via `curl http://192.168.68.112/api/screenshot -o /tmp/shot.png` -> PNG 320x240 palettized.
|
||||
|
||||
## Root cause (2-fold)
|
||||
|
||||
1. **SDK / OS version mismatch**
|
||||
- `manifest.properties` said `sdk=0.7.0-dev`
|
||||
- Board runs `0.8.0-dev` (checked `GET /info` and `GET /api/screenshot` header)
|
||||
- Compiled against `0.7.0-dev-esp32s3/TactilitySDK` but device exports differ.
|
||||
|
||||
2. **Unexported LVGL style API used**
|
||||
- Code added for less-cramped QVGA intro used:
|
||||
- `lv_obj_set_style_max_width(...)` -> **NOT exported** in 0.7 nor 0.8 (verified via grep firmware `ESP_ELFSYM_EXPORT` / `DEFINE_MODULE_SYMBOL`)
|
||||
- `lv_obj_set_scrollbar_mode(...)` -> version-dependent / not reliably exported
|
||||
- Header exists in SDK's vendored LVGL, so it compiles, but loader fails at runtime.
|
||||
- `check_symbols.py` pattern:
|
||||
```bash
|
||||
source $IDF_PATH/export.sh && xtensa-esp32s3-elf-nm -D build/.../*.app.elf | grep " U "
|
||||
grep -R "ESP_ELFSYM_EXPORT\|DEFINE_MODULE_SYMBOL" firmware/
|
||||
```
|
||||
- Found exactly 1 missing: `lv_obj_set_style_max_width`.
|
||||
|
||||
## Fix checklist
|
||||
|
||||
1. **Query board version first**
|
||||
```bash
|
||||
curl -s http://192.168.68.112:6666/info
|
||||
curl -s http://192.168.68.112/api/screenshot -o /tmp/intro.png
|
||||
```
|
||||
|
||||
2. **Bump manifest to match board**
|
||||
```properties
|
||||
[manifest]
|
||||
version=0.1
|
||||
[target]
|
||||
sdk=0.8.0-dev
|
||||
platforms=esp32,esp32s3,esp32c6,esp32p4
|
||||
```
|
||||
|
||||
3. **Avoid unexported LVGL**
|
||||
Replace `set_style_max_width` with fixed percents:
|
||||
```cpp
|
||||
// BAD (unexported)
|
||||
lv_obj_set_style_max_width(obj, 220, LV_PART_MAIN);
|
||||
|
||||
// GOOD – gives side gutters on 320px QVGA
|
||||
lv_obj_set_width(card, LV_PCT(92));
|
||||
lv_obj_set_width(btn, LV_PCT(80));
|
||||
lv_obj_set_width(exitBtn, LV_PCT(70));
|
||||
```
|
||||
For scroll:
|
||||
```cpp
|
||||
// BAD
|
||||
lv_obj_set_scrollbar_mode(area, LV_SCROLLBAR_MODE_AUTO);
|
||||
|
||||
// GOOD – default scroll works, add flag explicitly
|
||||
lv_obj_add_flag(scrollArea, LV_OBJ_FLAG_SCROLLABLE);
|
||||
```
|
||||
|
||||
4. **Rebuild with correct SDK**
|
||||
```bash
|
||||
export TACTILITY_SDK_PATH=/Users/adolforeyna/Projects/Tactility/firmware/release/TactilitySDK
|
||||
rm -rf Apps/PocketDungeon/build/cmake-build-esp32s3
|
||||
PYTHONPATH="" python3 tactility.py Apps/PocketDungeon build esp32s3 --local-sdk
|
||||
# -> Using SDK .../0.8.0-dev-esp32s3/TactilitySDK
|
||||
# -> Building esp32s3 ELF OK
|
||||
# -> Building package OK
|
||||
```
|
||||
|
||||
5. **Symbol verification (from app skill)**
|
||||
Run the skill's `check_symbols.py` – should show 89 undefined, 0 missing after fix.
|
||||
|
||||
6. **Install + screenshot verification**
|
||||
```bash
|
||||
PYTHONPATH="" python3 tactility.py Apps/PocketDungeon install 192.168.68.112 esp32s3 --local-sdk
|
||||
PYTHONPATH="" python3 tactility.py Apps/PocketDungeon run 192.168.68.112
|
||||
curl -s http://192.168.68.112/api/screenshot -o /tmp/intro_v3.png
|
||||
# vision analyze: not cramped, airy, no clipping
|
||||
```
|
||||
|
||||
## QVGA 320x240 cramped UI fix applied
|
||||
|
||||
Original cramped (intro.png vision):
|
||||
- GOAL card edge-to-edge, text `Loot gold, survive monsters` clipped at bottom
|
||||
- Buttons 85-90% wide touching edges, 4-8px gaps, Best touching bottom
|
||||
- No outer margin
|
||||
|
||||
Fixed v3 (intro_v3.png vision PASS):
|
||||
- Outer container `pad_all 10`, `pad_row 8`
|
||||
- Cards `PCT 92` width, `SIZE_CONTENT` height, `pad_all 10`, `pad_row 5`, radius 10, bg #1D2030
|
||||
- ScrollArea `PCT 100`, `flex_grow 1`, scrollable flag, flex col pad_row 8
|
||||
- TitleBox pad_row 3, title #F8F3E6, subtitle #9AA0B8
|
||||
- BtnRow `PCT 100`, `SIZE_CONTENT`, flex center, pad_row 8
|
||||
- Primary btn `PCT 80` 40px accent #365CF5 radius 10
|
||||
- Secondary exit `PCT 70` 30px bg #252836 radius 8
|
||||
- Shortened card bodies to fit QVGA: GOAL 1 line, HOW TO PLAY 3 lines, CONTROLS 4 lines
|
||||
- Ends up airy with 12-16px margins, no clipping.
|
||||
|
||||
Keep text short on QVGA; use `LV_LABEL_LONG_WRAP` + `PCT(100)`.
|
||||
|
||||
## Lessons
|
||||
|
||||
- Always probe board osVersion before build
|
||||
- The `/api/screenshot` endpoint (port 80, not 6666) is invaluable for both crash diagnosis (Error dialog) and layout QA without serial
|
||||
- `max_width` and `scrollbar_mode` specifically are trap APIs – prefer PCT widths for side gutters
|
||||
- TactilitySDK release folder contains both 0.7 and 0.8 subfolders – pick matching one via TACTILITY_SDK_PATH + --local-sdk automatically selects subfolder
|
||||
@@ -0,0 +1,77 @@
|
||||
# Symbol Check — BibleVerse session (updated July 12 v5 pretty font)
|
||||
|
||||
## Tool: undefined symbols vs firmware exports
|
||||
|
||||
```python
|
||||
import pathlib, re, subprocess
|
||||
elf = pathlib.Path("Apps/BibleVerse/build/cmake-build-esp32s3/BibleVerse.app.elf")
|
||||
r = subprocess.run(["zsh","-c", f"source ~/esp/esp-idf/export.sh >/dev/null 2>&1; xtensa-esp32s3-elf-nm -D {elf}"], capture_output=True, text=True)
|
||||
undef = [l.split()[-1] for l in r.stdout.splitlines() if len(l.split())>=2 and l.split()[-2]=='U']
|
||||
|
||||
pat = re.compile(r'(?:ESP_ELFSYM_EXPORT|DEFINE_MODULE_SYMBOL)\s*\(\s*([A-Za-z0-9_]+)\s*\)')
|
||||
exp=set()
|
||||
for fp in pathlib.Path("../firmware").rglob("*.*"):
|
||||
if 'build' in str(fp): continue
|
||||
if fp.suffix not in ('.c','.h','.cpp'): continue
|
||||
try: exp.update(m.group(1) for m in pat.finditer(fp.read_text(errors='ignore')))
|
||||
except: pass
|
||||
missing = [s for s in undef if s not in exp]
|
||||
print(f"undef {len(undef)} missing {missing}")
|
||||
```
|
||||
|
||||
## Session results
|
||||
|
||||
- Before fix: `undef 68, missing ['lv_font_montserrat_14']` — source used `&lv_font_montserrat_14`.
|
||||
- After `lvgl_get_text_font(FONT_SIZE_SMALL)`: `undef 68, missing []` ✅
|
||||
- Editorial v4: 0 missing, anim symbols ok, app 4413440 bytes, heap 54KB stable on .129
|
||||
- **v5 pretty font**: Georgia Italic embedded C 26/22/20/18/16 + regular 24 via `lv_font_conv`, 315KB `.rodata` in PSRAM, 0 missing, app 4.3MB, heap `47539 free largest 20480 psram 4810944` stable, screenshot 5336 bytes Habakkuk 1:17 serif italic editorial.
|
||||
- **v6 final beautiful 2142871**: 0 missing, `undef 0`, app 4.3M, heap `46215-47591 free largest 20480 psram 4810396` stable, screenshots 8683 menu visible (top Zeph 1:3 / 31094 + bottom pills) + 6882 immersive hidden, lift -7 15px corrected from -22 30px per user, black bar fixed. Gotcha `lv_timer_t user_data` field incomplete → `t->user_data` invalid → must use `lv_timer_get_user_data(t)` + `lv_timer_set_user_data` (exports 372/373/382/388). Checked `grep timer symbols.c`. Timer used for chrome auto-hide 10s + book browser anim.
|
||||
|
||||
## Known missing table
|
||||
|
||||
| Symbol | Status | Workaround |
|
||||
|---|---|---|
|
||||
| `lv_font_montserrat_14/18/24` | Not exported | `lvgl_get_text_font(FONT_SIZE_SMALL/DEFAULT/LARGE)` OR embedded C serif via `lv_font_conv` → see `pretty-font.md` |
|
||||
| `LV_SYMBOL_STAR` | May be absent | Use `LV_SYMBOL_OK`, `LV_SYMBOL_DUMMY` |
|
||||
| `xTaskGetCurrentTaskHandle` | Not exported | Avoid task identity check; release lock |
|
||||
| `lv_image_set_rotation` | Not exported | `lv_obj_set_style_transform_rotation(obj, deg*10, 0)` |
|
||||
| `cJSON`, `esp_websocket_client` | Static | Vendor source or raw `lwip_socket` APIs |
|
||||
|
||||
### New missing from v2/v3/v4/v5 UI passes
|
||||
|
||||
- `lv_obj_set_style_bg_grad_color/dir/stop` → **Not exported** 0.8.0-dev. Gradient fails `missing symbol`. Use solid `0x0E0E14`.
|
||||
- `LV_OPA_95` → **Not defined** (only 0/10/20/30/40/50/60/70/80/90/100/COVER/TRANSP). Use `COVER` or `90`.
|
||||
- `lv_arc_create`, `lv_arc_set_range/value/bg_angles`, `lv_arc_get_value` → **Not exported on 0.7.0-dev**, added `dff93cb6 Add lv_arc.h symbols (#496)` after 0.7 release. Symptom blue modal `Error / Application failed to start: missing symbol / OK`, heap healthy ~31KB not OOM. Fix horizontal slide using `lv_slider_create` + `lv_indev_get_point/active`. See `book-browser.md`.
|
||||
- `lv_roller_create` → **Not exported** 0.7 nor 0.8.
|
||||
- `lv_indev_get_point/active`, `lv_slider_create/range/value/set_value` → **Exported since 0.6** safe.
|
||||
- `lv_anim_init`, `set_var/values/duration/exec_cb/path_cb/start`, `path_ease_out/linear/ease_in`, `path_ease_in_out` → **Exported since 0.6** safe (checked lines 435-446). Used for editorial fade 220/300ms. Safe for 0.7/0.8 fleet.
|
||||
- `lv_binfont_create/destroy` → **Exported** 0.8 but VFS path fragile — `tt_app_get_assets_path` returns void, not bool (check `buf[0]!='\0'`). Prefer **embedded C** fonts via `LV_FONT_DECLARE` — lives in PSRAM (315KB `.rodata`), no FS needed. See `pretty-font.md`. Old `.fnt` bins 52KB still bundled in `assets/fonts/` but not used.
|
||||
- `lv_font_t` embedded C — no symbol needed, static const.
|
||||
|
||||
## Heap OOM vs missing-symbol distinction
|
||||
|
||||
- **OOM**: `heap free 275B total 310KB largest_block 23`, install `500 failed to create temp directory`, screenshot `500`. Cause 66-button browser 132-264 objs. Fix ~10 obj browser.
|
||||
- **Missing symbol**: `heap free 31779 largest 12288` healthy, install `200 ok`, run `200 ok`, but app shows blue `Error missing symbol` dialog, screenshot `200 2755`. Fix check `symbols.c`.
|
||||
|
||||
## Font scaling v4/v5
|
||||
|
||||
Only 3 sizes via `lvgl_get_text_font()` fallback, but v5 uses embedded serif as primary:
|
||||
|
||||
```c
|
||||
size_t len = strlen(verse);
|
||||
enum LvglFontSize fs; int line_sp, pad_v, pad_h;
|
||||
if (len < 40) { fs=FONT_SIZE_LARGE; line_sp=14; pad_v=10; pad_h=28; letter_sp=1; } // v4 tightened
|
||||
else if (len <100) { fs=FONT_SIZE_LARGE; line_sp=10; pad_v=8; pad_h=24; }
|
||||
else if (len <200) { fs=FONT_SIZE_DEFAULT; line_sp=8; pad_v=6; pad_h=20; }
|
||||
else if (len <350) { fs=FONT_SIZE_SMALL; line_sp=6; pad_v=4; pad_h=16; }
|
||||
else { fs=FONT_SIZE_SMALL; line_sp=4; pad_v=2; pad_h=12; } // Esther 492c
|
||||
|
||||
// v5: resolve to embedded Georgia Italic 26/20/16 else fallback
|
||||
lv_obj_set_style_text_font(lbl_verse, resolve_verse_font(ctx, fs), 0);
|
||||
```
|
||||
|
||||
Proven .112 Genesis 2:10 77c LARGE, .129 Ephesians 1:4 4138 bytes screenshot editorial warm `#F2F0E8` + ref uppercase tracking 2 `#9A9AA8` gap 10, v5 Habakkuk 1:17 5336 bytes serif italic editorial vision PASS.
|
||||
|
||||
Exported: `lvgl_get_text_font`, `get_text_font_height`, `get_shared_icon_font`, `set_style_text_font`, `timer_create`, `label_set_text`, `text_line_space`, `pad_*`, `text_opa`, `anim_*`, `LV_FONT_DECLARE` (macro, not symbol).
|
||||
|
||||
CI: fail if missing>0, heap stable >40KB with fonts.
|
||||
Reference in New Issue
Block a user