One Step
The lamplighter moves. Keep his column as a byte in memory, and on a held key erase his old cell, change the column, draw the new one. Cell-by-cell movement — working, for now, only because the floor is blank.
In Unit 4 the lamplighter felt the keys but stayed put — he just changed colour. Now he walks. Hold O and he steps left; hold P and he steps right, cell by cell along his row. This is the first half of Gloaming's second big technique — the cell sprite, a figure with a position that moves by being rubbed out where he was and drawn where he's going.
Where we start
Unit 4's figure reads O and P and recolours to show it — useful scaffolding, but he doesn't go anywhere. We throw the recolour away and spend the same key read on movement instead.
A position is state
Until now his column was fixed in the source — a constant baked in at assembly time. A thing that moves can't have a fixed position; it needs one the program can change. So his column becomes a byte in memory:
lamp_col:
defb 15 ; his column — we'll write new values here as he walks
That one byte is the difference between a picture and a game. A picture is drawn and done; a game keeps state — where things are, what's happened — and edits it as it runs. lamp_col is Gloaming's first piece of living state. Stepping left is dec it; stepping right is inc it.
Moving is erase, then draw
You can't just draw him in the new cell — the old one would still show him, and you'd leave a trail of lamplighters down the row. Every move is two halves: erase the cell he's in (blank its eight pixel rows back to bare floor), then draw him in the new cell once the column has changed. Erase old, change column, draw new. Miss the erase and he smears; get the order wrong and he rubs himself out.
One row, one addition
He only moves left and right, so he never leaves his character row — and that keeps the arithmetic gentle. Every cell in one row shares the same screen third and row-within-third (the awkward parts from Unit 2); only the column changes. So his cell address is just ROW_SCR + col, where ROW_SCR is column 0 of his row, worked out once as an equ. No thirds to decode at run time — add the column and we're there.
Milestone 1 — walk right
We swap the whole INPUT stage. Out goes the recolour; in comes a lamp_col byte, a draw_lamp and an erase_lamp routine, and — on a held P — the erase / change-column / draw dance. (Left follows in a moment; one direction first proves the machinery.)
| 1 | 1 | ; Gloaming — Unit 5: One Step | |
| 2 | 2 | ; Cumulative build; every step runs on its own. Narrative: the unit page. | |
| 3 | - | ; step-00 is Unit 4's key-reader, still only recolouring the figure. | |
| 3 | + | ; step-01 gives him a position in memory and walks him right while P is held. | |
| 4 | 4 | | |
| 5 | 5 | org 32768 | |
| 6 | 6 | | |
| 7 | - | COBBLE equ %00000001 ; PAPER black (0), INK blue (1) — dark ground | |
| 8 | - | WALL equ %00001111 ; PAPER blue (1), INK white (7) — pale stone | |
| 9 | - | LAMP_ATTR equ %01000111 ; BRIGHT, PAPER black, INK white — the figure at rest | |
| 10 | - | RIGHT_ATTR equ %01000100 ; BRIGHT, INK green (4) — holding P | |
| 11 | - | LEFT_ATTR equ %01000010 ; BRIGHT, INK red (2) — holding O | |
| 7 | + | COBBLE equ %00000001 ; PAPER black, INK blue — dark ground | |
| 8 | + | WALL equ %00001111 ; PAPER blue, INK white — pale stone | |
| 9 | + | LAMP_ATTR equ %01000111 ; BRIGHT, PAPER black, INK white — the figure | |
| 12 | 10 | | |
| 13 | - | LAMP_COL equ 15 | |
| 14 | - | LAMP_ROW equ 11 | |
| 15 | - | THIRD equ LAMP_ROW / 8 | |
| 16 | - | CHARROW equ LAMP_ROW - THIRD * 8 | |
| 17 | - | LAMP_SCR equ $4000 + THIRD * $0800 + CHARROW * 32 + LAMP_COL | |
| 18 | - | LAMP_ATTR_ADDR equ $5800 + LAMP_ROW * 32 + LAMP_COL | |
| 11 | + | ; --- his row is fixed; only his column changes --- | |
| 12 | + | ROW equ 11 | |
| 13 | + | ROW_THIRD equ ROW / 8 ; which screen third (0,1,2) | |
| 14 | + | ROW_CHARROW equ ROW - ROW_THIRD * 8 ; row within that third (0-7) | |
| 15 | + | ROW_SCR equ $4000 + ROW_THIRD * $0800 + ROW_CHARROW * 32 ; col-0 of the row | |
| 16 | + | ROW_ATTR equ $5800 + ROW * 32 ; col-0 attribute | |
| 17 | + | START_COL equ 15 | |
| 19 | 18 | | |
| 20 | - | ; --- the keyboard half-row holding O and P --- | |
| 21 | - | KEYS_OP equ $DFFE ; high byte $DF selects this half-row | |
| 19 | + | KEYS_OP equ $DFFE ; half-row with P(bit0) and O(bit1) | |
| 22 | 20 | | |
| 23 | 21 | start: | |
| 24 | - | ; --- the border goes black — the night beyond the square --- | |
| 25 | - | ld a, 0 | |
| 22 | + | ld a, 0 ; border black | |
| 26 | 23 | out ($FE), a | |
| 27 | 24 | | |
| 28 | - | ; --- wash the whole grid in cobbles --- | |
| 29 | - | ld hl, $5800 | |
| 25 | + | ld hl, $5800 ; wash the grid in cobbles | |
| 30 | 26 | ld de, $5801 | |
| 31 | 27 | ld (hl), COBBLE | |
| 32 | 28 | ld bc, 767 | |
| 33 | 29 | ldir | |
| 34 | 30 | | |
| 35 | - | ; --- top and bottom walls --- | |
| 36 | - | ld hl, $5800 | |
| 31 | + | ld hl, $5800 ; wall frame — top row | |
| 37 | 32 | ld b, 32 | |
| 38 | 33 | .top: | |
| 39 | 34 | ld (hl), WALL | |
| 40 | 35 | inc hl | |
| 41 | 36 | djnz .top | |
| 42 | 37 | | |
| 43 | - | ld hl, $5AE0 | |
| 38 | + | ld hl, $5AE0 ; bottom row | |
| 44 | 39 | ld b, 32 | |
| 45 | 40 | .bottom: | |
| 46 | 41 | ld (hl), WALL | |
| 47 | 42 | inc hl | |
| 48 | 43 | djnz .bottom | |
| 49 | 44 | | |
| 50 | - | ; --- left and right walls --- | |
| 51 | - | ld hl, $5800 | |
| 45 | + | ld hl, $5800 ; left and right columns | |
| 52 | 46 | ld b, 24 | |
| 53 | 47 | .sides: | |
| 54 | 48 | ld (hl), WALL | |
| ... | |||
| 60 | 54 | ld de, 32 | |
| 61 | 55 | add hl, de | |
| 62 | 56 | djnz .sides | |
| 63 | - | | |
| 64 | - | ; --- draw the lamplighter (colour, then shape) --- | |
| 65 | - | ld hl, LAMP_ATTR_ADDR | |
| 66 | - | ld (hl), LAMP_ATTR | |
| 67 | 57 | | |
| 68 | - | ld hl, LAMP_SCR | |
| 69 | - | ld de, lamplighter | |
| 70 | - | ld b, 8 | |
| 71 | - | .draw: | |
| 72 | - | ld a, (de) | |
| 73 | - | ld (hl), a | |
| 74 | - | inc de | |
| 75 | - | inc h | |
| 76 | - | djnz .draw | |
| 58 | + | call draw_lamp ; draw him once, at his starting column | |
| 77 | 59 | | |
| 78 | - | ; --- start the 50 Hz heartbeat --- | |
| 60 | + | ; --- the heartbeat: read P and, if held, step right --- | |
| 79 | 61 | im 1 | |
| 80 | 62 | ei | |
| 81 | 63 | | |
| 82 | 64 | game_loop: | |
| 83 | - | halt ; wait for the next frame | |
| 65 | + | halt | |
| 84 | 66 | | |
| 85 | - | ; --- INPUT: read O and P and recolour to show what's held --- | |
| 86 | - | ld bc, KEYS_OP ; BC = $DFFE — the address IS the question | |
| 87 | - | in a, (c) ; bottom 5 bits = keys, 0 = held (active low) | |
| 88 | - | ld d, LAMP_ATTR ; assume nothing held → white at rest | |
| 89 | - | bit 1, a ; O (left)? Z set = bit is 0 = held | |
| 90 | - | jr nz, .not_left | |
| 91 | - | ld d, LEFT_ATTR ; red | |
| 92 | - | .not_left: | |
| 67 | + | ld bc, KEYS_OP | |
| 68 | + | in a, (c) ; bottom bits, 0 = held | |
| 93 | 69 | bit 0, a ; P (right)? | |
| 94 | - | jr nz, .not_right | |
| 95 | - | ld d, RIGHT_ATTR ; green | |
| 96 | - | .not_right: | |
| 97 | - | ld a, d | |
| 98 | - | ld (LAMP_ATTR_ADDR), a ; one attribute write — his cell recolours | |
| 70 | + | jr z, .step_right | |
| 71 | + | jr game_loop ; nothing held — hold position | |
| 99 | 72 | | |
| 73 | + | .step_right: | |
| 74 | + | call erase_lamp ; rub him out where he is | |
| 75 | + | ld a, (lamp_col) | |
| 76 | + | inc a ; one cell right | |
| 77 | + | ld (lamp_col), a | |
| 78 | + | call draw_lamp ; draw him where he's going | |
| 100 | 79 | jr game_loop | |
| 101 | 80 | | |
| 102 | - | ; The lamplighter's shape — eight bytes, one per pixel row (from Unit 2). | |
| 81 | + | ; ---------------------------------------------------------------------------- | |
| 82 | + | ; draw_lamp — colour his cell and stamp his shape into it. | |
| 83 | + | ; cell address = ROW_SCR + col, attribute = ROW_ATTR + col. | |
| 84 | + | ; ---------------------------------------------------------------------------- | |
| 85 | + | draw_lamp: | |
| 86 | + | ld a, (lamp_col) | |
| 87 | + | ld e, a | |
| 88 | + | ld d, 0 ; DE = column offset | |
| 89 | + | ld hl, ROW_ATTR | |
| 90 | + | add hl, de | |
| 91 | + | ld (hl), LAMP_ATTR ; his cell takes the figure's colour | |
| 92 | + | | |
| 93 | + | ld hl, ROW_SCR | |
| 94 | + | add hl, de ; HL = top row of his cell | |
| 95 | + | ld de, lamplighter ; DE now walks the shape | |
| 96 | + | ld b, 8 | |
| 97 | + | .draw_row: | |
| 98 | + | ld a, (de) | |
| 99 | + | ld (hl), a | |
| 100 | + | inc de | |
| 101 | + | inc h ; down one screen row (+256) | |
| 102 | + | djnz .draw_row | |
| 103 | + | ret | |
| 104 | + | | |
| 105 | + | ; ---------------------------------------------------------------------------- | |
| 106 | + | ; erase_lamp — blank his cell back to bare cobbles. | |
| 107 | + | ; (Safe ONLY because the floor has no pixels — see the unit page.) | |
| 108 | + | ; ---------------------------------------------------------------------------- | |
| 109 | + | erase_lamp: | |
| 110 | + | ld a, (lamp_col) | |
| 111 | + | ld e, a | |
| 112 | + | ld d, 0 | |
| 113 | + | ld hl, ROW_ATTR | |
| 114 | + | add hl, de | |
| 115 | + | ld (hl), COBBLE ; the vacated cell is cobbles again | |
| 116 | + | | |
| 117 | + | ld hl, ROW_SCR | |
| 118 | + | add hl, de | |
| 119 | + | ld b, 8 | |
| 120 | + | xor a ; A = 0 — a blank pixel row | |
| 121 | + | .erase_row: | |
| 122 | + | ld (hl), a | |
| 123 | + | inc h | |
| 124 | + | djnz .erase_row | |
| 125 | + | ret | |
| 126 | + | | |
| 127 | + | ; ---------------------------------------------------------------------------- | |
| 128 | + | ; State and shape. | |
| 129 | + | ; ---------------------------------------------------------------------------- | |
| 130 | + | lamp_col: | |
| 131 | + | defb START_COL ; his column — changes as he walks | |
| 132 | + | | |
| 103 | 133 | lamplighter: | |
| 104 | 134 | defb %00111100 ; ..XXXX.. head | |
| 105 | 135 | defb %00111100 ; ..XXXX.. head |
The complete step 1 program
; Gloaming — Unit 5: One Step
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 gives him a position in memory and walks him right while P is held.
org 32768
COBBLE equ %00000001 ; PAPER black, INK blue — dark ground
WALL equ %00001111 ; PAPER blue, INK white — pale stone
LAMP_ATTR equ %01000111 ; BRIGHT, PAPER black, INK white — the figure
; --- his row is fixed; only his column changes ---
ROW equ 11
ROW_THIRD equ ROW / 8 ; which screen third (0,1,2)
ROW_CHARROW equ ROW - ROW_THIRD * 8 ; row within that third (0-7)
ROW_SCR equ $4000 + ROW_THIRD * $0800 + ROW_CHARROW * 32 ; col-0 of the row
ROW_ATTR equ $5800 + ROW * 32 ; col-0 attribute
START_COL equ 15
KEYS_OP equ $DFFE ; half-row with P(bit0) and O(bit1)
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_lamp ; draw him once, at his starting column
; --- the heartbeat: read P and, if held, step right ---
im 1
ei
game_loop:
halt
ld bc, KEYS_OP
in a, (c) ; bottom bits, 0 = held
bit 0, a ; P (right)?
jr z, .step_right
jr game_loop ; nothing held — hold position
.step_right:
call erase_lamp ; rub him out where he is
ld a, (lamp_col)
inc a ; one cell right
ld (lamp_col), a
call draw_lamp ; draw him where he's going
jr game_loop
; ----------------------------------------------------------------------------
; draw_lamp — colour his cell and stamp his shape into it.
; cell address = ROW_SCR + col, attribute = ROW_ATTR + col.
; ----------------------------------------------------------------------------
draw_lamp:
ld a, (lamp_col)
ld e, a
ld d, 0 ; DE = column offset
ld hl, ROW_ATTR
add hl, de
ld (hl), LAMP_ATTR ; his cell takes the figure's colour
ld hl, ROW_SCR
add hl, de ; HL = top row of his cell
ld de, lamplighter ; DE now walks the shape
ld b, 8
.draw_row:
ld a, (de)
ld (hl), a
inc de
inc h ; down one screen row (+256)
djnz .draw_row
ret
; ----------------------------------------------------------------------------
; erase_lamp — blank his cell back to bare cobbles.
; (Safe ONLY because the floor has no pixels — see the unit page.)
; ----------------------------------------------------------------------------
erase_lamp:
ld a, (lamp_col)
ld e, a
ld d, 0
ld hl, ROW_ATTR
add hl, de
ld (hl), COBBLE ; the vacated cell is cobbles again
ld hl, ROW_SCR
add hl, de
ld b, 8
xor a ; A = 0 — a blank pixel row
.erase_row:
ld (hl), a
inc h
djnz .erase_row
ret
; ----------------------------------------------------------------------------
; State and shape.
; ----------------------------------------------------------------------------
lamp_col:
defb START_COL ; his column — changes as he walks
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
end start
Hold P and he walks right, one cell at a time — and the floor stays clean behind him, because each step erases the old cell before drawing the new one:
Milestone 2 — and back the other way
Left is the mirror of right: the same erase / change-column / draw, but dec lamp_col instead of inc. We add the O test above the P test, and now he walks both ways.
| 1 | 1 | ; Gloaming — Unit 5: One Step | |
| 2 | 2 | ; Cumulative build; every step runs on its own. Narrative: the unit page. | |
| 3 | - | ; step-01 gives him a position in memory and walks him right while P is held. | |
| 3 | + | ; step-02 adds the other direction — O steps left; he walks both ways. | |
| 4 | 4 | | |
| 5 | 5 | org 32768 | |
| 6 | 6 | | |
| ... | |||
| 57 | 57 | | |
| 58 | 58 | call draw_lamp ; draw him once, at his starting column | |
| 59 | 59 | | |
| 60 | - | ; --- the heartbeat: read P and, if held, step right --- | |
| 60 | + | ; --- the heartbeat: read a direction and, if held, step that way --- | |
| 61 | 61 | im 1 | |
| 62 | 62 | ei | |
| 63 | 63 | | |
| ... | |||
| 66 | 66 | | |
| 67 | 67 | ld bc, KEYS_OP | |
| 68 | 68 | in a, (c) ; bottom bits, 0 = held | |
| 69 | + | bit 1, a ; O (left)? | |
| 70 | + | jr z, .step_left | |
| 69 | 71 | bit 0, a ; P (right)? | |
| 70 | 72 | jr z, .step_right | |
| 71 | 73 | jr game_loop ; nothing held — hold position | |
| 72 | 74 | | |
| 73 | - | .step_right: | |
| 75 | + | .step_left: | |
| 74 | 76 | call erase_lamp ; rub him out where he is | |
| 77 | + | ld a, (lamp_col) | |
| 78 | + | dec a ; one cell left | |
| 79 | + | ld (lamp_col), a | |
| 80 | + | call draw_lamp | |
| 81 | + | jr game_loop | |
| 82 | + | | |
| 83 | + | .step_right: | |
| 84 | + | call erase_lamp | |
| 75 | 85 | ld a, (lamp_col) | |
| 76 | 86 | inc a ; one cell right | |
| 77 | 87 | ld (lamp_col), a | |
| 78 | - | call draw_lamp ; draw him where he's going | |
| 88 | + | call draw_lamp | |
| 79 | 89 | jr game_loop | |
| 80 | 90 | | |
| 81 | 91 | ; ---------------------------------------------------------------------------- |
The complete program
; Gloaming — Unit 5: One Step
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-02 adds the other direction — O steps left; he walks both ways.
org 32768
COBBLE equ %00000001 ; PAPER black, INK blue — dark ground
WALL equ %00001111 ; PAPER blue, INK white — pale stone
LAMP_ATTR equ %01000111 ; BRIGHT, PAPER black, INK white — the figure
; --- his row is fixed; only his column changes ---
ROW equ 11
ROW_THIRD equ ROW / 8 ; which screen third (0,1,2)
ROW_CHARROW equ ROW - ROW_THIRD * 8 ; row within that third (0-7)
ROW_SCR equ $4000 + ROW_THIRD * $0800 + ROW_CHARROW * 32 ; col-0 of the row
ROW_ATTR equ $5800 + ROW * 32 ; col-0 attribute
START_COL equ 15
KEYS_OP equ $DFFE ; half-row with P(bit0) and O(bit1)
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_lamp ; draw him once, at his starting column
; --- the heartbeat: read a direction and, if held, step that way ---
im 1
ei
game_loop:
halt
ld bc, KEYS_OP
in a, (c) ; bottom bits, 0 = held
bit 1, a ; O (left)?
jr z, .step_left
bit 0, a ; P (right)?
jr z, .step_right
jr game_loop ; nothing held — hold position
.step_left:
call erase_lamp ; rub him out where he is
ld a, (lamp_col)
dec a ; one cell left
ld (lamp_col), a
call draw_lamp
jr game_loop
.step_right:
call erase_lamp
ld a, (lamp_col)
inc a ; one cell right
ld (lamp_col), a
call draw_lamp
jr game_loop
; ----------------------------------------------------------------------------
; draw_lamp — colour his cell and stamp his shape into it.
; cell address = ROW_SCR + col, attribute = ROW_ATTR + col.
; ----------------------------------------------------------------------------
draw_lamp:
ld a, (lamp_col)
ld e, a
ld d, 0 ; DE = column offset
ld hl, ROW_ATTR
add hl, de
ld (hl), LAMP_ATTR ; his cell takes the figure's colour
ld hl, ROW_SCR
add hl, de ; HL = top row of his cell
ld de, lamplighter ; DE now walks the shape
ld b, 8
.draw_row:
ld a, (de)
ld (hl), a
inc de
inc h ; down one screen row (+256)
djnz .draw_row
ret
; ----------------------------------------------------------------------------
; erase_lamp — blank his cell back to bare cobbles.
; (Safe ONLY because the floor has no pixels — see the unit page.)
; ----------------------------------------------------------------------------
erase_lamp:
ld a, (lamp_col)
ld e, a
ld d, 0
ld hl, ROW_ATTR
add hl, de
ld (hl), COBBLE ; the vacated cell is cobbles again
ld hl, ROW_SCR
add hl, de
ld b, 8
xor a ; A = 0 — a blank pixel row
.erase_row:
ld (hl), a
inc h
djnz .erase_row
ret
; ----------------------------------------------------------------------------
; State and shape.
; ----------------------------------------------------------------------------
lamp_col:
defb START_COL ; his column — changes as he walks
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
end start
Hold a key and he glides — right on P, left on O — one figure, no trail, the floor wiped clean at every step:
He moves once per frame while held — brisk, since the loop runs fifty times a second. Taming that into a steadier walk is the last "Try this" below.
The assumption, named out loud
Here's the catch, and it matters: erasing means writing zeros over his cell, and that only looks right because the floor is blank. Cobbles are pure attribute colour — no pixels — so blanking a floor cell destroys nothing.
The moment he steps onto something with pixels — a lamp, which arrives in Unit 9 — this same blank-erase would wipe the lamp out as he passed. The naive erase isn't wrong yet; it's wrong later, and we'll feel exactly when. Unit 6 fixes it by saving what's underneath him before he stands there. For now: blank floor, blank erase, and it genuinely works.
When it's wrong, see why
Movement bugs are almost always the erase and the order:
- He leaves a trail of figures. The erase isn't running, or runs in the wrong place. The order must be: erase the old cell, then change
lamp_col, then draw the new cell. - He vanishes the moment you press. You changed
lamp_colbefore erasing, so you blanked the cell he's moving to and never cleaned up the one he left. Erase first. - He runs off the right edge and the screen garbles. Nothing stops him yet — no wall collision (Unit 7), no edge bounds (Unit 8). Tap instead of holding, for now.
- Nothing moves at all. The key read isn't reaching the step branches. Check the
IN A,(C)from Unit 4, and thatjr z(held) routes into.step_left/.step_right.
Before and after
You started with a figure that could only change colour and finished with one that walks — built by spending the same key read on movement instead of decoration. The two ideas under it carry the rest of the game: a position is state in memory you edit, and moving is erase then draw, in that order. The figure glides cleanly today only because the floor beneath him is empty — the honest limitation Unit 6 exists to remove.
Try this: take the erase away
Comment out the call erase_lamp in one of the step branches and run it. Now he leaves a trail of lamplighters across the row — every cell he passes keeps his shape, because nothing rubbed it out. Ugly, but it shows you exactly what the erase is for. Put it back.
Try this: watch the assumption bite
Prove the blank-erase flaw to yourself before Unit 6 fixes it. In setup, poke a stray pixel into a floor cell in his path — a solid bar at column 22:
ld hl, ROW_SCR + 22
ld (hl), %11111111
Walk him over column 22. As he leaves, the erase wipes your bar away — he damaged the floor just by passing. That's the bug Unit 6 exists to kill.
Try this: slow him to a walk
Moving every frame is fast. Add a one-byte counter that only lets him step every few frames: load it from, say, 4 after a step, dec it each frame, and only move when it reaches zero. Holding a key now repeats at a steady, controllable pace — the start of how real games handle held keys.
What you've learnt
- A moving thing keeps its position as state in memory (
lamp_col); moving means editing that state. - A move is erase the old cell, then draw the new one — order matters.
- Within a single row, a cell's address is just
ROW_SCR + col— no thirds to decode. - The naive blank-erase only works over blank floor; pixels underneath would be destroyed — the problem Unit 6 solves.
What's next
The lamplighter walks, but he can only ever cross empty floor without harm. In Unit 6 we make him safe over anything: before he stands in a cell, save the eight bytes (and the colour) already there; when he leaves, restore them. He'll be able to walk over lamps, walls, anything — without scrubbing it away. It's the honest "before" of a technique a later game upgrades into true masking.