feat(overlay): show places gained since the start in the OBS tower
Heats and finals started together show ▲/▼ against the start order (new OBS option, on by default). In practice the best lap is no longer printed twice as both result and best. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,4 +62,29 @@ export async function runOverlaySmoke({ evaluate }) {
|
||||
}
|
||||
}
|
||||
console.log("PASS overlay heading alignment at 390/900/1600px, sorting preserves race position, OBS has no sort controls");
|
||||
|
||||
// OBS tower: in a final, places gained/lost since the start; in practice the best lap is
|
||||
// shown once (result and best were the same time twice).
|
||||
const obs = await evaluate(`(async () => {
|
||||
const { renderObsOverlay } = await import('/src/overlays.js');
|
||||
const { escapeHtml } = await import('/src/html_escape.js');
|
||||
const config = { layout: 'leaderboard', rows: 10, showClock: true, showLaps: true, showResult: true, showBest: true, showGap: true, showPositionChange: true };
|
||||
const deps = { t: (key) => key, escapeHtml, formatLap: (ms) => '0:' + (ms / 1000).toFixed(3), formatTeamActiveMemberLabel: () => '', getVisiblePassings: () => [], getEventName: () => 'Club', getSessionTypeLabel: () => 'Final', getStartModeLabel: () => 'Position', getStatusLabel: () => 'Running', getObsOverlayConfig: () => config, normalizeStartMode: (v) => v, renderPositionGrid: () => '', getSessionGridEntries: () => [], getSessionGridOrder: () => ['a', 'b', 'c', 'd', 'e'] };
|
||||
const rows = [
|
||||
{ key: 'e', driverId: 'e', displayName: 'Eve', laps: 14, resultDisplay: '14/5:02.312', bestLapMs: 20100, gapDisplay: '-' },
|
||||
{ key: 'a', driverId: 'a', displayName: 'Ann', laps: 14, resultDisplay: '14/5:05.000', bestLapMs: 20300, gapDisplay: '+2.688' },
|
||||
{ key: 'c', driverId: 'c', displayName: 'Cid', laps: 13, resultDisplay: '13/5:01.000', bestLapMs: 21000, gapDisplay: '+1L' },
|
||||
];
|
||||
const box = document.createElement('div');
|
||||
box.innerHTML = renderObsOverlay({ type: 'final', startMode: 'position', status: 'running' }, rows, {}, { remainingMs: 1000 }, {}, deps);
|
||||
const finalRows = [...box.querySelectorAll('.overlay-obs-tower-row')].map((row) => [...row.querySelector('.overlay-obs-tower-trail').children].map((span) => span.textContent.trim()).join(' '));
|
||||
box.innerHTML = renderObsOverlay({ type: 'open_practice', startMode: 'mass', status: 'running' }, [{ key: 'p', driverId: 'p', displayName: 'Pia', laps: 23, resultDisplay: '0:15.833', bestLapMs: 15833, gapDisplay: '-' }], {}, { untimed: true, elapsedMs: 1000 }, {}, deps);
|
||||
const practiceRow = [...box.querySelector('.overlay-obs-tower-trail').children].map((span) => span.textContent.trim()).join(' ');
|
||||
return { finalRows, practiceRow };
|
||||
})()`);
|
||||
assert.equal(obs.finalRows[0], "▲4 14L 14/5:02.312 0:20.100 -", "started 5th, leads: ▲4, laps and result");
|
||||
assert.equal(obs.finalRows[1], "▼1 14L 14/5:05.000 0:20.300 +2.688", "started 1st, now 2nd: ▼1");
|
||||
assert.equal(obs.finalRows[2], "13L 13/5:01.000 0:21.000 +1L", "same place as at the start: no arrow");
|
||||
assert.equal(obs.practiceRow, "23L 0:15.833 -", "practice: the best lap once, not twice");
|
||||
console.log("PASS OBS overlay shows places gained/lost since the start in finals, and the best lap once in practice");
|
||||
}
|
||||
|
||||
18
tests/overlay_position_change.test.js
Normal file
18
tests/overlay_position_change.test.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { getPositionChange } from "../src/overlays.js";
|
||||
|
||||
test("places gained or lost since the start - only for heats and finals started together", () => {
|
||||
const order = ["a", "b", "c", "d", "e"];
|
||||
const final = { type: "final", startMode: "position", status: "running" };
|
||||
assert.equal(getPositionChange(final, order, "e", 2), 3, "started 5th, now 2nd: +3");
|
||||
assert.equal(getPositionChange(final, order, "a", 3), -2, "started 1st, now 3rd: -2");
|
||||
assert.equal(getPositionChange(final, order, "c", 3), 0);
|
||||
assert.equal(getPositionChange(final, order, "late_entry", 1), null, "not in the start order");
|
||||
assert.equal(getPositionChange({ ...final, startMode: "mass", type: "heat" }, order, "e", 1), 4);
|
||||
assert.equal(getPositionChange({ ...final, status: "ready" }, order, "e", 2), null, "not before the start");
|
||||
assert.equal(getPositionChange({ ...final, type: "open_practice" }, order, "e", 2), null, "practice ranks on best lap");
|
||||
assert.equal(getPositionChange({ ...final, type: "qualification" }, order, "e", 2), null, "qualifying has individual clocks");
|
||||
assert.equal(getPositionChange({ ...final, startMode: "flying" }, order, "e", 2), null, "no common start order");
|
||||
assert.equal(getPositionChange(null, order, "e", 2), null);
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import "./auth.test.js";
|
||||
import "./prologue_logic.test.js";
|
||||
import "./suspect_laps_logic.test.js";
|
||||
import "./series_logic.test.js";
|
||||
import "./overlay_position_change.test.js";
|
||||
import "./tts.test.js";
|
||||
import "./piper_speech.test.js";
|
||||
import "./driver_profile_logic.test.js";
|
||||
|
||||
Reference in New Issue
Block a user