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

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.

44% of Shadowkeep

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

The pillared hall with a line of white chalk-cross marks across its floor, the red thief standing at the end of the trail he laid.
A trail of white chalk crosses laid across the slate, the thief at its end. He'll walk this out through the east door and back — and find it exactly where he left it.

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.

Step 1: rooms in RAM, and a chalk mark that's a byte in the buffer
+104-24
11 ; Shadowkeep — Unit 7: The Hero Remembers
22 ; 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.
44
55 org 32768
66
77 WALL_ATTR equ %01001000
88 FLOOR_ATTR equ %00001000
9+MARK_ATTR equ %00001111 ; dim, PAPER 1 (blue), INK 7 (white) — chalk, walkable
910 THIEF equ %01001010
1011 WALL_BIT equ 6
1112
...
1617 KEYS_OP equ $DFFE
1718 KEYS_Q equ $FBFE
1819 KEYS_A equ $FDFE
20+KEYS_SPACE equ $7FFE
1921
2022 ; ----------------------------------------------------------------------------
21-; SETUP.
23+; SETUP — copy the room templates into their RAM state buffers, then run.
2224 ; ----------------------------------------------------------------------------
2325 start:
2426 ld a, 0
2527 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
2637
2738 xor a
2839 ld (current_room), a
...
4051 .loop:
4152 halt
4253 call player_step
54+ call mark_step
4355 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
44102
103+; ----------------------------------------------------------------------------
104+; room_entry_addr / draw_room — draw_room now paints from the state buffer the
105+; room table points at.
106+; ----------------------------------------------------------------------------
45107 room_entry_addr:
46108 ld a, (current_room)
47109 ld l, a
...
121183 pop bc
122184 ret
123185
186+; ----------------------------------------------------------------------------
187+; player_step / wall_at / check_exit — unchanged from Unit 6.
188+; ----------------------------------------------------------------------------
124189 player_step:
125190 ld a, (thief_col)
126191 ld (tcol), a
...
180245 bit WALL_BIT, (hl)
181246 ret
182247
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-; ----------------------------------------------------------------------------
189248 check_exit:
190249 ld a, (thief_col)
191250 or a
...
198257 cp 23
199258 jr z, .south
200259 ret
201-.east: ; left by the east edge -> enter at the west
260+.east:
202261 call room_entry_addr
203262 ld de, 4
204263 add hl, de
...
206265 cp NO_EXIT
207266 ret z
208267 ld (current_room), a
209- ld a, 1 ; west side, one cell in; row unchanged
268+ ld a, 1
210269 ld (thief_col), a
211270 jr .enter
212-.west: ; left by the west edge -> enter at the east
271+.west:
213272 call room_entry_addr
214273 ld de, 5
215274 add hl, de
...
217276 cp NO_EXIT
218277 ret z
219278 ld (current_room), a
220- ld a, 30 ; east side, one cell in; row unchanged
279+ ld a, 30
221280 ld (thief_col), a
222281 jr .enter
223-.north: ; left by the north edge -> enter at the south
282+.north:
224283 call room_entry_addr
225284 inc hl
226285 inc hl
...
228287 cp NO_EXIT
229288 ret z
230289 ld (current_room), a
231- ld a, 22 ; bottom, one cell in; column unchanged
290+ ld a, 22
232291 ld (thief_row), a
233292 jr .enter
234-.south: ; left by the south edge -> enter at the north
293+.south:
235294 call room_entry_addr
236295 inc hl
237296 inc hl
...
240299 cp NO_EXIT
241300 ret z
242301 ld (current_room), a
243- ld a, 1 ; top, one cell in; column unchanged
302+ ld a, 1
244303 ld (thief_row), a
245304 .enter:
246305 call draw_room
...
248307 call draw_thief
249308 ret
250309
251-; ----------------------------------------------------------------------------
252-; Save / restore / draw the thief — unchanged.
253-; ----------------------------------------------------------------------------
254310 pos_bc:
255311 ld a, (thief_row)
256312 ld b, a
...
340396 ret
341397
342398 ; ----------------------------------------------------------------------------
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.
344400 ; ----------------------------------------------------------------------------
345401 palette:
346402 defb '.'
...
349405 defb '#'
350406 defw wall_tile
351407 defb WALL_ATTR
408+ defb '+'
409+ defw mark_tile
410+ defb MARK_ATTR
352411
412+; ----------------------------------------------------------------------------
413+; The room graph — pointers now name the RAM STATE buffers, not the templates.
414+; ----------------------------------------------------------------------------
353415 rooms:
354- defw room0_data
416+ defw room0_state
355417 defb NO_EXIT, NO_EXIT, 1, NO_EXIT
356- defw room1_data
418+ defw room1_state
357419 defb NO_EXIT, NO_EXIT, NO_EXIT, 0
358420
359-room0_data:
421+room0_template:
360422 defb "################################"
361423 defb "#..............................#"
362424 defb "#..............................#"
...
382444 defb "#..............................#"
383445 defb "################################"
384446
385-room1_data:
447+room1_template:
386448 defb "################################"
387449 defb "#..............................#"
388450 defb "#..............................#"
...
426488 defb %00010001
427489 defb %00000000
428490 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
429501 defb %00000000
430502
431503 thief:
...
438510 defb %00111100
439511 defb %00100100
440512
513+; ----------------------------------------------------------------------------
514+; Variables, then the room state buffers (RAM copies of the templates).
515+; ----------------------------------------------------------------------------
441516 current_room:
442517 defb 0
443518 thief_col:
...
456531 defb 0
457532 under_thief:
458533 defb 0, 0, 0, 0, 0, 0, 0, 0, 0
534+
535+room0_state:
536+ defs 768
537+room1_state:
538+ defs 768
459539
460540 end start
461541
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:

A chalk trail laid in the hall, then east through the doorway to the next room and back. Room 0's marks never moved — they sat in its state buffer while room 1 was on screen, and painted straight back when the thief returned. Persistence is the absence of overwriting.

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_ATTR is bright (bit 6 set), so wall_at thinks chalk is wall. Make it dim.
  • Marks never appear. under_thief isn'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 LDIR copies are wrong — each is HL = 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 768 each) 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.