Documento que resume el estado de la migracion (rama
rewrite/physics-gpu), las fases completadas con sus commits, lo que
queda inmediato y donde estan las decisiones persistentes (memoria
del proyecto). Para sobrevivir compactaciones de contexto y permitir
reanudar la migracion en sesiones futuras sin perder el hilo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Segunda entidad migrada. Los enemigos (Pentagon, Quadrat, Molinillo)
ahora viven en el PhysicsWorld con velocidad vectorial. Las colisiones
entre enemigos quedan habilitadas automaticamente (novedad: antes no
se chocaban).
Cambios en enemy.hpp:
- Eliminado: float velocity_ (escalar)
- Eliminado: void mou() (lo hace el world)
- Anadido: override postUpdate()
- Anadido: helper privado setVelocityFromAngle(angle, speed)
- Anadido: direction_change_timer_ para zigzag periodico del Pentagon
Cambios en enemy.cpp:
- Constructor configura body_ (mass=5 default, radius=0 inactivo,
restitution=1.0 elastico, sin damping)
- init() ajusta masa por tipo:
* Pentagon: 5.0 (esquivador ligero)
* Quadrat: 8.0 (tanque pesado)
* Molinillo: 4.0 (agil rapido)
- init() setea body_.radius = ENEMY_RADIUS al spawn
- behaviorPentagon: zigzag por probabilidad temporal (0.8/s) en lugar
de detectar paredes; el rebote contra muros lo hace PhysicsWorld
- behaviorQuadrat: tracking discreto cada TRACKING_INTERVAL — mezcla
velocity actual con direccion al ship (LERP por tracking_strength)
- behaviorMolinillo: solo boost de rotacion visual cerca del ship;
movimiento puramente lineal integrado por el world
- destruir() pone velocity=0, angular=0, radius=0
- postUpdate() sincroniza center_ desde body_.position
- setVelocity(speed) mantiene la direccion, cambia solo la magnitud
Renames a camelBack (.clang-tidy del proyecto):
- get_drotacio -> getRotationDelta
- get_base_velocity -> getBaseVelocity, get_base_rotation -> getBaseRotation
- set_ship_position -> setShipPosition
- set_velocity -> setVelocity, set_rotation -> setRotation
- set_tracking_strength -> setTrackingStrength
- get_temps_invulnerabilitat -> getInvulnerabilityTime
- actualitzar_animacio -> updateAnimation
- actualitzar_palpitacio -> updatePalpitation
- actualitzar_rotacio_accelerada -> updateRotationAcceleration
- comportament_pentagon/quadrat/molinillo -> behaviorPentagon/Quadrat/Molinillo
- calcular_escala_actual -> computeCurrentScale
- intent_spawn_safe -> attemptSafeSpawn
(callsites actualizados en spawn_controller y game_scene)
Cambios en GameScene:
- En init(): physics_world_.addBody(&enemy.getBody()) por cada slot
(los inactivos tienen radius=0, no estorban)
- En update(): postUpdate() de cada enemy tras physics_world_.update
Cambios de comportamiento visibles esperados:
- Enemigos rebotan elasticamente contra paredes (restitution=1.0)
- Enemigos se chocan entre si (impulsos elasticos con masas distintas
por tipo: Quadrat empuja mas, Molinillo rebota mas)
- Pentagon zigzag periodico en lugar de solo al chocar pared
- Molinillo: comportamiento mas predecible (linea recta)
Aviso: Bullet sigue con su movimiento ad-hoc (Fase 6e pendiente).
Smoke test xvfb OK. Validacion gameplay del usuario pendiente.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Primera entidad migrada. La nave del jugador ya NO mantiene su propio
estado cinemático ad-hoc — toda la física vive en Entity::body_ y el
movimiento lo realiza Physics::PhysicsWorld.
Cambios en ship.hpp:
- Eliminado: float velocity_ (escalar, polar)
- Eliminado: void applyPhysics() (lo hace el world)
- Añadido: override postUpdate() para sincronizar center_/angle_
- getVelocityVector() ahora devuelve body_.velocity (Vec2 cartesiano)
- Nuevo getter getSpeed() = body_.velocity.length()
- setCenter() actualiza tanto el mirror como body_.position
- markHit() detiene el body_ (velocity = 0)
Cambios en ship.cpp:
- Constructor configura el body_:
* mass = 10.0 (referencia para impulsos en choques)
* radius = SHIP_RADIUS (12.0)
* restitution = 0.6 (rebote moderado en paredes)
* linear_damping = 1.5 s⁻¹ (fricción exponencial)
* angular_damping = 0.0 (la rotación es por input, no inercial)
- init() resetea body_ a la posición/orientación nueva, velocity = 0
- processInput() ahora:
* Rotación: modifica body_.angle directamente (no física)
* Thrust: applyForce(direction * mass * ACCELERATION)
- update() solo gestiona timer de invulnerabilidad y aplica el cap de
MAX_VELOCITY (el thrust acumula fuerza sin tope; clampamos body_.velocity)
- postUpdate() copia body_.position -> center_ y body_.angle -> angle_
- draw() sin cambios funcionales (usa getSpeed() en lugar de velocity_)
Cambios en GameScene:
- En init(): physics_world_.addBody(&ship.getBody()) por cada nave activa
- En update(): physics_world_.update(dt) + ship.postUpdate(dt) al inicio
del frame (las fuerzas del frame N-1 se integran en el frame N; 1
frame de latencia ~16ms, imperceptible a 60fps)
Cambios de comportamiento visibles esperados:
- La nave ahora rebota contra las paredes del PLAYAREA con restitution=0.6
(antes: clipping silencioso). PRIMERA muestra de la nueva física.
- Inercia: tras soltar THRUST, la nave conserva velocidad y se decelera
exponencialmente con linear_damping. Sensación más espacial.
- Velocidad limitada en magnitud vectorial (antes: escalar). El cap
preserva el feel arcade aproximado de MAX_VELOCITY = 120 px/s.
Edge case pendiente para tuning:
- Naves muertas siguen en el world como obstáculos físicos (radius=12).
No es crítico mientras los enemies/bullets no estén migrados.
Smoke test xvfb: arranca correctamente. Validación de feeling requiere
test del usuario en vivo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Infraestructura mínima para la migración real de entidades a física
vectorial (Fase 6c-e). Sin cambios de comportamiento: las entidades
aún no usan body_ ni se registran al mundo.
Entity (core/entities/entity.hpp):
- Nuevo member protegido: Physics::RigidBody body_ (default-construido)
- Nuevo método virtual: postUpdate(dt) — no-op por default, override
opcional para sincronizar mirror center_/angle_ desde body_ tras
la integración física.
- Nuevos getters: getBody() (mutable y const)
- Include de core/physics/rigid_body.hpp
GameScene (game/scenes/game_scene.hpp/cpp):
- Nuevo member: Physics::PhysicsWorld physics_world_
- En init(): physics_world_.clear() + setBounds(PLAYAREA). Las
entidades migradas se registrarán cada una en su propio init().
El loop de GameScene::update() no se modifica todavía. La invocación
de physics_world_.update(dt) + postUpdate() se añade en Fase 6c junto
con la primera entidad migrada (Ship), para validar el flujo tri-fase
con un caso real en lugar de cambios especulativos al control de flujo.
Smoke test xvfb OK. Compila y arranca sin cambios visibles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Crea los componentes base del nuevo motor de fisica sin alterar
todavia el comportamiento del juego. La migracion de Ship/Enemy/
Bullet al nuevo sistema queda para Fase 6.
Nuevos archivos:
- core/physics/rigid_body.hpp - struct POD con:
* Vec2 position, velocity (cartesianas, NO polares)
* float angle, angular_velocity
* mass, inverse_mass (cacheado; 0 = estatico)
* restitution (elasticidad 0..1)
* linear_damping, angular_damping (s-1, exponencial)
* radius (circulo de colision)
* applyForce / applyImpulse / clearAccumulators
* setStatic() para paredes/obstaculos
- core/physics/physics_world.hpp/.cpp - mundo fisico:
* Almacena RigidBody* (no-owning, ownership en entidades)
* setBounds(SDL_FRect) para paredes implicitas (PLAYAREA)
* update(dt) = integrate + resolveBoundsCollisions + resolveBodyCollisions
* Integrador semi-implicito de Euler + damping exponencial
* Resolucion circulo-circulo con correccion posicional e impulsos elasticos
* Formula del impulso: j = -(1+e)*(v_rel . n) / (1/m_a + 1/m_b)
* Broadphase trivial O(n^2): suficiente para ~25 cuerpos del juego
Decisiones de diseno:
- Velocidad en cartesianas (Vec2) en lugar de la representacion polar
actual (escalar velocidad + cos/sin del angulo cada frame). Adios al
acoplamiento entre orientacion y direccion de movimiento.
- Composicion sobre herencia: RigidBody es un struct independiente que
las entidades incrustaran como member en Fase 6, no una clase base.
- El integrador semi-implicito es la version estandar para juegos
arcade (mas estable que Euler explicito sin coste extra).
- Damping exponencial (exp(-damping*dt)) en lugar de lineal: mantiene
el feeling consistente independientemente del framerate.
- Sin gravedad: el juego es top-down, no necesita campo de fuerzas
global. Las entidades aplican sus propias fuerzas (thrust).
Pendiente Fase 6:
- Anadir RigidBody body_ a Entity (member, no pointer)
- Migrar Ship: thrust como applyForce, en lugar de velocity_ escalar
- Migrar Enemy: cambios de direccion via applyImpulse, rebotes los
hace PhysicsWorld
- Migrar Bullet: lineal sin damping, restitution=0 (no rebotan)
- Anadir PhysicsWorld a GameScene, registrar bodies, llamar update()
Compila y enlaza. Smoke test xvfb OK: el juego arranca igual que antes
(la nueva infraestructura aun no se invoca).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reemplaza el audio antiguo de orni_attack (singleton con new/delete
raw, sin efectos, sin crossfade) por el subsistema moderno de AEEA
(unique_ptr, RAII, crossfade nativo, echo/reverb, pitch-shift,
callbacks de fin de pista, getMusicDurationMs para timelines
deterministas).
Eliminados:
- source/core/audio/audio_cache.{hpp,cpp} (1 cache por subsistema)
- source/core/audio/jail_audio.hpp viejo (motor inline globals)
- source/external/stb_vorbis.h (v1.20)
Añadidos (copiados de AEEA, traducidos comentarios al castellano):
- source/core/audio/audio.{hpp,cpp} — singleton con Audio::Config inyectada
- source/core/audio/audio_adapter.{hpp,cpp} — adapter para getMusic/getSound
- source/core/audio/audio_effects.{hpp,cpp} — Schroeder reverb + echo DSP
- source/core/audio/jail_audio.{hpp,cpp} — Ja::Engine class-based, streaming
- source/core/audio/sound_effects_config.{hpp,cpp} — presets YAML (opcional)
- source/external/stb_vorbis.c (v1.22) — OGG decoder, versión más reciente
- source/external/stb_vorbis_impl.cpp — TU aislada para evitar clang-tidy
Adaptaciones:
- audio_adapter.cpp implementado a medida para orni: usa
Resource::Helper::loadFile (no Resource::Cache de AEEA que orni no
tiene). Cache local con unique_ptr<Ja::Music> / unique_ptr<Ja::Sound>.
- Includes: utils/defaults.hpp -> core/defaults.hpp, utils/log.hpp
reemplazado por iostream con std::cerr/std::cout.
API breaking changes (callsites migrados):
- Audio::init() -> Audio::init(Config); el Director construye la Config
desde Defaults::Audio::* (ENABLED, VOLUME, MUSIC_*, SOUND_*).
- Audio::get()->getMusicState() -> Audio::getMusicState() (ahora static).
- AudioCache::getMusic/getSound -> AudioResource::getMusic/getSound.
Defaults::Audio consolidado: ahora aglutina las constantes que antes
estaban repartidas entre namespace Audio (VOLUME, ENABLED), namespace
Music (VOLUME, ENABLED), namespace Sound (VOLUME, ENABLED). Las pistas
y rutas de efectos siguen en Music::* / Sound::*. Añadidas FREQUENCY,
FORMAT, CHANNELS, CROSSFADE_MS, VOLUME_STEP para el motor.
Beneficios para fases siguientes:
- Crossfade en transiciones de escena (uso: playMusic(name, -1, 1500)).
- Pitch-shift para variaciones de SFX (Audio::playSound(name, group, 0.95)).
- Echo/reverb DSP via playSoundWithEcho/Reverb (sounds.yaml presets).
- Callbacks setOnMusicEnded para sincronizar eventos con el fin de pista.
Compila y enlaza. Pendiente: test runtime del usuario.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
El juego pasa de 4:3 a 16:9. Solo se tocan las constantes raíz:
todo lo demás (PLAYAREA, SCOREBOARD, CENTER_X/Y, P1/P2_TARGET,
VANISHING_POINT, etc.) se deriva de Game::WIDTH/HEIGHT y se
recalcula automáticamente.
Decisión del usuario: priorizar la base técnica sobre el feeling
del juego. Las velocidades, masas, radios de colisión y tamaños
de shape se mantienen sin cambios — la nave se verá más pequeña
en relación al área de juego y habrá más espacio. El tuning
jugable se hará tras completar la migración (post-Fase 7 GPU).
Cambios:
- Defaults::Window::WIDTH/HEIGHT: 640/480 -> 1280/720
- Defaults::Window::MIN_WIDTH/MIN_HEIGHT: 320/240 -> 640/360 (16:9)
- Defaults::Game::WIDTH/HEIGHT: 640/480 -> 1280/720
- Options::Window defaults: width{640}/height{480} -> 1280/720
- logo_scene.cpp: PANTALLA_ANCHO/ALTO ya no hardcoded;
deriva de Defaults::Game (era 640/480 magic numbers)
- Comentarios obsoletos limpiados en defaults.hpp
(// w = 640.0, // 320.0f, etc.)
- Catalán residual traducido (marges->márgenes, percentatges->porcentajes,
Àrea->Área, contenidor->contenedor, automàtic->automático)
Verificado: el ShipAnimator del título usa CENTER_X / CENTER_Y /
P1_TARGET_X/Y / VANISHING_POINT_X/Y, todos derivados de Game::WIDTH
y Game::HEIGHT. Se reposicionan automáticamente. CLOCK_RADIUS=150
se mantiene (escala relativa al centro).
PostFase: con 1280x720 el bug del HUD en ventana puede haber
cambiado de síntomas. Verificar visualmente cuando se haga la prueba.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
El sweep de comentarios de la Fase 1e cambió por error el string
literal yaml["quadrat"] a yaml["cuadrado"] dentro de
stage_loader.cpp:172 (sed sin distinción comentario vs string).
El archivo data/stages/stages.yaml seguía teniendo la clave
'quadrat:', lo que provocaba:
[StageLoader] Error: enemy_distribution incompleta
[GameScene] Error: no s'ha pogut load stages.yaml
[StageManager] Error: config es null
-> Violación de segmento al pasar de TITLE a GAME
Solución coherente con la política "código en inglés/castellano,
strings de UI en valenciano": el YAML es archivo de configuración,
no UI, así que se alinea con el código.
Cambios:
- data/stages/stages.yaml: quadrat -> cuadrado en las 10 stages
- build/resources.pack regenerado con `make pack`
Audit completo: verificado que ninguna otra clave YAML ni string
literal de filename (.shp, .wav) fue tocada por el sweep.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Primera sub-fase del naming sweep. Punt era un struct sense
operacions, conservat per compatibilitat amb el Pascal original.
Substituit per Vec2, un aggregate amb operadors aritmetics, dot,
length, normalized i length_squared (camelBack: lengthSquared)
seguint les regles del .clang-tidy del projecte.
Canvis:
- core/types.hpp reescrit: nou struct Vec2 amb +=,-=,*=,/=,
unary -, ==, dot, length, lengthSquared, normalized
- Operadors fora de la classe: +, -, *, / (amb float per ambdues
bandes), - unari, ==
- Vec2 segueix sent aggregate (sense constructors definits):
els 'designated initializers' del codi existent funcionen igual:
Vec2{.x = ..., .y = ...}
- Sed global sobre 35 fitxers: tots els 'Punt' -> 'Vec2'
Net: 35 fitxers tocats, +180 / -114. Compila i enllaça.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aplicada la directiva "res llegacy" abans d'arrencar la migracio a fisica
vectorial + SDL3 GPU. Cada bossa de cruft que arrossegava el port de Pascal
queda eliminada.
Borrats (huerfanos):
- source/core/rendering/primitives.hpp/.cpp (modul/diferencia/angle_punt/
crear_poligon_regular)
- source/core/rendering/polygon_renderer.hpp/.cpp (rota_tri/rota_pol)
- core::types::Triangle, Poligon, IPunt
- Defaults::Entities::MAX_IPUNTS i alias a constants.hpp
- EscenaJoc::chatarra_cosmica_ (mai usat)
- Bresenham comentat dins de Rendering::linea()
Simplificat (parametre 'dibuixar' llegacy que sempre era true):
- Rendering::linea(...): treta la signatura bool dibuixar, retorn void
- Rendering::render_shape(...): treta la signatura bool dibuixar
- 11 callsites de linea() actualitzats (escena_joc, debris_manager)
- 12 callsites de render_shape() actualitzats
Modernitzats:
- 5 fitxers .shp netejats de comentaris polar->cartesia historics
- types.hpp queda nomes amb Punt (l'unica coordenada del joc)
- debris_manager.hpp afegit include explicit de defaults.hpp
Net: 452 linies eliminades, 56 afegides. Compila i enllaca correctament.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Check 11: llvm-include-order (0 errors - codi ja compleix)
- Check 12: misc-include-cleaner (detectar includes no usats i faltants)
- Configurar IgnoreHeaders per SDL3 (genera falsos positius)
- Fix: afegir <cstdint> a nau.hpp, enemic.hpp, bala.hpp
- Fix: afegir <cmath> a nau.hpp, enemic.hpp (std::cos/sin)
Include order validat segons LLVM coding standards.
Headers més nets i compilació més ràpida.
- escena_joc.hpp: 7 includes cambiados de ../ a rutas absolutas
- pre-commit hook: añadir validación de includes relativos
- Bloquea commits con includes tipo #include "../foo.hpp"
- Coherencia con CMakeLists.txt (include_directories desde source/)
Cambios aplicados:
- Reemplazar std::endl con '\n' (91 casos)
* std::endl hace flush del buffer (más lento)
* '\n' solo inserta newline (más rápido)
* Mejora rendimiento de logging/debug
Check excluido:
- performance-enum-size: Tamaño de enum no es crítico para rendimiento
Cambios aplicados:
- [[nodiscard]] añadido a funciones que retornan valores
- .starts_with() en lugar de .find() == 0
- Inicializadores designados {.x=0, .y=0}
- auto en castings obvios
- = default para constructores triviales
- Funciones deleted movidas a public
- std::numbers::pi_v<float> (C++20)
Checks excluidos:
- use-trailing-return-type: Estilo controversial
- avoid-c-arrays: Arrays C aceptables en ciertos contextos
- Check 6 (readability-simplify-boolean-expr): No cal canvis
- Deshabilitada temporalment check 3 (identifier-naming) per evitar
cascada de 300+ canvis de nomenclatura
- Exclosa source/core/audio/ i source/legacy/ dels targets de tidy
(per evitar "no checks enabled" error)
- Crear .clang-tidy local en source/core/audio/ con Checks: '-*'
- Excluir jail_audio.hpp y archivos que dependen de él (código externo)
- Ajustar HeaderFilterRegex en .clang-tidy raíz
- Check 3 (readability-identifier-naming): código ya cumple convenciones
🤖 Generated with Claude Code
- Agregar paréntesis explícitos en operaciones matemáticas para claridad
- Ejemplos: '1.0F - a * b' → '1.0F - (a * b)'
- 291 correcciones aplicadas automáticamente con clang-tidy
- Check 2/N completado
🤖 Generated with Claude Code
- Cambiar todos los literales float de minúscula a mayúscula (1.0f → 1.0F)
- 657 correcciones aplicadas automáticamente con clang-tidy
- Check 1/N completado
🤖 Generated with Claude Code
- Renombrar getCacheSize() → get_cache_size() (match con .hpp)
- Renombrar resolvePath() → resolve_path() (match con .hpp)
- Cambiar base_path → base_path_ (match con .hpp)
- Eliminar 'static' de definiciones fuera de clase (error de C++)