Quando il giocatore cade per un salto fallito:
- CH1: pitch sweep verso il basso (parte acuta, scende rapidamente)
- CH4: rumore medio-basso (colpo di caduta)
Effetto 'Wile E. Coyote': tono discendente + impatto.
L'ottimizzatore SDCC eliminava la variabile locale will_fall (dead code
elimination). Riscritto senza variabile intermedia: fall_offset viene
impostato direttamente nel ramo if/else. Usa DIV_REG (registro hardware
che cambia ogni ciclo) invece di rand() per casualita' migliore.
Il calcolo (60 - stamina) * 255 overflow su 8-bit (int del Game Boy).
Cast a uint16_t per il calcolo corretto. Ora il rischio di caduta
funziona: 30 stamina = 50% caduta, 0 stamina = 100% caduta.
Ultima meccanica di gameplay: se hai meno di 60 stamina puoi comunque
tentare il salto, ma rischi di cadere nell'abisso. La probabilita' di
caduta e' proporzionale a quanto stamina ti manca da 60:
- 60 stamina: 0% caduta (salto sicuro)
- 30 stamina: 50% caduta
- 0 stamina: 100% caduta
Se cadi: il giocatore salta verso la cella intermedia (vuoto) invece di
quella di atterraggio, poi cade velocemente verso il basso (animazione
fall_offset, 2x pixel per frame) e game over con la schermata di morte.
Differenza chiave: il salto sicuro atterra a distanza 2; il salto fallito
si ferma a distanza 1 (sopra l'abisso) e cade.
Quando il giocatore raggiunge la botola (vittoria/finale), invece di
pulire subito lo schermo, il giocatore scende di pochi pixel verso il
basso (descend_offset) per 30 frame prima che appaia la schermata
Going Deeper / finale.
- descend_offset: variabile globale, incrementata durante game_over_timer
- render.c: y_offset negativo (discesa graduale, >>1 = meta' pixel)
- player_logic.c: rimossa la dissolvenza immediata, avviata l'animazione
- engine.c: animazione durante il timer, max 12 step (~6 pixel)
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.
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.