The Hero Remembers
Each room is redrawn from its template every time you enter, so the keep forgets everything. Move the rooms into RAM, let the thief chalk his route as he goes, and watch the marks survive leaving and returning. The keep gains a memory.
The thief steps cleanly between rooms now, but the keep has no memory. Each room is painted from its fixed template every time he walks in, so nothing he does to a room can last — change it, leave, come back, and it's pristine again, as if he'd never been. A keep you can get lost in has to remember. This unit gives it that.
What you'll see by the end
Hold SPACE and the thief chalks the floor as he walks, leaving a trail of marks — the old dungeoneer's trick for not walking in circles. Walk that trail east into the next room, then come back. The marks are still here, exactly as you left them. The keep remembered. That's the whole unit: a world that holds onto what you did to it.
Stop treating the map as read-only
The reason the keep forgets is that the room maps are templates — fixed data in the program, never changed. You can't write a chalk mark into a constant. So the first move is to make the keep's rooms live in RAM: at start-up we copy each template into a writable buffer, its state, and from then on the keep is those buffers.
ld hl, room0_template
ld de, room0_state ; a 768-byte RAM buffer
ld bc, 768
ldir ; copy the template into it
; ... same for room 1 ...
The room table's pointers now name the state buffers, not the templates, so draw_room paints from RAM. Nothing else about drawing changes — but now there's somewhere to write.
Chalk is a byte in the buffer
A chalk mark is just a third tile in the palette — glyph +, a little white cross — and crucially it's dim, so wall_at still reads it as walkable. (A bright mark would become solid stone and pen the thief in.) Marking a cell is one write into the room's state, at the thief's position:
mark_step:
; ... SPACE held? ...
call cell_state_addr ; HL -> this cell in the state buffer
ld (hl), '+' ; chalk it
; remember the mark as what's "under" him, so it shows when he moves:
ld hl, mark_tile
ld de, under_thief
ld bc, 8
ldir
ld a, MARK_ATTR
ld (under_thief + 8), a
Two writes, two effects. Writing + into the state buffer is what makes the mark persist — it's now part of the room, and every future draw_room will paint it. Updating under_thief is what makes it appear: the thief is standing on the cell, so the mark is hidden until he steps off and restore_under lays down what we told it was beneath him — the chalk.
Why it survives the journey
Follow the chalk through a doorway. You marked cells in room 0's state buffer. You walk east — check_exit switches to room 1 and draw_room paints room 1 from room 1's buffer. Room 0's buffer is untouched, sitting in RAM with your marks in it. Walk back west, draw_room paints room 0 again from that same buffer — and the chalk is precisely where you left it. Persistence isn't a feature you add; it's what you get for free once the world lives somewhere writable and you stop overwriting it.
Milestone — give the keep a memory
At start-up we LDIR each room's template into a 768-byte RAM state buffer and point the room table at the buffers, so draw_room paints from RAM. mark_step writes a dim + into that buffer at the thief's cell when SPACE is held — and into under_thief, so the mark shows the moment he steps off. Drawing reads from the buffer, so the chalk is now part of the room.
| 1 | 1 | ; Shadowkeep — Unit 7: The Hero Remembers | |
| 2 | 2 | ; Cumulative build; every step runs on its own. Narrative: the unit page. | |
| 3 | - | ; step-00 = Unit 6's end: rooms drawn from fixed templates — the keep forgets. | |
| 3 | + | ; step-01 copies rooms to RAM and chalks a trail that survives leaving and returning. | |
| 4 | 4 | | |
| 5 | 5 | org 32768 | |
| 6 | 6 | | |
| 7 | 7 | WALL_ATTR equ %01001000 | |
| 8 | 8 | FLOOR_ATTR equ %00001000 | |
| 9 | + | MARK_ATTR equ %00001111 ; dim, PAPER 1 (blue), INK 7 (white) — chalk, walkable | |
| 9 | 10 | THIEF equ %01001010 | |
| 10 | 11 | WALL_BIT equ 6 | |
| 11 | 12 | | |
| ... | |||
| 16 | 17 | KEYS_OP equ $DFFE | |
| 17 | 18 | KEYS_Q equ $FBFE | |
| 18 | 19 | KEYS_A equ $FDFE | |
| 20 | + | KEYS_SPACE equ $7FFE | |
| 19 | 21 | | |
| 20 | 22 | ; ---------------------------------------------------------------------------- | |
| 21 | - | ; SETUP. | |
| 23 | + | ; SETUP — copy the room templates into their RAM state buffers, then run. | |
| 22 | 24 | ; ---------------------------------------------------------------------------- | |
| 23 | 25 | start: | |
| 24 | 26 | ld a, 0 | |
| 25 | 27 | out ($FE), a | |
| 28 | + | | |
| 29 | + | ld hl, room0_template | |
| 30 | + | ld de, room0_state | |
| 31 | + | ld bc, 768 | |
| 32 | + | ldir | |
| 33 | + | ld hl, room1_template | |
| 34 | + | ld de, room1_state | |
| 35 | + | ld bc, 768 | |
| 36 | + | ldir | |
| 26 | 37 | | |
| 27 | 38 | xor a | |
| 28 | 39 | ld (current_room), a | |
| ... | |||
| 40 | 51 | .loop: | |
| 41 | 52 | halt | |
| 42 | 53 | call player_step | |
| 54 | + | call mark_step | |
| 43 | 55 | jr .loop | |
| 56 | + | | |
| 57 | + | ; ---------------------------------------------------------------------------- | |
| 58 | + | ; mark_step — while SPACE is held, chalk the cell the thief is on: write the | |
| 59 | + | ; mark glyph into the room's state buffer (so it persists and redraws), and | |
| 60 | + | ; set what's "under" him to the mark, so it shows the moment he steps off. | |
| 61 | + | ; ---------------------------------------------------------------------------- | |
| 62 | + | mark_step: | |
| 63 | + | ld bc, KEYS_SPACE | |
| 64 | + | in a, (c) | |
| 65 | + | bit 0, a | |
| 66 | + | ret nz ; SPACE not held | |
| 67 | + | | |
| 68 | + | call cell_state_addr ; HL -> this cell in the state buffer | |
| 69 | + | ld (hl), '+' | |
| 70 | + | | |
| 71 | + | ld hl, mark_tile ; remember the mark as what's under him | |
| 72 | + | ld de, under_thief | |
| 73 | + | ld bc, 8 | |
| 74 | + | ldir | |
| 75 | + | ld a, MARK_ATTR | |
| 76 | + | ld (under_thief + 8), a | |
| 77 | + | ret | |
| 78 | + | | |
| 79 | + | ; cell_state_addr — HL = current room's state buffer + thief_row*32 + thief_col. | |
| 80 | + | cell_state_addr: | |
| 81 | + | call room_entry_addr | |
| 82 | + | ld a, (hl) ; state buffer pointer (low) | |
| 83 | + | inc hl | |
| 84 | + | ld h, (hl) | |
| 85 | + | ld l, a ; HL = state base | |
| 86 | + | push hl | |
| 87 | + | ld a, (thief_row) | |
| 88 | + | ld l, a | |
| 89 | + | ld h, 0 | |
| 90 | + | add hl, hl | |
| 91 | + | add hl, hl | |
| 92 | + | add hl, hl | |
| 93 | + | add hl, hl | |
| 94 | + | add hl, hl ; HL = row * 32 | |
| 95 | + | ld a, (thief_col) | |
| 96 | + | ld e, a | |
| 97 | + | ld d, 0 | |
| 98 | + | add hl, de ; + col | |
| 99 | + | pop de | |
| 100 | + | add hl, de ; + base | |
| 101 | + | ret | |
| 44 | 102 | | |
| 103 | + | ; ---------------------------------------------------------------------------- | |
| 104 | + | ; room_entry_addr / draw_room — draw_room now paints from the state buffer the | |
| 105 | + | ; room table points at. | |
| 106 | + | ; ---------------------------------------------------------------------------- | |
| 45 | 107 | room_entry_addr: | |
| 46 | 108 | ld a, (current_room) | |
| 47 | 109 | ld l, a | |
| ... | |||
| 121 | 183 | pop bc | |
| 122 | 184 | ret | |
| 123 | 185 | | |
| 186 | + | ; ---------------------------------------------------------------------------- | |
| 187 | + | ; player_step / wall_at / check_exit — unchanged from Unit 6. | |
| 188 | + | ; ---------------------------------------------------------------------------- | |
| 124 | 189 | player_step: | |
| 125 | 190 | ld a, (thief_col) | |
| 126 | 191 | ld (tcol), a | |
| ... | |||
| 180 | 245 | bit WALL_BIT, (hl) | |
| 181 | 246 | ret | |
| 182 | 247 | | |
| 183 | - | ; ---------------------------------------------------------------------------- | |
| 184 | - | ; check_exit — on an edge, follow that edge's link AND set the entry position | |
| 185 | - | ; to the opposite edge at the same height: a step through the doorway, not a | |
| 186 | - | ; jump to the middle. He arrives one cell inside the edge so he doesn't sit on | |
| 187 | - | ; the doorway and bounce straight back. | |
| 188 | - | ; ---------------------------------------------------------------------------- | |
| 189 | 248 | check_exit: | |
| 190 | 249 | ld a, (thief_col) | |
| 191 | 250 | or a | |
| ... | |||
| 198 | 257 | cp 23 | |
| 199 | 258 | jr z, .south | |
| 200 | 259 | ret | |
| 201 | - | .east: ; left by the east edge -> enter at the west | |
| 260 | + | .east: | |
| 202 | 261 | call room_entry_addr | |
| 203 | 262 | ld de, 4 | |
| 204 | 263 | add hl, de | |
| ... | |||
| 206 | 265 | cp NO_EXIT | |
| 207 | 266 | ret z | |
| 208 | 267 | ld (current_room), a | |
| 209 | - | ld a, 1 ; west side, one cell in; row unchanged | |
| 268 | + | ld a, 1 | |
| 210 | 269 | ld (thief_col), a | |
| 211 | 270 | jr .enter | |
| 212 | - | .west: ; left by the west edge -> enter at the east | |
| 271 | + | .west: | |
| 213 | 272 | call room_entry_addr | |
| 214 | 273 | ld de, 5 | |
| 215 | 274 | add hl, de | |
| ... | |||
| 217 | 276 | cp NO_EXIT | |
| 218 | 277 | ret z | |
| 219 | 278 | ld (current_room), a | |
| 220 | - | ld a, 30 ; east side, one cell in; row unchanged | |
| 279 | + | ld a, 30 | |
| 221 | 280 | ld (thief_col), a | |
| 222 | 281 | jr .enter | |
| 223 | - | .north: ; left by the north edge -> enter at the south | |
| 282 | + | .north: | |
| 224 | 283 | call room_entry_addr | |
| 225 | 284 | inc hl | |
| 226 | 285 | inc hl | |
| ... | |||
| 228 | 287 | cp NO_EXIT | |
| 229 | 288 | ret z | |
| 230 | 289 | ld (current_room), a | |
| 231 | - | ld a, 22 ; bottom, one cell in; column unchanged | |
| 290 | + | ld a, 22 | |
| 232 | 291 | ld (thief_row), a | |
| 233 | 292 | jr .enter | |
| 234 | - | .south: ; left by the south edge -> enter at the north | |
| 293 | + | .south: | |
| 235 | 294 | call room_entry_addr | |
| 236 | 295 | inc hl | |
| 237 | 296 | inc hl | |
| ... | |||
| 240 | 299 | cp NO_EXIT | |
| 241 | 300 | ret z | |
| 242 | 301 | ld (current_room), a | |
| 243 | - | ld a, 1 ; top, one cell in; column unchanged | |
| 302 | + | ld a, 1 | |
| 244 | 303 | ld (thief_row), a | |
| 245 | 304 | .enter: | |
| 246 | 305 | call draw_room | |
| ... | |||
| 248 | 307 | call draw_thief | |
| 249 | 308 | ret | |
| 250 | 309 | | |
| 251 | - | ; ---------------------------------------------------------------------------- | |
| 252 | - | ; Save / restore / draw the thief — unchanged. | |
| 253 | - | ; ---------------------------------------------------------------------------- | |
| 254 | 310 | pos_bc: | |
| 255 | 311 | ld a, (thief_row) | |
| 256 | 312 | ld b, a | |
| ... | |||
| 340 | 396 | ret | |
| 341 | 397 | | |
| 342 | 398 | ; ---------------------------------------------------------------------------- | |
| 343 | - | ; Palette and the room graph (unchanged from Unit 5 — doors aligned at row 11). | |
| 399 | + | ; Palette — now with a chalk mark. The mark is dim, so it stays walkable. | |
| 344 | 400 | ; ---------------------------------------------------------------------------- | |
| 345 | 401 | palette: | |
| 346 | 402 | defb '.' | |
| ... | |||
| 349 | 405 | defb '#' | |
| 350 | 406 | defw wall_tile | |
| 351 | 407 | defb WALL_ATTR | |
| 408 | + | defb '+' | |
| 409 | + | defw mark_tile | |
| 410 | + | defb MARK_ATTR | |
| 352 | 411 | | |
| 412 | + | ; ---------------------------------------------------------------------------- | |
| 413 | + | ; The room graph — pointers now name the RAM STATE buffers, not the templates. | |
| 414 | + | ; ---------------------------------------------------------------------------- | |
| 353 | 415 | rooms: | |
| 354 | - | defw room0_data | |
| 416 | + | defw room0_state | |
| 355 | 417 | defb NO_EXIT, NO_EXIT, 1, NO_EXIT | |
| 356 | - | defw room1_data | |
| 418 | + | defw room1_state | |
| 357 | 419 | defb NO_EXIT, NO_EXIT, NO_EXIT, 0 | |
| 358 | 420 | | |
| 359 | - | room0_data: | |
| 421 | + | room0_template: | |
| 360 | 422 | defb "################################" | |
| 361 | 423 | defb "#..............................#" | |
| 362 | 424 | defb "#..............................#" | |
| ... | |||
| 382 | 444 | defb "#..............................#" | |
| 383 | 445 | defb "################################" | |
| 384 | 446 | | |
| 385 | - | room1_data: | |
| 447 | + | room1_template: | |
| 386 | 448 | defb "################################" | |
| 387 | 449 | defb "#..............................#" | |
| 388 | 450 | defb "#..............................#" | |
| ... | |||
| 426 | 488 | defb %00010001 | |
| 427 | 489 | defb %00000000 | |
| 428 | 490 | defb %01000100 | |
| 491 | + | defb %00000000 | |
| 492 | + | | |
| 493 | + | mark_tile: | |
| 494 | + | defb %00000000 | |
| 495 | + | defb %00011000 | |
| 496 | + | defb %00011000 | |
| 497 | + | defb %01111110 | |
| 498 | + | defb %01111110 | |
| 499 | + | defb %00011000 | |
| 500 | + | defb %00011000 | |
| 429 | 501 | defb %00000000 | |
| 430 | 502 | | |
| 431 | 503 | thief: | |
| ... | |||
| 438 | 510 | defb %00111100 | |
| 439 | 511 | defb %00100100 | |
| 440 | 512 | | |
| 513 | + | ; ---------------------------------------------------------------------------- | |
| 514 | + | ; Variables, then the room state buffers (RAM copies of the templates). | |
| 515 | + | ; ---------------------------------------------------------------------------- | |
| 441 | 516 | current_room: | |
| 442 | 517 | defb 0 | |
| 443 | 518 | thief_col: | |
| ... | |||
| 456 | 531 | defb 0 | |
| 457 | 532 | under_thief: | |
| 458 | 533 | defb 0, 0, 0, 0, 0, 0, 0, 0, 0 | |
| 534 | + | | |
| 535 | + | room0_state: | |
| 536 | + | defs 768 | |
| 537 | + | room1_state: | |
| 538 | + | defs 768 | |
| 459 | 539 | | |
| 460 | 540 | end start | |
| 461 | 541 | |
The complete program
; Shadowkeep — Unit 7: The Hero Remembers
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 copies rooms to RAM and chalks a trail that survives leaving and returning.
org 32768
WALL_ATTR equ %01001000
FLOOR_ATTR equ %00001000
MARK_ATTR equ %00001111 ; dim, PAPER 1 (blue), INK 7 (white) — chalk, walkable
THIEF equ %01001010
WALL_BIT equ 6
START_COL equ 15
START_ROW equ 11
NO_EXIT equ $FF
KEYS_OP equ $DFFE
KEYS_Q equ $FBFE
KEYS_A equ $FDFE
KEYS_SPACE equ $7FFE
; ----------------------------------------------------------------------------
; SETUP — copy the room templates into their RAM state buffers, then run.
; ----------------------------------------------------------------------------
start:
ld a, 0
out ($FE), a
ld hl, room0_template
ld de, room0_state
ld bc, 768
ldir
ld hl, room1_template
ld de, room1_state
ld bc, 768
ldir
xor a
ld (current_room), a
ld a, START_COL
ld (thief_col), a
ld a, START_ROW
ld (thief_row), a
call draw_room
call save_under
call draw_thief
im 1
ei
.loop:
halt
call player_step
call mark_step
jr .loop
; ----------------------------------------------------------------------------
; mark_step — while SPACE is held, chalk the cell the thief is on: write the
; mark glyph into the room's state buffer (so it persists and redraws), and
; set what's "under" him to the mark, so it shows the moment he steps off.
; ----------------------------------------------------------------------------
mark_step:
ld bc, KEYS_SPACE
in a, (c)
bit 0, a
ret nz ; SPACE not held
call cell_state_addr ; HL -> this cell in the state buffer
ld (hl), '+'
ld hl, mark_tile ; remember the mark as what's under him
ld de, under_thief
ld bc, 8
ldir
ld a, MARK_ATTR
ld (under_thief + 8), a
ret
; cell_state_addr — HL = current room's state buffer + thief_row*32 + thief_col.
cell_state_addr:
call room_entry_addr
ld a, (hl) ; state buffer pointer (low)
inc hl
ld h, (hl)
ld l, a ; HL = state base
push hl
ld a, (thief_row)
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl ; HL = row * 32
ld a, (thief_col)
ld e, a
ld d, 0
add hl, de ; + col
pop de
add hl, de ; + base
ret
; ----------------------------------------------------------------------------
; room_entry_addr / draw_room — draw_room now paints from the state buffer the
; room table points at.
; ----------------------------------------------------------------------------
room_entry_addr:
ld a, (current_room)
ld l, a
ld h, 0
ld d, h
ld e, l
add hl, hl
add hl, de
add hl, hl
ld de, rooms
add hl, de
ret
draw_room:
call room_entry_addr
ld a, (hl)
inc hl
ld h, (hl)
ld l, a
ld (map_ptr), hl
ld b, 0
.room_row:
ld c, 0
.room_col:
ld hl, (map_ptr)
ld a, (hl)
call lookup_tile
call draw_tile
ld hl, (map_ptr)
inc hl
ld (map_ptr), hl
inc c
ld a, c
cp 32
jr nz, .room_col
inc b
ld a, b
cp 24
jr nz, .room_row
ret
lookup_tile:
ld hl, palette
.scan:
cp (hl)
jr z, .found
inc hl
inc hl
inc hl
inc hl
jr .scan
.found:
inc hl
ld e, (hl)
inc hl
ld d, (hl)
ld (tile_ptr), de
inc hl
ld a, (hl)
ld (tile_attr), a
ret
draw_tile:
push bc
call attr_addr_cr
ld a, (tile_attr)
ld (hl), a
call scr_addr_cr
ld de, (tile_ptr)
ld b, 8
.tile_row:
ld a, (de)
ld (hl), a
inc de
inc h
djnz .tile_row
pop bc
ret
; ----------------------------------------------------------------------------
; player_step / wall_at / check_exit — unchanged from Unit 6.
; ----------------------------------------------------------------------------
player_step:
ld a, (thief_col)
ld (tcol), a
ld a, (thief_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
ret
.left:
ld hl, tcol
dec (hl)
jr .move
.right:
ld hl, tcol
inc (hl)
jr .move
.up:
ld hl, trow
dec (hl)
jr .move
.down:
ld hl, trow
inc (hl)
.move:
ld a, (trow)
ld b, a
ld a, (tcol)
ld c, a
call wall_at
ret nz
call restore_under
ld a, (tcol)
ld (thief_col), a
ld a, (trow)
ld (thief_row), a
call save_under
call draw_thief
call check_exit
ret
wall_at:
call attr_addr_cr
bit WALL_BIT, (hl)
ret
check_exit:
ld a, (thief_col)
or a
jr z, .west
cp 31
jr z, .east
ld a, (thief_row)
or a
jr z, .north
cp 23
jr z, .south
ret
.east:
call room_entry_addr
ld de, 4
add hl, de
ld a, (hl)
cp NO_EXIT
ret z
ld (current_room), a
ld a, 1
ld (thief_col), a
jr .enter
.west:
call room_entry_addr
ld de, 5
add hl, de
ld a, (hl)
cp NO_EXIT
ret z
ld (current_room), a
ld a, 30
ld (thief_col), a
jr .enter
.north:
call room_entry_addr
inc hl
inc hl
ld a, (hl)
cp NO_EXIT
ret z
ld (current_room), a
ld a, 22
ld (thief_row), a
jr .enter
.south:
call room_entry_addr
inc hl
inc hl
inc hl
ld a, (hl)
cp NO_EXIT
ret z
ld (current_room), a
ld a, 1
ld (thief_row), a
.enter:
call draw_room
call save_under
call draw_thief
ret
pos_bc:
ld a, (thief_row)
ld b, a
ld a, (thief_col)
ld c, a
ret
save_under:
call pos_bc
call scr_addr_cr
ld de, under_thief
ld b, 8
.save_row:
ld a, (hl)
ld (de), a
inc de
inc h
djnz .save_row
call pos_bc
call attr_addr_cr
ld a, (hl)
ld (under_thief + 8), a
ret
restore_under:
call pos_bc
call scr_addr_cr
ld de, under_thief
ld b, 8
.restore_row:
ld a, (de)
ld (hl), a
inc de
inc h
djnz .restore_row
call pos_bc
call attr_addr_cr
ld a, (under_thief + 8)
ld (hl), a
ret
draw_thief:
call pos_bc
call attr_addr_cr
ld (hl), THIEF
call pos_bc
call scr_addr_cr
ld de, thief
ld b, 8
.thief_row:
ld a, (de)
ld (hl), a
inc de
inc h
djnz .thief_row
ret
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
; ----------------------------------------------------------------------------
; Palette — now with a chalk mark. The mark is dim, so it stays walkable.
; ----------------------------------------------------------------------------
palette:
defb '.'
defw floor_tile
defb FLOOR_ATTR
defb '#'
defw wall_tile
defb WALL_ATTR
defb '+'
defw mark_tile
defb MARK_ATTR
; ----------------------------------------------------------------------------
; The room graph — pointers now name the RAM STATE buffers, not the templates.
; ----------------------------------------------------------------------------
rooms:
defw room0_state
defb NO_EXIT, NO_EXIT, 1, NO_EXIT
defw room1_state
defb NO_EXIT, NO_EXIT, NO_EXIT, 0
room0_template:
defb "################################"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#........##..........##........#"
defb "#........##..........##........#"
defb "#..............................#"
defb "#..............................."
defb "#..............................#"
defb "#..............................#"
defb "#........##..........##........#"
defb "#........##..........##........#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "################################"
room1_template:
defb "################################"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............##..............#"
defb "#..............##..............#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "...............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "################################"
floor_tile:
defb %10101010
defb %01010101
defb %10101010
defb %01010101
defb %10101010
defb %01010101
defb %10101010
defb %01010101
wall_tile:
defb %00010001
defb %00000000
defb %01000100
defb %00000000
defb %00010001
defb %00000000
defb %01000100
defb %00000000
mark_tile:
defb %00000000
defb %00011000
defb %00011000
defb %01111110
defb %01111110
defb %00011000
defb %00011000
defb %00000000
thief:
defb %00011000
defb %00111100
defb %01111110
defb %01111110
defb %01111110
defb %01111110
defb %00111100
defb %00100100
; ----------------------------------------------------------------------------
; Variables, then the room state buffers (RAM copies of the templates).
; ----------------------------------------------------------------------------
current_room:
defb 0
thief_col:
defb START_COL
thief_row:
defb START_ROW
tcol:
defb 0
trow:
defb 0
map_ptr:
defw 0
tile_ptr:
defw 0
tile_attr:
defb 0
under_thief:
defb 0, 0, 0, 0, 0, 0, 0, 0, 0
room0_state:
defs 768
room1_state:
defs 768
end start
Hold SPACE and walk Q A O P to chalk a trail. Walk it out through the east door, turn around, walk back — and the marks are waiting exactly where you left them. The keep remembered:
Try this: the honest cost
Two rooms cost two 768-byte buffers — 1,536 bytes of RAM just for state. Work out what a hundred-room keep would cost that way (it's most of the machine). This is the honest version: a full mutable copy per room, plain and clear. A bigger keep needs the state packed far tighter — a bitmap of "marked or not", or a short list of changed cells. Worth sketching how you'd shrink it; it's a real problem real games solved.
Try this: an eraser
Add a key (say M) that writes . back into the cell instead of +, scrubbing the chalk. You'll have proved the buffer is genuinely read-write, both ways — and given the thief second thoughts about his route.
Try this: remember something else
The chalk is only the first thing the keep remembers. Anything you write into a room's state survives the same way. Imagine a cell the thief can switch from wall to floor — a lever that opens a passage, still open when you return. You wouldn't change the engine at all; you'd just write a different glyph. (That's a door, and a later keep is built on it.)
When it's wrong, see why
- Marks vanish when you leave and return. You're writing the mark to the screen but not into the state buffer, or the room table still points at the templates instead of the state buffers. The buffer is the memory; the screen is only its reflection.
- The thief can't walk over his own marks.
MARK_ATTRis bright (bit 6 set), sowall_atthinks chalk is wall. Make it dim. - Marks never appear.
under_thiefisn't being set to the mark, so when he steps off, the original floor is restored over it. Chalk the cell and update what's under him. - The whole keep is corrupted at start. The
LDIRcopies are wrong — each isHL= template,DE= state buffer,BC= 768. Swap source and destination and you copy blank RAM over nothing. - The machine resets on start-up. The state buffers (
defs 768each) must fit in RAM below the stack. They sit after the variables here; if you move them, mind the room left.
Before and after
You started with a keep that forgot — every room repainted from a fixed template, every chalk mark wiped the moment you crossed a threshold. You finished with a keep that holds onto what you did: rooms copied into RAM at start-up, a chalk glyph written straight into a room's state, and the marks surviving a walk to the next room and back. The change was small — a copy at boot, a write on SPACE, and pointing the room table at the buffers — but it's the difference between a set of pictures and a place with a history. Persistence cost no new machinery; it cost you stopping discarding the world each time you drew it.
What you've learnt
- A world that changes must live in RAM. Templates are read-only; copy them into writable state buffers and the keep becomes something you can alter.
- Persistence is the absence of overwriting. Once each room's state lives in its own buffer and you draw from it, anything you write there stays. You don't add memory; you stop discarding it.
- A mark is a byte. Chalk, a lever, an opened door — all the same move: write a glyph into the room's state. The palette turns the byte back into a picture.
- The plain version has a cost. A full copy per room is clear but heavy; knowing where it won't scale is the first step to the version that does.
What's next
The keep remembers, the rooms connect, the thief walks and marks his way through them — the machinery of a flick-screen world is complete. It's time to use it. In Unit 8, "Three Rooms," we leave the test chambers behind and design a small, real keep — three connected rooms with character, laid out to be explored — closing the sub-arc with a place worth getting lost in, ready for the light, mood and inhabitants still to come.