Un juego hecho de mods en Godot 4: encender, apagar y borrar carpetas sin romper nada
Gotic son 31 mods que se encienden y se apagan en un menú y se quitan borrando su carpeta. Que aguantase el juego fue fácil; que aguantasen las pruebas, no tanto.

En Gotic no hay «el juego» y luego los extras. Hay una isla, un personaje que anda, nada y salta, y un inventario. Todo lo demás es un mod: los pueblos, la escoba, la pesca, la granja, el día y la noche, la cámara de fotos. Hoy son 31. En el menú inicial, MODS enseña cada uno con su interruptor, su descripción y lo que necesita, también en mitad de la partida.
Un mod también se quita a lo bruto: borrando su carpeta. El juego tiene que seguir arrancando y la partida no puede perder lo que ese mod había conseguido.
Cómo está hecho
Cada carpeta de modulos/ con un modulo.gd es un mod, y se descubre al arrancar:
static func available_modules(dir := "res://modulos") -> Array[String]:
var result: Array[String] = []
if not DirAccess.dir_exists_absolute(dir):
return result
for folder in DirAccess.get_directories_at(dir):
if ResourceLoader.exists("%s/%s/modulo.gd" % [dir, folder]):
result.append(folder)
result.sort()
return result
Pregunta a ResourceLoader y no a FileAccess porque dentro del APK los scripts van compilados (un .gdc con su .remap) y FileAccess no vería el modulo.gd.
Un mod es un nodo con cuatro funciones: setup(world), save_data(), load_data(data) y teardown(). No toca main.gd: usa enganches del juego (teclas, botones táctiles, marcas del mapa, USAR, bonificaciones, inventario). Las dependencias van como texto, REQUIRES = ["aldea"], nunca con un preload de otra carpeta: así quitar una no provoca errores de análisis en las demás.
Los mods tampoco se conocen entre sí: la cocina pregunta a cada mod encendido si tiene ingredients(), y así la pesca y la granja le sirven sin saber que existe. Al apagar uno, lo que tenía guardado se conserva aparte, aunque falte su carpeta: si vuelve, vuelven sus récords.

Lo que salió mal
Liberar el nodo no basta. La escoba va en la espalda del jugador, la corona en su cabeza y los botones en el HUD: no cuelgan del nodo del mod y no se van con él. teardown() tiene que retirarlo todo antes de liberar nada.
Modular en el juego no era modular en las pruebas. Quitar una carpeta funcionaba al jugar, pero tests/run.sh fallaba: las pruebas comunes nombraban mods concretos (main.modules["carrera"]) y la carrera usaba la escoba como «algo que vuela». Lo arreglé:
- Las pruebas comunes recorren los mods que haya sin nombrar ninguno. Una enciende y apaga cada mod y compara una «huella» del mundo (interacciones, marcas, botones, claros sin árboles, piezas colgadas del jugador y del HUD): reactivar no puede duplicar nada y apagar no puede dejar rastro. Al quitar a propósito una limpieza de la escoba, la prueba lo señaló.
- Lo propio de cada mod va en su carpeta (
pruebas.gd). Si le falta una dependencia, sus pruebas se omiten con un aviso. - Las dependencias se prueban con tres mods de mentira.
- Y se comprobó de verdad, moviendo carpetas fuera: con los siete mods de entonces, 583 comprobaciones; sin ninguno, 331; sin cada uno, también
PRUEBAS OK.
Cómo trabajan los agentes
Casi todo el código lo escriben agentes de IA (Claude) con normas escritas en el repositorio: un plan antes de empezar (PLAN.md: tareas, decisiones y lo que no se hará), un commit por tarea con el porqué, y tests/run.sh acabando en PRUEBAS OK antes de cada commit. Por cada fallo, una prueba nueva que hay que ver fallar quitando el arreglo. Cuando una de esas mutaciones sobrevive, falta una prueba… o sobra código: quitar una línea de new_game() no rompió nada porque era redundante, y se borró.
Las carpetas también reparten el trabajo. Cuando dos sesiones de Claude trabajaron a la vez, compartían la carpeta temporal y los datos de usuario de Godot, y las pruebas de una pisaban las partidas de prueba de la otra. Ahora cada una lleva su worktree y su XDG_DATA_HOME, se reparten por carpetas de mods y hacen git add con rutas explícitas.
Lo que falta por comprobar
En el móvil (un Samsung S24 Ultra) el menú inicial va a 60 FPS, y MODS, «atrás» y JUGAR respondieron a toques simulados con adb. Pero no he encendido ni apagado ningún mod en el teléfono, porque habría cambiado la partida de verdad, ni lo he probado con los dedos.