test(web): add real-replay test page and update data indexes
This commit is contained in:
parent
09fced7dfe
commit
e86c132d29
5 changed files with 949275 additions and 30 deletions
|
|
@ -1 +1 @@
|
|||
cd30484e8ceae7341a5f319798fba089b8173021
|
||||
09fced7dfee0774e3d66283631762f9b6c0de96f
|
||||
|
|
|
|||
|
|
@ -1,10 +1,225 @@
|
|||
{
|
||||
"$comment": "Placeholder file - replaced by index builder",
|
||||
"updated_at": null,
|
||||
"matches": [],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"per_page": 50,
|
||||
"total": 0
|
||||
}
|
||||
"updated_at": "2026-04-25T10:00:00Z",
|
||||
"matches": [
|
||||
{
|
||||
"id": "m_test_6p_v1",
|
||||
"completed_at": "2026-04-25T09:45:00Z",
|
||||
"participants": [
|
||||
{
|
||||
"bot_id": "b_swarm_001",
|
||||
"name": "SwarmBot",
|
||||
"score": 7,
|
||||
"won": true
|
||||
},
|
||||
{
|
||||
"bot_id": "b_hunter_001",
|
||||
"name": "HunterBot",
|
||||
"score": 3,
|
||||
"won": false
|
||||
},
|
||||
{
|
||||
"bot_id": "b_gatherer_001",
|
||||
"name": "GathererBot",
|
||||
"score": 2,
|
||||
"won": false
|
||||
},
|
||||
{
|
||||
"bot_id": "b_rusher_001",
|
||||
"name": "RusherBot",
|
||||
"score": 1,
|
||||
"won": false
|
||||
},
|
||||
{
|
||||
"bot_id": "b_guardian_001",
|
||||
"name": "GuardianBot",
|
||||
"score": 4,
|
||||
"won": false
|
||||
},
|
||||
{
|
||||
"bot_id": "b_random_001",
|
||||
"name": "RandomBot",
|
||||
"score": 0,
|
||||
"won": false
|
||||
}
|
||||
],
|
||||
"winner_id": "b_swarm_001",
|
||||
"map_id": "map_six_corners_v1",
|
||||
"turns": 487,
|
||||
"end_reason": "turn_limit",
|
||||
"enriched": true
|
||||
},
|
||||
{
|
||||
"id": "m_test_close_v1",
|
||||
"completed_at": "2026-04-25T09:30:00Z",
|
||||
"participants": [
|
||||
{
|
||||
"bot_id": "b_hunter_001",
|
||||
"name": "HunterBot",
|
||||
"score": 5,
|
||||
"won": true
|
||||
},
|
||||
{
|
||||
"bot_id": "b_gatherer_001",
|
||||
"name": "GathererBot",
|
||||
"score": 4,
|
||||
"won": false
|
||||
}
|
||||
],
|
||||
"winner_id": "b_hunter_001",
|
||||
"map_id": "map_open_field_v2",
|
||||
"turns": 500,
|
||||
"end_reason": "turn_limit",
|
||||
"enriched": false
|
||||
},
|
||||
{
|
||||
"id": "m_test_upset_v1",
|
||||
"completed_at": "2026-04-25T09:15:00Z",
|
||||
"participants": [
|
||||
{
|
||||
"bot_id": "b_random_001",
|
||||
"name": "RandomBot",
|
||||
"score": 3,
|
||||
"won": true
|
||||
},
|
||||
{
|
||||
"bot_id": "b_guardian_001",
|
||||
"name": "GuardianBot",
|
||||
"score": 2,
|
||||
"won": false
|
||||
}
|
||||
],
|
||||
"winner_id": "b_random_001",
|
||||
"map_id": "map_the_labyrinth",
|
||||
"turns": 234,
|
||||
"end_reason": "sole_survivor",
|
||||
"enriched": false
|
||||
},
|
||||
{
|
||||
"id": "m_test_domination_v1",
|
||||
"completed_at": "2026-04-25T09:00:00Z",
|
||||
"participants": [
|
||||
{
|
||||
"bot_id": "b_swarm_001",
|
||||
"name": "SwarmBot",
|
||||
"score": 7,
|
||||
"won": true
|
||||
},
|
||||
{
|
||||
"bot_id": "b_rusher_001",
|
||||
"name": "RusherBot",
|
||||
"score": 0,
|
||||
"won": false
|
||||
}
|
||||
],
|
||||
"winner_id": "b_swarm_001",
|
||||
"map_id": "map_narrow_corridors",
|
||||
"turns": 156,
|
||||
"end_reason": "annihilation",
|
||||
"enriched": true
|
||||
},
|
||||
{
|
||||
"id": "m_test_4p_v1",
|
||||
"completed_at": "2026-04-25T08:45:00Z",
|
||||
"participants": [
|
||||
{
|
||||
"bot_id": "b_hunter_001",
|
||||
"name": "HunterBot",
|
||||
"score": 6,
|
||||
"won": true
|
||||
},
|
||||
{
|
||||
"bot_id": "b_swarm_001",
|
||||
"name": "SwarmBot",
|
||||
"score": 4,
|
||||
"won": false
|
||||
},
|
||||
{
|
||||
"bot_id": "b_guardian_001",
|
||||
"name": "GuardianBot",
|
||||
"score": 3,
|
||||
"won": false
|
||||
},
|
||||
{
|
||||
"bot_id": "b_gatherer_001",
|
||||
"name": "GathererBot",
|
||||
"score": 2,
|
||||
"won": false
|
||||
}
|
||||
],
|
||||
"winner_id": "b_hunter_001",
|
||||
"map_id": "map_cross_v1",
|
||||
"turns": 412,
|
||||
"end_reason": "turn_limit",
|
||||
"enriched": false
|
||||
},
|
||||
{
|
||||
"id": "m_test_comeback_v1",
|
||||
"completed_at": "2026-04-25T08:30:00Z",
|
||||
"participants": [
|
||||
{
|
||||
"bot_id": "b_gatherer_001",
|
||||
"name": "GathererBot",
|
||||
"score": 4,
|
||||
"won": true
|
||||
},
|
||||
{
|
||||
"bot_id": "b_rusher_001",
|
||||
"name": "RusherBot",
|
||||
"score": 3,
|
||||
"won": false
|
||||
}
|
||||
],
|
||||
"winner_id": "b_gatherer_001",
|
||||
"map_id": "map_energy_rush",
|
||||
"turns": 398,
|
||||
"end_reason": "turn_limit",
|
||||
"enriched": false
|
||||
},
|
||||
{
|
||||
"id": "m_test_marathon_v1",
|
||||
"completed_at": "2026-04-25T08:15:00Z",
|
||||
"participants": [
|
||||
{
|
||||
"bot_id": "b_guardian_001",
|
||||
"name": "GuardianBot",
|
||||
"score": 3,
|
||||
"won": true
|
||||
},
|
||||
{
|
||||
"bot_id": "b_random_001",
|
||||
"name": "RandomBot",
|
||||
"score": 2,
|
||||
"won": false
|
||||
}
|
||||
],
|
||||
"winner_id": "b_guardian_001",
|
||||
"map_id": "map_wall_maze",
|
||||
"turns": 500,
|
||||
"end_reason": "turn_limit",
|
||||
"enriched": false
|
||||
},
|
||||
{
|
||||
"id": "m_test_quick_v1",
|
||||
"completed_at": "2026-04-25T08:00:00Z",
|
||||
"participants": [
|
||||
{
|
||||
"bot_id": "b_rusher_001",
|
||||
"name": "RusherBot",
|
||||
"score": 3,
|
||||
"won": true
|
||||
},
|
||||
{
|
||||
"bot_id": "b_random_001",
|
||||
"name": "RandomBot",
|
||||
"score": 0,
|
||||
"won": false
|
||||
}
|
||||
],
|
||||
"winner_id": "b_rusher_001",
|
||||
"map_id": "map_small_open",
|
||||
"turns": 89,
|
||||
"end_reason": "annihilation",
|
||||
"enriched": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,40 +6,40 @@
|
|||
"title": "Closest Finishes",
|
||||
"description": "Matches decided by the thinnest margins — nail-biters to the very end",
|
||||
"category": "close_games",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 2,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "biggest-upsets",
|
||||
"title": "Biggest Upsets",
|
||||
"description": "Lower-rated bots triumph against higher-rated opponents",
|
||||
"category": "upsets",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 1,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "best-comebacks",
|
||||
"title": "Best Comebacks",
|
||||
"description": "Bots that were down but never out — dramatic turnarounds and improbable victories",
|
||||
"category": "comebacks",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 1,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "marathon-matches",
|
||||
"title": "Marathon Matches",
|
||||
"description": "The longest, most grueling matches — endurance-tested battles",
|
||||
"category": "long_games",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 2,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "highest-rated",
|
||||
"title": "Clash of Titans",
|
||||
"description": "Matches between the highest-rated opponents on the ladder",
|
||||
"category": "featured",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 1,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "evolution-breakthroughs",
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
"description": "Evolved bots defeating top-rated opponents — AI strategy milestones",
|
||||
"category": "featured",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "rivalry-classics",
|
||||
|
|
@ -55,15 +55,15 @@
|
|||
"description": "The most closely contested matchups between frequent opponents",
|
||||
"category": "rivalry",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "domination",
|
||||
"title": "Total Domination",
|
||||
"description": "One-sided victories where the winner crushed all opposition",
|
||||
"category": "domination",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 1,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "new-bot-debuts",
|
||||
|
|
@ -71,31 +71,31 @@
|
|||
"description": "First matches of newly registered bots — watch their opening games",
|
||||
"category": "tutorial",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "season-highlights",
|
||||
"title": "Season Highlights",
|
||||
"description": "Top matches from the current season ranked by excitement",
|
||||
"category": "season",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 3,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "featured",
|
||||
"title": "Featured Matches",
|
||||
"description": "Recent highlights from the ladder",
|
||||
"category": "featured",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 8,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"slug": "best-of-week",
|
||||
"title": "Best of the Week",
|
||||
"description": "This week's top matches ranked by excitement: close finishes, upsets, marathon battles, and elite clashes",
|
||||
"category": "weekly",
|
||||
"match_count": 0,
|
||||
"updated_at": "2026-04-21T00:00:00.000Z"
|
||||
"match_count": 8,
|
||||
"updated_at": "2026-04-25T10:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
948769
web/public/data/real-replay.json
Normal file
948769
web/public/data/real-replay.json
Normal file
File diff suppressed because it is too large
Load diff
261
web/public/test-real-replay.html
Normal file
261
web/public/test-real-replay.html
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Replay Viewer Test - Real Replay</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 20px; font-family: sans-serif; background: #0f172a; color: #e2e8f0; }
|
||||
h1 { font-size: 1.2rem; margin-bottom: 10px; }
|
||||
.test-container { display: flex; gap: 20px; flex-wrap: wrap; }
|
||||
.viewer-section { flex: 1; min-width: 500px; }
|
||||
.results-section { width: 350px; background: #1e293b; padding: 15px; border-radius: 8px; max-height: 80vh; overflow-y: auto; }
|
||||
.canvas-wrapper { background: #1e293b; border-radius: 8px; padding: 10px; }
|
||||
canvas { display: block; }
|
||||
.test-result { padding: 8px; margin: 5px 0; border-radius: 4px; font-size: 12px; }
|
||||
.pass { background: #22c55e; color: white; }
|
||||
.fail { background: #ef4444; color: white; }
|
||||
.info { background: #3b82f6; color: white; }
|
||||
.controls { margin-top: 15px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
||||
button { padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
||||
button:hover { background: #2563eb; }
|
||||
button:disabled { background: #475569; cursor: not-allowed; }
|
||||
#turn-info { font-family: monospace; }
|
||||
.sparkline-container { margin-top: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Replay Viewer Test - Real Replay (m_tprjf4ij)</h1>
|
||||
<div class="test-container">
|
||||
<div class="viewer-section">
|
||||
<div class="canvas-wrapper">
|
||||
<canvas id="replay-canvas"></canvas>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button id="play-btn">Play</button>
|
||||
<button id="pause-btn">Pause</button>
|
||||
<button id="next-btn">+1 Turn</button>
|
||||
<button id="prev-btn">-1 Turn</button>
|
||||
<button id="reset-btn">Reset</button>
|
||||
<select id="speed-select">
|
||||
<option value="50">Fast (50ms)</option>
|
||||
<option value="100" selected>Normal (100ms)</option>
|
||||
<option value="200">Slow (200ms)</option>
|
||||
<option value="500">Very Slow (500ms)</option>
|
||||
</select>
|
||||
<span id="turn-info">Turn: 0</span>
|
||||
</div>
|
||||
<div class="sparkline-container" id="sparkline"></div>
|
||||
<div style="margin-top: 15px;">
|
||||
<h3>Transcript (Current Turn)</h3>
|
||||
<div id="transcript" style="background: #1e293b; padding: 10px; border-radius: 8px; min-height: 60px; font-size: 13px; max-height: 200px; overflow-y: auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="results-section">
|
||||
<h2>Test Results</h2>
|
||||
<div id="test-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import { ReplayViewer } from './src/replay-viewer.ts';
|
||||
|
||||
const results = document.getElementById('test-results');
|
||||
const canvas = document.getElementById('replay-canvas');
|
||||
const turnInfo = document.getElementById('turn-info');
|
||||
const transcript = document.getElementById('transcript');
|
||||
const sparklineContainer = document.getElementById('sparkline');
|
||||
const playBtn = document.getElementById('play-btn');
|
||||
const pauseBtn = document.getElementById('pause-btn');
|
||||
const nextBtn = document.getElementById('next-btn');
|
||||
const prevBtn = document.getElementById('prev-btn');
|
||||
const resetBtn = document.getElementById('reset-btn');
|
||||
const speedSelect = document.getElementById('speed-select');
|
||||
|
||||
let viewer;
|
||||
let testsPassed = 0;
|
||||
let testsFailed = 0;
|
||||
|
||||
function addResult(name, passed, message) {
|
||||
const div = document.createElement('div');
|
||||
div.className = `test-result ${passed ? 'pass' : 'fail'}`;
|
||||
div.textContent = `${passed ? '✓' : '✗'} ${name}: ${message}`;
|
||||
results.appendChild(div);
|
||||
if (passed) testsPassed++;
|
||||
else testsFailed++;
|
||||
results.scrollTop = results.scrollHeight;
|
||||
}
|
||||
|
||||
function addInfo(message) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'test-result info';
|
||||
div.textContent = message;
|
||||
results.appendChild(div);
|
||||
results.scrollTop = results.scrollHeight;
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
addInfo('Loading real replay from /data/real-replay.json...');
|
||||
try {
|
||||
const response = await fetch('/data/real-replay.json');
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const replay = await response.json();
|
||||
addResult('Fetch real replay', true, `Loaded ${replay.match_id}`);
|
||||
|
||||
// Test replay structure
|
||||
addResult('Replay has match_id', !!replay.match_id, replay.match_id);
|
||||
addResult('Replay has players', Array.isArray(replay.players) && replay.players.length > 0, `${replay.players.length} players`);
|
||||
addResult('Replay has turns', Array.isArray(replay.turns) && replay.turns.length > 0, `${replay.turns.length} turns`);
|
||||
addResult('Replay has map', !!replay.map, `${replay.map.rows}x${replay.map.cols}`);
|
||||
addResult('Replay has result', !!replay.result, `Winner: player ${replay.result.winner}, reason: ${replay.result.reason}`);
|
||||
|
||||
// Test win probability data
|
||||
if (replay.win_prob && replay.win_prob.length > 0) {
|
||||
addResult('Replay has win_prob data', true, `${replay.win_prob.length} entries`);
|
||||
} else {
|
||||
addResult('Replay has win_prob data', false, 'No win_prob data (sparkline will be empty)');
|
||||
}
|
||||
|
||||
// Create viewer
|
||||
addInfo('Creating ReplayViewer...');
|
||||
viewer = new ReplayViewer(canvas, { cellSize: 8 });
|
||||
addResult('Create ReplayViewer', true, 'Viewer instance created');
|
||||
|
||||
// Set up callbacks before loading replay
|
||||
viewer.onTurnChange = (turn) => {
|
||||
turnInfo.textContent = `Turn: ${turn}/${viewer.getTotalTurns() - 1}`;
|
||||
// Update transcript
|
||||
const transcriptText = viewer.getTranscriptForTurn(turn);
|
||||
transcript.textContent = transcriptText || 'No events this turn';
|
||||
// Refresh sparkline
|
||||
viewer.refreshWinProbSparkline();
|
||||
};
|
||||
|
||||
viewer.onPlayStateChange = (playing) => {
|
||||
playBtn.disabled = playing;
|
||||
pauseBtn.disabled = !playing;
|
||||
};
|
||||
|
||||
// Load replay
|
||||
addInfo('Loading replay into viewer...');
|
||||
viewer.loadReplay(replay);
|
||||
addResult('Load replay into viewer', true, `Loaded ${replay.turns.length} turns`);
|
||||
|
||||
// Test viewer state
|
||||
addResult('Get total turns', viewer.getTotalTurns() === replay.turns.length, `${viewer.getTotalTurns()} turns`);
|
||||
addResult('Get current turn', viewer.getTurn() === 0, `Turn ${viewer.getTurn()}`);
|
||||
|
||||
// Test turn navigation
|
||||
addInfo('Testing turn navigation...');
|
||||
viewer.setTurn(10);
|
||||
addResult('Set turn to 10', viewer.getTurn() === 10, `Turn ${viewer.getTurn()}`);
|
||||
viewer.setTurn(0);
|
||||
addResult('Reset to turn 0', viewer.getTurn() === 0, `Turn ${viewer.getTurn()}`);
|
||||
|
||||
// Test playback
|
||||
addInfo('Testing playback controls...');
|
||||
viewer.play();
|
||||
addResult('Start playback', viewer.getIsPlaying(), 'Playing');
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
viewer.pause();
|
||||
addResult('Pause playback', !viewer.getIsPlaying(), 'Paused');
|
||||
|
||||
// Test speed control
|
||||
viewer.setSpeed(50);
|
||||
addResult('Set speed to 50ms', viewer.getSpeed() === 50, `Speed: ${viewer.getSpeed()}ms`);
|
||||
viewer.setSpeed(200);
|
||||
addResult('Set speed to 200ms', viewer.getSpeed() === 200, `Speed: ${viewer.getSpeed()}ms`);
|
||||
|
||||
// Test win probability sparkline
|
||||
addInfo('Setting up win probability sparkline...');
|
||||
if (replay.win_prob && replay.win_prob.length > 0) {
|
||||
// Convert win_prob format to WinProbPoint[]
|
||||
const points = replay.win_prob.map((wp, idx) => ({
|
||||
turn: idx,
|
||||
probs: wp.probs || [0.5, 0.5]
|
||||
}));
|
||||
viewer.setWinProbabilityData(points);
|
||||
|
||||
const playerColors = replay.players.map((_, i) => {
|
||||
const colors = ['#3b82f6', '#ef4444', '#22c55e', '#f59e0b', '#8b5cf6', '#06b6d4'];
|
||||
return colors[i % colors.length];
|
||||
});
|
||||
viewer.setWinProbPlayerColors(playerColors);
|
||||
|
||||
viewer.createWinProbSparkline(sparklineContainer, 600, 80, (turn) => {
|
||||
viewer.setTurn(turn);
|
||||
});
|
||||
addResult('Create win prob sparkline', true, 'Sparkline created');
|
||||
} else {
|
||||
addResult('Create win prob sparkline', false, 'No win_prob data available');
|
||||
sparklineContainer.innerHTML = '<div style="color: #64748b; font-style: italic;">No win probability data available</div>';
|
||||
}
|
||||
|
||||
// Test transcript generation
|
||||
addInfo('Testing transcript generation...');
|
||||
const fullTranscript = viewer.generateTranscript();
|
||||
addResult('Generate full transcript', Array.isArray(fullTranscript) && fullTranscript.length > 0, `${fullTranscript.length} entries`);
|
||||
|
||||
// Show first turn transcript
|
||||
if (fullTranscript.length > 0) {
|
||||
transcript.textContent = fullTranscript[0].text;
|
||||
addResult('First turn transcript', !!fullTranscript[0].text, 'Has transcript text');
|
||||
}
|
||||
|
||||
// Test events
|
||||
const events = viewer.getTurnEvents();
|
||||
addResult('Get turn 0 events', Array.isArray(events), `${events.length} events at turn 0`);
|
||||
|
||||
// Test critical moments if available
|
||||
if (replay.critical_moments && replay.critical_moments.length > 0) {
|
||||
addResult('Has critical moments', true, `${replay.critical_moments.length} moments`);
|
||||
viewer.setCriticalMoments(replay.critical_moments.map(m => ({
|
||||
turn: m.turn,
|
||||
delta: m.delta,
|
||||
description: m.description
|
||||
})));
|
||||
} else {
|
||||
addResult('Has critical moments', false, 'No critical moments data');
|
||||
}
|
||||
|
||||
addInfo(`Tests completed: ${testsPassed} passed, ${testsFailed} failed`);
|
||||
|
||||
// Enable controls
|
||||
playBtn.disabled = false;
|
||||
pauseBtn.disabled = true;
|
||||
nextBtn.disabled = false;
|
||||
prevBtn.disabled = false;
|
||||
resetBtn.disabled = false;
|
||||
|
||||
} catch (error) {
|
||||
addResult('Test execution', false, error.message);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Wire up controls
|
||||
playBtn.addEventListener('click', () => viewer.play());
|
||||
pauseBtn.addEventListener('click', () => viewer.pause());
|
||||
nextBtn.addEventListener('click', () => {
|
||||
if (viewer.getTurn() < viewer.getTotalTurns() - 1) {
|
||||
viewer.setTurn(viewer.getTurn() + 1);
|
||||
}
|
||||
});
|
||||
prevBtn.addEventListener('click', () => {
|
||||
if (viewer.getTurn() > 0) {
|
||||
viewer.setTurn(viewer.getTurn() - 1);
|
||||
}
|
||||
});
|
||||
resetBtn.addEventListener('click', () => {
|
||||
viewer.pause();
|
||||
viewer.setTurn(0);
|
||||
});
|
||||
speedSelect.addEventListener('change', (e) => {
|
||||
viewer.setSpeed(parseInt(e.target.value));
|
||||
});
|
||||
|
||||
// Run tests
|
||||
runTests();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue