# 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.
