From 5077efc3a739a3a33eb0441f22409e488da080a7 Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Fri, 31 Jul 2026 08:01:16 -0400 Subject: [PATCH] Re-sequence the language queue now that libakgl closed the gaps The bump in 7f16337 updated sections 3, 7 and 8 but left the section 4 work queue still marking groups G and I "file it", which is no longer true. Groups G and I are unblocked outright. Group E splits: GET, GETKEY and SCNCLR need nothing further, while WINDOW, KEY, SLEEP, WAIT and TI still want the sink or a clock. LOCATE was listed in both E and G; it belongs to G alone, because in BASIC 7.0 it moves the graphics pixel cursor DRAW starts from and the text cursor verb is CHAR, already in group D. One gap is genuinely outstanding and upstream named it in the same commit that added the audio API: FILTER has no SDL3 primitive behind it. It is recorded in section 7 with the behaviour to apply until an akgl_audio_filter() exists -- refuse at execution rather than silently ignore, since a program that asks for a low-pass and gets an unfiltered square wave has been lied to. Also records why the libakgl requirement is pinned by commit rather than by version: 42b60f7 added 22 public symbols across four headers and left both VERSION 0.1.0 and the libakgl.so.0.1 soname alone, so AKGL_VERSION_AT_LEAST cannot tell the two trees apart and a binary built against the new headers resolves against an old .so without complaint. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 6 ++++-- TODO.md | 46 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8801270..2b16ff8 100644 --- a/README.md +++ b/README.md @@ -410,7 +410,9 @@ Plus six more the original already carried: a leading `0` selects base 8, so `PR * `USING`, `VERIFY`, `VOL`, `WAIT`, `WIDTH`, `WINDOW` * The I/O-channel variants (`GETIO`, `INPUTIO`, `OPENIO`, `PRINTIO`, `RECORDIO`) -None of those is blocked on a missing `libakgl` capability any more. Four were — text measurement, immediate-mode drawing, audio, and a non-blocking keystroke read — and rather than work around them here they were filed in [`deps/libakgl/TODO.md`](deps/libakgl/TODO.md) under "API gaps blocking akbasic". All four have since landed upstream (`akgl_text_measure`, the `akgl_draw_*` family, `akgl_audio_*`, and `akgl_controller_poll_key`), so what is left is akbasic-side work. +One of those is still blocked on a missing `libakgl` capability, and it is only one. Four were — text measurement, immediate-mode drawing, audio, and a non-blocking keystroke read — and rather than work around them here they were filed in [`deps/libakgl/TODO.md`](deps/libakgl/TODO.md) under "API gaps blocking akbasic". All four have since landed upstream (`akgl_text_measure`, the `akgl_draw_*` family, `akgl_audio_*`, and `akgl_controller_poll_key`), so what is left is akbasic-side work. + +The exception is `FILTER`, which sets the SID's filter cutoff, band switches and resonance. `akgl_audio_*` synthesises raw waveforms and mixes them; there is no filter stage to configure, and SDL3 supplies no primitive to build one from. Until an `akgl_audio_filter()` exists, `FILTER` parses and then refuses at execution rather than being silently ignored — a program that asks for a low-pass and gets an unfiltered square wave has been lied to. ## Deliberately out of scope @@ -422,7 +424,7 @@ None of those is blocked on a missing `libakgl` capability any more. Four were * [libakerror](https://source.starfort.tech/andrew/libakerror) 1.0.0 — TRY/CATCH-style error contexts. Every function that can fail returns one. * [libakstdlib](https://source.starfort.tech/andrew/libakstdlib) 0.1.0 — libc wrappers that report through `libakerror`. -* [libakgl](https://source.starfort.tech/andrew/libakgl) 0.1.0 — **optional**, only for `-DAKBASIC_WITH_AKGL=ON`. Pulls in SDL3. +* [libakgl](https://source.starfort.tech/andrew/libakgl) 0.1.0 at commit `42b60f7` or later — **optional**, only for `-DAKBASIC_WITH_AKGL=ON`. Pulls in SDL3. The requirement is pinned by submodule commit rather than by version on purpose: 42b60f7 added 22 public symbols and left the version and the `libakgl.so.0.1` soname alone, so `AKGL_VERSION_AT_LEAST(0, 1, 0)` cannot tell the two trees apart. Filed upstream; `TODO.md` section 8 has the detail. * [basicinterpret](https://source.starfort.tech/andrew/basicinterpret) — the Go original, vendored as the behavioural spec and the acceptance corpus. Not linked, not built. Everything is a submodule; `git submodule update --init --recursive` gets all of it. There is nothing to install first. diff --git a/TODO.md b/TODO.md index 6058358..b432164 100644 --- a/TODO.md +++ b/TODO.md @@ -424,13 +424,18 @@ Order by what unblocks the most, and by what does not need `libakgl` to grow fir | B. Housekeeping | `NEW`, `CLR`, `CONT`, `RESTORE`, `RENUMBER`, `SWAP`, `TRON`, `TROFF`, `HELP` | nothing | | C. Errors | `TRAP`, `RESUME`, `ER`, `ERR` | needs a BASIC-visible error object | | D. Strings/format | `USING`, `PUDEF`, `WIDTH`, `CHAR` | nothing | -| E. Console | `GET`, `GETKEY`, `SCNCLR`, `LOCATE`, `WINDOW`, `KEY`, `SLEEP`, `WAIT`, `TI` | sink + `libakgl` input | +| E. Console | `GET`, `GETKEY`, `SCNCLR`, `WINDOW`, `KEY`, `SLEEP`, `WAIT`, `TI` | `GET`/`GETKEY`/`SCNCLR` nothing; the rest want the sink or a clock | | F. Disk | `DOPEN`, `DCLOSE`, `APPEND`, `RECORD`, `HEADER`, `COLLECT`, `BACKUP`, `COPY`, `CONCAT`, `RENAME`, `SCRATCH`, `DIRECTORY`, `CATALOG`, `DCLEAR`, `DVERIFY`, `SAVE`, `LOAD`, `VERIFY`, `BLOAD`, `BSAVE`, `BOOT` | `aksl_f*` only; no new akgl | -| G. Graphics | `GRAPHIC`, `DRAW`, `BOX`, `CIRCLE`, `PAINT`, `COLOR`, `SCALE`, `SSHAPE`, `GSHAPE`, `LOCATE` | **`libakgl` immediate-mode draw API — file it** | +| G. Graphics | `GRAPHIC`, `DRAW`, `BOX`, `CIRCLE`, `PAINT`, `COLOR`, `SCALE`, `SSHAPE`, `GSHAPE`, `LOCATE` | nothing — `akgl_draw_*` landed at 42b60f7 | | H. Sprites | `SPRITE`, `MOVSPR`, `SPRCOLOR`, `SPRDEF`, `SPRSAV`, `COLLISION` | `libakgl` sprite/actor API — check before filing | -| I. Audio | `PLAY`, `SOUND`, `ENVELOPE`, `FILTER`, `VOL`, `TEMPO` | **`libakgl` has no mixer-backed audio API — file it** | +| I. Audio | `PLAY`, `SOUND`, `ENVELOPE`, `VOL`, `TEMPO` | nothing — `akgl_audio_*` landed at 42b60f7 | +| I′. Audio, still gapped | `FILTER`, `SOUND`'s sweep arguments | `libakgl` has neither; see §7 | | J. Machine | `SYS`, `FETCH`, `STASH`, `POKE`/`PEEK` variants | nothing | +`LOCATE` used to appear in both E and G. It belongs to G alone: in BASIC 7.0 `LOCATE` moves the +*graphics* pixel cursor that `DRAW` starts from, and the text cursor verb is `CHAR`, which is +already in group D. + **Out of scope, and staying that way** — the reference marks these as incompatible with a modern PC and that reasoning stands: `BANK` (no bank switching), `FAST` (irrelevant CPU speed control), `MONITOR` (no machine-language monitor). @@ -627,8 +632,21 @@ Building against any of it needs `-DAKBASIC_WITH_AKGL=ON`, which pulls in `libak submodules (SDL and friends). Those are not initialized in a default clone; `git submodule update --init --recursive` gets them. -No gaps are currently outstanding. When the next one turns up, file it there rather than -working around it here. +**One gap is outstanding, and upstream named it itself.** The commit that added the audio API +says plainly what it does not cover: *"Still missing for a complete BASIC sound vocabulary: +`FILTER` (SDL3 has no filter primitive; this would need writing), `TEMPO` and the `PLAY` +note-string parser, both of which belong in the interpreter rather than here."* + +- **`TEMPO` and the `PLAY` parser are ours**, not a gap. They are group I work in this + repository and nothing upstream is waiting on. +- **`FILTER` is a real gap.** BASIC 7.0's `FILTER freq, lp, bp, hp, res` sets the SID's + filter cutoff, the three band-pass switches and the resonance. `akgl_audio_*` synthesises + raw waveforms and mixes them; there is no filter stage to configure and SDL3 supplies no + primitive to build one from. Until an `akgl_audio_filter()` exists, `FILTER` is accepted by + the parser and refused at execution with `AKBASIC_ERR_DEVICE` rather than silently ignored — + a program that asks for a low-pass and gets an unfiltered square wave has been lied to. + +When the next gap turns up, file it there rather than working around it here. --- @@ -659,7 +677,23 @@ Dependency baseline: |---|---|---| | `deps/libakerror` | 1.0.0 | Private ownership-enforced status registry. akbasic reserves 512–767 in `akbasic_error_register()`. Does not namespace its `coverage` target when embedded — worked around in our `CMakeLists.txt`. | | `deps/libakstdlib` | 0.1.0 | soname `libakstdlib.so.0.1`. `AKSL_VERSION_CHECK()` asserted in `tests/version_check.c`. Its `aksl_ato*` family is banned here — see §1.9. | -| `deps/libakgl` | 0.1.0 | Migrated to the 1.0.0 registry and owns 256–260. Not yet linked: `AKBASIC_WITH_AKGL` defaults OFF and `src/sink_akgl.c` does not exist. | +| `deps/libakgl` | 0.1.0, **pinned at commit 42b60f7** | Migrated to the 1.0.0 registry and owns 256–260. Not yet linked: `AKBASIC_WITH_AKGL` defaults OFF and `src/sink_akgl.c` does not exist. The version number cannot carry this requirement — see below. | + +**The `libakgl` requirement is pinned by commit, not by version, and that is a defect +upstream.** 42b60f7 added 22 public symbols across four headers — `akgl_draw_*` (8), +`akgl_audio_*` (10), `akgl_controller_poll_key`/`_flush_keys`, and `akgl_text_measure`/ +`_measure_wrapped` — and left `project(akgl VERSION 0.1.0)` and the `libakgl.so.0.1` soname +unchanged. By that repository's own stated rule, *"0.1 and 0.2 are different ABIs"*, this +should have been 0.2.0. Two consequences land here: + +- A binary compiled against the new headers links happily against an installed + `libakgl.so.0.1` built from the old tree, and fails at symbol resolution rather than at + configure time. +- `AKGL_VERSION_AT_LEAST(0, 1, 0)` is true for both trees, so nothing here can feature-test + for the new API. The submodule pointer is the only thing that expresses the requirement. + +Filed in `deps/libakgl/TODO.md`. When it is fixed, add the floor to the `find_package` call +and say so here. CI is split in two. `.gitea/workflows/ci.yaml` runs on push -- the suite, ASan+UBSan, coverage and a two-file mutation run -- and `.gitea/workflows/release.yaml` is manual