Files
zombie-fps/docs/KIOSK-ARCADE.md
T
enne2 4af402f8bf Add Alt+F to toggle fullscreen and window mode
New remappable action `toggle_fullscreen` (Alt+F) handled in main.gd::_input(),
so it works in the menu, during a match with the pointer captured and in the boss
sandbox; the event is marked handled so the key does not reach the rest of the game.

toggle_fullscreen() switches between WINDOW_MODE_FULLSCREEN and WINDOW_MODE_WINDOWED;
the state lives in main.fullscreen (initialised from the real window mode) instead of
querying the driver on every press, so the toggle is deterministic and testable. On
Web it is a no-op because fullscreen belongs to the page.

Desktop HUD hint updated. New tools/test_fullscreen_shortcut.gd: 8 checks (action and
key mapping, alternation, bare F inert, works mid-match without freeing the cursor).
Verified on a real display: window_get_mode 0 -> 3 -> 0.
2026-10-24 12:55:15 +02:00

132 lines
7.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` |
| `scripts/game_hud.gd` | promemoria dei comandi desktop aggiornato con `Alt+F` |
| `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) |
## 1c. Scorciatoia Alt+F (finestra / schermo intero)
* Azione in `project.godot`: `toggle_fullscreen` = `Alt+F` (rimappabile dal pannello
Input Map come le altre).
* Gestita in `main.gd::_input()`, quindi vale **sempre**: menu, partita (anche con il
cursore catturato) e sandbox del boss. L'evento viene marcato come gestito, così il
tasto non arriva al resto del gioco.
* `main.gd::toggle_fullscreen()` alterna `WINDOW_MODE_FULLSCREEN` e
`WINDOW_MODE_WINDOWED`; lo stato vive in `main.fullscreen`, inizializzato dalla
modalità reale della finestra. Nel browser non fa nulla (il fullscreen è della pagina).
* Verificato su display reale: `window_get_mode` 0 (finestra) → 3 (schermo intero) → 0.
* `tools/test_fullscreen_shortcut.gd`: 8 controlli (azione e tasto mappati, alternanza,
F da solo inerte, funzionamento in partita senza liberare il cursore).
## 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
"$G" --headless --path . --script res://tools/test_fullscreen_shortcut.gd # 8/8 PASS
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).