Deliver GALAGA tutorial and akbasic enemy scripting example #39

Closed
logikoma wants to merge 7 commits from 34 into feature/reduce_memory_usage

7 Commits

Author SHA1 Message Date
dd10dc143a Close the remaining cold-read gaps: bind, labels, menus and main's shape
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 5m32s
akbasic CI Build / sanitizers (push) Failing after 15m58s
akbasic CI Build / coverage (push) Failing after 20m7s
akbasic CI Build / mutation_test (push) Failing after 4m29s
akbasic CI Build / akgl_build (push) Failing after 12m28s
Three more Haiku-class cold reads of the chapters, each against the
amended text. What each surfaced is now shown rather than described: the
akbasic_host_register_type()/akbasic_host_bind() boot calls, the
declare_play() label listing, the akgl_UiMenu static and its
handle_event signature, one control-handler pair, and main()'s
ATTEMPT/HANDLE_DEFAULT/FINISH_NORETURN shape with the CATCH-inside-
ATTEMPT rule stated. By the fourth read the generated player.c and
enemies.c compiled untouched and every remaining guess was a tuning
value the chapters deliberately leave open.

Co-authored-by: andrew <andrew@aklabs.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
2026-08-04 09:22:43 -04:00
54ab85a276 Benchmark the boundary and close the cold read's tutorial gaps
The interop test now ends with a measured comparison: 24,000 formation
updates through the script boundary against a line-for-line C
translation of the same state machine. 881 us against 0.01 us per call
on this machine, quoted verbatim in the new chapter 21 Step 11 with the
architectural decisions it prices.

A Haiku-class cold read of the chapters produced a build whose failures
were all mechanical -- invented include paths, never-shown sink statics,
guessed status codes and character names. The chapters now carry the
include lists, the script.c statics, the status-code roster, the
sprite/character table, the full CMake recipe and the explosion spawn's
HANDLE example, so none of those have to be guessed again.

Co-authored-by: andrew <andrew@aklabs.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
2026-08-04 09:01:59 -04:00
d5a0edd692 Write the GALAGA tutorial chapters and the repeated-host-calls guide
docs/20 builds the engine and the boundary: the startup order, the
starfield, actors and collision, booting a DEF-only script, the issue #8
mode workaround, the custom update hook, first light, screens, and the
headless harness. docs/21 builds the three shared structures and the AI:
the host type tables, the actor binding, the randomness route around
issue #16, the measured case against structure arguments (issue #36),
the three language rules that shape the script, the maneuvers, the
argued formation decision, the script-death policy, and the interop
proof. Every fenced block runs under tests/docs_examples.sh in both
build configurations; five new preludes carry the C fragments.

docs/10 gains the 'Calling a function every frame' section the chapters
lean on: the per-call akbasic_environment_zero() rule, the set_mode(RUN)
workaround, the clear_error() revival, and the case for rebinding over
structure arguments. Index rows and chapter counts updated.

Co-authored-by: andrew <andrew@aklabs.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
2026-08-04 08:47:43 -04:00
47c6be58c5 Update the breakout chapter's scope-pool figure to this branch's 12
The environment pool shrank from 32 to 12 in the memory-reduction work
and the chapter's exhaustion transcript still asserted the old number,
which is a docs_examples failure on every run of this branch.

Co-authored-by: andrew <andrew@aklabs.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
2026-08-04 08:47:42 -04:00
27837aeabc Add the galaga example: a C engine with akbasic as its enemy brain
A GALAGA-style fixed shooter whose engine is C on libakgl (null physics)
with the interpreter embedded as the scripting engine that owns every
enemy's behavior. One DEF-only script is called per enemy per frame
through a custom akgl_Actor update hook; SELF@, ACTOR@ and GAME@ are host
bindings, so the script reads and writes the engine's real memory -- the
boss even swaps its own damage sprite by raising an actor state bit from
BASIC. Bullets, collision, scoring and screens stay C.

Structure arguments were measured and rejected for the per-frame path:
each pointer parameter spends a value-pool slot the pool never reclaims,
1,015 calls to exhaustion against an unbounded rebind (issue #36).

Built when AKBASIC_WITH_AKGL=ON. Two CTest entries: a 600-frame headless
autoplay run under the dummy SDL drivers, and an interop round-trip test
that links the real script.c and galaga.bas and pins the four boundary
claims, 24,000 sustained calls among them. docs_galaga_figures
regenerates the two checked-in figures. Art is Kenney CC0, byte for
byte, with provenance.

Co-authored-by: andrew <andrew@aklabs.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
2026-08-04 08:47:21 -04:00
743e610f8f Reset scratch per line and unwind dead scopes in host function calls
akbasic_runtime_call_function()'s body loop drives process_line_run()
directly, skipping the per-line prologue akbasic_runtime_step() provides.
The call environment's value scratch therefore accumulated across the
whole body, and any body past about ten real lines died with 'Maximum
values per line reached' -- a limit that is supposed to be per line. The
loop now runs the same prologue step() does.

A body that died also left its call scopes active: nothing popped them,
so a host absorbing script errors drained the twelve-slot environment
pool after twelve dead calls. The loop now unwinds to the caller's
environment on every exit path.

New: akbasic_runtime_clear_error(), the missing half of host revival. A
run's first BASIC-level error latches deliberately, and set_mode(RUN)
alone cannot un-decide that; a host that absorbed the error calls this
beside it. Both defects and the revival dance are pinned in
tests/user_functions.c.

Co-authored-by: andrew <andrew@aklabs.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
2026-08-04 08:47:05 -04:00
dde1d91c6e Reset the scanner's leftover token type between lines
The REM early-exit leaves tokentype holding AKBASIC_TOK_REM, and the scan
loop's post-switch check reads it before the next line's first character
has assigned anything. A line opening with whitespace then re-triggered
the REM break and scanned to nothing: every indented line after a REM was
silently skipped. Numbered programs never saw it -- the line number is the
first token and overwrites the leftover -- which is why the whole golden
corpus missed it and the unnumbered, indented galaga.bas found it.

Co-authored-by: andrew <andrew@aklabs.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
2026-08-04 08:46:51 -04:00