**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>
**1. Eliminado logging de debug del FADE_OUT:**
- Removido log de timer/delta_time/progress (FADE_OUT inicial)
- Removido log de alpha1/alpha2
- Removido log de animaciones (ZOOM, ELASTIC, SPIRAL, BOUNCE)
- Removido log de completado de FADE_OUT
- Consola limpia en modo producción
**2. Fix centro del logo en animación ZOOM_ONLY:**
**Problema:**
- Centro del logo se calculaba basándose en width/height escalados
- Cuando scale cambiaba (1.2 → 1.0), corner_x/corner_y se movían
- Resultado: logo se desplazaba lateralmente durante zoom
**Solución:**
- Calcular esquina BASE (sin escala): corner_x_base, corner_y_base
- Calcular centro FIJO basándose en base_width/base_height
- Calcular width/height escalados DESPUÉS (solo para vértices)
- Resultado: centro permanece fijo, zoom crece/decrece alrededor del centro
**Archivos modificados:**
- source/app_logo.cpp:
- Líneas 343-347: Eliminado log FADE_OUT inicial
- Línea 347: Eliminado log completado
- Líneas 365-366: Eliminado log alphas
- Líneas 381-383: Eliminado log ZOOM
- Líneas 396-398: Eliminado log ELASTIC
- Líneas 414-417: Eliminado log SPIRAL
- Líneas 444-446: Eliminado log BOUNCE
- Líneas 609-625: Reordenado cálculo de centro (FIJO) y tamaño (ESCALADO)
**Resultado esperado:**
- Sin spam en consola
- Animación ZOOM perfectamente centrada en esquina inferior derecha
- Logo crece/decrece sin desplazamiento lateral
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problema:**
- SDL_SetTextureAlphaMod() no funciona correctamente con SDL_RenderGeometry()
- El alpha de los vértices (hardcodeado a 1.0) overrideaba el alpha de textura
- Resultado: fade invisible o instantáneo a pesar de valores correctos
**Solución:**
- Eliminar SDL_SetTextureAlphaMod()
- Convertir alpha de 0-255 a 0.0-1.0 (alpha_normalized)
- Aplicar alpha_normalized directamente al canal alpha de los 4 vértices
- Ahora SDL_RenderGeometry respeta el fade correctamente
**Archivos modificados:**
- source/app_logo.cpp:
- Línea 630: Crear alpha_normalized en lugar de SetTextureAlphaMod
- Líneas 669, 680, 691, 702: Aplicar alpha_normalized a vértices
**Resultado esperado:**
- Fade visible y suave durante 2 segundos completos
- Logo 2 con retraso de 0.25s como esperado
- Sincronización perfecta entre animación y fade
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Restaura fade lineal para el alpha de los logos, eliminando el easing
que hacía la transición casi imperceptible. Renombra constante para
mayor claridad sobre lo que controla.
Problema identificado:
- El alpha usaba easeInOutQuad, acelerando la transición en el medio
- Con 2 segundos, el easing hacía que el logo pareciera aparecer/
desaparecer instantáneamente sin fade visible
- El usuario reportó "el logo termina y desaparece directamente"
Solución implementada:
1. **Fade lineal restaurado**:
- FADE_IN: alpha aumenta linealmente 0→255
- FADE_OUT: alpha disminuye linealmente 255→0
- Progreso visible y constante durante toda la duración
2. **Constante renombrada**:
- `APPLOGO_FADE_DURATION` → `APPLOGO_ANIMATION_DURATION`
- Nombre más claro: controla duración de toda la animación
- Actualizado valor a 2.0 segundos (configurable por usuario)
3. **Animaciones mantienen easing**:
- Zoom, rotación, squash, etc. siguen usando sus easings
- Solo el alpha es lineal para fade visible
Confirmaciones:
✅ Sistema time-based: usa delta_time correctamente
✅ Blend mode configurado: SDL_BLENDMODE_BLEND en todas las texturas
✅ Alpha se aplica: SDL_SetTextureAlphaMod en renderizado
Resultado con APPLOGO_ANIMATION_DURATION = 2.0s:
- t=0.0s → Alpha=0 (invisible)
- t=0.5s → Alpha=64 (25% visible)
- t=1.0s → Alpha=127 (50% visible)
- t=1.5s → Alpha=191 (75% visible)
- t=2.0s → Alpha=255 (100% visible)
Nota: El logo solo se muestra en modos DEMO/DEMO_LITE/LOGO,
no en SANDBOX. Para probar: ./vibe3_physics --mode demo
🤖 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>
Implementa sistema de sincronización elegante entre logos con efecto de
"seguimiento" y fade suavizado para eliminar desincronización visual.
Cambios principales:
1. **Animación sincronizada**: Ambos logos usan la MISMA animación
- Eliminadas 4 variables independientes (logo1/logo2 × entry/exit)
- Una sola variable `current_animation_` compartida
- Misma animación para entrada y salida (simetría)
2. **Retraso de Logo 2**: 0.25 segundos detrás de Logo 1
- Logo 1 empieza en t=0.00s
- Logo 2 empieza en t=0.25s
- Efecto visual de "eco" o "seguimiento"
3. **Alpha independiente con retraso**:
- `logo1_alpha_` y `logo2_alpha_` separados
- Logo 2 aparece/desaparece más tarde visualmente
4. **Easing en alpha** (NUEVO):
- Aplicado `easeInOutQuad()` al fade de alpha
- Elimina problema de "logo deformado esperando a desvanecerse"
- Sincronización visual perfecta entre animación y fade
- Curva suave: lento al inicio, rápido en medio, lento al final
Comportamiento resultante:
FADE_IN:
- t=0.00s: Logo 1 empieza (alpha con easing)
- t=0.25s: Logo 2 empieza (alpha con easing + retraso)
- t=0.50s: Logo 1 completamente visible
- t=0.75s: Logo 2 completamente visible
FADE_OUT:
- t=0.00s: Logo 1 empieza a desaparecer (misma animación)
- t=0.25s: Logo 2 empieza a desaparecer
- t=0.50s: Logo 1 completamente invisible
- t=0.75s: Logo 2 completamente invisible
Archivos modificados:
- source/defines.h: +APPLOGO_LOGO2_DELAY
- source/app_logo.h: Reestructuración de variables de animación/alpha
- source/app_logo.cpp: Implementación de retraso + easing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implementación de sistema de 2 logos superpuestos con animaciones completamente independientes:
**Nuevas características:**
- Dos logos superpuestos (logo1.png + logo2.png) con animaciones independientes
- 4 tipos de animación: ZOOM_ONLY, ELASTIC_STICK, ROTATE_SPIRAL, BOUNCE_SQUASH
- Aleatorización independiente para entrada y salida de cada logo
- 256 combinaciones posibles (4×4 entrada × 4×4 salida)
**Ajuste de tamaño y posición:**
- Nueva constante APPLOGO_HEIGHT_PERCENT (40%) - altura del logo respecto a pantalla
- Nueva constante APPLOGO_PADDING_PERCENT (10%) - padding desde esquina inferior-derecha
- Logo anclado a esquina en lugar de centrado en cuadrante
- Valores fácilmente ajustables mediante constantes en defines.h
**Cambios técnicos:**
- Variables duplicadas para logo1 y logo2 (scale, squash, stretch, rotation)
- Variables compartidas para sincronización (state, timer, alpha)
- renderWithGeometry() acepta parámetro logo_index (1 o 2)
- Logo1 renderizado primero (fondo), Logo2 encima (overlay)
🤖 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>
**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>
Implementado sistema genérico de particionamiento espacial reutilizable
que reduce drásticamente la complejidad del algoritmo de boids.
**MEJORA DE RENDIMIENTO ESPERADA:**
- Sin grid: 1000 boids = 1M comparaciones (1000²)
- Con grid: 1000 boids ≈ 9K comparaciones (~9 vecinos/celda)
- **Speedup teórico: ~100x en casos típicos**
**COMPONENTES IMPLEMENTADOS:**
1. **SpatialGrid genérico (spatial_grid.h/.cpp):**
- Divide espacio 2D en celdas de 100x100px
- Hash map para O(1) lookup de celdas
- queryRadius(): Busca solo en celdas adyacentes (máx 9 celdas)
- Reutilizable para colisiones ball-to-ball en física (futuro)
2. **Integración en BoidManager:**
- Grid poblado al inicio de cada frame (O(n))
- 3 reglas de Reynolds ahora usan queryRadius() en lugar de iterar TODOS
- Separación/Alineación/Cohesión: O(n) total en lugar de O(n²)
3. **Configuración (defines.h):**
- BOID_GRID_CELL_SIZE = 100.0f (≥ BOID_COHESION_RADIUS)
**CAMBIOS TÉCNICOS:**
- boid_manager.h: Añadido miembro spatial_grid_
- boid_manager.cpp: update() poblа grid, 3 reglas usan queryRadius()
- spatial_grid.cpp: 89 líneas de implementación genérica
- spatial_grid.h: 74 líneas con documentación exhaustiva
**PRÓXIMOS PASOS:**
- Medir rendimiento real con 1K, 5K, 10K boids
- Comparar FPS antes/después
- Validar que comportamiento es idéntico
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
BUG CRÍTICO ENCONTRADO:
La fuerza de cohesión NO estaba normalizada, causando atracción
tipo "gravedad" que hacía que los boids colapsaran a puntos.
CAUSA RAÍZ:
```cpp
// ANTES (INCORRECTO):
float steer_x = (center_of_mass_x - center_x) * WEIGHT * delta_time;
// Si center_of_mass está a 100px → fuerza = 100 * 0.5 * 0.016 = 0.8
// ¡FUERZA PROPORCIONAL A DISTANCIA! Como una gravedad newtoniana
```
SOLUCIÓN IMPLEMENTADA:
```cpp
// DESPUÉS (CORRECTO):
float distance = sqrt(dx*dx + dy*dy);
float steer_x = (dx / distance) * WEIGHT * delta_time;
// Fuerza siempre normalizada = 1.0 * WEIGHT * delta_time
// Independiente de distancia (comportamiento Reynolds correcto)
```
CAMBIOS:
1. boid_manager.cpp::applyCohesion() - Fase 1.4
- Normalizar dirección hacia centro de masa
- Fuerza constante independiente de distancia
- Check de división por cero (distance > 0.1f)
2. defines.h - Ajuste de parámetros tras normalización
- BOID_COHESION_WEIGHT: 0.5 → 0.001 (1000x menor)
* Ahora que está normalizado, el valor anterior era gigantesco
- BOID_MAX_SPEED: 3.0 → 2.5 (reducida para evitar velocidades extremas)
- BOID_MAX_FORCE: 0.5 → 0.05 (reducida 10x)
- BOID_MIN_SPEED: 0.5 → 0.3 (reducida)
- Radios restaurados a valores originales (30/50/80)
RESULTADO ESPERADO:
✅ Sin colapso a puntos (cohesión normalizada correctamente)
✅ Movimiento orgánico sin "órbitas" artificiales
✅ Velocidades controladas y naturales
✅ Balance correcto entre las 3 fuerzas
TESTING:
Por favor probar con 100 y 1000 boids:
- ¿Se mantienen dispersos sin colapsar?
- ¿Las órbitas han desaparecido?
- ¿El movimiento es más natural?
Estado: Compilación exitosa
Rama: boids_development
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
PROBLEMA RESUELTO:
Los boids colapsaban al mismo punto dentro de cada grupo, haciendo
el sistema visualmente inutilizable.
CAMBIOS IMPLEMENTADOS:
1. BOIDS_ROADMAP.md creado (NEW FILE)
- Roadmap completo de 6 fases para mejora de boids
- Diagnóstico detallado de problemas actuales
- Plan de implementación con métricas de éxito
- Fase 1 (crítica): Fix clustering
- Fase 2 (alto impacto): Spatial Hash Grid O(n²)→O(n)
- Fases 3-6: Mejoras visuales, comportamientos avanzados
2. defines.h - Rebalanceo de parámetros (Fase 1.1)
- BOID_SEPARATION_RADIUS: 30→25px
- BOID_COHESION_RADIUS: 80→60px (REDUCIDO 25%)
- BOID_SEPARATION_WEIGHT: 1.5→3.0 (TRIPLICADO)
- BOID_COHESION_WEIGHT: 0.8→0.5 (REDUCIDO 37%)
- BOID_MAX_FORCE: 0.1→0.5 (QUINTUPLICADO)
- BOID_MIN_SPEED: 0.5 (NUEVO - evita boids estáticos)
3. boid_manager.cpp - Mejoras físicas
- Fase 1.2: Velocidad mínima en limitSpeed()
* Evita boids completamente estáticos
* Mantiene movimiento continuo
- Fase 1.3: Fuerza de separación proporcional a cercanía
* Antes: dividir por distance² (muy débil)
* Ahora: proporcional a (RADIUS - distance) / RADIUS
* Resultado: 100% fuerza en colisión, 0% en radio máximo
RESULTADO ESPERADO:
✅ Separación domina sobre cohesión (peso 3.0 vs 0.5)
✅ Boids mantienen distancia personal (~10-15px)
✅ Sin colapso a puntos únicos
✅ Movimiento continuo sin boids estáticos
PRÓXIMOS PASOS:
- Testing manual con 100, 1000 boids
- Validar comportamiento disperso sin clustering
- Fase 2: Spatial Hash Grid para rendimiento O(n)
Estado: Compilación exitosa, listo para testing
Rama: boids_development
🤖 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>
Sistema de transiciones suaves (0.5s) entre temas:
- Funciona entre CUALQUIER combinación (estático↔estático, estático↔dinámico, dinámico↔dinámico)
- Sistema de snapshots: Captura estado del tema origen antes de cambiar
- LERP durante transición: snapshot → tema destino (colors, background, text)
- Duración configurable: THEME_TRANSITION_DURATION = 0.5s (defines.h)
Nuevo archivo:
- source/themes/theme_snapshot.h: Estructura para capturar estado de tema
Implementación:
- captureCurrentSnapshot(): Captura 50,000 colores de pelotas + fondo + texto
- switchToTheme(): Captura snapshot y configura transición LERP
- update(): Avanza transition_progress (0.0→1.0) y libera snapshot al completar
- getInterpolatedColor(): LERP entre snapshot y tema destino si transitioning
- getBackgroundColors(): LERP de colores de fondo (top/bottom degradado)
- getCurrentThemeTextColor(): LERP de color de texto UI
Características:
✅ Transiciones suaves en Numpad 1-0 (cambio directo de tema)
✅ Transiciones suaves en Tecla B (cycling entre todos los temas)
✅ Transiciones suaves en DEMO mode (tema aleatorio cada 8-12s)
✅ Temas dinámicos siguen animándose durante transición (morph animado)
✅ Memoria eficiente: snapshot existe solo durante 0.5s, luego se libera
Mejoras visuales:
- Cambios de tema ya no son instantáneos/abruptos
- Morphing suave de colores de pelotas (cada pelota hace LERP individual)
- Fade suave de fondo degradado (top y bottom independientes)
- Transición de color de texto UI
Performance:
- Snapshot capture: ~0.05ms (solo al cambiar tema)
- LERP per frame: ~0.01ms adicional durante 0.5s
- Impacto: Imperceptible (<1% CPU adicional)
🤖 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>
- 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>
- 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>
El threshold anterior (SHAPE_NEAR_THRESHOLD * scale_factor = 80px)
era demasiado estricto. Las pelotas con spring physics oscilan
constantemente y nunca alcanzaban el 75% de convergencia requerido,
impidiendo que el logo se formara completamente.
Cambios:
- defines.h: Nueva constante LOGO_CONVERGENCE_DISTANCE = 20.0px
- engine.cpp: Usar threshold fijo de 20px (en lugar de 80px)
Resultado: Las pelotas se consideran "convergidas" con más facilidad,
permitiendo que el logo alcance el 75-100% threshold y se forme
antes de ser interrumpido.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implementa sistema adaptativo que evita interrupciones prematuras
en resoluciones altas. El timing ahora se ajusta según convergencia
de partículas en lugar de usar intervalos fijos.
Cambios:
- Ball: getDistanceToTarget() para medir distancia a objetivo
- Engine: shape_convergence_, logo_convergence_threshold_ y tiempos escalados
- defines.h: LOGO_CONVERGENCE_MIN/MAX (75-100%)
- updateShape(): Cálculo de % de pelotas convergidas
- toggleShapeMode(): Genera threshold aleatorio al entrar en LOGO
- setState(): Escala logo_min/max_time con resolución (base 720p)
- updateDemoMode(): Dispara cuando (tiempo>=MIN AND convergencia>=threshold) OR tiempo>=MAX
Funcionamiento:
1. Al entrar a SHAPE en LOGO: threshold random 75-100%, tiempos escalados con altura
2. Cada frame: calcula % pelotas cerca de objetivo (shape_convergence_)
3. Dispara acción cuando: (tiempo>=MIN AND convergencia>=threshold) OR tiempo>=MAX
4. Resultado: En 720p funciona como antes, en 1440p espera convergencia real
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## 1. Sistema de Estados AppMode (MANUAL/DEMO/DEMO_LITE/LOGO)
**engine.h:**
- Creado enum AppMode con 4 estados mutuamente excluyentes
- Reemplazadas 4 flags booleanas por 2 variables de estado:
* current_app_mode_: Modo actual
* previous_app_mode_: Para restaurar al salir de LOGO
- Añadido método setState() para gestión centralizada
**engine.cpp:**
- Implementado setState() con configuración automática de timers
- Actualizado updateDemoMode() para usar current_app_mode_
- Actualizado handleEvents() para teclas D/L/K con setState()
- Actualizadas todas las referencias a flags antiguas (8 ubicaciones)
- enterLogoMode/exitLogoMode usan setState()
**Comportamiento:**
- Teclas D/L/K ahora desactivan otros modos automáticamente
- Al salir de LOGO vuelve al modo previo (DEMO/DEMO_LITE/MANUAL)
## 2. Ajuste Ratio DEMO:LOGO = 6:1
**defines.h:**
- Probabilidad DEMO→LOGO: 15% → 5% (más raro)
- Probabilidad DEMO_LITE→LOGO: 10% → 3%
- Probabilidad salir de LOGO: 25% → 60% (sale rápido)
- Intervalos LOGO: 4-8s → 3-5s (más corto que DEMO)
**Resultado:** DEMO pasa 6x más tiempo activo que LOGO
## 3. Fix PNG_SHAPE no hace flip en modo LOGO
**Bugs encontrados:**
1. next_idle_time_ inicializado a 5.0s (hardcoded) > intervalos LOGO (3-5s)
2. toggleShapeMode() recrea PNG_SHAPE → pierde is_logo_mode_=true
**Soluciones:**
**png_shape.cpp (constructor):**
- Inicializa next_idle_time_ con PNG_IDLE_TIME_MIN/MAX (no hardcoded)
**png_shape.h:**
- Añadidos includes: defines.h, <cstdlib>
- Flag is_logo_mode_ para distinguir MANUAL vs LOGO
- Expandido setLogoMode() para recalcular next_idle_time_ con rangos apropiados
- PNG_IDLE_TIME_MIN_LOGO/MAX_LOGO: 2.5-4.5s (ajustable en defines.h)
**engine.cpp (toggleShapeMode):**
- Detecta si vuelve a SHAPE en modo LOGO con PNG_SHAPE
- Restaura setLogoMode(true) después de recrear instancia
**defines.h:**
- PNG_IDLE_TIME_MIN/MAX = 0.5-2.0s (modo MANUAL)
- PNG_IDLE_TIME_MIN_LOGO/MAX_LOGO = 2.5-4.5s (modo LOGO)
**Resultado:** PNG_SHAPE ahora hace flip cada 2.5-4.5s en modo LOGO (visible antes de toggles)
## 4. Nuevas Texturas
**data/balls/big.png:** 16x16px (añadida)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
MODO LOGO (Easter Egg):
- Modo especial que muestra logo JAILGAMES como "marca de agua"
- Activación manual: tecla K (perpetuo, no sale automáticamente)
- Auto-salto desde DEMO/DEMO_LITE (15%/10% probabilidad, ≥500 pelotas)
- Configuración fija: PNG_SHAPE + tiny texture + MONOCHROME + 120% escala + 5000 pelotas
- Sistema de 5 acciones variadas con probabilidades ajustadas:
* SHAPE→PHYSICS gravedad ON (50%) - caída dramática
* SHAPE→PHYSICS gravedad OFF (50%) - ver rotaciones sin caer
* PHYSICS→SHAPE (60%) - reconstruir logo y mostrar rotaciones
* PHYSICS: forzar gravedad ON (20%) - caer mientras da vueltas
* PHYSICS: forzar gravedad OFF (20%) - flotar mientras da vueltas
- Intervalos 4-8s (aumentado para completar ciclos de rotación PNG_SHAPE)
- Textos informativos suprimidos en Logo Mode
- Corrección cambio de textura: actualiza texture_ y setTexture() en pelotas
- PNG_SHAPE idle reducido a 0.5-2s para animación más dinámica
MEJORAS FÍSICAS GLOBALES:
- Impulso automático al quitar gravedad si >50% pelotas en superficie
- Usa isOnSurface() para detectar pelotas quietas (DEMO/DEMO_LITE/LOGO)
- Evita que quitar gravedad con pelotas paradas no haga nada visible
SISTEMA AUTOMÁTICO DE CURSOR:
- Importado mouse.h/mouse.cpp desde Coffee Crisis Arcade Edition
- Auto-oculta cursor tras 3s de inactividad (namespace Mouse)
- Reaparece inmediatamente al mover ratón
- Funciona en todos los modos (ventana, fullscreen F3, real fullscreen F4)
- Eliminadas llamadas manuales SDL_ShowCursor/HideCursor
- Soluciona bug: cursor visible al iniciar con argumento -f
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
REFACTORING:
- Movido array de escenarios desde engine.h a defines.h
- Nombre más descriptivo: test_ → BALL_COUNT_SCENARIOS
- Ahora es constexpr y accesible globalmente
MEJORA PNG_SHAPE:
- Priorizar calidad 2D sobre profundidad 3D
- Reducir capas AGRESIVAMENTE hasta 1 (antes se detenía en 3)
- Condiciones más estrictas: < total (antes < total * 0.8)
- Vértices activados hasta 150 pelotas (antes 100)
FILOSOFÍA NUEVA:
1. Reducir capas hasta 1 (llenar bien el texto en 2D)
2. Si no alcanza: filas alternas en relleno
3. Si no alcanza: cambiar a bordes
4. Si no alcanza: filas alternas en bordes
5. Último recurso: vértices
RESULTADO ESPERADO:
- 500 pelotas: RELLENO completo 1 capa (texto lleno, sin 3D)
- 100 pelotas: BORDES completos 1 capa (todo visible)
- 50 pelotas: VÉRTICES (esqueleto visible)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problema:
- PNG_USE_EDGES_ONLY = false usa ~22,000 puntos 3D
- Con 1, 10 o 100 pelotas, no hay suficientes para formar el logo
- Resultado: logo invisible o mal formado
Solución:
- Detectar automáticamente si num_pelotas < total_puntos / 2
- Si hay pocas pelotas → cambiar automáticamente a BORDES
- Bordes usa ~300 puntos × 15 capas = ~4,500 puntos 3D
- Mucho mejor ratio para pocos sprites
Implementación:
- generatePoints() verifica ratio pelotas/puntos
- Si insuficiente: llama detectEdges() y regenera
- getPoint3D() usa edge_points_ si están disponibles
- Mensajes informativos en consola
Ahora funciona:
Escenario 1 (1 pelota) → Auto-switch a bordes ✅
Escenario 2 (10 pelotas) → Auto-switch a bordes ✅
Escenario 5 (1000 pelotas) → Usa relleno completo ✅
Escenario 6+ (10K+ pelotas) → Usa relleno completo ✅
Output de debug muestra:
[PNG_SHAPE] Advertencia: Solo X pelotas para Y puntos
[PNG_SHAPE] Cambiando automáticamente a BORDES...
[PNG_SHAPE] Modo: BORDES/RELLENO
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Nuevas Características:
- PNG_SHAPE (tecla O): Logo JAILGAMES desde PNG 1-bit
- Extrusión 2D con detección de bordes/relleno configurable
- Rotación "legible": 90% frente, 10% volteretas aleatorias
- 15 capas de extrusión con relleno completo (22K+ puntos 3D)
- Fix: Z forzado a máximo cuando está de frente (brillante)
- Excluido de DEMO/DEMO_LITE (logo especial)
- Sistema de texturas dinámicas
- Carga automática desde data/balls/*.png
- normal.png siempre primero, resto alfabético
- Tecla N cicla entre todas las texturas encontradas
- Display dinámico del nombre (uppercase)
- Física mejorada para figuras 3D
- Constantes SHAPE separadas de ROTOBALL
- SHAPE_SPRING_K=800 (+167% rigidez vs ROTOBALL)
- SHAPE_DAMPING_NEAR=150 (+88% absorción)
- Pelotas mucho más "pegadas" durante rotaciones
- applyRotoBallForce() acepta parámetros personalizados
Archivos:
- NEW: source/shapes/png_shape.{h,cpp}
- NEW: data/shapes/jailgames.png
- NEW: data/balls/{normal,small,tiny}.png
- MOD: defines.h (constantes PNG + SHAPE physics)
- MOD: engine.cpp (carga dinámica texturas + física SHAPE)
- MOD: ball.{h,cpp} (parámetros física configurables)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes:
1. F5 ahora cicla correctamente entre 3 modos de escalado:
- INTEGER: Escalado entero con barras negras (píxel perfecto)
- LETTERBOX: Zoom hasta llenar una dimensión
- STRETCH: Estirar pantalla completa
2. Artefactos de renderizado en barras negras resueltos:
- SDL_RenderClear() ahora usa color negro
- Barras letterbox/integer se muestran negras correctamente
3. Texto duplicado de tema resuelto:
- Durante LERP, verifica tema actual Y destino
- Evita mostrar segunda línea si text_ es nombre de tema
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Sistema de múltiples texturas:
- Carga ball.png (10x10) y ball_small.png (6x6) al inicio
- Variable current_ball_size_ obtiene tamaño desde texture->getWidth()
- Eliminar constante BALL_SIZE hardcoded
Cambio de tamaño con ajuste de posiciones:
- updateBallSizes() ajusta pos según gravedad y superficie
- DOWN: mueve Y hacia abajo si crece
- UP: mueve Y hacia arriba si crece
- LEFT/RIGHT: mueve X correspondiente
- Solo ajusta pelotas en superficie (isOnSurface())
Ball class actualizada:
- Constructor recibe ball_size como parámetro
- updateSize(new_size): actualiza hitbox y sprite
- setTexture(texture): cambia textura del sprite
- setPosition() usa setRotoBallScreenPosition()
Sprite class:
- Añadido setTexture() inline para hot-swap
Tecla N:
- Cicla entre texturas disponibles
- Actualiza todas las pelotas sin reiniciar física
- Texto informativo "SPRITE: NORMAL" / "SPRITE: SMALL"
Fix bug initBalls():
- Ahora usa current_ball_size_ en constructor
- Pelotas nuevas tienen tamaño correcto según textura activa
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fondo negro degradado (similar a NEON)
- 8 tonos de gris: blanco puro a gris muy oscuro
- Estética minimalista monocromática
- Ciclo con tecla B incluye nuevo tema
- Actualizado README con documentación
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Nueva clase AtomShape con núcleo central + 3 órbitas
- Núcleo: esfera pequeña con distribución Fibonacci
- Órbitas: planos inclinados con electrones animados
- Rotación global + rotación orbital independiente
- Modelo atómico clásico de Bohr
- Compatible con física spring-damper y z-sorting
✅ TODAS LAS 8 FIGURAS 3D IMPLEMENTADAS:
Q-Sphere, W-WaveGrid, E-Helix, R-Torus, T-Cube, Y-Cylinder, U-Icosahedron, I-Atom
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Nueva clase IcosahedronShape con 12 vértices golden ratio
- Vértices basados en 3 rectángulos áureos ortogonales
- Subdivisión de caras para más de 12 puntos
- Rotación triple simultánea (X, Y, Z)
- Proyección a esfera circunscrita
- Compatible con física spring-damper y z-sorting
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Nueva clase CylinderShape con ecuaciones paramétricas
- Distribución uniforme en anillos y circunferencia
- Rotación simple en eje Y
- Dimensiones: radius=0.25, height=0.5 (proporción altura)
- Compatible con física spring-damper y z-sorting
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Nueva clase TorusShape con ecuaciones paramétricas
- Distribución uniforme en anillos y puntos por anillo
- Rotación triple simultánea (X, Y, Z)
- Radios: major=0.25, minor=0.12 (proporción altura)
- Compatible con física spring-damper y z-sorting
- Escalable con Numpad +/-
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Nueva clase WaveGridShape con ecuaciones de onda 2D
- Grid adaptativo según número de pelotas (1-N puntos)
- Ecuación: z = A*sin(kx*x + phase)*cos(ky*y + phase)
- Rotación lenta en Y + animación de fase rápida
- Compatible con física spring-damper y z-sorting
- Escalable con Numpad +/-
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Nueva clase HelixShape con ecuaciones paramétricas
- Distribución uniforme en 3 vueltas completas
- Rotación en eje Y + animación de fase vertical
- Pitch ajustado a 0.25 para evitar clipping (180px altura total)
- Compatible con física spring-damper y z-sorting
- Escalable con Numpad +/-
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>