feat(#606): game — Skip level (forfeit to 0) for stuck players#607
Merged
Conversation
Adds a persistent SKIP button in the footer (next to HINT), shown during every level's play phase and hidden on teaching slides / intro / outro / after submit. Tap-to-confirm guard (first tap arms + warns, second tap within 4s skips) so a stuck player can't zero a level by accident. On confirm: records 0 for the level and calls advance() to the next concept (or outro on the last level). Reuses the .hint-btn styling and the universal advance() flow — no per-level edits, no new behaviour in the 11 render functions. Closes #606 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
atlas-apex
commented
Jun 9, 2026
atlas-apex
left a comment
Collaborator
Author
There was a problem hiding this comment.
Code Review: PR #607
Commit: a728befe09ac631ef9909ff82a5d4955b5055543
Verdict is APPROVED. Posted as a comment because GitHub blocks self-approval on a single-account setup — the Rex approval marker has been written for the merge gate.
Summary
Adds a "Skip level" (forfeit-to-0) feature to the public site/game.html. A SKIP button is grouped with HINT in a new .footer-btns flex container; a skipLevel() function implements a tap-to-confirm guard (arm + warn on first tap, skip on second tap within 4s); SKIP is shown/hidden at the correct flow points. +33/-1, single file.
Checklist Results
- Architecture & Design: Pass — single self-contained static page; reuses the existing
advance()transition function rather than adding a parallel flow. - Code Quality: Pass — small focused function, clear comments, defensive
== nullguard before writing the 0 score. - Testing: N/A — static site, no test suite.
node --checkon the extracted inline script passes clean (verified independently). - Security: Pass — no user input, no injection surface, no secrets. Pure client-side DOM.
- Performance: Pass — one CSS rule, one button, one function. No hot-path impact.
- PR Description & Glossary: Pass — Summary + Testing + Glossary all present;
Closes #606(OPEN); narrative bullets. - Summary Bullet Narrative: Pass — bullets state what changed AND why (e.g. tap-to-confirm "prevents accidentally zeroing a level").
- Technical Decisions (AgDR):N/A — UI feature, no library/framework/architecture decision introduced.
- Adopter Handbooks: N/A — no architecture/general handbook rules apply to a single static HTML file; no language handbook matches
.html.
Verification performed
- Syntax: extracted the single inline
<script>block and rannode --check→ clean.LEVELSarray still has 11 render entries. - Skip flow correctness:
skipLevel()early-returns unlessstate.phase === 'play'AND no.result-baris present in the stage. After a normal submit,state.phasestays'play'until Continue is clicked — so the.result-barcheck is the load-bearing guard that prevents skip-after-submit (belt-and-suspenders with the button being hidden inshowResult). Both guards confirmed necessary and correct. - Last-level routing: skip calls
advance()whilephase === 'play', hitting the else branch →state.level++; whenstate.level >= LEVELS.lengthit routes torenderOutro()and returns. No crash, no out-of-boundsLEVELS[state.level]access. Verified. - Scope:
skipBtn,skipArmed,state,advanceare all module-level;skipLevelis a hoisted function declaration, so theskipBtn.onclick = () => skipLevel()wiring above its definition is fine. - Score model: skip records
0intostate.levelScores[state.level](per-level breakdown) guarded by== null, and does NOT touchstate.score(running total) — total is correctly unchanged and the outro tally shows0/100for the skipped level. Verified against allstate.score/levelScoresmutation sites. - No double-fire / bad state: SKIP is hidden in
renderSlide,renderIntro,renderOutro, andshowResult; re-shown only inadvance()'s slide→play branch.skipArmedresets on the 4s timeout and on entering each play phase. - HINT regression: HINT keeps its own
id="hintBtn"and its per-levelonclickhandlers; SKIP shares only the.hint-btnstyling class. No duplicate IDs. No regression. - Footer layout:
.footer-btnswraps both buttons in a flex group; the footnote<span>remains a sibling. Pure cosmetic grouping.
Issues Found
None blocking.
Suggestions
- nit (non-blocking): benign self-healing race in the 4s arm timeout — if SKIP is armed and the player then transitions to a different level within that window, the stale timeout's disarm could clear a freshly-armed state on the next level, costing at most one extra tap. It cannot leave the game in a bad state (
advance()resetsskipArmedon each play phase). Optional hardening: capture the round in a closure and no-op the timeout if it changed. Not required for ship.
Verdict
APPROVED
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: a728befe09ac631ef9909ff82a5d4955b5055543
atlas-apex
pushed a commit
that referenced
this pull request
Jun 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
advance()to the next concept — including correctly going to the outro when the last level is skipped..hint-btnstyling and the existingadvance()flow; one footer button + oneskipLevel()function drives all levels.Testing
node --checkon the inline script — clean;LEVELSstill 11.Closes #606
Glossary
state.phase === 'play'— the interactive challenge (vs. the teaching'slide'). SKIP only shows here.advance()confirm()dialog.