Compare commits

...

4 Commits

Author SHA1 Message Date
semantic-release-bot
03262dbf40 chore(release): 4.5.0 [skip ci]
## [4.5.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.4.15...v4.5.0) (2025-10-18)

### Features

* **complement-race:** add infinite win condition for Steam Sprint mode ([d8fdfee](d8fdfeef74))
2025-10-18 12:54:15 +00:00
Thomas Hallock
d8fdfeef74 feat(complement-race): add infinite win condition for Steam Sprint mode
PROBLEM:
After a few routes in Steam Sprint mode, the "victory!" page was appearing.
Steam Sprint is designed to be an infinite game that never ends.

ROOT CAUSE:
The default config had:
  winCondition: 'route-based'
  routeCount: 3

So after completing 3 routes, the validator's checkWinCondition method would
find a winner and trigger the results/victory screen (line 835 in Validator).

FIX:
1. Added 'infinite' as a new valid winCondition type (game-configs.ts:92)
2. Updated default config to use winCondition: 'infinite' (both in
   arcade-games/complement-race/index.tsx and lib/arcade/game-configs.ts)
3. Updated checkWinCondition to return null immediately when winCondition === 'infinite'
   (Validator.ts:815-818)

BEHAVIOR:
- Steam Sprint now runs indefinitely with infinite routes
- Train automatically advances to next route after completing each one
- Game never ends unless player manually quits or leaves room
- Score and deliveries continue to accumulate across all routes
- Other win conditions (route-based, score-based, time-based) still work for
  custom game modes

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 07:53:14 -05:00
semantic-release-bot
005d945ca8 chore(release): 4.4.15 [skip ci]
## [4.4.15](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.4.14...v4.4.15) (2025-10-18)

### Bug Fixes

* **complement-race:** track previous position to detect route threshold crossing ([a6c20aa](a6c20aab3b))
2025-10-18 00:58:29 +00:00
Thomas Hallock
a6c20aab3b fix(complement-race): track previous position to detect route threshold crossing
PROBLEM:
Previous fix broke route progression - train would never advance to next route.

ROOT CAUSE:
After removing the dual game loop, I changed line 97 to:
  const trainPosition = state.trainPosition

This meant the route completion check (lines 296-299) became:
  if (
    state.trainPosition >= threshold &&
    state.trainPosition < threshold  // Same variable!
  )

This is ALWAYS FALSE because a value can't be both >= and < threshold.

The previous code worked because:
- trainPosition was the newly calculated position for this frame
- state.trainPosition was the previous frame's position
- So it could detect threshold crossing: newPos >= threshold && oldPos < threshold

FIX:
1. Added previousTrainPositionRef to track position from previous frame (line 48)
2. Updated route completion check to use previousPosition instead of state.trainPosition (line 300)
3. Store current position in ref at end of each frame (line 323)
4. Reset previousPosition when route changes (line 82)
5. Added debug logging when route completes (line 310-312)

Now the check correctly detects:
  if (
    trainPosition >= threshold &&      // Current position
    previousPosition < threshold       // Previous position
  )

This properly detects when the train crosses the exit threshold.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 19:57:34 -05:00
6 changed files with 33 additions and 5 deletions

View File

@@ -1,3 +1,17 @@
## [4.5.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.4.15...v4.5.0) (2025-10-18)
### Features
* **complement-race:** add infinite win condition for Steam Sprint mode ([d8fdfee](https://github.com/antialias/soroban-abacus-flashcards/commit/d8fdfeef74a5d3bb9684254af1c9d64d264b46ad))
## [4.4.15](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.4.14...v4.4.15) (2025-10-18)
### Bug Fixes
* **complement-race:** track previous position to detect route threshold crossing ([a6c20aa](https://github.com/antialias/soroban-abacus-flashcards/commit/a6c20aab3b245d9893808d188d16a35ab80cfca9))
## [4.4.14](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.4.13...v4.4.14) (2025-10-18)

View File

@@ -45,6 +45,7 @@ export function useSteamJourney() {
const routeExitThresholdRef = useRef<number>(107) // Default for 1 car: 100 + 7
const missedPassengersRef = useRef<Set<string>>(new Set()) // Track which passengers have been logged as missed
const pendingBoardingRef = useRef<Set<string>>(new Set()) // Track passengers with pending boarding requests across frames
const previousTrainPositionRef = useRef<number>(0) // Track previous position to detect threshold crossings
// Initialize game start time
useEffect(() => {
@@ -78,6 +79,7 @@ export function useSteamJourney() {
useEffect(() => {
pendingBoardingRef.current.clear()
missedPassengersRef.current.clear()
previousTrainPositionRef.current = 0 // Reset previous position for new route
}, [state.currentRoute])
// Momentum decay and position update loop
@@ -292,10 +294,11 @@ export function useSteamJourney() {
// Check for route completion (entire train exits tunnel)
const ENTIRE_TRAIN_EXIT_THRESHOLD = routeExitThresholdRef.current
const previousPosition = previousTrainPositionRef.current
if (
trainPosition >= ENTIRE_TRAIN_EXIT_THRESHOLD &&
state.trainPosition < ENTIRE_TRAIN_EXIT_THRESHOLD
previousPosition < ENTIRE_TRAIN_EXIT_THRESHOLD
) {
// Play celebration whistle
playSound('train_whistle', 0.6)
@@ -305,6 +308,9 @@ export function useSteamJourney() {
// Auto-advance to next route
const nextRoute = state.currentRoute + 1
console.log(
`🏁 ROUTE COMPLETE: Train crossed exit threshold (${trainPosition.toFixed(1)} >= ${ENTIRE_TRAIN_EXIT_THRESHOLD}). Advancing to Route ${nextRoute}`
)
dispatch({
type: 'START_NEW_ROUTE',
routeNumber: nextRoute,
@@ -313,6 +319,9 @@ export function useSteamJourney() {
// Note: New passengers will be generated by the server when it handles START_NEW_ROUTE
}
// Update previous position for next frame
previousTrainPositionRef.current = trainPosition
}, UPDATE_INTERVAL)
return () => clearInterval(interval)

View File

@@ -812,6 +812,11 @@ export class ComplementRaceValidator
private checkWinCondition(state: ComplementRaceState): string | null {
const { config, players } = state
// Infinite mode: Never end the game
if (config.winCondition === 'infinite') {
return null
}
// Practice mode: First to reach goal
if (config.style === 'practice') {
for (const [playerId, player] of Object.entries(players)) {

View File

@@ -41,7 +41,7 @@ const defaultConfig: ComplementRaceConfig = {
passengerCount: 6,
maxConcurrentPassengers: 3,
raceGoal: 20,
winCondition: 'route-based',
winCondition: 'infinite', // Sprint mode is infinite by default (Steam Sprint)
routeCount: 3,
targetScore: 100,
timeLimit: 300,

View File

@@ -89,7 +89,7 @@ export interface ComplementRaceGameConfig {
raceGoal: number // questions to win practice mode (default 20)
// Win Conditions
winCondition: 'route-based' | 'score-based' | 'time-based'
winCondition: 'route-based' | 'score-based' | 'time-based' | 'infinite'
targetScore?: number // for score-based (e.g., 100)
timeLimit?: number // for time-based (e.g., 300 seconds)
routeCount?: number // for route-based (e.g., 3 routes)
@@ -171,7 +171,7 @@ export const DEFAULT_COMPLEMENT_RACE_CONFIG: ComplementRaceGameConfig = {
raceGoal: 20,
// Win conditions
winCondition: 'route-based',
winCondition: 'infinite', // Sprint mode is infinite by default (Steam Sprint)
routeCount: 3,
targetScore: 100,
timeLimit: 300,

View File

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