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>
- 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>
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>
Systems::Collision::detectWoundedChain itera parells d'enemics: si
exactament un està herit i toquen (Physics::checkCollision), el sa entra
en estat herit propagant last_hit_by_ → la cascada de morts segueix
acreditant el shooter original. El rebot físic ja el gestiona
PhysicsWorld; aquí només propaguem l'estat.
Hook a detectAll just després de detectBulletEnemy: les balles tenen
prioritat sobre la cadena del mateix frame.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Enemy::draw() ara, si wounded_timer_ > 0, alterna entre el color del
tipus i Defaults::Palette::WOUNDED (dorat) a Wounded::BLINK_HZ usant
fmod sobre el periode del cicle — patró reutilitzat del Ship::draw()
d'invulnerabilitat però aplicat a color en lloc de visibilitat.
A 10 Hz amb DURATION=1s dóna ~10 parpadeigs visibles abans d'explotar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
- 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>
- 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>
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.
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.
- 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>
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>
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>
- 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>
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.
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.
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.
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.