← Novità

v0.9

2026-06-15 → 2026-07-05 · 57 voce/i di changelog

Controlli touch: dimensione adattiva su schermi piccoli (iPhone) + regolazione manuale in Opzioni2026-07-05

  • [fix] Su iPhone il joystick e i tasti virtuali erano troppo piccoli per le dita. Le misure dei controlli sono in unità-canvas tarate per iPad (JOY_BASE_R=58, BTN_R=30, …): con lo stretch canvas_items+expand a base 1024×768 restano costanti in canvas, ma su un telefono piccolo e denso la loro resa fisica (mm sotto il dito) è molto minore che su iPad. Nuovo scaling adattivo in VirtualControls.gd: _compute_ui_scale()/_auto_scale() puntano a una dimensione fisica costante dei controlli usando DisplayServer.screen_get_dpi() e la scala di stretch corrente (raggio joystick bersaglio ~0.47" ≈ resa comoda iPad). Sui telefoni il fattore sale fino a 2.2×, sui tablet resta ~1.0. Tutte le misure (JOY_BASE_R, JOY_KNOB_R, BTN_R, BTN_SPACING, MARGIN, corpo della lettera Xbox) ora sono base × _ui_scale, ricalcolate a ogni _build_ui() (avvio, resize, rotazione). Fallback euristico sul lato fisico più corto se il DPI non è attendibile. BTN_CFG ora usa versori (offset reale = versore × BTN_SPACING scalato) così il diamante dei tasti scala in modo pulito.
  • [feat] Nuova voce Opzioni "DIMENSIONE CONTROLLI" — override manuale del giocatore su quello automatico: AUTO · PICCOLO · MEDIO · GRANDE · ENORME. Si cicla con ←/→, con tap sulla voce o con INVIO/A (come le altre voci del menu, navigabile anche da pad/tastiera). In AUTO la riga info mostra la taglia che il gioco ha rilevato per il dispositivo corrente (es. "ora: ENORME"). L'impostazione è persistente in Settings.touch_scale_mode (user://settings.cfg, sezione [general], default "auto") e il cambio si applica subito ai controlli via VirtualControls.refresh_scale().
  • [change] Settings.gd: aggiunto touch_scale_mode con setter set_touch_scale_mode() (valida, salva, applica) e helper statici touch_scale_modes()/touch_scale_label(); reset_to_defaults() lo riporta ad "auto". VirtualControls ricostruisce una volta in differita a _ready (gli autoload dopo di lui, come Settings, non sono ancora pronti nel suo _ready), così l'eventuale override manuale viene letto e applicato dal primo frame utile.
  • [test] Validato headless (Godot 4.6.2 ARM64): --check-only OK su VirtualControls.gd e Settings.gd; compile-check col progetto avviato (autoload registrati) OK su MainMenu.gd. Da provare sul device: dimensione dei controlli su iPhone vs iPad e la voce Opzioni.

Layout iPhone: safe-area (notch/bordi arrotondati) + tasto B mappa nei controlli touch2026-07-04

  • [fix] Su iPhone stat vita, punteggio in-game e controlli touch erano a filo dei bordi (schiacciati contro i lati e in parte "mangiati" dai bordi arrotondati/notch, perché il rapporto schermo è diverso da iPad/PC): nuovo helper _safe_insets() (in HUD.gd e VirtualControls.gd) che legge DisplayServer.get_display_safe_area() e lo converte da px fisici a unità-canvas (stretch canvas_items, rapporto viewport/finestra). Ora rientrano nella safe-area: pannello stat (alto-sinistra) + indicatori gatto/vino, meteo/punteggio/record (alto-destra), district label (sopra l'home indicator), pulsante mappa e joystick + pulsanti dei controlli virtuali. Ricalcolato a ogni resize/rotazione. Su desktop e iPad senza notch il risultato è identico a prima (solo un piccolo margine base di 8px).
  • [feat] Controlli touch: aggiunto il tasto B (mappa) — i pulsanti azione ora sono un diamante Xbox completo: Y in alto (suona), X a sinistra (gatto/scoreggia), B a destra (apri/chiudi mappa), A in basso (interagisci/elemosina). B inietta l'azione map come un vero tasto B del pad (già B in InputManager e nel tutorial; MapOverlay lo ignora di proposito, lo gestisce l'HUD). Il diamante è ora ancorato ai margini col suo bordo esterno, così nessun tasto resta più a filo dello schermo (prima la X poggiava sul bordo destro).
  • [change] Su touch il pulsante-icona mappa separato è nascosto (la mappa vive sul tasto B, stile Xbox): resta solo nel tutorial come indizio, o se forzato dal DebugPanel. Su desktop invariato (tasto T/TAB).
  • [feat] Orientamento: entrambi i versi landscape (flip 180°), niente portraitdisplay/window/handheld/orientation=4 (Sensor Landscape) in project.godot. Prima non era impostato (default 0 = un solo verso landscape), per questo su iPhone lo schermo non ruotava (solo "speaker a destra"); ora iPhone e iPad ruotano tra landscape e landscape-inverso (speaker a destra o a sinistra) ma mai in verticale. Attivo al prossimo export iOS (il preset non forza orientamenti: li deriva da questa impostazione).
  • [tooling] Script di release + task manualetools/release/prepare_release.sh raccoglie dall'ultimo tag (per data, che vive su main) le voci nuove del CHANGELOG, i commit non ancora rilasciati su dev e i file reali modificati (esclusi i falsi-modified LFS di sprites_raw//Sounds_raw/), scrive la bozza RELEASE_NOTES_DRAFT.md (gitignorata) pronta come messaggio del tag, e stampa i comandi da lanciare a mano (commit su dev → merge dev→main → tag annotato su main → push). Aggiunto il task programmato manuale "prepara-release-homeless" che lo esegue, rifinisce le note e propone lo slug del tag. Non esegue git di scrittura (la sandbox non può committare/pushare).

PRIMA SESSIONE DI AUTOTEST IN-ENGINE (sandbox) + fix orologio host spettatore2026-07-03

  • [test] Il gioco gira headless nella sandbox di Claude (Godot 4.6.2 Linux ARM64 fornito da Ivan): città a 145 fps, 49 NPC, 8 gatti, 0 nodi orfani, memoria stabile (~123MB). Verificati in-engine su ENet 2 istanze i fix di oggi: il client vede TUTTI i 49 NPC (snapshot a blocchi ✓), countdown calamità che scorre sul client (8.5→0.5) con fase attiva, drain e fine via relay ✓. Verificato l'intero flusso M6: morte client → spettatore (niente pausa, punteggio broadcast) → morte host → pannello finale su entrambi con classifica completa; host morto per primo → spettatore con mondo vivo per il client; REMATCH end-to-end (tutti collassati → run nuova per tutti: stats piene, orologio 08:00, classifica azzerata, 49 NPC).
  • [fix] Orologio dell'host spettatore inchiodato (trovato dal bot, invisibile a gdparse): trigger_game_over faceva set_process(false) DOPO il segnale game_over, annullando il set_process(true) del clock_only impostato da World durante il segnale. Ora set_process(clock_only): con l'host KO l'orologio avanza (08:05→08:51 in test) e i client restano sincroni.
  • [fix] TestBot: --bot-die-after/--bot-wave-after contano dall'inizio RUN (nuovo _run_t), non dal boot: in MP lobby+countdown mangiavano secondi e il reset di _cl_game_starting cancellava gli eventi troppo precoci (calamità forzata sparita in lobby).
  • [docs] README autotest: vincoli sandbox scoperti sul campo — processi che muoiono a fine chiamata (test in finestre ≤30s, host+client nella stessa chiamata), HOME e progetto su disco locale (il sync layer blocca Godot), import a spezzoni ripetendo la chiamata, errori EOSG attesi (la GDExtension vuole glibc 2.38, sandbox 2.35). Unica anomalia residua nei log: warning NavigationServer al primo frame (noto, benigno).

Harness di autotest headless (bot giocatore + runner multi-istanza)2026-07-03

  • [feat] TestBot (autoload NUOVO, ultimo in project.godot): bot di test completamente inerte in gioco normale (si attiva solo con --bot negli argomenti utente, stesso pattern dei --mp-*). Simula un giocatore con input veri (Input.action_press: movimento a scatti casuali + interact/busk/fart/sleep), salta il menu in SP, e stampa ogni N secondi una riga [BOT] {json} con lo stato completo: stats/orologio/wanted (GameState), rete (peer, punteggi, trasporto), fase+timer calamità, conteggi mondo (npcs/puppet/gatti/spettatore/pannello), memoria (MB statici, oggetti, nodi, orfani=candidati leak). Flag: --bot-dump/quit-after/die-after/wave-after/seed/speed/rematch (morte programmata → test game over/spettatore; rivincita automatica dell'host a run finita).
  • [feat] scripts/tools/check_scripts.gd: compile-check headless di TUTTI gli script (il compilatore vero risolve proprietà/metodi/tipi — ben oltre gdparse) + load di tutte le scene (riferimenti rotti), exit code per CI.
  • [feat] tools/autotest/: runner bash — run_check.sh (compile), run_sp.sh (partita SP col bot), run_mp.sh (host autostart + N client su ENet/localhost, con verifica automatica "il client vede gli NPC"). Istanze con XDG_DATA_HOME separate (settings/highscore non si sporcano). README con istruzioni.
  • [chore] Serve il binario Godot 4.6 Linux ARM64 in tools/godot/ (gitignored, nota METTI_QUI_IL_BINARIO.txt): la sandbox di Claude è Linux aarch64 con xvfb+Mesa (screenshot software possibili in futuro). EOS vero non gira in sandbox (si testa sui device); ENet locale copre tutta la logica di gioco.

Fix online: NPC spariti sui guest (limite pacchetto EOS), calamità congelate, pannello finale ONLINE dedicato2026-07-03

  • [fix] GROSSO — i guest online erano SENZA NPC: il P2P di Epic ha un limite di 1170 byte per pacchetto (EOS_P2P_MAX_PACKET_SIZE) e EOSGMultiplayerPeer NON frammenta: un rpc più grosso viene scartato con "Packet size exceeds limits" (verificato nel sorgente del plugin). Lo snapshot NPC (~50 NPC in un solo rpc) non partiva MAI verso i client — e senza snapshot i client scartano anche gli spawn singoli (_cl_npc_spawn è gated) → zero NPC per sempre. Su ENet/LAN funzionava perché ENet frammenta da solo. Fix: snapshot a blocchi da 14 NPC (NET_SNAPSHOT_CHUNK, reliable ordered, _net_make_puppet idempotente; con 0 NPC parte un rpc vuoto per sbloccare il flag) e trasformate a blocchi da 22 (NET_TRANSFORM_CHUNK: anche il batch da ~50 superava il limite → puppet congelati). Identico su ENet, solo qualche pacchetto in più.
  • [fix] Calamità congelate sui guest (countdown inchiodato a 20, barra sempre piena): WeatherSystem._process sul client usciva PRIMA di far scorrere il timer di fase — le fasi (warning/inizio/fine) arrivano via relay ma il countdown del banner era statico. Ora il client fa il tick locale del timer (clampato a 0; le transizioni restano SOLO all'host). Ramo client spostato PRIMA del gate run_active: il timer scorre anche per lo spettatore collassato.
  • [fix] Ondate morte con l'host spettatore: con l'host collassato (run_active=false) il suo WeatherSystem si fermava → niente più ondate per i vivi (e un'ondata attiva non finiva MAI: drain infinito sui superstiti). Il gate ora accetta anche clock_only (M6): l'host KO resta autorità meteo.
  • [feat] Pannello game over ONLINE dedicato (richiesta Ivan): la partita online non tocca più best e classifica LOCALI (niente name entry, niente HighScore.add_entry, niente top-5 locale nel pannello). Il pannello online mostra: motivo, riepilogo della propria run (giorni sopravvissuti, soldi, imprese — ora con "Avversari colpiti"), PUNTEGGIO, record online persistente col nome di chi l'ha fatto ("🏆 NUOVO RECORD ONLINE!" se è il tuo) e — CLASSIFICA ONLINE — (rinominata da "classifica partita"). Flag HUD._was_mp_run deciso a inizio scena (a fine run la sessione può già essere chiusa).
  • [feat] Record online persistente: Settings.best_online_score/best_online_name (user://settings.cfg) aggiornato da maybe_update_online_best() a OGNI punteggio finale che arriva in sessione (tuo o degli avversari) — il record ricorda anche i colpi grossi altrui.
  • [feat] Impresa "Avversari colpiti" (cat_player, 120 pt): punti al lanciatore quando il suo gatto colpisce un altro giocatore (hook in World.net_cat_hit_player, che gira solo sul peer del lanciatore — niente doppi conteggi). Nel pannello online il breakdown imprese sale a 4 righe.
  • [docs] Design doc §17. Validato con gdparse (WeatherSystem, ScoreSystem, Settings, World, HUD). Da testare online (2+ istanze via EOS): NPC visibili e mossi sui guest, countdown calamità che scorre, pannello finale senza classifica locale.

Multiplayer M6: spettatore post-morte, classifica completa, REMATCH2026-07-03

  • [feat] Chi collassa NON chiude più la rete: diventa SPETTATORE (design §4.3): via l'auto-close di NetworkManager sul game_over (resta solo per la run arena). Al collasso con altri vivi: fanfara + flash shader che rientra (non il game over pieno), barbone locale → fantasma (Player.enter_collapsed_mode: invisibile, zero fisica/input/collisioni, fuori dal gruppo "player", wanted azzerato), joystick virtuale nascosto. La camera segue i vivi (smoothing esistente): cambio barbone con interact (E/A) o tap sullo schermo; banner top-center "💀 SEI COLLASSATO — Xº PROVVISORIO · 👁 nome" aggiornato a ogni morte/cambio. Sugli altri schermi il puppet del morto si dissolve in fantasma (fade+salita), niente più sparizione secca.
  • [feat] Classifica di partita COMPLETA (risolto il limite di §12.2): il peer del morto resta aperto → riceve i punteggi di chi collassa dopo. Il pannello finale arriva a fine run condivisa (tutti collassati o nessuno rimasto da guardare), con name entry rimandata a quel momento e classifica ormai piena; per chi muore per ultimo il pannello resta immediato e si auto-aggiorna coi punteggi quasi-simultanei in volo.
  • [feat] REMATCH dalla schermata finale: con la sessione ancora viva l'host vede "Premi R per la RIVINCITA (stessi giocatori)", i guest "Aspetta che l'host lanci la RIVINCITA..."; ESC = esci al menu (chiude la sessione). NetworkManager.rematch() riusa _cl_game_starting con seed nuovo: su OGNI peer spausa, resetta gli autoload per-giocatore e ricarica Main. Big notify "🔁 RIVINCITA! SI RIPARTE..."; guardia anti doppio avvio (_start_scheduled, vale anche per l'AVVIA in lobby).
  • [feat] Host collassato = mondo ancora vivo: nuovo GameState.clock_only — l'host spettatore continua a far battere l'orologio per i client vivi (senza decay/danni/game-over sul proprio barbone KO); NPC/meteo/eventi già girano perché World non va più in pausa al collasso.
  • [feat] Disconnessioni al pannello finale: se l'host sparisce o si resta soli mentre si aspetta la rivincita, la sessione residua viene chiusa e il pannello torna al prompt classico "R per ricominciare" (in solitaria), con avviso. Guardavi uno che è uscito? Si passa al vivo successivo; nessuno rimasto → pannello finale.
  • [refactor] GameState.reset_run_state(): reset per-giocatore unificato (stats, orologio, giorno, gatto, sbornia, wanted, shelter, reputazione, clock_only) — usato da restart R, uscita al menu e rematch (prima duplicato in 2 punti dell'HUD).
  • [fix] Da spettatore niente vignetta rossa pulsante né avvisi fame/sonno (le stat del barbone KO sono a zero ma il pannello non c'è ancora).
  • [docs] Design doc §16 (architettura spettatore/rematch + limiti: host migration fuori scope v1, name entry persa se la rivincita parte mentre si scrive). Validato con gdparse su NetworkManager, GameState, World, Player, HUD. Da testare in-engine (piano in §16.5).

Fix MP: quick match EOS, zuffa gatti in rete, PRONTO in lobby2026-07-03

  • [fix] PARTITA RAPIDA online cercava una volta sola e ospitava: con l'host online attivo, un altro che faceva partita rapida non trovava la lobby e apriva la sua. Causa: available() è statico (plugin+credenziali), quindi si entra in _quick_match_eos che faceva una sola search_public() subito dopo il login, quando la lobby EOS dell'host non è ancora indicizzata sul backend Epic. Ora l'ordine è accendi EOS → cerca → se vuoto RIPROVA dopo 0.8–1.5s → solo allora ospita (stesso anti-corsa della LAN); messaggi di stato allineati.
  • [feat] Zuffa gatto-vs-gatto disegnata a TUTTI i giocatori nel punto d'impatto (come un colpo su un personaggio): da locale a networked. Rilevamento solo sui gatti autoritativi (CatProjectile, not visual_only) → World.net_cat_clash(pos, throw_id_a, throw_id_b)_cl_cat_clash.rpc (call_local) con dedup per coppia di throw_id (una sola nuvola per schermo), spawn della nuvola nel punto d'impatto e rimozione dei due gatti ovunque per throw_id. In single player (throw_id 0) niente dedup/rimozione-per-id: i due gatti li libera il rilevatore per riferimento. Supera l'approccio locale del changelog precedente.
  • [feat] Lobby: i guest mettono "PRONTO" e l'host lo vede (così può avviare in anticipo): registro players esteso con ready (host sempre pronto); NetworkManager.set_ready/_srv_set_ready/is_local_ready/all_guests_ready. UI (MainMenu): voce PRONTO per i guest (tastiera/stick/tap), marchio ● PRONTO / ○ attende su ogni slot non-host, stato host "TUTTI PRONTI — PREMI AVVIA!" e voce AVVIA in verde con "✓ TUTTI PRONTI". L'host può comunque avviare con ≥2 quando vuole (la readiness è un segnale, non un vincolo). Tap della voce guest preservato nei keep_stops del passthrough mouse.
  • [docs] Design doc §15 (+ nota di superamento in §14.5). Validato con gdparse: NetworkManager.gd, World.gd, CatProjectile.gd, MainMenu.gd. Da testare in-engine.

Gatti sempre a 8 in città + perdita soldi "Sonic" al colpo di gatto (MP)2026-07-03

  • [feat] I gatti in strada tornano sempre a 8 (CAT_TARGET): prima gli 8 gatti erano spawnati una volta sola e il conteggio calava a ogni raccolta finché la città non ne restava senza. Ora uno sweep host/single player in World._process (ogni 2s) + refill immediato alla raccolta (request_cat_refill da Interactable._use_cat) rimpiazza i gatti mancanti. Nuovo gruppo "cats" per il conteggio. "8" = gatti raccoglibili in strada; quello in braccio è extra e non conta.
  • [feat] Respawn "invisibile": _pick_hidden_cat_spawn() sceglie un punto oltre CAT_HIDE_DIST (520px, ~mezzo schermo) da OGNI barbone — player locale + puppet dei remoti (gruppi "player"/"mp_puppet") → vale in single e multiplayer. Best-effort al punto più lontano se nessuno è così distante.
  • [feat] Refill host-authoritative in MP: solo l'host respawna e broadcasta i gatti nuovi (_cl_cat_spawned, net_id dinamici da 1_000_000 per non collidere con gli id iniziali); i client li registrano uguali (la raccolta successiva li rimuove da tutti via _cl_interactable_consumed). Un gatto raccolto da un client fa scattare il refill sull'host.
  • [feat] Perdita soldi + monetine sparse stile Sonic (solo multiplayer, aggiorna §4.5/§8.1 che davano "niente perdita soldi"): chi è colpito dal gatto di un altro giocatore perde il 50% dei soldi (net_apply_cat_hit), che schizzano in fino a 8 MoneyCoin attorno alla vittima. Notifica "💸 -$N sparsi per strada!".
  • [feat] MoneyCoin (NUOVO: scripts/pickups/MoneyCoin.gd + scenes/pickups/MoneyCoin.tscn): Area2D con saltello (pop verticale + rimbalzo + bob), vita ~10s con lampeggio finale, raccoglibile dal barbone locale (gruppo "player"; i puppet no) → add_money + FX/SFX monetina. ARM_DELAY 0.45s prima di essere raccoglibile (lascia schizzare via le monete). La vittima può ri-raccoglierle se torna in tempo (stile Sonic). Distinta dalla gemma-XP dell'arena (CoinPickup.gd).
  • [feat] Monete condivise in MP: la vittima decide posizioni/valori e li broadcasta a tutti (net_scatter_coins_cl_coins_scattered, call_local); ognuno raccoglie col proprio barbone locale e propaga la rimozione (net_coin_collected_cl_coin_collected). Id monete a 64 bit (peer*1_000_000+seq) perché i peer id dei client Godot sono int32 random (un PackedInt32Array li troncherebbe).
  • [feat] Scontro gatto-contro-gatto in aria: se due gatti volanti si incrociano a mezz'aria (tipico di quando due barboni se li lanciano a vicenda) parte la zuffa nel punto d'impatto e spariscono entrambi. Nuovo CatClash (scripts/entities/CatClash.gd + scenes/entities/CatClash.tscn): nuvola catfight.png a sé stante + suono posizionale (catangry.mp3), autodistruzione ~1.6s. Rilevamento in CatProjectile._process (gruppo "cat_projectiles", CLASH_RANGE 22px) prima del ramo visual_only → vale su ogni peer; una sola nuvola per peer (tiebreak get_instance_id, perché in SP i throw_id sono 0); i gatti autoritativi broadcastano il despawn.
  • [docs] Design doc §14 (nuova) con modello di conteggio, garanzie single player e punti da verificare in-engine (compreso §14.5 sullo scontro gatto-vs-gatto). §4.5 e §8.1 marcate come superate/decise.
  • [test] Sintassi validata con gdparse su tutti i file toccati (MoneyCoin.gd, World.gd, Interactable.gd, Player.gd). Da testare in-engine: 2 istanze, uno lancia il gatto sull'altro → metà soldi persi, monetine raccoglibili da entrambi; in strada sempre 8 gatti.

Multiplayer M5: EOS/INTERNET implementato (dormiente fino all'attivazione)2026-07-02

  • [feat] EOSBridge.gd (autoload NUOVO): tutto l'online Epic in un file — rilevamento plugin a runtime (zero riferimenti statici: senza EOSG il progetto carica normale e resta LAN), login anonimo Device ID (i giocatori non servono account Epic), lobby con attributi (codice SU_CODE, visibilità SU_PUBLIC, bucket versionato), ricerca per codice/pubbliche, EOSGMultiplayerPeer (P2P relay Epic). Lookup dinamico via get_script_constant_map (le costanti degli Script non si leggono con .get()).
  • [feat] NetworkManager multi-trasporto: transport enet/eos scelto in automatico da EOSBridge.available(); codice sessione a 6 caratteri (alfabeto senza I/O/0/1); join_game accetta IP (LAN) o codice (internet); PARTITA RAPIDA cerca su internet quando l'online è attivo, altrimenti LAN come prima; discovery UDP solo per enet; leave/chiusure fanno anche il leave della lobby EOS. Tutta la logica di gioco (puppet, wanted, classifica, gatto) è identica sui due trasporti.
  • [feat] Menu: mp_home a 4 voci (RAPIDA / OSPITA / UNISCITI IP LOCALE / UNISCITI CON CODICE) + riga di stato online; nuova schermata mp_code a 6 slot (pad/stick/touch + scrittura diretta da tastiera); la lobby online dell'host mostra CODICE: XXXXXX invece di IP/porta.
  • [chore] eos_credentials.example.cfg (modello con i 5 campi del Dev Portal) + eos_credentials.cfg in .gitignore (chiavi mai committate); autoload EOSBridge in project.godot.
  • [docs] EOS_SETUP.md nella root: guida passo-passo per Ivan (account Epic + prodotto + client Peer2Peer, installazione plugin EOSG, credenziali). AVVERTENZA: bridge scritto contro l'API EOSG senza poterla eseguire — al primo avvio con plugin gli eventuali errori [EOSBridge] in console vanno incollati in chat per il fix. Design doc §13.
  • [test] ONLINE FUNZIONANTE cross-rete: partita rapida a 3 giocatori in contemporanea su PC (wifi), iPad (wifi) e iPhone (5G cellulare) — relay Epic attraverso NAT e rete mobile. Prima partita via internet del progetto.
  • [chore] Plugin EOSG in .gitignore (489MB di binari SDK = dipendenza terza non versionata; installazione documentata in EOS_SETUP.md, release 2.3.0). Nota: l'abilitazione del plugin scrive i suoi autoload in project.godot — su una macchina senza addon Godot logga errori di autoload all'avvio finché non lo si reinstalla (o si disabilitano le voci).
  • [fix] EOSBridge riscritto contro l'API REALE del plugin (EOSG 2.3.0, letto dalla copia installata in addons/ dopo il primo errore runtime EOSGLobbySearch.find): ora usa lo strato alto HEOS — HPlatform.setup_eos_async(HCredentials), HAuth.login_anonymous_async (device id, PUID da HAuth.product_user_id), HLobbies.create_lobby_async + HLobby.add_attribute/update_async (SU_CODE/SU_PUBLIC/SU_VER; permission sempre PublicAdvertised, la privacy la fa l'attributo), search_by_attribute_async, join_async, leave_async/destroy_async (owner distrugge). Aggiunto stato "plugin da ABILITARE" (gli autoload H* esistono solo a plugin attivo). Via il vecchio percorso raw (callback IEOS manuali).
  • [fix] Credenziali EOS mancanti nell'export iOS: eos_credentials.cfg non è una risorsa → l'export non lo impacchettava nel PCK (dall'editor funzionava, su iPad no). Aggiunto al preset iOS il filtro "export non-resource files" = eos_credentials.cfg: se il file c'è viene incluso in automatico, se manca l'app esce comunque (solo LAN). Nota in EOS_SETUP.md: export_presets.cfg è gitignored, il filtro va ri-aggiunto se si rigenera il preset o se ne creano altri (macOS/Win).

Multiplayer post-v0.14: wanted per-player, classifica condivisa, trombetta posizionale2026-07-02

  • [feat] La polizia ora insegue e ARRESTA anche i giocatori remoti (M1-lite): ogni client riferisce il proprio wanted all'host (1 Hz se cambiato, _srv_wanted); PoliceOfficer._check_vision è multi-bersaglio (player locale + puppet remoti, sceglie il ricercato più vicino con wanted ≥ 25, soglie 25/60/85 invariate — in single player identico a prima). Banner ARRESTO/INSEGUIMENTO + sirena solo sullo schermo dell'inseguito (net_police_notify). Arresto remoto: il peer colpito esegue il suo flusso locale (multa 30%, sveglia, stop busking, wanted a 0), l'host azzera la cache per il cooldown.
  • [feat] Classifica di partita condivisa: al game over il punteggio finale (nome, punti ScoreSystem, soldi) viene broadcastato PRIMA della pausa, con chiusura rete ritardata di 1.5s; si accumula in NetworkManager.session_scores (sopravvive alla chiusura). I vivi vedono "💀 XXX È COLLASSATO! (N pt)" (niente doppio "ha lasciato"); il pannello game over mostra "— CLASSIFICA PARTITA —" con i collassati ordinati + "ancora in strada..." per i vivi. Limite noto: chi muore per primo non riceve i punteggi successivi alla propria chiusura (serve lo spettatore M6).
  • [feat] Trombetta dei puppet POSIZIONALE: AudioStreamPlayer2D sul puppet che suona (max_distance 420, attenuazione, loop) — il busking dei vicini si sente col volume in base alla distanza; il busker locale resta pieno come prima.
  • [docs] Design doc §12. Rimangono per sessioni dedicate: EOS/internet (M5, servono account Epic + GDExtension), spettatore post-morte e rematch (M6).

Multiplayer: fix playtest 3 (aspetto NPC, interazioni client, gatto) + M4-lite2026-07-02

  • [fix] GROSSO — il client non interagiva con gli NPC (e il gatto si raccoglieva a fatica): _net_make_puppet impostava la variabile net_id ma NON il meta net_id usato da beg/busk con has_meta() → l'elemosina del client non partiva mai e ogni A veniva consumato dal beg fantasma (che aveva priorità sull'interact). Una riga: set_meta("net_id", id).
  • [fix] NPC "tutti maglietta rossa" sul client: l'host spawna ModularNPC con archetipi/varianti/palette, il client istanziava NPC.tscn liscio. Ora spawn/snapshot portano arch_id + look_seed e il client ri-esegue apply_archetype sotto lo stesso seed (NPCDatabase usa la RNG globale → replay esatto). Idem genere/variante poliziotti (seed durante _ready; rotta calcolata prima del seed, consumava RNG). DistrictSpawner.spawn_one_for accetta look_seed. Gate su ModularNPC._physics_process: junkie/cleaner puppet inerti (il tossico NON ruba sul client).
  • [feat] M4-lite — gatto vs giocatori: has_cat nel net-state (i puppet mostrano il gatto in braccio); lancio broadcastato con replica visual_only; il proiettile del lanciatore colpisce anche i puppet dei player (priorità player > polizia > civili) → il peer colpito riceve stun 1.5s + interrompe busking/sonno + perde il gatto con notifica comica + sfx zuffa. Colpi dei client su NPC/polizia puppet relayati all'host (stun/panico sull'NPC vero; punteggio e wanted restano al lanciatore, nessun doppio conteggio).
  • [docs] Design doc §11.6. Punti da guardare al playtest: aspetto NPC identico sui due schermi, beg/busk dal client, lancio gatto host↔client in entrambe le direzioni.
  • [fix] (playtest 4) Azzuffata visibile sul barbone colpito: nuvola catfight.png 4 frame @10fps con sprite nascosto (stesso pattern di NPC/polizia), durata 2.5s, broadcastata a TUTTI gli schermi (World._cl_cat_fight_fx, call_local: la vittima la mostra su di sé, gli altri sul suo puppet). Lo stun ora dura quanto l'azzuffata (2.5s invece di 1.5s), come per i poliziotti; i frame avanzano anche durante lo stun (update prima del return) e sui puppet.
  • [fix] (playtest 5) La replica del gatto non prosegue oltre il colpito: ogni lancio ha un throw_id (peer×4096+contatore); al colpo (player/polizia/civile) il lanciatore broadcasta _cl_cat_despawn e le repliche visive sugli altri peer spariscono nel punto d'impatto. Registro repliche in World con pulizia automatica delle scadute.
  • [feat] (playtest 5) Sbornia sincronizzata: is_drunk nel net-state del player → i puppet mostrano frames drunk (priorità sbornia > gatto > normale), tinta paonazza, barcollio e stelline orbitanti (orbita estratta in _animate_drunk_stars, condivisa tra visual locale e puppet); ripristino completo (tinta slot) a fine sbornia.
  • [feat] (playtest 5) Visione "animale" da ubriaco sui puppet: i puppet NPC chiamano _update_drunk_sprite() (+ variante police con flag) nel puppet-process — leggono il GameState locale, quindi ognuno vede cagnolini & co. solo quando è ubriaco LUI, come in single player. Funziona con i corpi archetipo (apply_archetype aggiorna le cache _normal/_drunk_frames).
  • [feat] (playtest 5) Busking visibile agli altri: _is_busking nel net-state → il puppet mostra tromba homelessmusic (sprite nascosto), note musicali e gatto ballerino se ha il gatto in braccio, con l'identica animazione locale (estratta in _animate_busking_visuals); il gatto ballerino segue live anche perdita/raccolta del gatto. Interazione pulita con l'azzuffata (lo sprite non ricompare sotto la tromba). Restano locali barra durata e loop audio (posizionale = futuro).

Multiplayer M2+M3: NPC host-simulati, risorse contese, meteo/ora condivisi2026-07-02

  • [feat] NPC e polizia simulati SOLO dall'host (M3): i client non spawnano più NPC propri — ricevono repliche "puppet" (NPC.is_net_puppet: zero AI, interpolazione + dead reckoning + anim direzionale + nuvolette). Sweep host a 10 Hz in World._net_host_npc_tick: cattura ogni spawn/despawn da qualunque via (respawn, polizia extra) e broadcasta trasformate batch. Snapshot on-demand con retry: robusto ai caricamenti sfalsati. Stesso movimento NPC su tutti i peer, come chiesto da Ivan.
  • [feat] Elemosina/busking autoritativi (M3): il client chiede all'host (_srv_beg/_srv_busk con net id del puppet + stats del richiedente); l'host rolla con NPC.net_resolve_beg/net_busk_donate (generosity, NPC "svuotato"/allarmato e cap-col-gatto CONDIVISI e quindi CONTESI) e accredita il peer giusto; reazioni NPC (nuvoletta/moneta/sfx) in broadcast a tutti. I civili dell'host vengono attratti dal puppet del busker remoto. Misfatti dei client relayati all'host → la polizia investiga sul posto (inseguimento/arresto dei remoti = M1, wanted per-player).
  • [feat] Risorse condivise (M2): net id deterministici per gli interactable (ordinati per posizione, identica col seed). Panchine/fontane/cestini: il cooldown di chi li usa vale PER TUTTI (visual grigio esistente). Gatti raccoglibili: stream RNG dedicato (world_seed+2674) → posizioni identiche, consumo broadcast — chi prima arriva se lo tiene. Negozi liberi per tutti.
  • [feat] Orologio/meteo/eventi città dall'host (M3): time sync ogni 5s; WeatherSystem passivo sui client (l'host pilota via relay le fasi ondata esistenti: warning con countdown, inizio, fine → banner/freccia/drain identici); eventi città estratti solo dall'host e relayati, van di beneficenza nello stesso parco ovunque (indice deterministico).
  • [docs] Design doc §11 con architettura e limiti noti: polizia cieca verso i remoti (M1), aspetto NPC può differire nei dettagli random, gatto vs NPC solo locale (M4), classifica finale locale (M6). File toccati: World.gd (+~350 righe layer rete), NPC.gd, PoliceOfficer.gd, Player.gd, Interactable.gd, WeatherSystem.gd, CityEvents.gd. Verificato con gdparse, da testare in-engine.

Multiplayer: fix dal primo playtest cross-play PC+iPad2026-07-02

  • [fix] PARTITA RAPIDA non trovava nulla su iPad: il broadcast UDP è bloccato su iOS senza entitlement Apple. _lan_discover ora fa broadcast globale + directed broadcast + sweep unicast dell'intera /24 (254 pacchetti minuscoli, l'unicast è sempre permesso) per ogni interfaccia privata, con attesa 1.6s.
  • [chore] export_presets.cfg (preset iOS): aggiunta NSLocalNetworkUsageDescription in Additional Plist Content — Godot la inietta nell'Info.plist a ogni export, Xcode non va toccato. Al primo uso della LAN iPadOS mostrerà il popup "Rete locale": va accettato (verificabile poi in Impostazioni → Street University → Rete locale).
  • [fix] GROSSO: host che usciva = spariva anche il barbone del client. Dopo la disconnessione ENet get_unique_id() torna 1, il client si scambiava per l'host e liberava il proprio nodo. Peer id locale ora cacheato a inizio sessione (_my_peer_id) + guardia in World._on_mp_player_left (mai liberare il player locale).
  • [change] Netcode 12→20 Hz + dead reckoning: nel pacchetto viaggia anche la velocità e il puppet estrapola il bersaglio tra un pacchetto e l'altro; smoothing 10→14; da fermi keepalive 2 Hz con change-detection (meno traffico di prima nonostante il rate più alto). Lag percepito ridotto a ~1 frame di rete.
  • [fix] Spawn separati: offset per slot a 4 angoli ±32px (prima 26px, sembravano sovrapposti).
  • [fix] I barboni si attraversano: puppet con collision layer/mask a 0 e shape disabilitata — niente più trascinamenti quando ci si tocca.
  • [change] Spawn iniziali deterministici in MP: RNG globale seedata dal seed condiviso durante gli spawn di partenza → stessi gatti raccoglibili, stessa polizia, stessi NPC iniziali (posizioni+rotte) su tutti i peer; filtro no-spawn misurato dal player_spawn condiviso. I movimenti NPC divergono nel tempo (reagiscono al player locale): NPC host-simulati = M3, prossimo lavoro grosso.

Multiplayer M0+M0.5: fondamenta rete + menu MULTIGIOCATORE2026-07-02

  • [feat] NetworkManager.gd (autoload NUOVO, ~540 righe): il cuore del multiplayer. Listen-server ENet (porta 7777): host_game(pubblica), join_game(ip) con timeout 5s, lobby host-authoritative (registro {peer_id: {name, slot}} broadcastato), start_game() con seed condiviso + countdown 3-2-1 e cambio scena sincronizzato. PARTITA RAPIDA (search-then-host): scoperta host in LAN via broadcast UDP porta 7778 (2 tentativi con jitter anti-corsa, filtro versione, entra nella lobby più popolata; nessuna → ospita lui una pubblica). Countdown automatico lobby pubbliche (30s da 2+, −10s a ogni ingresso, 5s piena). CLI per test: --mp-host (autostart a 2), --mp-join <ip>, --mp-quick. A fine run/game over la rete si chiude in silenzio (il gioco locale prosegue); se cade l'host in partita si continua da soli con notifica. Regola d'oro: active=false (default) = zero effetti sul single player.
  • [feat] Menu MULTIGIOCATORE (MainMenu.gd, voce separata da INIZIA PARTITA): schermata mp_home (PARTITA RAPIDA / OSPITA / UNISCITI + IP locale in vista), mp_join (IP a 4 ottetti: cifre da tastiera, ◄►▲▼ pad/stick, tap touch; ultimo IP ricordato), mp_lobby (4 slot colorati con nome/HOST/TU, countdown live, AVVIA PARTITA e toggle VISIBILE IN PARTITA RAPIDA solo per l'host, IP+porta in chiaro), mp_name (nome arcade a 3 lettere, chiesto alla prima entrata). ESC/B/tap gestiti ovunque: uscire da lobby/ricerca/connessione chiude la rete in modo pulito. In OPZIONI nuova voce NOME MULTIPLAYER (le voci passano da 2 a 3).
  • [feat] Spawn multiplayer nel mondo (World.gd): in MP il seed arriva dall'host (WorldGenerator.generate(seed) → stessa città su tutti i peer), il $Player fisso viene sostituito da un barbone per ogni peer (nodo P<peer_id>, authority al peer proprietario, offset spawn per slot); la var player punta al barbone locale (camera/drunk-warp/spawn-distance invariati); chi si disconnette sparisce con notifica "XXX HA LASCIATO LA CITTÀ".
  • [feat] Modalità puppet (Player.gd): i barboni degli altri giocatori non hanno input/logica/GameState (segnali scollegati, fuori dal gruppo player → gruppo mp_puppet, così NPC/polizia/distretti locali ignorano gli ospiti); ricevono posizione+animazione+flip via RPC a 12 Hz (unreliable_ordered) con interpolazione morbida e snap oltre 200px; nametag a 3 lettere colorato per slot + tinta leggera sullo sprite. In single player zero differenze (gate _mp_session).
  • [chore] Settings.gd: nuovi campi persistenti mp_name e last_mp_ip (sezione [multiplayer], esclusi dal reset statistiche). project.godot: autoload NetworkManager.
  • [docs] MULTIPLAYER_DESIGN.md §10: stato implementazione, scostamenti dal design (RPC esplicite invece di MultiplayerSynchronizer, cambio scena in NetworkManager, countdown pubbliche anticipato a M0) e guida alla prima apertura. NON ancora testato in-engine (verificato con gdparse): la prima apertura in Godot può richiedere piccoli fix di API. Limiti M0 noti e voluti: NPC/meteo/orologio/soldi ancora locali per istanza — condivisi solo mappa e barboni. Prossimo: M1 (split GameState).

Multiplayer: design della modalità (menu, UI, run condivisa)2026-07-02

  • [docs] MULTIPLAYER_DESIGN.md (root): design operativo della modalità multigiocatore, complementare a MULTIPLAYER_EOS_BRIEF.md. Decisioni prese con Ivan: voce di menu separata MULTIGIOCATORE, run condivisa con timer (la run roguelike 15 min, RunManager sull'host), max 4 giocatori (min 2). Copre: flusso schermate menu (mp_homemp_join a 4 ottetti stile NameEntry → mp_lobby con slot/AVVIA solo host), nome player a 3 lettere + colore per slot (riuso palette_swap.gdshader), ciclo di vita sessione (lobby → countdown → run → classifica → rematch), tabella individuale-vs-condiviso, proposte per le domande aperte (gatto = stun 1.5s + drop + interrupt; game-over individuale con spettatore; wanted/reputazione individuali), mappa file-per-file (nuovo autoload NetworkManager, Main.tscn unica con gate NetworkManager.active — zero fork della scena, zero regressioni SP) e milestone M0–M6 con test plan (--mp-host/--mp-join CLI). Su richiesta di Ivan aggiunta PARTITA RAPIDA (§2.4): matchmaking automatico search-then-host su lobby pubbliche (cerchi → entri nella più popolata; nessuna → ospiti tu in automatico), countdown auto-start 30s→5s, mitigazione della corsa doppia-lobby (jitter + seconda ricerca), bucket per versione, ownership transfer in lobby (nativo EOS), drop-in fuori scope v1; mp_home ora a 3 voci (RAPIDA/OSPITA privata-o-pubblica/UNISCITI), quick_match() nel contratto NetworkManager da subito, M0.5 opzionale = scoperta LAN via broadcast UDP. Prossimo passo: M0 = NetworkManager ENet + menu + spawn sincronizzato su localhost. Nessun codice ancora scritto.

Fix manuali: sprite personaggi a frame singolo + orchestratore build2026-07-01

  • [fix] Sprite personaggi tornate a frame singolo. Rigenerando i manuali avevo eseguito solo extract_sprites.py, che ritaglia l'INTERA spritesheet: header con 3 gatti (coprivano il barbone-trombettiere), civili/polizia/NPC mostrati con tutti i frame di movimento. Reintrodotti gli step mancanti: extract_frames.py (catdance = 1 gatto) e extract_cells.py (polizia/NPC/civili = 1 cella fronte/giù), quest'ultimo dopo frames. Verificato con diff vs PDF committato: pagina 2 identica, pagina 1 differisce solo nella riga MAPPA (T + B) voluta.
  • [chore] build_manuali.py (root): orchestratore unico che ricostruisce A4 + libretti A3 nell'ordine corretto e copia i PDF nella root — così non si può più saltare uno step. Path di tutti gli script resi relativi (extract_cells/extract_frames inclusi). Tutti e 3 i PDF rigenerati.

Mappa (chiusura/tasto B), fontana garantita, navigazione stick, leaderboard touch2026-07-01

  • [change] Mappa: si chiude con QUALSIASI tasto. Oltre al tocco sul velo e al tasto mappa, ora un tasto/bottone qualsiasi (tastiera o gamepad) richiude la mappa aperta. Nuovo MapOverlay._input: ignora il tasto "map" (lo gestisce già l'HUD in toggle) e una finestra di grazia di 200ms dopo l'apertura, così lo stesso tasto che apre non la richiude subito.
  • [change] Tasto MAPPA su gamepad → B (tasto azione, più comodo del View/Back). In project.godot l'azione map ora ha B (button_index 1) come primario + View/Back (4) come secondario, tastiera T invariata. InputManager: label "map" = "B" con icona btn_b.png; aggiunti i percorsi icona btn_view.png (due quadratini) e btn_menu.png (tre linee), con fallback a testo se il file non c'è ancora.
  • [change] Tutorial: passo MAPPA con icona del tasto. La scheda mostra l'icona vera del tasto (B su gamepad/touch, [T] su tastiera) via get_prompt_rich e spiega solo il pulsante per aprire la mappa. La chiusura con qualsiasi tasto resta attiva nel gioco ma non è più menzionata nella spiegazione (né nel tutorial né nel manuale).
  • [fix] Almeno una fontana per mappa (SEMPRE). La fontana era piazzata solo nei parchi con dado al 70%: poteva mancare del tutto. Ora la PRIMA fontana è garantita (il primo parco la piazza sempre) + rete di sicurezza finale in _scatter_interactables che la forza nel parco/piazza più grande se il conteggio è 0. Estratto helper _spawn_fountain().
  • [fix] Levetta analogica nei menu: un colpo = un passo. Lo stick sinistro faceva saltare la selezione di 2-3 voci (rumore vicino alla deadzone + rimbalzo al rilascio). Nuovo InputManager.menu_stick_step() con isteresi (scatta a 0.7, si riarma sotto 0.35): indipendente da intensità e durata. Usato in MainMenu (menu/opzioni/domanda tutorial) e in NameEntry (lettere/slot). D-pad e tastiera restano digitali puri.
  • [fix] Leaderboard: controlli touch. L'inserimento nome (NameEntry) non era usabile a tocco (niente modo di cambiare/confermare le lettere). Aggiunti bottoni a schermo: spinner ▲/▼ sotto ogni lettera + bottone CONFERMA, visibili solo con comandi touch. Il NameEntry non "mangia" più i tocchi (solo tastiera/gamepad), così i bottoni li ricevono; il GameOverBox si allarga in basso per far spazio.
  • [assets] Icone tasti btn_view + btn_menu generate da sprites_raw/btn_view.png e btn_menu.png (chroma-key magenta + trim), con stesse dimensioni/posizionamento del set btn_a/b/x/y in assets/ui/. btn_b riusato (già esistente). Vanno importate aprendo il progetto in Godot (fino ad allora i prompt cadono sul testo).
  • [docs] Manuali aggiornati (A4 + 2 libretti A3): la riga comando MAPPA mostra ora il tasto tastiera T (era erroneamente "TAB") e l'icona del tasto B del pad (era il testo "View"); tutto il resto invariato (verificato con diff visivo pagina per pagina). I libretti A3 (..._A3_LIBRETTO.pdf e _BIANCO) incorporano le pagine A4, quindi dentro mostrano B. Pipeline resa ricostruibile: path relativi in build_manuale_pdf.py, build_manuale_pdf_a3.py e tools/manual_sprites/extract_sprites.py (build in _manual_build/, ignorata da git) + estrazione di btn_b/btn_view/btn_menu e generazione di catfly_flip (specchio di catfly, per la copertina A3).

Manuali: copertina libretto A3 col vero splash del gioco2026-06-16

  • [docs] Copertina dei libretti A3 (scuro + bianco) ora usa il vero splash screen del gioco (assets/ui/splash.png, con logo STREET UNIVERSITY + barbone/gatto/poliziotto) invece dello splash provvisorio composto a mano. Croppato alla proporzione wide originale e sfumato in basso così il titolo "MANUALE DEL GIOCATORE" ci si appoggia sopra (impaginazione invariata). Script tools/manual_sprites/make_cover_splash.py rigenera sprites/splash_fade.png dal vero splash.
  • [fix] Pannello COMANDI (A4 pag.1): rimossa la riga ESC/Pausa (sbordava); busker e gatto volante allineati sullo stesso bordo destro; hint touch spostato in fondo al pannello.

Manuali PDF aggiornati (mappa fullscreen + igiene bassa)2026-06-16

  • [docs] Aggiornati i 3 PDF di guida (MANUALE_GIOCATORE.pdf A4 2 pagine + libretti A3 scuro/bianco) con le novità da metà giugno: MAPPA A PIENO SCHERMO (nuova riga comandi: TAB / View / pulsante mappa, mette in pausa e mostra i quartieri coi POI) e IGIENE BASSA <25% (la barra IGIENE ora spiega che la gente scappa dal puzzo e la polizia s'innervosisce; a pag. 2 "essere troppo sporco" è tra i trigger di RICERCATO + riga dedicata PUZZA nel pannello polizia).
  • [chore] Pipeline sprite del manuale ricostruita: gli script PDF dipendevano da sprite "small" generate in una sessione precedente e non versionate. Aggiunti in tools/manual_sprites/ gli script che le riestraggono da sprites_raw/ (chroma-key magenta → trim → resize; ritaglio frame singolo dagli spritesheet a griglia NxN per polizia/NPC; catfly_flip e splash_fade di copertina rigenerati). Corretti i path hardcoded della vecchia sessione in build_manuale_pdf.py e build_manuale_pdf_a3.py.

Tutorial: aggiunta stazione PANCHINA2026-06-16

  • [feat] Stazione PANCHINA: nuovo parchetto verde con una panchina in mezzo, subito dopo il PARCO con la fontana. Spiega come [b]riposare per recuperare energia[/b] (gratis, alternativa al rifugio). Aggiunto il visual/collisione BENCH in _add_interactable (sprite bench.png, ostacolo 32×16) e la scheda dedicata. Stazioni del tutorial ora 15.

Tutorial: la musica non si ferma più nelle schede2026-06-16

  • [fix] Musica del tutorial in pausa dal gelataio in poi: aprendo/chiudendo la mappa (stazione MAPPA), il MapOverlay.close() rimetteva il nodo Music a PROCESS_MODE_PAUSABLE (corretto per il gioco vero), quindi le schede modali successive (dal gelataio in poi) fermavano la musica. Ora nel tutorial la musica resta sempre ALWAYS: TutorialWorld._process ri-afferma il process_mode ogni frame, e TutorialManager lo forza a ALWAYS subito prima di mettere in pausa per una scheda. Nel [b]gioco vero[/b] la musica continua a fermarsi col menu di pausa (invariato).

Tutorial: edifici solidi per tutto lo sprite2026-06-16

  • [fix] Negozi/rifugio attraversabili nella metà alta: la collisione copriva solo la "porta" in basso (l'ostacolo dell'interactable a livello corsia), ma lo sprite dell'edificio sale verso l'alto → la metà superiore/dietro era attraversabile. Ora _place_store_sprite aggiunge un [b]muro che copre il corpo dell'edificio[/b] (dalla cima fin quasi alla base, largo come lo sprite); stesso muro aggiunto al rifugio in _build_shelter. Il fronte (porta) resta accessibile: sotto l'edificio resta un corridoio di ~50px sulla striscia dove ci si avvicina per interagire.

Menu: musica in loop2026-06-16

  • [feat] Musica del menu principale in loop: aggiunto un nodo Music a MainMenu.tscn + export [b]music_track[/b] (trascina un .mp3/.ogg nell'inspector del nodo MainMenu, o assegna lo stream al nodo). Loop robusto (finished→replay); si ferma da sola al cambio scena (start partita/tutorial).

Tutorial: striscia stretta, negozi solidi, musica MP3 in loop2026-06-16

  • [change] Striscia camminabile stretta + tutto nero attorno: tolta la strada in basso; ora c'è solo una [b]striscia di marciapiede[/b] (alta 2*LANE_HALF=140px) e sopra/sotto è tutto nero. Il barbone è [b]clampato dentro la striscia[/b] da muri invisibili sopra e sotto (_add_bounds_wall): non esce più né sopra né sotto. Camera Y abbassata di conseguenza.
  • [fix] Negozi attraversabili: liquor/vestiti/rifugio nel tutorial non avevano collisione (avevo tolto il muro pieno) → il barbone ci passava in mezzo. Ora l'interactable dei negozi ha un [b]ostacolo[/b] (con trigger più largo) così non si attraversa ma si attiva dal davanti. Si può passare sopra/sotto l'oggetto restando nella striscia.
  • [feat] Musica MP3 in loop nel tutorial: rimesso un nodo Music con export [b]music_track[/b] (trascina un .mp3/.ogg nell'inspector del nodo TutorialWorld). Loop robusto (finished→replay, vale anche per mp3 non-loop). Il nodo è PROCESS_MODE_ALWAYS così la musica continua durante schede modali e mappa. Da ubriaco la musica rallenta (pitch 1.0→0.60), come nel gioco vero.

Tutorial: fix layout (camera, van sfalsato, cartelli alti)2026-06-16

  • [fix] Camera mostrava troppo "cielo" nero: la camera seguiva il player anche in verticale e finiva in alto col vuoto sopra. Ora la camera tiene una [b]Y fissa[/b] (CAM_Y) e segue il player solo in orizzontale, inquadrando cartelli + corsia + un filo di strada. Sfondo scuro esteso oltre i bordi così nessun aspect ratio mostra il nero.
  • [fix] Camioncino gelati/hot dog: sprite e collisione sfalsati: il van era disegnato al centro del blocco ma la sua hitbox/trigger erano sulla corsia ~70px più in basso → ci si "entrava dentro" e c'era un muro invisibile. Ora [b]visual e interactable sono nello stesso punto[/b] (_spawn_anim_or_tile_at), così sprite, trigger e collisione coincidono.
  • [change] Layout rifatto a banda unica: addio "blocco sopra / corsia sotto". Ora player e oggetti (van, fontana, negozi, bidoni, NPC) stanno tutti sulla [b]stessa corsia[/b] (come nel gioco vero), i [b]cartelli sono in alto nel cielo[/b] (SIGN_Y, ben staccati dalla strada) e la strada è in basso. Negozi resi con sprite singolo ancorato (_place_store_sprite), niente muro pieno. Mondo più basso (540px).

Tutorial: schede modali, spiegazione barre, layout a bande2026-06-16

  • [feat] Spiegazioni come schede modali (in pausa): le istruzioni di ogni zona non sono più notifiche che si sovrappongono, ma pannelli che mettono in pausa il gioco, mostrano titolo + testo e si chiudono premendo INVIO/E (A su pad, tap su touch). Le schede in coda si mostrano una alla volta. Pannello costruito a runtime in TutorialManager (CanvasLayer ALWAYS, RichTextLabel BBCode, finestra anti "stesso tasto" 0.18s).
  • [feat] Nuova stazione "LE TUE BARRE": scheda iniziale che spiega le tre barre — [b]FAME[/b] (a zero = game over, mangia), [b]ENERGIA[/b] (a zero crolli, riposa), [b]IGIENE[/b] (sporco = niente elemosina + polizia nervosa, lavati) — e il [b]livello di RICERCATO[/b] in alto a destra.
  • [change] Layout del tutorial a bande verticali: prima i cartelli erano attaccati alla strada. Ora il mondo è più alto (760px) e diviso in fasce: cartello (ben staccato) → blocco → [b]corsia pedonale[/b] dove cammina il player davanti agli edifici → strada in basso. Gli oggetti "a terra" (bidone, gatto, NPC, ingressi negozi) stanno sulla corsia, le frecce verdi non finiscono più sull'asfalto. Testi delle schede riscritti più chiari e descrittivi (BBCode con grassetti e colori).

Tutorial: gli NPC si avvicinano quando suoni (busking)2026-06-16

  • [feat] Busking nel tutorial: la gente viene verso di te: ora quando suoni, i civili vicini si avvicinano al barbone (si vede l'animazione di camminata) e, finito di suonare, tornano al loro posto. Prima nel tutorial non si muovevano: l'attrazione busking di NPC.gd usa il navmesh (_move_via_nav), ma il tutorial non costruisce un NavigationRegion2D, quindi non c'era path.
  • [feat] Movimento diretto senza navmesh (NPC.gd): nuovo export force_direct_movement. Quando attivo, _move_via_nav ripiega su _move_direct() (linea retta verso il target) — sicuro nel tutorial, che è una strada aperta. TutorialWorld lo attiva sugli NPC statici (e sul poliziotto manichino). A fine busking l'NPC torna al suo singolo waypoint di spawn camminando in retta.

Tutorial: stazione MAPPA + mappa funzionante nel tutorial2026-06-16

  • [feat] Stazione MAPPA nel tutorial: aggiunto un isolato "MAPPA" (2° della fila, subito dopo MUOVITI) con un popup che dice di premere il tasto mappa (TAB / View su pad / pulsante mappa su touch) per vederla. Il testo si adatta al tipo di input.
  • [feat] La mappa ora funziona nel tutorial: prima MapOverlay non si agganciava (nessun generator) e premere il tasto era un no-op. Ora TutorialWorld espone l'interfaccia che la mappa si aspetta (generator=self, world_size, building_rects/park_rects/plaza_rects/bus_stop_rects/shelter_rect), così la mappa disegna la strada lineare del tutorial coi suoi POI (icone dal gruppo interactables + pensilina). Il pulsante mappa dell'HUD è sempre visibile nel tutorial (indizio visivo + unico modo su touch).

Tutorial: pioggia-calamità reale, niente musica, freccia riparo ridisegnata2026-06-16

  • [feat] Pioggia del tutorial = calamità vera (ma scenografica): la stazione PIOGGIA non usa più una pioggia "finta" sempre accesa. Ora, arrivando alla pensilina, viene innescata una vera calamità rain con preavviso 10s (freccia "vai al riparo" + banner "TEMPORALE" col countdown) e pioggia attiva 5s come nel gioco. Nuovo flag WeatherSystem.tutorial_wave_mode: fa avanzare le fasi dell'ondata anche con run_active=false, non schedula ondate casuali e forza get_wave_drain_per_sec()=0 → la pioggia insegna la meccanica del riparo senza far calare le statistiche (scelta di Ivan). Le particelle visive si accendono solo durante la fase attiva, sincronizzate col WeatherSystem. Il flag viene azzerato uscendo dal tutorial (fine, skip, o "Esci al menu").
  • [change] Tutorial senza musica: rimosso il nodo Music (e l'AudioStream main.wav) da TutorialWorld.tscn.
  • [fix] Freccia "vai al riparo" deforme: il poligono pixel-art di _shelter_arrow in Player.gd aveva i vertici in ordine errato e il Polygon2D la riempiva come un blob a rombo (vedi screenshot). Ridisegnata come freccia classica convessa (testa a triangolo + asta rettangolare) puntata verso +X, netta e leggibile, ruotata a runtime verso il riparo.

Fix tutorial (vineria, ubriachezza) + Esci al menu in pausa2026-06-16

  • [fix] Vineria del tutorial ripetuta a griglia: la stazione VINERIA disegnava liquor.png (96×96) con STRETCH_TILE su un blocco da 128px, quindi la texture si ripeteva (edificio clonato a mosaico, vedi screenshot). Ora è renderizzata come un solo sprite ancorato alla base e scalato (_place_sprite_anchored), come il negozio di vestiti. (Nel gioco vero il bug non si vedeva perché lì il blocco è 96px = esattamente la texture.)
  • [fix] Ubriachezza infinita nel tutorial: bevendo 2 drink si restava ubriachi per sempre. Causa: nel tutorial run_active=false, quindi GameState._process usciva subito e non chiamava mai _update_drunkdrunk_time_left non scendeva. Ora il timer dell'ubriachezza scorre sempre (è un effetto in tempo reale legato al player, non alla run); decay/tempo/ondate restano gated da run_active come prima.
  • [feat] ESCI AL MENU PRINCIPALE dalla pausa: il menu di pausa ora ha il pulsante ESCI AL MENU PRINCIPALE, presente sia nel tutorial sia nella partita vera. Sblocca il gioco, chiude la run e riporta GameState a uno stato pulito (soldi/stat/gatto/sbronza/reputazione/meteo) prima di tornare a MainMenu.tscn. Nel tutorial appare sotto a "SALTA TUTORIAL".

Livello TUTORIAL prima della partita2026-06-16

  • [feat] Nuovo livello TUTORIAL: mappa FISSA e lineare (scenes/world/TutorialWorld.tscn + scripts/world/TutorialWorld.gd) con una lunga strada orizzontale e un isolato in fila per ogni cosa da imparare: MUOVITI (intro), GELATAIO, VINERIA, PARCO (solo fontana), BIDONE, ELEMOSINA, MUSICA (busking), GATTO vs POLIZIA, PIOGGIA (pensilina), VESTITI, HOT DOG, RIFUGIO. Si parte da sinistra e si arriva al cartello FINE a destra, che fa partire il gioco vero. Ogni stazione ha un cartello pixel col titolo visibile da lontano.
  • [feat] Regia tutorial (scripts/world/TutorialManager.gd): all'ingresso di ogni isolato parte una big_notify che spiega l'azione e il tasto (prompt reali tastiera/gamepad). Prima di ogni negozio regala i soldi necessari ($10 gelato/vineria, $20 vestiti/hot dog) così si può sempre interagire. La pioggia parte all'ingresso della pensilina (particelle, indipendente dalle ondate meteo) e si ferma quando si prosegue. Popup di benvenuto iniziale con spiegazione del movimento. Feedback "✓" al completamento di elemosina/musica/gatto. Per la stazione gatto: un gatto raccoglibile + un poliziotto "manichino" (vista/udito/velocità azzerati: non insegue, ma può essere stordito dal gatto).
  • [feat] Domanda a "INIZIA PARTITA": se il tutorial è attivo, premendo INIZIA PARTITA compare la schermata VUOI FARE IL TUTORIAL? con 3 risposte: (carica il tutorial), NO (vai al gioco), NON MOSTRARE PIÙ (spegne il flag e va al gioco). Se il flag è già spento, si va dritti al gioco.
  • [feat] Opzioni → MOSTRA TUTORIAL (ON/OFF): la pagina Opzioni ora ha due voci navigabili (↑↓ + INVIO/E/A o tap): il toggle MOSTRA TUTORIAL e RESETTA STATISTICHE. Il flag persiste in user://settings.cfg (nuovo autoload Settings). Il reset statistiche riattiva anche il tutorial.
  • [feat] Salta tutorial dalla pausa: durante il tutorial, il menu di pausa mostra il pulsante SALTA TUTORIAL che passa subito al gioco vero — solo per questa volta, senza toccare il flag show_tutorial.
  • [note] Stato pulito tutorial→gioco: il tutorial regala soldi e fa raccogliere gatto/bere drink; prima di entrare nel gioco vero GameState viene riportato a una run pulita (soldi, fame/energia/igiene, gatto, sbronza, reputazione, ScoreSystem/DifficultyManager), così non ci si porta dietro nulla. Nel tutorial run_active=false → niente decay stat, niente game over, niente ondate meteo (gate già esistente in WeatherSystem/DifficultyManager).
  • [note] Nuovo autoload Settings (scripts/autoload/Settings.gd) registrato in project.godot. Non testato in Godot reale (la sandbox Cowork non ha l'eseguibile): fatta solo validazione statica (sintassi, tab, bilanciamento parentesi, enum, esistenza risorse/UID). Ivan deve aprire il progetto e testare a runtime.

Fix touch: tornare indietro da Comandi/Opzioni e chiudere la mappa2026-06-16

  • [fix] Comandi/Opzioni: "tocca per tornare" non rispondeva (touch): entrando in COMANDI o OPZIONI col touch, l'hint diceva di toccare per tornare ma il tap non faceva nulla. Causa: in Godot un InputEventScreenTouch assorbito dal sistema GUI non ricade in _unhandled_input (il click del mouse sì, grazie a un fallback — per questo col mouse funzionava). Soluzione: ogni sotto-schermata ora ha un Button trasparente a tutto schermo (back-catcher) dietro al contenuto; un tap/click ovunque chiama go_back(). I Button gestiscono touch+mouse+emulazione in modo deterministico, senza dipendere dalla propagazione degli eventi. Pad (B) ed ESC/BACKSPACE restano gestiti da _unhandled_input come prima.
  • [fix] Voci del menu di nuovo selezionabili al tocco: il tentativo precedente (root su MOUSE_FILTER_IGNORE + handler _input) aveva rotto il tap sulle voci (le selezionava ma non ci entrava). Ripristinato il routing originale del menu principale; il back-touch è ora isolato nelle sole sotto-schermate via back-catcher, senza toccare la schermata main.
  • [fix] Mappa: nessun modo di chiuderla col touch: aperta la mappa, il velo scuro copriva il pulsante-mappa dell'HUD e non c'era equivalente touch di TAB, lasciando il giocatore bloccato. Ora un tap/click sul velo (_dim) chiude la mappa e torna al gioco; aggiunto l'hint "TOCCA PER CHIUDERE" in basso, mostrato solo coi controlli touch. Su desktop resta valido TAB, sul pad il tasto View.

Igiene bassa (<25%): la gente scappa e la polizia si innervosisce2026-06-16

  • [feat] Civili scappano dal puzzo: in NPC.gd, sotto FILTHY_HYGIENE_THRESHOLD (25%) i civili entro FILTHY_FLEE_RANGE (70px) dal player fuggono via navmesh, riusando la stessa meccanica del panic da gatto (_run_away_from_player). A differenza del gatto (evento one-shot a tempo), è uno stato persistente: _update_filthy_flee ri-arma il panic ogni frame finché resti sporco e vicino; appena ti lavi o ti allontani, scade da solo (FILTHY_PANIC_REFRESH = 1s) e tornano a camminare. Niente _panic_cooldown (è del panic da drunk), così non si auto-blocca.
  • [feat] Frasi dedicate alla puzza: scappando i civili non dicono più "Aiuto!" ma frasi a tema da FILTHY_FLEE_LINES ("Che puzza!", "Si salvi chi può!", "Che tanfo!", "Non si respira!", "Puah, che schifo!", "Lavati!").
  • [feat] Polizia: wanted graduale + nervosismo: in PoliceOfficer.gd, _update_filthy_nuisance — se il player sporco è entro _effective_hearing(), il wanted_level sale gradualmente (FILTHY_WANTED_RATE = 6 pt/s, capped a 100) finché resti vicino. Il poliziotto si innervosisce passando a investigate (indicatore arancione + si avvicina) con battute dedicate (FILTHY_NUISANCE_LINES), su cooldown FILTHY_NERVOUS_REACT_CD (4s) per non spammare. Attivo negli stati patrol e investigate.
  • [note] Soglia condivisa: FILTHY_HYGIENE_THRESHOLD è definita in NPC.gd ed ereditata da PoliceOfficer.gd.

Frecce interagibili: distanza dagli oggetti alti (camioncini + shelter)2026-06-15

  • [fix] Freccia verde troppo attaccata ai camioncini: ice cream van, hot dog van e soup van usavano l'arrow_offset_y di default (-32) ma sono sprite alti (~60px) centrati, quindi la freccia finiva sul tetto. Portato a -52 per staccarla come succede coi bidoni.
  • [fix] Freccia verde troppo attaccata al rifugio: lo shelter ha il cartellone OPEN/CLOSED che sporge sopra il tetto; il margine -(size.y + 16) lasciava la freccia incollata al cartello. Aumentato a -(size.y + 44).
  • [note] Gli altri interagibili (negozio liquori/vestiti, fontana, panchina, bidoni) restano invariati: la loro distanza era già corretta.

Ombre personaggi: aggiunte a barbone e polizia2026-06-15

  • [fix] Ombra mancante sotto il barbone: aggiunto il nodo Shadow a Player.tscn (prima il player non aveva ombra, a differenza dei civili).
  • [fix] Ombra mancante sotto i poliziotti: aggiunto il nodo Shadow a Police.tscn.
  • [change] Aggiunta l'ombra anche all'NPC.tscn legacy (fallback) per coerenza. Stesso setup ovunque: z_index = -1, modulate alpha 0.85, dimensione nativa (shadow.png 16×4), position = (0, 4), texture res://assets/sprites/npc/shadow.png.
  • [note] Tentativo di ingrandire le ombre (scale 1.35) annullato su richiesta: tornate a dimensione originale.

Frecce in stile pixel-art (pensilina + polizia)2026-06-15

  • [change] Freccia "vai al riparo" (ondata di caldo/freddo) ora pixellosa: in Player.gd il poligono liscio della _shelter_arrow è stato sostituito con un contorno a gradini (stesso stile della freccia verde degli oggetti interagibili in Interactable.gd), mantenendo forma e direzione identiche (punta a +X ruotata verso il riparo, ali a y=±8, tacca di coda). Aggiunti antialiased = false e texture_filter = NEAREST per bordi netti.
  • [change] Triangolo sopra i poliziotti (indicatore warn/chase) ora pixelloso e più coerente con lo stile del gioco: in PoliceOfficer.gd il triangolo liscio del ChaseIndicator è stato sostituito con un contorno a gradini, mantenendo stessa forma e ingombro (verso il basso, largo ±7, ~14×14). Aggiunti antialiased = false e texture_filter = NEAREST.
  • [change] Triangolo polizia in inseguimento ora fisso e lampeggiante: rimosso il bob verticale sinuoso (sin) dalla posizione in _update_chase_indicator. La freccia resta ferma sopra la testa e lampeggia (rosso in chase, arancione fisso in warn), come l'indicatore usato in precedenza.
  • [note] Nessun asset esterno: entrambe le frecce restano Polygon2D disegnati a runtime, solo con vertici a scalini invece dei lati lisci.

Polizia: fix "sbatte sul muro in pattuglia mentre i civili si fermano"2026-06-15

  • [fix] CAUSA: PoliceOfficer._physics_process sovrascrive NPC._physics_process e non includeva il ramo if _is_waiting:. Quando un waypoint viene raggiunto (o _move_via_nav si ferma al punto raggiungibile più vicino vicino a un muro), _move_to_waypoint segna _is_waiting=true e ritorna. Sui civili il ramo _is_waiting di NPC._physics_process fa idle, scala il timer e chiama _advance_waypoint() → si fermano e cambiano direzione. Sulla polizia quel ramo non esisteva: continuava a chiamare _do_patrol_move_to_waypoint ogni frame mantenendo l'ultima velocità contro il muro, senza mai passare al waypoint successivo. (Spiega perché i fix precedenti su _move_via_nav sistemavano i civili ma non i poliziotti.)
  • [change] PoliceOfficer._do_patrol: ora gestisce lo stato _is_waiting come NPC.gd (idle + velocity=0 + countdown + _advance_waypoint()), così all'arrivo/incastro la polizia si ferma e riparte verso il prossimo waypoint invece di grattare il muro.

Navmesh: fix target off-mesh (poliziotto fermo sul muro) — root cause2026-06-15

  • [fix] CAUSA VERA del poliziotto fermo che cammina sul muro: i waypoint dei marciapiedi stanno a ~5px dai muri, ma il navmesh è eroso di 9px (raggio agente) → quei punti cadono fuori dall'area calpestabile = irraggiungibili. L'agent puntava al bordo del muro e ci spingeva contro all'infinito (is_target_reached() mai vero perché il target è off-mesh). Succedeva soprattutto ai poliziotti perché inseguono/pattugliano verso punti vicini agli edifici
  • [change] Snap del target sul navmesh (NavigationServer2D.map_get_closest_point): in _move_via_nav il bersaglio (waypoint o player) viene agganciato al punto calpestabile più vicino prima del pathing → diventa raggiungibile, is_target_reached() scatta, l'NPC si ferma in idle invece di grattare il muro
  • [change] _move_to_waypoint: se il nav segnala "arrivato" (punto raggiungibile più vicino al waypoint), considera il waypoint raggiunto → attende e passa al prossimo (prima restava a spingere perché non arrivava mai entro 8px dal waypoint off-mesh)
  • [change] Animazione walk/idle onesta: oltre alla velocità reale, ora usa lo spostamento NETTO su 0.25s (_update_anim_progress). Se l'NPC scivola avanti-indietro su uno spigolo (velocità ≠ 0 ma posizione ferma) mostra "idle" e smette di spingere (velocity azzerata) invece di "walk sul posto"

Navmesh: fix "poliziotto che cammina fermo sul muro"2026-06-15

  • [fix] Animazione "walk sul posto": l'animazione era scelta in base alla velocità RICHIESTA, quindi un NPC/poliziotto bloccato contro un muro mostrava la camminata pur restando fermo (è quello che Ivan vedeva negli screenshot). Ora si basa sulla velocità REALE (get_real_velocity()): se è bloccato mostra "idle", non "walk". Sintomo visivo eliminato
  • [fix] Wall-sliding fisico (_nav_wall_slide): quando il corpo collide con un muro/spigolo, ora la direzione viene proiettata lungo la superficie (tangente alla normale di collisione, presa da get_slide_collision()) verso il path-point → l'NPC costeggia il muro invece di spingerci contro fermo. Soluzione robusta basata sulla geometria reale, non più deviazione a cieco. Validato: poliziotto contro muro con meta dietro lo aggira in ~1.8s
  • [change] Anti-incastro (_nav_unstick) semplificato: solo re-path periodico se non fa progressi (lo slide fa il grosso del lavoro). Rimossi i parametri della vecchia strisciata a cieco
  • [note] Il navmesh è confermato corretto dagli screenshot di Ivan (178 poligoni, verde su tutte le strade, buchi giusti sotto edifici/van/parchi): il problema era solo animazione + wall-sliding, non il bake

Navmesh: evitamento player + anti-incastro + debug F12026-06-15

  • [feature] Evitamento del player reintrodotto (su modello navmesh): il barbone non è un ostacolo del navmesh (così gli NPC ti raggiungono per l'elemosina), ma se ti pari davanti a un NPC ora ti aggira invece di piantarcisi contro e farsi trascinare. NPC._nav_avoid_player applica una componente tangenziale alla direzione del nav quando il player è entro 24px e l'NPC gli va incontro. Disattivato per il poliziotto in inseguimento (avoid_player=false: deve chiuderti fino all'arresto)
  • [change] Anti-incastro (_nav_unstick): se un NPC/poliziotto non fa progressi reali (sbatte su muro/spigolo), forza il ricalcolo del path e scivola parallelo al muro finché si libera. Safety net: con un navmesh corretto il path non passa mai dentro un muro, quindi raramente serve
  • [feature] Debug navmesh nel pannello F1: nuovo pulsante "Toggle Navmesh" (sezione NPC SYSTEM) che mostra/nasconde l'area calpestabile (verde). Aggiunta anche una stampa diagnostica all'avvio (navmesh: N poligoni, M vertici, K ostacoli): se stampa 0 poligoni il bake è fallito (è la causa più probabile se vedi ancora NPC/poliziotti contro il muro)
  • [note] Se a runtime vedi ancora qualcuno contro il muro: apri F1 → Toggle Navmesh. Se il verde NON copre le strade (o è tutto nero), il bake è il problema e va sistemato lì; se il verde è corretto ma l'NPC ci va contro lo stesso, è il pathing/anti-incastro da tarare

Fix navmesh: NPC/poliziotti non vanno più dritti contro il muro2026-06-15

  • [fix] NPC e poliziotti che camminavano dritti contro il muro (e poliziotti che correvano in linea retta verso il player attraverso un edificio): due cause, entrambe in NPC._move_via_nav:
    • (1) target_position reimpostato OGNI frame con un bersaglio in movimento (il player) → l'agent non finiva mai di calcolare il path e restava "vuoto". Ora il target si reimposta solo se cambia >16px (NAV_TARGET_REPATH_DIST), dando all'agent il tempo di calcolare; _nav_invalidate() forza il ricalcolo al cambio waypoint/stato.
    • (2) Il fallback "vai dritto se lontano" scattava ogni volta che il path era vuoto → caricava il muro. Rimosso: ora se non c'è path l'NPC aspetta fermo un frame (is_target_reached() distingue "arrivato" da "path non pronto", invece di is_navigation_finished() che è true in entrambi i casi).
  • [fix] Bake del navmesh reso esplicito e robusto (build_navigation_region): invece di affidarsi alle outline della NavigationPolygon (comportamento ambiguo tra versioni), ora usa NavigationMeshSourceGeometryData2D.add_traversable_outline() (mondo) + add_obstruction_outline() (ostacoli). Il margine agente lo applica il baker (agent_radius), quindi i fori non sono più pre-dilatati (niente doppio margine).
  • [feature] Overlay debug navmesh (_NavDebugDraw, costante NAV_DEBUG_DRAW in WorldGenerator, default false): mettendola a true disegna in verde l'area calpestabile, per verificare a colpo d'occhio se il bake è corretto.

Movimento NPC riscritto con NavigationServer2D (pathfinding nativo)2026-06-15

  • [feature] Pathfinding nativo di Godot al posto dello steering euristico (che si piantava su panchine/bidoni e dava l'effetto nervoso): ora civili e poliziotti seguono percorsi calcolati che aggirano edifici E oggetti (panchine, bidoni, fontane, van, pensiline). Scelta concordata con Ivan: navmesh che ritaglia tutti gli ostacoli, usato da tutti gli NPC
  • [change] Navmesh (WorldGenerator.build_navigation_region, API 4.6 NavigationServer2D.bake_from_source_geometry_data): outline esterno = bordo mondo; un foro per ogni ostacolo, raccolto automaticamente dai chokepoint _make_static_wall/_add_obstacle_rect/_add_obstacle_circle (_register_nav_obstacle) + la pensilina. I fori sono dilatati di NAV_AGENT_RADIUS=9px così gli NPC non rasentano. Bakeato in World._ready prima di spawnare gli NPC
  • [change] NavigationAgent2D creato a runtime in NPC._ready (_setup_nav_agent, vale per ModularNPC e PoliceOfficer). Nuovo _move_via_nav(target, speed, delta, stop_dist) usato da wander (_move_to_waypoint), busking (_move_toward_busker), panico (_run_away_from_player) e dal poliziotto (_do_chase/_do_investigate; patrol usa già _move_to_waypoint)
  • [change] Il player NON è un ostacolo del navmesh → gli NPC lo raggiungono per chiedere/dare l'elemosina (richiesta esplicita di Ivan). Busking/investigate usano stop_dist (~30–36px) così l'NPC si para *vicino* al player senza spingere
  • [change] Rimosso tutto lo steering euristico (feelers, isteresi, _avoid_walls_dir, _pursue_dir, _wall_follow_dir, look-ahead proporzionale…): non serve più. Avoidance RVO dell'agent disattivata (collisione fisica + navmesh bastano, e il player non va evitato)
  • [change] Fallback anti-freeze in _move_via_nav: se il navmesh non è ancora pronto (primo frame) o il target è fuori mesh, l'NPC va dritto verso la meta invece di restare fermo. Mantenuto lo stuck-detection come safety net
  • [note] DA TESTARE IN GODOT REALE: la navigation richiede l'esecuzione (qui nel sandbox non c'è l'eseguibile). Fatta validazione statica (API 4.6 confermate da docs, parse/tab puliti, geometria fori validata in simulazione: tutti dentro il mondo, non degeneri). Il tuning fine di NAV_AGENT_RADIUS/stop_dist potrebbe richiedere 1–2 aggiustamenti a runtime

Steering NPC: niente sfarfallio + poliziotto non si incastra2026-06-15

  • [fix] Effetto "nervoso" (NPC che cambiavano direzione più volte al secondo davanti ai muri) e poliziotto bloccato contro un muro: lo steering anti-muro ricalcolava tutto ogni frame con una singola sonda corta (22px), quindi oscillava ±25° e, alle velocità d'inseguimento, "vedeva" il muro troppo tardi e si incastrava
  • [change] Area d'influenza più ampia (come suggerito da Ivan): WALL_LOOKAHEAD 22→42px + 3 sonde lungo il raggio (½, ¾, punta) + baffi laterali ±30° (_wall_blocked_wide) → l'NPC percepisce il palazzo *prima* di toccarlo e inizia a deviare in anticipo
  • [change] Look-ahead proporzionale alla velocità (_effective_lookahead): chi corre (poliziotto, chase 85) sente il muro più lontano e fa in tempo ad aggirare → niente più incastri
  • [change] Isteresi sullo sterzo (_wall_turn_sign + _wall_turn_hold, 0.5s): scelto un verso di rotazione lo si mantiene invece di ridecidere ogni frame → elimina alla radice lo sfarfallio. Il poliziotto ora riusa lo stesso _avoid_walls_dir condiviso (rimossi _chase_turn_sign/_wall_follow_dir duplicati)
  • [note] Validato in simulazione: NPC lungo un muro = 0 scatti nervosi (prima oscillava di continuo); poliziotto su lato opposto = prende il player in ~3s, 0 frame nel muro, 0 frame da fermo; civili che aggirano lo spigolo = 0 frame nel muro

Poliziotti: aggirano l'isolato durante l'inseguimento2026-06-15

  • [feature] I poliziotti non inseguono più dritti contro i muri: se c'è un edificio tra loro e il player, aggirano l'isolato e tornano verso di te; appena ti rivedono in linea d'aria riprendono l'inseguimento diretto come prima. Vale per _do_chase e _do_investigate (in patrol usano già il pathing anti-muro ereditato da NPC.gd)
  • [change] Nuovo PoliceOfficer._pursue_dir: linea di vista libera (has_line_of_sight_to, ereditata) → rotta diretta; occlusa → wall-following che si impegna su un verso di rotazione (_chase_turn_sign, orario/antiorario scelto sul lato inizialmente libero) e costeggia il palazzo finché non recupera la vista. L'impegno sul verso evita l'oscillazione "avanti-indietro davanti al muro" del semplice steering reattivo
  • [change] Helper di supporto in NPC.gd (riusabili): has_line_of_sight_to() + _segment_hits_rect() (intersezione segmento↔AABB su cache muri locale). World.has_line_of_sight() esposto per completezza
  • [note] Validato in simulazione: poliziotto su lati opposti dell'edificio (N/S, E/O, diagonale) aggira e prende il player anche se fugge (5–7s), con 0 frame dentro l'edificio e recupero della linea di vista a ~3s. La semplice deviazione reattiva, invece, si incastrava (oscillava all'infinito)

NPC non camminano più contro i muri degli edifici2026-06-15

  • [feature] Gli NPC non sbattono più contro i muri ("estetica anni '90"): se la meta è oltre un edificio, ora lo aggirano o cambiano direzione. Due livelli:
  • [change] Pathing con line-of-sight (World._build_route_for): le rotte ora si concatenano scegliendo solo tappe raggiungibili in linea retta senza attraversare un edificio (_seg_hits_wall + intersezione segmento↔rettangolo _segment_intersects_rect). Fonte ostacoli unica: nuovo WorldGenerator.wall_rects, riempito da _make_static_wall (copre edifici, liquori, vestiti, rifugio). Validato: 0 tratte di rotta attraversano un edificio, 0 rotte vuote su 5000 origini
  • [change] Steering anti-muro a runtime (NPC._avoid_walls_dir): se un muro è entro ~22px nella direzione di marcia, l'NPC prova a deviare (±25/50/75/90°) verso il primo lato libero, costeggiando il muro / girando l'angolo; se è circondato inverte (lo stuck-detection cambia poi waypoint). Rect dei muri cachati una volta dal World (get_wall_rects)
  • [note] WALL_PATH_MARGIN/WALL_AVOID_PAD tenuti a 3px (i punti-marciapiede distano ~5px dai muri: un margine maggiore scartava le rotte lungo il marciapiede). Validato in simulazione: 0 frame dentro l'ingombro di un edificio mentre l'NPC raggiunge una meta dall'altro lato

Fix: NPC non più "appiccicati" al player2026-06-15

  • [fix] NPC che restava incollato addosso al player finché non si sbloccava all'improvviso: gli NPC (mask collisione = solo muri) non collidono col player, ma il player collide con loro (mask 2). Quando il player gli stava davanti con un waypoint dietro di sé, l'NPC ci camminava contro scivolando sul corpo del player; il vecchio rilevamento "bloccato" guardava la velocità istantanea (< 30% di walk_speed) che però scivolando restava sopra-soglia → il timer non scattava mai. Si sbloccava solo per caso (waypoint raggiunto o route refresh a 25s)
  • [change] Evitamento del player: _move_to_waypoint ora sterza attorno al player se è entro PLAYER_AVOID_RANGE (26px) — _avoid_player_dir() somma una componente di repulsione + una tangenziale, così l'NPC gli gira intorno invece di spingerci dentro
  • [change] Stuck detection robusto basato sullo spostamento reale (_update_stuck_progress): se in 0.6s l'NPC si è mosso < 6px è incastrato → salta al waypoint successivo. Funziona anche quando scivola contro un corpo in movimento. Resettato negli stati non-camminata (allarme/panico/busking/attesa)
  • [note] Vale anche per i poliziotti (estendono NPC.gd, usano _move_to_waypoint in patrol). Validato in simulazione: con la fix l'NPC aggira il player e raggiunge il waypoint in ~1.3s; senza, restava bloccato >20s

Fix: cestini mai sotto la tettoia sporgente della pensilina2026-06-15

  • [fix] Restava un cestino mezzo nascosto sotto il bordo della pensilina: un bidone d'angolo di un isolato *adiacente* si infilava sotto la tettoia. Causa: la clearance dei cestini usava solo il centro della pensilina (raggio BLOCKED_CLEAR), ma lo sprite è 80×76 e sborda dal blocco verso l'alto → l'angolo era a ~45px dal centro (oltre i 44px) ma comunque sotto la tettoia
  • [change] Introdotto _bin_avoid_rects: il rect dell'ingombro visivo reale di pensilina (80×76, centro a bus_pos+(0,-22)) e van (~64×60) viene salvato e i cestini lo evitano del tutto (_point_in_bin_avoid), sia nel piazzamento sul blocco stesso sia per i cestini d'angolo dei palazzi vicini. Il raggio dal solo centro non bastava per oggetti larghi/sporgenti
  • [note] Conseguenza voluta: sul blocco pensilina (96px, troppo stretto per gli 80px di tettoia) non viene piazzato alcun cestino — meglio nessuno che uno nascosto. Sul blocco van i cestini stanno comunque negli angoli. Validato in simulazione (40k seed): 0 cestini sotto pensilina/van in tutti gli scenari (blocco proprio + blocco adiacente)

Fix: cestini negli isolati occupati solo agli angoli2026-06-15

  • [fix] Cestino davanti alla pensilina (e regola generale): nuova regola richiesta da Ivan — in un isolato che contiene un oggetto non-cestino (camioncino, pensilina, fontana…) i cestini possono stare solo nei 4 angoli; in un isolato vuoto vanno ovunque come prima
  • [fix] Bug storico trovato: _bus_stop_spots veniva azzerato a inizio _scatter_interactables(), cioè subito DOPO che _build_blocks() ci aveva registrato il centro della pensilina → il check anti-pensilina dei cestini era di fatto disattivato, per questo un bidone finiva davanti alla fermata. Ora la lista si svuota solo in generate()
  • [change] Introdotto _occupied_blocks (rect degli isolati con un oggetto). _find_bin_point ora applica la regola: blocco occupato → prova solo i 4 angoli (scartando quelli sotto l'ingombro via BLOCKED_CLEAR); blocco vuoto → posizione interna libera. I cestini d'angolo dei palazzi evitano anche di invadere un isolato occupato adiacente (_point_in_occupied_block)
  • [change] Centro pensilina aggiunto a _blocked_spots; BLOCKED_CLEAR 34→44px (copre la pensilina ~76×62 e il van). Validato in simulazione su 30k seed: 0 cestini sopra pensilina/van, e nei blocchi vuoti i cestini si piazzano sempre

Fix: ogni mappa ha sempre tutti gli oggetti chiave2026-06-15

  • [fix] A volte mancava un camioncino (gelati o hot dog) o altri oggetti: il tipo del van era deciso per-blocco a caso (is_icecream = randf() < 0.5 dentro _build_hot_dog_block), quindi potevi avere due hot dog e zero gelati (o viceversa); e con un solo blocco van ne mancava sempre uno. Anche shelter/liquori/vestiti/pensilina erano promozioni "best-effort" che potevano saltare se il pool casuale si svuotava
  • [change] Generazione garantita: _promote_special_footprints riscritta per riservare almeno un'istanza di ciascun oggetto chiave (rifugio, camioncino hot dog, camioncino gelati, liquori, vestiti, pensilina) prima degli extra opzionali. Nuovo helper _reserve_footprint(kind, prefer) che "ruba" un footprint 1×1 generico (preferendo piazze per i van, palazzi per i negozi)
  • [change] Aggiunto BlockKind.ICE_CREAM distinto da HOT_DOG; _build_hot_dog_block(rect, is_icecream) ora riceve il tipo dal chiamante invece di deciderlo a caso. Map/legenda già gestivano entrambi i tipi (Interactable.Type.FOOD_VAN/ICE_CREAM_VAN)
  • [note] Validato in simulazione su 20.000 seed: ogni mappa contiene tutti e 6 gli oggetti richiesti, 0 mancanti (footprint disponibili ~34, riserve necessarie ~8)

Fix: cestini non più sovrapposti a van/oggetti2026-06-15

  • [fix] I cestini potevano spawnare sopra il camioncino hot dog/gelati (e ad altri oggetti): il blocco del van viene registrato in plaza_rects, quindi il loop "cestini nelle piazze" ci scatterava sopra dei bidoni. Stessa logica fragile per fontane/panchine. Ora i cestini non si sovrappongono mai ad altri oggetti
  • [change] Introdotto _blocked_spots (centri di van, fontane, panchine — riempito in _build_hot_dog_block/scatter, svuotato a ogni generate()) + helper _too_close_to_blocked() con clearance BLOCKED_CLEAR = 34px. Nuovo _find_bin_point(): prova prima un punto interno (scartando oggetti ingombranti + fermate), e se il blocco è occupato ripiega sui bordi/angoli dell'isolato (_block_edge_points()) — esattamente la proposta di Ivan ("i bidoni al massimo ai bordi dell'isolato"). Anche i cestini d'angolo attorno ai palazzi ora evitano i van. Validato in simulazione: 0 sovrapposizioni su 20k piazzamenti (blocchi 1×1 e 2×2)

Fix: player non esce più dai bordi del mondo2026-06-15

  • [fix] Il player poteva camminare oltre i bordi della mappa e "sparire": la camera ha i suoi limit_* (si ferma al bordo del mondo), ma non c'era nessun muro perimetrale né clamp sul player → lo sprite continuava a muoversi fuori dall'inquadratura nel vuoto. Rientrando si ri-attraversava la DowntownZone (Area2D 1632² = tutto il mondo) e il suo body_entered rifireva → comparsa di "Entrato: Centro". Quindi non era un bug del messaggio: era il sintomo del player che usciva e rientrava nel mondo
  • [change] Aggiunto clamp ai bordi del mondo in Player._clamp_to_world() (chiamato dopo move_and_slide()): la posizione è limitata a [8px, world_size - 8px]. World passa world_size al player (player.world_bounds) subito dopo lo spawn. Ora il barbone si ferma al bordo invece di sparire, e niente più "Entrato: Centro" spurio
  • [change] Rimosso il messaggio "Chiudi: T" dalla mappa (MapOverlay): era posizionato sotto il bordo della cartina e di fatto non si vedeva; in più i giocatori chiudono la mappa da soli (stesso tasto/View/pulsante che la apre) senza bisogno del promemoria. Eliminati _hint, _update_hint() e la relativa riga di layout/posizionamento

Mappa: legenda staccata dal bordo + voce "Tu sei qui"2026-06-15

  • [polish] Mappa e legenda spostate leggermente a sinistra (MapOverlay): introdotta CONTENT_SHIFT_LEFT = 28px che rientra l'intero blocco mappa+legenda dal bordo destro della cartina, così la legenda non resta più attaccata al bordo (ora ~38px d'aria tra pannello e bordo carta). Map area + fascia legenda derivano da un nuovo rect content invece che direttamente da paper
  • [feature] Aggiunta la voce "Tu sei qui" come prima riga della legenda: pallino rosso in pixel disegnato a mano (nuova classe _LegendDot, stessa maschera DOT_MASK e colore COL_DOT del marker del player sulla mappa, ma statico e centrato nel box icona). Coerente con quello che il giocatore vede lampeggiare sulla mappa

Frecce interagibili pixel + lampeggio2026-06-15

  • [polish] Freccia verde "interagibile" (sopra bidoni, negozi, fontana, ecc. in Interactable.gd) ridisegnata in pixel-art: il triangolo Polygon2D liscio/anti-aliasato è stato sostituito da un contorno a gradini fini (step 1px×2px — via di mezzo tra il triangolo liscio e gradoni troppo grossi; 14px largo × 14px alto, rastrema a scatti fino alla punta centrale) con antialiased = false e texture_filter = NEAREST, così i bordi sono netti ma morbidi e si intonano con gli sprite pixel del gioco
  • [change] Movimento rimosso: niente più bob verticale (sin). La freccia ora resta ferma e lampeggia a intermittenza (ciclo on/off, ARROW_BLINK_PERIOD = 0.9s → ~0.45s accesa / ~0.45s spenta). Resta nascosta durante il cooldown dell'interazione come prima

Font pixel coerente con la grafica2026-06-15

  • [fix] Discrepanza font risolta: il gioco non aveva font né tema custom, quindi tutto il testo usava il default di Godot (Open Sans) renderizzato con antialiasing → testo liscio/vettoriale che stonava con gli sprite pixel (default_texture_filter=0 = Nearest). Ora tutta la UI usa un font pixel coerente con la grafica
  • [asset] Nuovo font StreetU-Pixel.ttf (assets/fonts/): le maiuscole/cifre/punteggiatura derivano dal font pixel 5×7 già usato nei titoli del manuale PDF (build_manuale_pdf.py, dict G) → coerenza tra manuale e gioco. Aggiunto un set di minuscole disegnato a mano (x-height 5px con ascendenti b d f h k l e discendenti g j p q y), la punteggiatura usata nell'HUD (% / ( ) , =) e le vocali accentate italiane (À È É Ì Ò Ù / à è é ì ò ù, composte sovrapponendo grave/acuto alle basi). 90 glifi totali. Ogni pixel è un quadrato vettoriale → nitido a ogni dimensione. Generato via fontTools con build_pixel_font.py (in root)
  • [note] Per fare spazio agli accenti sopra le maiuscole l'ascent del font è salito a 900u (9 celle): l'interlinea nei blocchi multi-riga è leggermente più alta. Se serve compattarla, regolare line_spacing nei Label/RichTextLabel interessati
  • [feature] Nuovo tema globale assets/ui/default_theme.tres (default_font = StreetU-Pixel, default_font_size = 16), impostato come gui/theme/custom in project.godot. Si applica a tutta la UI senza toccare i ~64 override font_size esistenti nelle scene
  • [note] Import del font con antialiasing disattivato + mipmaps off + hinting off, per mantenere i bordi netti. Il file .fontdata viene rigenerato dall'editor di Godot alla prima apertura. Se la tua versione di Godot rifiuta qualche parametro nel .import, cancellalo e lascia re-importare, poi imposta Antialiasing=None nella tab Import

Mappa: cornice cartina turistica wide2026-06-15

  • [asset] map_parchment.png aggiornata a una cartina turistica moderna in formato 3:2 (carta sgualcita con pieghe, bordo blu/oro, angoli consumati; generata da ChatGPT su magenta). Importata a 768×512 (chroma key + resize NEAREST). Più coerente con l'ambientazione urbana; il 3:2 è la via di mezzo tra il quadrato 4:3 iniziale e il wide 16:9
  • [feature] La mappa è incorniciata dalla cartina: MapOverlay la disegna come sfondo (massimizzata mantenendo l'aspect 3:2), mappa + legenda vivono dentro l'area carta interna (frazioni PAPER_L/R/T/B ricalibrate sulla nuova immagine). Fallback automatico al layout a tutto schermo se l'asset manca
  • [change] Rimossa la scritta "MAPPA" in alto a sinistra (ridondante)
  • [feature] Colori mappa integrati con la carta: la palette dei quartieri è ora "inchiostro/acquerello su carta antica" (toni caldi desaturati — marrone per i palazzi, verde oliva per i parchi, sabbia/terracotta per piazze e rifugio). Lo sfondo strade è seppia semi-trasparente così la grana della cartina traspare (niente più rettangolo nero "incollato"); bordo mappa da bianco a seppia scuro. La mappa ora sembra disegnata sulla carta
  • [polish] Legenda intonata alla carta (pannello seppia chiaro translucido + testi inchiostro scuro, niente più pannello nero contrastato). Palazzi resi più scuri e variazione dithering limitata al verso scuro così i blocchi non si schiariscono fino a confondersi con la carta (alcuni isolati erano quasi invisibili)
  • [polish] Icona pensilina sostituita con un semplice cartello fermata bus (cartello blu + simbolo bus bianco su palo): l'ibrido bus+ombrello+panchina era illeggibile a 20px. Label legenda → "Fermata bus (riparo)"
  • [polish] Icona fontana rimpicciolita: nuova map_fountain.png 20×20 (da fountain.png 56×56, autocrop + resize NEAREST) coerente con le altre icone mappa, invece della versione grande e troppo dettagliata. Fallback al fountain.png originale se manca
  • [polish] Bordi pixel "disegnati a mano": i contorni di quartieri, bordo mappa e riquadro legenda sono disegnati come file di tasselli quadrati (pixel grossi da 4px, BORDER_PIX) con offset a scatti deterministico, invece di polilinee anti-aliased — ora la grana è coerente con la carta pixel-art sotto. Anche il riempimento dithering (DITHER_CELL) allineato a 4px e snappato alla stessa griglia globale così bordo e interno combaciano

Mappa: dithering quartieri + legenda laterale2026-06-15

  • [feature] I blocchi quartiere (parchi/piazze/palazzi/rifugio) ora sono dithered/pixellosi invece che colore piatto: pattern a scacchiera di tasselli ~3px con due tinte + rumore deterministico + leggera variazione di tinta per blocco (_draw_dithered_rect + _hash01 in MapOverlay). Look pixel-art retro, tutto via codice, disegnato una volta sola all'apertura
  • [feature] Legenda laterale fissa a destra della mappa: pannello con icona + nome per ogni POI (rifugio, hot dog, gelati, liquori, vestiti, fontana, pensilina), riusa le sprite vere. L'area mappa si restringe per far spazio (LEGEND_WIDTH)

Mappa a pieno schermo2026-06-15

  • [feature] Nuovo scripts/ui/MapOverlay.gd (CanvasLayer, process_mode = ALWAYS): mappa a pieno schermo in sovraimpressione che mette il gioco in pausa piena mentre è aperta, con velo semi-trasparente per intravedere il mondo congelato sotto
  • [feature] La mappa disegna la città in scala leggendo i dati che WorldGenerator già espone: aree (strade/parchi/piazze/palazzi/rifugio) dai *_rects pubblici, e i POI dal gruppo interactables
  • [feature] Icone POI con le sprite vere del gioco: rifugio (shelter_sign), hot dog (hotdog), gelati (icecream), liquori (wine), vestiti (clothes_1), fontane (fountain). Esclusi panchine e cestini (deducibili: panchine nei parchi, cestini random per strada)
  • [feature] Marker del player = pallino rosso pixelloso intermittente (maschera 5×5 a pixel grossi, snappata alla griglia, solo acceso/spento via BLINK_SPEED). Sostituisce sia lo sprite del barbone sia il triangolino: marker minimale, pulito e visibile a colpo d'occhio. Funziona anche in pausa
  • [feature] La musica continua mentre la mappa è aperta (a differenza della pausa-menu): il nodo Music passa a process_mode = ALWAYS all'apertura e torna PAUSABLE alla chiusura, evitando lo stacco audio brusco
  • [feature] Apertura/chiusura: tasto T da tastiera, tasto View/Back (i due quadratini, JOY_BUTTON_BACK=4) sul gamepad, e pulsante mappa sull'HUD per touch/iPad
  • [change] Il pulsante mappa HUD ora appare solo coi controlli a video (DisplayServer.is_touchscreen_available() e non gamepad), non più su desktop dove copriva meteo/high-score. Spostato sul bordo destro centrato verticalmente per non collidere con HUD e controlli virtuali. Aggiunta opzione nel DebugPanel (F1) "Mostra pulsante mappa (desktop)" per forzarne la comparsa
  • [fix] Icone POI ricentrate sul blocco: shelter/liquori/vestiti hanno l'interactable davanti all'ingresso, quindi l'icona cadeva fuori dal quadratino. Ora MapOverlay cerca il blocco sopra l'ingresso e centra lì. Inoltre WorldGenerator registra i blocchi di liquori/vestiti in building_rects e quello dei van in plaza_rects, così appaiono come aree disegnate sulla mappa (prima i loro POI fluttuavano su sfondo nero)
  • [fix] Le pensiline ora appaiono sulla mappa: non sono Interactable (sono Node2D), quindi venivano ignorate. Aggiunto bus_stop_rects pubblico in WorldGenerator (popolato in _build_bus_stop_block, non svuotato dopo lo scatter come invece accade a _bus_stop_spots) e il blocco registrato anche in plaza_rects. MapOverlay le disegna leggendo bus_stop_rects
  • [asset] Aggiunte le icone mappa dedicate (generate da ChatGPT, stile pixel-art SNES/GBA su magenta): map_bus_shelter.png (pensilina = panchina + cartello bus + ombrello giallo) e map_shelter.png (rifugio = casetta con cuore rosso sul tetto). Importate a 20×20 in assets/sprites/world/ via il nuovo tools/import_map_icons.py (chroma key magenta strict+loose, autocrop, resize diretto NEAREST per look pixel-art chunky coerente con le altre icone mappa a 16px — niente LANCZOS che le rendeva troppo definite)
  • [feature] Nuova azione input map in project.godot; label aggiunte in InputManager.gd (T / "View"). ESC o Start chiudono la mappa se aperta invece di aprire il menu-pausa
  • [fix] Input mappa letto in HUD._process con Input.is_action_just_pressed (come fa il Player) invece che in _unhandled_input: TAB veniva intercettato dal sistema focus UI di Godot e non arrivava mai all'handler. Scelto T (tasto libero) al posto di TAB; SPAZIO scartato perché riservato (ui_accept)
  • [note] Il busking resta su M (tastiera) / Y (gamepad): la mappa usa T per evitare conflitti, in attesa di un riassetto completo dei tasti