Introduction
When you move between locations in The Hobbit, the game sometimes draws a small picture for that place, most famously the “tunnel-like hall”:
And when I say “it draws” - it really draws! These pictures aren’t stored as raw pixel data; instead they’re stored as a stream of drawing instructions which the game slowly interprets at run time, drawing it and filling it in before your very eyes!
For my The Hobbit disassembly I wanted that drawing data to appear in the
HTML as readable instructions instead of a long run of anonymous DEFB bytes.
Bytecode Format
Each of these location graphics starts with two bytes: border colour and paper/ ink (the usual Spectrum attribute layout).
And after that comes a sequence of data:
| Opcode | Bytes | Meaning |
|---|---|---|
$00 |
1 | Stop. End of this graphic. |
$08 X Y |
3 | Move the “pen” to (X, Y). |
$80–$FF |
2 | Draw line; opcode and next byte. |
$40–$7F X Y |
3 | Fill at (X, Y) with colour in opcode. |
$20–$3F H L … |
3+ | Paint background. |
So now we have: move to a position, draw lines (with a direction and “pixels, step every N pixels”), fill a point with a colour, paint attribute cells (with direction and step counts), and stop.
The draw-line instruction is the trickiest! The bottom three bits of the opcode tell us the direction-UP, RIGHT, DOWN, LEFT, or one of the four diagonals. Then a single data byte packs in both how many pixels to draw and how often to step sideways, so you can get thin lines or chunky ones. I didn’t want readers to have to decode “n” and “m” in their heads, so in the disassembly I spell it out: “Draw a line DOWN-RIGHT 08 pixels, step every 01 pixel(s).” which is much clearer.
Paint background works in the same way, with the “steps” idea: each step is a
direction (up, right, down, or left) and a count. We’re targeting a position in
the attribute buffer, the address comes from HL. The game stores it in
big-endian here, which feels a bit odd when you’re used to Z80’s usual
little-endian ways.
Block Boundaries
The graphic blocks are just laid out one after another in memory. If the
parser isn’t careful it can run past the end of one block and start decoding
into the next block’s header-and then sna2skool gets cross about overlapping
directives.
I got around that by passing an end address into the parser. For each block we know where the next one starts (or where memory ends, for the last block). Before we gobble bytes for a multi-byte instruction we check we’re not stepping over that line. If we would, we emit a short “(incomplete)” line and stop there, so the next block’s header and bytes stay untouched.
Why is this even a thing? It’s because some graphics use the drawing code from the next graphic, and this facilitates how the game “shares” the code. Take a look at: Goblins Dungeon and Dark Dungeon
| $E02C | $E049 |
|---|---|
|
|
Generator And Control File
A little Python script, hobbit_gfx2skool.py, walks through the bytecode and
spits out the matching instruction lines. It loads the snapshot (building it
with tap2sna if it’s not there yet), loops over the known graphics block
addresses, and for each block calls a parser that keeps an eye on the next
block’s start. Everything goes into sources/hobbit/graphics.ctl, which then
is merged into the main hobbit.ctl so when you run sna2skool, the location
graphics show up as nice grouped, commented bytes instead of one long DEFB
blob.
The same instruction set lives in Python in hobbit.py for the #DRAWING
macro: when the HTML build hits a location graphic it runs that bytecode and
turns the result into a PNG, so the disassembly can show the actual picture
right next to the bytes.
I got the idea for doing this from Richard Carlsson’s GitHub rep, which is a very helpful resource for decoding/ displaying this graphic data.
Example
Here’s a short snippet of how the output looks in the skool file:
; Graphics at $CC43: Tunnel Like Hall
b $CC43 Graphics: Tunnel Like Hall
@ $CC43 label=Graphics_TunnelLikeHall
N $CC43 #DRAWING(#PC,scale=$02)(tunnel-like-hall.png)
N $CC43 Location #LOCATION$01: "#LOCATIONNAME$01".
$CC43,$02 Border: #INK(#PEEK(#PC)). Colours: #COLOUR(#PEEK(#PC+$01)).
$CC45,$03 Move to X: #N(#PEEK(#PC+$01))/ Y: #N(#PEEK(#PC+$02)).
$CC48,$02 Draw a line UP-RIGHT 08 pixels, step every 01 pixel(s).
$CC4A,$02 Draw a line UP-RIGHT 08 pixels, step every 01 pixel(s).
...
$CC95,$01 Stop.So you get a clear list of moves, draws, fills, and paint steps-and the
#DRAWING macro drops the rendered graphic into the HTML. No more squinting at
that stretch of bytes and wondering what it’s for!