From ef2f5bea01fd24f53d5c5944ebac150887b297b0 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 5 Oct 2025 01:25:55 +0200 Subject: [PATCH] =?UTF-8?q?Sistema=20de=20convergencia=20para=20LOGO=20MOD?= =?UTF-8?q?E=20(resoluci=C3=B3n=20escalable)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- source/ball.cpp | 9 ++++ source/ball.h | 1 + source/defines.h | 8 +++- source/engine.cpp | 109 +++++++++++++++++++++++++++++++++------------- source/engine.h | 6 +++ 5 files changed, 100 insertions(+), 33 deletions(-) diff --git a/source/ball.cpp b/source/ball.cpp index be8acc3..82232b1 100644 --- a/source/ball.cpp +++ b/source/ball.cpp @@ -303,6 +303,15 @@ void Ball::enableRotoBallAttraction(bool enable) { } } +// Obtener distancia actual al punto objetivo (para calcular convergencia) +float Ball::getDistanceToTarget() const { + if (!rotoball_attraction_active_) return 0.0f; + + float dx = target_x_ - pos_.x; + float dy = target_y_ - pos_.y; + return sqrtf(dx * dx + dy * dy); +} + // Aplicar fuerza de resorte hacia punto objetivo en figuras 3D void Ball::applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime, float spring_k_base, float damping_base_base, float damping_near_base, diff --git a/source/ball.h b/source/ball.h index 2f1bcc0..172f8c8 100644 --- a/source/ball.h +++ b/source/ball.h @@ -91,6 +91,7 @@ class Ball { // Sistema de atracción física hacia figuras 3D void enableRotoBallAttraction(bool enable); + float getDistanceToTarget() const; // Distancia actual al punto objetivo void applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime, float spring_k = ROTOBALL_SPRING_K, float damping_base = ROTOBALL_DAMPING_BASE, diff --git a/source/defines.h b/source/defines.h index bbff4a6..e86dfef 100644 --- a/source/defines.h +++ b/source/defines.h @@ -211,10 +211,14 @@ constexpr int DEMO_LITE_WEIGHT_IMPULSE = 10; // Aplicar impulso (10%) // Configuración de Modo LOGO (easter egg - "marca de agua") constexpr int LOGO_MODE_MIN_BALLS = 500; // Mínimo de pelotas para activar modo logo constexpr float LOGO_MODE_SHAPE_SCALE = 1.2f; // Escala de figura en modo logo (120%) -constexpr float LOGO_ACTION_INTERVAL_MIN = 3.0f; // Tiempo mínimo entre alternancia SHAPE/PHYSICS -constexpr float LOGO_ACTION_INTERVAL_MAX = 5.0f; // Tiempo máximo entre alternancia SHAPE/PHYSICS (más corto que DEMO) +constexpr float LOGO_ACTION_INTERVAL_MIN = 3.0f; // Tiempo mínimo entre alternancia SHAPE/PHYSICS (escalado con resolución) +constexpr float LOGO_ACTION_INTERVAL_MAX = 5.0f; // Tiempo máximo entre alternancia SHAPE/PHYSICS (escalado con resolución) constexpr int LOGO_WEIGHT_TOGGLE_PHYSICS = 100; // Único peso: alternar SHAPE ↔ PHYSICS (100%) +// Sistema de convergencia para LOGO MODE (evita interrupciones prematuras en resoluciones altas) +constexpr float LOGO_CONVERGENCE_MIN = 0.75f; // 75% mínimo (permite algo de movimiento al disparar) +constexpr float LOGO_CONVERGENCE_MAX = 1.00f; // 100% máximo (completamente formado) + // Probabilidad de salto a Logo Mode desde DEMO/DEMO_LITE (%) // Relación DEMO:LOGO = 6:1 (pasa 6x más tiempo en DEMO que en LOGO) constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO = 5; // 5% probabilidad en DEMO normal (más raro) diff --git a/source/engine.cpp b/source/engine.cpp index fe847a6..f27912a 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -145,37 +145,37 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) { // Buscar todas las texturas PNG en data/balls/ namespace fs = std::filesystem; if (fs::exists(balls_dir) && fs::is_directory(balls_dir)) { - std::vector> texture_files; // (nombre, path) + struct TextureInfo { + std::string name; + std::string path; + int width; + }; + std::vector texture_files; + // Cargar información de todas las texturas (incluyendo dimensiones) for (const auto& entry : fs::directory_iterator(balls_dir)) { if (entry.is_regular_file() && entry.path().extension() == ".png") { - std::string filename = entry.path().stem().string(); // Sin extensión + std::string filename = entry.path().stem().string(); std::string fullpath = entry.path().string(); - texture_files.push_back({filename, fullpath}); + + // Cargar temporalmente para obtener dimensiones + auto temp_texture = std::make_shared(renderer_, fullpath); + int width = temp_texture->getWidth(); + + texture_files.push_back({filename, fullpath, width}); } } - // Ordenar alfabéticamente (normal.png será primero) - std::sort(texture_files.begin(), texture_files.end()); + // Ordenar por tamaño (grande → pequeño): big(16) → normal(10) → small(6) → tiny(4) + std::sort(texture_files.begin(), texture_files.end(), + [](const TextureInfo& a, const TextureInfo& b) { + return a.width > b.width; // Descendente por tamaño + }); - // Cargar texturas en orden (con normal.png primero si existe) - int normal_index = -1; - for (size_t i = 0; i < texture_files.size(); i++) { - if (texture_files[i].first == "normal") { - normal_index = static_cast(i); - break; - } - } - - // Poner normal.png primero - if (normal_index > 0) { - std::swap(texture_files[0], texture_files[normal_index]); - } - - // Cargar todas las texturas - for (const auto& [name, path] : texture_files) { - textures_.push_back(std::make_shared(renderer_, path)); - texture_names_.push_back(name); + // Cargar todas las texturas en orden de tamaño (0=big, 1=normal, 2=small, 3=tiny) + for (const auto& info : texture_files) { + textures_.push_back(std::make_shared(renderer_, info.path)); + texture_names_.push_back(info.name); } } @@ -185,8 +185,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) { success = false; } - // Establecer textura inicial (índice 0 = normal.png) - current_texture_index_ = 0; + // Buscar índice de "normal" para usarlo como textura inicial (debería ser índice 1) + current_texture_index_ = 0; // Fallback + for (size_t i = 0; i < texture_names_.size(); i++) { + if (texture_names_[i] == "normal") { + current_texture_index_ = i; // Iniciar en "normal" (índice 1) + break; + } + } texture_ = textures_[current_texture_index_]; current_ball_size_ = texture_->getWidth(); // Obtener tamaño dinámicamente @@ -1411,8 +1417,13 @@ void Engine::setState(AppMode new_mode) { float min_interval, max_interval; if (new_mode == AppMode::LOGO) { - min_interval = LOGO_ACTION_INTERVAL_MIN; - max_interval = LOGO_ACTION_INTERVAL_MAX; + // Escalar tiempos con resolución (720p como base) + float resolution_scale = current_screen_height_ / 720.0f; + logo_min_time_ = LOGO_ACTION_INTERVAL_MIN * resolution_scale; + logo_max_time_ = LOGO_ACTION_INTERVAL_MAX * resolution_scale; + + min_interval = logo_min_time_; + max_interval = logo_max_time_; } else { bool is_lite = (new_mode == AppMode::DEMO_LITE); min_interval = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN; @@ -1434,8 +1445,23 @@ void Engine::updateDemoMode() { // Actualizar timer demo_timer_ += delta_time_; + // Determinar si es hora de ejecutar acción (depende del modo) + bool should_trigger = false; + + if (current_app_mode_ == AppMode::LOGO) { + // LOGO MODE: Esperar convergencia + tiempo mínimo (o timeout máximo) + bool min_time_reached = demo_timer_ >= logo_min_time_; + bool max_time_reached = demo_timer_ >= logo_max_time_; + bool convergence_ok = shape_convergence_ >= logo_convergence_threshold_; + + should_trigger = (min_time_reached && convergence_ok) || max_time_reached; + } else { + // DEMO/DEMO_LITE: Timer simple como antes + should_trigger = demo_timer_ >= demo_next_action_time_; + } + // Si es hora de ejecutar acción - if (demo_timer_ >= demo_next_action_time_) { + if (should_trigger) { // MODO LOGO: Sistema de acciones variadas con gravedad dinámica if (current_app_mode_ == AppMode::LOGO) { // Elegir acción aleatoria ponderada @@ -1464,10 +1490,10 @@ void Engine::updateDemoMode() { } } - // Resetear timer con intervalos de Logo Mode + // Resetear timer con intervalos escalados (logo_min_time_ y logo_max_time_) demo_timer_ = 0.0f; - float interval_range = LOGO_ACTION_INTERVAL_MAX - LOGO_ACTION_INTERVAL_MIN; - demo_next_action_time_ = LOGO_ACTION_INTERVAL_MIN + (rand() % 1000) / 1000.0f * interval_range; + float interval_range = logo_max_time_ - logo_min_time_; + demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range; // Solo salir automáticamente si NO llegamos desde MANUAL // Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1) @@ -1968,6 +1994,13 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) { } } } + + // Si estamos en LOGO MODE, generar threshold aleatorio de convergencia (75-100%) + if (current_app_mode_ == AppMode::LOGO) { + logo_convergence_threshold_ = LOGO_CONVERGENCE_MIN + + (rand() % 1000) / 1000.0f * (LOGO_CONVERGENCE_MAX - LOGO_CONVERGENCE_MIN); + shape_convergence_ = 0.0f; // Reset convergencia al entrar + } } else { // Volver a modo física normal current_mode_ = SimulationMode::PHYSICS; @@ -2111,6 +2144,20 @@ void Engine::updateShape() { float depth_scale = depth_zoom_enabled_ ? (0.5f + z_normalized * 1.0f) : 1.0f; balls_[i]->setDepthScale(depth_scale); } + + // Calcular convergencia en LOGO MODE (% de pelotas cerca de su objetivo) + if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) { + int balls_near = 0; + float distance_threshold = SHAPE_NEAR_THRESHOLD * scale_factor; + + for (const auto& ball : balls_) { + if (ball->getDistanceToTarget() < distance_threshold) { + balls_near++; + } + } + + shape_convergence_ = static_cast(balls_near) / balls_.size(); + } } // Limitar escala de figura para evitar que se salga de pantalla diff --git a/source/engine.h b/source/engine.h index f2284ae..5ea1bb0 100644 --- a/source/engine.h +++ b/source/engine.h @@ -113,6 +113,12 @@ class Engine { float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos) + // Sistema de convergencia para LOGO MODE (escala con resolución) + float shape_convergence_ = 0.0f; // % de pelotas cerca del objetivo (0.0-1.0) + float logo_convergence_threshold_ = 0.90f; // Threshold aleatorio (75-100%) + float logo_min_time_ = 3.0f; // Tiempo mínimo escalado con resolución + float logo_max_time_ = 5.0f; // Tiempo máximo escalado (backup) + // Estado previo antes de entrar a Logo Mode (para restaurar al salir) ColorTheme logo_previous_theme_ = ColorTheme::SUNSET; size_t logo_previous_texture_index_ = 0;