Skip to content
Game 1 Unit 18 of 20 1 hr learning time

The Square Warms

Give the square a mood. As lamps light, the cold blue walls warm through to a dusk-magenta — a colour ramp keyed to your progress, repainted from a small table. The first taste of atmosphere, built from Unit 1's attribute writes.

90% of Gloaming

The game plays and sounds right. This unit adds the last layer to the play itself — atmosphere. As you light lamps, the square warms: the cold blue stone of the walls catches the spreading light and glows, brightening through to a dusk-magenta as the night is held back. Snuff a lamp and the stone cools again. The look of the place starts telling you how you are doing, before you ever glance at the tally.

Where we start

Unit 17's game — it plays, scores, threatens, wins, loses, and now makes a sound, but its walls have been the same flat blue since Unit 1. We make the frame respond to your progress.

Mood is just colour, driven by state

There is no new technique here — only the oldest one, used for feeling. We keep a small table mapping "lamps lit" to a wall colour, and whenever the count changes we repaint the frame in that colour:

wall_ramp:  blue → bright blue → magenta → bright magenta   (by lamps lit)

warm_walls reads lit_count, looks up the colour, and redraws the four edges of the frame in it — the same attribute writes you have made since the first unit, now reaching for atmosphere instead of structure. The inline wall-draw that lived in init_game becomes this one routine, called wherever the count changes: after a lamp lights (the stone catches the light) and after one is snuffed (the stone cools).

One bit you must not break

There is a constraint, and it is a good lesson in respecting your own systems. The walls have to stay solid — the collision test from Unit 7 reads the bottom bit of PAPER (bit 3) to decide what is a wall. So every colour in the ramp must keep that bit set. That limits the warm palette to PAPER colours whose low bit is 1: blue (1) and magenta (3), brightened for extra warmth. Warm the walls all you like — but never touch the bit that makes them walls. (Reach for red or yellow PAPER and the draught would start drifting straight through the frame.)

Milestone — warm the walls

We lift the wall-draw out of init_game into a warm_walls routine, add a wall_ramp table mapping each lamp count to a colour, and call warm_walls from the two places the count changes — the lamp-lit branch and the snuff branch. Every colour in the ramp keeps the collision bit set, so the walls stay solid as they warm.

Step 1: a wall-colour ramp keyed to lamps lit, repainted on change
+62-34
11 ; Gloaming — Unit 18: The Square Warms
22 ; Cumulative build; every step runs on its own. Narrative: the unit page.
3-; step-00 = Unit 17's end: a titled game with sound, walls a flat cold blue.
3+; step-01 warms the walls: a colour ramp keyed to lamps lit, repainted on change.
44
55 org 32768
66
...
3333 PROMPT_ROW equ 14
3434 FONT equ $3C00
3535
36-SPEAKER equ %00010000 ; bit 4 of port $FE, border black
36+SPEAKER equ %00010000
3737
3838 STATE_TITLE equ 0
3939 STATE_PLAY equ 1
...
131131 ld bc, 767
132132 ldir
133133
134- ld hl, $5820
134+ call warm_walls ; draw the frame at the current (cold) warmth
135+
136+ call draw_pips
137+ call draw_lives
138+ call draw_lamps
139+ call save_under
140+ call draw_lamp
141+ call save_draught
142+ call draw_draught
143+ ret
144+
145+; ----------------------------------------------------------------------------
146+; warm_walls — repaint the frame in the colour for the current lamp count.
147+; wall_ramp[lit_count] -> the wall attribute. Every entry keeps PAPER bit 0
148+; set, so the walls stay solid to collision.
149+; ----------------------------------------------------------------------------
150+warm_walls:
151+ ld a, (lit_count)
152+ ld e, a
153+ ld d, 0
154+ ld hl, wall_ramp
155+ add hl, de
156+ ld c, (hl) ; C = wall colour for this progress
157+
158+ ld hl, $5820 ; top wall (row 1)
135159 ld b, 32
136-.iwt:
137- ld (hl), WALL
160+.wt:
161+ ld (hl), c
138162 inc hl
139- djnz .iwt
140- ld hl, $5AE0
163+ djnz .wt
164+ ld hl, $5AE0 ; bottom wall (row 23)
141165 ld b, 32
142-.iwb:
143- ld (hl), WALL
166+.wb:
167+ ld (hl), c
144168 inc hl
145- djnz .iwb
146- ld hl, $5820
169+ djnz .wb
170+ ld hl, $5820 ; sides, rows 1..23
147171 ld b, 23
148-.iws:
149- ld (hl), WALL
172+.ws:
173+ ld (hl), c
150174 push hl
151175 ld de, 31
152176 add hl, de
153- ld (hl), WALL
177+ ld (hl), c
154178 pop hl
155179 ld de, 32
156180 add hl, de
157- djnz .iws
158-
159- call draw_pips
160- call draw_lives
161- call draw_lamps
162- call save_under
163- call draw_lamp
164- call save_draught
165- call draw_draught
181+ djnz .ws
166182 ret
167183
168-; ----------------------------------------------------------------------------
169-; beep — B = cycles, C = pitch delay. Bit 4 of $FE is the speaker.
170-; ----------------------------------------------------------------------------
171184 beep:
172185 di
173186 .bcyc:
174- ld a, SPEAKER ; speaker out, border black
187+ ld a, SPEAKER
175188 out ($FE), a
176189 ld a, c
177190 .bd1:
178191 dec a
179192 jr nz, .bd1
180- xor a ; speaker back (and border black)
193+ xor a
181194 out ($FE), a
182195 ld a, c
183196 .bd2:
...
189202
190203 blip_lit:
191204 ld b, $20
192- ld c, $18 ; short, bright
205+ ld c, $18
193206 jp beep
194207
195208 blip_snuff:
196209 ld b, $1A
197- ld c, $40 ; lower, colder
210+ ld c, $40
198211 jp beep
199212
200213 ; ----------------------------------------------------------------------------
...
246259 ret
247260
248261 ; ----------------------------------------------------------------------------
249-; player_step — now blips when a lamp lights.
262+; player_step — light a lamp: blip and warm the walls.
250263 ; ----------------------------------------------------------------------------
251264 player_step:
252265 ld a, (lamp_col)
...
317330 ld a, LAMP_LIT
318331 ld (under_lamp + 8), a
319332 call light_pip
320- call blip_lit ; a bright little note
333+ call blip_lit
334+ call warm_walls ; the stone catches the new light
321335 .pdrawn:
322336 call draw_lamp
323337 ret
324338
325339 ; ----------------------------------------------------------------------------
326-; draught_step — now blips a colder note when it snuffs a lamp.
340+; draught_step — snuff a lamp: blip and cool the walls.
327341 ; ----------------------------------------------------------------------------
328342 draught_step:
329343 ld a, (draught_timer)
...
394408 ld a, LAMP_UNLIT
395409 ld (under_draught + 8), a
396410 call unlight_pip
397- call blip_snuff ; a colder note
411+ call blip_snuff
412+ call warm_walls ; the stone cools as the dark returns
398413 .nosnuff:
399414 call draw_draught
400415 ret
...
706721 ; ----------------------------------------------------------------------------
707722 game_state:
708723 defb STATE_TITLE
724+
725+; wall colour by lamps lit (0..8). Cold blue -> bright blue -> magenta ->
726+; bright magenta. Every PAPER here has bit 0 set, so walls stay solid.
727+wall_ramp:
728+ defb %00001111 ; 0 — blue, cold
729+ defb %00001111 ; 1
730+ defb %01001111 ; 2 — BRIGHT blue
731+ defb %01001111 ; 3
732+ defb %00011111 ; 4 — magenta
733+ defb %00011111 ; 5
734+ defb %01011111 ; 6 — BRIGHT magenta
735+ defb %01011111 ; 7
736+ defb %01011111 ; 8 — fully warmed
709737
710738 lamp_data:
711739 defb 4, 3
The complete program
; Gloaming — Unit 18: The Square Warms
; Cumulative build; every step runs on its own. Narrative: the unit page.
; step-01 warms the walls: a colour ramp keyed to lamps lit, repainted on change.

            org     32768

COBBLE      equ     %00000001
WALL        equ     %00001111
LAMP_ATTR   equ     %01000111
LAMP_UNLIT  equ     %00000101
LAMP_LIT    equ     %01000110
WALL_BIT    equ     3

DRAUGHT_ATTR  equ   %01000101
DRAUGHT_SPEED equ   8

PIP_UNLIT   equ     %00101000
PIP_LIT     equ     %01110000
PIP_BASE    equ     $5800 + 12
NUM_LAMPS   equ     8

LIVES       equ     3
LIFE_PIP    equ     %01010000
LIFE_BASE   equ     $5800 + 28

MSG_ATTR    equ     %01000111
MSG_ROW     equ     11
WIN_COL     equ     7
LOSE_COL    equ     10
TITLE_COL   equ     12
PROMPT_COL  equ     10
TITLE_ROW   equ     8
PROMPT_ROW  equ     14
FONT        equ     $3C00

SPEAKER     equ     %00010000

STATE_TITLE equ     0
STATE_PLAY  equ     1
STATE_WIN   equ     2
STATE_LOSE  equ     3

START_COL   equ     15
START_ROW   equ     11
DRAUGHT_COL0 equ    18
DRAUGHT_ROW0 equ    3

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

; ============================================================================
; SETUP.
; ============================================================================
start:
            ld      a, 0
            out     ($FE), a
            ld      a, STATE_TITLE
            ld      (game_state), a
            call    draw_title_screen
            im      1
            ei

main_loop:
            halt
            ld      a, (game_state)
            cp      STATE_TITLE
            jr      z, .do_title
            cp      STATE_PLAY
            jr      z, .do_play
            jr      main_loop
.do_title:
            call    title_step
            jr      main_loop
.do_play:
            call    play_step
            jr      main_loop

title_step:
            ld      bc, KEYS_SPACE
            in      a, (c)
            bit     0, a
            ret     nz
            call    init_game
            ld      a, STATE_PLAY
            ld      (game_state), a
            ret

play_step:
            call    player_step
            ld      a, (game_state)
            cp      STATE_PLAY
            ret     nz
            call    draught_step
            ld      a, (game_state)
            cp      STATE_PLAY
            ret     nz
            ld      a, (lit_count)
            cp      NUM_LAMPS
            ret     nz
            call    draw_win_screen
            ld      a, STATE_WIN
            ld      (game_state), a
            ret

init_game:
            xor     a
            ld      (lit_count), a
            ld      a, LIVES
            ld      (lives), a
            ld      a, START_COL
            ld      (lamp_col), a
            ld      a, START_ROW
            ld      (lamp_row), a
            ld      a, DRAUGHT_COL0
            ld      (draught_col), a
            ld      a, DRAUGHT_ROW0
            ld      (draught_row), a
            ld      a, 1
            ld      (draught_dx), a
            ld      (draught_dy), a
            ld      a, DRAUGHT_SPEED
            ld      (draught_timer), a

            call    clear_bitmap

            ld      hl, $5800
            ld      de, $5801
            ld      (hl), COBBLE
            ld      bc, 767
            ldir

            call    warm_walls          ; draw the frame at the current (cold) warmth

            call    draw_pips
            call    draw_lives
            call    draw_lamps
            call    save_under
            call    draw_lamp
            call    save_draught
            call    draw_draught
            ret

; ----------------------------------------------------------------------------
; warm_walls — repaint the frame in the colour for the current lamp count.
;   wall_ramp[lit_count] -> the wall attribute. Every entry keeps PAPER bit 0
;   set, so the walls stay solid to collision.
; ----------------------------------------------------------------------------
warm_walls:
            ld      a, (lit_count)
            ld      e, a
            ld      d, 0
            ld      hl, wall_ramp
            add     hl, de
            ld      c, (hl)             ; C = wall colour for this progress

            ld      hl, $5820           ; top wall (row 1)
            ld      b, 32
.wt:
            ld      (hl), c
            inc     hl
            djnz    .wt
            ld      hl, $5AE0           ; bottom wall (row 23)
            ld      b, 32
.wb:
            ld      (hl), c
            inc     hl
            djnz    .wb
            ld      hl, $5820           ; sides, rows 1..23
            ld      b, 23
.ws:
            ld      (hl), c
            push    hl
            ld      de, 31
            add     hl, de
            ld      (hl), c
            pop     hl
            ld      de, 32
            add     hl, de
            djnz    .ws
            ret

beep:
            di
.bcyc:
            ld      a, SPEAKER
            out     ($FE), a
            ld      a, c
.bd1:
            dec     a
            jr      nz, .bd1
            xor     a
            out     ($FE), a
            ld      a, c
.bd2:
            dec     a
            jr      nz, .bd2
            djnz    .bcyc
            ei
            ret

blip_lit:
            ld      b, $20
            ld      c, $18
            jp      beep

blip_snuff:
            ld      b, $1A
            ld      c, $40
            jp      beep

; ----------------------------------------------------------------------------
; Screens.
; ----------------------------------------------------------------------------
draw_title_screen:
            call    clear_bitmap
            ld      hl, $5800
            ld      de, $5801
            ld      (hl), %00000000
            ld      bc, 767
            ldir
            ld      hl, title_text
            ld      b, TITLE_ROW
            ld      c, TITLE_COL
            call    print_string
            ld      hl, prompt_text
            ld      b, PROMPT_ROW
            ld      c, PROMPT_COL
            call    print_string
            ret

draw_win_screen:
            call    restore_under
            ld      hl, win_text
            ld      b, MSG_ROW
            ld      c, WIN_COL
            call    print_string
            ret

draw_lose_screen:
            ld      hl, $5800
            ld      de, $5801
            ld      (hl), %00000000
            ld      bc, 767
            ldir
            ld      hl, lose_text
            ld      b, MSG_ROW
            ld      c, LOSE_COL
            call    print_string
            ret

clear_bitmap:
            ld      hl, $4000
            ld      de, $4001
            ld      (hl), 0
            ld      bc, 6143
            ldir
            ret

; ----------------------------------------------------------------------------
; player_step — light a lamp: blip and warm the walls.
; ----------------------------------------------------------------------------
player_step:
            ld      a, (lamp_col)
            ld      (tcol), a
            ld      a, (lamp_row)
            ld      (trow), a

            ld      bc, KEYS_OP
            in      a, (c)
            bit     1, a
            jr      z, .pleft
            bit     0, a
            jr      z, .pright
            ld      bc, KEYS_Q
            in      a, (c)
            bit     0, a
            jr      z, .pup
            ld      bc, KEYS_A
            in      a, (c)
            bit     0, a
            jr      z, .pdown
            ret

.pleft:
            ld      hl, tcol
            dec     (hl)
            jr      .pmove
.pright:
            ld      hl, tcol
            inc     (hl)
            jr      .pmove
.pup:
            ld      hl, trow
            dec     (hl)
            jr      .pmove
.pdown:
            ld      hl, trow
            inc     (hl)
.pmove:
            ld      a, (trow)
            ld      b, a
            ld      a, (tcol)
            ld      c, a
            call    wall_at
            ret     nz

            ld      a, (tcol)
            ld      hl, draught_col
            cp      (hl)
            jr      nz, .pcommit
            ld      a, (trow)
            ld      hl, draught_row
            cp      (hl)
            jr      nz, .pcommit
            call    lose_life
            ret

.pcommit:
            call    restore_under
            ld      a, (tcol)
            ld      (lamp_col), a
            ld      a, (trow)
            ld      (lamp_row), a
            call    save_under
            ld      a, (under_lamp + 8)
            cp      LAMP_UNLIT
            jr      nz, .pdrawn
            ld      a, LAMP_LIT
            ld      (under_lamp + 8), a
            call    light_pip
            call    blip_lit
            call    warm_walls          ; the stone catches the new light
.pdrawn:
            call    draw_lamp
            ret

; ----------------------------------------------------------------------------
; draught_step — snuff a lamp: blip and cool the walls.
; ----------------------------------------------------------------------------
draught_step:
            ld      a, (draught_timer)
            dec     a
            ld      (draught_timer), a
            ret     nz
            ld      a, DRAUGHT_SPEED
            ld      (draught_timer), a

            ld      a, (draught_col)
            ld      b, a
            ld      a, (draught_dx)
            add     a, b
            ld      c, a
            ld      a, (draught_row)
            ld      b, a
            call    wall_at
            jr      z, .hok
            ld      a, (draught_dx)
            neg
            ld      (draught_dx), a
.hok:
            ld      a, (draught_row)
            ld      b, a
            ld      a, (draught_dy)
            add     a, b
            ld      b, a
            ld      a, (draught_col)
            ld      c, a
            call    wall_at
            jr      z, .vok
            ld      a, (draught_dy)
            neg
            ld      (draught_dy), a
.vok:
            ld      a, (draught_col)
            ld      b, a
            ld      a, (draught_dx)
            add     a, b
            ld      (dtcol), a
            ld      a, (draught_row)
            ld      b, a
            ld      a, (draught_dy)
            add     a, b
            ld      (dtrow), a

            ld      a, (dtcol)
            ld      hl, lamp_col
            cp      (hl)
            jr      nz, .dmove
            ld      a, (dtrow)
            ld      hl, lamp_row
            cp      (hl)
            jr      nz, .dmove
            call    lose_life
            ret

.dmove:
            call    restore_draught
            ld      a, (dtcol)
            ld      (draught_col), a
            ld      a, (dtrow)
            ld      (draught_row), a
            call    save_draught
            ld      a, (under_draught + 8)
            cp      LAMP_LIT
            jr      nz, .nosnuff
            ld      a, LAMP_UNLIT
            ld      (under_draught + 8), a
            call    unlight_pip
            call    blip_snuff
            call    warm_walls          ; the stone cools as the dark returns
.nosnuff:
            call    draw_draught
            ret

lose_life:
            ld      a, (lives)
            dec     a
            ld      (lives), a
            ld      e, a
            ld      d, 0
            ld      hl, LIFE_BASE
            add     hl, de
            ld      (hl), COBBLE
            ld      a, (lives)
            or      a
            jr      z, .gone
            call    restore_under
            ld      a, START_COL
            ld      (lamp_col), a
            ld      a, START_ROW
            ld      (lamp_row), a
            call    save_under
            call    draw_lamp
            ret
.gone:
            call    draw_lose_screen
            ld      a, STATE_LOSE
            ld      (game_state), a
            ret

; ----------------------------------------------------------------------------
; print_string / print_char.
; ----------------------------------------------------------------------------
print_string:
.ps:
            ld      a, (hl)
            cp      $FF
            ret     z
            push    hl
            push    bc
            call    print_char
            pop     bc
            pop     hl
            inc     hl
            inc     c
            jr      .ps

print_char:
            ld      l, a
            ld      h, 0
            add     hl, hl
            add     hl, hl
            add     hl, hl
            ld      de, FONT
            add     hl, de
            ex      de, hl
            push    de
            call    attr_addr_cr
            ld      (hl), MSG_ATTR
            call    scr_addr_cr
            pop     de
            ld      b, 8
.pc:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .pc
            ret

; ----------------------------------------------------------------------------
; light_pip / unlight_pip / draw_pips / draw_lives.
; ----------------------------------------------------------------------------
light_pip:
            ld      a, (lit_count)
            ld      e, a
            ld      d, 0
            inc     a
            ld      (lit_count), a
            ld      hl, PIP_BASE
            add     hl, de
            ld      (hl), PIP_LIT
            ret

unlight_pip:
            ld      a, (lit_count)
            dec     a
            ld      (lit_count), a
            ld      e, a
            ld      d, 0
            ld      hl, PIP_BASE
            add     hl, de
            ld      (hl), PIP_UNLIT
            ret

draw_pips:
            ld      hl, PIP_BASE
            ld      b, NUM_LAMPS
            ld      a, PIP_UNLIT
.dp:
            ld      (hl), a
            inc     hl
            djnz    .dp
            ret

draw_lives:
            ld      hl, LIFE_BASE
            ld      b, LIVES
            ld      a, LIFE_PIP
.dlv:
            ld      (hl), a
            inc     hl
            djnz    .dlv
            ret

; ----------------------------------------------------------------------------
; draw_lamps / draw_lantern.
; ----------------------------------------------------------------------------
draw_lamps:
            ld      hl, lamp_data
.next:
            ld      a, (hl)
            cp      $FF
            ret     z
            ld      c, a
            inc     hl
            ld      b, (hl)
            inc     hl
            push    hl
            call    draw_lantern
            pop     hl
            jr      .next

draw_lantern:
            call    attr_addr_cr
            ld      (hl), LAMP_UNLIT
            call    scr_addr_cr
            ld      de, lantern
            ld      b, 8
.dlt:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .dlt
            ret

; ----------------------------------------------------------------------------
; scr_addr_cr / attr_addr_cr / wall_at.
; ----------------------------------------------------------------------------
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

wall_at:
            call    attr_addr_cr
            bit     WALL_BIT, (hl)
            ret

; ----------------------------------------------------------------------------
; The lamplighter's save / restore / draw.
; ----------------------------------------------------------------------------
pos_bc:
            ld      a, (lamp_row)
            ld      b, a
            ld      a, (lamp_col)
            ld      c, a
            ret

save_under:
            call    pos_bc
            call    scr_addr_cr
            ld      de, under_lamp
            ld      b, 8
.su:
            ld      a, (hl)
            ld      (de), a
            inc     de
            inc     h
            djnz    .su
            call    pos_bc
            call    attr_addr_cr
            ld      a, (hl)
            ld      (under_lamp + 8), a
            ret

restore_under:
            call    pos_bc
            call    scr_addr_cr
            ld      de, under_lamp
            ld      b, 8
.ru:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .ru
            call    pos_bc
            call    attr_addr_cr
            ld      a, (under_lamp + 8)
            ld      (hl), a
            ret

draw_lamp:
            call    pos_bc
            call    attr_addr_cr
            ld      (hl), LAMP_ATTR
            call    pos_bc
            call    scr_addr_cr
            ld      de, lamplighter
            ld      b, 8
.dl:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .dl
            ret

; ----------------------------------------------------------------------------
; The draught's save / restore / draw.
; ----------------------------------------------------------------------------
dpos_bc:
            ld      a, (draught_row)
            ld      b, a
            ld      a, (draught_col)
            ld      c, a
            ret

save_draught:
            call    dpos_bc
            call    scr_addr_cr
            ld      de, under_draught
            ld      b, 8
.sd:
            ld      a, (hl)
            ld      (de), a
            inc     de
            inc     h
            djnz    .sd
            call    dpos_bc
            call    attr_addr_cr
            ld      a, (hl)
            ld      (under_draught + 8), a
            ret

restore_draught:
            call    dpos_bc
            call    scr_addr_cr
            ld      de, under_draught
            ld      b, 8
.rd:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .rd
            call    dpos_bc
            call    attr_addr_cr
            ld      a, (under_draught + 8)
            ld      (hl), a
            ret

draw_draught:
            call    dpos_bc
            call    attr_addr_cr
            ld      (hl), DRAUGHT_ATTR
            call    dpos_bc
            call    scr_addr_cr
            ld      de, draught_glyph
            ld      b, 8
.dd:
            ld      a, (de)
            ld      (hl), a
            inc     de
            inc     h
            djnz    .dd
            ret

; ----------------------------------------------------------------------------
; Data.
; ----------------------------------------------------------------------------
game_state:
            defb    STATE_TITLE

; wall colour by lamps lit (0..8). Cold blue -> bright blue -> magenta ->
; bright magenta. Every PAPER here has bit 0 set, so walls stay solid.
wall_ramp:
            defb    %00001111       ; 0 — blue, cold
            defb    %00001111       ; 1
            defb    %01001111       ; 2 — BRIGHT blue
            defb    %01001111       ; 3
            defb    %00011111       ; 4 — magenta
            defb    %00011111       ; 5
            defb    %01011111       ; 6 — BRIGHT magenta
            defb    %01011111       ; 7
            defb    %01011111       ; 8 — fully warmed

lamp_data:
            defb    4, 3
            defb    27, 3
            defb    9, 7
            defb    22, 7
            defb    6, 15
            defb    25, 15
            defb    13, 20
            defb    18, 20
            defb    $FF

lamp_col:
            defb    START_COL
lamp_row:
            defb    START_ROW
tcol:
            defb    0
trow:
            defb    0
lit_count:
            defb    0
lives:
            defb    LIVES

draught_col:
            defb    DRAUGHT_COL0
draught_row:
            defb    DRAUGHT_ROW0
draught_dx:
            defb    1
draught_dy:
            defb    1
draught_timer:
            defb    DRAUGHT_SPEED
dtcol:
            defb    0
dtrow:
            defb    0

under_lamp:
            defb    0, 0, 0, 0, 0, 0, 0, 0, 0
under_draught:
            defb    0, 0, 0, 0, 0, 0, 0, 0, 0

lamplighter:
            defb    %00111100
            defb    %00111100
            defb    %00011000
            defb    %01111110
            defb    %00011000
            defb    %00011000
            defb    %00100100
            defb    %01000010

lantern:
            defb    %00011000
            defb    %00100100
            defb    %01111110
            defb    %01111110
            defb    %01011010
            defb    %01111110
            defb    %01111110
            defb    %00111100

draught_glyph:
            defb    %00000000
            defb    %00111100
            defb    %01111110
            defb    %11111111
            defb    %11111111
            defb    %01111110
            defb    %00111100
            defb    %00000000

title_text:
            defb    "GLOAMING"
            defb    $FF
prompt_text:
            defb    "PRESS SPACE"
            defb    $FF
win_text:
            defb    "THE NIGHT IS HELD"
            defb    $FF
lose_text:
            defb    "NIGHT FALLS"
            defb    $FF

            end     start

Start cold and blue, then light lamps and watch the frame catch the warmth — the walls step from blue to bright blue and on to magenta as the count climbs:

From a cold blue square, four lamps are lit in turn. The walls warm with the count — blue while one or two burn, bright blue at two, magenta at four — each repaint triggered the instant a lamp catches. The draught drifts through, leaving the lit lamps alone.

Held at four lamps, the frame glows a deep magenta — the same square, a warmer hour:

The walled square with the walls glowing magenta, four lamps lit gold, the lamplighter at the left, and the tally part-filled at the top.
Four lamps lit, and the walls — blue and cold since Unit 1 — warmed to magenta. The colour is read straight from wall_ramp[lit_count], so the frame is an at-a-glance readout of how the night is going.

When it's wrong, see why

A cosmetic layer over a live mechanic fails in instructive ways:

  • The walls stop blocking — sprites pass through them. A ramp colour has a PAPER whose bottom bit is clear. Keep every PAPER odd (1, 3, 5, 7).
  • The walls never change colour. warm_walls is not called where the count changes, or it is indexing the wrong table. Call it after light_pip and unlight_pip.
  • The wrong colour, or a crash, at full lamps. The ramp must have an entry for every count 08 (nine entries). Indexing past the end reads junk.
  • A visible flicker as the frame repaints. Repainting ~110 cells is well within a frame; if you see tearing, you are repainting every frame instead of only when the count changes.

Before and after

You started with a frame that looked the same whether you were winning or losing and finished with one that shows your progress in its colour — and the whole effect is the attribute write from Unit 1, pointed at a ramp table. Mood turned out not to be a new system but an old one used for feeling: a byte of state, a lookup, a repaint on change. The one discipline it demanded — keep the collision bit set — is the real lesson: a cosmetic change must not quietly break a mechanic.

Try this: a different mood

Edit wall_ramp. You are free to use any PAPER whose bottom bit is set — blue (1), magenta (3), cyan (5), white (7) — plus the BRIGHT bit. Try a cold-to-icy ramp (blue → bright cyan → white) for a frostier night, or hold the cold longer and only warm in the final few lamps for a sudden dawn. The mood is yours; keep every PAPER odd.

Try this: warm the sky too

The border has no collision to respect, so it is a free second canvas. Add a border_ramp table and out ($FE), a the border colour alongside the walls — the night sky lifting as the square fills. (Keep it off blue so it does not blur into the walls.) Two surfaces warming together reads as a real change of hour.

Try this: break it on purpose

Put a red PAPER (%00010111) in the ramp and light enough lamps to reach it. The walls turn red — and the draught sails straight through the frame and off the screen, because red's PAPER bit 0 is clear, so the collision no longer sees a wall. It is a vivid lesson: a cosmetic change quietly violated an invariant. Then put it back.

What you've learnt

  • Mood is colour driven by state — the attribute writes from Unit 1, reaching for atmosphere.
  • A ramp table maps progress to appearance; repaint only when the state changes.
  • Respect your invariants: the walls must stay solid, so the ramp keeps the collision bit set — a cosmetic change must not break a mechanic.
  • Atmosphere is feedback you feel before you read it.

What's next

Gloaming is, by every measure, a finished little game — it plays, scores, threatens, sounds, and sets a mood. Only one thing keeps it from being a game you sit and play in a loop: it ends and stays ended. In Unit 19, "Again", we close that loop — win or lose, a key press returns you to the title for another go — and in doing so make sure a replayed game starts truly fresh. The state machine comes full circle.