Tachibana

Neo6502 › Artikelen › AsteroNeo

Porten: van Astéroric naar AsteroNeo

Hetzelfde spel, dezelfde C, een andere machine: wat verandert als je van een 6502 op 1 MHz en een zelfgemaakte bitmap overstapt naar een 65C02 op 6,25 MHz waarvan de RP2040 de lijnen tekent.

Door bmarty · Tachibana · 16 september 2026

Astéroric is onze studiekloon van Asteroids (Atari, 1979) voor de Oric-1 48K: de logica van de arcade-ROM rev 4 in cc65-C, een zelfgemaakte XOR-vectorrenderer, 25 Hz in 20.000 cycli per frame — het vorige artikel vertelt de veertig fasen die daarvoor nodig waren. AsteroNeo is de port naar de Neo6502 van Olimex: een echte W65C02S op 6,25 MHz, een RP2040 als chipset en een API op $FF00. Kaderbeslissingen: een eigen repository, de upstream-firmware in modus 0 (320×240, 256 kleuren), speelveld op volledig scherm, cc65-C voor de logica en ca65 voor de API-laag, 30 Hz, XOR-rendering via de API, Phosphoneo als testorakel en de officiële emulator neo voor de rooktest. Twee sprints later is het spel speelbaar in emulatie; dit is wat veranderde, en wat tegenstribbelde.

Oric-1 → Neo6502, in één tabel

Oric-1 (Astéroric)Neo6502 (AsteroNeo)
HIRES 240×200 mono, zelfgemaakte XOR-Bresenhammodus 0 320×240, XOR-lijnen en -pixels van de API (5,1 / 5,2 / 5,5)
asteroïden als vooraf gerenderde bitmaps (blit)veelhoeken afgeleid van de arcadevormen, gladgestreken (24/16/10 hoekpunten), getekend door de RP2040
toetsenbord: VIA/PSG-matrixKey Status per HID-code (1,2), vijf herconfigureerbare acties
AY-3-8912 onder Timer 1-IRQ4-kanaals blokgolf/ruis-generator, nootwachtrijen (8,7)
VSync/Timer 1, 25 Hzframeteller op 60 Hz (5,37), spelstap op 30 Hz
8-bits coördinatenint-coördinaten (320 > 255), 8.8-integratie met carry

1. Wat niet beweegt

De spellogica — golven, fragmentatie van asteroïden, AI van de twee schotels, hyperspace, highscores — blijft ongewijzigd: het is de overzetting van de arcade-disassemblage en hangt van geen enkele hardware af. De zeven C-modules van Astéroric (game, asteroids, ufo, hud, font, title, keys) gaan van de ene machine naar de andere. Alles wat de Oric-hardware raakte — HIRES, VIA, AY — wordt vervangen door een Neo6502-laag van vier bestanden: neo_gfx.s (tekenen), neo_time.c (timing), neo_input.c (toetsenbord), neo_sound.c (geluid).

2. Tekenen: van eigen Bresenham naar de lijnen van de firmware

Op de Oric kostte een lijn ~97 cycli per pixel, en dat dicteerde de 25 Hz. Op de Neo6502 tekent de RP2040 de lijn: de 65C02 zet de coördinaten klaar en roept functie 5,2 aan. Gemeten in co-simulatie op de echte firmware (Phosphoneo + libemul) kost een lijn ≈ 22 65C02-cycli (544 ARM-stappen), een pixel ≈ 5 cycli — ongeacht de lengte. Het budget verandert van aard: je telt geen pixels meer, je telt aanroepen.

De subtiliteit zit elders. De lijn van de firmware (EFLA-algoritme) is halfopen: ze tekent [P0, P1[ en laat het eindpunt weg. In XOR verliest een segment voor segment getekende veelhoek dan om het andere hoekpunt, of licht het twee keer op. neo_gfx.s maakt er twee primitieven van: een gesloten lijn (lijn + eindpixel) en een "open" lijn ]P0, P1], achterstevoren getekend, zodat elk hoekpunt precies één keer wordt ge-XOR'd. Het testprogramma t_xor controleert de idempotentie: een veelhoek twee keer tekenen laat het scherm leeg.

3. Coördinaten: 320 past niet in een byte

Astéroric leefde in 8 bits: 240 kolommen, alles paste in een register. Bij 320 pixels breed worden posities int, en de 8.8-vastekomma-integratie van snelheden gebeurt met carry (phys.c). Toroïdale botsing en wraparound door duplicatie (een object op een rand wordt twee keer getekend) zijn herschreven op 320×240 — en de spawn-, wrap- en HUD-constanten aangepast aan het volledige scherm.

4. Timing: 30 Hz op frames van 60 Hz

Geen VSync of Timer 1 meer: de firmware biedt een frameteller op 60 Hz (5,37). De spelstap staat op 30 Hz — twee frames — en alle voor 25 Hz gekalibreerde constanten zijn herberekend. Met de gemeten latentie hield sprint 1 het tempo met 3 late stappen op 255.

Toen vroeg de playtest om rondere asteroïden: de door gen_shapes.py gladgestreken Atari-silhouetten gaan van 11-13 naar 24/16/10 hoekpunten. De C-lus die de segmenten aaneenreeg kostte ~1.500 cycli per segment en het spel zakte naar 200 late stappen op 255. Het tekenen van veelhoeken is herschreven in assembler (poly_xor: clipping per segment, halfopen segmenten): 2 late stappen op 255. Op deze machine is de flessenhals niet de pixel, maar de C-verpakking rond elke aanroep.

5. Toetsenbord en geluid

De VIA/PSG-matrix van de Oric maakt plaats voor de toestand van elke toets per HID-code (1,2). De vijf acties — draaien, stuwkracht, hyperspace, vuren, stoppen — zijn vanuit het CONTROLS-scherm aan elke benoemde toets toe te wijzen; de toewijzing leeft in RAM. Het geluid verlaat de AY en zijn IRQ voor de 4-kanaals generator van de firmware (8,7): elk effect van Astéroric is nagemaakt als wachtrij van blokgolf/ruis-noten, jingle inbegrepen. Zonder hardware-envelope zijn de uitdovingen volumetrappen; op het gehoor gevalideerd in neo op 16 september — op een emulator, niet op een bordje.

6. Twee aandachtspunten in de toolchain

cc65 2.19 en OptStackOps. De asteroïden verschenen als een kluwen van segmenten terwijl dezelfde logica, met gcc op de host gecompileerd, correct was. De optimizer, die bewerkingen op de C-stack samenvoegt, indexeerde de tabel met hoekpuntaantallen met de lage byte van een pointer in plaats van met het vorm-id. Omweg: --disable-opt OptStackOps voor het hele project (enkele cycli per aanroep, zonder effect op 6,25 MHz) en n lezen vóór de pointers. Les: elke ingeschakelde optimalisatiepas moet opnieuw langs de referentieopnames.

Netjes afsluiten. Een jmp ($FFFC) om naar NeoBASIC terug te keren herstartte het spel: de resetvector is gepatcht naar het uitvoeradres van de .neo. De uitgang loopt nu via de API (1,3: NeoBASIC herladen) en dan jmp (0), zoals de kernel bij reset; de test t_exit typt PRINT 6*7 na het afsluiten en verwacht 42.

7. De tests

Drie niveaus, allemaal in make test. Op de host wordt de draagbare logica met gcc gecompileerd tegen stubs die de EFLA van de firmware nabootsen (8.8-integratie, botsingen, vormtabellen, XOR-idempotentie, wraparound, fragmentatie, toetsentabel). Op het doel speelt Phosphoneo deterministische scenario's — titelscherm, een partij, CONTROLS, de programma's t_line, t_xor, t_exit — en worden de opnames bit voor bit met de referenties vergeleken; het tempo wordt gemeten onder de in co-simulatie vastgelegde latentie. Tot slot een rooktest in de officiële emulator. Dat vangnet ving het cc65-geval, de ontbrekende hoekpunten en de valse terugkeer naar NeoBASIC.

8. Wat we niet beweren

Er is geen Neo6502-bordje aangesloten: alles is gecontroleerd in Phosphoneo, in co-simulatie met de echte firmware en in neo. De echte aanroeplatentie op een bordje moet nog gemeten worden (plan B: hardware-sprites). Het geluid is alleen op een emulator op het gehoor gevalideerd. Sprint 2 loopt: USB-gamepad, een 60 Hz-optie, bewaren van highscores en toetsentoewijzing op SD/USB. De binary asteroneo.neo (v0.2.0) staat op GitHub en Framagit; de CHANGELOG van de repository is leidend.

Bronnen & links

← Neo6502 · Prophet → · Neo6502 →