Solucionar bug donde las pelotas aparecían en el centro sin formar
la figura geométrica al entrar en modo DEMO con SimulationMode::SHAPE.
## Problema
Al randomizar el estado en modo DEMO, si se elegía una figura:
1. Se configuraba el modo SHAPE
2. Se llamaba a changeScenario() que creaba pelotas en el centro
3. NO se llamaba a generateShape() para calcular los puntos de la figura
4. Resultado: pelotas amontonadas en el centro sin formar figura
## Solución
Reordenar operaciones en executeRandomizeOnDemoStart():
1. Decidir PRIMERO el modo (PHYSICS o SHAPE) antes de changeScenario
2. Si SHAPE: configurar figura manualmente sin generar puntos
3. Llamar a changeScenario() con el modo ya establecido
4. Después de changeScenario(), generar figura y activar atracción
Cambios adicionales:
- Arreglar warning de formato %zu en textrenderer.cpp (MinGW)
- Usar %lu con cast para size_t en logs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Crear ResourceManager singleton para gestión centralizada de recursos
- Separar lógica de ResourcePack de la clase Texture
- Adaptar TextRenderer para cargar fuentes TTF desde pack
- Adaptar LogoScaler para cargar imágenes PNG desde pack
- Actualizar main.cpp y engine.cpp para usar ResourceManager
- Regenerar resources.pack con fuentes y logos incluidos
Fixes:
- Resuelve error de carga de fuentes desde disco
- Resuelve error de carga de logos (can't fopen)
- Implementa fallback automático a disco si no existe pack
- Todas las clases ahora pueden cargar recursos desde pack
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problema:**
Cuando se iniciaba con `-m demo`, `-m demo-lite` o `-m logo`, se llamaba
a `setState()` directamente, que es un método de bajo nivel que solo
cambia el estado interno SIN ejecutar las acciones de configuración.
**Resultado del bug:**
- `-m demo`: NO aleatorizaba (tema default, primer escenario)
- `-m demo-lite`: NO aleatorizaba física
- `-m logo`: NO configuraba tema, PNG_SHAPE, ni pelotas pequeñas
**Arquitectura correcta:**
- `setState()` = Método primitivo bajo nivel (solo cambia estado)
- `toggleDemoMode()` = Método alto nivel (setState + randomize)
- `toggleDemoLiteMode()` = Método alto nivel (setState + randomize)
- `enterLogoMode()` = Método alto nivel (setState + configuración completa)
**Solución implementada:**
En lugar de llamar a setState() directamente, usar los métodos de
alto nivel que ejecutan las acciones de configuración:
```cpp
if (initial_mode == AppMode::DEMO) {
state_manager_->toggleDemoMode(...); // Entra a DEMO + randomiza
}
else if (initial_mode == AppMode::DEMO_LITE) {
state_manager_->toggleDemoLiteMode(...); // Entra a DEMO_LITE + randomiza
}
else if (initial_mode == AppMode::LOGO) {
state_manager_->enterLogoMode(...); // Entra a LOGO + configura todo
}
```
**Archivos modificados:**
- source/engine.cpp (líneas 249-263):
- Reemplazado setState() por toggleDemoMode/toggleDemoLiteMode/enterLogoMode
- Agregados comentarios explicativos
**Resultado esperado:**
- ✅ `-m demo` → Aleatoriza todo como si pulsaras D
- ✅ `-m demo-lite` → Aleatoriza física como si pulsaras Shift+D
- ✅ `-m logo` → Configura tema/PNG_SHAPE/pelotas como si pulsaras K
- ✅ Comportamiento consistente entre CLI y teclas
- ✅ Arquitectura correcta: alto nivel para acciones, bajo nivel para estado
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Permite arrancar directamente en modo DEMO, DEMO_LITE, LOGO o SANDBOX
mediante argumentos de línea de comandos, eliminando necesidad de
cambiar manualmente el modo después del arranque.
Nuevos argumentos:
- `-m, --mode <mode>` - Establece modo inicial
- `sandbox` - Control manual (default)
- `demo` - Auto-play completo (figuras + temas + colisiones)
- `demo-lite` - Auto-play simple (solo física/figuras)
- `logo` - Modo logo (easter egg con convergencia)
Ejemplos de uso:
```bash
# Arrancar en modo DEMO
./vibe3_physics --mode demo
./vibe3_physics -m demo
# Arrancar en DEMO_LITE (solo física)
./vibe3_physics -m demo-lite
# Arrancar directo en LOGO
./vibe3_physics --mode logo
# Combinar con otros argumentos
./vibe3_physics -w 1920 -h 1080 --mode demo
./vibe3_physics -F -m demo-lite # Fullscreen + DEMO_LITE
```
Implementación:
1. main.cpp: Parsing de argumento --mode con validación
2. engine.h: Nuevo parámetro `initial_mode` en initialize()
3. engine.cpp: Aplicación del modo vía StateManager::setState()
Si no se especifica --mode, se usa SANDBOX (comportamiento actual).
El modo se aplica después de inicializar StateManager, garantizando
que todos los componentes estén listos antes del cambio de estado.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Nuevo sistema AppLogo que muestra el logo cada 20 segundos por 5 segundos
- Fade in/out suave de 0.5 segundos con alpha blending
- Máquina de estados: HIDDEN → FADE_IN → VISIBLE → FADE_OUT
- Logo posicionado en cuadrante inferior derecho (1/4 de pantalla)
- Añadido método setAlpha() a Texture para control de transparencia
- Habilitado SDL_BLENDMODE_BLEND en todas las texturas
- Filtrado LINEAR para suavizado del logo escalado
- Desactivado automáticamente en modo SANDBOX
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problema:
- Al cambiar de escenario (teclas 1-8) en modo BOIDS, la gravedad
se reseteaba a 720 en lugar de mantenerse en 0
- SceneManager::changeScenario() reinicializa bolas con gravedad default
- Esto rompía el invariante: "modo BOIDS = gravedad OFF siempre"
Solución:
- Añadido check en Engine::changeScenario() para forzar gravedad OFF
después del cambio de escenario si estamos en modo BOIDS
- Mantiene consistencia con el comportamiento de SHAPE mode
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**1. Bordes como obstáculos (no más wrapping):**
- Implementada fuerza de repulsión cuando boids se acercan a bordes
- Nueva regla: Boundary Avoidance (evitar bordes)
- Fuerza proporcional a cercanía (0% en margen, 100% en colisión)
- Constantes: BOID_BOUNDARY_MARGIN (50px), BOID_BOUNDARY_WEIGHT (7200 px/s²)
**2. Variables ajustables en runtime:**
- Añadidas 11 variables miembro en BoidManager (inicializadas con defines.h)
- Permite modificar comportamiento sin recompilar
- Variables: radios (separation/alignment/cohesion), weights, speeds, boundary
- Base para futuras herramientas de debug/tweaking visual
**3. Fix tecla G (BOIDS → PHYSICS):**
- Corregido: toggleBoidsMode() ahora acepta parámetro force_gravity_on
- handleGravityToggle() pasa explícitamente false para preservar inercia
- Transición BOIDS→PHYSICS ahora mantiene gravedad OFF correctamente
**Implementación:**
- defines.h: +2 constantes (BOUNDARY_MARGIN, BOUNDARY_WEIGHT)
- boid_manager.h: +11 variables miembro ajustables
- boid_manager.cpp:
- Constructor inicializa variables
- Todas las funciones usan variables en lugar de constantes
- applyBoundaries() completamente reescrito (repulsión vs wrapping)
- engine.h/cpp: toggleBoidsMode() con parámetro opcional
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Al entrar o salir del modo fullscreen real (F4), el área de juego cambia
de tamaño. Si estábamos en modo SHAPE, las bolas aparecían centradas pero
sin formar la figura.
PROBLEMA RAÍZ:
- changeScenario() recrea las bolas en nuevas posiciones
- NO se regeneraban los targets de la figura (puntos 3D)
- NO se reactivaba la atracción física hacia los targets
- Resultado: bolas centradas sin formar figura
SOLUCIÓN:
Después de changeScenario() en ambas transiciones de F4:
1. Llamar a generateShape() (Engine, no ShapeManager)
2. Reactivar enableShapeAttraction(true) en todas las bolas
Esto sigue el mismo patrón usado en Engine::changeScenario() (línea 515).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problemas resueltos:
- En modo F3 (letterbox), el overlay se centraba en pantalla física
en lugar de en el viewport visible, quedando desplazado
- Al salir de F3 a ventana, el overlay seguía roto
- Padding inferior no se respetaba correctamente
Cambios implementados:
1. render() ahora usa SDL_GetRenderViewport() para obtener área visible
2. Centrado calculado dentro del viewport (con offset de barras negras)
3. toggleFullscreen() restaura tamaño de ventana al salir de F3
4. Padding check movido ANTES de escribir línea (>= en lugar de >)
5. Debug logging añadido para diagnóstico de dimensiones
Resultado:
✅ Overlay centrado correctamente en F3 letterbox
✅ Overlay se regenera correctamente al salir de F3
✅ Padding inferior respetado en columna 0
Pendiente:
- Columna 2 (índice 1) todavía no respeta padding inferior
- Verificar que F4 (real fullscreen) siga funcionando correctamente
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
El modo LOGO ahora incluye cambios automáticos de dirección de
gravedad como parte de sus variaciones, aumentando la diversidad
visual de la demostración.
Cambios:
- Nueva acción en modo LOGO (PHYSICS): cambiar dirección gravedad (16%)
- Rebalanceo de probabilidades existentes:
• PHYSICS → SHAPE: 60% → 50%
• Gravedad ON: 20% → 18%
• Gravedad OFF: 20% → 16%
• Dirección gravedad: nuevo 16%
- Al cambiar dirección, se fuerza gravedad ON para visibilidad
Antes el modo LOGO solo alternaba entre figura/física y gravedad
on/off, pero nunca cambiaba la dirección. Ahora tiene las mismas
capacidades de variación que los modos DEMO y DEMO_LITE.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
PROBLEMA RESUELTO:
En modo SHAPE (figuras 3D), al cambiar el número de pelotas (teclas 1-8),
las nuevas pelotas aparecían en pantalla pero NO formaban la figura hasta
pulsar de nuevo la tecla de figura (Q/W/E/R/T).
CAUSA RAÍZ:
1. changeScenario() creaba pelotas nuevas en centro de pantalla
2. generateShape() generaba puntos target de la figura
3. PERO las pelotas nuevas no tenían shape_attraction_active=true
4. Sin atracción activa, las pelotas no se movían hacia sus targets
CAMBIOS IMPLEMENTADOS:
1. Ball class (ball.h/ball.cpp):
- Constructor ahora acepta parámetro Y explícito
- Eliminado hardcodeo Y=0.0f en inicialización de pos_
2. SceneManager (scene_manager.cpp):
- PHYSICS mode: Y = 0.0f (parte superior, comportamiento original)
- SHAPE mode: Y = screen_height_/2.0f (centro vertical) ✅
- BOIDS mode: Y = rand() (posición Y aleatoria)
- Ball constructor llamado con parámetro Y según modo
3. Engine (engine.cpp:514-521):
- Tras generateShape(), activar enableShapeAttraction(true) en todas
las pelotas nuevas
- Garantiza que las pelotas converjan inmediatamente hacia figura
RESULTADO:
✅ Cambiar escenario (1-8) en modo SHAPE regenera automáticamente la figura
✅ No requiere pulsar tecla de figura de nuevo
✅ Transición suave e inmediata hacia nueva configuración
ARCHIVOS MODIFICADOS:
- source/ball.h: Constructor acepta parámetro Y
- source/ball.cpp: Usar Y en lugar de hardcode 0.0f
- source/scene/scene_manager.cpp: Inicializar Y según SimulationMode
- source/engine.cpp: Activar shape attraction tras changeScenario()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
CAMBIOS:
- Debug HUD reorganizado en layout de 2 columnas (LEFT/RIGHT, sin centro)
- Añadidos getters públicos en Engine para info de sistema
- changeScenario() ahora preserva el SimulationMode actual
- Inicialización de pelotas según modo (PHYSICS/SHAPE/BOIDS)
- Eliminada duplicación de logo_entered_manually_ (ahora en StateManager)
ARCHIVOS MODIFICADOS:
- engine.h: Añadidos 8 getters públicos para UIManager
- engine.cpp: changeScenario() pasa current_mode_ a SceneManager
- scene_manager.h: changeScenario() acepta parámetro SimulationMode
- scene_manager.cpp: Inicialización según modo (RULES.md líneas 23-26)
- ui_manager.h: render() acepta Engine* y renderDebugHUD() actualizado
- ui_manager.cpp: Debug HUD con columnas LEFT (sistema) y RIGHT (física)
REGLAS.md IMPLEMENTADO:
✅ Líneas 23-26: Inicialización diferenciada por modo
- PHYSICS: Top, 75% distribución central en X, velocidades aleatorias
- SHAPE: Centro de pantalla, sin velocidad inicial
- BOIDS: Posiciones y velocidades aleatorias
✅ Líneas 88-96: Debug HUD con información de sistema completa
BUGS CORREGIDOS:
- Fix: Cambiar escenario (1-8) en FIGURE ya no resetea a PHYSICS ❌→✅
- Fix: Las pelotas se inicializan correctamente según el modo activo
- Fix: AppMode movido de centro a izquierda en Debug HUD
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implementadas 2 mejoras críticas para modo boids:
**1. Auto-exit de boids al activar gravedad (G/cursores):**
- handleGravityToggle(): Sale a PHYSICS si está en BOIDS
- handleGravityDirectionChange(): Sale a PHYSICS y aplica dirección
- Razón: La gravedad es conceptualmente incompatible con boids
- UX esperada: Usuario pulsa G → vuelve automáticamente a física
**2. Update screen size en F4 (real fullscreen):**
- toggleRealFullscreen() ahora llama a boid_manager_->updateScreenSize()
- Corrige bug: Boids no respetaban nuevas dimensiones tras F4
- Wrapping boundaries ahora se actualizan correctamente
Cambios:
- engine.cpp: Añadida comprobación de BOIDS en métodos de gravedad
- engine.cpp: Actualización de boid_manager en F4 (línea 420)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Cambios realizados:
- Creado BoidManager (source/boids_mgr/) con algoritmo de Reynolds (1987)
* Separación: Evitar colisiones con vecinos cercanos
* Alineación: Seguir dirección promedio del grupo
* Cohesión: Moverse hacia centro de masa del grupo
* Wrapping boundaries (teletransporte en bordes)
* Velocidad y fuerza limitadas (steering behavior)
- Añadido BOIDS a enum SimulationMode (defines.h)
- Añadidas constantes de configuración boids (defines.h)
- Integrado BoidManager en Engine (inicialización, update, toggle)
- Añadido binding de tecla J para toggleBoidsMode() (input_handler.cpp)
- Añadidos helpers en Ball: getVelocity(), setVelocity(), setPosition()
- Actualizado CMakeLists.txt para incluir source/boids_mgr/*.cpp
Arquitectura:
- BoidManager sigue el patrón establecido (similar a ShapeManager)
- Gestión independiente del comportamiento de enjambre
- Tres reglas de Reynolds implementadas correctamente
- Compatible con sistema de resolución dinámica
Estado: Compilación exitosa, BoidManager funcional
Próximo paso: Testing y ajuste de parámetros boids
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Limpieza:
- Eliminadas declaraciones de métodos privados obsoletos en engine.h
- Eliminado método Engine::enterLogoMode(bool) obsoleto
- Actualizados comentarios de callbacks para reflejar arquitectura final
- Documentadas todas las variables de estado DEMO/LOGO en Engine
Documentación:
- Aclarado que callbacks son parte de la arquitectura pragmática
- Explicado que StateManager coordina, Engine implementa
- Documentado propósito de cada variable de estado duplicada
- Actualizado comentarios de sistema de figuras 3D
Arquitectura final:
- StateManager: Coordina estados, timers y triggers
- Engine: Proporciona implementación vía callbacks
- Separación de responsabilidades clara y mantenible
- Sin TODO markers innecesarios
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implementación:
- StateManager::update() ahora maneja timers y triggers DEMO/LOGO
- Detección de flips de PNG_SHAPE migrada completamente
- Callbacks temporales en Engine para acciones complejas
- enterLogoMode() y exitLogoMode() públicos para transiciones automáticas
- Toggle methods en Engine delegados a StateManager
Callbacks implementados (temporal para Fase 9):
- Engine::performLogoAction()
- Engine::executeDemoAction()
- Engine::executeRandomizeOnDemoStart()
- Engine::executeToggleGravityOnOff()
- Engine::executeEnterLogoMode()
- Engine::executeExitLogoMode()
TODO Fase 9:
- Eliminar callbacks moviendo lógica completa a StateManager
- Limpiar duplicación de estado entre Engine y StateManager
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
ENFOQUE PRAGMÁTICO:
- ShapeManager creado e implementado completamente
- Código DUPLICADO entre Engine y ShapeManager temporalmente
- Engine mantiene implementación para DEMO/LOGO (hasta Fase 8)
- Compilación exitosa, aplicación funcional
ARCHIVOS CREADOS/MODIFICADOS:
1. shape_manager.h:
- Interfaz completa de ShapeManager
- Métodos públicos para control de figuras 3D
- Referencias a Scene/UI/StateManager
2. shape_manager.cpp:
- Implementación completa de todos los métodos
- toggleShapeMode(), activateShape(), update(), generateShape()
- Sistema de atracción física con spring forces
- Cálculo de convergencia para LOGO MODE
- Includes de todas las Shape classes
3. engine.h:
- Variables de figuras 3D MANTENIDAS (duplicadas con ShapeManager)
- Comentarios documentando duplicación temporal
- TODO markers para Fase 8
4. engine.cpp:
- Inicialización de ShapeManager con dependencias
- Métodos de figuras restaurados (no eliminados)
- Código DEMO/LOGO funciona con variables locales
- Sistema de rendering usa current_mode_ local
DUPLICACIÓN TEMPORAL DOCUMENTADA:
```cpp
// Engine mantiene:
- current_mode_, current_shape_type_, last_shape_type_
- active_shape_, shape_scale_factor_, depth_zoom_enabled_
- shape_convergence_
- toggleShapeModeInternal(), activateShapeInternal()
- updateShape(), generateShape(), clampShapeScale()
```
JUSTIFICACIÓN:
- Migrar ShapeManager sin migrar DEMO/LOGO causaba conflictos masivos
- Enfoque incremental: Fase 7 (ShapeManager) → Fase 8 (DEMO/LOGO)
- Permite compilación y testing entre fases
- ShapeManager está listo para uso futuro en controles manuales
RESULTADO:
✅ Compilación exitosa (1 warning menor)
✅ Aplicación funciona correctamente
✅ Todas las características operativas
✅ ShapeManager completamente implementado
✅ Listo para Fase 8 (migración DEMO/LOGO a StateManager)
PRÓXIMOS PASOS (Fase 8):
1. Migrar lógica DEMO/LOGO de Engine a StateManager
2. Convertir métodos de Engine en wrappers a StateManager/ShapeManager
3. Eliminar código duplicado
4. Limpieza final
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
PROBLEMA CRÍTICO RESUELTO:
- El programa compilaba pero crasheaba inmediatamente al ejecutar
- Stack trace apuntaba a UIManager::updatePhysicalWindowSize() (línea 135)
- Root cause: Engine::initialize() llamaba updatePhysicalWindowSize() en línea 228
ANTES de crear ui_manager_ en línea 232 → nullptr dereference
SOLUCIÓN:
- Calcular tamaño físico de ventana inline sin llamar al método completo
- Usar SDL_GetWindowSizeInPixels() directamente antes de crear ui_manager_
- Pasar valores calculados a UIManager::initialize()
CAMBIOS ADICIONALES:
1. engine.h: Documentar duplicación pragmática Engine ↔ StateManager
- Variables de estado DEMO/LOGO mantenidas temporalmente en Engine
- StateManager mantiene current_app_mode_ (fuente de verdad)
- Comentarios explicativos para futuras migraciones
2. shape_manager.cpp: Documentar facade pattern completo
- Añadidos comentarios extensivos explicando stubs
- Cada método stub documenta por qué Engine mantiene implementación
- Clarifica dependencias (SceneManager, UIManager, notificaciones)
RESULTADO:
✅ Compilación exitosa (sin errores)
✅ Aplicación ejecuta sin crashes
✅ Inicialización de UIManager correcta
✅ Todos los recursos cargan apropiadamente
Archivos modificados:
- source/engine.cpp: Fix de inicialización (líneas 227-238)
- source/engine.h: Documentación de estado duplicado
- source/shapes_mgr/shape_manager.cpp: Documentación facade
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implementa patrón facade/delegation para gestión de estado de aplicación.
Engine ahora consulta estado a través de StateManager en lugar de acceso directo.
## Cambios
**source/engine.cpp:**
- Reemplazar `current_app_mode_` con `state_manager_->getCurrentMode()` (18 ocurrencias)
- setState() delega a StateManager pero mantiene setup en Engine (temporal)
- toggleDemoMode/Lite/Logo() usan getCurrentMode() de StateManager
- updateDemoMode() consulta modo actual mediante StateManager
**source/state/state_manager.cpp:**
- setState() implementado con lógica básica de cambio de estado
- Maneja transiciones LOGO ↔ otros modos correctamente
- Reset de demo_timer_ al cambiar estado
## Patrón Facade Aplicado
**Justificación:** Token budget limitado requiere enfoque pragmático
- StateManager = Interfaz pública para consultas de estado
- Engine = Mantiene implementación compleja temporalmente
- Refactorización incremental sin reescribir 600+ líneas
**Próximo paso (Fase 4c):**
- Eliminar duplicación de miembros entre Engine y StateManager
- Migrar lógica compleja gradualmente
## Verificación
✅ Compilación exitosa
✅ Sin errores de asignación a lvalue
✅ Todas las consultas de estado delegadas correctamente
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Crea la infraestructura del StateManager para gestionar estados DEMO/LOGO
con patrón de callbacks al Engine. Estructura lista para migración de lógica.
## Archivos Nuevos
**source/state/state_manager.h:**
- Declaración de clase StateManager
- Forward declaration de Engine (patrón callback)
- Métodos públicos: initialize(), update(), setState()
- Métodos toggle: toggleDemoMode(), toggleDemoLiteMode(), toggleLogoMode()
- Getters: getCurrentMode(), getPreviousMode(), is*ModeActive()
- Métodos privados: performDemoAction(), randomizeOnDemoStart(), etc.
- Miembros para timers, convergencia, flip detection, estado previo
**source/state/state_manager.cpp:**
- Implementación de constructor/destructor
- initialize() con callback al Engine
- Stubs de todos los métodos (TODO: migrar lógica completa)
- Preparado para recibir ~600 líneas de lógica DEMO/LOGO
## Archivos Modificados
**CMakeLists.txt:**
- Agregado: source/state/*.cpp al glob de archivos fuente
**source/engine.h:**
- Agregado: #include "state/state_manager.h"
- Agregado: std::unique_ptr<StateManager> state_manager_
- NOTA: Miembros de estado aún no removidos (pendiente migración)
**source/engine.cpp:**
- initialize(): Crea state_manager_ con `this` como callback
- NOTA: Métodos DEMO/LOGO aún no migrados (pendiente)
## Estado Actual
- ✅ Estructura del StateManager creada y compila
- ✅ Patrón de callbacks al Engine configurado
- ✅ CMakeLists actualizado
- ⏳ Migración de lógica DEMO/LOGO: PENDIENTE (~600 líneas)
- ⏳ Remoción de miembros duplicados en Engine: PENDIENTE
## Próximos Pasos (Fase 4b)
1. Migrar updateDemoMode() → StateManager::update()
2. Migrar performDemoAction() → StateManager (privado)
3. Migrar randomizeOnDemoStart() → StateManager (privado)
4. Migrar enterLogoMode() → StateManager (privado)
5. Migrar exitLogoMode() → StateManager (privado)
6. Migrar toggleGravityOnOff() → StateManager (privado)
7. Migrar setState() completo
8. Delegar toggle*Mode() desde Engine a StateManager
9. Remover miembros de estado duplicados en Engine
10. Commit final de Fase 4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Migra el sistema de renderizado de HUD debug desde printPhysical()
(coordenadas lógicas escaladas) a printAbsolute() (píxeles físicos absolutos).
## Cambios
**engine.cpp (líneas 830-951):**
- Eliminadas líneas de cálculo de factores de escala (text_scale_x/y)
- Todas las coordenadas ahora en píxeles físicos absolutos
- FPS: `physical_window_width_ - text_width - margin` (esquina derecha física)
- 10 llamadas printPhysical() → printAbsolute() con SDL_Color
- 4 llamadas getTextWidth() → getTextWidthPhysical()
## Resultado
✅ HUD de tamaño fijo independiente de resolución lógica
✅ FPS siempre pegado a esquina derecha física
✅ Espaciado constante entre líneas
✅ Funciona en modo ventana y F4 (stretch fullscreen)
⚠️ PENDIENTE: Ajustar offset para modo F3 con letterbox
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Corrige bugs críticos en el manejo de ventanas cuando se inician
con parámetros de línea de comandos (-w, -h, -z).
## Problemas Resueltos
**1. Zoom incorrecto con parámetros CLI**
- El zoom calculado no se guardaba en current_window_zoom_
- F1/F2 usaban valor default (3) en lugar del zoom actual
- Resultado: Posicionamiento erróneo de ventana al hacer zoom
**2. Ventana no centrada al iniciar**
- Faltaba SDL_SetWindowPosition() después de crear ventana
- Ventana aparecía en posición aleatoria
**3. F4 restauraba tamaño incorrecto**
- toggleRealFullscreen() usaba DEFAULT_WINDOW_ZOOM hardcoded
- Al salir de fullscreen real, ventana cambiaba de tamaño
- No re-centraba ventana después de restaurar
## Cambios Implementados
**engine.cpp:initialize() línea 86-87:**
- Guardar zoom calculado en current_window_zoom_ antes de crear ventana
- Asegura consistencia entre zoom real y zoom guardado
**engine.cpp:initialize() línea 114-117:**
- Centrar ventana con SDL_WINDOWPOS_CENTERED al iniciar
- Solo si no está en modo fullscreen
**engine.cpp:toggleRealFullscreen() línea 1174-1175:**
- Usar current_window_zoom_ en lugar de DEFAULT_WINDOW_ZOOM
- Re-centrar ventana con SDL_WINDOWPOS_CENTERED al salir de F4
## Casos de Prueba Verificados
✅ Sin parámetros: vibe3_physics.exe
✅ Con resolución: vibe3_physics.exe -w 640 -h 480
✅ Con zoom: vibe3_physics.exe -z 2
✅ Combinado: vibe3_physics.exe -w 1920 -h 1080 -z 1
## Teclas Afectadas
- F1 (Zoom Out): ✅ Funciona correctamente
- F2 (Zoom In): ✅ Funciona correctamente
- F3 (Fullscreen Toggle): ✅ Funciona correctamente
- F4 (Real Fullscreen): ✅ Ahora restaura tamaño correcto
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Unifica el idioma de todas las notificaciones del sistema a castellano
para mantener consistencia en la interfaz de usuario.
## Traducciones Realizadas
### Gravedad
- "Gravity Off/On" → "Gravedad Off/On"
- "Gravity Up" → "Gravedad Arriba"
- "Gravity Down" → "Gravedad Abajo"
- "Gravity Left" → "Gravedad Izquierda"
- "Gravity Right" → "Gravedad Derecha"
### Modos
- "Physics Mode" → "Modo Física"
### Figuras 3D (array shape_names[] + notificaciones)
- "None" → "Ninguna"
- "Sphere" → "Esfera"
- "Cube" → "Cubo"
- "Helix" → "Hélice"
- "Torus" → "Toroide"
- "Lissajous" → "Lissajous" (mantiene nombre técnico)
- "Cylinder" → "Cilindro"
- "Icosahedron" → "Icosaedro"
- "Atom" → "Átomo"
- "PNG Shape" → "Forma PNG"
### Profundidad
- "Depth Zoom On/Off" → "Profundidad On/Off"
## Mantienen Inglés
- **Sprite**: Término técnico común en desarrollo
- **Nombres de temas**: Usan getCurrentThemeNameES() (ya en español)
- **Modos de aplicación**: Ya estaban en español
- **Número de pelotas**: Ya estaban en español
- **Escala**: Ya estaba en español
- **Páginas**: Ya estaban en español
## Resultado
✅ Interfaz de usuario 100% en castellano
✅ Consistencia en todas las notificaciones
✅ Mantiene términos técnicos apropiados (Lissajous, Sprite)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Corrige bug donde pulsar F en modo LOGO (llegando desde DEMO) causaba
salida automática a DEMO debido a uso incorrecto de previous_app_mode_
como flag de "¿puede salir automáticamente?".
## Problema
**Flujo con bug:**
1. SANDBOX → D → DEMO
2. DEMO → K → LOGO (guarda previous_app_mode_ = DEMO)
3. LOGO (SHAPE) → F → LOGO (PHYSICS) ← Acción MANUAL
4. updateDemoMode() ejecuta lógica de LOGO
5. Línea 1628: `if (previous_app_mode_ != SANDBOX && rand() < 60%)`
6. Como previous_app_mode_ == DEMO → Sale a DEMO ❌
**Causa raíz:**
La variable previous_app_mode_ se usaba para dos propósitos:
- Guardar a dónde volver (correcto)
- Decidir si puede salir automáticamente (incorrecto)
Esto causaba que acciones manuales del usuario (como F) activaran
la probabilidad de salida automática.
## Solución Implementada
**Nueva variable explícita:**
```cpp
bool logo_entered_manually_; // true si tecla K, false si desde DEMO
```
**Asignación en enterLogoMode():**
```cpp
logo_entered_manually_ = !from_demo;
```
**Condición corregida en updateDemoMode():**
```cpp
// ANTES (incorrecto):
if (previous_app_mode_ != AppMode::SANDBOX && rand() % 100 < 60)
// AHORA (correcto):
if (!logo_entered_manually_ && rand() % 100 < 60)
```
## Ventajas
✅ **Separación de responsabilidades:**
- previous_app_mode_: Solo para saber a dónde volver
- logo_entered_manually_: Solo para control de salida automática
✅ **Semántica clara:**
- Código más legible y expresivo
✅ **Más robusto:**
- No depende de comparaciones indirectas
## Flujos Verificados
**Flujo 1 (Manual desde SANDBOX):**
- SANDBOX → K → LOGO (logo_entered_manually_ = true)
- LOGO → F → PHYSICS
- No sale automáticamente ✅
**Flujo 2 (Manual desde DEMO):**
- SANDBOX → D → DEMO → K → LOGO (logo_entered_manually_ = true)
- LOGO → F → PHYSICS
- No sale automáticamente ✅
**Flujo 3 (Automático desde DEMO):**
- SANDBOX → D → DEMO → auto → LOGO (logo_entered_manually_ = false)
- LOGO ejecuta acciones automáticas
- Sale a DEMO con 60% probabilidad ✅🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Corrige desajuste entre el orden del enum ShapeType y el array
de nombres shape_names[] en el handler de tecla F (toggle).
## Problema
Al pulsar F para toggle PHYSICS ↔ SHAPE, la notificación mostraba
nombre incorrecto de la figura debido a que el array shape_names[]
NO coincidía con el orden del enum ShapeType.
**Enum ShapeType (defines.h):**
0=NONE, 1=SPHERE, 2=CUBE, 3=HELIX, 4=TORUS, 5=LISSAJOUS,
6=CYLINDER, 7=ICOSAHEDRON, 8=ATOM, 9=PNG_SHAPE
**Array previo (incorrecto):**
{"Sphere", "Lissajous", "Helix", "Torus", "Cube", ...}
Orden erróneo causaba que al activar CUBE (enum=2) mostrara
"Helix" (array[2]), etc.
## Solución
Reordenar array para coincidir exactamente con enum ShapeType:
```cpp
const char* shape_names[] = {
"None", // 0 = NONE
"Sphere", // 1 = SPHERE
"Cube", // 2 = CUBE
"Helix", // 3 = HELIX
"Torus", // 4 = TORUS
"Lissajous", // 5 = LISSAJOUS
"Cylinder", // 6 = CYLINDER
"Icosahedron", // 7 = ICOSAHEDRON
"Atom", // 8 = ATOM
"PNG Shape" // 9 = PNG_SHAPE
};
```
## Resultado
✅ Tecla F muestra nombre correcto al activar cada figura
✅ Comentario documentando correspondencia con enum
✅ "None" añadido en índice 0 (nunca usado, pero completa array)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implementa restricciones para modos DEMO y LOGO garantizando que
PNG_SHAPE sea exclusivo del modo LOGO y nunca aparezca en DEMO/DEMO_LITE.
## Cambios en Modo LOGO (enterLogoMode)
**Textura:**
- Cambiado de "tiny" a "small" como textura obligatoria
**Tema aleatorio:**
- Antes: Siempre MONOCHROME (tema 5)
- Ahora: Selección aleatoria entre 4 temas:
- MONOCHROME (5)
- LAVENDER (6)
- CRIMSON (7)
- ESMERALDA (8)
**Comportamiento:**
- No cambia de tema automáticamente durante ejecución
- Mantiene tema seleccionado hasta salir del modo
## Cambios en Transición LOGO → DEMO
**exitLogoMode (automático):**
- Al volver automáticamente a DEMO desde LOGO
- Si figura activa es PNG_SHAPE → cambia a figura aleatoria válida
- Excluye PNG_SHAPE de selección (8 figuras disponibles)
**randomizeOnDemoStart (manual):**
- Al entrar manualmente a DEMO/DEMO_LITE con tecla D/L
- Check inicial: si current_shape_type_ == PNG_SHAPE
- Fuerza cambio a figura aleatoria antes de randomización
- Soluciona bug: D → DEMO → K → LOGO → D dejaba PNG_SHAPE activa
## Garantías Implementadas
✅ PNG_SHAPE nunca aparece en acciones aleatorias de DEMO/DEMO_LITE
✅ PNG_SHAPE se cambia automáticamente al salir de LOGO (manual o auto)
✅ Modo LOGO elige tema aleatorio al entrar (4 opciones monocromáticas)
✅ Modo LOGO usa textura SMALL en lugar de TINY
## Flujos Verificados
- Manual: DEMO → LOGO → DEMO (tecla D) ✅
- Manual: DEMO_LITE → LOGO → DEMO_LITE (tecla L) ✅
- Automático: DEMO → LOGO → DEMO (5% probabilidad) ✅
- Dentro DEMO: PNG_SHAPE nunca seleccionada ✅🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problema resuelto:
1. Color del tema saliente: Notificaciones mostraban color del tema ANTIGUO
2. Sin transiciones LERP: Notificaciones no participaban en transiciones suaves
Cambios implementados:
- Arquitectura cambiada de estática a dinámica
- Notifier ahora consulta ThemeManager cada frame en render()
- Eliminados colores estáticos de struct Notification
- Notifier::init() recibe puntero a ThemeManager
- Notifier::show() ya no recibe parámetros de color
- Simplificado showNotificationForAction() (-23 líneas)
Fix crítico de inicialización:
- ThemeManager ahora se inicializa ANTES de updatePhysicalWindowSize()
- Previene nullptr en notifier_.init() que causaba que no se mostraran
Resultado:
- ✅ Notificaciones usan color del tema DESTINO (no origen)
- ✅ Transiciones LERP suaves automáticas durante cambios de tema
- ✅ Código más limpio y centralizado en ThemeManager
- ✅ -50 líneas de código duplicado eliminadas
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Cambios principales:
- Renombrado AppMode::MANUAL → AppMode::SANDBOX (nomenclatura más clara)
- Notificaciones ahora funcionan en TODAS las transiciones de modo
- Lógica de teclas D/L/K simplificada: toggle exclusivo modo ↔ SANDBOX
- Mensajes simplificados: "MODO DEMO", "MODO SANDBOX", etc. (sin ON/OFF)
- Eliminado check restrictivo en showNotificationForAction()
Comportamiento nuevo:
- Tecla D: Toggle DEMO ↔ SANDBOX
- Tecla L: Toggle DEMO_LITE ↔ SANDBOX
- Tecla K: Toggle LOGO ↔ SANDBOX
- Cada tecla activa su modo o vuelve a SANDBOX si ya está activo
- Notificaciones visibles tanto al activar como desactivar modos
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Sistema de texto:
- Reemplazado dbgtxt.cpp (bitmap 8x8) por TextRenderer (SDL_TTF)
- Creado source/text/ con TextRenderer class
- Añadidas fuentes TrueType en data/fonts/
- Implementados dos TextRenderer (display + debug) con escalado dinámico
- Constantes configurables: TEXT_FONT_PATH, TEXT_BASE_SIZE, TEXT_ANTIALIASING
Correcciones de centrado:
- Reemplazado text.length() * 8 por text_renderer_.getTextWidth() en ~25 lugares
- Texto de tecla F ahora se centra correctamente
- Texto de modo (Demo/Logo/Lite) fijo en tercera fila del HUD debug
- Implementado espaciado dinámico con getTextHeight()
Conversión a mixed case:
- ~26 textos de display cambiados de ALL CAPS a mixed case
- 15 nombres de temas en theme_manager.cpp convertidos a mixed case
- Ejemplos: "FPS" → "fps", "MODO FISICA" → "Modo Física", "DEMO MODE ON" → "Modo Demo: On"
- Temas: "SUNSET" → "Sunset", "OCEANO" → "Océano", etc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Arquitectura polimórfica implementada:
- Jerarquía: Theme (base) → StaticTheme / DynamicTheme (derivadas)
- Vector unificado de 10 temas (7 estáticos + 3 dinámicos)
- Eliminada lógica dual (if(dynamic_theme_active_) scattered)
Nuevos archivos:
- source/themes/theme.h: Interfaz base abstracta
- source/themes/static_theme.h/cpp: Temas estáticos (1 keyframe)
- source/themes/dynamic_theme.h/cpp: Temas dinámicos (N keyframes animados)
- source/theme_manager.h/cpp: Gestión unificada de temas
Mejoras de API:
- switchToTheme(0-9): Cambio a cualquier tema (índice 0-9)
- cycleTheme(): Cicla por todos los temas (Tecla B)
- update(delta_time): Actualización simplificada
- getInterpolatedColor(idx): Sin parámetro balls_
Bugs corregidos:
- Tecla B ahora cicla TODOS los 10 temas (antes solo 6)
- DEMO mode elige de TODOS los temas (antes excluía LAVENDER + dinámicos)
- Eliminada duplicación de keyframes en temas dinámicos (loop=true lo maneja)
Código reducido:
- theme_manager.cpp: 558 → 320 líneas (-43%)
- engine.cpp: Eliminados ~470 líneas de lógica de temas
- Complejidad significativamente reducida
Preparado para PHASE 3 (LERP universal entre cualquier par de temas)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refactoring semántico:
- Renombrar rotoball_* → shape_* (variables y métodos)
- Mejora legibilidad: aplica a todas las figuras 3D, no solo esfera
Fixes críticos:
- Fix convergencia: setShapeTarget2D() actualiza targets cada frame
- Fix getDistanceToTarget(): siempre calcula distancia (sin guarda)
- Fix lógica flip: destruir DURANTE flip N (no después de N flips)
- Añadir display CONV en debug HUD (monitoreo convergencia)
Mejoras timing:
- Reducir PNG_IDLE_TIME_LOGO: 3-5s → 2-4s (flips más dinámicos)
- Bajar CONVERGENCE_THRESHOLD: 0.8 → 0.4 (40% permite flips)
Sistema flip-waiting (LOGO mode):
- CAMINO A: Convergencia + tiempo (inmediato)
- CAMINO B: Esperar 1-3 flips y destruir durante flip (20-80% progreso)
- Tracking de flips con getFlipCount() y getFlipProgress()
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Cambiar figura "Wave Grid" (malla ondeante) por curva de Lissajous 3D,
con ecuaciones paramétricas más hipnóticas y resultónas visualmente.
## Cambios Principales
**Archivos renombrados:**
- `wave_grid_shape.h/cpp` → `lissajous_shape.h/cpp`
- Clase `WaveGridShape` → `LissajousShape`
**Ecuaciones implementadas:**
- x(t) = A * sin(3t + φx) - Frecuencia 3 en X
- y(t) = A * sin(2t) - Frecuencia 2 en Y
- z(t) = A * sin(t + φz) - Frecuencia 1 en Z
- Ratio 3:2:1 produce patrón de "trenza elegante"
**Animación:**
- Rotación global dual (ejes X/Y)
- Animación de fase continua (morphing)
- Más dinámica y orgánica que Wave Grid
**defines.h:**
- `WAVE_GRID_*` → `LISSAJOUS_*` constantes
- `ShapeType::WAVE_GRID` → `ShapeType::LISSAJOUS`
**engine.cpp:**
- Actualizado include y instanciación
- Arrays de figuras DEMO actualizados
- Tecla W ahora activa Lissajous
## Resultado
Curva 3D paramétrica hipnótica con patrón entrelazado,
rotación continua y morphing de fase. Más espectacular
que el grid ondeante anterior. 🌀🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
PROBLEMA:
Al cambiar de tema dinámico (SUNRISE/OCEAN WAVES/NEON PULSE) a tema
estático (SUNSET/LAVENDER/etc), el fondo se volvía negro o mostraba
colores corruptos durante la transición LERP.
CAUSA:
1. activateDynamicTheme() NO actualiza current_theme_ (queda en valor previo)
2. startThemeTransition() desactiva dynamic_theme_active_
3. renderGradientBackground() intenta LERP desde themes_[current_theme_]
4. Si current_theme_ era LAVENDER (índice 6), accede themes_[6] OK
5. Pero si era cualquier valor >= 7, accede fuera de bounds → basura
SOLUCIÓN IMPLEMENTADA:
✅ Detectar transición desde tema dinámico en startThemeTransition()
✅ Si dynamic_theme_active_ == true:
- Hacer transición INSTANTÁNEA (sin LERP)
- Cambiar current_theme_ inmediatamente al tema destino
- Actualizar colores de pelotas sin interpolación
- Evitar acceso a themes_[] con índices inválidos
✅ Eliminar asignación de current_theme_ en activateDynamicTheme()
- Cuando dynamic_theme_active_=true, se usa current_dynamic_theme_index_
- current_theme_ solo se usa cuando dynamic_theme_active_=false
RESULTADO:
- Dinámico → Estático: Cambio instantáneo limpio ✅
- Estático → Estático: Transición LERP suave (sin cambios) ✅
- Estático → Dinámico: Cambio instantáneo (sin cambios) ✅
- Dinámico → Dinámico: Cambio instantáneo (sin cambios) ✅
TRADE-OFF:
- Perdemos transición suave dinámico→estático
- Ganamos estabilidad y eliminamos fondo negro/corrupto
- Para implementar LERP correcto se requiere refactor mayor
(unificar todos los temas bajo sistema dinámico)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
PROBLEMA:
Crash (segfault) al activar temas dinámicos OCEAN WAVES (tecla 9) y
NEON PULSE (tecla 0). Funcionamiento correcto con SUNRISE (tecla 8).
CAUSA:
Múltiples lugares del código accedían a themes_[current_theme_] sin
verificar si current_theme_ era un tema dinámico (índices 7/8/9).
El array themes_[] solo tiene 7 elementos (índices 0-6):
- SUNSET, OCEAN, NEON, FOREST, RGB, MONOCHROME, LAVENDER
Los temas dinámicos están en dynamic_themes_[] (índices 0-2):
- DYNAMIC_1=7 (SUNRISE), DYNAMIC_2=8 (OCEAN WAVES), DYNAMIC_3=9 (NEON PULSE)
Acceder a themes_[7/8/9] causaba out-of-bounds → puntero inválido
→ crash en strlen(name_es).
PUNTOS DE FALLO IDENTIFICADOS:
1. render() línea ~738: Mostrar nombre del tema en pantalla
2. render() línea ~808: Debug display "THEME XXX"
3. initBalls() línea ~864: Seleccionar colores para pelotas nuevas
SOLUCIÓN:
✅ Añadir verificación dynamic_theme_active_ antes de acceder a arrays
✅ Si tema dinámico: usar dynamic_themes_[current_dynamic_theme_index_]
✅ Si tema estático: usar themes_[static_cast<int>(current_theme_)]
CORRECCIONES APLICADAS:
- render() (show_text_): Obtener color y nombre desde DynamicTheme
- render() (show_debug_): Obtener name_en desde DynamicTheme
- initBalls(): Seleccionar colores desde keyframe actual de DynamicTheme
RESULTADO:
- ✅ SUNRISE (Numpad 8) funciona correctamente
- ✅ OCEAN WAVES (Numpad 9) funciona correctamente (antes crasheaba)
- ✅ NEON PULSE (Numpad 0) funciona correctamente (antes crasheaba)
- ✅ Temas estáticos (1-7) siguen funcionando normalmente
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
PROBLEMA:
Las pelotas cambiaban bruscamente de color durante transiciones
de temas dinámicos en lugar de tener transiciones suaves.
CAUSAS IDENTIFICADAS:
1. getInterpolatedColor() interpolaba desde Ball::color_ (obsoleto)
en lugar de usar el color del keyframe actual
2. updateDynamicTheme() actualizaba Ball::color_ incorrectamente
al final de cada transición
SOLUCIÓN:
✅ getInterpolatedColor():
- Ahora interpola desde current_kf.ball_colors[index]
- Hasta target_kf.ball_colors[index]
- Elimina dependencia de Ball::color_ almacenado
✅ updateDynamicTheme():
- Elimina setColor() redundante al completar transición
- getInterpolatedColor() ya calcula color correcto cada frame
- Cuando progress=1.0, devuelve exactamente color destino
RESULTADO:
- Transiciones LERP suaves de 0% a 100% sin saltos bruscos
- Interpolación correcta entre keyframes actual y destino
- Coherencia entre renderizado y lógica de animación
ARCHIVOS MODIFICADOS:
- source/engine.cpp (2 funciones corregidas)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Arreglar getExecutableDirectory() para usar _NSGetExecutablePath en macOS
- Añadir getResourcesDirectory() con soporte MACOS_BUNDLE
- Actualizar main.cpp y engine.cpp para buscar recursos correctamente
- Eliminar referencias obsoletas a directorio 'config' en Makefile
Ahora resources.pack se busca en ../Resources/ cuando MACOS_BUNDLE está
definido, permitiendo que la app bundle funcione correctamente.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
SISTEMA DE RELEASE (Makefile):
- Adaptado windows_release de Coffee Crisis a ViBe3 Physics
- Comandos Unix-style (rm/cp/mkdir) compatibles con Git Bash/MSYS2
- Compresión ZIP via PowerShell Compress-Archive
- LICENSE opcional (si no existe, continúa)
- Genera: vibe3_physics-YYYY-MM-DD-win32-x64.zip
CARGA DINÁMICA DE RECURSOS:
- Añadido Texture::getPackResourceList() - Lista recursos del pack
- Añadido Texture::isPackLoaded() - Verifica si pack está cargado
- engine.cpp: Descubrimiento dinámico de texturas desde pack
- Sin listas hardcodeadas - Usa ResourcePack::getResourceList()
- Filtra recursos por patrón "balls/*.png" automáticamente
ARQUITECTURA:
- Descubrimiento de texturas híbrido:
1. Si existe data/balls/ → escanear disco
2. Si no existe + pack cargado → listar desde pack
3. Ordenar por tamaño (automático)
TESTING CONFIRMADO:
- ✅ Release con resources.pack funciona sin data/
- ✅ Carga 4 texturas desde pack dinámicamente
- ✅ make windows_release genera ZIP válido
- ✅ Ejecutable arranca correctamente desde release/
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Movido getExecutableDirectory() a defines.h como función inline
- Actualizado main.cpp para construir path absoluto a resources.pack
- Eliminadas definiciones duplicadas en engine.cpp y main.cpp
- Ahora funciona correctamente ejecutando desde cualquier carpeta (ej. build/)
TEST confirmado:
- Ejecutar desde raíz: ✅ Carga resources.pack
- Ejecutar desde build/: ✅ Carga resources.pack
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problema: Cada textura se cargaba DOS veces:
1. Primera carga temporal para obtener dimensiones (width)
2. Segunda carga real para almacenar en textures_
Solución: Reutilizar la textura cargada en lugar de crear nueva.
- TextureInfo ahora guarda shared_ptr<Texture> en lugar de solo path
- Se ordena por tamaño usando la textura ya cargada
- Se almacena directamente en textures_ sin recargar
Resultado: 4 cargas → 4 cargas (sin duplicados)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>