Fix: Sistema de convergencia y flip timing en LOGO mode
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>
This commit is contained in:
@@ -823,6 +823,13 @@ void Engine::render() {
|
||||
}
|
||||
dbg_print(8, 72, mode_text.c_str(), 0, 255, 128); // Verde claro para modo
|
||||
|
||||
// Debug: Mostrar convergencia en modo LOGO (solo cuando está activo)
|
||||
if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) {
|
||||
int convergence_percent = static_cast<int>(shape_convergence_ * 100.0f);
|
||||
std::string convergence_text = "CONV " + std::to_string(convergence_percent);
|
||||
dbg_print(8, 80, convergence_text.c_str(), 255, 128, 0); // Naranja para convergencia
|
||||
}
|
||||
|
||||
// Debug: Mostrar modo DEMO/LOGO activo (siempre visible cuando debug está ON)
|
||||
if (current_app_mode_ == AppMode::LOGO) {
|
||||
int logo_text_width = 9 * 8; // "LOGO MODE" = 9 caracteres × 8 píxeles
|
||||
@@ -1831,12 +1838,43 @@ void Engine::updateDemoMode() {
|
||||
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_;
|
||||
// LOGO MODE: Dos caminos posibles
|
||||
if (logo_waiting_for_flip_) {
|
||||
// CAMINO B: Esperando a que ocurran flips
|
||||
// Obtener referencia a PNGShape si está activa
|
||||
PNGShape* png_shape = nullptr;
|
||||
if (active_shape_ && current_mode_ == SimulationMode::SHAPE) {
|
||||
png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
||||
}
|
||||
|
||||
should_trigger = (min_time_reached && convergence_ok) || max_time_reached;
|
||||
if (png_shape) {
|
||||
int current_flip_count = png_shape->getFlipCount();
|
||||
|
||||
// Detectar nuevo flip completado
|
||||
if (current_flip_count > logo_current_flip_count_) {
|
||||
logo_current_flip_count_ = current_flip_count;
|
||||
}
|
||||
|
||||
// Si estamos EN o DESPUÉS del flip objetivo
|
||||
// +1 porque queremos actuar DURANTE el flip N, no después de completarlo
|
||||
if (logo_current_flip_count_ + 1 >= logo_target_flip_number_) {
|
||||
// Monitorear progreso del flip actual
|
||||
if (png_shape->isFlipping()) {
|
||||
float flip_progress = png_shape->getFlipProgress();
|
||||
if (flip_progress >= logo_target_flip_percentage_) {
|
||||
should_trigger = true; // ¡Trigger durante el flip!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// CAMINO A: Esperar convergencia + tiempo (comportamiento original)
|
||||
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_;
|
||||
@@ -1850,19 +1888,71 @@ void Engine::updateDemoMode() {
|
||||
int action = rand() % 100;
|
||||
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
// Logo quieto (formado) → 2 opciones posibles
|
||||
if (action < 50) {
|
||||
// 50%: SHAPE → PHYSICS con gravedad ON (caída dramática)
|
||||
toggleShapeMode(true);
|
||||
// Logo quieto (formado) → Decidir camino a seguir
|
||||
|
||||
// DECISIÓN BIFURCADA: ¿Cambio inmediato o esperar flips?
|
||||
if (logo_waiting_for_flip_) {
|
||||
// Ya estábamos esperando flips, y se disparó el trigger
|
||||
// → Hacer el cambio SHAPE → PHYSICS ahora (durante el flip)
|
||||
if (action < 50) {
|
||||
toggleShapeMode(true); // Con gravedad ON
|
||||
} else {
|
||||
toggleShapeMode(false); // Con gravedad OFF
|
||||
}
|
||||
|
||||
// Resetear variables de espera de flips
|
||||
logo_waiting_for_flip_ = false;
|
||||
logo_current_flip_count_ = 0;
|
||||
|
||||
// Resetear timer
|
||||
demo_timer_ = 0.0f;
|
||||
float interval_range = logo_max_time_ - logo_min_time_;
|
||||
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
||||
} else if (rand() % 100 < LOGO_FLIP_WAIT_PROBABILITY) {
|
||||
// CAMINO B (50%): Esperar a que ocurran 1-3 flips
|
||||
logo_waiting_for_flip_ = true;
|
||||
logo_target_flip_number_ = LOGO_FLIP_WAIT_MIN + rand() % (LOGO_FLIP_WAIT_MAX - LOGO_FLIP_WAIT_MIN + 1);
|
||||
logo_target_flip_percentage_ = LOGO_FLIP_TRIGGER_MIN + (rand() % 1000) / 1000.0f * (LOGO_FLIP_TRIGGER_MAX - LOGO_FLIP_TRIGGER_MIN);
|
||||
logo_current_flip_count_ = 0;
|
||||
|
||||
// Resetear contador de flips en PNGShape
|
||||
if (active_shape_) {
|
||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
||||
if (png_shape) {
|
||||
png_shape->resetFlipCount();
|
||||
}
|
||||
}
|
||||
|
||||
// NO hacer nada más este frame - esperar a que ocurran los flips
|
||||
// El trigger se ejecutará en futuras iteraciones cuando se cumplan las condiciones
|
||||
} else {
|
||||
// 50%: SHAPE → PHYSICS con gravedad OFF (dar vueltas sin caer)
|
||||
toggleShapeMode(false);
|
||||
// CAMINO A (50%): Cambio inmediato
|
||||
if (action < 50) {
|
||||
// 50%: SHAPE → PHYSICS con gravedad ON (caída dramática)
|
||||
toggleShapeMode(true);
|
||||
} else {
|
||||
// 50%: SHAPE → PHYSICS con gravedad OFF (dar vueltas sin caer)
|
||||
toggleShapeMode(false);
|
||||
}
|
||||
|
||||
// Resetear variables de espera de flips al cambiar a PHYSICS
|
||||
logo_waiting_for_flip_ = false;
|
||||
logo_current_flip_count_ = 0;
|
||||
|
||||
// Resetear timer con intervalos escalados
|
||||
demo_timer_ = 0.0f;
|
||||
float interval_range = logo_max_time_ - logo_min_time_;
|
||||
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
||||
}
|
||||
} else {
|
||||
// Logo animado (PHYSICS) → 3 opciones posibles
|
||||
if (action < 60) {
|
||||
// 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
|
||||
toggleShapeMode(false);
|
||||
|
||||
// Resetear variables de espera de flips al volver a SHAPE
|
||||
logo_waiting_for_flip_ = false;
|
||||
logo_current_flip_count_ = 0;
|
||||
} else if (action < 80) {
|
||||
// 20%: Forzar gravedad ON (empezar a caer mientras da vueltas)
|
||||
forceBallsGravityOn();
|
||||
@@ -1870,12 +1960,12 @@ void Engine::updateDemoMode() {
|
||||
// 20%: Forzar gravedad OFF (flotar mientras da vueltas)
|
||||
forceBallsGravityOff();
|
||||
}
|
||||
}
|
||||
|
||||
// Resetear timer con intervalos escalados (logo_min_time_ y logo_max_time_)
|
||||
demo_timer_ = 0.0f;
|
||||
float interval_range = logo_max_time_ - logo_min_time_;
|
||||
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
||||
// Resetear timer con intervalos escalados
|
||||
demo_timer_ = 0.0f;
|
||||
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)
|
||||
@@ -2224,9 +2314,16 @@ void Engine::enterLogoMode(bool from_demo) {
|
||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
||||
if (png_shape) {
|
||||
png_shape->setLogoMode(true);
|
||||
png_shape->resetFlipCount(); // Resetear contador de flips
|
||||
}
|
||||
}
|
||||
|
||||
// Resetear variables de espera de flips
|
||||
logo_waiting_for_flip_ = false;
|
||||
logo_target_flip_number_ = 0;
|
||||
logo_target_flip_percentage_ = 0.0f;
|
||||
logo_current_flip_count_ = 0;
|
||||
|
||||
// Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente)
|
||||
setState(AppMode::LOGO);
|
||||
}
|
||||
@@ -2321,7 +2418,7 @@ void Engine::updateBallSizes(int old_size, int new_size) {
|
||||
|
||||
// Si ajustamos posición, aplicarla ahora
|
||||
if (ball->isOnSurface()) {
|
||||
ball->setRotoBallScreenPosition(pos.x, pos.y);
|
||||
ball->setShapeScreenPosition(pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2389,7 +2486,7 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
|
||||
|
||||
// Desactivar atracción y resetear escala de profundidad
|
||||
for (auto& ball : balls_) {
|
||||
ball->enableRotoBallAttraction(false);
|
||||
ball->enableShapeAttraction(false);
|
||||
ball->setDepthScale(1.0f); // Reset escala a 100% (evita "pop" visual)
|
||||
}
|
||||
|
||||
@@ -2460,7 +2557,7 @@ void Engine::activateShape(ShapeType type) {
|
||||
|
||||
// Activar atracción física en todas las pelotas
|
||||
for (auto& ball : balls_) {
|
||||
ball->enableRotoBallAttraction(true);
|
||||
ball->enableShapeAttraction(true);
|
||||
}
|
||||
|
||||
// Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo o logo)
|
||||
@@ -2510,10 +2607,13 @@ void Engine::updateShape() {
|
||||
float target_x = center_x + x_3d;
|
||||
float target_y = center_y + y_3d;
|
||||
|
||||
// Actualizar target de la pelota para cálculo de convergencia
|
||||
balls_[i]->setShapeTarget2D(target_x, target_y);
|
||||
|
||||
// Aplicar fuerza de atracción física hacia el punto rotado
|
||||
// Usar constantes SHAPE (mayor pegajosidad que ROTOBALL)
|
||||
float shape_size = scale_factor * 80.0f; // 80px = radio base
|
||||
balls_[i]->applyRotoBallForce(target_x, target_y, shape_size, delta_time_, SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR, SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE);
|
||||
balls_[i]->applyShapeForce(target_x, target_y, shape_size, delta_time_, SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR, SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE);
|
||||
|
||||
// Calcular brillo según profundidad Z para renderizado
|
||||
// Normalizar Z al rango de la figura (asumiendo simetría ±shape_size)
|
||||
@@ -2539,6 +2639,10 @@ void Engine::updateShape() {
|
||||
}
|
||||
|
||||
shape_convergence_ = static_cast<float>(balls_near) / balls_.size();
|
||||
|
||||
// Notificar a la figura sobre el porcentaje de convergencia
|
||||
// Esto permite que PNGShape decida cuándo empezar a contar para flips
|
||||
active_shape_->setConvergence(shape_convergence_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user