The Spectra Server replay system allows you to record match events and play them back with various timing modes. This is invaluable for testing overlays, debugging issues, and demonstrating features.
Replay System Overview
The replay system consists of three main components:
Replay Recording - Matches automatically record events to replay log
Replay Player - Reads replay files and controls playback
Replay Connector - Connects to server and sends replay events
Source: src/replay/ directory
Recording Match Events
Matches automatically record events when configured. Each match maintains a replay log:
private replayLog : IAuthedData [] = [];
Events are timestamped and appended to the log as they occur:
data . timestamp = Date . now ();
// Event is processed and added to replay log
Source: src/controller/MatchController.ts:97
Replay files are JSON-formatted with a header and event array:
{
"obsName" : "Observer Name" ,
"groupCode" : "REPLAY" ,
"clientVersion" : "1.2.0" ,
"leftTeam" : {
"name" : "Team A" ,
"tricode" : "TMA" ,
"url" : "https://example.com/logo.png" ,
"attackStart" : true
},
"rightTeam" : {
"name" : "Team B" ,
"tricode" : "TMB" ,
"url" : "https://example.com/logo.png" ,
"attackStart" : false
}
}
{
"type" : "round_info" ,
"groupCode" : "REPLAY" ,
"timestamp" : 1234567890 ,
"data" : { /* event data */ }
}
{
"type" : "scoreboard" ,
"groupCode" : "REPLAY" ,
"timestamp" : 1234567891 ,
"data" : { /* event data */ }
}
Each event is a separate JSON object on its own line.
Replay Modes
The replay system supports four playback modes:
Instant Mode
Sends all events immediately without delays:
private playInstant () {
while ( this . sendNextEvent ()) {
/* empty on purpose */
}
this . finished ();
}
Source: src/replay/ReplayPlayer.ts:89-94
Use case : Testing event processing logic, quick overlay previews
Delay Mode
Sends events with a fixed delay between each event:
private playDelay () {
const intervalId = setInterval (() => {
if ( ! this . sendNextEvent ()) {
clearInterval ( intervalId );
this . finished ();
}
}, this . delay ); // Default 500ms
}
Source: src/replay/ReplayPlayer.ts:96-104
Use case : Controlled playback for demonstrations, slower debugging
Timestamps Mode
Replays events using original timing from recording:
private playTimestamps () {
if ( this . sendNextEvent ()) {
const timestamp1 = this . replayData [ this . currentReplayIndex - 1 ]. timestamp ;
const timestamp2 = this . replayData [ this . currentReplayIndex ]. timestamp ;
const nextDelay = timestamp2 - timestamp1 ;
setTimeout (() => this . playTimestamps (), nextDelay );
} else {
this . finished ();
}
}
Source: src/replay/ReplayPlayer.ts:106-116
Use case : Realistic match recreation, timing-sensitive testing
Manual Mode
Allows manual control via keyboard input:
private playManual () {
const stream = process . stdin ;
stream . on ( "data" , ( data ) => {
const s = data . toString ();
const amount = Number . parseInt ( s );
if ( s == " \n " ) {
// Send next event
this . sendNextEvent ();
} else if ( s == "exit \n " ) {
// Stop replay
ready = false ;
} else if ( s == "go \n " ) {
// Send all remaining events
const interval = setInterval (() => {
if ( ! this . sendNextEvent ()) {
clearInterval ( interval );
}
}, 1 );
} else if ( ! Number . isNaN ( amount )) {
// Send next N events
for ( let i = 0 ; i < amount ; i ++ ) {
this . sendNextEvent ();
}
}
});
}
Source: src/replay/ReplayPlayer.ts:118-153
Use case : Step-by-step debugging, precise event control
NPM Scripts
The server includes convenient npm scripts for replay:
{
"scripts" : {
"replay" : "tsx ./src/replay/replay.ts" ,
"replay_instant" : "tsx ./src/replay/replay.ts -instant" ,
"replay_delay" : "tsx ./src/replay/replay.ts -delay" ,
"replay_timestamps" : "tsx ./src/replay/replay.ts -timestamps" ,
"replay_manual" : "tsx ./src/replay/replay.ts -manual" ,
"replay_file" : "yarn run replay_instant -- -game customGameTest.replay"
}
}
Source: package.json:19-24
Running Replays
Instant Mode
Delay Mode (500ms)
Custom Delay
Timestamps Mode
Manual Mode
Custom Server
yarn replay_instant -game match.replay
Command Line Parameters
The replay script accepts the following parameters:
Parameter Description Example -instantUse instant replay mode -instant-delay [ms]Use delay mode with specified milliseconds -delay 1000-timestampsUse original event timestamps -timestamps-manualEnable manual control mode -manual-game [file]Specify replay file -game match.replay-server [url]Override server URL -server http://localhost:5100
Source: src/replay/replay.ts:12-50
Replay Player Implementation
Loading Replay Files
public loadReplayFile ( filePath : string ) {
const replayContent = readFileSync ( filePath ). toString ();
const replayObj = JSON . parse ( `[ ${ replayContent } ]` );
this . replayHeaderData = replayObj . shift ();
this . replayData = replayObj ;
}
Source: src/replay/ReplayPlayer.ts:29-38
Sending Events
private sendNextEvent (): boolean {
if ( this . currentReplayIndex % 50 === 0 ) {
Log . info ( `Current Event: ${ this . currentReplayIndex } ` );
}
this . connector . sendReplayData ( this . replayData [ this . currentReplayIndex ]);
// Check if game has ended
if (
this . replayData [ this . currentReplayIndex ]. type == "round_info" &&
( this . replayData [ this . currentReplayIndex ]. data as IFormattedRoundInfo ). roundPhase == "game_end"
) {
return false ; // Stop replay
}
this . currentReplayIndex ++ ;
return this . currentReplayIndex < this . replayData . length ;
}
Source: src/replay/ReplayPlayer.ts:66-80
Replay Connector Service
The ReplayConnectorService handles WebSocket communication:
public constructor ( ingestServerUrl : string ) {
this . ingestServerUrl = ingestServerUrl ;
}
public setAuthValues (
obsName : string ,
groupCode : string ,
accessKey : string ,
leftTeam : AuthTeam ,
rightTeam : AuthTeam ,
clientVersion : string
) {
this . obsName = obsName ;
this . groupCode = groupCode ;
this . key = accessKey ;
this . leftTeam = leftTeam ;
this . rightTeam = rightTeam ;
this . clientVersion = clientVersion ;
}
Source: src/replay/ReplayConnectorService.ts:29-47
Authentication
private handleAuthProcess () {
return new Promise < void >(( resolve , reject ) => {
this . ws = io . connect ( this . ingestServerUrl );
const authData : IAuthenticationData = {
type: DataTypes . AUTH ,
clientVersion: this . clientVersion ,
obsName: this . obsName ,
groupCode: this . groupCode ,
key: this . key ,
leftTeam: this . leftTeam ,
rightTeam: this . rightTeam ,
toolsData: { /* default tools data */ }
};
this . ws . emit ( "obs_logon" , JSON . stringify ( authData ));
this . ws . once ( "obs_logon_ack" , ( msg ) => {
const json = JSON . parse ( msg . toString ());
if ( json . value === true ) {
this . enabled = true ;
resolve ();
} else {
reject ();
}
});
});
}
Source: src/replay/ReplayConnectorService.ts:64-180
Sending Replay Data
public sendReplayData ( data : IAuthedData ) {
if ( this . enabled ) {
this . ws . emit ( "obs_data" , JSON . stringify ( data ));
}
}
Source: src/replay/ReplayConnectorService.ts:210-217
Use Cases
Testing Overlays
# Test overlay with instant replay
yarn replay_instant -game test-match.replay
# Test overlay animations with delays
yarn replay -delay 2000 -game test-match.replay
Debugging Issues
# Step through events manually to find issue
yarn replay_manual -game problematic-match.replay
# Commands in manual mode:
# Press Enter: Next event
# Type number + Enter: Skip N events
# Type "go" + Enter: Finish replay
# Type "exit" + Enter: Stop replay
Demonstration
# Replay match with realistic timing
yarn replay_timestamps -game demo-match.replay
Integration Testing
# Test against staging server
yarn replay_instant -game match.replay -server https://staging.example.com:5100
Best Practices
Create diverse replay files
Record matches with different scenarios:
Close games (12-14)
Blowouts (13-3)
Overtime matches
Different game phases (pistol, buy, eco)
Use appropriate modes
Instant : Quick tests, CI/CD pipelines
Delay : Live demonstrations
Timestamps : Realistic simulation
Manual : Debugging specific events
Version replay files
Include client version in replay filename: match-v1.2.0-overtime.replay
match-v1.3.0-normal.replay
Clean up after testing
Remember to stop the replay and disconnect: player . play (() => {
connector . close ();
});
The replay system uses the same authentication flow as live matches. Make sure your server has REQUIRE_AUTH_KEY=false or use a valid access key for replay testing.