Adopts the four-file rule set the player added to the working tree:
- CLAUDE.md grows from a 114-line pointer doc to the 571-line
`unified-3.0` rulebook: hard global constraints (§2), engine
rules (§3), asset rules (§4), code standards (§5), build +
verification (§6), git workflow (§7), the change-control
ASK BEFORE list (§8), and the Context Injection System (§14).
- CLAUDE_SPEC.md — formal architecture spec: crate dependency
graph with forbidden_deps, data ownership map, state-machine
invariants ("52 cards always exist", "no duplicate IDs",
"all cards belong to exactly one pile"), sync merge contract,
server contract, validation checklist.
- CLAUDE_WORKFLOW.md — two-agent Builder/Guardian pipeline with
hard-fail patterns that auto-reject (core uses IO/Bevy/network,
GameState mutated outside GameLogicSystem, blocking async on
main thread, duplicate logic, merge altered incorrectly).
- CLAUDE_PROMPT_PACK.md — task-type templates.
Three duplicate rule passages removed:
- CLAUDE_SPEC.md §0 dropped no_panics_in_core / core_is_pure /
event_driven_engine — already canonical in CLAUDE.md §2.1, §2.3,
§3.1. Kept single_source_of_truth and sync_is_additive (those
describe data flow, not in CLAUDE.md).
- CLAUDE_SPEC.md §11 Prohibited Patterns now references CLAUDE.md
§11 instead of restating the same five forbidden items.
- ARCHITECTURE.md Design Principles dropped the pure-core /
no-panics / UI-first bullets — those are enforcement constraints
living in CLAUDE.md §2.1, §2.3, §3.3; this file describes the
design that motivates them. Kept the offline-first, one-language,
and plugin-based-Bevy bullets (those are descriptive, not
enforcement).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.2 KiB
CLAUDE_SPEC.md
version: 1.0
0. Global Rules
(Core determinism, panic policy, and event-driven engine constraints live in CLAUDE.md §2.1, §2.3, §3.1. Listed here only when they add information CLAUDE.md doesn't carry.)
rules:
-
id: single_source_of_truth description: "GameStateResource is the only mutable game state in runtime"
-
id: sync_is_additive description: "Remote data must never destructively overwrite local data"
1. Crate Graph
crates: solitaire_core: depends_on: [rand, serde, chrono] forbidden_deps: [bevy, reqwest, tokio, std::fs]
solitaire_sync: depends_on: [serde, serde_json, uuid, chrono] role: "shared_types"
solitaire_data: depends_on: [solitaire_core, solitaire_sync, reqwest, tokio, keyring] role: "persistence_and_sync"
solitaire_engine: depends_on: [bevy, kira, solitaire_core, solitaire_data] role: "runtime_engine"
solitaire_server: depends_on: [solitaire_sync, axum, sqlx, jsonwebtoken] role: "backend"
solitaire_app: depends_on: [solitaire_engine] role: "entrypoint"
2. Data Ownership
ownership: GameState: owner: solitaire_core mutable_in: solitaire_engine access_pattern: "via GameStateResource only"
StatsSnapshot: owner: solitaire_data
PlayerProgress: owner: solitaire_data
AchievementRecord: owner: solitaire_data
SyncPayload: owner: solitaire_sync
3. State Transitions
state_machine: GameState: transitions:
- action: move_cards returns: Result<GameState, MoveError>
- action: draw
returns: Result<GameState, MoveError>
- action: undo
returns: Result<GameState, MoveError>
invariants:
- "52 cards always exist"
- "no duplicate card IDs"
- "all cards belong to exactly one pile"
4. Event System
events:
input:
- MoveRequestEvent
- DrawRequestEvent
- UndoRequestEvent
- NewGameRequestEvent
state:
- StateChangedEvent
- GameWonEvent
meta:
- AchievementUnlockedEvent
- SyncCompleteEvent
rules:
- "Input events trigger core logic"
- "Core logic emits state events"
- "UI reacts to state events only"
5. Sync Contract
sync:
provider_trait: methods:
- pull() -> SyncPayload
- push(payload) -> SyncResponse
guarantees:
- "non-blocking during gameplay"
- "blocking allowed on exit only"
merge: rules: counters: "max" best_times: "min" collections: "union" achievements: "never removed"
properties:
- deterministic
- idempotent
- lossless
6. Persistence
storage:
format: json
files:
- stats.json
- progress.json
- achievements.json
- settings.json
- game_state.json
guarantees:
- atomic_write: true
- crash_safe: true
7. Engine Rules
engine:
mutation_rules:
- "Only GameLogicSystem mutates GameState"
- "UI systems are read-only"
threading:
- "sync runs on AsyncComputeTaskPool"
- "main thread must never block"
plugins: pattern: "feature_isolation" communication: "events"
8. Server Contract
server:
auth: method: jwt access_expiry: 24h refresh_expiry: 30d
endpoints:
- POST /api/auth/register
- POST /api/auth/login
- GET /api/sync/pull
- POST /api/sync/push
limits: payload_max: 1MB rate_limit: "10 req/min auth routes"
9. Achievement System
achievements:
definition_location: solitaire_core state_location: solitaire_data
types:
- condition_based
- event_driven
rule:
- "achievements cannot be revoked"
10. Testing Rules
testing:
philosophy:
- "test real failures"
- "avoid redundant tests"
required_coverage: solitaire_core:
- move_validation
- undo_integrity
- win_detection
solitaire_sync:
- merge_correctness
- idempotency
11. Prohibited Patterns
(See CLAUDE.md §11 for the canonical forbidden-patterns list.)
12. Extension Points
extensibility:
sync_backends: pattern: "implement SyncProvider"
game_modes: location: solitaire_core::GameMode
plugins: rule: "new feature = new plugin"
13. Validation Checklist (for Claude)
validation:
- check: "crate dependency rules respected"
- check: "no panics in core"
- check: "events used for cross-system communication"
- check: "GameState mutations centralized"
- check: "merge function properties preserved"
- check: "no blocking operations in main loop"
14. Mental Model
model:
layers:
- core
- engine
- data
- server
flow:
- input -> engine -> core -> engine -> ui
- data <-> sync <-> server