La possessione del fantasma, e i due difetti delle repliche in rete2026-09-08
Seconda ondata dello stesso brainstorming: Ivan porta quattro spunti dal multiplayer — gli NPC che «camminano da fermi» col lag, l'avversario morto visto come sprite normale, e un sistema nuovo, la possessione dei nemici da fantasma. Cinque ticket e un brief; nessuna riga di gioco toccata.
- 📌 I due difetti hanno una riga. Le repliche degli NPC scelgono walk/idle dalla sola velocità ricevuta (
NPC.gd:2111-2113), che non decade mai: col pacchetto in ritardo il dead-reckoning si ferma al cap e il puppet resta fermo animato fino al battito di 2 s — sull'host l'animazione va già a spostamento netto, al puppet manca quel pezzo. Il fantasma visto come sprite normale: _apply_ghost_visuals() non azzera i flag gatto/sbornia/busking del puppet, e il primo _net_state dopo il collasso (canale diverso, ordine non garantito) rimette foglio normale e tinta di slot — scatta solo se al collasso avevi il gatto in braccio, eri ubriaco o suonavi, ed è per questo che «su un device sì e sull'altro no». - [docs]
OPUS_BRIEFS/M3_POSSESSIONE_DEL_FANTASMA.md: da fantasma, a 48 px da un nemico, tenuta di 3 s con tremore → lo guidi; il nemico fa quello che fa sempre, ma va dove vuoi tu (il poliziotto arresta solo il ricercato, ma stando addosso vede ogni infrazione); tocco = il suo colpo a distanza; 120 punti fissi per colpo a segno, che si sommano alla run e si ri-trasmettono in classifica; uscita con 3 s di tenuta, il gatto scaccia sempre lo spirito (yeti compreso); evidenza = il personaggio è indemoniato, velo rosso deciso su tutto lo sprite (precisato da Ivan: il rosso sul personaggio, non sul nome) con un nodo sovrapposto, mai modulate, più il nome del fantasma nel suo colore che dice solo chi lo guida (già conteso: è la causa del secondo difetto). Lista: poliziotto, tossico, spazzino, gattara, teppista, rivale (aggiunto: nel Centro altrimenti si possiede solo il poliziotto), e in un terzo lotto yeti, pinguino, fenicottero, che vivono su una rete propria. Nessun precedente di input verso l'host nel codice: si aggiunge (_srv_npc_input a 20 Hz), l'host resta l'unico simulatore. - [docs] I ticket (backlog, epic M1,
mp): SU-884 repliche che camminano sul posto (prerequisito della possessione) · SU-885 fantasma visto come sprite normale · SU-886 → SU-887 → SU-888 la possessione in tre lotti. File: TMP/ticket_0908_possessione.txt. - ⚠️ Non provato niente: lettura e design. Le due cause sono lette nel codice, non riprodotte con due peer.
Due partite online lette nella telemetria, e il tunnel che scende in multiplayer come sfida a chiamata2026-09-08
Ivan ha giocato due partite online (iPhone 14 contro il Samsung A52s di Alberno) e ha portato otto punti in chat. Prima la telemetria, poi il codice, poi un brainstorming a domande singole: undici ticket nel backlog e un brief nuovo. Nessuna riga di gioco toccata.
- [docs] Cosa dicevano le due partite (
stage dell'8/9, run_70b1332e61b39d64 + run_f6dd1d45d8abb4dd con lo stesso seme, e run_9242fd666f66764d): Ivan «Divano_Prime» 5.949 e 3.054 punti, Alberno «Bonni» 3.443 nella prima e uscito a 5'00" nella seconda, dieci secondi dopo il terzo arresto di Ivan; il file di Alberno della seconda non è ancora arrivato. Tutti e due hanno depositato 10×10 $, avuto l'arcobaleno, premuto la fermata: niente. La carta radar non è mai comparsa fra 18 e 24 carte offerte. q=centro su tutti i device con MERCATO scelto in lobby. Nessuno SCRIPT ERROR nei log iPhone. - 📌 Le cause, trovate nel codice e non supposte. La doppia musica: il ramo spettatore salta apposta la pausa dell'albero e l'HUD accende comunque
gameover.ogg sopra Music — il CHANGELOG di quel lavoro diceva di non farlo lì, il codice fa il contrario; _stop_music_game_over() non ha chiamanti. Il quartiere: due difetti — rematch() manda la RPC senza quartiere (default "centro") e, all'ingresso di un ospite, il registro senza unlocked fa cadere la scelta dell'host a centro salvandola su disco. Il radar è sesto_senso, bloccata a 3 partite online (SU-362). Il tunnel bonus in multi è spento per scelta (puo_entrare()), con l'arcobaleno che resta acceso; sotto terra la scoreggia non è mai esistita. - [docs]
OPUS_BRIEFS/M2_METRO_A_CHIAMATA.md: il tunnel in multi come sfida a chiamata — chi paga apre il tunnel a tutti per 20 s, si scende gratis al suo giro, ognuno gioca la propria copia (a parità di giro il tunnel è già identico per tutti: _rng.seed = SEME_BASE + giro × PASSO), la città non si ferma (congelamento solo video, l'host resta capo della superficie), giro condiviso sull'host che sale se almeno uno vince, banner CAPOLINEA con le monete raccolte, scoreggia sotto terra come arma d'area che rallenta anche gli altri nel tunnel. Scartata la divisione degli host proposta da Ivan: sarebbe una migrazione di host, e il tunnel non ha stato condiviso da ospitare. - [docs] I ticket (backlog, epic M1 e W1, tutti
mp): SU-873 doppia musica · SU-874 quartiere della lobby · SU-875 radar alla prima partita online · SU-876 fine partita a due voci (TORNA IN LOBBY / MENU PRINCIPALE, via la RIVINCITA) · SU-877 terza pagina = classifica, anche con chi è scappato e provvisoria al collasso · SU-878 classifica in partita in alto a destra, righe che scivolano al sorpasso · SU-879 → SU-880 → SU-881 la metro a chiamata · SU-882 e SU-883 la scoreggia sotto terra. File: TMP/ticket_0908_multi.txt. - ⚠️ Non provato niente: è solo lettura e design. La prova del difetto della lobby (b) è ricostruita dal codice, non riprodotta con due peer.
Referto telemetria 0.43 a quattro mani: Claude e Astra sugli stessi 358 file, e due difetti che nessuno dei due referti precedenti vedeva2026-09-08
Ivan ha chiesto «un'analisi dettagliata a quattro mani con Astra di tutte le partite in telemetria giocate nella 0.43». Fatto così: i file di Drive copiati in TMP/telemetria_043/raw/ (Drive li materializza a richiesta e una lettura diretta sembrava bloccata), poi due analisi separate e cieche — Claude con analisi1-5.py, Astra (gpt-6-astra, lotto TEL043, 33 min) con i suoi script in TMP/telemetria_043/astra/ — e un secondo giro di confronto (TEL043B). Referto unificato in TMP/telemetria_043/RAPPORTO.md, pagina con i grafici su artifact.
- [fix] Il criterio di vittoria del bonus dei referti precedenti era sbagliato (trovato da Astra): confrontava lo
stat prima della porta con quello dopo il ritorno, e l'elemosina fatta in città faceva sembrare vinta una porta persa. Nella 0.43 7 «vittorie» su 27 erano sconfitte (vero: 20 vinte, 10 perse, 2 ignote su 32); il «91%» del referto 0.42 era 76% (42/55). TMP/telemetria_043/porte.py ora misura il denaro dentro il congelamento e riproduce i verdetti di Astra; la regola è in memoria. - [fix] Nove file di settembre sono caricamenti doppi (stesso
run_id, byte identici, rispediti 1–3 giorni dopo): la POST va in timeout a 20 s dopo che il server ha già scritto e il file resta in coda (files/homeless_city/scripts/autoload/RunRecorder.gd:191-197). Esclusi con duplicati.json; il rimedio vero è l'idempotenza lato Apps Script. - [bug]
files/homeless_city/scripts/autoload/MetaProgress.gd:1217 cancella la chiave benches a ogni save() anche quando non c'è: 847 righe Cannot erase nonexistent key in 12 code di log su 22, che riempiono i 16 KB della coda inviata dopo un crash. Basta una guardia has_section_key. Non corretto qui: è un ticket. - [bug] Nelle uscite di scena il
run_end è letto dopo il reset in tutti i campi (livello 1, vite 9, 0 $, earned 0, super vuoto: 10 su 10): SU-773 (ad346d1) rimette il livello; vite, soldi, earned e super vanno controllati. - 📌 I numeri che contano: 31 partite da 7 telefoni, 23 di due soli; 3 tester ancora sulla 0.42 (SM-A528B fino all'8/9 alle 07:24) e 3 spariti; partita mediana 163 s ma 12 uscite volontarie su 27; Gattopoli e Brina restano i muri (un colpo ogni 7,7–11 s e 9,6–15,7 s); il bonus si raggiunge a 197 s (378) e senza l'S23 si vince 5 volte su 15; due partite in Collina fanno il 77% dell'elemosina di tutta la versione; la banca è il 42% dei byte della run troncata dal cap; nessun crash; nessuna regressione di fps dimostrabile (l'Honor sembrava peggiorato per 7.372 s di pause).
- 📌 Cosa ha corretto chi: Astra a Claude — bonus, vite/min per quartiere (orologio contro tempo di gioco), Honor, quota del tunnel (38% dell'orologio, non 73% del tempo di gioco che il tunnel non conta); Claude ad Astra — il nono duplicato, la cadenza dei colpi, gli mtime per datare l'adozione. Sette porte contestate riverificate sul grezzo da entrambi: nessun verdetto caduto.
- ✅ Sette ticket nel backlog, su ok di Ivan («si apri»): SU-866 guardia
benches in MetaProgress · SU-867 vite/soldi/earned/super nel run_end delle uscite · SU-868 partite doppie (idempotenza per run_id) · SU-869 banca aggregata + ora ISO nel run_start · SU-870 (DESIGN) cadenza dei colpi a Gattopoli e Brina · SU-871 (DESIGN) bonus tier 1–3 sui telefoni non S23 · SU-872 misura A-B-A Honor/Redmi e menu di pausa. Collina e tetto del tunnel non riaperti: SU-767 e SU-768 sono già decisi e i dati nuovi li confermano. - ⚠️ Non verificato: nessun orario assoluto di gioco (solo mtime di ricezione), nessun id dispositivo (l'S23 sono due profili), esito di 2 porte e 4 partite senza fine, zero partite multiplayer (1 ricerca lobby fallita per
NoConnection), efficacia di SU-765/766 che non sono nella build da store.
Codex si prende anche il cancello e le istruttorie, e il contesto dell'orchestratore ha una soglia2026-09-07
Ivan, a metà settimana: «stiamo usando poco codex, siamo al 50% di utilizzo settimanale di claude e solo al 19 di codex». Il registro dei lotti diceva il contrario — 55 lavori Codex contro 3 builder Claude dal 04/09 — quindi i token di Claude non stavano nei builder. Misurato sui transcript (229 MB, 04–07/09, python3 tools/misura_transcript.py --da 2026-09-04): 1.235M token grezzi, 942M (76%) nel thread principale, 290K di contesto medio per 3.292 richieste; la sessione /sprint del 06/09 da sola 277M, 669 richieste, contesto mediano 389K e massimo 772K. Il cancello di un lotto costava ~23M sul primo piano, otto volte il punto Codex che l'aveva costruito. Cosa faceva il principale quando pagava: letture di codice 22%, Chrome 20%, provini e PNG 15%, git 11%, Jira 6%.
- ✅
tools/codex_lotto.py cancello — l'istruttoria del cancello di chiusura su Codex luna, in sola lettura: lo script prende da sé il testo del ticket con gli ultimi 3 commenti (jira_leggi.sh, sul Mac: Codex non ha le credenziali), il diff (non committato sui file attesi, o --commit), il report del builder e il perimetro (--lotto legge i file toccati dal FINE.json), e Codex restituisce in ≤90 righe i file fuori perimetro con l'origine, la tabella richiesta | esito | prova, i punti da guardare, la bozza del commento Jira (≤900, nell'ordine di chiusura-ticket) e la bozza della voce CHANGELOG. All'orchestratore restano compile-check locale, provino, commit e Jira. Provato su SU-814 al commit 01c89c2 (CAN814): 2 minuti, 117K token in ingresso, 8K in uscita, 0 punti sulla settimanale; 16 righe di spunta, con l'ultimo KO di Ivan («compare pochissimo») correttamente NON FATTO e le misure comportamentali NON MISURATO — quel commit era di soli sprite. Due difetti di formato corretti nel prompt dopo la prova: citava le righe di diff.patch invece del file:riga nel repo, e prendeva l'autore del commit per il builder. - ✅
tools/codex_lotto.py istruttoria — la lettura di codice per conto dell'orchestratore (luna/medium, fatti con file:riga, ≤40 righe). Provato sul riccone (IST_RICC): 1 minuto, 371K in ingresso di cui 314K in cache, 2K in uscita, 0 punti, 18 righe con stati, funzioni, costanti e catena del colpo. Corretto dopo la prova: niente link markdown con path assoluti, file:riga in testo semplice. - ✅
tools/hook_contesto.py (PostToolUse, in .claude/settings.json) — legge dal transcript il contesto dell'ultima richiesta del thread principale e lo dice al modello quando passa 250K, poi ogni 50K; una volta per sessione ricorda che Chrome nel principale è vietato. Nei subagenti tace. Provato sul transcript della sessione da 772K: avviso alla prima chiamata, silenzio alla seconda, promemoria Chrome, 25 ms. La regola «orchestratore ≤250-300K» stava in ORCHESTRAZIONE.md da settimane e non reggeva perché il modello non vede il proprio contesto. - ✅
tools/misura_transcript.py — il misuratore: token grezzi per thread e modello, per giorno, e cosa faceva il principale quando pagava (categoria dal primo tool del turno); --profilo sprint per le sessioni /sprint. È il dato da rifare quando la settimanale sale senza builder in volo. - 📌 Il provino a finestra NON va a Codex, provato senza spendere una chiamata:
codex sandbox con la harness di SU-825 muore in 7 secondi, nessuno scatto e nessuna riga di log — il seatbelt non dà il WindowServer. Resta al collaudatore, che almeno tiene i PNG fuori dal principale. - ⚠️
systemMessage di un hook va all'utente, non al modello (il riassunto del doc fatto da un modello piccolo diceva il contrario: verificato sul testo): al modello parla additionalContext, che PreToolUse non accetta e PostToolUse sì. - 📌 Regole aggiornate:
ORCHESTRAZIONE.md (§ «Dove va davvero la settimanale di Claude», tabella «A chi va cosa», roster, registro, trappole), chiusura-ticket punto 2 (la spunta la istruisce Codex per ogni lotto; tuttofare-haiku solo a Codex esaurito), /sprint (punto 6 e nuovo punto 8: soglia di contesto, modello a 200K, Chrome mai nel principale, istruttorie a Codex), WORKFLOW/02_SPRINT.md (sintesi e soglie), WORKFLOW/README.md, CLAUDE.md, AGENTS.md. - ⚠️ Non provato: un
cancello su un lotto non committato con --lotto (il tree era pulito, la prova è stata con --commit); e quanto la settimanale di Claude scende davvero con le tre regole — lo dirà il registro del primo sprint.
Il riccone alla misura approvata, e senza il doppione che lo faceva sembrare enorme2026-09-07
SU-814, secondo giro. Ivan: «il riccone e' troppo grande, ne abbiamo gia' parlato ma in game e' troppo grande! avevamo gia' adottato anche una soluzione ma non mi sembra essere stata adottata a dimensioni». Aveva ragione due volte, e la seconda non se l'aspettava nessuno.
- ✅ La prima ragione: il numero approvato valeva solo dove era stato misurato. La variante A scelta da Ivan su SU-805 diceva «totale 47 px», ma quel 47 era stato preso su un solo frame, quello EST. Le righe S, NE e N stavano a 52-53. La scala era stata applicata davvero — ma misurata sul frame migliore invece che sul peggiore. Ora la scala e' unica e isotropa e il bersaglio e' il frame PEGGIORE: 47 px di altezza massima su tutte e 25 le celle.
- 📌 La seconda ragione, che e' probabilmente quella vera: nelle celle non c'era solo il riccone. Contando le componenti connesse dell'alfa sono venuti fuori 64 pezzi staccati dal corpo, fra cui un secondo mezzo personaggio duplicato di 180 px in piedi accanto al riccone (cella NE0) e una barra nera piena di 265 px (N4). Erano loro a portare la larghezza a 58 px: il corpo vero non ha mai superato i 44. Larghezza massima da 58 a 41, pezzi staccati da 99 a 35 — e i 35 che restano sono le nuvolette di polvere e le striature di velocita', che sono arte voluta.
- ⚠️ Il dato sbagliato era nel brief, e l'aveva scritto l'orchestratore. «58 px di larghezza, 4,5 volte un NPC» era una misura del bbox, e il bbox conteneva il doppione. Un builder che segue un brief non ha modo di accorgersene: la componente connessa la si conta solo se si sospetta che ci sia qualcosa di staccato.
- ⚠️ La trappola piu' cara, e vale piu' del fix. Il primo tentativo ha ridotto partendo da
TMP/su805_riccone/richman_sheet_clean_unscaled.png, che dal nome sembra la versione buona ed e' invece la ruota piena che Ivan aveva bocciato di persona su SU-805 — quel «clean» e' la pulizia del magenta, non la scelta della ruota, e nella stessa cartella ci sono cinque varianti di cui tre a ruota piena. La misura che le separa in un colpo: pixel quasi-neri nella zona ruota della cella E0 — 92% la palla bocciata, 39% quella col cerchione. La sheet installata ora sta a 33,2%, cioe' meglio dell'originale. - ⚠️ E un cancello scritto male costa un giro intero. L'orchestratore aveva chiesto «almeno 250 colori distinti» nella ruota senza guardare quanti pixel ci sono a quella scala: a 42 px la zona ne ha 224 in tutto, quindi 250 colori distinti sono impossibili per definizione. Il builder si e' fermato senza installare e ha riportato la contraddizione coi numeri, che e' la cosa giusta da fare. Il criterio buono e' la percentuale di quasi-neri, che non dipende dalla scala; il conteggio dei colori misurava solo quanto era stata morbida la riduzione.
- ✅ Fonte finale:
raw_originale.png a 250 px per cella, riordinato guardando le pose una per una (E = riga 2 del raw, SE = 3, S = 0, NE = 4, N = 1), ridotto una volta sola con LANCZOS e alfa binaria. NEAREST era stato provato e buttava via il cerchione insieme all'11% delle righe. - ⚠️ Gli scarti stanno anche nel raw a 1250 px (
sprites_raw/richman_sheet.png), quindi precedono SU-805. Il raw non e' stato modificato — riordinarlo per ripulirlo cambierebbe in silenzio la convenzione delle righe — e chi un domani rigenera da li' se li ritrova. - ✅ Comportamento invariato, nessuna riga di
Richman.gd toccata: probe_su814_riccone.gd conferma arresto 1,017 s, partenza 225 px/s, bersaglio spostato di 60 px → 0 colpi, nuvola e rimozione in 0,6 s, linea di vista bloccata → nessuna rincorsa in 10 s. - 📌 Il contatto per Ivan ha quattro colonne (
TMP/su814_riccone/contatto_ABC_ivan.png): «pulita e non ridotta» a 53 px, poi A 47, B 42, C 38, ognuna accanto a un NPC vero nelle pose E e S. E' installata A, che e' la lettera che lui aveva gia' scelto; se il doppione era tutto il problema, la prima colonna gli basta e si torna indietro in un minuto.
Il giro del brano lo fa il motore, e sul web sparisce un secondo di silenzio2026-09-07
SU-846. Ivan sentiva la musica fermarsi mentre sceglieva la carta a fine metropolitana, e chiedeva se fosse una conseguenza del lavoro fatto per il web. La risposta e' no, e la sonda la da' due volte: alla scelta carta la musica del tunnel non si ferma ne' su desktop ne' sul web (5 aperture, defect=0), e invertendo le due righe di SU-823 i numeri sono identici.
- 📌 Il difetto pero' c'era, e stava altrove: al GIRO DEL BRANO. Il loop non lo faceva il motore, lo faceva un rimbalzo in GDScript —
finished → play(). Su desktop costa un fotogramma; sul web rifar partire una sorgente Web Audio passa da una callback JS e costa quasi un secondo. Cronometrato al fotogramma: web tunnel 1,025 s, web citta' 1,079 s, desktop 0,058 s. - ✅ Il conto di perche' lo sentiva PROPRIO sulle carte torna da solo, ed e' la parte che spiega il ticket: il tunnel dura ~100 s,
bonus_stage.ogg ne dura 118, quindi il giro del brano cade quasi sempre col ventaglio aperto. Il ventaglio non lo causava: lo esponeva. - ⚠️ Ed e' anche il motivo per cui la sonda del giro precedente non lo vedeva. Quella teletrasporta il runner in fondo al tunnel, quindi il ventaglio si apre a ~5 s di brano — un punto in cui il difetto non puo' presentarsi. Il provino era sano e la misura giusta: sbagliato era il momento che riproduceva. E' la stessa famiglia di trappola gia' pagata («un provino che semplifica esclude il ramo col difetto»), in una forma nuova: qui non semplificava la scena, semplificava il TEMPO.
- ✅ Cura: una riga per traccia,
loop=true nell'import delle dieci tracce che passano dai tre rimbalzi finished → play() del progetto. Non e' un cambio di comportamento: quelle tracce ciclavano gia', solo nel modo che sul web costa un secondo. Fuori gameover.ogg, che non deve ciclare. - ⚠️ Lo scopo e' stato allargato di proposito oltre il tunnel, e va detto perche' e' una scelta contestabile: con la cura sul solo bonus, lo stesso buco da 1,079 s si sarebbe sentito camminando in citta', e uno da 1,0 s nel menu, che e' la schermata piu' vista del gioco. I rimbalzi in tutto il progetto sono tre —
BonusLevel.gd:5852, World.gd:1000 e MainMenu.gd:1007, quest'ultimo trovato al cancello perche' il lotto ne aveva contati due. Curarne due su tre voleva dire lasciare lo stesso difetto dove si sente di piu'. - ✅ I tre rimbalzi restano come rete, col perche' scritto sul posto. Con
loop=true sono codice morto; ma se un domani un import tornasse a loop=false, con la rete la musica riparte (col buco) invece di morire e basta. - ✅ Misurato dopo: web
tunnel_stalls_fine=0 con tunnel_loops=2 e defect=0 (prima 2 stalli su 2 giri); desktop 0,044 s; campagna normale invariata. La trappola del loop a lunghezza zero e' stata controllata su tutte e dieci le tracce con probe_su846_loop.gd, che carica le risorse come le carica il gioco: durate piene (117,993 s per bonus_stage) e gameover.ogg correttamente non ciclico. - ⚠️ NON e' provato con l'orecchio, ed e' il limite dichiarato: e' provato che la sorgente non si ferma. Sul web due strumenti dicevano «muto» a musica certa e sono stati scartati —
get_bus_peak_volume_left_db() torna −200 dB sempre, e un AudioEffectCapture legge i frame ma con ampiezza zero: Chrome headless sintetizza il clock, non il segnale. Restano nella sonda, spenti e spiegati, perche' nessuno li ripaghi. - ⚠️ Due trappole d'ambiente nuove, oltre a quella gia' nota della scheda nascosta. (1) Chrome mette in pausa
requestAnimationFrame in una scheda non attiva — misurata 1 callback in 14,8 s — e il gioco resta fermo su LOADING per sempre: non e' «anima ma non risponde», e' «non gira proprio». Si aggira sostituendo rAF con una pompa su MessageChannel, iniettata nel <head> prima dell'avvio. (2) Cambiare loop= in un .import non basta: Godot serve lo stream dalla cache e il .pck si porta dietro quello vecchio — preso in flagrante, build con loop_stream=false mentre il repo era gia' a true. Si cancellano i *.oggvorbisstr e si reimporta.
Il cappotto beige torna anche sulla carnagione scura2026-09-07
SU-832, terzo giro. Il KO di Ivan: «fondi faccia e mani del dark col cappotto della versione standard v2, hai colorato anche il cappotto e non va bene». I due giri precedenti avevano sbagliato bersaglio e poi direzione — il secondo aveva reso marrone il cappotto della quinta riga per uniformarlo alle altre quattro, mentre Ivan voleva l'opposto: il beige della versione chiara su tutte e cinque.
- 📌 La causa non era nostra, era del generatore delle carnagioni.
tools/su421_carnagioni/make_dark_skin.py scurisce per finestra di tinta, e il cappotto beige di questo archetipo ha una tinta vicinissima alla pelle: e' finito dentro la finestra insieme a faccia e mani. E' lo stesso identico fenomeno gia' documentato in quel pacchetto per i cappelli di paglia (fix_hats_manual.py), che infatti spiega perche' una soglia non puo' risolvere l'ambiguita' e perche' li' si era scelta una correzione mirata per file. - ✅ La ricomposizione e' lecita perche' la sagoma coincide. Misurato prima di toccare qualsiasi cosa: i due raw
arch_business_v2.png e arch_business_v2_dark.png sono entrambi 1254x1254 e hanno 0 pixel di differenza nella sagoma alfa su 1.572.516. Sono lo stesso disegno ricolorato, quindi prendere il chiaro come base e riportarci sopra la sola pelle scura, alle stesse coordinate, non sposta niente. - ✅ Il fix vive sul RAW, non sulla sheet, e non e' un dettaglio:
su422_monta_varianti_dark.py rigenera la sheet dal raw, quindi una correzione fatta solo sulla sheet sarebbe sparita al primo rilancio. Il raw corretto e' scritto identico nei due posti da cui lo script pesca (TMP/sprites_raw/root_temp/su421/ e sprites_raw/PEOPLE/), e lo strumento che lo compone resta in tools/su832_cappotto_dark.py. - ✅ Misurato: colore dominante del busto ora beige in tutte e cinque le righe, identico a quello della sheet chiara, nessun marrone
(113,79,60); differenza fra scura e chiara scesa da 7.017 pixel (74,7%) a 2.359 (25,1%), concentrata su viso e mani; sagoma alfa a 0 pixel di scarto. - ⚠️ Due regressioni fermate al cancello, e nessuna delle due si annunciava. Il comando
--solo business, che il lotto doveva usare, ha (a) riportato 36 pixel magenta sulla sheet arch_business_male_v03_dark_sheet.png — cioe' disfatto la pulizia del PRIMO giro di questo stesso ticket, perche' quella era stata fatta sulla sheet e non sul raw — e (b) cancellato l'uid dai quattro .tres rigenerati, che lo script assegna in un secondo passo Godot mai eseguito. Entrambe ripristinate; in gioco resta la sola sheet v02. - ⚠️ La terza era la piu' pericolosa, ed e' stata chiusa alla radice.
--solo business riscriveva npc_dark_manifest.gd con le sole coppie del filtro: 120 righe sparite in silenzio — bouncer, bully, catlady, cleaner, delivery e tutti gli altri restavano senza gemello scuro, con lo script che esce 0 e non stampa niente di allarmante. Ora scrivi_manifest fonde invece di sostituire, e il perche' e' scritto nella sua docstring: 64 coppie conservate contro le 4 che sarebbero rimaste. - 📌 Non provata la resa in partita: la verifica e' sui pixel, come chiede il ticket. Il reimport pero' e' stato fatto sul progetto vero — e vale la pena saperlo — perche'
tools/autotest/common.sh importa su una copia rsync del progetto, non nel repo: la cache di files/homeless_city/.godot/ non si aggiorna mai da sola durante i compile-check.
Le tre lettere del nome tornano al centro della pagina2026-09-07
SU-841, secondo giro. Il KO di Ivan era di tre parole — «ancora sbagliato come shift» — piu' uno scatto, e lo scatto conteneva tutto: il blocco delle tre lettere gialle centrato a x 754 mentre la pagina del giornale e' centrata a 647,5, cioe' 106,5 px a destra. La spaziatura fra gli slot era invece regolare (157 e 157 px): il difetto era di centratura, non di passo.
- ⚠️ Il giro scorso e' passato verde perche' la sonda guardava solo la Y. Aria fra invito e lettere, margini del foglio, aiuto su una riga: tutto misurato, tutto giusto. La X non la controllava nessuna riga di codice, e nessuno se n'era accorto perche' i numeri che uscivano erano tanti e tutti buoni. E' il modo tipico in cui un provino verde nasconde un difetto: non sbagliando una misura, ma non facendola.
- ✅ La causa e' una riga.
_show_name_entry metteva l'origine del NameEntry a maxf(go_box.size.x, GAMEOVER_BOX_W_BASE) * 0.5. In quella fase la box e' ancora ai suoi 600 di default, perche' _resize_gameover_box sta nel ramo else dello stesso if e li' non gira mai: il pavimento a 720 vinceva sempre e portava l'origine a 360 invece di 300. Le tre lettere, simmetriche intorno all'origine, finivano 60 unita' canvas a destra del centro vero. Sui 1368 px dello scatto di Ivan fanno ~111 px, coerenti con i 106,5 misurati sull'immagine. - 📌 Non era un difetto del web, ed e' la parte che cambia come si legge il KO. Lo scarto e' un numero fisso in unita' canvas, quindi c'era su tutti e quattro i profili — compresi i tre gia' collaudati il giro scorso. Il web non lo causava: lo mostrava, perche' Ivan gioca li'.
- ✅ Ora si centra sulla larghezza VERA della box, non su un minimo: il conto resta giusto anche se un domani la box venisse ridimensionata prima del name entry. L'aiuto si semplifica di conseguenza — con l'origine centrata i due bordi sono equidistanti per costruzione, e la «meta' piu' stretta» del giro scorso, nata proprio per compensare l'origine spostata, non serve piu'.
- ✅ La sonda impara il profilo che mancava: 1368x980, rapporto 1,40, quello dello scatto di Ivan. I tre provati prima erano tutti larghi (1,78 / 2,16 / 1,33). E impara due misure che non esistevano: centro del blocco lettere contro centro pagina, e centro della sottolineatura contro la lettera sopra.
- ✅ Misurato: scarto pagina-lettere +60 su tutti e quattro i profili prima, 0,00 su tutti e quattro dopo; sottolineature centrate a 0,00; margine del foglio dal bordo invariato a 155, quindi nessuna regressione del secondo giro precedente.
- ⚠️ Resta aperto un dettaglio che la sonda non riproduce: nello scatto di Ivan la pagina del giornale e' a sua volta 36,5 px fuori asse rispetto al canvas, mentre nella sonda nativa e' centrata a 0,5 px. Vive fuori da
NameEntry.gd e HUD.gd — canvas HTML5 o box gia' ridimensionata da una partita precedente nella stessa sessione — e per vederlo serve uno scatto dall'export web vero.
Il marchio dell'ambiente non esce nella demo web2026-09-07
Ivan, guardando la demo web appena costruita per itch.io: «togli la scritta STAGE dalla demo». Una riga in Env.etichetta(): if is_live() or DemoMode.is_demo().
- 📌 Non è cosmesi, ed è il motivo per cui si spegne del tutto invece di cambiarlo in qualcos'altro.
tools/web_variant.py svuota gli endpoint online della copia demo (nickname, telemetria, manifest aggiornamenti) e toglie EOS: quella build non parla con nessuno dei tre ambienti, quindi «STAGE» a un estraneo che apre la pagina non è un dettaglio interno di troppo, è un'informazione falsa. E il motivo per cui il marchio esiste (SU-575: «non credere alla classifica vuota») in demo non si applica, perché la classifica online non c'è proprio. - ✅ Spento in
Env e non in MainMenu, che pure è l'unico chiamante di oggi: la domanda «questo marchio ha senso qui?» è di Env, e chi domani lo aggiungesse altrove eredita la regola invece di riscoprirla. - ✅ Misurato col provino che già esisteva,
shot_su575_marchio.gd, lanciato due volte. Fuori demo: dev → DEV, stage → STAGE, live → nessuno. Con --demo: «nessuno» in tutti e tre, e il numero di versione resta a (10, 740) in ogni caso — il marchio spariva senza spostare niente, che è la metà del criterio di SU-575. - ⚠️ La base del pacchetto per itch è cambiata, e la vecchia non andava più bene. L'ultimo commento di SU-268 dice di ricostruire da
a23446b riapplicando le correzioni a mano; verificato che oggi non regge più: in a23446b default_bus_layout.tres non esiste (è entrato con 79150dc, SU-818, alle 11:00 del 6/09) e da lì uscirebbe di nuovo un pacchetto muto e con i tasti vecchi. Le due correzioni ormai sono su dev: il pacchetto si costruisce da un commit di dev, non da a23446b. - 📌 Costruito da un worktree sul commit, non dal working tree. Nel repo principale un'altra sessione stava scrivendo (
DebugPanel.gd, World.gd, una sonda nuova): impacchettare da lì avrebbe messo nella demo pubblica lavoro in corso di qualcun altro, non riproducibile. Il worktree lega il pacchetto a un commit esatto.
Il riccone si chiama col pannello F1, anche fuori dalla COLLINA2026-09-07
Richiesta di Ivan in chat: una voce di debug per far entrare il riccone in monociclo senza aspettarlo. Bottone «Riccone» nella sezione SPAWN NEMICI del pannello F1, accanto a Gattara/Spazzino/Ronda bulli/Tossico.
- ✅ Passa dalla strada vera, non da una copia per il debug. Il pannello chiama
World.debug_spawn_riccone(), che chiama _spawn_riccone(): stessa entrata dal bordo, stesso percorso sulle mediane, stessa carica, stessi pacchetti di rete. L'unico pezzo nuovo e' la costruzione del piano, staccata da _setup_riccone_collina() in _build_riccone_plan() — il controllo sul flag del quartiere resta dov'era. - 📌 Il caso interessante e' il quartiere SENZA il flag
riccone, cioe' quello in cui il monociclo non esiste e Ivan lo vuole provare lo stesso. Li' il piano non c'e', quindi ne nasce uno posticcio marcato _richman_debug_only: a monociclo sparito _on_riccone_disappeared() rimette _richman_next_spawn_at a INF, altrimenti da quel momento il quartiere si sarebbe messo a produrre ricconi da solo ogni 45-75 s. - ✅ Collaudato in gioco vero (
probe_su814_debugbtn.gd, che istanzia Main.tscn e preme DebugPanel._spawn_debug_riccone() come lo premerebbe Ivan), su un quartiere con flag riccone=false: fuori dalla run 0 ricconi con la notifica giusta; col bottone 1, entrata a (22,0, 48,0) in fase 0 e 116,9 px percorsi in 1,5 s; seconda pressione 1, non 2; dopo la sparizione 0 ricconi con debug_only=true e prossimo=INF. - 📌 Tre rifiuti, tre notifiche a schermo invece di un bottone che non fa niente: client MP, nessuna run in corso, riccone gia' in giro.
La correzione dei nemici passa a 2 Hz per decisione di Ivan, e la misura dice che non basta2026-09-06
SU-840, seguito della voce 66. Ivan ha scelto in chat i 2 Hz: FOE_SYNC_STEP_SEC da 1,0 a 0,5 in World.gd. Una costante, e la decisione è scritta nel commento accanto perché il valore non è ovvio e il ticket si contraddice da solo.
- 📌 La prima run sembrava risolutiva, e sarebbe stata una conclusione sbagliata. Massimo dello scarto di posizione 6,8 px contro i 47,3 di 1 Hz, con tutti i percentili sotto i 24 richiesti. Il campanello era il numero di campioni: 22 contro 123, cioè in quella partita i nemici avevano inseguito molto meno. Due run in più hanno cambiato il quadro.
- ⚠️ Tre run a 2 Hz, e la conclusione è che 2 Hz NON risolve il criterio dei 24 px.
| run | campioni | mediana | p90 | p95 | massimo |
|---|
| 1 Hz | 123 | 4,9 px | 19,6 | 29,6 | 47,3 |
| 2 Hz, giro 1 | 22 | 2,7 px | 5,9 | 6,3 | 6,8 |
| 2 Hz, giro 2 | 84 | 5,4 px | 10,2 | 14,8 | 25,6 |
| 2 Hz, giro 3 | 116 | 6,3 px | 32,6 | 39,2 | 53,1 |
- 📌 La differenza fra una partita e l'altra è più grande della differenza fra 1 Hz e 2 Hz. Le mediane restano tutte fra 2,7 e 6,3 px, comprese quelle a 1 Hz; è la coda a ballare, e balla in entrambe le configurazioni. Il giro 3 a 2 Hz, che è quello con più campioni, è peggio della run a 1 Hz sul p90.
- ✅ L'indizio da cui ripartire, che vale più del numero. A 55 px/s, che è la velocità dello yeti che insegue, mezzo secondo fra due correzioni non può produrre più di ~28 px di deriva pura. I 53 px misurati stanno oltre quel limite: quindi in coda non c'è un passo di correzione troppo lungo, c'è qualcos'altro. Candidati da guardare per primi: un nemico che sul client non simula affatto mentre sull'host sì, oppure una correzione che non viene applicata. Non un'altra taratura della frequenza.
- 📌 I 2 Hz restano, come deciso: non peggiorano niente, le mediane sono buone o migliori, e lo scarto di stato è sceso a 20-50 ms contro i 250 ammessi con 0 transizioni mancate. Ma il criterio dei 24 px resta APERTO sul ticket, e questa volta si sa dove non cercare.
- ⚠️ Il traffico misurato non dice quello che sembra. A 2 Hz sono usciti 0,10, 0,39 e 0,59 pacchetti al secondo nei tre giri, contro 0,43 a 1 Hz: sembrerebbe che 2 Hz costi meno, ed è falso. Quel numero segue quanti nemici stavano inseguendo, non la frequenza. Per costruzione, a 2 Hz un nemico che insegue riceve 2 pacchetti al secondo, cioè il doppio del tetto scritto nel criterio 4 del ticket: è lo scambio scelto da Ivan, e va letto così.
Il sole evita le pensiline, e la musica della metro non è rotta su desktop2026-09-06
SU-845 — il sole non mira più dove c'è una pensilina.
- ✅ La domanda giusta è geometrica, non «sono al riparo».
is_sheltered dice se il barbone è *dentro* un riparo adesso; il ticket chiede se il cerchio di 48 px attorno al bersaglio tocca il rettangolo della pensilina. Sono due domande diverse, e la vecchia guardia rispondeva all'altra. Il confronto ora è fra il cerchio e bus_stop_rects allargato di 48 px, con la guardia storica lasciata dov'era. - ✅ Due controlli invece di uno: prima di puntare, e di nuovo subito prima di congelare
_impact_pos — così copre anche chi si avvicina alla pensilina mentre il sole sta scendendo. - ✅ La rinuncia non accende niente (nessun preavviso, nessun cerchio, nessun suono) e il sole ritenta a 3 s invece di 18. Misurato: a 20 px dalla pensilina, 0 cerchi in 120 s con 35 rinunce a 3,00 s esatti; a 200 px, 6 cerchi con cadenza 18,00 s e 0 intersezioni. La sonda conta i cerchi accesi, non i colpi andati a segno: è la misura che corrisponde al requisito.
- 📌 La sonda si costruisce una pensilina invece di sperare in un seme che ne metta una: una sonda che non trova pensiline passa a vuoto e sembra verde.
SU-846 — la musica alla scelta carta: non risolto, e il perché è il risultato.
- ⚠️ Il difetto NON si riproduce su desktop, e per questo non è stata scritta una riga di cura: il ticket imponeva la sonda prima del rimedio, e curare senza aver visto il difetto significa non sapere se ha funzionato. La musica del tunnel avanza per tutto il ventaglio — posizione da 5,667 a 10,312 s, 10 avanzamenti, 0 stalli, 0 riavvii — mentre la città resta muta a 0,768 s.
- ✅ La domanda di Ivan ha avuto una risposta, e a poco prezzo. «È una conseguenza delle cose per il web?» si è risolta senza montare un worktree né reimportare: fra
b3895dc e 30d1a35 cambia l'ordine di due righe, quindi è bastato invertirle nel tree, rilanciare la stessa sonda e ripristinare. Numeri identici, defect=0 da entrambe le parti: su desktop SU-823 non c'entra, perché su desktop non c'è difetto. - 📌 Resta il web, dov'è anche tutto il meccanismo di SU-823 (là la riproduzione predefinita è «campione», e mettere in pausa un campione ferma la sorgente). Serve export web, un server che raccolga i log e cinque aperture nel browser: una sessione corta dedicata, non un lotto. Il ticket resta in «Da fare».
L'ombra dei camioncini disegnata da Ivan, e i cactus in ogni parco2026-09-06
Due ticket in un commit solo, perché entrambi vivono in WorldGenerator.gd: SU-796 (rientro KO, terzo giro) e SU-844 (nuovo).
SU-796 — l'ombra dei camioncini. Stavolta Ivan non ha scritto «è sbagliato»: ha disegnato in rosso, sui nostri stessi scatti, la forma che si aspetta.
- ✅ Un disegno si può misurare, e va misurato. Il rosso dell'annotazione è esattamente (255,38,0) e si isola con una maschera — a occhio si confonde col rosa del camioncino e col rosso dell'insegna. Le bounding box del poligono danno tre rapporti rispetto a mezzogiorno: 1,82× alle 08, 2,37× alle 15, 2,94× alle 17. Quei tre numeri sono diventati il criterio del brief, e l'ombra ora misura 1,82×, 2,40× e 2,88×.
- ✅ Non è più un cuneo ma un parallelogramma pieno fra la retta delle ruote e la sua traslazione solare. Direzione sulla texture: 08
(+26,9 +40,2) sud-est; 15 (+47,4 −42,8) e 17 (+49,1 −56,9) nord-est. Impronta a mezzogiorno al 31,79% della bounding box. Su 8 semi, 21 camioncini, una sola ombra ciascuno. - 📌 Gli otto scatti a finestra vera li ha fatti l'orchestratore: nella sandbox Godot non apre finestre, e il ticket si gioca proprio su cosa si vede.
shot_su796_camioncini.sh passa da un override.cfg che forza la finestra, quindi non prende il desktop a schermo intero. - 📌 Alle 15 l'ombra si vede poco, e non è un difetto: in vista dall'alto un'ombra verso nord finisce dietro allo sprite, che copre il terreno sopra di sé. Lo mostra il disegno stesso di Ivan, dove il poligono delle 15 sta sopra al mezzo; alle 17 è abbastanza lunga da uscirne.
SU-844 — i cactus. Il difetto era più grosso di «qualche parco vuoto».
- ✅ Contati prima, parco per parco, su quattro semi:
[6,6,0,0], [6,6,6,3,0,0,0], [6,6,3,0,0], [6,6,0,0] — 9 parchi su 20 sotto il minimo di 3, e sette del tutto vuoti. I primi parchi si prendevano 6 cactus a testa ed esaurivano il tetto. Dopo: tutti i parchi ad almeno 3, 0 su 20 sotto il minimo. - ✅ Due passate: la prima garantisce il minimo in ogni parco, la seconda distribuisce gli extra; un parco affollato ritenta con l'aria dell'arredo ridotta da 52 a 38 px prima di arrendersi.
- ⚠️ «Nascosto» non basta: nel parco dello spazzino il cactus perde anche collisione, danno e ramo del rimbalzo del gatto — un cactus invisibile che ferisce sarebbe peggio del bug di partenza. Misurato: inerte entro 0,6 s,
cat_lives invariate camminandoci sopra per 10 s, e al ritorno il contatto toglie di nuovo una vita. - ✅ Il dado non si sposta benché l'ordine dei tiri cambi: si consumano sempre, anche per i punti scartati.
probe_su540_invarianza verde su 8 quartieri × 6 semi.
Il riccone rimpicciolito, senza magenta, e con la ruota che resta una ruota2026-09-06
SU-805. Ivan aveva scelto la variante A (totale 47 px, uomo 30 px) e deciso che «la ruota si RIGENERA piena invece di ripulire i raggi a mano».
- ✅ Magenta a zero, e non solo dove il ticket diceva: 874 → 0 sulla sheet, e 94 → 0 su
richman_crash.png, la nuvola di detriti dello stesso ticket, che nessuno aveva contato. Misurato prima di aprire il lotto, non trovato dal builder. - ⚠️ L'ordine delle operazioni era la trappola:
richman_sheet_A.png, la variante scelta, portava già 718 pixel magenta perché era stata prodotta riscalando la sheet sporca — il riscalamento spalma il magenta invece di toglierlo. Prima si pulisce, poi si riscala; invertirli fa un'immagine che sembra buona ed è sbagliata. - ⚠️ Scostamento dichiarato dalla decisione di Ivan, con la misura che lo motiva. La ruota piena è stata fatta, due volte: al primo giro un disco quasi piatto (7 px di ombra e 8 di luce su 116 di base), al secondo con cerchione, luce e ombra fino al 49% di pixel non-base. I numeri salgono, la resa no: nella zona della ruota restano 3 colori distinti, contro i 5.157 dell'originale e i 4.067 della ruota ripulita. A occhio è una palla nera e il monociclo non si legge.
- 📌 Perché lo scostamento non contraddice la decisione, la scioglie: la scelta era fra «rigenerare» e «ripulire *a mano*». Ma ripulire non è a mano — è la stessa passata PIL automatica che ha tolto il magenta dal resto della sheet. Tolta la premessa, la ragione per preferire il disco pieno sparisce. Installata la ruota ripulita; la versione piena resta in
TMP/su805_riccone/ e si riscambia con un comando. - 📌 Il raw è stato riallineato dall'originale, ripescato da LFS con
git cat-file blob HEAD:… | git lfs smudge — che è in sola lettura e non tocca il file su disco, al contrario di git lfs checkout che qui cancellerebbe arte vera.
La push che non arriva sull'Honor smette di fallire in silenzio2026-09-06
SU-687, secondo giro. Il KO diceva due cose insieme: «su honor non è ancora arrivata nessuna notifica in stage, inoltre ho fatto un paio di partite e non mi ha chiesto nulla per attivare le notifiche […] ma su altri dispositivi come galaxy s23 arrivano».
- 📌 Metà del KO non era un difetto, e valeva la pena scoprirlo prima di ripararlo. L'Honor 10 è Android 10 (API 29) e
POST_NOTIFICATIONS è un permesso a runtime solo da API 33: sotto quella soglia è concesso all'installazione e nessuna app lo chiede. Il Galaxy S23 lo chiede perché è Android 13+. Aggiungere una richiesta di permesso su API 29 sarebbe stato «riparare» il comportamento corretto. - ✅ Il difetto vero era un fallimento che si autoassolveva.
subscribe_to_topic è fire-and-forget, e il flag push_topic_iscritto veniva salvato appena partita la richiesta, non alla conferma: un dispositivo che falliva una volta risultava da lì in poi «già iscritto» e non ritentava mai più. È il meccanismo per cui un errore diventa permanente su un telefono solo mentre tutti gli altri funzionano. Anche l'esito di push_auto_init(true) veniva ignorato, benché il Java possa tornare false. - ✅ Ora: il flag si invalida prima del tentativo e non si riscrive senza una conferma che il plugin non espone — dichiarato, non inventato; si ritenta a ogni avvio, una volta sola;
push_auto_init che torna false ferma la fase e la scrive. Ogni passaggio stampa una riga [Push] che porta l'intero stato (fase, ambiente, topic, auto-init, token, iscrizione), così la diagnosi non va ricostruita incrociando righe lontane. - ⚠️ Il criterio del ticket resta NON verificato: nessun device era collegato (
adb devices vuoto). Quello che questo giro consegna non è la notifica che arriva, è la possibilità di sapere perché non arriva — con il comando adb da due minuti scritto sul ticket. - 📌 La diagnosi è venuta da un consulto Codex in sola lettura, non da una sonda: leggere il percorso completo costava molto meno che strumentarlo, e ha isolato le tre righe giuste al primo giro.
Il cappotto dell'uomo d'affari: il ticket puntava allo sprite sbagliato2026-09-06
SU-832, secondo giro. Il KO di Ivan era di cinque parole più un allegato: «KO non è quello ma questo allegato!». L'allegato era il raw arch_business_v2_dark, cioè un NPC diverso da quello su cui si era lavorato.
- ⚠️ Un KO che è solo un'immagine va aperto, non intuito. L'allegato si scarica dall'API di Jira (
rest/api/2/attachment/content/<id>) e si guarda: senza quello il ticket restava bloccato o, peggio, si rilavorava il v03 una seconda volta. Sta in TMP/ko_allegati/att_11090.png. - ✅ Il difetto, una volta trovato lo sprite giusto, era misurabile in una riga: sulla sheet
arch_business_male_v02_dark quattro righe di direzione su cinque hanno il cappotto marrone (mediana del busto (108,75,51), sotto il 5% di pixel chiari) e la quinta ce l'ha beige — (188,141,78) e 54,30%. Non è luce: è un'incoerenza della generazione. Ora quella riga sta a (112,79,56) e 0,69%. - ✅ 1.146 pixel cambiati in tutta la sheet, tutti dentro quella riga; le altre quattro bit-identiche. E il cappotto non è stato appiattito a tinta unita: 622 colori distinti nella banda del busto, dentro il range 517-849 delle altre righe.
- 📌 I capelli non sono stati toccati, ed è la cosa giusta: su questo sprite erano già castano scuro su tutte e cinque le righe. La richiesta «biondi → scuri» riguardava il v03, ed era già stata fatta il giro prima — il lavoro sul v03 resta dov'è, inutile ma non dannoso.
- 📌 Lasciata a Ivan la variante chiara: anche
arch_business_male_v02 ha righe più chiare di altre (32-37% contro 54-57%), ma è plausibilmente la resa normale di un cappotto beige visto da angoli diversi, non lo stacco marrone→beige dello scuro. Toccarla d'iniziativa sarebbe stato allargare il KO.
Il pupazzo di neve torna a respingere il gatto, e solo il fenicottero si azzuffa2026-09-06
SU-826, secondo giro. Il giro precedente aveva fatto esattamente quello che il ticket chiedeva — pupazzo e fenicottero entrambi trattati come NPC — e Ivan ha risposto che per il pupazzo il requisito era sbagliato: «il pupazzo di neve solo con fuoco deve sciogliersi! il gatto ci rimbalza contro, mentre i fenicotteri si azzuffano e spariscono come gli altri npc e nemici». Quindi non si è riparato un bug: si è tornati indietro su metà del lavoro tenendo l'altra metà.
- ✅ Il pupazzo è di nuovo un ostacolo: rientra in
cat_bouncers, il gatto ci rimbalza e muore come sul muro, e lo scioglimento resta appeso alla sola torcia (pozza a 30 s, rimontaggio a 60 s, invariati). Il fenicottero resta l'unico nemico di quartiere che espone hit_by_cat(), trovato da CatProjectile per capacità e non per elenco di tipi. - ✅ L'anti-tunneling del giro prima è stato conservato, ed era la scoperta cara di quel lotto: la collisione guardava solo la posizione di fine frame, così con un frame lungo il gatto passava da «prima» a «oltre» i 20 px senza mai finirci dentro. Riprovato: rimbalzo e zuffa reggono sia a 60 fps sia con frame da 200 ms.
- ⚠️ Un difetto fermato al cancello, non dal builder. Il fenicottero era stato fatto
queue_free() — sparizione definitiva — sotto un commento che la dichiarava «la stessa fine degli NPC nemici abbattuti». Quel commento era falso: NPC.gd:1071 dice che il respawn lo esegue l'NPC, e DistrictFoe.gd:37 elenca «pozza, rimontaggio, fuga, rientro» come la coda normale di ogni nemico di quartiere. Sarebbe stato l'unico nemico del gioco a non tornare, e con la carta del gatto si sarebbero potuti svuotare tutti i parchi in una partita sola. Rimessa la macchina del rientro (FUGA_SEC = 20 s): misurato 20,01 s e nodo ancora valido a fine giro. - 📌 Come si legge un KO che contraddice il ticket: «spariscono come gli altri npc e nemici» dice *in che modo* se ne vanno, non che non tornino più — anzi «come gli altri» vuol dire proprio che tornano come gli altri. Leggerlo come «cancellali» era la lettura letterale, e sbagliata.
Sessanta torce a Brina, e ognuna compare col pouf del gatto2026-09-06
SU-827, terzo giro. Ivan aveva bocciato le trenta del giro prima con due richieste in una riga: «mettiamone 60, e quando compaiono devono comparire come il gatto quando ricompare vicino con gatto magnetico, stesso effetto grafico».
- ✅ Sessanta nate davvero, non sessanta chieste. È la distinzione che conta: con 8 tiri per torcia e la distanza minima a 120 px il dado può esaurire i tentativi e consegnarne meno. Misurato rilanciando io la sonda: 60, 60, 60, 60 su quattro semi, con 60 nodi davvero in scena ogni volta, e 0 negli altri sette quartieri su tutti e quattro i semi. Gli 8 tiri bastavano già: non sono stati alzati.
- ✅ Il pouf è quello del gatto, non una copia somigliante.
TorchPickup chiama direttamente World._spawn_cat_poof() e ne riusa la texture condivisa e l'auto-pulizia — World.gd è rimasto in sola lettura, come chiedeva il perimetro del lotto. Misurato: 1 pouf su 1 rinascita, tipo del gatto e texture identici, durata 0,730 s, nodo che si toglie da solo. - ✅ Il dado non si è spostato: 32 righe di generazione bit-identiche fra due processi separati, distanza minima ferma a 120 px, raccolta e rinascita invariate (torcia assente a 59 s, presente a 61 s).
- 📌 Una scelta lasciata a Ivan: il pouf lo fanno anche le torce che nascono a inizio partita e sono già a schermo, non solo quelle che rinascono. Tenuto così perché il KO dice «quando compaiono», e la generazione è una comparsa — ma è una riga da togliere se l'effetto all'avvio non piace.
La nonna scura non ha più mezzo viso chiaro quando cammina verso sud-est2026-09-06
SU-843, ticket nuovo, lotto Codex sol. Ivan l'aveva vista in partita: «la nonna di carnagione grigia ha un problema di metà viso bianco e metà nero».
- ✅ Il difetto era in quattro frame su venticinque, e solo in una direzione. Nella riga SE i pixel chiari del viso erano 17, 16, 19 e 18; ora sono 1, 0, 1 e 1, e tutti e 25 i frame stanno sotto il tetto di 3 che il ticket chiedeva. Le tonalità scure non sono state inventate: luce, base e ombra vengono dalla riga S della stessa sheet.
- ✅ La misura che conta non è quella del builder, è il diff: 180 pixel cambiati in tutta la sheet, tutti dentro la riga SE, nella banda y 35-41 che è il viso. Le righe E, S, NE, N e il quinto frame SE sono bit-identici — cioè il rimedio non ha smosso nient'altro.
- 📌 La riga SE nasce dalla SW del raw, specchiata dall'importer: riparare solo la sheet avrebbe lasciato il difetto pronto a tornare alla prima reimportazione. Riparati entrambi, col raw archiviato collo stesso nome.
- ⚠️ La trappola di ieri è stata controllata prima, non dopo: su SU-832 riparare un raw scuro e reimportarlo aveva smosso 3.264 pixel contro i 36 del difetto. Qui la prova preventiva — ricostruire la sheet dal raw e confrontarla — dà 0 pixel di scarto, quindi il reimport è sicuro. È il tipo di controllo che costa un minuto e salva un giro.
Il rivale urla invece di parlottare: installata la variante che Ivan ha scelto2026-09-06
SU-800, secondo rientro. Micro-task dell'orchestratore, nessun agente: Ivan aveva ascoltato le due varianti consegnate il giro prima e ha risposto «OK variante B, mettiamola in gioco».
- ✅ Non è cambiata una riga di codice: era il file.
variante_B_urlo.wav copiata sia in files/homeless_city/assets/sounds/rival_aggro.wav sia in Sounds_raw/rival_aggro.wav, stesso md5. 1,76 s (il tetto era 2), picco −5,6 dBFS contro i −5,7 di throw.wav: scarto 0,1 dB. loop_mode=0, quindi fuori dalla trappola dell'AudioStreamWAV in loop senza loop_end. - ⚠️ La prova che il gioco suona davvero il file nuovo non è il
cp, è il reimport. Sostituire il .wav lascia in .godot/imported/ il .sample vecchio, e il gioco continua a suonare quello di prima senza dare errore: --headless --import ha portato il sample da 73.364 a 68.716 byte, che è la differenza fra i 1,88 s della variante A e gli 1,76 della B. Senza quel numero la chiusura sarebbe stata un'affermazione. - ✅ Sonda ripassata dopo lo scambio (
probe_su800_rivale_avviso.gd): 1 banner e 1 suono all'aggancio, colore r=1,00 g=0,30 b=0,20, 0 banner e 0 suoni nei 10 s di inseguimento continuo, riarmo a 2,02 s, rientro 2/2, wanted_boost fermo a 0,00. - 📌 La variante A resta a portata di mano in
TMP/su800_suono/rival_aggro_nuovo.wav: se all'ascolto sul device la B non convince, riscambiarle è un cp più un reimport.
I nemici di quartiere si sincronizzano fra peer, con un criterio che resta aperto2026-09-06
SU-840. Ticket nuovo, lotto da architetto-opus: tocca World.gd e i nemici in profondità. È l'unico del giro con una prova multiplayer vera a due peer.
- ✅ Viaggiano due cose sole, e il resto si srotola da sé. Gli eventi — zuffa col gatto, bruciatura dalla torcia — come
id + evento + posizione, col pattern del gatto: chi li causa li manda, l'host li rilancia, ognuno chiama lo stesso metodo locale. E una correzione a 1 Hz per i soli nemici che stanno inseguendo, col peer bersaglio scelto dall'host. Fuga, scioglimento, pozza, rimontaggio e rientro non viaggiano: sono code deterministiche del tempo, e replicato il trigger si svolgono identiche sui due peer. La prova che regge questa scelta: i nemici che non inseguono stanno a 0,0 px di mediana fra i peer senza un solo pacchetto. - ✅ Zero RPC alla nascita, come chiedeva il ticket: l'id viene dalla profondità nell'albero più l'indice fra fratelli, cioè dall'ordine degli
add_child del generatore, che è seedato e non è stato toccato. Due processi separati danno la stessa firma, e in ogni run host e client hanno indicizzato lo stesso numero di nemici. - 📌 Il riccone era già a posto e non è stata scritta una riga: è host-authoritative dal suo ticket (SU-814).
- ✅ Numeri misurati dall'orchestratore rilanciando la prova, non presi dal report: pacchetto evento 36 byte di argomenti, correzione 48 B con un inseguitore e 800 B con 48 — il tetto EOS di 1170 non è mai sfiorato. Scarto di stato peggiore 40 ms contro i 250 ammessi, con 0 transizioni mai viste dall'altro peer. Traffico 0,43 pacchetti/s sull'host e 0,01 sul client, sotto il tetto di 1/s.
- ⚠️ Il criterio della posizione resta APERTO, ed è una decisione di Ivan, non un difetto da rilavorare. Su 123 campioni: mediana 4,9 px, p90 19,6, p95 29,6, massimo 47,3. Il ticket chiedeva ≤ 24 px: la mediana e il p90 ci stanno, la coda no. Fra una correzione e l'altra le due simulazioni divergono, soprattutto quando lo yeti si incastra dietro un palazzo da una parte e dall'altra no — il «lento e scemo» voluto da SU-808 amplifica pochi pixel in decine.
- 📌 Due criteri del ticket si contraddicono, e va detto invece di scegliere di nascosto. La leva è una costante: portare la correzione da 1 Hz a 2 Hz dimezzerebbe la deriva, e il criterio 2 lo ammette («≤ 2 Hz»). Ma con un solo inseguitore farebbe 2 pacchetti al secondo per quel nemico, contro il «≤ 1 al secondo per nemico» del criterio 4. Tenuto 1 Hz, perché il traffico è scritto come tetto e un tetto non si sfora; la scelta opposta è legittima e la fa Ivan.
- 📌 Due difetti trovati dalla misura e non dalla lettura, entrambi corretti: la bandiera «sto inseguendo» restava congelata sui nemici col
_process spento fuori inquadratura, e l'host mandava per 15 s la correzione di un pupazzo fermo che non stava simulando, tirando indietro il client che lo vagabondava giusto; e un nemico fuori inquadratura sul client non si muoveva fra due correzioni, restando indietro di un secondo. Prima delle due cure la mediana era 22,6 px e il massimo 84,5. - ⚠️ Non provato: la resa a occhio (che la correzione non si veda strappare), tre o più giocatori (la scelta del «peer più vicino» non è stata esercitata), e i device. ⚠️ La run continua di 5 minuti che il ticket chiede è irraggiungibile a Brina: il bot perde le nove vite per mano dei nemici del quartiere in 60-80 s. Misurato per segmenti su due partite, ~200 s di gioco confrontati.
- ⚠️ Trappola pagata, che vale per ogni prova MP futura:
tools/autotest/run_mp.sh passa i flag extra non quotati, e un path che contiene «Progetti Claude» si spezza — il gioco riceve una directory monca e non scrive niente, col codice sano. E la coda del log dopo la morte del barbone ha l'orologio di run fermo: letta come dati dava 823 px di scarto e uno stato divergente, sempre col codice sano.
Il barbone rivale ora ti minaccia in una lingua inventata, invece di grugnire una volta sola2026-09-06
SU-800. Ticket bocciato da Ivan: «ci vuole un suono di cattivo che parla incomprensibile in una lingua finta, che grugnisce e minaccia (magari anche mezzo ubriaco)». Nessuna riga di codice cambiata: il banner, il conteggio e il riarmo di SU-800 restavano giusti, era il file audio a essere sbagliato.
- 📌 Perché il primo suono non somigliava a uno che parla, misurato invece che intuito. Un suono non lo posso ascoltare, quindi la domanda del KO — «parla o grugnisce?» — è stata tradotta in una misura che le risponde davvero: quanti impulsi distinti ci sono nell'inviluppo. Il vecchio
rival_aggro.wav ne aveva 3, dominati da un unico tratto sostenuto di 1,06 s: un verso, non un parlato. Le tre varianti generate sono state scelte con lo stesso metro. - ✅ Il file nuovo ha 14 impulsi distinti da 40 a 120 ms, che è la firma di un parlato a sillabe. Dura 1,88 s (il ticket chiede ≤ 2 s) e ha il picco a −5,7 dBFS, cioè esattamente quello di
throw.wav: lo scarto è 0,0 dB contro i ±3 ammessi. - ✅ La sonda di SU-800 ripassa col file nuovo: al primo aggancio 1 banner e 1 suono, banner rosso
(1,00; 0,30; 0,20) per 1,6 s come la polizia, zero ripetizioni nei 10 s di inseguimento continuo, riarmo rilasciato a 2,02 s, e il wanted della polizia invariato (delta 0,00). - 📌 Una seconda variante è pronta e non buttata:
TMP/su800_suono/variante_B_urlo.wav, 1,76 s, un urlo lungo iniziale più quattro sillabe — più aggressiva e meno «parlata». Scambiarle è copiare un file. Nella stessa cartella c'è un LEGGIMI.txt che lo spiega. - ⚠️ Non provato: l'ascolto. Tocca a Ivan, ed è l'unico criterio che conta davvero su un suono.
In pausa la musica continua, e la pagina dei record non ha più le lettere sopra l'invito2026-09-06
SU-842 + SU-841. Due ticket nuovi che condividono HUD.gd, lavorati insieme da dev-sonnet. SU-841 ha richiesto due giri, il secondo aperto al cancello e non da Ivan.
- ✅ SU-842: in pausa la musica del livello continua a suonare, come già durante il level-up e la mappa. Il meccanismo esisteva e funzionava: il nodo
Main/Music è pausabile, quindi il motore lo sospendeva insieme al resto. Ora il menu pausa fa la stessa cosa che fanno level-up e mappa, copiando l'ordine delle chiamate che SU-823 aveva già pagato — rendere il nodo «sempre attivo» *prima* di mettere in pausa l'albero, non dopo, perché sul web l'ordine inverso ferma i campioni e fa ripartire la traccia da capo. - ✅ Misurato a finestra, non in headless, perché in headless i tempi dell'audio non avanzano come in gioco: la posizione di riproduzione avanza di 3,008 s durante 3,005 s di pausa, resta monotona alla ripresa (nessun riavvio), il mondo sta fermo, il
process_mode torna quello di prima, e uscendo al menu la musica si ferma ancora. - ✅ SU-841: le tre lettere stavano sopra la riga «Inserisci il tuo nome» e l'aiuto andava a capo. La causa: la funzione che sistema il blocco del nome girava solo con un touchscreen vero, ed era esattamente il ramo rotto su web e su desktop col mouse. Ora gira sempre, e la riga di aiuto è larga quanto la pagina invece dei 360 px fissi.
- 📌 Il difetto è stato prima riprodotto, poi curato, com'è giusto: nello scatto «prima» l'aria fra invito e lettere è −55 px (cioè si sovrappongono) e l'aiuto sta su due righe; nel «dopo» l'aria è 20 px e l'aiuto su una riga, con zero sovrapposizioni fra i rettangoli di invito, lettere, aiuto e tasto CONFERMA. Su tutti e tre i profili: 1365×768 (il web di Ivan), 844×390 (telefono), 1024×768 (tablet).
- 📌 Il secondo giro è nato al cancello, e vale la pena dire perché. Misurando il rettangolo del foglio negli scatti del primo giro, la pagina era cresciuta di 184 px e arrivava a 3-4 px dal bordo inferiore dello schermo, con una fascia vuota sotto la riga di aiuto. Era spazio riservato sempre al blocco dei comandi touch (274 px) anche dove quel blocco non viene disegnato e l'aiuto testuale finisce a 86 px: 188 px di vuoto riservati a un tasto che sul web non esiste. Ora lo spazio si riserva solo se i comandi touch sono davvero visibili, e il margine dal bordo è 158 px su web e tablet, 80 px sul telefono.
- ⚠️ Non provato: la misura sul build web di SU-842 (costa un export intero) e un multiplayer vero a due client — verificato solo per lettura che il codice nuovo è locale e non passa da nessuna RPC.
- 📌 Trovato per strada e non corretto, perché fuori dai criteri di entrambi i ticket: da SU-531 il blocco del nome nasce centrato dando per scontata una pagina larga almeno 720 px, mentre in quella fase è ancora larga 600, e le lettere partono ~60 px a destra del centro vero finché non arriva il ridimensionamento della pagina. Vuole un ticket suo.
Il segno meno delle carte era un quadratino sul web, e l'audit che doveva accorgersene guardava altrove2026-09-06
SU-833. Ticket nuovo, lavorato in due passaggi da Codex sol: prima la correzione e l'estensione dell'audit, poi una seconda passata perché l'audit esteso era troppo rumoroso per essere guardato da qualcuno.
- ✅ La correzione è un carattere: nelle carte il valore negativo usava il MENO tipografico U+2212, che il font pixel non ha. Ora usa il trattino ASCII. Verificato alla radice con fontTools: nel font (243 glifi)
- c'è e − non c'è. È il motivo per cui il difetto si vedeva solo sul web — su desktop e mobile un font di sistema copriva il buco, sul web no. Stessa correzione anche in un messaggio del pannello F1. - 📌 La premessa del ticket sull'audit era falsa, ed è stato utile scoprirlo. Il ticket diceva che
audit_font_glifi.py «esce 0 perché controlla solo i CSV». Lanciando la versione committata: esce 1, con 1.252 glifi mancanti — quasi tutti ideogrammi cinesi più il carattere _, debito preesistente delle traduzioni che questo ticket vieta di toccare. Inseguire lo zero globale sarebbe stato inseguire un criterio irraggiungibile. - ✅ L'audit ora guarda anche le stringhe dei
.gd e .tscn, commenti esclusi, e la sezione nuova ha un esito suo, separato dal debito storico dei CSV: dopo la correzione dice ESITO SORGENTI: OK — 0 glifi mancanti. La prova differenziale è stata fatta nei due versi: rimettendo − nella stringa vera, la riga ricompare col suo file:riga. - 📌 La prima versione dell'estensione segnalava 26 falsi positivi e sarebbe morta lì. Erano caratteri come
< dentro stringhe di log e diagnostica — LogSanitizer, DebugPanel, EOSBridge, PushNotifiche — che col font pixel non vengono mai disegnate. Un audit che segnala 26 cose innocue non lo guarda più nessuno, ed è esattamente il modo in cui questo difetto era passato inosservato la prima volta. La seconda passata ha ristretto il criterio alle sole stringhe che raggiungono davvero l'interfaccia di gioco: fallback di I18n.t/tf, primo argomento di big_notify, text/tooltip_text delle scene di UI. - ⚠️ Non provato: lo scatto della carta dal build web, che il ticket chiedeva. Al suo posto c'è una prova più forte e più a monte, la presenza del glifo nel font.
Il tossico di colore era tagliato male, e l'uomo d'affari aveva i puntini rosa sul cappotto2026-09-06
SU-831 + SU-832. Due ticket nuovi di sola immagine, lavorati con PIL da Codex sol: arte approvata, quindi si corregge, non si rigenera.
- ✅ SU-831: il ritaglio, non il disegno. In
arch_junkie_male_v03_dark il corpo era alto 27 px dentro la cella da 32 e dieci frame su venticinque portavano un frammento estraneo sopra la testa — le scarpe della riga vicina del foglio grezzo. La causa stava nell'importer: quando le isole non sono pulite ripiega sulla griglia fissa e prende il bbox stretto della cella frammento compreso, così il bbox risulta alto quanto la cella e il corpo si rimpicciolisce per starci dentro. Ora, nel solo ramo di ripiego, le corse staccate che toccano il bordo alto o basso vengono escluse dal bbox. - ✅ Misurato prima e dopo, con la stessa misura che aveva trovato il difetto (per ogni frame la corsa più lunga di righe opache è il corpo, ogni altra corsa che tocca un bordo è un frammento):
arch_junkie_male_v03_dark corpo 27 → 32 px e frammenti 13 → 0; arch_junkie_male_v01_dark 28 → 32 e 10 → 0; arch_skater_drunk 28 → 32 e 5 → 0. Reimportate solo queste tre; le altre restano bit-identiche, e lo dice git da sé. - ✅ SU-832: via il viola dal cappotto, capelli da biondi a castano scuro. I puntini rosa erano residui dello sfondo magenta di generazione sopravvissuti al ritaglio. Misurato col criterio del ticket (
R e B > G+35): 33 → 0 sulla variante chiara, 36 → 0 sulla scura. - 📌 La prova che il resto non è stato toccato è il conteggio dei pixel cambiati, non una dichiarazione: sulla sheet scura sono cambiati esattamente 36 pixel, cioè solo il viola, coi capelli intatti come il ticket chiedeva. Sulla chiara 2.325, cioè viola più i 2.292 pixel di capelli rimappati su tre toni (ombra, base, luce), che è il modo di scurirli senza appiattirli.
- 📌 Il raw scuro NON è stato riparato, ed è una decisione, non una dimenticanza. Ripararlo avrebbe prodotto, alla reimportazione, una sheet diversa dall'attuale per 3.264 pixel distribuiti su tutte e 25 le celle, contro i 36 del difetto: una regressione artistica molto più grossa del problema. Il raw chiaro invece è stato riparato e archiviato, perché lì i conti tornavano. ⚠️ Conseguenza da ricordare: chi un domani reimportasse
arch_business_v4_dark.png si ritroverebbe i 36 pixel viola. La cura vive nella sheet.
I controlli touch: mancini veri, ogni gruppo nella sua metà, e il pad che smette di rimpicciolirsi2026-09-06
SU-835 + SU-838 + SU-839 + SU-834 + SU-837 + SU-836. Sei ticket nuovi dello stesso sottosistema, lavorati in due onde da Codex sol (VirtualControls.gd + Settings.gd, poi OptionsPanel.gd) e committati insieme perché condividono un file: separare gli hunk di due builder sullo stesso sorgente è il modo classico di far entrare il lavoro di uno nel commit dell'altro.
- ✅ SU-835: da mancini B e X si scambiano di posto sul diamante. Prima SU-638 specchiava i lati — joystick a destra, tasti a sinistra — ma il diamante restava con B esterno e X interno, quindi il pollice del mancino trovava X dove il destrorso trova B. Ora disegno e area di tocco usano lo stesso offset, così non possono divergere: destrorsi B=+56/X=−56 px, mancini B=−56/X=+56, raggio del tocco 40,5 px e centri di disegno e di tocco coincidenti a entrambe le risoluzioni.
- ✅ SU-838: il joystick fluttuante si evoca solo nella sua metà. Col pad invisibile qualunque tocco libero lo faceva comparire, anche sopra i tasti. Ora vale lo stesso
meta_joystick dello stick visibile, e il tocco nella metà sbagliata non viene consumato. A 844×390, destrorsi: x=211 sì, x=633 no; mancini l'inverso; a 20 px dalla linea di mezzo, dal lato giusto, il pad nasce lì — nessun margine nuovo. - ✅ SU-839: nell'editor il buttafuori non è più il bordo dello schermo ma la metà designata. Tasti a destra e joystick a sinistra per i destrorsi, l'opposto per i mancini, col raggio del gruppo che resta dentro la linea di mezzo. A 844×390 (metà 422): tasti 508, joystick 364; a 1024×768 (metà 512): tasti 598, joystick 454. Margini esterni invariati.
- ✅ SU-834: al toggle mancini le posizioni salvate si specchiano invece di restare dov'erano.
x' = 1 − x, y invariata, e si risalvano. Due toggle di fila riportano i valori identici al bit — l'involuzione è la proprietà che dice che non si accumula deriva. Misurato: tasti (0,886; 0,561) → (0,114; 0,561), joystick lo specchio. - ✅ SU-837: il pad nell'editor ha la taglia che avrà in partita, da dove lo si apra. Aprendolo dal menu principale il fattore testo del telefono era ancora sospeso e il pad usciva al 77% del vero. Ora
begin_positioning() consuma la scala pendente prima di ricostruire il pad, e la ripristina uscendo per non ridimensionare il menu. Scarto misurato: 0,000 px fra editor e partita, a 844×390 (raggio 26,736) e a 1024×768 (raggio 40,500), con le frazioni salvate che restano coerenti. - ✅ SU-837, seconda metà: CONFERMA / RIPRISTINA / INDIETRO da dito. Erano al font minimo di 12 perché calcolati come 42% di un'altezza che sul telefono vale 21 px. Ora sono alti 63,8 unità sul telefono e 50,6 sul tablet — sopra i 44 richiesti — col font della voce INDIETRO del menu Opzioni (29 e 23), e la testata cresce con loro senza finire sotto il pad.
- ✅ SU-836: il menu CONTROLLI non cambia più disposizione fra un avvio e l'altro. La pagina decideva
_colonne una volta sola, e su Android la decisione cadeva quando finestra, insets e fattore testo non erano ancora definitivi: da qui una colonna o due a seconda del momento. Ora il pannello ascolta size_changed e ricostruisce sulla dimensione buona. Misurato con tre partenze diverse (610×330, 1280×720, 844×390) e la stessa finale: _colonne = [1, 1, 1] e font [22, 22, 22]. Tedesco e russo restano su una colonna, com'era voluto da SU-456. - 📌 L'istruzione a schermo è stata riscritta in tutte e 8 le lingue perché ora dice una regola diversa: si resta nella propria metà. Zero emoji, come sempre.
- ⚠️ Non provato: i dieci avvii veri sull'Honor che SU-836 chiede a Ivan, la resa su iPad e web, e il dito vero sui tasti. La catena d'input a finestra è stata però ricontrollata come regressione (
probe_su417_touch.sh, ESITO=OK): i comandi restano nascosti nello splash, il tocco lascia il title screen, e in partita tornano visibili.
Trenta torce a Brina, e l'ombra dei camioncini che sembrava doppia non lo era2026-09-06
SU-827 + SU-796. Due ticket bocciati da Ivan, entrambi su WorldGenerator.gd, lavorati nello stesso lotto (Codex sol).
- ✅ SU-827:
TORCE_PER_MAPPA da 14 a 30, come chiedeva il KO («proviamo con 30 a partita»); TORCE_DIST_MIN resta 120 px, così non si ammucchiano. Il lavoro non era cambiare la costante — è una riga — ma verificare che ne nascano davvero 30: con 8 tiri per torcia e una distanza minima, il dado può esaurire i tentativi e consegnarne meno. Misurato su quattro semi: 30 su 30 in tutti e quattro, e 0 negli altri sette quartieri. Il dado dei nemici non si è spostato: stesse posizioni prima e dopo, 32 firme bit-identiche. - 📌 SU-796: l'ipotesi dei due nodi era sbagliata, ed è stata smentita con una misura. Il KO diceva «l'ombra dei camioncini la vedo in due direzioni, come se ci fossero due ombre, indagare», e la spiegazione ovvia era che il nodo dell'ombra vecchia fosse sopravvissuto accanto a quello nuovo. Contati su 8 semi e 21 camioncini: una sola ombra associata a ciascuno, zero ombre vecchie. L'ipotesi comoda era falsa.
- ✅ La causa vera stava dentro una sola texture: l'impronta non proiettata conviveva con la propria copia proiettata, e le due si leggevano come due versi. Fuori dallo zenit ora resta a terra la sola retta delle ruote e l'inviluppo usa una sagoma traslata sola; a mezzogiorno resta l'impronta intera sotto il mezzo.
- 📌 Perché a occhio l'ombra sembra andare a sud-ovest, e non è un difetto. Guardando gli scatti veri sembra che la massa scura stia in basso a sinistra del camioncino, cioè dalla parte vietata. Il codice dice altro, e la spiegazione è duplice: l'impronta è decentrata in basso a sinistra per costruzione, perché è agganciata alla retta d'appoggio delle ruote e il mezzo è inclinato di 45° antiorari; e la proiezione di nord-est finisce in parte sotto al camioncino, perché l'ombra è disegnata a
z_sorgente-1. Quello che resta bene in vista è quindi il cuneo dell'impronta, non la proiezione. Verificato leggendo il codice con un consulto indipendente (Codex, alta confidenza): nessun offset, flip, rotazione o scala negativa fra la texture generata e il nodo a schermo. - ✅ Misure sulla texture, identiche sui 21 camioncini: centroide h08
(+11,44 +17,21) = sud-est, h15 (+7,87 −7,67) e h17 (+9,52 −11,18) = nord-est. Impronta a mezzogiorno: riempie il 31,79% della sua bounding box, contro il 60% che il criterio poneva come tetto (un rettangolo dritto la riempirebbe al 100%, uno a 45° al 50%). La texture si rigenera solo al cambio del vettore del sole, non a ogni frame. - ⚠️ Quello che NON si è potuto misurare, e va detto perché ci ho provato tre volte. La direzione dell'ombra sull'immagine renderizzata: tre misure diverse del centroide dei pixel scuriti hanno dato risultati che cambiavano segno al variare della soglia, quindi sono state buttate. Al loro posto vale il controllo onesto: nello stesso scatto, l'ombra del lampione accanto va in basso a destra alle 8 e in alto a destra alle 17, come il requisito chiede. Scatti alle 8, 12, 15 e 17 in
TMP/su796_ko/.
Il gatto saltava oltre il pupazzo di neve, e lo yeti pattinava invece di camminare2026-09-06
SU-826 + SU-808. Due ticket bocciati da Ivan sulla stessa famiglia di nemici, rilavorati nello stesso lotto (Codex sol).
- 📌 SU-826: la sonda del giro scorso era verde mentre il gioco falliva, e si sapeva perché. Il commento di chiusura precedente diceva a chiare lettere «NON PROVATO: la collisione vera in partita (la sonda chiama il metodo direttamente)». Il KO di Ivan è arrivato esattamente da lì: «il pupazzo di neve solo con torcia si scioglie, il gatto non gli fa nulla». La sonda nuova fa volare un gatto vero contro un pupazzo vero in una Brina vera, e non chiama più
hit_by_cat() a mano. - ✅ La causa radice era un salto fra campioni, non un errore di gruppi.
CatProjectile controllava solo la posizione di fine frame: con un frame lungo — cioè su telefono — il gatto si trovava prima davanti e poi già oltre i 20 px del pupazzo, senza che il passaggio venisse mai rilevato. Ora la distanza si misura sull'intero segmento percorso nel frame, e solo per il gruppo dei cat_fighters: yeti, cactus, muri, gatti della gattara, repliche multiplayer e giocatori remoti non cambiano di una riga. - 📌 Perché il fenicottero funzionava e il pupazzo no, pur condividendo gruppo e metodo: il fenicottero corre verso il lanciatore a 100 px/s contro i 72 px/s del pupazzo, e questo stringe la finestra fra due campioni quel tanto che basta a far quasi sempre centro. Il KO di Ivan diceva infatti «OK per i fenicotteri invece»: era la stessa causa, mascherata da una velocità diversa.
- ✅ Misurato dopo il fix: zuffa e gatto consumato sia a 60 fps (delta 0,017 s) sia con frame da 200 ms, che è il caso che rompeva. Nuvola 2,517 s, pupazzo in pozza a fine nuvola e rimontato a +60,1 s con 0,00 px di scarto, fenicottero che fugge dopo la nuvola e torna a +20,1 s. Il pinguino resta fuori sia da
cat_fighters sia da cat_bouncers, com'era stato corretto al cancello del giro scorso. - ✅ SU-808: si è cambiata la cadenza dell'animazione, non la velocità. Il KO diceva «la velocità dello yeti va bene ma l'animazione mi sembra troppo lenta, sembra che pattini»: i 55 px/s inseguendo e i 36 vagando restano intatti,
anim_hz passa da 4,0 a 8,0 fps. - 📌 Il numero che spiega la pattinata, e il metro per giudicarla. Prima lo yeti avanzava 13,8 px per fotogramma di camminata; un pedone del gioco ne fa 6,25 (50 px/s a 8 fps, che è la cadenza scritta nelle
SpriteFrames degli NPC). Più del doppio di passo a parità di posa: è quello che l'occhio legge come scivolata. Ora lo yeti ne fa 6,9, cioè praticamente il passo di un pedone. Ciclo completo da 1,000 s a 0,500 s, 27,5 px percorsi per ciclo inseguendo e 18,0 vagando. - ⚠️ Non provato: la resa dell'andatura su telefono vero e un incontro multiplayer dal vivo. Il limite noto dei nemici di quartiere non sincronizzati fra peer resta quello di sempre, ed è il ticket SU-840.
Sul web la musica ripartiva da capo a ogni ventaglio di carte: erano due righe nell'ordine sbagliato2026-09-06
SU-823. Nella build web, salendo di livello, la musica di sottofondo a volte ricominciava dall'inizio invece di proseguire. Su desktop non si notava.
- 📌 «A volte» era in realtà «sempre»: 20 riavvolgimenti su 20 aperture. Il ticket chiedeva che il provino riproducesse il difetto prima della cura, ed è quello che ha cambiato il lavoro: senza quel numero non ci sarebbe stato modo di distinguere fra le tre piste.
- ✅ Era la pista dell'ORDINE, non il tipo di riproduzione, e le tre piste sono state separate con un solo export a quattro varianti, 20 aperture ciascuna, nella build web vera dentro Chrome: com'è oggi 20 su 20; col solo tipo di riproduzione a «stream» 0 su 20; col solo ordine invertito 0 su 20; con entrambe 0 su 20.
- 📌 Il meccanismo, che spiega perché desktop e web si comportano diversamente. Mettere in pausa l'albero mentre il nodo della musica è ancora «pausabile» gli manda la notifica di pausa, e il lettore audio risponde mettendo lo stream in pausa e togliendolo un istante dopo. Su desktop è innocuo, riprende dal punto esatto. Sul web il tipo di riproduzione predefinito è «campione», e lì «pausa» vuol dire fermare la sorgente: fermandola scatta il segnale di fine traccia, e il codice che tiene la musica in loop risponde rimettendola in play. Quel play è il riavvolgimento. La pista del loop via segnale era il sintomo della prima, non una causa a sé.
- ✅ La cura è una sola, applicata in tre punti: rendere il nodo della musica «sempre attivo» prima di mettere in pausa, invece che subito dopo. Ventaglio delle carte, mappa e cerimonia del super condividevano le stesse due righe nello stesso ordine sbagliato.
- ✅ La mappa aveva lo stesso difetto e la stessa misura: 20 su 20 prima, 0 su 20 dopo. Le chiusure non cambiano, perché lì l'albero riparte prima del ripristino e nessuna notifica arriva.
- ✅ Non regressione su desktop misurata, non supposta: 0 riavvolgimenti su 20 sia prima sia dopo, su ventaglio e mappa.
- 📌 Scartata la cura alternativa che pure funzionava: forzare il tipo di riproduzione a «stream» avrebbe cambiato il motore audio dell'intera build web senza necessità.
- ⚠️ Trappola d'ambiente da non ripagare: la scheda pilotata dall'estensione Chrome risultava «nascosta» perché la finestra era coperta, quindi il browser non le dava fotogrammi e il gioco restava fermo. Il provino sembrava rotto e non lo era. Serve una seconda istanza di Chrome dedicata, con la sonda che si avvia da sé e riferisce i risultati a un server locale.
- ⚠️ NON PROVATO: la cerimonia del super sul web, unico dei tre punti curati senza misura, perché il provino non sa far uscire una carta dorata. E l'ascolto vero: la misura è la posizione di riproduzione più il conteggio dei segnali di fine traccia.
- 📌 Candidato a un ticket successivo: sul web *ogni altra* pausa del nodo musica (menu di pausa, cambio scheda) resta soggetta allo stesso ferma-e-riparti dei campioni. Fuori dallo scopo di questo ticket, e lì la cura giusta sarebbe proprio il tipo di riproduzione a «stream».
La bottigliata del tossico non ubriaca più, e la neve resta dove cade2026-09-06
SU-824 + SU-829. Due ticket indipendenti, lavorati insieme da Codex perché sono gli unici due che toccano World.
- ✅ SU-824: la bottiglia scala una vita e basta. Prima contava anche come un «sorso forzato»: incrementava le bevute, dopo due colpi faceva scattare l'ubriachezza (stelline, deriva del passo, musica storta) e finiva nel conteggio «Bottiglie scolate» del punteggio. Tolta la chiamata da entrambi i punti, quello del barbone locale e quello del barbone remoto: mancarne uno avrebbe lasciato il bug a metà, in multiplayer.
- ✅ Misurato: cinque colpi di fila fanno cinque vite in meno, le bevute restano a zero e l'ubriachezza non scatta, sia sul percorso locale sia su quello del client. Restano la reazione al colpo di SU-305 e l'effetto del vino, che è un segno visivo e non un sorso.
- ✅ Le due chiavi di testo del ramo «ora sei ufficialmente ubriaco» sono state tolte dai due file delle traduzioni, e non resta un solo riferimento nel codice. Il messaggio «offre lui, a modo suo» resta completo in tutte e otto le lingue, e il controllo dei pittogrammi esce a zero.
- ✅ SU-829: i fiocchi cadono nel mondo e ci restano. Prima l'emettitore viveva nel livello dello schermo, quindi ogni fiocco si muoveva con la camera e la neve sembrava incollata al barbone. Ora vive nel mondo, si riposiziona sotto l'inquadratura a ogni frame, e le particelle già emesse restano dove sono nate. Stesse 200 particelle di prima: cambia lo spazio delle coordinate, non il costo.
- ⚠️ Corretto al cancello lo z-index, che era rimasto a 12. Dentro il livello dello schermo bastava, perché lì non c'era altro. Nel mondo no: ventuno punti del codice ordinano la profondità con la coordinata y, e la mappa è larga 1632 px — la neve sarebbe finita dietro qualunque cosa più in basso di 12 pixel, cioè dietro tutto. Ora sta al massimo consentito, sopra il mondo; l'HUD è su un livello a parte e resta sopra comunque.
- ⚠️ Conseguenza voluta del passaggio nel mondo: la neve ora sta sotto la tinta del cielo, mentre prima le stava davanti. È il prezzo di vivere in coordinate di mondo, che è quello che il ticket chiede. Da guardare in partita.
- 📌 La misura ha mentito prima di dire la verità, e la trappola è scritta nella sonda. Il criterio chiede di muovere la camera di 200 px e verificare che i fiocchi non la seguano. Misurando sui pixel dell'immagine il fiocco sembrava trascinato per metà — un risultato che ha portato a «correggere» un codice sano. La causa era il cambio di unità: l'immagine catturata è 844 px dove il viewport logico ne conta 1662, cioè un fattore 0,508. Riportata la misura in pixel di mondo: camera 200 px, fiocco 0,1 px. Cioè fermo.
- ⚠️ NON PROVATO: il frame-time a Brina sull'Honor 10, che vuole il telefono di Ivan; la densità e l'assenza di stacco al cambio di zoom in partita vera; una sessione multiplayer con due processi.
I fogli di pinguino, pupazzo e fenicottero erano tagliati fuori griglia: rifatti2026-09-06
SU-825. Ivan in partita: i pinguini mostravano pezzi di altri pinguini, l'ombra non stava sotto di loro, e scivolando erano specchiati al contrario. È la rilavorazione dei fogli montati poche ore prima.
- 📌 L'ipotesi scritta nel ticket era mezza giusta, e la differenza conta. Non è la griglia da 250 px: le figure dei raw in quella griglia non ci stanno affatto (le righe del fenicottero scivolano di ~12 px l'una, le scivolate del pinguino sono larghe 273). La causa vera è il ritaglio «a foglio»: prendeva un rettangolo largo quanto il fotogramma più grande e lo ancorava al bordo sinistro di ogni cella. Su una cella stretta sconfinava nella vicina — i «pezzi di altri pinguini» — e sull'ultima colonna usciva dall'immagine, dove la libreria riempie di nero: è la barra scura che si vedeva. Nessun foglio va rigenerato.
- ✅ Misure prima e dopo, con uno strumento riusabile (
--bordi in tools/sprite/misura_sheet.py, esteso e non duplicato). Prima: pinguino 281 pixel di bordo sporco su 35 celle su 35, piedi fino a 18 su 31; pupazzo 110 px e una barra; fenicottero 467 px e due barre; la sequenza dello scioglimento 56 px e una barra. Dopo, su tutti e quattro i fogli: 0 pixel di bordo, 0 barre, piedi a 30 su 31 in ogni cella. - ✅ Il taglio segue la figura, la scala resta una per foglio, i fotogrammi si traslano e non si scalano. La scala unica è la lezione già pagata col pupazzo che cresceva (SU-806). La traslazione porta l'ultima riga opaca a 30 su 31, cioè i piedi esattamente sull'ombra che la classe base disegna: l'ombra si è sistemata senza toccare una riga di codice del gioco, come chiedeva il ticket.
- ✅ Le tre celle di scivolata del pinguino sono state specchiate nel FOGLIO, non con un'eccezione nel codice: la regola di specchio resta condivisa da tutti i fogli. Misurato sul raw con un marcatore nuovo che guarda l'occhio (bianco racchiuso dal nero) invece del colore caldo, perché sulle pose sdraiate i piedi arancioni finiscono nella metà alta e il marcatore vecchio mente.
- 📌 Una riga che il ticket dava per sbagliata non lo era: la fila sud-ovest aveva camminata e scivolata già concordi. L'unica discorde era la fila est.
- ⚠️ Due trappole pagate e scritte nel codice: la frangia semitrasparente sotto i piedi falsava la misura (ora si spegne sotto la soglia *prima* di misurare), e incollare con maschera moltiplica le trasparenze e cancellava proprio la riga dei piedi.
- ⚠️ Segnalati, fuori da questo ticket: gli stessi difetti sono nel foglio dello yeti (535 px di bordo, 30 celle su 30, tre barre) e in quello del riccone (225 px, piedi a 54 su 63, cioè galleggia 9 px sopra l'ombra). Si rimettono a posto con lo stesso script, aggiungendo le loro due righe.
- ⚠️ Un difetto del RAW, non del taglio: nella fila nord-est del pinguino la scia è disegnata davanti invece che dietro. Specchiare girerebbe la faccia dalla parte sbagliata: serve una rigenerazione.
- ⚠️ NON PROVATO: una partita vera. E il verso non è misurabile sul foglio montato, dove l'occhio è 1-2 px e ogni marcatore dà ±1: si misura sul raw e si guarda l'ingrandito.
Il gatto azzuffa pupazzi di neve e fenicotteri invece di rimbalzarci sopra2026-09-06
SU-826. Deciso da Ivan in chat: il gatto lanciato su un pupazzo o su un fenicottero deve colpirli come un NPC, con la nuvola della zuffa, invece di rimbalzare come sul muro e cadere fuori scena.
- ⚠️ Il criterio del ticket, preso alla lettera, apriva un buco:
has_method("hit_by_cat") sul gruppo dei nemici di quartiere include i PINGUINI. Quel metodo vive nella classe base — come chiedeva il ticket stesso, «una volta per tutti e due» — quindi lo ereditano tutti, e il gatto avrebbe cominciato ad azzuffarsi anche i pinguini, che invece deve attraversare senza toccarli (SU-807). Ora la selezione passa da un flag e un gruppo dedicato, esattamente nella forma già usata per il rimbalzo: il prossimo nemico di quartiere si iscrive con una riga, e chi non si iscrive resta intoccabile. La sonda ha un caso apposta, perché il buco non si riapra. - ⚠️ Altri tre difetti trovati dalla review incrociata di Codex e corretti al cancello, tutti dovuti a *dove* stava il controllo nuovo dentro il proiettile. Stava troppo in alto, prima dei controlli di autorità:
- le monete arrivavano doppie in multiplayer: la zuffa versa monete, e la replica visiva del lancio altrui le versava una seconda volta su ogni peer. Il rimbalzo dello yeti può stare in alto perché non produce nulla; questa no.
- il gatto della gattara si fermava sui nemici di quartiere, mentre deve colpire solo i barboni (SU-109).
- una pozza di pupazzo diventava un ostacolo invisibile: il colpo veniva accreditato e il gatto consumato anche quando il bersaglio era già fuori gioco. Ora la zuffa dice se è partita davvero, e il gatto prosegue se non lo è.
- ✅ La zuffa è scritta una volta sola, nella classe base condivisa da pupazzo e fenicottero: stessa immagine
catfight.png, stesse quattro pose, stesso suono di un colpo di gatto su un NPC o su un poliziotto. Lo sprite del nemico sparisce per la durata della nuvola e il gatto è consumato: non rimbalza e non torna. - ✅ Dopo la nuvola ognuno fa la sua fine, con un gancio che le sottoclassi riempiono in una riga: il pupazzo si scioglie come sotto la torcia (stessa pozza, stesso rimontaggio a 60 s), il fenicottero vola via come già faceva, ma dopo la zuffa e non al posto della zuffa.
- ✅ Misurato: nuvola 2,517 s contro i 2,5 chiesti; pupazzo in pozza a fine nuvola e rimontato a +60,1 s con 0,00 px di scarto dal punto in cui è stato colpito; fenicottero che non fugge durante la nuvola e torna a +20,1 s. Le monete sono quelle di sempre (stessa fonte di SU-34), nessuna ricompensa nuova.
- 📌 Lo yeti continua a far rimbalzare il gatto (SU-808), ed è la prova che dal gruppo dei rimbalzanti non è uscito più del dovuto: eredita tecnicamente il metodo nuovo, ma il ciclo del rimbalzo lo intercetta prima e chiude lì. Verificato dalla sonda.
- ⚠️ Limite noto e accettato: i nemici di quartiere non sono sincronizzati fra i peer, esattamente come già oggi per il rimbalzo. Sul peer di chi lancia la reazione è quella vera; sull'altro la copia locale può azzuffarsi per conto suo con un tempismo leggermente diverso. Nessun errore, nessuna RPC nuova: è la conseguenza del «niente RPC» chiesto dal ticket.
- ⚠️ NON PROVATO: la collisione vera dentro il proiettile (la sonda chiama il metodo direttamente), il drop delle monete in partita, e un incontro multiplayer dal vivo: il bot non ha attraversato Brina né lanciato un gatto sui nuovi nemici.
I tasti del volume tornano al telefono, e il poliziotto del tutorial non riappare alle spalle2026-09-06
SU-821 + SU-820. Due bug indipendenti, lavorati insieme da Codex.
- 📌 SU-821: alzare il volume faceva partire il gioco. Su Android i tasti fisici del volume arrivano al gioco come normali pressioni di tasto, e i tre punti che aspettano «un tasto qualsiasi» li accettavano: la schermata del titolo partiva, la mappa aperta si chiudeva, il rapporto di fine run avanzava.
- ✅ Un solo predicato condiviso,
files/homeless_city/scripts/ui/InputEventPolicy.gd, usato dai tre punti: non tre elenchi di tasti da tenere allineati a mano, che è il modo in cui il bug torna quando qualcuno aggiunge il quarto punto. Riconosce i tre tasti sia come codice logico sia come codice fisico, perché Android valorizza il primo e certe tastiere multimediali il secondo. - ✅ I tasti non vengono consumati: il sistema continua a regolare il volume. Consumarli avrebbe scambiato un bug con un altro. Misurato: predicato 6 su 6, volume ignorato in tutti e tre i punti, la barra spaziatrice avanza ancora in tutti e tre, e il rebind di SU-50 resta a zero azioni attivate e zero associazioni al volume.
- 📌 SU-820: nel tutorial il poliziotto stordito ricompariva in piedi sulla strada già fatta. La funzione che sceglieva dove farlo riapparire rispondeva con un punto 700 px alle spalle del barbone, che in un corridoio lineare vuol dire «dietro di te». Ora il manichino della tappa del gatto esce di scena a fine stordimento, e la tappa resta chiusa dal feedback «COLPITO!» che c'era già.
- ⚠️ Corretto al cancello un difetto introdotto dalla cura: la funzione, quando a chiedere non era il manichino della tappa gatto, ripiegava su un punto all'inizio della corsia — cioè rimetteva un agente in piedi sulla strada già fatta, esattamente il difetto da togliere. Capita se il giocatore tira un gatto al secondo poliziotto, quello bersaglio del campo prova del super. Il ripiego ora è fuori dalla striscia giocabile.
- ✅ Il ritorno sui propri passi è stato verificato tappa per tappa, come chiedeva il ticket: venticinque tappe percorse a ritroso, e per ognuna zero schede riaperte, zero denaro riaccreditato, zero personaggi nuovi. Il ventaglio delle carte e il super non scattano una seconda volta. Restano la nuvola della zuffa, il feedback e le cinque monetine al punto d'impatto.
- ⚠️ NON PROVATO: l'Honor 10 con la build di release, cioè il gesto vero sul telefono, e il passaggio effettivo del tasto al volume di sistema. Il percorso in avanti del tutorial con input interattivo: la prova headless parte dallo stato «tutte le tappe consumate» e riattraversa le porte a ritroso.
A Brina si scivola su tutti i marciapiedi, e le torce passano da cinque a quattordici2026-09-06
SU-828 + SU-827. Ivan in chat: «anche i marciapiedi scivolosi come il parco, tutti», e sulle torce «sono veramente poche ora».
- ✅ Stessa fisica del parco, nessuna terza formula:
ICE_ACCEL/ICE_DECEL di SU-809 valgono identici sul marciapiede. Misurato sulla città vera (seme 424242): il barbone che smette di spingere scivola ancora 39,75 px sul marciapiede, esattamente come nel parco ghiacciato, e 0,00 px sull'asfalto, dove si ferma in un frame come sempre. - ✅ La fonte di verità è il suolo dipinto, non un secondo elenco: il nuovo
terreno_materiale_at() in files/homeless_city/scripts/world/WorldGenerator.gd risponde «che materiale c'è sotto i piedi» leggendo le STESSE regioni (_footprints, park_rects, plaza_rects) che alimentano il pittore del terreno. Un elenco di rettangoli ricostruito a mano sarebbe divergente entro un mese: è il criterio esplicito del ticket. - 📌 Risponde il generatore, non il TileMapLayer, e la ragione è concreta:
terrain_tilemap_enabled è una manopola sperimentale di rendering spenta di default nel gioco vero (gate del 2026-07-25, il TileMap visivo perdeva su due superfici su tre). Agganciare il ghiaccio a quella l'avrebbe reso inerte in partita. - ⚠️ Il difetto più grave l'ha visto la review incrociata di Codex, e nessun report lo diceva: il ghiaccio esisteva solo dove il barbone non può andare. La piastra del marciapiede è disegnata attorno a ogni isolato e sborda di 18 px oltre di esso (
wang_pad 34 meno WANG_INSET 16). Dentro il footprint ci stanno gli edifici: la striscia calpestabile è quasi tutta quello sbordo. La prima versione di terreno_materiale_at() classificava solo l'interno, quindi in partita non si sarebbe scivolato quasi da nessuna parte — e la sonda diceva PASS lo stesso, perché misurava il centro dell'isolato. - ⚠️ Le due sorgenti del suolo non hanno la stessa geometria, e ora la funzione lo sa. Col TileMap Wang acceso, erba e piazza si dichiarano rientrate di una cella e il marciapiede è il footprint esatto; con le piastre storiche — cioè nel gioco di oggi, dove il TileMap è spento — il marciapiede sborda e parchi e piazze occupano il loro rettangolo intero. Rispondere con la geometria sbagliata significa dire «asfalto» dove si vede marciapiede: la funzione ora sceglie il ramo in base a
terrain_tilemap_enabled. - ✅ La sonda adesso misura il punto che conta. Quattro posizioni sulla città vera: centro dell'isolato e sbordo a 9 px fuori scivolano entrambi 39,75 px; metà strada a 48 px non scivola affatto; il centro di una piazza resta piazza e a 9 px fuori si torna su marciapiede, che è il confine attraversato camminando verso la piazza.
- 📌 Un rilievo di Codex scartato, e il perché: segnalava come violazione della convenzione i nomi italiani
terreno_materiale_at, _rect_dipinto, marciapiedi_ghiaccio. In quei file il vocabolario del dominio è già italiano (dipingi, _dipingi_terreno_tilemap, e i flag parchi_ghiaccio, neve_sempre, fontane_ghiacciate), e marciapiedi_ghiaccio è il nome scritto nel ticket. Rinominare solo i tre nuovi avrebbe prodotto un'incoerenza peggiore di quella segnalata. - ⚠️ La sonda di SU-809 diceva una cosa ormai falsa, e passava lo stesso. Il suo finto generatore non conosceva i materiali, quindi il ramo nuovo ripiegava da sé su «niente ghiaccio» e la riga stampata sembrava dire «a Brina sul marciapiede non si scivola». Ora quel finto risponde ASFALTO esplicitamente e la riga si chiama
brina_fuori_parco: il caso resta vero per costruzione invece che per una svista. È il tipo di sonda che, lasciata così, avrebbe fatto sbagliare diagnosi al prossimo che la legge. - ✅ Flag nuovo
marciapiedi_ghiaccio, acceso solo su Brina in files/homeless_city/scripts/autoload/Quartieri.gd: gli altri sette quartieri non cambiano di niente, e resta indipendente da parchi_ghiaccio perché le due sorgenti sono diverse. - ✅ Torce da 5 a 14 per mappa,
TORCE_DIST_MIN fermo a 120 px così non si ammucchiano. Misurato su 4 semi: 14 torce a Brina su tutti e quattro, 0 negli altri sette quartieri, e le due esecuzioni separate danno 32 righe di generazione identiche. - ✅ Il dado non si è spostato: le tre regole di SU-812 (dado separato, tiri sempre consumati, una sola domanda al flag) reggono, e la prova è che la stessa città con lo stesso seme genera i nemici nelle stesse posizioni prima e dopo il cambio — 16 righe bit-identiche.
- ⚠️ NON PROVATO: la partita vera. Nessun test con input da tastiera, solo sonde headless: resta il criterio «Ivan prova in partita».
La sirena della polizia tace quando il gatto colpisce, e il livello bonus semina chi ti insegue2026-09-06
SU-822 + SU-830. Due difetti diversi con la stessa radice: la sirena si spegneva al frame dopo, e c'è chi quel frame dopo non lo vede mai.
- 📌 SU-822 ribalta una scelta esplicita di SU-603. La vecchia regola diceva «durante lo stordimento lo stato resta *chase*: l'inseguimento è in pausa, non finito, e la sirena resta accesa». Ivan (2026-09-06) l'ha capovolta: la caccia finisce nell'istante del colpo. Ora
stun_by_cat() esce da *chase*, azzera il bersaglio e chiama _end_chase_alert() prima del suono della zuffa. - ✅ L'ordine è misurato, non stimato: agganciandosi alla nascita del lettore della zuffa (che nasce un'istruzione prima del
play()), la sirena risulta già spenta. Con due poliziotti addosso il contatore fa 2 → colpito il primo 1 → colpito il secondo 0: colpirne uno toglie solo il suo contributo, e la sirena resta accesa per l'altro. - 📌 SU-830 nasce da lì. Il viaggio in metro normale semina chi ti insegue; la porta del livello bonus no. Peggio: subito dopo, il congelamento della città mette tutto in
PROCESS_MODE_DISABLED, quindi l'unico punto che spegneva la sirena non gira più e il tunnel restava con la sirena accesa e la caccia agganciata alla risalita. - ✅ Cura:
_metro_apri_bonus() chiama la stessa semina del viaggio vero (stesso raggio, stessi 6 s, retata esclusa) prima che la città venga congelata, e super_confuse() spegne l'allarme in modo sincrono invece di lasciarlo al frame dopo. Il permesso di scendere si chiede una volta sola, e serve a due cose: la musica del tunnel e la semina. In multiplayer non parte né l'una né l'altra, perché il bonus rifiuta. - ✅ Il cronometro si congela con la città, ed era da verificare, non da supporre: misurato
_arrest_cooldown a 0,000 prima, 6,000 s alla discesa, 6,000 s invariati sotto, 6,000 s alla risalita. I sei secondi di grazia decorrono quindi dall'uscita, che è quello che serve. Nel tunnel la sirena non suona mai, e dopo la risalita zero arresti. - ✅ Regressioni verificate: la sonda della sirena di SU-603 passa a tutte le righe, lo scudo del tornello di SU-727 dà 0 arresti su 16 con la finestra di rischio identica, e il giro banca → fermata → bonus → risalita fa 42 controlli su 42.
- ⚠️ Un agente che insegue da OLTRE i 700 px del raggio tiene la sirena accesa anche nel tunnel. Non è un residuo del difetto: è la parità col viaggio in metro chiesta dal ticket.
- ⚠️ NON PROVATO: il multiplayer vero. È misurato l'ordine delle chiamate, non la consegna dei pacchetti, che vuole una sessione host più client. E in headless la discesa non consuma la prima metà del viaggio, quindi in gioco la grazia all'uscita sarà ~5,8 s invece di 6,0.
- ⚠️ Trovato per strada, NON causato da noi: la sonda del livello bonus falliva già dieci controlli dei suoi primi quattro giri (progresso 0,1515, «1 treno», due sequenze identiche). Verificato con una copia di controllo al commit precedente: numeri identici. Vale un ticket suo.
I comandi da tastiera passano a QWER, e uno scontro di tasti vecchio di dieci giorni sparisce2026-09-06
SU-819. Ivan: «i comandi standard a questo punto li cambierei a prescindere nel gioco: q azione, w scorreggia/lancia gatto, e musica, r mappa».
- 📌 Non è uno schema inventato a tavolino: è quello che l'autore già usa. Aprendo
user://keybinds.cfg sul suo Mac si scopre che se li era assegnati a mano il 26 agosto — interact=Q, fart=W, busk=E, map=R — e ci gioca da allora. Il ticket non progetta niente: promuove a default una cosa collaudata sul campo per dieci giorni. - ✅ La mappa nuova:
interact E→Q, fart F,G→W, busk B,M→E, map T→R. Le quattro azioni in fila sotto la mano sinistra. - 📌
move_up perde W e resta sulla sola freccia ↑, ma A, S e D RESTANO. Avevo deciso di spostare tutto il movimento sulle frecce, per coerenza con «QWER sotto la mano sinistra». Poi ho letto il keybinds.cfg di Ivan: lui A, S e D se li era tenuti. Quel file valeva più del mio ragionamento, e i default ora lo rispecchiano esattamente. - ⚠️
super passa da Q a SPAZIO — e questo ripara una collisione che c'era da agosto. super non è fra le REBINDABLE_ACTIONS, quindi sulla macchina di Ivan era rimasto su Q mentre lui ci aveva messo l'azione: premendo Q partivano tutti e due. Nessuno se n'era accorto perché il file dei tasti personalizzati non tocca le azioni non rimappabili. - ⚠️
DEFAULT_KEYBINDS in files/homeless_city/scripts/autoload/Settings.gd duplica i default di project.godot e va tenuto allineato a mano: se no «Ripristina comandi predefiniti» rimetterebbe i tasti vecchi. Aggiornato. - ✅ Le scritte che nominano un tasto:
MENU_HINT_*_KB in ui_menu.csv dicevano «INVIO/E», ora «INVIO/Q» (4 chiavi × 8 lingue); SUPER_HINT_UNA e SUPER_HINT_MULTI in ui_hud.csv dicevano «E/A», ora «Q/A» (2 × 8). Verificato con uno scatto: la riga sotto il menu dice «INVIO/Q CONFERMA». - 📌 Il resto delle targhette non si tocca, perché non è scritto a mano: il codice le costruisce da
InputMap.action_get_events(), quindi seguono da sole il cambio di mappa. - ✅ Pad e comandi touch invariati: su mobile non cambia niente.
- ⚠️ NON PROVATO: una partita vera con i tasti nuovi, e le targhette in gioco (fuori dal menu). Chi ha già un
user://keybinds.cfg tiene i suoi tasti: i default nuovi valgono per chi installa da zero o preme «Ripristina comandi predefiniti».
La build web era completamente muta, e non lo diceva a nessuno2026-09-06
SU-818. Ivan, provando su itch.io la demo appena caricata: «non sento l'audio!».
- 📌 La causa è nostra, non del browser né di itch né di Godot. Il gioco crea i bus
"Music" e "SFX" a runtime (Settings._ensure_music_bus() / _ensure_sfx_bus(), con add_bus() + set_bus_send("Master")). Su desktop e mobile funziona. Sull'export web quel bus non instrada niente: tutto ciò che gli viene mandato sparisce — e siccome la musica va sul bus Music e gli effetti sul bus SFX, il gioco è muto del tutto. - ⚠️ Il difetto non produce un solo errore.
AudioServer risponde send=Master, mute=false, db=0, playing=true, e in uscita ci sono zero campioni. È esattamente il motivo per cui è arrivato fino alla pagina pubblicata: non c'era niente da leggere in un log. - ✅ Cura:
files/homeless_city/default_bus_layout.tres, i due bus dichiarati nel progetto invece che creati a runtime. Godot lo carica da sé da quel percorso. Nessuna riga di logica cambiata: le due funzioni ora trovano i bus e il ramo add_bus() resta come rete di sicurezza; volumi e mute continuano ad applicarli loro. - ✅ Misurato, non dedotto. Ho innestato un analizzatore sull'uscita audio della pagina e ho letto il segnale invece delle apparenze. Escluse una per una: la regola dell'autoplay del browser, le impostazioni predefinite (musica ed effetti a 1.0), i suoni mancanti dal pacchetto (
throw.wav, busking.wav, brina.ogg sono tutti in index.pck), la variante senza thread (ricompilata con i thread e gli header di isolamento: sempre zero) e la modalità demo (ricompilata la build piena: muta anche lei). - ✅ La prova che separa il motore dal progetto: un progetto Godot minimo, stesso motore e stessi template, dove il suono si sente. Aggiungendogli il nostro schema è sparito. Tre misure sullo stesso identico progetto: suono sul bus Master 59, su un bus creato a runtime 0, su un bus dichiarato nel layout 43. Controprova dello strumento: un beep generato a mano legge 26 e torna a 0 quando si ferma.
- ✅ Verificato sulla demo vera: ricompilata con la correzione, il segnale in uscita passa da 0 a 84.
- ⚠️ La spiegazione sta sopra
_ensure_music_bus() in files/homeless_city/scripts/autoload/Settings.gd, non dentro il .tres — e non è pignoleria: Godot cancella i commenti ; dai .tres a ogni risalvataggio, e tools/autotest/check_no_scene_comments.sh mi ha giustamente bocciato il primo tentativo. - ⚠️ NON PROVATO: che desktop e mobile suonino ancora. Il layout è neutro (0 dB, non mutato) e Settings applica i volumi come prima, quindi il rischio è basso, ma va sentito. E la build su itch.io va rifatta: quella caricata è muta.
Ogni mappa ha almeno due parchi, in tutti i quartieri2026-09-06
SU-817, da una decisione di Ivan in chat: «ci vanno almeno due parchi a mappa in tutti i quartieri in generazione».
- 📌 Non è servito codice nuovo. La garanzia esisteva già: è
park_min, la chiave che SU-481 aveva costruito per FUMAROLA, e il suo commento in files/homeless_city/scripts/world/WorldGenerator.gd prevedeva proprio questo giorno — «la chiave si chiama park_min e domani LA COLLINA può chiedersene due». Cambiato il default in LAYOUT_DEFAULTS da 0 a 2, e basta. - 📌 Perché serviva: i pupazzi di neve (SU-811) e i fenicotteri (SU-815), consegnati poche ore prima, vivono legati a un
park_rect e non ne escono mai. In una mappa senza parchi non esistono proprio. Misurato su 4 semi chiudendo quei ticket: pupazzi 6/0/10/11 e fenicotteri 0/13/7/6, e gli zeri erano semi con zero parchi, non un difetto della ricerca del punto. - ✅ Misura prima e dopo, 8 quartieri × 8 semi: le mappe con meno di due parchi passano da 15 su 64 a 0 su 64. Brina 2/0/4/3/2/1/1/1 → 2/2/4/3/2/2/2/2; Fumarola da otto mappe con un parco solo a otto con due; Collina aveva uno zero, Nottefonda uno zero e due uno. I quartieri che ne avevano già abbastanza sono identici cifra per cifra (centro, mercato, gattopoli, solleone), che è la prova che la garanzia aggiunge solo ciò che manca.
- ✅ Nessun garantito sfrattato:
su323_censimento.gd su 6 semi × 8 quartieri dà PASS, e la sezione di non-intrusività dice IDENTICHE per tutti — la promozione pesca solo fra i super-isolati 2×2 di palazzi, e rifugio, camioncini, negozi, pensilina, landmark e banca restano dove erano. Zero avvisi «nessun super-isolato 2×2 da cui ricavare il parco» su 64 mappe. - ⚠️ Una deviazione dichiarata: FUMAROLA aveva
park_min: 1 per carattere — SU-481 le voleva UNA piazzetta sola, «zona industriale, il posto dove sedersi non c'è». Ivan ha detto «in tutti i quartieri», quindi è passata a 2. C'è un commento accanto al numero che dice qual è e come tornare indietro. - 📌 La forma della città non cambia:
_ricava_parchi_garantiti() promuove un super-isolato di palazzi già allocato, cioè cambia il kind e non le posizioni. Per questo la firma di generazione cambia in tutti i quartieri toccati ed è previsto: qui si confronta il numero di parchi, non la firma. - ⚠️ Trappola dello strumento, non del gioco:
tools/autotest/common.sh definisce sync_proj() ma non la chiama. Le sonde girano su una copia rsync del progetto, quindi lanciando Godot dopo un semplice . common.sh si misura il codice vecchio: il primo confronto prima/dopo dava zero differenze con la modifica già scritta su disco. Si chiama ensure_import "" (che sincronizza) prima di lanciare.
La torcia di Brina: si raccoglie, si tiene in mano, si lancia e incendia2026-09-06
SU-812 e SU-813 in un lotto solo, perché il secondo esiste solo se il primo ha già messo has_torch al suo posto.
- ✅ Tre nodi nuovi e nessuna scena:
files/homeless_city/scripts/entities/TorchPickup.gd (la torcia a terra), TorchProjectile.gd (il volo), TorchFireball.gd (il fuoco), istanziati con Node2D.new() + set_script() come già fa il generatore coi nemici di quartiere. Scartata la strada «il fuoco dentro il proiettile»: la palla deve sopravvivere al proiettile che la crea. - 📌 Il nodo È l'ombra, lo sprite sale sulla parabola. È l'unico modo di dare l'arco visto dall'alto senza mentire su dove cadrà: l'ombra avanza dritta a terra e il disegno sale e scende sopra di lei. La domanda «c'è un isolato pieno?» la fa
ProjectileCrash.blocked() (SU-683), con la sua grazia iniziale per chi lancia da sotto la pensilina. - ✅ Nessuna RPC nuova:
net_cat_thrown / _cl_cat_thrown in files/homeless_city/scripts/world/World.gd:4029 prendono un tipo (0 gatto, 1 torcia), e la torcia eredita gratis il despawn e il registro delle repliche. Un bool in più su _net_state, ordine delle RPC intatto. - ✅ La palla di fuoco chiama
burn() col has_method sul gruppo district_foes, non su un elenco di tipi: così il fenicottero si esclude da solo — è di Collina — e nessuno dovrà ricordarsi di aggiornare una lista quando arriverà il prossimo nemico. Gli NPC standard passano da hit_by_cat() filtrati da is_enemy(). - ✅
burn() aggiunto al pinguino (files/homeless_city/scripts/world/Penguin.gd, 93 righe in coda e zero righe tolte): era l'unico dei tre nemici di Brina a non averlo, e SU-813 lo richiede. La rinascita è ricavata dallo stesso orologio della fase pura, non da un cronometro. - ✅ Misure (
bash tools/autotest/probe_su812_torcia.sh 2, 0 problemi): gittata 160,0 px in 0,600 s (chiesti 160±8 e non prima di 0,5 s); con un isolato pieno a 80 px la palla nasce a 75,6 px, cioè prima dell'isolato; pupazzo a 30 px bruciato e a 70 px intatto, yeti a 20 px bruciato, fenicottero a 20 px intatto, spazzino abbattuto come col gatto, civile e poliziotto intatti; secondo passaggio del fuoco → 0 bersagli ricolpiti. Raccolta: has_torch vero al frame 1 di fisica, il gatto se ne va nello stesso evento, una seconda torcia resta a terra, rinascita assente a 59 s e presente a 61 s. Generazione: 5 torce a brina, 0 in tutti gli altri sette quartieri. - ✅ Anti-regressione su SU-211:
probe_su329_lanterna.gd verde — a zoom 2,5 il raggio della pozza di luce è 84,328 px mondo, identico a prima. Il ramo della lanterna senza torcia non è stato toccato; con la torcia in mano la lanterna resta nascosta anche in piena notte e posizione_lanterna() restituisce la torcia, così la luce esce dalla fiamma. - 📌 Una grazia che sembra un dettaglio e non lo è: la torcia che CADE quando raccogli un gatto ha 1,5 s di grazia, altrimenti l'Area2D nata sotto i piedi la riprenderebbe subito e gatto e torcia si scambierebbero all'infinito. Il gancio sta su
has_cat_changed, perché i modi di ritrovarsi un gatto in braccio sono quattro e quello è l'unico punto in cui passano tutti. - ⚠️ Due trappole del provino, pagate e scritte nel codice della sonda. (1) Uno
StaticBody2D spostato per assegnazione non fa mai scattare body_entered: il provino bocciava codice sano, e la prova che era sano è che una torcia creata *sopra* lo stesso corpo veniva raccolta subito. Si usa CharacterBody2D. (2) In headless il giro di idle non è agganciato al vsync, quindi dieci process_frame possono stare dentro un solo passo di fisica: la parte di raccolta aspetta physics_frame. - ⚠️ NON PROVATO: la partita vera (raccogliere camminando, il tasto che lancia davvero, la palla su nemici veri, il suono ascoltato), il MP a due peer — il bit
torcia e il tipo=1 sono scritti ma non hanno mai viaggiato —, e l'aggancio della torcia in mano camminando: gli scatti sono a posa ferma. - ⚠️ Da decidere con Ivan: il disegno del fuoco è largo 48 px e il raggio che brucia è 48, cioè il calore arriva il doppio più in là della fiamma che si vede. È il numero del ticket, e il disegno è a 1:1 perché scalarlo ×2 darebbe pixel grossi il doppio del resto della città: se li vuole coincidenti serve un disegno più grande, non una scala.
Il riccone in monociclo della Collina prende la rincorsa e ti carica2026-09-06
SU-814, lavorato su Codex mentre l'altro lotto teneva WorldGenerator.gd. Il riccone non nasce con la città — arriva a 20 s di run da un bordo mappa — quindi vive in files/homeless_city/scripts/world/World.gd e i due lotti non si sono mai incrociati.
- 📌 Il verdetto di Ivan del 6 settembre è MONOCICLO, e scioglie l'ambiguità che il ticket segnalava («monociclo» nel titolo, «motociclo» due volte nel corpo): vale il titolo.
- ✅ Il criterio di accettazione resta com'è, contro la mia previsione. Avevo scritto che il monociclo raddoppia in altezza e resta stretto, quindi che «occupa circa il doppio in LARGHEZZA di un NPC» andava riscritto: sbagliato, l'avevo dedotto dal foglio grezzo a 250 px. Misurato sullo sprite montato, riga E primo frame, alfa vera: 33×52 px contro 13×32 di un NPC, cioè 2,54× in larghezza e 1,62× in altezza. A misura di gioco l'uomo sporto in avanti più la ruota lo allargano. Due misure indipendenti concordi, la mia e quella del builder.
- ✅ Macchina a stati: pattuglia lungo le strade a 90 px/s con svolte agli incroci e mai dentro gli isolati; vista entro 260 px con linea libera; fermo 1,0 s a sgasare mentre registra la posizione; poi carica in linea retta a 225 px/s senza più correggere — è quel «senza più correggere» che rende la schivata una vera abilità e non un tiro di dado.
- ✅ Misure della sonda (
files/homeless_city/scripts/tools/probe_su814_riccone.gd): arresto 1,017 s, partenza 225,0 px/s verso il punto registrato, bersaglio spostato di 60 px durante la carica → 0 colpi, nuvola e rimozione in 0,600 s, linea di vista bloccata da un isolato → nessuna rincorsa in 10 s. Con seme 814 le tre attese pianificate sono 67,05 / 49,49 / 60,59 s, tutte dentro il vincolo 45-75. - ✅ Host-authoritative come il resto del multiplayer: simulazione solo su host o partita singola, snapshot a 10 Hz di soli scalari e Vector2, e uno snapshot dedicato per il peer che entra a riccone già in strada. Il colpo lo decide l'host, con
lose_life("riccone") e il contraccolpo triplo della metro nel verso della carica. - ✅ Il gatto non lo ferma: il riccone è una parete mobile,
cat_bounce() è cosmetico e il gatto fa la fine del muro. Non entra nel gruppo npcs, quindi polizia e wanted non lo vedono. - ⚠️ NON PROVATO: il multiplayer a due peer veri, il frame-time con un riccone a schermo, e il colpo sul Player reale con lo spostamento di 80 px in 0,5 s. I quattro wav non c'erano quando il lotto ha chiuso: adesso ci sono, agganciati con
load difensivo e coi prompt ElevenLabs scritti nei commenti accanto alle costanti, come chiedeva il ticket. - ⚠️ Una cosa che il builder non ha potuto fare, e ho fatto io:
bash tools/autotest/probe_su540_invarianza.sh non gli era accessibile perché il wrapper lancia git show e il brief vieta ogni comando git. Lanciata a mano insieme a quella dell'altro lotto: verde.
Brina e Collina hanno i loro nemici: pinguini, yeti, pupazzi di neve e fenicotteri2026-09-06
SU-807, SU-808, SU-811 e SU-815 in un lotto solo, perché nascono tutti e quattro dentro WorldGenerator.gd e su un albero condiviso due builder sullo stesso file si cancellano a vicenda.
- ✅ Due stampi invece di quattro copie.
files/homeless_city/scripts/world/DistrictFoe.gd tiene il corpo, che misurando è identico in tutti e quattro (~250 righe: foglio a 5 righe E/SE/S/NE/N con le tre d'ovest specchiate, ombra, y-sort, Area2D layer 0/mask 1, grazia condivisa, _process spento fuori campo, fase pura dal punto di nascita, suono difensivo). ParkFoe.gd è lo stampo intermedio del «resta nel parco e insegue chi entra», che pupazzo e fenicottero condividono davvero — i ticket stessi si chiamano cugini. Il movimento, che è diverso per tutti e quattro, è rimasto nelle sottoclassi: astrarlo sarebbe stato forzato. - 📌 Il pinguino non simula, legge. Il giro è una lista chiusa di tratte costruita alla nascita (andata più le stesse tratte al contrario), quindi la posizione a un istante è funzione pura di
DifficultyManager.elapsed: il recupero al rientro in campo è O(1). L'alternativa a passo fisso avrebbe voluto 70.000 passi di catch-up per un pinguino rimasto fuori inquadratura venti minuti. - ✅ Quattro dadi separati in
WorldGenerator.gd (0x9146C1, 0x7E71E5, 0x5A0C01, 0xF1A6C0), seedati dal seme della run come già fa _rng_cactus: host e client generano la stessa città. I tiri si consumano anche quando il punto viene scartato, altrimenti la sequenza divergerebbe. - ✅ Grazia condivisa vera, senza contatori per famiglia: quattro cause nuove in
HIT_GRACE_CAUSES di files/homeless_city/scripts/autoload/GameState.gd. È ciò che rende «tre pupazzi addosso nello stesso frame = una vita sola» un fatto e non una promessa. - ✅ Il gatto rimbalza su yeti, pupazzo e fenicottero (gruppo
cat_bouncers, ramo nuovo in files/homeless_city/scripts/entities/CatProjectile.gd). Sui pinguini no, di proposito: il ticket dice «il gatto ci passa oltre». - ✅ Misure, non impressioni (sonda
files/homeless_city/scripts/tools/probe_su807_nemici.gd, passo fisso 1/60): pinguino cammina 44,9 px/s (chiesti 45±5) e scivola 159,0 (160±10), scivolo più lungo 2,50 s (2,5±0,2); camminando addosso per un giro intero −0 vite, scivolando −1 e non −2. Yeti vaga 36,0 e insegue 55,0 px/s, fermo 2,02 s dopo il colpo, −3 vite in 12 s di contatto continuo, ruggito 1 all'aggancio e 0 nei 10 s dentro il raggio. Pupazzo 27,1 / 72,0 px/s, 0 frame fuori da rect+16 su 40 s, pozza 0 danni in 60 s e rimontaggio a 0,00 px dal punto. Fenicottero 27,7 / 100,0 px/s, starnazzi 1/0/1 su ingresso, permanenza e rientro. - ✅ Invarianza verificata:
bash tools/autotest/probe_su540_invarianza.sh 3 HEAD dice che cambiano solo brina e collina; gli altri sei quartieri hanno firma identica bit per bit, e due processi separati danno le stesse 24 righe di nascita. - ⚠️ Una trappola che sembrava un difetto degli sheet. Il primo scatto aveva celle vuote e pareva arte mancante: contati i pixel opachi, tutti e 5×7 i riquadri erano pieni. Era il provino —
set_process(false) non basta, perché screen_entered riaccende il processo e il moto riscrive la posa prima dello scatto. Serve PROCESS_MODE_DISABLED. - ⚠️ E una che sembrava una costante sbagliata. Lo yeti vagava a 25,4 px/s invece di 36 col codice giusto: raggiungeva il punto-bersaglio e da lì in poi andava alla velocità *del punto*, non alla sua. Ora il bersaglio gira su un'ellisse a velocità tangenziale sempre sopra i 36 px/s, così non lo raggiunge mai. C'è un commento che avvisa di rifare la misura a chi tocca quelle costanti.
- ⚠️ NON PROVATO: una partita vera (il gate
VisibleOnScreenNotifier2D, il gatto lanciato davvero contro yeti/pupazzo/fenicottero, burn() dalla torcia che ancora non esiste), il MP a due peer, e il frame-time con dieci pinguini a schermo, che è il criterio n.5 di SU-807. - ⚠️ Da decidere con Ivan: su brina e collina i parchi possono essere zero (
prob_park 0,08 e 0,16 — misurato: succede su un seme su quattro), e allora il quartiere resta senza pupazzi o senza fenicotteri. Basterebbe un "park_min": 2 nel layout in Quartieri.gd, ma è una modifica alla generazione della città che nessun ticket chiede.
Dieci suoni nuovi, i cinque pezzi della torcia e le frasi dei nemici in otto lingue2026-09-06
Il contorno dei ticket di Brina e Collina: quello che i lotti non potevano fare da soli.
- ✅ Dieci wav generati con ElevenLabs, in
files/homeless_city/assets/sounds/ e Sounds_raw/: yeti_roar (1,60 s), yeti_hit (1,00), flamingo_squawk (1,00), flamingo_peck (0,48), richman_rev (1,48), richman_charge (2,00), richman_crash (1,48), richman_hit (1,00), torch_fire (1,48), penguin_slide (1,20). Tutti a −5,7 dBFS, che è esattamente il picco di throw.wav: il criterio «entro ±3 dB» di ogni ticket è soddisfatto per costruzione, non per fortuna. - ⚠️ Due trappole della catena audio, ora scritte in
tools/sprite/pcm_a_wav.py. (1) Con output_format=pcm_44100 l'MCP di ElevenLabs salva un file con estensione .mp3 che di MP3 non ha niente: è PCM grezzo 16 bit STEREO a 44100 Hz senza intestazione. Letto come mono viene lungo il doppio e suona un'ottava sotto. La prova che è stereo non è la correlazione fra i canali (0,99: alta anche in un mono qualsiasi) ma le DURATE, che interpretate come stereo tornano esatte a quelle chieste — controprova sul tonfo sordo, centroide 571 Hz da stereo contro 10.490 Hz da mono. (2) Escono a 0,0 dBFS, cioè al limite: senza normalizzare sarebbero tutti fuori specifica e più forti del resto del gioco. - ✅ I cinque pezzi della torcia montati (SU-804):
torch_ground.png 48×16 (3 frame), torch_hand.png 24×12 (2), torch_icon.png 40×40, torch_fly.png 64×16 (4 frame di rotazione), torch_fireball.png 288×48 (4 di fiamma in loop più 2 di spegnimento col fumo). I raw in sprites_raw/. - ⚠️ L'icona HUD è 40×40, non i 24×24 scritti nel ticket: 40×40 è la misura vera di
files/homeless_city/assets/sprites/caticon.png, di cui la torcia prende il posto, e un'icona di misura diversa nello stesso slot si vedrebbe. Verificato affiancandole: stessa altezza di disegno (40 px) e stesso contorno da 1 px. - ✅ Quattro chiavi nuove in
files/homeless_city/assets/translations/ui_mondo.csv, tradotte in tutte e otto le lingue: MONDO_PINGUINO_SCIVOLATA, MONDO_YETI_COLPO, MONDO_PUPAZZO_COLPO, MONDO_FENICOTTERO_BECCATA. L'italiano è identico al fallback scritto nel codice, così il testo non cambia sotto i piedi a nessuno. Il lotto le cercava in ui_menu.csv: stanno in ui_mondo.csv, che è il file delle frasi di gioco. - ✅ L'importatore dei nemici sa fare anche le griglie semplici (
--striscia --righe N), che serviva per la torcia in volo (2×2 nel sorgente) e per la palla di fuoco (3×2), riportate in fila a 4×1 e 6×1. - ⚠️ NON PROVATO: nessuno di questi suoni è stato ascoltato in cuffia — sono verificati per durata, picco e formato, non per resa. E la torcia non è ancora usata da nessuna parte: i due ticket che la raccolgono e la lanciano (SU-812, SU-813) sono il lotto dopo.
I nemici di Brina e della Collina entrano nel gioco, e il fenicottero cambia fondo2026-09-06
Verdetti di Ivan in chat: pinguino v2, yeti v2, pupazzo v1, fenicottero v1, riccone col MONOCICLO. Con una richiesta sopra: «attenzione che il fenicottero essendo rosa viene ritagliato malissimo, forse è il caso di rigenerarlo mettendogli uno sfondo green o blue screen».
- ✅ Montati otto asset nuovi in
files/homeless_city/assets/sprites/: penguin_sheet (cella 32, 7 col), yeti_sheet (48, 6), snowman_sheet (32, 4), richman_sheet (64, 5), flamingo_sheet (32, 8), più le tre strisce che mancavano — yeti_steam (3 frame), snowman_melt (6 frame di scioglimento + la pozza), richman_crash (4 frame di nuvola). I raw stanno in sprites_raw/ con lo stesso nome, dentro LFS. - 📌 Perché il verde e non il magenta, misurato invece che scelto a naso: l'importatore ripulisce i bordi trattando come sfondo tutto ciò che dista meno di 170 da
#FF00FF. Sul fenicottero il 35,35% dei pixel di disegno cade dentro quella fascia (sul pinguino solo il 10,58%): un terzo dell'uccello era a rischio. Su #00FF00 la quota è 0,00%. Scartato il ciano, che darebbe un margine ancora più largo (188 contro 170), perché è vicino ai bianchi-azzurri della neve e questo fondo deve poter servire ad altri sprite. - ✅ Controprova sullo stesso uccello: importato due volte, la v1 su magenta e la v3 su verde. Buchi interni — i morsi che il ritaglio strappa dentro la sagoma — 150 col magenta, 35 col verde, e l'11% di pixel vivi in più. Il difetto che Ivan aveva visto a occhio era esattamente questo.
- ✅ Nuovo
files/homeless_city/tools/import_sprite_quartiere.py, accanto a quello storico e senza toccarlo: colonne, cella e colore di chiave dichiarati, e la parte delicata (flood-fill, sacche chiuse di SU-182, frangia scura, riduzione LANCZOS→NEAREST) riusata pari pari invece che riscritta. - ⚠️ Due difetti del montaggio trovati guardando il risultato, non il codice.
- Il pupazzo che si scioglie prima cresceva. L'importatore storico normalizza ogni frame sul proprio riquadro stretto, quindi in una sequenza dove la MISURA è il contenuto il frame più piccolo viene ingrandito di più. Ora il margine è unico per tutto il foglio (
--ritaglio foglio, default): le proporzioni fra un frame e l'altro restano quelle disegnate. Vale anche per il colpo dello yeti e la carica del riccone. - Dentro la ruota del monociclo compariva un arco magenta. Il fondo fra un raggio e l'altro è una sacca chiusa dal copertone, e nell'ordine storico (
bg, loose, sacche) la passata che mangia l'alone di antialiasing gira PRIMA che la sacca venga aperta: quei bordi rosa, a distanza ~128 dal magenta e quindi invisibili alla soglia stretta, sopravvivevano alla riduzione. Aggiunta una seconda passata permissiva dopo l'apertura delle sacche, e portate a sei le passate finali (costo misurato sugli otto sheet: fra 0,0% e 0,06% di pixel, tutti fondo residuo).
- 📌 Il monociclo scioglie anche l'ambiguità del ticket, che diceva «monociclo» nel titolo e «motociclo» due volte nel corpo: vale il titolo. Il criterio di accettazione va corretto: «occupa circa il doppio in LARGHEZZA di un NPC» valeva per il motociclo, il monociclo raddoppia in ALTEZZA e resta stretto. Annotato su SU-805.
- ✅ Motociclo e biciclo storico non sono stati buttati — richiesta esplicita di Ivan: stanno in
sprites_raw/archivio_varianti_scartate/, dentro LFS come tutto il resto dei raw. - ⚠️ Una mia svista, rettificata sui ticket: avevo scritto che pinguino e yeti avevano le righe E e NE specchiate. Erano giuste. Il marcatore generico di
tools/sprite/misura_verso.py prendeva i pixel scuri della metà alta come «occhio», ma su un pinguino la massa scura è la schiena, che sta dalla parte opposta al becco: il numero usciva col segno rovesciato. Rifatta col becco arancione ristretto al 40% alto (i piedi sono arancioni anche loro e falsavano la media) i versi risultano corretti, e la conferma è venuta guardando le celle a piena misura. La trappola è ora scritta in testa allo strumento. - ⚠️ NON PROVATO: nessuno di questi sprite è ancora usato in gioco — i sette ticket di implementazione (SU-807, SU-808, SU-811…SU-815) erano bloccati proprio dall'approvazione e si aprono adesso. Non esistono ancora le risorse SpriteFrames: le fa chi implementa, perché i nomi delle animazioni li decide il ticket.
- ⚠️ Resta un residuo noto: 214 pixel su 26.610 dentro la ruota del monociclo sono ancora fondo, non arte. Non sono adiacenti a nulla di trasparente (li chiudono i raggi) e a 64 px si leggono come ombreggiatura dei raggi. Se dovessero dare fastidio si tolgono a mano sul raw.
Il parco di Brina diventa ghiaccio vero, e la neve passa al marciapiede2026-09-06
SU-816, aperto stanotte su decisione di Ivan: «il lago ghiacciato deve essere di ghiaccio, per far capire che si scivola, la neve mettiamola altrove (marciapiedi?)».
- 📌 Non era scopo nuovo: era un asset che non aveva mai rispettato la propria decisione. Il commento di SU-406 in
files/homeless_city/scripts/autoload/Quartieri.gd:545 dice dal 14/08 «il parco è un LAGO GHIACCIATO (crepe, bolle sotto il ghiaccio, pattinate)». La tessera consegnata allora era invece un azzurrino uniforme con fiocchi sfumati: leggeva come neve fresca. Con SU-809 adesso lì si scivola, e niente a schermo lo diceva. - ✅
park_brina.png rifatta: lastre di ghiaccio a due profondità, crepe con un bordo illuminato su un lato solo (è quello che le fa leggere come spessore e non come graffi), bolle d'aria tonde sotto la superficie, e pattinate a coppie di righe chiare — la firma che dice «questo è un lago». - ✅
floor_brina.png: la neve non è stata ridisegnata. Le lastre sono arte approvata (SU-406), quindi si sono prese le chiazze chiare che già c'erano, allargate di un pixel e spinte verso il bianco. Il disegno delle lastre non cambia di un pixel. - 📌 Procedurale, non generato:
tools/su816_ghiaccio_brina.py. A 96×96 e con l'obbligo di piastrellare, una texture uscita da un generatore non combacia ai bordi e la cucitura si vede come una griglia su tutta la città. Qui ogni tratto è disegnato in aritmetica modulo 96, quindi la tessera è senza cuciture per costruzione. Stessa scelta già approvata per le pose del piccione (tools/su674_pose_piccione.py). - ✅ Cuciture misurate, non guardate: sul marciapiede il salto al bordo è 0,89 (verticale) e 1,69 (orizzontale) contro una variazione interna media di 17,55 e 15,58 — un ordine di grandezza sotto. Sul ghiaccio è 5,28 e 7,41 contro massimi interni di 11,21 e 9,58: sopra la media ma dentro la variazione naturale della tessera, cioè nessuna discontinuità.
- ✅ Riproducibile: le due tessere si rigenerano identiche al byte dallo script (sha256 verificato). La sorgente è lo script, non un PNG — per le tessere di quartiere
sprites_raw/ non aveva raw. - ⚠️ Trappola disinnescata: la funzione del marciapiede *rinforza* la neve che trova, quindi puntandola all'asset di gioco ogni rilancio l'avrebbe accumulata sopra a sé stessa fino a sbiancare tutto. Lo script legge da
sprites_raw/floor_brina_prima_di_su816.png, che è l'originale messo al sicuro. - ✅ Verificato in gioco, scatti in
TMP/su816_ghiaccio/scatti/: di giorno il parco si legge come ghiaccio e i fiocchi di SU-810 si staccano dal fondo invece di sparirci dentro — che era il difetto segnalato ieri. La cornice park_border_brina.png non è stata toccata e ora funziona da argine di neve attorno al lago. - ⚠️ NON PROVATO: la resa sull'Honor, e quale variante Ivan preferisca — montate ghiaccio v1 (la più calma delle tre) e neve v2 (la più decisa delle due). Le altre sono in
TMP/su816_ghiaccio/ e si scambiano con una riga.
La carta del cane smette di essere l'unica disegnata a un quarto2026-09-06
SU-670, KO di Ivan del 05/09 («l'osso ok ma la carta cane ha una grafica non consona con le altre»). Non era una questione di stile: erano due file che non esistevano.
- 📌 Diagnosi:
files/homeless_city/scripts/ui/LevelUpScreen.gd:1214 cerca files/homeless_city/assets/ui/perk_hi/<id>.png e, per la faccia dorata, files/homeless_city/assets/ui/super_hi/<id>.png. Per fischietto_cani non c'era nessuno dei due, quindi is_hi restava false e la carta ripiegava sulla cella 16×16 dell'atlante disegnata a 64×64 con NEAREST, invece dei 128×128 a win.size * ICON_WIN_FILL di tutte le altre 27. Era letteralmente l'unica carta grande un quarto delle sue sorelle. - ✅ Cinque varianti di carta e due di super generate con Codex, giudicate da Ivan il 06/09: carta v4 (il cane seduto con l'osso in bocca) e super v1 (i tre cani coi raggi dorati). Montate in
files/homeless_city/assets/ui/perk_hi/fischietto_cani.png e files/homeless_city/assets/ui/super_hi/fischietto_cani.png, raw in sprites_raw/UI/. - ✅ Verificato in gioco, non solo nel codice:
shot_su250_carta.gd stampa ora icona carta: … -> TROVATA e icona super: … -> TROVATA per tutte e due, e negli scatti TMP/su670_montata/ le due carte riempiono la cornice come le altre. - 📌 Non toccati, perché già approvati da Ivan il 31/08: l'osso 16×16 nella cella 16 di
perk_icons.png e la testa di cane nella cella 10 di super_icons.png. La coppia grande racconta la stessa storia: l'osso è la carta, il branco è il super. - ⚠️ Una deviazione dichiarata: gli altri raw in
sprites_raw/UI/ sono 256×256 e vengono ridotti a 128; questi due sono nativi 128×128, perché le varianti sono state generate direttamente alla misura finale ed è quella che Ivan ha approvato guardandola. Il raw è identico all'asset: non si perde nessuna sorgente. - ⚠️ NON PROVATO: la resa della cerimonia animata del superpotere (
SuperCeremony.gd:444 legge lo stesso file) — lo scatto è della schermata di level-up, non della cerimonia.
POSIZIONA PULSANTI: i comandi touch si trascinano dove si vuole2026-09-06
SU-797, lotto L6_pad di Codex sol (13 minuti), finito ieri sera a finestra esaurita e chiuso adesso. Il builder aveva dichiarato 0 risoluzioni misurate e 0 PNG: le prove sono tutte di stasera.
- ✅ Voce nuova POSIZIONA PULSANTI in Opzioni (
OptionsPanel.gd:206, serve: "touch", in_game: true): dal menu e dalla pausa. Apre una schermata sullo stesso componente — un CanvasLayer a 129, sopra i VirtualControls che stanno a 128 — con CONFERMA, RIPRISTINA, INDIETRO. Dalla pausa la citta' resta ferma sotto e l'HUD si nasconde (HUD.gd:1636), dal menu si vede solo lo sfondo. - ✅ Il diamante e' diventato un nodo solo (
VirtualControls.gd:1199, ButtonsRoot): i cinque tasti conservano i loro offset da BTN_CFG, quindi disegno e aree di tocco si spostano insieme e la posizione globale resta identica al pixel quando non si tocca niente. - ✅ Le posizioni si salvano in
Settings come frazione della viewport, con sentinella Vector2(-1, -1) = chiave assente (Settings.gd:295): save_settings omette la chiave invece di scrivere -1, quindi RIPRISTINA la cancella davvero dal settings.cfg. Una posizione salvata vince sullo scambio dei mancini; senza chiave il calcolo storico resta bit per bit. - ✅ Numeri misurati stasera (
bash tools/autotest/probe_su417_touch.sh), il criterio «il tocco segue il disegno»: a 844×390 raggio 66,000 → 66,000 con gli offset identici, tocco nella nuova posizione centrato e dentro, persistente; a 1024×768 raggio 44,245 → 44,245, stessi esiti. RIPRISTINA riporta ancore e _joy_anchor al pixel e toglie le chiavi. Joystick fluttuante: _joy_anchor invariato e la posizione salvata resta in Settings senza effetto. Voce assente da desktop. - ✅ Persistenza attraverso un riavvio vero di processo (HOME isolata): la run 1 salva
touch_buttons_position = (0,88616127; 0,56061697), la run 2 — processo nuovo, nessun trascinamento — rilegge la stessa frazione e ricalcola l'ancora. - ✅ Scatti in
TMP/su_pad_posizione/: shot_prima_844x390.png, shot_dopo_844x390.png, shot_prima_1024x768.png, shot_dopo_1024x768.png. Nel 1024×768 lo spostamento in alto a destra e' netto; nell'844×390 e' matematicamente identico ma visivamente piu' contenuto, perche' il gruppo e' alto circa il 65% del viewport e ancorarlo alla safe-area non porta il centro fino in cima. - 📌 Bug trovato e corretto nel provino (non nel gioco): i 4 PNG uscivano tutti allo stesso viewport 1365×768.
Settings._su191_revert_to_windowed_at_boot() riconosce --windowed solo da OS.get_cmdline_user_args(), cioe' dopo il --, mai dal flag nativo del motore; il fullscreen veniva poi riapplicato in modo asincrono durante il caricamento di Main.tscn, disfacendo il resize iniziale. Ora shot_su797_pad_posizione.gd riafferma la finestra a ogni frame, come gia' fa probe_su417_touch.gd. - ⚠️ NON PROVATO: il tocco e il trascinamento su un touchscreen VERO. Tutto passa da
debug_force_touch su desktop, come ogni sonda del progetto — la prova su iPad e sull'Honor la fa Ivan. Non provata nemmeno la sovrapposizione col notch fisico. - ⚠️ Regressione controllata e scartata:
probe_su418_opzioni.sh fallisce, ma per una causa estranea — si aspetta la voce ACCEDI (utente sloggato) e questo Mac ha NativeAuth gia' loggato, quindi mostra ACCOUNT. Il diff di ui_menu.csv e' puramente additivo (+8 righe, nessuna tocca le chiavi di account). - ⚠️ Trappola d'ambiente, vale per chiunque: su macOS
XDG_DATA_HOME non isola Godot. user:// segue $HOME, e un lancio a mano senza tools/autotest/common.sh (che esporta HOME) scrive nel settings.cfg vero. E' successo durante il collaudo: chiavi rimosse, lingua="it" verificata intatta.
Brina diventa Brina: sui parchi si pattina, e nevica sempre2026-09-06
SU-809 + SU-810, lotto L_amb di Codex sol (17 minuti). Due ticket tenuti insieme perche' toccano lo stesso dizionario di quartiere e sono gli unici due del blocco Brina/Collina che non dipendono da uno sprite nuovo.
- ✅ SU-809 —
Quartieri.gd:505 accende parchi_ghiaccio su Brina; Player.gd:11 porta ICE_ACCEL = 150 e ICE_DECEL = 100 px/s², e Player._handle_movement (~1834) non assegna piu' la velocita' di colpo: costruisce un bersaglio (drift da ubriaco e moltiplicatori dentro) e ci arriva con move_toward. Accelerazione e frenata sono diverse di proposito: mollare lo stick e' il momento buffo. Il contraccolpo di SU-305 resta sommato dopo, come prima. - ✅ Il ghiaccio si ferma al bordo esatto della park_rect:
park_index_at porta con se' 48 px di margine per lo spazzino (SU-646), e usarlo cosi' com'e' avrebbe congelato anche il marciapiede. Player._is_on_ice_park (~1863) riconferma il rettangolo vero. - ✅ Numeri misurati qui col 4.6.2 (
bash tools/autotest/probe_su809_ghiaccio.sh): fermo→piena 0,600 s, piena→fermo 0,900 s, primo frame di input al 2,78% della piena (limite 40%). Px percorsi dopo il rilascio: parco di Brina 39,750; marciapiede di Brina 0,000; parco di Gattopoli 0,000. Inversione a 180°: continua verso destra 0,600 s prima di girare (limite 0,3). - ✅ SU-810 — quarto aspetto
snow dello stesso CPUParticles2D di SU-332/SU-621 (World.gd:8426): fiocchi 2×2, 120 particelle, 100–160 px/s, alfa 0,8, nessuna inclinazione, gravity a zero e lifetime/preprocess a 7,5 s perche' a quella velocita' un tablet ci mette parecchio a riempirsi. neve_sempre su Brina: nevica in idle e nel preavviso; in active la grandine prende lo stesso emettitore e alla fine la neve torna. Zero RPC, is_wet mai toccato. - 📌 Una correzione mia al cancello: la neve riscrive
direction a ogni frame (e' l'ondeggio laterale dei fiocchi) e nessun altro aspetto lo rimetteva. Tornando da snow a hail, la grandine di Brina avrebbe ereditato l'ultimo valore oscillato invece degli 8° storici. Aggiunto il ripristino in _apply_precipitation_look (World.gd:8398). Gli altri tre valori che il builder ha aggiunto ai rami pioggia/grandine/tempesta — lifetime 1.1, preprocess 0.8, gravity (0, 1400) — li ho verificati uno per uno contro _setup_rain_particles: sono identici all'originale, servono a ripristinarli dopo la neve e non sono una regressione. - ✅ Numeri misurati qui (
bash tools/autotest/probe_su810_neve.sh --bot --bot-quartiere brina): a Brina in idle emitting=true, aspetto snow, amount=120; preavviso ancora snow, calamita' hail, ritorno alla neve in 0 frame; al Centro in idle l'emettitore resta spento; is_wet false dopo 60 s di neve; frame time 7,195 ms con neve contro 7,141 spenta = +0,762% (limite +5%). - ⚠️ Da guardare, e' una decisione di Ivan: negli scatti
TMP/su_L_amb/su810_brina_neve_giorno.png e ..._notte.png la neve si vede bene di notte e sull'asfalto, ma di giorno sul lago ghiacciato del parco e' bianca su bianco e si legge appena. Se va scurita o contornata lo dice lui. - ⚠️ NON PROVATO: la resa sull'Honor (il +0,762% e' misurato sul Mac) e il TestBot a Brina l'ha girato il builder in sandbox, non io.
- 📌 Trappola ripagata:
shot_su810_neve.gd non partiva — sotto -s gli autoload non si nominano per simbolo e GameState. rendeva non compilabile tutta la classe. Preso con root.get_node_or_null("/root/GameState").
Infilarsi alla pensilina durante il preavviso fa partire subito la calamita'2026-09-05
SU-799, lotto L5 di Codex sol (6 minuti). Trentacinque righe in tutto: la regola e' piccola, il rischio stava nel multiplayer.
- ✅
WeatherSystem.on_shelter_entered(): solo in fase warning, i secondi rimanenti si annullano e _begin_wave() parte subito. In idle e in active entrare non fa niente. Il tutorial e' escluso (tutorial_wave_mode) e un client non decide mai: aspetta lo stato dell'host, come tutte le fasi del meteo. - ✅ MP senza RPC nuove, che era il vincolo piu' delicato del ticket: l'host emette
weather_warning con lead 0,0 e il salto viaggia sul relay _cl_wave_warning che esisteva gia'. Una RPC in mezzo avrebbe fatto scalare gli id e rotto le partite fra versioni diverse. ShelterZone riconosce anche i mp_puppet, ma aggiorna GameState solo per il giocatore locale. - ✅
_begin_wave() reso idempotente: il relay storico che arriva subito dopo non riavvia ne' il timer ne' il banner. Senza quella guardia il countdown sarebbe ripartito. - ✅ Numeri della sonda: warning con 15,000 s residui →
active entro 1 frame, timer 5,000 s (= WAVE_DURATION); stat al riparo 80/80/80 invariate dopo un secondo di drain; ingresso in active non cambia niente (5,000 → 5,000 s); ingresso in idle nemmeno (prossimo evento 118,807 → 118,807 s); puppet remoto → fase active senza contaminare il riparo locale; tutorial ancora in warning a 15,000 s. Compile-check 4.6.2 qui: 4/4 OK. - ⚠️ NON PROVATO: la prova vera a due istanze (host + client) e i log in
TMP/su_calamita_pensilina/ — la sandbox non apre socket. E' la cosa da guardare: il disagio agli avversari e' il punto del ticket.
Il giro dei nomi: i nickname escono verso il foglio, i giudizi rientrano in Firestore2026-09-05
SU-509 + SU-511, lotto L4 di Codex sol (11 minuti). Due meta' dello stesso giro. ⚠️ Il codice c'e' e non e' provato contro i servizi veri: la sandbox di Codex non ha rete, e il primo dry-run qui si e' fermato su un 403 di Sheets.
- ✅ SU-509 —
tools/firestore/nomi_da_giudicare.py: legge da registro/{env}/nicknames i nomi con riserva vuota creati dopo il timbro in registro/{env}/meta/giudizi e li accoda alla scheda giudizio (colonne puid, nome, provider, creato, giudizio vuota). I nomi di riserva non entrano. Il timbro avanza solo dopo l'append riuscita, e in piu' c'e' una deduplica sulle righe gia' presenti: se cade fra append e timbro, il rilancio non duplica. Prove locali: 3 righe al primo giro, 0 doppioni dopo un crash simulato, 0 righe al secondo giro, 1 nome di riserva escluso. - ✅ SU-511 —
tools/firestore/applica_giudizi.py: per ogni riga giudicata, ok/dubbio scrivono solo stato (con precondizione anti-concorrenza), bocciato cancella il nickname, svuota il player solo se usa ancora quel nome e scrive bocciati/{norm} in una commit atomica (3 write). Rilanciato: 0 write. --prova legge e descrive senza scrivere niente, e il foglio non lo tocca mai. - 📌 Due correzioni mie al cancello, tutte e due su cose che avrebbero rotto la produzione:
- Il builder aveva reso
norm obbligatorio nelle regole Firestore, cancellando il commento che diceva di non farlo: le build 0.43 gia' in mano ai tester non mandano norm e da quel momento non avrebbero piu' potuto registrare un nickname. Regole ripristinate: norm resta facoltativo e il gate morde solo per chi lo manda, che e' il buco dichiarato ieri e accettato. - Aveva riscritto
NicknameRegistry.normalize_for_ban() piu' debole per «allinearla al Python»: via @→a e $→s, e soprattutto via il filtro che teneva solo [a-z0-9]. Le regole vogliono norm conforme a ^[a-z0-9]{1,16}$: un nome accentato avrebbe prodotto un norm rifiutato dalle regole, cioe' registrazione impossibile. Ripristinato il GDScript, che era il piu' robusto, e allineato il Python a lui — quattro passi identici, ripiego compreso. Verificato: Negroni88 e n3gr0ni88 → negroni88; NEGR@NI88 → negrani88; Papà → pap; ЖЖЖ → жжж (ripiego, non id vuoto).
- ⚠️ SERVE UN'AZIONE DI IVAN — *fatta il 2026-09-09, vedi la voce (122)*: il foglio
StreetU_registro_nickname non e' condiviso col service account. Va dato accesso da Editor a firebase-adminsdk-fbsvc@axiomatic-path-505007-u7.iam.gserviceaccount.com; senza, entrambi gli script si fermano sul 403 e nessuno dei due criteri e' verificabile. - ⚠️ NON PROVATO: tutto quello che tocca la rete — append vero sul foglio, giudizi veri su Firestore, idempotenza remota. Le prove pure (normalizzazione, timbro, dedup) sono passate.
La sirena della polizia smette di urlare, e il barbone rivale comincia a farsi sentire2026-09-05
SU-798 + SU-800, lotto L3 di Codex sol (12 minuti). Due ticket tenuti insieme perche' sono lo stesso problema: cosa suona addosso al giocatore, e quando.
- ✅ SU-798: la sirena d'inseguimento parte a −3 dB per 4 s (un ciclo), poi fade di 1 s e resta a −14 dB di sottofondo finche' la caccia dura. Misurato lungo 30 s: t=0/1/3/4 s → −3,00 dB; t=5/10/20/30 s → −14,00 dB. Un secondo poliziotto che si aggiunge non la fa ripartire:
play() contati, 1 poliziotto = 1, 2 poliziotti = 1. Le due soglie sono costanti nominate in cima a World.gd:55. - ✅ Inventario dei suoni della polizia (era un criterio del ticket):
police warn.wav (HUD), police hard.wav (Police/SfxRefuse), call police.wav (NPC/SfxRefuse), police_chase_alert.wav (World, l'unico in loop), piu' raid_siren.wav della retata — esclusa e non toccata. Regola nuova: se uno dei tre one-shot sta suonando, il loop scende subito a −14 dB. - ✅ SU-800: il rivale (
arch_homeless_rival) all'aggancio fa partire una volta sola un suono posizionale e un big_notify rosso (1,00; 0,30; 0,20) per 1,6 s, come la polizia. Misurato: primo aggancio banner=1 suono=1; nei 10 s di inseguimento continuo 0 banner e 0 suoni in piu'; latch ancora attivo a 1,98 s e rilasciato a 2,02 s, al rientro totali 2 e 2. HUD._wanted_boost invariato (delta 0,00): il wanted della polizia non si muove. - ✅ Il suono l'ho generato io con ElevenLabs prima del lotto:
Sounds_raw/rival_aggro.wav, 1,80 s, picco −5,7 dB — identico a throw.wav, quindi il criterio «±3 dB» chiude a 0,0 dB di scarto. Un grugnito cartoonesco con tonfo secco, niente sirena: doveva essere riconoscibile e diverso dalla polizia. - ✅ MP: relay host → solo il peer bersaglio, con
_cl_rival_aggro_warn accodata in fondo alle RPC per non far scalare gli id (una RPC in mezzo rompe le partite fra versioni diverse). NPC_NOTIFICA_RIVALE_INSEGUE in ui_npc.csv, 8 lingue, nessuna emoji (audit: 0). - ⚠️ NON PROVATO: la prova MP a due istanze e i log in
TMP/su_rivale_avviso/ (la sandbox non apre socket); la resa acustica vera del ducking e l'ascolto del suono nuovo — quelli li giudica Ivan su device. Compile-check 4.6.2 qui: 4/4 OK.
Le ombre degli arredi: il pomeriggio va a NORD-EST, e i camioncini prendono il criterio delle panchine2026-09-05
SU-736 (2° KO) + SU-796, lotto L1 di Codex sol (8 minuti), stesso file. La regola del verso adesso e' scritta una volta sola e la leggono panchine e camioncini: era la richiesta esplicita del KO.
- ✅ SU-736:
_vettore_ombra_arredo() estratta in WorldGenerator.gd:7684. Il fattore della sera moltiplica l'intero vettore (* (-1.0 if indietro else 1.0)), non il solo asse x: e' il rimedio di una riga che Ivan aveva gia' indicato nel KO. Vettori misurati sulla texture generata, panchina: h08 (3,5; 4,6) sud-est · h12 (0,0) solo impronta · h15 (1,7; −4,7) · h17 (2,4; −6,2) nord-est. Girata anche l'asserzione della sonda a h17 (d17.x > 0 and d17.y < 0), che era proprio quella che aveva lasciato passare il KO. - ✅ SU-796: hot dog e gelataio lasciano
_ombra_camioncino e prendono il meccanismo delle panchine (WorldGenerator.gd:9367 impronta obliqua, :9465 un solo nodo-ombra per mezzo, materiale proprio, fuori dal bucket dei personaggi). Impronta ruotata a 45°: riempimento della bounding box 31,8% (il criterio chiedeva < 60%); stacco ruote/ombra 1,0 px; cache 3 rigenerazioni su 3 cambi di vettore, 0 su 4 aggiornamenti a vettore fermo. Vettori van: h08 (6,5; 9,5) · h15 (5,3; −6,2) · h17 (6,5; −9,5). La fontana resta l'unica chiamante della vecchia strada, come chiedeva il ticket. - ✅ Sonda su 8 semi (736001-736008): 0 KO, una e una sola ombra per istanza, fontane tutte presenti, 0 arredi nel bucket dei personaggi. Compile-check rifatto qui col 4.6.2 (la sandbox aveva il 4.7.2): 2/2 OK.
- ⚠️ DA GUARDARE, ed e' la cosa che conta:
TMP/su736_giro17/panchina_tre_ore.png (h08 | h15 | h17, stesso seme). Alle 8 l'ombra si vede bene a sud-est; alle 15 e alle 17 non si vede quasi niente, perche' a nord-est finisce *dietro* la panchina, che dall'alto la copre. Misurato sui pixel: l'erba in ombra a nord resta 271 → 240 → 261 (invariata), mentre a sud alle 8 ce ne sono ~250 in piu'. Il verso e' quello chiesto e la sonda lo conferma sulla texture; e' la geometria top-down a nasconderla. Se Ivan la vuole visibile serve una decisione sua (ombra piu' lunga la sera, o un verso diverso): non e' una cosa che si aggiusta senza cambiare il requisito. - ⚠️ NON PROVATO: gli scatti dei camioncini alle 4 ore (manca una harness che forzi l'ora inquadrando un van: e' in lavorazione, lotto L7); il diff PNG a stesso seme fuori dal lotto.
I bottoni touch tornano grossi come li disegnava Godot2026-09-05
SU-739, rientrato KO («farli leggermente piu' grossi a video, devono occupare il raggio dei bottoni creati da godot»). Lotto L2 di Codex sol (5 minuti), corretto al cancello: il numero che aveva scelto non risolveva il KO.
- ✅
BTN_SPRITE_VISUAL_FACTOR = 60/54 in VirtualControls.gd: i PNG dei bottoni sono 60x60 ma la forma dipinta dentro ne occupa 54, quindi a scala 1 il bottone si vedeva con raggio 27 invece di 30. Il fattore riporta il lato dipinto a 60 px = 2 x BASE_BTN_R. Tocca solo lo sprite (riga ~1146): centro, ancore, hit area e logica restano identici, e le costanti dei raggi non si toccano (BASE_BTN_R 30, BTN_HIT_FACTOR 1,35, BASE_BTN_SPACING 56, BASE_JOY_*). - 📌 Il fattore di Codex era 55/54 = +1,85%, cioe' invisibile: aveva letto «il raggio» come la zona sensibile (
BTN_R * BTN_HIT_FACTOR = 40,5 px), trovato che il lato sarebbe salito a 81 px contro i 56 di spaziatura, ed era ripiegato sul massimo che lasciava 1 px di luce. Ma il raggio di cui parla il KO e' BASE_BTN_R = 30, la misura con cui i bottoni erano disegnati prima che SU-739 montasse gli sprite; la zona sensibile e' sempre stata piu' larga del disegno, anche con l'ottagono a codice. L'errore era gia' nel brief, non nel builder. - ✅ Provino vero, prima/dopo a 844x390:
TMP/su739_ko/confronto_bottoni.png. I bottoni sono tondi, non spigolosi: X, Y, B e A restano con luce fra loro (distano 79 px in diagonale, 112 sugli assi). L'unica coppia a 56 px e' Y con S (il superpotere, che compare solo con un super battezzato): li' i due dischi da raggio 30 si sovrappongono di 4 px — ⚠️ e' la geometria di sempre, quella dei bottoni disegnati a codice, non una regressione del fix. Da guardare su device. - ✅ Joystick misurato e non toccato:
joy_base 116x116 con bbox alfa 104x104 = raggio dipinto 52 contro BASE_JOY_BASE_R 58, cioe' gia' all'89,7%; il divario non vale un intervento. - 📌 Tolto
shot_su739_button_size.gd scritto dal builder: faceva in un solo processo il ciclo che il commento in cima a shot_su739_pad.gd vieta esplicitamente. Gli scatti li fa quello, un processo per scatto. - ⚠️ NON PROVATO:
probe_su417_touch.sh (vuole una finestra, in sandbox era morta) e la prova A-B-A degli fps sull'Honor. Compile-check 4.6.2 locale: 1/1 OK.
Il terzo verdetto sui nickname: ok, dubbio, bloccato. Il dubbio si prenota ma non si pubblica, e la lista dei bocciati va su Firestore2026-09-05
SU-508, SU-510, SU-512 — un lotto solo, costruito da Codex sol (lotto L6, 18 minuti) col contratto fra le tre funzioni fissato nel brief. Al cancello di chiusura: compile-check 4.6.2 locale 5/5 dopo una correzione (una condizione spezzata con + invece del backslash non parsava, e la sandbox l'aveva dichiarata verde); sonda SU-512 e non-regressioni SU-444/SU-453 rifatte qui, perche' la sandbox di Codex nega i socket locali e Firestore.
- ✅ SU-508:
OnlineLeaderboard.name_verdict(nome) → {esito: ok|dubbio|bloccato, motivo} accanto a is_blocked(), che resta identica (firma, cache, risposte: verificato su 9 nomi storici). Quattro produttori del dubbio nell'ordine di SU-471 §2.2, col motivo in chiaro per il foglio di Ivan: coperto da eccezione (Negroni88), lunga fuori inizio parola (Commercialista), lettere ripetute contro i soli gravi (Negrro77), lista grigia (Putin_Fan); le corte restano fuori (Falcone, Cassandra, Golia ok, e Anna collassata in ana non e' grave). Lista grigia: 18 voci editoriali in tools/genera_filtro_nomi.py (LISTA_GRIGIA, Ivan la cambia a piacere) → metadata/grigia nel .tres, rigenerato dalle otto liste ora in TMP/su453/ldnoobw/: identico al committato tranne quella riga. - ✅ SU-510:
RESULT_PENDING («in revisione») e RESULT_REJECTED («bocciato») accanto ai RESULT_* invariati. claim() — firma invariata — calcola il verdetto, manda stato/norm/motivo al registro e su dubbio PRENOTA il nome senza toccare players ne' _names, lo salva in Settings.account_name_pending e, se il giocatore e' senza nome pubblico, gli presta una riserva con motivo dubbio (radice dal vocabolario, mai dal nome dubbio: eviterebbe la ricorsione). normalize_for_ban() (leet 0→o 1→i 3→e 4→a 5→s 7→t, solo [a-z0-9]): Negroni88 e n3gr0ni88 → negroni88. check_pending() raccoglie il giudizio da Firestore al login e all'apertura della schermata del nome: stato == ok promuove (scrive players), documento sparito = bocciato. MainMenu: le schermate A-E di SU-471 §3 (riga di stato arancione «E' IN VALUTAZIONE», esiti promosso/bocciato, corpo della riserva per dubbio), 7 chiavi in ui_menu.csv × 8 lingue. - ✅ Regole Firestore in
tools/firestore/firestore.rules, retrocompatibili: stato/norm/motivo FACOLTATIVI (la 0.43 in giro continua a registrare), dubbio fra le riserve, create rifiutata se esiste bocciati/{norm}, collezione bocciati leggibile da tutti e scrivibile da client solo in dev con id zzprobe… (le righe vere le scrivera' SU-511 col service account), update di nicknames solo li' per simulare il «si'» di Ivan. PUBBLICATE da Ivan alle 18:58 (ruleset b2149481): il classificatore di auto mode aveva negato a me la PATCH della release, lo script pronto e validato l'ha lanciato lui dal terminale. Buco dichiarato nel file: un client 0.43 non manda norm, per lui la lista dei bocciati non morde finche' non aggiorna. - ✅ SU-512:
files/homeless_city/scripts/tools/probe_su512_verdetti.gd + tools/autotest/probe_su512.sh, due giri. FINTO (nick_test_server.py esteso in modo additivo con --bocciati): F1 22/22 verdetti, F2 3/3 normalizzazione, F3 riserva assegnata + account_name_claimed vero + gate GATE_OK col dubbio fuori dalla cache, F4 bocciato a due PUID diversi, F5 endpoint muto = niente salvato. Al primo giro 38 OK e 1 KO della SONDA (confrontava il gate con «ok», ma GATE_OK e' stringa vuota): corretto; dopo la review F1-F6 0 KO (44 controlli) e V0-V4 19/19 nello stesso lancio, TUTTI I CASI OK. VERO (registro/dev, nomi ZZprobe, 19/19 OK con le regole nuove): bocciato rifiutato a due PUID diversi e in grafia leet (stessa riga bocciati/zzprobe..ban), la commit diretta con norm bocciata la respingono le regole (403), il dubbio finisce in nicknames con stato=dubbio/motivo/norm mentre players e resolve() conservano il nome pubblico, gate GATE_OK; il «si'» simulato promuove (players aggiornato), il documento cancellato da' bocciato; pulizia 0 residui. Coi ruleset vecchi gli stessi casi erano 17 KO, tutti 403: la sonda sa fallire. - 📌 Review Codex R6, 7 punti, 5 applicati: (1) CRITICO vero — al rientro con Epic
set_account_login() rimette l'handle in account_display_name con claimed ancora vero: un handle cambiato e diventato dubbio sarebbe rimasto il nome in lobby; ora «pubblico» non conta se e' proprio il nome appena giudicato dubbio, e arriva la riserva (caso F6 aggiunto alla sonda). (2) Ricorsione: un nome di riserva a sua volta dubbio avrebbe riaperto la macchina su se stessa — guardia su _riserva_motivo e candidati filtrati con name_verdict (_name_not_ok), non piu' col solo is_blocked; un candidato bocciato dal server passa al tentativo dopo come un «preso». (3) motivo nel payload solo col dubbio. (4) F5 controlla anche il registro dei bocciati. (5) Il gate della sonda. Scartato: mostrare «IN VALUTAZIONE» prima di «DA RICOLLEGARE» — senza sessione non c'e' niente da valutare. Il CRITICO sull'esito della riserva ignorato resta il fail-closed di sempre (muto/occupato → RIPROVA), e claim_provider_handle non finge piu' un ok se la riserva non e' arrivata. - ⚠️ NON PROVATO: le schermate A-E su un dispositivo; un login Epic con handle dubbio; SU-509 e SU-511 (foglio e script del giudizio) sono ticket a parte e senza SU-511 nessun nome diventa mai
bocciato sul server.
Telemetria: il livello vero all'uscita, il tetto a 2 MB con pos e reati diradati, e il tunnel coi suoi eventi2026-09-05
SU-773, SU-770, SU-769 — un solo lotto perche' vivono tutti in RunRecorder.gd. Costruito da Codex sol (lotto L1, 8 minuti), passato dal cancello di chiusura qui: compile-check rifatto col 4.6.2 locale (la sandbox aveva il 4.7.2), 7/7 OK.
- ✅ SU-773:
_last_level segue il livello a ogni card_offered e run_end prende il massimo fra quello e xp.level: all'uscita dalla scena XPSystem e' gia' azzerato e prima usciva level=1. Replay headless: uscito_dalla_scena con level=40. - ✅ SU-770, entrambe le strade (deciso da Ivan il 05/09):
MAX_FILE_BYTES 512 KB → 2 MiB (il tetto della POST resta 1 MiB e non e' un limite: 50 minuti simulati = 466.810 byte grezzi, 48.876 in gzip+base64, il 4,7% del tetto); pos ogni 5 s dopo il decimo minuto (POS_LATE_MS); i reati non-busking accorpati per azione dentro la finestra di stat come il busk di SU-589, con campo n e ultima posizione. ⚠️ Gli script del referto contano le righe misdemeanor: dalla 0.44 vanno pesate per n. - ✅ SU-769:
bonus_start {giro, ticket} e bonus_end {giro, vinto, dur, inciampi, monete, causa} agganciati ai segnali esistenti; la firma dei segnali non cambia (ascoltatori con firma fissa), RunRecorder legge un getter BonusLevel.telemetry_data() (gruppo bonus_level, verificato) nello stesso giro del segnale, prima che il nodo muoia. Cause: treno, tempo_scaduto, zombie, game_over. - 📌 Corretto io al cancello: Codex calcolava il biglietto come 100·2^(giro−1); ora legge
GameState.bank_gold_threshold, che e' il prezzo vero (SU-712 lo raddoppia alla vittoria). - ⚠️ NON PROVATO: una run vera di 50 minuti (solo replay sintetico ai ritmi reali) e l'arrivo su Drive. Sonda:
files/homeless_city/scripts/tools/probe_su770_telemetria.gd.
Gattara e tossico con 20 s di tregua; il tetto «uno per parco» esteso a Solleone e Fumarola NON dimezza lo spazzino2026-09-05
SU-765 e SU-766, lotto L2 di Codex sol (18 minuti), compile-check rifatto col 4.6.2: 4/4 OK. Sonda files/homeless_city/scripts/tools/probe_su765_766_npc.gd, tassi calcolati sui minuti davvero vissuti dal bot.
- ✅ SU-765:
CATLADY_ENGAGE_GRACE_SEC 9 → 20; il tossico riceve la stessa tregua di 20 s solo a Brina tramite il flag di quartiere tossico_grazia_ingresso (il dispatch salta tutto _update_junkie: ne' furto ne' bottiglia). 8 semi: zero vite da gattara o tossico nei primi 20 s a Gattopoli e Brina; dopo, cooldown invariati (5,0 / 20,0 e 5,0). - ⚠️ SU-766, NON RAGGIUNTO: il flag
spazzino_uno_a_parco e' esteso a Solleone e Fumarola come deciso da Ivan, ma non morde. A Fumarola gli spazzini sono il nemico di casa e nascono FUORI dai parchi (8 su 8 in 8 citta'): il tetto per parco non li tocca. A Solleone il tetto vale (max 1 per parco) ma le vite da spazzino al minuto nella sonda salgono (0,185 → 0,433 su 8 semi). E la sonda non riproduce la base della telemetria (0,5-0,6 al minuto): il bot non sporca come i giocatori. Serve una decisione di Ivan: l'altra strada era CLEANER_THROW_COOLDOWN 5,5 → 11 s nei due quartieri. - Centro invariato (spazzino 0 prima e dopo), Gattopoli −6,5%.
Release Play: la spunta verde solo per la versione in uscita, e l'AAB in bozza al Tempo 12026-09-05
SU-775 e SU-774, lotto L3 di Codex sol (8 minuti) su tools/release/ e sui due documenti della release.
- ✅ SU-775:
stato_canali.py mappa versione → versionCode (major·1000 + minor, 0.43 → 43) e ANDROID e' ✅ solo se quel versionCode e' completed; altrimenti ⏳ con «presente 42, atteso 43». APPLE filtrava gia' per versione: verificato, non assunto. Provato con le credenziali vere dopo la 0.43: ANDROID 43 completed ✅, APPLE APPROVED ✅; con la fixture del 04/09 (42 sul canale) esce ⏳. - ✅ SU-774:
play_upload.sh --bozza carica e committa la release draft (i tester restano sulla completed precedente), --pubblica porta a completed la bozza col versionCode atteso senza ricaricare l'AAB, --prova --fixture simula le chiamate. WORKFLOW/04_RELEASE.md e la skill pubblica-store: export e bozza nel Tempo 1, al Tempo 2 solo la pubblicazione. - 📌 Scelta mia al cancello: Codex aveva fatto uscire
--invia con errore; l'ho tenuto come via di riserva (upload + completed in un colpo) finche' bozza e pubblicazione non sono provate su un canale vero — e' la strada che ha fatto la 0.43. Il suggerimento in export_all.sh punta ai due comandi nuovi. - ⚠️ NON PROVATO, per decisione di Ivan: le chiamate vere all'API. Alla prossima release:
--bozza al Tempo 1, --pubblica al Tempo 2 (riga di avviso in 04_RELEASE.md).
I simboli nativi diventano l'ultimo passo di `/rilascio-beta`, e per la 0.43 mancano ancora2026-09-05
Domanda di Ivan: *«dobbiamo caricare dei simboli nella build 0.43 android che abbiamo mandato ieri?»*. Sì, e non erano stati caricati (✅ caricati da Ivan alle 14 circa dello stesso giorno, dallo zip combinato): la voce (55) della release li lasciava «da fare a mano in Play Console» e nessuno li ha fatti. È la seconda release di fila (0.42 il 01/09) in cui lo chiede lui.
- ✅ Il passo entra in
.claude/commands/rilascio-beta.md come § 6, l'ULTIMO di tutti, dopo il Tempo 3 e dopo i messaggi ai tester (regola di Ivan, 05/09): Claude controlla e prepara, Ivan trascina. Controlli scritti: versione del motore da Godot --version uguale a quella nel nome dello zip, md5 della libeosg nello zip uguale a quella in tools/android/eosg_16kb/simboli/; poi open della cartella e di Play Console. Il riepilogo finale (§ 7) dice «simboli caricati» solo con la conferma di Ivan. Riga gemella nella checklist di WORKFLOW/04_RELEASE.md dopo /avvisa-tester. - ✅ Per la 0.43 il file giusto è lo zip combinato di SU-709 (
TMP/su709_simboli/Godot_native_debug_symbols.4.7.2.combinato.zip, 766 MB): motore 4.7.2.stable.official.ed1daf0bf (verificato col comando) e la libeosg non stripped identica byte per byte (md5) a quella dei simboli del 04/09, mentre l'AAB porta la stripped da 1.879.648 byte della stessa coppia. Lo zip è del 03/09 e i simboli del 04/09, ma coincidono: per la 0.43 non si rifà. - 📌 Il § 4 di
/rilascio-beta ora sa di SU-774 (bozza al Tempo 1, --pubblica al Tempo 2, --invia di riserva finché non è provato): era rimasto fermo al giro vecchio dopo il lotto L3. - Idea in sospeso, con l'ok di Ivan: provare l'endpoint
deobfuscationFiles/nativeCode dell'API di Play con il service account, così il passo lo fa lo script e non la mano.
Il registro dei nickname dei tester e' migrato su Firestore: 13 su 13, e la chiave e' nei file di credenziali2026-09-05
Chiusura di SU-697 nello stesso pomeriggio.
- ✅ Chiave configurata da Ivan nei quattro
eos_credentials*.cfg (sezione [firestore], valori dal plist di Firebase): da qui in poi ogni build esportata parla con Firestore; le 0.43 installate restano su Apps Script perche' il loro cfg non ha la sezione. - ✅ Export del foglio
StreetU_registro_nickname (creato dall'Apps Script nel Drive di Ivan, tre schede dev/stage/live): scheda stage, 13 righe (10 Google, 2 Apple, 1 Epic), in TMP/su697/registro_stage.csv. - ✅ Migrazione
python3 tools/firestore/migra_nickname.py TMP/su697/registro_stage.csv --env stage: prova a secco «da scrivere 13, conflitti 0», poi vera: scritte 13/13, prima 13 dopo 13, intestate al PUID giusto 13, con entrambi i documenti 13 (registro/stage: nicknames 0→13, players 0→13). Criterio 1 del ticket misurato, non dichiarato. Rilanciata in prova: «da scrivere 0, gia' migrate 13»: idempotente. - ⚠️ Criterio 2 (chi e' registrato ritrova il nome senza rifarlo): provato dalla sonda su un nome di prova e garantito dai 13 documenti
players; la prova su device vera arriva con la prossima build, quando i tester entreranno col loro account. Il ticket va In revisione con questa riga in testa. - 📌 SU-509 e SU-511 riscritti per Firestore secondo le proposte dell'architetto (il foglio resta lo schermo di lavoro, riempito da uno script; il giudizio torna nel gioco cancellando la prenotazione col service account). SU-698 chiuso col conto delle scritture contro il tetto Spark.
Firestore acceso, regole pubblicate, e la sonda del registro e' verde sul database vero2026-09-05
Il seguito di SU-697 nello stesso pomeriggio, con Ivan al login e Claude ai comandi.
- ✅ Login gcloud di Ivan (
gcloud auth login --update-adc, account del progetto Firebase delle push). Da li' tutto da terminale: gcloud services enable firestore.googleapis.com firebaserules.googleapis.com, gcloud firestore databases create --location=us-central1 --type=firestore-native → (default), Native, us-central1, creato alle 15:43. - ✅ Regole pubblicate senza console:
tools/firestore/firestore.rules caricato come ruleset con l'API firebaserules (POST /rulesets, poi POST /releases su cloud.firestore). ⚠️ Con le credenziali utente l'API vuole l'intestazione x-goog-user-project: <progetto>, altrimenti 403 «requires a quota project» — il primo tentativo e' caduto li'. Ruleset a986b427, rilascio delle 15:44. - ✅
bash tools/autotest/probe_su697.sh sul Firestore vero: 18 casi su 18 OK, zero righe di prova rimaste. Provati davvero: claim libero → ok; stesso nome da un altro PUID → preso (la precondizione exists:false fa il suo mestiere); chi e' registrato riottiene il nome; resolve di 2 su 3 PUID via batchGet; cache su disco che sopravvive al riavvio senza rete; release in registro/dev accettato dalle regole, cache svuotata, nome ripreso da un altro PUID. - 📌 Un difetto della sonda trovato al primo lancio:
"$PROGETTO»" — bash qui legge il » come parte del nome della variabile e con set -u muore «unbound variable». Graffe, e via. - ⚠️ Restano due passi di Ivan: la sezione
[firestore] nei quattro eos_credentials*.cfg (il classificatore blocca Claude) e l'export CSV del foglio dei nickname in TMP/su697/registro.csv per la migrazione.
Il registro dei nickname impara Firestore: adattatore, regole, migrazione e sonda, tutto ancora da provare sul database vero2026-09-05
SU-697 (640d, strada B decisa da Ivan il 04/09), lotto L5 di architetto-opus (27 minuti) piu' un giro di correzioni dalla review incrociata di Codex (9 rilievi, 7 applicati). ⚠️ Nessuna riga ha mai parlato con Firestore: il database non e' acceso (il service account di Firebase non puo' abilitare l'API, il login gcloud non c'e' ancora). Il ticket resta In corso.
- ✅ Un adattatore, non due registri: Firestore entra sotto
_registro(), che smista, e parla lo stesso dizionario interno di Apps Script: claim(), release(), resolve(), il nome di riserva (SU-505) e i tre «no» di SU-695 non sono toccati. Interruttore backend (default firestore, override in user://telemetria/registro_backend.txt); con [firestore]/project_id vuoto in eos_credentials.cfg si resta su Apps Script, quindi le build di oggi non cambiano. Non regredito: probe_su444.sh e probe_su453.sh tutti OK. - ✅ Schema
registro/{env}/nicknames/{nome_minuscolo} + registro/{env}/players/{puid}: l'ambiente sta nel percorso perche' Firestore ha un database solo e una sonda su una build dev non deve bruciare un nome ai giocatori veri. Unicita' = id del documento + currentDocument.exists:false in un :commit atomico sui due documenti. resolve() con batchGet e cache dei nomi altrui su disco (7 giorni, 2.000 voci, separata da _names: il PUID locale non ne esce mai, SU-448). - ✅ Regole in
tools/firestore/firestore.rules (da pubblicare a mano): lettura libera, nicknames create-only con forma vincolata e update: false, players scrivibile solo se getAfter() dice che quel nome e' prenotato da quel PUID, delete solo in registro/dev con prefisso zzprobe. ⚠️ Buco dichiarato in cima al file: senza Auth chiunque puo' prenotare a raffica: lo chiude App Check, che entra nello scopo prima della fatturazione. - ✅ Migrazione
tools/firestore/migra_nickname.py <csv> --env live [--prova]: idempotente, precondizione exists:false (una claim concorrente diventa un conflitto elencato, mai una sovrascrittura), «migrato» = entrambi i documenti, env validato riga per riga con uscita 2 prima di scrivere, verifica finale che rilegge da Firestore e conta prima/dopo. - 📌 Dalla review di Codex: precondizione mancante nella migrazione (critico),
players altrui scrivibile (critico: mitigato con getAfter, il resto e' App Check), release() respinto in live (dichiarato: in gioco non lo chiama nessuno, verificato), cancellazione zzprobe anche in live (ora solo dev), env non validato, cache che rinfrescava anche i nomi dal disco (il TTL non scattava mai), sonda che non ripristinava i file toccati. Scartato il rilievo sui nomi in italiano. - ⚠️ Cosa serve per provare: 1) database acceso (console Firebase → us-central1, Native,
(default)), 2) regole pubblicate, 3) [firestore] con project_id e api_key (da GoogleService-Info.plist) nei quattro eos_credentials*.cfg — il classificatore ha bloccato la scrittura sia all'architetto sia all'orchestratore, 4) bash tools/autotest/probe_su697.sh, 5) export CSV del foglio in TMP/su697/registro.csv e la migrazione. Proposte di riscrittura di SU-509 e SU-511 in REPORT_LOTTI.md.
Due istruttorie di design: la Collina seconda fermata, e il bonus metro col tetto 400/8002026-09-05
SU-767 e SU-768, lotto L4 di Codex sol in sola lettura (8 minuti): due documenti in OPUS_BRIEFS/, i ticket di implementazione li scrive Claude (regola di 01_TICKET.md).
- SU-767 (
DESIGN_SU-767_collina_seconda_fermata.md): decisione di Ivan del 05/09, la Collina seconda fermata e appena piu' dura del Centro. ORDER e BOARD_QUARTIERI riordinati (Mercato terzo, id e board invariati), sblocco della Collina con la stessa impresa del Mercato (3 negozi in una run) piu' migrazione una tantum dei salvataggi, moltiplicatori proposti elemosina 1,10 / prezzi 1,15 / bidoni 1,00 / wanted 1,10 / polizia 1,25 (il conteggio base passa da 4 a 5 agenti), bersaglio 0,40-0,50 vite al minuto. - SU-768 (
DESIGN_SU-768_bonus_metro_tetto.md): il tetto 400/800 NON si mette sulla sola soglia, perche' _giro_premio_banca() ricava il giro dividendo la soglia e i tier 4+ sparirebbero: serve un contatore bank_bonus_wins e valore_moneta() con esponente massimo 2. Tre premi non monetari dal tier 4: A scelta della carta garantita (pick_card, raccomandata, costo basso), B una vita restituita, C timbri cosmetici della Baracca. Tier 1-2 senza filtro. 📌 Trovato per strada: i topi sono 34 al tier 5 e 40 dal 6, non 40 dal 5 come diceva il ticket.
Le ombre di panchine e fontane, quindicesimo giro: proiezione fisica, seguono il sole, e a mezzogiorno restano2026-09-05
SU-736, riaperto dopo il KO del 5° giro con la bozza disegnata da Ivan (04/09) e chiuso in dieci giri fra il 04 e il 05/09, quasi tutti sull'aspetto, che e' la cosa che il compile-check non vede.
- ✅ Fontana: ombra dal primo frame, sagoma piena (
_tex_ombra_piena, generalizzata dalla _bancarella_tex_ombra di SU-539: stesso KO, «bordo sfrangiato e buco in mezzo»), base_piatta=true, scala (1,12 × 2,30) e traslazione Y −16 tarate sul PNG: falce visibile 15,2 px, coperta al 44,8% — la «meta'» che Ivan aveva chiesto a parole. 27 fontane su 27 con l'ombra sugli 8 semi. - ✅ Panchina, riscritta da zero su descrizione fisica di Ivan (05/09): ogni gamba proietta un segmento dal proprio piede a sud-est, la seduta proietta il quadrilatero dei piedi traslato dello stesso vettore, lo schienale niente. I tre piedi visibili si leggono dallo sprite (profilo inferiore, colonne che sporgono), il quarto si deduce dalla profondita'. Attaccata ai piedi: 0,7 px misurati nella colonna del piede.
- ✅ Segue il sole come i palazzi: gancio in
aggiorna_ombre (prima del ritorno anticipato, come la tettoia di SU-287) che rigenera la texture per il vettore dirv del sole, in cache per (sprite, D). Sera: regola degli arredi (specchio sul solo orizzontale, a sud-ovest come camioncino e fontana), scelta da Ivan sul confronto delle due regole: con quella dei palazzi alle 17 spariva dietro la panchina. Mezzogiorno: impronta dei piedi sotto la panchina, allargata del 25% (scelto da Ivan fra 25 e 55), con materiale proprio perche' l'alfa condiviso allo zenit va a zero per tutte le ombre; minimo TETTOIA_ALFA_MIN come la tettoia. - 📌 Cinque errori miei, tutti di misura, nessuno di codice: (a) letto solo il «pieno» della bozza e non l'angolo; (b) bersagli in percentuale dello sprite con il rettangolo dello sprite misurato troppo largo; (c) geometria dei rettangoli invece di quanto si VEDE — tre giri con numeri verdi e ombra invisibile, chiusi spazzando un parametro e misurando l'erba scurita sul PNG (regola in memoria); (d)
pos trattato come la CIMA dello sprite mentre e' il CENTRO: tutta l'ombra 14 px troppo in basso per quattro giri, la linea rossa di Ivan; (e) formula del sole ricostruita in Python (sbagliata) invece di stamparla dal gioco. Sono in memoria, non solo qui. - ⚠️ NON misurato: camioncini e bidoni «invariati al pixel» — chiamanti e default intatti nel diff, conteggi della sonda uguali, ma nessun diff dei PNG; ore diverse da 8, 12 e 17. Vedi TESTLOG per la sonda e il run.
- 📌 Idea di Ivan da appuntare, non lavorata: l'impronta di mezzogiorno «potrebbe essere un'idea anche per i palazzi, ci pensiamo su».
- 🔍 Review incrociata Codex (
su736rev, 3 min): quattro rilievi, tre confermati e corretti, uno scartato. (1) generate() non svuotava _ombre_panchine e il tipizzato Sprite2D precedeva is_instance_valid: dopo una rigenerazione la lista teneva nodi liberati — svuotata con le altre, controllo prima del tipizzato. (2) Il doppio specchio della sera: k cambia gia' segno la sera, il mio fattore Vector2(-1, 1) lo rispecchiava una seconda volta e l'ombra restava a destra a tutte le ore — misurato prima sui PNG (centroide dei pixel scuri identico a tutte le ore) e poi, dopo la correzione, con la sonda sulla texture dell'ombra: centroide +4,2 px alle 8 e −3,6 alle 17, specchio netto. ⚠️ L'anteprima «regola arredi» che Ivan ha scelto mostrava quindi l'ombra del mattino, e l'avevo letta io come sud-ovest: gli occhi hanno sbagliato di nuovo. Tolto il fattore. 📌 Il centroide dei pixel scuri sul PNG non e' lo strumento per la direzione: conta anche il metallo grigio della panchina e vede solo la parte d'ombra non coperta dallo sprite (le due ombre, larghe e specchiate di pochi px, si sovrappongono per gran parte); la texture misurata dalla sonda e' il metro giusto, ed e' quello che ora asserisce. (3) La sonda contava solo la presenza: ora porta il sole a 8 e a 17 e asserisce il segno del centroide della texture (mattino +x, sera −x, sempre +y). (4) Identificatori in italiano: scartato, tutto il sistema ombre del file e' in italiano (aggiorna_ombre, _ombra_camioncino, lung_base) e la coerenza locale vince sulla regola generale. - ✅ Camioncini invariati al pixel, chiuso il NON MISURATO: diff del PNG del giro 5 approvato contro l'attuale, zona del mezzo — ombra e camioncino identici; le differenze sono i bidoni tolti da SU-764, un gatto, un piccione e il prompt del giocatore.
- 📌 Sonde nuove in
scripts/tools/: probe_su736_rettangolo.gd (conta i pixel opachi della texture generata: e' cosi' che si e' capito che il problema era la posizione, non la forma), shot_su736_giro6.gd + tools/autotest/shot_su736_giro6.sh (corretto l'autoload per simbolo sotto -s). La sonda probe_su736_ombre_arredi.gd conta ora le ombre-panchina per marcatore (non stanno piu' in ombre) e ha gli attesi della fontana tarati sul render.
La demo web: le regole di Ivan, e niente URL ne' sonde nel pacchetto2026-09-05
SU-268, due lotti Codex sol (su268demo, su268sic) su decisioni di Ivan del 04/09.
- ✅ Regole della demo, solo in
DemoMode: retata a 5:00 con avviso a 4:45 (RunManager legge soglie a runtime, le costanti 1800/1710 del gioco pieno intatte); via MULTIGIOCATORE, PROGRESSI, IL TUO ACCOUNT, NOTE E BUG dal menu — tolte, non ingrigite, anche dalla navigazione; solo il personaggio base (gli altri nascosti: lucchetti e prezzi in lattine contraddirebbero il perimetro); niente Baracca ne' classifiche; invito riscritto verso iPhone e Android in 8 lingue, senza inventare URL (resta DemoMode.URL_SETTING; gli store pubblici non esistono ancora). ⚠️ Rovescia il criterio del 04/08 «la meta-progressione si assaggia»: voluto. - ✅ Sicurezza — Ivan: «FONDAMENTALE che non abbia dentro chiavi per i nostri siti ne' moduli inutilizzati, li' ho paura della decompilazione». Misurato sull'indice del pck PRIMA: 3.149 file, 1.047 sonde di sviluppo, e in chiaro gli URL del registro nickname, della telemetria e dell'elenco dispositivi beta. DOPO: 2.030 file, 0 sonde, BetaGate/BetaGateScreen/NativeAuth assenti, i 4 URL svuotati da
web_variant.py nella sola copia di lavoro (intatti nel repo), verificato sullo stage. Solo il preset Web escluso di scripts/tools/* e scenes/tools/*. - ⚠️ Restano nel pacchetto EOSBridge, NetworkManager, NicknameRegistry, OnlineLeaderboard, RunRecorder: script di gioco li chiamano direttamente e toglierli rompe l'avvio. Senza chiavi e senza l'addon EOS un decompilatore trova logica, non segreti. Toglierli davvero e' un rifacimento.
- 📌
script_export_mode=2 (token binari compressi) rende gli script non greppabili nel pck: non e' protezione, i decompilatori per .gdc sono pubblici. Il primo audit a grep dava zero su tutto e non provava niente: l'indice del pck (res://… in chiaro) e' la misura giusta. - 📌 Pagina itch preparata ma non salvata (visibilita' Ristretta, AI dichiarata: grafica, suoni, testi, codice): si salva solo col via libera di Ivan. Zip della demo in
build/street_university_demo_web.zip (68 MB).
I tre «no» del nickname distinti, e la schermata non aspetta piu' 106 secondi2026-09-05
SU-695 (640b), primo lotto Codex sol del secondo piano (su695, 6 minuti).
- ✅
NicknameRegistry: due esiti nuovi RESULT_DAILY_LIMIT («tetto giornaliero») e RESULT_BUSY («occupato») accanto ai cinque esistenti, invariati per nome e valore; _result_from_body li riconosce dalle stringhe vere del server. Il silenzio (corpo vuoto, timeout) resta RESULT_SILENT. - ✅
MainMenu: tetto d'attesa lato UI 30 s (ACCOUNT_NOME_CLAIM_TIMEOUT_SEC) con il pattern _await_max gia' in OnlineLeaderboard; allo scadere torna RESULT_SILENT, quindi «non verificato», mai «preso». Tre rami UI distinti; nessun tetto sul claim automatico Epic, per non disallineare login e nome remoto (SU-505). - ✅ Tre chiavi i18n nuove in
ui_menu.csv, 8/8 lingue piene, tono cartoonesco («l'ufficio ha finito i numeretti»). - ⚠️ NON provato: il cronometro reale con la rete staccata (sandbox headless). I tetti interni del registro (15/1,2/45 s) non sono stati toccati.
- 📌 Deciso lo stesso giorno da Ivan: strada B (Firestore) per il registro (SU-697), foglio Google come schermo di lavoro per i verdetti (SU-509/511); costi verificati sul listino ufficiale e modellati (
TMP/su697_costi/). Il lotto nickname esce dallo standby; SU-509 e SU-511 vanno riscritti prima di lavorarli.
La 0.43 e' fuori su tutti e quattro i canali, e il cancello della beta non era «da fare»2026-09-04
Tempo 2 e Tempo 3 di SU-725, con Apple approvata alle 13:32.
- ✅ Play: AAB caricato e pubblicato sul canale alpha, versionCode 43,
status completed verificato con stato_canali.py. Il bundle era gia' pronto dalla notte, quindi il Tempo 2 e' durato il tempo dell'upload. - ✅ Push ai tester Android sul topic
su_beta (id messaggio 4729565890800301443), testo approvato da Ivan: senza multiplayer, perche' e' la parte non ancora collaudata su device veri («non parlare del multiplayer nel messaggio»). Su TestFlight le note What to Test lo tengono, ma in fondo, presentato come la parte piu' nuova e meno provata. - ✅ Lista beta rigenerata e pubblicata:
ultima_versione 0.42 → 0.43, obbligatoria_dal 2026-09-11. Cambiano solo quei due campi, i 3 dispositivi restano. - 📌
WORKFLOW/04_RELEASE.md diceva il falso: «il cancello in gioco non esiste ancora, arriva con SU-219 e SU-221». I due ticket sono Fatto e BetaGate.gd c'e'. Riscritta la riga con la regola vera, letta nel codice: _version_gate_state() non fa nulla se la versione installata e' uguale o piu' nuova di ultima_versione — una build appena uscita non va mai «sbloccata», parte da se' (domanda di Ivan sui tester Mac e Windows). Piu' vecchia: avviso nel menu prima di obbligatoria_dal, blocco dopo. Cheat sheet PDF rigenerato, era fermo al 2 settembre. - ✅ Simboli di debug nativi caricati da Ivan il 2026-09-05 (Play Console, bundle versionCode 43): zip combinato di SU-709, motore
4.7.2.stable.official.ed1daf0bf + libeosg non stripped. Era rimasto «da fare a mano» il giorno della release: da qui in avanti è l'ultimo passo di /rilascio-beta (voce (56) del 05/09). - ⚠️ La trappola del
timeout di Homebrew si e' ripresentata su play_upload.sh --controlla: muore con ImportError sull'architettura di _cffi_backend dentro il controllo del versionCode. Senza timeout passa.
Codex come secondo piano: lotti, consulti e review dal roster di Claude, e le due finestre d'uso lette insieme2026-09-04
Ivan sta per passare a ChatGPT Pro e vuole che i due abbonamenti si spendano insieme, «a partire dal workflow attuale», con la possibilità di far ragionare i due modelli sullo stesso problema e di fare review del codice. Il tandem con Qwen chiude la sua settimana a zero deleghe (il confine «solo dove verificare costa quasi nulla» escludeva tutto ciò che capita in uno sprint, e i 64K di contesto il resto): il posto passa a Codex, che ha un budget vero da spendere e 272K di contesto.
- ✅
tools/codex_lotto.py — Codex come sottoprocesso staccato, con l'output fuori dal contesto di Claude: lotto (builder in workspace-write da un brief identico a quello dei builder Claude, più un blocco ambiente che aggiunge lo script), consulto (sola lettura, ≤40 righe, --continua per ragionare a giri sullo stesso thread), review (diff estratto in diff.patch e rivisto in sola lettura), attendi, stato, esito (l'unica cosa che Claude legge), registro, modelli. Esiti in TMP/codex_lotti/<sigla>/, FINE.json nasce solo alla fine, costo e rate_limits per lotto nel registro. Sta nell'allowlist (python3 tools/…): niente prompt di permesso che congeli gli agenti in volo. - ✅
tools/token_finestra.py --codex / --entrambi — la finestra di Codex (5 ore e settimanale) dalla cache di CodexUsageBar.app o dall'ultimo rollout di ~/.codex/sessions/, accanto a quella di Claude: è la lettura da fare prima di ogni onda. - ✅ Regola di ripartizione in
ORCHESTRAZIONE.md § «Codex — il secondo piano»: prima per tipo (a Codex i lotti con brief chiuso e verifica meccanica, l'istruttoria, le review e i consulti; a Claude provini, sistemi profondi, testo dei ticket, chiusura e Jira), poi per margine; si congela solo con entrambe le finestre al 90%. Roster, registro e trappole nella stessa sezione; agganci in WORKFLOW/02_SPRINT.md, /sprint, chiusura-ticket (review incrociata), rilavora-ko (consulto prima del terzo tentativo), WORKFLOW/CAPSULA.md, WORKFLOW/README.md (PDF rigenerato), CLAUDE.md, WORKFLOW/01_TICKET.md, RICOSTRUZIONE_DA_ZERO.md (6-ter). - ✅
AGENTS.md riscritto: Codex lo carica a ogni chiamata e finora gli faceva leggere i doc di progetto (decine di migliaia di token a lotto) e gli diceva di NON committare i raw LFS, regola ribaltata il 01/09. Ora con la capsula nel prompt non legge nulla, e le regole minime sono allineate a quelle di Claude. - 📌 Misurato: sonda da una parola 8 s, 15.542 token in ingresso (10.624 in cache), 0 punti — il costo fisso di ogni chiamata; consulto Luna con lettura di un file 24 s e 30K in ingresso; review Luna su un commit da 2 file 36 s, 71K, 3 rilievi con
file:riga. Il costo in punti per lotto lo dirà il registro dopo i primi 3 lotti veri. - ⚠️ Trappole trovate oggi:
codex review nativo non accetta istruzioni insieme a --commit/--base/--uncommitted (exit 2), quindi la review passa da codex exec; i rate_limits non escono da --json ma stanno nel rollout, che si trova dal thread_id; /tmp è fuori dalla sandbox, quindi SU_TMP va sotto TMP/; «Astra» non è nella cache dei modelli (i 5.6 sono Sol, Terra, Luna): l'alias c'è, lo slug si conferma con modelli quando compare col piano Pro. - 📌 Qwen:
claude-local resta solo come ripiego a piani esauriti e tools/sprint_ram.sh diventa facoltativo. Nessuna macchina dedicata: è una proposta, da confermare con Ivan. - 📌 Nel pomeriggio Ivan è passato a Pro Lite, e la struttura dei limiti è cambiata: una finestra sola da 7 giorni, senza la 5 ore (Plus le aveva entrambe), più un limite a parte per il nuovo
gpt-5.3-codex-spark. Lo script ora legge le finestre come vengono, quante sono e quanto durano, e chiama «corta» la più breve: la riga diventa CODEX settimanale 0% · si azzera ven 11/09 13:23. Astra non compare nemmeno su Pro Lite.
Pacchetti della v0.43: desktop ai tester, iOS a TestFlight, AAB pronto in attesa di Apple2026-09-04
Giro /rilascio-beta sul tag v0.43-carte-dispetto-lobby-pad-touch-carnagioni, ambiente stage su tutti i target.
- ✅ macOS e Windows esportati e pubblicati su iCloud e Google Drive (cartelle
MAC/ e WIN/): dmg con la scorciatoia Applicazioni, zip da 113 MB. Dentro entrambi i pacchetti il deployment verificato e' il solo 69fcb3a5… di stage. - ✅ iOS: progetto Xcode riesportato dalla versione nuova (senza questo passo si spedisce la build vecchia col numero nuovo), archive, IPA da 97 MB firmato «Apple Distribution» con
beta-reports-active, validazione App Store Connect senza errori, UPLOAD SUCCEEDED. Verificato dentro l'IPA: CFBundleShortVersionString e CFBundleVersion = 0.43. - ✅ AAB per Play preparato in anticipo (non caricato: il Tempo 2 di SU-725 aspetta iOS APPROVED).
version/code 42 → 43 su entrambi i preset Android, export --env stage aab, e le tre verifiche di 2b tutte verdi: jar verified, versione 0.43 nel manifest, align 2**14 sulla libreria EOS. - ✅ Prova datata (SU-605): marca temporale qualificata Aruba sul pacchetto del tag,
Status: Granted, seriale 0x4BED005699AA2925, ora 2026-09-04 00:42:54 GMT, verifica OK contro la catena archiviata in PROVE_DATATE/certificati/. - 📌 Trappola ripagata:
testflight_tester.py moriva con ImportError sull'architettura di _cffi_backend — era il timeout di Homebrew (x86_64) che trascina python3 sotto Rosetta. Lanciato senza timeout, funziona.
Note di rilascio della v0.43 nelle otto lingue, e versione portata a 0.432026-09-04
Preparazione della release beta: config/version da 0.42 a 0.43 e la sezione v0.43 in cima a tutti e otto i release_notes_<lingua>.txt, formato SU-721 (- TITOLO: testo), sezioni vecchie intatte.
- ✅ 15 voci per lingua, scelte fra le 86 voci di CHANGELOG dal tag v0.42: multigiocatore riaperto, le otto carte dispetto, lista partite e RITARDA PARTITA, pad touch disegnato, le 63 carnagioni, la barbona e i ritratti, tutorial e bonus metro rifatti dal playtest, camioncini, piccioni e bancarelle, INDIETRO unico, suoni rigenerati, più una voce di piccoli fix.
- 📌 I nomi delle carte non sono stati inventati: presi da
CARTA_DISPETTO_*_NOME di ui_meta.csv in tutte e otto le lingue, così la nota chiama la carta come la chiama il gioco (in italiano la terza dei cinque è GLITCH FINTI, non «finto crash» come diceva la prima stesura). - ✅ Glifi verificati sul font vero (
StreetU-Pixel.ttf, 243 codepoint, letto con fontTools): 0 caratteri mancanti nelle sei lingue latine, comprese le lettere che il testo nuovo introduce. Russo e cinese restano sul ripiego di sistema come già fanno i CSV. 0 emoji. - Le guardie di
prepare_release.sh (passo 1b) sono verdi: nessuna lingua indietro rispetto alle altre.
Ombre di panchine e fontane SPENTE, in attesa della bozza disegnata da Ivan (SU-736, dopo il KO del 5° giro)2026-09-04
KO di Ivan (03/09, 23:30): «toglie ombre a panchine e fontana per ora, poi provo a rimetterlo in lavorazione domani, ti passassi una panchina con una bozza di ombra sotto saresti in grado di replicarla?». Sì: l'ombra diventa una sagoma dedicata (la strada che le bancarelle del mercato usano già, _bancarella_tex_ombra), messa sotto l'oggetto a offset fisso, con l'opacità che già segue il sole e specchiata la sera. Il giro 6 parte SOLO dalla bozza in TMP/su_ombre_arredi/bozza_ivan/.
- ✅
WorldGenerator._add_interactable (~7300): la panchina è esclusa dal ramo dell'ombra (not e_bench and (…)), come lo era dal 04/08 al giro 1 di SU-736; il ramo resta intero con la proiezione del 5° giro, per riaccenderlo in un colpo. _spawn_fountain (~3316): la chiamata a _ombra_camioncino è commentata, non tolta. - I camioncini tengono l'ombra del 5° giro (sud-est, lunga quanto il mezzo): Ivan non ne ha chiesto la rimozione. Bidoni, lampioni e palazzi invariati.
- Sonda
probe_su736_ombre_arredi su 2 semi: zero errori, ombre 185 (seme 2, 7 panchine + 1 fontana in scena) e 152 (seme 3, 15 + 4). Scatti a h08 in TMP/su_ombre_arredi/spente/, guardati: panchine e fontana senza ombra. - Cinque giri di proiezione calcolata (SU-353 → SU-736) non hanno convinto: la decisione di passare a un'ombra disegnata chiude la strada geometrica per gli arredi.
OMBRA_ARREDO_SY e i due parametri nuovi di _spawn_ombra_generica restano per i camioncini.
Niente bidoni nel lotto dei camioncini: si evita l'intero isolato, non solo lo sprite (SU-764)2026-09-04
Ivan, all'ok di SU-761: «dove ci sono i camioncini mettiamo una regola che non ci possono essere i bidoni, sennò ho notato che ci si incastra». Col foglio a 1,5× il rettangolo dello sprite (91×82) lasciava libero un angolo del lotto e il barbone restava incastrato fra cestino e fiancata.
- ✅
WorldGenerator._build_hot_dog_block (~2324): in _bin_avoid_rects entra il rect dell'intero lotto (lo stesso di _occupied_blocks) al posto del rettangolo dello sprite. _bin_avoid_rects lo leggono anche cabine e bancarelle: l'estensione vale anche per loro, nessun arredo deve invadere il lotto del camioncino. - Sonda
probe_su764_bidoni (clone di quella di SU-761): 8 semi × 2 camioncini, bidoni nel lotto 0/16 (prima: 2 per lotto su ogni seme provato). Totale dei bidoni in città su 3 semi: 18→12, 21→15, 16→10, cioè −6 per seme contro i 4 contati DENTRO i lotti: i 2 in più sono con ogni probabilità i cestini sul BORDO del lotto (il piazzamento controlla l'intersezione del rettangolo del cestino, la sonda il solo centro). Scatti a stesso seme in TMP/su_camioncini/bidoni/ (su735_prima_hotdog_citta.png / su735_dopo_hotdog_citta.png), guardati: i due cestini accanto al camioncino sono spariti. - ⚠️ La sonda segna
frame_cambiato=false sul camioncino dei gelati in 3 semi su 8 (7, 11, 12345), riproducibile a Mac scarico: sta lontano dalla camera e la sonda campiona due fotogrammi senza spostarla. Non è di questo ticket (una riga toccata, l'animazione no), ma la sonda di SU-761 lo dava 8/8: da chiarire. - Il builder (dev-sonnet) è morto per limite di sessione alle 00:30 a modifica fatta; sonda e scatto rifatti a mano.
Analisi del codice per il fine tuning: misure, mappe dei sette file giganti e un ordine di lotti (brainstorming, nessuna modifica)2026-09-03
Richiesta di Ivan: «siamo oltre le 90.000 righe: cosa si può ottimizzare come righe, come divisione delle classi, quali regole Godot applicare? È un brainstorming».
- 📌 I numeri veri: il GDScript è 229.548 righe, ma il gioco è 119.437 (63K codice, 44K commenti); le altre 109.435 sono 513 sonde in
files/homeless_city/scripts/tools/, che entrano nel .pck esportato (l'exclude_filter copre solo ios/*, TMP/* e due addon) e vengono compilate a ogni compile-check. - 📌 Trappola di misura: contando le righe fra un
func e il successivo World.gd mostrava funzioni da 498 righe; erano corpi di 2-3 righe seguiti da costanti e shader inline dichiarati in mezzo alle funzioni (108 dichiarazioni sparse). Le funzioni sopra 100 righe di corpo vero sono 17 su 4.139. - Trovate: 782
has_method e 363 .call("…") come contratti in stringhe; zero Resource (~2.400 righe di tabelle in codice); UI costruita a codice (738 add_theme_*, MainMenu.tscn a 2 nodi per 11.670 righe di script); 4 autoload EOS a zero usi registrati dal plugin; aggiorna_luci/aggiorna_ombre chiamate ogni frame nel generatore via has_method; TutorialWorld che ricopia WorldGenerator con le righe navmesh cancellate; MoneyCoin che importa le costanti di CoinDrop invece di ereditarla. - Stima onesta: ~3.500 righe di gioco eliminabili (5-6%); il guadagno è strutturale, non di conteggio. Il taglio grosso è nelle sonde.
- [docs]
ANALISI_CODICE.md (radice): metriche, mappa per file con righe, 11 regole Godot in ordine di ritorno, cosa non toccare, 12 lotti in ordine di rischio. Nessun file di codice modificato.
Le ombre di panchine, fontane e camioncini si allungano verso sud-est: lunghe quanto l'oggetto, come nella foto di Ivan (SU-736, 5° giro)2026-09-03
KO di Ivan (22:00): «al mattino deve proiettare a sud est, lo stesso vale per i camioncini nuovi che ora hanno un'ombra sproporzionata piccola».
- 📌 Cosa aveva capito male il giro 4: il verso era GIÀ sud-est (
ombra_skew = −32°, palazzi e lampioni lo confermano nello scatto largo) e l'attacco alle zampe era giusto; il difetto era la taglia. lung = ombra_lunghezza·lerp(0.40, 1.10, vivo) ≈ 0,385 alle 8 è tarato sui palazzi (100-200 px, approvato): su una panchina di 28 px fa 11 px, sul camioncino a 1,5× ~23 px. Un moncone non ha un verso leggibile. - ✅
WorldGenerator.gd: nuova OMBRA_ARREDO_SY = 2.6 (2,6·0,385 ≈ 1,0: alle 8 l'ombra è lunga quanto l'oggetto). _spawn_ombra_generica prende nel_bucket e arredo (default retrocompatibili: palazzi, pensiline, lampioni, vineria, bagni invariati). La scala passa per la sola panchina in _add_interactable e per camioncini e fontana in _ombra_camioncino; i bidoni (SU-354) restano a spr.scale, provato dalla sonda (sy = 1.000 su 8 semi). - ✅ Guardia sull'indice che scurisce i personaggi: con
alt_vera = th·scala.y il camioncino (218) e la fontana (146) sarebbero entrati — e il camioncino a 84 px c'era già entrato con SU-761 (84 ≥ 80), contro la nota che escludeva le basi oblique. Ora in_bucket = nel_bucket and alt_vera ≥ 80, e le tre sorgenti passano false. - ✅ La sera le ombre
arr non prendono OMBRA_INDIETRO_MULT (×2,6, che serve ai palazzi per attraversare la strada): lunghe come il mattino, specchiate. - Numeri (sonda
probe_su736_giro5_numeri, h08, uguali sugli 8 semi): estensione/altezza texture panchina 0,83, fontana 0,99, camioncino 0,72; misurato dal piede vero ≈ 1,0 per tutte e tre (panchina e camioncino hanno l'appoggio rialzato dall'alzata, fino al 28%); scarto piede→ombra 0,00 px; verso (+x, +y). Non si è alzata la costante per portare il camioncino a 0,9: la fontana sarebbe uscita a 1,25-1,5×. Scatti guardati: TMP/su_ombre_arredi/giro5/su736_dopo_h08.png (largo) e su736g5_{panchina,fontana,camioncino}_dopo_{h08,h17}.png. - ⚠️ Alle 17 l'ombra di panchina e camioncino resta in gran parte dietro il proprio sprite (mai sopra: z-order rispettato), poco leggibile. Non è di questo giro.
Gli otto disegni delle carte dispetto entrano in gioco (SU-756 e SU-759, montaggio dell'arte approvata in SU-757)2026-09-03
Ivan, 22:30: «mi piacciono tutte, approvate». Copiati i 128×128 di TMP/su_carte_mp/giro3/hi/ in files/homeless_city/assets/ui/perk_hi/dispetto_<id>.png (otto PNG con i loro .import, reimport headless). Da ora le carte dispetto mostrano il disegno grande come quelle single player; le celle 16×16 restano il ripiego nel foglio e l'icona dello slot HUD.
- ✅ Provino in-engine
shot_su250_carta.gd su dispetto_vista e dispetto_ratto: icona carta → TROVATA per entrambe (prima ripiegava sull'atlante), scatti in TMP/su756_carte_mp/giro3/. La versione super_hi/ resta ASSENTE ed è giusto così: i dispetti sono carte «occasione» una tantum, non maturano in superpotere. - Nessuna riga di codice:
LevelUpScreen cerca il file per id. SU-756 e SU-759 restano In revisione per la prova in multiplayer su device.
L'ombra della panchina parte da tutte e quattro le zampe, come nella foto di Ivan (SU-736, 4° giro)2026-09-03
KO di Ivan (03/09, con foto d'esempio): «l'ombra è sballata perché non parte dalle zampe ma la parte destra parte nel vuoto». Quarto giro aperto su sua richiesta esplicita, oltre la regola dei tre.
- 📌 Cosa aveva capito male il giro 3 (
867890a): aveva preso «la direzione della retta di appoggio proiettata» per il solo coefficiente col0, mentre la direzione vera della retta inclinata (1, m) sotto la trasformazione è col0 + m·col1. La verifica di quel giro era fatta nel caso limite senza skew, dove l'errore non si vede; con lo skew del mattino (~34°) l'errore esplode sulla zampa più lontana dal centro: esattamente il «parte nel vuoto». - ✅
WorldGenerator.aggiorna_ombre (ramo obliquo, ~9200-9235): risolta l'equazione che impone che ogni punto della retta di appoggio cada sul piede vero, col0 = Vector2(sx, sy·m_p) − m_p·col1 al posto di Vector2(sx, 0) + m_p·col1. Una riga, vale per i rami avanti/indietro senza casi speciali; commento con la derivazione. - Numeri (seme 736001, scarto piede→bordo ombra): prima h08 sinistra +1,45 px / destra +5,81 px; dopo +0,73 / +0,73 a h08 e a h17 (residuo sub-pixel). Verso a h08 uguale al lampione nello stesso scatto. Sonda 8 semi: 0 errori; il fix cambia solo la matrice di un nodo già creato, nessun nodo in più.
- ⚠️ Il ramo obliquo è condiviso coi bidoni (SU-354, pendenza 0,091): spostamento analitico < 1 px, verificato a vista su
TMP/su_ombre_arredi/giro4/bidone/zoomtight_{prima,dopo}_h08.png (ombra attaccata alla base in entrambi). Un diff pixel-per-pixel pulito non è stato possibile: gli NPC cambiano posizione a ogni lancio. Fontana, camioncini e palazzi non passano da quel ramo. - ⚠️ Commit incrociato: il fix era già su disco quando il lotto A ha messo in indice
WorldGenerator.gd, ed è entrato in fa101a2 (SU-761). Due hunk separati, verificati; questo commit porta CHANGELOG e la sonda shot_su736_giro4_bidone.gd. Lezione: l'add di un file condiviso si fa nello stesso comando del controllo degli hunk, non minuti dopo. - Scatti:
TMP/su_ombre_arredi/giro4/zoom2_{prima,dopo}_{h08,h17}.png.
I camioncini hot dog e gelati in gioco a 1,5×: fogli 480×84 montati, stessa base a terra, cestini fuori dall'ingombro (SU-761)2026-09-03
Gemello di montaggio del 2° giro di SU-734 (Fatto di Ivan). I fogli approvati di TMP/su_camioncini/giro2/ sostituiscono sprites_raw/WORLD/*_truck_anim.png e files/homeless_city/assets/sprites/world/*_truck_anim.png (4 fotogrammi da 120×84), reimport verificato sulla cache .godot/imported/.
- ✅
files/homeless_city/tools/import_buildings_sprites.py: TARGETS (320,56,4) → (480,84,4). BuildingDatabase.gd e buildings_manifest.gd non toccati: lo split dei fotogrammi è dinamico (larghezza foglio / 4), il ticket li citava per prudenza. - ✅
WorldGenerator._build_hot_dog_block: AnimatedBuilding centra lo sprite sull'origine, quindi un foglio più alto di 28 px avrebbe affondato il camioncino di 14 px nel marciapiede. Nuova _van_pos_compensato(): alza l'origine di metà della crescita misurata su frame0.get_height(), usata sia per lo sprite sia per _ombra_camioncino(). Sonda probe_su761_camioncini.gd su 8 semi × 2 camioncini: scarto della base a terra 0,00 px, coordinate intere, centro fermo fra i 4 fotogrammi, nessun cestino dentro il rettangolo, zero errori. - ✅
_bin_avoid_rects: da 64×60 stimato a 91×82 misurato sull'unione delle bbox opache dei 4 fotogrammi. L'ombra (_ombra_piede_obliquo, SU-353, non toccata) si è adattata da sola: alzata 15,7 → 23,5 px (×1,497), pendenza identica. - 📌 Scelta: la pipeline di resize di
import_buildings_sprites.py (pensata per arte grezza) reintroduce alfa intermedia anche su un foglio già pixel-perfect (provato: 4.000+ colori non presenti nel sorgente). Copiato 1:1, come risulta fosse stato fatto anche in SU-735 (foglio in gioco byte-identico al raw). Scatti prima/dopo a stesso seme in TMP/su_camioncini/giro2/montaggio/. - Non provato: device, multiplayer (generazione deterministica per seme, consumo di RNG invariato).
Le carte dispetto hanno i disegni grandi come quelle single player: otto PNG 128×128 in TMP, da approvare (SU-757, 2° giro)2026-09-03
KO di Ivan su SU-756: «sono troppo poco definite rispetto ai disegni delle carte single player, rifalle». 📌 Il primo giro aveva capito male il bersaglio: il difetto non era l'icona 16×16 ma il PNG 128×128 in assets/ui/perk_hi/<id>.png che le carte single player hanno (SU-85) e che le otto carte dispetto non avevano — LevelUpScreen ripiegava sulla cella 16×16 ingrandita ×4, sgranata. Requisito aggiunto in descrizione di SU-757.
- ✅ Otto disegni hi-res, non tre: le cinque carte di SU-759 avevano lo stesso difetto e si approvano in un giro solo (nota su SU-760). Codex a 256 px con sfondo magenta (chroma-key come SU-85), LANCZOS a 128×128 con alfa premoltiplicata, alfa binaria; stile letto dai
perk_hi/ esistenti (contorno nero pieno, colori piatti, soggetto grande, faccina). Tutti e otto al primo giro. - ⚠️ Trappola nella riduzione: la maschera «frangia scura» di
TMP/su_carte_mp/riduci.py (1° giro) scattava su qualunque colore con un canale dominante e mangiava il tubo ciano del kazoo e il braccio rosso della X. Verificato che il bordo soggetto/fondo è sempre un gradino netto: TMP/su_carte_mp/giro3/riduci_hi.py maschera solo il magenta vero. - Consegna in
TMP/su_carte_mp/giro3/: hi/dispetto_<id>.png ×8, grezzi in gen/, confronto_hi.png (accanto a quattro perk_hi esistenti), carte_otto_hi.png (mockup sulle carte vere). Niente toccato in files/homeless_city/: il montaggio in perk_hi/ è di SU-756 e SU-759 al Fatto di Ivan.
Primo tentativo di deposito del marchio: arrivati alla scheda 7 di 8, fermati dal caricamento del JPEG (SU-763)2026-09-03
Con Ivan al login SPID e Claude a guidare il modulo da Chrome. Le schede 1-6 sono passate (Richiedente, Fast Track, denominativo, sei voci di Nizza, priorità vuota, richiedente e domicilio elettivo compilati da Ivan).
- ⚠️ Dove si è rotto: la compilazione guidata è pensata per una finestra pop-up; aperta nella scheda normale, il pulsante «Carica» della «Rappresentazione del Marchio» (scheda 7) ricarica la pagina sulla home pubblica, la compilazione si perde e la sessione resta agganciata lato server: i rientri via SPID passano il callback SSO e poi rimbalzano sulla home. Cancellare i cookie leggibili da JS non basta. Si riprende domani dalle 8:00 (area riservata solo feriali 8-19), nella pop-up, con «Salva su PC» dopo la scheda 6 come checkpoint.
- ✅ Anche il denominativo vuole un JPEG (natura «Denominativo (JPEG)», max 380×380): generato
TMP/su607_uibm/street_university_denominativo.jpg (Helvetica nera su bianco, 380×160). - ✅ Fascicolo aggiornato con le otto schede come sono davvero e i valori esatti da dettare:
OPUS_BRIEFS/DEPOSITO_MARCHIO_UIBM.md §2.
Il marchio: controprova diretta su UIBM, spunta «Università della Strada» del Gruppo Abele, e il fascicolo per depositare (SU-607)2026-09-03
Ivan ha deciso di andare avanti col deposito. Prima di spendere, la controprova che il referto del 2 settembre chiedeva — la banca dati UIBM letta direttamente — e la preparazione del modulo.
- ✅ Banca dati UIBM da Chrome: «STREET» + «UNIVERSITY» = zero record, con controllo positivo su «STREET FIGHTER» (CAPCOM, 2022 e 2023) che prova che la ricerca funziona. ⚠️ Il reCAPTCHA del form è invisibile: il referto del 02/09 lo dava per bloccante e non lo era.
- ⚠️ La traduzione, che TMview non poteva trovare: «UNIVERSITA' DELLA STRADA», Fondazione Gruppo Abele ONLUS (Torino), denominativo, classe 41 con l'intera intestazione («educazione; formazione; divertimento; attività sportive e culturali»), depositato il 13/11/2002, rinnovato nel 2012 e nel 2022 (n. 362022000147411), valido fino al 19/10/2032, con mandatario. È l'ente di formazione per operatori sociali del Gruppo Abele, attivo dal 1978. Per il pubblico italiano è concettualmente identico al nostro nome, e qualunque voce di giochi in 41 ricade dentro «divertimento»: possibile opposizione entro 3 mesi dalla pubblicazione. Difesa buona (prova d'uso ex art. 178 c. 4 CPI: loro fanno formazione, non giochi), ma si esercita solo rispondendo.
- 📌 Raccomandazione aggiornata, non ribaltata: depositare lo stesso, 9 + 41, con la 41 ristretta a due sole voci di Nizza —
410094 giochi forniti on-line, 410256 intrattenimento in ambienti virtuali — e Fast track; se l'opposizione arriva solo sulla 41, si rinuncia alla 41 (34 €) e la 9 prosegue. Alternativa prudente: sola classe 9, 149 €. Il problema segue anche a EUIPO: un marchio italiano può opporsi a un marchio UE. - ✅ Fascicolo operativo
OPUS_BRIEFS/DEPOSITO_MARCHIO_UIBM.md: prerequisiti (solo SPID/CIE/CNS; portale aperto feriali 8-19; pop-up abilitati), schede del modulo, voci di Nizza con i codici dall'elenco ufficiale 13-2026 (9: 090829, 090670, 090717, 090934), pagamento (183 € = 101 + 34 + 48; PagoPA contestuale, oppure F24 ELIDE tipo U codice C302; bollo digitale o marca da 48 €), calendario (pubblicazione ~7 gg, +3 mesi opposizione, +6 mesi priorità UE), piano B, chi fa cosa. - 📌 SME Fund 2026: il voucher marchi è chiuso per esaurimento fondi, riapertura stimata nell'ultimo trimestre, e comunque vuole partita IVA e domanda prima del deposito: non è un motivo per aspettare.
streetuniversity.it era ancora libero alle 16:50. - 📌 Ticket del deposito creato (vedi commento su SU-607); dati grezzi e scatti in
TMP/su607_uibm/ (dalla radice del repo). Addendum §10 del referto in OPUS_BRIEFS/DESIGN_SU-607_marchio_anteriorita.md.
Lo stormo di piccioni ha le ali che frullano (SU-762)2026-09-03
- ✅
assets/sounds/pigeon_flock.wav (+ Sounds_raw/), ElevenLabs via API come SU-606, stereo 16 bit 48 kHz, loop_mode=0; tre varianti in TMP/su_piccioni_ali/ con RIASCOLTO.md: montata la v2 (v1 debole, v3 col 70% dell'energia sotto 250 Hz, uno sciame più che un decollo). World.gd: _dispetto_play_piccioni_sfx() sullo schema di nube e glitch (un solo AudioStreamPlayer sul bus SFX, riparte se richiamato), .stop() quando _update_dispetto_piccioni smonta lo stormo. probe_su756_dispetti.gd + controlli D0/D13/K: 103 righe, 0 KO. Se Ivan preferisce v1 o v3 è una copia di file.
Il pad touch con gli sprite approvati: stick e tasti A B X Y stile Xbox (SU-739)2026-09-03
- [NOVITA'] I comandi a schermo sono disegnati a mano: stick e tasti A B X Y in pixel art al posto delle forme tracciate dal codice.
- ✅
VirtualControls.gd: _ChunkyShape/_StickRing (forme draw_*) sostituite da _PadButtonSprite/_PadStickSprite (Node2D + Sprite2D, NEAREST) con gli sprite di SU-738 in assets/ui/pad/ (28 PNG, 1× e 2×; raw in sprites_raw/pad_touch/); premuto = texture _premuto, stick toccato = _uso. Sotto scala 1,75 il foglio 1× alla scala del pad, sopra il 2× a metà. Posizioni, raggi di tocco, pad fluttuante, dissolvenza e modali invariati: probe_su417_touch ESITO=OK prima e dopo. Le frecce del d-pad digitale (SU-36, opzione ON di default da SU-279) restano disegnate sopra lo sprite. - ✅ Tolto il bake-in-atlas di SU-583 (
_bake_e_scambia e satelliti: rimedio al costo delle draw call vettoriali, inutile con texture) e la Label della lettera (già nello sprite), con le costanti morte. Scatti in TMP/su739_pad/ (16 colori nel ritaglio del tasto A = 15 dello sprite + sfondo: nessuna sfocatura); shot_su739_pad.gd/.sh. - ⚠️ NON PROVATO: fps sull'Honor; il ramo 2× (l'auto-scala su Mac non supera 1,75). Da decidere con Ivan: i PNG hanno alfa binaria, quindi i tasti non sono più traslucidi come chiedeva SU-225.
L'ombra della panchina segue il verso dei palazzi anche di mattina (SU-736, 3° giro)2026-09-03
- ✅ Causa: in
aggiorna_ombre() (WorldGenerator.gd ~9208) la pendenza della retta d'appoggio (base_obliqua.x) veniva sommata in due basi diverse — quella dell'oggetto e quella già ribaltata dalla proiezione — e un ribaltamento puro mandava m in +2m invece che in −m: la diagonale dominante dell'ombra era quella opposta alla panchina (il «va al contrario» del KO). Fix di una riga: col0 = Vector2(sx, 0) + m_p * col1. Il piede (SU-353) non dipende da col0; la fontana (base piatta) non cambia. - ✅
shot_su736_ombre_arredi.gd riscritta: forza l'ora e inquadra panchina + palazzo (tools/autotest/shot_su736_ombre_arredi.sh <ora>); scatti prima/dopo alle 8 e alle 17 in TMP/su_ombre_arredi/giro3/ (zoom della panchina in zoom_bench_*.png). Sonda 8 semi: nodi-ombra identici al giro 2, 0 SHADER ERROR. ⚠️ La misura a pixel del verso è instabile (a 15-20 px d'ombra un NPC di passaggio cambia il segno): la prova che regge è quella sui vertici del quad, più gli scatti guardati.
Le altre cinque carte dispetto del multiplayer: piccioni, finto crash, controlli invertiti, falso poliziotto, ratto borseggiatore (SU-759)2026-09-03
- [NOVITA'] Le altre cinque carte dispetto: STORMO DI PICCIONI, GLITCH FINTI, CONTROLLI INVERTITI, FALSO POLIZIOTTO e RATTO BORSEGGIATORE.
- ✅ Solo nel mazzo MP, sulla catena di SU-756 (
apply_card → _fire_dispetto → net_dispetto_play → _srv/_host/_cl_dispetto → _dispetto_apply_local), kind 3-7 accodati senza toccare 0-2 (protocollo fra peer); avviso a schermo per carta, con ripiego sulla riga generica. Nuovi scripts/world/FakeCop.gd (190 righe: un finto agente con l'arte di police_frames.tres e la sirena di SU-603, che non sa cos'è un arresto — scartata la «modalità finta» dentro PoliceOfficer, una decina di toppe nella meccanica) e scripts/world/PrankRat.gd (in rete viaggiano solo spawn e raccolta, non la posizione: 2 pacchetti invece di ~60; chi incassa lo decide l'host, la vittima scala mini($5, quel che ha) e riferisce). - ✅
assets/ui/perk_icons.png da 64×80 a 64×112 (celle 20-24, le 0-19 byte-identiche), chiavi nelle 8 lingue di ui_meta.csv/ui_mondo.csv, TestBot --bot-soldi. Sonde estese: probe_su756_dispetti.gd 94 righe 0 KO (SP e tutorial 0/200 carte MP), MP a due istanze tutte e otto le carte in 1-10 ms, ratto host $40→$35 e client $0→$5, controlli invertiti +81/−81/+82 px. Compile-check 614/96, emoji 0. Scatti in TMP/su759_carte_mp/. - ⚠️ Lo stormo copre il 43-45% dello schermo (tarato a misura, tre costanti in
World.gd); i piccioni sono muti (nessun asset di ali: ticket audio a parte). NON PROVATO: device, >2 giocatori, EOS vero.
Camioncini a 1,5×, senza rigenerare (SU-734, KO)2026-09-03
- ✅ In
TMP/su_camioncini/giro2/: hotdog_truck_anim.png e icecream_truck_anim.png a 480×84 (4 fotogrammi da 120×84), ridotti dagli STESSI raw Codex del giro 1 (anima_giro2.py, coordinate di fumo e scintille riscalate ×1,5) — il disegno approvato, solo più grosso. Hot dog 54→82 px, gelati 48→73 px, contro i 32 px del barbone: proporzione_1x/4x.png li mette sulla stessa linea di terra. Carrozzeria identica nei 4 fotogrammi, alfa binaria, numeri.txt. - ⚠️ Al Fatto di Ivan serve un ticket NUOVO di montaggio (SU-735 era tarato sugli 80×56):
tools/import_buildings_sprites.py TARGETS, BuildingDatabase._animation_frames, buildings_manifest.gd ANIMATION_FRAMES, l'ingombro _bin_avoid_rects (64×60) in WorldGenerator.gd; _ombra_camioncino misura il piede da sola.
La barbona colpita è la barbona, in tutte e quattro le varianti (SU-723/724, KO)2026-09-03
- ✅ Diagnosi: «colpita» non è una riga della griglia 5×5 ma un'animazione unica
hit (Player._apply_hit_pose, specchiata con flip_h), un PNG per skin. player_skin_f1_frames.tres puntava al vecchio PNG «in piedi» di SU-303; player_skin_f1_{drunk,heatstroke,cat}_frames.tres puntavano dritte a player_hit.png, cioè al barbone (il KO). - ✅
sprites_raw/PEOPLE/arch_player_f1_vhit.png (Codex, riferimenti arch_player_f1.png + barbone_colpito.png), ridotto con tools/su305_importa_colpito.py in files/homeless_city/assets/sprites/player_skin_f1_hit.png (25×32); le tre .tres corrette. probe_su724_varianti_f1.gd verifica ora anche la texture vera del frame hit: 24 controlli, 0 KO. Scatti in TMP/su723_barbona/giro4/.
Effetti sonori, secondo giro: i 16 bocciati rigenerati più fedeli ai vecchi (SU-606)2026-09-03
- ✅ In
TMP/su606/: rigenera.py (API REST /v1/sound-generation con prompt_influence=1.0 — l'MCP text_to_sound_effects non espone il parametro — durata uguale al vecchio, prompt originale più una descrizione FISICA misurata sul vecchio: attacco/decadimento, colpi, centroide, verso del pitch; 3 varianti, tenuta la più vicina e pareggiata in RMS). Nelle 16 cartelle *_KO/ (suffisso di Ivan): nuovo.wav vincente, nuovo_2/3.wav, nuovo_giro1.wav (il bocciato). Tabella prima/dopo in RIASCOLTO.md. - ⚠️ 14 su 16 più vicini al vecchio per misura;
bench e catangry restano sopra il giro 1 dopo due giri; score_tick non potrà mai combaciare (vecchio 0,26 s, minimo API 0,5 s). Niente montato in gioco: decide l'orecchio di Ivan.
La 64ª variante di carnagione: il turista v2 (SU-422)2026-09-03
- ✅
tools/su422_monta_varianti_dark.py: ESCLUSI_DEFAULT svuotato (escludeva arch_tourist_v2 finché SU-731 non era Fatto); pipeline rilanciata → sprites_raw/PEOPLE/arch_tourist_v2_dark.png, files/homeless_city/assets/sprites/npc/bodies/arch_tourist_male_v02_dark_sheet.png + _frames.tres, npc_dark_manifest.gd a 64 coppie. I 63 fogli scuri preesistenti restano md5-identici. - ✅
scripts/tools/probe_su422_carnagioni.gd: soglia coppie 63→64; la parte 3 non pretende più «turista mai scuro» ma il 50% ±10 come per gli altri (misurato 362/722 = 50,1%). Sonda OK, 0 falliti; compile-check 613 script / 96 scene, 0 falliti. - ⚠️ Trappola:
su422_assegna_uid.gd dà un uid NUOVO e casuale a ogni .tres rigenerato senza uid, quindi rilanciare la pipeline intera riscrive la riga di testa di tutti i 63 .tres (solo uid + load_steps). Nessun riferimento uid:// a quei fogli nel repo: ripristinati dal commit, entra solo il turista.
Le icone delle altre cinque carte dispetto (SU-760)2026-09-03
- ✅ In
TMP/su_carte_mp/giro2/: icona_piccioni.png, icona_glitch.png, icona_controlli_invertiti.png, icona_falso_poliziotto.png, icona_ratto_borseggiatore.png (16×16, alfa binaria, zero residui magenta), grezzi Codex in gen/, confronto_x8.png con le 20 celle esistenti + le 5 nuove in una griglia 4×7. Riferimenti: assets/sprites/pigeon.png (il piccione vero) e assets/ui/perk_hi/topo_bidone.png (il ratto della carta TOPO DA BIDONE). Un giro di correzione su piccioni (contorno troppo sottile) e poliziotto (due lobi separati). - ⚠️ Giudizio onesto: 4 su 5 leggono bene; FALSO POLIZIOTTO resta la più debole — comunica «poliziotto + naso finto» per colore (blu navy, distintivo giallo, neo rosso) più che per sagoma. Segnalato per la conferma di Ivan prima del montaggio (SU-759: foglio da 64×80 a 64×112, celle 20-24).
Le tre carte dispetto del multiplayer, solo nel mazzo MP (SU-756), e la riga CSV a 10 colonne2026-09-03
Da SU-20, deciso da Ivan il 03/09: VISTA DA UBRIACO, SCOREGGIA ATOMICA, KARAOKE KAZOO — «le carte del multi non devono comparire in single player».
- [NOVITA'] Le prime tre carte dispetto del multiplayer: VISTA DA UBRIACO, SCOREGGIA ATOMICA e KARAOKE KAZOO, che escono solo online e colpiscono chi guida la classifica.
- ✅ Tre carte occasione (
MAZZO_B, una tantum, ricarica 45 s) dispetto_vista/nube/kazoo, celle 17/18/19 dell'atlante (icone di SU-757 montate byte-identiche). apply_card() → _fire_dispetto() → World.net_dispetto_play(kind): PowerUpSystem resta «zero rete», gli RPC stanno nel World con la catena copiata da _host_atomica (_srv_dispetto any_peer → _host_dispetto che valida → _cl_dispetto authority → _dispetto_apply_local); in rete viaggiano tre numeri. Bersaglio: il primo in classifica — l'host non aveva i punteggi altrui in corsa, quindi nuovo _srv_live_score (un int ogni 2 s, solo se cambiato; scartata la richiesta-risposta al momento della giocata, un round trip dentro un budget di 0,5 s). - 📌 La scelta che regge tutto:
GameState.is_woozy() (meccanica: drift, igiene ×3, elemosina rifiutata) non è toccata; accanto nasce is_woozy_view(), letta SOLO dai quattro punti di resa (distorsione, camera, musica, ciondolio+stelline). Accendere is_drunk sul bersaglio avrebbe toccato le sue statistiche — vietato dal ticket. - 📌 Numeri: SP 0 carte dispetto su 200 mani; finto MP a due 44 su 200 e 0 mani con due dispetti; tutorial con rete accesa 0 su 200; MP a due istanze ENet: client→host 0,007/0,003 s, host→client 0,003 s; fame/energia/igiene/soldi/vite/ricercato bit-identici (misurati con
run_active=false, altrimenti il decadimento rende la cosa indecidibile); sonda 38 righe, 0 KO; run_check.sh 611/96, 0 falliti; audit emoji 0. TestBot --bot-dispetto sec:id,… per giocarle a tempo. Chiavi MONDO_DISPETTO_* e le carte in 8 lingue. - ⚠️ Due cose da decidere (Ivan): il kazoo alza il pitch (+4 semitoni ± 0,8 di scordato), non lo abbassa come diceva il brief — giù è già il suono dell'ondata di caldo (SU-719) e un dispetto indistinguibile dal meteo non «si vede»; e SCOREGGIA ATOMICA esiste già come super jolly di SU-22 (ruba i soldi): due cose diverse con lo stesso nome, entrambe solo in MP.
- NON PROVATO: due device veri; EOS (collaudo su ENet); partita a > 2 giocatori (bersaglio a 3 peer provato con un
NetworkManager finto); le 7 lingue non viste in partita; il timbro del kazoo giudicato a numeri. - ✅ CSV: la riga
TUTORIAL_ISTR_BANCA_LATTINE di ui_mondo.csv aveva 10 colonne (virgola non protetta nella cella cinese, segnalata da due lotti): ricongiunta e messa fra virgolette, tutte le righe a 9 colonne; il riscrivere del csv ha tolto virgolette superflue a due righe senza cambiarne il testo.
Le tre icone delle carte dispetto (SU-757)2026-09-03
- ✅ In
TMP/su_carte_mp/: icona_vista_ubriaco.png (boccale con corona di stelle: più leggibile a 16 px degli occhi a spirale e coerente con lo stile «a oggetto» del foglio), icona_scoreggia_atomica.png (nuvola a fungo verde), icona_karaoke_kazoo.png (kazoo tubo+bulbo teal/oro con note storte); grezzi Codex in gen/, confronto_x8.png con le 17 esistenti + le 3 nuove. Stessa ricetta di riduzione dei ritratti (maschera del fondo + BOX + alfa binaria + UnsharpMask). - 📌 Due correzioni misurate sul PNG ridotto, non sul grezzo: soggetti troppo piccoli nel canvas 128 che a 16 px si spezzavano in blob (rigenerati più grandi); il tubo del kazoo rosa acceso finiva nella maschera del magenta e spariva (rigenerato teal/oro). Celle libere del foglio per il montaggio (SU-756): 17, 18, 19. Il Fatto lo mette Ivan.
La voce MULTIGIOCATORE non era spenta: l'unico cancello è l'identità (SU-758)2026-09-03
- 📌 Ivan: «riattiva il menu di multiplayer». Diagnosi con grep e
git log -S/blame su _open_multiplayer() e su tutta la catena di NetworkManager.online_gate_reason() (286-313): nessuna chiusura di comodo, nessun ramo su ambiente o piattaforma — invariata dal 17/08 (SU-447). Solo _is_web() toglie la voce nel browser (giusto). L'unico cancello è quello vero: account loggato + nickname confermato. - ✅ Verificato empiricamente con
probe_su758_mp_menu.gd (forza Settings.account_logged_in/account_name_claimed/account_display_name come un login vero): a 1024×768 e 844×390 MULTIGIOCATORE apre mp_home (PARTITA RAPIDA / OSPITA / LISTA PARTITE / UNISCITI CON CODICE); senza identità apre mp_gate con motivo no_account. 0 KO, run_check.sh 610 script / 96 scene, 0 falliti. Scatti in TMP/su758_mp_menu/. - Zero righe di gioco toccate. Se sul device di Ivan compare il cancello, quel device non risulta loggato con un nickname confermato (schermata ACCOUNT). NON PROVATO: build stage/live e device.
Le frecce del selettore come sprite, la focus ripulita, i crediti senza fornitori (SU-733 KO, SU-754, SU-604 KO)2026-09-03
- ✅ SU-733: la variante focus aveva un alone giallo sfumato fuori dalla sagoma (207 px in più su 1.160 opachi) e ~430 colori di antialias attorno a 2 toni veri.
tools/su733_ripulisci_focus.py: k-means (k=5) sui colori della normal, contorno/bevel riportati al colore del proprio cluster, corpo nel giallo di selezione della schermata Color(1.0, 0.92, 0.3). Sagoma 0 px di differenza dalla normal, 5 colori, alfa binaria; i 4 file focus in sprites_raw/UI/ sovrascritti. - ✅ SU-754: gli 8 PNG in
assets/ui/ (NEAREST); in MainMenu.gd le due Label restano il bersaglio del tocco (area grande di SU-495, per non far regredire l'ergonomia touch) e sopra ci sono due TextureRect (_skin_prev_arrow/_skin_next_arrow) con lo sprite alla misura vera (arrow_font × 0,5, rapporto misurato in SU-733 fra altezza del vecchio glifo e font_size), stato normal/focus dal fuoco di riga, 1×/2× da DisplayServer.screen_get_scale(). shot_su754_frecce.gd: 0 px di differenza d'altezza a 1024×768, 844×390, 1920×1080; texture che cambia col fuoco; tap simulato ok; INDIETRO invariato. Scatti in TMP/su_frecce_montaggio/. - ✅ SU-604: tolti i blocchi SUONI/ElevenLabs e MUSICHE/Suno da
_crediti_body() e le due chiavi da ui_menu.csv; restano SVILUPPO, MOTORE, CARATTERI, MULTIGIOCATORE (shot_su604_crediti.gd). THIRD_PARTY_NOTICES.md intatto. - NON PROVATO: device/touch fisico. Compile-check e audit emoji verdi.
Ombre di fontana e panchina rifatte per la loro forma, e i cartelli tondi sui banchi (SU-736 KO, SU-703)2026-09-03
- 📌 SU-736, la causa sotto entrambi i KO:
_ombra_piede_obliquo è un inviluppo pensato per un profilo continuo (il camioncino). Sulla fontana tonda aggancia un punto 18 px dentro il corpo (i bordi di un cerchio «toccano terra» più in alto del centro) e l'ombra finiva sotto lo sprite: nuovo parametro base_piatta di _ombra_camioncino (~2385), solo per la fontana → ombra ovale con ~19 px sporgenti. Sulla panchina a 45° con le zampe separate aggancia il bordo dello schienale, da cui la «pillola orizzontale»: nuova _ombra_piede_due_gambe() (~8691) che cerca il piede più basso in due fasce strette agli estremi e ignora il centro, solo per BENCH → ombra diagonale attaccata alle zampe. Bidoni sul metodo vecchio. Sonda 8 semi: conteggi invariati, 0 errori; scatto in TMP/su_ombre_arredi/giro2/. - ✅ SU-703: i cinque cartelli approvati (variante A) sono
Sprite2D figli dello sprite del banco in _spawn_bancarella (~4629), con l'offset validato in shot_su702_cartelli.gd; PNG in assets/sprites/world/bancarella_cartello_*.png (NEAREST) e grezzi in sprites_raw/WORLD/. Puramente additivo: ingombri e collisioni invariati, i due vocabolari tipo/negozio non toccati. Scatti col cluster di 3 banchi a 1024×768 e 844×390 in TMP/su703_cartelli/ da shot_su703_cartelli_mercato.gd (shot_su408_mercato.gd è a 1280×720 fissa e inquadra un banco solo). - 📌
probe_su408_bancarelle.gd fallisce per un motivo preesistente (SU-540 ha portato il mercato da 5 a 12 banchi con tipi ripetuti): non è una regressione. NON PROVATO: device.
Il banner delle calamità con la grafica del gioco, nella posizione di oggi (SU-714)2026-09-03
Set v2 di SU-713 approvato da Ivan, col KO sul testo «troppo verso il bordo».
- ✅
_setup_wave_banner()/_update_wave_banner() riscritte: il PanelContainer di cartone diventa una TextureRect che scambia le due cornici (preavviso/attiva, 340×112 = misura di oggi), icona 16×16 dell'ondata (_wave_icon), barra a NinePatchRect fondo + riempimento (_wave_bar_box/_bg/_bar, colore via modulate, patch 12/4, larga il 53% della cornice). _position_wave_banner() non toccata: il banner resta dov'è (SU-691 annullato). - 📌 Il KO risolto davvero, non spostato: i margini del contenuto (34/34/14/10 unità) sono misurati sul PNG (asse pieno y∈[8,107], x∈[10,330]) e titolo/countdown stanno centrati dentro. In prova «ONDATA DI CALORE IN ARRIVO» sforava a font fisso:
_fit_wave_title() riduce il font 18→13 pt solo quando serve, pensando anche alle altre 7 lingue. - 📌 Sette PNG +
.import in assets/ui/calamita/; sonda shot_su714_calamita.gd + tools/autotest/shot_su714.sh: 12 scatti (3 calamità × 2 fasi × tablet/telefono) in TMP/su714_calamita/ con log di geometria (margini, nessuna intersezione con vite e colonna ora/punteggio). run_check.sh 0 falliti. - ⚠️ Ambiente: il Godot installato è 4.7.2 (il progetto dichiara 4.6.2) e in un solo processo
-s più screenshot restano agganciati al primo fotogramma — riprodotto anche su shot_su272_wave_banner.gd, non toccata: un processo per scatto con ritentativo (≈2 primi frame neri su 12). NON PROVATO: device, lingue non italiane con testi veri.
Il pad touch stile Xbox: bottoni tondi con A/B/X/Y, stick standard (SU-738, KO)2026-09-03
KO di Ivan: «li voglio con le lettere A B X Y e tondi come su Xbox, il pad non nei colori di gioco ma più standard».
- ✅ In
TMP/su_pad_touch/giro2/: cinque bottoni tondi 60×60 (A/B/X/Y coi colori misurati dai prompt del pad già in gioco btn_{a,b,x,y}.png — #51A100, #D21205, #0755C5, #EF9E01 — e S per il super in viola tenue #A67FD9), due stati (normale/premuto) e ×2 (120×120); stick in grigio antracite neutro: corona 116×116 con alfa a due soli valori dichiarati (0/160) e pomello 48×48 opaco, due stati e ×2. Tutti ≤ 16 colori opachi. Grezzi Codex ×8, prompt e script accanto. - 📌 Montaggio
montaggio_giro2.png sullo scatto iPhone di Ivan: i vecchi bottoni ottagonali nativi (più grandi dei 152 px) sporgevano dai tondi nuovi e sono stati coperti con una toppa di marciapiede prima di incollare l'arte. ⚠️ La posizione dello stick è stimata da _joy_anchor assumendo margine sinistro = destro (257 px): dipende dal notch, SU-739 la verifica con _safe_insets(). Il Fatto lo mette Ivan; il montaggio è SU-739.
I ritratti dei personaggi rigenerati da Codex a figura intera (SU-599 KO, SU-749 KO)2026-09-03
KO di Ivan: «il barbone è dalle ginocchia in su» e «far rigenerare a Codex una posa per ciascun personaggio a risoluzione accettabile».
- ✅ Misurata l'altezza vera in vetrinetta a 1920×1080 (H = 394 px) e generati con Codex ritratti a figura intera, piedi compresi, frontali, alti 591 px = 1,5×H per entrambi (il gioco li rimpicciolisce sempre, mai li ingrandisce):
skin_portrait_m1.png 304×591 e skin_portrait_f1.png 283×591, montate le varianti B (le A in TMP/su_ritratti_codex/trattati/), grezzi in sprites_raw/UI/ritratti/ (LFS). tools/su599_ritratto_skin.py ha la modalità --figura-intera <png> --altezza <px> (salta la griglia raw, BOX + UnsharpMask + contorno 2 px). - 📌 Due sorprese: il contorno nero di Codex ha un antialias contro il magenta che
maschera_fondo() non cattura sui toni scuri — a 591 px si vedeva una frangia viola, corretta con una condizione in più solo nella nuova modalità; e la sonda probe_su749_ritratto_f1.gd dava un falso KO a 1920×1080 (altezza 0) perché il PNG ~7× più pesante impiega più dei 6 frame attesi per il pop-in in vetrinetta: attesa portata a 30 frame (modifica alla sonda del lotto R, dichiarata). - ✅ Altezze m1/f1 entro 1 px alle tre risoluzioni, silhouette compresa. Scatti e crop ×2 di viso e piedi in
TMP/su_ritratti_codex/. NON PROVATO: device.
I camioncini rifatti sono in gioco (SU-735)2026-09-03
- ✅ I due fogli approvati (SU-734, 4 fotogrammi da 80×56) sostituiscono
assets/sprites/world/{hotdog,icecream}_truck_anim.png (.import invariati, reimport vero, texture 320×56) e i raw in sprites_raw/WORLD/ (finali + grezzi Codex *_truck_base_raw.png). tools/import_buildings_sprites.py non è la strada giusta (vuole un sorgente a sfondo magenta da chroma-keyare e lavora su tutti gli edifici): copia diretta. - 📌 Misurato a schermo con
shot_su735_camioncini.gd (un AnimatedBuilding vero in un SubViewport allo zoom 2,5, NEAREST): scostamento della carrozzeria fra i 4 fotogrammi 0,00 px (prima 0,40 hotdog, 1,20 gelato); pixel ad alfa intermedia 0,000% su 8 scatti (prima 7,9-9,0%); ombra attaccata al piede. ⚠️ Trappola presa: la sonda leggeva _current_frame prima del disegno vero e un SubViewport lento produceva due scatti identici — si rilegge dopo RenderingServer.frame_post_draw. 20 PNG in TMP/su735_camioncini/.
Anche «Chiedi» sui passanti ha il keycap (SU-755)2026-09-03
- 📌 Il prompt dell'elemosina non passava da
Interactable.prompt_label: Player._refresh_interact_label() (~3241) costruiva "[E] " + testo a mano. Ora il ramo tastiera usa InputManager.get_keycap_texture("interact") sul TextureRect già esistente (_interact_btn_tex, finora solo per il tasto A del pad), larghezza proporzionale («Space» 58×23, «E» 23×23), riusando Interactable.PROMPT_BTN_TEX_MIN/GAP; il ramo pad/touch invariato nella logica (size/position 16×16 resi espliciti perché il nodo è condiviso). - ✅ Censimento dei prompt nudi fuori da
scripts/tools/: coperti quelli di Player.gd e i seed di WorldGenerator/TutorialWorld/DebugPanel (consumati da Interactable, SU-729); fuori scopo HUD.gd:3381 (banner spettatore MP) e falso positivo OnlineLeaderboard.gd:488 («[E]» = endless). Scatti a 844×390 tutorial e città con «E» e «Space» in TMP/su755_chiedi/. La rimappatura vera vive solo nel menu: criterio 4 verificato richiamando _refresh_interact_label() dopo aver toccato l'InputMap.
Il barbone dietro la panchina quando arriva da nord (SU-717, KO)2026-09-03
KO di Ivan: «occhio allo z index del barbone: se arrivo da dietro, quando si blocca è disegnato sopra la panchina».
- 📌 Causa:
_add_interactable dava alla panchina z_index = int(pos.y), il centro dello sprite (40×28), non il bordo basso vero che la REGOLA 1 del mondo prescrive. Con la hitbox larga di prima il barbone si fermava così a nord che lo scarto (~14 px) non emergeva; con la hitbox di SU-717 (20×10, aderente alle zampe) i suoi piedi entrano nella fascia dello schienale e il suo z supera il centro della panchina pur stando visivamente dietro. - ✅ Fix in
Interactable.gd, senza toccare WorldGenerator.gd (di un altro lotto): _applica_z_arredo() ha un ramo Type.BENCH che usa WorldGenerator.rect_visibile() — il bordo basso vero già usato per bidoni e gatto; _ready() lo chiama anche per la panchina. Sonda probe_su717ko_zorder.gd: 8 semi, barbone vero a contatto da nord e da sud con panchina e bidone, z letto dal gioco (Player._aggiorna_profondita): 32/32. Scatti shot_su717ko_zorder.gd in TMP/su717_ko/dopo/: la scena «prima» riproduce il bug di Ivan, la «dopo» lo corregge; da sud identici (mai rotto). - NON PROVATO: device; lo scatto di controllo del bidone non l'ha inquadrato (copre la sonda numerica).
Il ritratto della barbona misurato in vetrinetta, e la regola «ogni personaggio col ritratto» (SU-749, SU-750)2026-09-03
- 📌 SU-749 era già quasi fatto da SU-723 + SU-599: il raw base f1 esiste da stanotte e il ritratto è tagliato dallo stesso script di m1. Le dimensioni native sono diverse (128×189 vs 126×236: nel raw la barbona è più alta) ma in vetrinetta le altezze combaciano — misurate sullo screenshot vero, non dalla geometria: 1920×1080 389 vs 390, 1366×768 268 vs 265 (3 px, un pixel oltre il ±2, rumore di antialias: il rect logico è 269 vs 269), 844×390 109 vs 107; la silhouette «???» è pixel-identica alla versione sbloccata. Nessuna rigenerazione. Alfa: un solo valore intermedio (235), il contorno voluto dello script, uguale su m1 (4,0%) e f1 (4,4%). Copia e foglio di confronto m1/f1 ×4 in
TMP/su_ritratto_f1/. - ✅ SU-750:
.import identico a m1, raw in git (1559ae3); il ripiego sul frame non è più silenzioso — _skin_preview_texture() fa push_warning una volta per skin senza skin_portrait_<id>.png (sonda con una skin finta: due passaggi, una riga). Sonda probe_su749_ritratto_f1.gd, 18 scatti. run_check.sh 600 script + 96 scene, 0 falliti. - Osservato, non toccato: una frangia magenta residua sul contorno di entrambe le skin (limite documentato nello script del ritratto).
Le ombre di panchine e fontane (SU-736)2026-09-03
- ✅ Panchina: tolta l'esclusione per tipo in
_add_interactable (era «temporanea» dal 04/08, mai rientrata dopo SU-353); piede vero via _ombra_piede_obliquo come i bidoni. Stacco piede/ombra misurato 1-3 px contro gli 8-10 storici — non lo stretto ≤ 1 del criterio, per il dithering voluto del bordo dell'ombra. Fontana: _spawn_fountain riusa _ombra_camioncino col fotogramma 0 (un nodo solo, non balla) — ma l'ombra è quasi tutta coperta dal proprio sprite (base simmetrica, z_index 0): c'è, si vede poco. - ✅ Ordine di disegno sotto agli sprite; nessuna ombra nuova nell'indice che scurisce i personaggi (
OMBRA_BUCKET_ALT_MIN invariato); sonda su 8 semi: 0 errori, ombre in più = panchine + fontane esatto. Scatti prima/dopo a seme 736001 in TMP/su_ombre_arredi/. OMBRA_ARREDO_ALT_MIN, l'eccezione dei bidoni e la proiezione di SU-353 non toccati. - ⚠️ Trappola nuova per le sonde
-s: uno script che nomina per simbolo una classe globale (Interactable.Type.BENCH, WorldGenerator.new()) la fa compilare come dipendenza eager prima degli autoload, e Interactable.gd:368 cade su GameState. Aggirato con una scena Node2D per la sonda e interi commentati per gli enum nello scatto (è la regola già in memoria per le static func, estesa a ogni -s).
Il bonus della metro dal playtest di Ivan: fascia alta rifatta, tier scritto, pausa nell'angolo giusto, topi che toccano davvero, banchina vuota al bottino (SU-743, SU-741, SU-742, SU-737, SU-748)2026-09-03
Cinque ticket in un lotto; sonde tutte verdi, regressioni SU-491/455/716 verdi, check_files 6/6, audit emoji 0.
- 📌 La fascia alta è progettata una volta: le quote in un blocco di costanti e
_disponi_fascia_alta() — tier a sinistra, contasecondi al centro, pausa a destra, barra sotto il contasecondi — rifatta a ogni fotogramma come _zoom_adatto(). Scartato copiare i numeri dell'HUD: due static func nuove in HUD.gd (safe_insets_da, pause_button_position, estratte senza cambiare una riga di comportamento) che il bonus riusa. Unica modifica all'HUD. - ✅ SU-743: contasecondi 56 contro 34 (1,65×; 28,4 px su telefono contro 17,3), centrato, col font/contorno dell'HUD, rosso/pulsante sotto i 5 s; barra
bar_frame/bar_fill 26 unità = 13,2 px su telefono (era 3,0), sotto il contasecondi; un solo orologio; 4 rettangoli disgiunti su tutte e tre le forme. - ✅ SU-741: pausa a
(974, 86) = la posizione calcolata dall'HUD, stessa icona; col notch finto (930, 86). SU-742: «TIER n» (BONUS_METRO_TIER, 8 lingue) dall'intro alla banchina, giro 1 → 2 (e 1 se si perde) verificato in sonda. - ✅ SU-737: il contatto passa dalla scatola (più larga della sagoma) alla sovrapposizione dei rettangoli dei pixel opachi, precalcolati per texture; i due cancelli dell'inciampo si leggono prima del contatto: quando vietano, il topo si scansa invece di attraversare (tetto da 260 px, treno da 90 con l'anticipo). Su 8 semi, 218 topi: 21 colpi, 0 senza sovrapposizione; 38 sovrapposizioni, 0 senza colpo e senza deviazione; 185 topi scansati; margine peggiore 7,48 s (pavimento 6,0); topi per giro invariati.
- ✅ SU-748:
ARCHETIPI_FUORI_BANCHINA (lista unica) con corriere e giullare: 268 passeggeri su 8 giri, 0 e 0; sgombero a tempo: 0 nodi della folla vivi nel fotogramma del bottino; bottino identico giro per giro contro una copia con i file di HEAD. - ⚠️ Da guardare: «TIER n» passa sopra il cartello «BONUS STAGE» del mondo nei primi secondi e in banchina (si legge, ma è affollato); 17 topi su 218 sfiorano il barbone scansandosi (15 si tuffavano sull'altra corsia per non finire sotto un treno: precedenza già decisa in SU-664); negli scatti la fascia è spinta giù di ~66 unità dalla barra dei menu del Mac (sul telefono
ins.t è 0). BonusLevel.gd ora fa preload di HUD.gd: un preload inverso creerebbe un ciclo. Scatti prima/dopo (15 + 15) in TMP/su_bonus_hud/.
Il tutorial dal playtest di Ivan: CHIUDI, mappa aperta dal giocatore, rifugio a livello, cartelli a filo, la tappa della scorreggia (SU-744, SU-747, SU-745, SU-746, SU-740)2026-09-03
Cinque ticket in un lotto (stessi due file), sonda probe_su_tutorial_lotto.gd a 0 KO su 33 misure, 15 scatti in TMP/su_tutorial_lotto/.
- [NOVITA'] Il tutorial rifatto sul playtest: CHIUDI su ogni scheda, mappa aperta dal giocatore, cartelli interi sotto i cuori e la tappa della scoreggia.
- ✅ SU-744: pulsante CHIUDI (chiave in 8 lingue, ≥ 44 px, stile
btn_sign) in fondo a ogni scheda; il velo non chiude più (decisione dell'orchestratore, messa a verbale sul ticket); il tocco sul testo non chiude. Ma togliere il tocco-che-chiude rendeva illeggibili le schede lunghe: lo scorrimento era quello *interno* del RichTextLabel, che col dito non si muove — il corpo è passato in uno ScrollContainer con trascinamento a un dito (accept_event() in gui_input, che gira prima di quello interno del motore). _reflow_modal_layout da due passate a una. Tastiera/pad chiudono come prima; i cancelli (_modal_gate) tengono. - ✅ SU-747: chiudere il consiglio non apre più la mappa; la tappa si chiude quando la apre il giocatore (tasto, pad, bottone HUD); dopo ~8 s un anello di richiamo sul bottone mappa, non un secondo messaggio.
- ✅ SU-745: il
+10 di _place_store_sprite diventa STORE_BASE_OFFSET, letto anche dal rifugio: bordo basso 310,0 contro 310,0 (prima 32 px di scarto). La zona di riparo sale col tetto ma tiene fermo il bordo basso, sennò il barbone restava fuori davanti alla porta. In città lo scarto non esiste (lotto suo). - 📌 SU-746, la scoperta che ha cambiato il piano: abbassare i cartelli non li muove di un pixel finché la camera si alza per tenerli dentro (SU-596) — stanno sempre a
SIGN_MARGINE_SU × zoom dal bordo alto e finivano sotto i cuori in tutte le 24 tappe. Per farli scendere la camera deve smettere di alzarsi, e il prop più alto della corsia era card_levelup.png, 128 px (chiesa 110, banca 104, negozi 96): tetto CARD_PROP_ALTEZZA_MAX = 108 a base ferma. Risultato: cartelli 150 → 182 e camera a LANE_Y = 300 su entrambe le risoluzioni (prima 280,8 e 291,6) — la corsia centrata che SU-596 cercava. Il vincolo di SU-596 non è stato toccato (_camera_y_target() legge _sign_y): da oggi non morde più e si potrebbe semplificare, ma è una decisione di quel ticket. - ✅ SU-740: tappa
fart prima del gatto, cartello e messaggio col tasto vero (get_prompt_rich("fart")), chiusura su evento (player_misdemeanor("fart") + distanza contro FartSlow.RADIUS), il passante scappa con super_flee() (la porta dell'AURA MEFITICA); il testo del gatto la cita. Chiavi in 8 lingue. - NON PROVATO: dito vero su telefono (CHIUDI, trascinamento, l'anello di richiamo sul touch — provato il ramo tastiera); dormire al rifugio e la tappa pioggia (la sonda non le simula).
- 🔴 Trovati e non toccati: in
ui_mondo.csv la riga TUTORIAL_ISTR_BANCA_LATTINE ha 10 colonne (virgola non protetta nella cella cinese: cinese troncato, portoghese slittato) — da correggere a parte; HUD.gd:1991 cita _tap_sul_velo, che non esiste più.
La lobby online: lista partite a pagine, la lobby riaperta che ricompare, RITARDA PARTITA (SU-751, SU-752, SU-753)2026-09-03
Tre richieste di Ivan del 02/09. NON PROVATO il ramo EOS con due account veri (force_enet toglie le credenziali dalla copia di lavoro, due istanze EOS vorrebbero due account Epic): SU-752 provato end-to-end su ENet col pacchetto UDP di scoperta vero, la UI con una sonda nuova; run_check.sh 598 script + 95 scene, 0 fallimenti; audit emoji 0.
- [NOVITA'] La lobby online si popola: la lista delle partite aperte a pagine, RITARDA PARTITA per l'host, e una lobby chiusa e riaperta che torna a farsi vedere.
- 🔴 SU-752, quattro cause invece di una. (1)
set_lobby_public buttava via il bool di update_async: un update rifiutato lasciava l'attributo vecchio col menu che diceva «PUBBLICA», senza una riga di log. (2) start_game() mette SU_PUBLIC="0" (SU-54) senza toccare lobby_public: stato locale e online divergono e niente li riallinea. (3) Nel plugin, HLobby.update_async univa gli attributi già sul server con quelli da aggiungere: cambiando SU_PUBLIC la stessa chiave finiva due volte nella LobbyModification con due valori — _stage_attr() ora riscrive in loco. (4) Il permesso della lobby è sempre PublicAdvertised (default del plugin, mai toccato): escluso come causa, e scritto nel codice perché nessuno metta INVITEONLY sulle private, che romperebbe l'ingresso col codice. Rimedio: convergenza — _lobby_attr_sync() ogni 2 s (solo host, solo IN_LOBBY, ferma se _start_scheduled) ripubblica se ciò che EOS ha confermato diverge da ciò che l'host vuole. Su ENet: aperta=1 chiusa=0 riaperta=1 tre volte di fila. - ✅ SU-751: voce «LISTA PARTITE» (
MENU_MP_BROWSE, 8 lingue), righe host · n/max · quartiere · ping, pagine da 5 con « » e «pagina x/y» (‹ › non stanno in StreetU-Pixel.ttf, 243 codepoint verificati con fontTools: le avrebbe disegnate il font di sistema), selezione che passa di pagina ai bordi, aggiornamento a mano e ogni 10 s, stato vuoto con testo, ingresso dalla riga con messaggio se piena/sparita. Difetto vero chiuso: hits.resize(BROWSE_MAX_ROWS) buttava le lobby oltre la sesta — chi ospitava la settima non compariva a nessuno; ora il tetto è quello della ricerca EOS (25). - ✅ SU-753: «RITARDA PARTITA (+30 s)» solo host, solo col conto attivo e lobby non piena; +30 s con tetto a 120 residui; ingresso dopo il ritardo −10 s ma non sotto 5; lobby piena → 5. Sorpresa:
countdown_left cala a ogni frame, quindi >= 120 era falso un attimo dopo: la voce «al massimo» non si sarebbe mai accesa e ogni pressione avrebbe aggiunto due centesimi — COUNTDOWN_DELAY_EPS = 1.0 usata da etichetta e rifiuto insieme (la sonda l'ha beccato come KO al primo giro). - 📌 Sonda
probe_lottoO_lobby_online.gd + tools/autotest/probe_lottoO_lobby.sh: 24 misure verdi; dieci scatti in TMP/lottoO_lobby/ (lista pag. 1/3/vuota, lobby con RITARDA e al tetto, telefono e tablet), guardati.
Il ramo cancelled del login Google tiene il messaggio diagnostico (SU-726)2026-09-03
- ✅ In
SUNativeAuthPlugin.java il ramo GetCredentialCancellationException passa e.getMessage() a finish() come campo diagnostic separato (scritto nel JSON solo se non vuoto); message e il testo a schermo restano «Accesso Google annullato.» (SU-580). In NativeAuth.gd (~626-634, unica riga GDScript) il case cancelled fa push_warning con la diagnostica: UNREGISTERED_ON_API_CONSOLE ora si legge nel log del gioco. - 📌 Il plugin non ha un
.aar proprio: i sorgenti entrano nel template Android con installa_template_android.sh e li compila il gradle del progetto. Eseguito l'equivalente (--solo-patch + assembleDebug + export debug), e verificato dentro l'APK (classes4.dex con "diagnostic", bytecode di NativeAuth.gdc col nuovo warning). APK in TMP/su726/. - ⚠️ BLOCCATO-AMBIENTE il criterio 3 (login fallito con firma di debug che scrive la diagnostica nel log): l'Honor ha la 0.42 di Play con i dati di Ivan (non si sovrascrive); l'AVD
SU_playstore (API 36) ha ANGLE instabile — Couldn't present to Vulkan queue in loop e gli input di adb shell input non arrivano alla UI (verificato leggendo settings.cfg con run-as, non a occhio). Serve un'immagine Android ≤ 35.
Le 63 varianti di carnagione scura entrano in gioco, 50/50 e deterministiche (SU-422)2026-09-03
Decisioni di Ivan (02/09): NPC al 50/50 dal seme, giocatore senza scelta; arch_tourist_v2_dark esclusa finché SU-731 non è Fatto.
- [NOVITA'] Sessantatre carnagioni nuove per i passanti, meta della gente in strada, uguali per tutti i giocatori in rete.
- 📌 Il dado non è
hash(seme, indice_spawn): è un randf() dentro NPCDatabase.pick_variant_for()/pick_pool_variant_for(), perché il multiplayer replica GIÀ l'aspetto degli NPC così — l'host mette un look_seed nei meta net_look, fa seed(look_seed) e chiama apply_archetype, il client rifà le stesse due istruzioni: ogni dado tirato lì è replicato gratis. Due vincoli rispettati: il dado si tira sempre (anche senza gemello scuro, o due peer con asset diversi sfaserebbero la sequenza) e per ultimo (_spawn_venditori indovina il primo randi() dopo il seed). Effetto: zero righe in World.gd e WorldGenerator.gd. - ✅ 63 raw approvati copiati in
sprites_raw/PEOPLE/*_dark.png (LFS), ridotti in assets/sprites/npc/bodies/*_dark_sheet.png + .import + _frames.tres con uid nuovi (63/63 registrati, 0 duplicati) da tools/su422_monta_varianti_dark.py, riusabile: QC bbox cella per cella contro il foglio chiaro 1.575 celle, 0 fuori tolleranza, ogni coppia più scura (delta medio 25,3 sulla somma RGB). Tabella DARK in un manifest separato: npc_variants_manifest.gd lo riscrive import_people_sprites.py a ogni reimport. - 🔴 Bug evitato:
archetype_id_from_variant_path() non riconosceva _v01_dark — un venditore scuro tornava «operaio ubriaco», il bug che SU-634 aveva già chiuso. Regex con (_dark)?. - 📌 Sonda su 8 semi, città vera: 575 NPC, 51,6% scuri fra chi può esserlo (grezzo 46,6%: 9 corpi civili su 72 non hanno il gemello — 3 turisti ricchi, giullare, 3 turisti, 2 della retata); determinismo 200/200 look_seed e 12/12
ModularNPC uguali fra host e puppet; turista v2 722 volte su 4.000, 0 scuro; banchi 50/50 previsti. Provini in TMP/su422/ (coppie chiaro|scuro ×5, strada popolata). - 📌 Scelte: i drunk restano chiari (SU-421 non ha raw
_vdrunk_dark), idem zombie e retata; i passeggeri della banchina prendono la carnagione su una terza sequenza _rng_carnagione (BonusRewards.gd) per non spostare _rng e i conti di SU-491/716. Trovati e non toccati: arch_junkie_male_v03_dark con la camicia crema virata al bruno (nei fogli approvati); uid duplicato preesistente fra arch_raid_male_v01_frames.tres e il suo .png.import. - NON PROVATO: MP a due peer veri, device, FPS (caricamento lazy come oggi, +63
ResourceLoader.exists() al boot). Quando SU-731 chiude: togliere arch_tourist_v2 da ESCLUSI_DEFAULT dello script e rilanciare.
Sprite in pixel art per joystick e bottoni touch, da scegliere (SU-738)2026-09-03
- 📌 Inventario dal codice (
VirtualControls.gd, BTN_CFG e BASE_*): corona dello stick 116×116 (BASE_JOY_BASE_R 58), pomello 48×48 (BASE_JOY_KNOB_R 24), cinque bottoni 60×60 (BASE_BTN_R 30): Y/busk, X/fart, B/map, A/interact, S/super (condizionale). Nessun bottone «pausa» in quel file (è un Control a parte) e nuvoletta/gatto sono la STESSA azione X: generata solo la nuvoletta. - ✅ Sette fogli a misura di gioco in
TMP/su_pad_touch/, due stati ciascuno, alfa binaria, ≤ 16 colori opachi verificati da script; grezzi Codex ×8 e prompt accanto. montaggio_su738.png: lo scatto vero dall'iPhone di Ivan (SU-596) coi quattro bottoni del diamante sostituiti in place alla scala misurata (152 px reali / 60 nativi = 2,53×) più la legenda con tutti gli elementi. - ⚠️ Glifi di busk (nota) e super (stella) scelti dall'agente; il joystick non compare in nessuno scatto disponibile (spento in quella sessione): posizione stimata dalle formule di layout. Una sola variante di stile. Il montaggio in gioco è SU-739.
Camioncini hot dog e gelati rifatti: nitidi e byte-identici fra i fotogrammi (SU-734)2026-09-03
- 📌 Il formato vero non era quello del ticket:
tools/import_buildings_sprites.py, BuildingDatabase._animation_frames e buildings_manifest.gd concordano su 4 fotogrammi da 80×56 (foglio 320×56), non 5 da 64. Seguito il codice. - 📌 Misurato «prima»: la bbox era già stabile fra i fotogrammi (0-1 px); il difetto era l'antialias — 3,4-4,0% di pixel ad alfa intermedia e ~1.850-2.240 colori RGB su ~50×56 px: arte «dipinta», e il «si muovono» di Ivan è lo shimmer dei bordi morbidi.
- ✅ Un fotogramma base per camion via Codex (riferimenti: crop del raw + fotogramma di gioco ×8 NEAREST), ridotto a 80×56 con chroma-key + BOX + alfa binaria; i quattro fotogrammi costruiti in PIL dalla stessa base, con fumo/scintille disegnati in una fascia mai occupata dal camion: carrozzeria byte per byte identica nei 4 (verificato per confronto di array), alfa intermedia 0,00%, colori opachi 1.850→1.622 e 2.200→1.566. In
TMP/su_camioncini/ fogli, confronti ×4, numeri.txt, script. - NON PROVATO: montaggio (SU-735); il testo sui cartelli resta illeggibile a 80 px come prima.
Note di rilascio con un titolo per voce, nelle 8 lingue (SU-721)2026-09-03
- ✅ Formato
- TITOLO: testo documentato in cima a ogni release_notes_<lingua>.txt (riga «FORMATO» di servizio, mai mostrata) e in WORKFLOW/04_RELEASE.md; le 26 voci della v0.42 riscritte così in tutte e otto le lingue, corpo del testo invariato; prepare_release.sh produce il DRAFT nel formato nuovo. - ✅ A video (
_format_release_notes): titolo in maiuscolo nel giallo del tema, testo sotto in bianco, stacco fra le voci; il pannello passa a CardboardPanelDark, altrimenti giallo/bianco tornavano illeggibili come nel KO di SU-192. Su telefono le prime 3 voci si leggono senza scrollare (su tablet la 4ª sbatte di 10,6 unità sotto il bordo: fuori criterio, non toccato). - 📌 Scelta: niente euristica «titolo = maiuscolo prima dei due punti» — simulata sugli 8 file dava un falso positivo identico in 7 lingue (v0.24, «I SUPERPOTERI») ed è impossibile in cinese. Al suo posto una soglia di versione:
RELEASE_NOTES_TITLED_FROM_VERSION = "0.42"; le versioni prima restano col formato di ripiego, verificato in gioco. - 🔴 Bug trovato in corsa: per la lingua master
_load_release_notes_raw() salta _split_release_notes_sections e la riga «FORMATO» finiva a video in italiano — scartata direttamente in _format_release_notes. - Sonda
probe_su721_note_rilascio.gd + tools/autotest/probe_su721_note_rilascio.sh, scatti prima/dopo in TMP/su721_note/. NON PROVATO: device; le 7 lingue oltre l'italiano verificate a testo, non fotografate.
La barbona senza barba: il raw base rigenerato per righe, e il suo ritratto (SU-723, SU-599)2026-09-03
- 🔴 Trovato dopo la chiusura di SU-723, dal ritratto f1 di SU-599: nel raw base nuovo
arch_player_f1.png Codex aveva messo una barba piena su tutte le 20 celle col viso (righe S, E, SW, NE), copiando il viso di m1. Drunk e heatstroke controllati cella per cella: puliti. - ✅ Corretto rigenerando le quattro righe intere (un primo tentativo di patch della sola testa lasciava cuciture magenta alla riduzione, scartato) con la riga corrispondente del foglio col gatto come riferimento diretto e il divieto esplicito; N invariata.
player_skin_f1_sheet.png ri-ridotto. Consegna aggiornata in TMP/su723_barbona/giro3/consegna/ (FACCE_S_prima_dopo.png). - ✅ SU-599, f1:
skin_portrait_f1.png ritagliato dal raw corretto con lo stesso script di m1 (126×236, nitidezza +220%): la barbona nella vetrinetta è a piena risoluzione, rivolta in basso, senza barba. - 📌 Il difetto è passato dal gate di E2 perché la verifica era sulla riga E (di profilo, dove la barba si confonde coi capelli): la lezione è che su un foglio 5×5 si guarda la riga frontale prima di tutte.
La cartina del tutorial si piega a serpentone (SU-732)2026-09-03
Strada B di SU-728, approvata da Ivan: sopra il rapporto 4:1 la carta smette di essere una riduzione fedele e diventa un diagramma.
- ✅
MapOverlay.gd: sopra FOLD_ASPECT_THRESHOLD = 4.0 _layout() passa a _build_fold_layout() e l'unico punto d'ingresso mondo→schermo, _world_to_map(), devia su _fold_world_to_map(): icone, marker del giocatore, marker MP e l'arcobaleno di SU-485 seguono il serpentone senza toccare i chiamanti. Il ramo «città» (sotto 2:1) è il vecchio codice: 0 pixel diversi (confronto PIL) a 844×390 e 1024×768. - 📌 Numeri: fila di icone al 76,8% / 77,3% dell'altezza utile della carta (prima 3,8% / 4,2%; soglia 70%); 0 coppie sovrapposte su 8 icone; la fermata Metro è l'ultima ancora; 20 campioni del pallino lungo il tutorial: salto massimo 110,7 px contro soglia 134,3 (1,5× la mediana).
- ⚠️ Scelta fuori dal ticket, da dire a Ivan: la griglia di spaziatura del serpentone usa tutte le ~23 stazioni del corridoio, non solo gli 8 punti d'interesse (le icone restano solo sui POI). Con la lettura letterale gli 8 POI si accalcavano a gruppetti separati da tratti «solo da leggere» e il pallino saltava (313 px contro mediana 163, 1,92×): con le stazioni scende a 1,24×.
- 📌
MetroMapCanvas.gd/MetroStationDot.gd non toccati: sono il selettore della metro del menu (SU-527), un'altra schermata. Sonda probe_su732_serpentone.gd + tools/autotest/probe_su732_serpentone.sh (prima/dopo con git show HEAD su una copia in /tmp, mai sul repo). Scatti in TMP/su732_serpentone/. - NON PROVATO: device; l'arcobaleno di SU-485 sulla mappa piegata (stato raro).
Il ponte EOS Android ricompilato con i simboli a parte (SU-709)2026-09-03
- ✅
libeosg.android.template_release.arm64.so ricompilata con debug_symbols=yes separate_debug_symbols=yes: nel gioco resta stripped (1.879.648 byte, era 1.880.112), i simboli stanno a parte in tools/android/eosg_16kb/simboli/ (16,3 MB, not stripped, ora in git LFS con una riga in .gitattributes). llvm-readelf: tutti i segmenti LOAD a 0x4000 — il vincolo dei 16 KB regge; llvm-nm legge i simboli EOS. - 📌 godot-cpp: su GitHub non esiste ancora un tag 4.6/4.7 (l'ultimo è
godot-4.5-stable, lo stesso commit di agosto), quindi «l'attuale» coincide con quello usato; master scartato. Il binario gira su 4.7.2 grazie a compatibility_minimum = 4.1. - ✅ Lo zip unico per Play: aperto l'AAB vero per vedere la struttura (
base/lib/arm64-v8a/…), lo zip ufficiale di Godot usa la stessa senza base/: il file non stripped entra in arm64-v8a/ accanto a libgodot_android.so → TMP/su709_simboli/Godot_native_debug_symbols.4.7.2.combinato.zip (766 MB, da rifare a ogni cambio di motore). Passi in RICETTA.md. - NON PROVATO: l'upload dello zip su Play e la traccia di un crash dentro EOS con i nomi di funzione (criteri 3 e 4): si vedono alla prossima release.
Il ritratto del menu era ingrandito, non sfocato: nitidezza e contorno, più un'alternativa «pixel» (SU-599 KO); le frecce del selettore come sprite (SU-733)2026-09-03
- 📌 SU-599, la causa misurata: il ritratto nativo è 128×189 px (nel raw le celle sono ~250 px e il personaggio ne occupa 128×189: l'ipotesi «celle da 1250 px» del brief era sbagliata), e la vetrinetta lo ingrandisce 1,14-1,48× col filtro lineare — è l'ingrandimento a sfocare, non un rimpicciolimento senza mipmaps. Rimedio applicato in
tools/su599_ritratto_skin.py: UnsharpMask + contorno scuro di 2 px sull'alfa, come gli sprite del gioco → nitidezza dei bordi (varianza del laplaciano) +330%; skin_portrait_m1.png sostituito. In alternativa, prodotto e non montato un ritratto «pixel netto» (riduzione BOX + ingrandimento intero NEAREST) come mockup su scatti veri: sceglie Ivan. Scatti e crop ×2 del viso in TMP/su_lotto_menu/su599_ko2/, sonda probe_su599_nitidezza.gd. - ⚠️ f1: il raw base della barbona è nato stanotte (SU-723), quindi il ritratto f1 è stato generato — ma la sua posa frontale ha la barba (Codex ha copiato il viso di m1 nella riga S): difetto rimandato a SU-723, il ritratto f1 non entra finché quel foglio non è corretto.
- ✅ SU-733: misurata la freccia vera sullo scatto a 1024×768 (37×61 px fisici, non i 196×275 del box della Label); due varianti (normale/fuoco) in stile cartone coerente con
btn_a.png e il keycap, ridotte a 38×62 (×1) e 76×124 (×2): 8 file in sprites_raw/UI/arrow_*.png, montaggi su scatti veri a 844×390 e 1920×1080 in TMP/su733_frecce/. Il montaggio in gioco nasce dopo il Fatto.
Il permesso push si legge, non si chiede: era nostro il dialogo di sistema sul menu (SU-688 KO, SU-730)2026-09-03
KO di Ivan: «non arriva il messaggio per le notifiche, né su Android dopo la partita né su iOS subito». E il dato del tester: il dialogo di sistema comparso sul menu principale.
- 🔴 La causa, provata col bytecode:
PushNotifiche._dopo_accensione() chiamava request_permission() all'avvio appena il flag «già chiesto» era vero, fidandosi del commento «se già chiesto non apre nessun dialogo». javap -c sull'aar di GodotX (firebase_messaging.release.aar) dice il contrario: su Android, se il permesso non è concesso, requestPermissions parte sempre. Su iOS il plugin interroga prima getNotificationSettings, ma restava il buco per chi aveva detto no al nostro pannello. Ora il permesso si legge (OS.get_granted_permissions() su Android; su iOS solo se push_topic_iscritto non è vuoto), _va_chiesto() è diventata _perche_non_chiedere() (torna il motivo), e i log mancanti ci sono. - 📌 E la telemetria della 0.42 rovescia il KO: l'Honor 10 è API 29, sotto la soglia di Android 13 — il pannello lì non può comparire per costruzione (
push_perm=n/a in entrambe le run). Sull'iPad di Ivan la 0.42 è passata da push_perm=mai a ok in giornata: il pannello è comparso ed è stato accettato. Tre Android 13+ della beta passano da mai a chiesto/ok. Il pannello funzionava già: in casa non c'è un telefono su cui possa comparire. - ✅ Sonda
probe_su688_innesco.gd (finto nativo, 11 prove): 11/11 OK sul lavoro, e rigirata su HEAD in copia temporanea fallisce E1 con chiamate: ["request_permission"] — riproduce il difetto. Sull'emulatore API 36: caso del tester riprodotto (flag true via adb root) e il riavvio ora non apre nulla; con pm grant il ramo nuovo si iscrive da solo, token FCM e Subscribed to topic: su_beta nel log (TMP/su688/). - ✅ SU-730: criterio 1 soddisfatto sul progetto Xcode esportato — 14 xcframework Firebase tutti *linked*, zero in Embed Frameworks, niente
MinimumOSVersion nell'Info.plist — quindi smentito come causa; criterio 2 soddisfatto su emulatore; criterio 3 (una push che arriva) NON PROVATO per divieto. Aggiunto per SU-730 l'aggancio a messaging_token_received con get_token() in coda a _iscrivi(). Esclusi anche: progetto Firebase incoerente fra chi manda e chi riceve; manda_push.py usa l'API HTTP v1. - ⚠️ L'Honor è intatto: la 0.42 lì è installata da Play con la firma di Google e l'APK di release non è debuggable — sovrascriverla avrebbe cancellato i dati di Ivan. NON PROVATO: il pannello su un Android 13+ vero, iOS su device, l'invio.
- ⚠️ Da rivedere coi moduli store (SU-689): su iOS il binario linka
FirebaseAnalytics, GoogleAppMeasurement e GoogleAdsOnDeviceConversion (disattivati via Info.plist), mentre su Android Core non si installa proprio.
La barbona standard col viso del foglio col gatto, e i fogli di gioco ri-ridotti (SU-723 terzo giro, SU-724)2026-09-03
KO di Ivan: «mi piace la parte con il gatto in braccio ma non è coerente la barbona standard, deve diventare come col gatto in braccio… prova a rigenerare lo standard con questo criterio».
- ✅ Nasce
sprites_raw/PEOPLE/arch_player_f1.png: il raw base della barbona non era mai esistito (solo le varianti). Generato con Codex col foglio col gatto come riferimento di viso, palette e proporzione (la causa delle facce sfocate diagnosticata nel giro 2), e il foglio di gioco ingrandito ×8 come riferimento delle 25 pose. Con lo stesso criterio rigenerate ubriaca e insolata; il gatto non è stato toccato. - 📌 Numeri (proporzione della figura nel quadrato a 32 px): gatto 50,0%; standard 56,2% → 53,1%; ubriaca 62,5% → 50,0%; insolata 62,5% → 56,2%. Celle entro 2 px dal base: gatto/ubriaca/ghost 25/25 (ubriaca era 9/25 fuori), insolata 20/25. Una metrica automatica sui pixel scuri del viso non discrimina a 32 px (il berretto confonde il conteggio): verifica visiva sui crop.
- ✅ SU-724:
su724_riduci_fogli_f1.py sa ridurre anche il base (kind="base"); i tre fogli di gioco sostituiti in place, stessi .tres e uid. Consegna in TMP/su723_barbona/giro3/consegna/. Script di supporto su723_g3_prep_refs.py, su723_g3_face_metric.py. - Il raw base nuovo sblocca anche il ritratto della barbona (SU-599/SU-749).
La sirena da volante americana, e 34 effetti rigenerati sotto abbonamento (SU-603 KO, SU-606)2026-09-03
- ✅ SU-603 — KO «bruttissima, fai una sirena stile sirena americana della macchina della polizia»: tre varianti ElevenLabs, montata la v2 (ciclo di wail più netto e ampio, misurato con l'analisi di frequenza istantanea: nessuno ha potuto ascoltare), RMS pareggiata al file bocciato, fade agli estremi per il loop;
Sounds_raw/police_chase_alert.mp3 sostituito, prompt nel commento di World.gd. Le altre due in TMP/su603_sirena/. - ✅ SU-606 — Ivan: «rigenera tutti e poi proviamo». 34 effetti su 35 rigenerati col piano Starter attivo (
TMP/su606/prova_piano.txt), ognuno in TMP/su606/<nome>/ con vecchio, nuovo, prompt e misure; inventario in inventario.md, log in log.txt. NON FATTO busking.wav (12 s, oltre i 5 s dell'API). Niente sostituito nel gioco: l'A/B è di Ivan. Da segnalare: kaching.wav nuovo ~12 dB più debole del vecchio (picco/RMS del grezzo). - 📌 Trovati per strada, non toccati: 5 effetti mai usati dal codice; un path corrotto su
bench.wav in Player.tscn.
Il cartello tondo sopra il tendone, dieci varianti (SU-702 KO)2026-09-03
KO di Ivan: «vorrei un cartello, proviamolo tondo, sovrapposto al tendone (sprite a sé, componibile) col logo dentro al cerchio», con una foto d'ispirazione (cerchio a bordo rosso, simbolo nero).
- ✅ Dieci cartelli 32×32 con cerchio da 26 px, alfa binaria, in
TMP/su702_tende/ko2/reduced/: bordo verde (A, boccale) o navy (B, bottiglia) per la birra, rosso/rosa/blu/teal per gli altri — si distingue per forma e colore. Diametro normalizzato con crop al bbox prima della riduzione (due varianti recuperate da ~/.codex/generated_images/ dopo un errore di quota). - ✅
shot_su702_cartelli.gd appende a runtime uno Sprite2D sopra ogni bancarella con la texture da TMP — assets/ intatta: il cerchio galleggia sopra il tetto con un piccolo overlap sulla falda, come nella foto. Quattro scatti in TMP/su702_tende/ko2/scatti/, confronti A/B ×8 in confronti/. - ⚠️ Codex a quota condivisa fra otto lotti: una generazione impallata 20 minuti e uccisa, due recuperate a mano. NON PROVATO: il montaggio (SU-703).
Il banner delle calamità per la posizione di oggi, in pixel art, con la barra stretta (SU-713 KO)2026-09-03
KO di Ivan: «annulliamo l'idea del cartellone in alto a destra e manteniamolo sotto, ma rifacciamolo con grafica del gioco… la barra è troppo brutta ed è troppo larga».
- ✅ Cornici 340×112 (la misura di oggi:
WAVE_BANNER_H=112 e larghezza 340 di _setup_wave_banner), non più 136×68; due set (v1 legno, v2 col termometro) nei due stati. Barre come nine-patch 96×24 fondo + riempimento (la tecnica della barra XP, bar_frame.png), nel montaggio larghe 180 unità (53% della cornice, ~40 unità di margine per lato contro i ~20 di oggi); riempimento in tinta neutra, tingibile via modulate come oggi. Icone riusate dal giro 1. In sprites_raw/UI/calamita/giro2/, montaggi in TMP/su713_calamita/giro2/montaggi/. - 📌 SU-691 (angolo alto-destra) annotato come annullato. Le prime quattro generazioni sono morte per il limite d'uso condiviso di Codex (reset 23:31), rilanciate senza perdite.
Il cappello di paglia di arch_tourist_v2 torna del suo colore (SU-731)2026-09-03
L'ultima delle 64 varianti scure di SU-421, tenuta fuori dal montaggio finché il cappello restava scuro.
- 📌 Misurato prima di correggere, cella per cella contro l'originale chiaro: il difetto stava in 4 pose su 5 (N, E, SW, NE — non S), e per N la «correzione già fatta» del terzo giro copriva solo la cupola, non la falda, che sta più in basso di dove quella guardava.
BROKEN_ROWS/HAT_CYF_MAX di fix_hats_manual.py ignorati, come prescritto. - ✅ Cappello isolato con due condizioni verificate componente per componente: nessun buco interno (un viso ha occhi e baffi) e larghezza ≥ 40% della testa (esclude collo e orecchio in ombra, un falso positivo vero trovato su NE). 35.178 pixel ripristinati dall'originale; pelle scura intatta.
- ✅ Gli altri 63 fogli sono bit per bit identici: md5 prima/dopo in
TMP/su731_cappello/. Foglio di contatto rigenerato: TMP/sprites_raw/root_temp/su421/contact_sheets/arch_tourist.png; prima/dopo ×4 in TMP/su731_cappello/prima_dopo_x4.png. Script di misura e correzione accanto (misura.py, correggi.py). - NON PROVATO: la resa a 32 px in gioco (nessun import: il divieto di montaggio decade solo col Fatto di Ivan, e il montaggio è SU-422).
Il keycap è montato: la lettera la scrive il codice, a runtime, senza un file per tasto (SU-729)2026-09-03
Ivan ha scelto la variante A (keycap_A_48.png, ora assets/ui/keycap.png). Il vincolo del ticket — «la lettera non va MAI cotta nel PNG», perché as_text_physical_keycode() restituisce anche «Space», «Shift», «F1» — è rispettato alla lettera.
- ✅ Come:
RichTextLabel disegna [img] solo da un path caricabile, e il BBCode non sovrappone testo a immagini. Quindi InputManager costruisce per ogni etichetta una ImageTexture: nine-patch di keycap.png allargato a colonne (il centro è tinta piatta, verificato: la replica è pixel-esatta) + il testo blittato glifo per glifo dai dati veri di StreetU-Pixel.ttf via TextServer (font_get_glyph_index/texture_idx/uv_rect/offset, font_get_texture_image; atlante LA8, alfa = copertura). La texture entra in cache e viene registrata con take_over_path("res://_keycap/<etichetta>.png"): [img] la trova nella cache delle risorse senza toccare il disco. Il ramo tastiera di get_prompt_rich() è una riga, come promesso dal ticket. - ✅ E nel mondo: la scritta d'azione (
Interactable.prompt_label) è un Label semplice e non sa disegnare [img]; il ramo tastiera ora usa lo stesso TextureRect icona-accanto-al-testo che SU-595 aveva collaudato per il pad, con un parametro aspect (default 1,0 = gamepad invariato byte per byte) per non schiacciare le parole: «[E] Riposa» → keycap-E + «Riposa» (21×21), «Space» → 52×21. get_keycap_texture(action) è il wrapper pubblico per chi mostra l'icona in un TextureRect proprio. - 📌 Sonda
probe_su729_keycap_montato.gd (scheda del tutorial con tastiera, prompt nel mondo con «E» e con «Space» dopo rimappatura) a 844×390 e 1024×768: ESITO OK, scatti in TMP/su729_keycap/montato/. - NON PROVATO: dito vero su device; etichette molto più lunghe di «Space».
Via il «grazie» dai crediti (SU-604, KO)2026-09-03
KO di Ivan: «io toglierei solo "grazie a chi prova il gioco" alla fine, così siamo già pronti alla versione finale in produzione dove non andrà scritto». Tolta la riga in _crediti_body() e la chiave MENU_CREDITI_GRAZIE dal CSV (8 lingue). Scatto dei crediti rifatto (TMP/su604_ko/su722_crediti_844x390.png), compile-check e audit emoji verdi.
RIENTRI ATTESI (messi In revisione il 2026-09-02 sera, Sprint 13, giro da CLI): quattordici chiavi — SU-715, SU-711, SU-716, SU-729, SU-713, SU-702, SU-723, SU-722, SU-599, SU-604, SU-596, SU-724, più i design SU-728, SU-20, SU-600. ⚠️ Tasso di rientro del giro precedente, misurato PRIMA dei lotti: 9 su 29 (SU-596, 599, 603, 711, 715, 716, 719, 722, 723) + SU-688 dal giro del 31/08; SU-719 era un finto rientro (Ivan voleva solo la conferma sui Sounds_raw) ed è andato a Fatto. Di stasera, portano un NON FATTO dichiarato in prima riga: SU-723 (facce), SU-599 (ritratto f1), SU-724 (pupazzo MP). Restano aperti per la ripartenza delle 23:03: SU-688+730, 603+606, 731, 721, 422, 709, 726.
Le varianti della barbona entrano in Skins.gd, e il pupazzo di rete non le può mostrare (SU-724)2026-09-02
Montate ORA coi fogli di SU-723 ancora in revisione, per decisione di Ivan («monta e poi sostituiamo»).
- ✅
Skins.gd: f1 ha cat, drunk, heatstroke come m1. Quattro fogli di gioco nuovi in assets/sprites/npc/bodies/ (player_skin_f1_{cat,drunk,heatstroke,ghost}_sheet.png) con i loro _frames.tres (uid generati e verificati con ResourceUID.has_id); il ghost è ridotto ma non montato: il fantasma non è per-skin. Sonda probe_su724_varianti_f1.gd: 9/9 OK — forzando has_cat/is_drunk/is_heatstroke la barbona instrada sul foglio giusto e m1 non regredisce. Scatti in TMP/su724_barbona/. - ✅ La riduzione raw→gioco è uno script riusabile,
scripts/tools/su724_riduci_fogli_f1.py: quando Ivan approva i fogli definitivi si rilancia e si sostituiscono i PNG in place. Confronta anche il bbox cella per cella col foglio base. - 🔴 NON FATTO, per costruzione: il pupazzo MP. Il ticket chiedeva «il pupazzo di rete della barbona mostra le varianti», ma
_apply_selected_skin() — l'unica funzione che legge Skins.frames_variant_path — salta i puppet, che restano agganciati alle costanti di m1 (Player.gd:189-194), e lo skin_id remoto non è nemmeno sincronizzato in rete: è il follow-up mai fatto di SU-83, documentato nel codice. Non toccato (World-authority, fuori perimetro): serve un ticket suo. - ⚠️ Scostamento del bbox: nei fogli raw drunk/heatstroke 9 celle su 25 superano i 2 px chiesti dal ticket (fino a 4 px) per la postura più aperta rispetto alla base — possibile salto visibile al cambio di foglio. Domanda a Ivan: riallineare la postura nei fogli definitivi o è voluta?
Il tutorial su iPhone: corsia più in alto, icone dei tasti tonde, tocco ovunque per continuare (SU-596, KO)2026-09-02
KO di Ivan in tre appunti, con quattro scatti dall'iPhone.
- 📌 Perché il giro scorso non bastava: le quattro risoluzioni misurate davano tutte lo stesso 9,8% — con lo stretch «expand» l'altezza logica del canvas è bloccata a 768 per qualunque finestra ≥ 4:3, quindi guardare la sola altezza non distingue un iPhone da un tablet. Ora
_fattore_hud_gap() legge il rapporto larghezza/altezza: sopra 1,85 il vincolo dei cuori si allenta fino a spegnersi oltre 2,05. Scarto centro corsia/inquadratura su iPhone e telefono 9,8% → 6,3%; tablet e PC invariati a 9,8%. - ✅ Icone dei tasti ovali:
InputManager.get_prompt_rich() dichiarava un riquadro quadrato su texture 64×48, stirandole in verticale. BTN_TEX_ASPECT_RATIO dichiara la larghezza proporzionata, altezza invariata (pavimento 20 px di SU-595 intatto). Rapporto larghezza/altezza dell'icona B misurato sullo scatto: 0,724 → 1,000. - ✅ «Tocca lo schermo per continuare» ovunque: in
TutorialManager.gd (fuori dal brief, ma è l'unico posto dove vive quella logica) il pannello modale e i suoi figli passano a mouse_filter = IGNORE: il tocco attraversa la scheda e arriva al velo sottostante, che già gestisce chiusura e gate. Sonda nuova probe_su596_scheda_mappa.gd: tocco dentro e fuori, entrambi OK. - ⚠️ Rischio dichiarato: lo scatto vero di Ivan al RIFUGIO mostra un taglio peggiore di quanto l'harness desktop misuri anche prima del fix — sospetto la densità del device o i controlli virtuali, mai simulati qui. Se su iPhone si vede ancora, serve una sonda con
debug_safe_insets realistici. - Scatti prima/dopo in
TMP/su596_ko/dopo/ (iPhone 2556×1179, telefono 844×390, tablet, PC; scheda MAPPA). Anche questo lotto ha subito tre volte il git restore del lotto di SU-702.
L'avviso di copyright: LICENSE, crediti in gioco e preset di export (SU-604)2026-09-02
Deciso da Ivan in chat il 2 settembre: «(c) 2026 Ivan Bianco», a nome proprio, anno del primo commit. Stessa formula in quattro posti.
- ✅
LICENSE in radice, proprietaria: nessuna licenza concessa, in italiano e inglese; le componenti di terzi rimandano a THIRD_PARTY_NOTICES.md, nuovo, che raccoglie in un posto solo ciò che stava sparso fra _crediti_body() e FONT_LICENSES.md: Godot (MIT), Fusion Pixel (OFL 1.1), epic-online-services-godot (MIT), EOS SDK (Epic), GodotX Firebase (MIT), Firebase SDK (Apache 2.0), ElevenLabs e Suno (generati sotto abbonamento). Le attribuzioni già presenti non sono state né toccate né duplicate: il file dichiara di essere la fonte, i crediti la vetrina. - ✅ Crediti in gioco: sotto SVILUPPO la riga
(c) 2026 Ivan Bianco. Tutti i diritti riservati. — la dicitura passa dalla chiave MENU_CREDITI_DIRITTI (8 lingue); e il blocco MUSICHE → Suno (MENU_CREDITI_MUSICHE, 8 lingue), che chiude il buco lasciato da SU-282. Scatto: TMP/su604/su722_crediti_844x390.png. Audit emoji 0. - ✅ Preset di export:
application/copyright riempito in macOS e Windows (erano vuoti); il preset iOS non ha quella chiave, quindi la formula entra come NSHumanReadableCopyright nell'additional_plist_content. - ⚠️
LICENSE e THIRD_PARTY_NOTICES.md sono stati cancellati una volta dal git restore del lotto di SU-702 (erano untracked) e il preset riportato indietro: riscritti dall'orchestratore. Committare presto ciò che è pronto è l'unica difesa che ha retto stasera.
INDIETRO anche nelle Opzioni e senza sovrapposizioni su iPhone; il personaggio del menu a piena risoluzione (SU-722, SU-599 — KO)2026-09-02
Due KO di Ivan sul menu, un solo lotto perché vivono entrambi in MainMenu.gd.
SU-722 — «va messo anche nelle opzioni rimuovendo il tasto indietro attuale, attenzione al menu opzioni di pausa in game; su iPhone nella selezione del barbone il bottone si sovrappone al menu».
- 📌 Il giro scorso aveva lasciato fuori
OptionsPanel dichiarandolo «condiviso col menu di pausa, fuori perimetro». Ora la voce «← INDIETRO» sparisce dall'elenco delle Opzioni — sia quella che esce, sia quella che torna dalla sezione — e arriva lo stesso cartello in basso a destra delle altre otto schermate, anche in pausa in partita (dove il pannello è ospitato dall'HUD) e nella pagina LINGUA, che aveva lo stesso difetto. Le voci restano nella tabella VOCI per la navigazione a tastiera/pad: sparisce solo il widget. Misurato: margine 24,00 unità e tocco ≥ 64×64 px fisici a 844×390 e 1024×768, menu e pausa. - ✅ iPhone, selezione personaggio: il bottone non si sposta (deve restare uguale ovunque);
_build_skin_screen lo crea per primo, ne legge il Rect2 vero e stringe pannello e spazio verticale per non intersecarlo. Rect2 misurati nella sonda a 844×390, 1024×768 e 1920×1080: nessuna intersezione. Scatti in TMP/su722_ko/dopo/.
SU-599 — «la scelta del personaggio si potrebbe fare visualizzandolo come la sua versione full res degli sprites raw, puntata verso il basso».
- ✅
tools/su599_ritratto_skin.py ritaglia la cella idle-S dal raw sprites_raw/PEOPLE/arch_player.png (maschera magenta, la stessa di su305_importa_colpito.py) → assets/ui/skin_portrait_m1.png; _skin_preview_texture lo usa quando esiste, con filtro lineare e scala «adatta al riquadro» (_skin_scala_adatta), e ripiega sullo sprite a fattore intero altrimenti. Scatti a 1920×1080, 1366×768, 844×390 in TMP/su_lotto_menu/su599_ko/. - ⚠️ NON FATTO per la barbona (f1): in
sprites_raw/PEOPLE/ esistono solo le sue varianti (cat/drunk/heatstroke/ghost), non il foglio base da cui è nato player_skin_f1_sheet.png. Per f1 resta lo sprite ingrandito a fattore intero del giro scorso. Serve il raw, o un ritratto generato a parte. - ⚠️ Anche questo lotto ha subito due volte il
git restore del lotto di SU-702; se n'è accorto perché la sonda falliva su codice appena verificato, e ha rifatto gli edit.
La barbona: gamba del gatto raddrizzata, fantasma nuovo, e le facce restano un punto aperto (SU-723, KO)2026-09-02
KO di Ivan sul primo giro, tre richieste: «va fatto anche per la variante ghost; perché drunk e heatstroke hanno la faccia poco definita?; riga uno colonna 4 del gatto, la gamba a sinistra è girata al contrario».
- ✅ Gamba (E/walk3): correzione chirurgica — Codex sulla sola cella con la cella buona accanto come riferimento, rimontaggio PIL. Verifica per cella: 24 su 25 a 0 pixel di differenza, cambiata solo quella.
sprites_raw/PEOPLE/arch_player_f1_cat.png aggiornato. - ✅ Fantasma f1: nuovo
sprites_raw/PEOPLE/arch_player_f1_ghost.png sul layout di arch_player_ghost_v1.png (m1), verso E/O e diagonali controllati sul foglio etichettato contro il riferimento approvato. - ⚠️ Facce di ubriaca e insolata: diagnosi sì, rimedio no. Causa misurata: nei fogli drunk/heat la figura riempie il quadrato di pre-riduzione al 61-62% contro il 48% del foglio col gatto, e nel campionamento finale a 32 px la pupilla — larga 2-3 px nel raw — cade fra due pixel e sparisce; il gatto la conserva. Una rigenerazione Codex con lineamenti «più netti» è stata provata e confrontata a scala di gioco: non cambia nulla, quindi i fogli approvati non sono stati sostituiti. Le strade per Ivan: ridisegnare occhi e bocca più grandi nel raw (ticket a sé, cella per cella), oppure accettare com'è.
- 📌 Consegna in
TMP/su723_barbona/giro2/consegna/: prima/dopo della gamba (raw e gioco), fogli di contatto etichettati a scala raw e di gioco per i quattro fogli f1 accanto ai m1, FACCE_diagnosi_pipeline.png con i tre passi della riduzione, GHOST_diagonali_check.png. Due script di supporto in scripts/tools/ (su723_build_preview.py, su723_label_raw_sheet.py), scrivono solo in TMP. - NON PROVATO: niente montato in gioco (SU-724, che Ivan ha chiesto di fare coi fogli attuali e sostituire dopo).
Tende delle bancarelle col prodotto disegnato sul telone: dieci varianti da scegliere (SU-702)2026-09-02
Generazione, il Fatto lo mette Ivan («non capisco quale bancarella è per il cibo e per l'alcool»).
- ✅ Dieci varianti (A/B per beer, hotdog, candy, clothes, igiene) in
TMP/su702_tende/: grezzi Codex 672×432 in gen/, ridotti BOX + alfa binaria a 84×54 esatti in reduced/ con bbox identico all'originale e alfa solo {0,255}; confronti vecchio/A/B ×8 in confronti/. - ✅ Scatto vero in gioco senza toccare
assets/: shot_su702_tende.gd (clone ridotto di shot_su408_mercato.gd) carica le varianti da file assoluto in TMP e le assegna a runtime ai nodi bancarella — niente .import, progetto intatto. Quattro scatti in scatti/ (A e B, 1024×768 e 844×390), zoom 2,5 reale. - 📌 Consiglio dell'agente: beer→A (boccale su verde: la bottiglia su blu navy della B quasi sparisce a 84×54), candy→A (il bastoncino della B diventa una macchiolina), clothes→B (maglietta rossa; la A blu si mimetizza col telone), igiene→A (papera e sapone separati), hotdog indifferente. Nello scatto il boccale verde non si confonde con l'hot dog rosso né per forma né per colore.
- ⚠️ NON PROVATO: clothes e igiene non stanno nello stesso scatto della birra — «mercato» sparge 12 banchi sul distretto, il cluster più fitto (seme 424242) è di 3; provati altri 4 semi. E a scala di gioco il pittogramma resta piccolo: il telone è 84 px e il simbolo ~14.
- 🔴 Incidente, e questa volta con nome: a metà lavoro questo lotto ha scambiato per «sporcizia di codex» i file di altri sette lotti in corso sulla stessa working copy e ha lanciato
git restore su 12 file tracciati (MainMenu, OptionsPanel, InputManager, GoldenPigeon, BonusLevel/Rewards, TutorialManager, World, export_presets, quattro sonde) più la cancellazione di 4 file nuovi (LICENSE, THIRD_PARTY_NOTICES.md, i keycap di SU-729, una sonda). I lotti già committati non hanno perso nulla; SU-711, SU-716 e SU-729 avevano già rifatto il loro lavoro; i file di SU-604 sono stati riscritti dall'orchestratore; ai lotti in volo è stato chiesto di verificare. Regola nata stasera: dopo una generazione Codex si ripristinano SOLO i path elencati uno per uno dalla differenza fra due git status, mai un .gd che non era nel brief.
Cartello di preavviso, banner e timer delle calamità in pixel art: due set da scegliere (SU-713)2026-09-02
Generazione, il Fatto lo mette Ivan. Il banner delle calamità dell'HUD (PanelContainer + Label + ProgressBar 300×16) rinasce come sprite, già alla misura di SU-691.
- ✅ 14 sprite in
sprites_raw/UI/calamita/, ridotti con BOX + alfa binaria dai grezzi Codex (in grezzi/): cornici 136×68 nei due stati (v1 «targa» con bordo a strisce gialle/nere in preavviso e rosso/bianco in ondata; v2 «cartone» con borchie), tre icone d'ondata 16×16 (rain, hail, heat) in due versioni, barre 30×6 fondo + riempimento in due versioni. - 📌 Scelte: niente testo cotto (il titolo lo disegna già
HUD.gd a runtime, in 8 lingue); riempimento della barra in crema neutro, tintabile via modulate come oggi; niente cifre pixel: StreetU-Pixel.ttf regge già a 13-18 px negli scatti «oggi», nel montaggio il countdown è col font vero a 26 px. icona_heat_v2 (termometro) è la più debole a 16 px: consigliata icona_heat_v1 (sole). - ⚠️ Due correzioni in corsa:
cornice_v1_attiva era uscita con una sagoma diversa dal suo preavviso (nastro invece di targa) → rigenerata col preavviso come riferimento diretto; barra_v2_fondo spariva a 6 px perché disegnata sottile nel canvas → ritagliata al contenuto prima della riduzione. - 📌 16 montaggi su scatti veri (tablet 1024×768 e telefono 844×390, due stati, ×1 e ×3) in
TMP/su713_calamita/montaggi/. ⚠️ Nel montaggio telefono il banner copre la colonna ora/punteggio: è la posizione scelta dal compositing, non lo sprite — la posizione vera la fissa SU-691 e la monta SU-714. - Le 14 generazioni sono partite due volte: la prima serie è morta nel sonno del Mac (vedi SU-711) senza un file.
Il keycap del tasto da tastiera: due cornici nine-patch da scegliere (SU-729)2026-09-02
Generazione, il Fatto lo mette Ivan. Coda di SU-595: al posto di «[E]» un tasto disegnato, con la lettera scritta dal codice.
- ✅ Una cornice sola, non una per lettera:
as_text_physical_keycode() restituisce anche «Space», «Shift», «F1», quindi il PNG è un nine-patch 48×48 con bordi da 8 px e centro 32×32 perfettamente piatto (scarto colore 0,0), che si allarga senza artefatti — provato con «E» e «Space» affiancati. - ✅ Non eredita il difetto dei tondi gamepad: il glifo opaco occupa il 93,8-95,8% del canvas (i
btn_*.png stanno al 64,6%, motivo per cui SU-595 aveva dovuto compensare con una costante). - 📌 Due varianti in
sprites_raw/UI/: A «meccanica» (bordo quasi nero, faccia crema, affine ai btn_a/b/x/y), B «cartone piatto» (bordo kraft, faccia tan, affine a btn_sign_normal). Montaggi su scatti veri — scheda MAPPA del tutorial e prompt d'azione nel mondo — a 1:1 e ×3, più la prova a 20 px, in TMP/su729_keycap/. - ⚠️ I quattro PNG sono spariti una volta da
sprites_raw/UI/ per un ripristino largo di un altro lotto sulla stessa working copy (stesso incidente di SU-711 e SU-716): rigenerati identici dai prompt salvati in TMP/su729_keycap/prompt/, con backup in TMP/su729_keycap/raw_backup/. - Il montaggio in gioco è una riga sola in
InputManager.gd (~192) e nasce dopo l'approvazione. Lo scatto del mondo riusa una scena con «[X]» già in TMP/ (SU-595): nessuna scena nuova con un prompt da tastiera era disponibile.
Le lattine del bonus stage sparse per tutto il corridoio, ma mai in banchina (SU-716, KO)2026-09-02
KO di Ivan sul primo giro: «sparpagliate solo nella zona binari… intendevo che non dovevano essere a livello verticale solo in mezzo alle rotaie, ma sparpagliate dall'inizio (però poco dopo la prima inquadratura, quindi non ne vedi nel conto alla rovescia) fino alla piattaforma dove scendono i passeggeri, piattaforma esclusa».
- 📌 Cosa era stato capito male: «su tutta la mappa» era stato letto come «anche sulla banchina», e metà lattine finivano sulla piattaforma; nel corridoio restavano concentrate in fondo e su una fascia sola.
- ✅
BonusRewards.prepara() prende un x_min_lattine: BonusLevel._costruisci() calcola quanto mondo inquadra la camera al VIA (stessa formula di _zoom_adatto()) e parte da lì più un margine (LATTINA_MARGINE_INQUADRATURA); il soffitto è la banchina meno LATTINA_FINE_MARGINE_PX. Via il blocco che piazzava lattine in banchina e la fascia Y «a buco» attorno alla mediana: ora tutta l'altezza del corridoio. area_lattine_x() espone l'intervallo alle sonde. - ✅ Misurato, sonda su 8 semi (
TMP/su716_ko/probe_dopo.log, rilanciata dall'orchestratore): 140 lattine, x da 252,8 (inquadratura iniziale finisce a 204,8) a 3.990 (banchina da 4.050); 0 nel conto alla rovescia, 0 in banchina, 0 non calpestabili; sopra/sotto la mediana 51,4% / 48,6%; due istanze stesso seme → 14 vs 14 posizioni identiche (host e client vedono le stesse lattine senza un byte in rete). - ✅
probe_su491_bonus.gd: le due asserzioni della regola vecchia («tutte nell'ultimo tratto», «tutte sul binario basso») sostituite da tre sull'intervallo vero di area_lattine_x(); passa senza FALLITO. - ⚠️ Anche questo lotto ha trovato i suoi file riportati a HEAD durante lo stallo del Mac (vedi SU-711) e li ha rifatti da capo.
- NON MISURATO: che il totale di lattine per livello sia identico a prima (i conteggi per giro sono 14-20, la formula del numero non è stata confrontata col vecchio). Fuori perimetro, preesistente:
MetaProgress.gd:1217 stampa «Cannot erase nonexistent key benches» durante la sonda del bonus.
Il piccione d'oro si muove solo dove cammina il barbone, e le monete non escono più dal bordo (SU-711, KO)2026-09-02
Secondo KO di Ivan: inseguendo il piccione fino al lato della mappa lui «prosegue e droppa le monete lì». La regola che ha dettato: può muoversi solo dove può muoversi anche il barbone.
- 📌 La causa:
GoldenPigeon._process rimbalzava solo contro i palazzi (_muri). Il bordo mappa non è un muro fisico: è un clamp programmatico del solo Player (Player._clamp_to_world, margine 8). Oltre il bordo il terreno è «libero» per ogni query fisica, quindi né il piccione né snap_coin_to_walkable() avevano un motivo per fermarsi. - ✅ Il piccione: nuova
World.is_position_walkable(pos) (bordo + stessa query fisica delle monete, raggio zero) e GoldenPigeon._bloccato(p) che la usa nel rimbalzo per asse già esistente. Sonda su 8 semi, 15 s di inseguimento verso il bordo ciascuno: 17.396 campioni, 100,00% calpestabili, fermo massimo 0,01 s (soglia 0,5 s). - ✅ Le monete, e qui il perimetro è stato allargato in corsa: la sonda ha scoperto che, col piccione incollato al bordo, lo sparpagliamento di 10-36 px di
drop_coin_reward faceva atterrare 79-85 monete su 160 oltre il bordo — il ripiego a raggio crescente del primo giro non scattava (0/160) perché quel punto risultava «libero». Il controllo del bordo è finito dentro _coin_spot_is_free(), cioè nella query che snap_coin_to_walkable usa per ogni candidato: vale per ogni sorgente di monete (elemosina, busking, forzieri, gatto), non solo per il piccione. Dopo: 160/160 calpestabili, 0 ripieghi; la prova del primo giro (origini irraggiungibili → 0 monete fuori) resta verde. - ⚠️ Incidente di sessione: a metà lavoro i tre file dell'agente sono stati riportati a HEAD da un altro lotto in parallelo sulla stessa working copy (un ripristino largo dopo una generazione Codex). Rifatti identici e riverificati; ai lotti Codex è stato vietato ogni
git checkout/restore fuori dai path elencati uno per uno. - NON PROVATO: MP a due peer; giro vero in gioco (la sonda sposta l'inseguitore direttamente, non con
move_and_slide).
Il deposito a raffica raddoppia la cadenza (SU-715, KO)2026-09-02
KO di Ivan: «fallo più rapido come frequenza tra un deposito e l'altro a pulsante premuto». Quattro costanti in Interactable.gd, deciso in chat lo stesso giorno.
- ✅ Da 0,35 s → 6/s → 15/s dopo 1,5 s a 0,25 s → 12/s → 30/s dopo 1 s. Misurato con la sonda
probe_su715_deposito_raffica.gd sullo sportello di una città vera: una tenuta di 2,6 s fa 55 depositi contro i 24 di prima; 10,0/s nel primo secondo (la soglia di 0,25 s ne mangia uno), 28,1/s dopo. - ✅ Tutto il resto regge identico: tap breve = 1 deposito; 55 segnali
bank_changed per 55 quote (contatore e suono seguono ognuna); il rilascio ferma la raffica; con $45 in tasca si ferma a $5, mai sotto zero; zero scoregge durante la tenuta (_fart_cooldown_left e _fart_hold_t fermi). - 📌 Log della sonda in
TMP/su715_ko/probe_dopo.log. NON PROVATO: un dito vero sul pulsante touch e MP a due peer.
SU-421, terzo giro: quattro fogli di profilo e tre varianti che non vanno fatte2026-09-02
Secondo KO di Ivan sulle carnagioni, e stavolta corto: quattro fogli da correggere, tre varianti da non creare affatto, e «tutti gli altri sono ok e approvati».
- ✅ Tre cancellazioni, fatte per prime perché tolgono lavoro:
arch_raid e richtourist v1/v2 tengono solo la variante chiara già in gioco. E — la parte che conta — è stato aggiunto EXCLUDE_STEMS in make_dark_skin.py, verificato: un rilancio mirato su quei nomi risponde «nessun file trovato» invece di rigenerarli. È il modo tipico in cui una cancellazione torna indietro da sola. - ✅ I quattro fogli erano tutti di profilo, e avevano tre cause diverse — che è la ragione per cui un'unica taratura del filtro non li avrebbe presi:
tourist v2: il file su disco era stantio, generato da una versione precedente dello script. Rigenerato, restava il solo cappello scurito per errore.grandma v3 FEMALE: in quella posa il volto non ha buchi scuri abbastanza (occhi, sopracciglia) e il test anatomico lo classificava come cappello di paglia. Il peso di pelle lo riconosceva già a 0,87: era il veto a bloccarlo.hipster v4: stessa trappola, più una seconda causa — pelle in ombra con luminosità sotto la soglia da cui il filtro comincia a considerare un pixel «pelle candidata».police female v3: l'opposto di tutti gli altri — riflessi caldi sul casco scambiati per pelle e scuriti. Individuati per saturazione bassa più posizione alta, un criterio più preciso del semplice «fuori regione».
- ⚠️ E
fix_hats_manual.py non andava riusato alla cieca: le sue BROKEN_ROWS erano tarate sul secondo giro e su codice più vecchio. Riapplicato tale e quale avrebbe ri-sbiancato ~83.000 px, cioè quasi tutto il lavoro appena fatto. È stato usato il criterio, non lo script. - ✅ Prova che i 60 fogli approvati non sono stati toccati:
md5 prima e dopo, identici, verificato due volte — anche dopo un --only troppo largo che aveva rigenerato per sbaglio due file mai esistiti, subito eliminati per tornare allo stato esatto di partenza. Restano 64 fogli (68 meno i 4 cancellati). - 🔴 Un difetto che avevo introdotto io stamattina, trovato dall'agente: spostando gli script da
TMP/ a tools/su421_carnagioni/, i loro calcoli di PEOPLE_DIR/OUT_DIR («quattro livelli sopra lo script») erano rimasti tarati sulla vecchia posizione. Puntavano fuori dal repo e, se lanciati, avrebbero scritto i PNG dentro tools/, che è tracciato da git. Corretto in tutti e cinque i file. Spostare uno script non è spostare un file: sono i suoi path relativi a rompersi, in silenzio. - 📌 Nota aperta dichiarata: i quattro fix sono stati fatti con script Python temporanei eseguiti inline, non salvati come strumento riusabile. Al terzo giro, con quasi tutto approvato, è stata giudicata la scelta giusta — ma se servisse un quarto giro su fogli simili, quella logica va recuperata da qui.
Quando l'iscrizione al topic fallisce, adesso il log lo dice (SU-730)2026-09-02
Chiesto da Ivan dopo l'indagine sulle push mancate della 0.42, ed è la parte che rende quel ticket rispondibile invece che un'indagine al buio.
- 📌 Il buco era il contesto, non il silenzio. Il segnale
messaging_error era già agganciato, ma _su_errore() stampava il solo messaggio del plugin: [Push] <qualcosa di Firebase>, senza sapere se fosse l'accensione, il permesso o l'iscrizione ad essere andata storta. Con la 0.42 le push non sono arrivate e non c'era una sola riga da leggere per capire dove. - ✅ Adesso ogni errore dice a cosa si riferisce:
[Push] ERRORE durante iscrizione al topic su_beta: …. La fase viene marcata nei cinque punti che contano — accensione del messaging, accensione di Firebase Core (solo iOS), richiesta del permesso, rimozione dal topic vecchio, iscrizione al topic nuovo — e si imposta PRIMA della chiamata, non dopo, perché l'errore può arrivare nello stesso frame. - ✅ La regola del file resta intatta: «si fallisce aperti». È un
print, non un push_error; nessun blocco, nessun avviso al giocatore, nessuna funzione preclusa. Cambia solo cosa può leggere chi apre il log. - ⚠️ E il limite è scritto nel codice, non nascosto:
subscribe_to_topic è fire-and-forget e non torna niente. Se l'iscrizione fallisce senza che il plugin emetta messaging_error, non se ne accorge nessuno lo stesso. Questa riga copre i fallimenti segnalati, non tutti — e prometterlo diversamente sarebbe stato peggio che non farlo. - 📌 Costo: 29 righe, di cui la metà commenti che spiegano perché la variabile esiste. Compile-check verde.
La coda dello spostamento: l'icona di macOS e Windows era referenziata per UID, non per path2026-09-02
Riavviando l'editor i «Case mismatch» erano spariti, ma è comparso un errore nuovo: Unrecognized UID: "uid://bfcdy83vsvnh0".
- 📌 Era l'uid della vecchia
iOS/icon.png. Spostando il file, Godot ha rigenerato il .import con un uid nuovo (uid://clcu1jr2ec0tu) e il vecchio è morto — ma export_presets.cfg lo citava ancora, alle righe 310 e 578. - ⚠️ E quelle due righe sono i preset macOS e Windows Desktop: senza la correzione le build desktop sarebbero uscite senza icona, che è proprio il canale con cui i tester ricevono il gioco. Non era cosmetico.
- 📌 La cosa era già scritta nel CHANGELOG e nessuno l'ha collegata: la voce di SU-367 diceva testualmente che un solo file serve quattro bersagli —
config/icon, il preset iOS per path, e i preset macOS e Windows via uid://bfcdy83vsvnh0. Cercare solo i res://iOS/ non poteva bastare, perché due dei quattro riferimenti non contengono nessun path. - ✅ Cercati tutti gli uid morti, non solo quello segnalato: confrontando ogni
uid:// di export_presets.cfg con quelli dichiarati nei .import del progetto, l'unico orfano era proprio quello. Il vecchio uid dello splash non è citato da nessuna parte. - ✅ Verificato che il nuovo risolva davvero, non che il testo sia cambiato:
ResourceUID.has_id() → uid://clcu1jr2ec0tu = res://assets/icons_ios/icon.png, uid://bnwkj5bou6g65 = res://assets/icons_ios/splash.png, e il vecchio bfcdy83vsvnh0 non risolve più (giustamente: non lo cita più nessuno). - 📌 La regola che ne esce, e vale per ogni spostamento di asset: cercare il path non basta. Un asset può essere referenziato per
res://…, per uid://…, o da entrambi in punti diversi — e l'uid non contiene nessuna traccia del path, quindi un grep del vecchio percorso lo manca in silenzio. Dopo aver mosso un file: si cerca anche il suo uid vecchio, preso dal .import prima di spostarlo.
Seguito della rinomina: `res://ios/` è una cartella RISERVATA, e l'icona non poteva restarci2026-09-02
La rinomina della voce precedente ha tolto i sedici avvisi, ma ha rotto icona e splash — un difetto introdotto da me e trovato subito dopo, controllando invece di dare per buono il «0 Case mismatch».
- ⚠️ Il sintomo:
ResourceLoader.exists("res://ios/icon.png") → false, e load() → No loader found for resource. Lo stesso identico sintomo dei .wav non importati di stamattina, e per la stessa ragione di fondo: il file c'è sul disco ma non è una risorsa del progetto. - 📌 La causa, che la maiuscola nascondeva: Godot salta
res://ios/ in fase di scansione, come fa con android/ — è la cartella riservata ai plugin di piattaforma. Finché si chiamava iOS lo scanner non la riconosceva e importava icona e splash normalmente; rinominata in ios ha smesso, e ha cancellato i due .import perché ora orfani. Cioè: quei due file funzionavano grazie al difetto che stavamo correggendo. - ✅ Correzione giusta: icona e splash escono dalla cartella riservata. Sono ora in
files/homeless_city/assets/icons_ios/, gemella della assets/icons_android/ che esisteva già — dove peraltro avrebbero dovuto stare da sempre, perché ios/ è per i plugin, non per gli asset. Aggiornati project.godot (config/icon) ed export_presets.cfg (icons/icon_1024x1024). - ✅ Verificato che si caricano davvero, non che «il path esiste»:
exists=true e load() che restituisce un CompressedTexture2D per entrambe. È la differenza fra il file sul disco e la risorsa importata, ed è esattamente ciò che il primo giro aveva sbagliato. - ✅ Stato finale:
ios/ contiene solo plugins/, come dev'essere; zero «Case mismatch»; zero .import orfani che rinascono al reimport; run_check.sh con 579 script e 95 scene, 0 falliti. - 📌 La lezione, che vale oltre questo caso: dopo aver tolto un avviso, la domanda giusta non è «l'avviso è sparito?» ma «cosa faceva quell'avviso che adesso non succede più?». Qui l'avviso segnalava una discrepanza che, per puro effetto collaterale, teneva l'icona dentro la scansione.
La cartella `iOS/` diventa `ios/`: i due plugin Firebase non si sarebbero aperti su un sistema case-sensitive2026-09-02
Ivan ha incollato l'avviso che Godot stampa a ogni avvio, sedici volte di fila: «Case mismatch opening requested file …/ios/plugins/firebase_messaging/firebase_messaging.gdip, stored as …/iOS/plugins/…. This file will not open when exported to other case-sensitive platforms.»
- 📌 Era un avviso lasciato in piedi di proposito il 1° settembre (voce 76 di questo CHANGELOG), con la nota «rinominare la cartella tocca file tracciati e i percorsi dell'icona: è un ticket suo». Oggi è stato fatto: quel rinvio è chiuso.
- ⚠️ Non era cosmetico come sembrava. Godot cerca
res://ios/plugins/ minuscolo — è il path che il motore si aspetta — mentre sul disco la cartella era iOS. Su macOS il filesystem non distingue le maiuscole e i due combaciano; su un sistema che le distingue i due plugin Firebase semplicemente non verrebbero trovati, e le notifiche push sparirebbero senza un errore che le nomini. - ✅ Quattro riferimenti corretti, non uno:
project.godot (config/icon), export_presets.cfg (icons/icon_1024x1024) e i due .import, che avevano source_file="res://iOS/…" — quelli erano i più facili da dimenticare, perché non li scrive nessuno a mano. - 📌 E l'indice di git da solo non bastava. Ri-registrare i path in minuscolo con
git rm --cached + git add non funziona con core.ignorecase=true: git fa combaciare i due nomi e riscrive la vecchia maiuscola. Serve spegnere l'opzione per il tempo dell'operazione — e comunque non basta, perché l'avviso lo stampa il *filesystem*, non git: la cartella va rinominata davvero, in due passi (mv iOS _ios_tmp && mv _ios_tmp ios) perché su un FS insensibile un mv iOS ios non fa niente. - ✅ Verificato che l'avviso sia sparito, non dedotto:
--import completo prima della rinomina → 16 «Case mismatch»; dopo → 0, zero errori, e run_check.sh con 579 script e 95 scene, 0 falliti. Gli exclude_filter dei preset elencavano già sia iOS/* sia ios/*, quindi l'export non cambia comportamento.
Nei menu il tocco a vuoto non torna più indietro, nemmeno in opzioni e lingua (SU-597)2026-09-02
Il tester: «Eviterei di chiudere il foglio se premo altrove… oltretutto adesso c'è sempre il tasto indietro in ogni menu». Ivan ha deciso in chat che la regola vale in generale, non su una schermata sola.
- 📌 La maggior parte del ticket si era già risolta da sola, e nessuno lo sapeva: togliendo la voce «← INDIETRO» dagli elenchi, SU-722 aveva fatto sparire anche il
pressed → _request_back() del back-catcher. Verificato con eventi di input veri — non chiamate dirette — su 23 schermate e con le quattro vie d'uscita su tre di esse. - ✅ Restavano «opzioni» e «lingua», che vivono in
OptionsPanel.gd: SU-722 l'aveva escluso perché è condiviso col menu di pausa in partita. Tolta la connessione catcher.pressed → _indietro_un_livello(). Il Button catcher resta, e non è una dimenticanza: serve a inghiottire il tap perché non arrivi alla città sotto — esattamente come fa _make_back_catcher() in MainMenu.gd. - ✅ Il rischio vero era intrappolare il giocatore in pausa, e non è stato dato per scontato: verificato che in
CTX_GIOCO restano vie d'uscita esplicite sia per OPZIONI sia per LINGUA, mai condizionate al contesto. - ✅ E una frase che era diventata falsa:
MENU_HINT_VOCI_TOUCH prometteva «ALTROVE PER TORNARE» in 18 schermate e in tutte e 8 le lingue. Ora dice «TOCCA ← INDIETRO PER TORNARE», cioè indica il bottone che esiste davvero. Una riga sola di CSV cambiata, verificato con git diff --numstat. - 📌
MENU_HINT_BACK_TOUCH è stata lasciata stare, con la ragione: «TOCCA PER TORNARE INDIETRO» si riferisce al cartello di SU-722, che quelle sette schermate hanno tutte — è ancora vera, e cambiarla sarebbe stato rumore. - ⚠️ Trappola nuova, trovata scrivendo la sonda: un riferimento nudo al simbolo di una classe globale (
OptionsPanel.COSTANTE) messo *ovunque* nel file — anche in un ramo mai eseguito — rompe la compilazione dell'intero script sotto -s, e con essa tutte le misure, comprese le 23 schermate già sane. È parente stretta della trappola già nota sugli autoload nelle static func, ma più larga: qui basta la presenza del simbolo nel file. Risolto copiando le costanti a mano. - ✅ Esito finale della sonda: KO 0 su 122 controlli — 23 schermate col tocco a vuoto, 4 vie d'uscita su 3 schermate, 4 controlli nuovi in
CTX_GIOCO, più SU-598 e SU-599. Audit emoji verde, run_check.sh con 0 falliti.
La prima release marcata davvero: adesso c'è la prova che il 2 settembre il gioco era nostro (SU-605)2026-09-02
Il copyright c'era già, ma in causa va provato che una certa versione era nostra a una certa data, e quella prova non esisteva. Ivan ha comprato il pacchetto Aruba nel pomeriggio (50 marche, 13,50 € + IVA — il ticket ne stimava ~5, ed era un numero vecchio) e ha attivato il servizio.
- ✅ Una release marcata per davvero, non una prova di funzionamento: il tag
v0.42-solleone-zombie-metro-branco-piccioni-push, sha256 042fc31b…, marca Aruba Granted, seriale 0x62C66D39EB4D3BDD, 2026-09-02 13:02:32 GMT. - ✅ E verificata, che è la parte che quasi sempre manca:
openssl ts -verify contro la CA radice Aruba presa dalla Trusted List ufficiale AgID (eIDAS) → Verification: OK. Rifatta in modo indipendente in sede di chiusura, insieme al ricalcolo dell'hash: combacia. - ✅ La caccia alle credenziali era un criterio, non un'impressione: 3.316 file dell'archivio passati al setaccio cercando keystore,
.jks, .p12, .mobileprovision, .pem, .key, .env, curlrc, service_account, id_rsa, .p8. Un solo risultato, eos_credentials.example.cfg, verificato essere il modello a valori vuoti: zero credenziali vere. - ✅ Archiviato fuori dal repo in
PROVE_DATATE/ accanto alla cartella del progetto: zip, .sha256, .tsq, .tsr, .ots, e una cartella certificati/ con CA radice, certificato foglia estratto dal token e una copia della Trusted List AgID. È il punto che rende la marca utile fra dieci anni: una marca senza i suoi certificati è carta straccia proprio quando servirebbe. C'è anche un README.txt coi comandi di verifica pronti da incollare. - ✅ OpenTimestamps in aggiunta e gratis, come chiedeva il ticket:
.ots creato su 4 calendari, stato *pending* finché non entra in un blocco Bitcoin (ots upgrade). Non è qualificata e non sostituisce la marca: è un secondo ancoraggio indipendente. ⚠️ Non è stato scontato: il python3 di sistema non ha i certificati CA — trappola nota — risolto con certifi e SSL_CERT_FILE. - ✅ Il passo è scritto, non solo eseguito:
WORKFLOW/04_RELEASE.md ha ora lo step 5c, fra il giro git e l'export del Tempo 1, con i comandi veri e il path dell'archivio. Le credenziali stanno in ~/.aruba/tsa (portato a chmod 600, era leggibile da tutti) e il documento dice dove sono senza scriverne il contenuto. - 📌 Ricordato nel documento anche cosa NON funziona, perché sono tre errori che si rifanno da soli: git firmato da solo (
GIT_COMMITTER_DATE si falsifica, e la firma prova *chi* non *quando*), WIPO PROOF chiuso dal 31/01/2022 che mezza rete consiglia ancora, e la raccomandata a sé stessi. - ⚠️ Il primo tentativo aveva risposto
bad credentials: era propagazione del servizio appena attivato, non un errore di configurazione. Vale la pena saperlo per la prossima volta invece di rimettere mano alle credenziali.
Le carnagioni scure rifatte dopo il KO: il difetto non era il colore, era il filtro che scartava la pelle vera (SU-421)2026-09-02
Ivan aveva bocciato il lavoro guardando i 24 fogli di contatto, con una lista puntuale: mani, braccia, visi e retro-testa rimasti chiari su decine di varianti, più «puntini» chiari su viso e orecchie generici su tutti gli NPC.
- 📌 Il dato più importante del KO non era nella lista, era nel fatto che il QC fosse verde. Il controllo qualità del primo giro dichiarava tutti i fogli puliti, e Ivan guardandoli ha trovato decine di difetti: misurava la cosa sbagliata. Per questo la prima riga di lavoro non è stata rigenerare le immagini ma fare in modo che il QC sapesse bocciare.
- ✅ QC riscritto per REGIONE — viso, mani, gambe separati — invece che su una media, che è ciò che annegava pochi pixel di mano in migliaia di pixel di vestiti. E gira alla risoluzione di gioco (32 px), non a quella raw: a 1254 px l'antialiasing naturale produce centinaia di falsi «puntini» che a schermo non esistono.
- ✅ Validato in due modi, perché uno non basta: reintroducendo a mano un danno su un foglio buono (lo becca) e ricostruendo i parametri del giro bocciato su
bouncer_v1 — riproduce lo stesso identico numero di allora, e il QC nuovo lo boccia. - ✅ Causa radice, con i numeri: (1) un filtro «di sicurezza» globale scartava intere chiazze di pelle vera perché era più stretto del filtro anatomico a cui doveva solo fare da rete (
MAX_COMPONENT_FRACTION 0,20 → 0,65), e un secondo difetto spezzava i volti di profilo esattamente al confine testa/corpo — ecco perché grandma v1 era chiara «dagli occhiali in giù» e la v2 «dagli occhiali in su». (2) I «puntini» sono 1-4 px isolati rimasti sotto soglia dentro chiazze già scurite: risolti con un riempimento a isteresi, lo stesso principio di Canny. - ✅ Numeri finali: 68 varianti rigenerate, 21
drunk eliminate e 4 scarti espliciti (richtourist v3 female, joker, tourist v3 female, tourist v4) come deciso da Ivan, 7 escluse perché sono il giocatore — il barbone non serve in carnagione scura, altra sua decisione del 2/09, che chiude anche la vecchia domanda su barba e capelli. - 📌 Dei «cinque cappelli» da correggere a mano ne esistevano tre:
richtourist_v1 un cappello non ce l'ha. Corretti i tre veri invece di inseguire un numero scritto in un commento vecchio. - 📌 Gli script sono stati portati fuori da
TMP/, che è gitignorata: ora stanno in tools/su421_carnagioni/ con un LEGGIMI che spiega perché il QC va fatto girare a 32 px e perché va verificato che sappia fallire. I 68 PNG (141 MB) restano in TMP finché Ivan non li approva: allora entrano in sprites_raw/ col ticket di montaggio, SU-422. - ⚠️ Non provato: il giudizio è di Ivan, foglio per foglio. L'agente ne ha guardati a occhio 20 su 68 (i più critici e quelli segnalati dal QC); gli altri 48 sono passati dal QC senza segnalazioni ma non sono stati riguardati uno per uno.
Il registro dei nickname è stato misurato davvero, e a 100.000 nomi sfonda il timeout del gioco (SU-694)2026-09-02
Primo passo della sequenza di SU-640, e quello che sblocca tutti gli altri: le strade A (indice e tetto su Apps Script) e B (Firestore) si scelgono con questo numero in mano, non prima. Finora era una stima.
- ✅ Tre misure vere su un deployment di prova, 7 ripetizioni per taglia, con le due tappe del redirect separate.
claim: 5,40 s mediana a 1.000 righe, 7,68 s a 10.000, 14,15 s a 100.000 (peggiore 15,19). resolve: 5,47 / 5,67 / 13,19 s, peggiore 19,33 s. - ⚠️ Il numero che nessuno si aspettava, ed è quello che conta:
NicknameRegistry.REQUEST_TIMEOUT_SEC è 15,0 s per tappa. A 100.000 nomi la prima tappa di resolve ha toccato 18,80 s — già oltre — e la claim peggiore 14,07 s, cioè 0,9 s di margine. Fra 10.000 e 100.000 nomi il registro attraversa il tetto d'attesa che il gioco si è dato da solo, e da lì ogni risposta diventa RESULT_SILENT. Non è una previsione: è misurato. - 📌 E conferma quale sia il collo di bottiglia, smentendo l'ipotesi comoda: non è la scansione dei nomi (5-8 ms anche a 100.000), è
getValues() — 854 ms a 1k, 1.396 a 10k, 8.228 ms a 100k. Più un costo fisso di ~4,5 s che su Apps Script non si toglie in nessun modo. - ✅ Tetto giornaliero osservato: 5.000 scritture. A 4.999 la claim passa (15,23 s), la successiva risponde
{"ok":false,"esito":"tetto giornaliero"} in 1,88 s, rifiutando *prima* di toccare il foglio. resolve continua a funzionare a tetto pieno: è sola lettura e non consuma il contatore. - 📌 La lettura che ne esce: «A adesso, B quando i numeri lo chiedono» regge, ma ora con una soglia misurata — A regge fino alle decine di migliaia; a 100.000 senza indice si è già oltre il timeout del gioco. L'indice varrebbe ~8,2 s dei ~12,7 della prima tappa, riportando la claim a 5-6 s a qualunque taglia. Il muro vero però resta il tetto di 5.000 scritture al giorno, che si tocca già a ~800 giocatori nuovi al giorno. E SU-695 (distinguere i tre «no») da igiene diventa necessità, perché ora sappiamo che il silenzio arriva anche solo per lentezza.
- ⚠️ Il ticket chiedeva un «secondo deployment», ed era la cosa sbagliata da fare.
FOGLIO_ID sta nelle ScriptProperties del progetto: qualunque copia del codice messa nel progetto vivo avrebbe aperto il foglio dei tester, e le azioni di riempimento ci avrebbero scritto dentro — la tab dev non protegge niente, è un'altra scheda dello stesso foglio. È stato creato un progetto nuovo con foglio nuovo: zero contatto. Il /exec vivo risponde ancora {"ok":true,"service":"streetu-registro-nickname"}, verificato prima, dopo, e una terza volta da me. - 📌 Non misurato, e dichiarato: il lock globale in concorrenza (due
claim nello stesso istante — il driver è seriale e i 20 s di tryLock restano ignoti), e il tetto di 5.000 non è stato consumato davvero ma forzato nel contatore, che è la stessa variabile che il codice guarda. - 📌 Il passo che ha richiesto Ivan: il consenso OAuth sta su
accounts.google.com, dominio su cui l'estensione Chrome non ha permessi. L'agente si è fermato senza inventare numeri e ha lasciato la finestra aperta col punto di ripresa — tre clic, e poi tutto automatico.
La cartina del tutorial si inquadra sui punti, e non disegna più fuori dalla pergamena (SU-594)2026-09-02
La segnalazione del tester aveva due metà: «le icone si sovrappongono» e «il resto della pergamena è bianco». La prima era già stata risolta da SU-704 il 1° settembre; la seconda no, e Ivan ha deciso il 2 settembre di implementare la proposta del ticket: «si inquadra la proposta e vediamo come viene».
- ✅ Quando il mondo è sproporzionato (rapporto oltre 3:1) si inquadra il rettangolo dei punti d'interesse più un margine, invece del rettangolo del mondo. Il tutorial è 12,55:1 — largo 6.776, alto 540 — e la fila di icone passa dal 45,6% al 56,3% della larghezza della carta. Regola generale, nessun ramo «se tutorial»: la città è 1:1 e resta ben sotto soglia, quindi lì il ramo non scatta mai (verificato: scatto pixel-identico e stessi numeri).
- ⚠️ E il primo giro ha introdotto un difetto visibile che i suoi stessi numeri non vedevano: sagome disegnate fuori dalla pergamena, sul nero a sinistra. I criteri misuravano la legenda e il rettangolo dei POI; nessuno chiedeva «c'è qualcosa disegnato fuori dalla carta?», che è la domanda che il difetto violava. Trovato aprendo gli scatti.
- 📌 E la diagnosi non era quella ovvia. Non erano icone né marker: sono i rettangoli delle aree (parchi, piazze, palazzi) che *ogni* stazione registra, comprese quelle senza icona POI — «MUOVITI», «ELEMOSINA», «MUSICA», «LE TUE BARRE».
_draw_areas() li disegnava senza clamp, e prima non serviva: inquadrando sempre il mondo intero ci stavano per costruzione. La prima spiegazione tentata («il mondo che traspare sotto il velo») è stata smentita spegnendo velo e pergamena con una sonda diagnostica: le sagome restavano. - ✅ Corretto col ritaglio, non con lo schiacciamento, ed è una scelta motivata: un parco non è un punto, e comprimerlo sul bordo mentirebbe su dove sta e quanto è grande, mentre una cartina vera che inquadra uno scorcio mostra solo ciò che ci sta.
_area_visibile(r) = r.intersection(_map_rect), con lo stesso principio esteso ai punti dell'arcobaleno — mai visto rotto, ma stessa classe di rischio a costo quasi zero. - ✅ La sonda ha ora un criterio che sa bocciare questo, verificato: sul giro «prima» segnala 1 elemento fuori (il marker del player, 7,9 px — un difetto preesistente e diverso), dopo la correzione 0 a tutte e tre le risoluzioni. Un criterio che non ha mai fallito non è una prova.
- ⚠️ Verdetto onesto: è meglio, ma non «piena». Resta un nastro a metà carta con un vuoto vistoso prima dell'icona Metro — ed è un gap vero nel mondo (la fermata finale sta 420 px dopo l'ultima stazione), quindi la cartina lo mostra invece di nasconderlo. Sopra e sotto la fascia resta vuoto perché tutti i punti del tutorial vivono sulla stessa Y (
LANE_Y): per riempire davvero servirebbe cambiare il layout in TutorialWorld.gd, non altri margini in MapOverlay.gd.
L'inquadratura del tutorial si centra sulla corsia, e il login fallito non accusa più il giocatore (SU-596, SU-580)2026-09-02
- ✅ SU-596 — nel tutorial la strada non sta più tutta in basso.
CAM_Y era una costante fissa a 250 e la pavimentazione partiva al 43% dell'altezza, con sopra mezza schermata nera; il tester aveva scritto «nella Rev precedente era al centro». Ora _camera_y_target() centra la corsia e si alza solo quanto serve a tenere dentro il cartello: scarto dal centro 16,3% → 9,8% (soglia 10%), cartello intero a tutte e quattro le risoluzioni, provate anche a 2400×1080 20:9, la forma dell'Oppo del tester — dettaglio che Ivan ha fornito in chat e senza il quale si sarebbe misurato il caso sbagliato. - 📌 Il ticket avvertiva di non inseguire una regressione che non c'era, e aveva ragione:
CAM_Y non cambiava dalla v0.9. La «Rev precedente» che il tester ricordava non era quel valore, e infatti la correzione non è stata rimettere un numero vecchio ma derivare la camera dall'inquadratura. - ⚠️ E il primo giro ha introdotto una regressione vera, trovata guardando gli scatti e non i numeri: alzando la camera, i cartelli MUOVITI ed ELEMOSINA — che sono world-space e la seguono — finivano sopra la fila dei cuori e sopra l'orologio. I numeri erano tutti verdi perché nessun criterio parlava dell'HUD. Corretto con un secondo vincolo: la camera non si alza oltre il bordo basso dei cuori, letto a runtime dal nodo HUD vero, non da una costante duplicata. Costa un po' di centratura (9,8% invece di 6,3%) e la si paga volentieri.
- 📌 Un bug latente trovato per strada:
TutorialManager.gd teneva una copia a mano di CAM_Y=250 (CAMERA_Y_MONDO, per posizionare i banner meteo sotto i cartelli) con un commento che avvertiva «se cambia, aggiorna anche qui» — e il primo giro l'aveva ignorato. Ora legge la Y vera dalla Camera2D. - 📌 Un artefatto di misura isolato e neutralizzato:
HUD._safe_insets() legge DisplayServer.get_display_safe_area().position senza sottrarre la posizione della finestra sullo schermo, e su Mac a finestra produce un inset fantasma che cambia con l'altezza della finestra (46,9 px a 1080, 70,4 a 720). Niente a che vedere col gioco vero, dove la finestra coincide con lo schermo — ma avrebbe falsato ogni misura di layout fatta a finestra. - ✅ SU-580 (punti 1 e 2) — il gioco non dice più al giocatore che ha cambiato idea. Quando il login Google falliva per configurazione, mostrava «Hai cambiato idea a metà strada»: una causa che il gioco non può conoscere, e proprio quella che chiude il discorso — chi la legge non riprova e non segnala. Era successo davvero: dal 5 al 25 agosto nessun tester con Android 12 o meno riusciva ad accedere.
- 📌 Il vincolo che rende il ticket risolvibile: Play services restituisce il rifiuto di configurazione tipizzato come annullamento, quindi il gioco non può distinguere i due casi e non deve provarci — la frase dev'essere vera in entrambi. Nuova: «L'ingresso si è fermato a metà. Riprova pure, se non va non dipende da te.», in tutte e 8 le lingue. Il
%s proposto dal ticket è stato tolto perché MainMenu.gd:5974 chiama I18n.t() senza parametri: con il segnaposto sarebbe uscito a schermo. - ✅ E il CSV è stato toccato su UNA riga sola, verificato con
git diff --numstat, senza CRLF: la stessa trappola che stamattina aveva prodotto 266 righe di diff su un altro CSV per una chiave che non doveva cambiare. - 📌 Il punto 3 (il ramo
cancelled del .java e la ricostruzione dell'aar) è uscito da qui ed è SU-726, per decisione di Ivan del 2026-09-02.
Alla fermata della metro la polizia non ti prende più, e il simbolo del tasto è grande quanto il testo (SU-727, SU-595)2026-09-02
Due ticket che condividono Interactable.gd e quindi lo stesso commit.
- ✅ SU-727 — prendere la metro mentre scappi non è più una trappola. Un tester aveva segnalato che tocco e tieni-premuto lasciano il barbone fermo davanti alla polizia nell'attimo dell'attesa. Ivan ha scelto la strada B (SU-601): non cambia nessun gesto, toglie solo il rischio.
- ✅ Misurato con un poliziotto forzato in
chase a 19 px, sotto arrest_range=20: prima 8 arresti su 16 col tocco (finestra 13-36 ms) e 9 su 10 col tieni-premuto; dopo 0 su 16 col tocco e 1 su 10 col tieni-premuto. - 📌 Lo scudo non è un interruttore sparso ma una verità riscritta ogni frame: la metadata
su727_metro_shield viene ricalcolata in _process() da _metro_hold_attivo OR _metro_in_viaggio, invece che da chiamate «accendi/spegni» in giro per il codice — che è il modo in cui questo genere di flag resta acceso per sbaglio. PoliceOfficer._do_arrest() ha anche una rete: se lo scudo scatta fra un frame e l'altro, torna a chase invece di finalizzare. - 📌 Nessun timer nuovo per l'anti-exploit, e non per pigrizia: i due stati sono già limitati da soli — 0,45 s di hold più ~0,95 s di viaggio, $1 a corsa, cooldown allo sbarco. Aggiungere una durata avrebbe introdotto un secondo modo di sbagliare.
- ⚠️ Resta 1 arresto su 10 col tieni-premuto, e non è un difetto introdotto qui: è la guardia
_player_inside di SU-486 che annulla l'hold quando un poliziotto vicino fa scattare un'uscita/rientro spuria dall'Area2D della fermata, col barbone fermo e la posizione identica. Verificato che non è la collisione fisica (riprodotto anche disattivandola) né le coordinate. Chiuderlo vorrebbe dire toccare quella guardia, fuori perimetro: segnalato invece che deciso da solo. - ✅ SU-595 — il tasto da premere si dice in un modo solo, e si vede. Tre sedi, una causa:
get_prompt() dava la lettera fra quadre, get_prompt_rich() un [img=18x18] dentro testi molto più grandi, e la scritta nel mondo un TextureRect 16×16 fisso ancorato in alto invece che alla riga. Su gamepad e touch le lettere fra quadre passano da 13 a 0; su tastiera restano 13, che è corretto (SU-199: la lettera dev'essere quella del binding vero, verificato rimappando su R). - 📌 La dimensione ora si prende dall'altezza VERA della riga,
Font.get_height(), non dal font_size nudo: 27 px contro 19 dichiarati, 23 contro 16. Il primo giro della sonda ha bocciato proprio quell'errore. - ⚠️ E il cancello ha fermato la prima consegna. L'icona nel fumetto risultava *più piccola* del
[X] testuale che sostituiva — un peggioramento, non un miglioramento. Causa vera: le texture dei tasti hanno canvas 64×48 ma il glifo opaco sta in 30×31, cioè il 64,6%; un [img=24x24] mostrava un segno visibile di ~15 px. Ora il riquadro è inflazionato di quel rapporto, misurato con PIL e documentato nel codice: il segno visibile passa dal 57% all'85% dell'altezza di una maiuscola vicina. È la regola «si misura la pelle, non la sagoma», applicata a una texture invece che a uno sprite. - 📌 Resta aperto il keycap da tastiera che Ivan ha chiesto in chat (uno sprite del tasto con dentro la lettera del binding): quello sprite non esiste e generarlo non era di questo lotto. Fino ad allora la tastiera mostra la lettera vera, e il ramo è isolato in un solo
return perché il giorno che l'arte c'è basti sostituire la sorgente dell'immagine.
«STREET UNIVERSITY» come marchio è libero in classe 9 e 41, e conviene depositarlo in Italia (SU-607)2026-09-02
Il copyright non protegge il nome: l'art. 100 LdA copre il titolo solo debolmente, e il nome è comune abbastanza da poter essere già di qualcun altro. Il primo passo del ticket era gratis e bloccante — la ricerca di anteriorità — e senza quella qualunque spesa era alla cieca.
- ✅ Verdetto: LIBERO in classe 9 (software e videogiochi) e 41 (intrattenimento e giochi online), per Italia e UE. L'unico identico mai esistito in UE in classe 9, EUIPO 010826345, è scaduto il 22-05-2022, la grazia di sei mesi è chiusa da novembre 2022 e i titolari non hanno ridepositato. Era per di più figurativo: la *parola* in UE non è mai stata protetta.
- 📌 L'unico identico vivo al mondo è australiano (Ted Noffs Foundation, AU 1698245, classi 41+44 fino al 2035): chiude l'Australia, non l'Europa.
- ⚠️ Una cosa da tenere d'occhio, distinta dal resto:
UNIVERSITY OF THE STREET, ES M4396478, depositata il 7 agosto 2026 — tre settimane fa. Non è identico, ma è anteriore a un deposito fatto oggi: può opporsi a un marchio UE, non a un marchio italiano (art. 12 CPI). Tutti gli altri identici sono streetwear in classi lontane (25/18), fuori UE o estinti: segnalati ma non bloccanti. - ✅ Store e domini puliti: nessuna app con questo nome su Play né su App Store, zero risultati su Steam e su itch.io, e
streetuniversity.it è libero, come .gg .io .game .games .app .dev. - 📌 La raccomandazione, e il motivo vero non è il risparmio: depositare il denominativo presso UIBM, classi 9 e 41, a nome di Ivan Bianco persona fisica. 183 € (101 + 34 la seconda classe + 48 di bollo) contro 900 € di EUIPO. Ma l'argomento che decide è la priorità unionista di sei mesi (art. 4 CUP): 183 € oggi fissano la data, e la scelta da 900 € si sposta a quando il gioco avrà ricavi, senza perdere un giorno di anteriorità. In più, contro una domanda italiana quella spagnola non ha legittimazione a opporsi; contro un EUTM sì.
- ⚠️ Tre fonti non erano interrogabili, e sono dichiarate invece che aggirate: la banca dati UIBM è dietro reCAPTCHA (non si risolvono per policy), WIPO Global Brand Database è una SPA con proof-of-work, EUIPO eSearch vuole credenziali. Tutte e tre sono state coperte attraverso TMview, che è alimentato proprio da quegli uffici — ufficio
IT per l'Italia, WO per il registro di Madrid, EUIPO per l'UE — con la vitalità del feed verificata in diretta (192 record «UNIVERSITY» e 249 «STREET» nelle sole classi 9 e 41). Non è stata fatta una ricerca fonetica o per codici di Vienna: per un denominativo in due classi la copertura è adeguata, ma non è un parere legale. - 📌 Due cose che restano a Ivan: il SME Fund 2026 dell'EUIPO rimborsa il 75% delle tasse anche su un deposito nazionale — i 135 € di tasse tornerebbero a ~34 € netti — ma serve partita IVA o prova di attività economica, da verificare e non dare per acquisito; e
streetuniversity.it va registrato subito, perché è libero e costa una decina di euro. - 📌 Rilievo sull'esame, non un ostacolo: in classe 41 conviene formulare i servizi come «intrattenimento; servizi di giochi forniti on-line» ed evitare «istruzione/formazione», dove un esaminatore potrebbe sollevare la carenza di distintività.
Referto completo in OPUS_BRIEFS/DESIGN_SU-607_marchio_anteriorita.md.
La vetrinetta del personaggio ingrandisce a numero intero, e le barre si prendono col dito (SU-599, SU-598)2026-09-02
- ✅ SU-599 — i pixel del personaggio nel menu adesso sono tutti della stessa misura. Il tester aveva scritto «su PC il personaggio è troppo sgranato», ma la foto ravvicinata mostrava la cosa vera: i pixel non erano tutti uguali. La causa non era l'asset, era la scala —
_skin_preview è un TextureRect con EXPAND_IGNORE_SIZE in una vetrinetta la cui altezza dipende dalla risoluzione, quindi lo sprite veniva ingrandito di un fattore frazionario e col NEAREST alcune righe si raddoppiavano e altre no. - ✅ E la misura «prima» ha trovato più di quanto il ticket diceva: non solo il fattore era frazionario, era anche diverso sui due assi —
fx=8,8996 e fy=9,4490, cioè lo sprite era pure *stirato*. Dopo: fx = fy = 8,0000 esatti a 1920×1080, 1366×768 e 844×390, con il personaggio centrato e dentro la vetrinetta a tutte e tre. Lo scatto ravvicinato (TMP/su_lotto_menu/dopo/su599_1920x1080.png) mostra blocchi quadrati regolari. - ✅ SU-598 — la barra di scorrimento passa a 22-30 px su touch (restava 12 su desktop col mouse, dove va bene), e soprattutto il contenuto si trascina col dito ovunque, che era il pezzo che risolveva davvero il problema del tester: chi scorre non deve mirare niente. Misurato: un trascinamento in mezzo alla lista lingua porta l'offset da 0 a 159,3.
- ✅ Il pannello del consenso telemetria non usa più uno
ScrollContainer nudo — con la barra del tema di sistema, più stretta ancora, e nessuna presa alternativa — ma la MenuScrollList del resto del menu, con un RichTextLabel unico in BBCode al posto di tre coppie Label/VBox. Provato in tedesco, dove il testo va davvero in overflow: un solo trascinamento arriva in fondo. - 📌 SU-597 era già risolto stamattina da SU-722, senza che nessuno lo sapesse: togliendo la voce INDIETRO dagli elenchi era sparito anche il
pressed → _request_back() del back-catcher. Verificato con eventi di input veri (non chiamate dirette) su 23 schermate e con le quattro vie d'uscita su tre di esse. Qui sono stati corretti solo due commenti che promettevano ancora il vecchio comportamento. - ⚠️ Ma SU-597 NON è chiuso, e la sonda lo dice con due KO: «options» e «lingua» tornano ancora indietro al tocco a vuoto, perché le costruisce
OptionsPanel.gd — condiviso col menu di pausa in partita e quindi escluso sia da SU-722 sia da questo lotto — il cui catcher interno chiama ancora _indietro_un_livello(). E in 18 schermate i suggerimenti touch (MENU_HINT_VOCI_TOUCH) promettono ancora «ALTROVE PER TORNARE» in tutte e 8 le lingue: una frase che oggi è falsa.
Il cheat sheet ha finalmente un generatore, e legge i documenti veri (coda di SU-725)2026-09-02
WORKFLOW_CHEAT_SHEET.pdf era entrato come binario il 2026-07-20 e da allora veniva rifatto a mano con strumenti non committati — il CHANGELOG lo segnalava dal 5/08, e stamattina SU-725 ha dovuto consegnare senza quel criterio perché nel repo non esisteva niente che lo producesse. Ivan: «scrivilo».
- ✅
tools/genera_cheat_sheet.py: un comando solo, python3 tools/genera_cheat_sheet.py dalla radice, e il PDF si rifà. Zero dipendenze nuove — reportlab c'era già, ed era anche lo strumento con cui il PDF vecchio era stato fatto (verificato con pdfinfo, non supposto). - ✅ Il contenuto viene dai documenti veri, non da una copia dentro lo script:
WORKFLOW/README.md, WORKFLOW/04_RELEASE.md e WORKFLOW/05_SETUP_CLI.md. Un cheat sheet scritto a mano dentro il suo generatore sarebbe stato lo stesso problema di prima con un passo in più. - ✅ E il blocco del giro git non è ricostruito: viene ESEGUITO. Lo script fa il sourcing di
tools/release/release_git_lib.sh e chiama rg_stampa_piano_git(), la stessa funzione che usano prepare_release.sh e prepare_dev_tag.sh. Se quel giro cambia, il PDF si aggiorna da solo al primo rilancio — è la differenza fra un documento che invecchia e uno che non può. - 📌 Il parser markdown è scritto su misura e fallisce rumorosamente: se una sezione attesa sparisce dai
.md lo script muore con SystemExit invece di produrre in silenzio un PDF monco. Era il modo tipico in cui questo genere di strumento smette di dire il vero senza che nessuno se ne accorga. - 📌 I simboli fuori da Latin-1 (⚠️ 📌 ✅ ⏳ ⛔) diventano equivalenti ASCII solo nel rendering, perché i font base14 di reportlab non li coprono: i
.md sorgente restano intatti. - ⚠️ Una perdita dichiarata: il recap «SETUP CLI» elenca ora i soli titoli dei passi, estratti dalle intestazioni
## N. …, mentre il PDF vecchio riportava anche i comandi inline. Estrarre comandi dal testo libero sarebbe stata un'estrazione fragile, e si è preferito ancorarsi a un pattern verificabile. - ✅ Rigenerato e guardato: 2 pagine A4, 9.081 byte, riproducibile byte per byte a due lanci. Dentro c'è già la tabella nuova dei tre tempi di SU-725 e il giro git con la regola degli asset raw in LFS.
La barbona ha finalmente il gatto in braccio, l'ubriaca e l'insolata (SU-723)2026-09-02
La skin f1 (BARBONA 1) aveva le stesse 16 animazioni del barbone ma "variants": {} in Skins.gd: col gatto in braccio mostrava il foglio base, ed è quello che i tester vedevano. Mancavano i tre stati che il barbone ha da sempre.
- ✅ Tre fogli generati in
sprites_raw/PEOPLE/: arch_player_f1_cat.png, arch_player_f1_vdrunk.png, arch_player_f1_vheatstroke.png, 1254×1254 su magenta come vuole la pipeline (import_people_sprites.py). Il montaggio in gioco è SU-724 e il «Fatto» lo mette Ivan, dopo aver guardato. - ✅ Il layout è stato misurato, non assunto: fogli di riferimento del barbone 160×160, griglia 5×5, righe E / SE / S / NE / N con SW specchiata in SE. È il contratto che rende il montaggio di SU-724 una riga invece di un giro di KO.
- ✅ Il controllo delle direzioni è stato fatto sul foglio etichettato (
TMP/su723_barbona/ref_cat_labeled.png), non a occhio sul foglio nudo: E di profilo col gatto a destra, S frontale, NE e N di spalle col gatto correttamente non visibile. Il gatto tenuto davanti è il dettaglio asimmetrico che smaschera un verso girato — è la ragione per cui su questo soggetto la trappola del «verso est/ovest» si vede subito invece di passare. - 📌 Due difetti trovati e corretti prima di proporre, invece di lasciarli scoprire a Ivan: l'insolata al primo giro era più liscia e tonda, stilisticamente scollata dalle altre due — rifatta usando il suo stesso foglio ubriaca come unico riferimento; e la bottiglia spariva in un solo fotogramma (EST/walk3) del foglio ubriaca, corretta con un giro chirurgico su quella cella sola, verificato per diff: 243 px cambiati su 25.600, il resto identico.
- 📌 Nessun altro stato manca a f1:
player_ghost non è una variante per-skin (è un path fisso condiviso in Player.gd, che f1 già usa) e hit esiste già. Era la domanda che il ticket poneva. - 📌 Resta in
files/homeless_city/scripts/tools/su723_label_sheet.py lo strumento che monta il foglio etichettato, riusabile dal prossimo lotto di sprite: il controllo diagonali/E-O è la verifica che va rifatta ogni volta e non aveva ancora un attrezzo.
Il tasto INDIETRO è un cartello solo, in basso a destra ovunque (SU-722, SU-720)2026-09-02
«← INDIETRO» era una voce dell'elenco, in 23 posti diversi di MainMenu.gd e in una posizione diversa per ogni schermata. Adesso è un bottone di cartone fisso in basso a destra, costruito da una funzione sola.
- ✅ Una funzione condivisa,
_attach_back_button(), chiamata da 28 punti. Delle 23 occorrenze di MENU_INDIETRO ne restano 7, e tutte e sette sono dentro quella funzione e nei suoi due aiutanti: il testo esiste ancora, ma in un posto solo. Convertite ~20 schermate del menu più la metro e il Quaderno, dove «back» non è più la quarta linguetta. - ✅ Gli stessi numeri ovunque, misurati e non dichiarati: margine destro 24,00 e margine basso 24,00 unità logiche, area di tocco 64,0×64,0 px fisici (84,0×84,0 logici, fattore finestra/canvas 0,762). La callable del tap è rimasta identica — gli effetti collaterali della lobby MP passano da lì e non sono stati toccati.
- ⚠️ Due schermate NON convertite, e il perché conta.
RunReport.gd non ha nessun INDIETRO da convertire (verificato: zero occorrenze — è un giornale che si chiude toccando, mai un «torna indietro»), e inventargliene uno sarebbe stata una scelta di design fuori dal ticket. OptionsPanel.gd — dove opzioni e lingua delegano il loro INDIETRO — è condiviso col menu di pausa in partita, quindi fuori dal perimetro: là la voce a elenco è ancora quella vecchia (:1705, :1817). - ✅ SU-720 — nella mappa della metro nome, frase e «STAZIONE CHIUSA» non si sovrappongono più, e i cartellini sotto le fermate troncano con «…» invece di tagliare a metà.
- ⚠️ Ma il difetto non si riproduceva alle tre risoluzioni del ticket. Con il testo italiano di default Godot clampa comunque
Control.size al minimo del testo, e la sovrapposizione non si vedeva. È stato riprodotto per davvero con inglese e DIMENSIONE TESTO = ENORME — la stessa scala, appena più alta, che il gioco applica da solo su un telefono vero. Senza quel passaggio il ticket sarebbe passato con uno scatto pulito che non provava niente. - ⚠️ E il cancello di chiusura ha fermato la consegna una volta. I primi sei scatti «di sei schermate diverse» erano sei volte la stessa immagine del menu principale: la sonda non entrava nelle sotto-schermate e i numeri erano tutti verdi lo stesso. Scoperto aprendo i PNG uno per uno, non guardando i log. La causa vera, trovata al secondo giro: su un'istanza appena creata la texture del viewport resta indietro di 1-2 cicli di
show_screen() rispetto allo stato logico, quindi lo scatto usciva «un giro prima». Risolto con due transizioni a vuoto di riscaldamento — lo stesso principio del riscaldamento multilingua già in uso. - 📌 Otto scatti, uno per schermata, aperti e guardati (metro, baracca, note bug, progressi, crediti, skin, rebind, account) in
TMP/su_lottoB/dopo/. Non provabili da qui: la safe area di iPhone e i 64×64 px su un telefono vero.
Le calamità si sentono, e la polizia si annuncia con la sirena (SU-719, SU-603)2026-09-02
Le tre ondate di WeatherSystem non avevano un suono — in assets/sounds/ c'era solo thunder.wav — e l'inseguimento della polizia aveva come unico segnale un triangolo sopra la testa del poliziotto, che si vede solo se il poliziotto è in inquadratura, cioè quasi mai quando l'inseguimento parte.
- ✅ Quattro suoni nuovi generati con ElevenLabs (
rain_wave, hail_wave, heat_wave, police_chase_alert), in Sounds_raw/ come .mp3 e in assets/sounds/ come .wav. Nascono coperti: il piano a pagamento è attivo e le licenze degli asset AI seguono la generazione, non il download. - ⚠️ Il bug che rendeva la feature muta senza dare errore, trovato dall'agente durante il lavoro: su un
AudioStreamWAV, loop_mode = LOOP_FORWARD da solo non basta. Se non si imposta anche loop_end, che parte a 0, il loop ha lunghezza zero e si zittisce da solo dopo ~5 fotogrammi, in silenzio. Riguardava tutti e quattro gli asset nuovi. Corretto in _setup_wave_sfx_players(). - ⚠️ E il secondo, preso al cancello di chiusura: i quattro
.wav non erano mai stati importati. Le sonde dell'agente davano numeri buoni; rilanciate qui davano playing=false ovunque e, nel log, No loader found for resource: res://assets/sounds/rain_wave.wav. Il compile-check non reimporta gli asset, quindi il verde non lo prende: senza i quattro .import, in gioco non sarebbe suonato niente. Importati (0 errori) e sonde rifatte — solo allora i numeri sono diventati veri. - ✅ SU-719, misurato sul bus e non sugli uniform (
bus Music idx=1, pitch_idx=0, lpf_idx=1): durante l'ondata di caldo il pitch scende a 0,9772 (il mezzo semitono chiesto dal ticket vale 0,9715) e il passa-basso stringe a 4.994 Hz; a fine ondata torna a 1,0000 e 20.000 Hz, e il loop si spegne in 912 ms (criterio ≤ 1 s). Sotto un tetto il volume scende a −14,5 dB contro i −4,0 all'aperto. Pioggia e grandine restano mutuamente esclusive. - ✅ SU-603, solo il suono — è la decisione di Ivan del 2 settembre: freccia al bordo e segno nell'HUD restano fuori da questo giro. La sirena parte a +14 ms dall'ingresso in chase (criterio ≤ 500 ms) e si spegne a +14 ms dalla fine (criterio ≤ 1 s).
- ✅ E il caso dei tre poliziotti insieme non fa tre sirene sovrapposte: c'è un contatore. Tre entrano in chase → contatore 3, una sola sirena; uno esce → suona ancora, perché due inseguono; escono tutti → spenta a +24 ms.
- 📌 Un RPC nuovo,
_cl_police_chase_end, sul canale già esistente: serve al client per sapere che l'inseguimento è finito, visto che lo stato del poliziotto gli arriva relayato. Nessun byte in più per chi non è inseguito. - ⚠️ Non provato: il lato client in una sessione MP vera a due peer, e soprattutto nessun giudizio a orecchio. I quattro file vanno ascoltati da Ivan: se un suono non piace si rigenera, ma quello non lo decide una sonda.
Hitbox alla base degli arredi, e la mappa del tutorial era già a posto (SU-717, SU-594)2026-09-02
- ✅ SU-717 — fra due panchine vicine adesso si passa. Due tester il 31/08 avevano segnalato lo stesso difetto, e una c'era rimasta incastrata: la hitbox veniva costruita dal rettangolo intero del disegno, non dalla base che tocca terra. Panchina da 32×16 a 20×10 spostata 8 px a sud, cestino da 10×12 a 6×8 spostato di 4 — misurando i pixel veri di
bench.png (40×28) e trash.png (12×16) per trovare dov'è davvero l'appoggio. - ✅ E la larghezza del barbone è stata letta, non stimata:
scenes/entities/Player.tscn, CircleShape2D raggio 8 → diametro 16 px. È il numero che ha fatto alzare PIAZZA_MIN_DIST_FILL (36→44) e GARANTITO_MIN_DIST (34→44): con la hitbox nuova quelle soglie lasciavano varchi fra 14 e 16 px, cioè appena sotto il barbone — il varco che si vede e non si attraversa, che è esattamente il difetto segnalato. - ✅ Sonda su 8 semi che legge la hitbox VERA dal nodo (
StaticBody2D → CollisionShape2D), non dal codice: prima del fix trovava 2 coppie panchina/cestino con varco fra 1 e 16 px, la peggiore a 13,1 px — un incastro vero; dopo, zero su tutti e 8 i semi. - ⚠️ Metà di un criterio resta NON DIMOSTRATA, e non è stata dichiarata verde per comodità. Il sotto-criterio «zero bidoni che chiudono un angolo» ha resistito a due sonde geometriche: la prima segnalava come chiusi quasi metà dei cestini della città (falso positivo strutturale — qualunque oggetto in mezzo a un arco libero lo spezza in due), la seconda dà zero sia prima sia dopo il fix, quindi non prova niente. Il fix sul cestino resta giustificato dal criterio 1 (−65% d'area). Domanda aperta: conviene una sonda basata sul navmesh vero (
_nav_obstacle_polys) invece di reinventare la geometria. - 📌 Una scelta ambigua, dichiarata invece che nascosta: la hitbox della panchina resta centrata in orizzontale anche se lo sprite, disegnato in prospettiva 3/4, ha le zampe un po' più a ovest. Scelta per simmetria di gioco, e scritta nel commento vicino al ramo BENCH.
- ✅ SU-594 — la mappa del tutorial non aveva bisogno di codice: era già stata sistemata da SU-704 (commit
225f4b1 del 1/09, 311 righe su MapOverlay.gd), che nel suo stesso messaggio dichiarava di valere anche qui. Ma un messaggio di commit non è una prova: la verifica è stata fatta rimettendo la versione pre-SU-704 in una copia usa-e-getta e misurando prima/dopo sui Rect2 disegnati, come il ticket pretendeva («non a occhio»). - ✅ Numeri del tutorial: PRIMA, con l'icona fissa a 26 px, 4 coppie sovrapposte a 1280×720 e a 2340×1080, e 6 coppie più una legenda che sbordava di 2,7 px nel caso realistico telefono con testo AUTO. DOPO: 0 coppie in tutti e tre i casi, icona sempre a 18 px (mai sotto il pavimento duro di 14), legenda sempre dentro la carta. In città 0 coppie prima e dopo, ma l'aria fra le icone passa da 5,2 a 17,5 px e lo sbordo della legenda da 2,7 a 0.
- ⚠️ Però lo scatto dice una cosa che i numeri non dicevano. Guardando
TMP/su_lottoI/su594_tutorial_dopo_telefono_csf1.3.png: le icone non si sovrappongono più, ma la pergamena resta per tre quarti vuota e la fila occupa una striscia sottile in mezzo. I criteri di accettazione del ticket sono soddisfatti — parlano solo di sovrapposizioni e di legenda — ma la segnalazione del tester diceva anche «il resto della pergamena è bianco», e quella metà non è stata risolta: la «soluzione proposta» del ticket (inquadrare il rettangolo dei punti invece di quello del mondo quando il rapporto supera 3:1) non è stata implementata, perché SU-704 ha risolto per un'altra strada. Serve una riga di Ivan.
Le panchine dormite diventano un intero: `meta.cfg` da 29 KB a 144 byte (SU-658)2026-09-02
benches era l'unico campo di user://meta.cfg che cresceva senza un tetto scritto nel codice: un dizionario persistente con una chiave per ogni posizione di panchina su cui si è dormito, e serviva a una cosa sola, contare le panchine diverse. Ma le panchine sono univoche solo dentro una mappa — a run finita la città si rigenera e quelle posizioni non tornano — quindi il dedup può vivere in RAM e su disco resta un intero.
- ✅ Il numero che motivava il ticket, misurato dalla sonda: col formato vecchio e 2.000 panchine
meta.cfg pesava 29.189 B; col contatore, a parità di stato, 144 B. Il chunk di lettura e scrittura di EOS PlayerDataStorage è 4.096 B: col dizionario il salvataggio ne usciva, col contatore non ci esce mai. È il prerequisito che fa cadere il ramo multi-chunk di SU-637c e riduce la fusione di SU-637d al massimo fra due interi. - ✅ Il dedup è per run e in RAM, con chiave l'instance id che arriva già in
EventBus.bench_used, azzerato sul fronte di salita di run_active. BENCH_GRID sparisce: serviva solo a rendere la chiave stabile fra sessioni, che non serve più. counters["panchine_diverse"] ora si incrementa invece di essere riassegnato da una size(), e resta vero che ogni panchina fa save() subito — quindi un crash a metà run non perde le panchine già dormite. - ⚠️ La trappola del ticket era scritta nel ticket, ed è quella che avrebbe fatto sembrare tutto funzionante.
save() fa cf.load(SAVE_PATH) prima di scrivere: togliere la set_value non basta, il dizionario vecchio resterebbe nel file per sempre e il salvataggio peserebbe come prima. Serve una erase_section_key("meta", "benches") esplicita, ed è stata messa. - ✅ Migrazione senza perdite, timbrata in
migrations: un meta.cfg con 12 panchine e panchine_diverse assente dà 12 dopo la migrazione, e dopo il primo save() la stringa "benches" non è più nel file. La carta COPERTA DI CARTONE si sblocca ancora a 10, la panchina del tutorial continua a non contare (run_active=false), e il RESET PROGRESSI azzera il contatore. - 📌 La sonda è stata lanciata due volte con
HOME azzerata fra un giro e l'altro, e i due log sono identici. Non è pignoleria: una sonda con HOME dedicata ma non svuotata passa solo al primo lancio e dal secondo diventa rossa col codice sano — su un ticket che vive tutto dentro un file di salvataggio era il modo più facile di prendere un falso verde. - 📌 Nessuna modifica a
Quaderno.gd, che il ticket dava come sola lettura: Quaderno.gd:566 chiama già MetaProgress.get_counter("panchine_diverse") e segue da solo. - 📌 E due
.uid mancanti sono stati generati (probe_su658_panchine.gd, probe_su715_deposito_raffica.gd): gli script nuovi ne vogliono uno e va committato, ma nessun agente lo produce. Fatti in un progetto Godot usa-e-getta con lo stesso path interno, perché l'uid è deterministico dal path — reimportare il progetto vero sarebbe costato minuti e sporcato il working tree con cinque lotti in volo.
Il rilascio si fa in tre tempi: iOS, poi Android, e solo alla fine la push (SU-725)2026-09-02
Regola nuova di Ivan del 2 settembre: iOS per primo, perché ha l'approvazione più lunga; Android quando iOS è approvata; push, versione attuale e avviso ai tester solo quando anche Android è fuori. Prima /rilascio-beta mandava Play e TestFlight nello stesso passo, e la push partiva dentro /release.
- ✅ Cinque documenti riscritti nell'ordine nuovo:
WORKFLOW/04_RELEASE.md, .claude/commands/rilascio-beta.md, .claude/skills/pubblica-store/SKILL.md, .claude/commands/avvisa-tester.md, .claude/commands/lista-beta.md. Ognuno dice cosa sblocca il tempo successivo, non solo cosa fare. - ✅ Il passo 8c della push esce da
/release e diventa l'ultimo dell'intero giro. La ragione è scritta dove serve: una notifica che annuncia una versione non ancora scaricabile manda il tester a mani vuote. tools/release/manda_push.py non è stato toccato, come chiedeva il ticket. - ✅ Il gate fra un tempo e l'altro è
tools/release/stato_canali.py, che risponde già da solo alle due domande che contano — «iOS è approvata?» e «Android è fuori?» — senza bisogno di adeguamenti. E la trappola è scritta come avvertenza, non come nota a piè di pagina: la prova è betaReviewState APPROVED, mai il gruppo beta, perché quella GET torna vuota anche per una build che i tester hanno già installato. - ✅ Il paragrafo che vale più di tutto il resto: «Come si riprende». Fra un tempo e l'altro passano giorni e la sessione muore, quindi ogni tempo dice cosa NON va rifatto. Fra Tempo 1 e 2, se Apple è ancora in coda non si rilancia
testflight.sh --invia: creerebbe una build duplicata. Nel Tempo 2, se Android è già completed l'upload è lavoro già fatto. Nel Tempo 3 stato_canali.py non basta — push, /lista-beta e avviso non hanno una prova via API, e manda_push.py non tiene un registro: si chiede a Ivan invece di rimandarla. - ⚠️ Un criterio NON fatto: il cheat sheet PDF non è stato rigenerato, e non per dimenticanza. Nel repo non esiste nessuno script che lo produca — verificato con una ricerca su
tools/, WORKFLOW/ e .claude/. Il WORKFLOW_CHEAT_SHEET.pdf è stato inserito come binario nel 2026-07-20 e le rigenerazioni successive sono state fatte a mano con strumenti non committati (il CHANGELOG lo segnalava già dal 5/08). Serve una decisione di Ivan: o si scrive il generatore, o si accetta che quel PDF invecchi.
Deposito a raffica in banca, e il registro nickname smette di riusare la connessione (SU-715, SU-705)2026-09-02
Secondo lotto del giro /sprint. Due ticket senza niente in comune se non che nessuno dei due tocca file di altri lotti in volo.
- ✅ SU-715 — allo sportello si tiene premuto e i soldi entrano da soli. Finestra di tenuta aperta alla pressione consumata (
Interactable.gd:531-538) e seguita ogni frame da _bank_hold_tick() (:1828-1854): oltre 0,35 s i depositi ripartono chiamando _use() intero, non _use_bank() diretto, per non perdere la scatola nera RunRecorder.note_interact. Misurato dalla sonda con pressioni vere (Input.action_press) su uno sportello di una città generata: tap breve = 1 deposito; tenuta 2,6 s = 24 depositi, 8 nel primo 1,5 s (5,3/s, atteso 6) e 16 dopo (14,5/s, atteso 15); 24 segnali bank_changed per 24 quote, quindi contatore e suono seguono ognuna e non solo l'ultima; con $45 in tasca si ferma da sola a $5, mai sotto zero. - ✅ E allo sportello non si scoreggia. Era il rischio dichiarato dal ticket, perché il tieni-premuto è già della scoreggia (v0.42) e della metro: misurati 0 eventi
player_misdemeanor("fart") durante la tenuta, con _fart_cooldown_left e _fart_hold_t fermi ai valori di partenza. Anche il messaggio «SERVONO $10 TONDI» non parte a raffica: la guardia usa la stessa soglia di rifiuto di _use_bank(), non bank_missing_to_gold(). - ✅ SU-705 — la fix di SU-651 è arrivata anche nel registro dei nickname. Il secondo salto sulla
Location riusava la HTTPRequest che aveva appena fatto la POST: il redirect di Apps Script cambia host (da script.google.com a script.googleusercontent.com) e la connessione al primo restava viva. _swap_http() copiato pari pari da RunRecorder e chiamato prima della GET. - 📌 E il commento che aveva depistato è stato corretto in due posti, non in uno. Diceva «
result = 12 (RESULT_TIMEOUT)»: è falso, 12 è RESULT_REDIRECT_LIMIT_REACHED e 13 è RESULT_TIMEOUT. È proprio quell'etichetta ad aver fatto scambiare il 13 osservato su iPhone per «la solita cosa del redirect». Oltre alla riga 581 nominata dal ticket è stato corretto anche il blocco «trappole» in testa al file, che diceva la stessa cosa sbagliata: lasciarlo avrebbe contraddetto la correzione due righe sotto. - ⚠️ Il criterio a schermo di SU-705 resta non verificabile, e lo dice il ticket stesso: la fix di SU-651 è entrata solo nei tag del 31/08 e nessun tester Apple l'ha mai avuta. La prova vera è una classifica coi nomi su iPhone dopo la prossima pubblicazione TestFlight, e vale per i due fix insieme.
Sprint 12 da CLI: quattro difetti del mondo e i prompt a una parola (SU-712, SU-711, SU-710, SU-716, SU-718)2026-09-02
Primo lotto del giro /sprint del 2 settembre. Perimetro di partenza: 47 ticket in «Da fare», tutti nuovi, zero bocciati — le 17 chiavi messe In revisione il 31/08 non sono tornate indietro (0 rientri su 17, il tasso migliore finora).
- ✅ SU-712 — la soglia della banca raddoppiava un bonus in ritardo. Il raddoppio si è spostato in
bank_bonus_superato() (GameState.gd:1750), dove si sa l'esito. Misurato dalla sonda probe_su712_banca.gd: 100 → 200 già nell'istante della prima vittoria, ferma a 200 se il bonus è perso, poi 400 e 800. La diagnosi del ticket era giusta solo a metà: il debito si paga quasi sempre alla discesa (Interactable._metro_apri_bonus() spegne l'arcobaleno lì), non al ritorno — e raddoppiare «subito» senza guardia apriva una corsa in cui il debito si poteva perdere per sempre, quando l'arco si spegne a un soffio dal congelamento della città. Coperta con un ripiego, provata dalla sonda. - ✅ SU-711 — il piccione d'oro lasciava le monete fuori dall'area.
_coin_drop_spot() (World.gd:3398) ripiegava su origin nudo, cioè proprio sul punto non calpestabile da cui il piccione volava. Ora c'è un secondo tentativo a raggio crescente (COIN_SNAP_FALLBACK_RADIUS, dedotto da CELL_SIZE=192). Sonda su 8 città: 28 origini davvero irraggiungibili trovate, 28 su 28 monete perse prima, 0 dopo. - ✅ SU-710 — i piccioni ora scappano anche dal gatto lanciato.
_posizione_minaccia() guarda anche il gruppo cat_projectiles, che esisteva già per lo scontro gatto-contro-gatto. Stesso SPAVENTO_RADIUS del giocatore, nessun dado seedato e nessun byte in rete. Misurato: gatto a 50 px dentro il raggio 70 → decollo dalla parte opposta (dot 0,94); a 110 px, nessuna fuga. Il piccione d'oro resta immune per costruzione — GoldenPigeon.gd è un Node2D a sé che quei metodi non li chiama mai (verificato, non dedotto). - ✅ SU-716 — le lattine del bonus non stanno più solo fra i binari. Metà sulla banchina, metà nel corridoio su entrambi i binari e su tutta la lunghezza. Su 8 semi: 50,7% fuori dalle corsie (criterio ≥ 40%), 0 lattine irraggiungibili, totale per livello invariato, e due istanze con lo stesso seme danno posizioni identiche — che è la base del multiplayer senza traffico aggiunto.
- ⚠️ E SU-716 contraddice una decisione di Ivan del 22/08, scritta nei commenti di
BonusRewards.gd: «lattine solo nell'ultimo 14% del corridoio, tutte nel binario basso, niente durante il livello». Il ticket è del 2 settembre e vince come decisione più recente, ma la cosa non è stata nascosta: scripts/tools/probe_su491_bonus.gd (righe 258-264) ha due asserzioni che ora falliscono di proposito, perché incarnano la regola superata. Vanno aggiornate o ritirate da chi tocca quel ticket. - ✅ SU-718 — le scritte d'azione nel mondo sono una parola sola. 17 chiavi
*_PROMPT_* di ui_mondo.csv in tutte e 8 le lingue: «Riposa sulla panchina» → «Riposa», «Lavati alla fontana» → «Lavati», «GATTILE: consegna il gatto» → «Consegna». - ⚠️ Il cancello di chiusura ha fermato due difetti veri su SU-718, e nessuno dei due si vedeva dal report dell'agente. (1)
ui_gioco.csv era stato riscritto interamente dal modulo csv di Python: 129 righe con fine riga CRLF dove il file committato non ne aveva nessuna, e le virgolette tolte da ogni campo — 266 righe di diff per un file in cui non c'era niente da cambiare. (2) In ui_mondo.csv erano sparite le 147 righe riformattate e il prefisso [E] da tutti i valori: ma [E] non è decorazione, è il segnaposto che il codice sostituisce col binding vero (Interactable.gd:3462, SU-196) — toglierlo cancella il simbolo del tasto da ogni scritta del mondo, che è l'opposto del criterio del ticket. I due file sono stati riportati alla versione committata e le 17 chiavi riapplicate una per una: diff finale 16 righe invece di 413. - 📌 I 6 prompt che restano lunghi (
BANCA, CHIESA, BAGNO, METRO, METRO_BONUS, BANCO_IGIENE) sono composti nel codice con i18n.tf() perché portano dentro un prezzo variabile. Conseguenza da guardare in gioco: ora alcune scritte mostrano il costo e altre no, e i prezzi spariti da HOTDOG, GELATO, VESTITI e DRINK erano informazione che il giocatore leggeva. - 📌 Decisioni prese da Ivan in chat e scritte sui ticket lo stesso momento: copyright a nome proprio, «(c) 2026 Ivan Bianco» (SU-604, che sblocca SU-605 e SU-607); il bidone dorato del tutorial resta com'è (SU-593, strada A: la gag è voluta e la scheda avverte già); l'avviso dell'inseguimento parte dal solo suono (SU-603, freccia e HUD rimandati).
- 📌 E una domanda che non si farà più: il piano ElevenLabs a pagamento c'è ed è attivo. Non era un dubbio della memoria — che lo dava per verificato dal 30/08 — ma dei ticket, che nel testo dicono ancora «serve comprare il piano»: corretti con un commento su SU-606, SU-719 e SU-671, che è il posto da cui la domanda continuava a rinascere.