Add technical report, update docs/README, remove orphaned victory asset
- 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.
This commit is contained in:
@@ -1,89 +1,87 @@
|
||||
# gioco carino (Cute Isometric Game Boy Maze)
|
||||
# A Scream from the Dark
|
||||
|
||||
Un motore isometrico sperimentale per Game Boy (DMG/CGB) scritto in C con **GBDK-2020**. Genera un labirinto casuale ed esegue il rendering in proiezione isometrica con autotiling dinamico, movimento interpolato del personaggio, e un set di test automatizzati headless tramite l'emulatore **PyBoy** ed elaborazione d'immagine con **OpenCV**.
|
||||
Un survival-horror procedurale in prospettiva isometrica per Game Boy (DMG/CGB), scritto in C con **GBDK-2020**. Sei imprigionato in un labirinto 7×7 generato casualmente, illuminato solo da un ristretto quadrato di visibilità. Un **fantasma** si nasconde nel buio e ti bracca non appena entri nel suo raggio visivo. L'unica via di fuga è la **botola** sul bordo sud della mappa: raggiungerla significa "sprofondare più giù" (*Going Deeper*) e generare un nuovo livello.
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Caratteristiche Principali
|
||||
## 🎮 Caratteristiche
|
||||
|
||||
* **Proiezione Isometrica**: Rendering di una mappa 2.5D su schermo Game Boy (tile 32x16 pixel disegnate a diamante).
|
||||
* **Labirinto Casuale Dinamico**: Algoritmo di backtracking iterativo (con stack in WRAM per evitare l'overflow dello stack hardware) che genera ogni volta un percorso 7x7 unico.
|
||||
* **Movimento Interpolato (Lerp)**: Spostamenti fluidi del personaggio e della telecamera interpolati linearmente su 16 tick macchina.
|
||||
* **Delayed Auto Shift (DAS)**: Controlli reattivi e confortevoli con delay iniziale di 12 frame e ripetizione ogni 6 frame per il movimento continuo tenendo premuto il D-Pad.
|
||||
* **Autotiling Intelligente**: Calcolo dei vicini (maschera a 4 bit) per selezionare automaticamente il bordo e gli angoli di ciascuna tessera del pavimento (16 varianti per ognuno dei 2 stili di pavimento alternati a scacchiera).
|
||||
* **Pipeline di Asset Ottimizzata**: Generazione procedurale di tile e sprite da script Python (`generate_assets.py`) e compilazione in C tramite `png2asset`.
|
||||
- Animazione completa dei movimenti del giocatore.
|
||||
- Transizione al Game Over alla collisione.
|
||||
- Menu Iniziale con copertina d'arte a 2-bit nativa per hardware Game Boy.
|
||||
- **Proiezione isometrica 2.5D**: rendering della mappa a diamante (tile 32×16 px) su schermo Game Boy, con autotiling dinamico.
|
||||
- **Labirinto casuale**: algoritmo DFS iterativo con stack in WRAM (per evitare overflow dello stack hardware) che genera un "perfect maze" 7×7 unico ad ogni partita, poi "rotto" con riaperture casuali al 15% per creare loop e percorsi alternativi.
|
||||
- **Fog of War**: visibilità 5×5 basata sulla distanza di Chebyshev, con affievolimento della luce sui bordi. Il fantasma si attiva solo quando entra in questo riquadro.
|
||||
- **Movimento interpolato (LERP)**: spostamenti fluidi del personaggio e della telecamera su 16 tick a punto fisso (no float).
|
||||
- **Delayed Auto-Shift (DAS)**: controlli alla Tetris — delay iniziale di 12 frame e ripetizione ogni 6 frame per il movimento continuo tenendo premuto il D-Pad.
|
||||
- **Salto evasivo con Stamina**: A+direzione scavalca il blocco adiacente atterrando 2 tile più in là (la cella intermedia deve essere un muro). Costa 60 punti stamina; la barra si ricarica di 1 punto al secondo.
|
||||
- **AI del fantasma**: pathfinding greedy con distanza al quadrato (niente sqrt, niente A*), cooldown di 1 secondo tra i passi, hitbox pixel-perfect (12×6 px) per una morte "giusta".
|
||||
- **Audio procedurale**: colonna sonora sintetizzata manipolando direttamente i registri APU via VBL interrupt (nessun campione).
|
||||
- **Schermate a tutto schermo**: copertina 2-bit nativa per il titolo, immagine "Going Deeper" per la vittoria, metasprite "GAME OVER" per la sconfitta.
|
||||
- **Test headless**: pipeline di verifica con PyBoy + OpenCV senza emulatore grafico.
|
||||
|
||||
### Soundtrack
|
||||
|
||||
Il gioco implementa la musica generata proceduralmente manipolando i registri audio hardware del Game Boy in `engine.c`.
|
||||
|
||||
1. **Title Theme:** Una solenne e misteriosa melodia composta da 32 battute basata sugli accordi La minore, Sol, Fa e Mi, che accompagna la schermata d'avvio a schermo intero.
|
||||
2. **Gameplay Theme:** Un battito ritmico ansioso che accelera progressivamente l'intensità all'inseguimento del fantasma, aumentando la pressione psicologica sul giocatore.
|
||||
3. **Game Over Theme:** Un concerto tragico polifonico di 128 note con percussioni sinfoniche, basso virtuoso e drammatica discesa melodica, per sottolineare la sconfitta del giocatore.
|
||||
|
||||
## Compilazione
|
||||
Assicurati di aver installato [GBDK-2020](https://github.com/gbdk-2020/gbdk-2020).
|
||||
1. **Title Theme**: solenne e misteriosa, 32 battute sugli accordi La minore, Sol, Fa e Mi.
|
||||
2. **Gameplay Theme**: battito ritmico ansioso ("eerie pulse") che accelera la tensione dell'inseguimento.
|
||||
3. **Game Over Theme**: concerto tragico polifonico di 128 note con percussioni (noise channel), basso virtuoso e drammatica discesa melodica.
|
||||
4. **Going Deeper**: melodia misteriosa discendente di 96 step (Am → Fmaj7 → Dm → E7 → C aug → abisso).
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Dettagli Tecnici
|
||||
## 🛠️ Dettagli tecnici
|
||||
|
||||
### Architettura dei File
|
||||
* [main.c](file:///home/enne2/dev/gameboy-hello/iso_test/main.c): Punto di ingresso del gioco. Esegue l'inizializzazione del ciclo macchina e del joypad e si sincronizza con l'intervallo di VBlank (`wait_vbl_done()`).
|
||||
* [engine.c](file:///home/enne2/dev/gameboy-hello/iso_test/engine.c) / [engine.h](file:///home/enne2/dev/gameboy-hello/iso_test/engine.h): Core del motore isometrico. Contiene la logica del labirinto, l'autotiling, lo scorrimento della telecamera, l'interpolazione del movimento e il supporto DAS.
|
||||
* [player.c](file:///home/enne2/dev/gameboy-hello/iso_test/player.c) / [player.h](file:///home/enne2/dev/gameboy-hello/iso_test/player.h) & [tiles.c](file:///home/enne2/dev/gameboy-hello/iso_test/tiles.c) / [tiles.h](file:///home/enne2/dev/gameboy-hello/iso_test/tiles.h): Asset grafici compilati (metasprite per il giocatore in 4 direzioni e varianti di piastrelle).
|
||||
* [generate_assets.py](file:///home/enne2/dev/gameboy-hello/iso_test/generate_assets.py): Script Python PIL per generare le texture di tiles e sprite a partire da matrici di pixel.
|
||||
### Architettura dei file
|
||||
- [`main.c`](src/main.c): entry point, loop VBL sincronizzato, macchina a stati `app_state` (0 = title, 1 = game).
|
||||
- [`engine.c`](src/engine.c) / [`engine.h`](src/engine.h): "direttore d'orchestra" — `title_init/update`, `engine_init`, `engine_update`.
|
||||
- [`globals.c`](src/globals.c) / [`globals.h`](src/globals.h): stato globale centralizzato (mappa, camera, player, enemy, stamina, `game_over`) per evitare dipendenze circolari tra moduli.
|
||||
- [`maze.c`](src/maze.c): generazione procedurale DFS + loop + posizionamento botola.
|
||||
- [`player_logic.c`](src/player_logic.c): input, DAS, state machine del movimento, salto, stamina.
|
||||
- [`enemy_logic.c`](src/enemy_logic.c): AI greedy, cooldown, rendering nemico, hitbox pixel-perfect.
|
||||
- [`render.c`](src/render.c): proiezione isometrica, fog of war, autotiling multi-pass, stamina UI, sprite player.
|
||||
- [`sound.c`](src/sound.c): sequencer audio via VBL interrupt, 4 tracce.
|
||||
- `tiles.c / player.c / enemy.c / gameover.c / next_level.c / stamina.c / title_bg.c`: asset generati da `png2asset`.
|
||||
- [`scripts/`](scripts/): generazione procedurale di tile/sprite (`generate_assets.py`, `generate_enemy.py`), quantizzazione immagini (`process_next_level.py`), test headless.
|
||||
|
||||
### Formato delle Coordinate Isometriche
|
||||
Le coordinate logiche del labirinto $2D$ `(lx, ly)` vengono convertite in coordinate dello schermo Game Boy `(iso_x, iso_y)` per i background tiles tramite la seguente formula:
|
||||
$$iso\_x = (lx - ly) \times 2 + 12$$
|
||||
$$iso\_y = (lx + ly) \times 1 + 2$$
|
||||
Questo permette di mappare una griglia ruotata a diamante perfettamente centrata nello spazio di visualizzazione.
|
||||
### Formato delle coordinate isometriche
|
||||
Le coordinate logiche `(lx, ly)` vengono convertite in coordinate schermo `(iso_x, iso_y)`:
|
||||
```
|
||||
iso_x = (lx - ly) * 2 + 12
|
||||
iso_y = (lx + ly) * 1 + 2
|
||||
```
|
||||
e in coordinate pixel fisiche per camera/collisione:
|
||||
```
|
||||
px = (lx - ly) * 16 + 96
|
||||
py = (lx + ly) * 8 + 16
|
||||
```
|
||||
Camera centrata: `scroll_x = px - 64`, `scroll_y = py - 72`.
|
||||
|
||||
Per un'analisi approfondita di codice, funzionalità e workaround storici, vedi [`doc/AScreamFromTheDark_report.md`](doc/AScreamFromTheDark_report.md).
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Requisiti e Build
|
||||
## 🚀 Requisiti e build
|
||||
|
||||
### Prerequisiti
|
||||
1. **GBDK-2020**: Installato in `/home/enne2/.local/gbdk`.
|
||||
2. **Python 3**: Con i seguenti pacchetti installati per i test e la rigenerazione degli asset:
|
||||
```bash
|
||||
pip install --user Pillow pyboy opencv-python numpy
|
||||
```
|
||||
1. **GBDK-2020** installato in `/home/enne2/.local/gbdk`.
|
||||
2. **Python 3** con i pacchetti per la rigenerazione degli asset e i test:
|
||||
```bash
|
||||
pip install --user Pillow pyboy opencv-python numpy
|
||||
```
|
||||
|
||||
### Compilazione
|
||||
Per compilare la ROM ed esportare `hello_iso.gb`:
|
||||
```bash
|
||||
make clean && make
|
||||
```
|
||||
Questo comando:
|
||||
1. Esegue `generate_assets.py` per creare `tiles.png` e `player.png`.
|
||||
2. Usa `png2asset` per convertire le PNG in codice sorgente C.
|
||||
3. Usa il compilatore `lcc` di GBDK per compilare e linkare tutti i file sorgente C nella ROM finale.
|
||||
1. Esegue gli script Python per creare `tiles.png`, `player.png`, `enemy.png`.
|
||||
2. Usa `png2asset` per convertire i PNG in sorgenti C.
|
||||
3. Usa il compilatore `lcc` di GBDK per compilare e linkare i sorgenti in `build/hello_iso.gb` (e `build/test_gameover.gb`).
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test e Analisi Automatica
|
||||
## 🧪 Test e analisi automatica
|
||||
|
||||
Il progetto include tre livelli di verifica headless per testare la correttezza logica e visuale senza avviare manualmente un emulatore grafico.
|
||||
1. **Screenshot** — `python3 scripts/test_pyboy.py`: avvia la ROM in PyBoy per 120 frame e salva `assets/hello_iso_gb.png`.
|
||||
2. **Test di movimento in WRAM** — `python3 scripts/test_movement.py`: legge la griglia del labirinto in WRAM (indirizzo risolto dinamicamente via `hello_iso.noi`) e simula pressioni direzionali verificando `player_lx/ly`.
|
||||
3. **Rilevamento glitch con OpenCV** — `python3 scripts/opencv_analyze_tiles.py`: esamina lo screenshot cercando disallineamenti o buchi neri tra le giunzioni isometriche.
|
||||
4. **ROM di test isolata** — `make build/test_gameover.gb`: renderizza solo player + metasprite GAME OVER per validare la schermata di sconfitta.
|
||||
|
||||
1. **Generazione Screenshot**:
|
||||
```bash
|
||||
python3 test_pyboy.py
|
||||
```
|
||||
Avvia la ROM in PyBoy per 120 frame e salva un'immagine `hello_iso_gb.png` del display.
|
||||
|
||||
2. **Test di Movimento in WRAM**:
|
||||
```bash
|
||||
python3 test_movement.py
|
||||
```
|
||||
Carica la ROM in PyBoy, legge lo stato della griglia del labirinto in WRAM (a partire dall'indirizzo `0xC0B1`) e simula la pressione dei tasti direzionali, verificando che la posizione del player (indirizzi `0xC4F4` e `0xC4F5`) cambi correttamente secondo le collisioni calcolate.
|
||||
|
||||
3. **Rilevamento Glitch con OpenCV**:
|
||||
```bash
|
||||
python3 opencv_analyze_tiles.py
|
||||
```
|
||||
Utilizza OpenCV per esaminare lo screenshot generato, cercando disallineamenti o buchi neri tra le giunzioni delle piastrelle isometriche, segnalando eventuali problemi di rendering.
|
||||
La documentazione tecnica dettagliata per modulo è in [`doc/`](doc/).
|
||||
Reference in New Issue
Block a user