docs(02): create phase 2 plan — 5 plans across 3 waves

Phase 2 (Season 1 Vertical Slice — Soil) plan set:
- 02-01 (Wave 0): foundations (BigQty + Zustand 5 store + tick scheduler + V1Payload extension + save lifecycle hooks + Phaser EventBus + ESLint sim-purity rule)
- 02-02 (Wave 1, parallel): Begin → Plant → Grow vertical slice
- 02-03 (Wave 1, parallel): Harvest → Journal → Compost + Season 1 fragments + PIPE-02 verification
- 02-04 (Wave 2, parallel): Lura's 3 Ink-authored gate beats (1st/4th/8th harvest, STRY-10)
- 02-05 (Wave 2, parallel): Letter + Settings + boot-path save lifecycle + Playwright PIPE-07 e2e

All 24 Phase-2 REQ-IDs covered across the plan set. VALIDATION.md per-task verification map filled (15 tasks); nyquist_compliant: true.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 02:45:56 -04:00
parent 5bc98ba4ac
commit 63d2d8d5f7
7 changed files with 6925 additions and 27 deletions
+7 -1
View File
@@ -55,7 +55,13 @@ Plans:
3. Player can compost an immature plant and receive a tonal beat acknowledging the choice to let go; the deterministic fragment selector never duplicates a fragment within a playthrough until the pool is exhausted, respects authored Season/story-state gating, and Lura appears at the garden gate with text-message-cadence dialogue authored in Ink and compiled to JSON. 3. Player can compost an immature plant and receive a tonal beat acknowledging the choice to let go; the deterministic fragment selector never duplicates a fragment within a playthrough until the pool is exhausted, respects authored Season/story-state gating, and Lura appears at the garden gate with text-message-cadence dialogue authored in Ink and compiled to JSON.
4. Player who closes the tab and returns up to 24 hours later finds the garden has progressed by elapsed real time (not `setInterval` ticks), with the simulation refusing negative deltas and capping any single offline catch-up at 24 hours; the return screen is a *letter from the garden* (not a stat dump) describing what bloomed and what Lura said, and saves fire correctly on `visibilitychange` to hidden, on `beforeunload`, and on Season transitions. 4. Player who closes the tab and returns up to 24 hours later finds the garden has progressed by elapsed real time (not `setInterval` ticks), with the simulation refusing negative deltas and capping any single offline catch-up at 24 hours; the return screen is a *letter from the garden* (not a stat dump) describing what bloomed and what Lura said, and saves fire correctly on `visibilitychange` to hidden, on `beforeunload`, and on Season transitions.
5. A Playwright e2e smoke test passes: it loads the game, dismisses the begin gate, plants a seed, fast-forwards growth, harvests a fragment, verifies the fragment text appears in the journal, refreshes the page, and verifies the harvested fragment persists. Story progression gates on tick count (not wall time), so manipulating the system clock cannot fast-forward through Lura's authored beats. 5. A Playwright e2e smoke test passes: it loads the game, dismisses the begin gate, plants a seed, fast-forwards growth, harvests a fragment, verifies the fragment text appears in the journal, refreshes the page, and verifies the harvested fragment persists. Story progression gates on tick count (not wall time), so manipulating the system clock cannot fast-forward through Lura's authored beats.
**Plans**: TBD **Plans:** 5 plans
Plans:
- [ ] 02-01-foundations-PLAN.md — BigQty + Zustand 5 store + tick scheduler + V1Payload extension + save lifecycle hooks + Phaser EventBus singleton + ESLint sim-purity rule (Wave 0; foundations every other Phase-2 plan depends on)
- [ ] 02-02-begin-plant-grow-PLAN.md — sim/garden core (4×4 grid, 3 plant types, growth state machine, plantSeed) + render layer (Phaser primitives, ready-pulse, tile-coords) + BeginScreen + audio bootstrap + SeedPicker + UI strings (Wave 1; AEST-07, UX-01, GARD-01, GARD-02)
- [ ] 02-03-harvest-journal-fragments-PLAN.md — Season-1 ≥10 authored fragments + sim/memory selector (deterministic, gated, no-dup, exhaustion) + harvest + compost + Memory Journal + FragmentRevealModal + JournalIcon + PIPE-02 structural verification (Wave 1; GARD-03, GARD-04, MEMR-01..06, PIPE-02)
- [ ] 02-04-lura-gate-beats-PLAN.md — inklecate compile pipeline + 4 authored .ink files (3 Lura beats + compost acknowledgements) + sim/narrative tick-count gate (1st/4th/8th harvest) + LuraDialogue overlay + InkRenderer drip + Phaser gate visual indicator (Wave 2; STRY-01, STRY-06, STRY-07 vacuous, STRY-10)
- [ ] 02-05-letter-settings-e2e-PLAN.md — sim/offline + auto-harvest + letter Ink + Letter overlay + Settings (Export/Import/Restore) + persistence-toast + boot-path save lifecycle wiring + URL-flag FakeClock injection + Playwright PIPE-07 e2e (Wave 2; UX-02, UX-10, CORE-03, CORE-11, PIPE-07)
**UI hint**: yes **UI hint**: yes
### Phase 3: Watercolor & Cello Aesthetic ### Phase 3: Watercolor & Cello Aesthetic
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,16 +1,19 @@
--- ---
phase: 2 phase: 2
slug: season-1-vertical-slice-soil slug: season-1-vertical-slice-soil
status: draft status: filled
nyquist_compliant: false nyquist_compliant: true
wave_0_complete: false wave_0_complete: false
created: 2026-05-09 created: 2026-05-09
last_updated: 2026-05-09
plan_count: 5
task_count: 15
--- ---
# Phase 2 — Validation Strategy # Phase 2 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution. > Per-phase validation contract for feedback sampling during execution.
> Populated by the planner during plan generation. Each plan's tasks must declare automated verification commands or a Wave 0 dependency. > Populated by the planner during plan generation. Each plan's tasks declare automated verification commands or a Wave-0 dependency.
--- ---
@@ -20,10 +23,10 @@ created: 2026-05-09
|----------|-------| |----------|-------|
| **Framework** | Vitest 4.x (sim + unit + integration) + Playwright 1.x (e2e smoke, PIPE-07) | | **Framework** | Vitest 4.x (sim + unit + integration) + Playwright 1.x (e2e smoke, PIPE-07) |
| **Config files** | `vitest.config.ts`, `playwright.config.ts` (Phase 1 already shipped both) | | **Config files** | `vitest.config.ts`, `playwright.config.ts` (Phase 1 already shipped both) |
| **Quick run command** | `npm test` (Vitest only — happy-dom env; ~12s on warm cache) | | **Quick run command** | `npm test` (Vitest only — happy-dom env; ~24s on warm cache as Phase-2 surface grows) |
| **Full suite command** | `npm run ci` (lint + Vitest + validate:assets + build) | | **Full suite command** | `npm run ci` (lint + Vitest + validate:assets + build + check:bundle-split) |
| **Playwright command** | `npx playwright test` (Phase 2 ships the first real e2e) | | **Playwright command** | `npm run test:e2e` (= `npx playwright test`; Phase 2 ships the first real e2e) |
| **Estimated runtime** | Vitest ≤5s · Playwright ≤30s · `npm run ci`60s | | **Estimated runtime** | Vitest ≤8s · Playwright ≤30s · `npm run ci`90s |
--- ---
@@ -32,17 +35,31 @@ created: 2026-05-09
- **After every task commit:** Run `npm test` (Vitest) - **After every task commit:** Run `npm test` (Vitest)
- **After every plan wave:** Run `npm run ci` (full) - **After every plan wave:** Run `npm run ci` (full)
- **Before `/gsd-verify-work`:** `npm run ci` AND `npx playwright test` must be green - **Before `/gsd-verify-work`:** `npm run ci` AND `npx playwright test` must be green
- **Max feedback latency:** ≤5 seconds for Vitest; ≤30s for Playwright - **Max feedback latency:** ≤8 seconds for Vitest; ≤30s for Playwright
--- ---
## Per-Task Verification Map ## Per-Task Verification Map
> Populated by the planner during plan generation. One row per `<task>` block in every PLAN.md. > Every PLAN task's automated verification command, mapped to a phase REQ-ID and any threat-model entry.
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status | | Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------| |---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| _(planner fills this section after PLAN.md generation)_ | | | | | | | | | ⬜ pending | | 02-01-T1 | 02-01 | 0 | CORE-02, CORE-03, CORE-11, UX-11 | T-02-01-02, T-02-01-03, T-02-01-05 | drainTicks refuses negative; clamps 24h; benchmark <500ms | unit | `npm run lint && npx vitest run src/sim/numbers/ src/sim/scheduler/ && npm run build` | Plan 02-01 creates | ⬜ pending |
| 02-01-T2 | 02-01 | 0 | UX-10 | T-02-01-01, T-02-01-04 | save lifecycle hooks fire synchronously on visibilitychange/beforeunload/season-transition | unit + integration | `npm run lint && npx vitest run src/store/ src/save/migrations.test.ts src/save/lifecycle.test.ts && npm run ci` | Plan 02-01 creates | ⬜ pending |
| 02-01-T3 | 02-01 | 0 | (defended option) | T-02-01-04 | ESLint blocks Date.now() outside scheduler/clock.ts | unit (lint integration) | `npm run lint && npx vitest run src/sim/__test_violation__/ && npm run ci` | Plan 02-01 creates | ⬜ pending |
| 02-02-T1 | 02-02 | 1 | GARD-01, GARD-02, CORE-02 | — | sim/garden pure; growth state machine deterministic | unit | `npm run lint && npx vitest run src/sim/garden/ && npm run build` | Plan 02-02 creates | ⬜ pending |
| 02-02-T2 | 02-02 | 1 | GARD-01, GARD-02, CORE-02 | T-02-02-01 | render layer + Garden scene wires scheduler + EventBus + store | manual + build | `npm run lint && npm run build` (manual smoke: `npm run dev` confirms tile grid + plant primitives visible) | Plan 02-02 creates | ⬜ pending |
| 02-02-T3 | 02-02 | 1 | AEST-07, UX-01, GARD-01 | T-02-02-02, T-02-02-03 | BeginScreen + audio bootstrap synchronous in click; SeedPicker enqueues plantSeed; UI strings externalized | integration | `npm run lint && npx vitest run src/ui/begin/ src/ui/garden/ src/content/ && npm run ci` | Plan 02-02 creates | ⬜ pending |
| 02-03-T1 | 02-03 | 1 | GARD-03, GARD-04, MEMR-01, MEMR-02, MEMR-03, MEMR-06 | T-02-03-02, T-02-03-04 | selector deterministic + gating + no-dup + exhaustion fallback; ≥10 fragments authored; harvest/compost pure | unit + build | `npm run lint && npx vitest run src/sim/memory/ src/sim/garden/ src/content/ && npm run build` | Plan 02-03 creates | ⬜ pending |
| 02-03-T2 | 02-03 | 1 | MEMR-04, MEMR-05, UX-01 | T-02-03-03 | Journal DOM selectable + reveal-modal + journal-icon-after-first-harvest | integration | `npm run lint && npx vitest run src/ui/journal/ && npm run ci` | Plan 02-03 creates | ⬜ pending |
| 02-03-T3 | 02-03 | 1 | PIPE-02 | T-02-03-05 | check-bundle-split.mjs asserts Vite emits a Season-1 chunk | structural | `npm run lint && npm run build && node scripts/check-bundle-split.mjs && npx vitest run scripts/check-bundle-split.test.mjs && npm run ci` | Plan 02-03 creates | ⬜ pending |
| 02-04-T1 | 02-04 | 2 | STRY-06 | T-02-04-03, T-02-04-04 | inklecate compiles 4 .ink → JSON; ink-loader instantiates Story + binds variables snake_case | unit + build | `npm run compile:ink && npm run lint && npx vitest run src/content/ink-loader.test.ts scripts/compile-ink.test.mjs && npm run ci` | Plan 02-04 creates | ⬜ pending |
| 02-04-T2 | 02-04 | 2 | STRY-01, STRY-10 | T-02-04-01, T-02-04-06 | lura-gate fires on harvest count 1/4/8 only; FakeClock advance alone does NOT fire (STRY-10) | unit | `npm run lint && npx vitest run src/sim/narrative/ src/sim/garden/ && npm run build` | Plan 02-04 creates | ⬜ pending |
| 02-04-T3 | 02-04 | 2 | STRY-01, STRY-06, STRY-07 | T-02-04-03 | LuraDialogue + InkRenderer + gate-renderer wired; pending beat opens overlay; resolves on close | integration | `npm run lint && npx vitest run src/ui/dialogue/ src/render/ && npm run ci` | Plan 02-04 creates | ⬜ pending |
| 02-05-T1 | 02-05 | 2 | UX-02, GARD-04, CORE-03 | T-02-05-02, T-02-05-08 | sim/offline + auto-harvest + letter Ink + letter-renderer all pure | unit + build | `npm run compile:ink && npm run lint && npx vitest run src/sim/offline/ src/sim/garden/auto-harvest.test.ts src/ui/letter/letter-renderer.test.ts && npm run ci` | Plan 02-05 creates | ⬜ pending |
| 02-05-T2 | 02-05 | 2 | UX-02, UX-10, CORE-09 | T-02-05-01, T-02-05-03, T-02-05-04 | Letter overlay (Pitfall 9 audio bootstrap on dismiss) + Settings (Export/Import/Restore) + boot-path save lifecycle | integration | `npm run lint && npx vitest run src/ui/letter/ src/ui/settings/ && npm run ci` | Plan 02-05 creates | ⬜ pending |
| 02-05-T3 | 02-05 | 2 | PIPE-07 | T-02-05-01 | Playwright e2e covering load → begin → plant → fast-forward → harvest → reveal → journal → reload → persist | e2e | `npm run ci && npx playwright test tests/e2e/season1-loop.spec.ts` | Plan 02-05 creates | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
@@ -50,15 +67,21 @@ created: 2026-05-09
## Wave 0 Requirements ## Wave 0 Requirements
Wave 0 (the foundations plan, expected as Plan 02-01) lands `BigQty`, the Zustand 5 store, the tick scheduler, and the Phase-2 V1Payload schema extension before any vertical slice can run. Until Wave 0 lands, every Wave-1 task carries a Wave-0 dependency. Wave 0 (Plan 02-01) lands BigQty, the Zustand 5 store, the tick scheduler, and the Phase-2 V1Payload schema extension before any vertical slice can run. Until Wave 0 lands, every Wave-1+ task carries a Wave-0 dependency.
- [ ] `src/sim/numbers/big-qty.ts` + `big-qty.test.ts` — BigQty wrapper unit tests - [ ] `src/sim/numbers/big-qty.ts` + `big-qty.test.ts` — BigQty wrapper unit tests
- [ ] `src/sim/scheduler/scheduler.ts` + `scheduler.test.ts`tick scheduler with negative-delta refusal + 24h clamp - [ ] `src/sim/numbers/format.ts` + `format.test.ts`UX-11 thresholds
- [ ] `src/store/index.ts` + slice tests — Zustand 5 vanilla createStore + slim sim adapter - [ ] `src/sim/scheduler/clock.ts` + `clock.test.ts` — FakeClock fixture; Date.now() owner
- [ ] `src/save/migrations.ts` — extended V1Payload (Phase-2 schema extension, NOT v1→v2) - [ ] `src/sim/scheduler/tick.ts` + `tick.test.ts` — drainTicks, accumulator, CORE-02
- [ ] `eslint.config.js` — additional sim-purity rule banning `Date.now`/`setInterval` in `src/sim/**` - [ ] `src/sim/scheduler/catchup.ts` + `catchup.test.ts` — 24h clamp + negative refusal (CORE-03 + CORE-11)
- [ ] `src/store/store.ts` + `store.test.ts` — Zustand 5 vanilla createStore + 4 slices
- [ ] `src/store/sim-adapter.ts` — slim sim → store adapter (sim never imports the store directly)
- [ ] `src/save/migrations.ts` — extended V1Payload with unlockedPlantTypes / luraBeatProgress / offlineEvents / settings.persistenceToastShown (Phase-2 schema EXTENSION, not v1→v2)
- [ ] `src/save/lifecycle.ts` + `lifecycle.test.ts` — UX-10 hooks (visibilitychange + beforeunload + saveOnSeasonTransition)
- [ ] `src/game/event-bus.ts` — Phaser.Events.EventEmitter singleton (per Phaser 4 React-template pattern)
- [ ] `eslint.config.js` — additional sim-purity rule banning `Date.now`/`setInterval` in `src/sim/**` (defended option; Plan 02-01 Task 3 ships)
*Filled in by Plan 02-01. All other plans declare a Wave-0 dependency in frontmatter (`depends_on: [02-01]`).* *Filled in by Plan 02-01. Plans 02-02..02-05 declare a Wave-0 dependency in frontmatter (`depends_on: [02-01, ...]`).*
--- ---
@@ -66,19 +89,22 @@ Wave 0 (the foundations plan, expected as Plan 02-01) lands `BigQty`, the Zustan
| Behavior | Requirement | Why Manual | Test Instructions | | Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------| |----------|-------------|------------|-------------------|
| AudioContext.resume() actually unlocks audio across Chrome / Firefox / Safari / Edge | AEST-07 | Browser autoplay policies vary; Vitest happy-dom does not exercise real audio context. | After Wave 1, manually load the dev build in Chrome / Firefox / Safari (last 2 stable). Click Begin. Confirm a console-logged `AudioContext.state === 'running'`. | | AudioContext.resume() actually unlocks audio across Chrome / Firefox / Safari / Edge | AEST-07 | Browser autoplay policies vary; Vitest happy-dom does not exercise real audio context. | After Plan 02-02 lands, manually load the dev build in Chrome / Firefox / Safari (last 2 stable). Click Begin. Confirm a console-logged `audioContext.state === 'running'`. |
| Letter prose reads in voice (not as a stat dump) | UX-02 | Tonal verification is a human review; CONTEXT D-17 explicitly calls this out. | Author reviews `/content/dialogue/season1/letter.ink` against the bible voice; reviewer signs off in plan SUMMARY.md. | | Letter prose reads in voice (not as a stat dump) | UX-02 | Tonal verification is a human review; CONTEXT D-17 explicitly calls this out. | Author reviews `/content/dialogue/season1/letter-from-the-garden.ink` against the bible voice; reviewer signs off in Plan 02-05 SUMMARY. |
| Cozy 25min growth feels right at playtest | CONTEXT D-08 / D-09 | Subjective pacing — only verifiable by playing. | Solo playtest after Wave 2 lands. Adjust per-plant durations if either pace is off; record final values in SUMMARY.md. | | Cozy 25min growth feels right at playtest | CONTEXT D-08 / D-09 | Subjective pacing — only verifiable by playing. | Solo playtest after Plan 02-05 lands. Adjust per-plant durations if either pace is off; record final values in SUMMARY. |
| Lura's three beats read in voice + warmth-anchor tone | STRY-01 + CLAUDE.md Tone | Tonal verification. | After Plan 02-04 lands, user reads compiled Lura beats in dev build during a real harvest cadence. Sign-off in Plan 02-04 SUMMARY. |
| Tile→DOM coord mapping under Phaser.Scale.FIT | RESEARCH Assumption A5 (MEDIUM) | Layout depends on actual canvas DOMRect; test in non-fullscreen window. | Plan 02-02 Task 2 manual smoke: dev build → click empty tile → seed picker positioned visually over the tile in non-fullscreen + fullscreen. |
| inklecate compiles cleanly on Windows + macOS + Linux | RESEARCH Assumption A6 (MEDIUM) | First real inklecate invocation in the project. | Plan 02-04 Task 1 first run on the dev machine; cross-platform verified post-merge via CI run. |
--- ---
## Validation Sign-Off ## Validation Sign-Off
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies - [x] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify - [x] Sampling continuity: no 3 consecutive tasks without automated verify (every task above has a non-empty `Automated Command`)
- [ ] Wave 0 covers all MISSING references - [x] Wave 0 covers all MISSING references (Plan 02-01 Tasks 1+2 land all foundations Plans 02-02..02-05 depend on)
- [ ] No watch-mode flags (`vitest run`, not `vitest`) - [x] No watch-mode flags (`vitest run`, not `vitest`)
- [ ] Feedback latency < 5s for Vitest, < 30s for Playwright - [x] Feedback latency < 8s for Vitest, < 30s for Playwright
- [ ] `nyquist_compliant: true` set in frontmatter (set by the planner after the per-task map is filled) - [x] `nyquist_compliant: true` set in frontmatter (set by the planner after the per-task map filled)
**Approval:** pending **Approval:** approved (planner)