Ir al contenido
YFYass Fuentes

Un motor que no sabe dibujar

Editar en GitHub
Actualizado:
· 10 min

Banner del devlog de Orbafall Legions, entrega 2: decide, aplica, recuerda. Y no tiene reloj.

Durante las dos primeras semanas Orbafall Legions no tuvo ni una sola pantalla. Se jugaba, si a eso se le puede llamar jugar, desde un cliente de texto: escribías órdenes en una consola y el motor te contestaba con lo que había pasado. Yo preguntaba cada pocos días cuándo tendría una demo, y cada pocos días me contestaba a mí mismo que prefería esperar un poco más. Esperaba por una razón: antes de dibujar nada quería un motor de reglas que no supiera dibujar, que no tuviera reloj y que guardase la partida como una lista de eventos. Una pequeña catedral arquitectónica, lo sé, pero de momento es la manía que más me ha devuelto.

1 - Qué es un decisor puro

¿Qué es un motor de reglas puro? Grosso modo, una función. Recibe el estado de la partida y una orden («mueve esta ficha aquí», «compra esta unidad», «termina el turno») y devuelve una de dos cosas: una lista de eventos (lo que esa orden provoca) o un rechazo con su motivo (por qué no se puede). Otra función aplica cada evento al estado y produce el estado siguiente. En el código se llaman decide y evolve, y viven en una carpeta sin un solo nodoLa pieza básica con la que se construye todo en Godot: una escena es un árbol de nodos (imágenes, botones, cámaras, sonidos). El motor de Orbafall no usa ninguno. de Godot, sin acceso al reloj y sin un generador de azar global.

Diagrama del motor: la interfaz manda una orden, decide devuelve eventos o un rechazo, los eventos se sellan y van al registro, evolve los aplica al estado, y la interfaz solo ve una vista filtrada por asiento.

Imagen 1. El motor como decisor puro: la interfaz manda órdenes, recibe eventos y nunca ve el estado completo.

De ahí salen unas cuantas consecuencias, y las que a mí me importan son estas:

Hay guardianes automáticos que vigilan todo esto en cada fusiónIntegrar en la rama principal los cambios preparados en una rama aparte. En inglés, merge.: uno falla si aparece un nodo de Godot en el núcleo, otro si una cifra de regla se escribe en el código en lugar de en los datos, y así hasta quince desde el primer día. Uno de ellos, el que falla ante cualquier rastro del juego original, es la segunda condición de la entrega 1 convertida en comprobación.

2 - La partida guardada es el registro

Cada evento que decide produce se sella con un identificador, un número de secuencia y una marca de tiempo, inyectados desde fuera (el motor no tiene reloj, recordad), y se añade a un registro. Ese registro es la partida guardada: un fichero .orbsave con la lista de eventos, instantáneas del estado por ronda, al cierre de cada combate y cada quinientos eventos para no tener que rejugar desde el principio, y unos convertidores que elevan los eventos de versiones antiguas a la actual cuando cambia el formato.

De ahí salió, sin haberlo buscado del todo, un verificador. Dado un guardado, rejuega el registro entero desde el primer evento, vuelve a pasar cada orden por decide y compara: o la partida es íntegra, o diverge en el evento número tal, o no es verificable. El mismo verificador mide, de paso, la plausibilidad de las entradas humanas de la arena [ritmo, tiempo de reacción, patrones repetidos], y solo marca la partida como sospechosa, nunca la rechaza; cuando lo calibré con mis propios guardados resultó que marcaba como sospechoso al propio autor (es decir, a mí), así que ajustamos los umbrales con partidas reales. Es la primera capa de lo que algún día será la defensa contra trampas en línea, y aquí solo cuento el principio, sin entrar en los vectores concretos.

3 - Una fase en un día, otra en ocho

El 1 de octubre se abrió y se cerró la primera fase, la del andamiaje, de las seis menos cuarto de la mañana a las siete de la tarde. Ese día quedaron en pie el proyecto de Godot con una escena mínima de arranque y su integración continuaUn servidor que, con cada cambio del código, lo compila y le pasa las pruebas sin que nadie se lo pida. Si algo falla, el cambio no entra. En inglés, CI., que corre los mismos trabajos en Windows y en un contenedorProceso empaquetado con sus dependencias que corre aislado sobre el kernel del anfitrión, sin virtualizar una máquina entera. En el homelab los servicios van en contenedores con Docker. Linux desde un clon limpioUna copia recién descargada del repositorio, sin ficheros generados ni ajustes locales, para que las pruebas no dependan de lo que haya en mi máquina.; el contrato del paquete de datos, un JSON SchemaDocumento que describe qué campos puede tener un fichero JSON y de qué tipo, para comprobarlo automáticamente. para las reglas, la geometría, los textos y la procedencia, con un validador fuera de Godot y una carga tipada dentro, y las dos partes calculando el mismo hashHuella de longitud fija calculada a partir de unos datos. Si cambia un solo bit de los datos, cambia la huella; dos huellas iguales dicen que los datos son los mismos.; dos paquetes, uno sintético para probar el motor y el del producto; y los quince guardianes. La licencia salió con un marcador donde debía ir el nombre del titular [que no tenía decidido], y el guardián correspondiente lo informa como aviso en lugar de fallar.

La segunda fase, el motor del tablero, se abrió esa noche y se cerró el 9 de octubre, con veinticinco fusiones de plan en ocho días. Al cerrar, el motor decidía y evolucionaba la partida desde la creación hasta el fin por la regla de victoria, salvo el desarrollo del combate: compra, colocación, anillos y orbe, turno con mantenimiento, ciclo de día, movimiento con visión, órdenes automáticas, trampas, poderes y muertes en cascada. El 5 de octubre entró el cliente de texto con el que ya se podía recorrer una partida hasta el primer combate, y al verificar la rama principal el día del cierre había 1310 casos de prueba y cero fallos.

4 - Medir en la Steam Deck

La 1.0 saldrá para Windows y Linux con la Steam Deck como objetivo explícito, y eso lo decidí el 3 de octubre, antes de tener nada que enseñar en ella. Así que el 8 de octubre, cuando las medidas de rendimiento dejaron tres umbrales por encima de lo previsto [reducir diez mil eventos, reducir cien mil y abrir un guardado], lo que importaba era cuánto tardaba cada cosa en la Deck.

Se midió por SSHSecure Shell. Protocolo para abrir una terminal cifrada en otra máquina a través de la red. Así se administra un servidor sin pantalla ni teclado., con el binario oficial de Godot para Linux, y el factor medio frente al escritorio salió en 2,06: la Deck va, en los bancos de pruebas de CPU, unas dos veces más lenta que mi máquina. Con ese factor se fijaron los umbrales. Probé también si el origen de la energía movía el resultado, con batería y enchufada, y no: 398,7 y 402,9 milisegundos para la misma reducción. Tras dos planes de optimización, en el escritorio reconstruir el estado desde diez mil eventos bajó de 688 a 193 milisegundos, y abrir un guardado de 740 a 25. El 14 de octubre, al cerrar la fase del combate, se repitió la sesión en la Deck: un tick de la arena cuesta 0,048 milisegundos con el paquete sintético y 0,107 con el del producto, frente a un umbral de 1, y esas son desde entonces las cifras de referencia de la Deck.

5 - Siete veces más rápido sin cambiar un resultado

El 23 de octubre, con la IA ya jugando turnos enteros, medir el turno de la IA reveló que el coste estaba en el motor: cada marcha recalculaba caminos largos y enumerar las habilidades disponibles crecía con el cuadrado del número de piezas. El arreglo, por lo que entendí, es de libro: cortar la búsqueda larga sin cambiar sus resultados, hacer lineal la enumeración y guardar en cachéGuardar el resultado de un cálculo para no repetirlo mientras sus entradas no cambien. las lecturas de visión y alcance por versión del estado, de modo que el estado lleva un número de versión que sube con cada cambio y quien escriba fuera del camino normal tiene que avisar.

Tres pares de barras: reconstruir diez mil eventos de 688 a 193 ms, abrir un guardado de 740 a 25 ms, y el turno de la IA en el mapa más grande de 2361 a 334 ms.

Imagen 2. Antes y después de las tres medidas, con los mismos resultados bit a bit.

Turno de la IA (escritorio, medianaEl valor que deja la mitad de las medidas por debajo. Un valor disparatado la mueve mucho menos que a la media. / máximo)AntesDespués
Mapa más grande, 4 asientos2361 / 5597 ms334 / 567 ms
Mapa mediano del producto1548 / 1722 ms225 / 376 ms

Tabla 1. El turno de la IA antes y después del plan de rendimiento del 23 de octubre.

Lo que me deja tranquilo es que no cambió un solo resultado: el corpus de partidas grabadas y las partidas de IA contra IA dan los mismos hashes que antes, y hay una comprobación en sombra que recalcula todo sin caché y compara. Creo que es ahí donde más me ha compensado el motor puro, porque se le pueden tocar las tripas y comprobar después que sigue decidiendo lo mismo.

Y un criterio que vale para todo el proyecto, fijado tres días antes: quiero que el juego vaya bien, pero no quiero que obsesionarme por la optimización estrangule el desarrollo, las opciones ni las capacidades. Los presupuestos de rendimiento avisan cuando algo se pasa y nunca bloquean una funcionalidad. Antes de decidir nada se mide: la lectura del turno en el mapa real del producto costaba 33 milisegundos en el PC y 68 en la Deck, con 200 de presupuesto, así que la mejora se aplazó con un disparador medible y se hizo cuando el disparador saltó.

6 - Palabras finales

Las dos semanas sin pantallas se me hicieron largas, lo reconozco, y entiendo a quien prefiera empezar por un tablero que se vea; a mí la manía me ha devuelto más de lo que esperaba, y una cosa que no estaba en el plan: como el combate de la arena es igual de determinista, se pudo portar entero a la tarjeta gráfica y comprobar que coincide con el juego duelo a duelo. Eso es la entrega 12, y es la que más me ha divertido. No sé si habría llegado hasta ahí empezando por dibujar un tablero; sospecho que no, pero es la primera vez que hago esto y a lo mejor me equivoco. Si alguien usa event sourcingGuardar el historial de todo lo que ha pasado, evento a evento, y reconstruir el estado actual rejugándolo, en lugar de guardar solo el estado. en sus juegos me interesa mucho una cosa: cómo gestionáis el versionado de los eventos en los guardados cuando el formato cambia cada semana. Yo tengo convertidores y una regla de pragmatismo [mientras no haya nada publicado, los esquemas se corrigen en su sitio y se resellan los casos], y sospecho que no es la última palabra.

6.1 - Para el futuro

La siguiente entrega es la otra mitad del motor: la arena. Diecisiete pasos por segundo, aritmética entera, un guardián que falla si asoma un número en coma flotante, una muerte súbita por fases y un defensor que ganaba demasiado solo por salir del mismo lado.

Orbafall Legions es un proyecto personal no oficial, no afiliado con Silicon Knights, SSI, Mindscape ni Ubisoft.


Versión 1.0R1 (02/11/2026)

Changelog