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
@@ -1,16 +1,19 @@
---
phase: 2
slug: season-1-vertical-slice-soil
status: draft
nyquist_compliant: false
status: filled
nyquist_compliant: true
wave_0_complete: false
created: 2026-05-09
last_updated: 2026-05-09
plan_count: 5
task_count: 15
---
# Phase 2 — Validation Strategy
> 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) |
| **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) |
| **Full suite command** | `npm run ci` (lint + Vitest + validate:assets + build) |
| **Playwright command** | `npx playwright test` (Phase 2 ships the first real e2e) |
| **Estimated runtime** | Vitest ≤5s · Playwright ≤30s · `npm run ci`60s |
| **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 + check:bundle-split) |
| **Playwright command** | `npm run test:e2e` (= `npx playwright test`; Phase 2 ships the first real e2e) |
| **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 plan wave:** Run `npm run ci` (full)
- **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
> 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 |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| _(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*
@@ -50,15 +67,21 @@ created: 2026-05-09
## 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/scheduler/scheduler.ts` + `scheduler.test.ts`tick scheduler with negative-delta refusal + 24h clamp
- [ ] `src/store/index.ts` + slice tests — Zustand 5 vanilla createStore + slim sim adapter
- [ ] `src/save/migrations.ts` — extended V1Payload (Phase-2 schema extension, NOT v1→v2)
- [ ] `eslint.config.js` — additional sim-purity rule banning `Date.now`/`setInterval` in `src/sim/**`
- [ ] `src/sim/numbers/format.ts` + `format.test.ts`UX-11 thresholds
- [ ] `src/sim/scheduler/clock.ts` + `clock.test.ts` — FakeClock fixture; Date.now() owner
- [ ] `src/sim/scheduler/tick.ts` + `tick.test.ts` — drainTicks, accumulator, CORE-02
- [ ] `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 |
|----------|-------------|------------|-------------------|
| 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'`. |
| 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. |
| 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. |
| 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-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 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
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags (`vitest run`, not `vitest`)
- [ ] Feedback latency < 5s for Vitest, < 30s for Playwright
- [ ] `nyquist_compliant: true` set in frontmatter (set by the planner after the per-task map is filled)
- [x] All tasks have `<automated>` verify or Wave 0 dependencies
- [x] Sampling continuity: no 3 consecutive tasks without automated verify (every task above has a non-empty `Automated Command`)
- [x] Wave 0 covers all MISSING references (Plan 02-01 Tasks 1+2 land all foundations Plans 02-02..02-05 depend on)
- [x] No watch-mode flags (`vitest run`, not `vitest`)
- [x] Feedback latency < 8s for Vitest, < 30s for Playwright
- [x] `nyquist_compliant: true` set in frontmatter (set by the planner after the per-task map filled)
**Approval:** pending
**Approval:** approved (planner)