Skip to content

feat(cardshed): bootstrap CARD SHED deterministic core rules engine #139

@w7-mgfcode

Description

@w7-mgfcode

Bootstrap the CARD SHED deterministic core rules engine in @lab/ll-CARDSHED/apps/core/, executing PRPs/cardshed-02-core-prp.md.

Scope

Pure-logic TypeScript rules engine encoding CARD SHED v2.0:

  • src/core/types.ts — Shared Contract + domain entities (Card, Player, PendingAttack, RoundState, MatchState, Action, GameEvent, view projections)
  • src/core/prng.ts — mulberry32 + seeded Fisher-Yates
  • src/core/rules.ts — full Rules Engine API surface (createDeck, shuffleDeck, dealInitialHands, startNewRound, validateAttack, submitAttack, canBeat, submitBeat, stopDefending, drawToMinimum, checkWin, advanceTurn*, getLegalActions, createPublicView, createPrivateView, pendingAttackCardCount)
  • src/core/index.ts — public re-exports
  • src/core/__tests__/ — 14 vitest files (78+ tests, including 4 property-based ≥200 iters via fast-check)
  • scripts/sim-smoke.ts — 1000-game random-legal-bot driver

Cross-PRP resolutions

All 5 contradictions flagged at the top of PRPs/cardshed-02-core-prp.md are now Resolved: and the PRP is updated. PRP 3 (cardshed-03-experience-prp.md) MUST consume these resolutions verbatim:

  1. beatenPairs live in pendingAttack; stopDefending flushes to discard on both branches
  2. pendingAttackCardCount(pa) helper exported and used by invariants
  3. checkWin runs after refill in both stopDefending branches — a defender can win on the round-trailing turn (PRP 3 must not assume "winner = current attacker")
  4. deck[0] = bottom (trump face); deck.pop() = top (drawn next)
  5. Card.id = "${letter}-${rank}-${slotHex}[-${saltB36}]"; startNewRound salts ids with the round seed → per-round disjoint id sets, deterministic

Validation gates (all pass)

  • npx tsc --noEmit — 0 errors
  • npx eslint src — 0 errors, determinism bans (Math.random/Date.now/new Date/performance.now/crypto.getRandomValues) active
  • npx vitest run82/82 tests across 14 files (200-iter property tests for conservation/hidden-info/termination)
  • npx tsx scripts/sim-smoke.ts — 1000 random-legal-bot games / 0 conservation violations / 100% reach RoundEnded / 3.7s (mean 92.2 turns/round)

Required scope addition

Add cardshed to .claude/rules/commit-format.md allow-list (mirrors how reliquary was added in #133).

Out of scope

  • UI / networking / AI / analytics — all in PRP 3 (cardshed-03-experience-prp.md)
  • Rust port — deferred per PRP 1 (cardshed-01-blueprint.md)
  • Stack scaffold (Compose, db, cache) — core is pure logic, no runtime services

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions