- src/sim/offline/: OfflineEventBlockSchema (Zod) + EMPTY_OFFLINE_EVENTS
+ aggregateOfflineEvent pure aggregator (D-19); 14 tests green
- src/sim/garden/auto-harvest.ts: autoHarvestReadyPlants silent-mode
branch (D-10); reuses harvest() pipeline so selector + Pitfall 10
unlocks + STRY-10 Lura gate all run identically; BLOCKER 3 invariant
preserved (no lastTickAt writes); 7 tests green
- simulateOneTick: ctx.silent triggers auto-harvest sweep before tick
increment; active-play path unchanged (silent defaults false)
- content/dialogue/season1/letter-from-the-garden.ink: authored skeleton
with VAR plants_bloomed / fragment_titles / lura_was_here per D-17/D-18;
bible voice, anti-FOMO compliant, 24h cap silent in voice (D-11)
- ink-loader: loadInkStory union extended with letter-from-the-garden;
separate letterStoryGlob for lazy code-split chunk; INK_VARIABLE_MAP
gains plants_bloomed / fragment_titles / lura_was_here slots reading
from session.pendingLetterEventBlock
- src/ui/letter/letter-renderer.ts: pure buildLetterSlots helper —
prefers fragment first-sentence body for tonal weight, slugified-id
fallback; 10 tests green
- npm run compile:ink emits 5 .ink.json files (was 4); Vite emits the
letter as a separate lazy chunk (letter-from-the-garden.ink-*.js)
- 295/295 tests green (was 264; +31 new); npm run ci exits 0
- src/sim/narrative/beat-queue.ts: LuraBeatId / LuraBeatProgress contracts
matching V1Payload.luraBeatProgress + NarrativeSlice; INITIAL frozen.
- src/sim/narrative/lura-gate.ts: LURA_BEAT_THRESHOLDS = {1: arrival,
4: mid, 8: farewell}; advanceLuraBeatProgress / resolvePendingLuraBeat /
isLuraBeatPending — pure, no inkjs import, no Date.now (sim-purity rule
green). The gate counts harvest events, never wall-clock time, so STRY-10
holds.
- src/sim/narrative/lura-gate.test.ts: 17 cases including the load-bearing
STRY-10 case (24 hours of FakeClock advance with 0 harvests leaves all
flags + pending false). Pitfall 10 boundaries pinned at 3/4/5 and 7/8/9.
pending-set-already + already-visited carry-throughs covered.
- src/sim/garden/commands.ts: harvest() now calls advanceLuraBeatProgress
AFTER the harvest commit (Pitfall 10 — same-tick boundary). The new
luraBeatProgress field flows through the returned SimState and into the
store via the existing Garden.update() path.
- src/sim/garden/commands.test.ts: +5 cases pinning the harvest → beat
gate edges (1st→arrival, 4th→mid, 8th→farewell, between-threshold
no-fire, pending preservation when player hasn't visited).
- src/sim/index.ts: re-export ./narrative.
67/67 sim tests green; npm run lint + build exit 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>