GERLA.CC

tutorial · game boy advancetutorial · game boy advance

La luce che faceva ombraThe light that cast a shadow

Sul Game Boy Advance non c'è un motore di luci: c'è un sommatore. Se gli chiedi una luce senza sapere come somma, ti restituisce una macchia di sporco. È successo davvero, scrivendo VECTOR, e il difetto era doppio. Vediamo come funziona la fusione dei colori su questa macchina, riga per riga, e perché sbagliarla non dà nessun errore. On the Game Boy Advance there is no lighting engine: there is an adder. Ask it for a light without knowing how it adds, and it hands you back a dirt stain. It really happened, writing VECTOR, and the fault was a double one. Let's see how colour blending works on this machine, line by line, and why getting it wrong raises no error at all.

Una camera di VECTOR, con i tubi al neon attorno alla soglia

i tubi al neon attorno alla soglia e i coni delle lampade sono sprite fusi con lo sfondo dall'hardware: costano trentadue tessere in tutto, e tutte le lampade se le passano. the neon tubes around the doorway and the lamp cones are sprites blended with the background by the hardware: they cost thirty-two tiles in total, and every lamp shares them.

01 · il meccanismothe mechanism

Due strati, due pesi, una sommaTwo layers, two weights, one sum

La macchina sa fondere due soli strati per volta, e li chiama primo e secondo bersaglio. Il primo è quello che sta sopra, il secondo quello che gli sta dietro. In un registro dici quali strati sono bersaglio e che effetto vuoi; in un altro dici con che peso entrano nella somma.

The machine can blend two layers only at a time, and it calls them first and second target. The first is the one on top, the second the one behind it. In one register you say which layers are targets and what effect you want; in another you say with what weight they enter the sum.

/* BLDCNT, 0x04000050: chi si fonde con chi, e come. bit 0..5 primo bersaglio (BG0, BG1, BG2, BG3, sprite, sfondo nudo) bit 6..7 effetto: 0 niente, 1 fusione, 2 schiarisci, 3 scurisci bit 8..13 secondo bersaglio, nello stesso ordine */ #define BLDCNT (*(volatile unsigned short*)0x04000050) #define BLDALPHA (*(volatile unsigned short*)0x04000052) #define PRIMO_SPRITE (1 << 4) /* gli sprite stanno sopra */ #define FUSIONE (1 << 6) /* effetto 1: alpha blending */ #define SECONDO_BG0 (1 << 8) /* il muro sta dietro */ BLDCNT = PRIMO_SPRITE | FUSIONE | SECONDO_BG0; BLDALPHA = 8 | (12 << 8); /* EVA = 8/16, EVB = 12/16 */
i registri della fusionethe blending registers

I due pesi si chiamano EVA e EVB, vanno da 0 a 16 sedicesimi, e sopra 16 si fermano. Il conto che l'hardware fa, canale per canale, è questo:

The two weights are called EVA and EVB, they run from 0 to 16 sixteenths, and above 16 they stop. The sum the hardware does, channel by channel, is this:

risultato = min(31, sopra × EVA/16 + sotto × EVB/16)

Va letto bene, perché è tutto lì: è una somma pesata, non una moltiplicazione e non uno «schiarisci». I due pesi non devono fare 16 insieme: possono farne di più, e il risultato si ferma a 31, che è il massimo di un canale a cinque bit. È questa libertà che permette di illuminare, ed è sempre questa che permette di sporcare.

Read it carefully, because everything is in there: it's a weighted sum, not a multiply and not a "lighten". The two weights don't have to add up to 16: they can add up to more, and the result stops at 31, the maximum of a five-bit channel. That freedom is what lets you light something up, and it's the same freedom that lets you dirty it.

02 · primo difettofirst fault

Un colore che non era mai arrivatoA colour that never arrived

I coni venivano più scuri del muro. Sembravano macchie di sporco. La prima causa non era nel disegno e non era nella fusione: era nel caricamento della tavolozza. Il ciclo che la copiava ne portava 96 voci su 128, e i coni stavano nel banco 7, cioè nelle ultime sedici. Quelle sedici voci non venivano scritte mai, quindi restavano a zero: nero.

The cones came out darker than the wall. They looked like dirt stains. The first cause wasn't in the art and wasn't in the blending: it was in loading the palette. The loop that copied it carried 96 entries out of 128, and the cones sat in bank 7, that is in the last sixteen. Those sixteen entries were never written, so they stayed at zero: black.

E un nero fuso con un muro, secondo la formula di prima, fa esattamente quello che ti aspetti: 0 × EVA/16 non aggiunge niente, e il muro entra nella somma con un peso minore di uno. Il risultato è il muro, più scuro. La luce era diventata un'ombra per via aritmetica.

And a black blended with a wall, by the formula above, does exactly what you'd expect: 0 × EVA/16 adds nothing, and the wall enters the sum with a weight below one. The result is the wall, darker. The light had become a shadow, arithmetically.

/* in 4 bit per punto la tavolozza e' fatta di 16 banchi da 16 colori: il banco 7 sono le voci da 112 a 127. Copiarne 96 vuol dire fermarsi al banco 5 e lasciare gli ultimi due a zero. */ unsigned short *tavolozza = (unsigned short*)0x05000200; /* sprite */ for(int i = 0; i < 96; i++) tavolozza[i] = sorgente[i]; /* il baco */ for(int i = 0; i < 128; i++) tavolozza[i] = sorgente[i]; /* il rimedio */

morale: un colore sbagliato a schermo non è per forza un problema di disegno. Può essere un colore che non è mai arrivato in memoria, e nessuno te lo dice: una tavolozza a metà non è un errore per la macchina, è solo una tavolozza con dentro degli zeri.

moral: a wrong colour on screen isn't necessarily an art problem. It can be a colour that never reached memory, and nobody tells you: a half-loaded palette isn't an error to the machine, it's just a palette with zeroes in it.

03 · secondo difettosecond fault

Perché una luce deve essere quasi biancaWhy a light has to be nearly white

Sistemata la tavolozza, i coni erano marroni. E continuavano a non illuminare. Il motivo è di nuovo la formula: se il colore sopra è più scuro di quello sotto in un canale, quel canale scende. Un marrone di mezzo ha il blu bassissimo, quindi al muro toglie blu, e il muro diventa uno straccio marrone invece che una zona illuminata.

With the palette fixed, the cones were brown. And they still didn't light anything. The reason is the formula again: if the colour on top is darker than the one below in a channel, that channel goes down. A mid brown has very low blue, so it takes blue away from the wall, and the wall turns into a brown rag instead of a lit area.

Con i colori a cinque bit per canale che usa questa macchina, e due pesi a metà, il conto è questo:

With the five-bit-per-channel colours this machine uses, and both weights at a half, the sum comes out like this:

muro R 6 G 7 B 10 scuro, bluastro cono marrone R 14 G 9 B 5 fuso a meta' R 10 G 8 B 7 <- il BLU e' SCESO: sporco cono quasi bianco R 30 G 28 B 24 fuso a meta' R 18 G 17 B 17 <- tutti e tre saliti: luce
lo stesso muro, due coni diversithe same wall, two different cones

Da cui la regola, che vale su qualunque macchina fonda per somma: il calore di una luce sta nella tinta, non nel valore. Il cono deve essere quasi bianco, e caldo solo per il fatto che il rosso è appena sopra il verde e il verde appena sopra il blu. Se metti il calore abbassando il blu, non stai accendendo una lampada: stai passando una velina marrone.

Hence the rule, which holds on any machine that blends by adding: the warmth of a light lives in the hue, not in the value. The cone has to be nearly white, and warm only because red sits just above green and green just above blue. If you put the warmth in by lowering the blue, you're not switching on a lamp: you're laying down a brown gel.

04 · la trappolathe trap

«Sprite fra i bersagli» vuol dire TUTTI gli sprite"Sprites among the targets" means ALL the sprites

Questa è la parte davvero specifica di questa macchina, e mi è costata un pomeriggio. Per far fondere il cono avevo acceso il bit degli sprite fra i primi bersagli, in BLDCNT. Sembra ragionevole: «gli sprite partecipano alla fusione». Solo che su questo hardware quel bit non vuol dire fondi quelli marcati. Vuol dire fondi tutti gli sprite.

This is the part that's really specific to this machine, and it cost me an afternoon. To make the cone blend I had switched on the sprite bit among the first targets, in BLDCNT. It sounds reasonable: "sprites take part in blending". Except that on this hardware that bit doesn't mean blend the marked ones. It means blend every sprite.

Risultato: il personaggio veniva fuori oliva invece che arancione, e il varco spariva, perché anche loro erano fusi col muro. Il meccanismo per scegliere quale sprite si fonde è un altro, e sta nell'attributo dello sprite, non nel registro globale: si chiama modo semitrasparente, e vale da solo, senza bisogno di toccare BLDCNT.

Result: the character came out olive instead of orange, and the portal vanished, because they were being blended with the wall too. The mechanism for choosing which sprite blends is a different one, and it lives in the sprite's own attribute, not in the global register: it's called semi-transparent mode, and it works on its own, with no need to touch BLDCNT.

/* SBAGLIATO: fonde ogni sprite a schermo, personaggio compreso */ BLDCNT = (1 << 4) | (1 << 6) | (1 << 8); /* GIUSTO: nessuno sprite fra i bersagli globali, e il singolo sprite del cono si dichiara semitrasparente nel suo attributo 0 (bit 10..11 = 01). Quel bit basta da solo. */ BLDCNT = (1 << 6) | (1 << 8); #define SEMITRASPARENTE (1 << 10) oam[i].attr0 = y | SEMITRASPARENTE; /* solo il cono */ oam[j].attr0 = y; /* il personaggio resta pieno */
il bit globale e il bit del singolo spritethe global bit and the per-sprite bit

morale: due comandi che sembrano dire la stessa cosa spesso non sono due modi di dirla. Uno è un interruttore generale, l'altro è un interruttore per stanza, e chi legge la documentazione di fretta accende la casa intera.

moral: two controls that seem to say the same thing are often not two ways of saying it. One is a master switch, the other a switch per room, and whoever reads the documentation in a hurry lights up the whole house.

come l'ho trovatohow i found it

Leggere i numeri, non guardare lo schermoRead the numbers, don't look at the screen

Il sintomo diceva «il personaggio è del colore sbagliato», e il primo istinto è andare a guardare il disegno o la tavolozza. Quello che ho fatto invece è stato leggerli davvero: il gioco gira dentro un emulatore comandato da uno script, che può leggere la memoria mentre la macchina va. La tavolozza in memoria era giusta, arancione. Allora ho letto i punti sullo schermo dove sta il casco: un quarto di arancione su tre quarti di muro.

The symptom said "the character is the wrong colour", and the first instinct is to go and look at the art or the palette. What I did instead was actually read them: the game runs inside an emulator driven by a script, which can read memory while the machine runs. The palette in memory was right, orange. So I read the pixels on screen where the helmet is: a quarter orange over three quarters wall.

Quel numero non dice «c'è un problema di colore»: dice esattamente quale operazione è stata fatta, e su chi. Da lì al registro della fusione è un passo solo. Il colore di un punto, la posizione di uno sprite nella tavola, il contenuto di una tessera in memoria video: su queste macchine sono tutte cose che si possono leggere invece che dedurre, e diverse volte il sintomo diceva una cosa e il numero ne diceva un'altra.

That number doesn't say "there's a colour problem": it says exactly which operation was performed, and on what. From there to the blend register is a single step. A pixel's colour, a sprite's slot in the table, the contents of a tile in video memory: on these machines they are all things you can read rather than infer, and more than once the symptom said one thing and the number said another.

← Tutti i tutorial← All tutorials La scheda di VECTOR →The VECTOR page →