The Spectranos: a title sequence for a 48K wiseguy
Every so often it's worth building something on this machine that isn't a game at all — just mood. The Spectranos is a non-interactive title-sequence demo: six hard-cut scenes tracing a drive from somewhere anonymous and industrial to somewhere quiet and green, each with its own colour and a short original musical phrase, looping forever until you press a key. If the shape of that journey rings a bell, it's meant to — this is an affectionate homage to a very well-known HBO opening, built entirely from scratch: original silhouettes, an original tune, and a punning title card, with nothing borrowed from the actual show.
Six shots, one screen each
No scrolling. Every scene is a full redraw, held for a few seconds, then a hard cut to the next — tunnel, skyline, overpass, smokestacks, suburb, home. Each scene is really just three calls to a shared toolkit: paint the sky one flat mood colour, paint the ground a dithered texture, and drop some silhouette rectangles onto the horizon:
scene2:
call clearbitmap
ld a,0x50 ; bright dusk-red sky
call setsky
ld a,0x07 ; road: white ink, black paper
ld de,road_cell
call setground
ld hl,bars2 ; the skyline's building data
ld a,0 ; silhouette ink: black
call drawbars
...
A skyline is not a bitmap anywhere in this program — it's nine numbers.
bars2 is a table of (column, width, height) triples; one small
routine turns that into rectangles standing on the horizon. The bridge in
scene 3 is the same routine called with a different table: one wide, flat
bar for the deck, five thin ones underneath for the support legs. The whole
demo's "set" only exists as a few dozen bytes of numbers.
Two bugs a logic test couldn't have caught
Every routine in this program passed its logic checks on the first try. The demo was still broken, and the only reason anyone would know is by actually looking at it.
Bug one: nothing cleared the screen between cuts. Setting a
scene's sky and ground colour only touches the attributes —
the bitmap underneath, where the previous scene's buildings lived, was never
wiped. Scene 3 was quietly inheriting scene 2's entire skyline as a ghost
image behind its own bridge. The fix is one routine, clearbitmap,
called at the top of every scene.
Bug two, more interesting: a Spectrum attribute cell has an ink colour and a paper colour, and paper is what shows wherever the bitmap is empty. Several of my "mood" colours were sitting in the wrong nibble of the byte — a sky with no buildings drawn on it yet has an entirely empty bitmap, so it can only ever show its paper colour, and I'd been setting ink. The result wasn't a wrong colour; it was no colour at all — scene 2's supposedly dusk-red sky rendered as flat black, because the red I'd set was sitting in the half of the byte nothing was reading.
Both bugs were invisible to every assertion in the test suite, because the suite checked memory values, not what a Spectrum actually draws on screen from those values. The fix, both times, was the same discipline as the earlier articles on this site: render the actual bytes to a picture and look at it.
A tune with no separate audio engine
There's no interrupt-driven player here, no second channel of anything running behind the scenes — and yet the visuals and the music are perfectly in sync, because the music is the clock. Each scene ends with a short blocking call that plays its handful of notes; the cut to the next scene happens exactly when the last note finishes. No timer, no frame counter, no drift — just note durations doing double duty as scene durations. It's the simplest possible synchronisation trick, and it's completely reliable because there's nothing else it has to stay in step with.
It's also exactly how a third bug shipped, undetected by anything in this site's test suite: the very first cut of the tune data played all six scenes in under a second on real hardware. Every note is a byte pair, (cycles, pitch), that controls a nested delay loop — and the values I'd picked, chosen by ear from how they looked in a table rather than measured, turned out to produce notes lasting tens of milliseconds instead of the couple of hundred I'd intended. The whole reel flew past in a blur.
No logic assertion catches this, because there's nothing logically wrong
— the tables are well-formed, every note terminates, the demo runs to
completion exactly as designed. It's only wrong on the clock. The fix was to
stop guessing and calculate: work out the exact Z80 T-state cost of the
beep routine's loop structure, solve for the byte pairs that
actually produce ~100–300ms notes at 3.5MHz, and add a permanent test
that computes each scene's real-hardware duration from its note table and
fails if any scene would fly by in under a second and a half. The same
lesson as the other two bugs, in a new disguise: if a property is
observable — visible on screen, audible over time — something in
the test suite has to actually check that property, not just check that the
code around it is well-formed.
Watch it, then take it apart
- spectranos.tap — ready to run in Fuse or any .tap-capable emulator. Press SPACE at any time to stop and return to BASIC.
- spectranos.asm
— the full source. Build it yourself:
pasmo --tapbas spectranos.asm spectranos.tap - compose_tunes.py — the note-timing solver that fixed the too-fast bug above: give it a target duration and a pitch, it works out the exact (cycles, pitch) byte pair for real hardware.
- test_spectranos.py — the full headless test suite, including the real-hardware timing check.
The whole reel is under 2KB. Fork it for a different journey entirely — a spy-film credits sequence, a weather forecast, your own commute — the toolkit doesn't care what the silhouettes are meant to be, only that you draw them with a colour the sky can actually show, and time them with a byte pair that's actually been measured rather than guessed.