Compare commits

...

8 Commits

Author SHA1 Message Date
semantic-release-bot
c5bfcf990a chore(release): 4.64.2 [skip ci]
## [4.64.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.64.1...v4.64.2) (2025-10-22)

### Bug Fixes

* **complement-race:** use individual player positions for ghost trains ([00dc4b1](00dc4b1d06))
2025-10-22 16:22:55 +00:00
Thomas Hallock
00dc4b1d06 fix(complement-race): use individual player positions for ghost trains
Previously all ghost trains used the local player's trainPosition,
causing them to render at the same location (hidden behind local train).

Now each ghost train uses its own player.position from multiplayer state,
allowing them to be visible at different positions on the track.

Also added logging to show ghost train positions for debugging.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 11:21:28 -05:00
semantic-release-bot
76063884af chore(release): 4.64.1 [skip ci]
## [4.64.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.64.0...v4.64.1) (2025-10-22)

### Bug Fixes

* **complement-race:** use local player instead of first player for train display ([915d8a5](915d8a5343))
2025-10-22 16:15:59 +00:00
Thomas Hallock
915d8a5343 fix(complement-race): use local player instead of first player for train display
Previously used `firstActivePlayer` which could show the wrong player's
name/emoji on the local train in multiplayer sessions. Now explicitly
finds the local player using `isLocal` flag.

Also updated passenger filtering to only show passengers claimed by
the local player, not the first player in the list.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 11:14:33 -05:00
Thomas Hallock
028b0cb86f debug: use useEffect to log only on changes, not every frame 2025-10-22 11:11:04 -05:00
Thomas Hallock
2bf00af952 debug: fix remaining verbose logs 2025-10-22 11:09:13 -05:00
Thomas Hallock
1d229333bc debug: reduce logging to essential info only 2025-10-22 11:08:28 -05:00
Thomas Hallock
0c67f63ac7 debug: add comprehensive logging for ghost trains troubleshooting 2025-10-22 11:06:09 -05:00
5 changed files with 68 additions and 14 deletions

View File

@@ -1,3 +1,17 @@
## [4.64.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.64.1...v4.64.2) (2025-10-22)
### Bug Fixes
* **complement-race:** use individual player positions for ghost trains ([00dc4b1](https://github.com/antialias/soroban-abacus-flashcards/commit/00dc4b1d06a4e1763deb16333a298145cafd9187))
## [4.64.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.64.0...v4.64.1) (2025-10-22)
### Bug Fixes
* **complement-race:** use local player instead of first player for train display ([915d8a5](https://github.com/antialias/soroban-abacus-flashcards/commit/915d8a5343e70a30c7a82bed645e6628fcc08a86))
## [4.64.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.11...v4.64.0) (2025-10-22)

View File

@@ -1,6 +1,6 @@
'use client'
import { useMemo, useRef } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import type { PlayerState } from '@/arcade-games/complement-race/types'
import type { RailroadTrackGenerator } from '../../lib/RailroadTrackGenerator'
@@ -43,6 +43,15 @@ export function GhostTrain({ player, trainPosition, trackGenerator, pathRef }: G
}
}, [trainPosition, pathRef])
// Log only once when this ghost train first renders
const hasLoggedRef = useRef(false)
useEffect(() => {
if (!hasLoggedRef.current && trainTransform.opacity > 0) {
console.log('[GhostTrain] rendering:', player.name, 'at position:', trainPosition.toFixed(1))
hasLoggedRef.current = true
}
}, [trainTransform.opacity, player.name, trainPosition])
// Don't render if position data isn't ready
if (trainTransform.opacity === 0) {
return null

View File

@@ -1,7 +1,7 @@
'use client'
import { animated, useSpring } from '@react-spring/web'
import { memo, useMemo, useRef, useState } from 'react'
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import { useGameMode } from '@/contexts/GameModeContext'
import { useUserProfile } from '@/contexts/UserProfileContext'
import { useComplementRace } from '@/arcade-games/complement-race/Provider'
@@ -101,10 +101,20 @@ export function SteamTrainJourney({
const { players } = useGameMode()
const { profile: _profile } = useUserProfile()
// Get the first active player's emoji
// Get the LOCAL player's emoji (not just the first player!)
const activePlayers = Array.from(players.values()).filter((p) => p.isActive)
const firstActivePlayer = activePlayers[0]
const playerEmoji = firstActivePlayer?.emoji ?? '👤'
const localPlayer = activePlayers.find((p) => p.isLocal)
const playerEmoji = localPlayer?.emoji ?? '👤'
// Log only when localPlayer changes
useEffect(() => {
console.log(
'[SteamTrainJourney] localPlayer:',
localPlayer?.name,
'isLocal:',
localPlayer?.isLocal
)
}, [localPlayer?.id, localPlayer?.name, localPlayer?.isLocal])
const svgRef = useRef<SVGSVGElement>(null)
const pathRef = useRef<SVGPathElement>(null)
@@ -165,14 +175,13 @@ export function SteamTrainJourney({
// Memoize filtered passenger lists to avoid recalculating on every render
// Arcade room multiplayer uses claimedBy/deliveredBy instead of isBoarded/isDelivered
// Only show passengers claimed by the first active player
// Only show passengers claimed by the LOCAL player
const boardedPassengers = useMemo(
() =>
displayPassengers.filter(
(p) =>
p.claimedBy === firstActivePlayer?.id && p.claimedBy !== null && p.deliveredBy === null
(p) => p.claimedBy === localPlayer?.id && p.claimedBy !== null && p.deliveredBy === null
),
[displayPassengers, firstActivePlayer?.id]
[displayPassengers, localPlayer?.id]
)
const nonDeliveredPassengers = useMemo(
@@ -194,12 +203,28 @@ export function SteamTrainJourney({
// Get other players for ghost trains (filter out local player)
const otherPlayers = useMemo(() => {
if (!multiplayerState?.players || !localPlayerId) return []
return Object.entries(multiplayerState.players)
if (!multiplayerState?.players || !localPlayerId) {
return []
}
const filtered = Object.entries(multiplayerState.players)
.filter(([playerId, player]) => playerId !== localPlayerId && player.isActive)
.map(([_, player]) => player)
return filtered
}, [multiplayerState?.players, localPlayerId])
// Log only when otherPlayers count changes
useEffect(() => {
console.log('[SteamTrainJourney] otherPlayers count:', otherPlayers.length)
if (otherPlayers.length > 0) {
console.log(
'[SteamTrainJourney] ghost positions:',
otherPlayers.map((p) => `${p.name}: ${p.position.toFixed(1)}`).join(', ')
)
}
}, [otherPlayers.length, otherPlayers])
if (!trackData) return null
return (
@@ -266,7 +291,7 @@ export function SteamTrainJourney({
<GhostTrain
key={player.id}
player={player}
trainPosition={trainPosition} // For now, use same position calculation
trainPosition={player.position} // Use each player's individual position
trackGenerator={trackGenerator}
pathRef={pathRef}
/>

View File

@@ -306,12 +306,18 @@ export function ComplementRaceProvider({ children }: { children: ReactNode }) {
// Get local player ID
const localPlayerId = useMemo(() => {
return activePlayers.find((id) => {
const foundId = activePlayers.find((id) => {
const player = players.get(id)
return player?.isLocal
})
return foundId
}, [activePlayers, players])
// Log only when localPlayerId changes
useEffect(() => {
console.log('[Provider] localPlayerId:', localPlayerId)
}, [localPlayerId])
// Debug logging ref (track last logged values)
const lastLogRef = useState({ key: '', count: 0 })[0]

View File

@@ -1,6 +1,6 @@
{
"name": "soroban-monorepo",
"version": "4.64.0",
"version": "4.64.2",
"private": true,
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
"workspaces": [