Compare commits

...

6 Commits

Author SHA1 Message Date
semantic-release-bot
659464d3b4 chore(release): 4.65.1 [skip ci]
## [4.65.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.65.0...v4.65.1) (2025-10-22)

### Bug Fixes

* **complement-race:** use sendMove with correct parameters for position updates ([06cd94b](06cd94b24c))
2025-10-22 17:43:07 +00:00
Thomas Hallock
06cd94b24c fix(complement-race): use sendMove with correct parameters for position updates
Fixed ReferenceError where makeMove was undefined. The correct function
is sendMove from useArcadeSession, which requires playerId and userId
parameters in addition to the move type and data.

Changes:
- Changed makeMove to sendMove
- Added playerId and userId to the move object
- Added localPlayerId guard to prevent updates before player is identified
- Updated dependency array to include localPlayerId, viewerId, and sendMove

This fixes the runtime error preventing ghost trains from working.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 12:41:44 -05:00
semantic-release-bot
ada0becee5 chore(release): 4.65.0 [skip ci]
## [4.65.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.64.2...v4.65.0) (2025-10-22)

### Features

* **complement-race:** implement position broadcasting for ghost trains ([c5fba5b](c5fba5b7dd))
2025-10-22 17:09:44 +00:00
Thomas Hallock
c5fba5b7dd 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>
2025-10-22 12:08:19 -05:00
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
7 changed files with 95 additions and 5 deletions

View File

@@ -1,3 +1,24 @@
## [4.65.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.65.0...v4.65.1) (2025-10-22)
### Bug Fixes
* **complement-race:** use sendMove with correct parameters for position updates ([06cd94b](https://github.com/antialias/soroban-abacus-flashcards/commit/06cd94b24cdd9dbd36fb5800c9ba7be194f7eed0))
## [4.65.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.64.2...v4.65.0) (2025-10-22)
### Features
* **complement-race:** implement position broadcasting for ghost trains ([c5fba5b](https://github.com/antialias/soroban-abacus-flashcards/commit/c5fba5b7dd0f36fd3bbe596409e01b0d3dbd4fbe))
## [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)

View File

@@ -47,10 +47,10 @@ export function GhostTrain({ player, trainPosition, trackGenerator, pathRef }: G
const hasLoggedRef = useRef(false)
useEffect(() => {
if (!hasLoggedRef.current && trainTransform.opacity > 0) {
console.log('[GhostTrain] rendering:', player.name)
console.log('[GhostTrain] rendering:', player.name, 'at position:', trainPosition.toFixed(1))
hasLoggedRef.current = true
}
}, [trainTransform.opacity, player.name])
}, [trainTransform.opacity, player.name, trainPosition])
// Don't render if position data isn't ready
if (trainTransform.opacity === 0) {

View File

@@ -217,7 +217,13 @@ export function SteamTrainJourney({
// Log only when otherPlayers count changes
useEffect(() => {
console.log('[SteamTrainJourney] otherPlayers count:', otherPlayers.length)
}, [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
@@ -285,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

@@ -564,6 +564,32 @@ 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' || !localPlayerId) {
return
}
// Send position update every 200ms
const interval = setInterval(() => {
sendMove({
type: 'UPDATE_POSITION',
playerId: localPlayerId,
userId: viewerId || '',
data: { position: clientPosition },
} as ComplementRaceMove)
}, 200)
return () => clearInterval(interval)
}, [
compatibleState.isGameActive,
compatibleState.style,
clientPosition,
localPlayerId,
viewerId,
sendMove,
])
// Keep lastLogRef for future debugging needs
// (removed debug logging)

View File

@@ -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
// ==========================================================================

View File

@@ -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

View File

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