@@ -88,6 +88,23 @@ function getAverageScore(snapshot: LeaderboardSnapshot) {
8888 return snapshot . scoreSum / snapshot . totalPlayers ;
8989}
9090
91+ function isSnapshotConsistent ( snapshot : LeaderboardSnapshot ) {
92+ if ( snapshot . totalPlayers === 0 || snapshot . entries . length === 0 ) {
93+ return true ;
94+ }
95+
96+ const averageScore = getAverageScore ( snapshot ) ;
97+ const bestScore = snapshot . entries [ 0 ] ?. score ;
98+
99+ if ( typeof bestScore !== "number" ) {
100+ return true ;
101+ }
102+
103+ return isHigherScoreBetter ( snapshot . gameId )
104+ ? averageScore <= bestScore
105+ : averageScore >= bestScore ;
106+ }
107+
91108function toNumber ( value : unknown , fallback = 0 ) {
92109 if ( typeof value === "number" && Number . isFinite ( value ) ) {
93110 return value ;
@@ -238,6 +255,9 @@ export async function GET(req: NextRequest) {
238255 if ( ! snapshot ) {
239256 snapshot = await rebuildSnapshotFromDatabase ( db , gameId , mode ) ;
240257 await writeSnapshot ( bucket , snapshot ) ;
258+ } else if ( ! isSnapshotConsistent ( snapshot ) ) {
259+ snapshot = await rebuildSnapshotFromDatabase ( db , gameId , mode ) ;
260+ await writeSnapshot ( bucket , snapshot ) ;
241261 }
242262
243263 return NextResponse . json (
@@ -289,14 +309,15 @@ export async function POST(req: NextRequest) {
289309 const { db, bucket } = await getCloudflareBindings ( ) ;
290310 const playerName = await generateStableName ( playerId ) ;
291311 const nowIso = new Date ( ) . toISOString ( ) ;
312+ const scoreOrder = isHigherScoreBetter ( gameId ) ? "DESC" : "ASC" ;
292313
293314 const existingBest = await db . prepare (
294315 `SELECT score
295316 FROM leaderboard
296317 WHERE game_id = ?
297318 AND mode = ?
298319 AND COALESCE(player_id, player_name) = ?
299- ORDER BY score ${ gameId === "reaction-time" || gameId === "schulte-table" ? "ASC" : "DESC" }
320+ ORDER BY score ${ scoreOrder }
300321 LIMIT 1`
301322 ) . bind ( gameId , mode , playerId ) . first ( ) ;
302323
0 commit comments