Sistema de convergencia para LOGO MODE (resolución escalable)

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>
This commit is contained in:
2025-10-05 01:25:55 +02:00
parent 042c3cad1a
commit ef2f5bea01
5 changed files with 100 additions and 33 deletions

View File

@@ -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<std::pair<std::string, std::string>> texture_files; // (nombre, path)
struct TextureInfo {
std::string name;
std::string path;
int width;
};
std::vector<TextureInfo> 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<Texture>(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<int>(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<Texture>(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<Texture>(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<float>(balls_near) / balls_.size();
}
}
// Limitar escala de figura para evitar que se salga de pantalla