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

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.

75% of Shadowkeep

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 lit by three sconces — one high in the centre wall and one on each side wall — their pools of light overlapping across the upper hall around the statue and banners, the floor falling into shadow below; the hooded thief stands in the light.
The Hall, grandly lit: three sconces, their pools overlapping into a broad wash across the upper chamber, the statue and banners standing in it, the floor sinking to shadow at the back. The light is no longer one circle but as many as the room has fires.

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 closestshade_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.

The Vault, lit by a single flame on the central altar: a tight pool of light around the altar, the rest of the room near-black, the hooded thief a dim figure entering from the south doorway below.
The same keep, the opposite character: the Vault's one cold flame and sharp falloff against the Hall's three sconces and broad wash. Same stone, same dither ramp, same fire — one number each sets them apart, and they still read as one building.

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.

Step 1: many torches per room, each cell lit by its nearest flame
+88-50
11 ; Shadowkeep — Unit 12: A Keep with Character
22 ; 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.
44
55 org 32768
66
...
1717 START_COL equ 15
1818 START_ROW equ 11
1919 NO_EXIT equ $FF
20-NO_TORCH equ $FF
2120 MAX_SHADE equ 4
21+MAX_TORCHES equ 4
2222
2323 KEYS_OP equ $DFFE
2424 KEYS_Q equ $FBFE
...
112112 add hl, de
113113 ret
114114
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
119122 call room_entry_addr
120123 ld a, (hl)
121124 inc hl
122125 ld h, (hl)
123126 ld l, a
124127 ld b, 0
125-.ft_row:
128+.fr_row:
126129 ld c, 0
127-.ft_col:
130+.fr_col:
128131 ld a, (hl)
129132 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:
136151 inc hl
137152 inc c
138153 ld a, c
139154 cp 32
140- jr nz, .ft_col
155+ jr nz, .fr_col
141156 inc b
142157 ld a, b
143158 cp 24
144- jr nz, .ft_row
159+ jr nz, .fr_row
145160 ret
146161
147162 ; ----------------------------------------------------------------------------
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.
151165 ; ----------------------------------------------------------------------------
152166 shade_for_cell:
153167 push bc
154- ld a, (torch_col)
155- cp NO_TORCH
156- jr z, .sf_dark
157168 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
164170 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)
166189 sub (hl)
167- jr nc, .sf_cpos
190+ jr nc, .sf_dr
168191 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
172196 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+
175207 ld a, (current_room)
176208 ld c, a
177209 ld b, 0
178210 ld hl, room_falloff
179211 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)
182214 inc b
183215 .sf_shift:
184216 dec b
...
198230 ret
199231
200232 draw_room:
201- call find_torch
233+ call find_torches
202234 call room_entry_addr
203235 ld a, (hl)
204236 inc hl
...
521553 defw shade3_tile
522554 defw shade4_tile
523555
524-; One byte of mood per room: how slowly its light fades.
525556 room_falloff:
526- defb 2 ; Hall — broad, soft pool
557+ defb 2 ; Hall — broad
527558 defb 1 ; Gallery — medium
528- defb 0 ; Vault — tight pool, deep dark
559+ defb 0 ; Vault — tight
529560
530561 rooms:
531562 defw room0_state
...
535566 defw room2_state
536567 defb NO_EXIT, 1, NO_EXIT, NO_EXIT
537568
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.
539571 room0_template:
540572 defb "###############T################"
541573 defb "#..............................#"
...
545577 defb "B..............S...............B"
546578 defb "#.............ooo..............#"
547579 defb "#..............................#"
548- defb "#........##..........##........#"
580+ defb "T........##..........##........T"
549581 defb "#........##..........##........#"
550582 defb "#..............................#"
551583 defb "#..............................."
...
562594 defb "#..............................#"
563595 defb "################################"
564596
597+; The Gallery — two torches: low on the south wall, and one on the east wall.
565598 room1_template:
566599 defb "###############.################"
567- defb "#..............................#"
568600 defb "#..............................#"
569601 defb "#..............................#"
570602 defb "#..............................#"
603+ defb "#..............................T"
571604 defb "#..............................#"
572605 defb "#..............................#"
573606 defb "#..............................#"
...
588621 defb "#..............................#"
589622 defb "###############T################"
590623
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.
593625 room2_template:
594626 defb "################################"
595627 defb "#..............................#"
...
734766
735767 current_room:
736768 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
741777 thief_col:
742778 defb START_COL
743779 thief_row:
...
754790 defb 0
755791 under_thief:
756792 defb 0, 0, 0, 0, 0, 0, 0, 0, 0
793+torch_list:
794+ defs MAX_TORCHES * 2
757795
758796 room0_state:
759797 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_torches is stopping at the first T, or shade_for_cell isn't looping over torch_count. Every T should 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_TORCHES modest (a handful) so the redraw stays a flick, not a freeze.
  • A room is all dark with torches present. torch_count came back zero — the map's Ts aren't being found, or min_dist isn'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_dist only when the new distance is less.
  • Garbage after adding torches. torch_list overflowed — you placed more than MAX_TORCHES and the count guard is missing. The cp MAX_TORCHES / jr nc check 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.