{"record":{"id":"611eade8946ca107","repo":"jackwener/OpenCLI","slug":"the-selected-players-were-not-both-present-in-the","errorCode":null,"errorMessage":"The selected players were not both present in the resolved mapstats pages","messagePattern":"The selected players were not both present in the resolved mapstats pages","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"warning","filePath":"clis/hltv/player-duel.js","lineNumber":289,"sourceCode":"}\n\nasync function buildDuelRows(page, args, mapUrls, scope, limit, context = {}) {\n  const playerA = parsePlayerRef(args.playerA);\n  const playerB = parsePlayerRef(args.playerB);\n  const rows = [];\n\n  for (const mapUrl of mapUrls.slice(0, limit)) {\n    const mapRows = await readMatchMap(page, mapUrl);\n    const playerARow = findPlayer(mapRows, playerA.playerId);\n    const playerBRow = findPlayer(mapRows, playerB.playerId);\n    if (!playerARow || !playerBRow) continue;\n    const matrix = playerARow.team === playerBRow.team ? [] : await readPerformanceKillMatrix(page, mapUrl);\n    const direct = parseDirectDuel(matrix, playerARow.playerName, playerBRow.playerName);\n    rows.push(toMapRow(scope, mapUrl, playerARow, playerBRow, direct));\n  }\n\n  if (rows.length === 0) {\n    throw new EmptyResultError('hltv player-duel', 'The selected players were not both present in the resolved mapstats pages');\n  }\n  return [toSummaryRow(scope, rows, context), ...rows];\n}\n\ncli({\n  site: 'hltv',\n  name: 'player-duel',\n  description: 'Compare two HLTV players on shared maps, including direct kill matrix when available',\n  access: 'read',\n  example: 'opencli hltv player-duel 19230/m0nesy 3741/niko --match https://www.hltv.org/stats/matches/mapstatsid/231594/falcons-vs-natus-vincere -f json',\n  domain: 'www.hltv.org',\n  strategy: Strategy.UI,\n  browser: true,\n  navigateBefore: false,\n  args: [\n    { name: 'playerA', type: 'string', positional: true, required: true, help: 'First player ref: 19230/m0nesy, player URL, or stats player URL' },\n    { name: 'playerB', type: 'string', positional: true, required: true, help: 'Second player ref: 3741/niko, player URL, or stats player URL' },\n    { name: 'match', type: 'string', default: '', help: 'Optional HLTV match, stats series, or mapstats URL' },","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/hltv/player-duel.js#L271-L307","documentation":"buildDuelRows navigates the resolved mapstats pages and extracts a row for each selected player per map. It throws this EmptyResultError when zero map rows were produced, i.e. the two players were never both present in any of the resolved mapstats pages. This guards the summary row from being computed over an empty set.","triggerScenarios":"The mapUrls passed in (e.g. from a resolved match/series) contain mapstats pages where one or both player names do not appear; player name parsing fails to match table rows for every map; an empty or wrong mapUrls array is supplied.","commonSituations":"Passing a match URL for a game where a substitute played; lineup changed between series maps (stand-ins); stale mapstats URLs after HLTV restructures; mismatched playerName casing/special characters preventing row matching.","solutions":["Confirm both players actually played in every resolved map (check for stand-ins)","Re-resolve mapUrls so they point to mapstats pages containing both players","Verify playerName strings match their HLTV display names exactly","Catch EmptyResultError and surface 'players not in this match' to the user"],"exampleFix":"// before\nconst mapUrls = [staleMapstatsUrl];\nconst rows = await buildDuelRows(page, args, mapUrls, scope, limit);\n// after\nconst mapUrls = await resolveMapStatsUrls(page, matchUrl); // fresh, includes both players\nconst rows = await buildDuelRows(page, args, mapUrls, scope, limit);","handlingStrategy":"validation","validationCode":"// check both players appear on the resolved mapstats pages before building rows\nconst present = await page.evaluate((names) => names.every((n) => document.body.innerText.includes(n)), [playerA.playerName, playerB.playerName]);\nif (!present) throw new Error('players not both present in mapstats');","typeGuard":"function bothPlayersExpected(mapUrls) { return Array.isArray(mapUrls) && mapUrls.length > 0; }","tryCatchPattern":"try {\n  const rows = await buildDuelRows(page, args, mapUrls, scope, limit);\n} catch (err) {\n  if (err instanceof EmptyResultError) return []; // no shared maps in this fixture\n  throw err;\n}","preventionTips":["Re-resolve mapstats URLs right before building rows instead of caching them","Account for stand-ins/substitutes between maps of a series","Match players by id where possible, not fragile display-name text","Check lineup changes on HLTV before comparing players in old matches"],"tags":["hltv","empty-result","parsing"],"backgroundTag":"empty-result-set","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}