From 5888c59ba4e78f5f0198e514e7e8e7f3a962908a Mon Sep 17 00:00:00 2001 From: Marti Vich Date: Fri, 9 Jan 2026 17:04:53 +0100 Subject: [PATCH] DEVLOG update: Documenting reversion of GameRenderer refactoring to stable state --- DEVLOG.md | 25 +++++++++++++++++++ src/engine/game/GameEngine.js | 24 ++++++++++++++++++ src/view/ui/UnitCardManager.js | 45 ++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index 8898dbd..a91ec09 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,26 @@ # Devlog - Warhammer Quest (Versión Web 3D) +## Sesión 12: Refactorización y Renderizado (Intento I) +**Fecha:** 9 de Enero de 2026 + +### Objetivos +- Refactorizar visualmente el `GameRenderer.js` para dividirlo en submódulos ordenados (`Dungeon`, `Entity`, `Effect`, `Interaction`). +- Mejorar la escalabilidad del código de renderizado y separar responsabilidades. + +### Ocurrido +- Se realizó un intento completo de refactorización que, aunque arquitectónicamente sólido, introdujo regresiones visuales críticas: + - **Pantalla Negra**: Debido a una inicialización del canvas basada en un contenedor de altura 0, corregida con `window.innerHeight`. + - **Pérdida de Estilo Píxel**: Las miniaturas de los héroes se veían borrosas/blanquecinas por olvidar `THREE.SRGBColorSpace` y `minFilter/magFilter: Nearest`. + - **Cambio de Estilo**: La visualización de rutas de movimiento era diferente a la original. + +### Acción Táctica +- **Reversión (Rollback)**: Se decidió revertir todos los cambios de renderizado (`git restore`) para volver a un estado visualmente perfecto y comenzar la refactorización (v2) de forma segura y controlada, aplicando las lecciones aprendidas (altura del canvas, filtros de textura) desde el principio. + +### Próximos Pasos (Inmediato) +- Re-implementar la refactorización de `GameRenderer` de forma "Quirúrgica", copiando la lógica visual original línea por línea para no alterar la estética pixel-art ni el comportamiento del juego. + +--- + ## Sesión 11: Sistema de Iniciativa y Niebla de Guerra (Fog of War) **Fecha:** 9 de Enero de 2026 @@ -37,6 +58,10 @@ - **Ocultación de Entidades en Niebla**: Los héroes y monstruos que quedan fuera del alcance de la lámpara (losetas no visibles) ahora desaparecen completamente de la vista, aumentando la inmersión. - **Salto de Turno Automático**: Si un héroe comienza su turno en una zona oscura (oculta por la Niebla de Guerra), pierde automáticamente su turno hasta que sea "rescatado" (iluminado de nuevo) por el Portador de la Lámpara. - **Botones de Fase**: Se ha reorganizado la barra superior. El botón de "Acabar Fase" ahora comparte espacio con un nuevo botón "Acabar Turno" específico para cada héroe, facilitando el flujo de juego sin tener que buscar en la ficha del personaje. +- **Sistema de Destrabado (Break Away)**: Implementada la mecánica para escapar del combate cuerpo a cuerpo ("Trabado"). + - Los héroes trabados verán un botón de "Destrabarse" en su ficha. + - Al pulsarlo, el sistema lanza 1D6 contra el valor `pin_target` del héroe. + - Éxito: El héroe recupera su libertad de movimiento. Fallo: El héroe pierde su movimiento y debe luchar. ### Estado Actual El juego ahora respeta las reglas de visión y turno del juego de mesa original con una fidelidad visual alta. La sensación de exploración es más tensa al ocultarse las zonas lejanas ("se perderán en la oscuridad"), y el orden táctico es crucial. La UI es más intuitiva y limpia durante el combate. diff --git a/src/engine/game/GameEngine.js b/src/engine/game/GameEngine.js index c14283c..549c259 100644 --- a/src/engine/game/GameEngine.js +++ b/src/engine/game/GameEngine.js @@ -77,6 +77,7 @@ export class GameEngine { this.heroes.forEach(hero => { hero.currentMoves = hero.stats.move; hero.hasAttacked = false; + hero.hasEscapedPin = false; // Reset pin escape status }); console.log("Refilled Hero Moves"); } @@ -598,6 +599,9 @@ export class GameEngine { isEntityPinned(entity) { if (!this.monsters || this.monsters.length === 0) return false; + // If already escaped this turn, not pinned + if (entity.hasEscapedPin) return false; + return this.monsters.some(m => { if (m.isDead) return false; const dx = Math.abs(entity.x - m.x); @@ -629,6 +633,26 @@ export class GameEngine { }); } + attemptBreakAway(hero) { + if (!hero || hero.hasEscapedPin) return { success: false, roll: 0 }; + + const roll = Math.floor(Math.random() * 6) + 1; + const target = hero.stats.pin_target || 6; + + const success = roll >= target; + + if (success) { + hero.hasEscapedPin = true; + } else { + // Failed to escape: Unit loses movement and ranged attacks? + // "The Adventurer must stay where he is and fight" + // So movement becomes 0. + hero.currentMoves = 0; + } + + return { success, roll, target }; + } + // Alias for legacy calls if any deselectPlayer() { this.deselectEntity(); diff --git a/src/view/ui/UnitCardManager.js b/src/view/ui/UnitCardManager.js index e3abdf1..413a036 100644 --- a/src/view/ui/UnitCardManager.js +++ b/src/view/ui/UnitCardManager.js @@ -231,6 +231,51 @@ export class UnitCardManager { card.appendChild(bowBtn); } + // Break Away Button (Destrabarse) + const isPinned = this.game.isEntityPinned(hero) && !hero.hasEscapedPin; + const canTryBreak = isPinned && hero.currentMoves > 0 && !hero.hasAttacked; // Can only try if hasn't acted yet? + // Rules say: "can attempt to escape... if achieved... moves as normal". + // If fails "must stay and fight". + + if (isPinned) { + const breakBtn = document.createElement('button'); + const target = hero.stats.pin_target || 6; + breakBtn.textContent = `🏃 DESTRABARSE (${target}+)`; + Object.assign(breakBtn.style, { + width: '100%', padding: '8px', marginTop: '8px', + color: '#fff', border: '1px solid #FFA500', borderRadius: '4px', + fontFamily: '"Cinzel", serif', + cursor: canTryBreak ? 'pointer' : 'not-allowed', + backgroundColor: canTryBreak ? '#FF8C00' : '#555' + }); + + if (!canTryBreak) { + if (hero.hasAttacked) breakBtn.title = "Ya has atacado."; + else if (hero.currentMoves <= 0) breakBtn.title = "No tienes movimiento."; + } else { + breakBtn.onclick = (e) => { + e.stopPropagation(); + const result = this.game.attemptBreakAway(hero); + + // Show result + const color = result.success ? '#00ff00' : '#ff0000'; + const msg = result.success ? "¡Escapada con éxito!" : "¡Fallo! Debes luchar."; + + if (this.callbacks.showModal) { + this.callbacks.showModal( + result.success ? '¡Destrabado!' : '¡Atrapado!', + `Resultado del dado: ${result.roll} (Necesitabas ${result.target}+)
${msg}` + ); + } + + // Update UI (Refresh card to show movement unlocked or locked) + this.updateHeroCard(hero.id); + if (this.callbacks.refresh) this.callbacks.refresh(); // Or just let update handle it + }; + } + card.appendChild(breakBtn); + } + // Inventory const invBtn = document.createElement('button'); invBtn.textContent = '🎒 INVENTARIO';