Commit 929263b6 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: a tournament started with no round no longer deadlocks

The manager's Start button flips el3ab_tournaments.status to in_progress
directly. For a tournament this engine owns, nothing else was ever going to pair
round 1 — so the tournament sat registered, started, and permanently unplayable,
with current_round 0 and no round row.

tournamentTick now opens the round when it finds that state, which also repairs
any tournament already stuck in it. Covered by e2e section 21.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent 76109e6f
...@@ -882,7 +882,16 @@ function tournamentTick($sdb, array $tournament, ?int $throttleSeconds = null): ...@@ -882,7 +882,16 @@ function tournamentTick($sdb, array $tournament, ?int $throttleSeconds = null):
if (!tournamentThrottle('tick:' . $tournament['id'], $throttleSeconds)) return; if (!tournamentThrottle('tick:' . $tournament['id'], $throttleSeconds)) return;
$roundNumber = (int)($tournament['current_round'] ?? 0); $roundNumber = (int)($tournament['current_round'] ?? 0);
if ($roundNumber < 1) return;
// A tournament can be left "in progress" with no round at all: the manager's
// Start button flips the status directly, and for a tournament this engine
// owns, nothing else was ever going to pair round 1. That is a deadlock —
// registered, started, and permanently unplayable. Open the round instead.
if ($roundNumber < 1 || !tournamentRound($sdb, $tournament['id'], $roundNumber)) {
$opened = tournamentOpenRound($sdb, $tournament, max(1, $roundNumber));
if (!$opened['ok']) return;
$roundNumber = (int)$opened['round']['round_number'];
}
$round = tournamentRound($sdb, $tournament['id'], $roundNumber); $round = tournamentRound($sdb, $tournament['id'], $roundNumber);
if (!$round || $round['status'] !== ROUND_STATUS_IN_PROGRESS) return; if (!$round || $round['status'] !== ROUND_STATUS_IN_PROGRESS) return;
......
...@@ -503,6 +503,28 @@ sql(`UPDATE matches SET started_at = now() WHERE id='${sm.match_id}'`); ...@@ -503,6 +503,28 @@ sql(`UPDATE matches SET started_at = now() WHERE id='${sm.match_id}'`);
const freshOne = await call('game.php', { action: 'find-active-match', player_id: P[0] }, P[2]); const freshOne = await call('game.php', { action: 'find-active-match', player_id: P[0] }, P[2]);
check('a current match is still offered', freshOne.data.match_id === sm.match_id, String(freshOne.data.match_id)); check('a current match is still offered', freshOne.data.match_id === sm.match_id, String(freshOne.data.match_id));
console.log('\n=== 21. a tournament started with no round self-heals ===');
// This is exactly what the manager's Start button leaves behind: status flipped
// to in_progress, current_round 0, and no round row anywhere.
r = await call('tournament-admin.php', {
action: 'create', name: 'Deadlock Cup', format: 'swiss', time_control: 'blitz_3_0',
rounds: 2, min_players: 4, max_players: 8, auto_start: false,
}, P[0]);
const dlid = r.data.tournament.id;
for (const p of P) await call('tournaments.php', { action: 'register', tournament_id: dlid }, p);
sql(`UPDATE el3ab_tournaments SET status='in_progress', current_round=0 WHERE id='${dlid}'`);
check('starts with no round at all',
sql(`SELECT count(*) FROM el3ab_tournament_rounds WHERE tournament_id='${dlid}'`) === '0');
// Any player interaction should notice and open round 1.
await call('tournament-match.php', { action: 'create-or-join', tournament_id: dlid }, P[0]);
const dlRounds = sql(`SELECT count(*) FROM el3ab_tournament_rounds WHERE tournament_id='${dlid}'`);
check('a player checking in opened round 1', dlRounds === '1', `${dlRounds} rounds`);
const dlT = JSON.parse(sql(`SELECT row_to_json(t) FROM (SELECT status,current_round FROM el3ab_tournaments WHERE id='${dlid}') t`));
check('the tournament is on round 1', dlT.current_round === 1, JSON.stringify(dlT));
const dlPairs = JSON.parse(sql(`SELECT pairings FROM el3ab_tournament_rounds WHERE tournament_id='${dlid}' AND round_number=1`));
check('round 1 has real pairings', dlPairs.length === 2 && !!dlPairs[0].white_id, JSON.stringify(dlPairs.map(p=>p.board)));
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
console.log(''); console.log('');
if (failures.length) { if (failures.length) {
......
import puppeteer from 'puppeteer';
const OUT=process.env.OUT, URL=process.env.URL, TAG=process.env.TAG||'live';
const b=await puppeteer.launch({executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',headless:'new',args:['--no-sandbox']});
for (const [name,w,h] of [['phone',390,844],['desktop',1440,900]]){
const p=await b.newPage();
p.on('pageerror',e=>console.log(` [${name} pageerror]`,String(e).slice(0,120)));
p.on('console',m=>{if(m.type()==='error')console.log(` [${name} console]`,m.text().slice(0,120));});
await p.setViewport({width:w,height:h,deviceScaleFactor:2});
await p.goto(URL,{waitUntil:'networkidle2',timeout:45000});
await new Promise(r=>setTimeout(r,2500));
await p.screenshot({path:`${OUT}/${TAG}-${name}.png`,fullPage:true});
const info=await p.evaluate(()=>({title:document.title,h:document.body.scrollHeight,
text:document.body.innerText.replace(/\s+/g,' ').slice(0,300)}));
console.log(` ${name}: ${info.title} | height ${info.h}px`);
console.log(` ${info.text}`);
await p.close();
}
await b.close();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment