From 50b354f11c69dfe8756da7e6f962aac871c38445 Mon Sep 17 00:00:00 2001 From: Valentin ROBIN Date: Tue, 25 Aug 2026 16:21:11 +0200 Subject: [PATCH] =?UTF-8?q?Corrige=20la=20ligne=20de=20danger=20de=20la=20?= =?UTF-8?q?Corne=20(franchissement=20complet,=20pas=20au=20contact)=20et?= =?UTF-8?q?=20redesigne=20l'=C3=A9chelle=20de=20rep=C3=A8re?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La pièce devait déjà entièrement dépasser la ligne rouge (bord bas, pas juste le sommet) pour compter dans le délai de grâce avant game over. L'échelle des tailles en bas de page passe d'une seule ligne à défilement horizontal à un flex-wrap sans scroll, avec un titre — l'intérêt d'une échelle de repère est de tout voir d'un coup d'œil. Corrige aussi une violation de react-hooks/purity (Date.now() en argument direct de useRef) introduite par le mécanisme anti-triche fusionné depuis main. Co-Authored-By: Claude Sonnet 5 --- src/app/corne/melon-game.tsx | 67 +++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/app/corne/melon-game.tsx b/src/app/corne/melon-game.tsx index b7055c6..4ddf6b0 100644 --- a/src/app/corne/melon-game.tsx +++ b/src/app/corne/melon-game.tsx @@ -84,7 +84,14 @@ export function MelonGame({ const pendingMergesRef = useRef([]); const mergingBodyIdsRef = useRef(new Set()); const dangerSinceRef = useRef(null); - const runStartedAtRef = useRef(Date.now()); + // Pas Date.now() directement en argument de useRef (impur pendant le + // rendu, react-hooks/purity) : la vraie valeur est posée dans l'effet de + // montage juste en dessous — resetGame() (rejouer après une partie) la + // réécrit de toute façon à chaque nouvelle partie. + const runStartedAtRef = useRef(0); + useEffect(() => { + runStartedAtRef.current = Date.now(); + }, []); const dropCountRef = useRef(0); const mergeCountRef = useRef(0); @@ -326,12 +333,16 @@ export function MelonGame({ // Renvoie les corps qui dépassent la ligne de danger (au repos, pas un // simple rebond) — utilisé à la fois pour déclencher/prolonger le - // délai de grâce et pour savoir quelles pièces doivent clignoter. + // délai de grâce et pour savoir quelles pièces doivent clignoter. Le + // cercle doit avoir ENTIÈREMENT franchi la ligne (son bord bas, + // position.y + radius) plutôt que simplement y toucher (son bord haut, + // position.y - radius) : une pièce qui dépasse juste du sommet ne doit + // pas encore compter. function overflowingBodyIds(): Set { const ids = new Set(); for (const [id, body] of bodiesRef.current) { const radius = body.circleRadius ?? 0; - if (body.position.y - radius < DANGER_LINE_Y && body.speed < 0.5) { + if (body.position.y + radius < DANGER_LINE_Y && body.speed < 0.5) { ids.add(id); } } @@ -528,27 +539,35 @@ export function MelonGame({ {/* Échelle de repère : ordre de fusion complet, du plus petit au plus grand — taille d'affichage proportionnelle au vrai rayon du palier (mais réduite et plafonnée) pour que la progression se - voie d'un coup d'œil, sans dépendre du nombre de Citoyens. Une - seule ligne défilante (pas de flex-wrap). items-center (pas - items-end) : aligner sur le bas avec des tailles très variables - donnait un décalage qui grandissait avec la taille (la flèche, - à décalage fixe, n'était plus au bon endroit pour les gros - ronds) — centrer chaque rond sur son propre milieu reste - symétrique quelle que soit sa taille. */} -
- {citizens.map((citizen, tier) => ( -
- - {tier < citizens.length - 1 && } -
- ))} + voie d'un coup d'œil, sans dépendre du nombre de Citoyens. + flex-wrap (pas de scroll horizontal comme avant) : tout le monde + doit être visible sans interaction, l'intérêt d'une échelle de + repère est justement de tout voir d'un coup plutôt que de devoir + faire défiler. items-center (pas items-end) : aligner sur le bas + avec des tailles très variables donnait un décalage qui + grandissait avec la taille (la flèche, à décalage fixe, n'était + plus au bon endroit pour les gros ronds) — centrer chaque rond + sur son propre milieu reste symétrique quelle que soit sa + taille. Largeur plafonnée à max-w-sm (pas CONTAINER_WIDTH comme + avant) : la page (corne-view.tsx) est bien plus large que le bac + de jeu, la contrainte à 300px n'avait pas lieu d'être et forçait + le scroll pour rien. */} +
+

+ Ordre de fusion +

+
+ {citizens.map((citizen, tier) => ( +
+ + {tier < citizens.length - 1 && } +
+ ))} +
);