diff --git a/docs/HIGHSCORES.md b/docs/HIGHSCORES.md index ec9d39e..8e9a063 100644 --- a/docs/HIGHSCORES.md +++ b/docs/HIGHSCORES.md @@ -125,13 +125,17 @@ sudo nginx -t && sudo systemctl reload nginx # se -t fallisce: ripristinar ## 5. Client Godot -* **Base URL** (`scripts/leaderboard.gd`, `_resolve_base()`): - 1. argomento `--api=http://host:porta` (test); - 2. su Web: `window.location.origin + /api/zombie-fps`; - 3. altrimenti **niente**: la classifica resta disattivata con - `DISABLED_REASON = "classifica non attiva in questa build"`. - Regola importante: una build desktop di prova **non deve** poter inviare punteggi - alla classifica pubblica (è già successo: ci sono finite righe `Test`). +* **Base URL** (`scripts/leaderboard.gd`, `resolve_base()`): + 1. argomento `--api=http://host:porta` (test o servizio alternativo); + 2. argomento `--offline` / `--no-api` → classifica **spenta** (partite di prova); + 3. su Web: `window.location.origin + /api/zombie-fps`; + 4. altrimenti il servizio pubblico `DEFAULT_API_BASE = + https://enne2.net/api/zombie-fps`. + La classifica è quindi **attiva di default**: non serve nesuna configurazione né + argomento per leggere e inviare punteggi. L'opt-out esiste solo per le partite di + prova, che non devono finire nella classifica pubblica (è già successo: ci sono + finite righe `Test`). Le suite di test girano con `--script` e restano spente a + meno di un `--api=` esplicito. * Due nodi `HTTPRequest` separati (lettura e invio non si sovrappongono); timeout 12 s, `body_size_limit` 256 KB / 64 KB. * Retry su 429/5xx/errore di trasporto: fino a 3 tentativi, 6 s di pausa, **stesso @@ -223,4 +227,4 @@ curl -s "https://enne2.net/api/zombie-fps/scores?limit=5" | `classifica non disponibile (rete non disponibile)` nel menu | servizio spento o nginx non ricaricato: `docker ps`, `curl 127.0.0.1:8791/health`, `sudo nginx -t` | | punteggio non inviato (`429`) | limite di frequenza per IP (20/ora, 5 s tra invii); i retry riusano lo stesso `submission_id`, quindi non raddoppiano | | la pagina resta su una release vecchia | `index.html` è servito con `cache-control: no-cache`; forzare il ricaricamento della pagina | -| righe di prova nella classifica pubblica | una build desktop ha inviato senza `--api=`: verificare `leaderboard.disabled` (test `test_start_screen.gd`) | +| righe di prova nella classifica pubblica | la classifica è attiva di default: le partite di prova vanno lanciate con `-- --offline` (verificare `leaderboard.disabled`, test `test_start_screen.gd`) | diff --git a/scripts/leaderboard.gd b/scripts/leaderboard.gd index 3e2256a..1545eef 100644 --- a/scripts/leaderboard.gd +++ b/scripts/leaderboard.gd @@ -3,8 +3,12 @@ extends Node ## ## - Web: stessa origine della pagina (window.location.origin) + /api/zombie-fps, cosi' ## il browser non ha bisogno di CORS. -## - Desktop: nessuna classifica, a meno di --api=http://... (serve ai test). Cosi' una -## partita di prova non finisce MAI nella classifica pubblica. +## - Desktop: servizio pubblico `DEFAULT_API_BASE`, quindi la classifica e' ATTIVA DI +## DEFAULT: non serve nessuna configurazione ne' argomento per leggere e inviare. +## - Override espliciti: `--api=` punta a un altro servizio (test), `--offline` +## (alias `--no-api`) spegne la classifica per le partite di prova. +## - Suite di test (avviate con `--script`): spente, a meno di un `--api=` esplicito, +## cosi' nessun test puo' scrivere nella classifica pubblica. ## - Due nodi HTTPRequest separati: Godot non ammette due richieste insieme sullo ## stesso nodo, e lettura e invio possono sovrapporsi (l'invio avviene alla morte). ## - Un errore di rete non deve mai rompere il gioco: si segnala e basta. @@ -14,6 +18,8 @@ signal score_submitted(info: Dictionary) signal submit_failed(reason: String) const API_PATH := "/api/zombie-fps" +## Servizio pubblico: la classifica funziona senza alcuna configurazione. +const DEFAULT_API_BASE := "https://enne2.net/api/zombie-fps" const TOP_LIMIT := 10 const RETRY_DELAY := 6.0 const RETRY_LIMIT := 3 @@ -22,7 +28,7 @@ const DISABLED_REASON := "classifica non attiva in questa build" var player_id := "" var base_url := "" -## Vero quando non c'e' nessun servizio da usare (build desktop senza --api=). +## Vero solo quando il servizio e' spento apposta (--offline) o in una suite di test. var disabled := false var _get_request: HTTPRequest @@ -52,19 +58,37 @@ func _ready() -> void: add_child(_retry_timer) _retry_timer.timeout.connect(_send_pending) -## Ordine di priorita': --api=..., poi l'origine della pagina sul Web. Senza nessuno -## dei due non si usa NIENTE: una build desktop di prova non deve poter inviare -## punteggi alla classifica pubblica (e' successo: righe "Test" in produzione). -func _resolve_base() -> String: - for argument in OS.get_cmdline_user_args(): +## Ordine di priorita': `--api=` (test), `--offline`/`--no-api` (spegne), origine +## della pagina sul Web, altrimenti il servizio pubblico. La classifica e' quindi +## abilitata di default e non dipende da configurazione; `--offline` esiste solo per +## le partite di prova che non devono finire nella classifica pubblica. +## `script_run` e' il guard delle suite di test: non e' configurazione utente, la +## passa `_resolve_base()` quando il processo gira con `--script`. +static func resolve_base(user_args: PackedStringArray, script_run := false) -> String: + for argument in user_args: if argument.begins_with("--api="): return argument.substr(6).trim_suffix("/") + for argument in user_args: + if argument == "--offline" or argument == "--no-api": + return "" if OS.has_feature("web") and ClassDB.class_exists("JavaScriptBridge"): var origin := str(JavaScriptBridge.eval("window.location.origin", true)) if origin.begins_with("http"): return origin.trim_suffix("/") + API_PATH - push_warning("Classifica disattivata: usa --api=http://host:porta per indicare il servizio.") - return "" + if script_run: + return "" # suite di test: mai verso il servizio pubblico + return DEFAULT_API_BASE + +## Le suite di test girano come script (`--script ...`): in quel caso la classifica +## resta spenta, a meno che il test non abbia passato un suo `--api=`. +static func _is_script_run() -> bool: + for argument in OS.get_cmdline_args(): + if argument == "--script" or argument == "-s": + return true + return false + +func _resolve_base() -> String: + return resolve_base(OS.get_cmdline_user_args(), _is_script_run()) ## Godot non ammette due richieste insieme sullo stesso nodo: una lettura chiesta ## mentre la precedente e' in volo viene rimandata a fine risposta invece di fallire diff --git a/tools/test_rebalance_branding.gd b/tools/test_rebalance_branding.gd index 4fc709c..f496d07 100644 --- a/tools/test_rebalance_branding.gd +++ b/tools/test_rebalance_branding.gd @@ -1,5 +1,6 @@ extends SceneTree -## Exact damage regression + visible game identity, no public score submissions. +## Exact damage regression + visible game identity. La classifica e' attiva di default +## nelle build di gioco; qui, girando come --script, resta spenta. const Player = preload("res://scripts/player.gd") const Grenade = preload("res://scripts/grenade.gd") const Boss = preload("res://scripts/alien_grunt.gd") @@ -41,7 +42,8 @@ func run_test() -> void: if label.text == "Work-Death Balance": title_found = true check(title_found, "menu mostra Work-Death Balance") - check(world.leaderboard.disabled, "build di prova non invia punteggi pubblici") + check(world.leaderboard.disabled and world.leaderboard.base_url == "", + "suite di test: nessun invio alla classifica pubblica") var zombie = load("res://scripts/zombie.gd").new() zombie.player = world.player zombie.position = Vector3(0, 0.05, -3) diff --git a/tools/test_start_screen.gd b/tools/test_start_screen.gd index 82b793c..2ffd849 100644 --- a/tools/test_start_screen.gd +++ b/tools/test_start_screen.gd @@ -8,6 +8,7 @@ extends SceneTree ## (con display, per gli eventi di puntamento: --rendering-method gl_compatibility) const Profile = preload("res://scripts/player_profile.gd") const StartScreen = preload("res://scripts/start_screen.gd") +const Leaderboard = preload("res://scripts/leaderboard.gd") var world: Node3D var screen: CanvasLayer @@ -101,10 +102,21 @@ func test_world_menu() -> void: or Input.mouse_mode == Input.MOUSE_MODE_CAPTURED), "playing=%s mode=%s display=%s" % [str(world.player.playing), str(Input.mouse_mode), DisplayServer.get_name()]) - # Regressione: senza --api= questa e' una build desktop di prova e non deve poter - # inviare punteggi alla classifica pubblica (ci sono finite righe "Test"). - check("senza --api= la build di prova non invia punteggi alla classifica pubblica", - world.leaderboard.disabled == true and world.leaderboard.base_url == "", + # La classifica e' attiva di default (nessuna configurazione). Il nodo qui e' spento + # perche' questa suite gira come --script: le regole si verificano su resolve_base(). + check("senza argomenti la classifica e' attiva sul servizio pubblico", + Leaderboard.resolve_base(PackedStringArray(), false) == Leaderboard.DEFAULT_API_BASE + and Leaderboard.DEFAULT_API_BASE == "https://enne2.net/api/zombie-fps", + "base=%s" % str(Leaderboard.resolve_base(PackedStringArray(), false))) + check("--offline (e --no-api) spengono la classifica per le partite di prova", + Leaderboard.resolve_base(PackedStringArray(["--offline"]), false) == "" + and Leaderboard.resolve_base(PackedStringArray(["--no-api"]), false) == "") + check("--api= vince su --offline e normalizza la barra finale", + Leaderboard.resolve_base(PackedStringArray(["--offline", "--api=http://127.0.0.1:8799/"]), false) + == "http://127.0.0.1:8799") + check("in una suite di test la classifica resta spenta", + world.leaderboard.disabled == true and world.leaderboard.base_url == "" + and Leaderboard.resolve_base(PackedStringArray(), true) == "", "base=%s disattivata=%s" % [str(world.leaderboard.base_url), str(world.leaderboard.disabled)]) world.queue_free() await process_frame