From f2e71657dc1587c2b6df1f4227160b8a261c6084 Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Wed, 1 Oct 2025 09:19:58 -0500 Subject: [PATCH] feat: preserve track and passengers during route transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../RaceTrack/SteamTrainJourney.tsx | 66 +++++++++++++++++-- .../context/ComplementRaceContext.tsx | 6 +- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/apps/web/src/app/games/complement-race/components/RaceTrack/SteamTrainJourney.tsx b/apps/web/src/app/games/complement-race/components/RaceTrack/SteamTrainJourney.tsx index afc4d3a1..8c115f61 100644 --- a/apps/web/src/app/games/complement-race/components/RaceTrack/SteamTrainJourney.tsx +++ b/apps/web/src/app/games/complement-race/components/RaceTrack/SteamTrainJourney.tsx @@ -142,11 +142,65 @@ export function SteamTrainJourney({ momentum, trainPosition, pressure, elapsedTi // Get current route theme const routeTheme = getRouteTheme(state.currentRoute) + // Track previous route data to maintain visuals during transition + const previousRouteRef = useRef(state.currentRoute) + const [pendingTrackData, setPendingTrackData] = useState | 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 useEffect(() => { 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 useEffect(() => { @@ -372,13 +426,13 @@ export function SteamTrainJourney({ momentum, trainPosition, pressure, elapsedTi // Memoize filtered passenger lists to avoid recalculating on every render const boardedPassengers = useMemo(() => - state.passengers.filter(p => p.isBoarded && !p.isDelivered), - [state.passengers] + displayPassengers.filter(p => p.isBoarded && !p.isDelivered), + [displayPassengers] ) const nonDeliveredPassengers = useMemo(() => - state.passengers.filter(p => !p.isDelivered), - [state.passengers] + displayPassengers.filter(p => !p.isDelivered), + [displayPassengers] ) // Memoize ground texture circles to avoid recreating on every render diff --git a/apps/web/src/app/games/complement-race/context/ComplementRaceContext.tsx b/apps/web/src/app/games/complement-race/context/ComplementRaceContext.tsx index ececfbb1..11c33ee8 100644 --- a/apps/web/src/app/games/complement-race/context/ComplementRaceContext.tsx +++ b/apps/web/src/app/games/complement-race/context/ComplementRaceContext.tsx @@ -374,11 +374,11 @@ function gameReducer(state: GameState, action: GameAction): GameState { ...state, currentRoute: action.routeNumber, stations: action.stations, - trainPosition: 0, + trainPosition: -5, // Start off-screen to the left for smooth fade-in deliveredPassengers: 0, showRouteCelebration: false, - momentum: 0, - pressure: 0 + momentum: 50, // Give some starting momentum for the new route + pressure: 50 } case 'COMPLETE_ROUTE':