Problem
The four advertised game modes do not have a coherent lifecycle and several mode-specific transformations never reach the playable state. The UI allows users to choose modes that behave like Classic or fail in edge cases.
Evidence
- every mode exposes
initializeGame(), but startGame() does not consistently use its returned word data.
- Puzzle mode creates
puzzle metadata, while the core renderer and answer logic continue to display/spell the normal full word.
- Story mode only has story templates for a subset of categories and mutates/reads progress separately from the round lifecycle.
- Timed mode owns timer state and callbacks, but navigation, repeated starts, round completion, and mode changes do not use a shared cleanup contract.
- duplicated
loadWord() and showScreen() declarations make the effective lifecycle depend on function hoisting and source order.
- mode state is persisted globally, not per child, and there are no mode tests.
Proposed implementation
Introduce a small mode contract and a single round controller:
prepareRound(context) -> round
presentWord(round, index) -> prompt
validateAttempt(prompt, answer) -> result
onAttempt(result)
onRoundEnd(reason)
dispose()
- Keep navigation, timers, analytics, scoring, hints, audio, and cleanup in the controller.
- Let each mode provide only its distinct prompt/validation/scoring behavior.
- Define graceful behavior for unsupported category/mode combinations.
- Make random puzzle generation deterministic under a seed and ensure anagrams are not unchanged where avoidable.
Acceptance criteria
- Classic, Story, Timed, and Puzzle each have visibly and behaviorally distinct rules documented in the UI.
- Puzzle prompts and validation match the generated puzzle type.
- Starting, restarting, changing modes, going Home, and finishing a round leave no orphan timers/listeners/modals.
- Story mode works or is explicitly unavailable for every selectable category.
- scoring and analytics record the selected mode and its relevant result.
- mode logic is unit tested; one end-to-end test completes and exits each mode.
Out of scope
Splitting the monolith is tracked separately in #6. This issue is about a correct behavioral contract and can be implemented alongside that refactor.
Problem
The four advertised game modes do not have a coherent lifecycle and several mode-specific transformations never reach the playable state. The UI allows users to choose modes that behave like Classic or fail in edge cases.
Evidence
initializeGame(), butstartGame()does not consistently use its returned word data.puzzlemetadata, while the core renderer and answer logic continue to display/spell the normal full word.loadWord()andshowScreen()declarations make the effective lifecycle depend on function hoisting and source order.Proposed implementation
Introduce a small mode contract and a single round controller:
Acceptance criteria
Out of scope
Splitting the monolith is tracked separately in #6. This issue is about a correct behavioral contract and can be implemented alongside that refactor.