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

Mood through Constraint

The lighting engine is built; now compose with it. Give each room its own falloff — one byte — so the Hall reads as a broad lit chamber and the Vault as a near-black crypt lit only at the altar. Same two colours, same dither; restraint is the technique.

69% of Shadowkeep

The keep is lit and furnished — but every room is lit the same. A grand hall and a burial crypt should not feel alike, and atmosphere is as much about restraint as addition. This unit stops adding features and starts composing with the ones we have. The whole palette is still two colours a cell and a ramp of dither; the art is in how we use the limit.

The lever is a single byte per room: how fast its light fades.

What you'll see by the end

The Vault as a crypt: a flame burns on the central altar, throwing a small, tight pool of light over the stone immediately around it; the rest of the room falls away into near-black, the hooded thief a dim figure approaching through the dark.
The Vault as a crypt: one flame on the altar, a tight pocket of light, and beyond it near-black — the dither thickening cell by cell until the walls are swallowed. The thief, a faint shape, drawn to the only light there is. One byte of falloff makes this room of the same engine that lights the Hall like an open chamber.

The Vault, now: a single flame on the altar, a tight pocket of light around it, and beyond that darkness — the dither thickening to near-black across the floor, the walls swallowed. The thief is a faint shape down in the gloom, drawn toward the only light there is. Step back into the Hall and it's the opposite: a wide, even glow, an open and unthreatening room. Two rooms, one lighting engine, two completely different feelings.

One byte of mood

Back in Unit 9 every room halved the distance to get its shade (distance >> 1). The new idea is to make that shift a property of the room — its falloff:

room_falloff:
            defb    2               ; Hall    — distance >> 2: a broad, soft pool
            defb    1               ; Gallery — distance >> 1: medium
            defb    0               ; Vault   — distance     : a tight pool, deep dark

A bigger shift makes distance grow slowly, so the light reaches far — a generous, well-lit room. A shift of zero lets distance count cell-for-cell, so the light collapses to a tight ring and everything past it is black. shade_for_cell just reads the current room's falloff and shifts by it:

            ld      a, (current_room)
            ; ... index room_falloff ...
            ld      b, (hl)         ; B = this room's falloff
            ld      a, e            ; A = distance to the torch
.sf_shift:
            dec     b
            jr      z, .sf_clamp
            srl     a               ; shift by falloff, then clamp
            jr      .sf_shift

That's the entire mechanism. The Hall is welcoming and the Vault is oppressive because of one number each.

Composing the dark

Mood isn't only the falloff — it's where the light is. The Hall's torch sits high in a wall, washing the room; the Vault has no wall torch at all. Its only flame is on the altar itself:

            defb    "#.............#T##.............#"   ; a flame set in the altar

So in the Vault the light is born at the thing you're meant to look at, falls off at once (falloff 0), and leaves the rest to the dark. The eye has nowhere to go but the altar. That's composition: not "make it bright" or "make it dark", but put the one light where it means something and let the constraint do the rest. The Spectrum's two-colour cell, which looked like a limitation in Unit 1, is now the instrument.

Milestone — a mood per room

The shift that used to be fixed (distance >> 1 everywhere) becomes a per-room falloff byte: shade_for_cell reads the current room's falloff and shifts the distance by that many places. A big shift reaches far (the Hall, falloff 2); a shift of zero collapses the light to a tight ring (the Vault, falloff 0, its only flame on the altar). The whole composition is one number per room.

Step 1: a per-room falloff byte drives the shade ramp
+38-17
11 ; Shadowkeep — Unit 11: Mood through Constraint
22 ; Cumulative build; every step runs on its own. Narrative: the unit page.
3-; step-00 = Unit 10's end: lit, furnished rooms, all lit the same.
3+; step-01 gives each room its own falloff — the Hall broad, the Vault a tight crypt.
44
55 org 32768
66
77 WALL_ATTR equ %01001000
88 FLOOR_ATTR equ %00001000
99 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+STATUE_ATTR equ %01001111
11+BANNER_ATTR equ %01001011
12+RUBBLE_ATTR equ %00001000
1313 MARK_ATTR equ %00001111
1414 THIEF equ %01001010
1515 WALL_BIT equ 6
...
144144 jr nz, .ft_row
145145 ret
146146
147+; ----------------------------------------------------------------------------
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).
151+; ----------------------------------------------------------------------------
147152 shade_for_cell:
153+ push bc
148154 ld a, (torch_col)
149155 cp NO_TORCH
150156 jr z, .sf_dark
...
165171 jr nc, .sf_max
166172 ld a, d
167173 .sf_max:
174+ ld e, a ; E = Chebyshev distance
175+ ld a, (current_room)
176+ ld c, a
177+ ld b, 0
178+ ld hl, room_falloff
179+ add hl, bc
180+ ld b, (hl) ; B = this room's falloff
181+ ld a, e
182+ inc b
183+.sf_shift:
184+ dec b
185+ jr z, .sf_clamp
168186 srl a
187+ jr .sf_shift
188+.sf_clamp:
169189 cp MAX_SHADE + 1
170190 jr c, .sf_done
171191 ld a, MAX_SHADE
172192 .sf_done:
193+ pop bc
173194 ret
174195 .sf_dark:
175196 ld a, MAX_SHADE
197+ pop bc
176198 ret
177199
178200 draw_room:
...
469491 add hl, de
470492 ret
471493
472-; ----------------------------------------------------------------------------
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.
475-; ----------------------------------------------------------------------------
476494 palette:
477495 defb '.'
478496 defw shade2_tile
...
502520 defw shade2_tile
503521 defw shade3_tile
504522 defw shade4_tile
523+
524+; One byte of mood per room: how slowly its light fades.
525+room_falloff:
526+ defb 2 ; Hall — broad, soft pool
527+ defb 1 ; Gallery — medium
528+ defb 0 ; Vault — tight pool, deep dark
505529
506530 rooms:
507531 defw room0_state
...
511535 defw room2_state
512536 defb NO_EXIT, 1, NO_EXIT, NO_EXIT
513537
514-; The Great Hall — torch above, banners flanking a statue, rubble at its feet.
538+; The Great Hall — broadly lit, a torch high in the wall.
515539 room0_template:
516540 defb "###############T################"
517541 defb "#..............................#"
...
538562 defb "#..............................#"
539563 defb "################################"
540564
541-; The Gallery — a banner on the west wall, a heap of rubble in the corner.
542565 room1_template:
543566 defb "###############.################"
544567 defb "#..............................#"
...
565588 defb "#..............................#"
566589 defb "###############T################"
567590
568-; The Vault — banners flanking the great altar.
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.
569593 room2_template:
570- defb "###############T################"
594+ defb "################################"
571595 defb "#..............................#"
572596 defb "#..............................#"
573597 defb "#..............................#"
...
577601 defb "#..............................#"
578602 defb "#..............................#"
579603 defb "#..............................#"
604+ defb "#.............#T##.............#"
580605 defb "#.............####.............#"
581- defb "B.............####.............#"
582- defb "#.............####.............B"
606+ defb "#.............####.............#"
583607 defb "#.............####.............#"
584608 defb "#..............................#"
585609 defb "#..............................#"
...
592616 defb "#..............................#"
593617 defb "###############.################"
594618
595-; ----------------------------------------------------------------------------
596-; Floor shade ramp.
597-; ----------------------------------------------------------------------------
598619 shade0_tile:
599620 defb %00000000
600621 defb %00100010
The complete program
; Shadowkeep — Unit 11: Mood through Constraint
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 gives each room its own falloff — the Hall broad, the Vault a tight crypt.

            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
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 — row in B, column in C. Distance to the torch, shifted by
; THIS ROOM's falloff, clamped. Bigger falloff = light fades slower = wider,
; softer pool. Preserves B and C (the draw loop needs them).
; ----------------------------------------------------------------------------
shade_for_cell:
            push    bc
            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:
            ld      e, a                ; E = Chebyshev distance
            ld      a, (current_room)
            ld      c, a
            ld      b, 0
            ld      hl, room_falloff
            add     hl, bc
            ld      b, (hl)             ; B = this room's falloff
            ld      a, e
            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_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:
            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

; One byte of mood per room: how slowly its light fades.
room_falloff:
            defb    2                   ; Hall — broad, soft pool
            defb    1                   ; Gallery — medium
            defb    0                   ; Vault — tight pool, deep dark

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 — broadly lit, a torch high in the wall.
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    "################################"

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 — a crypt. No wall torch; the only flame is on the altar itself, so
; with falloff 0 the light pools tight around it and the rest sinks to black.
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_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 the keep end to end and feel the rooms change under you — the broad light of the Hall, the medium Gallery, and the tight, fearful pocket of the Vault where the thief steps from black into a ring of altar-light:

East out of the broadly-lit Hall, through the Gallery's medium pool, and up into the Vault — where the light collapses to a tight ring on the altar and the thief stands in the dark below it. Same lighting engine in every room; only the falloff byte changes. Restraint is the technique.

Try this: re-light a room

Change a room's room_falloff and watch its character flip. Give the Hall a 0 and the welcoming chamber becomes a cramped, shadowed box. Give the Vault a 3 and the crypt floods with light and loses all its menace. You're not redrawing anything — you're tuning a feeling with a single digit.

Try this: a pitch-black room with one candle

Build a small room, no wall torches, a single T in a far corner, falloff 0. Almost the whole room is black; one corner glows. Drop a piece of treasure or a doorway just at the edge of the light, so the player has to step into the dark on faith to reach it. Darkness you have to enter is one of the oldest tricks in the genre.

Try this: contrast by neighbour

Put a bright room next to a dark one and a dark next to bright. Walking from the lit Hall into the black Vault feels darker than the Vault would on its own, because your eye arrives expecting light. Mood is relative — you compose the journey, not just the room.

When it's wrong, see why

  • Every room looks the same. shade_for_cell isn't reading room_falloff for the current room — check the index is current_room and the shift loop runs that many times.
  • A room is all black. Its falloff is low and its torch is far from the floor (or missing). With falloff 0 the light barely reaches; make sure there's a flame where you want the pool.
  • A room is flatly bright. Falloff is too high for the room's size, so even the far corners clamp to the lightest shades. Lower it, or move the torch.
  • The thief gets darker/lighter as he moves — flickering. Shade is baked per draw, not per frame; if it flickers, something is redrawing the room every frame. It should only redraw on entry.
  • The pool is square, not round. Still the Chebyshev distance — fine and characterful. Sum the axes for a diamond, or their squares for a circle, if you want softer pools.

Before and after

You started with a keep where every room was lit alike and finished with three rooms that feel different — the Hall open and welcoming, the Vault a tight, fearful crypt — without adding a thing. The lighting engine is exactly Unit 9's; the change is one falloff byte per room and the choice of where each flame sits. That's composition: not more features but restraint, using the Spectrum's two-colour cell and a dither ramp — the same limit that looked like a wall in Unit 1 — as the instrument. The keep stopped being uniformly lit and started having moods.

What you've learnt

  • Mood is a parameter. One byte of falloff per room turns the same lighting engine into welcoming, neutral, or fearful — composition, not new code.
  • Place the light with intent. Where the flame sits, and how fast it fades, decides where the eye goes. In the Vault the light is the altar; everything else is permission to be dark.
  • The constraint is the vocabulary. Two colours a cell and a dither ramp — the same limit that defined Unit 1 — is enough to make a room feel like somewhere. Restraint, not richness.
  • Atmosphere is relative. Rooms read against their neighbours; you compose the walk through the keep, not just each screen.

What's next

Each room can now hold a mood — but they were composed one at a time. In Unit 12, "A Keep with Character," we pull back and treat the whole keep as one composition: a consistent hand across every room, so the three chambers read as parts of a single place with a single atmosphere, not three separate experiments. It's the unit that turns "rooms with lighting" into "a keep you believe in" — the close of the Mood and Light sub-arc.