Generate the documentation's figures from the listings they illustrate

Chapters 6 and 8 described what a verb draws in prose. Eight figures now show
it, and each one is produced by running the BASIC listing printed immediately
above it -- so a picture cannot drift away from the code beside it, which is the
way a screenshot goes wrong and the way nothing notices.

tools/screenshot.c is a second SDL host, much smaller than the frontend: dummy
video driver, software renderer, run to completion, read the target back, write
a PNG. It draws no text layer on purpose, so a READY in the corner is not noise
in a figure about BOX and no font has to be resolved.

tools/docs_screenshots.sh reads the new screenshot=NAME fence tag straight out
of the markdown. size=WxH is the second tag, and SCALE's figure uses it: the
point being made is a 320x200 listing filling a larger window, which cannot be
made on a 320x200 surface.

Two gates, answering different questions. docs_examples fails a tagged block
with no image, in both configurations, so a figure cannot be added and
forgotten. docs_screenshots -- a CTest, AKGL build only -- re-renders every
figure and compares byte for byte, so a listing edited without regenerating
fails. Only the second catches a stale picture.

The PNGs are checked in because a reader on the forge has no build tree, and
docs/images/README.md says loudly that they are generated. Regenerating is never
part of a build: the target is run deliberately, so a make cannot put eight
binary diffs in front of whoever ran it.

Drawing the BOX figure caught a defect in TODO.md itself. Deviation 16 claimed
in bold that BOX fills on a negative angle while its own paragraph said the fill
was filed rather than implemented. BOX cannot fill, and filled_rect is reached
by no verb as a result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-01 07:37:04 -04:00
parent 737fdc760f
commit 16b38c1138
18 changed files with 759 additions and 40 deletions

View File

@@ -65,43 +65,86 @@ small number that is not a colour.
`GRAPHIC mode` chooses a screen mode; `GRAPHIC CLR` clears it. Mode 0 is text and
refuses to draw.
Every picture in this chapter is generated by running the listing above it; see
`MAINTENANCE.md` if you are editing one.
### DRAW
```basic requires=akgl
10 DRAW 1, 10, 20
20 DRAW 1, 0, 0 TO 100, 100 TO 200, 0
```basic requires=akgl screenshot=draw
10 COLOR 1, 8
20 DRAW 1, 20, 180 TO 90, 40 TO 160, 150 TO 230, 20 TO 300, 120
30 COLOR 2, 6
40 DRAW 2, 20, 190 TO 300, 190
50 LOCATE 160, 100
60 COLOR 3, 3
70 DRAW 3
```
![](images/draw.png)
One coordinate pair plots a point. Two or more, separated by `TO`, draw a polyline. A
bare `DRAW 1` plots wherever `LOCATE` left the pixel cursor.
bare `DRAW 3` — the last line above, and the single red pixel in the middle of the
picture — plots wherever `LOCATE` left the pixel cursor.
### BOX
```basic requires=akgl
10 BOX 1, 10, 10, 40, 40
```basic requires=akgl screenshot=box
10 COLOR 1, 8
20 BOX 1, 20, 30, 130, 140
30 COLOR 2, 6
40 BOX 2, 180, 30, 290, 140, 30
50 COLOR 3, 3
60 LOCATE 300, 190
70 BOX 3, 20, 160
```
Corners, and an optional rotation angle. An unrotated `BOX` outlines rather than fills.
![](images/box.png)
Corners, and an optional rotation angle — the green box is the same box turned 30
degrees about its own centre. Two coordinates instead of four take the other corner
from the pixel cursor, which is the red box.
**`BOX` always outlines; it cannot fill.** BASIC 7.0 selects fill with a seventh
argument and that is not implemented here — see Chapter 13. `PAINT` is the fill you
have.
### CIRCLE
```basic requires=akgl
10 CIRCLE 1, 160, 100, 50, 30
```basic requires=akgl screenshot=circle
10 COLOR 1, 8
20 CIRCLE 1, 80, 70, 60, 60
30 COLOR 2, 6
40 CIRCLE 2, 230, 70, 75, 45
50 COLOR 3, 3
60 CIRCLE 3, 160, 140, 130, 50, 90, 270
```
Source, centre, then the two radii — so it draws ellipses. Further arguments give a
start angle, an end angle, a rotation and the degree increment, which is what makes it
an arc or a polygon.
![](images/circle.png)
Source, centre, then the two radii — equal radii give a circle and unequal ones an
ellipse. Two further arguments are a start and an end angle, which is what makes the
red arc: 90 to 270 is the bottom half, because angles here are degrees clockwise from
straight up, the same convention `MOVSPR` uses. Beyond those come a rotation and the
degree increment, and a large increment is what turns a circle into a polygon.
### PAINT
```basic requires=akgl
10 PAINT 1, 160, 100
```basic requires=akgl screenshot=paint
10 COLOR 1, 8
20 CIRCLE 1, 100, 100, 70, 70
30 BOX 1, 180, 50, 290, 150
40 COLOR 2, 6
50 PAINT 2, 100, 100
60 COLOR 3, 3
70 PAINT 3, 230, 100
```
Flood-fills the region containing a point. If the region is too large for the fill's
own working space it stops and reports rather than leaving a half-painted screen with
no explanation.
![](images/paint.png)
Flood-fills the region containing a point, stopping at whatever is already drawn — so
the outline you fill inside can come from any verb. If the region is too large for the
fill's own working space it stops and reports rather than leaving a half-painted screen
with no explanation.
### LOCATE
@@ -110,10 +153,23 @@ coordinates finishes.
### SCALE
```basic requires=akgl
10 SCALE 1, 1023, 1023
```basic requires=akgl screenshot=scale size=640x400
10 COLOR 1, 3
20 BOX 1, 0, 0, 319, 199
30 SCALE 1, 319, 199
40 COLOR 2, 6
50 BOX 2, 0, 0, 319, 199
60 DRAW 2, 0, 0 TO 319, 199
```
![](images/scale.png)
That picture is 640 by 400, and both boxes name the same four numbers. The red one is
drawn with `SCALE` off, so its coordinates are pixels and it covers exactly the
top-left 320 by 200 of the window — which is what a C128 listing does here. The green
one is drawn after `SCALE 1, 319, 199` and fills the window, and the diagonal confirms
that 319, 199 reaches the last pixel rather than stopping one short of it.
Turns on user coordinates and gives their maxima. With it on, your coordinates are
mapped onto the drawing surface: 0 is the first pixel and the maximum you gave is the
*last* one, so `DRAW 1, 1023, 1023` above reaches the bottom-right corner rather than
@@ -137,12 +193,21 @@ parallel passes; see Chapter 13.
`SSHAPE` copies a rectangle off the screen and `GSHAPE` stamps it back:
```basic requires=akgl
10 BOX 1, 0, 0, 20, 20
20 SSHAPE A$, 0, 0, 20, 20
30 GSHAPE A$, 100, 100
```basic requires=akgl screenshot=shapes
10 COLOR 1, 8
20 CIRCLE 1, 40, 40, 30, 30
30 COLOR 2, 6
40 PAINT 2, 40, 40
50 SSHAPE A$, 8, 8, 72, 72
60 GSHAPE A$, 120, 20
70 GSHAPE A$, 200, 60
80 GSHAPE A$, 120, 120
```
![](images/shapes.png)
One disc drawn, captured, and stamped three times.
**`A$` holds a handle, not the pixels.** On a C128 the string holds the bitmap, so a
program could save it to disk or take its `LEN`. Here a string is a fixed 255 bytes and
the region is a device surface, so what goes in the string is a reference to it —

View File

@@ -58,12 +58,33 @@ operation.
## Showing and moving
```basic requires=akgl setup=ship
10 SPRSAV "ship.png", 1
20 SPRITE 1, 1, 3
30 MOVSPR 1, 100, 50
```basic requires=akgl screenshot=sprites
10 COLOR 1, 2
20 CIRCLE 1, 20, 20, 18, 18
30 PAINT 1, 20, 20
40 SSHAPE A$, 0, 0, 40, 40
50 GRAPHIC 1, 1
60 FOR N# = 1 TO 3
70 SPRSAV A$, N#
80 NEXT N#
90 SPRITE 1, 1
100 SPRITE 2, 1, 6
110 SPRITE 3, 1, 8, 0, 1, 1
120 MOVSPR 1, 30, 80
130 MOVSPR 2, 110, 80
140 MOVSPR 3, 190, 60
```
![](images/sprites.png)
Three sprites from one drawing: the first in the default colour, the second in colour
6, and the third in colour 8 with both expansion bits set, which is what makes it twice
the size. `GRAPHIC 1, 1` on line 50 wipes the drawing the sprites were captured from —
the picture would otherwise still show the original disc in the top-left corner.
**A sprite's colour multiplies the artwork rather than replacing it**, so a white disc
takes the colour cleanly and a coloured one comes out darker than you asked for.
`SPRITE n [,on] [,colour] [,priority] [,xexpand] [,yexpand] [,multicolour]`. Only the
number is required and **an argument you leave out is left alone**, so `SPRITE 1, 1`
turns sprite 1 on without disturbing its colour.
@@ -113,6 +134,27 @@ Only type 1, sprite-to-sprite, is implemented. Types 2 and 3 are refused by name
Collision is by bounding box, not by pixel: two sprites whose boxes overlap but whose
artwork does not are reported as colliding.
```basic requires=akgl screenshot=sprite-collision
10 COLOR 1, 2
20 CIRCLE 1, 20, 20, 14, 14
30 PAINT 1, 20, 20
40 BOX 1, 0, 0, 40, 40
50 SSHAPE A$, 0, 0, 40, 40
60 GRAPHIC 1, 1
70 SPRSAV A$, 1
80 SPRSAV A$, 2
90 SPRITE 1, 1, 6
100 SPRITE 2, 1, 3
110 MOVSPR 1, 110, 55
120 MOVSPR 2, 145, 90
```
![](images/sprite-collision.png)
The artwork here includes a border around the whole 40 by 40 sprite, so each sprite
draws its own bounding box. Those boxes overlap at one corner and the two discs are
nowhere near each other — and `BUMP(1)` reports a collision.
## Reading state back
| Function | Gives |

21
docs/images/README.md Normal file
View File

@@ -0,0 +1,21 @@
# Generated figures — do not edit
**Every PNG in this directory is output, not an asset.** Each one is produced by running
the BASIC listing shown immediately above it in the chapter, through
`tools/screenshot.c`, and it is checked in only because a reader on the forge has no
build tree to generate it from.
To change a picture, **change the listing** and regenerate:
```sh
cmake --build build-akgl --target docs_screenshots
```
Editing a PNG here by hand puts the picture out of step with the code it illustrates,
which is the exact failure this arrangement exists to prevent — and the `docs_screenshots`
test will fail on it, because it re-renders every figure and compares byte for byte.
The tag that ties a figure to its listing is `screenshot=NAME` in the fence info string,
and `MAINTENANCE.md` documents it along with `size=WxH` for a figure that needs a surface
other than 320 by 200. A tagged block with no image here fails `docs_examples` in both
build configurations, so a figure cannot be added to a chapter and then forgotten.

BIN
docs/images/box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
docs/images/circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
docs/images/draw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
docs/images/paint.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
docs/images/scale.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
docs/images/shapes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

BIN
docs/images/sprites.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B