From 5f03475e0fa81ec2fbd966526f5ba2800faf3b9d Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Fri, 31 Jul 2026 06:29:17 -0400 Subject: [PATCH] Record the API gaps blocking akbasic akbasic is a C port of the BASIC interpreter being built to link into libakgl as a scripting engine for game authors. Its interpreter core is complete and passes its whole acceptance corpus against a stdio text sink; four gaps here block the libakgl-backed sink and the graphics, sound and console verbs of Commodore BASIC 7.0. Filed rather than worked around downstream, because growing libakgl to serve a consumer is the wanted outcome. Each entry says what the BASIC verb needs, what the akgl_* entry point should look like, and what would cover it. Only the first blocks work already designed and waiting. text.h can load a font and render a string and cannot say how large that string will be, so there is no way to build a terminal-style surface on it -- a cursor needs the advance width of a cell and wrapping needs to know where a string crosses the margin. The reference interpreter derived its whole character grid from SDL2_ttf's SizeUTF8("A"). akgl_text_measure() over TTF_GetStringSize closes it. The other three block verbs not yet started: draw.h declares one function and src/draw.c is at 0%, so DRAW/BOX/CIRCLE/PAINT have nothing to plot with; there is no audio API at all despite SDL3_mixer being vendored, and PLAY/ENVELOPE want a synthesised voice SDL3_mixer does not provide; and controller.h is built around event handlers the host pumps, which suits a game loop but not GET/GETKEY -- those ask whether a keystroke is waiting and must not require the interpreter to own the event loop, which goal 3 forbids anyway. Co-Authored-By: Claude Opus 5 (1M context) --- TODO.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/TODO.md b/TODO.md index 3b57f8a..40e3c14 100644 --- a/TODO.md +++ b/TODO.md @@ -626,6 +626,73 @@ not saw every test abort before `main()` with "cannot open shared object file". `CMakeLists.txt` now sets `BUILD_RPATH` on the library, the utility, and every test target, and prepends the build tree to `LD_LIBRARY_PATH` for the CTest run. +## API gaps blocking akbasic + +`akbasic` (the C port of the BASIC interpreter, `source.starfort.tech/andrew/akbasic`) is +being built to link into `libakgl` as a scripting engine for game authors. Its interpreter +core is complete and passes its whole acceptance corpus against a stdio text sink; the +`libakgl`-backed sink and the graphics, sound and console verbs of Commodore BASIC 7.0 are +blocked on the four gaps below. + +These are filed here rather than worked around in `akbasic` because growing `libakgl` to serve +a consumer is the wanted outcome. Each entry says what the BASIC verb needs, what the +`akgl_*` entry point should look like, and what would cover it. + +1. **No way to measure rendered text.** `include/akgl/text.h` exposes `akgl_text_loadfont()` + and `akgl_text_rendertextat()`, and nothing that reports how large a string will be in a + given font. A terminal-style text surface cannot be built on that: a cursor needs the + advance width of one cell, and wrapping needs to know where a string crosses the right + margin. The reference interpreter got this from SDL2_ttf's `font.SizeUTF8("A")` and derived + its whole character grid from it. + + Wants `akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure(TTF_Font *font, char *text, int *w, int *h);` + over `TTF_GetStringSize`, and probably a companion + `akgl_text_measure_wrapped(font, text, wraplength, w, h)` matching the `wraplength` + argument `akgl_text_rendertextat` already takes. Tests: a known string in a known font at a + known size, the empty string, and a wrapped string wide enough to force two lines. + + This is the only one of the four that blocks work already designed and waiting. **`akbasic` + cannot render any output through `libakgl` until it lands.** + +2. **No immediate-mode drawing.** `include/akgl/draw.h` declares exactly one function, + `akgl_draw_background(int w, int h)`, and `src/draw.c` is at 0% coverage. BASIC 7.0's + graphics verbs are all immediate-mode plotting against the current screen: `DRAW` (line and + point), `BOX`, `CIRCLE`, `PAINT` (flood fill), `LOCATE` (set the pixel cursor), `COLOR`, + and `SSHAPE`/`GSHAPE` (save and restore a rectangle of pixels). + + Wants an `akgl_draw_*` family taking the renderer the host already initialized -- + `akgl_draw_line`, `_rect`, `_filled_rect`, `_circle`, `_point`, `_flood_fill`, + `_copy_region` -- in the shape of the existing `akgl_render_2d_draw_texture`. SDL3's + `SDL_RenderLine`/`SDL_RenderRect`/`SDL_RenderFillRect` cover most of it; the circle and the + flood fill do not exist in SDL3 and need writing. Tests belong with the offscreen renderer + harness described under "Remaining work": render a known shape, read the target back, and + compare against a reference surface with the existing `akgl_compare_sdl_surfaces`. + +3. **No audio API at all.** `SDL3_mixer` is a vendored dependency and `registry.h` declares + `AKGL_REGISTRY_MUSIC`, but there is no `src/audio.c`, no `include/akgl/audio.h`, and no + `akgl_*` symbol that opens a mixer, loads a chunk, or plays a note. BASIC 7.0's sound verbs + are `SOUND` (a tone on a voice, with a duration), `PLAY` (a string of notes in a + Commodore-specific notation), `ENVELOPE` (ADSR per voice), `FILTER`, `VOL` and `TEMPO`. + + `PLAY` and `ENVELOPE` want a synthesised voice rather than a sample, which SDL3_mixer does + not provide directly -- the honest first step is a small tone generator feeding + `SDL_AudioStream`, with `akgl_audio_init`, `akgl_audio_tone(voice, hz, ms)`, + `akgl_audio_envelope(voice, a, d, s, r)` and `akgl_audio_volume(level)`. This is the largest + of the four and the one most worth designing before writing. Tests can run under the dummy + audio driver and assert state transitions rather than sound. + +4. **No non-blocking keystroke read.** `include/akgl/controller.h` is built around SDL event + handlers the host pumps (`akgl_controller_handle_event` and friends), which suits a game + loop and does not suit `GET` and `GETKEY` -- those ask "is there a keystroke waiting, yes or + no" and must not require the interpreter to own the event loop. Goal 3 of `akbasic` forbids + it owning one. + + Wants `akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *available);` + reading a small ring buffer that `akgl_controller_handle_event` already fills, so the host + keeps pumping events and the interpreter drains characters at its own pace. Tests: push + synthetic `SDL_EVENT_KEY_DOWN` events through the existing handler and drain them, plus the + empty-buffer and overflow cases. + ## Carried over 1. **Make character-to-sprite state bindings release their references symmetrically.**