Files
tactility_apps/.claude/skills/tactility-app-development/references/intro-fullscreen.md
T
Adolfo Reyna e68909d64d
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
chore: move app skill in-repo + AGENTS.md
- 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
2026-07-17 09:51:20 -04:00

153 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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