Proyecto WideNES: vaya más allá de los límites de la pantalla NES

imagen

A mediados de la década de 1980, el Nintendo Entertainment System (NES) era una consola imprescindible. El mejor sonido, los mejores gráficos y los mejores juegos entre todas las consolas de la época: la consola amplió los límites de lo posible. Hasta ahora, proyectos como Super Mario Bros. , The Legend of Zelda y Metroid son considerados algunos de los mejores juegos de todos los tiempos.

Más de 30 años después del lanzamiento de NES, los juegos clásicos se sienten geniales, lo que no se puede decir sobre el hardware en el que trabajaron. Con una resolución de solo 256x240, la consola NES no podía proporcionar juegos con suficiente espacio. Sin embargo, los intrépidos desarrolladores lograron encajar en los juegos de NES mundos increíbles e inolvidables: las mazmorras tipo laberinto de The Legend of Zelda , vastos espacios del planeta en Metroid , niveles brillantes de Super Mario Bros. . Sin embargo, debido a las limitaciones de hardware de NES, los jugadores nunca podrían ir más allá de 256x240 ...

Hasta hace poco

Te presento el proyecto wideNES : ¡una nueva forma de jugar a los clásicos de NES!



wideNES es una nueva tecnología para marcar juegos NES de forma automática e interactiva en tiempo real .

Cuando los jugadores se mueven por el nivel, wideNES graba la pantalla, construyendo gradualmente un mapa de la parte explorada del mundo. En los niveles posteriores, wideNES sincroniza la jugabilidad en la pantalla con el mapa generado, ¡esencialmente permitiendo a los jugadores ver más al "mirar" más allá de los límites de la pantalla NES! Lo mejor de todo es que la forma en que marca los juegos wideNES es completamente universal , lo que permite que una amplia gama de juegos NES trabajen con wideNES sin ninguna configuración.

¿Pero cómo funciona todo?



Si desea comprobar cómo funciona wideNES antes de leer el artículo, ¡por favor! ANESE es el emulador NES que escribí, y actualmente es el único emulador que implementa wideNES. Sin embargo, vale la pena advertir que ANESE no es el mejor emulador NES del mundo, tanto en términos de IU como de precisión de emulación. La mayoría de las funciones (incluida la inclusión de wideNES) solo están disponibles a través de la línea de comandos, y aunque muchos juegos populares funcionan bien, algunos otros pueden comportarse de manera inesperada.



Cómo funciona wideNES


Antes de profundizar en los detalles, es importante explicar brevemente cómo NES representa los gráficos.

Transferencia de píxeles usando PPU


El corazón de NES es el venerable procesador MOS 6502. A finales de los años 70 y principios de los 80, 6502 se usaba en todas partes y funcionaba en máquinas legendarias como Commodore 64, Apple II y muchas otras. Era barato, fácil de programar y lo suficientemente potente como para ser peligroso.

Complementando 6502 en la consola NES había un poderoso coprocesador de gráficos llamado Picture Processing Unit (PPU). En comparación con los simples coprocesadores de video utilizados en sistemas más antiguos, PPU es una gran mejora en términos de usabilidad. Por ejemplo, cinco años antes del lanzamiento de NES, el procesador Atari 2600 6502 se usó para transmitir instrucciones gráficas al coprocesador para cada línea de trama , lo que dejó al procesador muy poco tiempo para ejecutar la lógica del juego. A modo de comparación: PPU solo necesitaba un par de comandos por cuadro , y esto le dio a 6502 tiempo suficiente para crear un juego interesante e innovador.

PPU es un chip increíble, su forma de renderizar gráficos no se parece en nada al trabajo de las GPU modernas, y se requerirá una serie completa de artículos para explicar completamente sus funciones. Como wideNES usa solo un pequeño subconjunto de las funciones de PPU, es suficiente considerarlas brevemente:

  • Resolución: 256x240 píxeles, 60 Hz
  • Funciona independientemente de la CPU.
    • Se comunica con la CPU mediante E / S con asignación de memoria (rango de direcciones 0x2000 - 0x2007)
  • 2 capas de representación: capa de sprite y capa de fondo
    • Capa de sprite
      • Cada sprite individual se puede colocar en cualquier lugar de la pantalla.
      • Ideal para mover objetos: jugador, enemigos, proyectiles.
      • Hasta 64 sprites de 8x8 píxeles
    • Capa de fondo
      • Atado a una cuadrícula
      • Ideal para elementos estáticos: plataformas, grandes obstáculos, decoraciones.
      • La memoria de video es suficiente para almacenar mosaicos de 64x30 de tamaño 8x8 píxeles
        • Resolución interna verdadera de 512x240, con una vista de 256x240
        • Admite desplazamiento de hardware para cambiar la vista de 256x240
          • El registro PPUSCROLL (dirección 0x2005) controla el desplazamiento de la ventana gráfica en X / Y

Después de haber tratado esta breve descripción, pasemos a lo más interesante: ¿cómo funciona wideNES?

Idea principal


Al final de cada trama, la CPU envía la información de cambio a la PPU. Estos incluyen nuevas posiciones de sprites, nuevos datos de nivel y, lo cual es crítico para wideNES, nuevas compensaciones de viewport . Como wideNES funciona en el emulador, es muy fácil para nosotros hacer un seguimiento de los valores escritos en el registro PPUSCROLL, lo que significa que es increíblemente fácil calcular cuánto se ha movido la pantalla entre dos cuadros.

Hmm, ¿qué sucederá si, en lugar de dibujar cada nuevo cuadro directamente sobre el cuadro anterior, los cuadros nuevos se dibujarán superpuestos en el cuadro anterior, pero se desplazarán al valor de desplazamiento actual? Luego, con el tiempo, una parte cada vez más grande del nivel permanecerá en la pantalla, ¡creando gradualmente una imagen completa del nivel!

Para verificar si esta idea era de algún valor, esbocé rápidamente la primera implementación.

Compilando ...
Lanzando ...
Descargar Super Mario Bros. ...

Voila!


Funcionó!

Parece ser ...



Otro enfoque: ¿por qué no extraer niveles directamente de los archivos ROM?


Sin siquiera considerar los detalles de implementación, resulta obvio que esta técnica tiene una seria limitación: un mapa completo del juego solo se puede recolectar cuando el jugador explora de forma independiente todo el juego.

¿Qué pasaría si hubiera alguna forma de extraer niveles de las ROM de NES sin procesar ?

¿Puede existir tal técnica?

Bueno, muy probablemente no.

Si tomas dos juegos para NES, puedes garantizar que solo tienen una cosa en común: ambos funcionan para NES. ¡Todo lo demás puede ser completamente diferente! ¡Tal desajuste es un verdadero desastre, porque los juegos de NES esencialmente tienen un número infinito de opciones para almacenar datos de nivel!

Algunas personas han extraído niveles completos mediante ingeniería inversa de la forma en que almacenan los datos de nivel de un par de juegos (¡a veces con la creación de editores de mapas con todas las funciones!), Pero esta es una tarea difícil, que requiere mucho trabajo, perseverancia e inteligencia.

Para extraer datos de nivel de ROM, es necesario determinar qué partes de ROM son código (no datos), y esto es difícil de hacer, ¡porque encontrar todo el código en un archivo binario es equivalente a un problema de detención !

WideNES utiliza un enfoque mucho más simple: en lugar de adivinar cómo el juego empacó los datos de nivel en ROM, wideNES simplemente inicia el juego y realiza un seguimiento de la salida.



Desplazarse más allá de 255


NES es un sistema de 8 bits, es decir, el registro PPUSCROLL solo puede recibir valores de 8 bits. Esto limita el desplazamiento máximo de desplazamiento a 255 píxeles, es decir, el número máximo de 8 bits. No es casualidad que la resolución de pantalla NES sea de 240x256 píxeles, es decir, un desplazamiento de 255 píxeles es suficiente para desplazar toda la pantalla.

Pero, ¿qué sucede al desplazarse más allá de 255?

En primer lugar, los juegos restablecen el registro PPUSCROLL a 0. Esto explica por qué SMB se lleva al principio cuando Mario se mueve demasiado a la derecha.

Luego, para compensar las restricciones PPUSCROLL de 8 bits, los juegos actualizan otro registro PPU: PPUCTRL (dirección 0x2000). Los 2 bits inferiores de PPUCTRL establecen el "punto de partida" de la escena actual en incrementos de pantalla completa. Por ejemplo, escribir un valor de 1 desplaza la vista hacia la derecha 256 píxeles; un valor de 2 desplaza la vista hacia abajo 240 píxeles. El desplazamiento PPUCTRL se inserta en la pila con el registro PPUSCROLL, que le permite desplazar la pantalla horizontalmente dentro de 512 píxeles o verticalmente dentro de 480 píxeles.

Pero construir, ¿hay suficiente memoria de video para pantallas de dos niveles? ¿Qué sucede cuando la ventana gráfica se desplaza demasiado a la derecha y "va más allá" de la VRAM? Para manejar este caso, PPU implementa la convolución: todas las partes de la ventana de visualización fuera de la memoria de video seleccionada simplemente se colapsan en el borde opuesto de la memoria de video.

¡Este plegado, combinado con la manipulación inteligente de registros PPUSCROLL y PPUCTRL, permite a los juegos de NES crear la ilusión de mundos infinitamente altos / anchos! Gracias a la carga perezosa de parte del nivel fuera de la ventana de visualización y al desplazamiento gradual hacia ella, ¡los jugadores nunca se dan cuenta de que dentro de VRAM realmente "corren en círculos"!

Una excelente ilustración de la wiki de nesdev muestra cómo Super Mario Bros. usa estas propiedades para crear niveles más largos que dos pantallas:


Volvamos a la pregunta que estamos discutiendo: ¿cómo maneja wideNES el desplazamiento más allá de 256?

Bueno, francamente, wideNES ignora por completo el registro PPUCTRL y solo realiza un seguimiento de la diferencia PPUSCROLL entre cuadros.

Si PPUSCROLL salta inesperadamente a aproximadamente 256, lo que generalmente significa que el personaje del jugador se movió hacia la izquierda / arriba en la pantalla, y si inesperadamente salta a alrededor de 0, entonces esto generalmente significa que el jugador se movió hacia la derecha / abajo en la pantalla.

Aunque esta heurística puede parecer simple, y lo es, de hecho, ¡funciona muy bien!

Después de implementar esta heurística, Super Mario Bros. , Metroid y muchos otros juegos han funcionado casi a la perfección.

Estaba emocionado, así que seguí adelante y subí otro clásico de NES: Super Mario Bros. 3 ...


Hmm ... No muy bonita.

Ignorando elementos de pantalla estáticos


Muchos juegos tienen elementos UI estáticos alrededor de los bordes de la pantalla. En el caso de SMB3, esta es la columna de la izquierda y la barra de estado se encuentra en la parte inferior del estado.

Por defecto, las muestras wideNES con incrementos de 16 píxeles desde los bordes de la pantalla, es decir, se muestrean todos los elementos estáticos en los bordes. No esta bien!

Para solucionar este problema, wideNES implementa reglas y heurísticas que intentan reconocer y enmascarar automáticamente los elementos estáticos de la pantalla.

En general, los juegos de NES usan tres tipos diferentes de elementos de pantalla estática: HUD, máscaras y barras de estado.

HUD: no hay problema


Si un juego impone un HUD en la parte superior de un nivel, entonces es probable que el HUD consista en varios sprites. Ejemplo: HUD en Metroid .

Afortunadamente, tales HUD no causan problemas, porque wideNES actualmente simplemente ignora la capa de sprites. Genial

Máscaras: nada más fácil


PPU tiene una función que permite a los juegos enmascarar los 8 píxeles más a la izquierda de la capa de fondo. Se activa configurando el segundo bit del registro (dirección 0x2001). Muchos juegos usan esta función, pero explicar por qué lo hacen está más allá del alcance de este artículo.

Reconocer la máscara incluida es increíblemente simple: wideNES solo realiza un seguimiento del valor de PPUMASK e ignora los 8 píxeles más a la izquierda cuando se establece el segundo bit en el registro.

Parece que la implementación de esta simple regla solucionó el problema con SMB3 :


... bueno, o casi eliminado.

Las barras de estado son las más difíciles.


Debido a las limitaciones de PPU en cualquier momento en la pantalla, no puede haber más de 64 sprites; Además, en cualquier momento en cada línea de trama no puede haber más de 8 sprites. Esta restricción evita que los desarrolladores creen HUD complejos a partir de sprites y los obliga a usar partes de la capa de fondo para mostrar información.

Además de las máscaras, no hay una manera fácil en PPU para separar la capa de fondo en el área de juego y el área de estado. Por lo tanto, los desarrolladores recurrieron a trucos, lo que condujo a un montón de formas poco ortodoxas de crear paneles de estado ...

WideNES utiliza varias heurísticas para reconocer diferentes tipos de paneles de estado, pero para ahorrar tiempo, consideraré solo uno de los más interesantes: el seguimiento de IR en el medio del cuadro (seguimiento de IRQ de cuadro medio).

Seguimiento de IRQ de cuadro medio


A diferencia de las GPU modernas con grandes búferes de trama interna, las PPU generalmente no tienen un búfer de trama. Para ahorrar espacio, PPU almacena escenas como una cuadrícula de mosaicos de 64x32 de 8x8 píxeles. En lugar de calcular previamente los datos de píxeles, los mosaicos se almacenan como punteros a la memoria CHR (memoria de caracteres), que contiene todos los datos de píxeles.

Desde que NES se desarrolló en los años 80, PPU se creó sin tener en cuenta las tecnologías de visualización modernas. En lugar de representar el cuadro completo al mismo tiempo, la PPU emite la señal de video NTSC, que debe mostrarse en una pantalla CRT que muestra video píxel por píxel , línea por línea , de arriba a abajo, de arriba a abajo, de izquierda a derecha.

¿Por qué es todo esto importante?

¡Dado que PPU procesa los cuadros de arriba a abajo, línea por línea, puede enviar instrucciones de PPU a mitad de cuadro para crear efectos de video que son imposibles con cualquier otro enfoque! Estos efectos pueden ser simples (por ejemplo, cambiar la paleta) o bastante complejos (por ejemplo, ¡lo has adivinado, creando barras de estado!).

Para explicar cómo una escritura PPU de mitad de cuadro puede crear barras de estado, grabé un volcado de porción de video de memoria PPU y CHR sin procesar para un solo cuadro SMB3 :


Todo se ve bien, nada especial ... ¡pero solo mira la barra de estado! Ella está completamente distorsionada!

Ahora mire el mismo volcado en bruto, pero hecho después de la línea 196 ...


Sí, el nivel se ve horrible, ¡pero la barra de estado se ve genial!

¿Qué está pasando aquí?

SMB3 establece un temporizador para activar IRQ (interrupción) exactamente después de representar la línea de trama 195. Pasa las siguientes instrucciones al controlador IRQ:

  • Establezca PPUSCROLL en (0,0) (para que la barra de estado permanezca en su lugar)
  • Reemplazamos la tarjeta de mosaico en la memoria CHR (ponemos en orden los gráficos de la barra de estado)

Como el resto de la capa ya está renderizada, la PPU no "actualizará" el marco. En cambio, continuará renderizando con estas opciones, ¡mostrando una hermosa barra de estado sin distorsiones!

Volvamos a wideNES: al observar todas las IRQ en el medio del cuadro y al recordar la línea ráster en la que ocurrieron, wideNES puede ignorar todas las líneas ráster posteriores en el registro. Si se produce IRQ en la línea ráster por encima de 240/2, todas las líneas anteriores se ignoran, porque la interrupción temprana de la línea ráster significa que la barra de estado puede estar en la parte superior de la pantalla.

Después de implementar esta heurística, Super Mario Bros. ¡3 ganados perfectos!




Brevemente consideré la posibilidad de usar una biblioteca de visión por computadora, como OpenCV, para reconocer paneles de estado (u otras áreas mayormente estáticas de la pantalla), pero como resultado decidí abandonarla. Usar una biblioteca de visión por computadora enorme, compleja y opaca es contrario a los ideales de wideNES, en el que trato de usar reglas y heurísticas compactas, simples y transparentes para obtener resultados.



Reconocimiento de escena


Con la excepción de algunos ejemplos destacados (por ejemplo, Metroid ), los juegos para NES generalmente no pasan dentro de un nivel enorme e inextricable. Por el contrario, la mayoría de los juegos de NES se dividen en muchas "escenas" pequeñas e independientes con puertas o pantallas de transición entre ellas.

Como wideNES no tiene el concepto de "escenas", suceden cosas malas cuando se cambian las escenas ...

Por ejemplo, aquí está la primera transición de la escena Castlevania , donde Simon Belmont entra en el castillo de Drácula:


¡Guau, todo está mal! ¡wideNES reescribió completamente la última parte del nivel con la primera pantalla de un nuevo nivel!

Obviamente, wideNES necesita alguna forma de reconocer los cambios de escena. Pero cual?

Hashing perceptual!

A diferencia de las funciones hash criptográficas , que tienden a distribuir de manera uniforme datos de entrada similares en el espacio de información de salida, las funciones hash perceptuales intentan mantener los datos de entrada similares "cerca" entre sí en el espacio de datos de salida. ¡Por lo tanto, los hash perceptuales son ideales para reconocer imágenes similares!

Las funciones hash perceptivas pueden ser increíblemente complejas, algunas de ellas pueden reconocer imágenes similares si una de ellas fue rotada, escalada, estirada y los colores cambiaron. Afortunadamente, wideNES no requiere funciones hash complejas porque se garantiza que cada cuadro tendrá el mismo tamaño. Por lo tanto, wideNES utiliza el hash perceptual más simple: ¡ sumando todos los píxeles en la pantalla!

¡Es simple, pero funciona bastante bien!

Por ejemplo, vea cómo se destacan las transiciones entre escenas si traza el hash perceptual a lo largo del tiempo en The Legend of Zelda :


Actualmente, wideNES usa un umbral fijo entre los valores hash perceptuales para completar la transición entre escenas, pero el resultado está lejos de ser ideal. Diferentes juegos usan diferentes paletas, y hay muchos casos en los que wideNES cree que se ha producido una transición, pero en realidad no fue así. Idealmente, wideNES debería usar un valor de umbral dinámico, pero hasta ahora el fijo lo hará.

Después de implementar esta nueva heurística, wideNES reconoce con éxito la entrada de Simon desde Castlevania al castillo y, en consecuencia, crea un nuevo lienzo.


Y con esta decisión, ponemos en marcha la última pieza importante del rompecabezas de wideNES.

Habiendo implementado la serialización más simple, finalmente pude ejecutar el juego para NES, jugar en varios niveles y generar automáticamente mapas de niveles.

¿Qué le espera a wideNES en el futuro?


wideNES consta de dos partes separadas: el kernel wideNES, que son las reglas / heurísticas subyacentes a la tecnología, y la implementación específica de wideNES dentro del emulador ANESE.

Ampliación del núcleo de WideNES


En primer lugar, wideNES es propenso a un reconocimiento demasiado agresivo de las transiciones entre escenas. El número de falsos positivos se puede minimizar utilizando un algoritmo de hashing perceptual más adecuado o cambiando a valores de umbral dinámico entre hashes perceptuales.

También se requiere trabajo adicional para reconocer elementos de pantalla estáticos.Por ejemplo, Megaman IV tiene una IRQ en el medio del cuadro, pero no hay una barra de estado, por lo que wideNES ignora por error la parte sólida del campo de juego. Aunque este caso particular puede corregirse mediante ajuste manual, es mejor usar heurísticas más inteligentes.

Algunos juegos de NES desplazan la pantalla de formas "únicas". Uno de los ejemplos más notables es The Legend of Zelda , que utiliza PPUSCROLL para desplazamiento horizontal, pero utiliza un registro completamente diferente para desplazamiento vertical: PPUADDR. Zelda es un juego bastante popular, por lo que wideNES implementa heurística específicamente para Zelda. Hay otros juegos con modos de desplazamiento "únicos" similares, que también requieren heurísticas individuales.

Sería útil encontrar alguna forma de "coser" escenas idénticas. Por ejemplo, si un usuario juega a Super Mario Bros. Nivel 1, pero se arrastra hacia la tubería para ingresar a la cueva subterránea con monedas, wideNES creará dos escenas separadas para el Nivel 1: escena A, nivel hasta que Mario ingrese a la zona con monedas, y la escena B, nivel, desde el momento cuando Mario sale de la tubería y sube al asta de la bandera. Si el juego se reinicia y el Nivel 1 se reproduce sin entrar en la tubería, wideNES simplemente actualizará la escena A, que contendrá un mapa de nivel completo, pero la escena B se "interrumpirá".

Finalmente, wideNES debe seguir las transiciones entre escenas. Sin estos datos, no será posible construir un gráfico de transiciones entre escenas para generar mapas mundiales de juegos que no consisten en un solo mundo grande.

Mejorando la implementación de wideNES en ANESE


Actualmente, wideNES se implementa solo en el emulador NES que escribí con el nombre ANESE. ANESE es un emulador muy espartano: la mayoría de las opciones están ocultas detrás de los indicadores de la CLI, y la única interfaz de usuario implementada es la superposición de selección de archivos más simple. Todavía está muy lejos del nivel de "producción".

Además de la falta de UI, ANESE y wideNES, las mejoras en compatibilidad y velocidad no afectarían. ¡ANESE es el primer emulador que escribí, y es notable!

Hay bastantes problemas de compatibilidad: muchos juegos no funcionan correctamente o no se inician en absoluto. Afortunadamente, la imperfección de ANESE no significa que wideNES sea una mala tecnología. ¡wideNES se basa en principios probados que serán fáciles de implementar en otros emuladores!

En términos de velocidad, ANESE y wideNES no son perfectos, e incluso en PC relativamente potentes, ¡el rendimiento a veces puede caer por debajo de 60 fps! ANESE y wideNES necesitan implementar muchas optimizaciones. Además de la mejora general del núcleo ANESE, existe la necesidad de mejorar la grabación de marcos wideNES, la representación de mapas y el muestreo hash.

Conclusión


En el artículo, hablé sobre los aspectos principales de wideNES, pero no pude describir muchas características pequeñas. Por ejemplo, wideNES almacena un mapa de los valores reales de hash y desplazamiento de cada cuadro, que se utilizan para habilitar escenas repetidas. Esta y muchas otras características se describen en el código fuente ampliamente comentado para wideNES, publicado en la página del proyecto wideNES .

Trabajar en wideNES fue una experiencia realmente sorprendente, pero con el enfoque del nuevo semestre académico en la Universidad de Waterloe, dudo que en el futuro cercano pueda continuar desarrollando wideNES. En este momento, las funciones principales de wideNES están funcionando, ¡y me alegra haber podido escribir esta publicación describiendo algunas de sus tecnologías!

¡Intenta usar wideNES y comparte tus sentimientos! Descarga ANESE , lanza Super Mario Bros. , The Legend of Zelda o Metroid , ¡y juega de una manera nueva!

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


All Articles