The Lamps
The first real game objects. Keep the lamp positions in a small data table and draw an unlit cyan lantern at each — placement as data, not code. And Unit 6 pays off: the lamplighter walks over them without scrubbing them out.
The movement engine is finished — the lamplighter roams the walled square and keeps whatever he crosses. Now the game begins, and it begins with the thing he's here to do: light the lamps. So first there must be lamps.
A lamp is another cell sprite — a little lantern drawn into a cell — but a still one, sitting on the floor in cold cyan, waiting to be lit. The interesting question isn't how to draw one; we've drawn sprites since Unit 2. It's how to place eight of them without writing eight copies of the same code. The answer is a data table.
Where we start
Unit 8's finished engine — a figure roaming an empty walled square. We give the square its contents: the lamps he's here to light.
Placement is data, not code
We could write draw a lantern at (4,3), draw a lantern at (27,3), eight times over. Instead we write the positions as plain bytes — a small table of column/row pairs — and a loop that draws whatever the table holds:
lamp_data:
defb 4, 3
defb 27, 3
...
defb $FF ; a column of $FF marks the end
draw_lamps walks that table two bytes at a time, drawing an unlit lantern at each pair, and stops when it hits the $FF sentinel. Want a different level? Edit the table — the program doesn't change at all. Separating what (the data) from how (the code) is one of the oldest and best ideas in programming, and a level map is the perfect place to meet it.
A lamp is walkable floor — with pixels
Two small but vital choices. First, the lantern's colour is %00000101 — cyan INK on black PAPER. Black paper means bit 3 is clear, so the Unit 7 wall test reads a lamp as floor: the lamplighter can step onto it, which is the whole point — he lights a lamp by standing on it (Unit 10).
Second, and this is the lovely part: a lamp has pixels, and the lamplighter can walk straight over it without harm. That's Unit 6 paying off in full. When he steps onto a lamp, save_under tucks the lantern's nine bytes into the buffer; while he stands there his figure covers it; when he leaves, restore_under puts the lantern back exactly as it was. The naive blank-erase from Unit 5 would have wiped the lamp out the instant he touched it. Save/restore protects it for nothing.
Milestone — scatter the lamps
We add a lamp_data table of column/row pairs, a draw_lamps loop that places an unlit lantern at each (called in setup, before the lamplighter goes down on top), and the lantern's eight-byte shape. The movement engine, collision, and save/restore are all untouched — the lamps just are the floor now.
| 1 | 1 | ; Gloaming — Unit 9: The Lamps | |
| 2 | 2 | ; Cumulative build; every step runs on its own. Narrative: the unit page. | |
| 3 | - | ; step-00 is Unit 8's finished movement engine — an empty square to roam. | |
| 3 | + | ; step-01 scatters lamps from a (col,row) data table; he crosses them unharmed. | |
| 4 | 4 | | |
| 5 | 5 | org 32768 | |
| 6 | 6 | | |
| 7 | 7 | COBBLE equ %00000001 ; PAPER black, INK blue — floor | |
| 8 | 8 | WALL equ %00001111 ; PAPER blue, INK white — solid | |
| 9 | 9 | LAMP_ATTR equ %01000111 ; BRIGHT, PAPER black, INK white — the figure | |
| 10 | - | WALL_BIT equ 3 ; PAPER bit 0: set on walls, clear on floor | |
| 10 | + | LAMP_UNLIT equ %00000101 ; PAPER black, INK cyan, no bright — a cold, unlit lamp | |
| 11 | + | WALL_BIT equ 3 | |
| 11 | 12 | | |
| 12 | 13 | START_COL equ 15 | |
| 13 | 14 | START_ROW equ 11 | |
| 14 | 15 | | |
| 15 | - | KEYS_OP equ $DFFE ; O (bit1) left, P (bit0) right | |
| 16 | - | KEYS_Q equ $FBFE ; Q (bit0) up | |
| 17 | - | KEYS_A equ $FDFE ; A (bit0) down | |
| 16 | + | KEYS_OP equ $DFFE | |
| 17 | + | KEYS_Q equ $FBFE | |
| 18 | + | KEYS_A equ $FDFE | |
| 18 | 19 | | |
| 19 | 20 | ; ============================================================================ | |
| 20 | 21 | ; SETUP — runs once. | |
| ... | |||
| 56 | 57 | add hl, de | |
| 57 | 58 | djnz .sides | |
| 58 | 59 | | |
| 59 | - | call save_under | |
| 60 | + | call draw_lamps ; place the lamps from the table | |
| 61 | + | call save_under ; then the lamplighter, on top | |
| 60 | 62 | call draw_lamp | |
| 61 | 63 | | |
| 62 | 64 | ; ============================================================================ | |
| 63 | - | ; THE HEARTBEAT — pick a direction (QAOP), test the target, move if clear. | |
| 65 | + | ; THE HEARTBEAT — QAOP movement (Unit 8), unchanged. | |
| 64 | 66 | ; ============================================================================ | |
| 65 | 67 | im 1 | |
| 66 | 68 | ei | |
| ... | |||
| 68 | 70 | game_loop: | |
| 69 | 71 | halt | |
| 70 | 72 | | |
| 71 | - | ld a, (lamp_col) ; start the target at his current cell | |
| 73 | + | ld a, (lamp_col) | |
| 72 | 74 | ld (tcol), a | |
| 73 | 75 | ld a, (lamp_row) | |
| 74 | 76 | ld (trow), a | |
| 75 | 77 | | |
| 76 | - | ld bc, KEYS_OP ; O / P | |
| 78 | + | ld bc, KEYS_OP | |
| 77 | 79 | in a, (c) | |
| 78 | - | bit 1, a ; O — left | |
| 80 | + | bit 1, a | |
| 79 | 81 | jr z, .left | |
| 80 | - | bit 0, a ; P — right | |
| 82 | + | bit 0, a | |
| 81 | 83 | jr z, .right | |
| 82 | - | ld bc, KEYS_Q ; Q — up | |
| 84 | + | ld bc, KEYS_Q | |
| 83 | 85 | in a, (c) | |
| 84 | 86 | bit 0, a | |
| 85 | 87 | jr z, .up | |
| 86 | - | ld bc, KEYS_A ; A — down | |
| 88 | + | ld bc, KEYS_A | |
| 87 | 89 | in a, (c) | |
| 88 | 90 | bit 0, a | |
| 89 | 91 | jr z, .down | |
| 90 | - | jr game_loop ; nothing held | |
| 92 | + | jr game_loop | |
| 91 | 93 | | |
| 92 | 94 | .left: | |
| 93 | 95 | ld hl, tcol | |
| ... | |||
| 105 | 107 | ld hl, trow | |
| 106 | 108 | inc (hl) | |
| 107 | 109 | .try: | |
| 108 | - | ld a, (trow) ; test the target cell | |
| 110 | + | ld a, (trow) | |
| 109 | 111 | ld b, a | |
| 110 | 112 | ld a, (tcol) | |
| 111 | 113 | ld c, a | |
| 112 | - | call wall_at ; NZ = wall | |
| 113 | - | jr nz, game_loop ; blocked — stay put | |
| 114 | + | call wall_at | |
| 115 | + | jr nz, game_loop | |
| 114 | 116 | | |
| 115 | - | call restore_under ; clear — commit the move | |
| 117 | + | call restore_under | |
| 116 | 118 | ld a, (tcol) | |
| 117 | 119 | ld (lamp_col), a | |
| 118 | 120 | ld a, (trow) | |
| ... | |||
| 122 | 124 | jr game_loop | |
| 123 | 125 | | |
| 124 | 126 | ; ---------------------------------------------------------------------------- | |
| 125 | - | ; scr_addr_cr — B=row(0-23), C=col(0-31) -> HL = top-scanline screen address. | |
| 126 | - | ; high = $40 + (row AND $18) (the third, ×8 in the high byte) | |
| 127 | - | ; low = ((row AND 7) << 5) OR col (row-within-third ×32, plus column) | |
| 128 | - | ; Preserves BC. | |
| 127 | + | ; draw_lamps — walk the table, drawing an unlit lantern at each (col,row). | |
| 128 | + | ; Table is column,row pairs, ended by a column of $FF. | |
| 129 | + | ; ---------------------------------------------------------------------------- | |
| 130 | + | draw_lamps: | |
| 131 | + | ld hl, lamp_data | |
| 132 | + | .next: | |
| 133 | + | ld a, (hl) ; column | |
| 134 | + | cp $FF | |
| 135 | + | ret z ; $FF column → end of table | |
| 136 | + | ld c, a | |
| 137 | + | inc hl | |
| 138 | + | ld b, (hl) ; row | |
| 139 | + | inc hl | |
| 140 | + | push hl ; keep the table pointer | |
| 141 | + | call draw_lantern ; draw at (B=row, C=col) | |
| 142 | + | pop hl | |
| 143 | + | jr .next | |
| 144 | + | | |
| 145 | + | ; ---------------------------------------------------------------------------- | |
| 146 | + | ; draw_lantern — B=row, C=col. Stamp an unlit lantern into the cell. | |
| 147 | + | ; ---------------------------------------------------------------------------- | |
| 148 | + | draw_lantern: | |
| 149 | + | call attr_addr_cr ; HL = attribute, BC preserved | |
| 150 | + | ld (hl), LAMP_UNLIT | |
| 151 | + | call scr_addr_cr ; HL = screen, BC preserved | |
| 152 | + | ld de, lantern | |
| 153 | + | ld b, 8 | |
| 154 | + | .dlt: | |
| 155 | + | ld a, (de) | |
| 156 | + | ld (hl), a | |
| 157 | + | inc de | |
| 158 | + | inc h | |
| 159 | + | djnz .dlt | |
| 160 | + | ret | |
| 161 | + | | |
| 162 | + | ; ---------------------------------------------------------------------------- | |
| 163 | + | ; scr_addr_cr / attr_addr_cr / wall_at / pos_bc (Unit 8). | |
| 129 | 164 | ; ---------------------------------------------------------------------------- | |
| 130 | 165 | scr_addr_cr: | |
| 131 | 166 | ld a, b | |
| 132 | - | and %00011000 ; bits that select the third (= third*8) | |
| 133 | - | or %01000000 ; + $40 screen base | |
| 167 | + | and %00011000 | |
| 168 | + | or %01000000 | |
| 134 | 169 | ld h, a | |
| 135 | 170 | ld a, b | |
| 136 | - | and %00000111 ; row within third (0-7) | |
| 137 | - | rrca ; ×32: rotate the 3 bits up into 5-6-7 | |
| 171 | + | and %00000111 | |
| 138 | 172 | rrca | |
| 139 | 173 | rrca | |
| 140 | - | or c ; | column | |
| 174 | + | rrca | |
| 175 | + | or c | |
| 141 | 176 | ld l, a | |
| 142 | 177 | ret | |
| 143 | 178 | | |
| 144 | - | ; ---------------------------------------------------------------------------- | |
| 145 | - | ; attr_addr_cr — B=row, C=col -> HL = attribute address ($5800 + row*32 + col). | |
| 146 | - | ; Preserves BC. | |
| 147 | - | ; ---------------------------------------------------------------------------- | |
| 148 | 179 | attr_addr_cr: | |
| 149 | 180 | ld a, b | |
| 150 | 181 | ld l, a | |
| 151 | 182 | ld h, 0 | |
| 152 | - | add hl, hl ; ×2 | |
| 153 | - | add hl, hl ; ×4 | |
| 154 | - | add hl, hl ; ×8 | |
| 155 | - | add hl, hl ; ×16 | |
| 156 | - | add hl, hl ; ×32 -> HL = row*32 | |
| 183 | + | add hl, hl | |
| 184 | + | add hl, hl | |
| 185 | + | add hl, hl | |
| 186 | + | add hl, hl | |
| 187 | + | add hl, hl | |
| 157 | 188 | ld de, $5800 | |
| 158 | 189 | add hl, de | |
| 159 | 190 | ld a, c | |
| 160 | 191 | ld e, a | |
| 161 | 192 | ld d, 0 | |
| 162 | - | add hl, de ; + col | |
| 193 | + | add hl, de | |
| 163 | 194 | ret | |
| 164 | 195 | | |
| 165 | - | ; ---------------------------------------------------------------------------- | |
| 166 | - | ; wall_at — B=row, C=col of the target. NZ if it's a wall, Z if walkable. | |
| 167 | - | ; ---------------------------------------------------------------------------- | |
| 168 | 196 | wall_at: | |
| 169 | 197 | call attr_addr_cr | |
| 170 | 198 | bit WALL_BIT, (hl) | |
| 171 | 199 | ret | |
| 172 | 200 | | |
| 173 | - | ; ---------------------------------------------------------------------------- | |
| 174 | - | ; pos_bc — load BC with his current position (B=row, C=col). | |
| 175 | - | ; ---------------------------------------------------------------------------- | |
| 176 | 201 | pos_bc: | |
| 177 | 202 | ld a, (lamp_row) | |
| 178 | 203 | ld b, a | |
| ... | |||
| 181 | 206 | ret | |
| 182 | 207 | | |
| 183 | 208 | ; ---------------------------------------------------------------------------- | |
| 184 | - | ; save_under / restore_under / draw_lamp — now position-general (Unit 6 logic, | |
| 185 | - | ; using the (col,row) address routines). | |
| 209 | + | ; save_under / restore_under / draw_lamp (Unit 8) — protect whatever is under | |
| 210 | + | ; him, lamps included. | |
| 186 | 211 | ; ---------------------------------------------------------------------------- | |
| 187 | 212 | save_under: | |
| 188 | 213 | call pos_bc | |
| ... | |||
| 235 | 260 | ret | |
| 236 | 261 | | |
| 237 | 262 | ; ---------------------------------------------------------------------------- | |
| 238 | - | ; State, buffer, and shape. | |
| 263 | + | ; Level data, state, buffer, and shapes. | |
| 239 | 264 | ; ---------------------------------------------------------------------------- | |
| 265 | + | lamp_data: | |
| 266 | + | defb 4, 3 ; column, row | |
| 267 | + | defb 27, 3 | |
| 268 | + | defb 9, 7 | |
| 269 | + | defb 22, 7 | |
| 270 | + | defb 6, 15 | |
| 271 | + | defb 25, 15 | |
| 272 | + | defb 13, 20 | |
| 273 | + | defb 18, 20 | |
| 274 | + | defb $FF ; end of table | |
| 275 | + | | |
| 240 | 276 | lamp_col: | |
| 241 | 277 | defb START_COL | |
| 242 | 278 | lamp_row: | |
| 243 | 279 | defb START_ROW | |
| 244 | 280 | tcol: | |
| 245 | - | defb 0 ; target column being tested | |
| 281 | + | defb 0 | |
| 246 | 282 | trow: | |
| 247 | - | defb 0 ; target row being tested | |
| 283 | + | defb 0 | |
| 248 | 284 | | |
| 249 | 285 | under_lamp: | |
| 250 | 286 | defb 0, 0, 0, 0, 0, 0, 0, 0, 0 | |
| ... | |||
| 258 | 294 | defb %00011000 ; ...XX... body | |
| 259 | 295 | defb %00100100 ; ..X..X.. legs | |
| 260 | 296 | defb %01000010 ; .X....X. feet | |
| 297 | + | | |
| 298 | + | lantern: | |
| 299 | + | defb %00011000 ; ...XX... handle top | |
| 300 | + | defb %00100100 ; ..X..X.. handle loop | |
| 301 | + | defb %01111110 ; .XXXXXX. cap | |
| 302 | + | defb %01111110 ; .XXXXXX. glass | |
| 303 | + | defb %01011010 ; .X.XX.X. glass panes | |
| 304 | + | defb %01111110 ; .XXXXXX. glass | |
| 305 | + | defb %01111110 ; .XXXXXX. base | |
| 306 | + | defb %00111100 ; ..XXXX.. foot | |
| 261 | 307 | | |
| 262 | 308 | end start | |
| 263 | 309 | |
The complete program
; Gloaming — Unit 9: The Lamps
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 scatters lamps from a (col,row) data table; he crosses them unharmed.
org 32768
COBBLE equ %00000001 ; PAPER black, INK blue — floor
WALL equ %00001111 ; PAPER blue, INK white — solid
LAMP_ATTR equ %01000111 ; BRIGHT, PAPER black, INK white — the figure
LAMP_UNLIT equ %00000101 ; PAPER black, INK cyan, no bright — a cold, unlit lamp
WALL_BIT equ 3
START_COL equ 15
START_ROW equ 11
KEYS_OP equ $DFFE
KEYS_Q equ $FBFE
KEYS_A equ $FDFE
; ============================================================================
; SETUP — runs once.
; ============================================================================
start:
ld a, 0 ; border black
out ($FE), a
ld hl, $5800 ; wash the grid in cobbles
ld de, $5801
ld (hl), COBBLE
ld bc, 767
ldir
ld hl, $5800 ; wall frame — top row
ld b, 32
.top:
ld (hl), WALL
inc hl
djnz .top
ld hl, $5AE0 ; bottom row
ld b, 32
.bottom:
ld (hl), WALL
inc hl
djnz .bottom
ld hl, $5800 ; left and right columns
ld b, 24
.sides:
ld (hl), WALL
push hl
ld de, 31
add hl, de
ld (hl), WALL
pop hl
ld de, 32
add hl, de
djnz .sides
call draw_lamps ; place the lamps from the table
call save_under ; then the lamplighter, on top
call draw_lamp
; ============================================================================
; THE HEARTBEAT — QAOP movement (Unit 8), unchanged.
; ============================================================================
im 1
ei
game_loop:
halt
ld a, (lamp_col)
ld (tcol), a
ld a, (lamp_row)
ld (trow), a
ld bc, KEYS_OP
in a, (c)
bit 1, a
jr z, .left
bit 0, a
jr z, .right
ld bc, KEYS_Q
in a, (c)
bit 0, a
jr z, .up
ld bc, KEYS_A
in a, (c)
bit 0, a
jr z, .down
jr game_loop
.left:
ld hl, tcol
dec (hl)
jr .try
.right:
ld hl, tcol
inc (hl)
jr .try
.up:
ld hl, trow
dec (hl)
jr .try
.down:
ld hl, trow
inc (hl)
.try:
ld a, (trow)
ld b, a
ld a, (tcol)
ld c, a
call wall_at
jr nz, game_loop
call restore_under
ld a, (tcol)
ld (lamp_col), a
ld a, (trow)
ld (lamp_row), a
call save_under
call draw_lamp
jr game_loop
; ----------------------------------------------------------------------------
; draw_lamps — walk the table, drawing an unlit lantern at each (col,row).
; Table is column,row pairs, ended by a column of $FF.
; ----------------------------------------------------------------------------
draw_lamps:
ld hl, lamp_data
.next:
ld a, (hl) ; column
cp $FF
ret z ; $FF column → end of table
ld c, a
inc hl
ld b, (hl) ; row
inc hl
push hl ; keep the table pointer
call draw_lantern ; draw at (B=row, C=col)
pop hl
jr .next
; ----------------------------------------------------------------------------
; draw_lantern — B=row, C=col. Stamp an unlit lantern into the cell.
; ----------------------------------------------------------------------------
draw_lantern:
call attr_addr_cr ; HL = attribute, BC preserved
ld (hl), LAMP_UNLIT
call scr_addr_cr ; HL = screen, BC preserved
ld de, lantern
ld b, 8
.dlt:
ld a, (de)
ld (hl), a
inc de
inc h
djnz .dlt
ret
; ----------------------------------------------------------------------------
; scr_addr_cr / attr_addr_cr / wall_at / pos_bc (Unit 8).
; ----------------------------------------------------------------------------
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
wall_at:
call attr_addr_cr
bit WALL_BIT, (hl)
ret
pos_bc:
ld a, (lamp_row)
ld b, a
ld a, (lamp_col)
ld c, a
ret
; ----------------------------------------------------------------------------
; save_under / restore_under / draw_lamp (Unit 8) — protect whatever is under
; him, lamps included.
; ----------------------------------------------------------------------------
save_under:
call pos_bc
call scr_addr_cr
ld de, under_lamp
ld b, 8
.su:
ld a, (hl)
ld (de), a
inc de
inc h
djnz .su
call pos_bc
call attr_addr_cr
ld a, (hl)
ld (under_lamp + 8), a
ret
restore_under:
call pos_bc
call scr_addr_cr
ld de, under_lamp
ld b, 8
.ru:
ld a, (de)
ld (hl), a
inc de
inc h
djnz .ru
call pos_bc
call attr_addr_cr
ld a, (under_lamp + 8)
ld (hl), a
ret
draw_lamp:
call pos_bc
call attr_addr_cr
ld (hl), LAMP_ATTR
call pos_bc
call scr_addr_cr
ld de, lamplighter
ld b, 8
.dl:
ld a, (de)
ld (hl), a
inc de
inc h
djnz .dl
ret
; ----------------------------------------------------------------------------
; Level data, state, buffer, and shapes.
; ----------------------------------------------------------------------------
lamp_data:
defb 4, 3 ; column, row
defb 27, 3
defb 9, 7
defb 22, 7
defb 6, 15
defb 25, 15
defb 13, 20
defb 18, 20
defb $FF ; end of table
lamp_col:
defb START_COL
lamp_row:
defb START_ROW
tcol:
defb 0
trow:
defb 0
under_lamp:
defb 0, 0, 0, 0, 0, 0, 0, 0, 0
lamplighter:
defb %00111100 ; ..XXXX.. head
defb %00111100 ; ..XXXX.. head
defb %00011000 ; ...XX... neck
defb %01111110 ; .XXXXXX. arms
defb %00011000 ; ...XX... body
defb %00011000 ; ...XX... body
defb %00100100 ; ..X..X.. legs
defb %01000010 ; .X....X. feet
lantern:
defb %00011000 ; ...XX... handle top
defb %00100100 ; ..X..X.. handle loop
defb %01111110 ; .XXXXXX. cap
defb %01111110 ; .XXXXXX. glass
defb %01011010 ; .X.XX.X. glass panes
defb %01111110 ; .XXXXXX. glass
defb %01111110 ; .XXXXXX. base
defb %00111100 ; ..XXXX.. foot
end start
Eight cyan lanterns appear from the table, and the lamplighter drops in over the top — the square stops being empty:
And the Unit 6 payoff, caught in the act: walk him straight over a lamp and it survives, untouched and still cold:
When it's wrong, see why
The table and the lamp's colour are where this unit slips:
- No lamps appear.
draw_lampsisn't called in setup, or the table is missing its$FFsentinel. Check the call sits after the walls and before the lamplighter. - Lamps appear, then garbage trails off after the last one. The
$FFsentinel is missing, so the loop runs past the table into whatever bytes follow. End the table withdefb $FF. - A lamp stops him like a wall. Its attribute has a non-black PAPER (bit 3 set).
LAMP_UNLITmust be black-paper (%00000101) so collision treats it as floor. - Walking over a lamp erases it. Your save/restore isn't protecting it — make sure you're drawing him with the Unit 6 save/restore, not an erase.
Before and after
You started with an empty square and finished with a level — eight lamps placed from a table you can rewrite at will, and a lamplighter who crosses them without harm. The placement is data, kept apart from the code that draws it; the protection is Unit 6, finally tested on something with real pixels to lose. The square has a goal now: those eight cold lamps, waiting.
Try this: redraw the level
Change the table. Move a lamp, add a ninth pair, or thin them down to three — edit the defb lines (and keep the $FF at the end). The level redraws itself with no other change. Try a lamp right next to a wall, or a tight cluster in one corner. The placement is yours to author, in data.
Try this: walk over a lamp and watch it live
Steer the lamplighter onto a lamp and off again. The lantern vanishes under him, then reappears, unharmed — proof that save/restore is preserving real pixels now. For contrast, picture Unit 5's blank-erase doing this: the lamp would be gone the moment he stepped on it. This is the bug Unit 6 existed to prevent, caught in the act of not happening.
Try this: reshape the lantern
Edit the eight lantern bytes. Give it a taller chimney, a wider base, a brighter pane pattern. Sketch the eight rows, write the bytes, and every lamp on the board changes at once — because they all draw from the one shape. One glyph, eight lamps.
What you've learnt
- Game objects can be placed from a data table, not hard-coded — what (data) kept separate from how (code).
- A loop walks a table of pairs with a sentinel (
$FF) marking the end. - Save/restore now protects real pixels: the lamplighter crosses lamps unharmed — Unit 6's reason for existing.
- Lamps are walkable floor-with-pixels (black PAPER), so he can stand on them — which the next unit needs.
What's next
The lamps are placed and cold. In Unit 10 the lamplighter lights them: step onto an unlit lamp and mark it lit — bright yellow — by changing the same bytes save_under is holding, so when he walks on the lamp glows behind him. The background beneath the sprite stops being scenery and becomes state he can change: the heart of how this little game is won.