feat(complement-race): implement position broadcasting for ghost trains
Clients now broadcast their train position to server every 200ms, allowing other clients to render ghost trains at correct locations. Added UPDATE_POSITION move type and server-side validation to sync client-calculated positions (from momentum physics) to server state. This fixes the issue where ghost trains were rendering but invisible because they all had position=0 from server. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c5bfcf990a
commit
c5fba5b7dd
|
|
@ -564,6 +564,23 @@ export function ComplementRaceProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
}, [multiplayerState.currentRoute, compatibleState.style, multiplayerState.passengers.length])
|
||||
|
||||
// Broadcast position to server for multiplayer ghost trains
|
||||
useEffect(() => {
|
||||
if (!compatibleState.isGameActive || compatibleState.style !== 'sprint') {
|
||||
return
|
||||
}
|
||||
|
||||
// Send position update every 200ms
|
||||
const interval = setInterval(() => {
|
||||
makeMove({
|
||||
type: 'UPDATE_POSITION',
|
||||
data: { position: clientPosition },
|
||||
})
|
||||
}, 200)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [compatibleState.isGameActive, compatibleState.style, clientPosition, makeMove])
|
||||
|
||||
// Keep lastLogRef for future debugging needs
|
||||
// (removed debug logging)
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ export class ComplementRaceValidator
|
|||
case 'UPDATE_INPUT':
|
||||
return this.validateUpdateInput(state, move.playerId, move.data.input)
|
||||
|
||||
case 'UPDATE_POSITION':
|
||||
return this.validateUpdatePosition(state, move.playerId, move.data.position)
|
||||
|
||||
case 'CLAIM_PASSENGER':
|
||||
return this.validateClaimPassenger(
|
||||
state,
|
||||
|
|
@ -397,6 +400,39 @@ export class ComplementRaceValidator
|
|||
return { valid: true, newState }
|
||||
}
|
||||
|
||||
private validateUpdatePosition(
|
||||
state: ComplementRaceState,
|
||||
playerId: string,
|
||||
position: number
|
||||
): ValidationResult {
|
||||
if (state.gamePhase !== 'playing') {
|
||||
return { valid: false, error: 'Game not in playing phase' }
|
||||
}
|
||||
|
||||
const player = state.players[playerId]
|
||||
if (!player) {
|
||||
return { valid: false, error: 'Player not found' }
|
||||
}
|
||||
|
||||
// Validate position is a reasonable number (0-100)
|
||||
if (typeof position !== 'number' || position < 0 || position > 100) {
|
||||
return { valid: false, error: 'Invalid position value' }
|
||||
}
|
||||
|
||||
const newState: ComplementRaceState = {
|
||||
...state,
|
||||
players: {
|
||||
...state.players,
|
||||
[playerId]: {
|
||||
...player,
|
||||
position,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return { valid: true, newState }
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Sprint Mode: Passenger Management
|
||||
// ==========================================================================
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ export type ComplementRaceMove = BaseGameMove &
|
|||
// Playing phase
|
||||
| { type: 'SUBMIT_ANSWER'; data: { answer: number; responseTime: number } }
|
||||
| { type: 'UPDATE_INPUT'; data: { input: string } } // Show "thinking" indicator
|
||||
| { type: 'UPDATE_POSITION'; data: { position: number } } // Sprint mode: sync train position
|
||||
| { type: 'CLAIM_PASSENGER'; data: { passengerId: string; carIndex: number } } // Sprint mode: pickup
|
||||
| { type: 'DELIVER_PASSENGER'; data: { passengerId: string } } // Sprint mode: delivery
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue