Projet WideNES - dépasser les limites de l'écran NES

image

Au milieu des années 80, la Nintendo Entertainment System (NES) était une console incontournable. Le meilleur son, les meilleurs graphismes et les meilleurs jeux de toutes les consoles de l'époque - la console a repoussé les limites du possible. Jusqu'à présent, des projets tels que Super Mario Bros. , The Legend of Zelda et Metroid sont considérés comme certains des meilleurs jeux de tous les temps.

Plus de 30 ans après la sortie de NES, les jeux classiques se sentent bien, ce qui ne peut pas être dit du matériel sur lequel ils fonctionnaient. Avec une résolution de seulement 256x240, la console NES ne pouvait pas fournir suffisamment d'espace aux jeux. Néanmoins, les développeurs intrépides ont réussi à s'intégrer dans les jeux NES à des mondes incroyables et inoubliables: les donjons labyrinthiques de The Legend of Zelda , de vastes espaces de la planète dans Metroid , des niveaux lumineux de Super Mario Bros. . Cependant, en raison des limitations matérielles NES, les joueurs ne pouvaient jamais dépasser 256x240 ...

Jusqu'à récemment.

Je vous présente le projet wideNES - une nouvelle façon de jouer aux classiques NES!



wideNES est une nouvelle technologie de marquage automatique et interactif des jeux NES en temps réel .

Lorsque les joueurs se déplacent dans le niveau, wideNES enregistre l'écran, construisant progressivement une carte de la partie explorée du monde. Dans les niveaux suivants, wideNES synchronise le gameplay sur l'écran avec la carte générée, permettant essentiellement aux joueurs de voir plus en «regardant» au-delà des limites de l'écran NES! Mieux encore, la façon dont vous marquez les jeux wideNES est complètement universelle , ce qui permet à une large gamme de jeux NES de fonctionner avec wideNES sans aucune configuration!

Mais comment ça marche?



Si vous souhaitez vérifier le fonctionnement de wideNES avant de lire l'article, n'hésitez pas! ANESE est l'émulateur NES que j'ai écrit, et actuellement c'est le seul émulateur qui implémente wideNES. Cependant, il convient de noter que l'ANESE n'est pas le meilleur émulateur NES au monde, en termes d'interface utilisateur et de précision d'émulation. La plupart des fonctionnalités (y compris l'inclusion de wideNES) ne sont disponibles que via la ligne de commande, et bien que de nombreux jeux populaires fonctionnent correctement, d'autres peuvent se comporter de manière inattendue.



Comment WideNES fonctionne


Avant de plonger dans les détails, il est important d'expliquer brièvement comment NES rend les graphiques.

Transfert de pixels à l'aide de PPU


Le cœur de NES est le vénérable processeur MOS 6502. À la fin des années 70 et au début des années 80, le 6502 était utilisé partout et fonctionnait dans des machines légendaires comme le Commodore 64, Apple II et bien d'autres. C'était bon marché, facile à programmer et suffisamment puissant pour être dangereux.

Le 6502 de la console NES était complété par un puissant coprocesseur graphique appelé Picture Processing Unit (PPU). Comparé aux coprocesseurs vidéo simples utilisés sur les anciens systèmes, le PPU représente une énorme amélioration en termes de convivialité. Par exemple, cinq ans avant la sortie de NES, le processeur Atari 2600 6502 était utilisé pour transmettre des instructions graphiques au coprocesseur pour chaque ligne raster , ce qui laissait très peu de temps au processeur pour exécuter la logique du jeu. À titre de comparaison: PPU n'avait besoin que de quelques commandes par image , ce qui a donné 6502 suffisamment de temps pour créer un gameplay intéressant et innovant.

PPU est une puce incroyable, sa façon de rendre les graphiques ne ressemble presque pas au travail des GPU modernes, et une série complète d'articles sera nécessaire pour expliquer pleinement ses fonctions. Étant donné que wideNES n'utilise qu'un petit sous-ensemble des fonctions PPU, il suffit de les considérer brièvement:

  • Résolution: 256x240 pixels, 60 Hz
  • Il fonctionne indépendamment du CPU
    • Communique avec le CPU à l'aide d' E / S avec mappage de mémoire (plage d'adresses 0x2000 - 0x2007)
  • 2 couches de rendu: couche de sprite et couche d' arrière - plan
    • Couche de sprite
      • Chaque sprite individuel peut être placé n'importe où sur l'écran.
      • Idéal pour déplacer des objets: joueur, ennemis, coquilles
      • Jusqu'à 64 sprites 8x8 pixels
    • Couche d'arrière-plan
      • Attaché à une grille
      • Idéal pour les éléments statiques: plates-formes, grands obstacles, décorations
      • La mémoire vidéo est suffisante pour stocker 64x30 tuiles de taille 8x8 pixels
        • Véritable résolution interne 512x240, avec une fenêtre 256x240
        • Prend en charge le défilement matériel pour modifier la fenêtre 256x240
          • Le registre PPUSCROLL (adresse 0x2005) contrôle le décalage de la fenêtre d'affichage en X / Y

Après avoir traité de ce très bref aperçu, passons au plus intéressant: comment fonctionne wideNES?

Idée principale


À la fin de chaque trame, le CPU envoie les informations de changement au PPU. Il s'agit notamment de nouvelles positions de sprite, de nouvelles données de niveau et, ce qui est essentiel pour wideNES, de nouveaux décalages de fenêtre . Étant donné que wideNES fonctionne dans l'émulateur, il est très facile pour nous de garder une trace des valeurs écrites dans le registre PPUSCROLL, ce qui signifie qu'il est incroyablement facile de calculer combien l'écran a bougé entre deux images!

Hmm, que se passera-t-il si, au lieu de dessiner chaque nouveau cadre directement au-dessus de l' ancien cadre, de nouveaux cadres seront dessinés en superposition sur le cadre précédent, mais décalés à la valeur de défilement actuelle? Puis, au fil du temps, une partie de plus en plus importante du niveau restera à l'écran, construisant progressivement une image complète du niveau!

Pour vérifier si cette idée était utile, j'ai rapidement esquissé la première implémentation.

Compilation ...
Lancement ...
Télécharger Super Mario Bros. ...

Voila!


Ça a marché!

Il semble que ce soit ...



Autre approche: pourquoi ne pas extraire des niveaux directement à partir de fichiers ROM?


Sans même considérer les détails de mise en œuvre, il devient évident que cette technique a une sérieuse limitation: une carte de jeu complète ne peut être collectée que lorsque le joueur explore indépendamment le jeu entier.

Et s'il y avait un moyen d'extraire les niveaux des ROM NES brutes ?!

Une telle technique peut-elle même exister?

Eh bien, très probablement pas.

Si vous prenez deux jeux pour NES, vous pouvez garantir qu'ils n'ont qu'une seule chose en commun: ils fonctionnent tous les deux pour NES. Tout le reste peut être complètement différent! Un tel décalage est un véritable désastre, car les jeux NES ont essentiellement un nombre infini d'options pour stocker des données de niveau!

Certaines personnes ont extrait des niveaux complets par rétro-ingénierie de la façon dont elles stockent les données de niveau de quelques jeux (parfois avec la création d' éditeurs de cartes complets!), Mais c'est une tâche difficile, nécessitant beaucoup de travail, de persévérance et d'intelligence.

Afin d'extraire des données de niveau de la ROM, il est nécessaire de déterminer quelles parties de la ROM sont du code (pas des données), ce qui est difficile à faire, car trouver tout le code dans un fichier binaire équivaut à un problème d'arrêt !

WideNES utilise une approche beaucoup plus simple: au lieu de deviner comment le jeu a compressé les données de niveau dans la ROM, wideNES lance simplement le jeu et garde la trace de la sortie!



Défilement au-delà de 255


NES est un système 8 bits, c'est-à-dire que le registre PPUSCROLL ne peut recevoir que des valeurs 8 bits. Cela limite le décalage de défilement maximal à 255 pixels, c'est-à-dire le nombre maximal de 8 bits. Il n'y a pas de coïncidence si la résolution d'écran NES est de 240x256 pixels, c'est-à- dire qu'un décalage de 255 pixels est juste suffisant pour faire défiler tout l'écran.

Mais que se passe-t-il lors d'un défilement supérieur à 255?

Premièrement, les jeux remettent le registre PPUSCROLL à 0. Cela explique pourquoi SMB est reporté au début lorsque Mario se déplace trop à droite.

Ensuite, pour compenser les restrictions PPUSCROLL 8 bits, les jeux mettent à jour un autre registre PPU: PPUCTRL (adresse 0x2000). Les 2 derniers bits de PPUCTRL définissent le «point de départ» de la scène actuelle par incréments plein écran. Par exemple, l'écriture d'une valeur de 1 décale la fenêtre vers la droite de 256 pixels; une valeur de 2 décale la fenêtre de 240 pixels. Le décalage PPUCTRL est poussé sur la pile avec le registre PPUSCROLL, qui vous permet de faire défiler l'écran horizontalement sur 512 pixels ou verticalement sur 480 pixels.

Mais construire, y a-t-il seulement assez de mémoire vidéo pour les écrans à deux niveaux? Que se passe-t-il lorsque la fenêtre défile trop à droite et «dépasse» la VRAM? Pour gérer ce cas, PPU implémente la convolution: toutes les parties de la fenêtre en dehors de la mémoire vidéo sélectionnée sont simplement réduites au bord opposé de la mémoire vidéo.

Un tel pliage, combiné à une manipulation intelligente des registres PPUSCROLL et PPUCTRL, permet aux jeux NES de créer l'illusion de mondes infiniment hauts / larges! Grâce au chargement paresseux d'une partie du niveau à l'extérieur de la fenêtre de visualisation et au défilement progressif à l'intérieur, les joueurs ne réalisent jamais qu'à l'intérieur de la VRAM, ils «tournent en rond»!

Une excellente illustration du wiki nesdev montre comment Super Mario Bros. utilise ces propriétés pour créer des niveaux supérieurs à deux écrans:


Revenons à la question dont nous discutons: comment wideNES gère-t-il le défilement au-delà de 256?

Eh bien, franchement, wideNES ignore complètement le registre PPUCTRL et ne fait que suivre la différence PPUSCROLL entre les images!

Si PPUSCROLL saute de façon inattendue à environ 256, ce qui signifie généralement que le personnage du joueur s'est déplacé vers la gauche / vers le haut sur l'écran, et s'il saute de manière inattendue vers environ 0, cela signifie généralement que le joueur s'est déplacé vers la droite / vers le bas sur l'écran.

Bien que cette heuristique puisse sembler simple - et elle l'est - en fait, elle fonctionne très bien!

Après avoir implémenté cette heuristique, Super Mario Bros. , Metroid et de nombreux autres jeux ont fonctionné presque parfaitement!

J'étais ravi, alors j'ai continué et j'ai téléchargé un autre classique NES - Super Mario Bros. 3 ...


Hmm ... Pas très joli.

Ignorer les éléments d'écran statiques


De nombreux jeux ont des éléments d'interface utilisateur statiques sur les bords de l'écran. Dans le cas de SMB3, il s'agit de la colonne de gauche et la barre d'état se trouve en bas de l'état.

Par défaut, les échantillons wideNES avec des incréments de 16 pixels à partir des bords de l'écran, c'est-à-dire que tous les éléments statiques aux bords sont échantillonnés! Pas bon!

Pour contourner ce problème, wideNES implémente des règles et des heuristiques qui essaient de reconnaître et de masquer automatiquement les éléments d'écran statiques.

En général, les jeux NES utilisent trois types différents d'éléments d'écran statiques: les HUD, les masques et les barres d'état.

HUD - pas de problème


Si un jeu impose un HUD au-dessus d'un niveau, il est probable que le HUD se compose de plusieurs sprites. Exemple: HUD dans Metroid .

Heureusement, de tels HUD ne posent pas de problèmes, car wideNES ignore actuellement simplement la couche de sprite. Super!

Masques - nulle part plus facile


PPU a une fonctionnalité qui permet aux jeux de masquer les 8 pixels les plus à gauche de la couche d'arrière-plan. Il est activé en définissant le deuxième bit du registre (adresse 0x2001). De nombreux jeux utilisent cette fonctionnalité, mais expliquer pourquoi ils le font dépasse le cadre de cet article.

Reconnaître le masque inclus est incroyablement simple: wideNES ne fait que suivre la valeur PPUMASK et ignore les 8 pixels les plus à gauche lorsque le deuxième bit est défini dans le registre!

Il semble que l'implémentation de cette règle simple ait résolu le problème avec SMB3 :


... enfin, ou presque éliminé.

Les barres d'état sont les plus difficiles


En raison des limitations de PPU à tout moment sur l'écran, il ne peut y avoir plus de 64 sprites; de plus, à tout moment dans chaque ligne raster, il ne peut y avoir plus de 8 sprites. Cette restriction empêche les développeurs de créer des HUD complexes à partir de sprites et les oblige à utiliser des parties de la couche d'arrière-plan pour afficher des informations.

En plus des masques, il n'y a pas de moyen facile dans PPU pour séparer la couche d'arrière-plan dans la zone de jeu et la zone d'état. Par conséquent, les développeurs sont allés à des astuces, conduisant à un tas de façons peu orthodoxes de créer des panneaux d'état ...

WideNES utilise diverses heuristiques pour reconnaître différents types de panneaux d'état, mais pour gagner du temps, je ne considérerai que l'un des plus intéressants: le suivi IRQ en milieu de trame.

Suivi IRQ à mi-hauteur


Contrairement aux GPU modernes avec de grands tampons de trame internes, les PPU n'ont généralement pas de tampon de trame! Pour économiser de l'espace, PPU stocke les scènes sous forme d'une grille de tuiles 64x32 de 8x8 pixels. Au lieu de pré-calculer les données de pixels, les tuiles sont stockées en tant que pointeurs vers la mémoire CHR (mémoire de caractères), qui contient toutes les données de pixels.

Depuis que NES a été développé dans les années 80, PPU a été créé sans tenir compte des technologies d'affichage modernes. Au lieu de restituer la trame complète en même temps, le PPU émet le signal vidéo NTSC, qui doit être affiché sur un écran CRT qui affiche la vidéo pixel par pixel , ligne par ligne , de haut en bas, de haut en bas, de gauche à droite.

Pourquoi est-ce si important?

Puisque PPU rend les images de haut en bas, ligne par ligne, vous pouvez envoyer des instructions PPU au milieu de l'image pour créer des effets vidéo impossibles avec toute autre approche! Ces effets peuvent être simples (par exemple, changer la palette), ou assez complexes (par exemple, vous l'avez deviné, en créant des barres d'état!).

Pour expliquer comment une écriture PPU en milieu de trame peut créer des barres d'état, j'ai enregistré un vidage de tranche vidéo PPU brut et mémoire CHR pour une seule image SMB3 :


Tout a l'air bien, rien de spécial ... mais il suffit de regarder la barre d'état! Elle est complètement déformée!

Regardez maintenant le même vidage brut, mais fait après la ligne 196 ...


Oui, le niveau a l'air horrible, mais la barre d'état est superbe!

Que se passe-t-il ici?

SMB3 définit un temporisateur pour déclencher l'IRQ (interruption) exactement après le rendu de la ligne raster 195. Il transmet les instructions suivantes au gestionnaire IRQ:

  • Réglez PPUSCROLL sur (0,0) (pour que la barre d'état reste en place)
  • Nous remplaçons la carte tuile dans la mémoire CHR (nous mettons dans l'ordre les graphiques de la barre d'état)

Puisque le reste de la couche est déjà rendu, le PPU ne «re-mettra à jour» le cadre. Au lieu de cela, il continuera le rendu avec ces options, affichant une belle barre d'état non déformée!

Revenons à wideNES: en observant tous les IRQ au milieu du cadre et en se souvenant de la ligne raster sur laquelle ils se sont produits, wideNES peut ignorer toutes les lignes raster suivantes dans l'enregistrement! Si l'IRQ se produit dans la ligne raster au-dessus de 240/2, toutes les lignes précédentes sont ignorées, car une interruption précoce de la ligne raster signifie que la barre d'état peut se trouver en haut de l' écran.

Après avoir implémenté cette heuristique, Super Mario Bros. 3 gagnés parfait!




J'ai brièvement examiné la possibilité d'utiliser une bibliothèque de vision par ordinateur, comme OpenCV, pour reconnaître les panneaux d'état (ou d'autres zones principalement statiques de l'écran), mais j'ai donc décidé de l'abandonner. L'utilisation d'une bibliothèque de vision par ordinateur énorme, complexe et opaque est contraire aux idéaux de wideNES, dans lequel j'essaie d'utiliser des règles et des heuristiques compactes, simples et transparentes pour obtenir des résultats.



Reconnaissance de scène


À l'exception de quelques exemples importants (par exemple, Metroid ), les jeux pour NES ne passent généralement pas dans un niveau énorme et inextricable. Au contraire, la plupart des jeux NES sont divisés en plusieurs petites «scènes» indépendantes avec des portes ou des écrans de transition entre elles.

Étant donné que wideNES n'a pas le concept de «scènes», de mauvaises choses se produisent lors du changement de scènes ...

Par exemple, voici la première transition de la scène Castlevania , où Simon Belmont entre dans le château de Dracula:


Wow, tout va mal! wideNES a complètement réécrit la dernière partie du niveau avec le premier écran d'un nouveau niveau!

De toute évidence, wideNES a besoin d'un moyen de reconnaître les changements de scène. Mais lequel?

Hachage perceptuel!

Contrairement aux fonctions de hachage cryptographiques , qui ont tendance à répartir uniformément des données d'entrée similaires dans l'espace d'informations de sortie, les fonctions de hachage perceptuelles essaient de garder des données d'entrée similaires «proches» les unes des autres dans l'espace de données de sortie. Par conséquent, les hachages perceptuels sont idéaux pour reconnaître des images similaires!

Les fonctions de hachage perceptuelles peuvent être incroyablement complexes, certaines d'entre elles sont capables de reconnaître des images similaires si l'une d'entre elles a été pivotée, mise à l'échelle, étirée et les couleurs changées. Heureusement, wideNES ne nécessite pas de fonctions de hachage complexes car chaque trame est garantie d'avoir la même taille. WideNES utilise donc le plus simple des hachages perceptuels existants: la somme de tous les pixels sur l'écran!

C'est simple, mais ça marche plutôt bien!

Par exemple, voyez comment les transitions entre les scènes se démarquent si vous tracez le hachage perceptuel au fil du temps dans The Legend of Zelda :


Actuellement, wideNES utilise un seuil fixe entre les valeurs de hachage perceptuelles pour achever la transition entre les scènes, mais le résultat est loin d'être idéal. Différents jeux utilisent des palettes différentes, et il y a de nombreux cas où wideNES pense qu'une transition s'est produite, mais en fait ce n'est pas le cas. Idéalement, wideNES devrait utiliser une valeur de seuil dynamique, mais jusqu'à présent, la valeur fixe fera l'affaire.

Après avoir implémenté cette nouvelle heuristique, wideNES reconnaît avec succès l'entrée de Simon de Castlevania au château et crée en conséquence une nouvelle toile.


Et avec cette décision, nous avons mis en place la dernière pièce majeure du puzzle wideNES.

Ayant implémenté la sérialisation la plus simple, j'ai finalement pu exécuter le jeu pour NES, jouer sur plusieurs niveaux et générer automatiquement des cartes de niveau!

Qu'est-ce qui attend WideNES à l'avenir?


wideNES se compose de deux parties distinctes: le noyau wideNES, qui est les règles / heuristiques mêmes qui sous-tendent la technologie, et l'implémentation spécifique de wideNES à l'intérieur de l'émulateur ANESE.

Amélioration du noyau WideNES


Premièrement, wideNES est sujet à une reconnaissance trop agressive des transitions entre les scènes. Le nombre de faux positifs peut être minimisé en utilisant un algorithme de hachage perceptuel plus approprié ou en passant à des valeurs de seuil dynamiques entre les hachages perceptuels.

Des travaux supplémentaires sont également nécessaires pour reconnaître les éléments d'écran statiques.Par exemple, Megaman IV a une IRQ au milieu du cadre, mais il n'y a pas de barre d'état, c'est pourquoi wideNES ignore par erreur la partie solide du terrain de jeu. Bien que ce cas particulier puisse être corrigé par un réglage manuel, il est préférable d'utiliser des heuristiques plus intelligentes.

Certains jeux NES font défiler l'écran de manière «unique». L'un des exemples les plus notables est The Legend of Zelda , qui utilise PPUSCROLL pour le défilement horizontal, mais utilise un registre complètement différent pour le défilement vertical - PPUADDR. Zelda est un jeu assez populaire, donc wideNES implémente l'heuristique spécifiquement pour Zelda. Il existe d'autres jeux avec des modes de défilement «uniques» similaires, qui nécessitent également une heuristique individuelle.

Il serait utile de trouver un moyen de «coudre» des scènes identiques. Par exemple, si un utilisateur joue à Super Mario Bros. Niveau 1, mais entre dans le tuyau pour entrer dans la grotte souterraine avec des pièces, puis wideNES créera deux scènes distinctes pour le niveau 1: scène A, niveau jusqu'à ce que Mario entre dans la zone avec des pièces, et scène B, niveau, à partir du moment quand Mario sort du tuyau et monte au mât. Si le jeu redémarre ensuite et que le niveau 1 est rejoué sans entrer dans le tuyau, alors wideNES mettra simplement à jour la scène A, qui contiendra une carte de niveau complet, mais la scène B «s'arrêtera».

Enfin, wideNES doit suivre les transitions entre les scènes. Sans ces données, il ne sera pas possible de construire un graphique des transitions entre les scènes pour générer des cartes du monde de jeux qui ne sont pas constitués d'un seul grand monde.

Améliorer la mise en œuvre de wideNES dans l'ANESE


Actuellement, wideNES n'est implémenté que dans l'émulateur NES que j'ai écrit sous le nom ANESE. ANESE est un émulateur très spartiate: la plupart des options sont cachées derrière les drapeaux CLI, et la seule interface utilisateur implémentée est la superposition de sélection de fichier la plus simple! Il est encore très loin du niveau de "production".

Mis à part le manque d'interface utilisateur, ANESE et wideNES, les améliorations de la compatibilité et de la vitesse ne nuiraient pas. ANESE est le premier émulateur que j'ai écrit, et cela se voit!

Il y a pas mal de problèmes de compatibilité - de nombreux jeux ne fonctionnent pas correctement ou ne démarrent pas du tout. Heureusement, l'imperfection de l'ANESE ne signifie pas que wideNES est une mauvaise technologie. wideNES est construit sur des principes éprouvés qui seront faciles à mettre en œuvre dans d'autres émulateurs!

En termes de vitesse, ANESE et wideNES ne sont pas parfaits, et même sur des PC relativement puissants, les performances peuvent parfois descendre en dessous de 60fps! L'ANESE et wideNES doivent implémenter de nombreuses optimisations. Outre l'amélioration générale du noyau ANESE, il est nécessaire d'améliorer l'enregistrement de trames NES larges, le rendu de carte et l'échantillonnage de hachage.

Conclusion


Dans l'article, j'ai parlé des principaux aspects de wideNES, mais je n'ai pas pu décrire de nombreuses petites fonctionnalités. Par exemple, wideNES stocke une carte des valeurs de hachage et de défilement réelles de chaque image, qui sont utilisées pour permettre la répétition de scènes. Ceci et de nombreuses autres fonctionnalités sont décrites dans le code source largement commenté de wideNES, publié sur la page du projet wideNES .

Travailler sur wideNES a été une expérience vraiment incroyable, mais avec l'approche du nouveau semestre académique à Waterloe University, je doute que dans un proche avenir, je serai en mesure de continuer à développer wideNES. Pour le moment, les principales fonctions de wideNES fonctionnent, et je suis heureux d'avoir pu écrire cet article décrivant certaines de ses technologies!

Essayez d'utiliser wideNES et partagez vos sentiments! Téléchargez ANESE , lancez Super Mario Bros. , The Legend of Zelda ou Metroid , et jouez-les d'une nouvelle manière!

Source: https://habr.com/ru/post/fr421555/


All Articles