feat(scoring): rescale scores to 0.0-1.0 with 0.05 resolution
Per-question score is now a float in [0.0, 1.0] snapped to a 21-level 0.05 grid, replacing the previous 0-1000 integer scale. Easier to read on a leaderboard, ties become acceptable rather than vanishingly rare, and small clock-skew differences no longer split rankings. DB schema: score is REAL now (SQLite type affinity is loose enough that existing rows still read fine, but new inserts go in as floats). Frontend: added fmtScore() helpers in admin.js and quiz.js to render two decimal places consistently (0.85, 1.20, 5.00) so float-arithmetic sums never display as 0.8500000000000001. Tests: linear_decay/flat/exponential_decay assertions updated; added a snap-to-grid invariant test.
This commit is contained in:
@@ -23,6 +23,14 @@ const store = {
|
||||
|
||||
let countdownTimer = null;
|
||||
|
||||
function fmtScore(value) {
|
||||
// Scores are floats on a 0.05 grid in [0, 1]. Display as a fixed
|
||||
// two-decimal string so users see e.g. "0.85" instead of
|
||||
// "0.8500000000000001" when float math drifts in the leaderboard sum.
|
||||
const n = Number(value || 0);
|
||||
return n.toFixed(2);
|
||||
}
|
||||
|
||||
function escapeText(value) {
|
||||
return String(value ?? "").replace(/[&<>"']/g, (c) => ({
|
||||
"&": "&",
|
||||
@@ -254,7 +262,7 @@ function renderSubmitted(message) {
|
||||
setView(`
|
||||
<div class="card narrow center">
|
||||
<p class="eyebrow">Question ${message.question_idx + 1}</p>
|
||||
<h1 class="big-score">+${message.score}</h1>
|
||||
<h1 class="big-score">+${fmtScore(message.score)}</h1>
|
||||
<p class="muted">submitted in ${seconds}s</p>
|
||||
<p class="muted small">Waiting for the reveal…</p>
|
||||
<div class="spinner" aria-hidden="true"></div>
|
||||
@@ -296,8 +304,8 @@ function renderReveal(message) {
|
||||
</ol>
|
||||
${message.explanation ? `<p class="explanation">${escapeText(message.explanation)}</p>` : ""}
|
||||
<div class="reveal-stats">
|
||||
<div class="stat"><span class="muted">Your score</span><b>+${message.your_score || 0}</b></div>
|
||||
<div class="stat"><span class="muted">Total</span><b>${message.your_total ?? 0}</b></div>
|
||||
<div class="stat"><span class="muted">Your score</span><b>+${fmtScore(message.your_score || 0)}</b></div>
|
||||
<div class="stat"><span class="muted">Total</span><b>${fmtScore(message.your_total)}</b></div>
|
||||
<div class="stat"><span class="muted">Rank</span><b>${message.your_rank ?? "—"}</b></div>
|
||||
</div>
|
||||
<h3>Top 5</h3>
|
||||
@@ -312,7 +320,7 @@ function renderBetween(message) {
|
||||
<p class="eyebrow">Up next</p>
|
||||
<h1>Question ${(message.next_idx ?? 0) + 1}</h1>
|
||||
<div class="reveal-stats">
|
||||
<div class="stat"><span class="muted">Total</span><b>${message.your_total ?? 0}</b></div>
|
||||
<div class="stat"><span class="muted">Total</span><b>${fmtScore(message.your_total)}</b></div>
|
||||
<div class="stat"><span class="muted">Rank</span><b>${message.your_rank ?? "—"}</b></div>
|
||||
</div>
|
||||
${renderBoard(message.top5)}
|
||||
@@ -327,7 +335,7 @@ function renderFinished(message) {
|
||||
<article class="card celebration-card">
|
||||
<div class="celebration-banner">Quiz complete</div>
|
||||
<div class="reveal-stats">
|
||||
<div class="stat big"><span class="muted">Your total</span><b>${message.your_total ?? 0}</b></div>
|
||||
<div class="stat big"><span class="muted">Your total</span><b>${fmtScore(message.your_total)}</b></div>
|
||||
<div class="stat"><span class="muted">Rank</span><b>${message.your_rank ?? "—"}</b></div>
|
||||
<div class="stat"><span class="muted">Correct</span><b>${message.questions_correct ?? 0} / ${message.questions_answered ?? 0}</b></div>
|
||||
</div>
|
||||
@@ -356,7 +364,7 @@ function renderBoard(rows = []) {
|
||||
<li class="${isYou ? "is-you" : ""}">
|
||||
<span class="rank">${r.rank}</span>
|
||||
<span class="who"><b>${escapeText(r.name)}</b></span>
|
||||
<span class="score">${r.score}</span>
|
||||
<span class="score">${fmtScore(r.score)}</span>
|
||||
</li>
|
||||
`;
|
||||
}).join("")}
|
||||
|
||||
Reference in New Issue
Block a user