2026-08-02 15:58:47 -04:00
|
|
|
# AKLABS MEGADEMO '85
|
|
|
|
|
|
|
|
|
|
A demoscene production for akbasic, written as if it were 1985 and this dialect were
|
|
|
|
|
the machine under the tree. Code and FX by Tachikoma, dedicated to GameCube,
|
|
|
|
|
megagreetz to starfort.
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
./build-akgl/basic examples/megademo/megademo.bas
|
|
|
|
|
```
|
|
|
|
|
|
Decompress a full-screen picture and six frames of video from strings
The megademo gains a scene: an 800x600 vaporwave sunset carried inside
the listing, then six looping delta frames of full motion video -- the
floor grid rolling forward and the sun's slices crawling, about 220
bytes a frame. DATA cannot carry an image (512 items, ~350 owned by the
stroke font), so the picture rides in RLE-encoded string literals
decoded with INSTR: 2 KB of base frame, 1.3 KB of video. Row records
jump the decoder between changed rows, an unreachable colour is the
skip, black draws so a delta can erase, and the rays are struck live
over every frame rather than encoded. The 63-byte bird gliding over it
is the one image DATA does have room for, via SPRSAV's type-in form.
examples/megademo/vaporwave.py composes the image in palette space,
dithers by row so the gradients band instead of shattering the RLE,
proves base-plus-deltas reproduces every frame against a simulation of
the decoder, and owns the generated block between the PICTURE markers.
TODO.md section 4 records the DATA ceiling and the wanted verb.
Co-Authored-By: Claude Code (Fable 5) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-02 16:26:56 -04:00
|
|
|
Q or ESC quits. Seven scenes: a title card, a Lissajous braid, a spinning wireframe
|
2026-08-02 15:58:47 -04:00
|
|
|
cube and octahedron — which drift together, ignite a pulsing origin point strobing
|
|
|
|
|
the next scene's colours, swallow the camera as the perspective scale outgrows the
|
Decompress a full-screen picture and six frames of video from strings
The megademo gains a scene: an 800x600 vaporwave sunset carried inside
the listing, then six looping delta frames of full motion video -- the
floor grid rolling forward and the sun's slices crawling, about 220
bytes a frame. DATA cannot carry an image (512 items, ~350 owned by the
stroke font), so the picture rides in RLE-encoded string literals
decoded with INSTR: 2 KB of base frame, 1.3 KB of video. Row records
jump the decoder between changed rows, an unreachable colour is the
skip, black draws so a delta can erase, and the rays are struck live
over every frame rather than encoded. The 63-byte bird gliding over it
is the one image DATA does have room for, via SPRSAV's type-in form.
examples/megademo/vaporwave.py composes the image in palette space,
dithers by row so the gradients band instead of shattering the RLE,
proves base-plus-deltas reproduces every frame against a simulation of
the decoder, and owns the generated block between the PICTURE markers.
TODO.md section 4 records the DATA ceiling and the wanted verb.
Co-Authored-By: Claude Code (Fable 5) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-02 16:26:56 -04:00
|
|
|
window, and split that origin into the first two raster bars — the raster bars
|
|
|
|
|
proper, a full-screen vaporwave sunset decompressed out of the listing itself, the
|
|
|
|
|
sprite bank going supernova — the letters fall into the middle, a fireball throws
|
|
|
|
|
them at the walls, and a black hole drags everything back into the centre as the
|
|
|
|
|
transition out — and a finale that composes all of it. A sawtooth chiptune in A
|
|
|
|
|
minor throughout, with a noise drum break, if a SID answers.
|
2026-08-02 15:58:47 -04:00
|
|
|
|
|
|
|
|
## What it leans on
|
|
|
|
|
|
|
|
|
|
This is a tour of the interpreter's edges, on purpose:
|
|
|
|
|
|
|
|
|
|
- **No `RND`** — it carries Chapter 17's LCG, seeded from `TI#`.
|
|
|
|
|
- **No palette writes** — the "colour cycle" on the logo is an honest redraw of the
|
|
|
|
|
same strokes in the next colour, and the raster bars are erased and redrawn two
|
|
|
|
|
scanlines at a time. The palette is sixteen fixed colours and the demo says so on
|
|
|
|
|
screen.
|
|
|
|
|
- **Eight sprites** — so the lettering *is* the sprite bank. Each letter of GAMECUBE
|
|
|
|
|
is drawn with the stroke font from `examples/breakout`, captured with `SSHAPE`,
|
|
|
|
|
installed with `SPRSAV`, and tinted with `SPRITE`.
|
|
|
|
|
- **`SOLID` walls and `COLLISION 2`** — the supernova registers four rectangles and
|
|
|
|
|
reverses a bearing per `RCOLLISION(n, 7)`. The interpreter does the physics;
|
|
|
|
|
BASIC does one subtraction. The letters leave the blast on `MOVSPR`'s continuous
|
|
|
|
|
form, eight bearings fanned evenly around the compass.
|
|
|
|
|
- **The black hole is `RSPPOS` plus one rotation** — read each letter's position
|
|
|
|
|
back, rotate the offset from centre, shrink it by 0.86, put it there. Thirty-four
|
|
|
|
|
frames later everything is in the middle. The screen goes with it: an iris of
|
|
|
|
|
collapsing black `CIRCLE`s, which works because the drawing verbs clip silently
|
|
|
|
|
at the window edge — a fireball and an iris can both start bigger than the
|
|
|
|
|
screen.
|
|
|
|
|
- **The camera is a multiplier** — the wireframe zoom-through is nothing but the
|
|
|
|
|
perspective scale growing fourteen percent a frame until the solids' edges leave
|
|
|
|
|
the window, which the silent clipping permits. The pulsing origin point strobes
|
|
|
|
|
through the bar ramp table because it *is* the next scene: it splits, rides the
|
|
|
|
|
Y axis to the band edges, and stretches into the first two raster bars in place.
|
Decompress a full-screen picture and six frames of video from strings
The megademo gains a scene: an 800x600 vaporwave sunset carried inside
the listing, then six looping delta frames of full motion video -- the
floor grid rolling forward and the sun's slices crawling, about 220
bytes a frame. DATA cannot carry an image (512 items, ~350 owned by the
stroke font), so the picture rides in RLE-encoded string literals
decoded with INSTR: 2 KB of base frame, 1.3 KB of video. Row records
jump the decoder between changed rows, an unreachable colour is the
skip, black draws so a delta can erase, and the rays are struck live
over every frame rather than encoded. The 63-byte bird gliding over it
is the one image DATA does have room for, via SPRSAV's type-in form.
examples/megademo/vaporwave.py composes the image in palette space,
dithers by row so the gradients band instead of shattering the RLE,
proves base-plus-deltas reproduces every frame against a simulation of
the decoder, and owns the generated block between the PICTURE markers.
TODO.md section 4 records the DATA ceiling and the wanted verb.
Co-Authored-By: Claude Code (Fable 5) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-02 16:26:56 -04:00
|
|
|
- **The picture is strings, because `DATA` is a straw** — the pool is 512 items
|
|
|
|
|
and the font owns ~350, so the full-screen sunset rides in string literals:
|
|
|
|
|
160x120 cells, sixteen colours, run-length encoded two characters a run and
|
|
|
|
|
decoded with `INSTR` into 5x5 blocks — about 2 KB for the whole base frame.
|
|
|
|
|
`vaporwave.py` composes the image in palette space — dithered by *row*, not
|
|
|
|
|
pixel, so the gradients band like a CRT instead of shattering the RLE — and
|
|
|
|
|
owns the generated block between the `PICTURE-BEGIN`/`PICTURE-END` markers. Do
|
|
|
|
|
not hand-edit it; rerun the script. The bird gliding over it is the
|
|
|
|
|
counterpoint: a genuine 24x21 `SPRSAV` type-in sprite, 63 bytes, which is
|
|
|
|
|
everything `DATA` has room to say. The paint is the reveal, and the exit is a
|
|
|
|
|
stride-63 column dissolve — 63 is coprime to 160, so the walk hits every
|
|
|
|
|
column once and looks random while carrying no state.
|
|
|
|
|
- **And then the picture is video.** Six delta frames loop the floor grid toward
|
|
|
|
|
you and crawl the sun's slice pattern — full motion video, 1.3 KB total, about
|
|
|
|
|
220 bytes a frame. A delta re-encodes only the rows that changed: an `R`
|
|
|
|
|
record jumps the decoder's cursor (two base-36 digits of row), `Q` skips what
|
|
|
|
|
stayed, and black *draws*, which is how a moved grid line is erased. The rays
|
|
|
|
|
are not in the encoding at all; they are struck live over every frame, which
|
|
|
|
|
is the difference between a thirty-run grid row and a five-run one. The loop
|
|
|
|
|
is mathematically exact: line k sits at `B*(r^(k+t)-1)` so six frames advance
|
|
|
|
|
t by one and land the set on itself, and the slice period divides the frame
|
|
|
|
|
count. The generator simulates the decoder and asserts base-plus-deltas
|
|
|
|
|
reproduces every frame before it will emit a byte.
|
2026-08-02 15:58:47 -04:00
|
|
|
- **3D with no matrices** — the wireframe scene rotates fourteen pooled vertices
|
|
|
|
|
through two composed axis rotations and a perspective divide, all of it `SIN`,
|
|
|
|
|
`COS` and one division, with the float kept on the left of every product because
|
|
|
|
|
the left operand decides the arithmetic. Erase is the raster bars' trick: redraw
|
|
|
|
|
the previous frame's twenty-four edges in black.
|
|
|
|
|
- **`PLAY` is one queue** — every note holds it for its length and the whole batch
|
|
|
|
|
plays on one instrument, so three `V` tracks come out end to end, not layered. The
|
|
|
|
|
soundtrack is therefore beeper music, the way single-channel tracker tunes were
|
|
|
|
|
written: sixteenths that take turns being the kick (an `O1` root), the pumping bass
|
|
|
|
|
(`O2`/`O3` octaves) and the rolling arpeggio (`O4` and up), two alternating
|
|
|
|
|
eight-beat batches — Am F C G, then Am F Dm E with the G sharp — and every third
|
|
|
|
|
batch a bar of noise drums. Noise is real: `ENVELOPE`'s sixth argument is a
|
|
|
|
|
waveform, 0 to 3, which Chapter 7 forgot to mention. Voice 1 carries the tune so
|
|
|
|
|
the collision blips (voice 2) and scene sweeps (voice 3) never steal its channel.
|
|
|
|
|
|
|
|
|
|
It also probes for its hardware like a proper boot loader, with one `TRAP` per
|
|
|
|
|
device: no graphics refuses by name and exits, no audio mutes the soundtrack, no
|
|
|
|
|
text grid drops the status row, and a host that never sets the clock — which is
|
|
|
|
|
exactly what `tools/screenshot.c` is — renders each scene as a still and leaves the
|
|
|
|
|
finale as the picture. The same file runs everywhere; nothing hangs.
|
|
|
|
|
|
|
|
|
|
## The ones that bit
|
|
|
|
|
|
|
|
|
|
**A scalar first assigned inside a `GOSUB` dies with that call's scope.** The first
|
|
|
|
|
cut computed text centring in a subroutine and read the result after `RETURN`; every
|
|
|
|
|
line of text rendered flush left, because the variable read back as a fresh zero.
|
|
|
|
|
Reads climb the scope chain, so a nested subroutine can *use* your globals — but a
|
|
|
|
|
write to a name that exists nowhere creates it in the current scope, and the scope
|
|
|
|
|
is not coming back. That is why Chapter 18's listings predeclare.
|
|
|
|
|
|
|
|
|
|
**Then the predeclaring bit back.** The obvious lesson — predeclare everything — ran
|
|
|
|
|
the 128-slot variable pool dry the moment the wireframes arrived: `Maximum runtime
|
|
|
|
|
variables reached`. A predeclared scratch variable occupies a slot forever; a scoped
|
|
|
|
|
one gives its slot back at `RETURN`. So the rule is narrower than it first looks:
|
|
|
|
|
predeclare a subroutine's *outputs* and nothing else. This program predeclares four.
|
|
|
|
|
|
|
|
|
|
**And the raster wait strangled the SID.** The `VSYNC` label used to spin on `TI#`
|
|
|
|
|
until the jiffy turned — romantic, and fatal. The host releases at most one queued
|
|
|
|
|
`PLAY` note per frame, and a frame of 256 *executed* spin statements is slow enough
|
|
|
|
|
to drop the release rate below the tune's fifteen notes a second. The queue never
|
|
|
|
|
reclaims slots until it drains completely, so the backlog compounded quietly for two
|
|
|
|
|
scenes and then `PLAY queue is full` killed the show. `VSYNC` now sleeps a frame —
|
|
|
|
|
`SLEEP` holds at no per-step cost — and `TUNE` traps the overflow on hosts slower
|
|
|
|
|
than this one, dropping a bar instead of the whole demo. `TODO.md` §4 carries the
|
|
|
|
|
measurement.
|
|
|
|
|
|
|
|
|
|
## What it wanted and could not have
|
|
|
|
|
|
|
|
|
|
Recorded properly in `TODO.md` §4, "What a demoscene program wanted and could not
|
|
|
|
|
have": `RND` as a real function, palette writes, `GSHAPE` XOR stamping, a way to ask
|
|
|
|
|
whether `PLAY` has drained, per-voice `PLAY` queues so three voices can actually
|
|
|
|
|
sound together, and a frame-edge wait that is not a busy loop. None of them blocked
|
|
|
|
|
the demo; all of them left a routine behind that every future game will carry too.
|