Compare commits

...

14 Commits

Author SHA1 Message Date
josh 5ddaabcdc1 docs(02): cite D-12, D-16, D-32 in plan must_haves + record planning complete
ci / lint + test + validate-assets + build (push) Successful in 9m39s
Decision-coverage gate found three CONTEXT.md decisions structurally
implemented but not literally cited by their D-NN tags. Added one-line
must_haves entries citing each:

- D-12 (Lura as discrete gate visits, 3 beats this Season) → 02-04
- D-16 (all Lura dialogue authored in Ink, runtime via inkjs) → 02-04
- D-32 (Zustand 5 store as the Phaser↔React bridge; sim never imports
  store, CORE-10 enforced) → 02-01

STATE.md flipped from in_progress (context gathered) to ready_to_execute
with the planning summary in stopped_at.

All 24 REQ-IDs + 34 D-XX decisions now covered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 03:19:44 -04:00
josh a641056364 fix(02): plan revision iter 3 — BLOCKER 3 cross-plan regression + W1/W2
BLOCKER 3 — cross-plan regression: Plans 02-03 and 02-05 BOTH re-author
src/sim/garden/commands.ts but had reverted simulateOneTick to the old
defective return shape (`return { ...next, lastTickAt: currentTick };`).
Wave 1's execution of 02-03 would overwrite 02-02's correct version,
breaking the invariant for the entire phase.

  - 02-03: simulateOneTick return now matches 02-02 line 457 exactly:
    `return { ...next, tickCount: next.tickCount + 1 };`
  - 02-05: same fix for the silent-mode update (Step 6).
  - 02-03 acceptance_criteria: add negative grep
    (`! grep -E "lastTickAt:\s*(this|currentTick)" src/sim/garden/commands.ts`)
    and positive grep (`grep -q "tickCount: next.tickCount" ...`).
  - 02-05 acceptance_criteria: add the same two greps for commands.ts so
    02-05's silent-mode edits cannot silently re-introduce the regression.

W1 — App.tsx import: 02-05 Step 11 used `useEffect` without importing it.
Combined `import { useState }` and `import { useRef }` into a single
`import { useState, useEffect, useRef } from 'react';` line.

W2 — helper arity divergence: Settings.tsx (one-arg, Date.now() inline)
and PhaserGame.tsx (two-arg, clock.now() injected) had two parallel
definitions of buildPayloadFromStore / hydrateStoreFromPayload. Fix:

  - New Step 3.5 introduces `src/save/payload.ts` with the unified
    two-arg signature: `buildPayloadFromStore(state, nowMs)` and
    `hydrateStoreFromPayload(state, payload)`.
  - `src/save/index.ts` re-exports both.
  - Settings.tsx imports from save barrel; passes Date.now() at the
    call site (no clock injection on hand).
  - PhaserGame.tsx imports from save barrel; passes clock.now() (the
    injected wallClock or FakeClock).
  - Inline duplicate definitions in both files removed; replaced with
    a comment pointing to the shared module.
  - files_modified updated to include src/save/payload.ts.
  - acceptance_criteria asserts: shared file exists, both helpers
    exported, both consumers import from save barrel, no inline
    duplicate definitions remain.

VALIDATION.md not updated — no `<automated>` verify command changed;
the new greps live inside `<acceptance_criteria>` (executor-checked
per task), and VALIDATION.md is not present in the phase dir.

All iteration-1 + iteration-2 fixes preserved; no regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 03:15:39 -04:00
josh d065922cad revise(02): mark Open Questions RESOLVED + tidy GrowthStage import order
- W1: 02-RESEARCH.md Open Questions section now flagged (RESOLVED) and each
  Recommendation prefixed with RESOLVED + a pointer to the artifact that
  codified the resolution.
- W8: 02-02 Plan example moves `import type { GrowthStage }` to the top of
  commands.ts (alongside the other type-only imports) and drops the trailing
  parenthetical apology — the executor doesn't need to fix anything.
2026-05-09 03:05:51 -04:00
josh e5c55b0aae revise(02): BLOCKER 3 — split lastTickAt (wall-clock) from tickCount (sim counter)
Two distinct fields with strict separation:
  - lastTickAt: wall-clock milliseconds. Written ONLY at saveSync time by
    the application layer. The sim NEVER writes this field.
    computeOfflineCatchup uses it as the wall-clock anchor.
  - tickCount: monotonic sim-internal counter (one per simulate() call).
    Used for STRY-10 narrative gating that must be immune to wall-clock
    manipulation. The sim writes this field; the application layer reads
    it via simAdapter.applyTickCount.

Changes:
  02-01: SimState + V1Payload gain `tickCount: number`; migrations[1]
  defaults to 0; GardenSlice exposes tickCount + lastTickAt + setters;
  simAdapter exposes applyTickCount; tests assert the round-trip.
  02-02: simulateOneTick increments next.tickCount + 1 (not lastTickAt:
  currentTick); Garden scene's SimState snapshot reads lastTickAt
  through from store and writes tickCount: this.currentTick locally;
  acceptance_criteria forbids `lastTickAt: this.*` in the sim and scene.
  02-05: buildPayloadFromStore now persists tickCount (from store);
  hydrateStoreFromPayload restores it via state.setTickCount.

This unblocks the offline-catchup math: computeOfflineCatchup(payload.lastTickAt,
nowMs) now reliably reads wall-clock ms because the sim never overwrites it
with a tick counter.
2026-05-09 03:04:45 -04:00
josh a9f190ed27 revise(02-05): fix migrate() bypass in boot+import paths + lifecycle leak + hotkey
- BLOCKER 1: PhaserGame.tsx boot path now runs unwrap(env) → migrate(raw, env.schemaVersion).
  Casting unwrap(record.envelope) directly to V1Payload silently accepted any
  future-shape payload as the current shape; only migrate() walks the schema
  version chain.
- BLOCKER 2: Settings.tsx onImport now correctly orders importFromBase64 →
  unwrap (CRC verify) → migrate. Previous code discarded migrate's result
  and then read v1.payload as if unwrap returned an envelope rather than
  the payload itself — runtime crash on every import.
- BLOCKER 3: documented the lastTickAt invariant as wall-clock milliseconds,
  written ONLY at saveSync time (never by the sim). Added acceptance_criteria
  greps proving (a) saveSync writes clock.now(), (b) Garden scene does not
  overwrite lastTickAt with a tick counter, (c) sim/garden/Garden.ts (if it
  exists; the Garden scene actually lives at src/game/scenes/Garden.ts)
  contains no lastTickAt: this.* writes.
- W2: D-29 keyboard shortcut wired in App.tsx — comma toggles Settings,
  'j' dispatches a window CustomEvent the JournalIcon picks up.
- W5: lifecycle handle now stored in useRef and detached in the OUTER
  useLayoutEffect cleanup (the previous IIFE-internal return was a closure
  return, never reaching React's effect cleanup contract).
2026-05-09 03:01:27 -04:00
josh 953784ae93 revise(02-03): bump warm-pool fragment count + journal hotkey listener
- W6: warm-tagged pool depth raised to ≥9 (8th-harvest threshold + 1 buffer)
  so a worst-case all-rosemary playthrough never exhausts. Total per-pool
  targets: ≥9 warm, ≥3 contemplative, ≥3 heavy, plus the sentinel.
- W2: JournalIcon now listens for the 'tlg:toggle-journal' window event so
  App.tsx can wire a 'j' hotkey without lifting open/close state into the
  store. Hotkey is gated on the same revealed selector as the icon itself.
2026-05-09 03:01:12 -04:00
josh f6bef061c3 revise(02-04): replace in-test compileAllInk() call with precondition check
Per W9: invoking the compiler from inside ink-loader.test.ts's beforeAll
creates a filesystem race against other concurrent tests because the
script wipes src/content/compiled-ink/ at start. Compile is already part
of the npm run ci chain (via npm run build); the test should only verify
the artefact exists and fail loudly with a fix-it message otherwise.
2026-05-09 02:57:15 -04:00
josh f7428da299 revise(02-04): fix inklecate binary path + drop unused .ink + add last_fragment_title slot
- BLOCKER 4: inklecateBinary() now resolves node_modules/inklecate/bin/inklecate{,.exe};
  the previous path (inklecate-windows/, inklecate-mac/, inklecate-linux/) does not
  exist in the package — fallback verification of Assumption A6 would have masked
  the wrapper-API failure.
- W3: removed lura-greeting-template.ink from files_modified (never authored).
- W4: added last_fragment_title to INK_VARIABLE_MAP (extracts first sentence of the
  most-recently-harvested fragment) so the must_haves slot promise is fulfilled.
2026-05-09 02:56:50 -04:00
josh 63d2d8d5f7 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>
2026-05-09 02:45:56 -04:00
josh 5bc98ba4ac docs(02): map phase 2 file targets to existing analogs
54 file targets classified (49 new, 5 modified) with 87% analog coverage.
Key patterns: V1Payload extension (not v1→v2 migration), per-layer
public barrel pattern, test colocation, Zustand vanilla store + Phaser
EventBus singleton as the dual sim↔React bridge, ESLint sim-purity rule
proposed as a defended option (not auto-locked, per minimum-viable bias).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 02:11:01 -04:00
josh 01e02dcdb8 docs(phase-02): add validation strategy
Nyquist VALIDATION.md scaffold for Phase 2. Defines test infrastructure
(Vitest + Playwright already wired by Phase 1), sampling rates (npm test
after each commit, npm run ci after each wave), Wave-0 dependency surface
(BigQty + scheduler + Zustand store + V1Payload extension), and three
manual-only verifications (AudioContext cross-browser, letter voice review,
cozy-pace playtest). The per-task verification map is intentionally empty —
the planner fills it during plan generation; nyquist_compliant flips to
true once it's complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 02:03:17 -04:00
josh c4589a56b4 docs(02): research phase 2 vertical slice — 24 REQ-IDs mapped
5-plan MVP slice proposal across 3 waves: Wave 0 lands the three
deferred foundations (BigQty wrapper around break_eternity.js, Zustand
5 store wiring, tick scheduler / monotonic clock). Wave 1 ships two
parallel vertical slices (Begin+Plant+Grow, Harvest+Journal+Fragments).
Wave 2 ships the Lura gate-visit slice and the offline-letter slice
including Playwright PIPE-07 e2e. All 24 REQ-IDs addressed in the
coverage map; 10 architectural patterns enumerated; tick rate locked at
5Hz with 24h offline cap; AudioContext.resume() bootstrap pattern
documented for first-run + returning-player paths; V1Payload extension
shape locked per CONTEXT D-34 (no migrate_v1_to_v2 added). 10
assumptions logged, 8 are LOW risk; 2 are MEDIUM (canvas-DOM coord
mapping under FIT scale, inklecate Windows binary invocation) and
flagged for early verification in Plans 02-02 and 02-04.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 02:01:22 -04:00
josh 350e976fed docs(state): record phase 2 context session
Phase 2 discuss-phase complete; STATE.md now reflects context-gathered
status, updated stopped_at narrative, and next-action pointer to
/gsd-plan-phase 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 01:39:52 -04:00
josh 69964ba17f docs(02): capture phase context
Phase 2 (Season 1 Vertical Slice — Soil) discuss-phase output. 02-CONTEXT.md
captures 34 implementation decisions across 8 gray areas (garden geometry &
input · time density · Lura's arc · letter composition · Begin screen ·
Memory Journal · plant placeholders · Phase-2 Settings UI scope). Locks the
4×4 grid + click+inline seed picker, 2–5min growth band per plant with
auto-harvest-while-offline, 3 Lura gate visits gated by 1st/4th/8th harvest,
authored Ink letter skeleton with templated slots, full-screen letter on
≥5min absence, tasteful placeholder Begin screen (Phase 3 paints), full-
screen Journal modal revealing after first harvest, simple Phaser-primitive
plants with subtle ready-state pulse, save-management-only Settings.
Phase 2's first commits: BigQty wrapper, Zustand 5 store, tick scheduler.
Save schema is a v1 *extension*, not v1→v2.

02-DISCUSSION-LOG.md preserves the alternatives considered for human review.

Next: /gsd-plan-phase 2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 01:39:48 -04:00
12 changed files with 10386 additions and 11 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.
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.
**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
### Phase 3: Watercolor & Cello Aesthetic
+10 -10
View File
@@ -2,9 +2,9 @@
gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: complete
stopped_at: "Phase 1 complete. All 16 REQ-IDs verified (CORE-01, CORE-04..CORE-10, PIPE-01, PIPE-03, PIPE-05, PIPE-06, AEST-08, AEST-09, STRY-09, UX-13). CI chain green: lint + 53 tests + validate-assets + build all exit 0. One known deferred item: 10-20 real north-star reference images (AEST-09 Task 2) recorded in 01-05-IOU.md — Phase 5 follow-up, does not block Phase 2. Next: /gsd-discuss-phase 2."
last_updated: "2026-05-09T00:20:00.000Z"
status: ready_to_execute
stopped_at: "Phase 2 planned. 5 PLAN.md files across 3 waves (Wave 0: 02-01 foundations [BigQty + Zustand store + tick scheduler + V1Payload extension]; Wave 1 parallel: 02-02 begin-plant-grow + 02-03 harvest-journal-fragments; Wave 2 parallel: 02-04 lura-gate-beats + 02-05 letter-settings-e2e). 24/24 REQ-IDs covered in plan frontmatter; 34/34 CONTEXT.md decisions cited across plan must_haves. RESEARCH.md, PATTERNS.md, VALIDATION.md (nyquist_compliant: true) all in place. Plan-checker iterations: 13 issues → 3 issues → 0 issues (BLOCKER 3 lastTickAt-vs-tickCount split was the cross-plan defect; final fix introduced src/save/payload.ts as a shared two-arg helper module). Next: /gsd-execute-phase 2 (Wave 0 must land before Waves 1+2 can start)."
last_updated: "2026-05-09T01:30:00.000Z"
last_activity: 2026-05-09
progress:
total_phases: 8
@@ -21,14 +21,14 @@ progress:
See: .planning/PROJECT.md (updated 2026-05-08)
**Core value:** Every idle mechanic must function as a metaphor that the player absorbs without being told. When economy and meaning conflict, meaning wins.
**Current focus:** Phase 02 — Season 1 Vertical Slice (Soil) — READY TO BEGIN
**Current focus:** Phase 02 — Season 1 Vertical Slice (Soil) — 5 plans ready; ready for `/gsd-execute-phase 2`
## Current Position
Phase: 01 (foundations-and-doctrine) — COMPLETE (verified 2026-05-09)
Plan: 7 of 7 complete
Status: All 16 Phase-1 REQ-IDs verified; CI green; ready for Phase 2
Last activity: 2026-05-09 -- Phase 1 verification complete
Phase: 02 (season-1-vertical-slice-soil) — planned, ready to execute
Plans: 5 of 5 created (3 waves)
Status: All 24 REQ-IDs + 34 CONTEXT.md decisions covered. Plan-checker PASSED after 3 revision iterations. Ready for `/gsd-execute-phase 2`.
Last activity: 2026-05-09 -- Phase 2 plan-phase complete
Progress: [█░░░░░░░░░] 12%
@@ -117,5 +117,5 @@ Items acknowledged and carried forward:
## Session Continuity
Last session: 2026-05-09
Stopped at: Phase 1 verification complete — VERIFICATION.md written, REQUIREMENTS.md updated (16 REQ-IDs marked Complete), STATE.md updated to `status: complete`. All gates green (lint, 53 tests, validate:assets, build, CI chain).
Next action: `/gsd-discuss-phase 2` to begin Season 1 Vertical Slice (Soil)
Stopped at: Phase 2 planning complete — 5 PLAN.md files written across 3 waves; plan-checker PASSED after 3 revision iterations (13 issues → 3 → 0); all 24 REQ-IDs and 34 D-XX decisions covered.
Next action: `/gsd-execute-phase 2` to execute the Season 1 Vertical Slice (Wave 0 first; Waves 1+2 can run in parallel within their wave)
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
@@ -0,0 +1,216 @@
# Phase 2: Season 1 Vertical Slice (Soil) - Context
**Gathered:** 2026-05-09
**Status:** Ready for planning
<domain>
## Phase Boundary
Ship the complete Season 1 (Soil) vertical slice end-to-end on real authored content, with no aesthetic polish required. Player launches → presses **Begin** (gesture-gate calls `AudioContext.resume()`, AEST-07) → places a seed in an empty tile of a 4×4 garden → watches it grow (state survives refresh) → harvests one Season-1 fragment → reads it in a Memory Journal → meets Lura at the gate via Ink-authored dialogue → closes the tab → returns to a *letter from the garden* describing what bloomed → the Playwright e2e smoke proves the whole loop. The simulation advances by elapsed real time (capped at 24h, refusing negative deltas), saves fire on `visibilitychange`/`beforeunload`/Season transitions, and story progression gates on tick count (not wall time).
Phase 2's first commits land the foundations Phase 1 deliberately deferred: the **`BigQty` wrapper around `break_eternity.js`** (per CLAUDE.md "from day one of feature code"), the **Zustand 5 store** wiring scenes ↔ React UI, and the **tick scheduler / monotonic clock** (the only owner of wall-clock access; sim modules stay pure).
**Out of scope for Phase 2 (deferred to later phases):**
- Watercolor post-process, painted plants, solo cello, ambient buses (Phase 3)
- Season transitions, die-off, Roothold prestige, cross-pollination (Phase 4)
- Place-memory vignettes, Memory Storms, the Nameless Man's full arc (Phase 5)
- The Below, ecosystem planting, the Loom, the Archivist (Phase 6)
- Final binary choice + credits/coda rest state (Phase 7)
- Audio sliders (UX-04), keyboard nav (UX-06), browser-zoom guarantees (UX-07), color-redundant icons (UX-08), tab-title bloom (UX-09), Lura-not-numbers UX (UX-12) (Phase 8)
- Visual regression for the asset library (PIPE-04, Phase 8)
**Phase 2 owns 24 REQ-IDs:** CORE-02, CORE-03, CORE-11, GARD-01, GARD-02, GARD-03, GARD-04, MEMR-01, MEMR-02, MEMR-03, MEMR-04, MEMR-05, MEMR-06, STRY-01, STRY-06, STRY-07 (vacuous in S1), STRY-10, AEST-07, UX-01, UX-02, UX-10, UX-11, PIPE-02, PIPE-07.
</domain>
<decisions>
## Implementation Decisions
### Garden Geometry & Input
- **D-01:** Garden is a **4×4 fixed grid (16 tiles)**. Intimate walled-garden feel; matches cozy/contemplative tone and Phase 2's minimum-viable bias. Later Seasons add new surfaces (the Below, etc.) rather than enlarging the grid.
- **D-02:** Seed placement is **click-empty-tile → inline seed picker** (small popover anchored to the clicked tile, listing currently-unlocked plant types; single tap commits). No persistent seed sidebar — honors UX-01 "no UI clutter".
- **D-03:** **23 plant types** ship in Season 1 — enough that the player exercises real choice and different plants gate different fragments via MEMR-06. Each plant has its own growth duration and fragment pool.
- **D-04:** **Infinite seed supply from start** — anti-FOMO. The meaningful constraint is *time* (growth duration, tile count), not seed inventory. Simplest sim/store shape; cleanest cozy framing.
- **D-05:** **First plant available from start; remaining 12 unlock by fragment-count thresholds** harvested. Drip-feed of progression without ceremony. Specific thresholds are Claude's discretion within reason.
- **D-06:** Empty tile look in Phase 2 = **faint outlined tile + subtle hover state** (Phaser primitive draw, no asset work). Phase 3 paints the watercolor treatment.
- **D-07:** Post-harvest tile **returns immediately to empty** with a brief acknowledgement beat (small text snippet, gentle particle, or pause beat — Claude's discretion on form). No "spent" cooldown state in the sim or save schema.
### Time Density (Growth + Offline)
- **D-08:** First-plant active-play growth = **~25 minutes** (sprout → ready). Cozy but watchable; lets a playtester complete the full loop in ~10 minutes while still feeling idle. The 24h offline cap is meaningful, not just theoretical.
- **D-09:** **Per-plant durations vary** (short / medium / longer) within the ~25min band. Lets the player make real time-vs-yield tradeoffs and gives each plant a tonal identity. Specific durations per plant are Claude's discretion; document chosen values in PLAN.md.
- **D-10:** **Auto-harvest during offline; manual in active play.** While the player is away, plants that ripen are auto-harvested and queued into the offline event log; the *letter* narrates them. In active play, the player still chooses when to harvest. Cleanest idle-game shape and gives the letter content to describe.
- **D-11:** **24h offline cap is surfaced silently in the letter's voice** — no numeric "you were gone for 28 hours" copy. The simulation hard-caps the elapsed delta at 24h (CORE-03 + CORE-11); the letter may lightly acknowledge a long absence in voice, but never shows the cap as a system message.
### Lura's Season 1 Arc
- **D-12:** Lura is present as **discrete visits at the gate** — not a persistent chat thread. Matches the bible's framing; each visit is its own Ink scene; bounded content target for Phase 2.
- **D-13:** **3 beats in Season 1: arrival · mid · farewell.** Tight prologue arc; small authoring surface; load-bearing for tone.
- **D-14:** Beats are **gated by fragment-count thresholds** — beat 1 fires after the **1st** harvest; mid-beat after the **4th**; farewell after the **8th**. Counts come from sim ticks, not wall time, so STRY-10 holds (system-clock manipulation cannot fast-forward through beats). Specific thresholds may shift slightly during playtest but the model — fragment-count thresholds — is locked.
- **D-15:** Beat-fire UX = **subtle gate indicator + player-initiated visit.** When a beat unlocks, a soft cue at the gate signals it (glow, mark, or small text); the player walks over (clicks the gate) when they choose. The conversation opens as a **React DOM dialogue overlay** (DOM, not canvas — supports MEMR-05-style selectable text from day one). Honors A Dark Room rule.
- **D-16:** All Lura dialogue is authored in **Ink (`.ink`) under `/content/dialogue/`**, compiled to JSON via `inklecate` (already in devDependencies), runtime-loaded via `inkjs` (already in dependencies). Per STRY-06 + ROADMAP.md.
### Letter-from-the-Garden (UX-02)
- **D-17:** Letter is composed from an **authored skeleton + templated insertions**. Hand-authored Ink passages in voice with named variable slots (e.g., `{plants_bloomed}`, `{fragment_titles}`, `{lura_was_here}`); specifics flow in from the offline event log. Best balance of voice control (Lura-anchor tone is preserved by author) and reactivity (each return reads true to what happened).
- **D-18:** Letter authoring lives in **Ink (`.ink` files in `/content/dialogue/`)** — same authoring layer as Lura's beats. Single narrative-rendering path for Phase 2; reuses STRY-06 stack; no second loader path.
- **D-19:** Save schema gains a small **`offlineEvents`** block: per-plant counts of plants bloomed, list of auto-harvested fragment IDs, and a flag for any newly-unlocked Lura beat queued for first-visit. Compact and bounded; sufficient for the slot vocabulary; cleanly fits inside the existing `V1Payload` shape (see `src/save/migrations.ts`). Phase 2 must update `V1Payload` to include this block — it's a Phase-2 schema *extension*, not a `migrate_v1_to_v2`, because Phase 1's v1 has not shipped any production saves yet.
- **D-20:** Letter triggers on return when **absence ≥ 5 minutes**. Below 5 minutes, the player simply resumes (no letter). At/above the threshold, a **full-screen React DOM overlay** delivers the letter; **one tap dismisses to the live garden**. Threshold avoids letter-spam during active tab-flicking; full-screen treatment honors the tonal weight.
### Begin Screen (AEST-07 + UX-01)
- **D-21:** **Tasteful placeholder; Phase 3 paints.** Phase 2 ships a clean, restrained Begin screen — typographic title + a single "Begin" affordance, calls `AudioContext.resume()` on tap. No painted canvas illustration. Phase 3 swaps in the watercolor gesture-gate without touching the gating logic.
- **D-22:** **Begin screen shows on first run only.** Subsequent loads skip directly to the live garden; `AudioContext` enables on the first interaction (any tile click or gate click counts as a user gesture). "First run" is determined by save existence (no save → first run; save present → returning player). Acknowledged tradeoff: returning players have a brief silent moment until their first interaction; deemed acceptable for cozy-pacing.
### Memory Journal (MEMR-04, MEMR-05)
- **D-23:** Journal affordance **reveals after the player's first harvest**, then is persistent. Pre-harvest, no journal icon at all. Most A-Dark-Room-consistent; the UI grows as the player progresses.
- **D-24:** Journal layout = **full-screen modal overlay** with fragments grouped by Season; a back affordance returns to the live garden. DOM-rendered text per MEMR-05 (selectable, copy-pasteable).
- **D-25:** Newly harvested fragments (in active play) **surface immediately in a full-text reveal modal**; dismissing files them into the journal under their Season. Off-line auto-harvested fragments are surfaced via the *letter* (D-17 .. D-20) and re-readable in the journal afterwards. Creates a memorable beat-per-harvest in active play; preserves the cozy/quiet feel of the offline return.
### Visual Placeholders (Phase 2 only)
- **D-26:** Plants render as **simple Phaser-primitive shapes per growth stage, tinted by plant type**. Sprout = small dot, mature = stem, ready = bloom shape; tint differentiates plant types. No PNG asset work in Phase 2; the architectural firewall stays clean (no asset deps in `src/sim/`). Phase 3 swaps in painted sprites.
- **D-27:** Ready-state cue = **subtle glow / pulse on ready tiles** (Phaser shader pulse or alpha cycle). Reads at a glance without text; works regardless of plant type; Phase 3 paints over with a warmer light treatment.
### Phase 2 Settings UI Scope
- **D-28:** Settings menu in Phase 2 ships **save-management surfaces only** — Export to Base64 (CORE-09 UI), Import from Base64 (CORE-09 UI), Restore previous snapshot (CORE-08 UI). Persistence-result toast (CORE-05 UI) folds in. **Audio sliders + keyboard nav + browser zoom + color-redundant icons stay in Phase 8** (UX-04, UX-06, UX-07, UX-08).
- **D-29:** Settings access = **small icon in a corner of the main view + keyboard shortcut**. Persistent (returning players need to find Export/Import for save recovery); restrained enough not to clutter the cozy surface. Same pattern as the Memory Journal affordance.
- **D-30:** `navigator.storage.persist()` outcome surfaced as a **one-time soft toast in voice on first save if denied; nothing if granted** (e.g., *"the garden may forget, if your browser asks it to"*). Honors UX-01 + cozy tone; respects CORE-05's "surfaces the result respectfully if the browser declines."
### Foundations That Must Land in Phase 2 (per CLAUDE.md)
- **D-31:** **`BigQty` wrapper around `break_eternity.js`** is Phase 2's first task. Lands in `src/sim/numbers/` (or similar firewall-respecting location). All economy values from this point forward go through `BigQty`. Even if Season 1's actual scaling never demands `break_eternity`, the wrapper is in place day-one of feature code per CLAUDE.md.
- **D-32:** **Zustand 5 store** is the bridge between the Phaser scene and React UI surfaces (Begin screen, Memory Journal, Settings, Letter, Lura dialogue). Sim writes to the store; React reads from it. Sim never imports the store directly — Phase 1's ESLint boundaries (CORE-10) enforce this. Exact slice shape is Claude's discretion.
- **D-33:** **Tick scheduler / monotonic clock** is the *only* owner of wall-clock access. Sim modules in `src/sim/` stay pure (no `Date.now`, no `setInterval`); the scheduler injects `currentTime` into sim updates. Tick rate is Claude's discretion (likely 410Hz for sim updates, decoupled from `requestAnimationFrame`-driven render). Negative deltas are refused at the scheduler boundary (CORE-11); single offline catch-up clamps to 24h (CORE-03 + CORE-11).
- **D-34:** **Save extension for Phase 2** updates `V1Payload` (in `src/save/migrations.ts`) to include the Phase-2-needed fields: garden tiles with plant state, plants array with growth-state + plantedAt-tick, harvested fragment IDs (already declared), `lastTickAt` (already declared), Lura-beat progress flags, `offlineEvents` block (D-19), settings (already declared). This is a Phase-2-scope schema *extension* (not a v1→v2 migration) because Phase 1's v1 envelope has shipped no production saves; the synthetic v0→v1 demo migration in `migrations[1]` continues to work.
### Claude's Discretion
- Specific growth-duration values per plant type within the 25min band (D-08 / D-09).
- Exact fragment-count threshold values for plant-type unlocks (D-05) and Lura beats — the model is locked (1st/4th/8th) but Claude may adjust by ±12 during playtest.
- Form of the post-harvest acknowledgement beat (text snippet, particle, pause — D-07).
- Form of the gate indicator when Lura's beat unlocks (D-15) — soft glow, mark, or small text.
- Tick rate / sim cadence (likely 410Hz; not 60Hz — sim should be cheap).
- Internal shape of the Zustand store slices.
- Internal shape of the scene/state machine inside Phaser (Boot → Preloader → Garden, or simpler).
- Specific copy of the tasteful Begin screen, the persistence-denied toast, and the post-harvest acknowledgement (all must match the bible voice; user reviews).
- Choice of e2e test fast-forward mechanism (hidden dev hotkey vs URL flag vs sim-clock injection — Phase 2 needs *some* way for Playwright to fast-forward growth in PIPE-07).
- Specific copy of the Memory Journal "no fragments yet" empty state (if shown pre-first-harvest in any flow).
- Whether the offline letter's slot vocabulary is finalized in this phase or expanded incrementally (Lura's variable can be the simplest first slot; more added if needed).
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Project-Level Source of Truth
- `.planning/PROJECT.md` — Story bible synthesis, core value ("every idle mechanic must function as a metaphor"), hard thematic constraints, Out of Scope, Key Decisions table.
- `.planning/REQUIREMENTS.md` — 77 v1 requirements with REQ-IDs. Phase 2 owns 24 (see Phase Boundary above for the complete list).
- `.planning/ROADMAP.md` §"Phase 2: Season 1 Vertical Slice (Soil)" — 5 success criteria. The plan must satisfy each.
- `.planning/STATE.md` — current position; Phase 1 verification table; carry-forward concerns.
- `CLAUDE.md` — stack lock, architectural firewall, banner concerns 110, hard thematic constraints, code style rules ("BigQty from day one of feature code", externalized strings, stable string IDs, no Date.now in sim).
### Phase 1 Outputs (load-bearing for Phase 2)
- `.planning/phases/01-foundations-and-doctrine/01-CONTEXT.md` — Phase 1 decisions (D-01..D-12) that constrain Phase 2 (save format locked, content pipeline locked, firewall locked, doctrine docs locked).
- `.planning/phases/01-foundations-and-doctrine/01-VERIFICATION.md` — Phase 1 PASS evidence; Phase 2 builds on every green REQ here.
- `.planning/anti-fomo-doctrine.md` — banned UX patterns; review checklist. **MUST be consulted at every UX decision in Phase 2.**
- `.planning/season-7-end-state.md` — the principle-level rest-state contract. Phase 2's growth-curve and Roothold-precursor decisions must not preclude the finite ceiling tied to authored content.
### Research Layer
- `.planning/research/SUMMARY.md` — stack at a glance, banner concerns, Phase 2 rationale (vertical slice that could plausibly ship as a free standalone prologue).
- `.planning/research/STACK.md` — stack rationale; especially the React-19-DOM-overlay vs Phaser-canvas split, Zustand 5 bridge, Howler.js, Ink/inkjs, idb + lz-string.
- `.planning/research/ARCHITECTURE.md` — three-layer firewall (sim → application → presentation); tick scheduler shape; save format `{schemaVersion, payload, checksum}`.
- `.planning/research/PITFALLS.md` — 14 critical pitfalls. Phase 2 directly hits #1 (story-ends-but-loop-doesn't — vertical-slice prologue must not foreclose Season 7's rest state), #2 (system-clock cheating — STRY-10 + CORE-11), #4 (tab throttling — CORE-02 + UX-10), #6 (Web Audio user-gesture — AEST-07 + Begin screen), #7 (offline catch-up — CORE-03), #10 (content/code divergence — MEMR-02 + MEMR-03 + STRY-09), #12 (anti-FOMO — UX-13 holds for every decision in Phase 2).
- `.planning/research/FEATURES.md` — must-have / should-have / defer / anti-feature classification. The "should-have" classification for Season 1 is the input to Phase 2's content authoring scope.
### Phase 1 Code Surfaces Phase 2 Will Consume
- `src/save/index.ts` — public barrel of the save layer (Phase 2 imports ONLY from this file).
- `src/save/migrations.ts``V1Payload` shape and migration registry. **Phase 2 extends `V1Payload` per D-34.**
- `src/save/envelope.ts` + `src/save/codec.ts` + `src/save/db.ts` + `src/save/snapshots.ts` + `src/save/persist.ts` — internal modules; Phase 2 does not import these directly.
- `src/content/loader.ts` + `src/content/schemas/index.ts` — Vite-native content pipeline. Phase 2 adds Season 1 fragment files under `/content/seasons/01-soil/` and Lura's Ink scenes under `/content/dialogue/`.
- `src/content/index.ts` — public barrel of the content layer.
- `src/game/main.ts` + `src/game/scenes/Boot.ts` — Phaser entry. Phase 2 expands the scene tree (Boot → Preloader → Garden, or simpler).
- `src/App.tsx` + `src/PhaserGame.tsx` — React shell + Phaser bridge. Phase 2 adds the Begin screen, HUD, Settings, Memory Journal, Lura dialogue overlay, and Letter overlay as React components beside the `<PhaserGame>` mount.
- `eslint.config.js` (Phase 1's flat config with `eslint-plugin-boundaries`) — enforces sim ↔ render/ui firewall. Phase 2 code MUST pass lint.
- `package.json scripts.ci``npm run ci` is the CI gate; Phase 2 plans must keep `npm run ci` green at every commit.
### Content Conventions
- `content/README.md` — content authoring conventions (stable string IDs, frontmatter shape, file location norms).
- `content/seasons/00-demo/fragments.yaml` — demo fragment from Phase 1. **Phase 2 removes this file** when Season 1 (`/content/seasons/01-soil/`) is authored.
### Phase 2 New Outputs (will be created during this phase)
- `/content/seasons/01-soil/` — real Season 1 fragments (Markdown + frontmatter, per MEMR-02). Fragment count and per-plant pool sizes are part of Phase 2 planning.
- `/content/dialogue/season1/` — Lura's 3 Ink scenes (arrival, mid, farewell) + the *letter from the garden* Ink passage with templated slots.
- `src/sim/numbers/big-qty.ts` (or similar) — `BigQty` wrapper around `break_eternity.js` (D-31).
- `src/store/` — Zustand 5 slices (D-32).
- `src/sim/scheduler/` — tick scheduler / monotonic clock (D-33). Single owner of wall-clock access.
- `src/sim/garden/` — garden tile state, plant state machine, harvest logic (sim-only, no DOM, no Date.now).
- `src/sim/narrative/` — Lura beat-gating logic (sim-side; reads fragment-count from sim state, not narrative-string content).
- `src/render/garden/` — Phaser scene rendering tiles, plants, ready-state pulse, gate.
- `src/ui/begin/`, `src/ui/journal/`, `src/ui/dialogue/`, `src/ui/letter/`, `src/ui/settings/` — React DOM overlays.
- `tests/e2e/` — Playwright smoke (PIPE-07) covering load → dismiss begin → plant → fast-forward → harvest → journal-shows-fragment → reload → fragment-persists.
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets (from Phase 1)
- **Save layer is complete and stable** (`src/save/`). Phase 2 imports `wrap`, `unwrap`, `migrate`, `snapshot`, `listSnapshots`, `requestPersistence`, `exportToBase64`, `importFromBase64`, `openSaveDB`, `LocalStorageDBAdapter`, `crc32hex`, `canonicalJSON` from `src/save/index.ts`. The `V1Payload` interface is the contract; Phase 2 extends it (D-34).
- **Content pipeline is Vite-native** (`src/content/loader.ts`). Phase 2 drops Markdown fragments under `/content/seasons/01-soil/fragments/*.md` and they're picked up by the existing glob; schema violations fail the build.
- **ESLint boundary rule** (`eslint.config.js`) enforces `src/sim/``src/render/`/`src/ui/` firewall. Phase 2 sim code must be pure.
- **Vitest + Playwright** are already wired (`vitest.config.ts`, `playwright.config.ts`). Phase 2 adds tests; the CI script (`npm run ci`) already runs them.
- **CRC-32 checksum + canonical JSON** for save integrity.
- **`fake-indexeddb`** is pre-installed for test environments.
- **Asset provenance gate** (`scripts/validate-assets.mjs`) — Phase 2 ships placeholder shapes via Phaser primitives, no PNG assets, so the gate is not exercised. (D-26.)
### Established Patterns (from Phase 1)
- **Single public barrel per layer** (`src/save/index.ts`, `src/content/index.ts`). Phase 2's `src/store/`, `src/sim/`, `src/render/`, `src/ui/` should each expose a `index.ts` barrel; cross-layer imports go through barrels only.
- **Stable string fragment IDs** (e.g., `season1.soil.lura_01.greeting`) — never numeric. Validated by Zod schema in `src/content/schemas/`.
- **Player-visible strings live in `/content/`**, not in TS.
- **Doc-lint pattern** (Vitest assertions over Markdown structure) — Phase 2 may extend for Lura's Ink scenes if structural invariants emerge.
- **Plan-bundle pattern** — each plan in `01-foundations-and-doctrine/` shipped a SUMMARY.md alongside its PLAN.md. Phase 2 plans should follow.
### Integration Points
- **Phaser scene ↔ React overlays via Zustand 5** (D-32). Sim writes; React reads. Phaser scenes should NOT import React; React should NOT import Phaser scenes — both go through the store.
- **Tick scheduler ↔ Phaser game loop.** The sim tick runs at its own cadence (D-33); Phaser's `requestAnimationFrame` drives rendering. The scheduler dispatches sim updates from the Phaser scene's `update()` hook (or an independent loop) but is the only place `Date.now()` is allowed.
- **`visibilitychange` + `beforeunload` save triggers** wire to the save layer's `wrap` + IndexedDB write path. Phase 2 must ensure save serialization is fast enough to complete in `beforeunload`'s tight window.
- **Begin screen → AudioContext.resume()** is the bootstrapping handshake for Howler.js (Phase 3 actually uses Howler — Phase 2 may stub or no-op the audio bus while still calling `resume()`).
- **Ink → inkjs runtime path.** `inklecate` (devDependencies) compiles `.ink``.json` at build time; `inkjs` (dependencies) loads the JSON at runtime. Phase 2 establishes this pipeline (it's a no-op stub today per `package.json scripts.compile:ink`).
- **Memory Journal text rendering** must be DOM (React), not canvas (Phaser) — MEMR-05 demands selectable/copyable text.
</code_context>
<specifics>
## Specific Ideas
- **Tone discipline:** the user pushed back on Phase 1 ceremony and prefers minimum-viable infrastructure. Apply the same lens to Phase 2: do not build a registry, framework, or DSL where a typed function table will do; do not pre-allocate scaffolding for Phase 4+ Roots/Canopy/Storm mechanics.
- **A Dark Room rule across every UX decision:** Begin screen has no clutter; Memory Journal affordance only appears after first harvest; Settings is a small corner icon; persistence-denied is a soft toast in voice. The UI grows as the player progresses.
- **Letter is a tonal load-bearing surface, not a stat dump.** UX-02 is explicit: "written in voice, not a stat dump." The authored Ink skeleton is what holds the voice; the slots are what make it true to what happened. Reviewer should read the letter Ink as if it were short fiction, not as if it were copy.
- **The vertical slice could plausibly ship as a free standalone prologue.** This is the project's escape hatch against the 7-Season scope risk (banner concern #2). Phase 2 must complete the loop end-to-end so that, in the worst case, this slice could go live alone. That includes: a satisfying first-pass arc shape (Lura's 3 beats), a real letter, working save/restore, working e2e smoke.
- **Architecture firewall is non-negotiable.** Sim is pure; render/UI talk to sim only via the Zustand store. ESLint enforces. Tick scheduler is the *only* place wall-clock enters the sim.
- **Save-schema extension, not migration.** Phase 1's `V1Payload` has not shipped any production saves; Phase 2 extends the v1 payload shape rather than adding `migrate_v1_to_v2`. The first real migration lands in Phase 4 (per Phase 1 D-04). The synthetic v0→v1 demo migration in `migrations[1]` continues to work as the proof-of-chain.
- **Tick-count gating, not wall-time gating, for narrative beats.** STRY-10 is satisfied because Lura's fragment-count thresholds count *harvest events* (which are sim ticks), not minutes elapsed. A player who manipulates their system clock cannot fast-forward through Lura's beats.
</specifics>
<deferred>
## Deferred Ideas
Items mentioned during discussion that belong in other phases:
- **Hybrid Lura presence (gate visits + ambient text-message drip)** — discussed and rejected for Phase 2 in favor of pure discrete-gate-visits (D-12). May be reconsidered in Phase 4+ if the narrative density warrants it.
- **Plant-type unlocks tied to specific authored fragments** — discussed and rejected for Phase 2 in favor of fragment-count thresholds (D-05). Phase 4+ may explore narrative-keyed unlocks.
- **Fully procedural letter from event-log templates** — discussed and rejected (D-17). Phase 2 commits to authored skeleton + slots. If the slot vocabulary turns out to be too small in playtest, expand in Phase 4 (longer Seasons, more event types).
- **Audio sliders (UX-04), keyboard nav (UX-06), browser-zoom guarantees (UX-07), color-redundant icons (UX-08), tab-title bloom (UX-09), Lura-not-numbers UX (UX-12)** — all confirmed for **Phase 8** (D-28). Settings menu in Phase 2 is save-management only.
- **Visual regression for asset library (PIPE-04)** — Phase 8.
- **Roothold prestige currency, Season transitions, die-off, finite ceiling enforcement** — Phase 4 (Season-Prestige Cycle & Season 2). Phase 2 plants nothing in the save schema for Roothold; Phase 4 owns that addition.
- **Cross-pollination, Memory Storms, place-memory vignettes, ecosystem planting, the Below, the Loom, the Archivist, Lura's full multi-Season arc, the Nameless Man** — Phase 47.
- **Watercolor post-process, painted plants, painted Begin screen, solo cello + ambient buses + crossfade, reduced-motion toggle (UX-05)** — Phase 3 (Watercolor & Cello Aesthetic).
- **Real production-volume AI assets + locked north-star reference set (Phase 1 IOU AEST-09)** — Phase 5 follow-up. Phase 2 ships zero AI-generated assets (D-26 = primitive shapes); the Phase-1 IOU is unblocked, not unblocking Phase 2.
- **Real `migrate_v1_to_v2`** — Phase 4 (when Roothold / Season-prestige state actually lands). Phase 2 only extends `V1Payload` shape (D-34); no new migration entry is added.
- **Per-plant duration variance via dynamic content authoring (e.g., player-modifiable growth times)** — out of scope; not a v1 capability; not in REQUIREMENTS.md.
- **Compost yielding seeds back** — discussed as a possible scarcity mechanic and rejected (D-04 = infinite seeds). Phase 4's cross-pollination introduces hybrid seeds, which is the proper place for seed-as-economy.
- **Persistent Settings element on Begin screen** — discussed as alt access pattern; rejected in favor of in-garden corner icon + hotkey (D-29).
</deferred>
---
*Phase: 2-season-1-vertical-slice-soil*
*Context gathered: 2026-05-09*
@@ -0,0 +1,311 @@
# Phase 2: Season 1 Vertical Slice (Soil) - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-05-09
**Phase:** 2 — Season 1 Vertical Slice (Soil)
**Areas discussed:** Garden geometry & input · Time density (growth + offline) · Lura's Season 1 arc shape · Letter-from-the-garden composition · Begin screen treatment · Memory Journal access + layout · Visual placeholder treatment for plants · Phase 2 Settings UI scope
---
## Garden Geometry & Input
### Garden shape
| Option | Description | Selected |
|--------|-------------|----------|
| Small fixed grid (4×4 = 16 tiles) | Intimate walled-garden feel; matches cozy/contemplative tone and Phase 2 minimum-viable bias. | ✓ |
| Medium fixed grid (6×6 = 36 tiles) | More room to experiment and more strategic depth, but Season 1 will feel emptier with sparse early plantings and authored-content scope grows. | |
| Freeform / non-grid (57 named planting points) | Bespoke, painterly walled-garden feel. More work upfront and harder to cleanly extend in Seasons 2+. | |
### Seed placement input
| Option | Description | Selected |
|--------|-------------|----------|
| Click empty tile → inline seed picker | Clicking an unoccupied tile pops a small inline picker. Honors UX-01 'no UI clutter'. | ✓ |
| Persistent seed sidebar + click empty tile | More discoverable but introduces a permanent UI element that fights the A Dark Room rule. | |
| Drag-from-inventory to tile | Tactile, but more failure modes and more Phase 3 polish to feel right. | |
### Plant type count
| Option | Description | Selected |
|--------|-------------|----------|
| 23 plant types | Player exercises real choice; small enough to author + concept-art under Phase 2's minimum-viable bias. | ✓ |
| Just 1 plant type | Most minimum-viable possible; risks slice feeling like a single-mechanic demo. | |
| 46 plant types | Richer Season 1 with more variety, but ≈4× the authored-content + concept-art surface. | |
### Seed supply
| Option | Description | Selected |
|--------|-------------|----------|
| Infinite seeds from start | Anti-FOMO; meaningful constraint is *time*, not seed inventory. | ✓ |
| Harvest yields seeds + fragment | Adds a light economy; risks turning the cozy slice into a resource-management loop. | |
| Limited starter seeds + replenish via composting | Couples composting and planting tightly; risks player feeling soft-locked. | |
### Plant unlock progression
| Option | Description | Selected |
|--------|-------------|----------|
| First plant from start, others unlock by fragment-count | Drip-feeds discovery without ceremony. | ✓ |
| All available from start | Simplest sim; loses small but real beat of progression. | |
| Unlocks tied to specific authored fragments | Most narrative; brittle if deterministic selector reorders things. | |
### Empty tile look in Phase 2
| Option | Description | Selected |
|--------|-------------|----------|
| Faint outlined tile + subtle hover state | Readable but unembellished; Phaser primitive; no asset work. | ✓ |
| Visible 'empty plot' placeholder texture | More legible at a glance, but bakes in a visual Phase 3 will throw away. | |
| Invisible tiles (only become visible on hover) | Maximum tonal restraint; risks confusing first-time players. | |
### Post-harvest tile state
| Option | Description | Selected |
|--------|-------------|----------|
| Returns to empty + brief acknowledgement | Fastest loop, cleanest sim, honors cozy 'release' feel. | ✓ |
| Leaves a 'spent' state for a short cooldown | Adds an extra plant lifecycle state to sim and save schema. | |
| Fragment must be 'taken to the journal' first | Tactile but couples sim and UI tightly and adds dangling state. | |
---
## Time Density (Growth + Offline)
### First-plant growth duration in active play
| Option | Description | Selected |
|--------|-------------|----------|
| ~25 minutes | Cozy but watchable; lets a playtest session complete the loop in ~10 minutes. | ✓ |
| ~30 seconds 2 minutes | Demo-pace; reads less 'idle'. | |
| ~1560 minutes | True idle; hard to playtest end-to-end without a debug fast-forward. | |
| ~612 hours (long-form idle) | Anchors 24h cap; risks empty active play; demands strong return-screen. | |
### Per-plant duration variance
| Option | Description | Selected |
|--------|-------------|----------|
| Vary (short / medium / longer) within band | Real time-vs-yield tradeoffs; tonal identity per plant. | ✓ |
| All identical growth times | Simplest sim; loses 'patience-is-rewarded' beat. | |
| All identical for Phase 2; vary later | Avoids re-litigating; means Season 1 plants feel interchangeable. | |
### Ready-state behavior
| Option | Description | Selected |
|--------|-------------|----------|
| Auto-harvest during offline; manual in active play | Cleanest idle-game shape; lets the letter tell a story. | ✓ |
| Wait indefinitely — manual always | Maximum agency; offline payoff feels thin. | |
| Decay if unharvested past a threshold | Adds tension and soft FOMO undercurrent — likely runs into anti-FOMO doctrine. | |
### 24h offline cap surfacing
| Option | Description | Selected |
|--------|-------------|----------|
| Capped silently in the letter's voice | Anti-FOMO; tonally consistent. | ✓ |
| Explicit cap notice in the letter | Honest about the cap; risks reading as a system message. | |
| Cap silently and never mention it | Cleanest tone; player returning after 3 days might be confused. | |
---
## Lura's Season 1 Arc Shape
### Lura form
| Option | Description | Selected |
|--------|-------------|----------|
| Discrete visits at the gate | Bounded each visit; matches bible's 'she appears at the garden gate' framing. | ✓ |
| Single ongoing text-message thread | More 'friend texting,' but loses gate-arrival beat. | |
| Hybrid: gate + occasional texts between visits | Two narrative-state mechanisms; risks Phase-2 scope creep. | |
### Beat count
| Option | Description | Selected |
|--------|-------------|----------|
| 3 beats: arrival · mid · farewell | Tight Season 1 arc; small authoring surface; load-bearing for tone. | ✓ |
| 5 beats (arrival, two mid, late, farewell) | More texture; doubles authoring + Ink-state plumbing surface. | |
| Just 1 arrival beat in Phase 2 | Simplest; Season 1 loses tonal anchor and prologue feels unfinished. | |
### Beat gating
| Option | Description | Selected |
|--------|-------------|----------|
| Fragment count thresholds (1st / 4th / 8th harvest) | Simple, content-coupled, robust to varying play sessions. STRY-10 satisfied. | ✓ |
| Specific authored-fragment IDs unlock specific beats | Most narratively rich; brittle if selector reorders things. | |
| Pure tick-count thresholds | Cleanest against STRY-10; least responsive to player action. | |
### Beat-fire UX
| Option | Description | Selected |
|--------|-------------|----------|
| Subtle gate indicator + player-initiated visit | Honors A Dark Room rule and player's pace. | ✓ |
| Auto-opens dialogue when beat fires | Disrupts gardening flow; less cozy. | |
| Queues silently; player finds Lura by clicking gate when curious | Most A-Dark-Room; risks player missing all 3 beats. | |
---
## Letter-from-the-Garden Composition
### Composition method
| Option | Description | Selected |
|--------|-------------|----------|
| Authored skeleton + templated insertions | Best balance of voice and reactivity. | ✓ |
| Fully procedural from event-log templates | Risks tonal drift; voice depends on templates not authoring. | |
| Fully hand-authored prose, conditional inclusion only | Maximum voice control; least reactive. | |
### Authoring format
| Option | Description | Selected |
|--------|-------------|----------|
| Ink (.ink files in /content/dialogue/) | Reuses STRY-06 stack — one runtime path, one tooling path. | ✓ |
| Markdown with frontmatter (in /content/letters/) | Aligns with fragment authoring; adds a second narrative-rendering path. | |
| Both — Ink for branching, Markdown for prose passages | Maximum power; introduces a third loader path. | |
### Offline event log scope
| Option | Description | Selected |
|--------|-------------|----------|
| Compact summary (counts + IDs of bloomed/harvested + any unfired Lura beat) | Compact, sufficient, bounded in size. | ✓ |
| Full ordered timeline (every state transition with timestamp) | Most powerful; biggest schema surface and storage cost. | |
| Just last/biggest event(s) | Smallest schema; letter has less to say across multiple returns. | |
### Letter UX (when shown / dismiss)
| Option | Description | Selected |
|--------|-------------|----------|
| On return after ≥5 minutes; full-screen overlay; one tap to dismiss | Avoids letter-spam; full-screen honors tonal weight. | ✓ |
| Always show on tab return regardless of duration | Most consistent; risks feeling intrusive. | |
| Inline pane (slides in from a corner) | Less disruptive; loses 'sit with this' tonal beat. | |
---
## Begin Screen Treatment
### Phase 2 vs Phase 3 split
| Option | Description | Selected |
|--------|-------------|----------|
| Tasteful placeholder; Phase 3 paints | Honors no-aesthetic-polish bias; avoids Phase 3 rework. | ✓ |
| Real painted gesture-gate now | Strongest first impression today; pulls Phase 3 work earlier and risks scope creep. | |
| Title text only (no visual treatment) | Most-A-Dark-Room; risks reading as unfinished. | |
### Subsequent-load behavior
| Option | Description | Selected |
|--------|-------------|----------|
| Every load (browser autoplay policy demands it) | Honest with browser constraints; Recommended option. | |
| Only first run; subsequent loads skip to garden + audio enables on first interaction | Smoother return; brief silent moment until first interaction. | ✓ |
| Only first run; explicit 'enable sound' prompt later | Two surfaces to maintain; risks system-message feel. | |
**Notes:** User picked the smoother-return path, accepting the brief silent moment until the first interaction as a worthwhile tradeoff for cozy pacing. CORE-05 + AEST-07 are both still satisfied — the gesture happens on first run; subsequent loads use any first interaction as the gesture.
---
## Memory Journal Access + Layout
### Open mechanism
| Option | Description | Selected |
|--------|-------------|----------|
| Small persistent icon + keyboard shortcut | Most discoverable without violating UX-01; Recommended option. | |
| Reveals after first harvest, then persistent | Most A-Dark-Room (UI grows as player progresses). | ✓ |
| Hidden until journal hotkey is discovered | Maximum tonal restraint; high risk players miss it. | |
### Layout
| Option | Description | Selected |
|--------|-------------|----------|
| Slide-in side panel; click a fragment to expand inline | Garden stays visible; lets player feel rooted; Recommended option. | |
| Full-screen modal overlay; back button to return to garden | More tonal weight per visit; player can't see plants ripening while reading. | ✓ |
| Dedicated 'journal view' you navigate to, like a separate room | Most narratively rich; biggest implementation lift. | |
### New-fragment surfacing in active play
| Option | Description | Selected |
|--------|-------------|----------|
| Immediate full-text reveal modal; dismiss to return to garden | Honors harvest as a small event; creates a memorable beat. | ✓ |
| Quiet deposit — small acknowledgement, find it in journal later | Most A-Dark-Room; risks player never reading the prose. | |
| Inline reveal at the harvested tile; tap to dismiss | Tactile but fragile (DOM positioned over Phaser canvas resizes). | |
---
## Visual Placeholder Treatment for Plants
### Plant look per growth stage
| Option | Description | Selected |
|--------|-------------|----------|
| Simple Phaser-primitive shape per stage, single color per type | No PNG asset work; firewall stays clean; Phase 3 swaps in painted sprites. | ✓ |
| Programmer-art sprite per stage per plant (PNG placeholders) | Differentiates more legibly; produces throwaway assets that go through provenance gate. | |
| Text labels at the tile instead of any visual | Most A-Dark-Room; reads as severe. | |
### Ready-state cue
| Option | Description | Selected |
|--------|-------------|----------|
| Subtle glow / pulse on ready tiles | Reads at a glance without text; Phase 3 paints over with warmer light. | ✓ |
| Bright color shift on the placeholder shape | Most legible; bakes a specific color Phase 3 will rework. | |
| Text indicator at tile ('ready') | Very explicit; least cozy. | |
---
## Phase 2 Settings UI Scope
### Settings UI scope
| Option | Description | Selected |
|--------|-------------|----------|
| Save management only (Export/Import + Restore prior snapshot) | Smallest scope that completes deferred Phase-1 surfaces; audio + accessibility stay in Phase 8. | ✓ |
| Save management + audio sliders | Doesn't fit 'no aesthetic polish' framing; risks dragging in keyboard nav and accessibility. | |
| No settings menu at all in Phase 2 | Smallest Phase-2 lift; deferred items still aren't player-reachable. | |
### Access
| Option | Description | Selected |
|--------|-------------|----------|
| Small icon in a corner of the main view + keyboard shortcut | Same restraint pattern as Memory Journal affordance. | ✓ |
| Hidden in a 'cog' on Begin screen + accessible from Lura's gate | Risks burying critical save-recovery affordances. | |
| Only accessible via hotkey (no visual affordance) | Risks players losing access to save recovery in a crash recovery moment. | |
### `navigator.storage.persist()` outcome surfacing
| Option | Description | Selected |
|--------|-------------|----------|
| One-time soft toast on first save if denied; nothing if granted | Honors UX-01 + cozy tone; respects 'surfaces the result respectfully'. | ✓ |
| Always show 'persistent storage: yes/no' indicator in Settings only | Player won't know save is at risk if persistence denied. | |
| Modal dialog if denied | Strongest call to attention; reads anti-cozy. | |
---
## Claude's Discretion
Areas where the user explicitly deferred to implementation choice (full list in CONTEXT.md `<decisions>` § "Claude's Discretion"):
- Specific growth-duration values per plant within the 25min band.
- Exact fragment-count thresholds for plant unlocks and Lura beats (model is locked, values may shift ±12 in playtest).
- Form of the post-harvest acknowledgement beat (text / particle / pause).
- Form of Lura's gate indicator on beat-unlock.
- Tick rate / sim cadence (likely 410Hz).
- Internal Zustand store slice shapes.
- Internal Phaser scene tree.
- Specific copy of Begin screen, persistence-denied toast, post-harvest acknowledgement (must match bible voice; user reviews).
- e2e fast-forward mechanism (hidden hotkey vs URL flag vs sim-clock injection).
- Memory Journal empty-state copy.
## Deferred Ideas
Items mentioned during discussion that belong in other phases (full list in CONTEXT.md `<deferred>`):
- Hybrid Lura presence (gate + ambient drip) — possibly Phase 4+.
- Plant-type unlocks tied to specific authored fragments — possibly Phase 4+.
- Fully procedural letter from event-log templates — reconsider if slot vocabulary too small in playtest.
- Audio sliders, keyboard nav, browser-zoom guarantees, color-redundant icons, tab-title bloom, Lura-not-numbers UX — Phase 8.
- Visual regression for asset library — Phase 8.
- Roothold prestige, Season transitions, die-off, finite ceiling enforcement, cross-pollination — Phase 4.
- Memory Storms, place-memory vignettes, Nameless Man's full arc — Phase 5.
- The Below, ecosystem planting, the Loom, the Archivist — Phase 6.
- Final binary choice + credits/coda rest state — Phase 7.
- Watercolor post-process, painted plants, painted Begin screen, solo cello + ambient buses, reduced-motion toggle — Phase 3.
- Real production-volume AI assets + locked north-star reference set — Phase 5 (Phase 1 IOU AEST-09).
- Real `migrate_v1_to_v2` — Phase 4 (when Roothold lands). Phase 2 only extends `V1Payload` shape.
- Compost yielding seeds back — rejected; Phase 4's cross-pollination is the proper place for seed-as-economy.
- Persistent Settings element on Begin screen — rejected in favor of in-garden corner icon + hotkey.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,110 @@
---
phase: 2
slug: season-1-vertical-slice-soil
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 declare automated verification commands or a Wave-0 dependency.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **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; ~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 |
---
## Sampling Rate
- **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:** ≤8 seconds for Vitest; ≤30s for Playwright
---
## Per-Task Verification Map
> 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 |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| 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*
---
## Wave 0 Requirements
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/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. Plans 02-02..02-05 declare a Wave-0 dependency in frontmatter (`depends_on: [02-01, ...]`).*
---
## Manual-Only Verifications
| 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 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
- [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:** approved (planner)