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.
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 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.
| 1 | 1 | ; Shadowkeep — Unit 10: Furnishings | |
| 2 | 2 | ; 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. | |
| 4 | 4 | | |
| 5 | 5 | org 32768 | |
| 6 | 6 | | |
| 7 | 7 | 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 | |
| 10 | 13 | MARK_ATTR equ %00001111 | |
| 11 | 14 | THIEF equ %01001010 | |
| 12 | 15 | WALL_BIT equ 6 | |
| ... | |||
| 109 | 112 | add hl, de | |
| 110 | 113 | ret | |
| 111 | 114 | | |
| 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 | - | ; ---------------------------------------------------------------------------- | |
| 116 | 115 | find_torch: | |
| 117 | 116 | ld a, NO_TORCH | |
| 118 | 117 | ld (torch_col), a | |
| ... | |||
| 145 | 144 | jr nz, .ft_row | |
| 146 | 145 | ret | |
| 147 | 146 | | |
| 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 | - | ; ---------------------------------------------------------------------------- | |
| 153 | 147 | shade_for_cell: | |
| 154 | 148 | ld a, (torch_col) | |
| 155 | 149 | cp NO_TORCH | |
| ... | |||
| 160 | 154 | jr nc, .sf_rpos | |
| 161 | 155 | neg | |
| 162 | 156 | .sf_rpos: | |
| 163 | - | ld d, a ; |row - torch_row| | |
| 157 | + | ld d, a | |
| 164 | 158 | ld a, c | |
| 165 | 159 | ld hl, torch_col | |
| 166 | 160 | sub (hl) | |
| 167 | 161 | jr nc, .sf_cpos | |
| 168 | 162 | neg | |
| 169 | 163 | .sf_cpos: | |
| 170 | - | cp d ; A = |dc|; max(|dc|, |dr|) | |
| 164 | + | cp d | |
| 171 | 165 | jr nc, .sf_max | |
| 172 | 166 | ld a, d | |
| 173 | 167 | .sf_max: | |
| 174 | - | srl a ; distance / 2 | |
| 168 | + | srl a | |
| 175 | 169 | cp MAX_SHADE + 1 | |
| 176 | 170 | jr c, .sf_done | |
| 177 | 171 | ld a, MAX_SHADE | |
| ... | |||
| 181 | 175 | ld a, MAX_SHADE | |
| 182 | 176 | ret | |
| 183 | 177 | | |
| 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 | - | ; ---------------------------------------------------------------------------- | |
| 188 | 178 | draw_room: | |
| 189 | 179 | call find_torch | |
| 190 | 180 | call room_entry_addr | |
| ... | |||
| 201 | 191 | ld a, (hl) | |
| 202 | 192 | cp '.' | |
| 203 | 193 | 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 | |
| 207 | 196 | ld e, a | |
| 208 | 197 | ld d, 0 | |
| 209 | 198 | ld hl, shade_tiles | |
| ... | |||
| 481 | 470 | ret | |
| 482 | 471 | | |
| 483 | 472 | ; ---------------------------------------------------------------------------- | |
| 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. | |
| 486 | 475 | ; ---------------------------------------------------------------------------- | |
| 487 | 476 | palette: | |
| 488 | 477 | defb '.' | |
| ... | |||
| 494 | 483 | defb 'T' | |
| 495 | 484 | defw torch_tile | |
| 496 | 485 | 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 | |
| 497 | 495 | defb '+' | |
| 498 | 496 | defw mark_tile | |
| 499 | 497 | defb MARK_ATTR | |
| 500 | 498 | | |
| 501 | - | ; The shade ramp: lightest (lit) to darkest (deep shadow), all blue/black dither. | |
| 502 | 499 | shade_tiles: | |
| 503 | 500 | defw shade0_tile | |
| 504 | 501 | defw shade1_tile | |
| ... | |||
| 514 | 511 | defw room2_state | |
| 515 | 512 | defb NO_EXIT, 1, NO_EXIT, NO_EXIT | |
| 516 | 513 | | |
| 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. | |
| 518 | 515 | room0_template: | |
| 519 | 516 | defb "###############T################" | |
| 520 | - | defb "#..............................#" | |
| 521 | - | defb "#..............................#" | |
| 522 | 517 | defb "#..............................#" | |
| 523 | 518 | defb "#..............................#" | |
| 524 | 519 | defb "#..............................#" | |
| 525 | 520 | defb "#..............................#" | |
| 521 | + | defb "B..............S...............B" | |
| 522 | + | defb "#.............ooo..............#" | |
| 526 | 523 | defb "#..............................#" | |
| 527 | 524 | defb "#........##..........##........#" | |
| 528 | 525 | defb "#........##..........##........#" | |
| ... | |||
| 541 | 538 | defb "#..............................#" | |
| 542 | 539 | defb "################################" | |
| 543 | 540 | | |
| 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. | |
| 545 | 542 | room1_template: | |
| 546 | 543 | defb "###############.################" | |
| 547 | 544 | defb "#..............................#" | |
| ... | |||
| 555 | 552 | defb "#..............................#" | |
| 556 | 553 | defb "#..............................#" | |
| 557 | 554 | defb "...............................#" | |
| 558 | - | defb "#..............................#" | |
| 559 | - | defb "#..............................#" | |
| 560 | 555 | defb "#..............................#" | |
| 561 | 556 | defb "#..............................#" | |
| 557 | + | defb "B..............................#" | |
| 562 | 558 | defb "#..............................#" | |
| 563 | 559 | defb "#..............................#" | |
| 564 | 560 | defb "#..............................#" | |
| 561 | + | defb "#........................ooo...#" | |
| 565 | 562 | defb "#..............................#" | |
| 566 | 563 | defb "#..............................#" | |
| 567 | 564 | defb "#..............................#" | |
| 568 | 565 | defb "#..............................#" | |
| 569 | 566 | defb "###############T################" | |
| 570 | 567 | | |
| 571 | - | ; The Vault — a torch in the top wall above the altar. | |
| 568 | + | ; The Vault — banners flanking the great altar. | |
| 572 | 569 | room2_template: | |
| 573 | 570 | defb "###############T################" | |
| 574 | 571 | defb "#..............................#" | |
| ... | |||
| 580 | 577 | defb "#..............................#" | |
| 581 | 578 | defb "#..............................#" | |
| 582 | 579 | defb "#..............................#" | |
| 583 | - | defb "#.............####.............#" | |
| 584 | - | defb "#.............####.............#" | |
| 585 | 580 | defb "#.............####.............#" | |
| 581 | + | defb "B.............####.............#" | |
| 582 | + | defb "#.............####.............B" | |
| 586 | 583 | defb "#.............####.............#" | |
| 587 | 584 | defb "#..............................#" | |
| 588 | 585 | defb "#..............................#" | |
| ... | |||
| 596 | 593 | defb "###############.################" | |
| 597 | 594 | | |
| 598 | 595 | ; ---------------------------------------------------------------------------- | |
| 599 | - | ; The five shades of floor — same blue/black, denser and darker each step. | |
| 596 | + | ; Floor shade ramp. | |
| 600 | 597 | ; ---------------------------------------------------------------------------- | |
| 601 | - | shade0_tile: ; lit — nearly all blue | |
| 598 | + | shade0_tile: | |
| 602 | 599 | defb %00000000 | |
| 603 | 600 | defb %00100010 | |
| 604 | 601 | defb %00000000 | |
| ... | |||
| 616 | 613 | defb %00000000 | |
| 617 | 614 | defb %10001000 | |
| 618 | 615 | defb %00000000 | |
| 619 | - | shade2_tile: ; the half-and-half slate from Unit 2 | |
| 616 | + | shade2_tile: | |
| 620 | 617 | defb %10101010 | |
| 621 | 618 | defb %01010101 | |
| 622 | 619 | defb %10101010 | |
| ... | |||
| 634 | 631 | defb %11111111 | |
| 635 | 632 | defb %01010101 | |
| 636 | 633 | defb %11111111 | |
| 637 | - | shade4_tile: ; deep shadow — nearly all black | |
| 634 | + | shade4_tile: | |
| 638 | 635 | defb %11111111 | |
| 639 | 636 | defb %11101110 | |
| 640 | 637 | defb %11111111 | |
| ... | |||
| 654 | 651 | defb %01000100 | |
| 655 | 652 | defb %00000000 | |
| 656 | 653 | | |
| 657 | - | torch_tile: ; a flame in its sconce | |
| 654 | + | torch_tile: | |
| 658 | 655 | defb %00010000 | |
| 659 | 656 | defb %00111000 | |
| 660 | 657 | defb %00111000 | |
| ... | |||
| 663 | 660 | defb %01111100 | |
| 664 | 661 | defb %00111000 | |
| 665 | 662 | 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 | |
| 666 | 693 | | |
| 667 | 694 | mark_tile: | |
| 668 | 695 | 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:
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_atonly 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_underlays 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_tilescans 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.