diff --git a/.claude/hooks/check-commit-format.sh b/.claude/hooks/check-commit-format.sh index d0f0cea..0bb1015 100755 --- a/.claude/hooks/check-commit-format.sh +++ b/.claude/hooks/check-commit-format.sh @@ -74,7 +74,7 @@ SUBJECT="$(printf '%s\n' "$MSG" | head -1)" # --- Validation ---------------------------------------------------------- # 1. Subject grammar. -SCOPE_ALLOW='knowrag|api|ui|mcp|ingest|repo|platform|stacks|state|ops|ci|docs|release' +SCOPE_ALLOW='knowrag|reliquary|cardshed|api|ui|mcp|ingest|repo|platform|stacks|state|ops|ci|docs|release' # Description: starts lowercase, may contain any non-newline (incl. internal # periods like SemVer `v0.1.0`), but must NOT end with a trailing period # immediately before the ` (#N)` issue ref. diff --git a/@lab/ll-CARDSHED/.claude/rules/core-determinism.md b/@lab/ll-CARDSHED/.claude/rules/core-determinism.md new file mode 100644 index 0000000..ff7306f --- /dev/null +++ b/@lab/ll-CARDSHED/.claude/rules/core-determinism.md @@ -0,0 +1,166 @@ +# Core Determinism — MANDATORY for `apps/core/` + +> **Load-bearing.** The rules engine is the *truth machine*. If purity breaks, replay breaks, anti-cheat breaks, bot simulation breaks, and online clients desync. This rule encodes the PRP 2 success criteria as a permanent guardrail. + +This rule applies to **every file under `@lab/ll-CARDSHED/apps/core/src/core/`**. It does NOT apply to `apps/ui/`, `apps/server/`, `scripts/`, or tests. + +--- + +## 1. Forbidden in `src/core/**` + +The following are **banned**, enforced by ESLint: + +- `Math.random()` — randomness goes through `prng.mulberry32(seed)`. +- `Date.now()`, `new Date()`, `performance.now()` — the engine has no concept of wall-clock time. Timestamps belong to PRP 3's event emitter. +- `crypto.getRandomValues()` — same reason as `Math.random`; the seed source lives outside core. +- `setTimeout`, `setInterval`, `requestAnimationFrame` — the engine is synchronous. +- `console.*` — emit events via the `GameEvent` union, not log lines. +- `fetch`, `XMLHttpRequest`, `WebSocket`, `fs`, `child_process`, `process.env` — no I/O. +- Module-level mutable state — every reducer derives next-state from inputs only. + +The only exception: `prng.ts` itself uses `Math.imul` (a math operation, not randomness). It does NOT call `Math.random`. + +--- + +## 2. Required patterns + +### 2.1 Errors are values + +Validation failures MUST return `{ ok: false, error: { code, message, details? } }`. Throws are reserved for **invariant violations** — programmer bugs that should crash the process so the test suite catches them. + +```ts +// CORRECT — user-facing validation +return { ok: false, error: { code: "ATTACK_INVALID_SIZE", message: "..." } }; + +// CORRECT — invariant (cannot happen if the engine is correct) +if (!c) throw new Error("INVARIANT: deck exhausted during initial deal"); + +// WRONG — throwing on user input +if (cards.length !== 1 && cards.length !== 3 && cards.length !== 5) { + throw new Error("Invalid attack size"); // ❌ should return ok:false +} +``` + +### 2.2 Pure reducers + +Every state-mutating function MUST return a fresh `MatchState`. The input is treated as read-only. + +```ts +// CORRECT +function submitAttack(state: MatchState, ...): ActionResult { + const next = structuredClone(state); // or .slice() per array + // ...mutate next, never state + return { ok: true, state: next, events }; +} + +// WRONG +function submitAttack(state: MatchState, ...): ActionResult { + state.round.phase = "AwaitingDefense"; // ❌ mutates input + return { ok: true, state, events }; +} +``` + +### 2.3 Seeded randomness only + +`shuffleDeck(deck, seed)` is the **only** randomness entry-point. The `seed` parameter is REQUIRED (per PRP 3 NEW #6). Same seed in → byte-identical permutation out, on every JS runtime. + +```ts +// CORRECT +const shuffled = shuffleDeck(deck, matchState.rngSeed); + +// WRONG +const shuffled = deck.slice().sort(() => Math.random() - 0.5); // ❌ ban +``` + +### 2.4 Hidden information is law + +`createPublicView(state, viewerId?)` MUST NOT leak opponent hand contents or deck order. `createPrivateView(state, viewerId)` reveals only the viewer's own hand. + +```ts +// CORRECT +players: state.players.map((p) => ({ + ..., + hand: { ownerId: p.id, count: p.hand.length, publiclyKnown: [] }, +})) + +// WRONG +players: state.players.map((p) => ({ ..., hand: p.hand })) // ❌ leaks +``` + +### 2.5 Card.id is opaque + +Consumers MUST use `card.suit` and `card.rank` for game logic. Never parse `card.id` to extract them. The id format is presentational and may change. + +```ts +// CORRECT +if (card.suit === trump && card.rank > attack.rank) return true; + +// WRONG +const [suit, rank] = card.id.split("-"); // ❌ never +``` + +--- + +## 3. Conservation invariant + +Across every legal state transition, exactly 52 cards must be accounted for: + +``` +sum(player.hand.length) + deck.length + discard.length + pendingAttackCardCount(round.pendingAttack) === 52 +``` + +The property test `conservation.property.test.ts` enforces this across ≥200 random-legal-action sequences. **Any change that breaks this invariant is a bug — never widen the property to make red green.** + +--- + +## 4. Win-check ordering + +`checkWin(state, playerId)` MUST run **after** `drawToMinimum` in every refill path. A player at 0 cards with a non-empty deck will be refilled to 5 and is NOT a winner. + +`checkWin` MUST run on BOTH the full-defence and partial-defence branches of `stopDefending` (per PRP 2 #3) — a defender CAN win on the trailing edge of a round. + +--- + +## 5. No reshuffles + +Once `deck.length === 0`, draws stop. The discard pile NEVER goes back into the deck. This is Rules v2.0 and is non-negotiable. + +--- + +## 6. When adding new functions + +1. Add the type signature to the Rules Engine API in `src/core/index.ts` exports. +2. Write the test FIRST under `src/core/__tests__/.test.ts`. +3. Implement the function with `structuredClone` for any state derivation. +4. Confirm the function returns `ActionResult` (or a pure value type) — never `void`, never `throw` on user input. +5. Re-run the property test `conservation.property.test.ts` to confirm the invariant still holds. +6. Re-run `sim-smoke.ts` (1000 games) to confirm no regressions. + +--- + +## 7. Validation + +These commands MUST pass on every PR that touches `apps/core/`: + +```bash +cd @lab/ll-CARDSHED/apps/core +npm run typecheck # 0 errors +npm run lint # 0 errors, 0 warnings (the no-Math.random rule must be active) +npm test # all 82+ tests pass +npm run sim-smoke # 1000 games, <30s, 0 conservation violations +``` + +If any of these go red, the engine is broken until they're green — no exceptions, no skip-failing-test commits. + +--- + +## 8. Why this rule exists + +The deterministic core is what lets us: + +- **Replay any match** from `(matchSeed, actions[])` — bug reproduction, balance analysis, anti-cheat audit. +- **Run millions of bot-vs-bot games** for balance metrics — only possible because the engine has zero I/O. +- **Mechanically port to Rust** for the online server (PRP 1 §2.3) — the TS implementation is the canonical spec; non-deterministic JS hides in plain sight when ported. +- **Trust client-side legal-action highlighting** — the client and server agree because both run the same engine. + +One `Math.random()` slip in this layer breaks all four. The rule is mechanical because the consequence is invisible until it bites. diff --git a/@lab/ll-CARDSHED/.claude/rules/ui-design-pipeline.md b/@lab/ll-CARDSHED/.claude/rules/ui-design-pipeline.md new file mode 100644 index 0000000..389ffa1 --- /dev/null +++ b/@lab/ll-CARDSHED/.claude/rules/ui-design-pipeline.md @@ -0,0 +1,148 @@ +# UI Design Pipeline — MANDATORY for ll-CARDSHED + +> **EPIC RULESET — load-bearing once `apps/ui/` exists.** The interface IS the game-feel layer. There is no hand-rolled UI escape hatch. + +This rule strengthens the root `.claude/rules/ui-design.md`. Where they conflict, **this file wins** inside `@lab/ll-CARDSHED/`. + +> Note: this rule has **no effect** until `apps/ui/` lands at PRP 3 M1. The bootstrap stub has no UI surface. From M1 onward, every UI change goes through the pipeline below. + +--- + +## 1. The Pipeline (every UI change goes through it) + +``` + ┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐ + │ IDEA │ → │ STITCH │ → │ AGENT-BROWSER │ + │ (PRP 3 §B │ │ (skill + MCP │ │ (Vercel-based │ + │ screen spec) │ generation) │ │ validation) │ + └──────────────┘ └──────────────────┘ └────────┬────────┘ + │ + ▼ if complex eval + ┌─────────────────────┐ + │ PLAYWRIGHT MCP + │ + │ SKILLS (deep e2e) │ + └─────────────────────┘ +``` + +You do **not** skip a stage. You do **not** reorder them. You do **not** invent UI without a Stitch artifact upstream. + +--- + +## 2. STAGE 1 — STITCH (generation / transformation) + +**Use one of these — nothing else:** + +| Tool | When | +|------|------| +| `Skill: stitch-design` | Default entry-point for any new or modified screen. Wraps prompt-enhancement, design-system synthesis, and screen generation/edit. | +| `Skill: enhance-prompt` | When the input idea is vague — runs first, then hands off to `stitch-design`. | +| `Skill: design-md` | When the design system itself needs synthesis into `.stitch/DESIGN.md`. | +| `Skill: stitch-loop` | When iterating on multiple screens with consistent system (baton-pass loop). | +| `mcp__stitch__*` | Direct MCP calls when the skill abstraction is wrong fit (rare). | + +**Mandatory inputs every Stitch invocation:** + +1. The screen spec from `PRPs/cardshed-03-experience-prp.md` §B. +2. The relevant engine API contract (`apps/core/src/core/rules.ts` + `types.ts`) — the UI must not invent state the engine doesn't expose. +3. The pre-Stitch wireframe (PRP 3 §B per-screen ASCII layout). +4. The current `.stitch/DESIGN.md` (so output stays coherent). + +**Output destination:** +- Screen mockups → `docs/SCREENS/.md` (+ exported assets if Stitch returns them). +- Design system updates → `.stitch/DESIGN.md` (only `stitch-design` / `design-md` write here). + +--- + +## 3. STAGE 2 — AGENT-BROWSER (validation / visualisation) + +Default validator. Use for: dogfooding, screenshot capture, exploratory QA, click-through flows, simple regression smoke. + +**Why agent-browser over Playwright MCP for the default case?** Per project memory: on Ubuntu 26.04, Playwright MCP can't install Chromium reliably; agent-browser ships a bundled browser. It also runs in Vercel-sandbox microVMs for clean-room runs. + +**Use the `agent-browser` skill** for every UI-change verification. Output goes under `dogfood-output//` as `report.md` + screenshots. + +**Mandatory verification gates after every Stitch generation:** +- ✅ Screen renders without console errors. +- ✅ Screenshot captured at the canonical viewport (desktop 1440×900 minimum; add mobile when the storyboard demands it). +- ✅ At least one game-relevant action exercised (submit an attack, beat a card, stop defending) and its result captured. + +--- + +## 4. STAGE 3 — PLAYWRIGHT MCP + SKILLS (complex eval) + +Reach for Playwright **only** when the case demands what agent-browser can't cleanly do: + +- Multi-tab / multi-context scenarios (hot-seat pass-and-play simulation across two viewports). +- Network interception / mock injection (testing the WebSocket reconnection path at PRP 3 M14+). +- Trace recording for performance analysis (card-flip animation budget). +- Programmatic accessibility-tree assertions. +- Long-running e2e suites with reporters. + +**Tools:** `mcp__plugin_playwright_playwright__*` and the `webapp-testing` skill. + +**Do NOT** use Playwright when agent-browser suffices — the heavier setup makes change loops slow. + +--- + +## 5. Hard prohibitions + +- ❌ **Hand-writing screens** — every screen has a Stitch origin in `docs/SCREENS/`. +- ❌ **Inventing design tokens** — read `.stitch/DESIGN.md` and apply via `mcp__stitch__apply_design_system`. If a token doesn't exist, regenerate the design system first. +- ❌ **Claiming a UI change is "done" without `agent-browser` evidence** under `dogfood-output/`. Type-check + unit-test passing ≠ UI works. +- ❌ **Re-implementing rules in the UI.** The UI must consume `@cardshed/core` for every legality check, legal-action enumeration, and view projection. If a UI helper duplicates a rule function, delete the helper and call the core. +- ❌ **Mixing UI frameworks.** React + Tailwind v4 + Radix + framer-motion + Zustand + TanStack Query + Immer. No Material UI, no shadcn-without-Stitch-mapping, no PixiJS (cards are DOM, not canvas). +- ❌ **Mocking the UI surface in screenshot tests** — capture from the running container. + +--- + +## 6. Decision flow (when uncertain) + +``` +UI task arrives + │ + ├─ Vague request ("make the table feel less cramped") + │ → enhance-prompt → stitch-design + │ + ├─ New screen needed + │ → stitch-design (with all 4 mandatory inputs) + │ + ├─ Design system feels incoherent across screens + │ → design-md → mcp__stitch__create_design_system → apply across all screens + │ + ├─ Many screens to evolve together + │ → stitch-loop + │ + ├─ Verify a change works in browser + │ → agent-browser → capture screenshot → save under dogfood-output/ + │ + ├─ Need multi-tab / network mock / a11y tree / trace + │ → playwright MCP + webapp-testing skill + │ + └─ Just code, no visual change? + → still verify with agent-browser; UI plumbing without proof = unverified +``` + +--- + +## 7. Acceptance checklist (every UI PR — applies from PRP 3 M1) + +- [ ] Screen spec referenced from `PRPs/cardshed-03-experience-prp.md` §B +- [ ] Stitch mockup under `docs/SCREENS/.md` with provenance (which prompt, which design-system version) +- [ ] Screen built against the current `.stitch/DESIGN.md` +- [ ] UI consumes `@cardshed/core` for every rule (no rule re-implementation) +- [ ] `agent-browser` run under `dogfood-output//` with `report.md` + screenshots +- [ ] If complex behaviour: Playwright trace under `dogfood-output//traces/` +- [ ] No new design tokens introduced outside `.stitch/DESIGN.md` +- [ ] Lint, type-check, unit-tests green (core + ui both) + +--- + +## 8. Why this rule exists + +Three failure modes we explicitly prevent: + +1. **Bespoke-UI drift** — once a developer hand-rolls one screen, the design system fractures. Every screen Stitch-touched stays coherent. +2. **Unverified-frontend claims** — "tests pass" doesn't mean the screen works. Real-browser evidence under `dogfood-output/` is the only proof we accept. +3. **Rule duplication** — the UI is *tempted* to re-implement "is this card legal?" for ergonomic reasons. That temptation is how clients and servers desync. The engine API is the single source of truth. + +This is policy, not preference. If you find a real reason the pipeline can't apply, document it in `docs/DECISIONS/` and propose a rule amendment — don't bypass. diff --git a/@lab/ll-CARDSHED/.env.example b/@lab/ll-CARDSHED/.env.example new file mode 100644 index 0000000..c737d6d --- /dev/null +++ b/@lab/ll-CARDSHED/.env.example @@ -0,0 +1,16 @@ +# ───────────────────────────────────────────────────────────── +# ll-CARDSHED — environment schema (committed, placeholders only) +# Copy to .env, fill values, NEVER commit .env. +# +# At bootstrap the stack ships a stub container only — these +# vars become load-bearing at PRP 3 M1 when apps/ui/ lands. +# ───────────────────────────────────────────────────────────── + +# ── Service ports (host-side) ──────────────────────────────── +UI_PORT=4343 + +# ── Logging ────────────────────────────────────────────────── +LOG_LEVEL=info + +# ── UI build-time (PRP 3 M1) ───────────────────────────────── +VITE_GAME_TITLE=CARD SHED diff --git a/@lab/ll-CARDSHED/.gitignore b/@lab/ll-CARDSHED/.gitignore new file mode 100644 index 0000000..8c18c2c --- /dev/null +++ b/@lab/ll-CARDSHED/.gitignore @@ -0,0 +1,23 @@ +# Runtime secrets — never commit (use .env.sops when ready) +.env + +# Container volumes +data/ + +# Dogfood / playwright artifacts (keep .gitkeep + curated reports) +dogfood-output/*/ +!dogfood-output/.gitkeep +!dogfood-output/*/report.md + +# Stitch generated screen exports (only DESIGN.md is committed) +.stitch/screens/ +.stitch/projects/ +.stitch/*.tmp + +# Vite / build outputs (PRP 3 M1+) +apps/ui/dist/ +apps/ui/.vite/ + +# Editor noise +.DS_Store +*.swp diff --git a/@lab/ll-CARDSHED/.stitch/DESIGN.md b/@lab/ll-CARDSHED/.stitch/DESIGN.md new file mode 100644 index 0000000..762579c --- /dev/null +++ b/@lab/ll-CARDSHED/.stitch/DESIGN.md @@ -0,0 +1,38 @@ +# Design System: CARD SHED + +**Project ID:** _not-yet-created_ +**Generated:** _placeholder — regenerate via `Skill: stitch-design` at PRP 3 M1_ +**Source seed:** PRP 3 §B (screen inventory) + `apps/core/src/core/types.ts` (state vocabulary) +**Status:** PLACEHOLDER. **Do not hand-edit** — see `.claude/rules/ui-design-pipeline.md`. Regenerate via the skill once the first screen needs design tokens. + +--- + +## Placeholder notice + +This file is intentionally empty of design content. The real design system will be generated by `Skill: stitch-design` (Stitch MCP, Gemini 3.1 Pro or current model) when PRP 3 M1 opens. + +**Until then, do NOT:** + +- Hand-author color tokens, type tokens, motion tokens here. +- Copy `@lab/ll-RELIQUARY/.stitch/DESIGN.md` into this file — Reliquary's "modern-museum × quiet-occult" aesthetic does not fit a card game and would mislead Stitch's design synthesis. +- Reference this file from `apps/ui/src/index.css` until it has real content. + +**When PRP 3 M1 opens, do:** + +1. Read `PRPs/cardshed-03-experience-prp.md` §B (screen inventory + per-screen wireframes). +2. Run `Skill: enhance-prompt` if the aesthetic direction is vague. +3. Run `Skill: stitch-design` with: premise + first screen wireframe + (later) the locked engine API as state vocabulary. +4. The skill will overwrite this file with sections 1–10 (visual theme, palette, typography, components, geometry, motion, layout, voice, Tailwind `@theme` block, provenance). +5. Commit the regenerated file in the same PR as the first screen mockup under `docs/SCREENS/`. + +--- + +## What the generated file will contain + +For reference, see `@lab/ll-RELIQUARY/.stitch/DESIGN.md` — the structure (sections 1–10) is canonical and is enforced by the `design-md` skill. CARD SHED's content will differ: where Reliquary is hushed-museum-warm, CARD SHED is plausibly tabletop-felt-cool (felt-green, ivory cards, decisive typography) — but that's a Stitch decision, not a hand-authored one. + +--- + +## Provenance (filled by `stitch-design`) + +_empty — populated at first generation_ diff --git a/@lab/ll-CARDSHED/.w7-meta b/@lab/ll-CARDSHED/.w7-meta new file mode 100644 index 0000000..4ff8912 --- /dev/null +++ b/@lab/ll-CARDSHED/.w7-meta @@ -0,0 +1,8 @@ +version: "1.0" +description: "ll-CARDSHED — CARD SHED card game (W7 @lab sandbox). Pure-logic core sealed (PRP 2); browser hot-seat MVP next (PRP 3 M1)." +git_trigger: + repository: "w7-mgfcode/w7-base" + branch: "master" +health_checks: + - type: "http" + endpoint: "http://localhost:4343/" diff --git a/@lab/ll-CARDSHED/AGENTS.md b/@lab/ll-CARDSHED/AGENTS.md new file mode 100644 index 0000000..24a0586 --- /dev/null +++ b/@lab/ll-CARDSHED/AGENTS.md @@ -0,0 +1,73 @@ +# AGENTS.md — ll-CARDSHED + +> Agent-side overview for this stack. For full project rules read `CLAUDE.md`; for the platform rules see the repo root `AGENTS.md` and `.claude/rules/`. + +## What this stack is + +ll-CARDSHED is a browser implementation of the CARD SHED card game in `@lab`. **Phase: Core Sealed.** The pure-logic rules engine ships at `apps/core/`; the UI/server/AI layers are PRP 3 work (17 milestones). + +## The non-negotiables + +1. **Core purity.** No `Math.random`, no time calls, no I/O inside `apps/core/`. ESLint enforces — see `.claude/rules/core-determinism.md`. +2. **UI pipeline.** Once `apps/ui/` exists, every UI change goes Stitch → agent-browser → (Playwright if needed). See `.claude/rules/ui-design-pipeline.md`. +3. **No rule variants.** CARD SHED v2.0 is frozen in PRP 2. Variants belong in PRP 3's simulation harness behind an explicit flag. +4. **Replay is law.** Every match is reproducible from `(matchSeed, actions[])`. Any change that breaks this is a bug. + +## Specialist tools for this stack + +| Surface | First-choice tool | +|---------|-------------------| +| Rules engine change | direct edit in `apps/core/src/core/*.ts` + add a test before touching `rules.ts` | +| New / edited screen (PRP 3 M1+) | `Skill: stitch-design` | +| Vague UI prompt | `Skill: enhance-prompt` → `stitch-design` | +| Multi-screen iteration | `Skill: stitch-loop` | +| Design system synthesis | `Skill: design-md` | +| Visual verification | `Skill: agent-browser` | +| Complex e2e / network mocks | `mcp__plugin_playwright_playwright__*` + `Skill: webapp-testing` | +| Stitch direct calls | `mcp__stitch__*` | + +## Useful root agents + +| Agent | When | +|-------|------| +| `policy-gatekeeper` | Before risky bash; before cross-zone changes | +| `repo-visibility-manager` | If you touch this stack's README / PRP index at release time | +| `Plan` | When breaking a PRP 3 milestone into atomic sub-issues before execution | +| `Explore` | Fast lookups within `@lab/ll-CARDSHED/` | + +## Daily commands + +```bash +# Core (library) — no container +cd @lab/ll-CARDSHED/apps/core +npm test # 82/82 green; conservation property ≥200 iters +npm run sim-smoke # 1000-game smoke + +# Stack (stub until PRP 3 M1) +w7 up @lab/ll-CARDSHED +w7 logs @lab/ll-CARDSHED +w7 down @lab/ll-CARDSHED +w7 prune @lab/ll-CARDSHED # destroy (allowed only in @lab) +``` + +## Conventions + +- **Commits**: `type(scope): description (#issue)`. Scope `cardshed` is already in the allow-list. +- **Branches**: `feat/cardshed-*`, `fix/cardshed-*`, `chore/cardshed-*`, kebab-case ≤50 chars. +- **Issue first**: every commit references `#N`. PRP 3 milestones map 1:1 to atomic issues. +- **No `:latest`**: image pins always. +- **No invented tokens**: once `.stitch/DESIGN.md` is generated, it's the source of truth. +- **Tests stay green on master**: `npm test` AND `npm run sim-smoke` AND `npm run lint` AND `npm run typecheck` all clean before push. + +## Bootstrap-phase acceptance checklist + +- [x] `.w7-meta`, `compose.yml`, `.env.example`, `.gitignore` written +- [x] `README.md`, `CLAUDE.md`, `AGENTS.md`, `BLUEPRINT.md` written +- [x] Local rules: `.claude/rules/ui-design-pipeline.md`, `.claude/rules/core-determinism.md` +- [x] `.stitch/DESIGN.md` placeholder (generation deferred to PRP 3 M1) +- [x] Stack discoverable via `w7 stat` (`.w7-meta` present) +- [ ] `w7 up @lab/ll-CARDSHED` boots the placeholder container +- [ ] Policy scripts (`prod-privileged`, `prod-no-root-mount`, `zone-ingress-naming`) exit 0 +- [ ] PRP 3 M1 umbrella issue opened with sub-issues + +When the boxes are ticked, the stack is ready for PRP 3 M1 execution. diff --git a/@lab/ll-CARDSHED/BLUEPRINT.md b/@lab/ll-CARDSHED/BLUEPRINT.md new file mode 100644 index 0000000..542690c --- /dev/null +++ b/@lab/ll-CARDSHED/BLUEPRINT.md @@ -0,0 +1,80 @@ +# BLUEPRINT — ll-CARDSHED + +> Index. The real blueprint is the PRP bundle under `PRPs/cardshed-*.md` at the repo root. This file exists so future contributors landing on the stack can navigate the PRPs without grep. + +--- + +## The PRP bundle + +CARD SHED is decomposed into three layered concerns (per `PRPs/INDEX.md`): + +| # | Layer | Brief | Generated PRP | Status | +|---|-------|-------|---------------|--------| +| 1 | **Strategy & Blueprint** | `PRPs/01-strategy-blueprint.md` | `PRPs/cardshed-01-blueprint.md` | ✅ locked | +| 2 | **Deterministic Core** | `PRPs/02-deterministic-core.md` | `PRPs/cardshed-02-core-prp.md` | ✅ shipped (#139, #140) | +| 3 | **Experience & Distribution** | `PRPs/03-experience-distribution.md` | `PRPs/cardshed-03-experience-prp.md` | ⏭️ next — 17 milestones | + +The bundle was designed for parallel PRP generation: each brief embeds the complete CARD SHED v2.0 rule set as its source of truth, and PRP 2's brief pre-commits the `Card`/`Suit`/`Rank` enum encoding so PRP 3 can be written against the same vocabulary without consuming PRP 2's output. + +--- + +## What's locked (from PRP 1) + +- **Backend**: Rust + Axum (over Go) — sum-type modelling + compile-time exhaustiveness for a rules-dominated codebase. +- **Frontend**: TypeScript + React + Vite + Tailwind v4 + Radix + framer-motion + Zustand + TanStack Query + Immer + Vitest + Playwright. +- **Sync model**: Hybrid snapshot + events with monotonic `seq: u64`. Reconnection via `lastSeenSeq`; replay from `(matchSeed, actions[])`. +- **Module taxonomy**: `GameManager / Deck / TurnController / RulesEngine / ActionValidator / StateReducer / EventBus / UIHandler / PersistenceAdapter`. +- **Persistence**: `sqlx + Postgres 16` durable; `sled` hot match-room cache. +- **Deployment**: Statically-linked Rust binary in `distroless/cc`; static React bundle in nginx; both behind Traefik on `w7-ingress`. + +--- + +## What's shipped (PRP 2 — `apps/core/`) + +- `types.ts` — full Shared Contract: `Card`, `Suit`, `Rank`, `MatchState`, `RoundState`, `PendingAttack`, `BeatenPair`, `Player`, `Action`, `GameEvent`, `PublicGameView`, `PrivatePlayerView`, `ValidationError`, `ActionResult`. +- `rules.ts` — every function on the Rules Engine API: `createDeck`, `shuffleDeck`, `dealInitialHands`, `determineTrumpFromBottomCard`, `startNewRound`, `validateAttack`, `submitAttack`, `canBeat`, `submitBeat`, `stopDefending`, `drawToMinimum`, `checkWin`, `advanceTurnAfter{Full,Partial}Defense`, `rotateDealer`, `getLegalActions`, `createPublicView`, `createPrivateView`, `pendingAttackCardCount`. +- `prng.ts` — `mulberry32(seed)` + seeded Fisher-Yates. The ONLY randomness source in `apps/core/`. +- `__tests__/*.test.ts` — 14 test files, 82 tests. Conservation property holds across ≥200 iterations. +- `scripts/sim-smoke.ts` — 1000 random-legal-bot games complete in <30s with 0 conservation violations. + +**Cross-PRP contradictions resolved** (see `PRPs/cardshed-02-core-prp.md` §"Cross-PRP Contradictions Flagged"): + +1. `beatenPairs` live inside `pendingAttack` until `stopDefending` flushes them — in BOTH full-defence and partial-defence branches. +2. Conservation operand uses `pendingAttackCardCount(pa) = unbeatenCards.length + 2 * beatenPairs.length`. +3. `checkWin(defenderId)` runs after refill in BOTH `stopDefending` branches — a defender CAN win on the trailing edge of a round. +4. `deck[0]` = bottom (trump face); `deck[deck.length-1]` = top (drawn next). +5. `Card.id` is `"{C|D|H|S}-{rank}-{slotHex}[-{saltB36}]"` — opaque; consumers MUST NOT parse the id. + +--- + +## What's next (PRP 3 — 17 milestones) + +Sub-deliverables A/B/C/D/E (per `PRPs/cardshed-03-experience-prp.md` §Implementation Blueprint): + +| Group | Scope | +|-------|-------| +| **A** — MVP Implementation | 17 milestones M1–M17 covering `apps/ui/` scaffold → hot-seat playable → bots → replay → online → polish | +| **B** — UI/UX Spec | Screen inventory + Stitch-driven design system + agent-browser dogfood gates | +| **C** — WebSocket Protocol | Wire envelope, JSON schemas, reconnection semantics, idempotency keys (PRP-1 vocab: `seq`/`clientSeq`/`idempotencyKey`) | +| **D** — Analytics & Simulation | Event taxonomy + balance metrics + bot-vs-bot simulation harness | +| **E** — AI Bot Ladder | Level 0 (random-legal) / Level 1 (heuristic) / Level 2 (MCTS information-set) | + +**Cross-PRP contradictions surfaced by PRP 3** (see PRP 3 §"Newly found"): + +- **NEW #6** — `shuffleDeck(deck, seed)` seed is REQUIRED everywhere; brief is being updated. +- **NEW #7** — wire envelope normalized to PRP-1 vocab: server→client = `seq`; client→server = `clientSeq + idempotencyKey`; ack = `ackClientSeq + seq`. +- **NEW #8** — emission order locked: `RoundStarted → TrumpSelected → CardsDealt`. +- **NEW #9** — MVP treats single round as a match; `RoundEnded` is followed by `MatchEnded`. Multi-round semantics deferred (no wire-format change required). +- **NEW #10** — `Resolving` phase is rendered as `AwaitingDefense` UX-wise with an "auto-suggest Stop" affordance. + +--- + +## How to read this stack + +1. **Start here** (`BLUEPRINT.md`) for the 60-second map. +2. **PRP 1** (`PRPs/cardshed-01-blueprint.md`) — the decision document. +3. **PRP 2** (`PRPs/cardshed-02-core-prp.md`) — the shipped truth machine. +4. **PRP 3** (`PRPs/cardshed-03-experience-prp.md`) — the active execution plan. +5. **`apps/core/src/core/`** — the actual code. +6. **`CLAUDE.md`** — stack-local Claude rules. +7. **`AGENTS.md`** — agent overview. diff --git a/@lab/ll-CARDSHED/CLAUDE.md b/@lab/ll-CARDSHED/CLAUDE.md new file mode 100644 index 0000000..f3eaafa --- /dev/null +++ b/@lab/ll-CARDSHED/CLAUDE.md @@ -0,0 +1,109 @@ +# CLAUDE.md — ll-CARDSHED + +> Project instructions for Claude Code inside this stack. Root rules at `~/w7-base/.claude/rules/` still apply; this file layers stack-specific rules on top. + +## Stack identity + +ll-CARDSHED is a browser implementation of the **CARD SHED** card game (3–4 player, shifting-trump, single-deck). It's a `@lab` sandbox — disposable, instant-deploy via webhook, no production gates. + +**Current phase: CORE SEALED.** The pure-logic rules engine (`apps/core/`) is shipped and tested. The active artifact is `PRPs/cardshed-03-experience-prp.md` — the 17-milestone plan covering MVP + UI/UX + WebSocket + analytics + AI bots. + +## Hard rules (stack-local) + +Always loaded from `.claude/rules/` inside this directory: + +- **`ui-design-pipeline.md`** — MANDATORY Stitch + agent-browser + Playwright workflow once `apps/ui/` exists. **No hand-rolled UI.** No invented design tokens. Every screen has a Stitch origin under `docs/SCREENS/` and an agent-browser validation under `dogfood-output//`. +- **`core-determinism.md`** — Purity guarantees for `apps/core/`. No `Math.random`, `Date.now`, `new Date()`, `performance.now`, `crypto.getRandomValues` outside `prng.ts`. Every reducer returns a fresh `MatchState`. ESLint enforces this. + +These layer on top of root rules (`commit-format.md`, `branch-naming.md`, `security-patterns.md`, etc.). + +## How to work in this stack + +1. **Read the PRP bundle.** Before touching anything, read `PRPs/cardshed-01-blueprint.md` (locked tech-stack + module taxonomy), `PRPs/cardshed-02-core-prp.md` (delivered core + flagged contradictions), and `PRPs/cardshed-03-experience-prp.md` (the active execution plan). +2. **Core is sealed.** Do not modify `apps/core/` rule logic without an explicit issue + the same level of property-test coverage. `npm test` and `npm run sim-smoke` must both stay green at every commit. +3. **UI work goes through the pipeline.** Period. See `.claude/rules/ui-design-pipeline.md`. Does not apply yet — there's no UI — but applies the moment `apps/ui/` lands. +4. **Use the W7 CLI.** `w7 up @lab/ll-CARDSHED`, `w7 logs`, `w7 down`. Direct `docker compose` is acceptable for debugging only. +5. **Commit grammar.** Per root `commit-format.md` — `feat|fix|chore|docs(cardshed): description (#issue)`. The `cardshed` scope is already in the allow-list (added in #139). +6. **Branches.** `feat/cardshed-*`, `fix/cardshed-*`, `chore/cardshed-*`, kebab-case ≤50 chars. +7. **Issue first.** Every commit references `#N`. Open an issue before committing. +8. **One PR per PRP 3 milestone.** PRP 3's 17 milestones are intentionally atomic. Bundling them = bad PRs. +9. **Don't invent rule variants.** CARD SHED v2.0 is frozen in PRP 2. Variant exploration belongs to PRP 3's simulation harness behind an explicit `RuleVariant` flag — never silently in the engine. + +## Common commands + +```bash +# Core (pure-logic library — no container) +cd @lab/ll-CARDSHED/apps/core +npm install +npm test # 82/82 green +npm run typecheck # 0 errors +npm run lint # 0 errors, 0 warnings +npm run sim-smoke # 1000 random-legal-bot games, <30s + +# Stack (bootstrap stub — boots a placeholder until PRP 3 M1) +w7 up @lab/ll-CARDSHED +w7 logs @lab/ll-CARDSHED +w7 down @lab/ll-CARDSHED +w7 prune @lab/ll-CARDSHED # destroy (allowed only in @lab) + +# Compose syntax + envvar coverage +docker compose --env-file .env.example -f @lab/ll-CARDSHED/compose.yml config -q +``` + +## Service map (target — most services do not exist yet) + +| Service | Tech | Container port | Host port | Status | +|---------|------|----------------|-----------|--------| +| `cardshed-placeholder` | alpine 3.20 | — | — | ✅ bootstrap stub | +| `cardshed-ui` | React + Vite + Tailwind v4 + Radix, served by nginx | 8080 | 4343 | ⏭️ PRP 3 M1 | +| `cardshed-server` | Rust + Axum, uvicorn-equivalent | 8383 | 8383 | ⏭️ PRP 3 M3 | +| `cardshed-db` | Postgres 16 | 5432 | 5555 | ⏭️ PRP 3 M3 | + +Start order will be healthcheck-gated once the real services exist: `db` → `server` → `ui`. + +## Design pipeline (active once `apps/ui/` exists) + +``` + Screen spec (PRP 3 §B) + ↓ + Skill: stitch-design (or stitch-loop for multi-screen) + ↓ + docs/SCREENS/.md ← committed + ↓ + Implement UI (consume .stitch/DESIGN.md tokens) + ↓ + Skill: agent-browser → dogfood-output// → screenshots + report.md + ↓ + (if complex eval) playwright MCP + webapp-testing skill + ↓ + PR with all evidence linked +``` + +**Hard prohibition**: claiming a UI change works without agent-browser evidence. Type-check + unit-tests passing ≠ UI works. + +## PRP execution map + +| PRP | What | Where | Status | +|-----|------|-------|--------| +| 1 — Blueprint | Tech-stack + sync model + module taxonomy + roadmap | `PRPs/cardshed-01-blueprint.md` | ✅ locked | +| 2 — Deterministic Core | TS rules engine + types + Vitest + sim-smoke | `apps/core/` + `PRPs/cardshed-02-core-prp.md` | ✅ shipped (#139, #140) | +| 3 — Experience & Distribution | MVP + UI/UX + WebSocket + analytics + AI bots (17 milestones) | `PRPs/cardshed-03-experience-prp.md` | ⏭️ next slice | + +## Out of scope (this iteration of the stack root) + +- Real UI / game screens (PRP 3 M1) +- Real server / WebSocket protocol (PRP 3 M3) +- Real bots (PRP 3 §E) +- Multi-round match semantics (PRP 3 NEW #9 — deferred post-MVP) +- `@prod` promotion +- Card art / asset pipeline + +## Reference + +- `PRPs/cardshed-01-blueprint.md` — locked decisions +- `PRPs/cardshed-02-core-prp.md` — delivered core + flagged contradictions +- `PRPs/cardshed-03-experience-prp.md` — next slice (17 milestones) +- `apps/core/src/core/types.ts` — frozen Shared Contract +- `apps/core/src/core/rules.ts` — Rules Engine API surface +- `.claude/rules/ui-design-pipeline.md` — mandatory UI pipeline (active from M1) +- `.claude/rules/core-determinism.md` — apps/core/ purity guarantees diff --git a/@lab/ll-CARDSHED/README.md b/@lab/ll-CARDSHED/README.md new file mode 100644 index 0000000..aca925c --- /dev/null +++ b/@lab/ll-CARDSHED/README.md @@ -0,0 +1,162 @@ +# ll-CARDSHED + +> Local-first browser implementation of the **CARD SHED** card game. +> A `@lab` sandbox inside W7-Base. Pure-logic core sealed; browser hot-seat MVP next. + +--- + +## Status + +📋 **Core sealed. UI/network/AI pending.** The deterministic rules engine is shipped and tested. The next slice is a hot-seat browser MVP per PRP 3 M1. + +| Layer | State | +|-------|-------| +| Stack contract (`.w7-meta`, `compose.yml`, env) | ✅ bootstrap stub | +| Pure-logic core (`apps/core/`) | ✅ sealed — 82/82 tests green, conservation property holds ≥200 iters | +| Strategy + Blueprint (`PRPs/cardshed-01-blueprint.md`) | ✅ locked | +| Experience & Distribution PRP (`PRPs/cardshed-03-experience-prp.md`) | ✅ generated — 17 milestones | +| Stitch design system (`.stitch/DESIGN.md`) | 📋 placeholder — generate via `stitch-design` skill at PRP 3 M1 | +| `apps/ui/` (React + Vite + Tailwind v4) | ⏭️ PRP 3 M1 | +| `apps/server/` (Rust + Axum) | ⏭️ PRP 3 M3 (online multiplayer) | +| Bot ladder (Levels 0/1/2) | ⏭️ PRP 3 M9/M11/M13 | + +--- + +## What it is + +CARD SHED is a 3–4 player, single-deck, shifting-trump card game (Russian-style "fool" variant — *durak*-shaped, not *durak*-identical). The W7 implementation targets: + +1. **A pure TypeScript rules engine** — the *truth machine*. UI, network, and AI are transformations over its state and event stream. Already shipped at `apps/core/`. +2. **A hot-seat browser MVP** — 3–4 humans share one device, pass-and-play. PRP 3 M1. +3. **A bot ladder** — Levels 0 (random-legal), 1 (heuristic), 2 (basic-search). PRP 3. +4. **An online server** — Rust + Axum, hybrid snapshot+event sync, replay from `(seed, actions[])`. PRP 3 M3+. + +The rule set is **CARD SHED v2.0**, frozen in `PRPs/cardshed-02-core-prp.md`. The engine is deterministic given `(matchSeed, actions[])`; replay reproduces every match byte-identically. + +--- + +## Stack + +| Layer | Tech | Status | +|-------|------|--------| +| `@cardshed/core` | TypeScript 5.7 + Vitest 4 + fast-check 3 | ✅ shipped | +| `cardshed-ui` (future) | React 18 + Vite 5 + Tailwind v4 + Radix + framer-motion + Zustand + TanStack Query | ⏭️ PRP 3 M1 | +| `cardshed-server` (future) | Rust + Axum + tokio-tungstenite + sqlx + Postgres 16 | ⏭️ PRP 3 M3+ | + +All eventual containers pin image tags (see `.claude/rules/docker-infra.md`). No `:latest`. + +--- + +## Quick start + +The core is a pure-logic library — no containers needed today: + +```bash +cd @lab/ll-CARDSHED/apps/core +npm install +npm test # 82/82 green; conservation property holds ≥200 iters +npm run sim-smoke # 1000 random-legal-bot games +``` + +Once PRP 3 M1 lands, the browser MVP will boot via: + +```bash +# (forward-looking — apps/ui/ does not exist yet) +cd ~/w7-base +cp @lab/ll-CARDSHED/.env.example @lab/ll-CARDSHED/.env +w7 up @lab/ll-CARDSHED +open http://localhost:4343 +``` + +Tear down (keeps `data/`): + +```bash +w7 down @lab/ll-CARDSHED +``` + +--- + +## The mandatory UI pipeline (applies once `apps/ui/` exists) + +This stack adopts the same **enforced** design workflow as `ll-RELIQUARY` — see `.claude/rules/ui-design-pipeline.md`. + +``` + IDEA (PRP 3 §B screen inventory) + ↓ + STITCH (stitch-design skill + Stitch MCP) + ↓ + AGENT-BROWSER (visual validation, Vercel-sandboxed) + ↓ + PLAYWRIGHT MCP (only for complex eval: multi-tab, network mocks, traces) +``` + +No hand-rolled screens. No invented design tokens. Every UI change has Stitch provenance and `dogfood-output//` validation evidence. + +--- + +## Phase plan (high level) + +| Milestone | Output | Trigger to next | Source | +|-----------|--------|-----------------|--------| +| **M0 — Core sealed** ← *we are here* | Pure rules engine + tests + sim-smoke + this stack-root scaffold | PRP 3 M1 issue opens | PRP 2 (#139) + #143 | +| **M1 — Hot-seat MVP** | `apps/ui/` scaffolded; 3-player hot-seat playable on one device | 4 humans complete a full round | PRP 3 §A M1–M5 | +| **M2 — Bot ladder + replay** | Levels 0/1/2 bots + `(seed, actions[])` replay viewer | Level 1 beats Level 0 ≥60%/100 matches | PRP 3 §E + §A M9–M13 | +| **M3 — Online multiplayer** | Rust + Axum server; lobby; reconnect + replay; anti-cheat | 2 remote + 1 bot finish with mid-match reconnect | PRP 3 §C + §A M14–M17 | +| **M4 — Polish & scale** | Ranked matchmaking; tournaments; Capacitor native shell; Prometheus | First external playtester onboarded | `cardshed-v0.1.0` tag cut | + +--- + +## Repository layout + +``` +@lab/ll-CARDSHED/ +├── .w7-meta W7 contract metadata +├── compose.yml stub (alpine sleep) — replaced at PRP 3 M1 +├── .env.example env schema (placeholders) +├── .gitignore runtime + build outputs +│ +├── README.md you are here +├── CLAUDE.md Claude Code project rules +├── AGENTS.md agent guide +├── BLUEPRINT.md index → PRPs/cardshed-{01,02,03}-*.md +│ +├── .claude/ +│ └── rules/ +│ ├── ui-design-pipeline.md MANDATORY Stitch + agent-browser + Playwright (PRP 3 M1+) +│ └── core-determinism.md apps/core/ purity guarantees from PRP 2 +│ +├── .stitch/ +│ └── DESIGN.md generated design system (placeholder) +│ +├── apps/ +│ └── core/ @cardshed/core — pure-logic rules engine (PRP 2) +│ ├── package.json +│ ├── src/core/{types,rules,prng,index}.ts +│ ├── src/core/__tests__/*.test.ts +│ └── scripts/sim-smoke.ts +│ └── ui/ PRP 3 M1 (does not exist yet) +│ └── server/ PRP 3 M3+ (does not exist yet) +│ +├── docs/ +│ ├── SCREENS/ Stitch-generated mockups (one per screen) +│ └── DECISIONS/ ADRs as they accumulate +│ +├── data/ persistent volumes (gitignored) +└── dogfood-output/ agent-browser + playwright artifacts (gitignored) +``` + +--- + +## Next moves + +1. **Land this stack-root bootstrap** (#143) — current PR. +2. **Open the PRP 3 M1 umbrella issue** with sub-issues for: `apps/ui/` scaffold, Stitch design-system run, hot-seat shell, attack/defend interactions, dogfood baseline. +3. **Run `Skill: stitch-design`** with PRP 3 §B screen inventory + the locked engine API. Save outputs to `docs/SCREENS/`. +4. **Wire `@cardshed/core` into `apps/ui/`** as a workspace dep — the UI must not re-implement any rule. +5. **Run agent-browser** against the running stack to capture the first hot-seat baseline. + +--- + +## License + +Inherits W7-Base MIT. diff --git a/@lab/ll-CARDSHED/compose.yml b/@lab/ll-CARDSHED/compose.yml new file mode 100644 index 0000000..326a1e7 --- /dev/null +++ b/@lab/ll-CARDSHED/compose.yml @@ -0,0 +1,26 @@ +# ll-CARDSHED — bootstrap topology +# +# CARD SHED card game. Pure-logic deterministic core lives at +# apps/core/ (sealed via PRP 2). This compose file is intentionally +# a STUB at bootstrap — there is no deployable service yet. +# +# PRP 3 M1 will replace this placeholder with the real cardshed-ui +# service (React + Vite + Tailwind v4, served by nginx). Online +# multiplayer (PRP 3 M3+) adds cardshed-server (Rust + Axum). +# +# Image pins follow .claude/rules/docker-infra.md — never :latest. + +services: + cardshed-placeholder: + image: alpine:3.20.3 + container_name: ${COMPOSE_PROJECT_NAME:-lab-ll-cardshed}-placeholder + command: tail -f /dev/null + volumes: + - ./data:/data + restart: unless-stopped + networks: + - cardshed-net + +networks: + cardshed-net: + driver: bridge diff --git a/@lab/ll-CARDSHED/data/.gitkeep b/@lab/ll-CARDSHED/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/@lab/ll-CARDSHED/docs/DECISIONS/.gitkeep b/@lab/ll-CARDSHED/docs/DECISIONS/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/@lab/ll-CARDSHED/docs/SCREENS/.gitkeep b/@lab/ll-CARDSHED/docs/SCREENS/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/@lab/ll-CARDSHED/dogfood-output/.gitkeep b/@lab/ll-CARDSHED/dogfood-output/.gitkeep new file mode 100644 index 0000000..e69de29