fix: pantalla completa, integer scale i vsync

This commit is contained in:
2026-03-21 18:10:48 +01:00
parent 9df3f1b929
commit 3a2015256a
7 changed files with 68 additions and 1721 deletions

View File

@@ -1,141 +0,0 @@
# BUGS DETECTADOS - Reescritura Player
**Fecha:** 2025-10-30
**Commit funcional de referencia:** `7cd596a0b9876c75ff75efc13708a2ca00c8cfcd`
**Estado:** La reescritura según PLAYER_MECHANICS.md ha introducido múltiples regresiones
---
## 🐛 BUGS CRÍTICOS DETECTADOS
### 2. **Salto recto en rampa: atraviesa la rampa al caer**
- **Descripción:** Si el jugador salta verticalmente (sin movimiento horizontal) estando en una rampa, al caer atraviesa la rampa
- **Estado esperado:** Salto recto (vx_ == 0) debería pegarse a la rampa al descender
- **Regla violada:** PLAYER_MECHANICS.md líneas 386-391 - "JUMPING con vx_ == 0 se PEGA a la rampa"
- **Posible causa:** Lógica en `moveVerticalDown()` no detecta correctamente el caso vx_ == 0
---
### 4. **No suena al saltar**
- **Descripción:** Los sonidos de salto no se reproducen
- **Estado esperado:** Sonidos progresivos basados en distancia vertical
- **Posible causa:**
- `y_prev_` no se inicializa correctamente
- Sistema de detección de cambio de índice no funciona
- `last_grounded_position_` no se actualiza en el momento correcto
---
### 5. **No suena al caer**
- **Descripción:** Los sonidos de caída no se reproducen
- **Estado esperado:** Sonidos progresivos basados en distancia vertical caída
- **Posible causa:** Similar al bug #4, sistema distance-based no detecta cambios
---
### 6. **Caer desde precipicio con inercia sobre rampa: resbala en estado FALLING**
- **Descripción:** Al caer con inercia horizontal sobre una rampa, el jugador resbala en lugar de pegarse
- **Estado esperado:** FALLING siempre debe tener `vx_ = 0` y pegarse a rampas
- **Regla violada:** PLAYER_MECHANICS.md línea 147 - "FALLING se PEGA a TODAS las rampas"
- **Posible causa:** `vx_` no se establece a 0 correctamente en transición a FALLING, o se ejecuta después del movimiento horizontal
---
### 7. **No muere al caer desde gran altura**
- **Descripción:** El jugador puede caer desde cualquier altura sin morir
- **Estado esperado:** Caída > 32 píxeles (4 tiles) debería matar al jugador
- **Regla violada:** PLAYER_MECHANICS.md líneas 158-178
- **Posible causa:**
- La verificación de muerte en `moveVerticalDown()` no se ejecuta
- `previous_state_` no es FALLING cuando debería
- Cálculo de `FALL_DISTANCE` es incorrecto
---
### 8. **No muere al caer sobre conveyor belt desde altura**
- **Descripción:** El jugador puede caer sobre una conveyor belt desde cualquier altura sin morir
- **Estado esperado:** Caída > 32 píxeles sobre conveyor belt también debería matar
- **Regla violada:** PLAYER_MECHANICS.md líneas 158-178
- **Posible causa:**
- La verificación de muerte solo está en la rama de suelo normal (`checkTopSurfaces`)
- Posiblemente necesita estar también cuando aterriza en `checkAutoSurfaces`
- Podría ser un subcaso del bug #7
---
## 🔍 ANÁLISIS PRELIMINAR
### Problema raíz principal: **Orden de ejecución**
El orden actual es:
```cpp
move() {
y_prev_ = y_;
applyGravity();
updateStateAndVelocity(); // ← Cambia estado y DEBERÍA cambiar vx_
moveHorizontal(); // ← Aplica vx_ (que puede ser incorrecto)
moveVertical();
updateColliderGeometry();
}
```
**Problema:** `updateStateAndVelocity()` puede cambiar el estado a FALLING y establecer `vx_ = 0`, pero esto ocurre DENTRO del switch que solo se ejecuta DESPUÉS de que `previous_state_` ya esté establecido. En el código actual, cuando se hace la transición STANDING → FALLING, `setState()` NO establece `vx_ = 0` (se supone que lo hace `updateStateAndVelocity()`), pero esto puede no ejecutarse hasta el siguiente frame.
### Problema raíz secundario: **setState() incompleto**
En el commit funcional (`7cd596a0b9876c75ff75efc13708a2ca00c8cfcd`), `setState()` probablemente establecía TODAS las variables necesarias inmediatamente. En la reescritura actual, se delegó parte de esta responsabilidad a `updateStateAndVelocity()`, creando una desincronización.
---
## 📋 PLAN DE SOLUCIÓN
### Opción A: Volver al commit funcional y refactorizar con cuidado
1. Hacer `git diff` entre commit funcional y actual
2. Identificar QUÉ funcionaba en el original
3. Aplicar solo los cambios necesarios (eliminar `jumping_time_`, unificar variables)
4. Mantener la lógica de setState() completa
### Opción B: Corregir bugs uno por uno en código actual
1. Arreglar `setState()` para que establezca TODAS las variables inmediatamente
2. Revisar orden de ejecución en `move()`
3. Depurar sistema de sonidos
4. Probar cada bug individualmente
### Opción C: Híbrido
1. Extraer lógica funcional del commit 7cd596a
2. Aplicar solo las correcciones de PLAYER_MECHANICS.md que no rompen funcionalidad:
- Cambiar `jump_init_pos_``last_grounded_position_` (con cuidado)
- Eliminar `jumping_time_` (secundario, no crítico)
- Implementar sistema de sonidos distance-based (después de que todo funcione)
---
## 🎯 RECOMENDACIÓN
**Opción C (Híbrido)** parece la más sensata:
1. Revertir a un punto funcional
2. Aplicar cambios incrementales uno por uno
3. Testear después de cada cambio
4. No hacer cambios arquitecturales grandes (mantener `updateState()` y `updateVelocity()` separados si funcionaba así)
---
## 📝 NOTAS ADICIONALES
- El commit `7cd596a` funciona perfecto según el usuario, aunque el código sea "caótico"
- **Lección aprendida:** Refactorizar código funcional sin tests automáticos es peligroso
- La sincronización estado-velocidad es más sutil de lo que parecía
- PLAYER_MECHANICS.md puede tener asunciones incorrectas sobre el orden de ejecución óptimo
---
## ✅ PRÓXIMOS PASOS
1. Comparar código actual vs commit 7cd596a
2. Identificar diferencias críticas en:
- `setState()`
- `move()`
- `updateState()`/`updateVelocity()`
- Transiciones de estado
3. Decidir estrategia: revertir completo, revertir parcial, o corregir in-place

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +0,0 @@
# Reglas para la clase Player
## Estados
El jugador tiene tres estados diferenciados:
- STANDING
- JUMPING
- FALLING
## Puntos de colision
El jugador tiene un rectabgulo que lo delimita. Se obtiene con getRect()
Tiene ademas dos puntos en un vector de puntos llamado feet_ con el punto inferior izquierdo y el inferior derecho
Tiene otros dos puntos llamados under_feet_ que son los puntos inmediatamente inferiores a los puntos en feet_. Se usan para saber SOBRE qué tipo de superficie esta el jugador o si bajo solo tiene aire
Tiene otros 8 puntos (el jugador ocupa dos tiles en vertical del mapa, los tiles son de 8 pixeles) que son las 4 esquinas de los dos rectangulos de 8px que formarian al jugador. Se usa para comprobar si el jugador está tocando algun tile de los que matan
## Comprobar colisiones con el mapa
Esto es un poco marciano pero, el jugador genera un rectangulo de proyeccion. Digamos que si se va a mover 3 pixeles hacia la derecha, el rectangulo de proyeccion es el rectangulo que cubre la superficie entre donde está el jugador y donde estará el jugador. Se usa para ver si ese rectangulo (que suele ser de ancho o alto 1, ya que el jugador no llega a moverse mas de un pixel cada vez) colisiona con alguna superficie del mapa. En caso de colision se devuelve el punto de colision y se recoloca al jugador en ese punto. Eso para las superficies rectas.
## Reglas
A continuacion las reglas que se han de cumplir para el jugador en todo momento
- Si está STANDING -> vy_ = 0;
- Si no tiene suelo debajo y no está JUMPING -> FALLING
- Si está JUMPING y tropieza contra el techo -> FALLING
- Si está FALLING -> vx_ = 0
- Si está STANDING y tropieza lateralmente con una Slope, se pega a la slope
- Si esta FALLING y tiene una Slope bajo los pies, se pega a la slope
- La unica forma de atravesar una Slope es en estado JUMPING y con vx_ != 0
- Si salta, se guarda la posicion inicial en Y, si durante el salto está mas bajo de Y (es decir, el valor de y pasa a ser superior al inicial) -> FALLING
## Tipos de superficies
Hay tres tipos de superficies:
- Suelo normal
- Suelo tipo conveyor belt
- Rampas
## Reglas explicadas de manera mas abierta
Debido a que las reglas anteriores pueden sonar confusas y ser incompletas, se describen aqui con lenguaje mas natural:
- El jugador camina sobre las superficies
- Si tiene los dos pies sobre el aire -> cae
- El jugador sube y baja por las rampas
- La unica forma de atravesar las rampas es saltando sobre ellas en movimiento
- Un salto recto o una caida (las caidas siempre son rectas) hacen aterrizar al jugador sobre las rampas
- Las conveyor belt desplazan al jugador en la direccion de la conveyor belt
- El jugador cuando esta siendo desplazado por una conveyor belt, no puede cambiar de direccion
- Solo puede saltar y la propia inercia le hace saltar en movimiento
- Hay una excepcion en las conveyor belts y es que cuando el jugador cae sobre ellas, puede mantener la direccion que tenia el jugador. En el momento que el jugador deja de estar pulsando una direccion, ya se acopla al movimiento de la conveyor belt y no puede cambiar de direccion
- El jugador deja de estar ligado al movimiento de la conveyor belt cuando sus dos pies ya no la tienen debajo, bien porque hay suelo o bien porque hay aire -> cae
- El jugador no puede cambiar de direccion en el aire

View File

@@ -431,12 +431,18 @@ void Screen::toggleDebugInfo() { show_debug_info_ = !show_debug_info_; }
void Screen::toggleIntegerScale() {
Options::video.integer_scale = !Options::video.integer_scale;
SDL_SetRenderLogicalPresentation(renderer_, Options::game.width, Options::game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
if (shader_backend_) {
shader_backend_->setScaleMode(Options::video.integer_scale);
}
}
// Alterna entre activar y desactivar el V-Sync
void Screen::toggleVSync() {
Options::video.vertical_sync = !Options::video.vertical_sync;
SDL_SetRenderVSync(renderer_, Options::video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
if (shader_backend_) {
shader_backend_->setVSync(Options::video.vertical_sync);
}
}
// Getters
@@ -470,6 +476,10 @@ void Screen::initShaders() {
}
shader_backend_->init(window_, tex, "", "");
// Propagar flags de vsync e integer scale al backend GPU
shader_backend_->setVSync(Options::video.vertical_sync);
shader_backend_->setScaleMode(Options::video.integer_scale);
if (Options::video.postfx) {
applyCurrentPostFXPreset();
} else {

View File

@@ -2,6 +2,8 @@
#include <SDL3/SDL_log.h>
#include <algorithm> // std::min, std::max, std::floor
#include <cmath> // std::floor
#include <cstring> // memcpy, strlen
#ifndef __APPLE__
@@ -218,7 +220,7 @@ auto SDL3GPUShader::init(SDL_Window* window,
}
SDL_SetGPUSwapchainParameters(device_, window_,
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
SDL_GPU_PRESENTMODE_VSYNC);
vsync_ ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE);
}
// ----------------------------------------------------------------
@@ -410,6 +412,29 @@ void SDL3GPUShader::render() {
if (pass != nullptr) {
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
// Calcular viewport para mantener relación de aspecto (letterbox o integer scale)
float vx = 0.0F;
float vy = 0.0F;
float vw = 0.0F;
float vh = 0.0F;
if (integer_scale_) {
const int scale = std::max(1, std::min(
static_cast<int>(sw) / tex_width_,
static_cast<int>(sh) / tex_height_));
vw = static_cast<float>(tex_width_ * scale);
vh = static_cast<float>(tex_height_ * scale);
} else {
const float scale = std::min(
static_cast<float>(sw) / static_cast<float>(tex_width_),
static_cast<float>(sh) / static_cast<float>(tex_height_));
vw = static_cast<float>(tex_width_) * scale;
vh = static_cast<float>(tex_height_) * scale;
}
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
SDL_GPUViewport vp = {vx, vy, vw, vh, 0.0F, 1.0F};
SDL_SetGPUViewport(pass, &vp);
SDL_GPUTextureSamplerBinding binding = {};
binding.texture = scene_texture_;
binding.sampler = sampler_;
@@ -525,4 +550,17 @@ void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
uniforms_.bleeding = p.bleeding;
}
void SDL3GPUShader::setVSync(bool vsync) {
vsync_ = vsync;
if (device_ != nullptr && window_ != nullptr) {
SDL_SetGPUSwapchainParameters(device_, window_,
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
vsync_ ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE);
}
}
void SDL3GPUShader::setScaleMode(bool integer_scale) {
integer_scale_ = integer_scale;
}
} // namespace Rendering

View File

@@ -50,6 +50,12 @@ class SDL3GPUShader : public ShaderBackend {
// Actualiza los parámetros de intensidad de los efectos PostFX
void setPostFXParams(const PostFXParams& p) override;
// Activa/desactiva VSync en el swapchain
void setVSync(bool vsync) override;
// Activa/desactiva escalado entero (integer scale)
void setScaleMode(bool integer_scale) override;
private:
static auto createShaderMSL(SDL_GPUDevice* device,
const char* msl_source,
@@ -80,6 +86,8 @@ class SDL3GPUShader : public ShaderBackend {
int tex_width_ = 0;
int tex_height_ = 0;
bool is_initialized_ = false;
bool vsync_ = true;
bool integer_scale_ = false;
};
} // namespace Rendering

View File

@@ -72,6 +72,16 @@ class ShaderBackend {
*/
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
/**
* @brief Activa o desactiva VSync en el swapchain del GPU device
*/
virtual void setVSync(bool /*vsync*/) {}
/**
* @brief Activa o desactiva el escalado entero (integer scale)
*/
virtual void setScaleMode(bool /*integer_scale*/) {}
/**
* @brief Verifica si el backend está usando aceleración por hardware
* @return true si usa aceleración (OpenGL/Metal/Vulkan)