2026-07-31 21:50:50 -04:00
|
|
|
# 8. Sprites
|
|
|
|
|
|
|
|
|
|
Eight sprites, numbered 1 to 8, as on a C128. They need the SDL build.
|
|
|
|
|
|
|
|
|
|
A sprite here is a real object in the host's graphics library, registered alongside
|
|
|
|
|
whatever the host's own game is drawing — so a game that embeds this interpreter can
|
|
|
|
|
see and manipulate a script's sprites.
|
|
|
|
|
|
|
|
|
|
## Giving a sprite a picture
|
|
|
|
|
|
|
|
|
|
`SPRSAV` does it, and it takes three kinds of source.
|
|
|
|
|
|
|
|
|
|
### From an image file
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic requires=akgl setup=ship
|
2026-07-31 21:50:50 -04:00
|
|
|
10 SPRSAV "ship.png", 1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The most useful form, and not one a C128 has. The path is tried against the working
|
|
|
|
|
directory first and then against the directory the running program was loaded from — so
|
|
|
|
|
a `.bas` stored beside its artwork works wherever you launch it from. Anything the
|
|
|
|
|
image library can decode will do.
|
|
|
|
|
|
|
|
|
|
A sprite loaded this way takes **the image's own size**. It is not forced to 24 by 21.
|
|
|
|
|
|
|
|
|
|
### From a saved region
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic requires=akgl
|
2026-07-31 21:50:50 -04:00
|
|
|
10 BOX 1, 0, 0, 24, 21
|
|
|
|
|
20 SSHAPE A$, 0, 0, 24, 21
|
|
|
|
|
30 SPRSAV A$, 1
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Draw it with the graphics verbs, capture it, install it. The C128 documents `SPRSAV`'s
|
|
|
|
|
string as the `SSHAPE` data format at a fixed 24 by 21, so sharing the mechanism is
|
|
|
|
|
faithful rather than a shortcut.
|
|
|
|
|
|
|
|
|
|
### From data
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic norun
|
2026-07-31 21:50:50 -04:00
|
|
|
10 DIM P#(63)
|
|
|
|
|
20 FOR I# = 0 TO 62
|
|
|
|
|
30 READ P#(I#)
|
|
|
|
|
40 NEXT I#
|
|
|
|
|
50 SPRSAV P#, 1
|
|
|
|
|
60 DATA 255, 129, 129, ...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
63 bytes: three per row, twenty-one rows, most significant bit leftmost. This is the
|
|
|
|
|
form a type-in listing uses.
|
|
|
|
|
|
|
|
|
|
**It takes an integer array, not a string.** A C128 puts the raw bytes in a string; a
|
|
|
|
|
string here is NUL-terminated, so it cannot hold a zero byte and therefore cannot hold
|
|
|
|
|
a sprite. `DIM P#(63)` is exactly the right size.
|
|
|
|
|
|
|
|
|
|
Writing a sprite back out — `SPRSAV 1, A$` — is refused. That would be a disk
|
|
|
|
|
operation.
|
|
|
|
|
|
|
|
|
|
## Showing and moving
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic requires=akgl setup=ship
|
|
|
|
|
10 SPRSAV "ship.png", 1
|
|
|
|
|
20 SPRITE 1, 1, 3
|
|
|
|
|
30 MOVSPR 1, 100, 50
|
2026-07-31 21:50:50 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`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.
|
|
|
|
|
|
|
|
|
|
`MOVSPR` has four forms, and the punctuation is what tells them apart:
|
|
|
|
|
|
|
|
|
|
| Form | What it does |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `MOVSPR 1, 100, 50` | put it at (100, 50) |
|
|
|
|
|
| `MOVSPR 1, +10, -20` | move it *by* that much |
|
|
|
|
|
| `MOVSPR 1, 10 ; 90` | move it 10 pixels along bearing 90 |
|
|
|
|
|
| `MOVSPR 1, 45 # 8` | set it moving along bearing 45 at speed 8 |
|
|
|
|
|
|
|
|
|
|
A bearing is degrees **clockwise from straight up**, so 0 is north and 90 is east. In
|
|
|
|
|
the polar form the distance comes first and the angle second — the opposite order from
|
|
|
|
|
the continuous form, which is a trap worth remembering.
|
|
|
|
|
|
|
|
|
|
The continuous form does not move anything on the statement itself. The sprite moves as
|
|
|
|
|
the program runs, paced by the host's clock. Speed 0 stops it.
|
|
|
|
|
|
Draw into the whole window, not its top-left 320x200
The graphics verbs documented a coordinate transform that did not exist. With
SCALE off a coordinate went straight to akgl_draw_* as a pixel address, so an
800x600 window drew a C128 listing into its corner and left the rest unused --
while the chapter said coordinates were 320x200 and stretching to fit was the
host's business.
akbasic_GraphicsBackend gains a size entry point, require_graphics() asks it
before every verb that draws so a resized window is honoured between two
statements, and 320x200 becomes the fallback for a backend that leaves it NULL.
It is the record's one optional member, so a host written against the old header
keeps the behaviour it had.
SCALE now maps onto the device, and RGR(1)/RGR(2) report the drawing surface so
a program can use a window whose size it did not choose. RGR(0) is BASIC 7.0's
own field, the GRAPHIC mode.
SCALE also mapped xmax onto the width rather than onto the last pixel, so
SCALE 1, 319, 199 followed by DRAW 1, 319, 199 drew nothing at all -- one pixel
past the surface. Fixed in the same line, because it is what makes "SCALE gives
a C128 listing the whole window" true rather than nearly true.
The akgl test renders against a 128x128 target, deliberately smaller than the
old constants: a SCALE still dividing by them misses it entirely rather than
landing somewhere plausible.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:35:20 -04:00
|
|
|
Coordinates are the same window pixels the drawing verbs use, not the VIC-II's raster
|
|
|
|
|
coordinates — see Chapter 6, and `RGR(1)` and `RGR(2)` for how big the window is.
|
|
|
|
|
`SCALE` does not apply to them: a sprite is positioned in device pixels whatever the
|
|
|
|
|
drawing verbs are doing.
|
2026-07-31 21:50:50 -04:00
|
|
|
|
|
|
|
|
## Collision
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic norun
|
2026-07-31 21:50:50 -04:00
|
|
|
10 COLLISION 1, BUMPED
|
|
|
|
|
20 REM ... main loop ...
|
|
|
|
|
90 GOTO 20
|
|
|
|
|
100 LABEL BUMPED
|
|
|
|
|
110 PRINT "HIT: " + BUMP(1)
|
|
|
|
|
120 RETURN
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`COLLISION 1, target` calls a subroutine when two sprites overlap. The handler is
|
|
|
|
|
entered between lines and must end in `RETURN`, exactly like a `GOSUB` body. Omitting
|
|
|
|
|
the target disarms it.
|
|
|
|
|
|
|
|
|
|
`BUMP(1)` returns a bitmask of which sprites have collided — bit 0 is sprite 1 — and
|
|
|
|
|
**reading it clears it**, which is what makes "has anything hit me since I last looked"
|
|
|
|
|
answerable.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
## Reading state back
|
|
|
|
|
|
|
|
|
|
| Function | Gives |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `RSPPOS(n, 0)` | x |
|
|
|
|
|
| `RSPPOS(n, 1)` | y |
|
|
|
|
|
| `RSPPOS(n, 2)` | speed |
|
|
|
|
|
| `RSPRITE(n, f)` | one of `SPRITE`'s settings, in `SPRITE`'s own argument order |
|
|
|
|
|
| `RSPCOLOR(1)` or `RSPCOLOR(2)` | one of the shared multicolour registers |
|
|
|
|
|
|
|
|
|
|
These read the interpreter's own state rather than asking the device, so they work
|
|
|
|
|
even with no device attached.
|
|
|
|
|
|
|
|
|
|
## SPRDEF
|
|
|
|
|
|
|
|
|
|
Not implemented, and deliberately. It is an interactive full-screen sprite editor
|
|
|
|
|
driven by single keystrokes, not something a program can call — and this interpreter
|
|
|
|
|
does not own the screen it would take over. The three `SPRSAV` forms replace it.
|