Riscritti tutti i file doc/ e README.md per riflettere lo stato attuale del gioco (8 livelli, multi-nemico, corsa, finale tragico, musica dedicata, fog scalabile, flush dinamico, claimed.png death screen, ecc.): - index.md: nuovo indice con riferimenti ai 7 capitoli aggiornati. - architecture.md: tabella moduli con righe, variabili chiave globals, pipeline asset, vincoli hardware (ROM/WRAM/VRAM/OAM). - graphics.md: proiezione iso, fog scalabile (fog_radius), auto-tiling multi-pass, flush dinamico 16-righe con wrap, HUD stamina + livello. - generation.md: DFS con stack WRAM, dimensioni crescenti 7->21, botola a distanza scalabile, spawn multi-nemico. - gameplay.md: DAS, camminata, corsa, salto, stamina scalabile, progressione 8 livelli, schermate (death/going deeper/finale). - ai.md: multi-entity (8 fantasmi), greedy, cooldown scalabile, hitbox, culling. - audio.md: 4 canali APU, 5 tracce (title 112/3canali, gameplay 96, gameover 128, finale 192 loop, going deeper 96), SFX. - AScreamFromTheDark_report.md: riscritto completamente (~200 righe), tutte le sezioni aggiornate, workaround storici, debiti tecnici. - README.md: intro, caratteristiche, soundtrack, architettura, test.
2.5 KiB
Intelligenza Artificiale (Fantasmi)
Multi-Entity (fino a 8 fantasmi)
Il gioco supporta fino a MAX_ENEMIES = 8 fantasmi coesistenti. Il numero attivo è num_enemies = level (1 al livello 1, 8 al livello 8). Lo stato di ciascun fantasma è in array indicizzati:
uint8_t enemy_lx[8], enemy_ly[8];
uint8_t enemy_is_moving[8], enemy_move_progress[8];
int8_t enemy_start_lx[8], enemy_start_ly[8];
int8_t enemy_target_lx[8], enemy_target_ly[8];
int16_t enemy_start_px[8], enemy_start_py[8];
int16_t enemy_target_px[8], enemy_target_py[8];
uint8_t enemy_cooldown[8];
Ogni fantasma usa 2 slot OAM a partire da 2 + i*2 (il player usa 0-1). Tutti condividono la stessa tile data (ghost sprite), con palette invertita (OBP1 = 0x1B, bianco su scuro).
Pathfinding: Greedy invece di A*
A* richiederebbe heap, nodi e calcoli pesanti — inammissibile su 4 MHz, soprattutto con 8 fantasmi. L'approccio Greedy è leggero:
Per ciascun fantasma, tra le 4 celle adiacenti calpestabili, sceglie quella che minimizza la distanza al quadrato verso il giocatore (dx² + dy²). No sqrt (i quadrati bastano per il confronto A < B ⇔ A² < B²).
Difetto voluto: il Greedy si incastra nei vicoli a U. In un labirinto con loop, questo diventa una dinamica di gioco — il giocatore astuto sfrutta il level design per seminare i fantasmi.
Attivazione e Cooldown Scalabile
Attivazione
Il fantasma insegue solo se entro il raggio di nebbia:
if (dist <= fog_radius) { // inizia a inseguire }
fog_radius = 2 (5×5) ai livelli 1-6, = 1 (3×3) ai livelli 7-8.
Cooldown
Dopo ogni passo (16 frame di LERP), il fantasma aspetta enemy_step_cooldown frame:
- Livello 1: 60 frame (1 secondo) — lento, gestibile.
- Livello 8: 11 frame — implacabile, quasi continuo.
I cooldown iniziali sono sfasati (enemy_step_cooldown + i*8) così i fantasmi non si muovono in sincrono.
Hitbox Pixel-Perfect
La morte non si basa sulla coincidenza di cella logica, ma sulla sovrapposizione dei pixel fisici renderizzati:
if (|p_px - enemy_px| < 12 && |p_py - enemy_py| < 6) {
game_over = 1; // sconfitta
}
Questo rende la morte "giusta" agli occhi del giocatore e permette scambi per il rotto della cuffia. Al primo fantasma che catcha il player, il loop si ferma (return).
Culling
I fantasmi sono renderizzati solo se:
- Entro
fog_radiusdal giocatore (Chebyshev). - On-screen (con wrap
& 255+ soglie fisiche-8..168/-8..152).
Altrimenti lo sprite è spostato offscreen (0,0).