Arcade/kiosk name flow, free cursor in menu and portable Linux export
Every game over unlocks the profile and frees the name, so the menu always asks for a new player name: the field is empty at every boot, restart or game over and the game does not start without a name (no anonymous scores). The player identity is derived from the entered name (deterministic FNV-1a, 16 hex chars valid for the public API) and the profile keeps the best score per name. Backend untouched. Mouse: player._ready() captured the pointer before the menu existed, so the start screen was not clickable. The cursor now always starts visible; the capture happens only in player.begin_play(), called by start_game(). New `playing` flag gates Esc and the Web click capture to the actual match. Linux export preset: single-file x86_64 binary with the PCK embedded, no libstdc++ dependency, official 4.7.1 template (glibc >= 2.28). Notes and the export_presets.cfg `#`-is-not-a-comment trap documented in docs/KIOSK-ARCADE.md. Tests: profile 30/30, start screen 15/15 (cursor free at menu, captured only in game), boss 50/50, rebalance 14/14, touch 14 PASS, API 42/42, all other suites pass.
This commit is contained in:
+17
-12
@@ -27,11 +27,11 @@ File coinvolti:
|
||||
| `services/scores-api/docker-compose.yml` | compose (volume, limiti, log rotation) |
|
||||
| `tools/nginx-zombie-fps-api.conf` | snippet nginx del reverse proxy |
|
||||
| `scripts/leaderboard.gd` | client HTTP Godot (lettura + invio, retry) |
|
||||
| `scripts/player_profile.gd` | profilo locale: nome scelto, id giocatore, miglior punteggio |
|
||||
| `scripts/player_profile.gd` | profilo locale: nome dell'ultima partita, id giocatore, miglior punteggio per nome |
|
||||
| `scripts/start_screen.gd` | menu: nome, pulsante GIOCA, classifica |
|
||||
| `tools/test_scores_api.py` | 42 controlli sul servizio (funzionalità, validazione, limiti) |
|
||||
| `tools/test_leaderboard.gd` + `.sh` | 14 controlli sul client, contro un'API locale usa e getta |
|
||||
| `tools/test_player_profile.gd` | 26 controlli sul profilo (nome, id, riparazioni, migrazione dopo rinomina) |
|
||||
| `tools/test_player_profile.gd` | 30 controlli sul profilo (campo del nome sempre libero, id derivato dal nome, record per nome, riparazioni, migrazione dopo rinomina) |
|
||||
| `tools/test_start_screen.gd` | 10 controlli sulle regole del menu |
|
||||
|
||||
## 2. Contratto dell'API
|
||||
@@ -148,19 +148,24 @@ sudo nginx -t && sudo systemctl reload nginx # se -t fallisce: ripristinar
|
||||
|
||||
| Chiave | Contenuto |
|
||||
| --- | --- |
|
||||
| `zombie_fps_name` | nome scelto |
|
||||
| `zombie_fps_player_id` | id anonimo, 32 caratteri esadecimali |
|
||||
| `zombie_fps_best` | miglior punteggio locale |
|
||||
| `zombie_fps_locked` | `1` se il nome è già stato scelto |
|
||||
| `zombie_fps_name` | nome dell'ultima partita (solo come chiave del suo record) |
|
||||
| `zombie_fps_player_id` | id anonimo, 32 caratteri esadecimali, o 16 derivato dal nome |
|
||||
| `zombie_fps_best` | miglior punteggio del nome salvato |
|
||||
| `bests` (sezione del file `.cfg`) | miglior punteggio **per nome** |
|
||||
| `zombie_fps_locked` | sempre `0` in modalità arcade/kiosk |
|
||||
|
||||
* Sul Web si scrive **anche** in `localStorage` perché è la memoria del browser che
|
||||
sopravvive davvero alla ricarica della pagina (l'IndexedDB di Godot può non essere
|
||||
sincronizzata). Alla lettura i due archivi si fondono: l'id del giocatore resta
|
||||
stabile, così i punteggi vecchi restano collegati.
|
||||
* **Il nome, una volta scelto, non viene più richiesto**: se `locked` è attivo il menu
|
||||
mostra `giocatore: Mat` e il campo di testo non esiste proprio.
|
||||
* Un nome diverso da quello predefinito (`Giocatore`) vale come nome scelto, anche per
|
||||
i profili salvati dalle versioni precedenti.
|
||||
* **Modalità arcade/kiosk (`ALWAYS_ASK_NAME`)**: il nome non viene mai dato per scontato.
|
||||
Il campo del menu è sempre presente e arriva vuoto a ogni avvio, riavvio o game over;
|
||||
senza nome scritto la partita non parte (nessun punteggio anonimo in classifica).
|
||||
Il profilo conserva solo il **miglior punteggio per nome**. Dettagli: `docs/KIOSK-ARCADE.md`.
|
||||
* L'identità della partita nasce dal nome inserito: `player_id_for_name()` è un FNV-1a
|
||||
deterministico (16 caratteri esadecimali, valido per il server). Stesso nome = stesso id
|
||||
anche dopo un riavvio del kiosk, nomi diversi = identità diverse. Il backend non cambia.
|
||||
* Un `locked` arrivato da profili o dal browser di versioni precedenti viene ignorato.
|
||||
* **Riparazione automatica**: `str(null)` in GDScript produce la stringa `"null"`, e
|
||||
una versione precedente l'ha salvata come nome. Alla lettura i valori `null`,
|
||||
`undefined` e vuoti vengono scartati, il nome torna al predefinito e il blocco si
|
||||
@@ -198,9 +203,9 @@ sh tools/test_leaderboard.sh # 14 controlli
|
||||
|
||||
# profilo e menu (menu: serve un display)
|
||||
$HOME/.local/opt/godot-4.7.1/Godot_v4.7.1-stable_linux.x86_64 --headless --path . \
|
||||
--script res://tools/test_player_profile.gd # 26 controlli
|
||||
--script res://tools/test_player_profile.gd # 30 controlli
|
||||
$HOME/.local/opt/godot-4.7.1/Godot_v4.7.1-stable_linux.x86_64 --path . \
|
||||
--rendering-method gl_compatibility --script res://tools/test_start_screen.gd # 10 controlli
|
||||
--rendering-method gl_compatibility --script res://tools/test_start_screen.gd # 11 controlli
|
||||
```
|
||||
|
||||
In produzione si verifica a mano, con `curl`:
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# Modalità arcade/kiosk (macchina condivisa) e build Linux
|
||||
|
||||
## 1. Regola di flusso
|
||||
|
||||
Il gioco è installato su un computer condiviso. Il profilo precedente **non viene mai
|
||||
dato per scontato**:
|
||||
|
||||
* il campo del nome del menu è **sempre presente e vuoto** a ogni avvio e a ogni
|
||||
riavvio;
|
||||
* alla morte (game over) il profilo viene **sbloccato** e il nome liberato, così al
|
||||
ritorno al menu — `R` o pulsante `RICOMINCIA` — viene richiesto il nome della nuova
|
||||
partita;
|
||||
* senza nome scritto la partita **non parte** (`GIOCA` non emette `started`), perché un
|
||||
punteggio senza nome sarebbe anonimo in classifica.
|
||||
|
||||
Il `name lock` delle versioni precedenti (`zombie_fps_locked = 1` → menu con
|
||||
`giocatore: Mat` e nessun campo) è ignorato: `Profile.is_name_locked()` restituisce
|
||||
`false` con `ALWAYS_ASK_NAME`. I profili vecchi e i valori di `localStorage` arrivano
|
||||
ancora letti, ma solo come chiave del miglior punteggio.
|
||||
|
||||
**Identità** (backend invariato, API pubblica così com'è): `player_id` nasce dal nome
|
||||
inserito, `player_id_for_name()` = FNV-1a 32 bit con due semi → 16 caratteri
|
||||
esadecimali, accettato dal server (`8–64 [A-Za-z0-9_-]`). Stesso nome = stessa persona
|
||||
anche dopo un riavvio; nome diverso = identità diversa. Il record è tenuto **per nome**
|
||||
(`bests` nel file `.cfg`).
|
||||
|
||||
Permalink del flusso: `start_screen.gd::_start()` → `main.gd::start_game()` (id e best
|
||||
del nome, `leaderboard.player_id`, `player.begin_play()`) → morte → `main.gd::_on_player_died()`
|
||||
(`record_best` con il nome della partita appena conclusa, `unlock_name`, salvataggio,
|
||||
`reset_for_new_player()`) → reload → menu con campo vuoto.
|
||||
|
||||
## 1b. Cursore del mouse
|
||||
|
||||
Bug risolto: `player.gd::_ready()` catturava il pointer (`MOUSE_MODE_CAPTURED`) alla
|
||||
costruzione del giocatore, cioè **prima** del menu, quindi il cursore era già bloccato
|
||||
sulla schermata iniziale e i pulsanti non erano cliccabili.
|
||||
|
||||
* `player.gd::_ready()` imposta **sempre** `MOUSE_MODE_VISIBLE`.
|
||||
* La cattura avviene solo in `player.gd::begin_play()`, chiamata da
|
||||
`main.gd::start_game()` quando si preme GIOCA.
|
||||
* Nuovo flag `player.playing`: `Esc` (pausa) e il click sul Web catturano il cursore
|
||||
solo durante la partita, mai nel menu.
|
||||
* In modalità touch il cursore non viene catturato (serve ai controlli on-screen).
|
||||
* Alla morte `_die()` lo riporta visibile, quindi la schermata di morte è cliccabile.
|
||||
|
||||
### File toccati
|
||||
|
||||
| File | Modifica |
|
||||
| --- | --- |
|
||||
| `scripts/player_profile.gd` | `ALWAYS_ASK_NAME`, `player_id_for_name()`, `fnv1a32()`, `best_for_name()`, `record_best()`, `unlock_name()`; nome non presupposto, record per nome |
|
||||
| `scripts/start_screen.gd` | campo del nome sempre mostrato e vuoto, `reset_for_new_player()`, `GIOCA` bloccato a campo vuoto |
|
||||
| `scripts/main.gd` | `start_game()` deriva id e record dal nome e chiama `player.begin_play()`; `_on_player_died()` sblocca il profilo e invia il nome della partita conclusa |
|
||||
| `scripts/player.gd` | cursore visibile al menu, `begin_play()` con la cattura, flag `playing` |
|
||||
| `tools/test_player_profile.gd` | 30 controlli (campo libero, id derivato, record per nome, game over) |
|
||||
| `tools/test_start_screen.gd` | 15 controlli (campo sempre richiesto, partenza bloccata a campo vuoto, campo liberato dopo il game over, cursore libero al menu e catturato solo in partita) |
|
||||
|
||||
## 2. Verifiche eseguite
|
||||
|
||||
```bash
|
||||
G="$HOME/.local/opt/godot-4.7.1/Godot_v4.7.1-stable_linux.x86_64"
|
||||
"$G" --headless --path . --script res://tools/test_player_profile.gd # 30/30 PASS
|
||||
"$G" --path . --rendering-method gl_compatibility --script res://tools/test_start_screen.gd # 15/15 PASS
|
||||
# suite precedenti, riuscite dopo la modifica:
|
||||
"$G" --headless --path . --script res://tools/test_boss.gd # 50/50
|
||||
"$G" --headless --path . --script res://tools/test_rebalance_branding.gd # 14/14
|
||||
"$G" --headless --path . --script res://tools/test_spawn_windows.gd
|
||||
"$G" --headless --path . --script res://tools/test_wave_growth.gd
|
||||
"$G" --headless --path . --script res://tools/test_gameplay_improvements.gd
|
||||
python3 tools/test_scores_api.py # 42/42
|
||||
```
|
||||
|
||||
Nessuna modifica al backend: nessun cambiamento a nginx, Docker, API o database.
|
||||
La release Web pubblicata (`https://enne2.net/pub/zombie-fps/`) resta quella precedente
|
||||
finché non viene riesportata e rimpiantata con questa regola.
|
||||
|
||||
## 3. Build Linux portabile
|
||||
|
||||
`export_presets.cfg` → `preset.1` (name `Linux`, platform `Linux`):
|
||||
|
||||
```ini
|
||||
export_filter="all_resources"
|
||||
exclude_filter="addons/*,tools/*,docs/*,build/*,assets/**/*.mdl,assets/half_life_manifest.json"
|
||||
export_path="build/linux/work_death_balance.x86_64"
|
||||
binary_format/embed_pck=true
|
||||
binary_format/architecture="x86_64"
|
||||
```
|
||||
|
||||
* **PCK incorporato**: sul kiosk serve un solo file (`work_death_balance.x86_64`,
|
||||
80.517.080 byte = template 73.470.264 + PCK 7.046.808), verificato eseguendolo da
|
||||
una cartella vuota (`--headless -- --smoke` → `SMOKE: wave=0 alive=0 player_hp=100.0`).
|
||||
* **Dipendenze dinamica minime**: `libc`, `libm`, `librt`, `libpthread`, `libdl` +
|
||||
`ld-linux-x86-64.so.2`. Nessuna `libstdc++` (già linkata staticamente nel template).
|
||||
* **Baseline glibc del template ufficiale 4.7.1**: simboli richiesti fino a
|
||||
`GLIBC_2.28` (`readelf -V`) → il binario gira su glibc ≥ 2.28 (distro dal 2018 in poi).
|
||||
È il miglior risultato ottenibile senza ricompilare l'engine.
|
||||
* **musl statico scartato**: un binario completamente statico non carica le librerie
|
||||
grafiche, perché i driver OpenGL/Vulkan linkano glibc (funziona solo headless). Non è
|
||||
adatto a un gioco con finestra.
|
||||
* Runtime richiesto sul kiosk (non linkato, caricato a runtime): driver OpenGL
|
||||
(Compatibility) o Vulkan (Forward+, renderer nativo attuale), X11 **o** Wayland,
|
||||
ALSA o PulseAudio. Sono `dlopen`, quindi non rompono la portabilità del binario.
|
||||
* Attenzione: `#` dentro `export_presets.cfg` non è un commento per il parser → una riga
|
||||
commentata dopo `binary_format/embed_pck=true` ha fatto esportare il PCK separato.
|
||||
|
||||
### Avvio sul kiosk
|
||||
|
||||
La build nativa non ha un'origine di pagina, quindi la classifica va indicata
|
||||
esplicitamente (è la stessa API pubblica già in uso):
|
||||
|
||||
```bash
|
||||
./work_death_balance.x86_64 --api=https://enne2.net/api/zombie-fps/
|
||||
```
|
||||
|
||||
Senza `--api=` la classifica è disattiva di proposito (una build di prova non deve
|
||||
poter scrivere alla classifica pubblica).
|
||||
Reference in New Issue
Block a user