Files
zombie-fps/docs/KIOSK-ARCADE.md
T
enne2 cb14fd5400 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.
2026-10-24 12:50:03 +02:00

116 lines
6.2 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.
# 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).