Files
ascreamfromthedark-gb/doc/ai.md
T
enne2 e5c37c7c86 Aggiorna e approfondisci tutta la documentazione
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.
2026-06-24 20:42:35 +02:00

61 lines
2.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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**:
```c
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:
```c
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:
```c
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:
1. Entro `fog_radius` dal giocatore (Chebyshev).
2. On-screen (con wrap `& 255` + soglie fisiche `-8..168` / `-8..152`).
Altrimenti lo sprite è spostato offscreen `(0,0)`.