From c3d9e91bbd27c454da361a9a94eeb1b11f37b3bd Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Sun, 2 Aug 2026 00:23:36 -0400 Subject: [PATCH] Write down that a redraw has to fit inside one batch "Drawing does not persist across frames; redraw it every frame" was the advice, and it is not sufficient on its own. The host runs a fixed number of source lines and then presents, and presenting discards the drawing buffer -- so a run of drawing verbs longer than one budget is **torn**, not merely transient, and an `SSHAPE` at the end of it captures only what was issued since the present, over whatever the frame before left behind. Measured against the standalone frontend's 256: after synchronising to a jiffy edge, 220 lines of drawing survive a capture and 250 do not. There is no fix available that does not change what a host owns -- the budget is the host's and so is the present -- so this is documentation, in the three places it belongs. Chapter 13 beside the note it qualifies, with the numbers. Chapter 6 where `SSHAPE` is introduced, which is where a program meets it. Chapter 14 from the step loop's side, naming `AKBASIC_FRONTEND_STEPS_PER_FRAME` and explaining the one thing a script can do about it: `settime()` is called once a frame, so `TI#` changes on the first step of a batch and nowhere else, and spinning until it changes is the only frame synchronisation this dialect has. **Untested, deliberately.** Reproducing it needs the real frontend, a clock and a timing window, and a test that reproduced it would be reproducing a race. Recorded in TODO.md rather than left looking like an oversight. TODO.md section 9 item 5, struck as a documentation outcome. Co-Authored-By: Claude Opus 5 (1M context) --- docs/06-graphics.md | 8 ++++++++ docs/14-architecture.md | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/06-graphics.md b/docs/06-graphics.md index 2cd93c1..723b299 100644 --- a/docs/06-graphics.md +++ b/docs/06-graphics.md @@ -244,3 +244,11 @@ The graphics verbs draw straight to the renderer rather than into a display list anything drawn is overwritten by the text layer on the next frame. A program that wants its drawing to persist has to redraw it. This is recorded as a defect rather than a design; see Chapter 13. + +**A redraw also has to fit inside one batch.** The host runs a fixed number of source +lines and then presents, and presenting throws the drawing buffer away — so a run of +drawing verbs longer than one batch is torn rather than merely transient, and an +`SSHAPE` at the end of it captures only the part issued since the present. Watching +`TI#` change is how a program finds the boundary; Chapter 13 has the measured numbers and +[Chapter 18](18-tutorial-breakout-artwork.md#step-4-find-the-frame-boundary) has a +routine that uses them. diff --git a/docs/14-architecture.md b/docs/14-architecture.md index 6535570..2a18c7a 100644 --- a/docs/14-architecture.md +++ b/docs/14-architecture.md @@ -168,8 +168,22 @@ resume there. `akbasic_runtime_run(rt, n)` is `step()` in a `while` with a budget: at most `n` steps, then return regardless. That bound is the only thing standing between a script containing `10 GOTO 10` and your frame rate. `n <= 0` means unbounded, which is right for a test and -wrong for a game. The SDL frontend uses 256; the stdio driver uses 1, because it wants to -refresh the clock between steps. +wrong for a game. The SDL frontend uses 256 (`AKBASIC_FRONTEND_STEPS_PER_FRAME`); the +stdio driver uses 1, because it wants to refresh the clock between steps. + +**That budget is visible to a script, and drawing is where it shows.** The frontend +presents the frame when `run()` comes back, and presenting discards the drawing buffer — +so a sequence of drawing verbs longer than one budget is cut in half by the present in +the middle of it, and an `SSHAPE` afterwards captures only what was issued since. Not a +transient artefact: a torn capture, over whatever the frame before left behind. + +A script cannot read the budget, but it can *see* it: `akbasic_runtime_settime()` is +called once per frame by the host, so `TI#` changes on the first step of a batch and +nowhere else. Spinning until it changes is the only frame synchronisation this dialect +has, and it is what +[Chapter 18](18-tutorial-breakout-artwork.md#step-4-find-the-frame-boundary) is built on. +A host that gives the interpreter a larger budget makes more drawing fit; one that never +calls `settime()` takes the synchronisation away entirely. Time comes in from outside. `akbasic_runtime_settime(rt, ms)` is how the interpreter learns what time it is; it reads no clock, because it owns no loop and must not block. A