demo técnica · software · legado
Reconstruir la documentación de un software leyendo su código, con examen incluido
Le dimos a un sistema el código fuente de DOOM, cambiamos cuatro cifras a sus espaldas y le pedimos que documentara cómo funciona. Cazó las cuatro: reportó el valor cambiado, con la línea exacta como prueba, mientras la documentación que humanos han escrito de DOOM durante treinta años sigue diciendo lo viejo.
Y para que no te fíes de nuestra palabra, el juego corre aquí mismo, en tu navegador, compilado desde esa misma copia cambiada. Abres una puerta y un cronómetro mide cuánto espera antes de cerrarse: 3 segundos, los que dijo la doc, no los 4,3 de siempre. La prueba deja de leerse y se ejecuta.
Elige un sistema del que dependa tu empresa: el ERP que se instaló en 1998, el programa que calcula las nóminas, el que decide qué pedidos pasan. Funciona. Pero la persona que lo escribió se fue hace años, el manual que hay dentro de un cajón describe una versión de hace tres reformas, y nadie se atreve a tocarlo porque nadie sabe del todo qué hace por dentro. El conocimiento no está perdido: está escrito, línea por línea, en el código. Solo que nadie lo lee.
La capacidad que probamos aquí es esa: reconstruir cómo funciona un sistema a partir del código, no de sus manuales, y demostrar que no se lo inventa. Lo enseñamos con DOOM porque su código es público, está bajo licencia libre y cualquiera puede comprobar lo que decimos. El caso es DOOM; el patrón sirve para tu ERP.
Y como documentar no es una cosa sola, generamos cuatro documentaciones desde la misma lectura del código, cada una en el formato que usa un equipo distinto: la técnica para quien va a mantenerlo, la funcional para el analista de negocio, un backlog ágil con historias de usuario para un equipo Scrum, y un manual llano para quien solo lo usa. Las cuatro están justo debajo del juego, y se pueden abrir y descargar.
El mismo código, corriendo: cronometra el gazapo
No enseñamos el código por un lado y el juego por otro: el motor de aquí abajo está compilado desde el código que documentamos. Arráncalo, abre una puerta con la barra espaciadora y mira el cronómetro de arriba a la derecha. No está pintado: mide el reloj real del motor (35 pasos por segundo) entre que la puerta acaba de abrir y empieza a cerrarse. Cambia entre la copia de siempre y la nuestra con el botón, y verás cambiar la cuenta.
0,00s
copia: canónica (la de siempre)
motor GPL · webassembly
DOOM, desde nuestra copia
El motor corre en tu navegador, compilado desde el código con los cuatro cambios. Se descarga solo cuando le des a arrancar (unos 30 MB), nada pesado se carga antes.
Las cuatro documentaciones: una para cada equipo
Esto es lo que produjo el sistema, y es el objeto de la demo: la misma lectura del código, contada de cuatro formas, cada una en el formato que un equipo distinto ya usa. La técnica para quien mantiene el código, la funcional para negocio y QA, el backlog ágil para un equipo Scrum, y el manual para quien solo juega. La tesis es esa: el mismo conocimiento, en el formato que tu equipo use.
Cada afirmación con número trae su archivo y su línea: pincha cualquiera y abres el código público de id Software en esa línea exacta. Verás el valor de siempre (150, no 105), porque GitHub tiene el código público y aquí documentamos nuestra copia con las cuatro cifras cambiadas. Esa diferencia es justo el experimento, y más abajo te contamos cómo lo montamos.
El ciclo de una puerta, generado leyendo el código a la vista. Cada número lleva su línea y su grado de confianza; no se afirma ningún valor que no esté en una línea leída.
Constantes de tiempo y velocidad
VDOORSPEED = FRACUNIT*3 p_spec.h:364.
Con FRACUNIT = 65536, son 3 unidades de mapa por tic para la puerta normal.
· confianza alta
VDOORWAIT = 105 tics p_spec.h:365.
A 35 tics por segundo doomdef.h:122,
son 105/35 = 3,00 segundos de espera arriba.
· confianza alta
Nota de método: VDOORWAIT y VDOORSPEED no se comprueban con la Doom Wiki (que puede reflejar otra versión); se comprueban leyendo p_spec.h en este árbol y, si se quiere, cronometrando la puerta en el binario compilado desde este código. Eso es justo lo que hace el cronómetro de arriba.
Estructura de estado: vldoor_t
Definida en p_spec.h:343-360.
El campo direction vale 1 (sube), 0 (espera arriba) o -1 (baja); al llegar
arriba, una puerta normal pasa a direction = 0 y arranca el contador
topcountdown = topwait p_doors.c:182.
Máquina de estados: T_VerticalDoor
El "thinker" que corre una vez por tic p_doors.c:63
ramifica por direction: subiendo mueve el plano hacia el techo
p_doors.c:168;
esperando decrementa el contador y, al llegar a cero, manda cerrar y suena el cierre
p_doors.c:81;
bajando, si algo la cruza, se reabre en lugar de aplastar
p_doors.c:151.
Constantes fuera de la puerta (parte del examen)
Daño de bala: damage = 7*(P_Random()%3+1) p_pspr.c:633,
es decir 7, 14 o 21 por impacto. Cargador:
clipammo[am_clip] = 15 p_inter.c:59,
un cargador da 15 balas (tope 200).
Puntos seguros de cambio
| Quiero cambiar… | Toco… | Radio de impacto |
|---|---|---|
| Espera de la puerta arriba | VDOORWAIT p_spec.h:365 | todas las puertas con topwait = VDOORWAIT |
| Velocidad de la puerta | VDOORSPEED p_spec.h:364 | puerta normal; las rápidas usan VDOORSPEED*4 y se escalan también |
| Daño de bala | multiplicador en p_pspr.c:633 | pistola y ametralladora, ambas vía P_GunShot |
| Balas por cargador | clipammo[0] p_inter.c:59 | recogida de cargadores y munición inicial |
Incertidumbre declarada
Cuánto sube una puerta en unidades absolutas depende de la geometría del nivel, no de una constante: no se puede afirmar una altura fija sin un mapa concreto. Solo la espera arriba (VDOORWAIT) es un tiempo fijo y citable en segundos.
Reglas observables, no código. Para analista, QA o product: lo que se puede ver y cronometrar, con su evidencia y cómo comprobarlo.
R2 · La puerta espera 3,00 segundos arriba antes de cerrarse
La espera es VDOORWAIT = 105 tics p_spec.h:365; 105 ÷ 35 = 3,00 s. Cómo comprobarlo: cronómetro desde que la puerta queda abierta del todo hasta que arranca a bajar; deben ser ~3,0 s, no ~4,3 s. · confianza alta
R3 · La puerta normal se mueve a 3 unidades por tic
Velocidad VDOORSPEED = FRACUNIT*3 p_spec.h:364, consumida por el motor de movimiento p_doors.c:171. Cómo comprobarlo: medir los tics de subida sobre una distancia conocida. · confianza alta
R8 · Una bala de pistola hace 7, 14 o 21 de daño
El daño por impacto es 7*(P_Random()%3+1) p_pspr.c:633: cae en {7, 14, 21}, no en {5, 10, 15}. Cómo comprobarlo: instrumentar el disparo o sembrar el generador y registrar el daño aplicado. · confianza alta
R9 · Un cargador da 15 balas
clipammo[am_clip] = 15 p_inter.c:59; el tope de balas es 200 p_inter.c:58. Cómo comprobarlo: recoger un cargador con el contador visible y ver el incremento de 15. · confianza alta
Otras reglas leídas
Una puerta con cerradura exige la llave del color correcto y suena un gruñido si no la llevas p_doors.c:376; una puerta que baja y te pilla se reabre en vez de aplastarte p_doors.c:151; un enemigo puede abrir puertas normales pero no cerrarlas p_doors.c:430.
Incertidumbre declarada
La duración total de apertura en segundos no es una regla fija: depende de la altura del hueco (geometría del nivel) dividida por la velocidad. El daño es aleatorio dentro de un conjunto: la afirmación correcta es "uno de 7, 14 o 21", no "un valor fijo".
El mismo conocimiento como backlog Scrum: tres épicas, doce historias de usuario y criterios de aceptación en Gherkin (Dado / Cuando / Entonces), listos para automatizar como pruebas. La prioridad usa MoSCoW; la estimación en puntos es ilustrativa (mide esfuerzo relativo que acuerda un equipo con su velocidad, y aquí no hay tal equipo). Lo que sí es verificable son las citas: cada criterio con número lleva su archivo y su línea, igual que en las otras tres.
Las tres épicas
A · El ciclo de vida de una puerta: se abre al usarla, sube, espera arriba un tiempo fijo, baja sola y no aplasta a quien la cruza p_doors.c:168. B · Control de acceso y variantes: llaves de color, puertas rápidas, puertas por tiempo y las tres vías de apertura. C · Armas de bala y munición: cuánto quita una bala, cuánto gasta un disparo y cuánto da un cargador.
Muestra de historias (12 en el PDF)
HU-02 · La puerta se cierra sola tras la espera. Como jugador quiero que la puerta se cierre sola para no tener que cerrarla a mano.
HU-10 · Cada bala hace un daño variable. Como jugador quiero que cada bala haga daño para eliminar enemigos, con algo de variación por disparo.
HU-11 · Un cargador repone munición hasta el tope. Como jugador quiero recoger cargadores para seguir disparando, sin pasarme del máximo.
Lo que este backlog no cubre
La duración total de apertura de una puerta en segundos no es una historia estimable sin un nivel concreto: depende de la geometría del WAD. Y el daño de bala es aleatorio dentro de un conjunto: el criterio correcto es "uno de 7, 14 o 21", no un número fijo por disparo. Automatizarlo exige sembrar el generador de azar.
En llano, sin jerga. Las cifras salen del código fuente; la referencia entre paréntesis queda por si alguien quiere comprobarla, pero puedes ignorarla si solo quieres jugar.
Puertas
Muchas paredes de DOOM son en realidad puertas. Te acercas y pulsas la tecla de usar (la barra espaciadora). Una puerta normal sube hasta abrirse, se queda abierta unos 3 segundos y se cierra sola (espera de 105 tics, p_spec.h:365; a 35 pasos por segundo, 3,0 s). Si te pilla debajo mientras se cierra, no te aplasta: se vuelve a abrir (p_doors.c:151).
Algunas puertas de colores necesitan una tarjeta o calavera del mismo color. Si no la llevas, no se abren y oyes un gruñido de fastidio. Y hay puertas rápidas que se abren y cierran de golpe, a cuádruple velocidad: no te entretengas debajo.
Tu pistola
Cada disparo gasta una bala. Cada bala que acierta hace entre 7 y 21 puntos de daño, y varía en cada disparo (7, 14 o 21, p_pspr.c:633). Cuando recoges un cargador ganas 15 balas (p_inter.c:59), y puedes acumular hasta 200 en total.
En una frase
Abre puertas con la barra espaciadora (algunas piden llave), no te quedes debajo cuando se cierran, y recuerda que cada cargador te da 15 balas y cada bala hace de 7 a 21 de daño.
Cómo lo hacemos: le cambiamos el examen sin avisar
Aquí hay una trampa que resolver antes de empezar: DOOM es probablemente el programa más diseccionado de la historia. Hay libros enteros sobre él, y cualquier IA moderna se lo sabe de memoria. Así que pedirle "documenta DOOM" no prueba nada: te lo recita. La pregunta buena no es si lo conoce, es si documenta el código que tiene delante o recita lo que pone en la Doom Wiki.
Para saberlo, cambiamos cuatro cifras del código a sus espaldas: cuánto espera una puerta antes de cerrarse, a qué velocidad se mueve, cuánto daño hace una bala y cuántas balas trae un cargador. Cuatro números, cambiados en su sitio, sin decírselo. Luego le pedimos que documentara cómo funciona todo eso.
Es el truco del profesor que cambia una cifra del enunciado para ver quién leyó y quién copió del libro. Si dice la cifra nueva, leyó el código. Si dice la del libro, copió. Y lo bonito es que se comprueba solo: cada afirmación de la doc trae su archivo y su línea, y el corrector va a esa línea a ver si el número está ahí.
El resultado, con el truco a la vista: mundo, cambio, doc
Como el público de un mago que sí ve el truco: por cada cifra, tres columnas. Lo que dice el mundo (la Doom Wiki y el código público de id Software), lo que cambiamos nosotros en secreto, y lo que la doc generada acabó diciendo. El ojo salta solo: la doc coincide con lo que cambiamos, no con lo que dice el mundo.
El código real, con la línea que lo respalda
No hace falta que sepas C. Pincha una afirmación y salta al fichero real de id Software, con la línea exacta que la respalda señalada en ámbar. Es el mismo código público que enlazan las citas: verás ahí el valor de siempre (150, no 105), porque GitHub tiene la copia pública y nosotros documentamos la nuestra cambiada. Esa diferencia es justo el experimento.
int topwait;
// (keep in case a door going down is reset)
// when it reaches 0, start going down
int topcountdown;
} vldoor_t;
#define VDOORSPEED FRACUNIT*2
#define VDOORWAIT 150
void
EV_VerticalDoor
( line_t* line,
mobj_t* thing ); case 1:
// UP
res = T_MovePlane(door->sector,
door->speed,
door->topheight,
false,1,door->direction);
if (res == pastdest)
{
switch(door->type)
{
case blazeRaise:
case normal:
door->direction = 0; // wait at top
door->topcountdown = door->topwait;
break;void
P_GunShot
( mobj_t* mo,
boolean accurate )
{
angle_t angle;
int damage;
damage = 5*(P_Random ()%3+1);
angle = mo->angle;
if (!accurate)
angle += (P_Random()-P_Random())<<18;
P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage);
}
// a weapon is found with two clip loads,
// a big item has five clip loads
int maxammo[NUMAMMO] = {200, 50, 300, 50};
int clipammo[NUMAMMO] = {10, 4, 20, 1};
//Resaltado con Shiki en tiempo de build, sobre el fichero C real. La numeración coincide con la del enlace a GitHub.
4/4
gazapos cazados: reportó el valor cambiado, no el de siempre, en los cuatro
87/87
citas trazables: las 87 referencias archivo:línea de las tres docs apuntan a una línea real
0/4
control negativo: una doc que recita los valores de siempre saca cero, el instrumento sabe suspender
2/3
docs con grados de confianza y sección de incertidumbre (el manual de usuario no la lleva a propósito)
El control negativo es lo que separa una prueba de un truco: le pasamos al corrector una documentación que recita los valores de siempre, y la suspendió en las cuatro. Si el instrumento no supiera dar un cero, aprobar no significaría nada.
Llévatelo: el método, no la palabra
Lo que hemos hecho aquí lo puedes hacer tú con cualquier documentación escrita por una IA. Te dejamos las herramientas: la lista de comprobaciones en llano, el corrector con el que puntuamos, y las cuatro documentaciones ya maquetadas, cada una en el formato estándar de su gremio.
La checklist para auditar doc de IA
Nueve comprobaciones de sentido común, en llano, que convierten "me fío" en "lo miro yo":
- Cada afirmación cita su fuente (sin cita, no cuenta).
- La fuente sí dice eso (vas y lo compruebas).
- Un cambio a escondidas lo caza (el truco del enunciado).
- El corrector sabe suspender (si aprueba siempre, no vale).
- Declara lo que no sabe · distingue lo fijo de lo variable · separa dato de deducción · habla el idioma de quien lee · se puede reproducir.
El corrector, abierto
El mismo corrige.py que
saca las cuentas de esta página: va a cada línea citada, comprueba el número a mano y decide
sin criterio propio. Lo publicamos con las mutaciones y el informe reproducible, para que lo
corras tú. Es heurístico, no un parser semántico: en los peros están sus dos
falsos negativos.
enlace al repo en cuanto se publique
Los peros
Lo que esta primera versión todavía no prueba.
¿Y en tu empresa?
Cambia DOOM por lo que te quita el sueño: el ERP que nadie se atreve a migrar, el programa de nóminas que solo entendía quien ya no está, las reglas de negocio enterradas en un procedimiento que nadie ha vuelto a leer. El conocimiento sigue ahí, escrito en el código. Esto lo saca, lo cuenta para quien lo mantiene, para quien decide y para quien lo usa, y deja la línea exacta como prueba de que no se lo ha inventado.
Si esto te suena a un problema tuyo, escríbenos y lo comentamos. Sin rollo: te decimos si se puede o no.
Con qué está hecho. La documentación la escribió un modelo de lenguaje leyendo el código: es la parte que redacta y relaciona. Pero quien decide si aprueba o suspende no es el modelo: es un corrector determinista en Python que va a cada línea citada y comprueba el número a mano, sin criterio propio. El modelo propone; el corrector, que no opina, verifica. Las cuatro mutaciones son parches deterministas sobre el código, reproducibles byte a byte, y las 87 citas se resuelven contra el fichero real. El juego es el motor GPL de id Software compilado a WebAssembly con Emscripten; el cronómetro lee el reloj del propio motor (parche nuestro, publicado con el fork), no lo simula.
Licencias y atribución
Esta demo existe porque id Software liberó el código de DOOM. Gracias.
El motor de DOOM es de id Software, publicado bajo licencia GPL-2.0. El código que mostramos y el que compilamos a WebAssembly salen de github.com/id-Software/DOOM, commit a77dfb9, árbol linuxdoom-1.10, vía el port doomgeneric. Nuestra copia con las cuatro mutaciones y el parche del cronómetro se distribuye también bajo GPL-2.0, como pide esa licencia. Los recursos del juego son Freedoom Phase 1 (licencia BSD). DOOM es una marca de id Software; esto no está avalado por id Software ni por ZeniMax.
¿Puedes usar esto? Nuestros textos y análisis de esta página se publican bajo licencia CC BY 4.0: úsalos citando la fuente, Team Banzai (team-banzai.com). El código de DOOM conserva su licencia GPL-2.0.