2026-07-31 21:50:50 -04:00
# 12. Function reference
Every function, alphabetically, generated from the interpreter's own dispatch table.
A function is called with parentheses and yields a value; it cannot stand alone as a
statement. The arity column is how many arguments it takes — the interpreter checks it,
so a call with the wrong number is a syntax error rather than a surprise.
| Function | Args | Form | What it gives |
|---|---|---|---|
| `ABS` | 1 | `ABS(n)` | The absolute value of an integer or float. |
| `ATN` | 1 | `ATN(n)` | Arctangent, in radians. |
| `BUMP` | 1 | `BUMP(1)` | Which sprites have collided, as a bitmask. **Reading clears it. ** |
| `CHR` | 1 | `CHR(n)` | The character for a Unicode code point, as a string. |
| `COS` | 1 | `COS(n)` | Cosine, in radians. |
| `ERR` | 1 | `ERR(n)` | The message text for an error code. `ERR(ER#)` names the last one. |
| `HEX` | 1 | `HEX(n)` | An integer as hexadecimal text. |
| `INSTR` | 2 | `INSTR(A$, B$)` | Where `B$` appears in `A$` , counting from zero, or -1. |
| `LEFT` | 2 | `LEFT(A$, n)` | The leftmost `n` characters. Clamped to the string's length. |
| `LEN` | 1 | `LEN(x)` | The length of a string, or the element count of an array. |
| `LOG` | 1 | `LOG(n)` | Natural logarithm. |
| `MID` | 3 | `MID(A$, start, len)` | A substring, counting from zero. |
| `MOD` | 2 | `MOD(a, b)` | The remainder of `a / b` . Integers only. |
| `PEEK` | 1 | `PEEK(addr)` | The byte at a real address. |
| `POINTER` | 1 | `POINTER(V)` | The address of a variable's value. |
| `POINTERVAR` | 1 | `POINTERVAR(V)` | The address of the variable structure itself, metadata included. |
| `RAD` | 1 | `RAD(n)` | Degrees converted to radians. |
Say what was hit, which way is out, and how far
The narrowphase has been producing a contact since it went in, and the interpreter
was throwing it away. `RCOLLISION(n, f)` reports it: what was hit (a sprite or a
`SOLID` rectangle), which one, the contact normal, the penetration depth, the
contact point, and which axis to reverse.
**The normal points out of the other thing and toward this one**, so a program
moves along it by the depth and is exactly clear. That sign is the one assertion
in the new test that could not be caught any other way -- both parties of a
sprite-against-sprite hit get their own record, each pointing the way *that*
sprite has to move, and sharing one would tell both to go the same direction,
which is how two things end up stuck inside each other.
**Field 7 is the one that deletes the most BASIC.** It is the minimum translation
axis, computed from the normal in C, and it is there because doing it in BASIC
means comparing two floats -- which is exactly where this dialect's left-operand
rule catches people. `BALLBRICKS`/`TESTCELL` in the artwork breakout spend six
lines computing an overlap rectangle and comparing its width to its height to get
this number.
The record is **sticky and deepest-wins**: replaced whenever that sprite is in a
contact and otherwise left alone, so `BUMP` stays the event and this stays the
detail of it. Making it clear itself when nothing touches would break the pairing,
because `BUMP` accumulates across steps and a once-a-frame poll would find the
detail already gone. Reading `BUMP` clears both, so they cannot disagree.
Deliberately narrower than `akgl_Contact`: no actor pointers, because BASIC has no
actor; no tile fields, because there is no tilemap; no z, because every test is
planar; and **no `dt` and no `sensor`**, which libakgl documents as filled in by
the resolver. This interpreter never resolves anything, so those two come back
zero and mean nothing, and an always-zero field in a reference table is a lie.
Documented with the two caveats that matter: fields 2, 3 and 4 are floats and want
a `%` variable, and the contact *point* is exact only for two boxes -- libakgl's
solver returns a point on the portal it converged to, while the normal and depth
are exact for every pair.
Chapter 8's collision section stops claiming only type 1 exists, which has been
false since the previous commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
2026-08-02 10:53:26 -04:00
| `RCOLLISION` | 2 | `RCOLLISION(n, f)` | What sprite `n` last collided with and how: what, which, the normal, the depth, the point, and which axis to reverse. See Chapter 8. |
Let a program ask how big the text grid is
`RGR(1)` and `RGR(2)` gave the window in pixels and nothing gave columns, rows
or the cell size -- so anything placing a character *and* a sprite at the same
spot had to hardcode a number measured by hand against whatever font the host
loaded. The Breakout in examples/ does exactly that, `CW# = 16`, and it is the
one thing in that listing that breaks on a different font or window.
**`RWINDOW` is BASIC 7.0's own answer and had never been implemented here.**
`RWINDOW(0)` is the current text window's rows and `RWINDOW(1)` its columns.
`RWINDOW(2)` reports a C128's 40 or 80 column screen mode, and this interpreter
has neither -- refused by name, because answering 0 would be a plausible lie,
which is worse than a refusal that says why.
The cell size in pixels is `RGR(3)` and `RGR(4)`, beside the surface's own
dimensions rather than on `RWINDOW`. Two reasons: a cell size is a fact about
the surface, and `RWINDOW` reports the *window*, so dividing `RGR(1)` by a
column count stops being right the moment a program calls `WINDOW`.
Both read a new optional `grid` entry point on `akbasic_TextSink` -- columns,
rows, cell width, cell height -- implemented by the akgl sink and forwarded by
the tee, in the shape `moveto` and `window` already had. NULL everywhere else,
so both verbs refuse by name against a sink with no grid. `akbasic_sink_init_
stdio()` clears it for the same reason it now clears the other two.
Measured on the standalone build: `RGR(3)` answers 16 and `RWINDOW` answers 50
columns by 37 rows -- the three numbers the Breakout listing had written out as
constants -- and `RWINDOW` follows a `WINDOW` call while `RGR(3)` does not.
tests/console_verbs.c drives the answers through a stand-in sink with a grid,
since the harness sink is stdio and has none; tests/graphics_verbs.c covers the
new `RGR` fields, their refusal, and the moved range bound. The `c excerpt=`
block in docs/10-embedding.md moves with the header, which is `docs_examples`
doing its job.
TODO.md section 6 item 31's second half, struck.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 00:05:17 -04:00
| `RGR` | 1 | `RGR(f)` | The `GRAPHIC` mode (0), the drawing surface's width (1) or height (2) in pixels, or a character cell's width (3) or height (4). |
2026-07-31 21:50:50 -04:00
| `RIGHT` | 2 | `RIGHT(A$, n)` | The rightmost `n` characters. Clamped. |
Let a program ask how big the text grid is
`RGR(1)` and `RGR(2)` gave the window in pixels and nothing gave columns, rows
or the cell size -- so anything placing a character *and* a sprite at the same
spot had to hardcode a number measured by hand against whatever font the host
loaded. The Breakout in examples/ does exactly that, `CW# = 16`, and it is the
one thing in that listing that breaks on a different font or window.
**`RWINDOW` is BASIC 7.0's own answer and had never been implemented here.**
`RWINDOW(0)` is the current text window's rows and `RWINDOW(1)` its columns.
`RWINDOW(2)` reports a C128's 40 or 80 column screen mode, and this interpreter
has neither -- refused by name, because answering 0 would be a plausible lie,
which is worse than a refusal that says why.
The cell size in pixels is `RGR(3)` and `RGR(4)`, beside the surface's own
dimensions rather than on `RWINDOW`. Two reasons: a cell size is a fact about
the surface, and `RWINDOW` reports the *window*, so dividing `RGR(1)` by a
column count stops being right the moment a program calls `WINDOW`.
Both read a new optional `grid` entry point on `akbasic_TextSink` -- columns,
rows, cell width, cell height -- implemented by the akgl sink and forwarded by
the tee, in the shape `moveto` and `window` already had. NULL everywhere else,
so both verbs refuse by name against a sink with no grid. `akbasic_sink_init_
stdio()` clears it for the same reason it now clears the other two.
Measured on the standalone build: `RGR(3)` answers 16 and `RWINDOW` answers 50
columns by 37 rows -- the three numbers the Breakout listing had written out as
constants -- and `RWINDOW` follows a `WINDOW` call while `RGR(3)` does not.
tests/console_verbs.c drives the answers through a stand-in sink with a grid,
since the harness sink is stdio and has none; tests/graphics_verbs.c covers the
new `RGR` fields, their refusal, and the moved range bound. The `c excerpt=`
block in docs/10-embedding.md moves with the header, which is `docs_examples`
doing its job.
TODO.md section 6 item 31's second half, struck.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 00:05:17 -04:00
| `RWINDOW` | 1 | `RWINDOW(f)` | The current text window's rows (0) or columns (1). Field 2 is a C128 screen mode and is refused. |
2026-07-31 21:50:50 -04:00
| `RSPCOLOR` | 1 | `RSPCOLOR(n)` | One of `SPRCOLOR` 's two shared registers, 1 or 2. |
Let a program say what part of a sprite collides
`SPRHIT n, kind [,x1, y1, x2, y2]` gives a sprite a collision shape: a box, a
circle inscribed in it, or a capsule. `RSPHIT(n, f)` reads it back in SPRHIT's
own argument order, the way RSPRITE and RSPPOS already do, and needs no device
because it answers from interpreter state.
The rectangle is two corners measured from the sprite's top-left, in device
pixels -- the same `x1, y1, x2, y2` that `BOX` and `SSHAPE` take. A dialect with
two spellings for a rectangle is one nobody can write from memory. Omit it and
the shape fits whatever the picture turned out to be, which is what a sprite
loaded from a file needs: `SPRSAV "ship.png", 1` takes the image's own size and
the program never learns what that was.
**A sprite nobody has shaped collides with its whole frame, expansion bits
included, exactly as before.** That is a promise rather than a convenience, and
it has its own test: the same two sprites in the same two places, once with no
SPRHIT and once with a four-pixel box, reporting a collision and then not.
Named SPRHIT rather than SPRSHAPE because "shape" already means "a region SSHAPE
saved" in this dialect, in this very chapter -- `SPRSAV A$, 1` takes one -- and a
reader who typed `SPRSHAPE A$, 1` would have had every reason to. Both names, and
RSPHIT, were grepped against every label in docs/, examples/ and both corpora
first: a bare word is a label here, so a verb and a label share one namespace and
taking a name a checked-in listing already uses would break it silently.
`SPRHIT n, 0` takes a sprite out of collision while leaving it on the screen --
the ghost, the flashing invulnerable player, the pickup already taken. Hiding it
with `SPRITE n, 0` stops it colliding too, and is what you want when it should
not be seen either.
The circle answers the complaint chapter 8 already ships a figure of. That figure
shows two discs whose *boxes* touch at a corner while the artwork is nowhere
near, and `BUMP(1)` reporting a collision; two `SPRHIT n, 2` and it stops. The
test asserts both halves so the figure's caption stays true.
`tests/verbs_table.c` caught RSPHIT filed after RSPPOS rather than before it,
which is the sorted-table test doing exactly the job it exists for.
Docs: a new section in chapter 8, rows in the verb and function references in
alphabetical order, and chapter 13's "collision is by bounding box" becomes
"collision is by shape" with the addition named. 111 with akgl, 110 without, and
the artwork breakout still runs clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
2026-08-02 10:01:33 -04:00
| `RSPHIT` | 2 | `RSPHIT(n, f)` | One of `SPRHIT` 's settings for sprite `n` , in `SPRHIT` 's own argument order: 0 the kind, 1 to 4 the two corners. |
2026-07-31 21:50:50 -04:00
| `RSPPOS` | 2 | `RSPPOS(n, f)` | A sprite's x (0), y (1) or speed (2). |
| `RSPRITE` | 2 | `RSPRITE(n, f)` | One of a sprite's `SPRITE` settings, in `SPRITE` 's argument order. |
| `SGN` | 1 | `SGN(n)` | -1, 0 or 1 according to the sign. |
| `SHL` | 2 | `SHL(n, bits)` | Shift left. |
| `SHR` | 2 | `SHR(n, bits)` | Shift right. |
| `SIN` | 1 | `SIN(n)` | Sine, in radians. |
| `SPC` | 1 | `SPC(n)` | A string of `n` spaces. |
| `STR` | 1 | `STR(n)` | A number as a string. |
| `TAN` | 1 | `TAN(n)` | Tangent, in radians. |
| `VAL` | 1 | `VAL(A$)` | A string as a number. Refuses text that is not one. |
| `XOR` | 2 | `XOR(a, b)` | Bitwise exclusive or. |
## Reserved variables
Two things a C128 exposes as bare names are ordinary global variables here, because
this dialect has no variable name without a type suffix:
| Here | On a C128 | Holds |
|---|---|---|
| `ER#` | `ER` | the code of the last trapped error |
| `EL#` | `EL` | the line it happened on |
| `TI#` | `TI` | jiffies — sixtieths of a second — since the host's clock started |
| `TI$` | `TI$` | the same time as `HHMMSS` |
`ER#` carries this interpreter's error code, not a Commodore error number. There is no
correspondence to reproduce — the errors raised here are not the errors a 1985 ROM
Document every error code a script can see, as chapter 15
ER# held numbers nothing explained. The appendix lists the four error classes
the interpreter prints, the eight codes it owns and what raises each, and the
codes that reach ER# from errno, libakerror and libakgl underneath it.
The table is not asserted. A program in the chapter trips seven of the eight and
prints what it got, and docs_examples byte-compares the result -- so the numbers
are checked rather than claimed. The eighth, 516, is not usefully trappable and
the chapter says why: entering a handler takes a scope, and the pool being empty
is what raised it.
Two things worth a reader's attention came out of writing it. Two codes register
the same ERR() text, so a program must compare the number and print the text.
And VAL reports libakerror's Value Error rather than the interpreter's 517,
which makes that number the platform's rather than ours -- filed as section 6
item 21, not fixed here, because deciding which libakstdlib failures to
translate is a boundary question and ENOENT out of DOPEN is the counter-case.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:36:36 -04:00
raised — so print `ERR(ER#)` rather than the number. * * [Chapter 15 ](15-error-codes.md )
lists every value it can hold.**