Agent Integration - CRT Basic

Table of Contents

Back to main documentation

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.

Created: 2026-08-31 ma 01:26

Validate