fix(complement-race): correct passenger boarding to use multiplayer fields

Passengers weren't boarding because the arcade room version was still checking for single-player fields (isBoarded/isDelivered) instead of multiplayer fields (claimedBy/deliveredBy).

Changes:
- Update useSteamJourney to check claimedBy/deliveredBy instead of isBoarded/isDelivered
- Update Validator to skip position validation in sprint mode (position is client-side)
- Trust client-side spatial checking for boarding/delivery in sprint mode

Sprint mode architecture:
- Client (useSteamJourney) continuously checks if train is at stations
- Client sends CLAIM_PASSENGER / DELIVER_PASSENGER moves when conditions met
- Server validates passenger availability (not position, since position is client-side)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-10-17 08:05:56 -05:00
parent 43f1f92900
commit 7ed1b94b8f
2 changed files with 33 additions and 22 deletions

View File

@@ -114,7 +114,9 @@ export function useSteamJourney() {
const CAR_SPACING = 7 // Must match SteamTrainJourney component
const maxPassengers = calculateMaxConcurrentPassengers(state.passengers, state.stations)
const maxCars = Math.max(1, maxPassengers)
const currentBoardedPassengers = state.passengers.filter((p) => p.isBoarded && !p.isDelivered)
const currentBoardedPassengers = state.passengers.filter(
(p) => p.claimedBy !== null && p.deliveredBy === null
)
// Debug logging flag - enable when debugging passenger boarding issues
// TO ENABLE: Change this to true, save, and the logs will appear in the browser console
@@ -150,7 +152,7 @@ export function useSteamJourney() {
const dest = state.stations.find((s) => s.id === p.destinationStationId)
console.log(` [${idx}] ${p.name} (ID: ${p.id})`)
console.log(
` Status: ${p.isDelivered ? 'DELIVERED' : p.isBoarded ? 'BOARDED' : 'WAITING'}`
` Status: ${p.deliveredBy !== null ? 'DELIVERED' : p.claimedBy !== null ? 'BOARDED' : 'WAITING'}`
)
console.log(
` Route: ${origin?.emoji} ${origin?.name} (pos ${origin?.position}) → ${dest?.emoji} ${dest?.name} (pos ${dest?.position})`
@@ -180,7 +182,7 @@ export function useSteamJourney() {
// FIRST: Identify which passengers will be delivered in this frame
const passengersToDeliver = new Set<string>()
currentBoardedPassengers.forEach((passenger, carIndex) => {
if (!passenger || passenger.isDelivered) return
if (!passenger || passenger.deliveredBy !== null) return
const station = state.stations.find((s) => s.id === passenger.destinationStationId)
if (!station) return
@@ -232,7 +234,7 @@ export function useSteamJourney() {
// Find waiting passengers whose origin station has an empty car nearby
state.passengers.forEach((passenger) => {
if (passenger.isBoarded || passenger.isDelivered) return
if (passenger.claimedBy !== null || passenger.deliveredBy !== null) return
const station = state.stations.find((s) => s.id === passenger.originStationId)
if (!station) return
@@ -300,7 +302,7 @@ export function useSteamJourney() {
// Check for deliverable passengers
// Passengers disembark when THEIR car reaches their destination
currentBoardedPassengers.forEach((passenger, carIndex) => {
if (!passenger || passenger.isDelivered) return
if (!passenger || passenger.deliveredBy !== null) return
const station = state.stations.find((s) => s.id === passenger.destinationStationId)
if (!station) return
@@ -393,7 +395,8 @@ export function useSteamJourney() {
if (!state.isGameActive || state.style !== 'sprint') return
// Check if all passengers are delivered
const allDelivered = state.passengers.length > 0 && state.passengers.every((p) => p.isDelivered)
const allDelivered =
state.passengers.length > 0 && state.passengers.every((p) => p.deliveredBy !== null)
if (allDelivered) {
// Generate new passengers after a short delay

View File

@@ -414,15 +414,19 @@ export class ComplementRaceValidator
return { valid: false, error: 'Passenger already claimed' }
}
// Check if player is at the origin station (within 5% tolerance)
const originStation = state.stations.find((s) => s.id === passenger.originStationId)
if (!originStation) {
return { valid: false, error: 'Origin station not found' }
}
// Sprint mode: Position is client-side, trust client's spatial checking
// (Client checks position in useSteamJourney before sending CLAIM move)
// Other modes: Validate position server-side
if (state.config.style !== 'sprint') {
const originStation = state.stations.find((s) => s.id === passenger.originStationId)
if (!originStation) {
return { valid: false, error: 'Origin station not found' }
}
const distance = Math.abs(player.position - originStation.position)
if (distance > 5) {
return { valid: false, error: 'Not at origin station' }
const distance = Math.abs(player.position - originStation.position)
if (distance > 5) {
return { valid: false, error: 'Not at origin station' }
}
}
// Claim passenger
@@ -477,15 +481,19 @@ export class ComplementRaceValidator
return { valid: false, error: 'Passenger already delivered' }
}
// Check if player is at destination station (within 5% tolerance)
const destStation = state.stations.find((s) => s.id === passenger.destinationStationId)
if (!destStation) {
return { valid: false, error: 'Destination station not found' }
}
// Sprint mode: Position is client-side, trust client's spatial checking
// (Client checks position in useSteamJourney before sending DELIVER move)
// Other modes: Validate position server-side
if (state.config.style !== 'sprint') {
const destStation = state.stations.find((s) => s.id === passenger.destinationStationId)
if (!destStation) {
return { valid: false, error: 'Destination station not found' }
}
const distance = Math.abs(player.position - destStation.position)
if (distance > 5) {
return { valid: false, error: 'Not at destination station' }
const distance = Math.abs(player.position - destStation.position)
if (distance > 5) {
return { valid: false, error: 'Not at destination station' }
}
}
// Deliver passenger and award points