WENOPAL

16 · Casos reales

Seis bugs reales de nuestros mods, con síntoma, pistas falsas, causa y lección, para practicar el método de depuración.

Curso técnico · Build 42 · Actualizado el

Objetivo: ver el método del módulo 15 aplicado a bugs que pasaron de verdad en los mods de Wenopal. Cada caso tiene el síntoma tal como se vio, las pistas falsas, cómo se encontró la causa y la lección general.

Lee cada caso tapando la causa: con el síntoma y lo que sabes del curso, escribe tu hipótesis antes de seguir.


Caso 1 · Zapatillas con hoyos (Catimbas)

Síntoma: las zapatillas puestas se veían incompletas: faltaban trozos, se veía el interior y el suelo a través de la punta. Con jeans era peor. En el suelo, en el inventario y en los renders de Blender se veían perfectas.

Pistas falsas descartadas:

  • Caras al revés (culling): invertirlas empeoró todo.
  • Malla de doble cara: no cambió nada (y eso descarta el culling).
  • Transparencia en la textura: era 100 % opaca.
  • La cámara cortando la malla: la cámara de personajes es ortográfica y la profundidad es real.

Causa: el juego recorta cada prenda con las máscaras de las prendas que van encima, para que no asome lo tapado. Las máscaras están en el UV del cuerpo (media/textures/Body/Masks/). Una zapatilla con UV propio recibía esos recortes en lugares arbitrarios. En el código de Java (ModelInstanceTextureCreator): si la prenda declara la carpeta de máscaras "none", no se recorta nada.

Arreglo: <m_MasksFolder>none</m_MasksFolder> en el XML de la prenda, como los zapatos 3D del juego (Shoes_Fancy.xml).

Lección: cuando algo se ve bien en un contexto y mal en otro, la diferencia entre los contextos es la causa. Aquí: "puesta" aplica máscaras, "en el suelo" no. Y una regla: malla con UV propio (calzado, cascos, accesorios) → m_MasksFolder en none; malla del juego con UV del cuerpo → no.


Caso 2 · El sable que aturde (Sable de Luz)

Síntoma: en multijugador "el sonido es abrumador, le suena a todos los que están cerca; los golpes te revientan el audio". En un jugador no se notaba tanto.

Causa (dos, sumadas):

  1. distanceMin por defecto es 10. El constructor de zombie.audio.GameSoundClip pone volumen 1, distanceMin 10 y distanceMax 10. Un .wav sin distanceMin suena a volumen completo hasta 10 casillas. Los sonidos del juego son eventos FMOD con atenuación propia; los .wav de los mods no.
  2. Golpes apilados. CombatManager.attackCollisionCheck reproduce el HitSound una vez por zombi golpeado, y en multijugador lo reenvía a los demás. Con 14 zombis por golpe, 14 sonidos a volumen 1.

Arreglo: distanceMin = 1 en cada clip, maxInstancesPerEmitter = 1..2 en el sound, volúmenes bajos (0,10 a 0,40).

Y una tercera cosa que se descubrió después: los .wav de los mods no siguen el volumen de efectos de las opciones (van a un grupo de FMOD al que el juego nunca le aplica la perilla). Para lo que se lanza desde Lua hay que multiplicar a mano por getCore():getOptionSoundVolume() / 10.

Lección: los valores por defecto viven en Java. Cuando un campo es opcional, decompila el constructor y mira qué pasa si no lo pones.


Caso 3 · La receta que nunca se habilita (Tocomples Food)

Síntoma: la receta de masa de marraqueta no se podía hacer aunque tuvieras todo. La ventana marcaba la levadura como el ingrediente que faltaba, y tenías levadura.

Causa: en B42, una línea -fluid modifica el ítem de la línea anterior: le exige que contenga ese líquido. La receta tenía:

item 1 [Base.Yeast],
-fluid 1.0 [Water],      ← le pedía a la levadura que tuviera 1 L de agua

El juego (MakeBreadDough) pone primero un recipiente cualquiera, item 1 [*],, y después el -fluid.

Cómo se encontró: con una herramienta de diagnóstico nuestra, que revisa cada insumo de la receta como lo ve el cliente.

Lección: cuando un formato se lee "por líneas", el orden puede tener significado. Copia la estructura de una receta del juego que haga lo mismo antes de inventar la tuya.


Caso 4 · "Desarmar muebles no da XP" (DesarmeXP, un mod nuestro sin publicar)

Síntoma: desarmar muebles no daba experiencia.

No era un bug. ISMoveableSpriteProps.scrapGiveXp solo da XP si tu nivel es menor que SandboxVars.LevelForDismantleXPCutoff, y esa opción vale 0 por defecto en todos los presets: en el juego base, desarmar muebles da 0 XP (salvo Electricidad).

Cómo se resolvió: el mod DesarmeXP envuelve scrapGiveXp y ISHandcraftAction.performRecipe (es el ejemplo del módulo 09). Antes de pagar XP en las recetas, filtra las que ya tienen xpAward (recipe:getXPAwardCount() == 0): esas ya reciben XP desde Java (CraftRecipe.addXP) y pagarlas otra vez sería duplicar.

Lección: antes de buscar el bug, confirma que el comportamiento "esperado" es realmente el del juego. Muchos reportes son diseño del juego o una opción de sandbox.


Caso 5 · El Barney que no revive (BarneyZombie, un mod nuestro sin publicar)

Síntoma: BarneyZombie, copiado de Telezombies, otro de nuestros mods, no revivía ni soltaba loot. Ningún error.

Causa: el sed TeletuZombi → BarneyZombie dejó:

lua
string.sub(outfit, 1, 12) == "BarneyZombie_"

"TeletuZombi_" tiene 12 letras; "BarneyZombie_" tiene 13. La comparación nunca da verdadero.

Arreglo:

lua
local PREFIX = "BarneyZombie_"
string.sub(outfit, 1, #PREFIX) == PREFIX

Y al copiar un mod, cambiar también el espacio de GUIDs del generador: con los mismos GUIDs, dos mods activos se pisan la ropa.

Lección: un número mágico que depende de un texto es un bug esperando el próximo renombre. "Sin error y no hace nada" casi siempre es una condición que nunca se cumple: imprime los dos lados de la comparación.


Caso 6 · Todas las pruebas en un bosque (servidor local)

Síntoma: en el servidor de pruebas, getRoom() daba nil en todas las casillas y no había contenedores. Varios mods "fallaban" de formas raras.

Causa: el servertest.ini tenía Map=B43, un mapa que no existe. El servidor no avisa: genera un mundo de puro bosque, sin casas. Todas las pruebas anteriores a ese día habían corrido en ese bosque.

Arreglo: Map=Muldraugh, KY (el mapa completo en B42). Nuestras herramientas de servidor local ahora lo fuerzan.

Lección: cuando muchas cosas distintas fallan a la vez, sospecha del entorno antes que del código. Y valida el entorno de prueba con algo trivial (¿hay casas?) antes de probar el mod.


Ejercicios

Ejercicio 16.1 · Reproducir dos

Elige dos casos y reprodúcelos en CursoLab, en pequeño:

  • Caso 2: un sonido .wav sin distanceMin y otro con distanceMin = 1; aléjate y compara.
  • Caso 3: una receta con el -fluid en el lugar equivocado; mira qué marca la ventana de crafteo como faltante.
  • Caso 5: una comparación de prefijo con un número fijo, y renombra.

Listo cuando: ves el síntoma, aplicas el arreglo y ves que desaparece.

Ejercicio 16.2 · Leer el valor por defecto

Decompila zombie.audio.GameSoundClip (módulo 08) y comprueba los valores por defecto del caso 2. ¿Hay algún otro campo de clip cuyo valor por defecto te sorprenda?

Ejercicio 16.3 · Tu propio caso

Busca un bug abierto en un mod tuyo (o en CursoLab). Como referencia, después del caso 2 nos quedaron varios mods con .wav sin distanceMin: Batalla de Aura, Finish Him, Pistola de Gravedad, Wanaco, Telezombies y un mod sin publicar (xpranksCustom). Arréglalo y escribe su entrada en tus notas con el mismo formato: síntoma, pistas falsas, causa, arreglo, lección.

Desafío 16

Escribe pzaudit.py: revisa todos los mods de una carpeta en busca de los patrones de estos casos que se pueden detectar sin abrir el juego: clip sin distanceMin, -fluid después de un item que no es un recipiente, string.sub(x, 1, <número>) == "<texto>" con largo distinto, prendas 3D con UV propio sin m_MasksFolder. Córrelo y arregla lo que encuentre.


Anterior: 15 · Depuración en serio · Siguiente: 17 · Publicar y mantener