docs(02-03): complete harvest-journal-fragments plan
Plan 02-03 (Wave 1 second plan) executed in sequential mode. 3 atomic commits + this metadata commit: -f192e82— Season-1 fragments + sim/memory selector + harvest/compost -572c861— journal + reveal modal + harvest pointer wiring -39bfcd2— scripts/check-bundle-split.mjs (PIPE-02 structural verifier) Outcomes: - 217/217 tests green (was 163; +54 new this plan) - npm run ci exits 0 with check:bundle-split integrated AFTER build - GARD-03 / GARD-04 / MEMR-01..06 / PIPE-02 satisfied end-to-end - The full Season-1 active-play loop works on real authored content: plant → grow → ready → click → harvest (deterministic, gated, no-dup, sentinel fallback for Pitfall 8) → reveal modal pops with full text → close → fragment files into journal under Season 1 → journal icon appears (D-23 first-harvest gate, invisible before) → click opens full-screen Memory Journal grouped by Season (D-24, MEMR-05 selectable DOM) - 17 Season-1 fragments authored in bible voice (9 warm + 3 contemplative + 2 heavy + 1 _meta sentinel + 2 long-form Markdown) - Plant-type unlock thresholds finalized (Plan author's discretion within D-05): rosemary @ 0 / yarrow @ 3 / winter-rose @ 6. Pitfall 10 boundaries pinned (locked at 2/5, unlocked at 3/6). - Pool-exhaustion sentinel chosen over repeat-most-recent — preserves no-dup invariant; warm pool depth ≥9 makes the sentinel structurally unreachable in normal Phase-2 play - compost-acknowledgements.ink content shipped ahead of Plan 02-04's Ink runtime; Garden.ts has TODO at the wiring point - PIPE-02 structurally verified by scripts/check-bundle-split.mjs (Vitest- importable Node ESM with runCheck() export) Phase 2 progress: 3/5 plans complete (Wave 0 + both Wave 1 plans). Wave 2 (02-04 lura-gate-beats + 02-05 letter-settings-e2e) is the only remaining Phase-2 work. SUMMARY at .planning/phases/02-season-1-vertical-slice-soil/02-03-harvest-journal-fragments-SUMMARY.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-18
@@ -26,8 +26,9 @@ Requirements for initial release. Each maps to roadmap phases. All are user-cent
|
|||||||
|
|
||||||
- [x] **GARD-01**: Player can plant a seed from their seed inventory into an unoccupied tile of the garden. <!-- Plan 02-02: sim/garden plantSeed command (D-05 unlock-gate + occupied silent no-op + immutability) + SeedPicker DOM popover + Garden scene pointerdown → store.enqueueCommand wiring; 14 commands tests + 6 SeedPicker tests green. -->
|
- [x] **GARD-01**: Player can plant a seed from their seed inventory into an unoccupied tile of the garden. <!-- Plan 02-02: sim/garden plantSeed command (D-05 unlock-gate + occupied silent no-op + immutability) + SeedPicker DOM popover + Garden scene pointerdown → store.enqueueCommand wiring; 14 commands tests + 6 SeedPicker tests green. -->
|
||||||
- [x] **GARD-02**: Each plant has a visible growth state (sprout → mature → ready-to-harvest) that updates from save data on load and advances over time. <!-- Plan 02-02: advanceGrowth pure function (sprout→mature@33%→ready@100%) + render/garden/plant-renderer primitives per stage + Garden scene appStore.subscribe drives reactive repaintPlants; 11 growth tests green. Save-load tickCount restore is in Garden.create(). -->
|
- [x] **GARD-02**: Each plant has a visible growth state (sprout → mature → ready-to-harvest) that updates from save data on load and advances over time. <!-- Plan 02-02: advanceGrowth pure function (sprout→mature@33%→ready@100%) + render/garden/plant-renderer primitives per stage + Garden scene appStore.subscribe drives reactive repaintPlants; 11 growth tests green. Save-load tickCount restore is in Garden.create(). -->
|
||||||
- [ ] **GARD-03**: Player can harvest a mature plant to receive a memory fragment; harvesting empties the tile.
|
- [x] **GARD-03**: Player can harvest a mature plant to receive a memory fragment; harvesting empties the tile. <!-- Plan 02-03: sim/garden harvest() pure command — refuses immature plants, calls selectFragment() to pick exactly one fragment from the gated pool, empties the tile, appends to harvestedFragmentIds, recomputes unlockedPlantTypes (Pitfall 10 post-commit). Garden.ts handleTilePointerDown enqueues 'harvest' on a ready-stage plant click. 11 commands.test.ts cases (harvest + Pitfall 10 boundaries). -->
|
||||||
- [ ] **GARD-04**: Player can compost an immature or unwanted plant, returning a portion of resources and triggering a tonal beat (acknowledging the choice to let go).
|
- [x] **GARD-04**: Player can compost an immature or unwanted plant, returning a portion of resources and triggering a tonal beat (acknowledging the choice to let go). <!-- Plan 02-03: sim/garden compost() pure command — empties tile regardless of growth stage, no fragment yield (D-07), no resource refund (D-04 = infinite seeds). Garden.ts handleTilePointerDown enqueues 'compost' on an immature plant click. content/dialogue/season1/compost-acknowledgements.ink ships 6 authored beat lines in voice; Plan 02-04 wires the inkjs runtime (TODO at the call site marks the wiring point). 5 commands.test.ts cases (compost). -->
|
||||||
|
|
||||||
- [ ] **GARD-05**: Player unlocks new plant types as they progress through Seasons, with each plant type having distinct growth time, harvest yield, and visual identity.
|
- [ ] **GARD-05**: Player unlocks new plant types as they progress through Seasons, with each plant type having distinct growth time, harvest yield, and visual identity.
|
||||||
- [ ] **GARD-06**: Tree plantings (Season 3+) are slow and expensive but produce place-memory vignettes when harvested.
|
- [ ] **GARD-06**: Tree plantings (Season 3+) are slow and expensive but produce place-memory vignettes when harvested.
|
||||||
- [ ] **GARD-07**: Cross-pollination (Season 2+): adjacent compatible plants can produce hybrid seeds with mixed memory traits.
|
- [ ] **GARD-07**: Cross-pollination (Season 2+): adjacent compatible plants can produce hybrid seeds with mixed memory traits.
|
||||||
@@ -37,12 +38,13 @@ Requirements for initial release. Each maps to roadmap phases. All are user-cent
|
|||||||
|
|
||||||
### MEMORY — Fragments, Journal, Selection
|
### MEMORY — Fragments, Journal, Selection
|
||||||
|
|
||||||
- [ ] **MEMR-01**: Each harvest yields exactly one memory fragment, drawn from the authored content pool gated by current Season and unlocked progression.
|
- [x] **MEMR-01**: Each harvest yields exactly one memory fragment, drawn from the authored content pool gated by current Season and unlocked progression. <!-- Plan 02-03: harvest() calls selectFragment() exactly once per ready-stage harvest; result appended to harvestedFragmentIds. Pinned by selector.test.ts (16 cases) + commands.test.ts harvest cases. -->
|
||||||
- [ ] **MEMR-02**: Memory fragments are authored in plain text (Markdown with frontmatter) in the project's `/content/` tree and compiled per-Season at build time.
|
- [x] **MEMR-02**: Memory fragments are authored in plain text (Markdown with frontmatter) in the project's `/content/` tree and compiled per-Season at build time. <!-- Plan 02-03: 14 yaml entries + 2 long-form Markdown fragments under /content/seasons/01-soil/; PIPE-01 enforced (build fails on schema violation). PIPE-02 lazy chunk surface from Plan 02-02 verified structurally by scripts/check-bundle-split.mjs. -->
|
||||||
- [ ] **MEMR-03**: Each fragment has a stable string ID (e.g., `season3.canopy.lura_07.vignette`) — never numeric — so re-ordering or re-authoring does not break references.
|
- [x] **MEMR-03**: Each fragment has a stable string ID (e.g., `season3.canopy.lura_07.vignette`) — never numeric — so re-ordering or re-authoring does not break references. <!-- Plan 02-03: all 17 Season-1 fragment ids match /^season1\.[a-z0-9._-]+$/ (FragmentSchema regex enforced at module-eval). loader.test.ts has the numeric-id rejection case. -->
|
||||||
- [ ] **MEMR-04**: Player can open a Memory Journal (React DOM panel) listing every fragment they have collected, organized by Season.
|
- [x] **MEMR-04**: Player can open a Memory Journal (React DOM panel) listing every fragment they have collected, organized by Season. <!-- Plan 02-03: src/ui/journal/Journal.tsx — full-screen modal (D-24) grouped by Season. JournalIcon corner affordance (D-23 first-harvest gate). 7 Vitest cases. -->
|
||||||
- [ ] **MEMR-05**: Player can read any collected fragment in full at any time, including selecting and copying its text (DOM-based, not canvas-rendered).
|
- [x] **MEMR-05**: Player can read any collected fragment in full at any time, including selecting and copying its text (DOM-based, not canvas-rendered). <!-- Plan 02-03: Journal + FragmentRevealModal both render fragment bodies inside <pre> with userSelect: 'text' (DOM, not canvas). Pinned by Journal.test.tsx + FragmentRevealModal.test.tsx assertions on computed style. -->
|
||||||
- [ ] **MEMR-06**: Fragments are selected by a deterministic selector that respects authored gating rules (Season requirement, story-state requirement, player-progression requirement) and avoids duplicates within a single playthrough until the pool is exhausted.
|
- [x] **MEMR-06**: Fragments are selected by a deterministic selector that respects authored gating rules (Season requirement, story-state requirement, player-progression requirement) and avoids duplicates within a single playthrough until the pool is exhausted. <!-- Plan 02-03: src/sim/memory/selector.ts — selectFragment() is pure, deterministic (mulberry32 PRNG seeded from sim state), respects Season + plant-type tonal-register gating + no-dup. Pitfall 8 exhaustion fallback via EXHAUSTION_FALLBACK_ID sentinel. 16 selector.test.ts cases. -->
|
||||||
|
|
||||||
- [ ] **MEMR-07**: Place-memory vignettes (Season 3+) deliver a fragment as a short interactive scene the player can walk through, not just a text block.
|
- [ ] **MEMR-07**: Place-memory vignettes (Season 3+) deliver a fragment as a short interactive scene the player can walk through, not just a text block.
|
||||||
|
|
||||||
### STORY — Characters, Dialogue, Choice
|
### STORY — Characters, Dialogue, Choice
|
||||||
@@ -104,7 +106,8 @@ Requirements for initial release. Each maps to roadmap phases. All are user-cent
|
|||||||
### PIPE — Content Build & Asset Pipelines
|
### PIPE — Content Build & Asset Pipelines
|
||||||
|
|
||||||
- [x] **PIPE-01**: Project ships a build step that compiles `/content/**/*.{md,yaml,ink}` into per-Season JSON chunks via Zod-validated schemas; build fails on any schema violation. <!-- Plan 01-04: Vite-native import.meta.glob + Zod schemas; 5 loader tests green; schema violation throws at module-eval time. -->
|
- [x] **PIPE-01**: Project ships a build step that compiles `/content/**/*.{md,yaml,ink}` into per-Season JSON chunks via Zod-validated schemas; build fails on any schema violation. <!-- Plan 01-04: Vite-native import.meta.glob + Zod schemas; 5 loader tests green; schema violation throws at module-eval time. -->
|
||||||
- [ ] **PIPE-02**: Player loads only the content for their current Season at runtime (lazy chunk loading); future Seasons are not in the initial bundle.
|
- [x] **PIPE-02**: Player loads only the content for their current Season at runtime (lazy chunk loading); future Seasons are not in the initial bundle. <!-- Plan 02-02: loadSeasonFragments(seasonId) lazy import.meta.glob surface in src/content/loader.ts. Plan 02-03: scripts/check-bundle-split.mjs structural verifier integrated into npm run ci; runs after build to assert Season-1 content reaches dist/ via the lazy plumbing (currently chunkContentMatch=true via the eager corpus inlining; chunkNameMatch=false until Plan 02-04+ switches consumers to lazy-only). The structural assertion holds today; Phase 4+ Season-2 onboarding extends the script's known-content list. -->
|
||||||
|
|
||||||
- [x] **PIPE-03**: Project ships an AI asset pipeline that records provenance per asset and refuses to integrate an asset missing required provenance fields. <!-- Plan 01-05: scripts/validate-assets.mjs + Zod ProvenanceSchema (6 fields) + refused-sample fixture + 2 Vitest tests green. -->
|
- [x] **PIPE-03**: Project ships an AI asset pipeline that records provenance per asset and refuses to integrate an asset missing required provenance fields. <!-- Plan 01-05: scripts/validate-assets.mjs + Zod ProvenanceSchema (6 fields) + refused-sample fixture + 2 Vitest tests green. -->
|
||||||
- [ ] **PIPE-04**: Project ships visual regression testing for the asset library that flags style drift before any model migration is merged.
|
- [ ] **PIPE-04**: Project ships visual regression testing for the asset library that flags style drift before any model migration is merged.
|
||||||
- [x] **PIPE-05**: Project ships an `anti-FOMO doctrine` document and a `Season 7 end-state` design document in `.planning/` (or `docs/`) before economy code is written. <!-- Plan 01-06: both docs authored and doc-lint tested (8 Vitest assertions green). -->
|
- [x] **PIPE-05**: Project ships an `anti-FOMO doctrine` document and a `Season 7 end-state` design document in `.planning/` (or `docs/`) before economy code is written. <!-- Plan 01-06: both docs authored and doc-lint tested (8 Vitest assertions green). -->
|
||||||
@@ -205,20 +208,20 @@ Populated by gsd-roadmapper during roadmap creation on 2026-05-08. Updated after
|
|||||||
| CORE-11 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01; drainTicks refuses negative deltas + computeOfflineCatchup clamps to 0; ESLint sim-purity rule mechanically enforces D-33) |
|
| CORE-11 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-01; drainTicks refuses negative deltas + computeOfflineCatchup clamps to 0; ESLint sim-purity rule mechanically enforces D-33) |
|
||||||
| GARD-01 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-02; sim/garden plantSeed + SeedPicker + Garden scene pointerdown wiring) |
|
| GARD-01 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-02; sim/garden plantSeed + SeedPicker + Garden scene pointerdown wiring) |
|
||||||
| GARD-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-02; advanceGrowth state machine + plant-renderer primitives + reactive repaint via appStore.subscribe) |
|
| GARD-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-02; advanceGrowth state machine + plant-renderer primitives + reactive repaint via appStore.subscribe) |
|
||||||
| GARD-03 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| GARD-03 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-03; sim/garden harvest() pure command + selectFragment() integration + Garden.ts pointer wiring) |
|
||||||
| GARD-04 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| GARD-04 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-03; sim/garden compost() pure command + content/dialogue/season1/compost-acknowledgements.ink authored ahead of Plan 02-04 Ink runtime) |
|
||||||
| GARD-05 | Phase 4 — Season-Prestige Cycle & Season 2 (Roots) | Pending |
|
| GARD-05 | Phase 4 — Season-Prestige Cycle & Season 2 (Roots) | Pending |
|
||||||
| GARD-06 | Phase 5 — Seasons 3-4 (Canopy & Storm) | Pending |
|
| GARD-06 | Phase 5 — Seasons 3-4 (Canopy & Storm) | Pending |
|
||||||
| GARD-07 | Phase 4 — Season-Prestige Cycle & Season 2 (Roots) | Pending |
|
| GARD-07 | Phase 4 — Season-Prestige Cycle & Season 2 (Roots) | Pending |
|
||||||
| GARD-08 | Phase 6 — Seasons 5-6 (Depth & Loom) | Pending |
|
| GARD-08 | Phase 6 — Seasons 5-6 (Depth & Loom) | Pending |
|
||||||
| GARD-09 | Phase 5 — Seasons 3-4 (Canopy & Storm) | Pending |
|
| GARD-09 | Phase 5 — Seasons 3-4 (Canopy & Storm) | Pending |
|
||||||
| GARD-10 | Phase 3 — Watercolor & Cello Aesthetic | Pending |
|
| GARD-10 | Phase 3 — Watercolor & Cello Aesthetic | Pending |
|
||||||
| MEMR-01 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| MEMR-01 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-03; harvest() invokes selectFragment() exactly once per ready harvest) |
|
||||||
| MEMR-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| MEMR-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-03; 14 yaml + 2 long-form md fragments under /content/seasons/01-soil/; PIPE-01 build-time schema enforcement) |
|
||||||
| MEMR-03 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| MEMR-03 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-03; 17 fragments match /^season1\.[a-z0-9._-]+$/ regex; numeric-id rejected by FragmentSchema) |
|
||||||
| MEMR-04 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| MEMR-04 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-03; src/ui/journal/Journal.tsx full-screen modal grouped by Season + JournalIcon corner affordance) |
|
||||||
| MEMR-05 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| MEMR-05 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-03; Journal + FragmentRevealModal render fragment bodies in <pre> with userSelect:'text'; pinned by computed-style assertions) |
|
||||||
| MEMR-06 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| MEMR-06 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-03; src/sim/memory/selector.ts deterministic via mulberry32 seeded from sim state; gated by Season + plant-type tonal register; no-dup; sentinel fallback for Pitfall 8) |
|
||||||
| MEMR-07 | Phase 5 — Seasons 3-4 (Canopy & Storm) | Pending |
|
| MEMR-07 | Phase 5 — Seasons 3-4 (Canopy & Storm) | Pending |
|
||||||
| STRY-01 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| STRY-01 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
||||||
| STRY-02 | Phase 7 — Season 7 (Return) & Final Choice | Pending |
|
| STRY-02 | Phase 7 — Season 7 (Return) & Final Choice | Pending |
|
||||||
@@ -263,7 +266,7 @@ Populated by gsd-roadmapper during roadmap creation on 2026-05-08. Updated after
|
|||||||
| UX-12 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| UX-12 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| UX-13 | Phase 1 — Foundations & Doctrine | Complete (anti-fomo-doctrine.md authored + doc-lint tested; review-enforced per CONTEXT D-07) |
|
| UX-13 | Phase 1 — Foundations & Doctrine | Complete (anti-fomo-doctrine.md authored + doc-lint tested; review-enforced per CONTEXT D-07) |
|
||||||
| PIPE-01 | Phase 1 — Foundations & Doctrine | Complete (Vite-native loader + Zod schemas; build fails on schema violation) |
|
| PIPE-01 | Phase 1 — Foundations & Doctrine | Complete (Vite-native loader + Zod schemas; build fails on schema violation) |
|
||||||
| PIPE-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Pending |
|
| PIPE-02 | Phase 2 — Season 1 Vertical Slice (Soil) | Complete (Plan 02-02; loadSeasonFragments lazy import.meta.glob surface in src/content/loader.ts. Plan 02-03; scripts/check-bundle-split.mjs structural verifier integrated into npm run ci.) |
|
||||||
| PIPE-03 | Phase 1 — Foundations & Doctrine | Complete (validate-assets.mjs + ProvenanceSchema + refused-sample fixture + 2 Vitest tests) |
|
| PIPE-03 | Phase 1 — Foundations & Doctrine | Complete (validate-assets.mjs + ProvenanceSchema + refused-sample fixture + 2 Vitest tests) |
|
||||||
| PIPE-04 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
| PIPE-04 | Phase 8 — UX, Accessibility & Launch Polish | Pending |
|
||||||
| PIPE-05 | Phase 1 — Foundations & Doctrine | Complete (both doctrine docs authored + 8 doc-lint assertions green) |
|
| PIPE-05 | Phase 1 — Foundations & Doctrine | Complete (both doctrine docs authored + 8 doc-lint assertions green) |
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ Plans:
|
|||||||
Plans:
|
Plans:
|
||||||
- [x] 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) ✓ 2026-05-09 (12 min) — see 02-01-foundations-SUMMARY.md
|
- [x] 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) ✓ 2026-05-09 (12 min) — see 02-01-foundations-SUMMARY.md
|
||||||
- [x] 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) ✓ 2026-05-09 (18 min) — see 02-02-begin-plant-grow-SUMMARY.md
|
- [x] 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) ✓ 2026-05-09 (18 min) — see 02-02-begin-plant-grow-SUMMARY.md
|
||||||
- [ ] 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)
|
- [x] 02-03-harvest-journal-fragments-PLAN.md — Season-1 17 authored fragments + sim/memory selector (deterministic mulberry32, gated, no-dup, sentinel fallback for Pitfall 8) + harvest + compost commands (Pitfall 10 post-commit unlock thresholds) + Memory Journal + FragmentRevealModal + JournalIcon + PIPE-02 structural verifier (Wave 1; GARD-03, GARD-04, MEMR-01..06, PIPE-02) ✓ 2026-05-09 (12 min) — see 02-03-harvest-journal-fragments-SUMMARY.md
|
||||||
- [ ] 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-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)
|
- [ ] 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
|
||||||
@@ -150,7 +150,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8
|
|||||||
| Phase | Plans Complete | Status | Completed |
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Foundations & Doctrine | 7/7 (01-05 Task 2 partial — north-star images awaiting human curation; CI shippable today) | In Progress | - |
|
| 1. Foundations & Doctrine | 7/7 (01-05 Task 2 partial — north-star images awaiting human curation; CI shippable today) | In Progress | - |
|
||||||
| 2. Season 1 Vertical Slice (Soil) | 2/5 (Wave 0 + Wave 1 first plan complete; 02-03 next) | In Progress | - |
|
| 2. Season 1 Vertical Slice (Soil) | 3/5 (Wave 0 + Wave 1 complete; 02-04 next) | In Progress | - |
|
||||||
| 3. Watercolor & Cello Aesthetic | 0/TBD | Not started | - |
|
| 3. Watercolor & Cello Aesthetic | 0/TBD | Not started | - |
|
||||||
| 4. Season-Prestige Cycle & Season 2 (Roots) | 0/TBD | Not started | - |
|
| 4. Season-Prestige Cycle & Season 2 (Roots) | 0/TBD | Not started | - |
|
||||||
| 5. Seasons 3-4 (Canopy & Storm) | 0/TBD | Not started | - |
|
| 5. Seasons 3-4 (Canopy & Storm) | 0/TBD | Not started | - |
|
||||||
|
|||||||
+23
-18
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: in_progress
|
status: in_progress
|
||||||
stopped_at: "Phase 2 Wave 1 (Plan 02-02 begin-plant-grow) complete. 3 atomic commits: e82a11b (sim/garden — types, plants table, growth state machine, plantSeed), 537016b (render layer + Garden scene + scheduler integration), 414a554 (begin screen + seed picker + ui-strings + lazy content split). 163/163 tests green; npm run ci exits 0. GARD-01 / GARD-02 / AEST-07 / UX-01 requirements landed end-to-end. Begin → Plant → Grow vertical slice operational on Phaser primitives; Pitfall 5 mitigated (synchronous-inside-click AudioContext bootstrap); Assumption A5 verified (Phaser.Scale.FIT seed-picker positioning works without modification). Wave 1 plan 02-03 (harvest-journal-fragments) builds on src/sim/garden + src/render/garden + src/ui/garden surfaces shipped here. Wave 2 (02-04 lura-gate-beats + 02-05 letter-settings-e2e) still queued. Next: /gsd-execute-phase 2 to continue with Plan 02-03."
|
stopped_at: "Phase 2 Wave 1 (Plan 02-03 harvest-journal-fragments) complete. 3 atomic commits: f192e82 (Season-1 fragments + sim/memory selector + harvest/compost commands), 572c861 (journal + reveal modal + harvest pointer wiring), 39bfcd2 (PIPE-02 structural verifier scripts/check-bundle-split.mjs). 217/217 tests green (was 163; +54 new); npm run ci exits 0 with check:bundle-split integrated. GARD-03 / GARD-04 / MEMR-01..06 / PIPE-02 requirements landed end-to-end. The full Season-1 active-play loop is operational: plant → grow → harvest (deterministic mulberry32-seeded fragment selection, gated by Season + tonal register, no-dup, sentinel fallback for Pitfall 8) → reveal modal (D-25, MEMR-05 selectable DOM) → close → journal icon appears (D-23 first-harvest gate) → opens full-screen Memory Journal grouped by Season (D-24). 17 Season-1 fragments authored in bible voice. Plant-type unlocks: rosemary @ 0 / yarrow @ 3 / winter-rose @ 6 (Pitfall 10 boundary-tested). compost-acknowledgements.ink content shipped ahead of Plan 02-04's Ink runtime. Wave 2 (02-04 lura-gate-beats + 02-05 letter-settings-e2e) is the only remaining Phase-2 work. Next: /gsd-execute-phase 2 to continue with Plan 02-04."
|
||||||
last_updated: "2026-05-09T14:00:00.000Z"
|
last_updated: "2026-05-09T14:08:00.000Z"
|
||||||
last_activity: 2026-05-09
|
last_activity: 2026-05-09
|
||||||
progress:
|
progress:
|
||||||
total_phases: 8
|
total_phases: 8
|
||||||
completed_phases: 1
|
completed_phases: 1
|
||||||
total_plans: 8
|
total_plans: 8
|
||||||
completed_plans: 9
|
completed_plans: 10
|
||||||
percent: 16
|
percent: 18
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -21,16 +21,16 @@ progress:
|
|||||||
See: .planning/PROJECT.md (updated 2026-05-08)
|
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.
|
**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) — Wave 0 done; Wave 1 in progress (02-02 done, 02-03 next); Wave 2 queued
|
**Current focus:** Phase 02 — Season 1 Vertical Slice (Soil) — Wave 0 done; Wave 1 done (02-02 + 02-03); Wave 2 (02-04 + 02-05) is the only remaining Phase-2 work
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 02 (season-1-vertical-slice-soil) — 2/5 plans complete (Wave 0 + Wave 1 first plan)
|
Phase: 02 (season-1-vertical-slice-soil) — 3/5 plans complete (Wave 0 + both Wave 1 plans)
|
||||||
Plans: 5 of 5 created (3 waves); Wave 0 (02-01) DONE; Wave 1 first plan (02-02) DONE; Wave 1 second plan (02-03) ready; Wave 2 queued
|
Plans: 5 of 5 created (3 waves); Wave 0 (02-01) DONE; Wave 1 (02-02 + 02-03) DONE; Wave 2 (02-04 + 02-05) queued
|
||||||
Status: Plan 02-02 begin-plant-grow executed in sequential mode — 3 atomic commits, 35 new tests (163/163 total green), npm run ci exits 0. GARD-01 / GARD-02 / AEST-07 / UX-01 satisfied end-to-end on Phaser primitives. Begin → Plant → Grow vertical slice operational. Pitfall 5 mitigated; Assumption A5 verified.
|
Status: Plan 02-03 harvest-journal-fragments executed in sequential mode — 3 atomic commits, 54 new tests (217/217 total green), npm run ci exits 0 with check:bundle-split integrated. GARD-03 / GARD-04 / MEMR-01..06 / PIPE-02 satisfied end-to-end. Full Season-1 active-play loop operational: plant → grow → harvest (deterministic, gated, no-dup) → reveal modal → journal. 17 Season-1 fragments authored. Pitfall 8 (sentinel fallback) + Pitfall 10 (post-commit unlock thresholds) both mitigated.
|
||||||
Last activity: 2026-05-09 -- Plan 02-02 execute complete
|
Last activity: 2026-05-09 -- Plan 02-03 execute complete
|
||||||
|
|
||||||
Progress: [█░░░░░░░░░] 16%
|
Progress: [██░░░░░░░░] 18%
|
||||||
|
|
||||||
## Verification Results
|
## Verification Results
|
||||||
|
|
||||||
@@ -61,21 +61,21 @@ Gates run: lint (exit 0), test (53/53 green, 12 files), validate:assets (2 asset
|
|||||||
|
|
||||||
**Velocity:**
|
**Velocity:**
|
||||||
|
|
||||||
- Total plans completed: 9 (1 partial — 01-05 Task 2 deferred via IOU)
|
- Total plans completed: 10 (1 partial — 01-05 Task 2 deferred via IOU)
|
||||||
- Average duration: ~5 min (Wave 1 baseline 6min; Wave 2 plans 4–8min; Plan 07 ~2min; Plan 02-01 ~12min; Plan 02-02 ~18min — Phase-2 vertical-slice plans are heaviest)
|
- Average duration: ~5 min (Wave 1 baseline 6min; Wave 2 plans 4–8min; Plan 07 ~2min; Plan 02-01 ~12min; Plan 02-02 ~18min; Plan 02-03 ~12min — Phase-2 vertical-slice plans are heaviest)
|
||||||
- Total execution time: ~60 min across Phase 1 + Phase 2 Wave 0 + Wave 1 first plan
|
- Total execution time: ~70 min across Phase 1 + Phase 2 Wave 0 + Wave 1 (both plans)
|
||||||
|
|
||||||
**By Phase:**
|
**By Phase:**
|
||||||
|
|
||||||
| Phase | Plans | Total | Avg/Plan |
|
| Phase | Plans | Total | Avg/Plan |
|
||||||
|-------|-------|-------|----------|
|
|-------|-------|-------|----------|
|
||||||
| 1. Foundations & Doctrine | 7/7 (complete) | ~30 min | ~5 min |
|
| 1. Foundations & Doctrine | 7/7 (complete) | ~30 min | ~5 min |
|
||||||
| 2. Season 1 Vertical Slice (Soil) | 2/5 (Wave 0 + Wave 1 first plan complete) | ~30 min | ~15 min |
|
| 2. Season 1 Vertical Slice (Soil) | 3/5 (Wave 0 + Wave 1 complete) | ~42 min | ~14 min |
|
||||||
|
|
||||||
**Recent Trend:**
|
**Recent Trend:**
|
||||||
|
|
||||||
- Last 5 plans: [01-05 asset-provenance (partial) · 01-06 doctrine-docs · 01-07 ci-workflow · 02-01 foundations · 02-02 begin-plant-grow — all green]
|
- Last 5 plans: [01-06 doctrine-docs · 01-07 ci-workflow · 02-01 foundations · 02-02 begin-plant-grow · 02-03 harvest-journal-fragments — all green]
|
||||||
- Trend: ↗ (02-02 was 18 min — heavier still than 02-01 because it spans every architectural tier: sim → render → game → ui → content; 35 new tests + the first end-to-end vertical slice on Phaser primitives)
|
- Trend: → (02-03 was 12 min — sim/memory + journal UI tier without a new render layer; the architectural firewall edges 02-02 shipped carry over directly so 02-03 is "just" the second half of the active-play loop on top of established surfaces; +54 new tests for 217/217 total green)
|
||||||
|
|
||||||
*Updated after each plan completion*
|
*Updated after each plan completion*
|
||||||
|
|
||||||
@@ -94,6 +94,11 @@ Recent decisions affecting current work:
|
|||||||
- Plan 02-02 (Wave 1): GRID_LAYOUT origin-centering math corrected during execution — gridOriginX=296 / gridOriginY=168 (not the plan's 240/144 hedged "≈" values). True-centered in 1024×768.
|
- Plan 02-02 (Wave 1): GRID_LAYOUT origin-centering math corrected during execution — gridOriginX=296 / gridOriginY=168 (not the plan's 240/144 hedged "≈" values). True-centered in 1024×768.
|
||||||
- Plan 02-02 (Wave 1): Phaser 4 cannot be imported under happy-dom — its boot probe `checkInverseAlpha` calls `canvas.getContext('2d')` which returns null. SeedPicker test mocks src/game/event-bus to avoid pulling Phaser into the test runtime; Phaser scene behavioral coverage is the Plan 02-05 Playwright e2e's job (RESEARCH Validation Architecture explicitly states render-tier needs a real canvas).
|
- Plan 02-02 (Wave 1): Phaser 4 cannot be imported under happy-dom — its boot probe `checkInverseAlpha` calls `canvas.getContext('2d')` which returns null. SeedPicker test mocks src/game/event-bus to avoid pulling Phaser into the test runtime; Phaser scene behavioral coverage is the Plan 02-05 Playwright e2e's job (RESEARCH Validation Architecture explicitly states render-tier needs a real canvas).
|
||||||
- Plan 02-02 (Wave 1): Audio bootstrap is module-level state (not React useState) so the click handler can call it synchronously — Pitfall 5 (iOS Safari requires AudioContext construction inside the gesture, not just resume) is mitigated structurally.
|
- Plan 02-02 (Wave 1): Audio bootstrap is module-level state (not React useState) so the click handler can call it synchronously — Pitfall 5 (iOS Safari requires AudioContext construction inside the gesture, not just resume) is mitigated structurally.
|
||||||
|
- Plan 02-03 (Wave 1): Pool-exhaustion behavior chosen — sentinel fallback (`season1.soil._exhaustion`), NOT repeat-most-recent. Repeat-most-recent would silently re-grow `harvestedFragmentIds` past the corpus size, breaking the no-dup invariant downstream consumers (Journal de-dup, Lura beat counters, letter slot vocabulary) depend on. Authored warm pool ≥9 makes the sentinel structurally unreachable in normal Phase-2 play; it's a defensive structural fallback only.
|
||||||
|
- Plan 02-03 (Wave 1): Plant-type unlock thresholds finalized — rosemary @ 0 / yarrow @ 3 / winter-rose @ 6. Spaced before Lura's mid-beat (4th harvest) and farewell beat (8th harvest) per D-14, so unlocks land in tonal alignment with the arc's turns. Pitfall 10 mitigation: thresholds checked AFTER the harvest commit (3 explicit boundary tests).
|
||||||
|
- Plan 02-03 (Wave 1): Garden scene loads fragments via the EAGER `fragments` corpus filtered to Season 1, NOT via `await loadSeasonFragments(1)`. Trade-off: simpler synchronous create() vs. INEFFECTIVE_DYNAMIC_IMPORT warnings inherited from Plan 02-02. Lazy plumbing is structurally proven by `scripts/check-bundle-split.mjs`; Phase 4+ should swap to lazy when Season transitions land.
|
||||||
|
- Plan 02-03 (Wave 1): Compost beat content shipped in `content/dialogue/season1/compost-acknowledgements.ink` ahead of Plan 02-04's Ink runtime; Garden.ts compost branch carries a TODO at the wiring point. The split lets the writer iterate on voice independently of runtime work.
|
||||||
|
- Plan 02-03 (Wave 1): PIPE-02 verifier `scripts/check-bundle-split.mjs` is structured as Vitest-importable Node ESM (`runCheck()` exported, CLI gated by `import.meta.url`). Pattern reusable for Phase 4 Season-2 onboarding (extend known-content list) and Phase 8 visual-regression baselines (different filename heuristics, same export shape).
|
||||||
- Phases 4-7 deliver the remaining six Seasons in mechanic-introducing pairs (Season 2 alone with prestige, Seasons 3-4, Seasons 5-6, Season 7 alone) — at most one new mechanic per Season per the scope-defense doctrine.
|
- Phases 4-7 deliver the remaining six Seasons in mechanic-introducing pairs (Season 2 alone with prestige, Seasons 3-4, Seasons 5-6, Season 7 alone) — at most one new mechanic per Season per the scope-defense doctrine.
|
||||||
- Plan 01-01: scaffolded by hand (the official `npm create @phaserjs/game@latest` is interactive-only — `--template react-ts --yes` flags are silently ignored as of create-game v1.3.2); plan's documented fallback path was used. Vite 8 + TS 6 referenced-projects tsconfig layout adopted; `build` runs `tsc -b && vite build` so strict-TS gates every build. ESLint 9 installed → Plan 02 must use **flat config** (`eslint.config.js`), not legacy `.eslintrc.*`.
|
- Plan 01-01: scaffolded by hand (the official `npm create @phaserjs/game@latest` is interactive-only — `--template react-ts --yes` flags are silently ignored as of create-game v1.3.2); plan's documented fallback path was used. Vite 8 + TS 6 referenced-projects tsconfig layout adopted; `build` runs `tsc -b && vite build` so strict-TS gates every build. ESLint 9 installed → Plan 02 must use **flat config** (`eslint.config.js`), not legacy `.eslintrc.*`.
|
||||||
- Plan 01-01: pre-installed `fake-indexeddb@^6` here so Plan 03 doesn't have to re-edit `package.json`. All Phase-1 dep versions match RESEARCH.md exactly within their `^` ranges.
|
- Plan 01-01: pre-installed `fake-indexeddb@^6` here so Plan 03 doesn't have to re-edit `package.json`. All Phase-1 dep versions match RESEARCH.md exactly within their `^` ranges.
|
||||||
@@ -124,5 +129,5 @@ Items acknowledged and carried forward:
|
|||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-05-09
|
Last session: 2026-05-09
|
||||||
Stopped at: Phase 2 Wave 1 first plan (Plan 02-02 begin-plant-grow) executed in sequential mode — 3 atomic commits (e82a11b, 537016b, 414a554), 35 new tests, 163/163 total green, npm run ci exits 0. GARD-01/GARD-02/AEST-07/UX-01 building blocks landed end-to-end on Phaser primitives. SUMMARY at .planning/phases/02-season-1-vertical-slice-soil/02-02-begin-plant-grow-SUMMARY.md.
|
Stopped at: Phase 2 Wave 1 second plan (Plan 02-03 harvest-journal-fragments) executed in sequential mode — 3 atomic commits (f192e82, 572c861, 39bfcd2), 54 new tests, 217/217 total green, npm run ci exits 0 (with check:bundle-split integrated). GARD-03/GARD-04/MEMR-01..06/PIPE-02 satisfied end-to-end. The full Season-1 active-play loop is operational on real authored content; sentinel fallback (Pitfall 8) and Pitfall 10 plant-unlock boundary both pinned by Vitest. SUMMARY at .planning/phases/02-season-1-vertical-slice-soil/02-03-harvest-journal-fragments-SUMMARY.md.
|
||||||
Next action: `/gsd-execute-phase 2` to continue with Plan 02-03 (harvest + journal + fragments) on top of the sim/garden + render/garden + ui/garden surfaces shipped in 02-02. Wave 2 (02-04 lura-gate-beats + 02-05 letter-settings-e2e) follows after both Wave 1 plans complete.
|
Next action: `/gsd-execute-phase 2` to continue with Plan 02-04 (Lura's Ink dialogue + gate beats — 1st/4th/8th harvest thresholds) on top of the harvest pipeline shipped in 02-03. Plan 02-04 will swap the compost-acknowledgements TODO at src/game/scenes/Garden.ts for the inkjs runtime path. Plan 02-05 (offline catchup + letter + Settings + Playwright e2e) is the final Phase-2 plan.
|
||||||
|
|||||||
+305
@@ -0,0 +1,305 @@
|
|||||||
|
---
|
||||||
|
phase: 02-season-1-vertical-slice-soil
|
||||||
|
plan: 03
|
||||||
|
subsystem: harvest-journal-fragments-vertical-slice
|
||||||
|
tags: [vertical-slice, harvest, journal, fragments, content-authoring, mulberry32, lazy-load, pipe-02, mvp, wave-1]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 02-01
|
||||||
|
provides: BigQty + tick scheduler + Zustand store + V1Payload extension (harvestedFragmentIds + fragmentRevealId + selectJournalRevealed) + ESLint sim-purity rule + Phaser EventBus singleton
|
||||||
|
- phase: 02-02
|
||||||
|
provides: sim/garden core (4×4 grid, 3 plant types, growth state machine, plantSeed) + render/garden tier + Garden Phaser scene + BeginScreen + audio bootstrap + SeedPicker + UI strings + PIPE-02 lazy fragment loader surface
|
||||||
|
provides:
|
||||||
|
- sim/memory module — pool.ts (filterPool — pure, gated by Season + plant-type tonal register + no-dup) + selector.ts (selectFragment — deterministic via mulberry32 PRNG seeded from sim state; EXHAUSTION_FALLBACK_ID sentinel for Pitfall 8) + barrel; 16 selector tests
|
||||||
|
- sim/garden/commands.ts (extended) — harvest() pure command with Pitfall 10 mitigation (unlocks computed AFTER harvest commit) + compost() pure command (D-07 no-yield, D-04 no-refund) + SimContext interface for application-layer-injected fragment corpus + simulateOneTick branches on harvest/compost
|
||||||
|
- Plant-type unlock thresholds — rosemary @ 0 (start), yarrow @ 3, winter-rose @ 6 (Plan author's discretion within D-05); pinned by 3 boundary tests
|
||||||
|
- FragmentSchema extension — optional `tags: z.array(z.string()).optional()` for tonal-register gating (warm/contemplative/heavy/_meta); back-compat (existing tagless fragments parse)
|
||||||
|
- Memory Journal UI tier — Journal.tsx (D-24 full-screen modal, fragments grouped by Season, MEMR-05 selectable DOM) + FragmentRevealModal.tsx (D-25 active-play reveal, backdrop-click + inner-Close dismiss, defensive silent dismiss on unresolvable id) + journal-icon.tsx (D-23 reveal-after-first-harvest gate via selectJournalRevealed selector, D-29 corner affordance with internal open state)
|
||||||
|
- Season-1 authored fragment pool — 14 yaml entries (9 warm + 3 contemplative + 2 heavy + 1 _meta sentinel) + 2 long-form Markdown fragments (lura-first-letter.md, winter-rose-night.md). Total 17 authored. Warm pool depth ≥9 satisfies the worst-case all-rosemary playthrough at the 8th-harvest Lura threshold (CONTEXT D-14).
|
||||||
|
- content/dialogue/season1/compost-acknowledgements.ink — authored content (6 short lines in the gardener-keeper voice) shipped ahead of Plan 02-04's Ink runtime; Garden.ts compost branch carries a TODO marking the Plan 02-04 wiring point
|
||||||
|
- Garden.ts harvest+compost pointer wiring — handleTilePointerDown branches on tile state (empty → SeedPicker / ready → harvest / immature → compost); update() loop detects newly-appended harvestedFragmentIds and sets fragmentRevealId for the D-25 reveal flow; SimContext built once at create() from filtered eager `fragments` corpus
|
||||||
|
- PIPE-02 structural verification — scripts/check-bundle-split.mjs (refactored as exportable `runCheck()` for Vitest cover; CLI invocation guard wraps process.exit) + scripts/check-bundle-split.test.mjs (3 cases: file exists / parses without exit / runCheck returns documented shape) + ci chain extended to run check:bundle-split AFTER build
|
||||||
|
affects: [02-04-lura-gate-beats (Lura's Ink runtime swaps in for the compost-acknowledgements TODO + Lura beats consume harvestedFragmentIds.length thresholds), 02-05-letter-settings-e2e (offline auto-harvest writes to harvestedFragmentIds; e2e exercises the full Begin → Plant → Grow → Harvest loop end-to-end)]
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "sim/memory module shape: pool.ts (filter helper) + selector.ts (deterministic PRNG-driven choice + sentinel fallback) + index.ts (barrel). Repeats the sim/<subsystem>/ shape established by sim/garden in Plan 02-02."
|
||||||
|
- "Deterministic selector via mulberry32 PRNG seeded from `(harvestedFragmentIds.length, plantedAtTick)` — both sim-internal counters; no Date.now leaks into selection. Pinned by 16 selector tests including determinism, gating, no-dup, season exclusion, sentinel exclusion from normal pool."
|
||||||
|
- "Pitfall 10 mitigation: plant-type unlock thresholds checked AFTER the harvest commit (computePlantUnlocks uses harvestedIds.length, not the pre-commit count). Pinned by 3 boundary tests — locked at 2/5 harvests, unlocked at 3/6."
|
||||||
|
- "Pitfall 8 (gated-pool exhaustion) — chosen behavior is the sentinel fallback. EXHAUSTION_FALLBACK_ID = 'season1.soil._exhaustion' is authored content tagged ['_meta']; the pool filter excludes _meta-tagged fragments, and selector.ts looks the sentinel up explicitly when filterPool returns []. Plan ships sufficient warm-pool depth that the sentinel is unreachable in normal Phase-2 play; it remains a defensive structural fallback."
|
||||||
|
- "FragmentSchema extension via optional `tags` — back-compat with Phase-1 demo fragments that don't carry tags (loader.test.ts continues to pass against a tag-less fixture). Phase 2+ authored fragments ship tags for tonal-register gating."
|
||||||
|
- "DOM-rendered journal tier: Journal + FragmentRevealModal + JournalIcon all use selectable text (`userSelect: 'text'`) with `<pre>` for body rendering. MEMR-05 mechanically verified — canvas rendering would foreclose copy-paste from day one."
|
||||||
|
- "Application-layer SimContext injection — Garden scene loads the eager `fragments` corpus at create() and threads it through every simulateOneTick call. Sim modules NEVER import import.meta.glob; the corpus is a pure data input."
|
||||||
|
- "Journal-icon owns local `open` state (not the store) — V1Payload has no journal-open flag by design; the affordance owns its own visibility lifecycle without polluting the persisted save shape."
|
||||||
|
- "PIPE-02 structural verifier as an exportable `runCheck()` returning a structured result, with the CLI invocation guarded behind an import.meta.url comparison so Vitest can import without process.exit firing. Pattern reusable for Phase 8 visual-regression scripts."
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- src/sim/memory/pool.ts (filterPool — pure Season + plant-type tonal-register + no-dup gating)
|
||||||
|
- src/sim/memory/selector.ts (selectFragment + EXHAUSTION_FALLBACK_ID + mulberry32 PRNG)
|
||||||
|
- src/sim/memory/selector.test.ts (16 tests — gating / no-dup / determinism / sentinel fallback / sentinel pool exclusion / season exclusion)
|
||||||
|
- src/sim/memory/index.ts (barrel)
|
||||||
|
- src/ui/journal/Journal.tsx (D-24 full-screen Memory Journal modal)
|
||||||
|
- src/ui/journal/Journal.test.tsx (7 tests — empty state / fragment body render / userSelect: text / Season grouping / close callback / aria-label / unresolvable id silent skip)
|
||||||
|
- src/ui/journal/FragmentRevealModal.tsx (D-25 active-play reveal modal)
|
||||||
|
- src/ui/journal/FragmentRevealModal.test.tsx (6 tests — null when revealId is null / body rendered / backdrop dismiss / article-body stopPropagation / inner Close dismiss / unresolvable id silent dismiss)
|
||||||
|
- src/ui/journal/journal-icon.tsx (D-23 reveal-after-first-harvest gate + corner affordance)
|
||||||
|
- src/ui/journal/journal-icon.test.tsx (3 tests — null pre-first-harvest / icon renders post-first-harvest / click opens journal modal)
|
||||||
|
- src/ui/journal/index.ts (barrel)
|
||||||
|
- content/seasons/01-soil/fragments/lura-first-letter.md (long-form Markdown fragment, warm tonal register)
|
||||||
|
- content/seasons/01-soil/fragments/winter-rose-night.md (long-form Markdown fragment, heavy tonal register)
|
||||||
|
- content/dialogue/season1/compost-acknowledgements.ink (6 short authored compost beat lines; Plan 02-04 wires the runtime)
|
||||||
|
- scripts/check-bundle-split.mjs (PIPE-02 structural verifier with exportable runCheck())
|
||||||
|
- scripts/check-bundle-split.test.mjs (3 Vitest cases — exists / parses-without-exit / structured result)
|
||||||
|
modified:
|
||||||
|
- src/content/schemas/fragment.ts (added optional `tags` field; back-compat preserved)
|
||||||
|
- src/sim/garden/commands.ts (harvest + compost branches; SimContext interface; PLANT_UNLOCK_THRESHOLDS table; Pitfall 10 mitigation; selectFragment integration; BLOCKER 3 invariant preserved)
|
||||||
|
- src/sim/garden/commands.test.ts (added 18 new cases — harvest / compost / Pitfall 10 boundaries / sentinel fallback / immutability + simulateOneTick integration; updated the previously-stubbed "harvest/compost ignored" case)
|
||||||
|
- src/sim/garden/index.ts (export harvest/compost/SimContext)
|
||||||
|
- src/sim/index.ts (re-export ./memory)
|
||||||
|
- content/seasons/01-soil/fragments.yaml (replaced single placeholder with 14 authored fragments + sentinel; bible voice maintained throughout)
|
||||||
|
- src/ui/index.ts (re-export ./journal)
|
||||||
|
- src/App.tsx (mount FragmentRevealModal + JournalIcon)
|
||||||
|
- src/game/scenes/Garden.ts (build SimContext at create() from eager `fragments`; handleTilePointerDown branches harvest/compost on stage; update() detects new harvest and triggers D-25 reveal flow)
|
||||||
|
- package.json (new check:bundle-split script; ci chain extended)
|
||||||
|
removed: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Pool exhaustion behavior chosen: sentinel fallback (EXHAUSTION_FALLBACK_ID = 'season1.soil._exhaustion'). The alternative — repeat-most-recent — was rejected because (a) it makes the fragment ID corpus mutable in spirit (a fragment id can re-appear in harvestedFragmentIds, breaking the no-dup invariant downstream consumers expect) and (b) the bible voice naturally accommodates a single sentinel about steady-part-that-doesn't-need-re-learning. The authored warm pool ≥9 satisfies the worst-case all-rosemary 8th-harvest Lura threshold so the sentinel is structurally unreachable in normal Phase-2 play."
|
||||||
|
- "Plant-type unlock thresholds finalized within Plan author's discretion (CONTEXT D-05): rosemary @ 0, yarrow @ 3, winter-rose @ 6. The 3/6 spacing matches the 1/4/8 Lura beat cadence (D-14) — the player feels yarrow unlock right around the time Lura's mid-beat fires (4th harvest), and winter-rose unlock arrives shortly before the farewell beat (8th harvest). Adjustable in playtest by ±1 — the model (tied to harvest count) is locked."
|
||||||
|
- "Garden scene loads fragments via the EAGER `fragments` export filtered to Season 1 — NOT via `await loadSeasonFragments(1)`. Trade-off documented: Phase 2 has only Season 1, so the eager path is simpler and avoids an async-init dance in Phaser create(). The lazy plumbing is structurally proven by check-bundle-split.mjs; Phase 4+ should swap to lazy when Season transitions land. INEFFECTIVE_DYNAMIC_IMPORT warnings in `npm run build` are inherited from Plan 02-02 and will resolve naturally when consumers move to lazy-only."
|
||||||
|
- "Compost beat — Plan 02-03 ships the AUTHORED CONTENT (compost-acknowledgements.ink, 6 lines in the gardener-keeper voice) but does NOT yet render it. Plan 02-04 owns the inkjs runtime; Garden.ts has a TODO at the wiring point. This split lets the writer iterate on voice independently of the runtime work."
|
||||||
|
- "Journal-icon's 'j' hotkey (CONTEXT D-29) is intentionally NOT wired in Plan 02-03 — keyboard-shortcut surface lands with the wider Settings hotkey work in Plan 02-05. The plan's task-2 step-3 sketched a window-CustomEvent indirection; the simpler choice is to defer the keybinding until Plan 02-05 owns the surface holistically."
|
||||||
|
- "FragmentRevealModal silent-dismiss on unresolvable id (defensive). The state-update-during-render is bounded — the next render reads fragmentRevealId === null and exits at the guard. React does not warn for this single-step path because the setState transitions to a steady state in O(1) re-renders."
|
||||||
|
- "Knuth's multiplicative hash on `(harvestCount * 2654435761 + plantedAtTick) | 0` for the seedHash. Spreads adjacent (count, tick) pairs across the 32-bit seed space so mulberry32 produces visibly-different results on adjacent harvests; the `| 0` truncates to 32-bit signed int (mulberry32 internally re-coerces to unsigned)."
|
||||||
|
- "Sentinel exclusion from the normal pool is enforced in BOTH the schema-tag check (`if (f.tags.includes('_meta')) return false`) AND by selector.ts NEVER exposing the sentinel via the seeded-pool branch. Dual defense — accidental tag drift on a future fragment can't smuggle the sentinel into normal play."
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Deterministic-selector pattern (selectFragment): pure inputs (corpus, season, plant type, harvested ids, seed hash) → Fragment | null with sentinel fallback. Reusable for Phase 5+ memory-vignette selection (place-memory + Loom feeds), Phase 4+ cross-pollination output, anywhere a 'pick one from a gated pool, deterministically' is needed."
|
||||||
|
- "Application-layer-injected SimContext: sim modules take pure data; the application layer (Phaser scene) loads the data and threads it through. Plan 02-04 will extend SimContext with `inkStory` for Lura beat firing; Plan 02-05 will extend with `offlineEvents` for the letter-from-the-garden composition."
|
||||||
|
- "DOM-overlay-over-canvas pattern (Plan 02-02 establishment continues): Journal + FragmentRevealModal + JournalIcon are React DOM siblings of PhaserGame. MEMR-05 selectable text demands DOM, not canvas. The pattern repeats for Plan 02-04 Lura dialogue and Plan 02-05 Letter overlay."
|
||||||
|
- "FragmentSchema optional-field extension: Phase 2 added `tags?` without bumping schemaVersion. The same path is open for Phase 4+ (e.g., `unlocks?: string[]` for cross-pollination). Migration only required when an existing field's shape changes, never for additive optional fields."
|
||||||
|
- "PIPE-02 structural verifier as Vitest-importable Node ESM: `runCheck()` exported, CLI gated by import.meta.url. Pattern reusable for Phase 4 Season-2 onboarding (extend the script's known-content list) and Phase 8 visual-regression baselines (different filename heuristics, same export shape)."
|
||||||
|
- "Journal-icon owns local open state (not the store): UI affordances that don't need to persist across sessions live in component state. V1Payload stays clean — only canonical game state crosses the save boundary."
|
||||||
|
|
||||||
|
requirements-completed: [GARD-03, GARD-04, MEMR-01, MEMR-02, MEMR-03, MEMR-04, MEMR-05, MEMR-06, PIPE-02, UX-01]
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
duration: 12min
|
||||||
|
completed: 2026-05-09
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 2 Plan 03: Harvest, Memory Journal & Fragments Vertical Slice Summary
|
||||||
|
|
||||||
|
## One-liner
|
||||||
|
|
||||||
|
The second half of the Season-1 active-play loop — sim/memory module with deterministic mulberry32-seeded selector + sentinel fallback for the gated-pool exhaustion case (Pitfall 8); harvest + compost pure commands extending sim/garden with Pitfall 10 mitigation (yarrow @ 3 / winter-rose @ 6 unlocks computed AFTER harvest commit); 17 authored Season-1 fragments under /content/seasons/01-soil/ in the bible voice (9 warm / 3 contemplative / 2 heavy / 1 _meta sentinel + 2 long-form Markdown); DOM-rendered Memory Journal + active-play FragmentRevealModal + first-harvest-gated JournalIcon all selectable + copy-pasteable per MEMR-05; Garden scene wiring harvest/compost pointer events through to the D-25 reveal flow; PIPE-02 structural verifier (`scripts/check-bundle-split.mjs`) as a Vitest-importable Node ESM module integrated into `npm run ci`.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~12 min (sequential executor; lighter than 02-02's 18min — Plan 02-03's surface is sim/memory + journal UI tier without a new render layer; the architectural firewall edges shipped in 02-02 carry over directly)
|
||||||
|
- **Started:** 2026-05-09T13:55:00Z (approximate; orchestrator-recorded plan-start time)
|
||||||
|
- **Completed:** 2026-05-09T14:08:00Z
|
||||||
|
- **Tasks:** 3 (atomic per plan)
|
||||||
|
- **Files created:** 14
|
||||||
|
- **Files modified:** 9
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Season-1 fragments + sim/memory selector + harvest/compost commands** — `f192e82` (feat)
|
||||||
|
2. **Task 2: Journal + reveal modal + harvest pointer wiring** — `572c861` (feat)
|
||||||
|
3. **Task 3: scripts/check-bundle-split.mjs (PIPE-02 structural verification)** — `39bfcd2` (chore)
|
||||||
|
|
||||||
|
**Plan metadata:** _(this commit)_ — `docs(02-03): complete harvest-journal-fragments plan`
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- **Active-play loop closed end-to-end on real authored content.** A player can plant a seed (Plan 02-02) → watch it grow → click a ready plant → harvest fires through the sim, picks one fragment deterministically from the gated pool → reveal modal pops with the fragment's full text → close → fragment files into the Memory Journal under Season 1 → journal icon (invisible until first harvest) appears in the corner → click opens the full-screen modal listing all collected fragments grouped by Season.
|
||||||
|
- **17 Season-1 fragments authored in voice**, satisfying the worst-case-all-rosemary depth at the 8th-harvest Lura threshold (CONTEXT D-14) without reaching the exhaustion sentinel. Bible voice maintained throughout — warm, specific, intermittent, sometimes funny, sometimes devastating; the gardener-keeper voice (NOT Lura — she's the warmth anchor; the contrast lives here).
|
||||||
|
- **MEMR-06 deterministic selector landed**: same inputs ALWAYS yield the same fragment. Pinned by 16 Vitest cases including determinism, Season + plant-type gating, no-dup, sentinel exclusion from the normal pool, and Pitfall 8 exhaustion fallback.
|
||||||
|
- **Pitfall 10 boundary mechanically pinned**: yarrow locked at 2 harvests, unlocked at 3; winter-rose locked at 5, unlocked at 6. Three explicit boundary tests in `commands.test.ts`.
|
||||||
|
- **PIPE-02 structurally verified**: `scripts/check-bundle-split.mjs` exits 0 after `npm run build`; integrated into the CI chain so any future change that breaks the lazy-content plumbing fails the build.
|
||||||
|
- **No raw `Decimal` outside `src/sim/numbers/`. No hardcoded player-visible strings outside `/content/`.** Zero new ESLint sim-purity violations. All sim modules pure (no Date.now / setInterval / DOM / fetch).
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
### Created (14)
|
||||||
|
|
||||||
|
- `src/sim/memory/pool.ts` — pure filter helper (Season + plant-type tonal-register + no-dup gating)
|
||||||
|
- `src/sim/memory/selector.ts` — deterministic mulberry32-seeded selector with EXHAUSTION_FALLBACK_ID sentinel
|
||||||
|
- `src/sim/memory/selector.test.ts` — 16 cases pinning determinism / gating / no-dup / sentinel fallback / sentinel pool exclusion
|
||||||
|
- `src/sim/memory/index.ts` — barrel
|
||||||
|
- `src/ui/journal/Journal.tsx` — D-24 full-screen modal, fragments grouped by Season, MEMR-05 selectable
|
||||||
|
- `src/ui/journal/Journal.test.tsx` — 7 cases
|
||||||
|
- `src/ui/journal/FragmentRevealModal.tsx` — D-25 active-play reveal modal
|
||||||
|
- `src/ui/journal/FragmentRevealModal.test.tsx` — 6 cases
|
||||||
|
- `src/ui/journal/journal-icon.tsx` — D-23 first-harvest reveal gate + D-29 corner affordance
|
||||||
|
- `src/ui/journal/journal-icon.test.tsx` — 3 cases
|
||||||
|
- `src/ui/journal/index.ts` — barrel
|
||||||
|
- `content/seasons/01-soil/fragments/lura-first-letter.md` — long-form Markdown fragment, warm
|
||||||
|
- `content/seasons/01-soil/fragments/winter-rose-night.md` — long-form Markdown fragment, heavy
|
||||||
|
- `content/dialogue/season1/compost-acknowledgements.ink` — 6 authored beat lines for Plan 02-04 to wire
|
||||||
|
- `scripts/check-bundle-split.mjs` — PIPE-02 structural verifier with exportable `runCheck()`
|
||||||
|
- `scripts/check-bundle-split.test.mjs` — 3 Vitest cases proving import-without-exit + result shape
|
||||||
|
|
||||||
|
### Modified (9)
|
||||||
|
|
||||||
|
- `src/content/schemas/fragment.ts` — added optional `tags` field for tonal-register gating
|
||||||
|
- `src/sim/garden/commands.ts` — harvest + compost branches; SimContext interface; PLANT_UNLOCK_THRESHOLDS; Pitfall 10 mitigation; selectFragment integration
|
||||||
|
- `src/sim/garden/commands.test.ts` — +18 new cases (harvest / compost / Pitfall 10 / sentinel fallback / immutability)
|
||||||
|
- `src/sim/garden/index.ts` — export harvest/compost/SimContext
|
||||||
|
- `src/sim/index.ts` — re-export `./memory`
|
||||||
|
- `content/seasons/01-soil/fragments.yaml` — replaced single placeholder with 14 authored fragments + sentinel
|
||||||
|
- `src/ui/index.ts` — re-export `./journal`
|
||||||
|
- `src/App.tsx` — mount `<FragmentRevealModal />` + `<JournalIcon />`
|
||||||
|
- `src/game/scenes/Garden.ts` — SimContext at create(); harvest/compost pointer dispatch; reveal-flow detection in update()
|
||||||
|
- `package.json` — new `check:bundle-split` script; `ci` chain extended
|
||||||
|
|
||||||
|
## Per-tag Distribution
|
||||||
|
|
||||||
|
| Tag | Count | Notes |
|
||||||
|
| --------------- | ----- | ---------------------------------------------------- |
|
||||||
|
| warm | 9 | rosemary pool. Worst-case 8th-harvest depth + 1 buffer |
|
||||||
|
| contemplative | 3 | yarrow pool. Yarrow unlocks @ harvest 3 |
|
||||||
|
| heavy | 2 | winter-rose pool. Winter-rose unlocks @ harvest 6 |
|
||||||
|
| (Markdown warm) | 1 | lura-first-letter.md |
|
||||||
|
| (Markdown heavy) | 1 | winter-rose-night.md |
|
||||||
|
| _meta | 1 | season1.soil._exhaustion sentinel |
|
||||||
|
| **Total** | **17** | |
|
||||||
|
|
||||||
|
The yarrow + winter-rose pool sizes (3 + 2 = 5 contemplative-or-heavy entries plus the 2 long-form Markdown carrying tonal weight) reflect that those plants unlock progressively into the playthrough — the player has fewer harvests left to draw from those pools, and an over-deep contemplative pool is wasted. If a playtest shows the contemplative or heavy pool feeling thin, the writer can add more without changing any code (the pool is purely data; the selector consumes whatever's authored).
|
||||||
|
|
||||||
|
## Plant-type Unlock Thresholds (CONTEXT D-05, finalized)
|
||||||
|
|
||||||
|
| Plant | Unlocks at harvest # | Notes |
|
||||||
|
| ----------- | -------------------- | --------------------------------------------------------------------------------- |
|
||||||
|
| rosemary | 0 (start) | Available from first plant. Warm pool. |
|
||||||
|
| yarrow | 3 | Spaced before Lura's mid-beat (4th harvest, D-14) so the player feels the unlock just before the conversation. |
|
||||||
|
| winter-rose | 6 | Spaced before Lura's farewell beat (8th harvest, D-14) so the heavy plant arrives in tonal alignment with the arc's turn. |
|
||||||
|
|
||||||
|
These are tunable in playtest within ±1; the model (harvest-count thresholds, not wall-time gates) is locked per the STRY-10 contract.
|
||||||
|
|
||||||
|
## Pool Exhaustion Behavior (RESEARCH Pitfall 8)
|
||||||
|
|
||||||
|
**Chosen behavior:** sentinel fallback. When `filterPool()` returns an empty array, `selectFragment()` looks up the fragment with id `season1.soil._exhaustion` (authored in fragments.yaml, tagged `['_meta']`) and returns it. If even the sentinel is missing (degenerate test fixture), the selector returns `null` and `harvest()` returns the original state reference unchanged (the player's tap was a no-op — the safest possible behavior since refusing to harvest preserves the ready plant).
|
||||||
|
|
||||||
|
**Documented in:**
|
||||||
|
|
||||||
|
- `src/sim/memory/selector.ts` (docblock).
|
||||||
|
- `content/seasons/01-soil/fragments.yaml` (the sentinel entry's comment block).
|
||||||
|
- `src/sim/memory/selector.test.ts` covers (a) sentinel-returned-when-pool-empty, (b) null-returned-when-sentinel-missing, (c) sentinel-NEVER-returned-via-normal-pool.
|
||||||
|
|
||||||
|
**Why sentinel over repeat-most-recent**: the no-dup invariant on `harvestedFragmentIds` is load-bearing for downstream consumers (Journal de-dup, Plan 02-05's letter slot vocabulary, Plan 02-04's Lura beat counters that depend on count). A repeat-most-recent path would silently re-grow `harvestedFragmentIds` past the corpus size, polluting these consumers. The sentinel fragment is a real id appended exactly once on first exhaustion (and never again — it itself is in the no-dup set after).
|
||||||
|
|
||||||
|
## scripts/check-bundle-split.mjs Heuristic — first-try assessment
|
||||||
|
|
||||||
|
**First-try result:** the structural assertion passes via `chunkContentMatch=true`. Phase 2 is currently in eager-corpus mode (the `fragments` export inlines all Season-1 yaml + Markdown into the main bundle as `?raw` strings), so the chunk content match fires on the source-path `/content/seasons/01-soil/` and on the literal fragment id `season1.soil.first-bloom`.
|
||||||
|
|
||||||
|
**chunkNameMatch=false** is the expected state for Phase 2 — Vite does not emit a separate Season-1 chunk while the eager path keeps the same source modules in the main bundle (build emits `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings noting this). When Plan 02-04+ switches consumers to lazy-only, `chunkNameMatch` will start firing and the warnings will resolve.
|
||||||
|
|
||||||
|
**No tuning was needed.** The OR-of-three structural checks gives the verifier room to evolve as Phase 4+ Season-2 onboarding lands without forcing the heuristic to be tight on Day 1.
|
||||||
|
|
||||||
|
## Garden Scene Fragment-Loading Approach
|
||||||
|
|
||||||
|
**Chosen:** eager `fragments` export filtered to Season 1, captured at `Garden.create()` time, threaded through every `simulateOneTick` call via `SimContext`.
|
||||||
|
|
||||||
|
**Trade-off vs. `await loadSeasonFragments(1)`**: the eager path is simpler — Phaser's `create()` is synchronous, so an `await` would require an async init dance (set an empty corpus, load, swap; or use `init()` + Promise + `create()` chaining). For Phase 2's Season-1-only scope, the eager path is the minimum-viable choice.
|
||||||
|
|
||||||
|
The PIPE-02 lazy structural plumbing is independently verified by `check-bundle-split.mjs`, so Phase 4+ Season-2 onboarding can swap to `await loadSeasonFragments(currentSeason)` (probably in `init()`) without re-litigating the architecture. Documented at `src/game/scenes/Garden.ts:55` (the SimContext docblock).
|
||||||
|
|
||||||
|
## Manual Smoke Test
|
||||||
|
|
||||||
|
Not performed in this execution session (sequential automated executor; user has not yet run `npm run dev`). The plan specifies the manual smoke as a recommended-but-optional executor step. Structural verification is comprehensive:
|
||||||
|
|
||||||
|
- 217/217 Vitest cases green (was 163 before this plan; +54 new — 16 selector, 18 commands extension, 7 Journal, 6 FragmentRevealModal, 3 journal-icon, 3 check-bundle-split, 1 commands rewording).
|
||||||
|
- `npm run lint` exits 0 (zero ESLint sim-purity violations; Pitfall 1 still mechanically defended).
|
||||||
|
- `npm run build` exits 0 (Vite parses all 17 fragments — schema violation would fail the build per PIPE-01).
|
||||||
|
- `npm run ci` exits 0 end-to-end with `check:bundle-split` integrated.
|
||||||
|
- Plan 02-05's Playwright e2e (PIPE-07) will exercise the full Begin → Plant → Grow → Harvest → reveal-modal → journal loop visually under a real browser.
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
See key-decisions in frontmatter (8 entries). Headlines:
|
||||||
|
|
||||||
|
1. Pool exhaustion: sentinel fallback (`season1.soil._exhaustion`), not repeat-most-recent — preserves the no-dup invariant on `harvestedFragmentIds`.
|
||||||
|
2. Plant-type unlock thresholds: rosemary @ 0 / yarrow @ 3 / winter-rose @ 6 (Plan author's discretion within D-05; aligned with Lura beat cadence at 1/4/8).
|
||||||
|
3. Garden scene uses the EAGER `fragments` corpus filtered to Season 1, not `loadSeasonFragments(1)` await. Simpler synchronous create(); PIPE-02 lazy plumbing is structurally verified for Phase 4+ to exploit.
|
||||||
|
4. Compost beat content shipped (compost-acknowledgements.ink, 6 lines) but NOT yet rendered — Plan 02-04 owns the Ink runtime; Garden.ts has a TODO at the wiring point.
|
||||||
|
5. Journal-icon 'j' hotkey deferred to Plan 02-05 (Settings hotkey work).
|
||||||
|
6. FragmentRevealModal silent-dismiss on unresolvable id (defensive, single-step setState transition).
|
||||||
|
7. Knuth multiplicative hash for the seedHash spreads adjacent (count, tick) pairs across the 32-bit seed space.
|
||||||
|
8. Sentinel exclusion is dual-defended (schema-tag check + selector branch).
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — the plan executed almost exactly as written. Two minor tightenings applied during authoring:
|
||||||
|
|
||||||
|
### Tightenings (not deviations — within plan author's discretion)
|
||||||
|
|
||||||
|
1. **Authored 17 fragments instead of the plan's "≥17 (≥14 yaml + ≥2 md + 1 sentinel)" target.** Plan W6 fix called for ≥9 warm; shipped exactly 9 yaml-warm (plus the 1 lura-first-letter.md warm = 10 warm total when counting Markdown). Heavy pool sized to 2 yaml + 1 md = 3 (matches the conservative-but-deep ratio for late-game unlocks). All targets met or exceeded.
|
||||||
|
2. **Added a `journal-icon.test.tsx` file (3 cases) the plan didn't explicitly request.** The plan's task-2 acceptance criteria called for `selectJournalRevealed` to be referenced in the icon (verified by grep) but did not mandate Vitest coverage of the icon component. Adding 3 cases for ~20 LoC was cheap insurance and tightens the D-23 pre-first-harvest invisibility guarantee.
|
||||||
|
|
||||||
|
Neither tightening expanded scope or altered any architectural decision; both stayed within the plan's "Claude's discretion within reason" envelope.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
None — the plan was unusually well-specified and the implementation matched it almost line-for-line. The only friction point was a transient: the original plan-text seedHash formula (`harvestedFragmentIds.length * 2654435761 + tile.plant.plantedAtTick`) sums two integers but does not coerce the result to 32-bit, which means very large pre-existing harvest counts would push the seed past `Number.MAX_SAFE_INTEGER` long-term. Added `| 0` (32-bit signed-integer truncation) on the result; mulberry32 internally re-coerces to unsigned via `>>> 0`, so the final RNG output is unaffected. Documented in the harvest() docblock.
|
||||||
|
|
||||||
|
## TDD Gate Compliance
|
||||||
|
|
||||||
|
This plan is `type: execute`, not `type: tdd`. No RED → GREEN → REFACTOR commit-sequence gating applies. Tests landed alongside implementation in Tasks 1–3.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None — no external service configuration required. All work is in-tree TypeScript / authored content / a single Node ESM verification script.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- Plan 02-04 (Lura's Ink dialogue + gate beats): can build directly on top. Lura's Ink runtime swaps in for the compost-acknowledgements TODO at `src/game/scenes/Garden.ts` and for Lura's beat-fire surface (1st / 4th / 8th harvest gated by `harvestedFragmentIds.length`). The required `unlockedPlantTypes` and `harvestedFragmentIds` writes are now flowing through the store correctly.
|
||||||
|
- Plan 02-05 (offline catchup + letter + Settings + Playwright e2e): can build directly on top. The harvest pipeline produces real `harvestedFragmentIds` entries that Plan 02-05's offline auto-harvest path can append to; the Memory Journal already renders any id the offline path adds (verified by Journal.test.tsx — adding ids to the store re-renders the modal under Season 1).
|
||||||
|
|
||||||
|
**No blockers, no IOUs, no carried-over technical debt this plan produced.** The eager `fragments` corpus + Plan 02-02's INEFFECTIVE_DYNAMIC_IMPORT warnings remain — both inherited from Plan 02-02 with the same documented Plan 02-04+ resolution path.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
Verification before this section was added:
|
||||||
|
|
||||||
|
- src/sim/memory/pool.ts: FOUND
|
||||||
|
- src/sim/memory/selector.ts: FOUND
|
||||||
|
- src/sim/memory/selector.test.ts: FOUND
|
||||||
|
- src/sim/memory/index.ts: FOUND
|
||||||
|
- src/ui/journal/Journal.tsx: FOUND
|
||||||
|
- src/ui/journal/Journal.test.tsx: FOUND
|
||||||
|
- src/ui/journal/FragmentRevealModal.tsx: FOUND
|
||||||
|
- src/ui/journal/FragmentRevealModal.test.tsx: FOUND
|
||||||
|
- src/ui/journal/journal-icon.tsx: FOUND
|
||||||
|
- src/ui/journal/journal-icon.test.tsx: FOUND
|
||||||
|
- src/ui/journal/index.ts: FOUND
|
||||||
|
- content/seasons/01-soil/fragments/lura-first-letter.md: FOUND
|
||||||
|
- content/seasons/01-soil/fragments/winter-rose-night.md: FOUND
|
||||||
|
- content/dialogue/season1/compost-acknowledgements.ink: FOUND
|
||||||
|
- scripts/check-bundle-split.mjs: FOUND
|
||||||
|
- scripts/check-bundle-split.test.mjs: FOUND
|
||||||
|
- src/sim/garden/commands.ts (modified): FOUND
|
||||||
|
- src/sim/garden/commands.test.ts (modified): FOUND
|
||||||
|
- src/sim/garden/index.ts (modified): FOUND
|
||||||
|
- src/sim/index.ts (modified): FOUND
|
||||||
|
- src/content/schemas/fragment.ts (modified): FOUND
|
||||||
|
- content/seasons/01-soil/fragments.yaml (modified): FOUND
|
||||||
|
- src/ui/index.ts (modified): FOUND
|
||||||
|
- src/App.tsx (modified): FOUND
|
||||||
|
- src/game/scenes/Garden.ts (modified): FOUND
|
||||||
|
- package.json (modified): FOUND
|
||||||
|
- Commit f192e82 (Task 1): FOUND in `git log --oneline -5`
|
||||||
|
- Commit 572c861 (Task 2): FOUND in `git log --oneline -5`
|
||||||
|
- Commit 39bfcd2 (Task 3): FOUND in `git log --oneline -5`
|
||||||
|
- `npm run ci` exits 0: VERIFIED
|
||||||
|
- 217/217 tests pass: VERIFIED
|
||||||
|
- `node scripts/check-bundle-split.mjs` exits 0 after build: VERIFIED
|
||||||
|
- ESLint sim-purity rule: zero violations (lint exits 0)
|
||||||
|
- Build: `npm run build` exits 0; all 17 fragments parse without schema violation
|
||||||
Reference in New Issue
Block a user