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>
Cambio de comportamiento:
- ANTES: Notificaciones en cola FIFO esperaban turno secuencialmente
- AHORA: Solo se muestra la última notificación inmediatamente
Implementación:
- show() destruye notificación actual con current_notification_.reset()
- Vacía cola completa descartando notificaciones pendientes
- Activa nueva notificación inmediatamente sin esperar en cola
Resultado:
- ✅ Usuario pulsa 5 teclas rápido → Solo ve la 5ª notificación
- ✅ Feedback inmediato → Nueva reemplaza vieja instantáneamente
- ✅ Sin esperas → Siempre información actualizada
- ✅ UX mejorada → Respuesta inmediata a última acción del usuario
🤖 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>
Problema:
- cycleTheme() cambiaba current_theme_index_ directamente
- Se saltaba todo el sistema de transición LERP
- Resultado: Cambio instantáneo/abrupto con tecla B
Solución:
- cycleTheme() ahora delega a switchToTheme(next_index)
- switchToTheme() maneja snapshot + transición automáticamente
- Resultado: Transición suave de 0.5s con tecla B ✅
Ahora TODAS las formas de cambiar tema tienen LERP:
✅ Numpad 1-0: Transición suave
✅ Tecla B: Transición suave (FIXED)
✅ DEMO mode: Transición suave
🤖 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>
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>
- Crear icono oficial de ViBe3 Physics (icon.afdesign + exportaciones)
- Generar icon.png, icon.ico (Windows), icon.icns (macOS)
- Eliminar coffee.res (archivo residual sin uso de proyecto anterior)
🤖 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>
**Inicialización en main.cpp:**
- Llamada a Texture::initResourceSystem("resources.pack") antes de Engine
- Intenta cargar pack, si falla usa fallback a disco automáticamente
**Fix normalizePath:**
- Mejorado para extraer rutas relativas desde paths absolutos
- Busca "data/" en cualquier parte del path y extrae lo siguiente
- Convierte "C:/Users/.../data/balls/big.png" → "balls/big.png"
**Tests realizados (3/3 exitosos):**
✅ TEST 1 - Sin pack (solo disco):
- Output: "resources.pack no encontrado - usando carpeta data/"
- Carga: 4 texturas desde disco
- Resultado: Funciona perfectamente en modo desarrollo
✅ TEST 2 - Con pack completo (5 recursos):
- Output: "resources.pack cargado (5 recursos)"
- Carga: 4 texturas desde pack
- Resultado: Sistema de pack funcionando al 100%
✅ TEST 3 - Híbrido (pack parcial con 2 recursos):
- Output: "resources.pack cargado (2 recursos)"
- Carga: big.png y small.png desde pack
- Fallback: normal.png y tiny.png desde disco
- Resultado: Sistema de fallback perfecto
**Sistema completo y funcional:**
- ✅ Carga desde pack cuando existe
- ✅ Fallback automático a disco por archivo
- ✅ Modo híbrido (mix pack + disco)
- ✅ Desarrollo (sin pack) sin cambios
🤖 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>
Sistema completo de packaging para distribuir ViBe3 Physics:
**Core - ResourcePack:**
- source/resource_pack.{h,cpp}: Clase para empaquetar/desempaquetar recursos
- Header binario "VBE3" con índice de archivos
- Encriptación XOR simple para ofuscar contenido
- Checksums para verificar integridad
**Integración en Texture:**
- source/external/texture.cpp: Carga desde pack con fallback a disco
- Método estático Texture::initResourceSystem()
- 1. Intenta cargar desde resources.pack
- 2. Si falla, carga desde carpeta data/ (modo desarrollo)
**Herramienta de empaquetado:**
- tools/pack_resources.cpp: Herramienta CLI para generar .pack
- tools/README.md: Documentación actualizada para ViBe3
- make pack_tool: Compila herramienta
- make resources.pack: Genera pack desde data/
**Sistema de releases (Makefile):**
- Makefile: Adaptado de Coffee Crisis a ViBe3 Physics
- Targets: windows_release, macos_release, linux_release
- APP_SOURCES actualizado con archivos de ViBe3
- Variables: TARGET_NAME=vibe3_physics, APP_NAME="ViBe3 Physics"
- Elimina carpeta config (no usada en ViBe3)
**Recursos de release:**
- release/vibe3.rc: Resource file para Windows (icono)
- release/Info.plist: Bundle info para macOS (.app)
- release/icon.{ico,icns,png}: Iconos multiplataforma
- release/frameworks/SDL3.xcframework: Framework macOS
- release/SDL3.dll: DLL Windows
- release/create_icons.py: Script generador de iconos
**Resultado:**
- resources.pack generado (5 recursos, ~1.3KB)
- Compila correctamente con CMake
- Listo para make windows_release / macos_release
🤖 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>
Cambios:
1. **PNG_SHAPE pivoteo sutil** (similar a WAVE_GRID):
- Añadidas variables tilt_x_ y tilt_y_ en png_shape.h
- Actualización continua de tilt en update()
- Aplicación de pivoteo en getPoint3D() con:
* Cálculo correcto de logo_size para normalización
* Normalización a rango [-1, 1] usando logo_size * 0.5
* Amplitudes 0.15 y 0.1 (matching WAVE_GRID)
* z_tilt proporcional al tamaño del logo
- Fix crítico: usar z_base en lugar de z fijo (línea 390)
2. **Eliminación de debug output**:
- Removidos 13 std::cout de png_shape.cpp
- Removidos 2 std::cout de engine.cpp (Logo Mode)
- Consola ahora limpia sin mensajes [PNG_SHAPE]
🤖 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>
Sistema de optimización en 3 niveles para cualquier cantidad de pelotas:
Nivel 1 - Cambio de modo:
- Si RELLENO y pocas pelotas → switch a BORDES
- Reduce de ~22K puntos a ~4.5K puntos
Nivel 2 - Reducción de capas:
- Si aún insuficiente → dividir capas a la mitad
- 15 capas → 7 capas → 3 capas → 1 capa
- Reduce profundidad pero mantiene forma visible
Nivel 3 - Sampling de píxeles:
- Si aún insuficiente → tomar cada N píxeles
- Sampling 1/2, 1/3, 1/4... hasta 1/10
- Funciona en BORDES o RELLENO
- Mantiene forma general con menos detalle
Resultado:
- Con 1 pelota: Funciona (1 píxel visible) ✅
- Con 10 pelotas: Forma reconocible ✅
- Con 100 pelotas: Forma clara ✅
- Con 1000+ pelotas: Relleno completo ✅
Output informativo:
[PNG_SHAPE] Paso 1: Cambiando RELLENO → BORDES
[PNG_SHAPE] Paso 2: Reduciendo capas a 3
[PNG_SHAPE] Paso 3: Aplicando sampling 1/4
[PNG_SHAPE] === CONFIGURACIÓN FINAL ===
[PNG_SHAPE] Modo: BORDES (optimizado)
[PNG_SHAPE] Píxeles 2D: 75 (sampling 1/4)
[PNG_SHAPE] Capas: 3
[PNG_SHAPE] Ratio: 1.33 pelotas/punto ✅🤖 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>
Problema:
- Al usar -w/-h, la ventana se creaba correcta
- Pero el renderizado interno seguía usando SCREEN_WIDTH/HEIGHT (320x240)
- Resultado: ventana grande con área de juego pequeña en esquina
Solución:
- Añadidas variables base_screen_width/height_
- Guardan resolución configurada por CLI (o default)
- current_screen_* ahora se inicializa con valores base
- toggleRealFullscreen() restaura a resolución base, no constantes
Cambios:
- engine.h: Añadir base_screen_width/height_
- engine.cpp: Inicializar con valores CLI
- engine.cpp: Usar base_* al salir de fullscreen real
Ahora funciona:
./vibe3_physics -w 1920 -h 1080 # Renderiza en 1920x1080 ✅
./vibe3_physics # Renderiza en 1280x720 ✅🤖 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>
- **CylinderShape**: Añadido sistema de tumbling ocasional
- Volteretas de 90° en eje X cada 3-5 segundos
- Interpolación suave con ease-in-out (1.5s)
- Rotación Y continua + tumbling X ocasional = más dinámico
- **WaveGridShape**: Reemplazada rotación por pivoteo sutil
- Eliminada rotación completa en eje Y
- Pivoteo en esquinas (oscilación lenta 0.3/0.5 rad/s)
- Grid paralelo a pantalla con efecto "sábana ondeando"
- Ondas sinusoidales + pivoteo = movimiento más orgánico
🤖 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>