fix(#614): game total score exceeding max (level1 + levelTemp double-count)#615
Conversation
state.score could exceed the 1100 max (observed share text "1188/1100") and didn't match the per-level breakdown. Two levels called addScore() per round (summing into state.score) but recorded only the average in levelScores: - level1 (Token splitter): drop the per-round addScore(rs); call addScore(avg) once at finish, after levelScores is set — so the level contributes its recorded 0-100 exactly once (matching every other level / level6's pattern). - levelTemp (Temperature dial): same per-round fix, plus fix a double-division (pts = rs/3 then avg = totalScore/SCEN.length capped the level at ~33 even on a perfect play). Now accumulate totalScore += rs so avg is the true average and a perfect run scores 100. Result: every level adds exactly its 0-100 to state.score once; total maxes at 1100 and matches the breakdown. node --check clean; LEVELS still 11. Closes #614 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #615
Commit: 3bf04a434a1a4d08441fa6b5f30872278cf96682
Summary
Fixes a scoring bug in site/game.html where the game total could exceed the 1100 max (share text showed "1188/1100") and didn't match the per-level breakdown. Two levels called addScore() per round (summing rounds into state.score) while recording only the average in state.levelScores. The fix drops the per-round adds and calls addScore(avg) once at each level's finish — the pattern the other 9 levels already use. levelTemp also had a double-division (pts = rs/3 then avg = totalScore/SCEN.length) that capped the level at ~33 on a perfect play; now totalScore += rs so the average is true. Diff: +3/-5, single file.
Verification
node --checkon the inline script: clean. One inline<script>block; parses without error.LEVELSstill 11 — render entries: level1, level2, levelTemp, level3, levelCutoff, level4, level5, level6, level7, levelCost, levelLoop.- The invariant holds across all 11 levels. Every
state.levelScores[state.level] = Xwrite is immediately followed byaddScore(X)with the identical variable — verified by pairing all 11 sites:- level1@1273
avg→ addScore@1274avg - level2@1360
sc→ @1361sc - levelTemp@2245
avg→ @2246avg - level3@1522
sc→ @1523sc - levelCutoff@1704
sc→ @1705sc - level4@1803
sc→ @1804sc - level5@2090
sc→ @2091sc - level6@1930
totalScore→ @1931totalScore(legitimately once = its levelScores; fine) - level7@2343
sc→ @2344sc - levelCost@2475
sc→ @2476sc - levelLoop@2600
sc→ @2601sc - No level adds per-round or adds a value different from what it records.
addScoredoes a barestate.score += n(no clamp), sostate.score === Σ levelScores, max = 11 × 100 = 1100. Defect fixed.
- level1@1273
levelTemp:ptsfully removed (the only remainingptsis the literal UI string'SKIP? (0 pts)', not the variable — no dangling reference).totalScore += rscorrect;rsdeclared in the lock handler and in scope where used. Perfect in-band play → every roundrs=100→avg = Math.round(totalScore/SCEN.length) = 100(no longer 33).addScore(avg)is in the finish branch.level1:rsstill computed (Math.round((correct/real.size)*100 - wrong*15 - missed*12), clamped 0–100) andtotalScore += rsper round retained — the removed line was onlyaddScore(rs).rsstill used for the per-round comparison display.avg = Math.round(totalScore / ROUNDS.length)unchanged.addScore(avg)is in the finishelsebranch, not per round.- No other behavior changed; diff matches the stated +3/-5.
Checklist Results
- ✅ Architecture & Design: Pass (static single-file change, no layering concern)
- ✅ Code Quality: Pass (minimal, surgical, removes the double-division dead path)
- ✅ Testing: Pass (
node --checkis the project's gate for this static site; clean. No test suite by design) - ✅ Security: Pass (no auth/secrets/user-data; pure client-side scoring)
- ✅ Performance: Pass (no hot-path or query changes)
- ✅ PR Description & Glossary: Pass (Summary + Testing + Glossary all present; narrative bullets)
- ✅ Summary Bullet Narrative: Pass (each bullet states what + why)
- ✅ Technical Decisions (AgDR): N/A (bug fix, no library/architecture/pattern decision)
- ✅ Adopter Handbooks: N/A (no architecture/general/language violations triggered)
Issues Found
None.
Handbook Findings
- Migration Safety (blocking): not triggered — no migration files in the diff.
- Clean Architecture Layers (advisory): N/A — single static HTML file, no cross-layer imports.
- Commit Message Quality (advisory): Pass — commit
3bf04a4has a descriptive WHY-bearing subject and a body explaining problem, root cause, the two fixes with rationale, result, and verification. SingleCloses #614, co-authorship line present.
Suggestions
None blocking. (Optional, for a future PR: the per-level scoring pattern is now uniform — a one-line console.assert(state.score === state.levelScores.reduce(...)) dev-only guard could lock the invariant in if the game grows past 11 levels. Not needed for this fix.)
Verdict
APPROVED
PR body has Summary + Testing + Glossary; Closes #614 (issue OPEN); commit SHA matches HEAD (3bf04a4...). Base is dev, single file, +3/-5.
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 3bf04a434a1a4d08441fa6b5f30872278cf96682
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #615
Commit: 3bf04a434a1a4d08441fa6b5f30872278cf96682
Summary
Fixes a scoring bug in site/game.html where the game total could exceed the 1100 max (share text showed "1188/1100") and didn't match the per-level breakdown. Two levels called addScore() per round (summing rounds into state.score) while recording only the average in state.levelScores. The fix drops the per-round adds and calls addScore(avg) once at each level's finish — the pattern the other 9 levels already use. levelTemp also had a double-division (pts = rs/3 then avg = totalScore/SCEN.length) that capped the level at ~33 on a perfect play; now totalScore += rs so the average is true. Diff: +3/-5, single file.
Verification
node --checkon the inline script: clean. One inline<script>block; parses without error.LEVELSstill 11 — render entries: level1, level2, levelTemp, level3, levelCutoff, level4, level5, level6, level7, levelCost, levelLoop.- The invariant holds across all 11 levels. Every
state.levelScores[state.level] = Xwrite is immediately followed byaddScore(X)with the identical variable — verified by pairing all 11 sites:- level1@1273
avg→ addScore@1274avg - level2@1360
sc→ @1361sc - levelTemp@2245
avg→ @2246avg - level3@1522
sc→ @1523sc - levelCutoff@1704
sc→ @1705sc - level4@1803
sc→ @1804sc - level5@2090
sc→ @2091sc - level6@1930
totalScore→ @1931totalScore(legitimately once = its levelScores; fine) - level7@2343
sc→ @2344sc - levelCost@2475
sc→ @2476sc - levelLoop@2600
sc→ @2601sc - No level adds per-round or adds a value different from what it records.
addScoredoes a barestate.score += n(no clamp), sostate.score === Σ levelScores, max = 11 × 100 = 1100. Defect fixed.
- level1@1273
levelTemp:ptsfully removed (the only remainingptsis the literal UI string'SKIP? (0 pts)', not the variable — no dangling reference).totalScore += rscorrect;rsdeclared in the lock handler and in scope where used. Perfect in-band play → every roundrs=100→avg = Math.round(totalScore/SCEN.length) = 100(no longer 33).addScore(avg)is in the finish branch.level1:rsstill computed (Math.round((correct/real.size)*100 - wrong*15 - missed*12), clamped 0–100) andtotalScore += rsper round retained — the removed line was onlyaddScore(rs).rsstill used for the per-round comparison display.avg = Math.round(totalScore / ROUNDS.length)unchanged.addScore(avg)is in the finishelsebranch, not per round.- No other behavior changed; diff matches the stated +3/-5.
Checklist Results
- ✅ Architecture & Design: Pass (static single-file change, no layering concern)
- ✅ Code Quality: Pass (minimal, surgical, removes the double-division dead path)
- ✅ Testing: Pass (
node --checkis the project's gate for this static site; clean. No test suite by design) - ✅ Security: Pass (no auth/secrets/user-data; pure client-side scoring)
- ✅ Performance: Pass (no hot-path or query changes)
- ✅ PR Description & Glossary: Pass (Summary + Testing + Glossary all present; narrative bullets)
- ✅ Summary Bullet Narrative: Pass (each bullet states what + why)
- ✅ Technical Decisions (AgDR): N/A (bug fix, no library/architecture/pattern decision)
- ✅ Adopter Handbooks: N/A (no architecture/general/language violations triggered)
Issues Found
None.
Handbook Findings
- Migration Safety (blocking): not triggered — no migration files in the diff.
- Clean Architecture Layers (advisory): N/A — single static HTML file, no cross-layer imports.
- Commit Message Quality (advisory): Pass — commit
3bf04a4has a descriptive WHY-bearing subject and a body explaining problem, root cause, the two fixes with rationale, result, and verification. SingleCloses #614, co-authorship line present.
Suggestions
None blocking. (Optional, for a future PR: the per-level scoring pattern is now uniform — a one-line console.assert(state.score === state.levelScores.reduce(...)) dev-only guard could lock the invariant in if the game grows past 11 levels. Not needed for this fix.)
Verdict
APPROVED
PR body has Summary + Testing + Glossary; Closes #614 (issue OPEN); commit SHA matches HEAD (3bf04a4...). Base is dev, single file, +3/-5.
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 3bf04a434a1a4d08441fa6b5f30872278cf96682
Summary
addScore()per round (sostate.scoregot the sum of the rounds) while recording only the average instate.levelScores(what the results breakdown shows):level1(Token splitter) — 3 rounds × per-roundaddScore(rs), recordedavg. Over-added ~200.levelTemp(Temperature dial) — per-roundaddScore(pts), recordedavg. Over-added ~67. Plus a double-division bug:pts = rs/3and thenavg = totalScore/SCEN.length, which capped the level at ~33/100 even on a perfect play (why it showed 33).state.score) and 922 (ΣlevelScores).addScore; calladdScore(avg)once at each level's finish (afterlevelScoresis set), so every level contributes exactly its recorded 0–100 (the patternlevel6already uses). InlevelTemp, accumulatetotalScore += rs(notrs/3) so the average is true and a perfect run scores 100. Net: total maxes at 1100 and matches the breakdown.Testing
node --checkon the inline script — clean;LEVELSstill 11;addScoreis now called once per level (11 calls + the definition).addScore(rs|pts|roundScore)remain; theptsdouble-division variable is removed.Closes #614
Glossary
state.scorestate.levelScores[]state.scorereceiving more additions than the per-level scores sum to.