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

Furnishings

A lit room is still a bare room. Add the things light falls on — a statue, banners, a scatter of rubble — as tiles in the palette. Bright ones you walk around, dim ones you walk over, and the keep starts to feel inhabited.

63% of Shadowkeep

The keep has light and shadow now, but its rooms are empty — stone, pillars, floor, and nothing to look at. A place you believe in has things in it: an altar, a statue, banners on the wall, a heap of rubble where the ceiling came down. This unit furnishes the keep — and it needs almost no new code, because furniture is just more tiles in the palette, placed in the map like everything else.

There's one idea worth naming, and the engine already handles it: the difference between furniture you walk around and furniture you walk over.

What you'll see by the end

The Hall, torch-lit, with a pale stone statue standing in the light below the flame, magenta banners hung on the side walls, and a scatter of rubble at the statue's feet; the hooded thief stands in the centre among the pillars.
The same lit Hall as last unit, now inhabited: a statue in the torchlight, banners on the side walls, rubble at the statue's feet. Walk up to the statue and you stop; walk onto the rubble and you stride over it. Two kinds of furniture, sorted by one rule the keep already had.

The same lit Hall as last unit, but inhabited: a statue stands in the torchlight, banners hang on the walls, broken stone litters the floor at the statue's base. Walk up to the statue and you stop — it's solid. Walk onto the rubble and you stride right over it, and it's still there behind you. Two kinds of furniture, and the keep didn't need a single new rule to tell them apart.

Blocking is just brightness

Remember how collision works: a cell is solid if it's BRIGHT, walkable if it's dim. That one rule, from way back, sorts the furniture for free:

STATUE_ATTR equ     %01001111       ; BRIGHT white  — solid: walk around it
BANNER_ATTR equ     %01001011       ; BRIGHT magenta — solid: it's on the wall
RUBBLE_ATTR equ     %00001000       ; dim (like floor) — walkable: walk over it

A statue is a wall that happens to look like a statue. Rubble is a floor that happens to look like broken stone. wall_at never knew the difference and never needs to — it just reads the brightness, exactly as it did for stone walls and slate floor. The furniture sorts itself.

Furniture is glyphs in the map

Each piece is a palette entry — a glyph, a tile, an attribute — and you place it by typing its letter into the room:

            defb    "B..............S...............B"   ; banners flanking a statue
            defb    "#.............ooo..............#"   ; rubble at its feet

S a statue, B a banner, o rubble. The Hall now reads as a shrine: a figure in the light, hangings to either side, the floor strewn where time has worked on the stone. None of it is special-cased; it's the same draw_room, the same lookup_tile, three more rows in the palette.

Walked-over, and remembered

The rubble is the interesting one, because it's non-blocking — the thief walks over it. And here the work you did earlier pays off twice. His save-and-restore carries the rubble beneath him just as it carried slate and chalk, so he crosses it without smearing it. And because rubble is a glyph in the room's state buffer, it persists: leave the Hall and come back, and the broken stone is exactly where it lay. Furniture is part of the keep's memory, for free.

(One honesty: furniture is drawn flat — it isn't dimmed by distance to the torch the way the floor is. A statue in the dark is as bright as one in the light. Lighting the furniture too is a fine exercise, and the next unit leans hard on shade; for now, objects keep their own look.)

Milestone — furnish the keep

No new engine: furniture is palette entries placed in the maps. A statue and banners take a bright attribute, so collision already treats them as solid; rubble takes the dim floor attribute, so it stays walkable. draw_room, lookup_tile, collision and the state buffers are all unchanged — the diff is three palette lines and some letters typed into the templates.

Step 1: statue, banner and rubble tiles — bright blocks, dim doesn't
+66-39
11 ; Shadowkeep — Unit 10: Furnishings
22 ; Cumulative build; every step runs on its own. Narrative: the unit page.
3-; step-00 = Unit 9's end: lit rooms, but bare.
3+; step-01 dresses the rooms with furniture — bright pieces block, dim ones you walk over.
44
55 org 32768
66
77 WALL_ATTR equ %01001000
8-FLOOR_ATTR equ %00001000 ; the shade ramp all share this (PAPER 1, INK 0); only the pattern differs
9-TORCH_ATTR equ %01001110 ; BRIGHT, PAPER 1 (blue), INK 6 (yellow) — a lit sconce (solid)
8+FLOOR_ATTR equ %00001000
9+TORCH_ATTR equ %01001110
10+STATUE_ATTR equ %01001111 ; BRIGHT white on blue — pale stone (solid)
11+BANNER_ATTR equ %01001011 ; BRIGHT magenta on blue — a hanging (solid)
12+RUBBLE_ATTR equ %00001000 ; dim, like floor — walkable broken stone
1013 MARK_ATTR equ %00001111
1114 THIEF equ %01001010
1215 WALL_BIT equ 6
...
109112 add hl, de
110113 ret
111114
112-; ----------------------------------------------------------------------------
113-; find_torch — scan the current room's map for 'T', remember where it is (or
114-; NO_TORCH if the room is unlit).
115-; ----------------------------------------------------------------------------
116115 find_torch:
117116 ld a, NO_TORCH
118117 ld (torch_col), a
...
145144 jr nz, .ft_row
146145 ret
147146
148-; ----------------------------------------------------------------------------
149-; shade_for_cell — row in B, column in C. Returns a shade 0..4 in A: the
150-; Chebyshev distance to the torch, halved and clamped. Near the flame = 0
151-; (lightest); far away = 4 (darkest). No torch = darkest everywhere.
152-; ----------------------------------------------------------------------------
153147 shade_for_cell:
154148 ld a, (torch_col)
155149 cp NO_TORCH
...
160154 jr nc, .sf_rpos
161155 neg
162156 .sf_rpos:
163- ld d, a ; |row - torch_row|
157+ ld d, a
164158 ld a, c
165159 ld hl, torch_col
166160 sub (hl)
167161 jr nc, .sf_cpos
168162 neg
169163 .sf_cpos:
170- cp d ; A = |dc|; max(|dc|, |dr|)
164+ cp d
171165 jr nc, .sf_max
172166 ld a, d
173167 .sf_max:
174- srl a ; distance / 2
168+ srl a
175169 cp MAX_SHADE + 1
176170 jr c, .sf_done
177171 ld a, MAX_SHADE
...
181175 ld a, MAX_SHADE
182176 ret
183177
184-; ----------------------------------------------------------------------------
185-; draw_room — now lights as it draws. A floor cell ('.') is shaded by distance
186-; to the torch; everything else (wall, torch, chalk) is drawn flat.
187-; ----------------------------------------------------------------------------
188178 draw_room:
189179 call find_torch
190180 call room_entry_addr
...
201191 ld a, (hl)
202192 cp '.'
203193 jr nz, .not_floor
204-
205- call shade_for_cell ; A = shade 0..4
206- add a, a ; index the pointer table
194+ call shade_for_cell
195+ add a, a
207196 ld e, a
208197 ld d, 0
209198 ld hl, shade_tiles
...
481470 ret
482471
483472 ; ----------------------------------------------------------------------------
484-; Palette — '.' is handled by the lighting path, so its entry here is only a
485-; fallback; '#', 'T' and '+' are looked up as normal.
473+; Palette — now furnished. 'S' statue and 'B' banner are bright (solid); 'o'
474+; rubble is dim (walkable). '.' is still handled by the lighting path.
486475 ; ----------------------------------------------------------------------------
487476 palette:
488477 defb '.'
...
494483 defb 'T'
495484 defw torch_tile
496485 defb TORCH_ATTR
486+ defb 'S'
487+ defw statue_tile
488+ defb STATUE_ATTR
489+ defb 'B'
490+ defw banner_tile
491+ defb BANNER_ATTR
492+ defb 'o'
493+ defw rubble_tile
494+ defb RUBBLE_ATTR
497495 defb '+'
498496 defw mark_tile
499497 defb MARK_ATTR
500498
501-; The shade ramp: lightest (lit) to darkest (deep shadow), all blue/black dither.
502499 shade_tiles:
503500 defw shade0_tile
504501 defw shade1_tile
...
514511 defw room2_state
515512 defb NO_EXIT, 1, NO_EXIT, NO_EXIT
516513
517-; The Great Hall — a torch ('T') set in the top wall, column 15.
514+; The Great Hall — torch above, banners flanking a statue, rubble at its feet.
518515 room0_template:
519516 defb "###############T################"
520- defb "#..............................#"
521- defb "#..............................#"
522517 defb "#..............................#"
523518 defb "#..............................#"
524519 defb "#..............................#"
525520 defb "#..............................#"
521+ defb "B..............S...............B"
522+ defb "#.............ooo..............#"
526523 defb "#..............................#"
527524 defb "#........##..........##........#"
528525 defb "#........##..........##........#"
...
541538 defb "#..............................#"
542539 defb "################################"
543540
544-; The Gallery — a torch low on the south wall.
541+; The Gallery — a banner on the west wall, a heap of rubble in the corner.
545542 room1_template:
546543 defb "###############.################"
547544 defb "#..............................#"
...
555552 defb "#..............................#"
556553 defb "#..............................#"
557554 defb "...............................#"
558- defb "#..............................#"
559- defb "#..............................#"
560555 defb "#..............................#"
561556 defb "#..............................#"
557+ defb "B..............................#"
562558 defb "#..............................#"
563559 defb "#..............................#"
564560 defb "#..............................#"
561+ defb "#........................ooo...#"
565562 defb "#..............................#"
566563 defb "#..............................#"
567564 defb "#..............................#"
568565 defb "#..............................#"
569566 defb "###############T################"
570567
571-; The Vault — a torch in the top wall above the altar.
568+; The Vault — banners flanking the great altar.
572569 room2_template:
573570 defb "###############T################"
574571 defb "#..............................#"
...
580577 defb "#..............................#"
581578 defb "#..............................#"
582579 defb "#..............................#"
583- defb "#.............####.............#"
584- defb "#.............####.............#"
585580 defb "#.............####.............#"
581+ defb "B.............####.............#"
582+ defb "#.............####.............B"
586583 defb "#.............####.............#"
587584 defb "#..............................#"
588585 defb "#..............................#"
...
596593 defb "###############.################"
597594
598595 ; ----------------------------------------------------------------------------
599-; The five shades of floor — same blue/black, denser and darker each step.
596+; Floor shade ramp.
600597 ; ----------------------------------------------------------------------------
601-shade0_tile: ; lit — nearly all blue
598+shade0_tile:
602599 defb %00000000
603600 defb %00100010
604601 defb %00000000
...
616613 defb %00000000
617614 defb %10001000
618615 defb %00000000
619-shade2_tile: ; the half-and-half slate from Unit 2
616+shade2_tile:
620617 defb %10101010
621618 defb %01010101
622619 defb %10101010
...
634631 defb %11111111
635632 defb %01010101
636633 defb %11111111
637-shade4_tile: ; deep shadow — nearly all black
634+shade4_tile:
638635 defb %11111111
639636 defb %11101110
640637 defb %11111111
...
654651 defb %01000100
655652 defb %00000000
656653
657-torch_tile: ; a flame in its sconce
654+torch_tile:
658655 defb %00010000
659656 defb %00111000
660657 defb %00111000
...
663660 defb %01111100
664661 defb %00111000
665662 defb %00010000
663+
664+statue_tile:
665+ defb %00111100
666+ defb %01111110
667+ defb %00111100
668+ defb %00011000
669+ defb %00011000
670+ defb %00111100
671+ defb %01111110
672+ defb %01111110
673+
674+banner_tile:
675+ defb %01111110
676+ defb %01111110
677+ defb %01011010
678+ defb %01011010
679+ defb %01111110
680+ defb %01111110
681+ defb %00111100
682+ defb %00011000
683+
684+rubble_tile:
685+ defb %00000000
686+ defb %01100000
687+ defb %01100000
688+ defb %00000110
689+ defb %00000110
690+ defb %00011000
691+ defb %00011000
692+ defb %00000000
666693
667694 mark_tile:
668695 defb %00000000
The complete program
; Shadowkeep — Unit 10: Furnishings
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 dresses the rooms with furniture — bright pieces block, dim ones you walk over.

            org     32768

WALL_ATTR   equ     %01001000
FLOOR_ATTR  equ     %00001000
TORCH_ATTR  equ     %01001110
STATUE_ATTR equ     %01001111       ; BRIGHT white on blue — pale stone (solid)
BANNER_ATTR equ     %01001011       ; BRIGHT magenta on blue — a hanging (solid)
RUBBLE_ATTR equ     %00001000       ; dim, like floor — walkable broken stone
MARK_ATTR   equ     %00001111
THIEF       equ     %01001010
WALL_BIT    equ     6

START_COL   equ     15
START_ROW   equ     11
NO_EXIT     equ     $FF
NO_TORCH    equ     $FF
MAX_SHADE   equ     4

KEYS_OP     equ     $DFFE
KEYS_Q      equ     $FBFE
KEYS_A      equ     $FDFE
KEYS_SPACE  equ     $7FFE

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
            ld      hl, room2_template
            ld      de, room2_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:
            ld      bc, KEYS_SPACE
            in      a, (c)
            bit     0, a
            ret     nz
            call    cell_state_addr
            ld      (hl), '+'
            ld      hl, mark_tile
            ld      de, under_thief
            ld      bc, 8
            ldir
            ld      a, MARK_ATTR
            ld      (under_thief + 8), a
            ret

cell_state_addr:
            call    room_entry_addr
            ld      a, (hl)
            inc     hl
            ld      h, (hl)
            ld      l, a
            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
            ld      a, (thief_col)
            ld      e, a
            ld      d, 0
            add     hl, de
            pop     de
            add     hl, de
            ret

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

find_torch:
            ld      a, NO_TORCH
            ld      (torch_col), a
            ld      (torch_row), a
            call    room_entry_addr
            ld      a, (hl)
            inc     hl
            ld      h, (hl)
            ld      l, a
            ld      b, 0
.ft_row:
            ld      c, 0
.ft_col:
            ld      a, (hl)
            cp      'T'
            jr      nz, .ft_skip
            ld      a, c
            ld      (torch_col), a
            ld      a, b
            ld      (torch_row), a
.ft_skip:
            inc     hl
            inc     c
            ld      a, c
            cp      32
            jr      nz, .ft_col
            inc     b
            ld      a, b
            cp      24
            jr      nz, .ft_row
            ret

shade_for_cell:
            ld      a, (torch_col)
            cp      NO_TORCH
            jr      z, .sf_dark
            ld      a, b
            ld      hl, torch_row
            sub     (hl)
            jr      nc, .sf_rpos
            neg
.sf_rpos:
            ld      d, a
            ld      a, c
            ld      hl, torch_col
            sub     (hl)
            jr      nc, .sf_cpos
            neg
.sf_cpos:
            cp      d
            jr      nc, .sf_max
            ld      a, d
.sf_max:
            srl     a
            cp      MAX_SHADE + 1
            jr      c, .sf_done
            ld      a, MAX_SHADE
.sf_done:
            ret
.sf_dark:
            ld      a, MAX_SHADE
            ret

draw_room:
            call    find_torch
            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)
            cp      '.'
            jr      nz, .not_floor
            call    shade_for_cell
            add     a, a
            ld      e, a
            ld      d, 0
            ld      hl, shade_tiles
            add     hl, de
            ld      e, (hl)
            inc     hl
            ld      d, (hl)
            ld      (tile_ptr), de
            ld      a, FLOOR_ATTR
            ld      (tile_attr), a
            call    draw_tile
            jr      .cell_done
.not_floor:
            call    lookup_tile
            call    draw_tile
.cell_done:
            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:
            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 furnished. 'S' statue and 'B' banner are bright (solid); 'o'
; rubble is dim (walkable). '.' is still handled by the lighting path.
; ----------------------------------------------------------------------------
palette:
            defb    '.'
            defw    shade2_tile
            defb    FLOOR_ATTR
            defb    '#'
            defw    wall_tile
            defb    WALL_ATTR
            defb    'T'
            defw    torch_tile
            defb    TORCH_ATTR
            defb    'S'
            defw    statue_tile
            defb    STATUE_ATTR
            defb    'B'
            defw    banner_tile
            defb    BANNER_ATTR
            defb    'o'
            defw    rubble_tile
            defb    RUBBLE_ATTR
            defb    '+'
            defw    mark_tile
            defb    MARK_ATTR

shade_tiles:
            defw    shade0_tile
            defw    shade1_tile
            defw    shade2_tile
            defw    shade3_tile
            defw    shade4_tile

rooms:
            defw    room0_state
            defb    NO_EXIT, NO_EXIT, 1, NO_EXIT
            defw    room1_state
            defb    2, NO_EXIT, NO_EXIT, 0
            defw    room2_state
            defb    NO_EXIT, 1, NO_EXIT, NO_EXIT

; The Great Hall — torch above, banners flanking a statue, rubble at its feet.
room0_template:
            defb    "###############T################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "B..............S...............B"
            defb    "#.............ooo..............#"
            defb    "#..............................#"
            defb    "#........##..........##........#"
            defb    "#........##..........##........#"
            defb    "#..............................#"
            defb    "#..............................."
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#........##..........##........#"
            defb    "#........##..........##........#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "################################"

; The Gallery — a banner on the west wall, a heap of rubble in the corner.
room1_template:
            defb    "###############.################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "###############.################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "...............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "B..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#........................ooo...#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "###############T################"

; The Vault — banners flanking the great altar.
room2_template:
            defb    "###############T################"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#.............####.............#"
            defb    "B.............####.............#"
            defb    "#.............####.............B"
            defb    "#.............####.............#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "#..............................#"
            defb    "###############.################"

; ----------------------------------------------------------------------------
; Floor shade ramp.
; ----------------------------------------------------------------------------
shade0_tile:
            defb    %00000000
            defb    %00100010
            defb    %00000000
            defb    %00000000
            defb    %00000000
            defb    %10001000
            defb    %00000000
            defb    %00000000
shade1_tile:
            defb    %00100010
            defb    %00000000
            defb    %10001000
            defb    %00000000
            defb    %00100010
            defb    %00000000
            defb    %10001000
            defb    %00000000
shade2_tile:
            defb    %10101010
            defb    %01010101
            defb    %10101010
            defb    %01010101
            defb    %10101010
            defb    %01010101
            defb    %10101010
            defb    %01010101
shade3_tile:
            defb    %10101010
            defb    %11111111
            defb    %01010101
            defb    %11111111
            defb    %10101010
            defb    %11111111
            defb    %01010101
            defb    %11111111
shade4_tile:
            defb    %11111111
            defb    %11101110
            defb    %11111111
            defb    %10111011
            defb    %11111111
            defb    %11101110
            defb    %11111111
            defb    %10111011

wall_tile:
            defb    %00010001
            defb    %00000000
            defb    %01000100
            defb    %00000000
            defb    %00010001
            defb    %00000000
            defb    %01000100
            defb    %00000000

torch_tile:
            defb    %00010000
            defb    %00111000
            defb    %00111000
            defb    %01111100
            defb    %01111100
            defb    %01111100
            defb    %00111000
            defb    %00010000

statue_tile:
            defb    %00111100
            defb    %01111110
            defb    %00111100
            defb    %00011000
            defb    %00011000
            defb    %00111100
            defb    %01111110
            defb    %01111110

banner_tile:
            defb    %01111110
            defb    %01111110
            defb    %01011010
            defb    %01011010
            defb    %01111110
            defb    %01111110
            defb    %00111100
            defb    %00011000

rubble_tile:
            defb    %00000000
            defb    %01100000
            defb    %01100000
            defb    %00000110
            defb    %00000110
            defb    %00011000
            defb    %00011000
            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

current_room:
            defb    0
torch_col:
            defb    NO_TORCH
torch_row:
            defb    NO_TORCH
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
room2_state:
            defs    768

            end     start

Walk up to the statue and it turns you back; the rubble at its feet you stride right over, and it stays where it lay behind you. Two kinds of furniture, one collision rule:

Up to the statue — bright, so solid — and the thief stops at its feet, standing on the rubble. Then sideways across the rubble — dim, so walkable — and off it, the broken stone still in place. The keep never needed a new rule to tell the two apart: bright blocks, dim doesn't.

Try this: furnish a room yourself

Open a room template and dress it. Put a statue at the end of the Gallery; line its walls with banners; pile rubble where a wall has failed. You're set-dressing by typing letters — the fastest art tool there is. Keep the thief's start and the doorways clear, and remember bright is solid.

Try this: rubble you can clear

Rubble is a glyph in the room's state. Add a key that, when the thief stands on rubble, writes . over it — he's cleared the debris, and it stays cleared because the state remembers. You've made the world not just furnished but changeable: the first step from scenery toward things the player acts on.

Try this: a new piece

Design one more: a well (a dark round hole — solid, you can't cross it), or a rug (a coloured floor — walkable, just decoration). One tile, one palette line, one attribute (bright to block, dim to walk). See how few bytes a new piece of the world costs.

When it's wrong, see why

  • The thief walks through the statue. Its attribute isn't bright — wall_at only blocks bright cells. Statues and banners must have bit 6 set.
  • The thief can't cross the rubble. Its attribute is bright. Rubble must be dim (share FLOOR_ATTR) to stay walkable.
  • The rubble vanishes when he walks over it. That's a save/restore fault — but it shouldn't happen, since rubble is in the state buffer and restore_under lays back whatever was beneath him. Check you didn't special-case the draw.
  • A glyph draws as blank or garbage. It isn't in the palette, so lookup_tile scans off the end. Every furniture letter (S, B, o) needs an entry.
  • A room is shifted. A furnished template row isn't 32 characters any more — adding a letter means removing a ., not pushing the row longer. Count it.

Before and after

You started with rooms that were lit but bare and finished with a keep that looks inhabited — a statue in the torchlight, banners on the walls, rubble underfoot. None of it took new engine code: furniture is glyphs in the palette, and the brightness rule that already sorted wall from floor sorts the solid statue from the walkable rubble for nothing. The walkable pieces ride the hero's save/restore and live in the room's state, so he crosses them cleanly and they persist. Set-dressing turned out to be design — letters typed into a map — not engineering.

What you've learnt

  • Furniture is tiles. A statue, a banner, rubble — each a glyph, a pattern and a colour in the palette, placed by typing into the map.
  • Blocking is brightness, again. The collision rule you already had sorts solid furniture from walkable furniture with no new code: bright blocks, dim doesn't.
  • Non-blocking scenery is free and persistent. Walkable decoration rides the hero's save/restore and lives in the room's state, so he crosses it cleanly and it remembers.
  • Set-dressing is design, not engineering. Once the palette is rich enough, making a room feel inhabited is something you do in a text editor.

What's next

The keep is lit and furnished — now to compose with it. In Unit 11, "Mood through Constraint," we stop adding features and start using restraint: dark rooms with a single lit pool, sharp contrast between gloom and flame, the eye led by light. With only two colours a cell and a ramp of dither, the limit itself becomes the vocabulary — and a plain stone room becomes a place with a feeling. Atmosphere is as much what you withhold as what you add.