Add discreet anticheat mechanism for the games
Build and deploy / deploy (push) Failing after 32s

This commit is contained in:
2026-08-24 12:07:10 +02:00
parent 35704e50fc
commit b7f3f8b0fa
6 changed files with 230 additions and 53 deletions
+29 -2
View File
@@ -30,7 +30,20 @@ function randomTier(min: number, max: number): number {
return min + Math.floor(Math.random() * (max - min + 1));
}
export function MelonGame({ citizens, onFinish }: { citizens: MemberRow[]; onFinish: (score: number) => void }) {
export type MelonRunTelemetry = {
v: 1;
duration_ms: number;
actions: number;
merges: number;
};
export function MelonGame({
citizens,
onFinish,
}: {
citizens: MemberRow[];
onFinish: (score: number, run: MelonRunTelemetry) => void;
}) {
const containerRef = useRef<HTMLDivElement | null>(null);
const previewRef = useRef<HTMLDivElement | null>(null);
const guideLineRef = useRef<HTMLDivElement | null>(null);
@@ -71,6 +84,9 @@ export function MelonGame({ citizens, onFinish }: { citizens: MemberRow[]; onFin
const pendingMergesRef = useRef<PendingMerge[]>([]);
const mergingBodyIdsRef = useRef(new Set<number>());
const dangerSinceRef = useRef<number | null>(null);
const runStartedAtRef = useRef(Date.now());
const dropCountRef = useRef(0);
const mergeCountRef = useRef(0);
const spawnPiece = useCallback(
(tier: number, x: number, y: number): Piece => {
@@ -128,6 +144,10 @@ export function MelonGame({ citizens, onFinish }: { citizens: MemberRow[]; onFin
pendingMergesRef.current = [];
mergingBodyIdsRef.current.clear();
dangerSinceRef.current = null;
runStartedAtRef.current = Date.now();
dropCountRef.current = 0;
mergeCountRef.current = 0;
lastDropAtRef.current = -Infinity;
scoreRef.current = 0;
statusRef.current = "playing";
const firstNextTier = randomTier(DROP_MIN_TIER, dropMaxTier);
@@ -160,6 +180,7 @@ export function MelonGame({ citizens, onFinish }: { citizens: MemberRow[]; onFin
// incrémente le compteur d'id) : appelé une seule fois ici, jamais
// depuis l'intérieur du updater de setPieces, qui doit rester pur.
const dropped = spawnPiece(tier, x, radius + 6);
dropCountRef.current += 1;
setPieces((current) => [...current, dropped]);
// La file glisse d'un cran : ce qui était annoncé en second devient la
@@ -281,6 +302,7 @@ export function MelonGame({ citizens, onFinish }: { citizens: MemberRow[]; onFin
if (!bodyA || !bodyB) continue;
Matter.Composite.remove(engine.world, [bodyA, bodyB]);
mergeCountRef.current += 1;
bodiesRef.current.delete(merge.pieceIdA);
bodiesRef.current.delete(merge.pieceIdB);
nodeRefs.current.delete(merge.pieceIdA);
@@ -338,7 +360,12 @@ export function MelonGame({ citizens, onFinish }: { citizens: MemberRow[]; onFin
} else if (now - dangerSinceRef.current >= GAME_OVER_GRACE_MS) {
statusRef.current = "dead";
setStatus("dead");
onFinishRef.current(scoreRef.current);
onFinishRef.current(scoreRef.current, {
v: 1,
duration_ms: Math.max(0, Date.now() - runStartedAtRef.current),
actions: dropCountRef.current,
merges: mergeCountRef.current,
});
}
} else {
dangerSinceRef.current = null;