chore: neteja de notes obsoletes a l'arrel
This commit is contained in:
-346
@@ -1,346 +0,0 @@
|
||||
# Auditoría arquitectónica — Orni Attack
|
||||
|
||||
> Pasada de revisión de **diseño y arquitectura** (no de estilo) sobre la rama `main` tras cerrar el ciclo de lint (cppcheck/clang-tidy a 0 hits).
|
||||
> Generada el 2026-05-20 por el subagente `Plan` y verificada parcialmente.
|
||||
> Las referencias `file:line` son relativas a la raíz del repo.
|
||||
|
||||
## Cómo usar este documento
|
||||
|
||||
- **Prioridad** indica urgencia subjetiva (alta/media/baja).
|
||||
- **Tipo** distingue:
|
||||
- **[estructural]** = bug latente, deuda técnica medible, código muerto
|
||||
- **[opinable]** = cambio de estilo/diseño que el usuario puede rechazar legítimamente
|
||||
- **Esfuerzo**: small (1 commit < 30 min) / medium (1-3 commits) / large (varias sesiones).
|
||||
|
||||
Antes de actuar sobre un hallazgo, **re-verificar** que sigue siendo válido (el código habrá cambiado).
|
||||
|
||||
---
|
||||
|
||||
## Hallazgos por área
|
||||
|
||||
### A. Escenas
|
||||
|
||||
#### 1. `Scene` no declara `init()`, pero `GameScene` lo expone — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**: `source/core/system/scene.hpp:31-40`, `source/game/scenes/game_scene.hpp:47`, `source/game/scenes/game_scene.cpp:64` (`init()` se llama desde el ctor).
|
||||
- **Problema**: La interfaz `Scene` declara `handleEvent/update/draw/isFinished` como obligatorios. `GameScene::init()` es un método público adicional, pero el Director nunca lo llama — `GameScene` lo invoca desde su propio ctor. El comentario del header dice "init() — llamado por Director tras crear la escena", pero eso no ocurre. `TitleScene` y `LogoScene` no tienen ese método: hacen todo en el ctor. Inconsistencia de lifecycle.
|
||||
- **Propuesta**: (a) eliminar `init()` y mover su contenido al ctor (lo más simple), o (b) añadirlo a la interfaz `Scene` y llamarlo desde el Director tras `buildScene`. Lo coherente es (a).
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 2. No existe clase base `MenuScene` para el patrón `timer + state-machine + skip-button` — [PRIORIDAD: media, opinable]
|
||||
- **Dónde**: `source/game/scenes/title_scene.cpp:368-388`, `source/game/scenes/logo_scene.cpp:219-280`, partes de `game_scene.cpp` (INIT_HUD/LEVEL_START).
|
||||
- **Problema**: Las tres escenas implementan la misma idea: un enum `State`, un timer acumulado, un `switch` por estado, una transición "skip". `checkSkipButtonPressed()` aparece en `TitleScene` y `LogoScene` con la misma signatura estática.
|
||||
- **Propuesta**: Mixin pequeño `TimedStateScene<EnumT>` con `enterState(EnumT)`, `time_in_state()`, helpers de "esperar N segundos y avanzar". Al menos consolidar `checkSkipButtonPressed()` y la pareja `temps_estat_actual_ + cambio de estado` en un helper.
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 3. `TitleScene::initTitle()` duplica dos veces el cálculo de bounding box + posicionado — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**: `source/game/scenes/title_scene.cpp:104-254` (función de 150 líneas).
|
||||
- **Problema**: Carga "ORNI" (108-166) y "ATTACK!" (178-241) con el mismo código copiado. `inicialitzarJailgames()` (256-325) hace lo mismo por tercera vez. Tres copias del cómputo de bounding box.
|
||||
- **Propuesta**: Extraer `loadLetterRow(filenames, scale, y_position, espai) -> std::vector<LetraLogo>`.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 4. `PLAYER_JOIN_PHASE` en TitleScene mezcla animación + audio + late join — [PRIORIDAD: media, opinable]
|
||||
- **Dónde**: `source/game/scenes/title_scene.cpp:451-483`.
|
||||
- **Problema**: `updatePlayerJoinPhaseState` hace varias cosas no relacionadas. La lógica de "late join" reaprovecha `checkStartGameButtonPressed()` (que *muta* `match_config_` — efecto secundario no advertido por la firma).
|
||||
- **Propuesta**: Renombrar `checkStartGameButtonPressed()` a `tryActivatePlayersOnStart()`. Separar la lógica de "late join" en un helper.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 5. `SceneContext::actual` es estado global redundante — [PRIORIDAD: baja, opinable]
|
||||
- **Dónde**: `source/core/system/scene_context.hpp:81`, escrita en `director.cpp:267,275` y `global_events.cpp:29,58`.
|
||||
- **Problema**: `inline SceneContext::SceneType actual` como variable global, sincronizada manualmente. No la consulta nadie excepto el propio Director (que lee `context.nextScene()`, no `actual`).
|
||||
- **Propuesta**: Verificación profunda. Si nadie la consulta, eliminarla.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
### B. Sistemas
|
||||
|
||||
#### 6. `StageManager::update(dt, pause_spawn)` tiene `pause_spawn` muerto — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**: `source/game/stage_system/stage_manager.hpp:28`, `source/game/stage_system/stage_manager.cpp:128-135`, `game_scene.cpp:359-374,423-424`.
|
||||
- **Problema**: El parámetro `pause_spawn` existe en la API pero ningún caller lo pasa con sentido — la pausa real ocurre por otra ruta (`spawn_controller_.update(dt, enemies, PAUSE_SPAWN)`). El comentario en `stage_manager.cpp:131-134` ya admite que no se usa.
|
||||
- **Propuesta**: Quitar el parámetro.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 7. `SpawnController` hardcoda `std::array<Enemy, 15>` en su firma — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/game/stage_system/spawn_controller.hpp:33,37,39`; `.cpp:44,89,96`.
|
||||
- **Problema**: Literal `15` en lugar de `Defaults::Entities::MAX_ORNIS`. Acoplamiento silencioso por número.
|
||||
- **Propuesta**: Usar `std::array<Enemy, Defaults::Entities::MAX_ORNIS>&` o un alias `using EnemyArray = ...;`.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 8. `Systems::Collision::Context` y `Systems::ContinueScreen::Context` son "god structs" — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**: `source/game/systems/collision_system.hpp:31-43`, `source/game/systems/continue_system.hpp:25-38`.
|
||||
- **Problema**: Cada `Context` agrupa entre 9 y 12 referencias mutables a estado de `GameScene` + un `std::function` callback. Punto único donde cualquier cambio de campo en GameScene rompe el módulo. Los sistemas pueden mutar todo (no hay distinción "lo que leo" vs "lo que modifico"). La separación `GameScene` ↔ `Systems::*` es nominal.
|
||||
- **Propuesta**: Que `detectAll` devuelva una struct "outcome" (qué balas se desactivaron, qué enemigos murieron, qué jugador fue hit) y que GameScene aplique el resultado. Si es demasiado, al menos partir el Context en `In` (lectura) e `Out` (escritura).
|
||||
- **Esfuerzo**: large
|
||||
|
||||
#### 9. `CollisionSystem` y `PhysicsWorld` se solapan — [PRIORIDAD: media, opinable]
|
||||
- **Dónde**: `source/game/systems/collision_system.cpp` (todo), `source/core/physics/physics_world.cpp:118-179`.
|
||||
- **Problema**: `PhysicsWorld::resolveBodyCollisions` resuelve colisiones cuerpo-cuerpo con impulsos elásticos; `CollisionSystem::detectBulletEnemy` itera otra vez los mismos pares con un *amplifier* distinto y aplica daño. Dos bucles O(n²) sobre el mismo conjunto. El comentario en `game_scene.cpp:159` explica la sutileza pero es frágil.
|
||||
- **Propuesta**: Verificación profunda. ¿Deberían las balas vivir fuera del world directamente?
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 10. `init_hud_animator` es solo 3 funciones libres en un namespace — [PRIORIDAD: baja, opinable]
|
||||
- **Dónde**: `source/game/systems/init_hud_animator.hpp`, `.cpp` (99 líneas).
|
||||
- **Problema**: A diferencia de `CollisionSystem`/`ContinueSystem`, no tiene `Context`; son 3 funciones puras. La nomenclatura sugiere algo más grande.
|
||||
- **Propuesta**: Mover a `source/core/rendering/` o renombrar `systems/` a `gameplay/`.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
### C. Entidades
|
||||
|
||||
#### 11. `Ship` expone tres métodos equivalentes: `isAlive()`, `isHit()`, `isActive()` — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**: `source/game/entities/ship.hpp:27,38,39`.
|
||||
- **Problema**: `isActive() = !is_hit_`, `isAlive() = !is_hit_`, `isHit() = is_hit_`. Tres formas de preguntar lo mismo, usadas en sitios distintos.
|
||||
- **Propuesta**: Quedarse con `isActive()` (override de Entity) y reescribir los call-sites.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 12. `Entity::renderer_` raw pointer; ctor por defecto deja la entidad inservible — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/entities/entity.hpp:56,67`, `source/game/entities/ship.hpp:15-16` (`Ship() : Entity(nullptr) {}`), idem en `enemy.hpp:38-39`, `bullet.hpp:15-16`.
|
||||
- **Problema**: El ctor por defecto existe solo para soportar `std::array<Ship, 2>`, pero deja `renderer_` nullptr. Si alguien dibuja antes de la reasignación, segfault. `renderer_` no tiene in-class initializer.
|
||||
- **Propuesta**: (a) Inicializador en miembro `Rendering::Renderer* renderer_{nullptr};`. (b) Guard en `draw()` para `renderer_ == nullptr`. (c) Considerar `std::array<std::optional<Ship>, 2>`.
|
||||
- **Esfuerzo**: small (a)+(b); medium (c)
|
||||
|
||||
#### 13. Duplicación de estado: `center_/angle_` en Entity vs `body_.position/body_.angle` en RigidBody — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/entities/entity.hpp:58-59,64`; `source/game/entities/ship.cpp:131-135` (postUpdate sincroniza), `enemy.cpp:214-219`, `bullet.cpp:121-125`.
|
||||
- **Problema**: Cada entidad mantiene `center_/angle_` como mirror del body. Cualquier código que lea `getCenter()` durante `update()` (antes del `postUpdate`) verá el valor del frame anterior. El patrón está documentado pero es frágil.
|
||||
- **Propuesta**: Hacer que `getCenter()` devuelva `body_.position` directamente (eliminando `center_`).
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 14. `Entity::renderer_` `protected:` con acceso directo + getter por referencia mutable a `body_` — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/entities/entity.hpp:51-52` (`getBody() -> RigidBody&`), `:54-64`.
|
||||
- **Problema**: Encapsulación abierta + getters que devuelven referencias no-const al `body_`. El contrato de quién muta `body_.velocity`/`position` no está claro.
|
||||
- **Propuesta**: Documentar el contrato. Hacer `body_` privado con API estricta (`registerInWorld(world)`).
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 15. `Enemy` tiene `drotacio_/rotacio_` (rotación visual) Y `body_.angle/angular_velocity` — [PRIORIDAD: baja, estructural]
|
||||
- **Dónde**: `source/game/entities/enemy.hpp:92-93`, `.cpp:210-212`.
|
||||
- **Problema**: El body de un enemy nunca rota (angular_velocity siempre 0), pero la visual sí. Dos representaciones de orientación viviendo en la misma entidad.
|
||||
- **Propuesta**: Eliminar `body_.angle/angular_velocity` para enemies (no se usa).
|
||||
- **Esfuerzo**: small
|
||||
|
||||
### D. Renderizado y recursos
|
||||
|
||||
#### 16. `Rotation3D` (struct + parámetro en `renderShape`) es código muerto — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/rendering/shape_renderer.hpp:18-35,52`, `.cpp:13-78`.
|
||||
- **Problema**: La función `renderShape` acepta `const Rotation3D* rotation_3d = nullptr`. **Todos** los callers pasan `nullptr` (`ship.cpp:161`, `enemy.cpp:232`, `bullet.cpp:137`). 28 líneas de código muerto.
|
||||
- **Propuesta**: Eliminar `Rotation3D`, el parámetro y el branch.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 17. `Resource::Helper` es una fachada delgada sobre `Resource::Loader` — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/resources/resource_helper.hpp`, `.cpp` (80 líneas); `resource_loader.hpp` (54 líneas).
|
||||
- **Problema**: `Helper::loadFile()` es `Loader::get().loadResource(normalizePath(...))`. La única "lógica real" de Helper es `normalizePath()`. Duplicación de API.
|
||||
- **Propuesta**: Mover `normalizePath` a `Resource::Loader` y eliminar `Helper`.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 18. `ShapeLoader::resolvePath` parece código muerto — [PRIORIDAD: baja, opinable]
|
||||
- **Dónde**: `source/core/graphics/shape_loader.hpp:36`, `.cpp:70-83`.
|
||||
- **Problema**: `resolvePath` está declarada como helper privado, pero no se llama desde ningún sitio.
|
||||
- **Propuesta**: Eliminar `resolvePath` y `BASE_PATH`.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 19. `ShapeLoader::cache` sin política de invalidación — [PRIORIDAD: baja, estructural]
|
||||
- **Dónde**: `source/core/graphics/shape_loader.cpp:13,15-60`.
|
||||
- **Problema**: Caché estática `unordered_map<string, shared_ptr<Shape>>`. `clearCache()` existe pero nadie la llama. Sin límite de tamaño.
|
||||
- **Propuesta**: Documentar en el header que la caché vive toda la vida del proceso.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 20. `GpuFrameRenderer::device()` y `isInsideFrame()` son getters públicos sin callers — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/rendering/gpu/gpu_frame_renderer.hpp:96,97`. Similar `SDLManager::getScaleFactor()` (línea 42).
|
||||
- **Problema**: API pública sin clientes.
|
||||
- **Propuesta**: Eliminar o documentar como API reservada.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
### E. Configuración
|
||||
|
||||
#### 21. `Options::physics`, `Options::audio`, `Options::gameplay` se cargan desde YAML pero nunca se consultan en runtime — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**:
|
||||
- `Options::physics` se rellena en `options.cpp:194-201`, parse 286-291, write 597-603. Único call-site lector: `director.cpp:109` (informativo). Ship/Enemy/Bullet leen `Defaults::Physics::*` directamente.
|
||||
- `Options::audio` documentado como "deliberadamente desacoplado" (`audio.hpp:22-25`); `director.cpp:235-244` construye `Audio::Config` desde `Defaults::Audio`.
|
||||
- `Options::gameplay` (max_enemies, max_bullets): las arrays son compile-time (`std::array<Enemy, MAX_ORNIS>`).
|
||||
- **Problema**: El usuario edita su `config.yaml`, cambia `physics.rotation_speed: 6.0`, reinicia el juego — el juego sigue usando el default. Prometemos configurabilidad que no entregamos.
|
||||
- **Propuesta**: O bien (a) conectar Options→runtime, o (b) eliminar los campos muertos. Híbrido: borrar `audio`/`gameplay`, conectar `physics` (apoya Fase 10).
|
||||
- **Esfuerzo**: small (eliminar) o medium (conectar correctamente).
|
||||
|
||||
#### 22. `defaults.hpp` mezcla constantes de cinco dominios distintos en un solo archivo de 530 líneas — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/defaults.hpp` (todo).
|
||||
- **Problema**: Contiene Window, Game, Zones, Entities, Palette, Ship, Physics, Math, Brightness, Rendering, Audio, Music, Sound, Controls, Enemies (con sub-namespaces), Title, FloatingScore. **22 archivos** lo incluyen. Editar una constante de Audio fuerza recompilación de todo. Dos `namespace Game` separados (líneas 22-25 y 132-210).
|
||||
- **Propuesta**: Partir en `defaults/window.hpp`, `defaults/game.hpp`, `defaults/zones.hpp`, etc. Con `defaults.hpp` como umbrella.
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 23. `Defaults::Window::WIDTH = 1280` y `Defaults::Game::WIDTH = 1280` son duplicados sin relación enforced — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/defaults.hpp:10` vs `:23`.
|
||||
- **Problema**: Dos `WIDTH` distintos pero con el mismo valor. Nadie obliga a que coincidan. Si alguien quisiera arrancar a 1366×768 cambiando `Window::WIDTH`, el cálculo `zoom_factor = (float)width / Window::WIDTH` (`sdl_manager.cpp:101`) usa `Window::WIDTH`, no `Game::WIDTH` — bug latente.
|
||||
- **Propuesta**: Decidir la fuente de verdad. Si es `Game::{WIDTH,HEIGHT}`, derivar `Window::WIDTH = Game::WIDTH`.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 24. `game/constants.hpp` contiene aliases legacy mayoritariamente muertos — [PRIORIDAD: baja, estructural]
|
||||
- **Dónde**: `source/game/constants.hpp:10-23`.
|
||||
- **Problema**: `Constants::MARGIN_*`, `MAX_ORNIS`, `MAX_BALES`, `VELOCITAT`, `VELOCITAT_MAX` — no se usan. Solo se usan los helpers (PI, isInPlayArea, getSafePlayAreaBounds, getPlayAreaCenter).
|
||||
- **Propuesta**: Eliminar las constantes muertas.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 25. `Defaults::Physics::{ENEMY_SPEED, BULLET_SPEED, VELOCITY_SCALE}` legacy de Pascal son código muerto — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/defaults.hpp:218-220`.
|
||||
- **Problema**: Los valores están en unidades/frame, comentados como "actuales del juego", pero la migración a SDL3 los reemplazó (`bullet.cpp:22` define su propio `BULLET_SPEED = 140.0F`; enemy speeds vienen de `Defaults::Enemies::{Pentagon,Cuadrado,Molinillo}::VELOCITAT`).
|
||||
- **Propuesta**: Eliminar.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
### F. Naming y consistencia de idioma
|
||||
|
||||
#### 26. Variables del estado de juego mezclan catalán y castellano — [PRIORIDAD: baja, opinable]
|
||||
- **Dónde**: `source/game/scenes/game_scene.hpp:67-77`, `source/game/scenes/title_scene.hpp:65-84`.
|
||||
- **Problema**: Conviven `hit_timer_per_player_` (inglés), `lives_per_player_` (inglés), `match_config_.jugador1_actiu` (catalán). En TitleScene: `estat_actual_` (catalán) junto a `factor_lerp_`. No hay convención clara dentro de la misma clase.
|
||||
- **Propuesta**: Si el usuario quiere preservar el sabor, reglar: identificadores expuestos en headers públicos en catalán, detalles internos en inglés.
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 27. Dos namespaces `Constants` no relacionados — [PRIORIDAD: baja, opinable]
|
||||
- **Dónde**: `source/game/constants.hpp:8` (`namespace Constants`) y `source/game/stage_system/stage_config.hpp:82` (`namespace StageSystem::Constants`).
|
||||
- **Problema**: Mismo nombre, scopes distintos. Lecturalmente confuso.
|
||||
- **Propuesta**: Renombrar el del stage system a `StageSystem::Messages`.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
### G. Headers y dependencias
|
||||
|
||||
#### 28. `core/` depende de `game/` — violación de capas — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**:
|
||||
- `source/core/system/director.cpp:25-28` incluye `game/scenes/*.hpp` y `game/options.hpp`
|
||||
- `source/core/rendering/sdl_manager.cpp:16` incluye `game/options.hpp`
|
||||
- `source/core/system/debug_overlay.cpp:8` incluye `game/options.hpp`
|
||||
- `source/core/input/input.cpp:11` incluye `game/options.hpp`
|
||||
- **Problema**: La estructura sugiere un layered design, pero `core/` lee y muta `game::Options`. Si se intenta convertir `core/` en librería estática reusable, no compila.
|
||||
- **Propuesta**: Mover `Options` a `source/core/config/`, o invertir la dependencia con `IConfigProvider*`. Relacionado con #21.
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 29. `continue_system.hpp` forward-declara `enum class GameOverState` definida a nivel global en `game_scene.hpp` — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/game/scenes/game_scene.hpp:28-32` (declaración en namespace global, NO dentro de la clase), `source/game/systems/continue_system.hpp:18-20`.
|
||||
- **Problema**: Si la subyacente cambia de `uint8_t` a `int`, las dos declaraciones divergen silenciosamente.
|
||||
- **Propuesta**: Mover `GameOverState` a `source/game/systems/game_over_state.hpp` (header de tipos puros).
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 30. `core/defaults.hpp` se incluye en 22 archivos arrastrando ~530 líneas — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: Ver #22.
|
||||
- **Problema**: Tiempo de compilación incremental innecesariamente alto.
|
||||
- **Propuesta**: Ver #22.
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
### H. Bugs latentes / code smells
|
||||
|
||||
#### 31. `hit_timer_per_player_` usa el valor mágico `999.0F` como sentinela "jugador permanentemente inactivo" — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**: `source/game/scenes/game_scene.cpp:140,229,230,239,312,332`.
|
||||
- **Problema**: El timer combina tres estados en un float: `== 0.0F` = vivo, `> 0 && < 3.0` = secuencia de muerte, `== 999.0F` = sin vidas. Si en algún momento se decrementa accidentalmente, el sentinela se pierde.
|
||||
- **Propuesta**: Introducir un enum `PlayerStatus { ALIVE, DYING, OUT }` separado del timer; el timer solo cuenta `DYING`.
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 32. `std::rand()` en todo el gameplay; `std::mt19937` solo en LogoScene; reseed solo en GameScene::init — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `std::srand` en `game_scene.cpp:80`; `std::rand` en `spawn_controller.cpp:131`, `enemy.cpp:133-447`, `stage_manager.cpp:84`, `debris_manager.cpp:121-352`. `std::mt19937` en `logo_scene.cpp:171-173`.
|
||||
- **Problema**: Mezcla de PRNG. `std::rand()` tiene calidad mala y es global. `srand` solo se llama una vez por partida; el logo y el título usan la seed por defecto. Repetibilidad accidental.
|
||||
- **Propuesta**: Centralizar un `Random` (single mt19937 con seeds explícitas). Al menos `srand` en main/Director para que se siembre antes.
|
||||
- **Esfuerzo**: small (centralizar srand) o medium (reemplazar todos).
|
||||
|
||||
#### 33. `LineRenderer` mantiene dos globales mutables: `g_current_line_color` y `g_current_line_thickness` — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/core/rendering/line_renderer.cpp:11,15`.
|
||||
- **Problema**: Estado mutable global en la TU. Si alguna escena cambia color y se olvida de restaurarlo, contamina las siguientes. Misma problemática con `g_current_scale_factor` en `coordinate_transform.cpp:9`.
|
||||
- **Propuesta**: Moverlos a miembros del `GpuFrameRenderer`. El color global puede vivir como uniform de pipeline.
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 34. `GameScene::init()` reasigna `enemies_[i] = Enemy(...)` duplicando el trabajo del constructor — [PRIORIDAD: alta, estructural]
|
||||
- **Dónde**: `source/game/scenes/game_scene.cpp:56-59` (ctor) vs `:149-153` (init).
|
||||
- **Problema**: En el ctor se hace `enemy = Enemy(sdl.getRenderer())`. Inmediatamente después se llama `init()` (línea 64), que reasigna otra vez. Trabajo perdido. Inconsistencia con bullets (que solo se inicializan una vez).
|
||||
- **Propuesta**: Eliminar la inicialización en el ctor o en init. Decidir cuál es la fuente única.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 35. `runCollisionDetections()` reconstruye un `Context` enorme cada frame — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/game/scenes/game_scene.cpp:470-484`, similar `stepContinueScreen` (`:252-282`).
|
||||
- **Problema**: Cada frame en PLAYING se construye una struct con 9 referencias + un `std::function` (que aloca en heap por la captura `[this]`). 60 allocs/segundo del `std::function`.
|
||||
- **Propuesta**: Cachear el Context como miembro o pasar el callback como puntero a función miembro.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 36. `SpawnController::generateSpawnEvents` precomputa toda la cola pero `update()` solo procesa secuencialmente — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/game/stage_system/spawn_controller.cpp:54-82`.
|
||||
- **Problema**: El flag `event.spawnejat` parece sobrante (el index ya marca progreso).
|
||||
- **Propuesta**: Verificación profunda. Simplificable.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 37. `Director::run()` es estático pero usa estado de instancia via singletons — [PRIORIDAD: baja, estructural]
|
||||
- **Dónde**: `source/core/system/director.hpp:21-25`, `.cpp:41-48,150`.
|
||||
- **Problema**: `executable_path_` se asigna en ctor pero nadie lo lee. `system_folder_` se usa en ctor pero no después. El patrón "Director con state" es decorativo.
|
||||
- **Propuesta**: O hacer `run()` no-estática, o eliminar los miembros y mover `run()` a una función libre `App::main()`.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
### Otros
|
||||
|
||||
#### 38. `MatchConfig::jugador{1,2}_actiu` mutación desde dos sistemas independientes sin notificar — [PRIORIDAD: media, estructural]
|
||||
- **Dónde**: `source/game/scenes/game_scene.cpp:912-916` (joinPlayer), `source/game/systems/continue_system.cpp:29-33` (revivePlayer).
|
||||
- **Problema**: `MatchConfig` viaja como referencia mutable por los `Context` y se modifica desde múltiples sistemas. Rompe la intuición de que `MatchConfig` es "la configuración decidida en TitleScene".
|
||||
- **Propuesta**: Renombrar el campo a `player_active_runtime` o mover el flag a otro lado (ej. dentro de `lives_per_player_`: si lives > 0 → activo).
|
||||
- **Esfuerzo**: medium
|
||||
|
||||
#### 39. Inconsistencia: `LogoScene::~LogoScene()` llama `stopAllSounds()`, `TitleScene::~TitleScene()` llama `stopMusic()`, `GameScene::~GameScene()` es default — [PRIORIDAD: baja, opinable]
|
||||
- **Dónde**: `source/game/scenes/logo_scene.cpp:62-66`, `title_scene.cpp:99-102`, `game_scene.hpp:38`.
|
||||
- **Problema**: Cada escena gestiona limpieza de audio a su manera. Si una olvida un sonido en loop, fuga.
|
||||
- **Propuesta**: Decidir si el Director debe llamar `Audio::stopAllSounds()` y `stopMusic()` antes de destruir cada escena.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
#### 40. `Bullet::BULLET_SPEED = 140.0F` en anonymous namespace, no en `defaults.hpp` — [PRIORIDAD: baja, opinable]
|
||||
- **Dónde**: `source/game/entities/bullet.cpp:22-23`.
|
||||
- **Problema**: Constante de gameplay escondida en una TU privada. Si alguien quiere tunear, mira en defaults.hpp y no la encuentra (ahí hay otro `BULLET_SPEED` diferente y muerto, ver #25).
|
||||
- **Propuesta**: Mover a `Defaults::Entities::BULLET_SPEED_PXS`; eliminar el legacy de Pascal.
|
||||
- **Esfuerzo**: small
|
||||
|
||||
---
|
||||
|
||||
## Resumen ejecutivo
|
||||
|
||||
### Top picks "bug latente / deuda clara"
|
||||
|
||||
| # | Hallazgo | Esfuerzo |
|
||||
|---|---|---|
|
||||
| **21** | `Options::physics/audio/gameplay` se cargan del YAML pero nadie las consulta | small-medium |
|
||||
| **28** | `core/` depende de `game/` (4 ficheros incluyen `game/options.hpp`) | medium |
|
||||
| **11** | `Ship::isAlive() == isActive() == !isHit()` (tres alias del mismo bool) | small |
|
||||
| **34** | `GameScene` construye `enemies_` dos veces (en ctor y en init) | small |
|
||||
| **31** | `hit_timer_per_player_ = 999.0F` como sentinela | medium |
|
||||
| **16** | `Rotation3D` es código muerto (28 líneas que nunca se ejecutan) | small |
|
||||
| **1** | `GameScene::init()` no encaja en el lifecycle de `Scene` | small |
|
||||
|
||||
### Top picks "limpieza con impacto"
|
||||
|
||||
| # | Hallazgo | Esfuerzo |
|
||||
|---|---|---|
|
||||
| **22** | `defaults.hpp` (530 líneas, 22 includes) dividido en subarchivos | medium |
|
||||
| **13** | `Entity::center_/angle_` mirror de `body_.position/angle` | medium |
|
||||
| **25** | Constantes legacy del Pascal muertas en `Defaults::Physics` | small |
|
||||
| **18** | `ShapeLoader::resolvePath` + `BASE_PATH` sin callers | small |
|
||||
|
||||
### Hallazgos que requieren verificación más profunda antes de actuar
|
||||
- **#5** (`SceneManager::actual`)
|
||||
- **#9** (separación CollisionSystem/PhysicsWorld)
|
||||
- **#20** (getters dead public)
|
||||
- **#36** (`event.spawnejat`)
|
||||
|
||||
### Hallazgos puramente opinables que el usuario podría rechazar legítimamente
|
||||
- **#2, #4, #10, #14, #15, #19, #26, #27, #37, #39, #40**
|
||||
|
||||
### Hallazgos estructurales claros (bug latente o deuda técnica medible)
|
||||
- **#1, #6, #11, #12, #16, #21, #28, #31, #34**
|
||||
|
||||
---
|
||||
|
||||
## Ya identificados y resueltos en la rama `chore/lint` (no incluidos arriba)
|
||||
- Patrón draw-by-state de `GameScene` (refactorizado: complejidad 59 → 3)
|
||||
- Complejidad de `TitleScene::update` (68 → 4)
|
||||
- Duplicado `LetraLogo` Title/Logo
|
||||
- Double `isActive()` en Bullet (redeclaración tras rename)
|
||||
- Refactor de `DebrisManager::explode` (39 → 3)
|
||||
- `Options::loadXXXConfigFromYaml` boilerplate (con templates `readField`)
|
||||
- `PhysicsWorld::resolveBodyCollisions` (35 → 5)
|
||||
|
||||
## Plan de ataque sugerido
|
||||
|
||||
1. **#21** (en curso): borrar `Options::audio` y `Options::gameplay`, conectar `Options::physics` a runtime
|
||||
2. **#16, #18, #24, #25** (dead code, commits limpios y rápidos)
|
||||
3. **#34** (eliminar doble inicialización de `enemies_`)
|
||||
4. **#11** (consolidar `isActive/isAlive/isHit`)
|
||||
5. **#1** (limpiar lifecycle de `Scene`)
|
||||
6. **#22 + #30** (partir `defaults.hpp` en subarchivos)
|
||||
7. **#28** (mover `Options` a `core/`)
|
||||
8. **#31** (enum `PlayerStatus` reemplazando 999.0F)
|
||||
9. **#13** (eliminar mirror `center_/angle_`)
|
||||
|
||||
A partir de ahí, decidir si seguir con el resto o pasar a otras prioridades (SDL_CALLBACKS, tuning de físicas).
|
||||
@@ -1,140 +0,0 @@
|
||||
# Plan de migración Orni Attack → SDL3 GPU + física vectorial
|
||||
|
||||
Documento maestro de la rama `rewrite/physics-gpu`. Resumen del progreso y
|
||||
qué queda. Si pierdes el contexto de la sesión, **léeme primero**.
|
||||
|
||||
## Visión
|
||||
|
||||
Modernizar la base técnica del juego antes de tunear jugabilidad:
|
||||
1. Limpiar legacy (polares, mezcla catalán/inglés, header guards).
|
||||
2. Adoptar formato 16:9 (1280×720).
|
||||
3. Importar subsistemas robustos de AEEA (audio, *input deferido*).
|
||||
4. Implementar física vectorial 2D con rigid bodies + colisiones elásticas.
|
||||
5. **Migrar renderizado a SDL3 GPU sin fallback** ([memoria: project-no-sdl-fallback](./.claude/projects/-home-sergio-gitea-orni-attack/memory/project_no_sdl_fallback.md)).
|
||||
6. Postprocesado y color.
|
||||
|
||||
El tuning de feeling se hace al final, post-GPU.
|
||||
|
||||
## Estado actual
|
||||
|
||||
Rama de trabajo: **`rewrite/physics-gpu`** (pusheada a Gitea).
|
||||
Tag de seguridad: **`beta-3.0`** (snapshot de `main` antes de empezar).
|
||||
|
||||
| Fase | Estado | Commits |
|
||||
|---|---|---|
|
||||
| 0 — Limpieza de código muerto (primitives, polígonos, Bresenham, polares) | ✅ | `6cf990b` |
|
||||
| 1a — `Punt → Vec2` con operadores modernos | ✅ | `cd38101` |
|
||||
| 1b — Renames de entidades + métodos virtuales a camelBack | ✅ | `ae5cc1c` |
|
||||
| 1c — Renames de escenas | ✅ | `5871d29` |
|
||||
| 1d — Renames effects/stage_system/locales | ✅ | `7ee359b` |
|
||||
| 1e — Pragma once + locales restantes + comentarios castellano | ✅ | `bf83f16` |
|
||||
| (fix) — clave YAML `quadrat → cuadrado` post-sweep | ✅ | `56533ca` |
|
||||
| 2 — Cambio a 1280×720 (16:9) | ✅ | `a4f6a55` |
|
||||
| 3 — Import del subsistema de Audio desde AEEA | ✅ | `ed98ef6` |
|
||||
| **4** — Input parcial AEEA | ⏭️ **saltado** (decisión usuario) | — |
|
||||
| 5 — Infraestructura física (`RigidBody`, `PhysicsWorld`) | ✅ | `0fd9360` |
|
||||
| 6a+b — `body_` en Entity + `world_` en GameScene | ✅ | `0574077` |
|
||||
| 6c — Migrar Ship | ✅ | `2fe22ff` |
|
||||
| 6d — Migrar Enemy | ✅ | `27242f5` |
|
||||
| 6e — Migrar Bullet | ✅ | `9993b2d` |
|
||||
| 7a — Infra GPU (shaders + wrappers, runtime dormido) | ✅ | `ba6fd00` |
|
||||
| 7b+c — Swap atómico a SDL3 GPU + line_renderer al pipeline | ✅ | `fa7da4c` |
|
||||
| 7d — Validación visual del usuario en hardware Vulkan | ✅ | — |
|
||||
| 9a — Extraer `Systems::Collision` | ✅ | `896a899` |
|
||||
| 9b — Extraer `Systems::ContinueScreen` | ✅ | `816bc02` |
|
||||
| 9c — Extraer `Systems::InitHud` | ✅ | `a4942fc` |
|
||||
| 9d — Descomponer `GameScene::update` (339→18 LOC) | ✅ | `808abb2` |
|
||||
| **8 — Postprocesado + paleta de colores por entidad** | 🔲 **siguiente** | — |
|
||||
| 10 — Tuning final de masa/restitución/damping | 🔲 | — |
|
||||
| Mac/Metal — shaders MSL en build | 🔲 | — |
|
||||
| 8 — Postprocesado, color, paleta por tipo | 🔲 | — |
|
||||
| 9 — Refactor de GameScene (2.877 LOC → módulos) | 🔲 | — |
|
||||
| 10 — Tuning final de masa/restitución/damping | 🔲 | — |
|
||||
|
||||
## Lo que queda inmediato (Fase 8 — postprocesado + paleta de colores)
|
||||
|
||||
Fase 7 y Fase 9 cerradas. Validación visual SDL3 GPU OK (`Backend GPU: vulkan`).
|
||||
`GameScene.cpp` 1429 → 1015 LOC; `update()` 339 → 18 LOC, complexity baja a
|
||||
< 10 por sub-paso. Tres sistemas extraídos a `source/game/systems/`:
|
||||
- `Systems::Collision` (~210 LOC propios)
|
||||
- `Systems::ContinueScreen` (~160 LOC propios)
|
||||
- `Systems::InitHud` (~155 LOC propios)
|
||||
|
||||
**Fase 8 — siguientes pasos**:
|
||||
1. Color por entidad (paleta semántica): naves blancas, balas verdes,
|
||||
pentagons azules, quadrats rojos, molinillos magenta, debris hereda
|
||||
color del padre. Hoy todo se pinta con el color global del oscilador
|
||||
(`g_current_line_color`). Hay que pasar el color al `pushLine` desde
|
||||
las entidades (cada entity expone un `getColor()`).
|
||||
2. Postprocesado básico:
|
||||
- Bloom/glow: render-to-texture al swapchain real con fragment shader
|
||||
que aplique kernel separable de blur sobre los píxeles iluminados.
|
||||
Requiere un pipeline extra y un par de texturas off-screen.
|
||||
- Opcional: scanlines o leve aberración cromática para feel CRT.
|
||||
|
||||
**Fase 10** (tras 8): tuning de mass/restitution/damping con feedback
|
||||
visual de los colores.
|
||||
|
||||
**Mac/Metal**: cuando vayamos a release de macOS, añadir compilación
|
||||
de los shaders también a MSL (con `spirv-cross` o `glslangValidator`).
|
||||
|
||||
## Memoria del proyecto
|
||||
|
||||
Las preferencias y decisiones persistentes están en:
|
||||
`~/.claude/projects/-home-sergio-gitea-orni-attack/memory/MEMORY.md` y los
|
||||
archivos enlazados desde ahí. Léelos al empezar sesión nueva.
|
||||
|
||||
Resumen:
|
||||
- **No fallback a SDL_Renderer** en Fase 7 — solo GPU o falla.
|
||||
- **Naming**: `.clang-tidy` exige `CamelCase` tipos, `camelBack` métodos,
|
||||
`lower_case_` miembros privados, `UPPER_CASE` constantes.
|
||||
- **Costuras del título tras 16:9** son tuning artístico, no bug, post-Fase 10.
|
||||
- **Push automático** tras cada commit en `rewrite/physics-gpu`.
|
||||
- **Smoke test xvfb** (`timeout 5 xvfb-run -a ./build/orni 2>&1 | head -40`)
|
||||
tras builds importantes para validar arranque.
|
||||
|
||||
## Convenciones técnicas
|
||||
|
||||
- **Lenguaje código**: inglés. **Lenguaje comentarios**: castellano. **Strings
|
||||
de UI**: valenciano (preservados).
|
||||
- **Coordenadas**: cartesianas (`Vec2`). Polares prohibidas (legacy).
|
||||
- **Resolución lógica**: 1280×720. Constantes en `core/defaults.hpp`.
|
||||
- **Audio**: import de AEEA. API: `Audio::get()->playSound/playMusic` con
|
||||
crossfade y efectos.
|
||||
- **Física**: `Physics::RigidBody` en `core/physics/rigid_body.hpp`,
|
||||
`Physics::PhysicsWorld` en `core/physics/physics_world.hpp/.cpp`.
|
||||
Entity tiene `body_` como member. Flujo tri-fase por frame en
|
||||
`GameScene::update`:
|
||||
1. `physics_world_.update(dt)` — integrar y resolver
|
||||
2. `entity.postUpdate(dt)` — sincronizar mirror (center_, angle_)
|
||||
3. Lógica del juego (input, AI, etc.) — aplica fuerzas/cambia velocity
|
||||
|
||||
## Cómo reanudar tras compactación
|
||||
|
||||
1. Lee este archivo.
|
||||
2. Lee `MEMORY.md` (memorias persistentes).
|
||||
3. `git log --oneline -20` para ver últimos commits.
|
||||
4. `git status` para ver si hay trabajo en curso sin commitear.
|
||||
5. Si la fase actual estaba a medias: continúa donde se quedó (este archivo
|
||||
indica en qué fase estamos).
|
||||
6. Tras cada commit, push automático: `git push origin rewrite/physics-gpu`.
|
||||
|
||||
## Configuración física actual (Fase 6e)
|
||||
|
||||
| Entidad | mass | radius (world) | restitution | linear_damping | angular_damping |
|
||||
|---|---|---|---|---|---|
|
||||
| Ship | 10.0 | 12 | 0.6 | 1.5 | 0.0 |
|
||||
| Enemy Pentagon | 5.0 | 20 | 1.0 | 0.0 | 0.0 |
|
||||
| Enemy Quadrat | 8.0 | 20 | 1.0 | 0.0 | 0.0 |
|
||||
| Enemy Molinillo | 4.0 | 20 | 1.0 | 0.0 | 0.0 |
|
||||
| Bullet | 0.5 | **0** (cinemática) | 0 | 0 | 0 |
|
||||
| Wall (PLAYAREA bounds) | ∞ (estático) | — | — | — | — |
|
||||
|
||||
Nota: Bullet usa `radius=0` en el body físico para que no participe en las
|
||||
colisiones del world (ni body-body ni bounds). El radio de gameplay
|
||||
(`Defaults::Entities::BULLET_RADIUS = 3`) lo expone vía `getCollisionRadius()`
|
||||
y lo consumen `check_collision` y la detección de salida del PLAYAREA en
|
||||
`Bullet::update`.
|
||||
|
||||
Las paredes son implícitas: `physics_world_.setBounds(PLAYAREA)` en
|
||||
`GameScene::init`. No son `RigidBody` separados, sino un AABB que rebota.
|
||||
@@ -1,15 +0,0 @@
|
||||
ORNI TODO:
|
||||
- Crear escena titulo: muestra el logo, el copyright, el texto de insert coin o press start y quizas un starfield de fondo
|
||||
- Revisar contador de frames: hace cosas raras, hacer que se actualice cada segundo
|
||||
- Añadir clase audio -> añadir ruidos a logo, title y game
|
||||
- Añadir resource.pack
|
||||
- Crear estados en GAME: GET_READY -> GAME -> GAME_OVER -> CONTINUE
|
||||
-> STAGE_COMPLETED -> GET_READY (next stage)
|
||||
- Añadir nuevos enemigos: cuadrado, ... otras formas (revisar Geometry Wars). Darles fiferente personalidad
|
||||
- La bala ha de salir de la punta de la nave, con forma cuadrada, va rotando
|
||||
- Pensar si todas las entidades deberian tener dos radios de colision: uno pequeño para colision con bordes o jugador, uno mas grande para colision con balas
|
||||
- Revisar que los debris salgan realmente en direccion contraria al centro de la figura
|
||||
- Crear progresion: fichero con enemigos para ese nivel. cuando se acaba la lista de niveles -> loop con dificultad aumentada. Pensar si jefe final. Juego no tiene fin
|
||||
- Menu de servicio? numero de vidas, numero de continues o free play, controles de audio, controles de video, colores, activar parpadeo o cambiar frecuencia
|
||||
- Cuando se destruye un enemigo, salen dos mas pequeños
|
||||
- El escalado de pantalla hace cosas raras al salir de full screen: permite ventanas no-4:3
|
||||
Reference in New Issue
Block a user