Objetivo: cuando algo falla (o peor, cuando "no pasa nada"), encontrar la causa con método y no a tientas. Este módulo junta las herramientas de todo el curso y los errores que nos pasaron en nuestros mods.
1. Dónde está la evidencia
| Qué | Dónde |
|---|---|
| Log de la sesión actual | ~/Zomboid/Logs/<fecha>_DebugLog.txt |
| Sesiones anteriores | ~/Zomboid/Logs/logs_<fecha-hora>/ (cada vez que abres el juego, la anterior se archiva ahí) |
| Consola | ~/Zomboid/console.txt (otro flujo: puede no tener la sesión que buscas) |
| Servidor (multijugador) | server-console.txt en la carpeta del servidor; el Lua de server/ escribe ahí, no en tu cliente |
Lo que vas a buscar:
- Tus
printsalen comoLOG : Lua f:0> [CursoLab] .... Ponle siempre prefijo con el nombre del mod. - Un error de tu mod sale como
ERROR: General ... Lua((MOD:<nombre>))...seguido deSTACK TRACEcon archivo y línea. player:Say(...)no queda en ningún log. Para depurar,printademás.
grep -n -A14 "MOD:CursoLab" ~/Zomboid/Logs/*_DebugLog.txt # todo lo de tu mod con contexto
grep -c "Exception thrown" ~/Zomboid/Logs/*_DebugLog.txt # cuántos errores hubo2. Leer los errores de Kahlua
Los mensajes de Kahlua son cortos y engañosos. El diccionario:
| Mensaje | Qué significa casi siempre |
|---|---|
Object tried to call nil in <función> | Llamaste un método que no existe en ese objeto: mal escrito (:Is en vez de :has), o existe en otra clase (setWorldZRotation es de InventoryItem, no de IsoWorldInventoryItem) |
attempted index: X of non-table: null | algo.X donde algo es nil: un getVehicle() que devolvió nil, una tabla global que todavía no cargó |
attempted index: derive of non-table: null en archivos del juego | Un archivo tuyo falló al cargar y rompió el estado del intérprete. El error real está más arriba (el caso de WarFactions, un mod nuestro sin publicar, en la sección 4) |
UnknownFormatConversionException ... Formatting "Sandbox_..." | Un % suelto en un texto de traducción: el traductor lo pasa por String.format |
| Nada, pero no funciona | Sospecha de un nil silencioso: Kahlua descarta sin error una clave nil en un constructor de tabla ({ [X] = 1 } con X == nil da una tabla vacía) |
Dos verdades que te ahorran horas:
- El primer error es la causa; el resto son consecuencias. Un error al cargar un archivo produce cientos de errores en archivos del juego. Siempre lee desde el primero.
pcallno es silencioso en PZ. Atrapa el error para tu código, pero igual lo escribe en el log conSTACK TRACEy sube el contador rojo. No sirve para "probar si un método existe". Para eso:local f = obj.metodo; if f then f(obj, ...) end(indexar un método inexistente en un objeto Java devuelvenilsin error).
3. El método
1. Reproducir → pasos exactos para que pase siempre. Sin esto, no sabes si lo arreglaste.
2. Aislar → ¿es tu mod? ¿qué archivo? ¿qué línea?
3. Primer error → en el log, el primero de la sesión que nombra tu mod.
4. Hipótesis → una explicación concreta y comprobable.
5. Verificar → en el Lua del juego (_juego/lua) o en el .jar (módulos 07 y 08), no en la memoria.
6. Arreglar → el cambio mínimo.
7. Comprobar → los pasos del 1 ya no fallan, y lo que funcionaba sigue funcionando.
8. Anotar → una entrada en tus notas: síntoma, causa, arreglo, lección.Aislar: bisectar mods
Si no sabes qué mod causa un problema: desactiva la mitad, prueba, y sigue con la mitad culpable. Con 32 mods son 5 pruebas. Lo mismo dentro de un mod: comenta la mitad de los archivos que registran eventos.
Herramientas
| Herramienta | Para |
|---|---|
Consola Lua (tecla ` `` en debug) | Probar una línea en el juego vivo; inspeccionar objetos con tu CursoLab.dump (desafío 6) |
| Depurador de Lua (F11 en debug) | Puntos de quiebre, ver variables, recargar un archivo |
luajit -bl archivo.lua | Comprobar sintaxis sin abrir el juego (LuaJIT es Lua 5.1, igual que Kahlua; en macOS se instala con Homebrew) |
| Un simulador offline | Probar lógica pura sin el juego. Finish Him, uno de nuestros mods, tiene uno que reemplaza pcall para contar errores como el juego |
javap / Vineflower | Confirmar que un método existe y qué hace (módulo 08) |
| Un servidor local | Reproducir bugs que solo pasan con dos jugadores (módulo 12) |
4. Programar para que los bugs se vean
- Nada que pueda fallar en el nivel superior de un archivo. Envuélvelo en una función que corre en un evento. Un error al cargar no solo rompe tu archivo: deja el intérprete en mal estado y los
requiresiguientes del juego fallan (nos pasó en WarFactions, dondeuuid.uuid5no existía en Kahlua). requirelo que usas al cargar. Los archivos de una carpeta cargan en orden alfabético: en un mod nuestro sin publicar (WowArmory),WowArmoryantes queWowConstantsdejó una tabla vacía sin ningún error.- Un log que siempre está. Una línea con prefijo por cada cosa importante que hace tu mod, siempre activa, y uno detallado detrás de una opción de sandbox de depuración. Otro mod nuestro sin publicar (Zombie Piensa) lo explica en un comentario: "sin esto los errores quedan ocultos".
- Verifica los nombres contra el juego, no contra la memoria:
rg -n ":has\(IsoFlagType" _juego/luaantes de escribir:Is(.
Ejercicios: bugs plantados
Cada ejercicio es un archivo de CursoLab con un bug real (todos nos pasaron en nuestros mods). Para cada uno: pégalo en CursoLab, reprodúcelo, encuentra la causa con el log y arréglalo. No mires la pista hasta tener una hipótesis.
Bug 15.1 · "No pasa nada"
shared/CursoLab_Armas.lua:
CursoLab = CursoLab or {}
CursoLab.armasPorFaccion = {
[CursoLab.F_HUMANO] = { "Base.Axe", "Base.Bat" },
[CursoLab.F_ORCO] = { "Base.Machete" },
}shared/CursoLab_Constantes.lua:
CursoLab = CursoLab or {}
CursoLab.F_HUMANO = "humano"
CursoLab.F_ORCO = "orco"Síntoma: CursoLab.armasPorFaccion.humano es nil. No hay ningún error en el log.
Pista
Orden alfabético de carga y claves nil en un constructor. Arreglo: require "CursoLab_Constantes" al principio del primer archivo.
Bug 15.2 · El agua
server/CursoLab_Agua.lua:
local function esAgua(sq)
local floor = sq:getFloor()
return floor and floor:getProperties():Is(IsoFlagType.water)
end
Events.LoadGridsquare.Add(function(sq)
if esAgua(sq) then CursoLab.log("agua en", sq:getX(), sq:getY()) end
end)Síntoma: el log se llena de errores al moverte por el mapa.
Pista
Object tried to call nil in esAgua. Busca en _juego/lua cómo pregunta el juego por IsoFlagType.water.
Bug 15.3 · El pcall que "no falla"
Events.OnTick.Add(function()
local p = getPlayer()
local ok, loc = pcall(function() return p:getBodyLocation() end)
if ok then CursoLab.log(loc) end
end)Síntoma: el contador rojo de errores sube sin parar, aunque todo está en pcall.
Pista
pcall no es silencioso en PZ, y este se ejecuta cada tick. Comprueba si el método existe una sola vez (p.getBodyLocation) en vez de tantear.
Bug 15.4 · El archivo que rompe el juego
shared/CursoLab_Ids.lua:
CursoLab = CursoLab or {}
CursoLab.ID_MOD = uuid.uuid5("cursolab")
function CursoLab.idDe(nombre) return CursoLab.ID_MOD .. ":" .. nombre endSíntoma: al entrar a la partida, cientos de errores en ISPanel.lua, ISUIElement.lua y otros archivos del juego. Algunas ventanas del juego no abren.
Pista
Busca el primer error del log. Kahlua no tiene librerías externas. Arreglo: un hash propio (por ejemplo djb2) y nunca código que pueda fallar en el nivel superior.
Bug 15.5 · El texto que no carga
media/lua/shared/Translate/ES/Sandbox.json:
{ "Sandbox_CursoLab_Peso_tooltip": "Probabilidad de loot, en %. 100% = normal." }Síntoma: un error de formato al abrir las opciones de sandbox.
Pista
El traductor pasa los textos por String.format. Cambia el texto para no usar %.
Bug 15.6 · Solo en multijugador
Tu ejercicio 12.2 funciona en un jugador. En el servidor local, el cliente pide la sopaipilla y no recibe nada. En el log del cliente no hay errores. (Este no tiene pista: usa el método completo, incluyendo el log del servidor.)
Desafío 15
Escribe pzlog.py: lee un DebugLog.txt, agrupa los errores por mod y por mensaje, cuenta cuántas veces se repite cada uno, y para cada grupo muestra el primer stack trace completo. Úsalo con un log real de una sesión con todos los mods que usas activos y arregla el error más repetido.
Anterior: 14 · Datos persistentes y rendimiento · Siguiente: 16 · Casos reales