refactor: constantes SCENE_BALLS_N y fix HUD overlay en SHAPE mode

- defines.hpp: añadir SCENE_BALLS_1..8 (topado en 50K), SCENARIO_COUNT,
  reconstruir BALL_COUNT_SCENARIOS con esas constantes
- theme_manager: añadir max_ball_count_ y setMaxBallCount() para capturar
  colores hasta el máximo real (custom incluido), eliminando literal 50000
- engine.cpp: llamar setMaxBallCount() tras inicializar ThemeManager
- gpu_sprite_batch: addFullscreenOverlay() escribe vértices directamente
  sin pasar por el guard de pushQuad(), igual que addBackground(); esto
  corrige el HUD/overlay invisible en SHAPE mode con escenario 8 (50K bolas)
- Textos UI actualizados: tecla 8, help overlay y --skip-benchmark → 50.000

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 00:55:57 +01:00
parent 093b982e01
commit e1f6fd0f39
5 changed files with 46 additions and 16 deletions

View File

@@ -59,10 +59,22 @@ constexpr float BALL_SPAWN_MARGIN = 0.15f; // Margen lateral para spawn (0.25 =
// Escenarios de número de pelotas (teclas 1-8)
// Fase 1 (instanced rendering): límit pràctic ~100K a 60fps (physics bound)
constexpr int BALL_COUNT_SCENARIOS[8] = {10, 50, 100, 500, 1000, 5000, 10000, 100000};
constexpr int SCENE_BALLS_1 = 10;
constexpr int SCENE_BALLS_2 = 50;
constexpr int SCENE_BALLS_3 = 100;
constexpr int SCENE_BALLS_4 = 500;
constexpr int SCENE_BALLS_5 = 1000;
constexpr int SCENE_BALLS_6 = 5000;
constexpr int SCENE_BALLS_7 = 10000;
constexpr int SCENE_BALLS_8 = 50000; // Máximo escenario estándar (tecla 8)
constexpr int SCENARIO_COUNT = 8;
constexpr int BALL_COUNT_SCENARIOS[SCENARIO_COUNT] = {
SCENE_BALLS_1, SCENE_BALLS_2, SCENE_BALLS_3, SCENE_BALLS_4,
SCENE_BALLS_5, SCENE_BALLS_6, SCENE_BALLS_7, SCENE_BALLS_8
};
// Límites de escenario para modos automáticos (índices en BALL_COUNT_SCENARIOS)
// BALL_COUNT_SCENARIOS = {10, 50, 100, 500, 1000, 5000, 10000, 50000}
// 0 1 2 3 4 5 6 7
constexpr int DEMO_AUTO_MIN_SCENARIO = 2; // mínimo 100 bolas
constexpr int DEMO_AUTO_MAX_SCENARIO = 7; // máximo sin restricción hardware (ajustado por benchmark)

View File

@@ -282,6 +282,12 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
// Inicializar ThemeManager PRIMERO (requerido por Notifier y SceneManager)
theme_manager_ = std::make_unique<ThemeManager>();
theme_manager_->initialize();
{
int max_balls = BALL_COUNT_SCENARIOS[DEMO_AUTO_MAX_SCENARIO];
if (custom_scenario_enabled_ && custom_scenario_balls_ > max_balls)
max_balls = custom_scenario_balls_;
theme_manager_->setMaxBallCount(max_balls);
}
// Inicializar SceneManager (gestión de bolas y física)
scene_manager_ = std::make_unique<SceneManager>(current_screen_width_, current_screen_height_);

View File

@@ -9,10 +9,10 @@
bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
max_sprites_ = max_sprites;
// Pre-allocate GPU buffers large enough for (max_sprites_ + 1) quads.
// The +1 reserves a guaranteed slot for the fullscreen overlay.
Uint32 max_verts = static_cast<Uint32>(max_sprites_ + 1) * 4;
Uint32 max_indices = static_cast<Uint32>(max_sprites_ + 1) * 6;
// Pre-allocate GPU buffers large enough for (max_sprites_ + 2) quads.
// The +2 reserves one slot for the background quad and one for the fullscreen overlay.
Uint32 max_verts = static_cast<Uint32>(max_sprites_ + 2) * 4;
Uint32 max_indices = static_cast<Uint32>(max_sprites_ + 2) * 6;
Uint32 vb_size = max_verts * sizeof(GpuVertex);
Uint32 ib_size = max_indices * sizeof(uint32_t);
@@ -55,8 +55,8 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
return false;
}
vertices_.reserve(static_cast<size_t>(max_sprites_ + 1) * 4);
indices_.reserve(static_cast<size_t>(max_sprites_ + 1) * 6);
vertices_.reserve(static_cast<size_t>(max_sprites_ + 2) * 4);
indices_.reserve(static_cast<size_t>(max_sprites_ + 2) * 6);
return true;
}
@@ -130,10 +130,17 @@ void GpuSpriteBatch::addSprite(float x, float y, float w, float h,
}
void GpuSpriteBatch::addFullscreenOverlay() {
// El overlay es un slot reservado fuera del espacio de max_sprites_, igual que el background.
// Escribe directamente sin pasar por el guard de pushQuad().
overlay_index_offset_ = static_cast<int>(indices_.size());
size_t before = indices_.size();
pushQuad(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
overlay_index_count_ = static_cast<int>(indices_.size() - before); // 0 si pushQuad falló, 6 si ok
uint32_t vi = static_cast<uint32_t>(vertices_.size());
vertices_.push_back({ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f });
vertices_.push_back({ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f });
vertices_.push_back({ 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f });
vertices_.push_back({ -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f });
indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2);
indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0);
overlay_index_count_ = 6;
}
bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) {
@@ -182,7 +189,8 @@ void GpuSpriteBatch::toNDC(float px, float py,
void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
float u0, float v0, float u1, float v1,
float r, float g, float b, float a) {
if (vertices_.size() + 4 > static_cast<size_t>(max_sprites_) * 4) return;
// +1 reserva el slot del background que ya entró sin pasar por este guard.
if (vertices_.size() + 4 > static_cast<size_t>(max_sprites_ + 1) * 4) return;
uint32_t vi = static_cast<uint32_t>(vertices_.size());
// TL, TR, BR, BL

View File

@@ -640,10 +640,10 @@ std::unique_ptr<ThemeSnapshot> ThemeManager::captureCurrentSnapshot() const {
snapshot->name_en = themes_[current_theme_index_]->getNameEN();
snapshot->name_es = themes_[current_theme_index_]->getNameES();
// Capturar colores de pelotas (suficientes para escenario máximo: 50,000)
// Esto asegura LERP correcto incluso en escenarios grandes
snapshot->ball_colors.reserve(50000);
for (size_t i = 0; i < 50000; i++) {
// Capturar colores de pelotas para el máximo real de esta sesión
// (SCENE_BALLS_8 o más si hay escenario custom)
snapshot->ball_colors.reserve(max_ball_count_);
for (int i = 0; i < max_ball_count_; i++) {
snapshot->ball_colors.push_back(
themes_[current_theme_index_]->getBallColor(i, 0.0f)
);

View File

@@ -43,6 +43,7 @@ class ThemeManager {
// Inicialización
void initialize(); // Inicializa 15 temas unificados (9 estáticos + 6 dinámicos)
void setMaxBallCount(int n) { max_ball_count_ = n; } // Máximo real (escenario 8 o custom si mayor)
// Interfaz unificada (PHASE 2 + PHASE 3)
void switchToTheme(int theme_index); // Cambia a tema 0-14 con transición LERP suave (PHASE 3)
@@ -99,6 +100,9 @@ class ThemeManager {
// Snapshot del tema origen (capturado al iniciar transición)
std::unique_ptr<ThemeSnapshot> source_snapshot_; // nullptr si no hay transición
// Máximo de bolas posible en esta sesión (max(SCENE_BALLS_8, custom_balls))
int max_ball_count_ = SCENE_BALLS_8;
// ========================================
// MÉTODOS PRIVADOS
// ========================================