Bruit d'écran cyclique pendant le chargement des bitmaps hardware-sprite de l'ASIC

[Hardware/GX4000] Bruit d’écran cyclique pendant le transfert bitmap des sprites matériels de l’ASIC (&4000-&4FFF) — véritable 6128 Plus, RASM/4-bank .cpr

Salut à tous,

Je suis en train de déboguer une cartouche homebrew pour GX4000/CPC+ (RASM, 4-bank .cpr, testée sur un vrai 6128 Plus via RGB SCART) et je rencontre un problème de corruption d’affichage que j’ai isolé de manière assez précise après de nombreuses sessions de test, mais que je n’arrive pas à expliquer. Je poste ceci au cas où cela rappellerait quelque chose à quelqu’un ayant de l’expérience sur la vraie machine.

Symptôme

Après le transfert bitmap des sprites (16 x 256 octets via LDIR vers &4000-&4FFF), l’écran affiche un bruit visuel cyclique — de fins motifs répétés en diagonale/points avec des couleurs changeantes, alternant avec de brefs moments où l’image est propre, environ toutes les quelques secondes. Cela ressemble à un problème de synchro composite/RVB (style « dot crawl ») et non à un plantage du CPU — l’exécution continue de tourner parfaitement en arrière-plan (confirmé par d’autres moyens).

Ce qui est confirmé comme fonctionnant isolément (chaque point testé seul sur le vrai matériel, image stable, aucun bruit)

  • Séquence de déverrouillage de l’ASIC (séquence standard de 17 octets en &BC00)
  • Sélection du mode du Gate Array (&7F00,&80 — mode 0, ROMs intouchées ; à noter que &7F00,&88, qui désactive aussi la ROM supérieure, provoque une perte totale de synchro vidéo sur ce matériel — « Pas de signal » sur le téléviseur, persistant après un reset — découverte séparée, qui mériterait peut-être son propre fil)
  • Initialisation complète du CRTC, R0-R9 + R12/R13 (valeurs standard Amstrad 50Hz) via &BC00/&BD00, effectuée pendant que l’ASIC est déverrouillé
  • Transfert de la palette écran + palette sprites (2 LDIRs, 32+30 octets, vers &6400/&6422) via le même mécanisme de pagination d’I/O de l’ASIC (&7F00,&B8 / &7F00,&A0)

Ce qui reproduit le bruit

Le transfert des 16 bitmaps de sprites (256 octets chacun, simple LDIR, vers &4000, &4100, &4200 … &4F00) — confirmé de manière isolée, avec tout le reste ci-dessus présent et stable. Le bruit apparaît peu importe :

  • que les registres de sprites (&6000-&607F) soient mis à zéro (mag=0, c.-à-d. « non affiché » selon la documentation) avant ou après le transfert du bitmap
  • que les 16 LDIRs soient effectués en une seule rafale continue (~21ms, soit plus longtemps qu’une trame de 20ms) ou étalés sur plusieurs trames avec une attente VSync entre chaque transfert
  • que le transfert ait lieu avant ou après l’initialisation du CRTC/vidéo (c.-à-d. qu’une image soit déjà activement générée ou non)

Code (extrait pertinent)

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

InitSprites:
    call ASIC_IN
    ; sprites masqués d'abord (mag=0, Y=255), testé avant et après le transfert
    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 autres blocs LDIR identiques, de = #4100, #4200, ... #4f00
    call ASIC_OUT
    ret

Ce que j’ai exclu

  • Pas un bug de code/logique dans notre propre boucle de jeu (se reproduit même si la boucle de jeu est réduite à une boucle vide WaitVSync / jr)
  • Pas la configuration du CRTC (confirmée propre de manière isolée, R0-R9 tous présents)
  • Pas le mécanisme de déverrouillage/verrouillage de l’ASIC lui-même (confirmé via le transfert de palette utilisant exactement le même mécanisme &7FB8/&7FA0, aucun problème de ce côté)
  • Pas un problème de « sprite affichant du contenu corrompu en plein transfert » pour autant que je puisse en juger — réordonner le masquage avant le transfert n’a rien changé, et mag=0 dans la doc signifie déjà « non affiché », et non « taille 1x » (ce que je pensais initialement à tort)
  • Vérification croisée avec le tutoriel sur les sprites matériels publié par roudoudou (programmation assembleur Z80) — notre séquence de déverrouillage, l’utilisation de RMR2 (&7F00,&B8/&A0) et la technique de copie par LDIR correspondent exactement à son implémentation de référence, donc cela ne ressemble pas à une erreur d’implémentation de notre côté.

Note sur les émulateurs

Fait intéressant, CaPriCe Forever (Windows) fait tourner cette cartouche sans problème visuel pour les parties CRTC/déverrouillage ASIC, mais plante complètement si l’on écrit dans les registres CRTC 8 ou 9 (même individuellement, même avec des valeurs inoffensives comme 0 ou 7) — une divergence par rapport au vrai matériel, où R8/R9 sont requis et fonctionnent très bien. cap32 (Linux) n’allant pas assez loin pour tester (semble ignorer ou mal gérer le .cpr 4-bank dans son ensemble, boote directement sur le BASIC du firmware même avec le CPC 6128+ sélectionné — possiblement un problème de compatibilité avec le format .cpr du côté de cap32, car un .cpr 4/8-bank commercial connu comme fonctionnel — Bomb Jack GX — a le même problème là-bas alors qu’il fonctionne très bien sur CaPriCe Forever et sur la vraie machine).

Question

Est-ce que quelqu’un a déjà observé ce comportement spécifique de « bruit pendant/après le transfert des données bitmap de sprites matériels » sur le vrai matériel Plus/GX4000 ? Y a-t-il une contrainte de timing documentée qui m’aurait échappé — par exemple, la logique de lecture des sprites continue-t-elle de lire en &4000-&4FFF quel que soit le registre mag/visibilité, de sorte qu’y écrire pendant qu’une image est générée provoque des interférences visibles sur la puce (errata connu de l’ASIC) ? Si oui, existe-t-il un contournement recommandé (par ex. faire le transfert uniquement pendant une fenêtre de balayage spécifique, ou ajouter une étape de verrouillage/stabilisation autour de la pagination de l’ASIC) ?

Je peux partager le code source complet ou un .cpr de test si cela peut aider. Merci d’avoir lu jusque-là !

1 « J'aime »

Je n’ai jamais vu de corruption d’écran lors de la mise à jour des sprites. Il peut y avoir des problèmes étranges si tu effectues un défilement inférieur à un pixel, quel que soit le mode dans lequel tu te trouves actuellement, mais j’imagine que ce n’est pas ce que tu fais ?

La principale différence entre l’affichage de sprites émulé et réel survient lorsqu’il y a un conflit entre l’écriture des données de sprites et la mise à jour de l’écran. Aucun des éulateurs actuels (à ma connaissance) ne gère cela correctement.

Merci pour la réponse — cette remarque sur le « conflit entre l’écriture des données de sprites et les rafraîchissements d’écran » apporte un contexte très utile. Et non, je ne fais pas de défilement au sub-pixel (j’ai complètement retiré les registres de défilement fin, ils n’étaient même pas activés).

Cependant, une clarification me semble changer la donne : le parasite n’est pas un phénomène transitoire ponctuel lié au transfert lui-même — c’est un motif persistant et récurrent qui tourne en boucle (environ toutes les quelques secondes) pendant le jeu normal, bien après la fin du transfert des sprites (qui n’a lieu qu’une seule fois au démarrage, ~21 ms au total). Ça ne ressemble donc pas à un conflit de bus continu à chaque image, mais plutôt au fait que le transfert laisse ensuite le matériel dans un état légèrement différent ou instable (timing CRTC, état interne de l’ASIC, fréquence de synchro), ce qui se traduit par un problème récurrent de verrouillage de la synchro sur le téléviseur.

J’ai essayé de désactiver complètement l’affichage pendant le transfert (CRTC R6=0 pendant toute la durée du transfert, puis restauré après) en me disant que supprimer tout balayage actif pendant l’écriture éviterait le conflit — aucun changement, les mêmes parasites récurrents apparaissent ensuite. Donc, quel que soit l’état laissé derrière lui, il subsiste après le transfert proprement dit, et pas seulement pendant celui-ci.

Pour être tout à fait transparent : mes propres connaissances sur l’Amstrad remontent à environ 35 ans, et je coordonne ce débogage via un assistant IA qui écrit le code de test et interprète pour moi les sorties du débugger matériel (ACE), car je ne me souviens plus moi-même des détails fins de l’ASIC et du CRTC. Veuillez donc me pardonner si je mets un peu de temps à répondre aux questions techniques plus détaillées — je ferai avec plaisir n’importe quel test spécifique ou toute capture de registres dans ACE si vous me dites exactement quoi regarder.

Concrètement, si c’est utile, je peux :

  • Exécuter le code avec les panneaux de registres CRTC/ASIC ouverts dans ACE et faire une capture d’écran ou un court enregistrement au moment que vous souhaitez (par exemple juste avant/après le transfert des sprites, ou pendant un moment où le parasite apparaît) ;
  • Tester des valeurs de registres spécifiques que vous me suggéreriez ;
  • Partager l’intégralité du code source ou un fichier .cpr minimal de reproduction.

Encore merci d’avoir pris le temps.

Si tu peux publier un fichier .cpr minimal permettant de reproduire le bug, je suis sûr qu’on pourra l’isoler très rapidement :+1:

Si tu peux faire eine vidéo, ça pourrait aussi aider. Et, juste par curiosité, est-ce que tu as des périphériques d’extension connectés à ton Plus ?

Une vidéo si possible et un code source minimaliste seraient utiles.

S’agit-il d’un motif qui ressemble à un rampage de points de pixels physiques (comme le « snow » du ZX Spectrum) ou plutôt d’un motif d’interférence à l’écran ?

Sur les deux, le Plus et le GX4000 ?

Le GX4000 a une fréquence d’horloge principale légèrement ajustée pour offrir un « meilleur signal » avec les téléviseurs.

Le GX4000 et le Plus ont tous deux, il me semble, déjà montré des « barreaux de prison » verticaux à l’image, qui peuvent selon moi être filtrés, mais pas des pixels en tant que tels.

Quel est l’écran utilisé ? Un moniteur à écran plat moderne, un vieux CRT ?

Le problème principal que j’ai constaté est qu’écrire les données de pixels pendant que le sprite est affiché provoque une coupure visuelle ou une disparition temporaire du sprite (généralement pendant la durée de l’écriture). Je n’ai constaté aucune autre perturbation, et rien une fois les sprites écrits.

Y a-t-il un sprite spécifique sur lequel cela se produit ? Si tu n’en actives qu’un seul, est-ce que cela se produit aussi ?

Est-ce que cela change si le mag est configuré pour simuler les largeurs de pixels des modes 0 et 1 ?

1 « J'aime »

J’ai raté le passage sur le fait que le mode 0 pose problème. Tu peux partager du code qui provoque ça ?

Ça ressemble plutôt à un problème matériel. Je n’ai jamais vu le mode 0 causer des soucis de synchro.

Mon avis : vérifie l’alimentation de l’ordinateur. Vérifie le quartz de 40 MHz. Vérifie la synchro au niveau du connecteur Péritel (une bonne Vsync et Hsync, surtout en utilisant le mode 0). Si tu utilises un Pico GX ou équivalent et que ce n’est pas déjà fait, reproduis le problème avec du code qui tourne sur une cartouche basique branchée, par exemple.

1 « J'aime »

Quelle est la valeur de ton pointeur de pile (Stack pointer) ? J’ai eu du grand n’importe quoi d’affiché sur mon écran il y a quelques mois, jusqu’à ce que je me rende compte que j’avais oublié d’initialiser le registre SP, et tous les push & pops s’effectuaient dans mon double buffer :face_without_mouth:

1 « J'aime »

Merci pour vos réponses, j’ai fini par trouver l’origine de ce bug !
En résumé, ma fonction NextRandom utilise le registre B comme variable de travail sans le sauvegarder, alors que SpawnMeteors (et InitStars) l’utilisent en parallèle comme compteur de boucle djnz. Chaque appel à NextRandom à l’intérieur de la boucle écrasait ce compteur avec une valeur pseudo-aléatoire, faisant déraper le nombre d’itérations et corrompant la mémoire bien au-delà des tableaux prévus.
Correctif : push bc / pop bc autour du corps de NextRandom.
Il me reste qq bug encor incompréhensible
Collisions (tir↔alien, vaisseau↔météore) ne fonctionne toujours pas.
Toujours aucune Musique DMA (pas de son).
Et enfin le pire de tous, Image recadrée dans le coin haut-gauche. Mon jeux ne s’affcihe que sur le tiers gauche de l’ecran mais si je déplace le vaisseau vers la droit il reaparait à gauche et encore une fois mais s’arrete au millieu sur le deuxième coup.
Ci_joint le code commenté avec l’aide de claude IA.
si qqun à une idée, encore merci.

lien vers une video du jeux

; ================================================================
; 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

Merci. Au fait, assure-toi de bien initialiser TOUTES les valeurs du CRTC, de définir le mode IM 1, de configurer le contrôle du PPI par défaut, de régler la ROM, le mode, etc. En effet, certains périphériques comme le PicoGX ont pu les configurer pour leur propre menu. Le jeu héritera donc de ces valeurs, mais si tu le lances sans le menu et/ou que tu le « brûles » sur une cartouche, le matériel ne sera pas configuré, en particulier les registres R2, R3, R4 et R7 pour les synchronisations.

1 « J'aime »

Ravi d’apprendre que tu as pu régler ça.

1 « J'aime »

Aussi :

Pas besoin d’attendre la vsync pour initialiser les sprites, vous pouvez donc retirer ça pour accélérer cette partie de la configuration. De plus, inutile d’effacer l’écran en utilisant R6 car vous ne verrez pas la mise à jour des sprites à moins qu’ils ne soient visibles.

Une dernière chose : demandez à l’outil d’IA de vérifier que le code de nombre aléatoire qu’il a choisi est bon, car il se peut qu’il ne donne pas l’aspect aléatoire dont vous avez besoin.

J’ testerai la cartouche ce soir, j’espère sur ma GX4000, pour voir si je constate le motif que vous décrivez.

1 « J'aime »

J’ai fait une petite expérience. Il semble que les nombres se répètent après un cycle très court de seulement 10 valeurs.

Que pensez-vous de cette version simplifiée du générateur de nombres aléatoires (RNG) du jeu Elite ?

rng:
    LD HL,seedValue  ;;  3 ;; Charger l'état.
    LD A,H : ADD A,L ;;  2 ;; Créer une nouvelle valeur par addition.
    LD H,L : LD L,A  ;;  2 ;; Construire le nouvel état à partir de l'ancien et de la nouvelle valeur.
    LD (rng+1),HL    ;;  5 ;; Sauvegarder l'état.
    ;;               ;; -- ;; 
    ;;               ;; 12 ;; 

J’ai testé sur ma GX4000 et je n’ai constaté aucun artefact visuel. Je n’ai pas testé sur un 6128Plus. Ça ressemble à un problème matériel qui te concerne uniquement. J’ai bien modifié le code pour réinitialiser tous les registres du CRTC aux valeurs par défaut. J’utilise un écran TFT moderne branché en péritel avec une alimentation neuve achetée sur eBay, c’est possible que ça masque le problème. Tu utilises un écran cathodique (CRT) ou un moniteur Amstrad ? Est-ce que le motif apparaît sur tout l’écran, uniquement sur la partie graphique, ou juste là où sont/étaient les sprites ? C’est un peu bizarre qu’il apparaisse puis disparaisse.

GX4000 Jail bars or PSU issue?

C’est incompréhensible (voir vidéo) je n’ai pas le même comportement sur une versionavec corection du jeux entre la gx4000 et caprice for ever, alors que les deux focntionnent nickel avec tout mes jeux !
La gx est équipe d’un picogx de Rodrick, l’émualteur version winwos, tout comme rasm fonctionne via wine sous linux.
Une version c’est ecran noir sur la gx avec les suggestion que l’on ma donné, mais mieux sur l’emulateur.
Toujours ce drole de bug ecrant limité à l’affichage mais qui semble avoir le bon nombre de pixel, mais ramasser sur lui même.
et enfin toujours pas de son.
Erreur de compilation ???
Je ragarde comment mettre en zip mon projet en attendant voici la video de mes test.
encore merci à vous tous

InvaderGX_final11 (copie).zip (350,2 Ko) version avec correction que l’on ma suggerer (ecran noir sur gx)

InvaderGX_final11.zip (327,5 Ko) version originale (marche sur les 2)

Écran compressé : votre PlayerX est un octet, plage 0-255, mais les coordonnées X des sprites sont sur 16 bits. Les coordonnées des sprites sont basées sur le mode 2, donc la plage d’affichage est de 0 à 640.

1 « J'aime »

build_cpr.asm (26,8 Ko)

Eureka j’ai fait beaucoup de correction, en fait je calculé sur 80 caractère et non 40 lol.

Reste encore des soucis de detection de collision et toujours no music

1 « J'aime »