A mediados de los años ochenta, el ZX Spectrum de Sinclair representaba un auténtico milagro doméstico. Con un microprocesador Zilog Z80 corriendo a apenas 3,5 MHz, 48 Kilobytes de RAM compartida y una estructura de memoria de vídeo profundamente restrictiva, el ordenador de la gomita negra parecía condenado a la bidimensionalidad más plana.
Sin embargo, entre 1984 y 1988, una legión de programadores consiguió romper la barrera del plano cartesiano $X/Y$ e introdujo a toda una generación en entornos tridimensionales navegables. No lo hicieron mediante compleja geometría poligonal —inalcanzable para la época—, sino mediante una sofisticada ilusión visual y matemática: la perspectiva isométrica.
Este artículo analiza en profundidad la evolución histórica de esta corriente gráfica en el ZX Spectrum y desglosa, a nivel informático y de ensamblador Z80, los entresijos técnicos que permitieron levantar mundos de tres dimensiones donde teóricamente solo había una pantalla de $256 \times 192$ píxeles.
Parte 1: Génesis e historia del 3D isométrico en los 8 bits
Para entender cómo nació el género en el ZX Spectrum hay que comprender el impacto del formato. La proyección isométrica (técnicamente, una axonometría dimétrica con relación de aspecto 2:1 adoptada por simplicidad de dibujado) permitía proyectar tres ejes coordenados ($X, Y, Z$) sobre un plano bidimensional sin que las líneas paralelas se conmovieran hacia un punto de fuga. Esto significaba que los objetos no cambiaban de escala con la distancia, simplificando drásticamente los cálculos de dibujado.
Eje Z (Altura)
|
| Eje Y (Profundidad)
| /
|/
---------+---------
/
/
Eje X (Anchura)
El punto de inflexión: Ultimate Play the Game y la revolución de Knight Lore

Antes de 1984, los juegos del Spectrum se dividían en arcades de scroll lateral o juegos de laberintos vistos desde arriba (top-down). Aunque títulos como Ant Attack (1983) de JK Greye Software ya exploraban entornos tridimensionales mediante bloques sólidos, la fluidez, el detalle y la coherencia espacial eran aún rudimentarias.
Todo cambió en diciembre de 1984 con el lanzamiento de Knight Lore, desarrollado por los hermanos Chris y Tim Stamper bajo el sello Ultimate Play the Game.
La paradoja comercial de Ultimate: Se sabe que los hermanos Stamper tenían Knight Lore prácticamente terminado a principios de 1984. Sin embargo, retenieron su lanzamiento durante casi un año. Sabían que el motor que habían diseñado era tan avanzado que, de haberlo publicado de inmediato, habría destruido por completo las ventas de sus títulos contemporáneos (Sabre Wulf y Underwurlde), los cuales aún usaban gráficos bidimensionales tradicionales.
Cuando Knight Lore llegó a los comercios, el impacto fue catastrófico para la competencia. El jugador controlaba a Sabreman en una visión diáfana en tres dimensiones dentro del castillo de Melkhior. Podía saltar sobre mesas, empujar bloques, esquivar trampas a distintas alturas y ver cómo las sombras y los objetos se solapaban de manera coherente.
El secreto detrás de esta proeza fue bautizado por Ultimate como el motor Filmation. Filmation no solo definía un estilo estético; incorporaba por primera vez una gestión estricta de máscaras de sprites y un algoritmo elemental de ordenación de profundidad (frecuentemente asociado al algoritmo del pintor).
Evolución de los motores isométricos en ZX Spectrum (1983-1987):
[1983] Ant Attack ───> Primitiva proyección de bloques y libertad de cámara.
[1984] Knight Lore ───> Motor Filmation: Máscaras de ocultación y salas estáticas.
[1985] Alien 8 ───> Perfeccionamiento de Filmation, temática de ciencia ficción.
[1986] MOVIE ───> Introducción de perspectiva isométrica con scroll.
[1987] Head over Heels -> Cúspide técnica: Puzles complejos, simulación de físicas y dos personajes.
Sinclair ZX Spectrum 48K Original
Consigue una pieza fundamental de la historia de los videojuegos y la informática personal. Unidad revisada, con teclado de goma icónico en excelente estado y conector de cassette original.
La fiebre isométrica: Clones, refinamientos y obras maestras
Tras la irrupción de Knight Lore, la industria del videojuego en el Reino Unido y España sufrió una conmoción. Decenas de empresas trataron de aplicar ingeniería inversa al motor de Ultimate.
- La era Filmation I y II (Alien 8, Pentagram): Ultimate explotó su propio motor con Alien 8 (1985), trasladando la fórmula a una nave espacial. Más tarde evolucionaron la técnica con Filmation II en juegos como Nightshade (1985) y Gunfright (1986), tratando de implementar un scroll isométrico continuo en lugar de pantallas estáticas habitación por habitación, aunque a costa de aumentar el parpadeo (flicker) de los sprites.
- La madurez de Ocean Software y los gráficos de Bernie Drummond: En 1986, el programador Jon Ritman y el artista gráfico Bernie Drummond publicaron Batman. Ritman no copió el código de Ultimate; creó su propio motor desde cero. Los personajes ganaron en tamaño, expresividad y un control de colisiones incomparablemente superior.
- La obra cumbre: Head over Heels (1987): Ritman y Drummond alcanzaron el cenit del subgénero con Head over Heels. El juego permitía controlar a dos criaturas distintas (Head y Heels) con habilidades físicas complementarias (uno saltaba alto y planeaba, el otro corría y transportaba objetos). El diseño de niveles, la gestión del búfer gráfico y el uso impecables de la memoria convirtieron a este título en el referente absoluto del género de 8 bits.
- La visión de Imagine y la escuela hispana: Títulos como MOVIE (Imagine Software, 1986) aportaron una ambientación de cine negro con uso intensivo de menús e íconos en pantalla. En España, dinámicas similares dieron vida a clásicos de la «Edad de Oro del Software Español» como La Abadía del Crimen (Opera Soft, 1987), ideada por Ignacio Zoco y programada por Paco Menéndez. Aunque la Abadía utiliza una proyección oblicua/axonométrica singular más que una isometría estricta de 2:1, resolvió los mismos problemas de oclusión y arquitectura espacial a un nivel técnico deslumbrante.
También te puede interesar sobre Tecnología
Parte 2: Arquitectura de hardware del ZX Spectrum y sus limitaciones
Para valorar el mérito informático de programar un motor isométrico en el ZX Spectrum, primero hay que enfrentarse a la cruda realidad del hardware sobre el que corría.
1. El chip Zilog Z80A
El Z80 es un procesador de 8 bits con un bus de direcciones de 16 bits (capaz de direccionar hasta 64 KB de memoria). Carece por completo de:
- Unidades de punto flotante (FPU).
- Instrucciones nativas de multiplicación o división de enteros (solo sumas, restas, desplazamientos de bits y operaciones lógicas).
- Instrucciones de renderizado por hardware o manipulación de bloques de memoria gráfica dedicados (sin «blitter»).
Todas y cada una de las operaciones de renderizado tenían que ser calculadas instrucción a instrucción por la CPU, restando ciclos que también debían emplearse en la lógica del juego, las canciones en el altavoz buzzer y el escaneo de entradas de teclado/joystick.
2. La disposición estrambótica de la VRAM (Video RAM)

La memoria gráfica del ZX Spectrum ocupa un bloque contiguo de $6.912$ bytes comenzando en la dirección de memoria 16384 ($4000 en hexadecimal). Se compone de dos partes:
- Búfer de píxeles (Bitmap): $6.144$ bytes (desde
$4000hasta$57FF). - Búfer de atributos (Color): $768$ bytes (desde
$5800hasta$5BFF).
El verdadero desafío estriba en que el mapa de píxeles no es lineal. Si escribes en el primer byte de la VRAM, modificas los 8 píxeles superiores izquierdos de la pantalla. Pero el byte contiguo en memoria no es la línea de abajo, sino la línea situada 8 píxeles más abajo.
La pantalla de $256 \times 192$ píxeles se divide verticalmente en 3 tercios de 64 líneas cada uno:
Direcciones de memoria por Tercios en la VRAM:
------------------------------------------------------------------
Tercio Superior (Líneas 0 - 63) : $4000 a $47FF
Tercio Medio (Líneas 64 - 127) : $4800 a $4FFF
Tercio Inferior (Líneas 128 - 191): $5000 a $57FF
------------------------------------------------------------------
Dentro de cada tercio, la dirección de una línea de píxeles concreta se codifica mediante la siguiente estructura de bits en un registro de 16 bits HL:
Bit: 15 14 13 12 11 10 09 08 | 07 06 05 04 03 02 01 00
Valor: 0 1 0 T T L L L | R R R C C C C C
Donde:
TT: Número de Tercio (00,01,10).LLL: Línea del carácter (0 a 7 dentro del bloque).RRR: Fila del carácter (0 a 7 dentro del tercio).CCCCC: Columna horizontal (0 a 31, representando bloques de 8 píxeles).
Esta disposición causa que calcular la posición vertical $+1$ (bajar un píxel) no consista en sumar simplemente una constante (como 32 bytes), sino en aplicar operaciones lógicas INC H, manipulando los bits del registro alto.
3. El problema del «Attribute Clash» (Choque de Atributos)
En el Spectrum, la información de color se aplica en bloques de $8 \times 8$ píxeles. Cada byte del búfer de atributos asigna a un bloque de $8 \times 8$:
- 1 color de tinta (INK, 3 bits: 8 colores).
- 1 color de fondo (PAPER, 3 bits: 8 colores).
- 1 bit de brillo (BRIGHT).
- 1 bit de parpadeo (FLASH).
Dado que un dibujo isométrico exige diagonales constantes que cortan transversalmente las cuadrículas de $8 \times 8$, era inviable colorear los sprites sin provocar un horrendo «choque de color» alrededor del personaje.
La solución isométrica: Salvo excepciones contadas, la inmensa mayoría de motores isométricos (incluido Knight Lore) tomaron la drástica decisión de renderizar el área jugable en estricto monocromo (por ejemplo, Tinta Negra sobre Fondo Amarillo, o Tinta Blanca sobre Fondo Negro). Los atributos de color únicamente se utilizaban para separar la interfaz (HUD) del mundo del juego o para dar color uniforme a pantallas completas.
Parte 3: Matemáticas de la proyección isométrica e implementación gráfica
La conversión entre el sistema de coordenadas tridimensional del mundo del juego ($X, Y, Z$) y el plano bidimensional de la pantalla ($PantallaX, PantallaY$) constituye la columna vertebral del motor gráfico.
El sistema de coordenadas virtuales
En un juego clásico de tipo Filmation, una habitación se concibe como una caja delimitada por tres ejes:
- $X$: Longitud que va de la parte superior izquierda a la parte inferior derecha.
- $Y$: Profundidad que va de la parte superior derecha a la parte inferior izquierda.
- $Z$: Altura vertical perpendicular al suelo.
+Z (Altura)
|
|
|
/ \
/ \
/ \
/ \
-Y <-----*---------> +X
/ \ /
/ \ /
/ \ /
v v
+Y -X
Para simplificar la aritmética en el procesador Z80, se estandariza el ángulo de las diagonales. La relación matemática óptima para gráficos de píxeles es una pendiente 2:1 (por cada 2 píxeles en horizontal, nos desplazamos 1 píxel en vertical). Esto equivale a un ángulo visualizado de aproximadamente $26,56^\circ$, aunque popularmente se le denomine isometría ($30^\circ$).
Ecuaciones de transformación
Las fórmulas teóricas para transformar las coordenadas del espacio tridimensional $(X, Y, Z)$ a las coordenadas planas de pantalla $(Px, Py)$ son:
$$Px = (X – Y) \cdot \cos(\alpha) + OrtocentroX$$
$$Py = (X + Y) \cdot \sen(\alpha) – Z + OrtocentroY$$
Dado que en pantalla la pendiente es exactamente $2:1$, la implementación en un microprocesador sin coma flotante prescinde de las funciones trigonométricas $\cos$ y $\sen$. Las fórmulas discretas en enteros se simplifican a:
$$Px = (X – Y) + C_x$$
$$Py = \frac{X + Y}{2} – Z + C_y$$
Donde $C_x$ y $C_y$ son las constantes de desfase (offsets) para centrar el origen del escenario en la pantalla.
Implementación optimizada en ensamblador Z80
Realizar una división por 2 en el Z80 se resuelve de forma ultraeficiente mediante una instrucción de desplazamiento a la derecha (SRA o SRL).
A continuación se muestra una rutina típica en ensamblador del Z80 que transforma las coordenadas relativas de un objeto (almacenadas en los registros B=$X$, C=$Y$, A=$Z$) en coordenadas absolutas de pantalla guardadas en DE ($D=Px$, $E=Py$).
Fragmento de código
; =====================================================================
; RUTINA: Transformacion_Coordenadas_Isometricas
; Entrada:
; B = Coordenada X (0 - 255)
; C = Coordenada Y (0 - 255)
; A = Coordenada Z (0 - 255) [Altura]
; Salida:
; D = Pantalla X (Px)
; E = Pantalla Y (Py)
; Clobbers: A, H, L
; =====================================================================
Transformar_XYZ_a_PXPY:
; --- Calcular Px = (X - Y) + OFFSET_X ---
LD A, B ; A = X
SUB C ; A = X - Y
ADD A, 128 ; OFFSET_X = 128 (Centrado horizontal)
LD D, A ; D = Px
; --- Calcular Py = (X + Y) / 2 - Z + OFFSET_Y ---
LD A, B ; A = X
ADD A, C ; A = X + Y
RRA ; Divide por 2 (Rotación a la derecha usando el Carry)
AND %01111111 ; Limpia el bit superior (asegura valor positivo)
LD L, A ; Guarda (X+Y)/2 temporalmente en L
; Restar Z
; Re-obtenemos la altura Z enviada originalmente en el registro E o A
; Asumamos que Z venía guardado en un registro auxiliar como H
LD A, L ; A = (X+Y)/2
SUB H ; A = ((X+Y)/2) - Z
ADD A, 40 ; OFFSET_Y = 40 (Offset inicial de la escena)
LD E, A ; E = Py
RET
Nota de rendimiento: La ejecución de esta rutina toma apenas 45 a 55 ciclos de reloj, lo que permite transformar decenas de coordenadas por frame sin saturar la CPU.
Fin de la Primera Parte
(Dado el nivel de detalle técnico requerido para cubrir la totalidad del código del motor isométrico, la implementación de máscaras de transparencia, la ordenación por profundidades y la simulación del búfer doble en la VRAM, este artículo continúa en la Segunda Parte).
La ilusión de las tres dimensiones: Historia y arquitectura técnica del 3D isométrico en el ZX Spectrum (Parte 2)
En la primera entrega analizamos el contexto histórico y la transformación matemática teórica para proyectar el espacio $X, Y, Z$ en coordenadas de pantalla de 2D. En esta segunda parte entraremos de lleno en la ingeniería del software: la manipulación de píxeles mediante operaciones bit a bit, la resolutiva técnica de máscaras de transparencias, el algoritmo de ordenación por profundidades (Depth Sorting) y las optimizaciones de bajo nivel para mantener un refresco de pantalla estable.
Parte 4: Gestión del Framebuffer y dibuja de Sprites con Máscaras
El principal escollo gráfico en el ZX Spectrum es la falta de un chip secundario de vídeo que soporte sprites por hardware. Todo elemento que se dibuja en pantalla (un bloque de madera, una mesa, un monstruo o el propio protagonista) es grabado directamente sobre la VRAM escribiendo bytes a mano.
Si intentamos copiar los píxeles de un personaje directamente encima del fondo mediante una operación destructiva de sobrescritura (LD), el recuadro rectangular del sprite borrará el escenario que lo rodea. Para solucionar esto, los motores tipo Filmation utilizaban sprites enmascarados.
El concepto del Sprite enmascarado
Cada fotograma de un elemento gráfico se almacena en la ROM/RAM como una pareja de bloques de datos:
- La Máscara (AND Mask): Un mapa de bits donde los píxeles transparentes valen
1y los píxeles sólidos del objeto valen0. - El Píxel (OR Graphic): El dibujo real del sprite, donde los píxeles transparentes valen
0y los píxeles dibujados tienen su valor correspondiente (1para tinta negra/blanca).
Grafico Sprite Máscara (AND) Fondo en VRAM
(0 = Transp) (1 = Transp) (Píxeles previos)
0 0 1 1 1 0 0 1 1 0 0 0 1 1 1 1 1 1 1 1 1
0 1 1 1 1 1 0 1 0 0 0 0 0 1 1 1 1 1 1 1 1
0 0 1 1 1 0 0 1 1 0 0 0 1 1 1 1 1 1 1 1 1
Para dibujar un byte del sprite sobre el fondo sin alterar lo que hay detrás, la CPU realiza una secuencia de operaciones lógicas bit a bit:
$$\text{NuevoByteVRAM} = (\text{ViejoByteVRAM} \text{ AND } \text{Máscara}) \text{ OR } \text{PíxelGraphic}$$
- Paso 1 (
AND): Se combina el byte existente en la VRAM con la Máscara. Los bits marcados con0en la máscara recortan un «agujero negro» en el fondo exactamente con la forma del objeto. Los bits con1conservan los píxeles del fondo. - Paso 2 (
OR): Se fusiona el gráfico real. Como la zona vacía dentro del agujero vale0, los píxeles del gráfico sustituyen el área recortada sin romper los píxeles del fondo que bordean el objeto.
Algoritmo optimizado en Ensamblador Z80
A continuación se presenta el bucle crítico de renderizado de un bloque de sprite de $8 \times 8$ píxeles en la memoria de pantalla.
Fragmento de código
; =====================================================================
; RUTINA: Dibujar_Byte_Sprite_Mascara
; Entrada:
; HL = Dirección de destino en VRAM
; DE = Puntero a los datos del Sprite (Intercalado: Byte Máscara, Byte Gráfico)
; Salida:
; HL = Siguiente línea de VRAM
; DE = Avanza al siguiente par de bytes del Sprite
; =====================================================================
Renderizar_Linea_Sprite:
LD A, (DE) ; Lee Byte de la MÁSCARA
INC DE
LD B, A ; Guarda Máscara en B
LD A, (DE) ; Lee Byte del GRÁFICO
INC DE
LD C, A ; Guarda Gráfico en C
; --- Aplicación del algoritmo lógico ---
LD A, (HL) ; A = Píxeles actuales del fondo en VRAM
AND B ; Aplica MÁSCARA (Abre el hueco del sprite)
OR C ; Aplica GRÁFICO (Inyecta los píxeles del objeto)
LD (HL), A ; Vuelve a escribir el resultado en VRAM
; --- Calcular la dirección de la línea vertical siguiente en VRAM ---
INC H ; En la VRAM del Spectrum, sumar 256 al registro de
; dirección alto (H) avanza exactamente 1 píxel
; verticalmente dentro de un mismo bloque de carácter.
RET
Parte 5: El orden de profundidad (Depth Sorting) y resolución de Oclusiones
El verdadero reto en un entorno isométrico 3D es saber en qué orden se deben dibujar los objetos.
Si un objeto $A$ está situado detrás de un objeto $B$, el motor debe dibujar primero el objeto $A$ y posteriormente el objeto $B$ para que la máscara de $B$ tape parcialmente a $A$. Si dibujáramos $B$ primero y luego $A$, el objeto que está al fondo taparía al objeto que está delante, destruyendo por completo la ilusión de profundidad.
Incorrecto (A tapa a B) Correcto (B tapa a A)
+-------+ +-------+
| [A] | | [A] |
+--+----+ | +--+----+ |
| | | | | [B] | |
| +----+--+ | +----+--+
+-------+ +-------+
La relación de dominancia espacial
En tres dimensiones, la ordenación no se puede resolver mediante un algoritmo simple de ordenación lineal sobre un único eje. Debe evaluarse la posición de las cajas delimitadoras (Bounding Boxes) de los objetos en las 3 dimensiones.
Dados dos objetos en el espacio, Objeto $A$ y Objeto $B$, definidos por sus esquinas de origen y sus dimensiones $(\text{Min}_x, \text{Max}_x, \text{Min}_y, \text{Max}_y, \text{Min}_z, \text{Max}_z)$:
Se afirma que el Objeto A está detrás del Objeto B (y por tanto $A$ debe dibujarse antes que $B$) si se cumple al menos una de las siguientes condiciones espaciales:
- El Objeto $A$ está completamente más lejos en el eje $X$: $$\text{Max}_x(A) \le \text{Min}_x(B)$$
- El Objeto $A$ está completamente más lejos en el eje $Y$: $$\text{Max}_y(A) \le \text{Min}_y(B)$$
- El Objeto $A$ está completamente por debajo en el eje $Z$:$$\text{Max}_z(A) \le \text{Min}_z(B)$$
Si los objetos no se solapan en sus proyecciones de pantalla, su orden de dibujado no es crítico. Sin embargo, cuando sus rectángulos de pantalla intersecan, esta prueba lógica decreta irrefutablemente la jerarquía de prioridad.
Algoritmos de ordenación: De las Listas Enlazadas al Orden Topológico
El motor Filmation de los hermanos Stamper mantenía en memoria una estructura de datos de los elementos presentes en la habitación. Durante cada frame de ciclo de juego:
- Construcción del Grafo de Dependencias: El motor itera sobre cada par de objetos visibles ($i, j$). Compara sus Bounding Boxes. Si el objeto $i$ debe dibujarse antes que el objeto $j$, se añade un arco dirigido en una matriz de dependencias.
- Ordenación Topológica (Topological Sort): Se busca un nodo que no tenga ninguna dependencia previa (un objeto que no tape a nadie o que esté al fondo de todos). Ese objeto se extrae y se coloca en la primera posición de la lista de renderizado.
- Resolución de ciclos (Cyclic Dependencies): En casos raros, tres o más objetos pueden generar una oclusión circular (el Objeto $A$ tapa a $B$, $B$ tapa a $C$, y $C$ tapa a $A$). Los motores de 8 bits no cortaban la geometría por hardware para resolver ciclos por coste de rendimiento; en su lugar, aplicaban algoritmos de decisión basados en la posición de la cámara para forzar la ruptura del bucle, aceptando pequeños fallos visuales menores a cambio de mantener la tasa de fotogramas.
Parte 6: Técnicas de optimización avanzada para mantener los 25 FPS
El refresco de pantalla en los televisores PAL de la época funcionaba a 50 Hz (50 cuadros por segundo interlazados). Dibujar una pantalla entera de $256 \times 192$ píxeles en el Spectrum mediante llamadas repetidas de manipulación de bits requería consumir decenas de miles de ciclos de reloj. Si el procesamiento tomaba más de $69.888$ ciclos (el tiempo de un cuadro de TV PAL en un Spectrum de 48K), el juego bajaba a 25 FPS, 12,5 FPS o provocaba parpadeos horribles (flicker).
Para evitar el colapso de la CPU, los motores isométricos empleaban cuatro trucos maestros de arquitectura de software:
1. Dibujado por Áreas Sucias (Dirty Rectangles)
Los motores nunca redibujaban la pantalla entera en cada frame.
- El fondo (paredes del castillo, suelos, bloques fijos) se renderizaba de forma estática en un búfer secundario en RAM.
- Si el personaje principal caminaba por la sala, el motor calculaba únicamente la región de pantalla que el sprite había ocupado en el frame anterior y la región a la que se acababa de mover.
- Proceso: Se limpiaba el «área sucia» restaurando los píxeles originales del fondo estático almacenado en RAM, y únicamente se volvían a procesar y aplicar las máscaras de los sprites que caían dentro de esa pequeña ventana de píxeles modificados.
2. Tablas de Búsqueda Precalculadas (Look-Up Tables – LUT)
El procesador Z80 pierde demasiados ciclos si calcula multiplicaciones o posiciones de memoria mediante álgebra en tiempo real. Los programadores sacrificaban valiosos bytes de memoria RAM a cambio de velocidad extrema, precalculando tablas de búsqueda:
- Tabla de direcciones VRAM: Una tabla con 192 entradas de 16 bits que guardaba directamente la dirección exacta de memoria de vídeo de inicio de cada línea de la pantalla.
Fragmento de código
; Tabla en RAM/ROM:
Tabla_VRAM_Lineas:
DW $4000, $4100, $4200, $4300, $4400, $4500, $4600, $4700
DW $4020, $4120, $4220, $4320, $4420, $4520, $4620, $4720
; ... Repetido para las 192 líneas de pantalla
Para obtener la dirección de la línea 85, el motor no ejecutaba operaciones lógicas bit a bit; simplemente leía el elemento número 85 de la tabla con una única instrucción de lectura de memoria.
- Tablas de desplazamiento horizontal (Bit Shift Tables): Dado que la VRAM se direcciona por bytes (grupos de 8 píxeles), mover un objeto 1 píxel a la derecha exige desplazar todos los bits de su gráfico de forma horizontal. Dado que el Z80 solo puede rotar bits de 1 en 1 (
SRL A), rotar un sprite de 16 píxeles de ancho una distancia de 7 píxeles consumía decenas de ciclos. Para evitar esto, muchos motores guardaban hasta 8 copias pre-rotadas de los mismos gráficos en la memoria RAM.
3. Código Automodificable (Self-Modifying Code)
Una práctica extremadamente común y venerada en la programación de ensamblador Z80 para juegos de 8 bits era el uso de código automodificable.
En lugar de tener una rutina genérica de dibujado cargada de saltos condicionales (JP, CALL, JR NZ) que evaluaban flags en cada iteración del bucle, el programa escribía instrucciones binarias directamente en el flujo de ejecución de la CPU justo antes de saltar a la rutina.
Fragmento de código
; Ejemplo conceptual de Código Automodificable:
Preparar_Rutina_Render:
LD A, ($FE00) ; Lee el valor del parámetro dinámico del juego
LD (Instruccion_Inyectada + 1), A ; Modifica el byte de operando de la instrucción siguiente
Instruccion_Inyectada:
LD A, 00 ; El "00" será sobreescrito en tiempo de ejecución
ADD A, B
RET
Esta técnica eliminaba los chequeos dentro de los bucles de dibujado, ahorrando de 4 a 10 ciclos de reloj por cada byte renderizado en pantalla.
Si el renderizado de Knight Lore ya fue un logro, lo que hizo Jon Ritman en Head over Heels (1987) con la simulación física fue directamente brujería sobre el Z80.
A diferencia de los motores isométricos contemporáneos —donde las colisiones eran rígidas y el movimiento se reducía a un simple «si hay bloque no pasas»—, Ritman implementó un verdadero motor de físicas discretas tridimensionales. Permitía inercias, aceleraciones, gravedad variable, plataformas móviles, empuje de objetos y la posibilidad histórica de que un personaje se subiera encima de otro para formar una entidad combinada con físicas unificadas.
Así resolvió Ritman la física y la detección de colisiones a bajo nivel en los 48 KB del Spectrum.
1. El espacio lógico: Cajas AABB y subpíxeles
Para evitar el coste prohibitivo de comprobar colisiones contra los píxeles reales (Pixel-Perfect Collision), Ritman basó su motor en Bounding Boxes de tipo AABB (Axis-Aligned Bounding Boxes).
Cada entidad del mundo (plataformas, enemigos, bloques empujables y los propios Head y Heels) se definía mediante 6 variables de 8 bits en la RAM:
X_min,X_max(Anchura)Y_min,Y_max(Profundidad)Z_min,Z_max(Altura)
Subpíxeles y precisión de movimiento
Si intentas calcular aceleraciones e inercias usando solo números enteros de píxeles, los saltos se ven a tirones o demasiado rápidos. Ritman implementó posiciones y velocidades de subpíxel:
Estructura de un objeto dinámico en RAM (16 bits por eje):
+-------------------------+-------------------------+
| Parte Entera (Píxel) | Fracción (Subpíxel) | [16 bits]
+-------------------------+-------------------------+
En cada ciclo de juego (a 25 FPS), el procesador sumaba el vector de velocidad (VelX, VelY, VelZ) a la posición. Al usar acumuladores de 16 bits (HL = Pos + Vel), la parte entera indicaba el movimiento real en pantalla, mientras que los subpíxeles absorbían la inercia suave.
2. La detección de colisiones: Algoritmo de separación por ejes (AABB 3D)
Comprobar si dos objetos ocupan el mismo espacio en 3D requiere evaluar si sus cajas se solapan en los tres ejes al mismo tiempo.
Para que dos objetos $A$ y $B$ colisionen, debe cumplirse la condición lógica:
$$\text{Overlap} = (A.x_{1} < B.x_{2} \land A.x_{2} > B.x_{1}) \land (A.y_{1} < B.y_{2} \land A.y_{2} > B.y_{1}) \land (A.z_{1} < B.z_{2} \land A.z_{2} > B.z_{1})$$
En el Z80, comprobar esto objeto por objeto contra toda la habitación ralentizaría el juego al instante. Ritman optimizó la rutina mediante un proceso de tres pasos:
1.1. Filtrado rápido por celda (Grid Broad-Phase):Economía de CPU.
La habitación se dividía en un mapa de rejilla ($8 \times 8 \times 8$ unidades). La CPU solo comprobaba colisiones complejas contra aquellos objetos o bloques estáticos que compartieran la misma celda lógica que el personaje.
2.2. Detección de solapamiento en AABB:Evaluación exacta.
Se ejecutaba la rutina en ensamblador comparando los límites X, Y y Z. Si uno solo de los ejes no se solapaba, la rutina salía inmediatamente mediante un salto condicional (RET NC), ahorrando decenas de ciclos.
3.3. Resolución del vector de respuesta:Físicas de contacto.
Si la colisión era inevitable, el motor deshacía el movimiento únicamente en el eje responsable del choque y anulaba la velocidad en esa dirección.
Rutina crítica de comprobación de solapamiento en Z80
A continuación, una simplificación de la rutina en ensamblador que utilizaba Ritman para verificar si dos rangos de un mismo eje se solapan:
Fragmento de código
; =====================================================================
; RUTINA: Comprobar_Solapamiento_Eje
; Entrada:
; A = A_min, B = A_max (Objeto A)
; C = B_min, D = B_max (Objeto B)
; Salida:
; Carry Flag (C) = 1 si HAY solapamiento, 0 si NO lo hay.
; =====================================================================
Solapamiento_Eje:
; Test 1: ¿Esta A_max <= B_min? (Si es así, A está del todo antes que B)
CP D ; Compara A_min con B_max
JR NC, Sin_Solape ; Si A_min >= B_max, no hay contacto
LD A, B ; A = A_max
CP C ; Compara A_max con B_min
JR Z, Sin_Solape
JR C, Sin_Solape ; Si A_max <= B_min, no hay contacto
SCF ; Set Carry = 1 (¡Hay solapamiento!)
RET
Sin_Solape:
OR A ; Clear Carry = 0
RET
3. Simulación de gravedad, saltos y masa diferencial
Una de las joyas del motor de Jon Ritman fue dar una personalidad física totalmente distinta a los dos protagonistas:
| Parámetro Físico | Head | Heels | Head + Heels (Combinados) |
| Aceleración horizontal | Baja (Pies lentos) | Alta (Corre rápido) | Alta (Usa las patas de Heels) |
| Velocidad máxima ($XY$) | 1.5 píxeles/frame | 3.0 píxeles/frame | 3.0 píxeles/frame |
| Fuerza de Salto ($\Delta VelZ$) | Muy alta (Planea) | Muy baja (Salto corto) | Muy alta (Capacidad de Head) |
| Gravedad efectiva | Reducida (Caída lenta) | Estándar (Caída rápida) | Estándar |
| Caja de Colisión ($Z$) | 8 píxeles de alto | 8 píxeles de alto | 16 píxeles de alto |
La mecánica del salto y la gravedad
La gravedad se aplicaba en cada frame como una resta constante a la velocidad vertical:
$$VelZ_{nuevo} = VelZ_{actual} – \text{Gravedad}$$
- Head: Al pulsar el botón de salto, se inyectaba un valor alto a $VelZ$. Además, si el jugador mantenía pulsado el botón mientras caía, la constante de
Gravedadse reducía a la mitad en la rutina, generando el famoso efecto de planeo. - Heels: Al no tener capacidad de planeo, su constante de
Gravedadera fija y alta, ofreciendo un control de plataformas mucho más seco y rápido.
4. El hito técnico: Apilamiento de objetos y la unión «Head + Heels»
Permitir que un personaje se suba encima de otro (o sobre un bloque móvil) suele ser la pesadilla de cualquier desarrollador de 8 bits. Ritman lo resolvió introduciendo la jerarquía de soporte (Support Graph).
+-------------------+
| HEAD (A) | <-- Objeto Transportado
+-------------------+
| HEELS / BLOQUE | <-- Objeto Soporte
+-------------------+
¿Cómo detectaba el motor que un personaje estaba «de pie» sobre otro?
- En el eje $Z$, el motor comprobaba si
Z_min(Head) == Z_max(Heels). - Si los ejes $X$ e $Y$ de Head caían dentro de los márgenes de Heels, Head entraba en estado
ON_TOP(Apoyado). - Herencia de movimiento: Cuando Heels se movía en el plano $XY$, el motor leía la lista de objetos apoyados sobre él y aplicaba exactamente el mismo vector de desplazamiento $(\Delta X, \Delta Y)$ a Head antes de calcular sus propias colisiones.
Esto permitía no solo que Heels llevara a Head a cuestas, sino que pudieras apilar bloques empujables sobre plataformas móviles para resolver los complejos puzles del juego.
La fusión de personajes
Cuando Head saltaba encima de Heels, el jugador podía pulsar una tecla para «unirlos». Técnicamente, el motor desactivaba la entidad Head de la lista de físicas activas, redimensionaba el Bounding Box de Heels en el eje $Z$ (de 8 a 16 píxeles de altura) y cambiaba los punteros de los sprites a la criatura combinada.
Un truco de memoria brillante: Al fusionarse, el motor liberaba el tiempo de procesado que consumía la física de Head de forma independiente, lo que permitía al Spectrum mantener la fluidez de 25 FPS sin resentirse a pesar de tener un sprite más grande en pantalla.
Resumen del ciclo de físicas por frame
Cada $1/25$ de segundo, el Z80 ejecutaba este bucle estricto en ensamblador:
[Inicio Frame]
│
├── 1. Leer Teclado/Joystick -> Actualizar vectores de velocidad deseada
├── 2. Aplicar Gravedad a objetos en el aire (VelZ = VelZ - G)
├── 3. Mover objetos en Eje Z -> Detectar colisiones suelo/techo -> Resolver
├── 4. Mover objetos en Eje X -> Detectar colisiones paredes -> Resolver
├── 5. Mover objetos en Eje Y -> Detectar colisiones paredes -> Resolver
├── 6. Actualizar Jerarquía de Soporte (¿Quién está encima de quién?)
└── 7. Pasar lista resultante al motor de Render/Depth Sorting
Esta separación de la física en ejes individuales ($Z$, luego $X$, luego $Y$) eliminó casi por completo los problemas de traspasar paredes (clipping) y convirtió a Head over Heels en la obra maestra indiscutible del 3D isométrico en los 8 bits.
Conclusión: El legado imperecedero de una era dorada
El ascenso y perfeccionamiento de la perspectiva isométrica en el ZX Spectrum se mantiene como uno de los capítulos más deslumbrantes de la historia de la informática.
Programadores sin acceso a aceleradoras gráficas, sin motores como Unity o Unreal, y limitados por una máquina con menos potencia de cálculo que la llave de un coche moderno, lograron levantar estructuras tridimensionales llenas de dinamismo, coherencia física y belleza estética.
El motor Filmation y sus célebres derivados demostraron que la innovación técnica no depende siempre de la fuerza bruta del procesador, sino de la elegancia matemática, el ingenio en el diseño de algoritmos de optimización de datos y el dominio profundo de la arquitectura interna del hardware. Las lecciones de optimización de memoria, oclusión y ordenación de profundidades desarrolladas en el modesto ordenador de Sinclair sentaron los cimientos de la arquitectura de motores que, décadas más tarde, darían vida a las tres dimensiones de la era moderna del videojuego.


