feat: preserve track and passengers during route transitions

- Store pending track data and only apply when train resets to beginning
- Preserve passenger list display until entire train exits
- Prevent visual jumps by keeping old route data visible during fade-out
- Track transitions now seamless: old track/passengers persist until trainPosition < 0

🤖 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-01 09:19:58 -05:00
parent e704a28524
commit f2e71657dc
2 changed files with 63 additions and 9 deletions

View File

@ -142,11 +142,65 @@ export function SteamTrainJourney({ momentum, trainPosition, pressure, elapsedTi
// Get current route theme // Get current route theme
const routeTheme = getRouteTheme(state.currentRoute) const routeTheme = getRouteTheme(state.currentRoute)
// Track previous route data to maintain visuals during transition
const previousRouteRef = useRef(state.currentRoute)
const [pendingTrackData, setPendingTrackData] = useState<ReturnType<typeof trackGenerator.generateTrack> | null>(null)
// Preserve passengers during route transition
const [displayPassengers, setDisplayPassengers] = useState(state.passengers)
const previousPassengersRef = useRef(state.passengers)
// Generate track on mount and when route changes // Generate track on mount and when route changes
useEffect(() => { useEffect(() => {
const track = trackGenerator.generateTrack(state.currentRoute) const track = trackGenerator.generateTrack(state.currentRoute)
setTrackData(track)
}, [trackGenerator, state.currentRoute]) // If we're in the middle of a route (position > 0), store as pending
// Only apply new track when position resets to beginning (< 0)
if (state.trainPosition > 0 && previousRouteRef.current !== state.currentRoute) {
setPendingTrackData(track)
} else {
setTrackData(track)
previousRouteRef.current = state.currentRoute
setPendingTrackData(null)
}
}, [trackGenerator, state.currentRoute, state.trainPosition])
// Apply pending track when train resets to beginning
useEffect(() => {
if (pendingTrackData && state.trainPosition < 0) {
setTrackData(pendingTrackData)
previousRouteRef.current = state.currentRoute
setPendingTrackData(null)
}
}, [pendingTrackData, state.trainPosition, state.currentRoute])
// Manage passenger display during route transitions
useEffect(() => {
// If we're starting a new route (position < 0) or passengers haven't changed, update immediately
if (state.trainPosition < 0 || state.passengers === previousPassengersRef.current) {
setDisplayPassengers(state.passengers)
previousPassengersRef.current = state.passengers
}
// Otherwise, if we're mid-route and passengers changed, keep showing old passengers
else if (state.trainPosition > 0 && state.passengers !== previousPassengersRef.current) {
// Keep displaying old passengers until train exits
// Don't update displayPassengers yet
}
// When train resets to beginning, switch to new passengers
if (state.trainPosition < 0 && state.passengers !== previousPassengersRef.current) {
setDisplayPassengers(state.passengers)
previousPassengersRef.current = state.passengers
}
}, [state.passengers, state.trainPosition])
// Update display passengers during gameplay (same route)
useEffect(() => {
// Only update if we're in the same route (not transitioning)
if (previousRouteRef.current === state.currentRoute && state.trainPosition >= 0 && state.trainPosition < 100) {
setDisplayPassengers(state.passengers)
}
}, [state.passengers, state.currentRoute, state.trainPosition])
// Generate ties and rails when path is ready // Generate ties and rails when path is ready
useEffect(() => { useEffect(() => {
@ -372,13 +426,13 @@ export function SteamTrainJourney({ momentum, trainPosition, pressure, elapsedTi
// Memoize filtered passenger lists to avoid recalculating on every render // Memoize filtered passenger lists to avoid recalculating on every render
const boardedPassengers = useMemo(() => const boardedPassengers = useMemo(() =>
state.passengers.filter(p => p.isBoarded && !p.isDelivered), displayPassengers.filter(p => p.isBoarded && !p.isDelivered),
[state.passengers] [displayPassengers]
) )
const nonDeliveredPassengers = useMemo(() => const nonDeliveredPassengers = useMemo(() =>
state.passengers.filter(p => !p.isDelivered), displayPassengers.filter(p => !p.isDelivered),
[state.passengers] [displayPassengers]
) )
// Memoize ground texture circles to avoid recreating on every render // Memoize ground texture circles to avoid recreating on every render

View File

@ -374,11 +374,11 @@ function gameReducer(state: GameState, action: GameAction): GameState {
...state, ...state,
currentRoute: action.routeNumber, currentRoute: action.routeNumber,
stations: action.stations, stations: action.stations,
trainPosition: 0, trainPosition: -5, // Start off-screen to the left for smooth fade-in
deliveredPassengers: 0, deliveredPassengers: 0,
showRouteCelebration: false, showRouteCelebration: false,
momentum: 0, momentum: 50, // Give some starting momentum for the new route
pressure: 0 pressure: 50
} }
case 'COMPLETE_ROUTE': case 'COMPLETE_ROUTE':