GERLA.CC

dietro le quinte · FIDATI.

Bug e pezzi di codice

Portare lo stesso gioco su Commodore 64 e Sega Mega Drive significa litigare con due hardware molto diversi, e ognuno morde a modo suo. Qui non c'è teoria: problemi reali incontrati durante lo sviluppo, e il frammento di codice, piccolo, che li ha risolti. Ogni pezzo è raccontato in parole semplici.

Commodore 64 · chip audio SID

Il gracchio fantasma

la musica di sottofondo «gracchia», un pop a ogni nota

Il SID dà a ogni voce un inviluppo: quando «premi il tasto» (il gate), il volume sale da zero. Il modo ovvio di suonare una melodia è ri-premere il tasto a ogni nota. Ma ogni ri-attacco fa ripartire il volume da zero di colpo, e quel salto brusco nell'onda si sente come un click. Su una melodia continua diventa una raffica di pop: il gracchiare. La soluzione è suonare legato, come un violinista che cambia nota senza staccare l'arco: il gate resta acceso e cambia solo la frequenza. Il tasto si ri-preme soltanto dopo una pausa vera.

; melodia (voce 3): la nota nuova entra senza staccare l'arco lda tmp sta $d40e ; frequenza, byte basso lda tmp+1 sta $d40f ; frequenza, byte alto lda m1g ; il gate e' gia' acceso? bne mtb ; si': solo frequenza, niente pop lda #$59 ; no: attacco lento (56 ms), decay dolce sta $d413
game2.asm · motore musicale

morale: il bug non era nelle note ma negli attacchi. Su hardware del 1982 il silenzio tra due note è una scelta di design, non un dettaglio.

Commodore 64 · processore 6502

Il registro che cancella gli indizi

le piattaforme a intermittenza risultano sempre solide, anche da spente

Il 6502 ha tre registri in croce (A, X, Y) e ogni istruzione che li tocca aggiorna anche i flag, gli indizi su cui si basano i salti condizionati. Qui il codice legge dalla griglia lo stato di una piattaforma lampeggiante e poi ripristina X e Y che aveva messo da parte. Peccato che quel ripristino sovrascriva i flag: il salto subito dopo non decideva più sul valore letto, ma sull'ultimo registro ripristinato.

tay lda BS,y ; stato del blocco: 1 = acceso ldy tmp2 ; ripristino Y... ldx tmp3 ; ...e X, che pero' RISCRIVE i flag! ; bne hyes ; il baco: decideva sul valore di X cmp #1 ; il fix: ri-confronta A prima di saltare beq hyes ; solido solo se davvero acceso
game2.asm · collisioni con le piattaforme a tempo

morale: in assembly non esistono variabili locali: ogni ripristino è un'istruzione come le altre, con effetti collaterali come le altre.

Commodore 64 · 64 KB per tutto

La memoria che trabocca in silenzio

aggiunto il secondo mondo, il gioco si blocca all'avvio senza un errore

Sul C64 codice, grafica, livelli e musica convivono negli stessi 64 KB, e ogni blocco ha il suo indirizzo fisso. Quando i dati dei livelli sono cresciuti (sei schemi invece di tre), hanno sconfinato dentro il set di caratteri. E l'assemblatore non ha detto nulla: per lui erano solo byte su byte. Il gioco leggeva «quante piattaforme lampeggianti ci sono?» e trovava un pezzo di font: 108. Il fix vero è stato dare a ogni blocco un'area dedicata; la lezione è stata mettere guardie nel sorgente: se un blocco cresce oltre il suo confine, la build fallisce con un messaggio chiaro, oggi e ogni volta.

; confini di memoria: se si sfora, la build FALLISCE (mai piu' in silenzio) .cerror * > $2000, "CODICE oltre $2000: invade il charset" .cerror * > INPUT, "LDATA oltre l'area input script" .cerror * > $4400, "TESTI oltre $4400: invadono ldata"
game2.asm · mappa di memoria

morale: il bug peggiore non è quello che fa crashare: è quello che corrompe piano e lontano. La difesa è far esplodere la build, non il gioco.

Mega Drive · due processori

Lo schermo viola

la ROM con l'audio nuovo non parte: schermo viola fisso

Il Mega Drive ha due cervelli: il 68000 (il gioco) e uno Z80 (di solito, l'audio). Qui l'audio lo guida direttamente il 68000, quindi allo Z80 va chiesto il bus: «fermati, passo io». C'è però una regola scritta in piccolo: uno Z80 tenuto in reset non concede mai il bus. Il codice aspettava la concessione prima di togliergli il reset: attesa infinita, prima ancora di accendere lo schermo. Il viola era il colore di sfondo di un gioco mai partito.

static void z80_stop(void) { uint16_t n = 16384; /* prima si toglie il RESET: uno Z80 in reset non concede mai il bus */ Z80_BUSREQ = 0x100; /* bus al 68000, per sempre */ Z80_RESET = 0x100; /* PRIMA di aspettare! */ while ((Z80_BUSREQ & 0x100) && --n) {} }
audio.c · inizializzazione audio

morale: e da quel giorno ogni attesa sull'hardware ha un contatore: al peggio parte il gioco senza audio, mai uno schermo viola.

Mega Drive · grafica a tile

La striscia che non voleva crollare

il pavimento finto crolla, ma a schermo resta una striscia di blocco

Sul Mega Drive lo schermo è un mosaico di tessere 8×8, e ogni blocco del gioco ne occupa quattro. La funzione put16 disegna un blocco scrivendo quattro tessere consecutive: la numero base, base+1, base+2, base+3. Per cancellare, l'istinto dice «disegna il blocco zero». Ma put16(0) scrive le tessere 0, 1, 2 e 3, e le tessere 1-3 sono pezzi del primo blocco. Ecco la striscia.

static void put16(/* ... */ uint16_t base, uint16_t pal) { plane_cell(PLANE_A, col, row, (pal<<13)|(base)); plane_cell(PLANE_A, col+1, row, (pal<<13)|(base+1)); /* ...con base=0 scrive le tile 1-3: pezzi di blocco! */ } static void clear16(uint16_t col, uint16_t row) /* il fix */ { plane_cell(PLANE_A, col, row, 0); /* quattro zeri veri: vuoto, non "blocco numero zero" */
main.c · disegno del livello

morale: «cancella» e «disegna il nulla» sembrano la stessa cosa. Su una console a tile, non lo sono.

Mega Drive · metodo di lavoro

Collaudare una console senza la console

come si verifica il codice audio se non puoi «vedere» dentro il chip?

Il trucco che regge tutto il porting: lo stesso identico sorgente C viene compilato due volte. Una per il 68000 del Mega Drive, dove le scritture vanno ai registri veri dei chip; una per il computer di sviluppo, dove le stesse scritture finiscono in un log. Sul log un programma di verifica controlla tutto: che il basso suoni legato, che gli effetti durino i frame giusti, che i canali si spengano. Se il collaudo passa, la ROM monta quello stesso codice, non una copia.

#ifdef AUDIO_TEST /* collaudo su PC */ #define YMW(part,reg,val) test_ym(part,reg,val) /* -> log */ #else /* ROM vera */ #define YM_A0 (*(volatile uint8_t*)0xA04000) /* -> chip */ #endif
audio.c · doppio bersaglio

morale: la stessa idea vale per la fisica del gioco: la logica del Mega Drive è verificata frame per frame, bit per bit, contro la versione originale. L'orecchio giudica il timbro; la matematica, tutto il resto.

← Torna a FIDATI. Il devlog