A Keep with Character
Compose the whole keep as one place. Let a room hold more than one torch — each floor cell lit by its nearest flame — and use that with per-room falloff to give every chamber its own character from one coherent toolset.
We've given rooms moods one at a time. This unit pulls back and treats the whole keep as a single composition: three chambers that read as parts of one place, each lit to its own character, all from the same coherent set of tools. It's the unit that turns "rooms with lighting" into "a keep you believe in", and it closes the Mood and Light sub-arc.
The capability that makes it possible is a small but real upgrade: a room can have more than one torch.
What you'll see by the end
The Hall, grandly lit now — three flames, their pools overlapping into a broad wash of light across the upper chamber, the statue and its banners standing in it, the floor sinking to shadow toward the back. Walk to the Vault and it's still a single flame in the black; the Gallery, two torches and a medium glow. One keep, one engine, three characters — and they feel like the same keep, lit by the same hand.
Many flames, nearest wins
Until now a room had one torch and find_torch looked for it. Now find_torches gathers every T in the room into a little list:
find_torches:
; ... scan the map; for each 'T', record (col, row) ...
; ... up to MAX_TORCHES, counting them in torch_count ...
And a floor cell takes its light from whichever flame is closest — shade_for_cell measures the distance to each torch and keeps the smallest:
.sf_loop:
; distance from this cell to torch[i] ...
ld e, a ; this torch's distance
ld a, (min_dist)
cp e
jr c, .sf_keep ; already nearer
ld a, e
ld (min_dist), a ; new nearest
.sf_keep:
djnz .sf_loop
; ... then shift by the room's falloff and clamp, as before ...
Two torches make two pools that overlap where they meet; three light a whole hall. The light stopped being one circle and became as many as the room has fires.
Character from a shared toolset
Now each room is composed with the same two dials — how many flames, and how fast they fade — turned to a different setting:
room_falloff:
defb 2 ; Hall — 3 sconces, broad: grand and bright
defb 1 ; Gallery — 2 torches, medium
defb 0 ; Vault — 1 flame on the altar, tight: a crypt
The Hall is generous and public — three sconces, soft falloff, light everywhere you'd gather. The Vault is private and fearful — one flame, sharp falloff, dark everywhere you're not meant to linger. They're opposites, and yet they're plainly the same building, because they're drawn with the same stone, the same dither ramp, the same flames. That coherence — variety within one clear hand — is what "character" means for a place. You're no longer lighting rooms; you're lighting a keep.
Milestone — light a keep, not a room
find_torch becomes find_torches: it gathers every T in a room into a short
list. shade_for_cell then measures a floor cell's distance to each flame and keeps
the smallest, so two torches make two overlapping pools and three light a whole
hall. Paired with the per-room falloff from Unit 11, two dials — count of flames and
falloff — compose the grand Hall, the working Gallery and the crypt-dark Vault from
one shared toolset.
| 1 | 1 | ; Shadowkeep — Unit 12: A Keep with Character | |
| 2 | 2 | ; Cumulative build; every step runs on its own. Narrative: the unit page. | |
| 3 | - | ; step-00 = Unit 11's end: per-room falloff, one torch each. | |
| 3 | + | ; step-01 lets a room hold many torches — each floor cell lit by its nearest flame. | |
| 4 | 4 | | |
| 5 | 5 | org 32768 | |
| 6 | 6 | | |
| ... | |||
| 17 | 17 | START_COL equ 15 | |
| 18 | 18 | START_ROW equ 11 | |
| 19 | 19 | NO_EXIT equ $FF | |
| 20 | - | NO_TORCH equ $FF | |
| 21 | 20 | MAX_SHADE equ 4 | |
| 21 | + | MAX_TORCHES equ 4 | |
| 22 | 22 | | |
| 23 | 23 | KEYS_OP equ $DFFE | |
| 24 | 24 | KEYS_Q equ $FBFE | |
| ... | |||
| 112 | 112 | add hl, de | |
| 113 | 113 | ret | |
| 114 | 114 | | |
| 115 | - | find_torch: | |
| 116 | - | ld a, NO_TORCH | |
| 117 | - | ld (torch_col), a | |
| 118 | - | ld (torch_row), a | |
| 115 | + | ; ---------------------------------------------------------------------------- | |
| 116 | + | ; find_torches — collect every 'T' in the current room (up to MAX_TORCHES) into | |
| 117 | + | ; torch_list as (col, row) pairs; torch_count says how many. | |
| 118 | + | ; ---------------------------------------------------------------------------- | |
| 119 | + | find_torches: | |
| 120 | + | xor a | |
| 121 | + | ld (torch_count), a | |
| 119 | 122 | call room_entry_addr | |
| 120 | 123 | ld a, (hl) | |
| 121 | 124 | inc hl | |
| 122 | 125 | ld h, (hl) | |
| 123 | 126 | ld l, a | |
| 124 | 127 | ld b, 0 | |
| 125 | - | .ft_row: | |
| 128 | + | .fr_row: | |
| 126 | 129 | ld c, 0 | |
| 127 | - | .ft_col: | |
| 130 | + | .fr_col: | |
| 128 | 131 | ld a, (hl) | |
| 129 | 132 | cp 'T' | |
| 130 | - | jr nz, .ft_skip | |
| 131 | - | ld a, c | |
| 132 | - | ld (torch_col), a | |
| 133 | - | ld a, b | |
| 134 | - | ld (torch_row), a | |
| 135 | - | .ft_skip: | |
| 133 | + | jr nz, .fr_skip | |
| 134 | + | ld a, (torch_count) | |
| 135 | + | cp MAX_TORCHES | |
| 136 | + | jr nc, .fr_skip | |
| 137 | + | push hl | |
| 138 | + | add a, a ; count * 2 | |
| 139 | + | ld e, a | |
| 140 | + | ld d, 0 | |
| 141 | + | ld hl, torch_list | |
| 142 | + | add hl, de | |
| 143 | + | ld (hl), c ; column | |
| 144 | + | inc hl | |
| 145 | + | ld (hl), b ; row | |
| 146 | + | pop hl | |
| 147 | + | ld a, (torch_count) | |
| 148 | + | inc a | |
| 149 | + | ld (torch_count), a | |
| 150 | + | .fr_skip: | |
| 136 | 151 | inc hl | |
| 137 | 152 | inc c | |
| 138 | 153 | ld a, c | |
| 139 | 154 | cp 32 | |
| 140 | - | jr nz, .ft_col | |
| 155 | + | jr nz, .fr_col | |
| 141 | 156 | inc b | |
| 142 | 157 | ld a, b | |
| 143 | 158 | cp 24 | |
| 144 | - | jr nz, .ft_row | |
| 159 | + | jr nz, .fr_row | |
| 145 | 160 | ret | |
| 146 | 161 | | |
| 147 | 162 | ; ---------------------------------------------------------------------------- | |
| 148 | - | ; shade_for_cell — row in B, column in C. Distance to the torch, shifted by | |
| 149 | - | ; THIS ROOM's falloff, clamped. Bigger falloff = light fades slower = wider, | |
| 150 | - | ; softer pool. Preserves B and C (the draw loop needs them). | |
| 163 | + | ; shade_for_cell — row in B, column in C. Distance to the NEAREST torch, | |
| 164 | + | ; shifted by the room's falloff, clamped. Preserves B and C. | |
| 151 | 165 | ; ---------------------------------------------------------------------------- | |
| 152 | 166 | shade_for_cell: | |
| 153 | 167 | push bc | |
| 154 | - | ld a, (torch_col) | |
| 155 | - | cp NO_TORCH | |
| 156 | - | jr z, .sf_dark | |
| 157 | 168 | ld a, b | |
| 158 | - | ld hl, torch_row | |
| 159 | - | sub (hl) | |
| 160 | - | jr nc, .sf_rpos | |
| 161 | - | neg | |
| 162 | - | .sf_rpos: | |
| 163 | - | ld d, a | |
| 169 | + | ld (cell_row), a | |
| 164 | 170 | ld a, c | |
| 165 | - | ld hl, torch_col | |
| 171 | + | ld (cell_col), a | |
| 172 | + | ld a, (torch_count) | |
| 173 | + | or a | |
| 174 | + | jr z, .sf_dark | |
| 175 | + | ld a, 255 | |
| 176 | + | ld (min_dist), a | |
| 177 | + | ld hl, torch_list | |
| 178 | + | ld a, (torch_count) | |
| 179 | + | ld b, a ; loop count | |
| 180 | + | .sf_loop: | |
| 181 | + | ld a, (cell_col) | |
| 182 | + | sub (hl) ; - torch col | |
| 183 | + | jr nc, .sf_dc | |
| 184 | + | neg | |
| 185 | + | .sf_dc: | |
| 186 | + | ld d, a ; |dc| | |
| 187 | + | inc hl ; -> torch row | |
| 188 | + | ld a, (cell_row) | |
| 166 | 189 | sub (hl) | |
| 167 | - | jr nc, .sf_cpos | |
| 190 | + | jr nc, .sf_dr | |
| 168 | 191 | neg | |
| 169 | - | .sf_cpos: | |
| 170 | - | cp d | |
| 171 | - | jr nc, .sf_max | |
| 192 | + | .sf_dr: | |
| 193 | + | inc hl ; -> next torch pair | |
| 194 | + | cp d ; A = |dr|; take the larger | |
| 195 | + | jr nc, .sf_mx | |
| 172 | 196 | ld a, d | |
| 173 | - | .sf_max: | |
| 174 | - | ld e, a ; E = Chebyshev distance | |
| 197 | + | .sf_mx: | |
| 198 | + | ld e, a ; this torch's distance | |
| 199 | + | ld a, (min_dist) | |
| 200 | + | cp e | |
| 201 | + | jr c, .sf_keep ; min already smaller | |
| 202 | + | ld a, e | |
| 203 | + | ld (min_dist), a | |
| 204 | + | .sf_keep: | |
| 205 | + | djnz .sf_loop | |
| 206 | + | | |
| 175 | 207 | ld a, (current_room) | |
| 176 | 208 | ld c, a | |
| 177 | 209 | ld b, 0 | |
| 178 | 210 | ld hl, room_falloff | |
| 179 | 211 | add hl, bc | |
| 180 | - | ld b, (hl) ; B = this room's falloff | |
| 181 | - | ld a, e | |
| 212 | + | ld b, (hl) | |
| 213 | + | ld a, (min_dist) | |
| 182 | 214 | inc b | |
| 183 | 215 | .sf_shift: | |
| 184 | 216 | dec b | |
| ... | |||
| 198 | 230 | ret | |
| 199 | 231 | | |
| 200 | 232 | draw_room: | |
| 201 | - | call find_torch | |
| 233 | + | call find_torches | |
| 202 | 234 | call room_entry_addr | |
| 203 | 235 | ld a, (hl) | |
| 204 | 236 | inc hl | |
| ... | |||
| 521 | 553 | defw shade3_tile | |
| 522 | 554 | defw shade4_tile | |
| 523 | 555 | | |
| 524 | - | ; One byte of mood per room: how slowly its light fades. | |
| 525 | 556 | room_falloff: | |
| 526 | - | defb 2 ; Hall — broad, soft pool | |
| 557 | + | defb 2 ; Hall — broad | |
| 527 | 558 | defb 1 ; Gallery — medium | |
| 528 | - | defb 0 ; Vault — tight pool, deep dark | |
| 559 | + | defb 0 ; Vault — tight | |
| 529 | 560 | | |
| 530 | 561 | rooms: | |
| 531 | 562 | defw room0_state | |
| ... | |||
| 535 | 566 | defw room2_state | |
| 536 | 567 | defb NO_EXIT, 1, NO_EXIT, NO_EXIT | |
| 537 | 568 | | |
| 538 | - | ; The Great Hall — broadly lit, a torch high in the wall. | |
| 569 | + | ; The Great Hall — three sconces (top-centre and the two side walls), broadly | |
| 570 | + | ; lit; banners flank a statue with rubble at its feet. | |
| 539 | 571 | room0_template: | |
| 540 | 572 | defb "###############T################" | |
| 541 | 573 | defb "#..............................#" | |
| ... | |||
| 545 | 577 | defb "B..............S...............B" | |
| 546 | 578 | defb "#.............ooo..............#" | |
| 547 | 579 | defb "#..............................#" | |
| 548 | - | defb "#........##..........##........#" | |
| 580 | + | defb "T........##..........##........T" | |
| 549 | 581 | defb "#........##..........##........#" | |
| 550 | 582 | defb "#..............................#" | |
| 551 | 583 | defb "#..............................." | |
| ... | |||
| 562 | 594 | defb "#..............................#" | |
| 563 | 595 | defb "################################" | |
| 564 | 596 | | |
| 597 | + | ; The Gallery — two torches: low on the south wall, and one on the east wall. | |
| 565 | 598 | room1_template: | |
| 566 | 599 | defb "###############.################" | |
| 567 | - | defb "#..............................#" | |
| 568 | 600 | defb "#..............................#" | |
| 569 | 601 | defb "#..............................#" | |
| 570 | 602 | defb "#..............................#" | |
| 603 | + | defb "#..............................T" | |
| 571 | 604 | defb "#..............................#" | |
| 572 | 605 | defb "#..............................#" | |
| 573 | 606 | defb "#..............................#" | |
| ... | |||
| 588 | 621 | defb "#..............................#" | |
| 589 | 622 | defb "###############T################" | |
| 590 | 623 | | |
| 591 | - | ; The Vault — a crypt. No wall torch; the only flame is on the altar itself, so | |
| 592 | - | ; with falloff 0 the light pools tight around it and the rest sinks to black. | |
| 624 | + | ; The Vault — one flame, on the altar. Its character is the dark. | |
| 593 | 625 | room2_template: | |
| 594 | 626 | defb "################################" | |
| 595 | 627 | defb "#..............................#" | |
| ... | |||
| 734 | 766 | | |
| 735 | 767 | current_room: | |
| 736 | 768 | defb 0 | |
| 737 | - | torch_col: | |
| 738 | - | defb NO_TORCH | |
| 739 | - | torch_row: | |
| 740 | - | defb NO_TORCH | |
| 769 | + | torch_count: | |
| 770 | + | defb 0 | |
| 771 | + | cell_row: | |
| 772 | + | defb 0 | |
| 773 | + | cell_col: | |
| 774 | + | defb 0 | |
| 775 | + | min_dist: | |
| 776 | + | defb 0 | |
| 741 | 777 | thief_col: | |
| 742 | 778 | defb START_COL | |
| 743 | 779 | thief_row: | |
| ... | |||
| 754 | 790 | defb 0 | |
| 755 | 791 | under_thief: | |
| 756 | 792 | defb 0, 0, 0, 0, 0, 0, 0, 0, 0 | |
| 793 | + | torch_list: | |
| 794 | + | defs MAX_TORCHES * 2 | |
| 757 | 795 | | |
| 758 | 796 | room0_state: | |
| 759 | 797 | defs 768 |
The complete program
; Shadowkeep — Unit 12: A Keep with Character
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 lets a room hold many torches — each floor cell lit by its nearest flame.
org 32768
WALL_ATTR equ %01001000
FLOOR_ATTR equ %00001000
TORCH_ATTR equ %01001110
STATUE_ATTR equ %01001111
BANNER_ATTR equ %01001011
RUBBLE_ATTR equ %00001000
MARK_ATTR equ %00001111
THIEF equ %01001010
WALL_BIT equ 6
START_COL equ 15
START_ROW equ 11
NO_EXIT equ $FF
MAX_SHADE equ 4
MAX_TORCHES 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_torches — collect every 'T' in the current room (up to MAX_TORCHES) into
; torch_list as (col, row) pairs; torch_count says how many.
; ----------------------------------------------------------------------------
find_torches:
xor a
ld (torch_count), a
call room_entry_addr
ld a, (hl)
inc hl
ld h, (hl)
ld l, a
ld b, 0
.fr_row:
ld c, 0
.fr_col:
ld a, (hl)
cp 'T'
jr nz, .fr_skip
ld a, (torch_count)
cp MAX_TORCHES
jr nc, .fr_skip
push hl
add a, a ; count * 2
ld e, a
ld d, 0
ld hl, torch_list
add hl, de
ld (hl), c ; column
inc hl
ld (hl), b ; row
pop hl
ld a, (torch_count)
inc a
ld (torch_count), a
.fr_skip:
inc hl
inc c
ld a, c
cp 32
jr nz, .fr_col
inc b
ld a, b
cp 24
jr nz, .fr_row
ret
; ----------------------------------------------------------------------------
; shade_for_cell — row in B, column in C. Distance to the NEAREST torch,
; shifted by the room's falloff, clamped. Preserves B and C.
; ----------------------------------------------------------------------------
shade_for_cell:
push bc
ld a, b
ld (cell_row), a
ld a, c
ld (cell_col), a
ld a, (torch_count)
or a
jr z, .sf_dark
ld a, 255
ld (min_dist), a
ld hl, torch_list
ld a, (torch_count)
ld b, a ; loop count
.sf_loop:
ld a, (cell_col)
sub (hl) ; - torch col
jr nc, .sf_dc
neg
.sf_dc:
ld d, a ; |dc|
inc hl ; -> torch row
ld a, (cell_row)
sub (hl)
jr nc, .sf_dr
neg
.sf_dr:
inc hl ; -> next torch pair
cp d ; A = |dr|; take the larger
jr nc, .sf_mx
ld a, d
.sf_mx:
ld e, a ; this torch's distance
ld a, (min_dist)
cp e
jr c, .sf_keep ; min already smaller
ld a, e
ld (min_dist), a
.sf_keep:
djnz .sf_loop
ld a, (current_room)
ld c, a
ld b, 0
ld hl, room_falloff
add hl, bc
ld b, (hl)
ld a, (min_dist)
inc b
.sf_shift:
dec b
jr z, .sf_clamp
srl a
jr .sf_shift
.sf_clamp:
cp MAX_SHADE + 1
jr c, .sf_done
ld a, MAX_SHADE
.sf_done:
pop bc
ret
.sf_dark:
ld a, MAX_SHADE
pop bc
ret
draw_room:
call find_torches
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:
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
room_falloff:
defb 2 ; Hall — broad
defb 1 ; Gallery — medium
defb 0 ; Vault — tight
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 — three sconces (top-centre and the two side walls), broadly
; lit; banners flank a statue with rubble at its feet.
room0_template:
defb "###############T################"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "B..............S...............B"
defb "#.............ooo..............#"
defb "#..............................#"
defb "T........##..........##........T"
defb "#........##..........##........#"
defb "#..............................#"
defb "#..............................."
defb "#..............................#"
defb "#..............................#"
defb "#........##..........##........#"
defb "#........##..........##........#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "################################"
; The Gallery — two torches: low on the south wall, and one on the east wall.
room1_template:
defb "###############.################"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................T"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "###############.################"
defb "#..............................#"
defb "#..............................#"
defb "...............................#"
defb "#..............................#"
defb "#..............................#"
defb "B..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#........................ooo...#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "###############T################"
; The Vault — one flame, on the altar. Its character is the dark.
room2_template:
defb "################################"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#.............#T##.............#"
defb "#.............####.............#"
defb "#.............####.............#"
defb "#.............####.............#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "#..............................#"
defb "###############.################"
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_count:
defb 0
cell_row:
defb 0
cell_col:
defb 0
min_dist:
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
torch_list:
defs MAX_TORCHES * 2
room0_state:
defs 768
room1_state:
defs 768
room2_state:
defs 768
end start
Walk the whole keep, end to end: the Hall opens out under its three flames, the Gallery settles to a working glow, the Vault closes in to its one cold light. Three rooms, one place — the two stills above are its grandest and its darkest, drawn by the same hand.
Try this: add a flame, take one away
Drop another T into a room's map and watch a new pool bloom and overlap the others; delete one and watch a corner fall dark. You're lighting by placing fires — the most physical way there is. Mind MAX_TORCHES; raise it if you want a room ablaze.
Try this: a colonnade of light
Line a long room's wall with a row of evenly-spaced sconces, falloff 1 or 2. The overlapping pools merge into a continuous band of light down the room — a lit gallery, a cathedral nave. Repetition of one element is its own kind of grandeur.
Try this: ruin the character
Give the Vault three torches and a falloff of 2. It floods with light and instantly stops being a crypt — it's just another bright room. A useful failure: it shows that the Vault's character was never the altar or the stone, it was the restraint. Put it back, and notice how much the darkness was doing.
When it's wrong, see why
- Only one torch lights a room.
find_torchesis stopping at the firstT, orshade_for_cellisn't looping overtorch_count. EveryTshould be collected and every one measured. - The machine slows to a crawl entering a room. Many torches × 768 cells is real work; it's a one-time cost on entry, but keep
MAX_TORCHESmodest (a handful) so the redraw stays a flick, not a freeze. - A room is all dark with torches present.
torch_countcame back zero — the map'sTs aren't being found, ormin_distisn't being reset to a large value before the loop. - Two pools don't overlap / one wins everywhere. The nearest test is backwards. Keep the smaller distance: update
min_distonly when the new distance is less. - Garbage after adding torches.
torch_listoverflowed — you placed more thanMAX_TORCHESand the count guard is missing. Thecp MAX_TORCHES/jr nccheck protects the list.
Before and after
You started with rooms that each held a single mood and finished with a whole keep composed as one place — the Hall grand under three sconces, the Vault a crypt with one cold flame, the Gallery somewhere between, and all three plainly the same building. The upgrade was small: a room gathers every torch it holds, and each cell takes its light from the nearest. With that and the falloff byte, two dials compose grand, working and fearful rooms from one shared toolset. You stopped lighting rooms and started lighting a keep — the difference between a set of screens and somewhere you believe in.
What you've learnt
- Light is as many pools as there are flames. Collect every torch; light each cell from the nearest. Pools overlap into the shape of the room's fires.
- Character is variety within one hand. Two dials — count of flames and falloff — compose grand, working, and fearful rooms that still read as one building.
- Restraint is a feature. The Vault's darkness, not its altar, is its character. Over-light it and it vanishes.
- A keep is a composition. You stopped lighting rooms and started lighting a place — the difference between a set of screens and somewhere to be.
What's next
The keep looks like somewhere now — lit, furnished, composed. But it's silent. A place you believe in has sound: footsteps on stone, the creak of a door, a flame's hiss. The next sub-arc, The Keep Has a Voice, gives it that. We begin in Unit 13, "Footsteps and Doors," building a small sound-effects driver on the beeper — the one from your first game, grown up — so the keep is heard as well as seen. Atmosphere reaches the ear.