3ffde038c5
Card faces, card backs, board backgrounds, and the UI font are loaded
via Bevy's AssetServer at startup (see commit fbe984c). The CLAUDE.md
hard rule still claimed cards/backgrounds were rendered procedurally
with no AssetServer, and ARCHITECTURE.md §14 / §20 still described
PNGs and TTFs as embedded via include_bytes!(). Update both docs:
- CLAUDE.md hard rule lists which assets ship in assets/ and notes the
Option<Res<AssetServer>> fallback used under MinimalPlugins (tests).
- ARCHITECTURE.md §2/§3/§5/§14 rewritten to describe the AssetServer
loaders for CardImageSet, BackgroundImageSet, and FontResource, and
the Text2d / solid-colour fallbacks.
- ARCHITECTURE.md §20 decision log replaces the two reversed
embed-via-include_bytes!() entries with a single entry covering the
switch to AssetServer plus a note that audio remains embedded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
114 lines
5.0 KiB
Markdown
114 lines
5.0 KiB
Markdown
# Solitaire Quest — Claude Code Instructions
|
|
|
|
See @ARCHITECTURE.md for full project design, crate responsibilities, data models, and API reference.
|
|
|
|
---
|
|
|
|
## Project Layout
|
|
|
|
```text
|
|
solitaire_core/ # Pure Rust game logic — NO Bevy, NO network, NO I/O
|
|
solitaire_sync/ # Shared API types — NO Bevy, serde/uuid/chrono only
|
|
solitaire_data/ # Persistence + SyncProvider trait + server client
|
|
solitaire_engine/ # Bevy ECS systems, components, plugins
|
|
solitaire_server/ # Axum sync server binary
|
|
solitaire_app/ # Thin binary entry point
|
|
assets/ # Source assets — embedded at compile time via include_bytes!()
|
|
```
|
|
|
|
---
|
|
|
|
## Build & Test Commands
|
|
|
|
```bash
|
|
# Dev run (fast compile via dynamic linking)
|
|
cargo run -p solitaire_app --features bevy/dynamic_linking
|
|
|
|
# Release build
|
|
cargo build --workspace --release
|
|
|
|
# All tests — MUST pass before any commit
|
|
cargo test --workspace
|
|
|
|
# Lint — MUST pass clean (zero warnings)
|
|
cargo clippy --workspace -- -D warnings
|
|
|
|
# Run sync server locally
|
|
cargo run -p solitaire_server
|
|
|
|
# Check a single crate
|
|
cargo test -p solitaire_core
|
|
cargo clippy -p solitaire_core -- -D warnings
|
|
```
|
|
|
|
---
|
|
|
|
## Hard Rules
|
|
|
|
- `solitaire_core` and `solitaire_sync` must never gain Bevy or network dependencies.
|
|
- No `unwrap()` or `panic!()` in game logic. All state transitions return `Result<_, MoveError>`.
|
|
- Audio assets are embedded at compile time using `include_bytes!()` in `audio_plugin.rs`.
|
|
- Card faces (52 PNGs in `assets/cards/faces/`), card backs (`assets/cards/backs/back_N.png`), board backgrounds (`assets/backgrounds/bg_N.png`), and the UI font (`assets/fonts/main.ttf`) are loaded at runtime via `AssetServer::load()` and stored as `Handle<Image>`/`Handle<Font>` in the `CardImageSet`, `BackgroundImageSet`, and `FontResource` resources. The `assets/` directory must ship alongside the binary.
|
|
- Asset-loading systems take `Option<Res<AssetServer>>` so they degrade cleanly under `MinimalPlugins` (tests). When `CardImageSet` is absent, `card_plugin` falls back to a `Text2d` rank+suit overlay; when `BackgroundImageSet` is absent, the board falls back to a solid colour.
|
|
- Atomic file writes only: write to `filename.json.tmp`, then `rename()`.
|
|
- Passwords and tokens are stored in the OS keychain via the `keyring` crate — never in plaintext files or logs.
|
|
- Sync runs on `AsyncComputeTaskPool` — never block the Bevy main thread.
|
|
- All sync backends implement the `SyncProvider` trait. The `SyncPlugin` is backend-agnostic — never `match` on `SyncBackend` inside a Bevy system.
|
|
- `cargo clippy --workspace -- -D warnings` must pass clean after every change.
|
|
- `cargo test --workspace` must pass after every change.
|
|
|
|
---
|
|
|
|
## Code Style
|
|
|
|
- Use `thiserror` for error types. Never `Box<dyn Error>` in library crates.
|
|
- Prefer `Into<T>` over concrete types in public API function parameters.
|
|
- All public items must have doc comments (`///`). Private items: comment only when non-obvious.
|
|
- Derive order convention: `#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]`
|
|
- Bevy systems: one responsibility per system. Use `Events` for cross-system communication, never shared mutable state.
|
|
- SQL queries: use `sqlx::query!` macros (compile-time checked), not raw string queries.
|
|
- No `clone()` calls in hot paths (game loop systems). Profile before optimising elsewhere.
|
|
|
|
---
|
|
|
|
## Bevy Conventions
|
|
|
|
- One `Plugin` per major feature: `CardPlugin`, `AudioPlugin`, `AchievementPlugin`, `UIPlugin`, `SyncPlugin`.
|
|
- Resources own shared state. Events communicate between systems. Components own per-entity data.
|
|
- All UI screens are built with Bevy UI (`bevy::ui`). Never mix UI layout and game logic in the same system.
|
|
- Layout is recomputed on `WindowResized` — never assume a fixed window size.
|
|
|
|
---
|
|
|
|
## Git Workflow
|
|
|
|
- Commit after each passing phase, not after every file change.
|
|
- Commit message format: `type(scope): description`
|
|
- `feat(core): add draw-three mode validation`
|
|
- `fix(engine): card z-order during drag`
|
|
- `test(core): undo stack boundary conditions`
|
|
- `chore(server): add sqlx migration 002`
|
|
- Never commit with failing tests or clippy warnings.
|
|
- Never commit secrets, `.env` files, or `*.db` files.
|
|
|
|
---
|
|
|
|
## Ask Before Doing
|
|
|
|
- Adding a new crate dependency (discuss alternatives first).
|
|
- Changing a type in `solitaire_sync` (breaking change on both client and server).
|
|
- Altering the database schema (requires a new sqlx migration).
|
|
- Introducing `unsafe` code anywhere.
|
|
- Changing the merge strategy in `solitaire_sync::merge()`.
|
|
|
|
---
|
|
|
|
## Lessons Learned
|
|
|
|
> Add entries here when Claude makes a mistake so it isn't repeated.
|
|
|
|
- Bevy's `Time` resource uses `f32` seconds; convert to `u64` only when writing to `StatsSnapshot`.
|
|
- `sqlx::migrate!()` macro path is relative to the crate root, not the workspace root.
|
|
- `keyring` on Linux requires a running secret service (e.g. GNOME Keyring or KWallet) — handle `Error::NoStorageAccess` gracefully and fall back to prompting the user.
|
|
- `dirs::data_dir()` returns `None` on some minimal Linux environments — always handle the `None` case explicitly, do not unwrap.
|