Da due documenti a un gioco funzionante
Un CLAUDE.md di 115 righe e uno SPEC.md di 336 entrano nel contesto di un modello di frontiera. Ne escono quaranta file TypeScript, cento test e un maze-chase giocabile. Questo diagramma segue quel percorso passaggio per passaggio: cosa arriva davvero al modello, cosa decide, dove sbaglia in modo prevedibile, e cosa fa la differenza fra un risultato utile e una pila di codice plausibile.
SPEC.md — 14 sezioni, 12 tabelle, 7 fasi di consegna
≈ 10.500 token di documenti, ≈ 16.000 di contesto iniziale.
I due documenti non sono l'unica cosa nel contesto
L'utente scrive una riga. Il modello riceve sedicimila token: circa un terzo li ha scritti il fornitore dello strumento, e il resto sono documenti che l'utente ha scritto prima, non in quel messaggio. Sapere cosa c'è davvero nella finestra è il primo passo per capire come si comporterà.
Come viene montato, e perché in quest'ordine
Il modello riceve un unico flusso di token. La divisione in blocchi serve all'infrastruttura — e l'ordine non è estetico: determina cosa si può riusare senza rielaborarlo.
Sono identici a ogni chiamata, quindi il lavoro già svolto per elaborarli si riusa: si pagano una frazione e non si aspetta di rielaborarli. Su una sessione da duecento passi sono 16.000 token × 200 che non vengono ricalcolati, e lo stesso vale per la cronologia già vista (stadio 08).
Su contesti lunghi le istruzioni all'inizio e la richiesta alla fine ricevono più peso di quelle in mezzo. Un vincolo sepolto al centro di un documento di ottomila token viene rispettato meno di uno scritto in cima — ragione pratica per cui il CLAUDE.md è breve e sta prima.
I vincoli diventano numeri interi
Prima di qualsiasi elaborazione, ogni carattere dei due documenti viene spezzato in pezzi di vocabolario. Il modo in cui questo avviene spiega alcune debolezze molto concrete che compariranno più avanti.
«75.7576» arriva come quattro o cinque token separati; «1033» come due. Il modello non «vede» il numero: vede una sequenza di frammenti. È una delle ragioni per cui ricopiare e manipolare numeri è fragile. Ma riportare a memoria una tabella vista durante l'addestramento è inaffidabile soprattutto perché i pesi conservano i dati rari in modo approssimato. Per questo lo SPEC fa bene a mettere tutti i numeri in un file dati.
Una riga di 28 caratteri della mappa ASCII arriva come 6-10 token. Verificare che siano esattamente 28 richiederebbe di contare dentro i token — un'operazione che l'architettura non favorisce. Se ne riparla allo stadio 14.
Sedicimila token, un colpo solo
Tutti i token del contesto attraversano la rete insieme, in parallelo. È l'unica fase in cui l'hardware lavora a pieno regime, e dura meno di un secondo: leggere è economico, scrivere no.
La prima decisione: non scrivere codice
A un modello addestrato su milioni di richieste di programmazione, la continuazione più naturale di «iniziamo dalla fase 0» è aprire un file e cominciare. Il CLAUDE.md deve batterlo su quella inclinazione, e lo fa con quattro parole.
«All'inizio di ogni fase produci un piano: file toccati, funzioni/tipi introdotti, test previsti, rischi. Fermati e aspetta l'OK.»
È imperativa, non descrittiva · dice cosa fare, non cosa evitare · elenca i quattro contenuti richiesti, quindi è verificabile · sta nelle prime righe del documento · e il post-training ha reso il modello particolarmente attento alle istruzioni procedurali esplicite.
«Cerca di pianificare prima di codificare» — attenuata, quindi trattata come preferenza. «Non buttarti subito sul codice» — negativa, lascia aperto tutto il resto. Sepolta a pagina tre — pesa meno di quella in cima.
Il punto 3 chiede qualcosa che quasi nessuno chiede: «se una richiesta della spec ti sembra sbagliata, dillo prima di implementarla». Il post-training spinge verso la cooperazione, e senza un permesso esplicito il comportamento naturale è eseguire in silenzio anche ciò che si ritiene un errore. Questa riga, insieme al punto 2 («se emerge una decisione non coperta, fermati e chiedi»), è ciò che produce lo stadio 15.
Lo stesso effetto si può imporre fuori dal modello. Gli agenti di coding hanno una modalità piano in cui il software non esegue scritture né comandi finché l'utente non approva. Una regola nel CLAUDE.md chiede di fermarsi; la modalità piano rende impossibile non farlo. È lo stesso principio dello stadio 13, applicato al primo turno.
La bozza che non vedi
Prima di scrivere il piano, il modello produce qualche migliaio di token di ragionamento. Non è una quantità fissa: il modello decide quanto ragionare in base alla difficoltà, entro un livello di sforzo scelto dall'utente, e all'utente di solito ne arriva solo un riassunto. Non è nemmeno una formalità: è il luogo in cui i conflitti fra i due documenti vengono trovati, e senza quello spazio verrebbero risolti a caso mentre si scrive.
rendering statico, overlay debug. Non chiede player né fantasmi.
Ma il parsing della mappa richiede la mappa. §1 dice 240 pellet + 4 energizer,
28×31, e dà l'alfabeto — ma il layout non è fornito. Devo produrlo io.
Non posso copiare quello originale: §0 vieta asset di terze parti.
→ serve un labirinto originale, simmetrico, connesso, con esattamente 240 pellet.
→ contarli a mano è inaffidabile. Scrivo un validatore prima della mappa.
§12 chiede un test «il conteggio pellet è esattamente 244». Ottimo: è il mio oracolo.
CLAUDE.md vieta a core/ di toccare il DOM e chiede un test che lo verifichi.
Questo è fase 0: l'architettura va imposta subito, dopo sarebbe un refactor.
Timestep fisso a 60: accumulatore, max 5 tick di recupero. Attenzione — §11 chiede
anche pausa su visibilitychange. È fase 6 nel piano di consegna, ma il loop lo scrivo
ora: lascio l'aggancio senza implementarlo? → lo segnalo nel piano, non decido da solo.
Il piano esce, e ogni riga ha una fonte
Circa 1.200 token, prodotti uno alla volta. Quello che segue non è una risposta generica su come si struttura un progetto: quasi ogni riga è tracciabile a un punto preciso dei due documenti.
Il modello si ferma, e non «aspetta»
Emesso il token di fine turno, il processo termina. Non c'è nulla in esecuzione, nessuno stato che persiste, nessuna attesa. Quando l'utente risponde, tutto il contesto viene rimandato da capo — con due messaggi in più in fondo.
Un vincolo negativo che funziona
La §2 chiede una direzione artistica e poi fa una cosa insolita: elenca esplicitamente i cliché da non produrre. È il rimedio diretto al comportamento più prevedibile di un modello lasciato libero — proporre la media di ciò che ha visto.
Fondo nero puro, accento verde acido o vermiglio, scanline CRT, vignettatura, font pixel-art. Non perché sia una scelta: perché è la continuazione più probabile di «gioco arcade retrò» in tutto ciò che ha letto.
Toglie dal tavolo i candidati più probabili e costringe la generazione verso regioni meno battute. Non rende il modello più creativo: gli impedisce di cadere nel canale che scorre più veloce.
--wall-enamel #2B4CE0 corpo del muro, smalto vetrificato
--wall-specular #7FA0FF highlight in alto, 1 unità
--pellet-brass #E8C36A borchie
--alarm #FF5A3C solo energizer attivo e stato di allarme
signature: i muri sono nastri arrotondati con luce da sopra, non linee.
La stessa geometria genera il bordo, l'highlight e l'ombra interna:
un solo path, tre passate. Riconoscibile in uno screenshot da 200 px.
Il primo file, annotato
Sessanta righe di TypeScript. Nessuna di queste scelte è generica: ognuna risponde a una riga precisa dei due documenti, e su un progetto senza quei documenti sarebbero state tutte diverse.
const MAX_CATCHUP_TICKS = 5;
export function advance(state: GameState, input: Input, rng: Rng): GameState {
// pure: nessun accesso a Date/performance/Math.random qui dentro
...
}
Ventiquattro passi, in traccia
Da qui in avanti il modello lavora in ciclo: emette una chiamata a uno strumento, il software la esegue, il risultato rientra nel contesto, si riparte. La traccia di una fase reale è ripetitiva — ed è esattamente questo il punto.
Fra una chiamata e l'altra il modello ragiona sul risultato appena ricevuto, prima di decidere il passo successivo. E quando le azioni sono indipendenti, come i tre file di configurazione dei passi 1-3, può chiederle tutte insieme in un solo passo.
Ogni comando passa da un filtro: l'utente lo approva, oppure è in una lista di comandi consentiti, oppure gira in una sandbox senza accesso alla rete né ai file fuori dal progetto. npm install, che scarica codice da internet, è il tipico comando che si fa approvare.
Il momento in cui l'ambiente fa il suo lavoro
Il modello ha scritto una mappa di 31 righe che sembra giusta. Non lo è, e non poteva accorgersene rileggendola. Se ne accorge il validatore.
✗ row 22: expected width 28, got 29
✗ pellet count: expected 240, got 231
✓ energizer count: 4
✗ symmetry: 6 mismatched tiles on the vertical axis
✓ connectivity: all pellets reachable from spawn
Contare a 28 caratteri per 31 righe, mantenendo simmetria e un totale esatto, è precisamente il tipo di compito su cui la rappresentazione a token è debole. Non è una lacuna di ragionamento: è una lacuna percettiva.
Il messaggio dice quale riga, quanto, e di quanto sbagliata. Con quell'informazione la correzione è un compito facile, e in quattro giri si arriva a verde. Un errore che dicesse solo «mappa non valida» avrebbe prodotto tentativi alla cieca.
Un modello si corregge bene quando l'ambiente gli dice che ha sbagliato e in cosa; si corregge male quando deve accorgersene da solo. Tutto il valore di uno SPEC che impone test verificabili sta qui: trasforma un compito di scrittura, dove il modello è bravo ma fallibile in silenzio, in un compito di ricerca con un oracolo, dove è affidabile.
Trasformare una regola in un guardiano
Il CLAUDE.md non si limita a vietare a core/ di toccare il DOM: chiede di scrivere un test che fallisca se la regola viene violata. È la mossa più intelligente dei due documenti.
"Math.random", "Date", "setTimeout"];
const FORBIDDEN_IMPORTS = ["render/", "audio/", "input/", "ui/"];
test("core stays pure", () => {
for (const file of walk("src/core")) { ... }
});
Vale finché il modello ci presta attenzione. Alla fase 5 la riga è ancora nel contesto, ma sepolta sotto centomila token di codice e risultati. Il modello potrebbe scrivere performance.now() dentro core/ senza malizia, perché in quel punto la regola pesa poco.
Vale per sempre e non occupa contesto. Alla fase 5 il test fallisce, il messaggio d'errore rientra nel contesto, e il vincolo si riafferma da solo esattamente quando serve. Un vincolo eseguibile è un vincolo che non si dimentica.
Il test si riafferma solo se qualcuno lo esegue, e il passo successivo è non affidare nemmeno l'esecuzione al modello. Gli agenti di coding permettono di agganciare comandi (hook) a eventi del ciclo: dopo ogni scrittura di file parte il test di architettura, e il suo errore rientra nel contesto senza che nessuno l'abbia chiesto.
Cinque errori prevedibili, e la contromisura
Non sono errori casuali: sono conseguenze dirette di come il modello è fatto, e si possono prevedere prima di iniziare. Tre dei cinque sono già disinnescati dai due documenti.
| Errore | Perché accade | Contromisura |
|---|---|---|
| Mappa ASCII con righe di lunghezza sbagliata | i caratteri non sono visibili singolarmente nei token | già nella spec: validatore + test dei 244 |
| Tabelle numeriche riportate a memoria e imprecise | i pesi conservano i dati rari in modo approssimato; le cifre spezzate in più token peggiorano le cose | già nella spec: tutti i numeri in levels.ts, con nota sulla fonte |
| setTimeout per il freeze di 0,5 s sul fantasma mangiato | è la soluzione più frequente nel codice JavaScript esistente | già nella spec: divieto esplicito + «ogni durata in tick» |
| Allocazioni dentro il game loop | lo stile idiomatico moderno alloca liberamente, ed è quello su cui è addestrato | serve un test di performance, o una revisione mirata: la regola scritta da sola non basta |
| Cornering implementato male | è facile da descrivere e sottile da realizzare; gli esempi pubblici sono per lo più semplificati | un test deterministico con input registrati tick per tick cattura la meccanica; la sensazione di gioco resta un criterio di accettazione manuale |
Le domande che il modello riporta indietro
Uno SPEC di 336 righe scritto con cura contiene comunque una dozzina di punti sottodeterminati. Eccone sette. Trovarli è uno dei contributi più utili del modello — e avviene solo perché il CLAUDE.md lo autorizza esplicitamente a fermarsi.
Sette fasi, una finestra che si degrada
Ogni file letto, ogni uscita di test, ogni versione scartata resta nel contesto. Con una finestra da 200k alla fase 4 non ci si sta più. Con una da un milione ci si sta, ma ogni passo rilegge tutto, costa di più e il modello presta meno attenzione alle regole lontane. In entrambi i casi va deciso cosa buttare.
Il CLAUDE.md, che il software ricarica sempre · lo SPEC, se lo si rilegge dal disco o se è collegato dal CLAUDE.md: allegato una volta in chat, verrebbe solo riassunto · le decisioni prese, in docs/DECISIONS.md · lo stato del lavoro corrente · gli ultimi passi per esteso.
Il contenuto dei file già scritti — si rileggono dal disco quando servono · le uscite dei test già superati · le versioni intermedie della mappa · i passi di fasi chiuse.
A compattare è il modello stesso. Il software gli chiede di riassumere la sessione, e il riassunto prende il posto della cronologia. Quello che il riassunto omette è perso, a meno che non stia su disco. Prima di arrivarci si usano rimedi più leggeri: i risultati di strumento più vecchi vengono sostituiti da un segnaposto, e le ricerche esplorative si affidano a un sottoagente.
Un altro modo per non riempire la finestra è non farci entrare il lavoro. L'agente principale può delegare un compito a un sottoagente, cioè un'altra chiamata al modello con un contesto suo: per esempio rivedere tutto il codice della fase 2 contro lo SPEC. Il sottoagente legge trenta file e restituisce dieci righe di rilievi, e nel contesto principale entrano solo quelle. E un revisore che non ha scritto il codice ne vede meglio gli errori.
Il conto, dall'inizio alla fine
Sette fasi, con l'utente che approva ogni piano e gioca alla fine di ogni fase. I numeri sono stime plausibili, non un conto esatto, per un progetto di questa dimensione con un modello di frontiera.
| Quantità | Nota | |
|---|---|---|
| Chiamate al modello | ~250 | di cui ~200 passi di strumento |
| Token in ingresso letti | ~15 M | il contesto, sempre più lungo, si rimanda a ogni passo: in media ~60k per chiamata |
| Di cui riusati dalla cache | ~85% | a ogni passo si rielabora solo la coda nuova; il resto del prefisso viene dalla cache |
| Token prodotti | ~280 k | codice, test, piani, ragionamento |
| Righe di TypeScript consegnate | ~4.500 | in ~40 file |
| Rapporto prodotto / consegnato | ~4 : 1 | tre quarti dei token generati non sopravvivono |
| Ordine di spesa | 10-30 € | con la cache, secondo la fascia del modello; senza cache circa tre volte tanto |
| Tempo del modello | ~3 ore | sommando le generazioni |
| Tempo dell'utente | ~10 ore | revisione dei piani, prove di gioco, decisioni |
Le otto righe che hanno fatto la differenza
Ripercorrendo il tracciato, il risultato non dipende quasi mai da quanto è capace il modello. Dipende da otto scelte fatte prima di iniziare, tutte contenute nei due documenti.
| La riga | Cosa ha impedito | Stadio |
|---|---|---|
| «Produci un piano. Fermati e aspetta l'OK» | dieci file plausibili prima di poter intervenire | 05 |
| «Se emerge una decisione non coperta, fermati e chiedi» | una dozzina di ambiguità risolte a caso in silenzio | 15 |
| «Dillo prima di implementarla» | l'esecuzione muta di ciò che si ritiene un errore | 05 |
| «Aggiungi un test che fallisce se la regola è violata» | l'erosione dell'architettura quando il contesto si allunga | 13 |
| «Il conteggio pellet è esattamente 244» | un labirinto sbagliato scoperto alla fase 3 | 12 |
| «Tutti i numeri stanno in core/data» | tabelle riportate a memoria sparse nel codice | 14 |
| La tabella degli anti-pattern | cinque errori tipici, elencati prima che accadessero | 14 |
| «Se un test non passa, non allentare l'assert» | la scorciatoia statisticamente più probabile | 12 |
Il modello non ha capito la specifica. L'ha resa eseguibile.
Ogni stadio di questo percorso è la stessa operazione ripetuta: prevedere il pezzo di testo successivo, dato tutto quello che c'è nella finestra. Ciò che trasforma quella previsione in un gioco funzionante non è nascosto nei pesi — sono i test che dicono quando è sbagliata, e le due persone-giorno spese a scrivere i documenti prima di cominciare.