https://retrowiki.es/viewtopic.php?f=114&t=200039791
Se ha visto que para emular bordes de color en el juego, NO se necesita ni Accurate cycle (ciclo exacto) ni Contended memory (memoria en contienda). Además, la solución planteada, al no consumir CPU, ni memoria adicional, garantiza su inmediata implantación sin falta de tener que comprobar rendimientos. Juegos como el Beach Head II y UGH!, siguen la misma solución trivial.
Ahora le toca el turno al juego de 2014, DreamWalker (Alter Ego 2), que por muchos sitios figura que se requiere:
- Cycle accurate (ciclo exacto)
- Contended memory (memoria en contienda).
¿Es el zx 48K una máquina tan única, exacta y especial que puede superar la fumada del IBM5100 con la historia de John Titor?
https://es.wikipedia.org/wiki/John_Titor
Para no perder minutos de nuestra vida y hacer esto más corto, NO se necesita nada de lo antes comentado, es más, tampoco se necesita al actor tiempo. Dado que en este foro, se respetan las leyes de la termodinámica, no se puede viajar en el tiempo. La mera existencia de un objeto, ya sea vivo o inerte, ya garantiza que el tiempo está avanzando, y eso es lo único que interesa, como veremos más adelante explicado.
Y ahora, toda la chapa:
Antes de empezar, aclaración de conceptos:
- Cycle accurate (ciclo exacto) https://emulation.gametechwiki.com/index.php/Emulation_accuracy
- Contended memory (memoria en contienda) https://sinclair.wiki.zxnet.co.uk/wiki/Contended_memory
- NIRVANA engine: https://sinclair.wiki.zxnet.co.uk/wiki/NIRVANA_Engine
- Tiempos de video: http://www.zxdesign.info/interrupts.shtml
- Memoria de video: https://sinclair.wiki.zxnet.co.uk/wiki/Spectrum_Video_Modes
La solución basada en emuladores de ciclo exacto, es el equivalente a usar la fuerza bruta para sacar las claves de criptografía, es decir, es un opción, pero con una sobrecarga en la máquina host. Aunque se suele utilizar múltiples mecanismos que gestionan todos los conceptos arriba nombrados, al final se suele acabar con una resincronización que suma y resta tiempos, o bien muestreos para que las cosas cuadren entre el binomio ULACPU y la sincronización con la función de redibujado.
Cada escanline de los 192 posibles, consta de:
- 48 tstates borde + 128 bitmap + 48 tstates borde.
- 96 pixels borde + 256 pixels bitmap + 96 pixels borde.
- 1 scanline completo (224 tstates).
- 4 caracteres (32 pixels) de un scanline.
- 8 pixels de un carácter en un scanline.
- 4 pixels de un carácter de 8 en un scanline.
Por eso , aquí se puede ver una manera alternativa:
La solución más eficiente es la simulación del engine NIRVANA, al estilo del engine BIFROST, como había comentado hace tiempo para localizar los btiles, ctiles y tilemap en memoria:
https://retrowiki.es/viewtopic.php?f=115&t=200038449#p200168173
Pero mientras tanto, dado que hay que implementar el 100% de la funcionalidad del engine, se puede llegar a un sistema intermedio (ni pa ti ni pa mi). Lo primero que necesitamos, un core de emulación que permita ejecutar el juego, como puede ser el LKF (LIN KE FONG) en el ESP32, aunque se vea mal. Esto es fundamental, porque si el core no carga un juego, aunque tenga defectos visuales, poco se puede hacer.
Podemos usar cualquier otro core que se nos ocurra (incluidos primitivos), ya sea de MAME, libretro, etc...
Tenemos que detectar el juego, que puede ser algo tan sencillo como a la hora de cargar el SNA (Snapshot) detectar la cadena en la zona de memoria: Lo siguiente es usar un algoritmo primitivo, como es el algoritmo del pintor, para guardar cada vez que se modifica en tiempo real un atributo de color en un array de atributos, así como un contador con el conteo actual. Se necesita pues, un array de 768 contadores de un byte.
Se requiere modificar la rutina de escritura a memoria del core de Z80, sólo para este juego, que permita controlar cuando se escribe en la zona de atributos, para que guarde su contador, y el atributo, que antes hacía algo tan básico como escribir en zonas de memoria:
Código: Seleccionar todo
void writebyte(uint16_t addr, uint8_t data)
{
gb_real_write_ptr_ram[(addr>>14)][(addr & 0x3fff)] = data;
}
Código: Seleccionar todo
unsigned char gb_atributos[768*16];
unsigned char gb_atributos_cont[768];
void writebyte(uint16_t addr, uint8_t data)
{
gb_real_write_ptr_ram[(addr>>14)][(addr & 0x3fff)] = data;
if (addr>=0x5800 && addr<(0x5800+768))
{
unsigned short int idAtributo= addr-0x5800;
unsigned char contador= gb_atributos_cont[idAtributo];
if (contador<16)
{
gb_atributos[(idAtributo*16)+contador]= data;
contador++;
gb_atributos_cont[idAtributo]= contador;
}
}
Por tanto, no necesitamos llevar la medición del tiempo, tal y como se comentó al principio del post, respetando las leyes de la termodinámica.
Hemos pasado del tiempo absolutamente, y sin embargo logramos el mismo resultado visual que si se usara micro segundos.
Al seguir teniendo una rutina de redibujado basada en una instantánea, además de los cambios de cada atributo en cada escritura, no hace falta tener un proceso de seguimiento, ni hilos paralelos, lo que permite portarlo cómodamente a múltiples plataformas.
Para este post, he añadido al emulador la posibilidad de procesar el modo normal (izquierda) y el NIRVANA (derecha) en tiempo real, con el core de LKF, pudiendo ver las diferencias.
La pantalla de portada, que es muy bonita no hace uso de NIRVANA ni multicolor, así que esta parte podríamos no procesarla, para optimizar: Al igual que el anterior, en esta pantalla tampoco se hace uso del NIRVANA, así que se puede procesar o no, lo que viene siendo un tanto da, que da lo mismo: Luego viene el logo de NIRVANA, que aquí, si hace uso del NIRVANA, y se puede ver la diferencia, entre una y otra. La siguiente pantalla también hace uso del NIRVANA: La pantalla del zx spectrum es de 32x24, donde cada carácter es de 8x8.
En este pantalla del modo nirvana, he añadido al emulador, que saque en tiempo real las estadísticas del conteo de los atributos que están cambiando, con el conteo del número de escrituras que han ocurrido en un frame completo de 20 ms:
Código: Seleccionar todo
(0,0) (31,0)
00000000000000000000000000000000
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
04444444444444444444444444444440
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
(0,23) (31,23)
Pero si no queremos exprimir mucho, y queremos un algoritmo genérico, son (3840 bytes):
- 768x4 = 3072 bytes de atributos
- 768 bytes de contadores
La memoria de video para BITMAP en un spectrum se divide en 3 zonas:
- Zona 1: 0x4000 - 0x47FF
- Zona 2: 0x4800 - 0x4FFF
- Zona 3: 0x5000 - 0x57FF
- columna 7:
- fila: 1
El valor BITMAP de las posiciones:
Código: Seleccionar todo
linea Valor hex Valor bin Memoria
0 0xE0 11100000 0x4127
l 0x38 00111000 0x4227
2 0x0C 00001100 0x4327
3 0x06 00000110 0x4427
4 0x06 00000110 0x4527
5 0x03 00000011 0x4627
6 0x03 00000011 0x4727
7 0x03 00000011 0x4827
El valor que hemos acumulado de las escrituras en las 4 llamadas ha sido:
Código: Seleccionar todo
iteración Valor Hex Brillo Primer plano (ink) Fondo(Paper)
0 0x41 SI 1(azul brillante) 0 negro
1 0x43 SI 3(magenta brillante) 0 negro
2 0x03 NO 3(magenta oscuro) 0 negro
3 0x4D SI 5(cian brillante) 1 azul brillante
Código: Seleccionar todo
Bit: 7 6 5 4 3 2 1 0
Flash Brillo |---- Paper ----| |------ Ink------|
Código: Seleccionar todo
linea iteración
0 0
1 0
2 1
3 1
4 2
5 2
6 3
7 3
Nivel 3, que también tiene NIRVANA, lo mismo, sigue 4 llamadas en contadores: Con todo esto, conseguimos que se pueda añadir la funcionalidad del NIRVANA a cualquier core que no lo tenga, con un consumo muy bajo de CPU y RAM. Así mismo la funcionalidad, permite una gran escalabilidad y mantenibilidad, al ser modular, pudiendo ser aplicada de manera custom a un juego, sin falta de afectar al resto, evitando tener que hacer tests unitarios y repetir el día de la marmota.