Sub-issue of #146. PRP 3 milestones M15 (Analytics MVP slice) + M16 (Bot Level 0 filler). Reference: `PRPs/cardshed-03-experience-prp.md` lines 1015–1054.
Scope
M15 — Analytics
- `packages/analytics/src/{events,subscribe}.ts` + `sinks/{consoleSink,jsonlSink}.ts`
- `apps/ui/src/dispatcher/LocalDispatcher.ts` updated to forward `result.events` → `analytics.subscribe`
- Every event gets a `ts` OUTSIDE `@cardshed/core` (core has no time, per `.claude/rules/core-determinism.md`)
- Persisted to `localStorage["cardshed:events:"]` as JSONL
M16 — Bot Level 0
- `packages/ai/src/{index,level0-random}.ts`
- PlayerSetup: per-seat "Human" / "Bot 0" radio
- `matchStore.autoplayBotTurn()` effect — when `isBot(currentActor)`, calls `bot.decide → dispatch`
- `Bot.decide(view: PrivatePlayerView, playerId): Promise`
- `level0.decide()` uses `getLegalActions` + picks uniformly at random — NEVER reads full `MatchState`
Acceptance
Common bugs (from PRP 3)
- `Date.now()` inside `@cardshed/core` (FORBIDDEN — analytics adds it)
- localStorage quota overflow on long sessions
- Bot reading full `MatchState` (FORBIDDEN)
- Infinite loop on bot turn — `getLegalActions` returns `[Stop]` worst case so progress is guaranteed
Complexity
S + S = ~5–6 hours.
Sub-issue of #146. PRP 3 milestones M15 (Analytics MVP slice) + M16 (Bot Level 0 filler). Reference: `PRPs/cardshed-03-experience-prp.md` lines 1015–1054.
Scope
M15 — Analytics
M16 — Bot Level 0
Acceptance
Common bugs (from PRP 3)
Complexity
S + S = ~5–6 hours.