Skip to content
Game 2 Unit 1 of 16 1 hr learning time

A Hooded Figure

Shadowkeep begins where Gloaming ended — the same engine, a new face. Design a hooded thief as eight bytes and stand him, alone, in the cold blue dark of the keep.

6% of Shadowkeep

You have already finished a game. Gloaming — a complete thing, played from a title and back again, verified on real iron. Shadowkeep is the next one, and it is bigger: a lit, multi-room keep you explore by night. But it does not start from nothing, and it does not start from a blank file.

It starts from Gloaming's engine. The cell sprite, the save-and-restore, the collision, the game loop, the state machine — you wrote all of it, and Shadowkeep carries it over wholesale. So the first thing a new game needs isn't new code. It's a new face.

What you'll see by the end

A single hooded figure, drawn in bright red, standing alone in the centre of a screen washed in cold blue stone, framed by a black border.
The keep's first inhabitant: a red hooded thief, dead centre in the cold blue stone. Eight bytes of silhouette, and the moment you see him you know whose game this is.

One small figure, dead centre, in the cold blue dark of the keep. He is a hooded thief — a cloaked silhouette, no arms, a pointed hood — and he is the reason this is Shadowkeep and not Gloaming. He doesn't move yet. He doesn't do anything yet. He just stands there, and that's enough: the moment you see him, you know whose game this is.

Starting from Gloaming

Most of this unit's code you have met before. Lift your eyes from the page and you could almost write it from memory:

  • A black borderld a, 0 / out ($FE), a. The keep wants the dark.
  • The stone wash — one LDIR that pours a single attribute byte across all 768 cells, exactly as Gloaming washed its square in cobble. Here the byte is STONE: PAPER 1 (blue), INK 0. Cold, flat, and dim — a keep at night.
  • The address helpersscr_addr_cr and attr_addr_cr, carried over byte-for-byte. Give them a row in B and a column in C, and they hand back the screen or attribute address of that cell. You built these in Gloaming; here they are again, doing the same job.
  • The eight-row draw loopld a, (de) / ld (hl), a / inc de / inc h, eight times. The inc h is the Spectrum's famous screen quirk: the next pixel row of a character cell lives 256 bytes further on, so stepping H up by one walks straight down the cell.

None of that is new. What's new is the eight bytes that loop reads.

Designing a silhouette

A Spectrum sprite, at its simplest, is eight bytes — eight rows of eight pixels, a bit per pixel. Sixty-four pixels to say "this is the hero." It isn't much — and Ultimate's famous characters were bigger than this, several cells across and masked, drawn with far more effort than a single cell takes. But they won their recognisability the same way one cell must, and the trick is always the same: get the silhouette right and the detail doesn't matter. At eight-by-eight you have room for nothing but silhouette — which makes it the purest place to learn the rule.

Here is the thief, written so you can read the figure in the ones and zeros:

            defb    %00011000       ; ...XX...   the hood's peak
            defb    %00111100       ; ..XXXX..   the hood
            defb    %01111110       ; .XXXXXX.   hood meets shoulders
            defb    %01111110       ; .XXXXXX.   the cloak
            defb    %01111110       ; .XXXXXX.   the cloak
            defb    %01111110       ; .XXXXXX.   the cloak
            defb    %00111100       ; ..XXXX..   the cloak narrows
            defb    %00100100       ; ..X..X..   two feet at the hem

Read the Xs down the page and the shape is there: a narrow point at the top for the hood, widening through the shoulders, a full cloaked body, then narrowing to two feet at the hem. A 1 bit is a lit pixel; a 0 is bare stone showing through. No face, no hands — a silhouette doesn't need them. Gloaming's hero was a little lamplighter with his arms out; the thief is all cloak and hood. Same eight bytes of effort, an entirely different character.

That difference — same engine, different silhouette — is the whole of this unit.

His colour, and the keep's

A sprite's shape is in the bitmap; its colour is in the one attribute byte of the cell it stands in. We wash the whole keep in STONE (blue), then overwrite the thief's own cell with THIEF:

STONE       equ     %00001000       ; PAPER 1 (blue), INK 0 — cold blue stone
THIEF       equ     %01001010       ; BRIGHT, PAPER 1 (blue), INK 2 (red)

THIEF keeps the blue PAPER, so he stands on the same stone as everything around him — but its INK is 2 (red) and its BRIGHT bit is set, so his lit pixels burn a vivid red. A red hooded figure on cold blue stone: he reads at a glance, even one character-cell tall.

Drawing him

draw_thief is Gloaming's draw_lamp with one word changed — it points at thief instead of the lamplighter:

draw_thief:
            ld      b, HERO_ROW
            ld      c, HERO_COL
            call    attr_addr_cr        ; colour his cell
            ld      (hl), THIEF
            call    scr_addr_cr         ; B,C still hold row and column
            ld      de, thief            ; his eight bytes
            ld      b, 8
.dt:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .dt
            ret

Colour the cell, then lay the eight rows. That's the same draw a moving sprite will use a thousand times a game — but here it runs once, at the centre, and stops.

Milestone — stand him in the keep

We wash the whole keep dark and empty in cold blue stone, then stand the thief in its centre: colour his cell, then lay his eight rows into the bitmap with the carried-over draw loop.

A screen washed entirely in cold blue stone, framed by a black border, with no figure in it.
Step 1: the keep, washed dark and empty — one LDIR pours STONE across all 768 cells. Cold, flat, and waiting for an inhabitant.
Step 2: stand the hooded thief in the centre of the keep
+76-2
11 ; Shadowkeep — Unit 1: A Hooded Figure
22 ; Cumulative build; every step runs on its own. Narrative: the unit page.
3-; step-01 washes the keep in cold blue stone — dark, and empty.
3+; step-02 stands the hooded thief in the centre of the keep.
44
55 org 32768
66
77 STONE equ %00001000 ; PAPER 1 (blue), INK 0 — cold blue stone
8+THIEF equ %01001010 ; BRIGHT, PAPER 1 (blue), INK 2 (red) — the thief
9+HERO_COL equ 15 ; the middle of the keep, as in Gloaming
10+HERO_ROW equ 11
811
912 ; ----------------------------------------------------------------------------
10-; SETUP — the keep is dark, and empty.
13+; SETUP — the keep is dark, and a figure stands in it.
1114 ; ----------------------------------------------------------------------------
1215 start:
1316 ld a, 0
...
1821 ld (hl), STONE
1922 ld bc, 767
2023 ldir
24+
25+ call draw_thief
2126
2227 im 1
2328 ei
2429 .loop:
2530 halt
2631 jr .loop
32+
33+; ----------------------------------------------------------------------------
34+; draw_thief — colour his cell, then lay his eight rows into the bitmap.
35+; Exactly Gloaming's draw_lamp, pointed at a different eight bytes.
36+; ----------------------------------------------------------------------------
37+draw_thief:
38+ ld b, HERO_ROW
39+ ld c, HERO_COL
40+ call attr_addr_cr
41+ ld (hl), THIEF
42+ call scr_addr_cr ; B,C still hold the row and column
43+ ld de, thief
44+ ld b, 8
45+.dt:
46+ ld a, (de)
47+ ld (hl), a
48+ inc de
49+ inc h ; next pixel row is one cell-third down: +256
50+ djnz .dt
51+ ret
52+
53+; ----------------------------------------------------------------------------
54+; scr_addr_cr / attr_addr_cr — row in B, column in C, address out in HL.
55+; Carried verbatim from Gloaming.
56+; ----------------------------------------------------------------------------
57+scr_addr_cr:
58+ ld a, b
59+ and %00011000
60+ or %01000000
61+ ld h, a
62+ ld a, b
63+ and %00000111
64+ rrca
65+ rrca
66+ rrca
67+ or c
68+ ld l, a
69+ ret
70+
71+attr_addr_cr:
72+ ld a, b
73+ ld l, a
74+ ld h, 0
75+ add hl, hl
76+ add hl, hl
77+ add hl, hl
78+ add hl, hl
79+ add hl, hl
80+ ld de, $5800
81+ add hl, de
82+ ld a, c
83+ ld e, a
84+ ld d, 0
85+ add hl, de
86+ ret
87+
88+; ----------------------------------------------------------------------------
89+; The hooded thief — eight bytes, drawn here so you can read the figure in the
90+; ones and zeros: a pointed hood, a cloaked body, two feet at the hem.
91+; ----------------------------------------------------------------------------
92+thief:
93+ defb %00011000 ; ...XX... the hood's peak
94+ defb %00111100 ; ..XXXX.. the hood
95+ defb %01111110 ; .XXXXXX. hood meets shoulders
96+ defb %01111110 ; .XXXXXX. the cloak
97+ defb %01111110 ; .XXXXXX. the cloak
98+ defb %01111110 ; .XXXXXX. the cloak
99+ defb %00111100 ; ..XXXX.. the cloak narrows
100+ defb %00100100 ; ..X..X.. two feet at the hem
27101
28102 end start
29103
The complete program
; Shadowkeep — Unit 1: A Hooded Figure
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-02 stands the hooded thief in the centre of the keep.

            org     32768

STONE       equ     %00001000       ; PAPER 1 (blue), INK 0 — cold blue stone
THIEF       equ     %01001010       ; BRIGHT, PAPER 1 (blue), INK 2 (red) — the thief
HERO_COL    equ     15              ; the middle of the keep, as in Gloaming
HERO_ROW    equ     11

; ----------------------------------------------------------------------------
; SETUP — the keep is dark, and a figure stands in it.
; ----------------------------------------------------------------------------
start:
            ld      a, 0
            out     ($FE), a        ; black border — the keep wants the dark

            ld      hl, $5800       ; wash all 768 attribute cells in stone
            ld      de, $5801
            ld      (hl), STONE
            ld      bc, 767
            ldir

            call    draw_thief

            im      1
            ei
.loop:
            halt
            jr      .loop

; ----------------------------------------------------------------------------
; draw_thief — colour his cell, then lay his eight rows into the bitmap.
; Exactly Gloaming's draw_lamp, pointed at a different eight bytes.
; ----------------------------------------------------------------------------
draw_thief:
            ld      b, HERO_ROW
            ld      c, HERO_COL
            call    attr_addr_cr
            ld      (hl), THIEF
            call    scr_addr_cr     ; B,C still hold the row and column
            ld      de, thief
            ld      b, 8
.dt:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h               ; next pixel row is one cell-third down: +256
            djnz    .dt
            ret

; ----------------------------------------------------------------------------
; scr_addr_cr / attr_addr_cr — row in B, column in C, address out in HL.
; Carried verbatim from Gloaming.
; ----------------------------------------------------------------------------
scr_addr_cr:
            ld      a, b
            and     %00011000
            or      %01000000
            ld      h, a
            ld      a, b
            and     %00000111
            rrca
            rrca
            rrca
            or      c
            ld      l, a
            ret

attr_addr_cr:
            ld      a, b
            ld      l, a
            ld      h, 0
            add     hl, hl
            add     hl, hl
            add     hl, hl
            add     hl, hl
            add     hl, hl
            ld      de, $5800
            add     hl, de
            ld      a, c
            ld      e, a
            ld      d, 0
            add     hl, de
            ret

; ----------------------------------------------------------------------------
; The hooded thief — eight bytes, drawn here so you can read the figure in the
; ones and zeros: a pointed hood, a cloaked body, two feet at the hem.
; ----------------------------------------------------------------------------
thief:
            defb    %00011000       ; ...XX...   the hood's peak
            defb    %00111100       ; ..XXXX..   the hood
            defb    %01111110       ; .XXXXXX.   hood meets shoulders
            defb    %01111110       ; .XXXXXX.   the cloak
            defb    %01111110       ; .XXXXXX.   the cloak
            defb    %01111110       ; .XXXXXX.   the cloak
            defb    %00111100       ; ..XXXX..   the cloak narrows
            defb    %00100100       ; ..X..X..   two feet at the hem

            end     start

A black border, a screen of cold blue stone, and a single red hooded figure standing in the middle of it. The keep has its first inhabitant.

Try this: redraw the silhouette

The eight bytes are a drawing grid. Sketch your own hero on paper — eight rows, eight squares each — fill the squares that should be lit, then read each row off as a binary number into a defb. Make the hood taller, give him a hint of a face by clearing a pixel, widen the cloak. Reassemble and he changes. This is the most direct relationship in the whole machine: the bits are the picture.

Try this: a thief of a different colour

Change THIEF's INK from 2 (red) to another colour — %01001110 for yellow (INK 6), %01001111 for white (INK 7). Which reads best against the blue stone? Try clearing the BRIGHT bit (%00001010) and watch him dim into the gloom. You're tuning a character's presence with three bits.

Try this: a warmer keep

Change STONE from %00001000 (PAPER 1, blue) to %00010000 (PAPER 2, red) or %00000000 (black). The whole keep's mood shifts with one byte — a cold blue dungeon, a hellish red one, or a pitch-black void where only the figure shows. We'll keep the cold blue; but feel how much the background colour alone decides the place.

When it's wrong, see why

  • No figure at all. draw_thief isn't being called, or de doesn't point at thief. The draw loop reads its eight bytes from (de), so ld de, thief must come first.
  • The figure is there but the wrong shape. Check the defb bytes — a mistyped bit flips a pixel. Read the Xs in the comments against what's on screen.
  • The figure is a single solid block, or scrambled. The inc h in the loop is what walks down the cell. With inc l instead you'd write eight bytes along one pixel row, not down eight rows — a thin streak, not a figure.
  • The screen is grey-white, not blue. The LDIR stone wash didn't run, or DE wasn't set to HL + 1. Without the wash the keep is whatever the ROM left behind.
  • Wrong place on screen. HERO_ROW and HERO_COL set the cell. scr_addr_cr and attr_addr_cr expect the row in B, the column in C — swap them and he lands somewhere unexpected.

Before and after

You started with a finished game behind you and a blank ambition ahead, and ended with Shadowkeep's hero standing in its first dark room — and almost none of it was new code. The black border, the LDIR stone wash, scr_addr_cr and attr_addr_cr, the eight-row inc h draw loop all carried over from Gloaming intact; the one new thing was eight bytes of silhouette and a colour. That is how a sequel starts — not from a blank file, but from a finished engine wearing a new face.

What you've learnt

  • A game's identity starts with its silhouette. Eight bytes, sixty-four pixels — get the shape right and the character reads, even tiny.
  • A sprite is shape plus colour. The bitmap (eight defb rows) is the shape; the one attribute byte of its cell is the colour.
  • The bits are the picture. Lit pixels are 1s, bare cell is 0s — you can draw straight in binary.
  • Shadowkeep stands on Gloaming. The border, the LDIR wash, scr_addr_cr / attr_addr_cr, the inc h draw loop — all carried over. A new game reuses a finished engine; it doesn't rebuild it.

What's next

The thief stands in an empty blue void. A keep needs walls. In Unit 2, "The First Hall," we draw the keep's first room — a designed hall of stone walls and floor, laid from a data table the way Gloaming drew its square, but composed to look like somewhere. By the end the thief will be standing in a place, not a void — the first room of a keep that will grow, over this game, into many.