6.7 KiB
game-server — enhanced content spec delta (Wave C-3)
ADDED Requirements
Requirement: LeaderboardManager multi-board ranking
LeaderboardManager SHALL maintain independent leaderboards keyed by
string id, each configured with one of three ranking modes
(score / wins / elo) and an optional maxEntries cap.
Scenario: submitScore inserts and re-ranks
- GIVEN a leaderboard
"global"configured forscoremode - WHEN the caller invokes
submitScore("global", { playerId: "alice", displayName: "Alice", score: 100 }) - THEN Alice's entry has
rank === 1 - AND the
game_server.leaderboard.submit_totalcounter is incremented
Scenario: maxEntries evicts the lowest-ranked entry
- GIVEN a leaderboard with
maxEntries: 2and entries(a:10, b:20, c:30) - WHEN all three submissions complete
- THEN the leaderboard retains only two entries
- AND the lowest-ranked entry (
a) has been evicted
Scenario: submitScore rejects an unknown leaderboard
- WHEN the caller invokes
submitScore("missing", ...) - THEN
GameServerError(GAME_SERVER.LEADERBOARD_NOT_FOUND)is thrown
Scenario: Elo update follows the standard formula
- GIVEN a leaderboard configured for
elomode withinitialElo: 1200 - WHEN a player submits
{ isWin: true, opponentElo: 1200 } - THEN the player's Elo strictly increases above 1200
Requirement: GamePersistence StorageLike-backed saves
GamePersistence SHALL persist game state through any conforming
StorageLike implementation, encoding SaveRecord payloads as
UTF-8 JSON under the key
game-server:save:<gameId>:<id>.
Scenario: save writes a record and returns its id
- WHEN the caller invokes
save(state, { name: "quicksave" }) - THEN the result's
success === true - AND subsequent
load(id)returns the samestate - AND the
game_server.persistence.save_total{outcome=ok}counter is incremented
Scenario: load() of unknown id throws PERSISTENCE_NOT_FOUND
- WHEN the caller invokes
load("missing") - THEN
GameServerError(GAME_SERVER.PERSISTENCE_NOT_FOUND)is thrown
Scenario: createSnapshot rotates past maxSnapshots
- GIVEN a persistence configured with
maxSnapshots: 2 - WHEN three snapshots are created in sequence
- THEN only the most recent two are retained on the record
Scenario: corrupt storage throws PERSISTENCE_LOAD_FAILED
- GIVEN a
StorageLikereturning invalid JSON bytes - WHEN the caller invokes
load(id) - THEN
GameServerError(GAME_SERVER.PERSISTENCE_LOAD_FAILED)is thrown
Requirement: ReplayRecorder deterministic input + snapshot capture
ReplayRecorder SHALL record inputs with monotonic sequence numbers
and periodic state snapshots into an in-memory buffer that is emitted as
a ReplayData<TState, TInput> document on stop().
Scenario: start from non-idle is rejected
- GIVEN a recorder in
recordingstatus - WHEN the caller invokes
start(initialState) - THEN
GameServerError(GAME_SERVER.REPLAY_RECORDING_STATE_INVALID)is thrown
Scenario: recordInput respects buffer cap
- GIVEN a recorder configured with
maxInputBuffer: 1 - AND one input recorded
- WHEN the caller invokes
recordInput(input) - THEN
GameServerError(GAME_SERVER.REPLAY_RECORDING_STATE_INVALID)is thrown
Scenario: tick auto-snapshots at the configured interval
- GIVEN a recorder with
snapshotInterval: 2 - WHEN
tick(state)is invoked twice with a state - THEN the buffer contains at least one snapshot with
reason === "interval"
Scenario: pause/resume excludes paused interval from elapsed time
- GIVEN a recorder started at clock 1000
- AND paused at clock 2000 then resumed at clock 5000
- WHEN elapsed time is read
- THEN the elapsed time equals 1000 (the unpaused gameplay duration)
Requirement: ReplayPlayer tick-driven deterministic playback
ReplayPlayer SHALL load ReplayData<TState, TInput> documents and
advance playback exclusively in response to tick() calls, applying
the configured PlaybackSpeed multiplier to each tick.
Scenario: play before load throws REPLAY_NOT_LOADED
- WHEN the caller invokes
play()without a loaded replay - THEN
GameServerError(GAME_SERVER.REPLAY_NOT_LOADED)is thrown
Scenario: tick delivers replayed inputs to listeners
- GIVEN a player with a loaded replay containing two inputs
- AND an
onInputlistener subscribed - WHEN
tick()is invoked enough times to reach the second input's tick - THEN the listener receives both inputs in
sequenceorder
Scenario: setSpeed rejects values outside the canonical set
- WHEN the caller invokes
setSpeed(7) - THEN
GameServerError(GAME_SERVER.REPLAY_INVALID_SPEED)is thrown
Scenario: loop=true rewinds at end
- GIVEN a player configured with
loop: true - AND a replay of 3 ticks
- WHEN
tick()is invoked 5 times during playback - THEN status remains
"playing"and the cursor wraps to ≤ 3
Requirement: TournamentManager bracket generation + advancement
TournamentManager SHALL accept participants in the pending phase,
build a single-elimination or round-robin bracket on start(), and
advance winners through nextMatchId links as results are recorded.
Scenario: start requires at least 2 participants
- GIVEN a tournament with one registered participant
- WHEN the caller invokes
start() - THEN
GameServerError(GAME_SERVER.TOURNAMENT_INSUFFICIENT_PARTICIPANTS)is thrown
Scenario: single-elimination propagates first-round byes
- GIVEN a single-elimination tournament with 3 participants
- WHEN
start()is invoked - THEN the first round contains at least one match with
status === "bye" - AND the surviving participant is advanced into the next round
Scenario: round-robin completes after every match recorded
- GIVEN a round-robin tournament with 4 participants
- WHEN every scheduled match has a recorded result
- THEN the tournament status transitions to
"completed" - AND
getWinner()returns the standings leader
Scenario: recordResult rejects a non-participant winner
- GIVEN an in-progress match between
aandb - WHEN the caller invokes
recordResult(matchId, "z") - THEN
GameServerError(GAME_SERVER.TOURNAMENT_INVALID_RESULT)is thrown
Scenario: shuffleSeeds uses injected Rng deterministically
- GIVEN a tournament configured with
shuffleSeeds: trueand a deterministicRng - WHEN
start()is invoked - THEN the participants are re-ordered using the
Rng'snextIntInRangeand theirseedfields are renumbered 1..N