Commit Graph

144 Commits

Author SHA1 Message Date
JailDesigner f30b195778 feat(grid): graella verda fosca de fons al playfield (16x8) 2026-05-21 20:16:44 +02:00
JailDesigner 2bc07f8e8d tune(stage): typewriter ràpid però visible al missatge de nivell completat 2026-05-21 20:02:02 +02:00
JailDesigner ca6f863c0f tune(audio): efectes a 48000 Hz u8 mono i ajust de volums per defecte 2026-05-21 19:58:45 +02:00
JailDesigner 66faa07c00 tune(debug): overlay més endins del playfield i en color daurat 2026-05-21 19:50:45 +02:00
JailDesigner 51308fa25e tune(trail): vida més llarga, offset darrere i paleta vermella per al P2 2026-05-21 19:40:15 +02:00
JailDesigner 74d855357d feat(trail): estela daurada de partícules quan la nau accelera 2026-05-21 19:29:32 +02:00
JailDesigner dec72340de feat(audio): so hit.wav quan l'enemic passa a ferit 2026-05-21 19:05:42 +02:00
JailDesigner 1c1fd1273b tune(ship): puja MAX_VELOCITY de 120 a 180 px/s 2026-05-21 18:55:01 +02:00
JailDesigner e6eaf870c6 tune(bullets): puja MAX_BALES a 50 i deshardcoded el slot per jugador 2026-05-21 18:51:55 +02:00
JailDesigner 625cb19cba feat(postfx): toggle F6 per activar/desactivar el postprocessat 2026-05-21 18:45:29 +02:00
JailDesigner ae946b578e feat(bloom): glow separable two-pass amb composite preserve-core i paleta neon 2026-05-21 18:39:16 +02:00
JailDesigner 0cc1f7623a feat(fireworks): burst radial blanc al explotar enemic + tuning 2026-05-21 17:41:10 +02:00
JailDesigner 56ce1a3236 feat(fireworks): infraestructura (manager + pool + render, sin spawn aún) 2026-05-21 17:22:46 +02:00
JailDesigner 2869c63517 tune(debris): N=1, shrink completo y sin herencia angular en enemigos 2026-05-21 17:11:08 +02:00
JailDesigner 7505de074c feat(debris): rebote contra los limites del playarea (restitution 0.7) 2026-05-21 13:55:32 +02:00
JailDesigner ae1d1397b1 revert: vuelve al modelo de efd18ff + ENEMY_LIFETIME 3.0 -> 4.5 2026-05-21 13:46:25 +02:00
JailDesigner 0c8a9b744e tune(debris): un poco mas de rotacion + shrink mas rapido (1.4s) 2026-05-21 13:41:20 +02:00
JailDesigner 9b25e875f3 fix(debris): bug rotacion cuadratica + shrink exponencial; geometria autoritativa 2026-05-21 13:37:12 +02:00
JailDesigner e84f555a66 fix(debris): rotación visual decae con fricción + modulada por size_factor 2026-05-21 13:23:16 +02:00
JailDesigner 048263a1d0 feat(debris): modelo INTACTO→MENGUANDO→0 (sin pop, fade-out por tamaño) 2026-05-21 12:53:01 +02:00
JailDesigner efd18ff852 feat(debris): vida híbrida (mínima + umbral velocidad) + multiplier para enemigos 2026-05-21 12:07:50 +02:00
JailDesigner 44aa4e76e2 fix(physics): salta body-body collision quan algun cos té radius=0
resolveBodyPair afegeix early-out per a parells on a.radius<=0 o b.radius<=0.
Honra el comentari de bullet.cpp:30 ("radius=0 → sin colisión física,
cinemática pura") que abans no s'aplicava: amb bala radius=0 + enemic
radius=ENEMY_RADIUS, SUM_R era enemic radius i el body-body disparava
si la bala (a 700 px/s) penetrava el cos l'enemic entre frames.

Símptomes corregits:
- Pentagon: la bala "rebotava espectacularment" en lloc d'impactar.
- Quadrat: rebut un impulse double del cantó de la física que es
  sumava (o cancel·lava, segons l'angle) al manual, fent l'efecte
  inconsistent.

Ara la gameplay collision (Physics::checkCollision amb entity radius,
que ja és més generós) és l'única que tracta el parell bala-enemic.

A més: IMPACT_MOMENTUM_FACTOR 2.0 → 3.0 per compensar la pèrdua del
rebot físic i donar més empenta:
  - Pentagon (m=5) Δv = 210 px/s
  - Quadrat  (m=8) Δv = 131 px/s
  - Molinillo (m=4) Δv = 262 px/s

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:45:59 +02:00
JailDesigner e3af88ea8c tune(enemy): més empenta + cos inert quan està herit
- IMPACT_MOMENTUM_FACTOR: 1.0 → 2.0 (doble del moment de la bala).
  Pentagon Δv = 140 px/s (≈4× la seva velocity base), prou clar.
- Enemy::update: salta el switch de behavior (Pentagon zigzag,
  Quadrat tracking, Molinillo proximity-spin) mentre wounded_timer_>0.
  El enemic herit és un "cos mort" inert: només respon a la inèrcia
  del impulse rebut i a les col·lisions físiques resoltes per
  PhysicsWorld. Abans, el Quadrat renormalitzava la velocity cada 1s
  cap al ship, esborrant la inèrcia.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:33:03 +02:00
JailDesigner ff5dfab94d tune(bullet): empuje cuasi-físico (momento real de la bala)
Sustitueix IMPACT_IMPULSE (magnitud arbitrària radial) per
IMPACT_MOMENTUM_FACTOR (factor de transferència del moment de la bala).

El impulse ara és bullet.body.velocity * (bullet.body.mass * factor),
és a dir el moment lineal real de la bala, dirigit cap a on viatjava.
Amb factor=1.0 i la bala (m=0.5, v=700 px/s):
  - Pentagon (m=5)  → Δv = 70 px/s (doble de la seva velocity base)
  - Quadrat  (m=8)  → Δv = 44 px/s
  - Molinillo (m=4) → Δv = 88 px/s

Visiblement notable durant el segon de "ferit" abans de l'explosió.
El factor és tunable per pujar/baixar segons gusts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:37:23 +02:00
JailDesigner 5cb547db0a feat(collision): primer impacte fereix, segon mata; mort diferida via timer (Fase 3)
- Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE (placeholder 1.0).
- Enemy::herir(shooter_id) emmagatzema last_hit_by_ per a atribució posterior.
- collision_system: helper anònim explodeNow(ctx, enemy, shooter_id) que
  llegeix velocity/dades ABANS de destruir() (corregeix bug latent: el codi
  anterior llegia getVelocityVector() després de destruir, que zera velocity
  → l'explosió mai heretava inèrcia).
- detectBulletEnemy: primer impacte aplica impulse + herir(); segon impacte
  sobre enemy ferit dispara explodeNow immediata.
- processWoundedDeaths: explota enemics amb wound timer expirat aquest frame.
- detectAll: processWoundedDeaths abans de detectBulletEnemy (les expiracions
  maten primer; les bales del mateix frame ja no toquen el cos destruït).

Puntos s'atribueixen a la mort real, no a l'impacte inicial.

Build neta i smoke test xvfb OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:26:13 +02:00
JailDesigner dc2824a095 feat(collision): la bala transmet impulse mass-aware al enemic (Fase 2)
- Defaults::Physics::Bullet::IMPACT_IMPULSE (50 px·s placeholder)
- detectBulletEnemy: calcula normal bullet→enemy, normalitza
  (fallback a direcció de bala o (0,-1) si estan solapats) i crida
  enemy.applyImpulse(normal * IMPACT_IMPULSE) abans de destruir.

El destruir() immediat encara zera la velocity, així que l'efecte
visual no es nota: serà visible quan la Fase 3 difereixi la mort.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:22:25 +02:00
JailDesigner d169a1997c feat(enemy): afegeix estat "wounded" amb timer i API base (Fase 1)
- Defaults::Palette::WOUNDED ({255,215,0}) dorat per a parpadeig
- Defaults::Enemies::Wounded::{DURATION, BLINK_HZ}
- Enemy: wounded_timer_, wound_expired_this_frame_
- API: herir(), isWounded(), getWoundedTimer(),
  woundExpiredThisFrame(), consumeWoundExpired(), applyImpulse()
- update() decrementa timer i marca expiració al creuar 0
- destruir() reseteja l'estat wounded

Sense efectes visuals ni canvis de comportament: cap callsite invoca
encara herir() ni applyImpulse(). Build verda i smoke test xvfb OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:20:42 +02:00
JailDesigner 23bcd0816f tune(bullet): augmenta velocitat de la bala (×5) 2026-05-21 09:55:07 +02:00
JailDesigner 7139dea7f6 refactor(defaults): centralitza init hud, tips, hit timer, line thickness i debug overlay 2026-05-21 09:45:55 +02:00
JailDesigner 08100f60e8 refactor(defaults): centralitza constants de bullet, ship, enemy, hud i notifier 2026-05-21 09:39:36 +02:00
JailDesigner 61ae211dab chore(iwyu): subheaders concrets i pragma exports al umbrella
Reemplaça core/defaults.hpp pels subheaders concrets a director.cpp i
config_yaml.cpp (silencia unused-includes de clangd). Marca el umbrella
amb IWYU pragma: begin_exports/end_exports per evitar falsos positius
als consumidors transitius.
2026-05-21 08:52:19 +02:00
JailDesigner 5d1dae1d86 feat(render): resolució d'offscreen configurable via YAML
Separa el tamany lògic (1280×720) del render target offscreen. Llista
tancada de 5 presets 16:9 (720p/900p/1080p/1440p/2160p) llegida de
rendering.render_{width,height} amb fallback a 1280×720 si invàlida.
Inclou API resizeRenderTarget() preparada per al menú de servei futur.
2026-05-21 08:46:22 +02:00
JailDesigner 4252f3327f fix(notifier): ESC només confirma sobre el propi prompt de sortida 2026-05-21 08:24:22 +02:00
JailDesigner 9a79fb9774 chore(shaders): regenera postfx_frag_spv.h 2026-05-21 08:18:12 +02:00
JailDesigner 6629e9b9aa fix(warnings): cast RAND_MAX a float per evitar conversió implícita 2026-05-21 08:16:55 +02:00
JailDesigner 6259f594c8 feat(gpu): suport Metal/MSL a macOS i shaders SPIR-V embedits 2026-05-20 23:07:49 +02:00
JailDesigner d1ca0df1ab tune(notifier): notifyInfo en cian i text una mica més gran
- COLOR_INFO passa de blanc neutre (230,230,230) a cian (80,230,255)
  per a diferenciar més els toasts informatius dels d'avís/error.
- TEXT_SCALE de 0.4 → 0.55 perquè el text sigui més llegible amb
  l'aspect-fit del viewport.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:30:36 +02:00
JailDesigner 9eb8c58d87 feat(notifier): doble pulsació d'ESC per confirmar sortida
La primera ESC ja no tanca el joc directament: dispara un toast
"PREMEU ESC UN ALTRE COP PER EIXIR" en vermell. Mentre el toast està
entrant o aguantant (Notifier::isActiveWindow()), una segona ESC
confirma i tanca. Si l'usuari espera a que el toast comenci a sortir
o desaparegui, ESC torna a obrir la finestra de confirmació sense
tancar — només una doble pulsació consecutiva tanca.

Si el Notifier no existeix (no hauria de passar dins runFrameLoop),
ESC manté el comportament antic de tancar directament.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:11:43 +02:00
JailDesigner 470d2b85a4 feat(notifier): notificacions visuals als toggles F1-F5
Substitueixen els std::cout dels handlers per crides a notifyInfo() del
Notifier:
- F1/F2: ZOOM: X.YX (amb el valor actual)
- F3: PANTALLA COMPLETA / MODE FINESTRA
- F4: VSYNC ACTIU / VSYNC INACTIU
- F5: AA ACTIU / AA INACTIU

Tots els missatges en majúscules perquè la font vectorial actual només
té glyphs A-Z. Es manté la lògica de toggle i de persistència de cfg;
únicament canvia el canal de feedback (consola → toast HUD).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:10:24 +02:00
JailDesigner 81330f8432 feat(notifier): infrastructura del sistema de notificacions toast
- Notifier singleton (System::init/get/destroy) que dibuixa un cuadre
  centrat al centre-superior amb fons semitransparent (derivat oscur del
  color del text) i bordes en línies.
- Màquina d'estats HIDDEN → ENTERING → HOLDING → EXITING amb easing
  outCubic (entrada) i inCubic (sortida), slide de 300 ms.
- pushRect() afegit a GpuFrameRenderer (2 triangles, edge_dist=0) per
  poder pintar el fons opac/semitransparent reutilitzant el pipeline de
  línies — sense afegir cap pipeline nou.
- VectorText::render/renderCentered admeten color RGBA explícit
  (default {0,0,0,0} preserva el comportament previ amb oscil·lador
  global de color).
- Easing header-only a core/utils/easing.hpp (outCubic, inCubic).
- Director crea Notifier just després del DebugOverlay i el draweja com
  a última capa per damunt de l'escena i el debug.

Encara cap consumer el crida; els F1-F5 i la doble pulsació d'ESC
arriben en commits posteriors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:07:56 +02:00
JailDesigner 1ef9ca551f feat(antialias): toggle F5 i indicador AA al debug overlay
Permet alternar l'AA geomètric en runtime:

- Action::TOGGLE_ANTIALIAS bound a F5.
- GlobalEvents::handle reacciona al scancode F5 cridant sdl.toggleAntialias().
- SDLManager::toggleAntialias muta cfg_->rendering.antialias i propaga a
  gpu_renderer_.setAntialias().
- GpuFrameRenderer manté l'estat antialias_enabled_ (true per defecte) i
  pushLine adapta extrusió i edge_dist en funció del flag — geometria nua
  quan està OFF, fade als bords quan està ON.
- RenderingConfig guanya el camp `antialias{1}` per coherència amb vsync;
  l'estat NO es persisteix al YAML de moment (decisió volgudament conservadora,
  podem afegir-ho en un commit a part si cal).
- DebugOverlay (F11) mostra una tercera línia "AA: ON/OFF" sota VSYNC per
  poder comparar a temps real.
2026-05-20 21:39:24 +02:00
JailDesigner b10f2da647 feat(antialias): AA geomètric a les línies amb edge attribute i smoothstep
Afegim antialias geomètric (sense MSAA) al pipeline de línies aprofitant
que la línia ja es construeix com a quad extruït a CPU:

- LineVertex: nou camp edge_dist (±1 als laterals del quad, 0 al centre).
- pushLine: extrudeix 0.5px extra per banda (AA_PADDING) per allotjar el
  fade sense menjar gruix nominal.
- line.vert: passa l'edge_dist al fragment com a varying.
- line.frag: alpha *= 1 - smoothstep(0.7, 1.0, |edge_dist|) — fade Hermite
  C¹ als bords, sense banding.

AA actiu per defecte. El toggle a runtime (F5) ve en el commit següent.
2026-05-20 20:25:12 +02:00
JailDesigner 6063309932 fix(vsync): comprovar suport de present mode i loggejar el mode efectiu
setVSync demanava SDL_GPU_PRESENTMODE_IMMEDIATE sense comprovar suport.
A SDL_GPU només VSYNC està garantit; IMMEDIATE i MAILBOX són opcionals.
Si no estaven suportats (típicament Wayland/X11 amb compositor), SDL
retornava error i la swapchain es quedava en VSYNC sense que ho sabéssim.

Ara:
- Consultem SDL_WindowSupportsGPUPresentMode abans de fer la crida.
- En VSync OFF: provem IMMEDIATE → fallback a MAILBOX → si cap, ens
  quedem en VSYNC i avisem (driver/compositor força VSync).
- Loggejem sempre el mode efectiu (no només els errors), perquè ara mateix
  no hi havia forma de saber des de fora si el toggle havia tingut efecte.
2026-05-20 20:17:28 +02:00
JailDesigner 7c2499cd91 fix(fullscreen): preservar zoom_factor_ de windowed durant transicions a fullscreen
Quan arribava un SDL_EVENT_WINDOW_RESIZED en fullscreen, recalculàvem
zoom_factor_ a partir de la mida física i el clampàvem a max_zoom_. Això
"consumia" el zoom_factor_ que tenia l'usuari en windowed, així que en
tornar a windowed (F3) la mida quedava la del clamp, no la prèvia.

Ara, en fullscreen, zoom_factor_ i windowed_*_ es deixen intactes (no
participen del càlcul del viewport, que ja és aspect-fit sobre la mida
física). En windowed, comportament inalterat.
2026-05-20 20:15:28 +02:00
JailDesigner e0f8cf78ee fix(fullscreen): seleccionar mode 'borderless desktop' explícitament en toggleFullscreen
A SDL3, SDL_SetWindowFullscreen(true) sol hereta el SDL_DisplayMode que tingués
la finestra. Sense una crida prèvia a SDL_SetWindowFullscreenMode(win, nullptr),
el comportament és no determinista entre invocacions (i pot acabar en mode
exclusiu si abans hi havia hagut un mode setejat).

Afegim la crida amb mode=nullptr just abans d'activar el fullscreen perquè
sempre entrem en "borderless desktop" (cobrint el monitor on viu la finestra).
2026-05-20 20:12:33 +02:00
JailDesigner 20cfadeb0b fix(viewport): deslligar el viewport de zoom_factor_ (aspect-fit per pantalla física)
El viewport del pase final es calculava com `Game::WIDTH * zoom_factor_`. Però
`zoom_factor_` està capat a `max_zoom_`, que es deriva de `display - 100px`
(marge per a decoracions). En fullscreen això deixa marc negre als 4 costats:
amb display 1920×1080 max_zoom_≈1.25 → viewport 1600×900 dins de 1920×1080.

Ara l'escala es calcula directament de la finestra física actual com a
aspect-fit (`min(curW/1280, curH/720)`), de manera que el viewport sempre
omple un eix i lletraboxeja l'altre, independentment del zoom_factor_. El
zoom_factor_ continua dimensionant la finestra en mode windowed (F1/F2).
2026-05-20 20:10:10 +02:00
JailDesigner 329ae7a38e refactor(#28): renombrar Options → ConfigYaml + netejar aliases
Pas 7 final del hallazgo #28. La capa de game/options havia esdevingut
exclusivament una capa de persistència YAML que llegia i escrivia
Config::EngineConfig. El nom "Options" no reflectia bé aquest rol.

Canvis:

- Renomenats fitxers: game/options.{hpp,cpp} → game/config_yaml.{hpp,cpp}
  (preservant la història de git via mv).
- Renomenat namespace: Options → ConfigYaml.
- Esborrades del .hpp les referències-alias inline (window, rendering,
  player1, player2, keyboard_controls, gamepad_controls, console) que
  ja no tenien call-sites externs (només existien per a la transició).
  El .hpp ara només exposa engine_config + version + path + funcions.
- A config_yaml.cpp s'introdueixen aliases internes (anonymous namespace)
  per mantenir llegible el codi de la implementació, sense exposar-les.
- Actualitzat main.cpp per a usar ConfigYaml::*.
- Actualitzats els comentaris stale a sdl_manager.hpp, director.hpp,
  engine_config.hpp i audio.hpp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:47:18 +02:00
JailDesigner 41ce3fece5 refactor(#28): Director rep EngineConfig + ConfigPersistence, main orquestra
Pas 5/N del hallazgo #28.

Director deixa d'incloure game/options.hpp i les seves crides a
Options::*. El seu ctor accepta ara:
- Config::EngineConfig& cfg     → la struct runtime (window, console, ...).
- Config::ConfigPersistence     → 4 lambdes (init/set_path/load/save)
  que delegen la persistència a la capa concreta (game/Options::*).

Cap més referència a Options:: ni a "game/..." dins del Director:
- cfg_->* substitueix tot Options::* (window, console, player1/2,
  rendering, engine_config).
- persistence_.{init,save,load,set_path} substitueix les funcions
  d'I/O de YAML.

run() i checkProgramArguments deixen de ser estàtics (necessiten
accés a cfg_ i persistence_). Això també desfà el smell del
hallazgo #37 (Director::run estàtic que llegia estat d'instància).

main.cpp queda com a orquestrador: construeix la struct
ConfigPersistence amb lambdes que enllacen amb Options::* i la
injecta al Director.

Afegit: Config::ConfigPersistence a engine_config.hpp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:40:52 +02:00
JailDesigner fdd34eb943 refactor(#28): SDLManager rep Config::EngineConfig + on_persist callback
Pas 4/N del hallazgo #28.

SDLManager deixa d'incloure game/options.hpp. El ctor accepta ara una
Config::EngineConfig& (per llegir/mutar window i rendering) i un
opcional std::function<void()> on_persist callback.

Canvis funcionals:
- Es mantenen les mutacions de window.{width,height,zoom_factor,fullscreen}
  però ara sobre cfg_->window en lloc d'Options::window. Comportament
  idèntic perquè Options::window és un alias a engine_config.window.
- toggleVSync deixa de cridar Options::saveToFile() directament i
  invoca on_persist_ si està connectat. El Director li passa una lambda
  que fa la persistència (mantenint sdl_manager agnòstic).
- initWindowAndGpu (free function) rep el vsync inicial per paràmetre.
- Eliminat el ctor per defecte (SDLManager()) que no era cridat des de
  cap call-site del projecte.

Cleanup preexistent surfat per clang-tidy en treure el ctor default:
- finestra_, max_width_, max_height_, max_zoom_ passen a tindre
  default member initializers; eliminat el seu ctor mem-init redundant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:35:35 +02:00
JailDesigner d118218662 refactor(#28): Input rep Config::PlayerBindings per paràmetre
Pas 3/N del hallazgo #28.

Input deixa d'incloure game/options.hpp. Els antics applyPlayerXFromOptions
es renombren a applyPlayerXBindings(const Config::PlayerBindings&) i
reben els bindings per paràmetre en lloc de llegir-los del global
Options::*. El Director hi passa Options::player1/2 als call-sites.

Esborrats applyKeyboardBindingsFromOptions i applyGamepadBindingsFromOptions
que no eren cridats per ningú (dead code aprofitat).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:30:55 +02:00