@@ -120,6 +120,84 @@ const resetSocketRaceState = (
120120 stores . suspiciousPlayers . delete ( socketId ) ;
121121} ;
122122
123+ const buildCompletedPlayerPlacement = (
124+ player ,
125+ race ,
126+ stores = {
127+ playerProgress,
128+ playerAvatars
129+ }
130+ ) => {
131+ const progress = stores . playerProgress . get ( player . id ) || { } ;
132+ const finishTimestampMs = Number . isFinite ( progress . timestamp ) && Number . isFinite ( race ?. startTime )
133+ ? Math . max ( 0 , progress . timestamp - race . startTime )
134+ : null ;
135+
136+ const completionTime = Number . isFinite ( progress . completion_time )
137+ ? progress . completion_time
138+ : ( Number . isFinite ( finishTimestampMs ) ? finishTimestampMs / 1000 : null ) ;
139+
140+ return {
141+ netid : player . netid ,
142+ wpm : Number . isFinite ( progress . wpm ) ? progress . wpm : null ,
143+ accuracy : Number . isFinite ( progress . accuracy ) ? progress . accuracy : null ,
144+ completion_time : Number . isFinite ( completionTime ) ? completionTime : null ,
145+ finishTimestampMs : Number . isFinite ( finishTimestampMs ) ? finishTimestampMs : null ,
146+ avatar_url : stores . playerAvatars . get ( player . id ) || null
147+ } ;
148+ } ;
149+
150+ const compareCompletedPlayerPlacements = ( a , b , isTimedTest = false ) => {
151+ if ( isTimedTest ) {
152+ const aWpm = Number . isFinite ( a . wpm ) ? a . wpm : Number . NEGATIVE_INFINITY ;
153+ const bWpm = Number . isFinite ( b . wpm ) ? b . wpm : Number . NEGATIVE_INFINITY ;
154+ if ( aWpm !== bWpm ) {
155+ return bWpm - aWpm ;
156+ }
157+
158+ const aAccuracy = Number . isFinite ( a . accuracy ) ? a . accuracy : Number . NEGATIVE_INFINITY ;
159+ const bAccuracy = Number . isFinite ( b . accuracy ) ? b . accuracy : Number . NEGATIVE_INFINITY ;
160+ if ( aAccuracy !== bAccuracy ) {
161+ return bAccuracy - aAccuracy ;
162+ }
163+ }
164+
165+ const aTime = Number . isFinite ( a . completion_time ) ? a . completion_time : Number . POSITIVE_INFINITY ;
166+ const bTime = Number . isFinite ( b . completion_time ) ? b . completion_time : Number . POSITIVE_INFINITY ;
167+ if ( aTime !== bTime ) {
168+ return aTime - bTime ;
169+ }
170+
171+ const aFinishTimestamp = Number . isFinite ( a . finishTimestampMs ) ? a . finishTimestampMs : Number . POSITIVE_INFINITY ;
172+ const bFinishTimestamp = Number . isFinite ( b . finishTimestampMs ) ? b . finishTimestampMs : Number . POSITIVE_INFINITY ;
173+ if ( aFinishTimestamp !== bFinishTimestamp ) {
174+ return aFinishTimestamp - bFinishTimestamp ;
175+ }
176+
177+ return a . netid . localeCompare ( b . netid ) ;
178+ } ;
179+
180+ const getRankedCompletedPlayers = (
181+ players ,
182+ race ,
183+ stores = {
184+ playerProgress,
185+ playerAvatars
186+ }
187+ ) => {
188+ const isTimedTest = Boolean ( race ?. snippet ?. is_timed_test ) ;
189+
190+ return ( players || [ ] )
191+ . filter ( player => player . completed && stores . playerProgress . has ( player . id ) )
192+ . map ( player => buildCompletedPlayerPlacement ( player , race , stores ) )
193+ . filter ( result => (
194+ Number . isFinite ( result . completion_time ) ||
195+ Number . isFinite ( result . finishTimestampMs ) ||
196+ Number . isFinite ( result . wpm )
197+ ) )
198+ . sort ( ( a , b ) => compareCompletedPlayerPlacements ( a , b , isTimedTest ) ) ;
199+ } ;
200+
123201// Get player data for client, including avatar URL and basic stats
124202const getPlayerClientData = async ( player ) => { // Make async
125203 // Use cached avatar if available, otherwise use null
@@ -1562,6 +1640,7 @@ const initialize = (io) => {
15621640 if ( newLobby ?. code ) {
15631641 activeRaces . delete ( newLobby . code ) ;
15641642 racePlayers . delete ( newLobby . code ) ;
1643+ sessionWins . delete ( newLobby . code ) ;
15651644
15661645 for ( const { socket : migratedSocket } of migratedPlayers ) {
15671646 try {
@@ -2415,27 +2494,17 @@ const handlePlayerFinish = async (io, code, playerId, resultData) => {
24152494 } ) ;
24162495
24172496 // Collect all results from completed players
2418- const allResults = players
2419- . filter ( p => p . completed && playerProgress . has ( p . id ) )
2420- . map ( p => {
2421- const prog = playerProgress . get ( p . id ) ;
2422- const avatarUrl = playerAvatars . get ( p . id ) ;
2423-
2424- // Log avatar status for debugging
2425- console . log ( `Player ${ p . netid } avatar status:` , {
2426- hasAvatar : ! ! avatarUrl ,
2427- avatarUrl : avatarUrl || 'null'
2428- } ) ;
2429-
2430- return {
2431- netid : p . netid ,
2432- wpm : prog . wpm ,
2433- accuracy : prog . accuracy ,
2434- completion_time : prog . completion_time ,
2435- avatar_url : avatarUrl // Include avatar URL
2436- } ;
2437- } )
2438- . sort ( ( a , b ) => a . completion_time - b . completion_time ) ; // Sort by time initially
2497+ const allResults = getRankedCompletedPlayers ( players , race ) . map ( result => {
2498+ const { finishTimestampMs, ...clientResult } = result ;
2499+
2500+ // Log avatar status for debugging
2501+ console . log ( `Player ${ result . netid } avatar status:` , {
2502+ hasAvatar : ! ! result . avatar_url ,
2503+ avatarUrl : result . avatar_url || 'null'
2504+ } ) ;
2505+
2506+ return clientResult ;
2507+ } ) ;
24392508
24402509 // Broadcast updated results list
24412510 io . to ( code ) . emit ( 'race:resultsUpdate' , { code, results : allResults } ) ;
@@ -2483,11 +2552,7 @@ const endRace = async (io, code) => {
24832552 // Update session win tally for private lobbies
24842553 if ( race . type === 'private' ) {
24852554 const players = racePlayers . get ( code ) || [ ] ;
2486- // Find the winner: completed player with fastest completion time
2487- const completedPlayers = players
2488- . filter ( p => p . completed && playerProgress . has ( p . id ) )
2489- . map ( p => ( { netid : p . netid , completion_time : playerProgress . get ( p . id ) . completion_time } ) )
2490- . sort ( ( a , b ) => a . completion_time - b . completion_time ) ;
2555+ const completedPlayers = getRankedCompletedPlayers ( players , race ) ;
24912556 if ( completedPlayers . length > 0 ) {
24922557 const winnerNetid = completedPlayers [ 0 ] . netid ;
24932558 const wins = sessionWins . get ( code ) || { } ;
@@ -2605,6 +2670,9 @@ module.exports = {
26052670 acquirePlayAgainLock,
26062671 releasePlayAgainLock,
26072672 clearLobbyTransientState,
2608- resetSocketRaceState
2673+ resetSocketRaceState,
2674+ buildCompletedPlayerPlacement,
2675+ compareCompletedPlayerPlacements,
2676+ getRankedCompletedPlayers
26092677 }
26102678} ;
0 commit comments