diff --git a/app/features/scanner/core/detectors/scoreboard-battle-log-replay/header.ts b/app/features/scanner/core/detectors/scoreboard-battle-log-replay/header.ts index c8be0ae78..99d1362a2 100644 --- a/app/features/scanner/core/detectors/scoreboard-battle-log-replay/header.ts +++ b/app/features/scanner/core/detectors/scoreboard-battle-log-replay/header.ts @@ -67,12 +67,14 @@ interface TopBandParse { /** * In the BlitzMain glyphs the digit forms are near-identical to their letter * lookalikes (0/O, the 1/I/l/| bars), so a stage read can surface the digit - * ("R0M-en") and burn an edit the snap threshold cannot spare. Stage names - * are digit-free in every language (sole exception carries a '9'), so digits - * and bars fold to letters before snapping. + * ("R0M-en") or the wrong bar ("WnrId") and burn an edit the snap threshold + * cannot spare. Stage names are digit-free in every language (sole exception + * carries a '9'), so digits and bars fold to letters before snapping — on + * both the reading and the entry, so an entry's own 'I' ("Inkblot") lands in + * the same folded space. */ function foldDigitLookalikes(s: string): string { - return s.replace(/0/g, "o").replace(/[1|]/g, "l"); + return s.replace(/0/g, "o").replace(/[1|I]/g, "l"); } function parseTopBand(reading: string): TopBandParse { @@ -94,7 +96,7 @@ function parseTopBand(reading: string): TopBandParse { const match = closestBy( foldDigitLookalikes(stageReading), ALL_STAGE_ENTRIES, - (e) => e.text, + (e) => foldDigitLookalikes(e.text), ); if (match) { stageScore = match.score; diff --git a/app/features/scanner/tests/fixtures/scoreboard-battle-log/private-battle-splat-zones-wahoo-world/expected.json b/app/features/scanner/tests/fixtures/scoreboard-battle-log/private-battle-splat-zones-wahoo-world/expected.json new file mode 100644 index 000000000..e26f953f2 --- /dev/null +++ b/app/features/scanner/tests/fixtures/scoreboard-battle-log/private-battle-splat-zones-wahoo-world/expected.json @@ -0,0 +1,88 @@ +{ + "event": "ScoreboardBattleLog", + "data": { + "lobby": "PRIVATE", + "mode": "SZ", + "stage": 11, + "stageLabel": "Wahoo World", + "timestamp": "7/8/2026 21:43", + "matchScores": [ + 100, + 0 + ], + "povIndex": 7, + "players": [ + { + "name": "アリ", + "weaponId": 211, + "paint": 351, + "ka": 7, + "d": 1, + "s": 1 + }, + { + "name": "p", + "weaponId": 2070, + "paint": 630, + "ka": 6, + "d": 0, + "s": 2 + }, + { + "name": "hannah", + "weaponId": 50, + "paint": 361, + "ka": 4, + "d": 1, + "s": 1 + }, + { + "name": "goji", + "weaponId": 6011, + "paint": 298, + "ka": 2, + "d": 0, + "s": 1 + }, + { + "name": "Comrade", + "weaponId": 2070, + "paint": 575, + "ka": 1, + "d": 0, + "s": 2 + }, + { + "name": "☆☆1_¡[⊃し☆☆", + "weaponId": 4001, + "paint": 528, + "ka": 1, + "d": 3, + "s": 2 + }, + { + "name": "☆★」V「▲GN№★☆", + "weaponId": 211, + "paint": 239, + "ka": 2, + "d": 3, + "s": 1 + }, + { + "name": "Sendou", + "weaponId": 6011, + "paint": 265, + "ka": 0, + "d": 4, + "s": 1 + } + ] + }, + "options": { + "skipFields": [ + "players.1.name", + "players.3.name" + ], + "notes": "1080p stream capture with horizontal banding. Live misread: the header stage read came back 'Wahnn WnrId l' (banding cuts the o bowls) and failed to snap to Wahoo World before bar-lookalike folding covered 'I'. players.1.name (p) reads P and players.3.name (goji) reads gパi under the same banding." + } +} diff --git a/app/features/scanner/tests/fixtures/scoreboard-battle-log/private-battle-splat-zones-wahoo-world/frame.png b/app/features/scanner/tests/fixtures/scoreboard-battle-log/private-battle-splat-zones-wahoo-world/frame.png new file mode 100644 index 000000000..0d0a7dd35 Binary files /dev/null and b/app/features/scanner/tests/fixtures/scoreboard-battle-log/private-battle-splat-zones-wahoo-world/frame.png differ diff --git a/wahoo.png b/wahoo.png new file mode 100644 index 000000000..0d0a7dd35 Binary files /dev/null and b/wahoo.png differ