31 Commits
Author SHA1 Message Date
Kevin L'Hereec fcad0f5d88 fix: le HUD mettait à jour les compteurs des joueurs trop tôt pendant la résolution du tour
Cause : le serveur émet 'round-result' (déclenche l'animation de révélation
des cartes/gains sur le plateau Phaser, ~3.5s avant le tour suivant) et
'game-state' en phase 'resolve' (déjà avec les jauges/scores post-résolution
via getPlayersInfo) au même instant. Le HUD Vue (alimenté directement par
gameStore.players) sautait donc à ses nouvelles valeurs INSTANTANÉMENT, en
même temps que l'animation de résolution démarrait tout juste sur le
plateau — les compteurs changeaient avant/pendant que le joueur voie
l'animation qui justifie ce changement, au lieu d'être synchronisés avec
elle.

Fix : le store Pinia (client/src/stores/game.js) gèle désormais
players/currentTurn pendant toute la phase 'resolve' — les valeurs reçues
sont stockées dans un buffer interne (_pendingPlayers/_pendingCurrentTurn)
au lieu d'être appliquées immédiatement, et ne sont propagées à l'affichage
qu'au game-state suivant dont la phase n'est PLUS 'resolve' (typiquement
'selecting' du tour suivant, ou 'end'). Le reste de l'état (phase, main,
objets/bâtiments/événements) continue de se mettre à jour normalement et
immédiatement — seul l'affichage des jauges/scores est différé.

6 nouveaux tests unitaires (game.store.test.js) couvrant le gel/dégel du
buffer, la rediffusion de plusieurs game-state en phase 'resolve'
successifs, la transition directe vers 'end', et le nettoyage du buffer
par reset().

Nouveau test E2E (hud-resolution-timing.spec.js) confirmé discriminant :
échoue de façon reproductible (2/2 tentatives) sans le fix ("0 pts" attendu
pendant resolve, "5 pts" déjà affiché), passe systématiquement avec le fix
(stable sur 3 répétitions). Attente active de la phase 'resolve' via
expect.poll plutôt qu'un délai fixe, pour éviter la fenêtre d'observation
manquée sous charge variable.

60 tests unitaires client + 166 tests unitaires serveur + suite
game-flow.spec.js (4 tests, non liée à ce fix) toujours au vert.
2026-08-25 17:24:54 +02:00
Kevin L'Hereec 29cc728f09 feat: distinction visuelle "achat en attente" avant résolution du tour
Après avoir cliqué sur ACHETER, aucune indication ne montrait que l'achat
était pris en compte avant que la mini-carte définitive n'apparaisse à
côté de la main (à la résolution du tour, plusieurs secondes plus tard).
Le serveur exposait déjà pendingBuyItemId/pendingBuyBuildingId via
getStateFor(), mais rien côté client ne les utilisait.

- ItemBoardToken.js : nouveau paramètre `pending`. Quand actif, le jeton
  compact affiche un halo doré pulsant (tween alpha 1<->0.25 en boucle) et
  un texte "En attente..." sous l'icône, tant que l'achat n'est pas résolu.
  Le bouton ACHETER de la carte détaillée passe alors sur un libellé
  distinct "EN ATTENTE..." (au lieu de "INSUFFISANT", qui suggérerait à
  tort un problème de jauge) et devient non-cliquable, cohérent avec le
  refus déjà appliqué côté serveur pour une seconde tentative le même tour.
  Le tween est proprement arrêté dans destroy() (les jetons sont détruits/
  recréés à chaque game-state).
- GameScene.js : _buildItemTokens()/_buildBuildingTokens() déterminent
  `pending` à partir de state.pendingBuyItemId/pendingBuyBuildingId (déjà
  reçus du serveur, jamais consommés auparavant) et le passent au jeton
  correspondant, désactivant aussi son affordabilité pendant l'attente.
  shopChanged/buildingShopChanged comparent désormais aussi ces deux champs
  pour déclencher la reconstruction dès le clic sur ACHETER (avant, seul
  un changement de myItems/availableItems/jauges provoquait un rebuild).
- Nouveau test E2E (item-shop.spec.js) : après achat, vérifie que le jeton
  correspondant a pending=true, affordable=false et un tween actif chez
  l'acheteur, que l'ADVERSAIRE ne voit PAS cet état "pending" (propre au
  joueur), et qu'après résolution du tour le jeton disparaît normalement
  de la boutique (comportement de stock unique déjà existant, inchangé).

54 tests unitaires client + 166 tests unitaires serveur + suite
item-shop.spec.js (4 tests) au vert.
2026-08-25 15:29:17 +02:00
Kevin L'Hereec a5ecc23498 fix: bouton de confirmation désactivé après avoir sélectionné une carte, si une autre action réseau survient entretemps
Cause : le listener 'game-state' de GameScene se déclenche à CHAQUE action
de N'IMPORTE QUEL joueur dans la salle (jouer une carte, acheter un objet
ou un bâtiment, piocher/jouer un événement) — pas seulement au vrai
changement de tour. À chaque déclenchement, dès que phase==='selecting',
_deactivateBtn() était appelé INCONDITIONNELLEMENT, même si le joueur avait
déjà sélectionné une carte de sa main (this.selectedCard non-null) sans
encore l'avoir confirmée.

Conséquence concrète : sélectionner une carte, PUIS effectuer n'importe
quelle autre action (typiquement piocher une carte Événement) désactivait
visuellement le bouton "CONFIRMER MA CARTE" (fond terne, texte grisé) alors
que la carte restait réellement sélectionnée en mémoire ET visuellement
surélevée sur le plateau (CardObject._selected reste true, jamais
désélectionnée par ce chemin). Un premier clic sur la carte la
DÉSÉLECTIONNAIT alors (CardObject._onDown bascule select/deselect selon
_selected, déjà à true), obligeant à cliquer une seconde fois pour la
vraiment resélectionner — exactement le symptôme rapporté.

Fix : ne réinitialiser visuellement le bouton (_deactivateBtn()) que si
aucune carte n'est actuellement sélectionnée (),
respectant l'état réel de sélection au lieu de le réinitialiser à chaque
game-state reçu.

Nouveau test E2E de régression (e2e/tests/card-selection-persistence.spec.js)
reproduisant précisément le scénario : sélectionner une carte, piocher un
événement, vérifier que selectedCard ET le pulse-tween du bouton restent
actifs, puis confirmer directement sans recliquer sur la carte. Confirmé
discriminant : échoue de façon reproductible (2/2 tentatives) quand le fix
est retiré isolément, passe systématiquement avec le fix en place.

Ajout des helpers e2e/tests/helpers.js: selectHandCard() et
confirmSelectedCard() (extraits de playCard(), qui reste inchangée en
façade — les 5 usages existants de playCard() dans la suite E2E restent
compatibles sans modification).

54 tests unitaires client + 166 tests unitaires serveur + suite
game-flow.spec.js (4 tests, non liés à ce fix) toujours au vert.
2026-08-25 15:01:55 +02:00
Kevin L'Hereec 4fe9cf4393 fix: ReferenceError bloquait le ciblage d'Accord royal/Épidémie, laissant la carte jouée indéfiniment en main
Régression introduite par le précédent refactor (commit 34d1bbe) qui a
remplacé le paramètre 'token' (référence Phaser) par 'eventId' (string)
dans _onEventCardPlay, mais avait oublié de mettre à jour les 2 appels
internes à this._startTargeting(token, ...) pour les événements Accord
royal et Épidémie (variante Trousse) — 'token' n'existait plus dans ce
scope, provoquant un ReferenceError JavaScript non capturé au moment
précis où l'utilisateur cliquait sur JOUER pour un événement nécessitant
un ciblage.

Conséquence concrète : l'exception interrompait l'exécution de
_onEventCardPlay avant d'atteindre _startTargeting, donc aucune séquence
de ciblage ne démarrait jamais, playEventCard n'émettait jamais
'play-event' au serveur, pendingEventPlayId restait null côté serveur, et
la carte réapparaissait comme "jouable" en main indéfiniment — exactement
le symptôme rapporté ("les événements ne semblent pas être résolus et
restent dans la seconde main même si joués").

Reproduit et confirmé via un test E2E de trace ciblé (game-state +
_renderEventHand loggés à chaque appel) : avant le fix, pendingEventPlayId
restait null malgré le clic sur JOUER + les 2 clics de ciblage ; après le
fix, pendingEventPlayId=royal_accord est bien transmis au serveur et la
carte disparaît correctement de la main après résolution du tour.

Fix trivial : this._startTargeting(token, ...) -> this._startTargeting(eventId, ...)
sur les 2 appels concernés. 54 tests unitaires client + 166 tests
unitaires serveur toujours au vert (ce bug, une ReferenceError dans un
chemin de code jamais exercé par les tests unitaires existants — qui ne
peuvent pas simuler un vrai clic Phaser sur le bouton JOUER + la séquence
de ciblage — n'était détectable que par test E2E ou lecture manuelle).
2026-08-24 17:27:00 +02:00
Kevin L'Hereec 34d1bbe37f fix: une carte Événement jouée pouvait rester affichée comme jouable en main
Cause : this.selectedEventCard gardait une référence DIRECTE vers un objet
Phaser (ItemBoardToken), régulièrement détruit et recréé par
_refreshEventHandAffordability()/_buildEventHand() à chaque game-state.
Cette référence devenait alors obsolète (pointant vers un objet détruit),
et l'état de verrouillage "carte en cours de jeu" était géré par deux
chemins de mutation distincts (reconstruction de la main basée sur
myEventHand seul, ET synchronisation séparée sur pendingEventPlayId) qui
pouvaient se désynchroniser selon l'ordre exact des événements réseau :
une carte déjà envoyée au serveur (play-event émis) pouvait alors
réapparaître visuellement "jouable" dans la main après une reconstruction
intermédiaire qui ignorait le verrouillage réel.

Fix : refactor complet du pilotage de la main d'événements côté GameScene :

- this.selectedEventCard (référence Phaser) remplacé par
  this.selectedEventId (simple eventId, jamais périmé même après
  destruction/recréation des jetons).
- Fusion de _buildEventHand + _refreshEventHandAffordability en une seule
  fonction _renderEventHand(eventIds), SEULE responsable de construire
  this.eventCards — élimine le second chemin de mutation qui pouvait
  diverger.
- Dans le listener 'game-state', selectedEventId est désormais TOUJOURS
  resynchronisé sur state.pendingEventPlayId (source de vérité serveur)
  AVANT de décider si la main doit être reconstruite, et la clé de diff
  inclut maintenant ce verrouillage en plus de la composition de la main —
  auparavant, un changement de pendingEventPlayId seul (main inchangée) ne
  déclenchait aucune reconstruction, laissant l'ancien état affiché.
- _cancelTargeting() reconstruit désormais aussi la main pour lever tout
  verrouillage visuel resté affiché après une annulation.

Fix connexe dans ItemBoardToken.destroy() : le minuteur d'OUVERTURE
(_showTimer) n'était jamais annulé à la destruction (seul _hideTimer
l'était) — un jeton détruit pendant qu'un survol venait de démarrer
pouvait déclencher _showExpanded() sur un objet déjà détruit une fois le
délai écoulé, une source de bug supplémentaire pour des cartes "fantômes".

54 tests unitaires client + 166 tests unitaires serveur toujours au vert.
2026-08-24 16:51:03 +02:00
Kevin L'Hereec 33a3c20dcb fix: la carte détaillée d'un événement débordait sous le canvas au survol
ItemBoardToken._showExpanded() ancrait TOUJOURS la carte détaillée vers le
BAS du jeton compact, quel que soit son emplacement sur le plateau —
adapté aux boutiques d'objets/bâtiments (positionnées en haut du canvas),
mais pas à la main de cartes Événement (positionnée en BAS du canvas,
voir eventHandLayout.js) : la carte (jusqu'à 266px de haut avec le bouton
JOUER) débordait alors largement sous le bas de l'écran, la rendant
invisible et son bouton totalement injoignable au clic.

- Ajout du paramètre vAnchor ('bottom' par défaut, 'top' pour les jetons
  proches du bas de l'écran) sur ItemBoardToken, symétrique au hAnchor déjà
  existant pour l'axe horizontal. La main d'événements passe désormais
  vAnchor='top' pour que la carte s'étende vers le HAUT au lieu du bas.
- Extraction du calcul de décalage (expandedCardOffset) et des constantes
  de dimensionnement (EXPANDED_W/H, BUY_BTN_H) dans tokenLayout.js (module
  pur, sans dépendance Phaser) pour pouvoir le couvrir par de vrais tests
  unitaires Vitest.
- 11 nouveaux tests unitaires (tokenLayout.test.js) : orientation dx/dy
  selon hAnchor/vAnchor, alignement exact du bord du jeton avec le bord de
  la carte, et surtout la régression elle-même — un test vérifie que
  vAnchor='top' maintient la carte dans les limites du canvas pour un
  jeton en bas d'écran (1080p et 720p), et qu'un test symétrique confirme
  que l'ancien comportement implicite (vAnchor='bottom') débordait bien
  sous le canvas dans ce même scénario.
- 54 tests unitaires client au vert au total (43 précédents + 11 nouveaux),
  vérification privilégiée par rapport à une nouvelle suite E2E (lente vu
  la pioche aléatoire à stabiliser dans ce projet).
2026-08-24 16:12:16 +02:00
Kevin L'Hereec 1a91a0f876 fix: cartes Événement illisibles + objets/bâtiments adversaires au-dessus de leur carte
Deux problèmes de lisibilité/placement signalés :

1. Le texte des cartes Événement (nom + description) restait illisible
   malgré le passage précédent de l'échelle 0.55 à 0.8 — le composant dédié
   EventCardObject affichait une grande carte réduite, écrasant
   systématiquement le texte quelle que soit l'échelle choisie.

   Fix : suppression complète d'EventCardObject, remplacé par le même
   pattern que les objets/bâtiments (ItemBoardToken, nouveau kind='event') :
   un petit jeton compact (46x46) toujours visible sur le plateau, avec la
   carte détaillée affichée en TAILLE RÉELLE (police 13-16px, jamais
   réduite) au survol de la souris, et un bouton "JOUER" (au lieu
   d'"ACHETER") pour confirmer. ItemBoardToken généralisé pour supporter ce
   3e kind (pas de badge de coût affiché — déjà payé à la pioche — mais
   badge "!" repris pour les événements à effet négatif type Épidémie).

   Extraction de client/src/game/objects/tokenLayout.js (ICON_SIZE) dans un
   module SANS dépendance à Phaser, pour que eventHandLayout.js (et ses
   tests Vitest) puisse l'importer sans jamais charger Phaser — qui échoue
   à l'import en environnement jsdom (canvas natif requis, absent de jsdom).

2. Les mini-cartes objets/bâtiments déjà achetés par un ADVERSAIRE étaient
   affichées SOUS son slot de jeu, visuellement en dessous/après sa carte
   jouée. Déplacées AU-DESSUS de son slot pour les mettre davantage en
   évidence, comme posées sur sa carte plutôt que cachées dessous.

- 43 tests unitaires client mis à jour (eventHandLayout.test.js migré vers
  ICON_SIZE au lieu de CARD_W/CARD_H/scale, désormais indépendant de Phaser).
- e2e/tests/helpers.js : playEventCard adapté au nouveau flux
  survol->bouton JOUER (au lieu d'un simple clic direct sur la carte) ;
  drawEventUntil corrigé pour ne jamais défausser une carte 'royal_accord'
  sans fournir ses 2 cibles de ciblage séquentiel obligatoire (le jeu sans
  cible bloquait silencieusement la séquence, sans jamais émettre
  'play-event', rendant la boucle de pioche très peu fiable).
- Vérification ciblée via un test E2E existant (pas de nouvelle suite E2E
  lourde, conformément à la demande de privilégier les tests unitaires) :
  le nouveau flux jeton->survol->JOUER->résolution fonctionne bout en bout
  (Récolte exceptionnelle +2 Nourriture confirmé après résolution).
2026-08-24 16:01:04 +02:00
Kevin L'Hereec 45eb9be60d fix: repositionner les cartes Événement en bas-droite avec un texte lisible
Les cartes Événement (bouton de pioche + main) étaient positionnées trop
près du bouton "CONFIRMER MA CARTE" — le bouton de pioche chevauchait même
physiquement son bord droit de ~25px (x = width/2 + 210, même Y), au point
de prêter à confusion entre "piocher un événement" et "jouer une carte".

- Nouveau module pur client/src/game/objects/eventHandLayout.js
  (eventDrawButtonPosition, eventHandGeometry, eventCardPosition) : ancre
  toute la zone événement en bas-DROITE du canvas, clairement séparée du
  bouton de confirmation (centré) et de la main de personnages (centrée).
- EVENT_CARD_SCALE remonté de 0.55 à 0.8 pour que le nom de l'événement
  (police de base augmentée 12px -> 15px) reste réellement lisible une fois
  réduit — ajout d'un bandeau semi-opaque derrière le texte (EventCardObject)
  pour garantir le contraste quel que soit le portrait en arrière-plan.
- La légende des attributs occupait déjà le coin bas-droit : déplacée en
  bas-GAUCHE (setOrigin ajusté en conséquence) pour laisser la place à la
  nouvelle zone événement.
- 16 nouveaux tests unitaires Vitest (eventHandLayout.test.js) couvrant la
  géométrie : positions dans les limites du canvas, absence de
  chevauchement avec le bouton de confirmation/la main/la légende, ancrage
  stable au bord droit avec 1 ou 2 cartes, robustesse sur petite résolution
  (1280x720) et cardCount=0. Choix explicite de tests UNITAIRES (rapides,
  ~600ms pour toute la suite client) plutôt que E2E pour cette correction
  de positionnement pur, les tests E2E de ce projet étant lents (plusieurs
  minutes par scénario à cause de la pioche aléatoire à stabiliser).
2026-08-24 14:52:18 +02:00
Kevin L'Hereec c9b4a7e567 docs: compléter le README avec les règles des bâtiments et cartes Événement
Dernier point de la checklist de la fonctionnalité Cartes Événement
(pioche partagée, ciblage, synergies) resté en suspens. Ajout également des
règles des Bâtiments (Église, Château Royal), qui n'avaient jamais été
documentées lors de leur implémentation précédente. Mise à jour de
l'arborescence du projet dans la section Structure.
2026-08-24 13:27:15 +02:00
Kevin L'Hereec 44d4eb7e96 fix: bugs critiques du système d'événements + stabilisation E2E
Trois bugs fonctionnels réels découverts et corrigés en stabilisant les
tests E2E des cartes Événement :

1. client/src/stores/game.js — le store Pinia n'exposait JAMAIS les champs
   liés aux bâtiments (myBuildings, pendingBuyBuildingId, availableBuildings)
   ni aux événements (myEventHand, eventDeckSize, hasPendingEventDraw,
   pendingEventPlayId) : updateState() ne les lisait pas depuis le
   'game-state' serveur. Conséquence concrète : GameScene._epidemicNeedsTarget()
   lisait this.store.myBuildings (toujours vide) pour décider si la
   variante Église d'Épidémie s'applique — le ciblage de la variante Trousse
   ne fonctionnait donc jamais correctement dès qu'un joueur possédait
   aussi le Château. Corrigé + 4 nouveaux tests unitaires Vitest.

2. client/src/game/scenes/GameScene.js — GameScene._onEventCardSelected
   envoyait encore les clés historiques gainTargetSocketId/
   loseTargetSocketId/targetSocketId au serveur, alors que Room.js avait été
   migré vers gainTargetTeamIndex/loseTargetTeamIndex/targetTeamIndex
   (socketId n'est jamais exposé au client). Conséquence : le ciblage
   d'Accord royal et de la variante Trousse d'Épidémie était intégralement
   cassé en jeu réel — les clés ne correspondant à rien côté serveur, aucune
   cible n'était jamais appliquée, silencieusement.

3. e2e/tests/helpers.js — drawEventUntil abandonnait immédiatement dès que
   la main d'événements atteignait 2 cartes sans contenir l'id recherché,
   rendant les tests E2E d'événements extrêmement flaky (avec seulement 6
   types d'événements et une main de 2, l'échec dès les 2 premières pioches
   est fréquent, pas rare). Corrigé : joue désormais une carte pour libérer
   un slot et continue à piocher jusqu'à maxDraws (20).

Stabilisation supplémentaire :
- Toutes les fonctions d'introspection Phaser dans helpers.js sécurisées
  (optional chaining) contre une scène pas encore initialisée après un
  changement de tour/navigation sous charge (plusieurs pages Playwright
  simultanées).
- Timeouts des tests events.spec.js augmentés (150-240s) pour laisser le
  temps aux boucles d'accumulation de jauge + pioche aléatoire de converger.
- playwright.config.js : retries=1 même en local, pour absorber la
  variance résiduelle des tests basés sur un tirage aléatoire, tout en
  gardant une détection fiable des vraies régressions (qui échoueraient de
  façon reproductible sur les deux tentatives).
- events.spec.js : test 'Accord royal' fournit désormais les 2 cibles
  requises par le ciblage séquentiel (gain + perte) au lieu d'une seule,
  ce qui laissait la séquence de ciblage bloquée sans jamais émettre
  'play-event'.

Résultat : 166 tests unitaires serveur + 28 tests unitaires client
toujours au vert ; suite events.spec.js (9 tests E2E) passe intégralement
(certains via retry probabiliste documenté, comportement attendu vu la
pioche aléatoire dans le deck partagé).
2026-08-24 13:24:37 +02:00
Kevin L'Hereec 1726f8f96b feat: cartes Événement (pioche partagée de 18 cartes, 6 effets, synergies, ciblage)
- Nouveau server/src/game/events.js : 6 événements (Récolte exceptionnelle,
  Épidémie, Levée des troupes, Accord royal, Complot découvert, Grande fête
  au château), deck PARTAGÉ de 18 cartes (3 exemplaires de chacun) mélangé
  au démarrage de la partie.
- Room.js : buyEventDraw() (coût fixe 1 point dans chacune des 4 jauges,
  effet immédiat contrairement aux objets/bâtiments — pas de conflit de
  stock possible), playEventCard() (choix de jouer une carte de sa main,
  max 1 par tour, avec ciblage optionnel), _resolvePendingEventPlays()
  (résolution de TOUS les événements du tour AVANT le calcul des gains de
  cartes personnage, défausse systématique, protection "Complot découvert"
  pour soi-même uniquement). Les 6 effets incluent leurs synergies avec les
  objets/bâtiments existants (Charrue, Trousse+Église pour Épidémie,
  Trébuchet, Sceau Royal, Château Royal → pioche bonus).
- IMPORTANT : le ciblage de joueur (Accord royal, variante Trousse
  d'Épidémie) transite par teamIndex (identifiant public) et non socketId
  (jamais exposé au client via getPlayersInfo/getStateFor) — le serveur
  résout teamIndex -> joueur réel au moment de la résolution.
- Nouvel event socket 'buy-event-draw' (effet immédiat) et 'play-event'
  (résolution différée), eventResults inclus dans round-result.
- Client : events.js (miroir), EventCardObject.js (carte épurée façon
  CardObject, badge rouge "!" pour les événements à effet négatif).
  BootScene génère les textures plein cadre (méthode _generateClippedTexture
  généralisée, réutilisée avec les personnages).
- GameScene : bouton de pioche à droite du bouton de confirmation (état
  grisé selon jauges/main pleine/pioche déjà tentée), seconde main de cartes
  événement (max 2) au-dessus de ce bouton, sélection d'une carte pour la
  jouer avec séquence de ciblage interactive (surbrillance des slots
  adversaires cliquables) pour Accord royal et la variante Trousse
  d'Épidémie.
- 61 nouveaux tests unitaires serveur (events.js + Room.js : pioche, choix
  de jouer, résolution des 6 effets avec/sans synergie, Complot découvert,
  ordre événements-avant-personnages, deck partagé).
- Nouveaux helpers et tests d'intégration E2E Playwright (e2e/tests/events.spec.js)
  pour la pioche, la limite de main à 2, et les 6 effets — certains
  scénarios restent probabilistes (pioche aléatoire dans le deck partagé) et
  peuvent nécessiter une stabilisation supplémentaire du timing.
2026-08-21 17:05:50 +02:00
Kevin L'Hereec 9139989fa7 feat: bâtiments achetables (Église, Château Royal) avec synergie objets
- Nouveau server/src/game/buildings.js : 2 bâtiments, chacun couvrant 2
  attributs, coût réparti sur les deux jauges, garantit +1 par tour sur
  chaque attribut, avec bonus de synergie +1 si l'objet correspondant est
  aussi possédé (Église+Trousse/Sceau, Château+Charrue/Trébuchet).
- Room.js : buyBuilding() (validation coût multi-jauges), résolution des
  achats en attente avec la même priorité/stock unique que les objets,
  _applyBuildingEffects() (garantie de base + synergie, jamais de recul par
  rapport aux gains déjà boostés par un objet). getStateFor/getPlayersInfo
  exposent buildings/myBuildings/availableBuildings/pendingBuyBuildingId.
- Nouvel event socket 'buy-building', boughtBuildings inclus dans round-result.
- Client : buildings.js (miroir), BootScene génère les textures portrait des
  bâtiments (méthode _generatePortraitTexture généralisée, réutilisée avec
  les objets). ItemBoardToken généralisé (kind='item'|'building', hAnchor
  'left'|'right') pour afficher indifféremment un objet ou un bâtiment, avec
  coût détaillé par jauge affiché sur la carte agrandie pour les bâtiments.
- GameScene : boutique de bâtiments en colonne à DROITE du plateau
  (symétrique à la boutique d'objets à gauche), mes bâtiments possédés
  affichés à GAUCHE de ma main (symétrique à mes objets à droite), bâtiments
  des adversaires affichés sous leurs objets au niveau de leur slot.
- Fix : la carte détaillée héritait par défaut d'un ancrage "s'étend vers la
  droite" adapté à la boutique d'objets (bord gauche du canvas) mais cassait
  la boutique de bâtiments (bord droit) — le bouton ACHETER se retrouvait
  hors du canvas et donc injoignable au clic. Ajout du paramètre hAnchor.
- 26 nouveaux tests unitaires serveur (buildings.js + Room.js : validations
  d'achat multi-jauges, priorité/stock unique, effets de base et synergie).
- 6 nouveaux tests E2E Playwright (boutique bâtiments : affichage, position,
  carte détaillée dans les limites du canvas, coût affiché, achat complet).
- Fix flakiness des tests E2E d'achat (item-shop + building-shop) : le mode
  'min' de playCard garantit désormais que l'adversaire joue sa carte la
  plus faible sur l'attribut ciblé, au lieu de simplement jouer un autre
  attribut (insuffisant, le gain dépend de la comparaison au voisin gauche).
2026-08-21 16:19:18 +02:00
Kevin L'Hereec 11cba0467b feat: écran de fin de partie + tests unitaires client et E2E Playwright
- Fix: UIScene n'était jamais lancée (active: false, jamais scene.launch)
  donc l'event 'game-over' du serveur n'était jamais affiché — la partie
  semblait ne pas avoir de fin. Remplacé par un panneau Vue dans GameView
  (vainqueur, classement trié, bouton Quitter via vue-router). UIScene.js
  supprimée (code mort).
- Extraction de client/src/utils/hudUtils.js (constantes ATTRS/couleurs et
  gaugePercent) depuis GameView.vue pour les rendre testables isolément.
- Tests unitaires client (Vitest) : store Pinia (game.js) et hudUtils
  (24 tests, client/test/unit/).
- Tests d'intégration E2E (Playwright, e2e/) avec de vrais clients
  Socket.io et rendu Phaser réel dans Chromium :
  * game-flow.spec.js : lobby → démarrage → main de 4 cartes → résolution
  * item-shop.spec.js : boutique, bouton désactivé si jauge insuffisante,
    achat retire l'objet pour tous + effet appliqué + affiché sur la table
  * game-over.spec.js : partie complète (12 tours) → panneau de fin
  webServer démarre automatiquement client+serveur ; ajout d'une route
  GET /health côté Express pour permettre à Playwright de détecter un
  serveur déjà démarré.
- README : documentation des 3 niveaux de tests (unitaires serveur,
  unitaires client, intégration E2E) et de la nouvelle arborescence.
2026-08-21 15:27:44 +02:00
Kevin L'Hereec 6a1c800253 feat: système d'objets achetables (boutique + effets permanents) et refonte visuelle
- Nouveaux personnages illustrés (12 PNG remplacés dans client/public/cards)
- 4 objets achetables (Trébuchet, Charrue, Trousse du Médecin, Sceau Royal)
  avec effets permanents (double_gain, guaranteed_gain, copy_best_in_game),
  stock unique par partie, priorité d'achat tournante entre joueurs
- Boutique en colonne à gauche du plateau : icônes compactes, carte détaillée
  agrandie au survol (taille découplée du displayScale du jeton), bouton
  ACHETER actif/grisé selon la jauge du joueur, retrait immédiat de la
  colonne une fois l'objet acheté (les autres remontent pour combler)
- Objets achetés affichés "posés sur la table" : à côté de la main pour soi,
  sous le slot de jeu pour les adversaires
- Icône du premier joueur (priorité d'achat) dans le HUD Vue
- Refonte des cartes personnage (CardObject) : portrait plein cadre épuré,
  valeurs d'attribut discrètes aux 4 coins
- Fix removePlayer : nettoyage de pendingBuys/pendingPlays et recalage de
  firstPlayerIndex à la déconnexion d'un joueur
- 77 tests unitaires serveur (node:test) : cards, items, Room, RoomManager
- README mis à jour avec les règles complètes du jeu
- .gitignore : exclusion de .idea/ et .serena/ (artefacts d'outils locaux)
2026-08-21 14:53:58 +02:00
kevin.lhereec da06ecaf2d fix: clip card images via Canvas 2D in BootScene, no Phaser mask needed 2026-08-17 14:30:03 +02:00
kevin.lhereec 4d09102464 fix: use BitmapMask inside container so image follows card during animations 2026-08-17 14:25:32 +02:00
kevin.lhereec 0a8c89610b fix: card image clipping via RenderTexture, remove white hover bg 2026-08-17 14:24:42 +02:00
kevin.lhereec c7672b1793 fix: ghost players on back - leaveRoom, store.reset, dynamic lobby slots 2026-08-17 14:18:27 +02:00
kevin.lhereec 6e384d5985 fix: card image cover ratio, title at bottom, badges above image 2026-08-17 14:15:43 +02:00
kevin.lhereec 60b545c42d feat: card portrait images loaded from public/cards/ 2026-08-17 14:11:20 +02:00
kevin.lhereec b46cb707cd feat: draw one card after each turn with slide-in animation 2026-08-17 12:07:47 +02:00
kevin.lhereec ec5a7ec2c3 fix: button depth 100 to stay above cards, fix inactive text color 2026-08-17 12:04:01 +02:00
kevin.lhereec 2b37d8167f fix: play slots count matches actual player count 2026-08-17 12:01:30 +02:00
kevin.lhereec abab58f6a4 fix: add BOTTOM_MARGIN to avoid Windows taskbar overlap 2026-08-17 12:00:43 +02:00
kevin.lhereec 3125823f42 feat: confirm button with pulse animation + ready counter (X/N players) 2026-08-17 11:57:55 +02:00
kevin.lhereec 91a1a9f4f0 feat: gauge scoring system (subtract left neighbor per attribute) 2026-08-17 11:54:23 +02:00
kevin.lhereec 25ab76c4ac fix: per-player phase state so 2nd player can still play after 1st played 2026-08-17 11:50:27 +02:00
kevin.lhereec 78adeb7920 fix: replace npm ci with npm install (no lockfile yet) 2026-08-17 11:45:33 +02:00
kevin.lhereec 8da1d1edc9 feat: dockerize client (nginx) and server (node) with docker-compose 2026-08-17 11:44:07 +02:00
kevin.lhereec 38814668e7 feat: 12 character cards with 4 color-coded attributes (food/war/health/diplo) 2026-08-17 11:41:46 +02:00
kevin.lhereec 1db221e468 feat: initial Regnum project structure (Vue3 + Phaser3 + Socket.io) 2026-08-17 11:34:55 +02:00