Zyklischer Bildschirm-Rauschen während des Uploads von ASIC-Hardware-Sprite-Bitmaps

[Hardware/GX4000] Περιοδικός θόρυβος οθόνης κατά τη μεταφόρτωση bitmap hardware-sprite του ASIC (&4000-&4FFF) — πραγματικό 6128 Plus, RASM/4-bank .cpr

Γεια σε όλους,

Κάνω debugging σε μια homebrew κασέτα για GX4000/CPC+ (RASM, 4-bank .cpr, δοκιμασμένο σε πραγματικό 6128 Plus μέσω RGB SCART) και συνάντησα ένα πρόβλημα αλλοίωσης της εικόνας (display corruption) το οποίο απομόνωσα με αρκετή ακρίβεια μετά από πολλούς γύρους δοκιμών, αλλά δεν μπορώ να εξηγήσω. Το δημοσιεύω μήπως θυμίζει κάτι σε κάποιον με εμπειρία σε πραγματικό υλικό (real hardware).

Συμπτώματα

Μετά τη μεταφόρτωση του sprite bitmap (16 x 256-byte LDIR στο &4000-&4FFF), η οθόνη εμφανίζει έναν περιοδικό οπτικό θόρυβο — λεπτά επαναλαμβανόμενα διαγώνια/κουκκιδωτά μοτίβα σε μεταβαλλόμενα χρώματα, που εναλλάσσονται με σύντομες στιγμές καθαρής εικόνας, περίπου κάθε λίγα δευτερόλεπτα. Μοιάζει με πίεση στον συγχρονισμό composite/RGB (παρόμοιο με dot-crawl) και όχι με κατάρρευση της CPU (CPU crash) — η εκτέλεση του κώδικα συνεχίζεται κανονικά καθ’ όλη τη διάρκεια (επιβεβαιωμένο με άλλα μέσα).

Τι λειτουργεί επιβεβαιωμένα χωρίς πρόβλημα μεμονωμένα (το καθένα δοκιμασμένο μόνο του σε πραγματικό υλικό, σταθερή οθόνη, χωρίς θόρυβο)

  • Ακολουθία ξεκλειδώματος ASIC (Standard ακολουθία 17 bytes στο &BC00)
  • Επιλογή λειτουργίας (mode) του Gate Array (&7F00,&80 — mode 0, τα ROM ανέγγιχτα. Σημείωση: το &7F00,&88, το οποίο επίσης απενεργοποιεί το upper ROM, προκαλεί πλήρη απώλεια συγχρονισμού βίντεο σε αυτό το υλικό — “No signal” στην τηλεόραση, που παραμένει και μετά από reset — ξεχωριστό εύρημα, που ενδεχομένως αξίζει δικό του thread)
  • Πλήρης αρχικοποίηση (init) του CRTC, R0-R9 + R12/R13 (τυπικές τιμές Amstrad 50Hz) μέσω &BC00/&BD00, όσο το ASIC είναι ξεκλείδωτο
  • Μεταφόρτωση παλέτας οθόνης + παλέτας sprites (2 LDIR, 32+30 bytes, στο &6400/&6422) μέσω του ίδιου μηχανισμού ASIC I/O page (&7F00,&B8 / &7F00,&A0)

Τι αναπαράγει τον θόρυβο

Η μεταφόρτωση των 16 sprite bitmaps (256 bytes το καθένα, απλό LDIR, στα &4000, &4100, &4200 … &4F00) — επιβεβαιωμένο μεμονωμένα, με όλα τα παραπάνω παρόντα και σταθερά. Ο θόρυβος εμφανίζεται ανεξάρτητα από το:

  • αν οι καταχωρητές (registers) των sprites (&6000-&607F) μηδενίζονται (mag=0, δηλαδή “δεν προβάλλονται” σύμφωνα με τη τεκμηριωμένη μορφή) πριν ή μετά τη μεταφόρτωση του bitmap
  • αν τα 16 LDIRs εκτελούνται ως μία συνεχόμενη ριπή (continuous burst) (~21ms, δηλαδή μεγαλύτερη από ένα frame των 20ms) ή αν κατανέμονται σε πολλαπλά frames με αναμονή VSync μεταξύ κάθε μεταφοράς
  • αν η μεταφόρτωση γίνεται πριν ή μετά την αρχικοποίηση του CRTC/βίντεο (δηλαδή, αν δημιουργείται ενεργά εικόνα εκείνη τη στιγμή)

Κώδικας (σχετικό απόσπασμα)

ASIC_IN:
    ld bc,#7fb8
    out (c),c
    ret
ASIC_OUT:
    ld bc,#7fa0
    out (c),c
    ret

InitSprites:
    call ASIC_IN
    ; τα sprites αποκρύπτονται πρώτα (mag=0, Y=255), δοκιμάστηκε και πριν/μετά τη μεταφόρτωση
    ld hl,#6002
    ld de,8
    ld b,16
.hide:
    ld a,255
    ld (hl),a
    push hl
    inc hl
    inc hl
    xor a
    ld (hl),a
    pop hl
    add hl,de
    djnz .hide

    ld hl,SpritePlayer
    ld de,#4000
    ld bc,#100
    ldir
    ; ... 15 ακόμη ίδια LDIR blocks, de = #4100, #4200, ... #4f00
    call ASIC_OUT
    ret

Πράγματα που έχω αποκλείσει

  • Δεν πρόκειται για σφάλμα κώδικα/λογικής στο δικό μας game loop (αναπαράγεται ακόμα και αν το game loop μειωθεί σε ένα άδειο WaitVSync / jr loop)
  • Δεν οφείλεται στο CRTC config (επιβεβαιωμένα καθαρό μεμονωμένα, R0-R9 όλα παρόντα)
  • Δεν οφείλεται στον ίδιο τον μηχανισμό ξεκλειδώματος/κλειδώματος του ASIC (επιβεβαιωμένο μέσω της μεταφόρτωσης παλέτας που χρησιμοποιεί τον ακριβώς ίδιο μηχανισμό &7FB8/&7FA0, χωρίς κανένα πρόβλημα εκεί)
  • Δεν πρόκειται για πρόβλημα τύπου “το sprite προβάλλει προφανή σκουπίδια κατά τη διάρκεια της μεταφόρτωσης” από όσο μπορώ να καταλάβω — η αλλαγή της σειράς απόκρυψης πριν από τη μεταφόρτωση δεν άλλαξε τίποτα, και το τεκμηριωμένο mag=0 σημαίνει ήδη “δεν προβάλλεται” και όχι “μέγεθος 1x” (αρχικά υπέθεσα το δεύτερο, που ήταν λάθος)
  • Διασταυρώθηκε με τον δημοσιευμένο οδηγό hardware-sprite του roudoudou (programmation assembleur Z80) — η ακολουθία ξεκλειδώματος, η χρήση του RMR2 (&7F00,&B8/&A0) και η τεχνική αντιγραφής LDIR ταυτίζονται όλα ακριβώς με την υλοποίηση αναφοράς του, οπότε δεν φαίνεται να πρόκειται για λάθος υλοποίησης από πλευράς μας.

Σημείωση για εξομοιωτές (Emulators)

Ενδιαφέρον είναι ότι το CaPriCe Forever (Windows) εκτελεί αυτή την κασέτα οπτικά μια χαρά όσον αφορά τα μέρη του CRTC/ASIC-unlock, αλλά κολλάει τελείως εάν εγγραφούν καθόλου οι καταχωρητές 8 ή 9 του CRTC (ακόμη και μεμονωμένα, ακόμη και με ακίνδυνες τιμές 0/7) — μια απόκλιση από το πραγματικό υλικό, όπου οι R8/R9 είναι απαραίτητοι και λειτουργούν μια χαρά. Το cap32 (Linux) δεν φτάνει αρκετά μακριά για να δοκιμαστεί (φαίνεται να αγνοεί/διαχειρίζεται εσφαλμένα το 4-bank .cpr συνολικά, κάνοντας boot απευθείας στο BASIC του firmware ακόμα και με επιλεγμένο το CPC 6128+ — πιθανώς ένα ξεχωριστό πρόβλημα συμβατότητας με τη μορφή .cpr από την πλευρά του cap32, καθώς ένα γνωστό εμπορικό 4/8-bank .cpr που λειτουργεί σωστά — το Bomb Jack GX — έχει το ίδιο πρόβλημα εκεί, ενώ λειτουργεί μια χαρά στο CaPriCe Forever και σε πραγματικό υλικό).

Ερώτηση

Έχει δει κανείς αυτή τη συγκεκριμένη συμπεριφορά “θορύβου κατά/μετά τη μεταφόρτωση δεδομένων bitmap hardware sprite” σε πραγματικό υλικό Plus/GX4000; Υπάρχει κάποιος τεκμηριωμένος περιορισμός συγχρονισμού (timing constraint) που μου διαφεύγει — π.χ. η λογική ανάγνωσης των sprites (sprite-fetch logic) συνεχίζει να διαβάζει τα &4000-&4FFF ανεξάρτητα από τον καταχωρητή mag/ορατότητας, με αποτέλεσμα η εγγραφή εκεί την ώρα που δημιουργείται εικόνα να προκαλεί ορατές παρεμβολές στο πραγματικό πυρίτιο (γνωστά σφάλματα/errata του ASIC); Και αν ναι, υπάρχει κάποια προτεινόμενη εναλλακτική λύση (workaround) (π.χ. μεταφόρτωση μόνο κατά τη διάρκεια ενός συγκεκριμένου παράθυρου raster, ή κάποιο επιπλέον βήμα κλειδώματος/σταθεροποίησης γύρω από το ASIC page-in);

Ευχαρίστως να μοιραστώ τον πλήρη πηγαίο κώδικα ή ένα δοκιμαστικό .cpr αν είναι χρήσιμο. Ευχαριστώ που διαβάσατε ως εδώ!

1 «Μου αρέσει»

Δεν έχω δει ποτέ αλλοίωση στην οθόνη κατά την ενημέρωση των sprites. Μπορεί να υπάρξουν περίεργα θέματα αν σκρολάρεις λιγότερο από ένα pixel σε όποια λειτουργία κι αν βρίσκεσαι αυτή τη στιγμή, αλλά υποθέτω πως δεν κάνεις κάτι τέτοιο;

Η κύρια ασυμφωνία μεταξύ της εξομοιούμενης και της πραγματικής προβολής των sprite εμφανίζεται όταν υπάρχει διένεξη ανάμεσα στην εγγραφή δεδομένων sprite και την ενημέρωση της οθόνης. Κανένας από τους τρέχοντες εξομοιωτές (απ’ όσο γνωρίζω) δεν το διαχειρίζεται αυτό σωστά.

Ευχαριστώ για την απάντηση — αυτή η σημείωση σχετικά με τη «σύγκρουση μεταξύ εγγραφής δεδομένων sprite και ενημερώσεων οθόνης» είναι πολύ χρήσιμο πλαίσιο, και όχι, δεν κάνω υπο-πίξελ κύλιση (αφαίρεσα εντελώς τους καταχωρητές soft-scroll, δεν ήταν καν ενεργοποιημένοι).

Μια διευκρίνιση πάντως που πιστεύω ότι αλλάζει τα δεδομένα: ο θόρυβος δεν είναι ένα μεμονωμένο παροδικό φαινόμενο που συνδέεται με το ίδιο το ανέβασμα — είναι ένα επίμονο, επαναλαμβανόμενο μοτίβο που συνεχίζει να ανακυκλώνεται (περίπου κάθε λίγα δευτερόλεπτα) κατά τη διάρκεια του κανονικού παιχνιδιού, πολύ μετά την ολοκλήρωση του ανεβάσματος των sprite (το οποίο γίνεται μόνο μία φορά στην εκκίνηση, συνολικά ~21ms). Επομένως, δεν φαίνεται να πρόκειται για μια συνεχή σύγκρουση διαύλου που συμβαίνει σε κάθε καρέ — μοιάζει περισσότερο σαν το ανέβασμα να αφήνει το υλικό σε μια ελαφρώς διαφορετική/οριακή κατάσταση στη συνέχεια (χρονισμός CRTC, εσωτερική κατάσταση ASIC, συχνότητα συγχρονισμού), η οποία στη συνέχεια εκδηλώνεται ως επαναλαμβανόμενο πρόβλημα κλειδώματος συγχρονισμού στην τηλεόραση.

Δοκίμασα να σβήσω εντελώς την οθόνη κατά τη διάρκεια του ανεβάσματος (CRTC R6=0 για όλη τη μεταφορά, επαναφορά στη συνέχεια) με τη θεωρία ότι η αφαίρεση της ενεργής σάρωσης κατά την εγγραφή θα απέφευγε τη σύγκρουση — καμία αλλαγή, ο ίδιος επαναλαμβανόμενος θόρυβος στη συνέχεια. Οπότε όποια κατάσταση κι αν μένει πίσω, επιβιώνει μετά την ίδια τη μεταφορά, όχι μόνο κατά τη διάρκειά της.

Για να είμαι πλήρως ειλικρινής: οι δικές μου γνώσεις γύρω από τον Amstrad είναι παρωχημένες κατά περίπου 35 χρόνια αυτή τη στιγμή, και συντονίζω αυτήν τη σφαλμάτωση μέσω ενός βοηθού τεχνητής νοημοσύνης που γράφει τον δοκιμαστικό κώδικα και ερμηνεύει την έξοδο του αποσφαλματωτή υλικού (ACE) για μένα, καθώς δεν θυμάμαι πλέον ο ίδιος τις λεπτομέρειες των εσωτερικών λειτουργιών των ASIC/CRTC. Γι’ αυτό ζητώ συγγνώμη αν αργώ να παρακολουθήσω πιο λεπτομερείς τεχνικές ερωτήσεις — ευχαρίστως να τρέξω οποιαδήποτε συγκεκριμένη δοκιμή ή να πάρω οποιοδήποτε συγκεκριμένο register dump από τον ACE, αν μπορείτε να μου πείτε ακριβώς τι να κοιτάξω.

Συγκεκριμένα, αν βοηθάει, μπορώ να:

  • Τρέξω το παιχνίδι με ανοιχτά τα πάνελ καταχωρητών CRTC/ASIC του ACE και να βγάλω ένα στιγμιότυπο οθόνης/σύντομη καταγραφή σε οποιοδήποτε конкреμένο σημείο θέλετε (π.χ. αμέσως πριν/μετά το ανέβασμα των sprite, ή κατά τη διάρκεια μιας στιγμής που εμφανίζεται ο θόρυβος)
  • Δοκιμάσω συγκεκριμένες τιμές καταχωρητών που θα προτείνατε
  • Μοιραστώ τον πλήρη πηγαίο κώδικα ή ένα ελάχιστο .cpr αναπαραγωγής

Ευχαριστώ και πάλι για τον χρόνο σας.

Αν μπορείς να δημοσιεύσεις ένα ελάχιστο .cpr στο οποίο να αναπαράγεται το σφάλμα, είμαι σίγουρος ότι θα μπορέσουμε να το εντοπίσουμε πολύ γρήγορα :+1:

Av μπορείτε να τραβήξετε ένα βίντεο, αυτό επίσης θα βοηθούσε. Και, έτσι από ενδιαφέρον, έχετε συνδεδεμένες συσκευές επέκτασης στο Plus σας;

Ένα βίντεο, αν είναι δυνατόν, και ο ελάχιστος δυνατός πηγαίος κώδικας θα ήταν χρήσιμα.

Πρόκειται για μοτίβο κουκκίδων φυσικών pixel που μοιάζει με «χιόνι» (όπως το «snow» του ZX Spectrum) ή περισσότερο για μοτίβο παρεμβολής στην οθόνη;

Συμβαίνει και στα δύο, Plus και GX4000;

To GX4000 έχει μια ελαφρώς ρυθμισμένη συχνότητα κύριου ρολογιού για να παρέχει «καλύτερο σήμα» στις τηλεοράσεις.

Πιστεύω ότι τόσο το GX4000 όσο και το Plus έχουν παρατηρηθεί να εμφανίζουν κάθετες «κάγκελα φυλακής» (jail bars) στην εικόνα, τα οποία νομίζω ότι μπορούν να φιλτραριστούν, αλλά όχι pixels ως τέτοια.

Ποια είναι η οθόνη; Μια σύγχρονη επίπεδη οθόνη, μια παλιά CRT;

Το κύριο πρόβλημα που έχω δει είναι ότι η εγγραφή των δεδομένων pixel ενώ εμφανίζεται το sprite προκαλεί το οπτικό κόψιμο ή την εξαφάνιση του sprite για μικρό χρονικό διάστημα (συνήθως για όσο διαρκεί η εγγραφή). Δεν έχω δει άλλη διαταραχή και τίποτα μετά την εγγραφή των sprites.

Υπάρχει κάποιο συγκεκριμένο sprite στο οποίο συμβαίνει αυτό; Αν ενεργοποιήσεις μόνο ένα, εμφανίζεται;

Αλλάζει κάτι αν η μεγέθυνση (mag) ρυθμιστεί για εξομοίωση πλάτους pixel των mode 0 και mode 1;

1 «Μου αρέσει»

Έχασα το σημείο σχετικά με το mode 0 που δημιουργεί προβλήματα, μπορείς να μοιραστείς κώδικα που το προκαλεί αυτό;

Μοιάζει περισσότερο με θέμα hardware. Δεν έχω δει ποτέ το mode 0 να προκαλεί θέματα συγχρονισμού.

Οι σκέψεις μου είναι: Έλεγξε την τροφοδοσία του υπολογιστή. Έλεγξε τον κρύσταλλο των 40Mhz. Έλεγξε το Sync στην υποδοχή Scart (καλό Vsync και Hsync, ειδικά όταν χρησιμοποιείς mode 0). Αν χρησιμοποιείς Pico GX ή κάτι παρόμοιο και δεν το έχεις κάνει ήδη, αναπαρήγαγέ το χρησιμοποιώντας κώδικα που τρέχει με συνδεδεμένο ένα basic cartridge, για παράδειγμα.

1 «Μου αρέσει»

Ποια είναι η τιμή του δείκτη στοίβας (Stack pointer) σου; Είχα κι εγώ κάτι σκουπίδια στην οθόνη μου πριν από λίγους μήνες, μέχρι που κατάλαβα ότι είχα ξεχάσει να αρχικοποιήσω τον καταχωρητή SP, και όλα τα push & pop γίνονταν μέσα στο double buffer μου :face_without_mouth:

1 «Μου αρέσει»

Eυχαριστώ για τις απαντήσεις σας, τελικά βρήκα την αιτία αυτού του bug!
Συνοπτικά, η συνάρτησή μου NextRandom χρησιμοποιεί τον καταχωρητή B ως μεταβλητή εργασίας χωρίς να τον αποθηκεύει, ενώ η SpawnMeteors (και η InitStars) τον χρησιμοποιούν παράλληλα ως μετρητή βρόχου djnz. Κάθε κλήση της NextRandom μέσα στον βρόχο αντικαθιστούσε αυτόν τον μετρητή με μια ψευδοτυχαία τιμή, προκαλώντας απόκλιση στον αριθμό των επαναλήψεων και καταστρέφοντας τη μνήμη πολύ πέρα από τους προβλεπόμενους πίνακες.
Διόρθωση: push bc / pop bc γύρω από το σώμα της NextRandom.
Μου απομένουν μερικά bugs που ακόμα δεν καταλαβαίνω:
Οι συγκρούσεις (βλήμα↔alien, σκάφος↔μετεωρίτης) εξακολουθούν να μην λειτουργούν.
Ακόμη καμία μουσική DMA (καθόλου ήχος).
Kai τέλος το χειρότερο από όλα, η εικόνα είναι περικεκομμένη πάνω αριστερά. Το παιχνίδι μου εμφανίζεται μόνο στο αριστερό τρίτο της οθόνης, αλλά αν μετακινήσω το σκάφος προς τα δεξιά, επανεμφανίζεται στα αριστερά και άλλη μία φορά, αλλά σταματά στη μέση τη δεύτερη φορά.
Επισυνάπτεται ο κώδικας σχολιασμένος με τη βοήθεια του Claude AI.
Αν κάποιος έχει καμιά ιδέα, ευχαριστώ και πάλι.

σύνδεσμος για ένα βίντεο του παιχνιδιού

; ================================================================
; INVADER GX4000 - RASM cartridge build wrapper
; ================================================================
BUILDCPR “build\INVADERGX.CPR”

; Banque 0 : le jeu complet (code + assets), inchange.
BANK 0
ORG #0000
INCLUDE “invader.asm”

; — CORRECTIF ---------------------------------------------------
; Le firmware GX4000/CPC+ mappe TOUJOURS la banque 3 de la cartouche
; en ROM haute (&C000-&FFFF) au demarrage, quelle que soit la taille
; reelle du programme. Une cartouche d’une seule banque (comme
; c’etait le cas ici) ne fournit pas les banques 1 a 3 : selon
; l’emulateur/le materiel, la cartouche peut alors ne pas etre
; reconnue comme demarrable, meme si le code de la banque 0 est
; parfaitement valide (c’est ce qu’on observait : le code s’executait
; bien lors des tests, mais la cartouche entiere n’etait jamais
; consideree comme bootable). On complete donc a 4 banques minimum
; (64 Ko), comme le fait toute cartouche CPC+ qui fonctionne
; (l’exemple Bomb Jack GX en comporte 8).
; -------------------------------------------------------------------
BANK 1
ORG #0000
ds #4000,#ff

BANK 2
ORG #0000
ds #4000,#ff

BANK 3
ORG #0000
ds #4000,#ff

=====================================================================

; CPC+ / GX4000 hardware constants
ASIC_PAGE_IN equ #7fb8
ASIC_PAGE_OUT equ #7fa0
SCREEN_BASE equ #c000
STACK_TOP equ #bffe

; Sprite attributes: 8 bytes per sprite, x/y are words, zoom at +4.
SPR_ATTR equ #6000
SPR_BITMAP equ #4000
SPR_PALETTE equ #6422

; PSG/PPI
PPI_A equ #f400
PPI_B equ #f500
PPI_C equ #f600
PPI_CTRL equ #f700

=======================================================================

; ================================================================
; INVADER GX - version complete source
; Amstrad GX4000 / CPC Plus - Z80
; Build with RASM.
;
; Gameplay:
; - 5 levels
; - 10 aliens, 4 families / movement engines
; - player free X/Y movement, joystick only
; - one active shot
; - 4 indestructible meteors, pseudo-random drift
; - hardware sprites: 1 player + 1 shot + 10 aliens + 4 meteors
; - CPC+ 4096-colour palette, raster split, soft scroll, DMA music
; - score, lives and level HUD
;
; Memory policy:
; ROM 0000-3FFF: code + assets
; RAM 8000-BFFF: game state / music DMA list
; RAM C000-FFFF: screen (write-through under cartridge ROM)
; ASIC page: only mapped while touching 4000-7FFF registers.
; ================================================================

org #0000
jp Boot

include ‘hardware.inc’

; — CORRECTIF CRITIQUE -------------------------------------------
; Reserve l’adresse &0038 (vecteur standard d’interruption du CPC,
; cible du RST 38h) avec un gestionnaire minimal sûr, comme le fait
; toute cartouche CPC+ qui fonctionne (verifie par comparaison
; binaire avec Bomb Jack GX, qui fait exactement pareil). Sans ca,
; si une interruption survient avant meme que “di” ait pu s’executer
; (le firmware peut laisser les interruptions actives juste avant de
; sauter dans la cartouche), le CPU saute en plein milieu d’une
; instruction de notre code → plantage immediat, ecran noir des le
; premier instant.
; ---------------------------------------------------------------------
ds #0038-$,0
IrqVector:
ei
reti

Boot:
di
ld sp,STACK_TOP
call UnlockASIC
call InitSprites
call InitVideo
call InitPalette
call InitGame
call InitMusic
; — CORRECTIF (voir rapport) ------------------------------------
; “ei” etait active ici sans jamais poser “im 1” et sans le moindre
; gestionnaire d’interruption (aucun “reti” dans tout le source).
; Le Gate Array genere pourtant une IRQ materielle 300 fois/seconde,
; quoi qu’il arrive : en IM 0 (mode par defaut), cela revient a un
; “rst &38” force qui saute EN PLEIN MILIEU du code de FrameLoop et
; empile une adresse de retour jamais depilee (fuite de pile a
; chaque IRQ) → plantage quasi immediat, ecran noir.
; Ce moteur pilote tout par attente active du VSync (WaitVSync) et
; n’a besoin d’aucune interruption CPU : on ne les active donc pas.
; Si des interruptions redeviennent necessaires plus tard, il faudra
; ajouter “im 1” AVANT “ei” et fournir un vrai gestionnaire a &0038.
; -------------------------------------------------------------------

FrameLoop:
call WaitVSync
call ReadJoystick
call UpdatePlayer
call UpdateShot
call UpdateAliens
call UpdateMeteors
call CollideShotAliens
call CollidePlayerMeteors
call CheckWave
call DrawAllSprites
call DrawHUD
call Animate
jr FrameLoop

; ----------------------------------------------------------------
; CPC+ ASIC unlock. 17 writes = sync 0xFF,0x00 then 15-key sequence.
; ----------------------------------------------------------------
UnlockASIC:
ld bc,#bc00
ld hl,ASIC_UNLOCK
ld e,17
.unlock:
ld a,(hl)
out (c),a
inc hl
dec e
jr nz,.unlock
ret

ASIC_UNLOCK:
db #ff,#00,#ff,#77,#b3,#51,#a8,#d4,#62,#39,#9c,#46,#2b,#15,#8a,#cd,#ee

; ----------------------------------------------------------------
; Video setup: MODE 0, screen base C000, simple raster split at line 16.
; Soft-scroll is used for a tiny starfield drift; sprites are independent.
; ----------------------------------------------------------------
InitVideo:
; Gate Array MODE 0, ROM HAUTE DESACTIVEE (#7f88). Necessaire pour
; que les lectures CPU de l’ecran (&C000+) renvoient la vraie RAM et
; non la ROM (le CRTC, lui, lit toujours la RAM directement pour
; l’affichage - mais DrawStars fait un lire-modifier-ecrire sur
; l’ecran a CHAQUE trame : avec la ROM haute active, cette lecture
; renvoyait du contenu ROM au lieu du vrai pixel, source probable
; d’une partie du bruit observe). Le “Aucun signal” vu precedemment
; avec cette valeur etait du a l’absence d’init CRTC dans ce test
; isole precis, pas a #7f88 lui-meme (confirme par un nouveau test
; cible, CRTC complet + #7f88 : stable). Merci a roudoudou (Discord
; Praline / CPCWiki) d’avoir repere ce point.
ld bc,#7f88
out (c),c
; CRTC : programme la table complete R0-R9 + R12/R13. Auparavant seuls
; R12/R13 (adresse ecran) etaient ecrits ; R0-R9 (qui definissent la
; synchro elle-meme) restaient sur leurs valeurs par defaut au boot
; direct sur cartouche → defilement/roulement d’image observe.
call InitCRTC
; Clear screen RAM C000-FFFF
ld hl,SCREEN_BASE
ld de,SCREEN_BASE+1
ld bc,#3fff
xor a
ld (hl),a
ldir
; ASIC split / scroll : RETIRE. Ces ecritures (&6801-&6804) causaient
; une distorsion/bruit generalise sur tout l’ecran sur materiel reel
; (confirme par test isole). Cette fonctionnalite n’etait de toute
; facon jamais activee (juste “preparee” sans le bit d’activation),
; donc aucune perte fonctionnelle a la retirer.
; seed a deterministic star field
call InitStars
ret

; ----------------------------------------------------------------
; Table CRTC standard Amstrad (50 Hz) + R12/R13 pour ecran a &C000.
; Format : paires (registre, valeur).
; ----------------------------------------------------------------
InitCRTC:
ld hl,CRTCTable
ld b,CRTCTableLen
.crtc_loop:
push bc
ld bc,#bc00
ld a,(hl)
out (c),a ; selectionne le registre
inc hl
ld bc,#bd00
ld a,(hl)
out (c),a ; ecrit la valeur
inc hl
pop bc
djnz .crtc_loop
ret

CRTCTable:
; TEST : on ne touche plus R0/R2/R3/R4/R5/R7 (total horizontal/
; vertical, position de synchro), a l’instar de Bomb Jack GX (verifie
; par desassemblage : il ne les ecrit jamais, laissant le firmware
; gerer ces valeurs). On garde R1 (notre bitmap suppose 80 octets/
; ligne = 40 caracteres) et R6 (nombre de lignes affichees).
db 1,#28 ; R1 horizontal affiche (40 car., nécessaire pour notre bitmap)
db 6,#19 ; R6 vertical affiche (25 lignes car.)
db 8,0 ; R8 entrelacement/skew : aucun
db 9,7 ; R9 hauteur de caractere - 1 (8 lignes)
db 12,#30 ; R12 adresse ecran (poids fort) → &C000
db 13,0 ; R13 adresse ecran (poids faible)
CRTCTableEnd:
CRTCTableLen equ (CRTCTableEnd-CRTCTable)/2

; ----------------------------------------------------------------
; CPC+ palette: 16 main colours + 15 sprite colours.
; ----------------------------------------------------------------
InitPalette:
call ASIC_IN
ld hl,MainPalette
ld de,#6400
ld bc,32
ldir
ld hl,SpritePalette
ld de,#6422
ld bc,30
ldir
call ASIC_OUT
ret

MainPalette:
; black, dark blue, blue, cyan, dark red, red, magenta, yellow,
; dark green, green, light cyan, white, orange, violet, grey, bright grey
dw #0000,#0018,#00af,#00ff,#1800,#f000,#f00f,#0ff0
dw #0180,#0f80,#0fff,#0fff,#0f60,#a0af,#0888,#0eee

SpritePalette:
dw #0ff0,#0f00,#00ff,#0fff,#0f80,#0f08,#08ff,#0ff8
dw #0f88,#088f,#0fff,#0f0f,#08f0,#0ff8,#0f40

; ----------------------------------------------------------------
; Hardware sprite pixel data. 16x16, byte per pixel, pen 0 transparent.
; ----------------------------------------------------------------
InitSprites:
call ASIC_IN
; TEST : coupe totalement l’affichage (CRTC R6=0) pendant tout
; l’upload, plutot que de compter sur le masquage individuel des
; sprites (Mag=0) qui n’empeche apparemment pas un conflit de bus
; ecriture/balayage sur materiel reel (confirme par un contributeur
; du forum CPCWiki : “the main discrepancy between emulated and
; real sprite display is when there’s a clash between writing
; sprite data and the screen being updated”).
ld bc,#bc06
out (c),c
ld bc,#bd00
out (c),c ; R6 = 0 : plus aucune ligne affichee
; masque les 16 sprites (Mag=0 + Y hors ecran) - conserve par
; prudence en plus de la coupure d’affichage
ld hl,#6002
ld de,8
ld b,16
.hide:
ld a,255
ld (hl),a ; Y (poids faible) hors ecran
push hl
inc hl
inc hl
xor a
ld (hl),a ; Mag = 0
pop hl
add hl,de
djnz .hide
ld hl,SpritePlayer
ld de,#4000
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteShot
ld de,#4100
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteAlien1
ld de,#4200
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteAlien2
ld de,#4300
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteAlien3
ld de,#4400
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteAlien4
ld de,#4500
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteMeteor
ld de,#4600
ld bc,#100
ldir
call WaitVSync
; replicate 4 alien/meteor images into sprite slots 2..15
ld hl,SpriteAlien1
ld de,#4700
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteAlien2
ld de,#4800
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteAlien3
ld de,#4900
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteAlien4
ld de,#4a00
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteMeteor
ld de,#4b00
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteMeteor
ld de,#4c00
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteMeteor
ld de,#4d00
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteMeteor
ld de,#4e00
ld bc,#100
ldir
call WaitVSync
ld hl,SpriteMeteor
ld de,#4f00
ld bc,#100
ldir
; restaure l’affichage (R6 = 25 lignes de caracteres, valeur normale)
ld bc,#bc06
out (c),c
ld bc,#bd19
out (c),c
call ASIC_OUT
ret

; ----------------------------------------------------------------
; Game initialisation.
; ----------------------------------------------------------------
InitGame:
xor a
ld (Level),a
ld (Frame),a
ld (Anim),a
ld (ShotActive),a
ld (GameOver),a
ld a,3
ld (Lives),a
ld a,72
ld (PlayerX),a
ld a,174
ld (PlayerY),a
xor a
ld (Score0),a
ld (Score1),a
ld (Score2),a
ld (Score3),a
ld a,2
ld (FormationDir),a
ld a,1
ld (RandomSeed),a
call SpawnWave
call SpawnMeteors
ret

SpawnWave:
ld a,(Level)
ld e,a
add a,a
add a,e ; level*3
ld e,a
ld d,0
ld hl,LevelParams
add hl,de
ld a,(hl)
ld (MoveEngine),a
inc hl
ld a,(hl)
ld (AlienSpeed),a
inc hl
ld a,(hl)
ld (AlienSprite),a
ld a,10
ld (AlienAliveCount),a
; formation positions: X=20..110, Y=35..62
ld hl,AlienX
ld de,AlienY
ld bc,AlienVX
ld ix,AlienType
ld iy,AlienAlive
ld a,10
ld (TmpCount),a
ld a,20
ld (TmpX),a
xor a
ld (FormationDir),a
ld b,0
.spawn_loop:
ld a,(TmpX)
ld (hl),a
add a,9
ld (TmpX),a
ld a,b
and 3
ld (ix+0),a
ld a,1
ld (iy+0),a
ld a,35
ld c,b
ld a,c
and 3
add a,a
add a,35
ld (de),a
inc hl
inc de
inc ix
inc iy
inc b
ld a,b
cp #0a
jr nz,.spawn_loop
ret

; Level parameters: engine, speed divisor, sprite family.
; Engine 0 formation sweep, 1 staircase, 2 sine-like, 3 split rush, 4 zigzag.
LevelParams:
db 0,4,0
db 1,3,1
db 2,2,2
db 3,2,3
db 4,1,4

; ----------------------------------------------------------------
; Joystick 1 only: line 9, direct PSG/PPI scan. Active high in JoyState.
; ----------------------------------------------------------------
ReadJoystick:
; Select PSG register 14 on PPI port A.
ld bc,#f40e
out (c),c
ld bc,#f6c0
out (c),c
xor a
out (c),a ; required inactive phase on CPC+
ld bc,#f792 ; PPI: port A input
out (c),c
ld a,#49 ; line 9 + read operation (bit 6 set)
ld b,#f6
out (c),a
ld b,#f4
in a,(c)
cpl
ld (JoyState),a
ld bc,#f782 ; restore port A output
out (c),c
xor a
ld b,#f6
out (c),a
ret

; ----------------------------------------------------------------
; Player: 2D movement with acceleration-free arcade controls.
; Game coordinates are 0..144 logical pixels; sprite X is *4 in ASIC.
; ----------------------------------------------------------------
UpdatePlayer:
ld a,(JoyState)
bit 2,a
jr nz,.left
bit 3,a
jr nz,.right
jr .x
.left:
ld hl,PlayerX
ld a,(hl)
or a
jr z,.x
dec (hl)
dec (hl)
jr .x
.right:
ld hl,PlayerX
ld a,(hl)
cp 142
jr nc,.x
inc (hl)
inc (hl)
.x:
ld a,(JoyState)
bit 0,a
jr nz,.up
bit 1,a
jr nz,.down
jr .done
.up:
ld hl,PlayerY
ld a,(hl)
cp 26
jr c,.done
dec (hl)
dec (hl)
jr .done
.down:
ld hl,PlayerY
ld a,(hl)
cp 182
jr nc,.done
inc (hl)
inc (hl)
.done:
ret

UpdateShot:
ld a,(JoyState)
bit 4,a
jr nz,.fire
jr .move
.fire:
ld a,(ShotActive)
or a
jr nz,.move
ld a,1
ld (ShotActive),a
ld a,(PlayerX)
add a,4
ld (ShotX),a
ld a,(PlayerY)
sub 6
ld (ShotY),a
.move:
ld a,(ShotActive)
or a
ret z
ld hl,ShotY
ld a,(hl)
sub 6
ld (hl),a
cp 18
ret nc
xor a
ld (ShotActive),a
ret

; ----------------------------------------------------------------
; Alien movement engines. All 10 positions updated from the same phase,
; but each level has a different motion model.
; ----------------------------------------------------------------
UpdateAliens:
ld hl,AlienX
ld de,AlienY
ld bc,AlienVX
ld ix,AlienType
ld iy,AlienAlive
ld a,(Frame)
ld (TmpPhase),a
ld b,0
.loop:
ld a,(iy+0)
or a
jp z,.next
ld a,(MoveEngine)
cp 0
jr z,.eng0
cp 1
jr z,.eng1
cp 2
jr z,.eng2
cp 3
jr z,.eng3
jr .eng4
.eng0:
ld a,(FormationDir)
or a
jr z,.e0r
inc (hl)
jr .e0chk
.e0r:
dec (hl)
.e0chk:
ld a,(hl)
cp #08
jr nc,.e0right
ld a,1
ld (FormationDir),a
ld a,(de)
inc a
ld (de),a
jr .next
.e0right:
cp #88
jr c,.next
xor a
ld (FormationDir),a
ld a,(de)
inc a
ld (de),a
jr .next
.eng1:
ld a,(Frame)
and 3
jr nz,.next
ld a,b
and 1
jr z,.e1a
inc (hl)
inc (hl)
jr .next
.e1a:
dec (hl)
dec (hl)
jr .next
.eng2:
; cheap triangle wave: phase + alien index, reflected at 0/120
ld a,(TmpPhase)
add a,b
and 127
cp 64
jr c,.e2p
ld c,a
ld a,128
sub c
.e2p:
ld (hl),a
ld a,b
and 3
add a,a
add a,34
ld (de),a
jr .next
.eng3:
; paired diagonal rush, reverses at edges
ld a,b
and 1
jr z,.e3r
dec (hl)
ld a,(de)
inc a
ld (de),a
jr .next
.e3r:
inc (hl)
ld a,(de)
dec a
ld (de),a
jr .next
.eng4:
; fast zigzag: horizontal + occasional vertical hop
inc (hl)
ld a,(Frame)
and 15
jr nz,.next
ld a,(de)
xor 12
ld (de),a
.next:
inc hl
inc de
inc bc
inc ix
inc iy
inc b
ld a,b
cp #0a
jp nz,.loop
ret

; ----------------------------------------------------------------
; Meteors. Four hardware sprites, never affected by shot collision.
; ----------------------------------------------------------------
SpawnMeteors:
ld a,#5a
ld (RandomSeed),a
ld hl,MeteorX
ld de,MeteorY
ld b,4
.sm:
call NextRandom
and 127
ld (hl),a
call NextRandom
and 127
add a,32
ld (de),a
inc hl
inc de
djnz .sm
ret

UpdateMeteors:
ld hl,MeteorX
ld de,MeteorY
ld b,4
.loop:
ld a,(hl)
inc a
cp 150
jr c,.sx
xor a
.sx:
ld (hl),a
ld a,(de)
add a,1
cp 190
jr c,.sy
sub 158
.sy:
ld (de),a
inc hl
inc de
djnz .loop
ret

NextRandom:
push bc ; preserve BC pour l’appelant (SpawnMeteors/InitStars
ld a,(RandomSeed) ; utilisent B comme compteur de boucle “djnz” : sans
ld b,a ; cette sauvegarde, cet appel l’ecrasait avec une
add a,a ; valeur pseudo-aleatoire au lieu de le laisser se
xor b ; decrementer normalement, faisant partir la boucle
rrca ; pour un nombre d’iterations imprevisible et
xor #1d ; corrompant la memoire au-dela des tableaux prevus.
ld (RandomSeed),a
pop bc
ret

; ----------------------------------------------------------------
; Collision: shot vs 10 aliens using 8x8 boxes. One hit kills one alien.
; ----------------------------------------------------------------
CollideShotAliens:
ld a,(ShotActive)
or a
ret z
ld hl,AlienX
ld de,AlienY
ld ix,AlienAlive
ld b,0
.ca_loop:
ld a,(ix+0)
or a
jr z,.ca_next
ld a,(ShotX)
sub (hl)
jr c,.ca_next
cp 9
jr nc,.ca_next
ld a,(ShotY)
ld c,a
push de
ld a,(de)
ld d,a
ld a,c
sub d
pop de
jr c,.ca_next
cp #0a
jr nc,.ca_next
xor a
ld (ix+0),a
ld (ShotActive),a
ld a,(AlienAliveCount)
dec a
ld (AlienAliveCount),a
call AddScore
ret
.ca_next:
inc hl
inc de
inc ix
inc b
ld a,b
cp #0a
jp nz,.ca_loop
ret

CollidePlayerMeteors:
ld hl,MeteorX
ld de,MeteorY
ld b,4
.cm:
ld a,(PlayerX)
sub (hl)
jr c,.cm_next
cp #0a
jr nc,.cm_next
ld a,(PlayerY)
ld c,a
push de
ld a,(de)
ld d,a
ld a,c
sub d
pop de
jr c,.cm_next
cp 12
jr nc,.cm_next
call LoseLife
ret
.cm_next:
inc hl
inc de
djnz .cm
ret

AddScore:
ld hl,Score0
inc (hl)
ld a,(hl)
cp #0a
ret c
xor a
ld (hl),a
inc hl
inc (hl)
ld a,(hl)
cp #0a
ret c
xor a
ld (hl),a
inc hl
inc (hl)
ld a,(hl)
cp #0a
ret c
xor a
ld (hl),a
inc hl
inc (hl)
ret

LoseLife:
ld a,(Lives)
or a
ret z
dec a
ld (Lives),a
xor a
ld (ShotActive),a
ld a,72
ld (PlayerX),a
ld a,174
ld (PlayerY),a
ld a,(Lives)
or a
ret nz
ld a,1
ld (GameOver),a
ret

CheckWave:
ld a,(GameOver)
or a
ret nz
ld a,(AlienAliveCount)
or a
ret nz
ld a,(Level)
inc a
cp 5
jr c,.next
xor a
.next:
ld (Level),a
call SpawnWave
ret

; ----------------------------------------------------------------
; Hardware sprite drawing. Sprite slots:
; 0 player, 1 shot, 2..11 aliens, 12..15 meteors.
; ----------------------------------------------------------------
DrawAllSprites:
call ASIC_IN
; player
ld a,(PlayerX)
add a,a
add a,a
ld l,a
ld h,0
ld (#6000),hl
ld a,(PlayerY)
ld (#6002),a
xor a
ld (#6003),a
ld a,5
ld (#6004),a
; shot
ld a,(ShotActive)
or a
jr z,.hideShot
ld a,(ShotX)
add a,a
add a,a
ld l,a
ld h,0
ld (#6008),hl
ld a,(ShotY)
ld (#600a),a
xor a
ld (#600b),a
ld a,5
ld (#600c),a
jr .aliens
.hideShot:
xor a
ld (#600c),a
ld a,255
ld (#600a),a ; Y hors ecran : un sprite a mag=0 reste
; visible a sa derniere position sinon.
.aliens:
ld hl,AlienX
ld de,AlienY
ld ix,AlienAlive
ld b,0
ld iy,#6010
.al:
ld a,(ix+0)
or a
jr z,.aloff
ld a,(hl)
add a,a
add a,a
ld (iy+0),a
xor a
ld (iy+1),a
ld a,(de)
ld (iy+2),a
xor a
ld (iy+3),a
ld a,5
ld (iy+4),a
jr .alnext
.aloff:
xor a
ld (iy+4),a
ld a,255
ld (iy+2),a ; Y hors ecran, meme raison que .hideShot
.alnext:
inc hl
inc de
inc ix
push bc
ld bc,8
add iy,bc
pop bc
inc b
ld a,b
cp #0a
jr nz,.al
; meteors
ld hl,MeteorX
ld de,MeteorY
ld iy,#6060
ld b,4
.me:
ld a,(hl)
add a,a
add a,a
ld (iy+0),a
xor a
ld (iy+1),a
ld a,(de)
ld (iy+2),a
xor a
ld (iy+3),a
ld a,5
ld (iy+4),a
inc hl
inc de
push bc
ld bc,8
add iy,bc
pop bc
djnz .me
call ASIC_OUT
ret

; ----------------------------------------------------------------
; HUD: tiny 4x5 digits, score/lives/level. Rendered directly in screen RAM.
; ----------------------------------------------------------------
DrawHUD:
; efface les 8 premieres lignes video (bandeau HUD), une par une
; avec la bonne adresse entrelacee
ld b,0
.clearline:
push bc
ld a,b
call GetLineAddr
ld b,80
xor a
.cl:
ld (hl),a
inc hl
djnz .cl
pop bc
inc b
ld a,b
cp 8
jr nz,.clearline
; ligne separatrice juste sous le bandeau (ligne video 8)
ld a,8
call GetLineAddr
ld b,80
ld a,#ff
.hline:
ld (hl),a
inc hl
djnz .hline
; draw stars into playfield
call DrawStars
ret

; ----------------------------------------------------------------
; Adressage ecran du CPC (Mode 0/1/2) : la memoire n’est PAS lineaire.
; Une ligne video (0-199) se decompose en :
; - une “ligne de caracteres” (0-24) = ligne div 8
; - une “sous-ligne” (0-7) = ligne mod 8, qui saute par blocs de 2048o
; adresse = SCREEN_BASE + (ligne div 8)*80 + (ligne mod 8)*2048
; Entree : A = ligne video (0-199). Sortie : HL = adresse de debut de
; cette ligne (colonne 0).
; ----------------------------------------------------------------
GetLineAddr:
push bc
push de
ld c,a
and 7
ld l,a
ld h,0
add hl,hl ; index * 2 (mot de 16 bits)
ld de,SubScan2048Table
add hl,de
ld e,(hl)
inc hl
ld d,(hl) ; de = sous-ligne * 2048
ld a,c
srl a
srl a
srl a ; a = ligne de caracteres (0-24)
ld l,a
ld h,0
add hl,hl
ld bc,Line80Table
add hl,bc
ld a,(hl)
inc hl
ld h,(hl)
ld l,a ; hl = ligne de caracteres * 80
add hl,de ; hl += sous-ligne * 2048
ld de,SCREEN_BASE
add hl,de ; hl += base ecran
pop de
pop bc
ret

SubScan2048Table:
dw 0,2048,4096,6144,8192,10240,12288,14336

Line80Table:
dw 0,80,160,240,320,400,480,560,640,720
dw 800,880,960,1040,1120,1200,1280,1360,1440,1520
dw 1600,1680,1760,1840,1920

InitStars:
ld hl,StarX
ld de,StarY
ld b,32
.is:
call NextRandom
and 159
ld (hl),a
call NextRandom
and 159
add a,24
ld (de),a
inc hl
inc de
djnz .is
ret

DrawStars:
ld ix,StarX
ld iy,StarY
ld b,32
.ds:
push bc
ld a,(iy+0) ; StarY = ligne video (24..182)
call GetLineAddr ; hl = debut de cette ligne
ld a,(ix+0) ; StarX
srl a ; /2 : 2 pixels par octet en Mode 0
ld c,a
ld b,0
add hl,bc
ld a,(hl)
xor #11
ld (hl),a
pop bc
inc ix
inc iy
djnz .ds
ret

Animate:
ld hl,Frame
inc (hl)
ld a,(Frame)
and 3
ld (Anim),a
ret

; ----------------------------------------------------------------
; DMA music: three-channel AY list. One list is enough for an ambient loop;
; DMA writes occur during HBL and do not consume the Z80 each frame.
; ----------------------------------------------------------------
InitMusic:
call ASIC_IN
ld hl,MusicList
ld (#6c00),hl
xor a
ld (#6c02),a
ld a,1
ld (#6c0f),a
call ASIC_OUT
ret

; LOAD R,D = 0RDD, PAUSE = 1NNN, REPEAT = 2NNN, LOOP=4001, STOP=4020.
; Notes are intentionally slow and arpeggiated for a classic arcade feel.
MusicList:
dw #0738 ; tone A/B/C on, noise off
dw #000e,#0102 ; A period
dw #080f ; A volume 15
dw #0900,#0a00 ; B/C silent
dw #100f ; ~16 HBL units
dw #000f,#0102
dw #080c
dw #100f
dw #0013,#0101
dw #080a
dw #100f
dw #0017,#0101
dw #0808
dw #100f
dw #0013,#0101
dw #080a
dw #100f
dw #203f ; repeat 64 times
dw #4001
dw #4020
align 2

; ----------------------------------------------------------------
; RAM state (never mapped as cartridge ROM).
; ----------------------------------------------------------------
Level equ #8000
Frame equ #8001
Anim equ #8002
JoyState equ #8003
Lives equ #8004
GameOver equ #8005
PlayerX equ #8006
PlayerY equ #8007
ShotX equ #8008
ShotY equ #8009
ShotActive equ #800a
RandomSeed equ #800b
MoveEngine equ #800c
AlienSpeed equ #800d
AlienSprite equ #800e
AlienAliveCount equ #800f
FormationDir equ #8010
TmpCount equ #8011
TmpX equ #8012
TmpPhase equ #8013
Score0 equ #8014
Score1 equ #8015
Score2 equ #8016
Score3 equ #8017
AlienX equ #8020
AlienY equ #8030
AlienVX equ #8040
AlienType equ #8050
AlienAlive equ #8060
MeteorX equ #8070
MeteorY equ #8074
StarX equ #8080
StarY equ #80a0

; ----------------------------------------------------------------
; Utility ASIC paging.
; ----------------------------------------------------------------
ASIC_IN:
ld bc,#7fb8
out (c),c
ret
ASIC_OUT:
ld bc,#7fa0
out (c),c
ret
WaitVSync:
ld b,#f5
.w0:
in a,(c)
rra
jr nc,.w0
.w1:
in a,(c)
rra
jr c,.w1
ret

; ----------------------------------------------------------------
; Sprite art. Each block is 16 rows x 16 pixels. Generated with DB so the
; source stays editable without a binary asset dependency.
; ----------------------------------------------------------------
SpritePlayer:
db 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
db 0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0
db 0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0
db 0,0,0,0,1,1,2,2,2,2,1,1,0,0,0,0
db 0,0,0,1,1,2,2,2,2,2,2,1,1,0,0,0
db 0,0,1,1,2,2,2,2,2,2,2,2,1,1,0,0
db 0,1,1,2,2,2,3,3,3,3,2,2,2,1,1,0
db 1,1,2,2,2,3,3,3,3,3,3,2,2,2,1,1
db 1,2,2,2,3,3,3,3,3,3,3,3,2,2,2,1
db 0,2,2,2,2,3,3,3,3,3,3,2,2,2,2,0
db 0,0,2,2,2,2,3,3,3,3,2,2,2,2,0,0
db 0,0,0,2,2,2,2,3,3,2,2,2,2,0,0,0
db 0,0,0,0,2,2,2,2,2,2,2,2,0,0,0,0
db 0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0
db 0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0
db 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0

SpriteShot:
; petit tir vertical visible (avant : “ds 256,0” = totalement transparent)
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

SpriteAlien1:
db 0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0
db 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0
db 0,0,1,1,1,2,1,1,1,1,2,1,1,1,0,0
db 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
db 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
db 1,1,2,1,1,1,1,1,1,1,1,1,2,1,1,1
db 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
db 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
db 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
db 0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0
db 0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0
db 0,1,1,0,0,0,1,1,1,1,0,0,0,1,1,0
ds 64,0

SpriteAlien2:
ds 16,0
db 0,0,1,0,0,1,1,1,1,1,1,0,0,1,0,0
db 0,1,1,1,1,1,2,1,1,2,1,1,1,1,1,0
db 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
db 1,1,2,1,1,1,1,1,1,1,1,1,2,1,1,1
db 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
db 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
db 0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0
db 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0
ds 112,0

SpriteAlien3:
ds 32,0
db 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1
db 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
db 1,1,2,2,1,1,1,1,1,1,1,1,2,2,1,1
db 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
db 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
ds 176,0

SpriteAlien4:
ds 48,0
db 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
db 0,1,1,2,1,1,1,1,1,1,1,1,2,1,1,0
db 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
db 1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1
db 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
db 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
db 0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0
ds 136,0

SpriteMeteor:
ds 32,0
db 0,0,1,1,0,0,0,2,2,0,0,1,1,0,0,0
db 0,1,1,1,1,0,2,2,2,2,0,1,1,1,1,0
db 1,1,1,1,1,1,2,2,2,2,1,1,1,1,1,1
db 1,1,2,2,1,1,1,2,1,1,1,2,2,1,1,1
db 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
db 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0
ds 128,0

; pad cartridge ROM to 16 KiB. RASM can then be used with -cpr or the
; project packaging script to create a 16K CPR; the source is intentionally
; small enough for the first cartridge page.
if $ < #4000
ds #4000-$,#ff
endif

Eυχαριστώ. Στο μεταξύ, βεβαιώσου ότι αρχικοποιείς ΟΛΕΣ τις τιμές CRTC, όρισε IM 1, ρύθμισε την προεπιλεγμένη ρύθμιση PPI, όρισε Rom, mode κ.λπ. επειδή ορισμένες συσκευές όπως το PicoGX ενδέχεται να τις έχουν ρυθμίσει για το μενού τους και έτσι το παιχνίδι θα κληρονομήσει τις τιμές, αλλά αν το τρέξεις χωρίς το μενού ή/και το «γράψεις» σε μια κασέτα (cart), τότε το υλικό δεν θα είναι διαμορφωμένο, ειδικά τα R2, R3, R4, R7 για τους συγχρονισμούς.

1 «Μου αρέσει»

Χαίρομαι που το τακτοποίησες.

1 «Μου αρέσει»

Επίσης:

Δεν χρειάζεται να περιμένεις το vsync για να αρχικοποιήσεις τα sprites, οπότε αυτό μπορεί να αφαιρεθεί για να επιταχυνθεί το στάδιο της προετοιμασίας. Επίσης, δεν χρειάζεται να μαυρίσεις την οθόνη χρησιμοποιώντας το R6, επειδή δεν θα δεις τα sprites να ενημερώνονται εκτός αν είναι ορατά.

Και κάτι ακόμα: ζήτησε από το εργαλείο AI να επαληθεύσει ότι ο κώδικας τυχαίων αριθμών που επέλεξε είναι καλός, γιατί ενδέχεται να μην δίνει την τυχαιότητα που χρειάζεσαι.

Ελπίζω να δοκιμάσω την κασέτα στο GX4000 μου απόψε για να δω αν παρατηρώ το μοτίβο που περιγράφεις.

1 «Μου αρέσει»

Έκανα ένα μικρό πείραμα. Φαίνεται ότι οι αριθμοί επαναλαμβάνονται μετά από έναν πολύ σύντομο κύκλο μόλις 10 τιμών.

Τι λέτε για αυτή την απλοποιημένη έκδοση της γεννήτριας τυχαίων αριθμών (RNG) του παιχνιδιού Elite:

rng:
    LD HL,seedValue  ;;  3 ;; Φόρτωση κατάστασης.
    LD A,H : ADD A,L ;;  2 ;; Δημιουργία νέας τιμής με πρόσθεση.
    LD H,L : LD L,A  ;;  2 ;; Κατασκευή νέας κατάστασης από την παλιά και τη νέα τιμή.
    LD (rng+1),HL    ;;  5 ;; Αποθήκευση κατάστασης.
    ;;               ;; -- ;; 
    ;;               ;; 12 ;; 

Δοκίμασα στο GX4000 μου και δεν είδα καμία οπτική ατέλεια (visual artifacts). Δεν το έχω δοκιμάσει σε 6128Plus. Μοιάζει με πρόβλημα υλικού (hardware) που αφορά μόνο εσένα. Άλλαξα τον κώδικα ώστε να ρυθμιστούν όλες οι καταχωρητές crtc στις προεπιλογές. Χρησιμοποιώ μια μοντέρνα οθόνη TFT συνδεδεμένη με scart και ένα νέο τροφοδοτικό που αγόρασα από το Ebay, οπότε είναι πιθανό να κρύβει το πρόβλημα. Χρησιμοποιείς CRT ή οθόνη Amstrad; Το μοτίβο εμφανίζεται σε ολόκληρη την οθόνη, πάνω από το τμήμα των γραφικών ή μόνο εκεί που είναι/ήταν τα sprites; Είναι λίγο περίεργο που εμφανίζεται και μετά εξαφανίζεται.

GX4000 Jail bars or PSU issue?

Είναι ακατανόητο (δείτε το βίντεο), δεν έχω την ίδια συμπεριφορά σε μια έκδοση με διορθώσεις του παιχνιδιού μεταξύ του GX4000 και του Caprice Forever, ενώ και τα δύο λειτουργούν ρολόι με όλα τα παιχνίδια μου!
Το GX είναι εξοπλισμένο με ένα PicoGX του Rodrick, ενώ ο εξομοιωτής (έκδοση Windows), όπως και το RASM, τρέχουν μέσω Wine σε Linux.
Μια έκδοση βγάζει μαύρη οθόνη στο GX με τις προτάσεις που μου δώσατε, αλλά πάει καλύτερα στον εξομοιωτή.
Πάντα αυτό το περίεργο σφάλμα: η οθόνη είναι περιορισμένη στην προβολή, αλλά φαίνεται να έχει τον σωστό αριθμό πίξελ, αν και «συμπιεσμένη» στον εαυτό της.
Και τέλος, ακόμα δεν υπάρχει ήχος.
Σφάλμα μεταγλώττισης (compilation);;;;
Κοιτάζω πώς να συμπιέσω σε zip το project μου, εν τω μεταξύ ορίστε το βίντεο από τις δοκιμές μου.
Ευχαριστώ και πάλι όλους σας

InvaderGX_final11 (copie).zip (350,2 KB) έκδοση με τη διόρθωση που μου προτάθηκε (μαύρη οθόνη στο gx)

InvaderGX_final11.zip (327,5 KB) αρχική έκδοση (δουλεύει και στα 2)

Συμπιεσμένη οθόνη: Το PlayerX σας είναι ένα byte, με εύρος 0-255, αλλά οι συντεταγμένες X των sprites είναι 16 bits. Οι συντεταγμένες των sprites βασίζονται στο mode 2, επομένως το εύρος για την προβαλλόμενη περιοχή είναι 0-640.

1 «Μου αρέσει»

build_cpr.asm (26,8 KB)

Εύρηκα! Έκανα πολλές διορθώσεις, τελικά υπολόγιζα για 80 χαρακτήρες αντί για 40 lol.

Μένουν ακόμα κάποια θέματα με την ανίχνευση σύγκρουσης (collision detection) και ακόμα δεν υπάρχει μουσική

1 «Μου αρέσει»