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>
Problema:
- Radio fijo de 80px funcionaba bien en 320x240
- En F4 fullscreen (1920x1080), radio de 360px era correcto visualmente
- PERO las fuerzas físicas (spring_k, damping) seguían siendo para 80px
- Resultado: pelotas nunca llegaban a pegarse en resoluciones altas
Solución:
1. Radio proporcional a altura de pantalla (ROTOBALL_RADIUS_FACTOR = 0.333)
2. Escalar TODAS las constantes de física proporcionalmente al radio
3. Fórmula: scale = sphere_radius / BASE_RADIUS (80px)
Cambios técnicos:
- defines.h: ROTOBALL_RADIUS → ROTOBALL_RADIUS_FACTOR (0.333)
- engine.cpp: Calcular radius dinámicamente en generate/update
- ball.h: applyRotoBallForce() ahora recibe sphere_radius
- ball.cpp: Escalar spring_k, damping_base, damping_near, near_threshold, max_force
Resultado:
- 320x240: Radio 80px, scale=1.0 (idéntico a antes)
- 640x480: Radio 160px, scale=2.0 (fuerzas 2x)
- 1920x1080: Radio 360px, scale=4.5 (fuerzas 4.5x)
Comportamiento físico IDÉNTICO en todas las resoluciones ✅🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Incrementar constantes de damping para lograr amortiguamiento crítico
y eliminar oscilaciones durante la convergencia a la esfera RotoBall.
## Cambios
**defines.h - Nuevos valores:**
- `ROTOBALL_DAMPING_BASE`: 15.0 → 35.0 (+133%)
- Amortiguamiento crítico calculado: c ≈ 2*√(k*m) = 2*√(300*1) ≈ 34.64
- `ROTOBALL_DAMPING_NEAR`: 50.0 → 80.0 (+60%)
- Absorción rápida cuando están cerca del punto
## Problema Resuelto
**Antes (subdamped):**
- Las pelotas oscilaban varias veces antes de estabilizarse
- Sobrepasaban el punto destino repetidamente
- Convergencia lenta con "rebotes" visuales
**Ahora (critically damped):**
- Las pelotas convergen directamente sin oscilar
- Se "pegan" suavemente a la esfera
- Absorción rápida y visualmente limpia
## Teoría
Sistema masa-resorte-amortiguador:
- Subdamped (c < 2√km): Oscila antes de estabilizar
- Critically damped (c = 2√km): Converge rápido sin oscilar
- Overdamped (c > 2√km): Converge muy lento
Valores ajustados para estar en el punto crítico, logrando
la convergencia más rápida posible sin oscilación.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Reemplazar interpolación lineal artificial por sistema de fuerzas físicamente
realista usando Ley de Hooke con amortiguación variable.
## Cambios Principales
### Sistema de Física Implementado
**Fuerza de Resorte (Hooke's Law):**
```cpp
F_spring = k * (target - position)
F_damping = c * velocity
F_total = F_spring - F_damping
acceleration = F_total / mass
```
**Constantes (defines.h):**
- `ROTOBALL_SPRING_K = 300.0f`: Rigidez del resorte
- `ROTOBALL_DAMPING_BASE = 15.0f`: Amortiguación lejos del punto
- `ROTOBALL_DAMPING_NEAR = 50.0f`: Amortiguación cerca (estabilización)
- `ROTOBALL_NEAR_THRESHOLD = 5.0f`: Distancia considerada "cerca"
- `ROTOBALL_MAX_FORCE = 1000.0f`: Límite de seguridad
### Nuevas Funciones (Ball class)
- `enableRotoBallAttraction(bool)`: Activa/desactiva atracción física
- `applyRotoBallForce(target_x, target_y, deltaTime)`: Aplica fuerza de resorte
### Comportamiento Físico
**Al entrar (PHYSICS → ROTOBALL):**
1. Pelotas mantienen velocidad actual (vx, vy)
2. Fuerza de atracción las acelera hacia puntos en esfera rotante
3. Amortiguación variable evita oscilaciones infinitas
4. Convergen al punto con aceleración natural
**Durante RotoBall:**
- Punto destino rota constantemente
- Fuerza se recalcula cada frame hacia posición rotada
- Pelotas "persiguen" su punto móvil
- Efecto: Convergencia orgánica con ligera oscilación
**Al salir (ROTOBALL → PHYSICS):**
1. Atracción se desactiva
2. Pelotas conservan velocidad tangencial actual
3. Gravedad vuelve a aplicarse
4. Caen con la inercia que traían de la esfera
### Archivos Modificados
- `defines.h`: 5 nuevas constantes físicas
- `ball.h/cpp`: Sistema de resorte completo
- `engine.cpp`: Enable/disable atracción en toggle, updateRotoBall() usa física
- `CLAUDE.md`: Documentación técnica completa
## Ventajas del Sistema
✅ Física realista con conservación de momento
✅ Transición orgánica (no artificial)
✅ Inercia preservada entrada/salida
✅ Amortiguación automática (no oscila infinito)
✅ Constantes ajustables para tuning
✅ Performance: O(1) por pelota
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Añadido modo alternativo de simulación que transforma las pelotas en una
esfera 3D rotante proyectada en 2D, inspirado en efectos clásicos de demoscene.
## Características Principales
- **Algoritmo Fibonacci Sphere**: Distribución uniforme de puntos en esfera 3D
- **Rotación dual**: Matrices de rotación en ejes X e Y simultáneos
- **Profundidad Z simulada**: Color modulation según distancia (oscuro=lejos, brillante=cerca)
- **Transición suave**: Interpolación de 1.5s desde física a esfera
- **Sin sprites adicionales**: Usa SDL_SetTextureColorMod para profundidad
- **Performance optimizado**: >60 FPS con 100,000 pelotas
## Implementación Técnica
### Nuevos Archivos/Cambios:
- `defines.h`: Enum SimulationMode + constantes RotoBall (radio, velocidades, brillo)
- `ball.h/cpp`: Soporte 3D (pos_3d, target_2d, depth_brightness, setters)
- `engine.h/cpp`: Lógica completa RotoBall (generate, update, toggle)
- `generateRotoBallSphere()`: Fibonacci sphere algorithm
- `updateRotoBall()`: Rotación 3D + proyección ortográfica
- `toggleRotoBallMode()`: Cambio entre PHYSICS/ROTOBALL
- `README.md`: Documentación completa del modo
- `CLAUDE.md`: Detalles técnicos y algoritmos
## Parámetros Configurables (defines.h)
```cpp
ROTOBALL_RADIUS = 80.0f; // Radio de la esfera
ROTOBALL_ROTATION_SPEED_Y = 1.5f; // Velocidad rotación eje Y (rad/s)
ROTOBALL_ROTATION_SPEED_X = 0.8f; // Velocidad rotación eje X (rad/s)
ROTOBALL_TRANSITION_TIME = 1.5f; // Tiempo de transición (segundos)
ROTOBALL_MIN_BRIGHTNESS = 50; // Brillo mínimo fondo (0-255)
ROTOBALL_MAX_BRIGHTNESS = 255; // Brillo máximo frente (0-255)
```
## Uso
- **Tecla C**: Alternar entre modo física y modo RotoBall
- Compatible con todos los temas de colores
- Funciona con 1-100,000 pelotas
- Debug display muestra "MODE PHYSICS" o "MODE ROTOBALL"
## Performance
- Batch rendering: Una sola llamada SDL_RenderGeometry
- Fibonacci sphere recalculada por frame (O(n) predecible)
- Color mod CPU-side sin overhead GPU
- Delta time independiente del framerate
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Renombrar pushUpBalls() → pushBallsAwayFromGravity() con lógica direccional
- ESPACIO ahora impulsa en dirección opuesta a gravedad actual:
* Gravedad DOWN → impulsa ARRIBA (comportamiento original)
* Gravedad UP → impulsa ABAJO
* Gravedad LEFT → impulsa DERECHA
* Gravedad RIGHT → impulsa IZQUIERDA
- Corregir modVel() para aplicar impulso horizontal a todas las pelotas
- Actualizar documentación de controles en README.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Añadido nuevo tema RGB con fondo blanco puro y colores matemáticos
- Actualizado README.md con controles actuales y nuevas características
- Reorganizada documentación de controles por categorías
- Corregida información obsoleta (resolución, temas, problemas)
- Añadido control KP_5 para selección directa del tema RGB
- Mejorada visibilidad del texto adaptando colores por tema
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
MODO REAL FULLSCREEN (F4):
- F4: Cambia resolución interna a resolución nativa del escritorio
- Pelotas usan dimensiones dinámicas del terreno de juego
- Interfaz se adapta automáticamente (texto, debug, gradiente)
- Reinicio automático de escena con nuevas dimensiones
RESOLUCIÓN DINÁMICA:
- Ball constructor acepta screen_width/height como parámetros
- Colisiones usan dimensiones dinámicas en lugar de constantes
- Spawn de pelotas usa margen configurable (BALL_SPAWN_MARGIN)
- Toda la interfaz se adapta a resolución actual
MODOS FULLSCREEN MUTUAMENTE EXCLUYENTES:
- F3 (fullscreen normal) y F4 (real fullscreen) se desactivan mutuamente
- F1/F2 (zoom) bloqueados durante cualquier modo fullscreen
- Sin estados mixtos que rompan el renderizado
- Transiciones seguras entre todos los modos
MEJORAS DE CONFIGURACIÓN:
- BALL_SPAWN_MARGIN: margen lateral configurable para spawn de pelotas
- Resolución base actualizada a 640x360 (16:9)
- Spawn margin reducido a 15% para mayor dispersión
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Comentar todas las constantes, structs y enums
- Organizar en secciones: ventana, zoom, física, interfaz, rebotes, masa
- Documentar valores y unidades (píxeles, ms, factores)
- Explicar propósito de cada tema de colores
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Agregar zoom dinámico de ventana con F1/F2
- F1: reducir zoom hasta 1x mínimo
- F2: aumentar zoom hasta máximo basado en resolución
- Centrado inteligente al cambiar zoom
- Cálculo correcto de zoom máximo usando SDL_GetCurrentDisplayMode()
- F3: toggle fullscreen entre ventana y pantalla completa
- Mover temas de colores de F1-F4 a teclado numérico (KP_1-4)
- Mejorar resolución base a 640x480 con zoom inicial 2x
- Resolver paths absolutos para data/ball.png
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
🎯 Problema solucionado:
- Pelotas se movían en líneas perfectas al cambiar gravedad
- Todas llegaban exactamente una encima de otra
✨ Solución implementada:
- Empuje lateral aleatorio muy sutil (2.4-4.8 px/s)
- Se aplica automáticamente al cambiar dirección de gravedad
- Perpendicular a la gravedad: UP/DOWN → empuje X, LEFT/RIGHT → empuje Y
🔧 Implementación técnica:
- Nueva función Ball::applyRandomLateralPush()
- Integrada en Engine::changeGravityDirection()
- Velocidades ajustadas para ser apenas perceptibles
📊 Valores finales:
- GRAVITY_CHANGE_LATERAL_MIN = 0.04f (2.4 px/s)
- GRAVITY_CHANGE_LATERAL_MAX = 0.08f (4.8 px/s)
- Rango: ~3-5 píxeles en 1 segundo (muy sutil)
🎮 Resultado:
- Rompe la simetría perfecta sin crear caos
- Movimiento más natural y orgánico
- Mantiene la física realista
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
✨ Nueva característica:
- Cada pelota tiene un factor de masa único que afecta la gravedad
- Simula pelotas de diferente peso/densidad para física más realista
🔧 Implementación:
- GRAVITY_MASS_MIN = 0.7f (pelotas ligeras - 70% gravedad)
- GRAVITY_MASS_MAX = 1.3f (pelotas pesadas - 130% gravedad)
- Factor aleatorio generado por pelota en initBalls()
- Gravedad efectiva = gravity_force × mass_factor × deltaTime
📊 Comportamiento:
- Pelotas ligeras (0.7): Caen 30% más lento, efecto "flotante"
- Pelotas pesadas (1.3): Caen 30% más rápido, más "densas"
- Variabilidad continua entre 0.7-1.3 para cada pelota
🎯 Resultado visual:
- FIN del problema: "todas llegan al mismo tiempo"
- Ahora las pelotas llegan escalonadas al cambiar gravedad
- Física más realista y visualmente interesante
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>