Replace the reused gameover music for game_over==3 with an original
192-step (24-chord) finale piece: a descending D-minor lament
(Dm-C-Bb-A7) that intensifies with high arpeggios (the 'scream'),
then plunges into the abyss as the bass descends to the lowest C and
fades to silence. Three channels: CH1 melody, CH2 bass, CH4 noise toll
(mid toll on chord changes, crash at the climax, deep toll in the abyss),
at a somber 14 frames/note.
- sound.c: finale_ch1_seq[192], finale_ch2_seq[192], play_finale_step(),
finale_music_timer/step, sequencer branch for game_over==3.
- sound.h: play_finale_step prototype.
Verified via PyBoy: all three APU channels active throughout the finale.
The finale text is now 'YOUR TORCH HAS / RUN OUT, / YOU ARE TRAPPED. /
JUST ANOTHER SCREAM / FROM THE DARK. / GAME OVER' — matching the
tragic dark theme of the game. BGP inverted for black background with
white text.
Maze size grows +2/level from 7x7 to 21x21 (cap MAX_MAP_SIZE=21, reached
at level 8). Additional difficulty axes scale with level:
- +1 ghost per level (up to MAX_ENEMIES=8); enemy state moved to arrays,
enemy_logic.c rewritten as a multi-entity loop (own AI, render at OAM
2+i*2, staggered cooldowns).
- Ghost cooldown shrinks: 60 -> ~11 frames between steps.
- Stamina recharge slows: 60 -> 144 frames per point.
- Fog tightens to 3x3 (fog_radius=1) from level 7.
Fog rendering uses a dynamic 16-row centered flush (with wrap) instead of
the fixed rows 2-17 / full 32x32, so the fog stays correct on big mazes
without the multi-frame stall of the full flush.
Game ends after level 8: reaching the hatch at level 8 sets game_over=3
(finale) instead of 2. Finale screen reloads the IBM font and writes
'YOU ESCAPED / THE DARKNESS / LEVEL 8 CLEARED / PRESS START' into the BG
map; START returns to the title (new game starts at level 1).
Verified via PyBoy: sizes 7..21, fog 2->1 at L7, enemies 1..8 (distinct,
on floor, can catch the player), cooldown/recharge scaling, L1 hatch ->
game_over=2, L8 hatch -> game_over=3, finale renders + START->title->L1.
The maze side length is now a runtime global 'map_size' (MAP_SIZE=7 at
level 1, +2 per level, capped at MAX_MAP_SIZE=17), while the maze array
is allocated with the MAX_MAP_SIZE bound. All modules (maze DFS, fog
rendering, player/enemy bounds, hatch and enemy-spawn placement) now use
map_size at runtime.
- globals: maze[MAX_MAP_SIZE][MAX_MAP_SIZE], map_size global, MAX_MAP_SIZE=17.
- maze.c: DFS on map_size; stack/valid arrays made static (WRAM) and sized
for the max maze to avoid hardware-stack overflow; hatch min distance
scales with map_size/2.
- engine.c: sets map_size from level before generate_maze; enemy spawn min
distance scales with map_size/2.
- render.c: flush the full 32x32 background map (the old rows 2-17
optimization broke for large mazes where the 5x5 fog window wraps outside
that range); dynamic bounds; signed/unsigned casts.
- player_logic/enemy_logic: dynamic map_size bounds.
Verified via PyBoy: sizes 7,9,11,13,15,17,17 across levels 1-7, each with
a hatch; rendering and movement intact at 17x17 and at 7x7.
On game over (defeat) + START, restart the maze at the same level the
player had reached instead of resetting to 1. Only starting a new game
from the title screen zeroes the counter. Victory still increments as
before.
Run (B + direction):
- player_logic.c: while holding B + direction with stamina >= 10, each tile
takes 8 frames instead of 16 (move_progress += 2, LERP >>4 unchanged) and
costs 10 stamina per tile. DAS_REPEAT_RUN=2 chains tiles fluidly.
- Falls back silently to normal walk when stamina < 10.
Level progression:
- New global 'level' (starts at 1). title->game resets to 1; reaching the
hatch (victory/Going Deeper) + START increments before engine_init (new
maze); defeat + START resets to 1.
- Top-left HUD 'L<n>' via 3 sprites (OAM 23-25) from new level.png asset
(glyphs L, 0-9), generated by scripts/generate_level.py and converted
with png2asset -keep_duplicate_tiles for predictable tile ordering.
- VRAM base aligned to an EVEN index (8x16 hardware ignores tile LSB) and
placed after the stamina load claim to avoid the shared BG tile block.
- update_level_display() called every frame; hides during game over/title.
Docs: README, doc/gameplay.md and the technical report updated.
The victory hatch (maze[y][x]==2) is now chosen among all walkable cells
at Chebyshev distance >= MIN_GOAL_DIST (3) from the player start (1,1),
instead of being constrained to the bottom row. This lets the goal appear
anywhere in the maze while always staying far from the start.
- maze.c: collect all floor cells with chebyshev(1,1) >= 3 and pick one
at random; fallback to the farthest floor cell if none qualifies.
- doc/generation.md + report: document the new placement rule.
- Add doc/AScreamFromTheDark_report.md: deep technical report covering
architecture, features, historical workarounds and known debt.
- Rewrite README.md to reflect the current 'A Scream from the Dark'
identity (ghost enemy, stamina/jump, fog of war, going-deeper victory)
instead of the stale 'gioco carino' copy with old file paths.
- Update doc/generation.md: goal is now a random south-edge hatch,
not the max-Manhattan-distance portal.
- Remove orphaned src/victory.{c,h} and its include in render.c;
the victory sequence was replaced by the full-screen next_level image.
- Regenerate tracked png2asset outputs to match current build.