Context:
Two issues on the Freenove gamebook: (1) long passages were clipped —
the reading band was small and text overflowed; (2) the 5-way pad was
decoded wrong, so navigation felt random, and on this unit the physical
Up button is electrically dead (held = no voltage change).
Approach:
Give the reading text the whole screen height in a vertically
scrollable container, split selection from scrolling, and remap the
keys to the pad's MEASURED voltages.
Changes:
- display_ui: gamebook view reworked — compact title, a full-height
scrollable reading container (the body wraps and scrolls, never
clipped), and a one-line choice selector pinned at the bottom; new
display_ui_gamebook_scroll() + reset-on-new-passage; show() gains a
reset_scroll arg; body buffer 512 -> 1100.
- gamebook.c: controls matched to the measured ladder
(key2=LEFT, key4=DOWN, key5=RIGHT, key1=CLICK; key3 unused; Up dead):
Left/Right scroll the text up/down, Down cycles the answer, Click
validates; library nav cycles tiles without needing Up.
Impact:
Long passages are fully readable (they scroll), and all four working
pad buttons do the right thing; nothing depends on the dead Up button.
Hardware-calibrated against the real pad voltages.
Context:
The gamebook played a single hard-coded book. We want the master to
boot into a library of several stories, shown as tiles, and let the
player pick one with the pad.
Approach:
Turn the gamebook component into a two-mode machine (LIBRARY / STORY).
On boot it loads /sdcard/gamebook/library.json and shows the stories as
a tile grid; selecting one loads that book's <id>.json and plays it;
finishing a story returns to the library.
Changes:
- display_ui: new library view — a 2x3 grid of title tiles with the
selected one highlighted (display_ui_library_show/hide); it wins the
view selector while open.
- gamebook.c: LIBRARY/STORY modes; load library.json + per-book
<id>.json from the SD; grid navigation (up/down +/-2, left/right
+/-1, click opens); story ending click returns to the library.
- main.c: gamebook_init only installs the pad hook; gamebook_start()
(which opens the library) is now called after the SD + media_manager
are up, so the boot picker actually finds the SD pack.
Impact:
The Freenove boots straight into a story picker and runs any book from
the SD library, fully offline. Hardware-validated: boots into a
6-story library.
Context:
We wanted a "livre dont vous etes le heros" playable on the Freenove
master with NO model in RAM: narration as say()-rendered WAV staged on
the SD card, and navigation on the physical 5-way pad. Nothing in the
firmware drove a branching, button-navigated story before this.
Approach:
A self-contained gamebook component reads a JSON book from the SD,
drives the display and audio, and owns the 5-way pad through a
registered display_ui key hook (so display_ui keeps no dependency on
gamebook — no cycle). A dedicated full-screen gamebook view renders
each page (title / wrapped body / choice list).
Changes:
- New gamebook component: loads /sdcard/gamebook/gamebook.json; per
passage plays its WAV (media_manager, absolute /sdcard path) and
renders the title, wrapped text, and a choice list with a ">" cursor.
- Navigation: D-pad up/down (and left/right) move the cursor, the
center click confirms; endings (no choices) restart on click; a
choice interrupts the current narration (media_manager_stop + play).
- display_ui: add display_ui_set_key_hook (consume keys when a takeover
mode is active) and a dedicated gamebook view (title + height-bounded
body band + reserved bottom menu band) with display_ui_gamebook_show
/ display_ui_gamebook_hide; the gamebook view wins the view selector
while open.
- game_endpoint: POST /game/gamebook[?action=stop] to start/stop.
- ota_server: bump max_uri_handlers 20 -> 22 for the new endpoint.
- main: call gamebook_init() once the buttons are up.
Impact:
The Freenove plays a branching audio gamebook driven entirely by the
pad, fully offline (no gateway, no model). Hardware-validated end to
end: cursor navigation, click select, WAV narration from SD, endings
restart.