josh
c46fc75549
fix(02-06,G2): first-run hint after Begin — close A-Dark-Room first-prompt gap
...
Adds a single bible-voice line ("Begin where the soil is bare.") that
surfaces immediately after BeginScreen dismisses on first run and
auto-dismisses when the player makes their first plant. Closes G2
first-impression UX gap from 2026-05-09 live UAT — the post-Begin state
no longer leaves a brand-new player staring at a 4×4 grid with no
instruction.
Implementation:
- content/seasons/01-soil/ui-strings.yaml: first_run_hint key added
(recommended copy from plan; bible voice — warm, specific, contemplative)
- src/content/schemas/ui-strings.ts: UiStringsSchema extended with
first_run_hint: z.string().min(1) — MANDATORY because Zod default strip
mode silently drops unknown keys from parsed.data
- src/store/session-slice.ts: firstRunHintDismissed + dismissFirstRunHint
added (session state ONLY — NOT persisted to V1Payload, no migrations[2])
- src/ui/first-run/FirstRunHint.tsx: subscribes to tiles slice, dismisses
on first plant !== null transition; renders externalized line via
uiStrings[1]?.first_run_hint
- src/ui/first-run/{index.ts}, src/ui/index.ts: barrel + re-export wired
- src/App.tsx: <FirstRunHint /> mounted between BeginScreen and SeedPicker
Vitest: 6 new behavioral cases green (hidden when Begin still up, hidden
when dismissed, renders externalized line, reads uiStrings, auto-dismisses
on first plant, stays dismissed on subsequent tile changes). 324/324
total green; npm run ci exits 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-09 12:13:04 -04:00
josh
f192e8298c
feat(02-03): Season-1 fragments + sim/memory selector + harvest/compost commands
...
Task 1 of Plan 02-03: ship Season-1 authored content + the deterministic
fragment selector + extend sim/garden/commands.ts with harvest + compost.
Content (≥17 Season-1 fragments under /content/seasons/01-soil/):
- 14 in fragments.yaml (9 warm / 3 contemplative / 2 heavy + 1 _meta sentinel)
- 2 long-form Markdown fragments (lura-first-letter.md, winter-rose-night.md)
- Pool depth (W6): warm pool ≥9 satisfies the worst-case all-rosemary
playthrough at the 8th-harvest Lura threshold (CONTEXT D-14)
- All ids match /^season1\.[a-z0-9._-]+$/ (FragmentSchema regex; CLAUDE.md
stable-string-ID rule); bible voice maintained throughout
FragmentSchema extension (back-compat — tags is optional):
- Optional `tags: z.array(z.string()).optional()` for tonal-register gating
- Reserved tag `_meta` excludes the exhaustion sentinel from the normal pool
src/sim/memory/ (new module):
- pool.ts — filterPool() pure helper (Season + tonal-register + no-dup gates)
- selector.ts — selectFragment() deterministic + mulberry32 PRNG +
EXHAUSTION_FALLBACK_ID for Pitfall 8 fallback
- selector.test.ts — 16 tests covering gating / no-dup / determinism /
sentinel-fallback / sentinel-exclusion-from-normal-pool
- index.ts — barrel; src/sim/index.ts re-exports
src/sim/garden/commands.ts (extended):
- harvest() pure command — empties tile, appends one fragment id,
re-computes unlockedPlantTypes (Pitfall 10: thresholds checked AFTER
the harvest commit). Refuses immature plants and OOR indices.
- compost() pure command — empties tile regardless of stage; no fragment
yield (D-07); no resource refund (D-04 = infinite seeds).
- SimContext interface — application-layer-injected (fragments, currentSeason)
- simulateOneTick() takes optional ctx (default empty pool); harvest/compost
branches added to the kind switch.
- BLOCKER 3 invariant preserved — sim writes tickCount, never lastTickAt.
Plant-type unlock thresholds (CONTEXT D-05, plan author's discretion):
- rosemary @ count 0 (start)
- yarrow @ count 3 (third harvest)
- winter-rose @ count 6 (sixth harvest)
commands.test.ts: +18 new cases (harvest / compost / Pitfall 10 boundary
on yarrow + winter-rose / sentinel fallback / immutability). 65/65 tests
green across src/sim/memory + src/sim/garden + src/content; lint exits 0;
build green (Vite parses all 17 fragments without schema violation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-09 10:00:38 -04:00
josh
414a554549
feat(02-02): begin screen + seed picker + ui-strings + lazy content split
...
- content/seasons/01-soil/ui-strings.yaml: player-visible Phase-2 copy externalized per CLAUDE.md (Begin / seed picker / post-harvest beat / journal / settings / plant display names); voice reviewed against bible + anti-fomo-doctrine.md
- content/seasons/01-soil/fragments.yaml: placeholder Season-1 fragment file (Plan 02-03 expands to ≥10 authored)
- content/seasons/00-demo/: deleted (Phase-1 demo replaced)
- src/content/schemas/ui-strings.ts: UiStringsSchema (Zod) — validates structure of every season's ui-strings.yaml at load time
- src/content/schemas/index.ts + src/content/index.ts: re-export UiStringsSchema/UiStrings
- src/content/loader.ts: eager `uiStrings` glob + PIPE-02 lazy `loadSeasonFragments(seasonId)` (Plan 02-03+ exploit)
- src/ui/begin/use-audio-bootstrap.ts: bootstrapAudioContext() lazy-creates + resumes (RESEARCH Pattern 9; Pitfall 5 mitigation — context construction inside the gesture for iOS Safari) + installFirstInteractionGestureHandler() one-shot for D-22 returning players + __resetAudioBootstrapForTest()
- src/ui/begin/BeginScreen.tsx: D-21 typographic Begin screen — title + subtitle + CTA from uiStrings[1].begin; onClick calls bootstrapAudioContext synchronously inside the click event then dismisses the session gate (D-22)
- src/ui/begin/BeginScreen.test.tsx: 4 tests — render / D-22 skip / click bootstraps + dismisses / subtitle string
- src/ui/garden/SeedPicker.tsx: D-02 inline DOM popover; subscribes to 'tile-clicked-coords'; renders one button per unlocked plant type from uiStrings[1].plants; click enqueues plantSeed command via store.enqueueCommand
- src/ui/garden/SeedPicker.test.tsx: 6 tests — initial-null / coords-positioned / unlocked-only / enqueue / dismiss / multi-plant; mocks game/event-bus to avoid Phaser canvas init under happy-dom (deviation Rule 3)
- src/ui/{begin,garden,index}.ts: barrels
- src/App.tsx: mount BeginScreen + SeedPicker as overlay siblings to PhaserGame
- src/PhaserGame.tsx: bootstrap unlockedPlantTypes=['rosemary'] for first-run; install gesture handler + scene-ready listener
- npm run ci exits 0; 163/163 tests pass (10 new this commit + 25 from Task 1 + 128 baseline)
2026-05-09 09:43:47 -04:00