dc99af6bb3
Build and deploy / deploy (push) Successful in 36s
Un score falsifié envoyé directement à submit_icarus_score/submit_melon_score pouvait se convertir en vraies gloires au Tribunal (plafond abaissé de 1 000 000 à 5000, contrainte + RPC). Les fonctions cron-only award_*_points_if_due() n'étaient protégées que par un commentaire, pas un vrai REVOKE. Le bucket avatars n'avait aucune limite de taille/type. La banque complète des questions du Char (spoiler du jeu) était lisible par tout Citoyen via /char ou un appel direct — RLS restreinte aux juges, /char passe désormais par la RPC chariot_revealed_question(). 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 = 5000;
|
||
|
||
// 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[];
|
||
}
|