docs(03): create phase plan — komplettes spielerlebnis
5 plans across 3 waves covering all 17 requirements: - Plan 01 (W1): Word lists, Progress extension, rate limits - Plan 02 (W2): Lesson 3-phase state machine, review/repeat - Plan 03 (W1): Forest visual scene with SVG + grid - Plan 04 (W3): Reward screen, pre-generation, full flow wiring - Plan 05 (W2): Parent area with stats and settings Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
---
|
||||
phase: 03-komplettes-spielerlebnis
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [03-01]
|
||||
files_modified:
|
||||
- src/game/typing.ts
|
||||
- src/game/typing.test.ts
|
||||
- src/ui/screens.ts
|
||||
- index.html
|
||||
autonomous: true
|
||||
requirements: [TYPE-05, TYPE-06, TYPE-07, LEVL-05, LEVL-06]
|
||||
must_haves:
|
||||
truths:
|
||||
- "Entdecken phase shows companion text and highlights the new key on keyboard"
|
||||
- "Woerter phase displays whole words with active letter highlighted"
|
||||
- "Lesson flows through discover -> practice -> words automatically"
|
||||
- "Every 3rd session is a review mixing letters from last 2-3 levels"
|
||||
- "After 2x same level, companion shows encouragement with curiosity trigger"
|
||||
artifacts:
|
||||
- path: "src/game/typing.ts"
|
||||
provides: "Extended createExerciseState with mode='letters'|'words', generateWordLetters()"
|
||||
exports: ["createExerciseState", "generateWordLetters"]
|
||||
- path: "src/ui/screens.ts"
|
||||
provides: "Lesson sub-phase state machine: discover -> practice -> words"
|
||||
contains: "LessonPhase"
|
||||
key_links:
|
||||
- from: "src/ui/screens.ts"
|
||||
to: "src/game/typing.ts"
|
||||
via: "createExerciseState for practice and word phases"
|
||||
pattern: "createExerciseState"
|
||||
- from: "src/ui/screens.ts"
|
||||
to: "src/companion/companion.ts"
|
||||
via: "getLetterIntro for discover phase"
|
||||
pattern: "getLetterIntro"
|
||||
- from: "src/ui/screens.ts"
|
||||
to: "src/game/words.ts"
|
||||
via: "getWordsForLevel for word phase"
|
||||
pattern: "getWordsForLevel"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the complete lesson flow with 3 sub-phases (Entdecken -> Ueben -> Woerter) as an internal state machine, plus review mechanic and repetition protection.
|
||||
|
||||
Purpose: This is the core game loop — the child experiences discover, practice, then words in one fluid session.
|
||||
Output: Extended typing engine with word mode, lesson screen with 3-phase state machine, review and repetition logic.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/03-komplettes-spielerlebnis/03-01-SUMMARY.md
|
||||
@src/game/typing.ts
|
||||
@src/ui/screens.ts
|
||||
@src/companion/companion.ts
|
||||
@src/game/words.ts
|
||||
@src/game/levels.ts
|
||||
@src/types.ts
|
||||
@index.html
|
||||
|
||||
<interfaces>
|
||||
<!-- From src/types.ts (after Plan 01): -->
|
||||
export type LessonPhase = 'discover' | 'practice' | 'words';
|
||||
export interface Progress {
|
||||
id: 1;
|
||||
currentLevel: number;
|
||||
completedLevels: number[];
|
||||
totalSessions: number;
|
||||
sessionDates: string[];
|
||||
selectedCharacter: CompanionType;
|
||||
selectedLayout: KeyboardLayout;
|
||||
totalCorrect: number;
|
||||
totalErrors: number;
|
||||
errorKeyCounts: Record<string, number>;
|
||||
lastPlayedLevels: number[];
|
||||
}
|
||||
|
||||
<!-- From src/game/words.ts (after Plan 01): -->
|
||||
export const wordsByLevel: Record<number, string[]>;
|
||||
export function getWordsForLevel(level: number, count: number): string[];
|
||||
|
||||
<!-- From src/companion/companion.ts (existing): -->
|
||||
export async function getLetterIntro(characterType: CompanionType, letter: string, fingerDescription: string, db: IDBDatabase): Promise<string>;
|
||||
|
||||
<!-- From src/game/typing.ts (existing): -->
|
||||
export function createExerciseState(level: number): TypingExerciseState;
|
||||
export function handleKeyPress(state: TypingExerciseState, pressedKey: string): KeyPressResult;
|
||||
export function getCurrentLetter(state: TypingExerciseState): string | null;
|
||||
|
||||
<!-- From src/game/levels.ts (existing): -->
|
||||
export function getLevelByNumber(n: number): Level | undefined;
|
||||
export function getKeysUpToLevel(n: number): string[];
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Extend typing engine with word mode</name>
|
||||
<files>src/game/typing.ts, src/game/typing.test.ts</files>
|
||||
<read_first>src/game/typing.ts, src/game/words.ts, src/types.ts</read_first>
|
||||
<behavior>
|
||||
- Test: createExerciseState(level) still works as before (letters mode, backwards compatible)
|
||||
- Test: createWordExerciseState(level, words) creates state with letters from the words joined, e.g. words=["eis","die"] -> letters=["e","i","s","d","i","e"]
|
||||
- Test: createWordExerciseState stores words and wordBoundaries in state for UI rendering
|
||||
- Test: handleKeyPress works the same way for word-mode state
|
||||
- Test: getWordAtIndex(state, letterIndex) returns which word the letter belongs to
|
||||
</behavior>
|
||||
<action>
|
||||
Extend `src/game/typing.ts` (per D-03):
|
||||
|
||||
1. Add `ExerciseMode` type: `'letters' | 'words'`
|
||||
|
||||
2. Extend `TypingExerciseState` interface (in types.ts) with optional word fields:
|
||||
```typescript
|
||||
export interface TypingExerciseState {
|
||||
currentLetterIndex: number;
|
||||
letters: string[];
|
||||
intervalMs: number;
|
||||
correctStreak: number;
|
||||
totalCorrect: number;
|
||||
totalErrors: number;
|
||||
mode: 'letters' | 'words';
|
||||
words?: string[]; // original words for display
|
||||
wordBoundaries?: number[]; // indices where each word starts in letters[]
|
||||
}
|
||||
```
|
||||
|
||||
3. Keep existing `createExerciseState(level)` unchanged but set `mode: 'letters'`, `words: undefined`, `wordBoundaries: undefined`.
|
||||
|
||||
4. Add new function:
|
||||
```typescript
|
||||
export function createWordExerciseState(words: string[]): TypingExerciseState {
|
||||
const letters: string[] = [];
|
||||
const wordBoundaries: number[] = [];
|
||||
for (const word of words) {
|
||||
wordBoundaries.push(letters.length);
|
||||
for (const ch of word) {
|
||||
letters.push(ch);
|
||||
}
|
||||
}
|
||||
return {
|
||||
currentLetterIndex: 0,
|
||||
letters,
|
||||
intervalMs: 0, // no falling in word mode
|
||||
correctStreak: 0,
|
||||
totalCorrect: 0,
|
||||
totalErrors: 0,
|
||||
mode: 'words',
|
||||
words,
|
||||
wordBoundaries,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
5. Add helper: `getCurrentWordIndex(state: TypingExerciseState): number` — returns which word index (0-based) the currentLetterIndex is in, using wordBoundaries.
|
||||
|
||||
6. `handleKeyPress` already works generically — no changes needed.
|
||||
|
||||
Create `src/game/typing.test.ts` (or extend if exists) with vitest tests for the new functions plus backwards compatibility.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npx vitest run src/game/typing.test.ts</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- grep "createWordExerciseState" src/game/typing.ts returns export
|
||||
- grep "getCurrentWordIndex" src/game/typing.ts returns export
|
||||
- grep "mode.*letters.*words" src/types.ts returns match showing mode field
|
||||
- npx vitest run src/game/typing.test.ts exits 0
|
||||
</acceptance_criteria>
|
||||
<done>Typing engine supports word mode with word boundaries tracking. Backwards compatible with letter mode. Tests pass.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Lesson screen 3-phase state machine with review and repetition</name>
|
||||
<files>src/ui/screens.ts, index.html</files>
|
||||
<read_first>src/ui/screens.ts, src/game/typing.ts, src/companion/companion.ts, src/game/words.ts, src/game/levels.ts, src/types.ts, index.html</read_first>
|
||||
<action>
|
||||
Rewrite `initLessonScreen` in `src/ui/screens.ts` as a 3-phase state machine (per D-01, D-02, D-03):
|
||||
|
||||
**Add to index.html** inside `#screen-lesson .lesson`:
|
||||
```html
|
||||
<div class="lesson__phase-indicator" id="lesson-phase-indicator"></div>
|
||||
```
|
||||
This shows which phase is active (Entdecken / Ueben / Woerter). Add above the letter-area.
|
||||
|
||||
**Signature change:**
|
||||
```typescript
|
||||
export function initLessonScreen(
|
||||
db: IDBDatabase,
|
||||
level: number,
|
||||
layout: KeyboardLayout,
|
||||
onComplete: (level: number, stats: { totalCorrect: number; totalErrors: number; errorKeys: Record<string, number> }) => void,
|
||||
onCancel?: () => void,
|
||||
): () => void
|
||||
```
|
||||
The `onComplete` callback now receives exercise stats (per D-16).
|
||||
|
||||
**State machine flow:**
|
||||
|
||||
1. **DISCOVER phase** (per D-02, TYPE-05):
|
||||
- Show phase indicator: "Entdecken"
|
||||
- Get the first new key of this level via `getLevelByNumber(level).newKeys[0]`
|
||||
- Fetch companion letter intro via `getLetterIntro(characterType, letter, fingerDescription, db)`
|
||||
- Display intro text in `#lesson-companion-text`
|
||||
- Highlight the target key on keyboard (already have `highlightKey()`)
|
||||
- Make key pulse (add class `keyboard__key--discover-pulse` to the key)
|
||||
- Wait for child to press the correct key ONCE to proceed
|
||||
- If level has a second newKey, repeat discover for that key too
|
||||
- Then transition to PRACTICE
|
||||
|
||||
2. **PRACTICE phase** (existing behavior, TYPE-01-04):
|
||||
- Show phase indicator: "Ueben"
|
||||
- Run existing letter exercise with `createExerciseState(level)`
|
||||
- On complete, accumulate stats, transition to WORDS
|
||||
|
||||
3. **WORDS phase** (per D-03, TYPE-06):
|
||||
- Show phase indicator: "Woerter"
|
||||
- Get 3-4 random words via `getWordsForLevel(level, 4)` (fallback to 3 if < 4 available)
|
||||
- Create word exercise via `createWordExerciseState(words)`
|
||||
- Display current word fully, highlight active letter:
|
||||
```html
|
||||
<div class="word-display">
|
||||
<span class="word-display__letter word-display__letter--done">e</span>
|
||||
<span class="word-display__letter word-display__letter--active">i</span>
|
||||
<span class="word-display__letter">s</span>
|
||||
</div>
|
||||
```
|
||||
- When word complete, show next word. After all words done, call `onComplete(level, stats)`.
|
||||
|
||||
**Review mechanic (per D-05, LEVL-05):**
|
||||
Add `isReviewSession` parameter to `initLessonScreen`. When `true`:
|
||||
- Discover phase: skip (reviews don't introduce new letters)
|
||||
- Practice phase: use mixed letters from last 2-3 levels (combine allKeys from current and previous 1-2 levels)
|
||||
- Words phase: pick words from last 2-3 levels randomly
|
||||
|
||||
The review trigger logic (`totalSessions % 3 === 0`) is handled in `app.ts` (Plan 04), not here. This function just needs to accept the `isReview: boolean` flag.
|
||||
|
||||
**Repetition protection (per D-06, LEVL-06):**
|
||||
Add `showEncouragement` parameter. When `true`:
|
||||
- Before discover phase, show companion encouragement text: a hardcoded array of 3-4 messages like "Magst du sehen, was als Naechstes kommt? Im Wald gibt es noch so viel zu entdecken!"
|
||||
- Show for 3 seconds, then proceed to discover (or practice if review).
|
||||
|
||||
The trigger logic (checking `lastPlayedLevels` for 2x same) is handled in `app.ts` (Plan 04).
|
||||
|
||||
**Cumulative stats across phases:**
|
||||
Track `totalCorrect`, `totalErrors`, `errorKeys: Record<string, number>` across all 3 phases. In handleKeyPress wrapper, if `!result.correct`, increment `errorKeys[expectedKey]`. Pass accumulated stats to `onComplete`.
|
||||
|
||||
**CSS for phase indicator** — add styles in action:
|
||||
```css
|
||||
.lesson__phase-indicator {
|
||||
text-align: center;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1rem;
|
||||
color: var(--text-warm);
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
```
|
||||
|
||||
**CSS for word display:**
|
||||
```css
|
||||
.word-display {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.25rem;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 2.5rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.word-display__letter {
|
||||
color: var(--text-light);
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.word-display__letter--active {
|
||||
color: var(--accent-gold);
|
||||
font-weight: 700;
|
||||
}
|
||||
.word-display__letter--done {
|
||||
color: var(--accent-green);
|
||||
}
|
||||
```
|
||||
|
||||
Add CSS for discover pulse:
|
||||
```css
|
||||
.keyboard__key--discover-pulse {
|
||||
animation: discover-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
@keyframes discover-pulse {
|
||||
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 var(--accent-gold); }
|
||||
50% { transform: scale(1.1); box-shadow: 0 0 12px 4px var(--accent-gold); }
|
||||
}
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npx tsc --noEmit && npx vitest run</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- grep "lesson-phase-indicator" index.html returns a match
|
||||
- grep "discover.*practice.*words" src/ui/screens.ts shows state machine phases
|
||||
- grep "getLetterIntro" src/ui/screens.ts shows companion integration in discover
|
||||
- grep "createWordExerciseState" src/ui/screens.ts shows word mode usage
|
||||
- grep "getWordsForLevel" src/ui/screens.ts shows word list integration
|
||||
- grep "isReview" src/ui/screens.ts shows review parameter
|
||||
- grep "showEncouragement" src/ui/screens.ts shows repetition protection parameter
|
||||
- grep "word-display" src/styles/main.css returns CSS matches
|
||||
- grep "discover-pulse" src/styles/main.css returns CSS matches
|
||||
- npx tsc --noEmit exits 0
|
||||
</acceptance_criteria>
|
||||
<done>Lesson screen runs 3 phases (discover->practice->words) with companion letter intro, word display UI, review flag support, and repetition encouragement. Stats accumulated across phases and passed to onComplete callback.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `npx tsc --noEmit` — no type errors
|
||||
- `npx vitest run` — all tests pass
|
||||
- Manual: lesson screen shows phase indicator, discover phase displays companion text and highlights key, words phase shows word with active letter
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
1. Typing engine has createWordExerciseState() and getCurrentWordIndex()
|
||||
2. Lesson screen flows: discover -> practice -> words -> onComplete(level, stats)
|
||||
3. Discover phase: companion introduces letter, key pulses, child presses once to proceed
|
||||
4. Words phase: 3-4 words displayed with active letter highlighted, typed letter by letter
|
||||
5. Review flag skips discover and mixes letters/words from recent levels
|
||||
6. Encouragement shown when showEncouragement=true
|
||||
7. Stats (correct, errors, errorKeys) accumulated and passed to onComplete
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/03-komplettes-spielerlebnis/03-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user