23 de oct de 20261.846 palabras9 min de lectura
Cómo construí Kessler Drift con un equipo de agentes de IA
La historia honesta de cómo hice un roguelite tipo Zuma en pixel art con agentes de IA: el equipo, el registro de decisiones, las ideas que mataron los números, lo que encontró QA y lo que faltó.

Introducción
Kessler Drift es un shooter de bolitas en pixel art retro con partidas roguelite. Si jugaste Zuma o Luxor, la base te va a resultar conocida: una cadena de bolitas avanza por un riel hacia un agujero y vos disparás bolitas para armar grupos de tres. Acá las bolitas son chatarra espacial que cae hacia un agujero negro llamado the Well, el que dispara es un remolcador de chatarra y cada partida es un contrato: limpiás etapas para cumplir una Quota, elegís mejoras, pasás por la Company Store y al final del sector peleás contra un jefe llamado el Magpie.
Yo no escribí el código, no dibujé los sprites ni compuse la música. Lo hizo un equipo de agentes de IA, con Claude como project manager y product owner, y yo como stakeholder tomando las decisiones grandes. En este artículo te cuento cómo fue de verdad: cómo se armó el equipo, cómo una idea vaga terminó en un build jugable, qué ideas mataron los números, qué encontró QA y dónde se quedó corto.
No es un tutorial. Es la historia honesta de un proyecto, contada desde su registro de decisiones y su historial de git.

Armando el equipo
Mi primer mensaje fue corto: sé el PM de un roguelite tipo Zuma, armá un equipo y trabajá por tandas. Las referencias eran Zuma, Luxor, Peglin, Bills Must Be Paid y Hades, y el estilo, pixel art retro. Claude volvió con un charter y cuatro roles, cada uno con una definición corta de agente en el repo, dentro de .claude/agents/: UX para dirección y descubrimiento, UI para arte y pantallas, Dev para el código y QA para testear. Esta es la de QA, recortada:
---
name: qa
description: QA for the Zuma roguelite — test plans, bug reports, balance checks, automated smoke tests. Use after each build or milestone.
---
You are QA on a retro pixel-art Zuma-like roguelite. Before any work, read
docs/00-team/CHARTER.md, DECISIONS.md, the GDD and the latest CHANGELOG. [...]
You do not fix bugs; you report them to Dev and the PO.Lo importante es la primera instrucción. Los agentes arrancan de cero en cada tanda y no recuerdan nada, así que la carpeta de docs es la memoria del equipo: cada agente lee el charter y el registro de decisiones antes de tocar algo, y deja lo que produce como documento. Después sumé dos roles más, un Animator y un Sound designer, cuando el juego ya funcionaba pero se sentía plano.
El registro de decisiones terminó siendo el archivo más útil del proyecto. Cada decisión queda anotada, con quién la tomó:
| Date | Decision | Rationale | Owner |
|------------|-----------------------------------------------------------|--------------------------------------------|-------|
| 2026-10-15 | Setting: B — Kessler Drift (space junk → black hole) | Avoids cultural-research overhead | Juan |
| 2026-10-16 | Salvage Manifests KILLED (Triple 73–74% of score > 70% cap) | Paper tests, paper-test-results.md | Juan |
| 2026-10-16 | Parallel CI runs must use separate PORT / CI_OUT | False failure in batch 4 | PO |Las filas con Juan son mías; las que dicen PO son decisiones que Claude tomó solo, dentro del alcance que yo había aprobado. Esa división me sacó de los detalles sin perder el control del rumbo.
Descubrimiento y concepto
La primera tanda fue de investigación. UX analizó los juegos de referencia y las tendencias recientes de roguelites, UI propuso una dirección de arte (640x360, bolitas de 16 px, una paleta de 32 colores) y tres ambientaciones, y Dev evaluó el motor. Las propuestas eran una cinta de sushi con yokais, chatarra espacial cayendo a un agujero negro y un rosario de folk horror. El equipo recomendaba la del sushi; yo elegí la espacial porque no necesitaba investigación cultural.

La plataforma cambió mientras el descubrimiento todavía estaba en marcha. Primero dije Godot para salir en Steam y después pasé a web primero, pensando en builds nativos más adelante. Dev comparó el export web de Godot con Phaser y condicionó la elección a una prueba web de una semana: un presupuesto de descarga de 15 MB y cinco segundos hasta poder jugar en desktop. El greybox dio 10,3 MB comprimido con gzip y 2,8 segundos en localhost, así que Godot se quedó.

Después UX convirtió la investigación en un concepto: la Quota es la barra de progreso, las reacciones en cadena (cascadas) multiplican el puntaje y perder significa quedarte sin puntos de Hull, no morir de un golpe. Eso último lo decidí yo.
Construyendo el vertical slice
El objetivo era un vertical slice: una partida completa, de principio a fin. Llevó cinco tandas, cada una cerrando con una pasada de QA y un build que yo podía jugar: rieles cargados desde datos, después la partida en sí (mapa, recompensas, Store, Dry Dock, eventos al azar, autoguardado) y al final la pelea con el Magpie y sus tres fases. La versión 0.6.1 fue la primera partida completa.


El balance lo hicieron bots, no sensaciones. Dev escribió un bot headless que juega etapas con niveles de habilidad novato, promedio y experto, y UX lo usó para ajustar. La primera pasada jugó unas 14.000 etapas simuladas y encontró el juego demasiado fácil: el bot promedio cumplía cada Quota en 21 a 55 segundos contra un objetivo de 55 a 100. El jefe pasó por el mismo proceso hasta que el bot promedio ganaba alrededor del 62% de las peleas.

Ideas que mataron los números
Lo que más me gustó del proceso fue lo barato que era descartar ideas. Le pedí al equipo que explorara deck-building para las bolitas que disparás. Antes de escribir código del juego, UX armó una simulación en papel, y mostró que dejar que tu mazo decida los colores subía los tiros sin objetivo del 4,7% al 16,8%. Así que el deck-building pasó a ser de efectos, no de colores.
Después los dos mejores candidatos pasaron por tests con bots, con criterios de aprobación y descarte escritos de antemano. Salvage Manifests, tipos de combo con nombre como las manos de póker de Balatro, murió porque los grupos simples de tres eran el 73 a 74% del puntaje, así que los combos casi no importaban. Overtime Push, una opción de riesgo y recompensa después de cumplir la Quota, murió porque en el mejor caso sumaba 7% de Scrip contra una vara del 15%. Cada test costó una fracción de día en lugar de un prototipo.

El pipeline de arte
No hay un solo pixel dibujado a mano en el juego. Cada sprite sale de un script de Python que lo dibuja pixel por pixel sobre la paleta fija, y un verificador hace fallar la tanda si algún PNG usa un color que no está en ella. Este es el comienzo de la función que arma una bolita de chatarra de 16 px:
def scrap_index_map(symbol, frame, size=16, radius=7.0):
"""Return a size x size array of LUT names (or None) for one scrap frame."""
c = size / 2.0
grid = [[None] * size for _ in range(size)]
inside = np.zeros((size, size), bool)
for y in range(size):
for x in range(size):
if math.hypot(x + 0.5 - c, y + 0.5 - c) <= radius:
inside[y, x] = True
# ...outline pixels, shading bands and the colour-blind symbol follow

Como el arte es código, cambiarlo es barato: UI regenera una hoja entera cuando cambia una regla. UI también corrió un chequeo de daltonismo sobre los seis colores de chatarra y le dibujó un símbolo a cada bolita, para que se distingan aunque no veas bien los colores. El Animator se sumó después con sus propios generadores para frames de squash, chispas, transiciones y avisos del jefe, y con una especificación de movimiento con tiempos en frames que Dev implementó.

Audio
El Sound designer hizo cada sonido con un pequeño kit de síntesis escrito en Python y numpy, así que no hay temas de licencias. El estilo es chip-tune con un golpe metálico y crujiente de chatarra en cada impacto. Las reacciones en cadena suben por una escala pentatónica de Re menor, un archivo por paso, y el manifest le dice a Dev exactamente cómo reproducir cada evento:
"cascade_step": {
"files": ["sfx/cascade_step_02.ogg", "...", "sfx/cascade_step_11.ogg"],
"pick": "by_index",
"bus": "SFX",
"volume_db": -6,
"pitch": [1.0, 1.0],
"hook": "on_match with depth >= 2",
"notes": "Never randomise pitch: it is a D minor pentatonic ladder (D5 ... C7)."
}El set final son 90 archivos (efectos, tres loops de música y dos capas de peligro) en 2,41 MB, por debajo de un presupuesto de 3 MB. El detalle: el sandbox donde corrían los agentes no tiene parlantes. El audio se revisó con formas de onda, espectrogramas y medidas de volumen, y yo fui la primera persona en escuchar algo. Sonaba bien, por suerte.

Los ciclos de QA
QA nunca arregló nada; reportaba bugs con seed, pasos y severidad, y Dev los arreglaba en la tanda siguiente. Cada tanda cerraba con un comando que corre los tests unitarios, reproduce partidas grabadas, exporta el build web y lo maneja en un navegador:
PORT=8862 CI_OUT=/tmp/ci-dev-2 game/tools/ci.shAlgunos hallazgos que me convencieron de que el ciclo valía la pena:
- Un bug de puntaje donde los multiplicadores de las cascadas daban un paso de más. Los tests pasaban igual, porque se habían escrito contra los números equivocados.
- Cargar una repetición desde un link congelaba la pestaña del navegador, una regresión que había metido la integración del arte.
- Recargar la página en la pantalla de derrota revivía una partida muerta.
- Una falla falsa del CI porque dos agentes corrieron los tests en el mismo puerto a la vez. El arreglo quedó como regla en el registro de decisiones.
El juego es determinístico: la misma seed y los mismos inputs dan el mismo hash de estado. Eso hizo segura la etapa de pulido, porque cada cambio de animación tenía que dejar las repeticiones idénticas bit a bit, lo que probaba que solo cambiaba cómo se ve el juego.

Publicando el build web
Cada build salió como un export web de Godot en un zip. El export single-threaded no necesita headers especiales en el servidor, así que cualquier servidor estático sirve:
cd kessler-drift-0.7.2
python3 -m http.server 8000Un par de veces pedí un link publicado, desde una compu sin el repo. No salió: el sandbox de los agentes no acepta conexiones de afuera, y el archivo del motor era demasiado grande para el hosting de páginas que había disponible, así que me quedé con el zip. La versión 0.7.2 es la que está embebida arriba.
Qué funcionó y qué no
Lo que funcionó:
- Los docs como memoria. Un charter, un registro de decisiones y una nota de traspaso alcanzaron para que agentes nuevos siguieran donde habían quedado los anteriores.
- Criterios de aprobación y descarte escritos antes de cada test. Convirtieron "me gusta esta idea" en un número que podía decir que no.
- Roles separados que se revisan entre sí. El Animator y el Sound designer revisaron cómo Dev integró sus especificaciones y encontraron problemas reales.
- Que yo tomara solo las decisiones grandes: ambientación, plataforma, condición de derrota, qué explorar después.
Lo que no:
- El sandbox no tiene GPU ni parlantes, así que los frames por segundo, la sensación y el sonido necesitan a una persona.
- El contenedor en la nube se reinició varias veces en medio de una tanda. El trabajo sobrevivió porque estaba commiteado en una rama de trabajo en curso, pero cada reinicio costó tiempo.
- Los agentes escribían largo. Casi todos los documentos se pasaron de su objetivo de palabras.
- El slice nunca pasó formalmente sus criterios de salida. Firefox, celulares reales y playtests con más gente siguen pendientes.

¡Eso es todo! Lo podés jugar en pantalla completa en /es/games/kessler-drift. Lo que sigue son los arreglos pendientes, decidir si entran dos Modules nuevos que pasaron la segunda ronda de tests y un playtest de verdad con más gente que yo.

