Agent Integration - CRT Basic
Table of Contents
1. Built to be driven by an AI agent
CRT Basic exists so that an AI agent can work with BASIC programs end to end — write one, run it, see what it drew, press its keys, and assert on its internal state — with no human in the loop. Every piece of the runtime is therefore observable and scriptable:
| Capability | Mechanism | |
|---|---|---|
| Run without a display | --headless |
|
| Bound endless loops | --steps=N statement budget |
|
| See the screen | --screenshot=out.png (visual page as PNG) |
|
| Compare pixels exactly | --raw=out.raw (raw indexed video memory) |
|
| Press keys programmatically | GameDriver scripted keystrokes |
|
| Move/click the mouse programmatically | GameDriver --mouse scripted deltas and button presses |
|
| Read program state | --vars=a%,score prints variables after the run |
|
| Count work done | "Statements executed" report |
The interactive window and the headless path share the same
VgaDevice and KeyboardQueue, so a program cannot tell which one
it runs under.
In the interactive window the mouse uses a grab model, DOSBox-style:
the program sees no mouse input until you click the window, which
captures the pointer (cursor hidden, movement reported as deltas).
Ctrl+F10 releases the grab. The grab never warps the pointer —
on Wayland, XTest injection (Robot, xdotool) goes through a
RemoteDesktop permission popup, and Robot.mouseMove hangs on some X
servers. Instead, capturing covers the whole desktop (all screens)
with a transparent, always-on-top overlay window that receives every
mouse event, so the pointer effectively cannot leave the program.
Without a compositor (translucency unsupported) the grab degrades to
in-window tracking.
2. Command line
crtbasic program.bas [--headless] [--screenshot=out.png] [--raw=out.raw] [--steps=N]
| Option | Effect |
|---|---|
| (none) | Interactive: Swing window, live screen ~30 fps, keyboard input |
--headless |
No window; input never blocks; dumps afterwards |
--screenshot=FILE |
PNG of the visual page (implies headless) |
--raw=FILE |
Raw indexed pixels of all video pages (implies headless) |
--steps=N |
Stop after N executed statements (implies headless) |
A run reports its statement count to stderr, so "did the loop spin?" is answerable without looking at pixels.
3. The GameDriver: scripted play-testing
--headless cannot inject keys mid-run. For keyboard-driven games
there is a dedicated driver (in test sources):
java -cp target/classes:target/test-classes \ eu.svjatoslav.crtbasic.drivers.GameDriver \ worm.bas --keys="RR~~~UU~~~LL" --interval=12 \ --steps=8000000 --screenshot=end.png --vars=score%,lives%
The key script delivers one input event per --interval milliseconds
(default 12) while the program runs:
| Script character | Meaning |
|---|---|
R L U D |
Arrow keys (right/left/up/down) |
~ |
Pause: no key, just let time pass |
| anything else | The typed character itself |
A --mouse script feeds the emulated mouse TSR: space-separated
tokens, one event per interval, interleaved with the key script:
| Token | Meaning |
|---|---|
dx,dy |
movement delta in video-mode pixels (-10,0, 0,5) |
b0 b1 b2 |
left / right / middle button press |
After the script is exhausted the program keeps running until it ends
or the statement budget (default 8M) is spent. The named --vars are
then printed from the global scope — so an agent can verify "after
this play sequence, score% must be 3" directly, and look at
end.png to verify what the screen showed.
4. Debugging aid: the line log
Setting the system property crtbasic.linelog to a file path makes
every LINE statement append its post-conversion coordinates
(x1,y1,x2,y2,color) to that file — handy for tracing exactly which
coordinates a program drew, frame by frame.
5. Programmatic embedding
The runtime is a plain library — no window system required:
String source = Files.readString(path, Charset.forName("IBM437")); List<Ast.Stmt> program = Parser.parse(source); VgaDevice vga = new VgaDevice(); KeyboardQueue keys = new KeyboardQueue(); Interpreter interpreter = new Interpreter(vga, keys, new SoundQueue()); interpreter.setMaxStatements(100_000); // statement budget interpreter.setBlockOnInput(false); // headless: input never blocks interpreter.setBaseDirectory(path.getParent()); // OPEN resolves here keys.pushChar(' '); // inject input any time interpreter.machine().mouse().pushMove(5, 0); // inject mouse (qbext TSR) interpreter.run(program); vga.dumpPng(Path.of("out.png")); // screenshot byte[] pixels = vga.framebuffer().pageData(0); // raw indexed pixels Object score = interpreter.variable("score%"); // program state long steps = interpreter.statementsExecuted(); // work performed
This is exactly what Main and GameDriver do; nothing is hidden
behind the CLI.
6. Visual verification showcase
VideoDemoMain (test sources) renders a set of showcase frames —
SCREEN 13 color stripes with text being erased by spaces, a
VIEW PRINT scroll region, the SCREEN 12 color table, SCREEN 0
CP437 box drawing — as upscaled PNGs:
mvn package java -cp target/classes:target/test-classes eu.svjatoslav.crtbasic.video.VideoDemoMain # frames written to /tmp/crt-basic-demo/
These exist so that an agent with vision can verify the video subsystem by looking at its output, not just by reading assertions.