Pictures nine times faster
Entering a new place stops the game for as long as its picture takes to draw -- nearly seven seconds for Bag End, the first thing a game shows, and almost twelve for the slowest. patches/hobbit_fast_draw.s draws every picture about nine times faster -- Bag End in about half a second, the slowest in one -- and draws each one exactly as the original does.

Bag End, from the title screen: 6.7 s, and 0.55 s patched.
Bag End, from the title screen: 6.7 s, and 0.55 s patched.

Location 6, the hidden path with trolls' footprints, the slowest picture in the game: 11.7 s, and 1.04 s patched.
Location 6, the hidden path with trolls' footprints, the slowest picture in the game: 11.7 s, and 1.04 s patched.

Location 4, the lonelands, the second slowest: 11.5 s, and 0.48 s patched.
Location 4, the lonelands, the second slowest: 11.5 s, and 0.48 s patched.

Location 39, the front gate, the third slowest: 10.4 s, and 0.76 s patched.
Location 39, the front gate, the third slowest: 10.4 s, and 0.76 s patched.

Recorded on the emulator at real speed, the original on the left and the patched game on the right; each loops once its picture is done.

Why it was slow. Both drawing routines, DRAW_LINE and FLOOD_FILL, step a point one pixel at a time, so they always know where the next pixel is -- and then work its screen address out again from the coordinates, through PIXEL_ADDRESS, which rotates a bit into place one position at a time. The fill does that four times for every pixel it fills: the pixel above, the pixel below, the pixel itself and the next one along. Measured in the simulator, PIXEL_ADDRESS is 52% of all the time spent drawing.
What the patch does. The same algorithms, in the same order, pushing the same fill seeds onto the stack; only the addressing changes. The screen address and the bit mask are carried along with the point and stepped with it -- a rotate of the mask to move across, the Spectrum's next-line and previous-line arithmetic to move up and down -- and worked out from scratch only where a line or a fill's sweep begins, with the mask from an eight-byte table. The line drawer keeps the address in the alternate registers, so the line's own logic is untouched. The attribute rule, that the ink is flipped where it would match the paper, is kept, with its three values written into the comparison once per line or fill.
And then the fill. With the addressing gone, the fill's own bookkeeping was most of what was left, and three more changes take most of that away. The bytes above and below the sweep are held in IX and IY, worked out once when a sweep starts and stepped along with it, rather than found again for every pixel. A cell is coloured only as the sweep enters it: colouring writes the same byte however many times it is done, so the other seven pixels of a cell only repeated it. And a whole byte is filled at once wherever doing it pixel by pixel could only have filled it -- at the start of a byte that is clear, where each row either side is either clear and already seeded, or all set and not seeded, or off the canvas. Then each of the eight pixels would push no seed and change no flag, so they can all be plotted together. That is most of the inside of every large area. Everywhere else the fill goes one pixel at a time, as before.
Where it goes. Nowhere new: the plotting code is rewritten in place, either side of the four ATTR_ routines RUN_PICTURE's paint command uses, which stay where they are, and what does not fit goes into two places nothing else uses: the 165 zeros after the last picture (AFTER_PICTURES), which nothing reads or writes, and memory below the game at $5D00-$5DBF, which the BASIC loader used and the running game never touches -- its stack starts at $5EFF and, in play that gave orders, fought and walked through the pictures, never went below $5E85. The game never enables interrupts, so the fill can borrow IY. The build refuses a patched game that differs anywhere else.
A mistake, now put right. Until 24 September 2026 the patch put that last part into special word slot 0's handler, ORDER_BEGINS, which this disassembly then had as code nothing can reach: slot 0 holds no word, so it seemed nothing could choose it. But a quote mark reaches the parser as exactly that empty word, and the handler is the quote's -- SPECIAL_QUOTE, which files what is said to a character as orders. The patched game restarted the Spectrum at the first thing said in quotes. The handler is back as it was, and the patch's code is below the game instead.
How it was checked. Every one of the 22 pictures is drawn from the same machine in the original and the patched game, and the whole of memory is compared afterwards -- the screen, the attributes and every variable, not only what can be seen. All 22 are identical. The stack never goes deeper than the original's; it stays two to four bytes shallower in every picture.
Each picture's time is the T-states DRAW_LOCATION_PICTURE takes in each game, at the Spectrum's 3.5 MHz, slowest first. The small pictures gain least: less of their time is spent filling, and more on the lines and on clearing the canvas, which cost what they did. Location 1 is Bag End.
LocationOriginalPatchedFaster by
6 trolls path11.7 s1.04 s11.2x
4 lonelands11.5 s0.48 s24.1x
39 front gate10.4 s0.76 s13.7x
41 lower halls10.3 s0.89 s11.6x
35 lake town8.2 s0.55 s14.8x
26 spider threads place8.1 s1.02 s7.9x
1 tunnel like hall6.7 s0.55 s12.2x
13 goblins dungeon6.6 s0.56 s11.8x
49 great river6.5 s0.59 s11.0x
31 dark dungeon6.4 s0.53 s12.2x
25 bewitched gloomy place6.4 s1.06 s6.0x
11 narrow place6.3 s0.60 s10.5x
8 running river6.3 s0.75 s8.4x
38 dale valley5.6 s0.69 s8.1x
5 trolls clearing3.1 s0.48 s6.5x
28 levelled elvish clearing2.7 s0.42 s6.4x
24 forest gate2.4 s0.48 s5.0x
32 elvenkings cellar2.0 s0.47 s4.3x
37 dragons desolation1.8 s0.47 s3.8x
7 trolls cave1.5 s0.42 s3.5x
16 big goblins cavern1.3 s0.44 s2.9x
43 smooth straight passage0.3 s0.17 s1.7x
All 22 pictures126.1 s13.4 s9.4x
Building it. python scripts/build_hobbit.py --tape "The Hobbit v1.2.tzx" --fast-draw builds and verifies the disassembly, then assembles the patch on top of it into hobbit_fast.sna, with a source map for debugging it. python scripts/check_fast_draw.py runs the comparison above.