tutorial · game boy advancetutorial · game boy advance
Compila, gira, e non funzionaIt compiles, it runs, it doesn't work
Quando scrivi per una macchina nuda, senza sistema operativo e senza libreria standard, «compila e gira» non vuol dire «funziona». Vuol dire soltanto che nessuno si è lamentato. Questi sono cinque guasti veri incontrati scrivendo VECTOR per Game Boy Advance: nessuno dei cinque ha prodotto un errore, e solo per alcuni c'era qualcuno che avrebbe potuto avvisare, se glielo avessi chiesto. When you write for a bare machine, with no operating system and no standard library, "it compiles and runs" doesn't mean "it works". It only means nobody complained. These are five real faults met while writing VECTOR for the Game Boy Advance: none of the five produced an error, and only for some of them was there anyone who could have warned me, had I asked.
Una variabile su tre non cambiava maiOne variable in three never changed
Per un pezzo il gioco è stato muto. Il codice del suono era giusto, i registri erano quelli. Il difetto stava nello script del collegatore: mancava la sezione .data, quella delle variabili che partono da un valore.
For a while the game was mute. The sound code was right, the registers were the right ones. The fault was in the linker script: the .data section was missing, the one for variables that start from a value.
Perché è grave proprio su questa macchina: la cartuccia sta a 0x08000000 ed è sola lettura; la memoria scrivibile sta altrove. Una variabile inizializzata deve stare in due posti: il valore di partenza dentro la cartuccia, la variabile vera in memoria. All'accensione qualcuno copia il primo nella seconda. Se la sezione non c'è, quel qualcuno non copia niente e la variabile vive dentro la ROM: si legge benissimo, e le scritture cadono nel vuoto senza un lamento.
Why it bites hard on this machine in particular: the cartridge sits at 0x08000000 and is read-only; writable memory is elsewhere. An initialized variable has to live in two places: the starting value inside the cartridge, the real variable in RAM. At boot, someone copies the first into the second. If the section isn't there, that someone copies nothing and the variable lives in ROM: it reads perfectly, and writes fall into the void without a murmur.
Il rimedio sta nello script del collegatore: si dichiara la sezione con due indirizzi diversi, quello dove la variabile vive e quello da cui il suo valore parte, e all'avvio si copia.
The fix lives in the linker script: you declare the section with two different addresses, the one where the variable lives and the one its value comes from, and you copy at boot.
morale: il collegatore non si era lamentato. Aveva messo tutto in ROM e il programma girava. Un guasto che non impedisce l'avvio è più costoso di uno che lo impedisce, perché ti fa cercare nel posto sbagliato per giorni.
moral: the linker never complained. It put everything in ROM and the program ran. A fault that doesn't stop the boot costs more than one that does, because it sends you looking in the wrong place for days.
Il ramo scritto due volteThe branch written twice
La schermata di congedo non appariva mai. Il codice era questo, ed è il guasto più insidioso che abbia incontrato in questo progetto:
The closing screen never appeared. The code was this, and it's the most insidious fault I met in this project:
È C perfettamente valido. Il primo ramo cattura la condizione e non fa niente; il secondo, che contiene il lavoro, non viene eseguito mai. Nessun errore, e con le opzioni normali nemmeno un avviso.
It's perfectly valid C. The first branch catches the condition and does nothing; the second, the one holding the work, is never executed. No error, and with the usual options not even a warning.
Qui però qualcuno che avvisa c'è, e va solo acceso. GCC ha un'opzione fatta esattamente per questo: guarda le catene di if / else if e segnala quando la stessa condizione compare due volte, perché il secondo ramo è codice morto per costruzione.
Here, though, there is someone who warns, and it just has to be switched on. GCC has an option made exactly for this: it looks at if / else if chains and reports when the same condition appears twice, because the second branch is dead code by construction.
C'era poi un secondo difetto sovrapposto, e vale la pena dirlo perché è tipico del passaggio fra schermate. I registri della luminosità li scriveva solo il ramo del gioco. Il passaggio da una camera all'altra avviene dentro un lampo bianco, che è il vecchio trucco per nascondere il caricamento: uscendo dal gioco proprio lì, i registri restavano fermi sull'ultimo valore. Schermo bianco per sempre. Adesso, fuori dal gioco, si rimettono a posto una volta per quadro.
There was a second fault layered on top, and it's worth telling because it's typical of screen transitions. The brightness registers were written only by the game branch. Moving from one room to the next happens inside a white flash, which is the old trick for hiding a load: leaving the game right there, the registers stayed frozen on their last value. White screen forever. Now, outside the game, they're reset once per frame.
morale: quando lo stato della macchina lo scrive un solo ramo del programma, quel ramo diventa obbligatorio. E prima o poi ne uscirai da un'altra parte.
moral: when the machine's state is written by a single branch of the program, that branch becomes mandatory. And sooner or later you'll leave through another door.
Due cose nello stesso postoTwo things in the same place
Gli sprite pescano i loro disegni da un magazzino di tessere, e ogni sprite dichiara da quale posizione parte. Quelle posizioni erano somme di lunghezze scritte a mano: il personaggio prende tanto, poi il cubo comincia lì, poi il raggio comincia lì.
Sprites take their art from a store of tiles, and each sprite declares which slot it starts at. Those slots were sums of lengths written by hand: the character takes this much, then the cube starts there, then the beam starts there.
Una somma sbagliata non dà errore: dà due cose nello stesso posto. A schermo si vede una che mostra i pezzi dell'altra. È successo col raggio laser, che passando da due quadri a quattro è finito dentro le tessere della piattaforma.
A wrong sum raises no error: it gives you two things in the same place. On screen you see one of them showing pieces of the other. It happened with the laser beam, which growing from two frames to four ended up inside the platform's tiles.
morale: qualunque numero che dipenda da tutti i numeri scritti sopra di lui è una bomba a tempo. La difesa non è stare attenti: è far calcolare le posizioni a chi prepara i dati, e farlo fermare con il nome dei due blocchi che si pestano i piedi.
moral: any number that depends on every number written above it is a time bomb. The defence isn't being careful: it's letting whoever prepares the data compute the positions, and having it stop with the names of the two blocks treading on each other.
L'edera che cresceva nelle camere puliteThe ivy that grew in the clean rooms
Ogni camera ha una tabella di piante: quattordici caselle, e le camere che non ne hanno le lasciano vuote. «Vuoto» però non è un tipo di dato, è un accordo, e i due lati dell'accordo non erano d'accordo. Chi scriveva la tabella marcava la casella vuota con -1. Chi la leggeva saltava le caselle sotto -100.
Every room has a table of plants: fourteen slots, and the rooms that have none leave them empty. But "empty" isn't a data type, it's an agreement, and the two sides of the agreement disagreed. Whoever wrote the table marked an empty slot with -1. Whoever read it skipped slots below -100.
Preso da solo, ciascuno dei due è ragionevole. Messi insieme vogliono dire che -1 non è vuoto: è un valore buono. Così ogni camera in ordine, quelle della prima metà del gioco dove il cemento è pulito, si disegnava in silenzio quattordici rampicanti, tutti nello stesso posto, accatastati contro la parete di sinistra.
Taken on its own, each of the two is reasonable. Put together they mean that -1 isn't empty: it's a good value. So every tidy room, the ones in the first half of the game where the concrete is clean, quietly drew itself fourteen creepers, all in the same spot, stacked against the left wall.
Il compilatore qui non può fare niente, ed è la ragione per cui questo guasto è nella lista. Non c'è nessun errore di tipo: -1 è uno short valido come tutti gli altri, e < -100 è un confronto legittimo. Il vincolo che è stato violato non esiste nel linguaggio, esiste solo nella testa di chi ha scritto le due righe, e fra le due righe passavano parecchi mesi.
The compiler can do nothing here, and that's why this fault is on the list. There's no type error: -1 is as valid a short as any other, and < -100 is a legitimate comparison. The constraint that was broken doesn't exist in the language, it exists only in the head of whoever wrote the two lines, and several months passed between them.
Il rimedio non è ricordarsi meglio: è togliere l'accordo di mezzo. O il valore convenuto lo decide un posto solo, e tutti e due i lati usano quello, oppure non c'è nessun valore convenuto e la tabella porta con sé quante caselle sono piene, che è un numero e non una convenzione.
The cure isn't remembering better: it's taking the agreement out of the middle. Either the agreed value is decided in one place only, and both sides use that, or there is no agreed value at all and the table carries how many slots are filled, which is a number and not a convention.
Ma la parte che mi ha fatto perdere più tempo non è il difetto: è che non si vedeva. In un gioco dove la seconda metà è un impianto abbandonato invaso dall'edera, dell'edera in più non grida che c'è un errore. A colpo d'occhio sembrava perfino al posto giusto. Un guasto che produce qualcosa di plausibile è più caro di uno che produce qualcosa di assurdo, e va cercato con lo stesso metodo di tutti gli altri: leggendo la tabella in memoria invece che guardando il muro.
But the part that cost me most time isn't the fault: it's that you couldn't see it. In a game whose second half is an abandoned facility overrun with ivy, a bit of extra ivy doesn't shout that something is wrong. At a glance it even looked like it belonged. A fault that produces something plausible costs more than one producing something absurd, and it has to be hunted the same way as all the others: by reading the table in memory instead of looking at the wall.
morale: un valore convenuto è un contratto che nessuno fa rispettare. Se il linguaggio non può controllarlo, prima o poi le due parti si allontanano, e te ne accorgi solo se il risultato è abbastanza assurdo da farsi notare.
moral: an agreed value is a contract nobody enforces. If the language can't check it, sooner or later the two sides drift apart, and you only notice if the result is absurd enough to be noticed.
Finisce, e finisce in silenzioIt runs out, and it runs out quietly
Le camere, arrivate a sei, non ci stavano più. Il tetto su questa macchina non è la dimensione della memoria video: è che la casella della mappa dedica dieci bit al numero della tessera. Dieci bit sono 1024 numeri, e quindi uno sfondo può nominare al massimo 1024 tessere diverse, per quanta memoria tu abbia. Le camere ne volevano milleseicento.
The rooms, once there were six of them, didn't fit any more. The ceiling on this machine isn't the size of video memory: it's that the map entry gives ten bits to the tile number. Ten bits are 1024 numbers, so a background can name at most 1024 different tiles, however much memory you have. The rooms wanted sixteen hundred.
La causa non era la quantità di disegni: era la grana. I blocchi del muro avevano una grana di rumore unica per ogni posizione, quindi ogni blocco produceva quattro tessere sue e nessuna si poteva riusare. La cura è stata farla ripetere ogni quattro blocchi: a sessantaquattro punti di periodo la ripetizione va cercata per accorgersene, e nel frattempo le tessere identiche si fondono in una sola. Da 1600 a 900.
The cause wasn't the amount of art: it was the grain. The wall blocks had a noise grain unique to each position, so every block produced four tiles of its own and none could be reused. The cure was making it repeat every four blocks: at sixty-four pixels of period the repetition has to be hunted for to be noticed, and meanwhile identical tiles merge into one. From 1600 to 900.
E il guasto, di nuovo, non si annuncia: se sfori, la casella della mappa non ha i bit per il numero che le stai dando, e a schermo compare la tessera sbagliata. Nessun avviso, nessun blocco, solo un muro che ha addosso il pezzo di un'altra cosa.
And the fault, once more, doesn't announce itself: if you overflow, the map entry hasn't got the bits for the number you're handing it, and the wrong tile shows up on screen. No warning, no halt, just a wall wearing a piece of something else.
Il guasto che invece si lamentaThe fault that does complain
Per equilibrio vale la pena chiudere con l'unico che non sta zitto. Il processore di questa macchina non sa dividere: non esiste un'istruzione che lo faccia. Quando scrivi a / b con b qualunque, il compilatore non sbaglia e non inventa: mette una chiamata a una routine di libreria. E siccome qui la libreria standard non c'è, il programma non si collega più, con un bel riferimento non risolto e il nome della routine mancante.
For balance it's worth closing with the one that doesn't keep quiet. This machine's processor cannot divide: there is no instruction for it. When you write a / b with an arbitrary b, the compiler neither errs nor invents: it plants a call to a library routine. And since the standard library isn't here, the program stops linking, with a clean unresolved reference and the name of the missing routine.
Per questo in VECTOR ogni ciclo, ogni cadenza e ogni rampa è una potenza di due o un contatore che si riavvolge: uno spostamento di bit lo sa fare il processore, una divisione no. È un vincolo che all'inizio sembra una scomodità e dopo un po' diventa un modo di pensare.
That's why in VECTOR every cycle, every cadence and every ramp is a power of two or a counter that wraps: a bit shift the processor can do, a division it cannot. It's a constraint that feels like an inconvenience at first and after a while becomes a way of thinking.
morale: quando qualcosa si lamenta, ringrazia. I guasti che ti fermano subito sono i più economici che ti capiteranno.
moral: when something complains, be grateful. The faults that stop you immediately are the cheapest ones you'll ever get.
← Tutti i tutorial← All tutorials La scheda di VECTOR →The VECTOR page →