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.
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.
| 1 | 1 | ; Gloaming — Unit 18: The Square Warms | |
| 2 | 2 | ; 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. | |
| 4 | 4 | | |
| 5 | 5 | org 32768 | |
| 6 | 6 | | |
| ... | |||
| 33 | 33 | PROMPT_ROW equ 14 | |
| 34 | 34 | FONT equ $3C00 | |
| 35 | 35 | | |
| 36 | - | SPEAKER equ %00010000 ; bit 4 of port $FE, border black | |
| 36 | + | SPEAKER equ %00010000 | |
| 37 | 37 | | |
| 38 | 38 | STATE_TITLE equ 0 | |
| 39 | 39 | STATE_PLAY equ 1 | |
| ... | |||
| 131 | 131 | ld bc, 767 | |
| 132 | 132 | ldir | |
| 133 | 133 | | |
| 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) | |
| 135 | 159 | ld b, 32 | |
| 136 | - | .iwt: | |
| 137 | - | ld (hl), WALL | |
| 160 | + | .wt: | |
| 161 | + | ld (hl), c | |
| 138 | 162 | inc hl | |
| 139 | - | djnz .iwt | |
| 140 | - | ld hl, $5AE0 | |
| 163 | + | djnz .wt | |
| 164 | + | ld hl, $5AE0 ; bottom wall (row 23) | |
| 141 | 165 | ld b, 32 | |
| 142 | - | .iwb: | |
| 143 | - | ld (hl), WALL | |
| 166 | + | .wb: | |
| 167 | + | ld (hl), c | |
| 144 | 168 | inc hl | |
| 145 | - | djnz .iwb | |
| 146 | - | ld hl, $5820 | |
| 169 | + | djnz .wb | |
| 170 | + | ld hl, $5820 ; sides, rows 1..23 | |
| 147 | 171 | ld b, 23 | |
| 148 | - | .iws: | |
| 149 | - | ld (hl), WALL | |
| 172 | + | .ws: | |
| 173 | + | ld (hl), c | |
| 150 | 174 | push hl | |
| 151 | 175 | ld de, 31 | |
| 152 | 176 | add hl, de | |
| 153 | - | ld (hl), WALL | |
| 177 | + | ld (hl), c | |
| 154 | 178 | pop hl | |
| 155 | 179 | ld de, 32 | |
| 156 | 180 | 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 | |
| 166 | 182 | ret | |
| 167 | 183 | | |
| 168 | - | ; ---------------------------------------------------------------------------- | |
| 169 | - | ; beep — B = cycles, C = pitch delay. Bit 4 of $FE is the speaker. | |
| 170 | - | ; ---------------------------------------------------------------------------- | |
| 171 | 184 | beep: | |
| 172 | 185 | di | |
| 173 | 186 | .bcyc: | |
| 174 | - | ld a, SPEAKER ; speaker out, border black | |
| 187 | + | ld a, SPEAKER | |
| 175 | 188 | out ($FE), a | |
| 176 | 189 | ld a, c | |
| 177 | 190 | .bd1: | |
| 178 | 191 | dec a | |
| 179 | 192 | jr nz, .bd1 | |
| 180 | - | xor a ; speaker back (and border black) | |
| 193 | + | xor a | |
| 181 | 194 | out ($FE), a | |
| 182 | 195 | ld a, c | |
| 183 | 196 | .bd2: | |
| ... | |||
| 189 | 202 | | |
| 190 | 203 | blip_lit: | |
| 191 | 204 | ld b, $20 | |
| 192 | - | ld c, $18 ; short, bright | |
| 205 | + | ld c, $18 | |
| 193 | 206 | jp beep | |
| 194 | 207 | | |
| 195 | 208 | blip_snuff: | |
| 196 | 209 | ld b, $1A | |
| 197 | - | ld c, $40 ; lower, colder | |
| 210 | + | ld c, $40 | |
| 198 | 211 | jp beep | |
| 199 | 212 | | |
| 200 | 213 | ; ---------------------------------------------------------------------------- | |
| ... | |||
| 246 | 259 | ret | |
| 247 | 260 | | |
| 248 | 261 | ; ---------------------------------------------------------------------------- | |
| 249 | - | ; player_step — now blips when a lamp lights. | |
| 262 | + | ; player_step — light a lamp: blip and warm the walls. | |
| 250 | 263 | ; ---------------------------------------------------------------------------- | |
| 251 | 264 | player_step: | |
| 252 | 265 | ld a, (lamp_col) | |
| ... | |||
| 317 | 330 | ld a, LAMP_LIT | |
| 318 | 331 | ld (under_lamp + 8), a | |
| 319 | 332 | 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 | |
| 321 | 335 | .pdrawn: | |
| 322 | 336 | call draw_lamp | |
| 323 | 337 | ret | |
| 324 | 338 | | |
| 325 | 339 | ; ---------------------------------------------------------------------------- | |
| 326 | - | ; draught_step — now blips a colder note when it snuffs a lamp. | |
| 340 | + | ; draught_step — snuff a lamp: blip and cool the walls. | |
| 327 | 341 | ; ---------------------------------------------------------------------------- | |
| 328 | 342 | draught_step: | |
| 329 | 343 | ld a, (draught_timer) | |
| ... | |||
| 394 | 408 | ld a, LAMP_UNLIT | |
| 395 | 409 | ld (under_draught + 8), a | |
| 396 | 410 | 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 | |
| 398 | 413 | .nosnuff: | |
| 399 | 414 | call draw_draught | |
| 400 | 415 | ret | |
| ... | |||
| 706 | 721 | ; ---------------------------------------------------------------------------- | |
| 707 | 722 | game_state: | |
| 708 | 723 | 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 | |
| 709 | 737 | | |
| 710 | 738 | lamp_data: | |
| 711 | 739 | 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:
Held at four lamps, the frame glows a deep magenta — the same square, a warmer hour:
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_wallsis not called where the count changes, or it is indexing the wrong table. Call it afterlight_pipandunlight_pip. - The wrong colour, or a crash, at full lamps. The ramp must have an entry for every count
0–8(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.