Files
mckero f938d0b3bd Build overlay apps without the Arm toolchain, and say how far verification got
pip install ziglang cross-compiles to thumb-freestanding-eabi, which is enough to build a .app with no arm-none-eabi-gcc and no Docker. Measured refusals: --defsym, -Ttext and --section-start come back as unsupported linker args, so the VMA is resolved into a copy of app.ld; -T is forwarded (a missing script errors); --image-base is accepted but page-aligns the segments into 0x200103C8 and 0x20020C94. tools/elf2bin.py extracts allocated sections rather than program headers, because lld maps the ELF header and phdr table as a 180-byte LOAD of its own -- following the headers starts the image at 0x20000000 and APP_ERR_VMA. One division pulled in __aeabi_uidiv, which a -nostdlib blob cannot have: Minesweeper now avoids division entirely. app_main carries the .text.entry attribute upstream's apps use, so the entry is first for the loader's jump to offset 0.

Minesweeper builds to 2408 bytes of code against a 4096-byte budget. Installed through the page, the firmware reads the slot header twice and then exactly code_size bytes from slot+0x1000 -- the only read of that size in the boot log -- so the blob shape, header, CRC, VMA and offset are all accepted. Whether control reaches the overlay is unproven: the 100 ms PC probe saw no overlay address, no APP ERROR screen appears, and the app does not draw. test_elf2bin pins the phantom-header-segment lesson; both AGENTS files record the rest.
2026-10-02 08:32:22 +08:00
..

Minesweeper — an overlay app for the F4HWN Labs edition

Our own app: a 9x9 minesweeper that runs on the radio inside the 4 KiB overlay that App/apps/app_overlay.h reserves. It is written against upstream's App/apps/app_api.h and built with upstream's app.ld — neither is vendored here (both are Apache-2.0 files from armel/uv-k1-k5v3-firmware-custom), so drop this folder into App/apps/minesweeper/ next to them, or point -I at a copy.

Why it looks the way it does

constraint consequence
the radio has no left/right keys (UP, DOWN, MENU, EXIT, STAR, F, 0-9 only) the cursor walks the field with UP/DOWN and digits jump to a row then a column: 3 5 = row 3, column 5
4 KiB for text+rodata+data+bss together no lookup tables, no floats, no libc; adjacency is counted on the fly and each cell is one bit
81 cells do not fit a 16-bit mask three 9-byte bit arrays addressed by cell >> 3, cell & 7 — a uint16_t version compiled fine and was wrong past cell 15
the resident pixel helpers do not bound-check put()/invert() clip
no rand() in a freestanding blob a small LCG; mines are placed after the first reveal, keeping the 3x3 around it clear

Keys: UP/DOWN move, 1-9 pick row then column, MENU reveal, F flag, STAR new game, EXIT quit. M in the corner is the remaining-mine count, A1 is the cursor.

Build

arm-none-eabi-gcc -mcpu=cortex-m0plus -mthumb -Os -std=gnu11 -ffreestanding \
    -nostdlib -nostartfiles -T app.ld -Wl,--defsym,APP_VMA=0x20000280 \
    -o minesweeper.elf minesweeper_app.c
arm-none-eabi-objcopy -O binary minesweeper.elf minesweeper.bin
pack_app.py minesweeper.bin Minesweeper.app --name Minesweeper --ver 1.0 \
    --vma 0x20000280 --api-min 1      # pack_app.py lives in App/apps/

build.sh does exactly that and needs the Arm GNU Toolchain on PATH.

Then install it from the page: Overlay apps → a slot → pick Minesweeper.app → Install → Ask the radio should answer Minesweeper. On the radio press F then 7 and MENU to run it.

What is verified, and what is not

Verified here: the source compiles clean with gcc -Wall -Wextra -Werror against upstream's real app_api.h (that check caught the API's actual member names — api->fb, print_tiny(s, x, y, statusbar, fill) with five arguments — and the fact that APP_KEY_LEFT/APP_KEY_RIGHT do not exist).

Not verified: it has never been built for ARM or run on the radio, because no arm-none-eabi-gcc and no Docker exist on the machine it was written on. Treat the first build and the first run as the real review.

The host harness (how far verification got)

host_test.c includes the app source and hands it a fake app_api_t, so the real state machine runs on the PC -- every string it draws is recorded and the lit pixels are counted:

gcc -std=gnu11 -O1 -Wall -Wextra -Werror -I.. -o host_test host_test.c && ./host_test

Measured: a reveal/flag/new-game/digit/quit script returns normally, draws the title and the mine count, and lights pixels; a script that blindly reveals 85 cells reaches a terminal state eleven times, draws BOOM, and MENU starts a new game afterwards. The win path (CLEAR) was not reached by blind play, so it is the one branch this harness does not exercise.

Still unverified: never built for ARM, never run on the radio -- no arm-none-eabi-gcc and no Docker where it was written. The first build and the first run are the real review.