Add the akgl_ui subsystem: clay-backed menus, HUDs and dialogs #3
Reference in New Issue
Block a user
Delete Branch "clay-ui"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Nine commits bringing user interface layouts into the library on the clay layout engine (v0.14, vendored at
deps/clay), per the plan we settled: hybrid API, runtime-optional, and the tutorials left untouched as the comparison point.What this is
CLAY_IMPLEMENTATIONTU (src/ui_clay.c,-w), symbols deliberately exported because consumers'CLAY()macros resolve againstlibakgl.so.clay.hinstalls besidesemver.h; its zlib notice beside libccd's.akgl_ui_init. Static BSS arena, nomalloc; everyAKGL_UI_*ceiling is#ifndef-overridable and init refuses with both byte counts when the arena doesn't fit (measured: 812544 bytes needed at the default 1024 elements / 4096 words, against 1 MiB).akgl_ui_dialog/akgl_ui_label/akgl_ui_menuwidgets for the common cases, and the wholeCLAY()DSL betweenakgl_ui_frame_begin/frame_endwhen a screen outgrows them.akgl_draw_filled_rounded_rect,akgl_draw_arc,akgl_draw_set_clip— fixed arrays, no VLAs.akgl_ui_handle_eventwith a consumed contract (UI first, control maps second). Hit tests run against the previous frame's retained tree, with clay's full-screen root container excluded.mouseid/penidon control maps stay dead on purpose — TODO.md says where that work lands when a game wants mouse-driven actors.examples/uidemo+ headlessexample_uidemotest: title menu widget, options screen in rawCLAY(), HUD + dialog play screen, toured end-to-end by synthesized events. The tour caught a real bug — a stationary pointer pinning the menu selection against the keyboard — fixed with its own commit and regression test.docs/22-ui.md(appendix moves to 23): one linked-and-run example, eight checked excerpts, and the side-by-side withexamples/jrpg/textbox.c— the dialog widget's default style reproduces the textbox palette exactly so the 125-lines-vs-one-call trade is shown, not asserted. Chapter 21 keeps teaching the hand-rolled panel on purpose.AKGL_ERR_UI), new subsystem; the soname's major.minor makes the ABI change unmixable.Known deferrals, all documented
Image tint and CUSTOM render commands skipped; per-corner radii collapse to top-left; immediate-mode text cost per frame — the text ring cache (TODO.md plan item 2) now has the UI executor as its second consumer. The AGENTS.md "Testing a Tutorial" weaker-model pass has not yet run against chapter 22.
Verification
Full non-perf suite green (33/33), including the new
uisuite (lifecycle, arena exhaustion, fontId table, measure agreement, executor pixel round-trips, consumed contract, widget state machines),headers,api_surface,error_protocol,docs_examples,docs_screenshots, and all three example smoke runs. Every touched C file is a fixed point ofscripts/reindent.sh --check.🤖 Generated with Claude Code
https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
Chapter 22 weaker-model reader test — complete (commit
f2efbdf)Ran the AGENTS.md "Testing a Tutorial" protocol against the UI chapter: tree copied with
examples/anddocs/removed, chapter asTUTORIAL.md, the test font, and a screenshot helper marked as scaffolding. Build path proven first with a throwaway consumer (add_subdirectory+target_link_libraries(... akgl)— configures and builds clean, draws a label). The reader, on a weaker model, had to build a title-menu / score-HUD / dialog program from the chapter alone, drive it headlessly with synthesized events, and screenshot it.What happened
The reader built a program that compiled, ran, exited 0 — and reported success their own screenshots disproved. Their "play screen with dialog" capture was the title menu again: their synthesized key events set only
scancode,akgl_ui_menu_handle_eventmatches keycodes (key.key), so Return never fired and the program never left the title screen. Their report claimed the dialog and score "confirmed working". This is exactly the failure mode the protocol's verify-their-work-yourself rule exists for, and it caught it.With that one line fixed by me (plus an off-by-one where they screenshot and close the dialog on the same frame), everything they had built from the chapter worked: menu with inverted highlight, score label ticking top-right, dialog wrapping at the bottom. The chapter's actual teachings — the frame bracket, the widgets, the event routing, the activation pattern — all survived contact with a reader who had nothing else.
Findings: 3 confirmed of 8 reported
Confirmed and fixed in
f2efbdf:gotoerror handling formain()because no runnable snippet shows one. Added "What this chapter assumes" pointing at chapters 3, 7 and 17, and at the demo'sstartup()/main()as the complete reference.--demoscript teaches. The menus section now namesSDLK_UP/DOWN/RETURN, the gamepad buttons, and thekey.keytrap.AKGL_UI_DIALOG_HEIGHTruns visibly out of the panel. Deliberate (same behaviour as the hand-rolled textbox; visible overflow beats silent truncation) — now said so inakgl_ui_dialog's header docs.Rejected after reproduction, for the record: "must call
akgl_registry_init_propertiesbeforeakgl_set_property" (akgl_game_initalready does, src/game.c:219); "fonts live inlibakgl/assets/nottests/assets/" (that's the build tree'sfile(COPY)of tests/assets); plus three complaints that were artifacts of the sandbox or the headless harness rather than the chapter (missingexamples/, headless env vars, dummy-driver behaviour).Verification
docs_examplesgreen on the amended chapter (1 linked+run program with pinned output, 8 excerpts),headersgreen, reader's amended binary re-run by hand with both screenshots inspected. Sandbox preserved locally if you want to poke at what they wrote.🤖 Generated with Claude Code
https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
The env-list fix got every test green on the runner for the first time -- 37 of 37, the JUnit publish included -- and the job then failed in the artifact upload: upload-artifact@v4 speaks GitHub's v2 artifact service and hard-refuses any host that is not github.com ("not currently supported on GHES"). Gitea implements the v3 artifact API, so the three upload steps -- coverage, the benchmark tables, the valgrind logs -- pin to v3. Co-Authored-By: Claude Code (Claude Fable 5, claude-fable-5) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71Merged — first fully green CI run in the repository's history
All four jobs green on
5560edf: cmake_build 8m54s, performance 9m48s (budgets enforced at full scale), memory_check 14m39s (every suite and all three example games under valgrind, zero findings), mutation_test 27m24s. Merged to main asbbb7b8f.The CI failure was never this PR — no run in the retained history had ever passed, and getting to green peeled five layers, each hidden behind the one before it:
git@github.com:SSH URLs the runner has no key for → https (2242cfa).2242cfa).doxygen Doxyfilefails on warnings and every dot graph errored → package added (219d718).set_tests_propertieseats list values — PROPERTIES parses as name/value pairs, so every multi-var test ENVIRONMENT was silently truncated to its first entry and the LD_LIBRARY_PATH prepend lists to their first directory. Invisible for years: SDL3 falls through to the dummy video driver headless anyway, real audio works on every dev machine, RPATH covered the paths. A runner with no sound card was the first place it mattered →set_property(TEST ...)everywhere a list is set (d8458d1).upload-artifact@v4hard-refuses non-github.com hosts → pinned to v3, which Gitea implements (be3c8ae); and valgrind was memchecking/bin/bashfor the two shell-script docs tests (414 findings, all bash's own) → excluded by name with the reasoning beside the exclusion (5560edf).Items 1 and 3–5 were latent in CI config that had never executed end to end; item 2 and 4 were real defects in the tree that this PR flushed out. Every fix was pre-flighted locally in CI-shaped builds before pushing — the one thing that couldn't be (the runner's own package set and artifact API) is what took the extra iterations.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71