35704e50fc
Build and deploy / deploy (push) Successful in 36s
5000 restait trop haut pour être un vrai plafond réaliste : Icare abaissé à 500 (calculé sur le rythme le plus rapide théoriquement atteignable dans le jeu), Corne à 2000 (estimation plus prudente, économie de score plus dure à borner). Ajoute icarus_runs/start_icarus_run() : le serveur enregistre l'instant réel de début de partie et rejette un score incohérent avec le temps écoulé, sans jamais faire confiance à une durée envoyée par le client. Corrige aussi le nombre de jetons de /urne, peu lisible en gold-bright sur fond marbre (passe à text-sea, même convention qu'Icare/Corne). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
86 lines
3.3 KiB
TypeScript
86 lines
3.3 KiB
TypeScript
// Zone de jeu en pixels CSS fixes (pas de canvas redimensionné comme Icare
|
||
// — un conteneur DOM de taille fixe suffit et évite toute logique de
|
||
// "world scale").
|
||
export const CONTAINER_WIDTH = 300;
|
||
export const CONTAINER_HEIGHT = 420;
|
||
export const WALL_THICKNESS = 20;
|
||
|
||
// Un palier par Citoyen existant (chacun associé à une taille fixe pour
|
||
// toute la partie, voir melon-game.tsx) plutôt qu'un nombre de paliers
|
||
// arbitraire : le nombre de tailles suit donc le nombre de Citoyens, pas
|
||
// une constante fixe. Rayon minimal + croissance géométrique assez marquée
|
||
// (×1.25 par palier — un ×1.19 précédent rendait deux paliers consécutifs
|
||
// trop proches en taille : une fusion semblait "changer le visage d'une
|
||
// pièce déjà posée" plutôt que "remplacer deux petites pièces par une
|
||
// nettement plus grosse"), plafonnée à MAX_RADIUS pour rester dans
|
||
// CONTAINER_WIDTH même avec beaucoup de Citoyens.
|
||
const MIN_RADIUS = 15;
|
||
const RADIUS_GROWTH = 1.25;
|
||
const MAX_RADIUS = 110;
|
||
|
||
export function buildTierRadii(tierCount: number): number[] {
|
||
const count = Math.max(1, tierCount);
|
||
return Array.from({ length: count }, (_, index) =>
|
||
Math.min(MAX_RADIUS, Math.round(MIN_RADIUS * RADIUS_GROWTH ** index)),
|
||
);
|
||
}
|
||
|
||
// Palier de départ pour la pièce à lâcher : parmi les plus petits
|
||
// uniquement (sinon les premières fusions seraient trop rares/tardives).
|
||
export const DROP_MIN_TIER = 0;
|
||
export const DROP_MAX_TIER_CAP = 3;
|
||
|
||
export const DROP_COOLDOWN_MS = 450;
|
||
|
||
// Ligne de débordement (depuis le haut du bac) + délai de grâce avant game
|
||
// over : évite qu'un simple rebond juste après une fusion ne déclenche une
|
||
// fin de partie instantanée.
|
||
export const DANGER_LINE_Y = CONTAINER_HEIGHT * 0.15;
|
||
export const GAME_OVER_GRACE_MS = 5000;
|
||
|
||
export const FIXED_DT_MS = 1000 / 60;
|
||
export const MAX_FRAME_DT_MS = 250;
|
||
|
||
export const MIN_SCORE = 0;
|
||
// Doit rester aligné avec la contrainte melon_scores_best_score_check
|
||
// (supabase/schema.sql) — celle-ci est la vraie garantie, cette constante
|
||
// n'est qu'une valeur de référence côté client.
|
||
export const MAX_SCORE = 2000;
|
||
|
||
// Positions figées à la main pour certains Citoyens (id de profil → index
|
||
// de palier, 0 = le plus petit) — les Citoyens restants remplissent les
|
||
// paliers non réservés, dans leur ordre existant (alphabétique par
|
||
// pseudo, voir corne/page.tsx). "Position N" telle que demandée par un
|
||
// humain (compte à partir de 1) correspond à l'index N-1 ici.
|
||
export const TIER_OVERRIDES: Record<number, string> = {
|
||
6: "7f0f24dc-ede0-4190-a64d-976393073c28", // Épicure, position 7
|
||
};
|
||
|
||
export function applyTierOverrides<T extends { id: string }>(
|
||
citizens: T[],
|
||
overrides: Record<number, string>,
|
||
): T[] {
|
||
const byId = new Map(citizens.map((c) => [c.id, c]));
|
||
const usedIds = new Set<string>();
|
||
const result: (T | undefined)[] = new Array(citizens.length);
|
||
|
||
for (const [tierKey, id] of Object.entries(overrides)) {
|
||
const tier = Number(tierKey);
|
||
const citizen = byId.get(id);
|
||
if (citizen && tier >= 0 && tier < citizens.length) {
|
||
result[tier] = citizen;
|
||
usedIds.add(id);
|
||
}
|
||
}
|
||
|
||
const remaining = citizens.filter((c) => !usedIds.has(c.id));
|
||
let remainingIndex = 0;
|
||
for (let tier = 0; tier < result.length; tier++) {
|
||
if (!result[tier]) {
|
||
result[tier] = remaining[remainingIndex++];
|
||
}
|
||
}
|
||
|
||
return result as T[];
|
||
}
|