Record the API gaps blocking akbasic
Some checks failed
libakgl CI Build / cmake_build (push) Failing after 19s
libakgl CI Build / mutation_test (push) Failing after 17s

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 06:29:17 -04:00
parent 6dfe7487ae
commit 5f03475e0f

67
TODO.md
View File

@@ -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.**