# Animacions i direccionalitat del jugador Documenta els afegits d'aquesta sessió a `source/game/entities/player.*`: sis animacions direccionals amb `facing_` persistent, regla de flip només per al lateral i transició automàtica a `stand` quan la col·lisió bloqueja el moviment. Tot el canvi és local al `Player`; no s'ha tocat l'EntityManager ni el bus. ## 1. YAML — sis animacions El fitxer `data/player/player.yaml` defineix ara sis animacions, agrupades en tres files de 4 frames sobre `player.gif` (convenció habitual: d'esquerra a dreta i de dalt a baix): | Nom | Frames | Descripció | |---------------|------------|----------------------------| | `stand_down` | `[0]` | parat mirant cap avall | | `walk_down` | `[0..3]` | caminant cap avall | | `stand_up` | `[4]` | parat mirant cap amunt | | `walk_up` | `[4..7]` | caminant cap amunt | | `stand` | `[8]` | parat lateral | | `walk` | `[8..11]` | caminant lateral | Les laterals (`stand` i `walk`) ja existien; les variants `_up` i `_down` són noves i permeten distingir visualment la direcció de moviment. ## 2. `Facing` persistent S'ha afegit l'enum privat `Facing { SIDE, UP, DOWN }` amb el membre `facing_{SIDE}`. La tria es fa a cada `update`, amb prevalença lateral per si un dia es permetessin diagonals: - `vx != 0` → `SIDE` (inclou diagonals). - `vx == 0 && vy < 0` → `UP`. - `vx == 0 && vy > 0` → `DOWN`. - Cap input → es conserva l'últim `facing_` perquè l'animació `stand_*` coincideixi amb l'última direcció caminada. ## 3. Regla de flip - `SIDE`: el sprite es flipeja horitzontalment si `vx < 0`, i es restaura a `FLIP_NONE` si `vx > 0`. El sprite original mira a la dreta. - `UP` / `DOWN`: el flip es força explícitament a `FLIP_NONE`. Els sprites frontals/dorsals no s'han d'espillar només perquè l'últim lateral havia estat cap a l'esquerra. ## 4. `stand` automàtic a la col·lisió Quan el jugador prem una tecla però la col·lisió (mapa o sarcòfag) li impedeix avançar, l'animació `walk*` s'atura i es força la variant `stand_*` corresponent al facing. L'algorisme: 1. Al principi del frame es captura `FRAME_START_X` / `FRAME_START_Y`. 2. S'aplica snap al passadís perpendicular si hi ha pur moviment H o V. 3. `animate(delta_time)` avança el frame de l'animació actual sense tocar la posició. 4. `tryMove(vx*dt, vy*dt, collidesFn)` (a `MovingSprite`) aplica el moviment per eix: si la `collisionBox()` resultant col·lisiona — el Player la sobreescriu retornant `getBaseBox()` —, l'eix es reverteix. Així el jugador llisca contra murs/sarcòfags en lloc de bloquejar-se en sec. 5. Després de `clampToPlayfield`, es compara la posició final amb el valor capturat al pas 1. Si és idèntica, `MOVED = false` i es tria `stand` / `stand_up` / `stand_down` segons `facing_`. Si hi ha hagut moviment real (incloent-hi snap), s'aplica la variant `walk*` del mateix facing. D'aquesta manera, xocar contra un mur o un sarcòfag fa que el jugador es quede immòbil en compte d'animar el caminat "en el lloc". El facing segueix reflectint la intenció (on vol anar), no el resultat, perquè l'`stand_up` d'empentar-se contra un mur ha de mostrar el personatge mirant amunt. ## 5. Estat - DONE: sis animacions al YAML, `facing_` persistent, flip condicionat, `stand` automàtic a la col·lisió. - Build i smoke tests (13/13) verdes. (Aquesta nota deia "14/14" abans; el comptatge real al `tests/test_entities_smoke.cpp` són 13 funcions `test*` invocades des de `main()` — corregit a `refactor/options-class`.) - Sense smoke test dedicat: la lògica està entrellaçada amb teclat i `Map::collides`; l'smoke actual de `Player` no cobreix teclat simulat. - Pendent (ajornat): si algun dia es vol feedback auditiu de wall-bump (so en xocar), la condició natural és `!MOVED && (vx != 0 || vy != 0)` al final d'`update`.