Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77df2d2aef | |||
| 11bfb4f1c8 | |||
| 3e006a1e94 | |||
| 18ed1549e0 | |||
| 0fffce9a29 | |||
| 3cec200ac0 | |||
| ec7e2b7c08 | |||
| 9e04b389af | |||
| 09fcd2097e | |||
| f0b9536e09 | |||
| a09ec48097 | |||
| d5c95f9a0f | |||
| 494bd8b8ca | |||
| b04781178e | |||
| 4af19c4d62 | |||
| e6c67d03c2 | |||
| 484db22208 | |||
| 4315c0ae70 | |||
| f417177858 | |||
| 31d0a1b6e3 | |||
| 6fa1b28902 | |||
| 56dbc3ff2c | |||
| 19ba065154 | |||
| 3e98872f15 | |||
| 6cee4e9a2b | |||
| 98f9933ed0 | |||
| 0ef75a0c9a | |||
| a6030f4b7b | |||
| 28b1d38951 | |||
| efe930af1e | |||
| 022a749f5f | |||
| 6e3ce8ea59 | |||
| 0c673e3bb6 | |||
| f3b28a1b9d | |||
| 597aba200a | |||
| 8396f0f067 | |||
| 9f8e32db36 | |||
| 7f333443dd | |||
| 29b8c33d3f | |||
| edf2013ab1 | |||
| e3864c60a0 | |||
| 44493a2200 |
@@ -1,98 +0,0 @@
|
|||||||
name: Android Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
env:
|
|
||||||
APK_OUT: target/release/apk/ferrous-solitaire.apk
|
|
||||||
GITEA_URL: https://git.aleshym.co
|
|
||||||
REPO: funman300/Ferrous-Solitaire
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-apk:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: git.aleshym.co/funman300/android-builder:latest
|
|
||||||
credentials:
|
|
||||||
username: ${{ gitea.actor }}
|
|
||||||
password: ${{ secrets.CI_TOKEN }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Cache Cargo registry
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
/usr/local/cargo/registry/index
|
|
||||||
/usr/local/cargo/registry/cache
|
|
||||||
/usr/local/cargo/git/db
|
|
||||||
key: cargo-registry-android-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
restore-keys: cargo-registry-android-
|
|
||||||
|
|
||||||
- name: Cache sccache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /root/.cache/sccache
|
|
||||||
key: sccache-android-aarch64-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
restore-keys: sccache-android-aarch64-
|
|
||||||
|
|
||||||
- name: Get tag name
|
|
||||||
id: tag
|
|
||||||
run: echo "name=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Decode release keystore
|
|
||||||
run: echo "${{ secrets.RELEASE_KEYSTORE_B64 }}" | base64 -d > release.jks
|
|
||||||
|
|
||||||
- name: Build release APK
|
|
||||||
env:
|
|
||||||
PROFILE: release
|
|
||||||
ABIS: arm64-v8a
|
|
||||||
KEYSTORE: ./release.jks
|
|
||||||
KEYSTORE_PASS: ${{ secrets.RELEASE_KEYSTORE_PASS }}
|
|
||||||
KEY_ALIAS: release
|
|
||||||
KEY_PASS: ${{ secrets.RELEASE_KEYSTORE_PASS }}
|
|
||||||
VERSION_NAME: ${{ steps.tag.outputs.name }}
|
|
||||||
RUSTC_WRAPPER: sccache
|
|
||||||
SCCACHE_DIR: /root/.cache/sccache
|
|
||||||
run: bash scripts/build_android_apk.sh
|
|
||||||
|
|
||||||
- name: sccache stats
|
|
||||||
if: always()
|
|
||||||
run: sccache --show-stats
|
|
||||||
|
|
||||||
- name: Create or get Gitea release
|
|
||||||
id: release
|
|
||||||
run: |
|
|
||||||
TAG="${{ steps.tag.outputs.name }}"
|
|
||||||
BASE="${{ env.GITEA_URL }}/api/v1/repos/${{ env.REPO }}"
|
|
||||||
AUTH="Authorization: token ${{ secrets.CI_TOKEN }}"
|
|
||||||
|
|
||||||
ID=$(curl -sf -H "$AUTH" "$BASE/releases/tags/$TAG" \
|
|
||||||
| python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" \
|
|
||||||
2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -z "$ID" ]; then
|
|
||||||
ID=$(curl -sf -X POST \
|
|
||||||
-H "$AUTH" -H "Content-Type: application/json" \
|
|
||||||
"$BASE/releases" \
|
|
||||||
-d "{
|
|
||||||
\"tag_name\": \"$TAG\",
|
|
||||||
\"name\": \"$TAG\",
|
|
||||||
\"body\": \"## Android release $TAG\n\n**Install / update with Obtainium** — add this source URL:\n\`\`\`\nhttps://git.aleshym.co/funman300/Ferrous-Solitaire\n\`\`\`\n\nOr download \`ferrous-solitaire.apk\` below and sideload it directly.\",
|
|
||||||
\"draft\": false,
|
|
||||||
\"prerelease\": false
|
|
||||||
}" \
|
|
||||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
|
||||||
fi
|
|
||||||
echo "id=$ID" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Upload APK to release
|
|
||||||
run: |
|
|
||||||
curl -sf -X POST \
|
|
||||||
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
|
|
||||||
-F "attachment=@${{ env.APK_OUT }};type=application/vnd.android.package-archive" \
|
|
||||||
"${{ env.GITEA_URL }}/api/v1/repos/${{ env.REPO }}/releases/${{ steps.release.outputs.id }}/assets"
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
name: Build Android Builder Image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
paths:
|
|
||||||
- 'docker/android-builder.Dockerfile'
|
|
||||||
- '.gitea/workflows/builder-image.yml'
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE: git.aleshym.co/funman300/android-builder
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Log in to Gitea registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: git.aleshym.co
|
|
||||||
username: ${{ gitea.actor }}
|
|
||||||
password: ${{ secrets.CI_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
with:
|
|
||||||
driver-opts: network=host
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/android-builder.Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.IMAGE }}:latest
|
|
||||||
cache-from: type=registry,ref=${{ env.IMAGE }}:buildcache
|
|
||||||
cache-to: type=registry,ref=${{ env.IMAGE }}:buildcache,mode=max
|
|
||||||
@@ -3,13 +3,11 @@ name: Build and Deploy
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
# Only run when server code changes, not when CI itself updates deploy/.
|
||||||
- 'solitaire_server/**'
|
paths-ignore:
|
||||||
- 'solitaire_sync/**'
|
- 'deploy/**'
|
||||||
- 'solitaire_core/**'
|
- 'argocd/**'
|
||||||
- 'Cargo.toml'
|
- '**.md'
|
||||||
- 'Cargo.lock'
|
|
||||||
- '.gitea/workflows/docker-build.yml'
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: git.aleshym.co
|
REGISTRY: git.aleshym.co
|
||||||
@@ -57,7 +55,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install kustomize
|
- name: Install kustomize
|
||||||
run: |
|
run: |
|
||||||
curl -sL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz | tar xz
|
curl -sL "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
|
||||||
sudo mv kustomize /usr/local/bin/kustomize
|
sudo mv kustomize /usr/local/bin/kustomize
|
||||||
|
|
||||||
- name: Pin image tag in deploy manifests
|
- name: Pin image tag in deploy manifests
|
||||||
@@ -70,9 +68,6 @@ jobs:
|
|||||||
git config user.email "ci@gitea.local"
|
git config user.email "ci@gitea.local"
|
||||||
git config user.name "Gitea CI"
|
git config user.name "Gitea CI"
|
||||||
git add deploy/kustomization.yaml
|
git add deploy/kustomization.yaml
|
||||||
git diff --cached --quiet && exit 0 # nothing to commit — skip push
|
git commit -m "chore(deploy): bump image to ${{ steps.meta.outputs.sha }} [skip ci]" || true
|
||||||
git commit -m "chore(deploy): bump image to ${{ steps.meta.outputs.sha }} [skip ci]"
|
git pull --rebase origin master
|
||||||
for i in 1 2 3; do
|
git push
|
||||||
git pull --rebase origin master && git push && break
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
|
|||||||
@@ -16,8 +16,3 @@ data/
|
|||||||
*.jks.bak
|
*.jks.bak
|
||||||
*.jks.bak*
|
*.jks.bak*
|
||||||
*.keystore
|
*.keystore
|
||||||
|
|
||||||
# Kubernetes secrets — apply manually, never commit
|
|
||||||
deploy/matomo-secret.yaml
|
|
||||||
deploy/*-secret.yaml
|
|
||||||
deploy/*-auth-secret.yaml
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "SELECT username FROM users WHERE id = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "06c945e50567c6801f1346d436cdc86a82a4e13dd45d8286295ba37cdbdc045e"
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "UPDATE users SET avatar_url = ? WHERE id = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 2
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "15d08e02b102147bc74848ec3692b99edbf4c33078191f0132991ee94d4507b1"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT OR IGNORE INTO analytics_events\n (id, user_id, session_id, event_type, payload, client_time, received_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 7
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "f23630e78ae88e72d7930184f7cd8fc67e71f3930609f1cf14061d35d6de8ec3"
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "SELECT username, avatar_url FROM users WHERE id = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "avatar_url",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "fae33e7159b43c756131b1545360dcb0250988b43550881e3f0a336d9516dd45"
|
|
||||||
}
|
|
||||||
@@ -58,7 +58,7 @@ Pure-core, no-panics-in-game-logic, and UI-first-interaction constraints are enf
|
|||||||
## 2. Workspace Structure
|
## 2. Workspace Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
ferrous_solitaire/
|
solitaire_quest/
|
||||||
│
|
│
|
||||||
├── Cargo.toml # Workspace manifest
|
├── Cargo.toml # Workspace manifest
|
||||||
├── .env.example # Server environment variable template
|
├── .env.example # Server environment variable template
|
||||||
@@ -366,12 +366,12 @@ Minimum window: 800×600. At this size cards are small but usable.
|
|||||||
|
|
||||||
### Local Storage
|
### Local Storage
|
||||||
|
|
||||||
All files stored under `dirs::data_dir() / "ferrous_solitaire"/`:
|
All files stored under `dirs::data_dir() / "solitaire_quest"/`:
|
||||||
|
|
||||||
```
|
```
|
||||||
~/.local/share/ferrous_solitaire/ (Linux)
|
~/.local/share/solitaire_quest/ (Linux)
|
||||||
~/Library/Application Support/ferrous_solitaire/ (macOS)
|
~/Library/Application Support/solitaire_quest/ (macOS)
|
||||||
%APPDATA%\ferrous_solitaire\ (Windows)
|
%APPDATA%\solitaire_quest\ (Windows)
|
||||||
│
|
│
|
||||||
├── stats.json # StatsSnapshot
|
├── stats.json # StatsSnapshot
|
||||||
├── progress.json # PlayerProgress (XP, level, unlocks, daily challenge)
|
├── progress.json # PlayerProgress (XP, level, unlocks, daily challenge)
|
||||||
@@ -426,7 +426,7 @@ pub enum SyncBackend {
|
|||||||
url: String,
|
url: String,
|
||||||
username: String,
|
username: String,
|
||||||
// JWT access + refresh tokens stored in OS keychain
|
// JWT access + refresh tokens stored in OS keychain
|
||||||
// key: "ferrous_solitaire_server_{username}"
|
// key: "solitaire_quest_server_{username}"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -980,8 +980,8 @@ Add `--features bevy/dynamic_linking` during development to dramatically reduce
|
|||||||
### Docker Compose (Recommended)
|
### Docker Compose (Recommended)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/yourname/ferrous_solitaire
|
git clone https://github.com/yourname/solitaire_quest
|
||||||
cd ferrous_solitaire
|
cd solitaire_quest
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Edit .env — set JWT_SECRET and SERVER_PORT
|
# Edit .env — set JWT_SECRET and SERVER_PORT
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|||||||
@@ -6,30 +6,6 @@ project follows [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [0.29.0] — 2026-05-16
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- **APK versionCode hardcoded to 1** (`AndroidManifest.xml`, `build_android_apk.sh`).
|
|
||||||
Every release shipped with `versionCode="1"` / `versionName="1.0"`, so Android
|
|
||||||
silently refused upgrades and Obtainium permanently showed a false update
|
|
||||||
notification. The CI now derives the version code from the release tag
|
|
||||||
(e.g. v0.29.0 → 2900) and stamps it into the APK via `aapt2 link
|
|
||||||
--version-code / --version-name`.
|
|
||||||
- **CI kustomize install flaky** (`.gitea/workflows/docker-build.yml`).
|
|
||||||
The `curl | bash install_kustomize.sh` pattern hit GitHub API rate limits
|
|
||||||
on the shared runner IP, causing a `tar: no such file` failure. Replaced
|
|
||||||
with a direct pinned tarball download (kustomize v5.4.3).
|
|
||||||
|
|
||||||
## [0.28.0] — 2026-05-14
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- **Rename: Solitaire Quest → Ferrous Solitaire.** Android package id changed
|
|
||||||
from `com.solitairequest.app` to `com.ferrousapp.solitaire`; existing installs
|
|
||||||
must be uninstalled first (Android treats the new id as a new app).
|
|
||||||
Data directory renamed from `solitaire_quest/` to `ferrous_solitaire/`.
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **BUG-3: Multi-modal stacking** (`hud_plugin.rs`). `handle_menu_button`
|
- **BUG-3: Multi-modal stacking** (`hud_plugin.rs`). `handle_menu_button`
|
||||||
@@ -1455,7 +1431,7 @@ candidate — the app-icon round — stays open.
|
|||||||
- **Android build target — first working APK** (`fb8b2ac`).
|
- **Android build target — first working APK** (`fb8b2ac`).
|
||||||
`cargo apk build -p solitaire_app --target x86_64-linux-android`
|
`cargo apk build -p solitaire_app --target x86_64-linux-android`
|
||||||
now produces a 54 MB debug-signed APK at
|
now produces a 54 MB debug-signed APK at
|
||||||
`target/debug/apk/ferrous-solitaire.apk`. Five gating points
|
`target/debug/apk/solitaire-quest.apk`. Five gating points
|
||||||
resolved end-to-end:
|
resolved end-to-end:
|
||||||
- **`solitaire_app` split into bin + lib.** cargo-apk needs a
|
- **`solitaire_app` split into bin + lib.** cargo-apk needs a
|
||||||
`cdylib` to bundle as `libmain.so`; pure-bin crates panic
|
`cdylib` to bundle as `libmain.so`; pure-bin crates panic
|
||||||
@@ -1572,7 +1548,7 @@ candidate — the app-icon round — stays open.
|
|||||||
achievements, replays, game-state, time-attack sessions, user
|
achievements, replays, game-state, time-attack sessions, user
|
||||||
themes). New `solitaire_data::platform::data_dir()` shim falls
|
themes). New `solitaire_data::platform::data_dir()` shim falls
|
||||||
through to `dirs::data_dir()` on desktop and returns the per-app
|
through to `dirs::data_dir()` on desktop and returns the per-app
|
||||||
sandbox at `/data/data/com.ferrousapp.solitaire/files` on Android
|
sandbox at `/data/data/com.solitairequest.app/files` on Android
|
||||||
— no JNI needed, since the package id is pinned in
|
— no JNI needed, since the package id is pinned in
|
||||||
`[package.metadata.android]`. Six call sites across
|
`[package.metadata.android]`. Six call sites across
|
||||||
`solitaire_data` plus `solitaire_engine/assets/user_dir.rs`
|
`solitaire_data` plus `solitaire_engine/assets/user_dir.rs`
|
||||||
@@ -1714,7 +1690,7 @@ fully reverted and is not part of this release.
|
|||||||
The test's single-frame `app.update()` was sensitive to
|
The test's single-frame `app.update()` was sensitive to
|
||||||
first-frame `Time::delta_secs()` variance under heavy parallel
|
first-frame `Time::delta_secs()` variance under heavy parallel
|
||||||
cargo-test load, and to production-disk
|
cargo-test load, and to production-disk
|
||||||
`~/.local/share/ferrous_solitaire/game_state.json` state leaking
|
`~/.local/share/solitaire_quest/game_state.json` state leaking
|
||||||
into the test world via `GamePlugin::build`'s load path.
|
into the test world via `GamePlugin::build`'s load path.
|
||||||
`test_app` now resets `PendingRestoredGame(None)` after plugin
|
`test_app` now resets `PendingRestoredGame(None)` after plugin
|
||||||
build (preventing the dev machine's saved-game state from
|
build (preventing the dev machine's saved-game state from
|
||||||
@@ -2410,7 +2386,7 @@ the binary shipped with bundled artwork.
|
|||||||
patterns.
|
patterns.
|
||||||
- **Ambient audio loop** wired through the kira mixer.
|
- **Ambient audio loop** wired through the kira mixer.
|
||||||
- **Arch Linux PKGBUILDs** for the game client and sync server (under
|
- **Arch Linux PKGBUILDs** for the game client and sync server (under
|
||||||
the separate `ferrous-solitaire-pkgbuild` directory).
|
the separate `solitaire-quest-pkgbuild` directory).
|
||||||
- **Workspace README, CI workflow, migration guide.**
|
- **Workspace README, CI workflow, migration guide.**
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ raw `z_index` values — they drift and cause ordering bugs.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo apk build --package solitaire_app --lib
|
cargo apk build --package solitaire_app --lib
|
||||||
adb install -r target/debug/apk/ferrous-solitaire.apk
|
adb install -r target/debug/apk/solitaire-quest.apk
|
||||||
```
|
```
|
||||||
|
|
||||||
## 15.2 Coordinate system reminder
|
## 15.2 Coordinate system reminder
|
||||||
|
|||||||
@@ -0,0 +1,497 @@
|
|||||||
|
# CLAUDE_PROMPT_PACK.md
|
||||||
|
|
||||||
|
version: 1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 0. GLOBAL INSTRUCTION (prepend to every prompt)
|
||||||
|
|
||||||
|
```
|
||||||
|
You must follow CLAUDE_SPEC.md strictly.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Do not expand scope beyond what is defined
|
||||||
|
- Do not refactor unrelated code
|
||||||
|
- Do not introduce new dependencies to solitaire_core or solitaire_sync without confirmation
|
||||||
|
- Prefer minimal, surgical changes
|
||||||
|
- Use existing patterns in the codebase
|
||||||
|
- Return minimal diffs or changed functions only
|
||||||
|
|
||||||
|
Before writing code:
|
||||||
|
1. List relevant constraints from CLAUDE_SPEC.md
|
||||||
|
2. Identify risks
|
||||||
|
3. Then implement
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. FEATURE IMPLEMENTATION
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Feature Implementation
|
||||||
|
|
||||||
|
feature: "<name>"
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<clear outcome>"
|
||||||
|
|
||||||
|
scope:
|
||||||
|
crates: []
|
||||||
|
systems: []
|
||||||
|
files: []
|
||||||
|
|
||||||
|
non_goals:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must follow CLAUDE_SPEC.md
|
||||||
|
- event-driven architecture required
|
||||||
|
- no blocking operations
|
||||||
|
- no cross-crate leakage
|
||||||
|
|
||||||
|
acceptance_criteria:
|
||||||
|
- ""
|
||||||
|
- ""
|
||||||
|
|
||||||
|
edge_cases:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Patterns
|
||||||
|
|
||||||
|
Use this pattern for systems:
|
||||||
|
<PASTE EXISTING SYSTEM SNIPPET HERE>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
intent:
|
||||||
|
plan:
|
||||||
|
constraints_used:
|
||||||
|
risks:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
(minimal diffs only)
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2. BUGFIX
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Bug Fix
|
||||||
|
|
||||||
|
bug_description:
|
||||||
|
"<what is broken>"
|
||||||
|
|
||||||
|
expected_behavior:
|
||||||
|
"<correct behavior>"
|
||||||
|
|
||||||
|
root_cause_hint (optional):
|
||||||
|
""
|
||||||
|
|
||||||
|
scope:
|
||||||
|
crates: []
|
||||||
|
files: []
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- minimal fix only
|
||||||
|
- no refactors unless required
|
||||||
|
- must add regression protection if applicable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
1. Identify root cause
|
||||||
|
2. Fix it minimally
|
||||||
|
3. Preserve all invariants
|
||||||
|
4. Do not change unrelated logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
root_cause:
|
||||||
|
fix_strategy:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
(minimal diff)
|
||||||
|
|
||||||
|
regression_test (only if high-value):
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3. REFACTOR
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Refactor
|
||||||
|
|
||||||
|
target:
|
||||||
|
"<what is being improved>"
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<what improves>"
|
||||||
|
|
||||||
|
scope:
|
||||||
|
crates: []
|
||||||
|
files: []
|
||||||
|
|
||||||
|
non_goals:
|
||||||
|
- no behavior changes
|
||||||
|
- no new features
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must preserve behavior exactly
|
||||||
|
- must respect crate boundaries
|
||||||
|
- must not duplicate logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Refactor Type
|
||||||
|
|
||||||
|
- [ ] simplify logic
|
||||||
|
- [ ] reduce duplication
|
||||||
|
- [ ] improve readability
|
||||||
|
- [ ] performance (non-invasive)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
issues_found:
|
||||||
|
|
||||||
|
refactor_plan:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
(diff only)
|
||||||
|
|
||||||
|
verification:
|
||||||
|
- behavior unchanged: yes/no
|
||||||
|
- invariants preserved: yes/no
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4. SYSTEM DESIGN (NEW FEATURE)
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: System Design
|
||||||
|
|
||||||
|
feature:
|
||||||
|
"<name>"
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<what problem it solves>"
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must fit existing architecture
|
||||||
|
- must follow plugin + event model
|
||||||
|
- must not violate crate boundaries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Output
|
||||||
|
|
||||||
|
design:
|
||||||
|
|
||||||
|
components:
|
||||||
|
- plugins:
|
||||||
|
- systems:
|
||||||
|
- events:
|
||||||
|
- resources:
|
||||||
|
|
||||||
|
data_flow:
|
||||||
|
(step-by-step)
|
||||||
|
|
||||||
|
integration_points:
|
||||||
|
- where it connects to existing systems
|
||||||
|
|
||||||
|
risks:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
tradeoffs:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DO NOT
|
||||||
|
|
||||||
|
- write full implementation
|
||||||
|
- modify unrelated systems
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5. NEW BEVY SYSTEM
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Add Bevy System
|
||||||
|
|
||||||
|
system_name:
|
||||||
|
""
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
(event or condition)
|
||||||
|
|
||||||
|
reads:
|
||||||
|
[Resources]
|
||||||
|
|
||||||
|
writes:
|
||||||
|
[Resources]
|
||||||
|
|
||||||
|
emits:
|
||||||
|
[Events]
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must be event-driven
|
||||||
|
- must not directly mutate unrelated state
|
||||||
|
- must be single responsibility
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
system_signature:
|
||||||
|
|
||||||
|
implementation:
|
||||||
|
(code only)
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6. CORE LOGIC FUNCTION (solitaire_core)
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Core Logic Implementation
|
||||||
|
|
||||||
|
function:
|
||||||
|
"<name>"
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<what it does>"
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- no IO
|
||||||
|
- no async
|
||||||
|
- no Bevy
|
||||||
|
- deterministic
|
||||||
|
|
||||||
|
invariants:
|
||||||
|
- ""
|
||||||
|
- ""
|
||||||
|
|
||||||
|
errors:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
constraints_checked:
|
||||||
|
|
||||||
|
implementation:
|
||||||
|
(code only)
|
||||||
|
|
||||||
|
edge_case_handling:
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7. SYNC / MERGE LOGIC
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Sync Logic
|
||||||
|
|
||||||
|
goal:
|
||||||
|
"<what is being merged or synced>"
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- must be deterministic
|
||||||
|
- must be idempotent
|
||||||
|
- must be lossless
|
||||||
|
- must not delete data
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- counters → max
|
||||||
|
- times → min
|
||||||
|
- collections → union
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
|
||||||
|
merge_logic:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
|
||||||
|
invariants_verified:
|
||||||
|
- deterministic
|
||||||
|
- idempotent
|
||||||
|
- lossless
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8. PERFORMANCE OPTIMIZATION
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Optimization
|
||||||
|
|
||||||
|
target:
|
||||||
|
"<what is slow>"
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- no behavior change
|
||||||
|
- no architecture change
|
||||||
|
- minimal code changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
bottleneck:
|
||||||
|
|
||||||
|
optimization_strategy:
|
||||||
|
|
||||||
|
code_changes:
|
||||||
|
|
||||||
|
impact_estimate:
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 9. TEST GENERATION (STRICT MODE)
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Test Generation
|
||||||
|
|
||||||
|
target:
|
||||||
|
"<function/system>"
|
||||||
|
|
||||||
|
reason:
|
||||||
|
- bugfix | complex logic | invariant protection
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
- no redundant tests
|
||||||
|
- must test real behavior
|
||||||
|
- must fail if logic breaks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
test_cases:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
test_code:
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 10. DEBUGGING / INVESTIGATION
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Debug
|
||||||
|
|
||||||
|
problem:
|
||||||
|
"<symptom>"
|
||||||
|
|
||||||
|
context:
|
||||||
|
"<relevant code or system>"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Required Steps
|
||||||
|
|
||||||
|
1. List possible causes
|
||||||
|
2. Narrow down most likely
|
||||||
|
3. Suggest verification steps
|
||||||
|
4. Provide minimal fix
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
hypotheses:
|
||||||
|
|
||||||
|
most_likely:
|
||||||
|
|
||||||
|
verification_steps:
|
||||||
|
|
||||||
|
fix:
|
||||||
|
|
||||||
|
notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 11. HARD CONSTRAINT OVERRIDE (RARE)
|
||||||
|
|
||||||
|
```
|
||||||
|
# TASK: Exception Handling
|
||||||
|
|
||||||
|
reason:
|
||||||
|
"<why constraints must be bent>"
|
||||||
|
|
||||||
|
requested_exception:
|
||||||
|
"<rule being broken>"
|
||||||
|
|
||||||
|
justification:
|
||||||
|
"<why unavoidable>"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
|
||||||
|
alternatives_considered:
|
||||||
|
|
||||||
|
final_decision:
|
||||||
|
|
||||||
|
risk:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 12. STOP CONDITIONS (always append)
|
||||||
|
|
||||||
|
```
|
||||||
|
Stop when:
|
||||||
|
- acceptance criteria are met
|
||||||
|
- code is minimal and correct
|
||||||
|
|
||||||
|
Do NOT:
|
||||||
|
- expand scope
|
||||||
|
- refactor unrelated code
|
||||||
|
- optimize prematurely
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# END
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
# 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_wasm:
|
||||||
|
depends_on: [solitaire_core, wasm-bindgen, serde-wasm-bindgen]
|
||||||
|
role: "wasm_replay_player"
|
||||||
|
|
||||||
|
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 and resources"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
# CLAUDE_WORKFLOW.md
|
||||||
|
|
||||||
|
version: 1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Overview
|
||||||
|
|
||||||
|
This workflow defines a **two-agent system**:
|
||||||
|
|
||||||
|
* **Builder Agent** → writes and modifies code
|
||||||
|
* **Guardian Agent** → enforces architecture + rejects invalid changes
|
||||||
|
|
||||||
|
No code is considered valid unless it passes Guardian validation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Agent Roles
|
||||||
|
|
||||||
|
### 1.1 Builder Agent
|
||||||
|
|
||||||
|
role: "code_generation"
|
||||||
|
|
||||||
|
responsibilities:
|
||||||
|
|
||||||
|
* implement features
|
||||||
|
* refactor code
|
||||||
|
* generate tests (only when justified)
|
||||||
|
* follow CLAUDE_SPEC.md
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
|
||||||
|
* cannot bypass validation
|
||||||
|
* must declare intent before writing code
|
||||||
|
|
||||||
|
output_contract:
|
||||||
|
must_produce:
|
||||||
|
- change_summary
|
||||||
|
- files_modified
|
||||||
|
- reasoning (short)
|
||||||
|
- code_diff
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.2 Guardian Agent
|
||||||
|
|
||||||
|
role: "architecture_enforcement"
|
||||||
|
|
||||||
|
responsibilities:
|
||||||
|
|
||||||
|
* validate against CLAUDE_SPEC.md
|
||||||
|
* detect violations
|
||||||
|
* reject or approve changes
|
||||||
|
* suggest minimal fixes (not full rewrites)
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
|
||||||
|
* no feature implementation
|
||||||
|
* no large rewrites
|
||||||
|
* must be deterministic
|
||||||
|
|
||||||
|
output_contract:
|
||||||
|
must_produce:
|
||||||
|
- status: APPROVED | REJECTED
|
||||||
|
- violations[]
|
||||||
|
- required_fixes[]
|
||||||
|
- optional_improvements[]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Workflow Pipeline
|
||||||
|
|
||||||
|
```text
|
||||||
|
User Request
|
||||||
|
↓
|
||||||
|
Builder Agent (proposal + code)
|
||||||
|
↓
|
||||||
|
Guardian Agent (validation)
|
||||||
|
↓
|
||||||
|
IF approved → commit
|
||||||
|
IF rejected → feedback → Builder retry
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Builder Protocol
|
||||||
|
|
||||||
|
### Step 1 — Intent Declaration
|
||||||
|
|
||||||
|
Builder MUST start with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
intent:
|
||||||
|
feature: "<name>"
|
||||||
|
crates_touched: []
|
||||||
|
systems_affected: []
|
||||||
|
risk_level: low|medium|high
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 2 — Plan
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
plan:
|
||||||
|
- step: "..."
|
||||||
|
- step: "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 3 — Implementation
|
||||||
|
|
||||||
|
* Only modify declared crates
|
||||||
|
* Follow ownership rules
|
||||||
|
* Use events for cross-system communication
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 4 — Output
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
change_summary: "..."
|
||||||
|
|
||||||
|
files_modified:
|
||||||
|
- path: ...
|
||||||
|
change: "..."
|
||||||
|
|
||||||
|
violations_self_check:
|
||||||
|
- none | list
|
||||||
|
|
||||||
|
notes: "short reasoning"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Guardian Protocol
|
||||||
|
|
||||||
|
### Step 1 — Spec Validation
|
||||||
|
|
||||||
|
Check against:
|
||||||
|
|
||||||
|
* crate boundaries
|
||||||
|
* mutation rules
|
||||||
|
* event system usage
|
||||||
|
* sync guarantees
|
||||||
|
* forbidden patterns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 2 — Invariant Validation
|
||||||
|
|
||||||
|
Must verify:
|
||||||
|
|
||||||
|
* GameState invariants preserved
|
||||||
|
* no new panic paths
|
||||||
|
* no blocking calls in engine
|
||||||
|
* merge properties unchanged
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 3 — Output Decision
|
||||||
|
|
||||||
|
#### APPROVED
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status: APPROVED
|
||||||
|
|
||||||
|
notes:
|
||||||
|
- "no violations"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### REJECTED
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status: REJECTED
|
||||||
|
|
||||||
|
violations:
|
||||||
|
- id: core_purity_violation
|
||||||
|
file: "solitaire_core/src/..."
|
||||||
|
reason: "uses std::fs"
|
||||||
|
|
||||||
|
required_fixes:
|
||||||
|
- "move IO to solitaire_data"
|
||||||
|
|
||||||
|
optional_improvements:
|
||||||
|
- "simplify event naming"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Enforcement Rules
|
||||||
|
|
||||||
|
### Hard Fail (automatic rejection)
|
||||||
|
|
||||||
|
* core crate uses IO / Bevy / network
|
||||||
|
* GameState mutated outside GameLogicSystem
|
||||||
|
* blocking async on main thread
|
||||||
|
* duplicate logic across crates
|
||||||
|
* merge function altered incorrectly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Soft Fail (allowed but flagged)
|
||||||
|
|
||||||
|
* unnecessary complexity
|
||||||
|
* redundant tests
|
||||||
|
* minor architectural drift
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Iteration Loop
|
||||||
|
|
||||||
|
Max attempts per task: **3**
|
||||||
|
|
||||||
|
```text
|
||||||
|
Attempt 1 → Reject → Fix
|
||||||
|
Attempt 2 → Reject → Fix
|
||||||
|
Attempt 3 → Final decision
|
||||||
|
```
|
||||||
|
|
||||||
|
If still failing:
|
||||||
|
→ escalate to user
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Diff Strategy
|
||||||
|
|
||||||
|
Builder MUST produce:
|
||||||
|
|
||||||
|
* minimal diffs
|
||||||
|
* no unrelated refactors
|
||||||
|
* no formatting-only changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Test Strategy Integration
|
||||||
|
|
||||||
|
Builder rules:
|
||||||
|
|
||||||
|
* only add tests if:
|
||||||
|
|
||||||
|
* fixing a bug
|
||||||
|
* protecting complex logic
|
||||||
|
* validating invariants
|
||||||
|
|
||||||
|
Guardian rejects:
|
||||||
|
|
||||||
|
* redundant tests
|
||||||
|
* no-op tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Optional Extensions
|
||||||
|
|
||||||
|
### 9.1 Third Agent (Optimizer)
|
||||||
|
|
||||||
|
role: performance + cleanup
|
||||||
|
|
||||||
|
runs AFTER approval:
|
||||||
|
|
||||||
|
* reduce allocations
|
||||||
|
* simplify logic
|
||||||
|
* improve ECS scheduling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9.2 CI Integration
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Builder → Guardian → cargo check → clippy → tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Guardian runs BEFORE compilation to catch structural issues early.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Example Interaction
|
||||||
|
|
||||||
|
### Builder
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
intent:
|
||||||
|
feature: "undo stack limit fix"
|
||||||
|
crates_touched: [solitaire_core]
|
||||||
|
risk_level: low
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
change_summary: "limit undo stack to 64 entries"
|
||||||
|
|
||||||
|
files_modified:
|
||||||
|
- solitaire_core/src/game_state.rs
|
||||||
|
|
||||||
|
notes: "prevents unbounded memory growth"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Guardian
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status: APPROVED
|
||||||
|
|
||||||
|
notes:
|
||||||
|
- "respects core constraints"
|
||||||
|
- "no invariant violations"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Mental Model
|
||||||
|
|
||||||
|
* Builder = **creative**
|
||||||
|
* Guardian = **strict**
|
||||||
|
|
||||||
|
Builder explores
|
||||||
|
Guardian enforces
|
||||||
|
|
||||||
|
Neither replaces the other.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Success Criteria
|
||||||
|
|
||||||
|
System is working if:
|
||||||
|
|
||||||
|
* architectural violations go to ~0
|
||||||
|
* code stays consistent across features
|
||||||
|
* refactors become safe
|
||||||
|
* complexity grows sub-linearly
|
||||||
@@ -4034,14 +4034,9 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
"color_quant",
|
|
||||||
"gif",
|
|
||||||
"image-webp",
|
|
||||||
"moxcms",
|
"moxcms",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
"zune-core",
|
|
||||||
"zune-jpeg",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7018,10 +7013,8 @@ dependencies = [
|
|||||||
"bevy",
|
"bevy",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs",
|
"dirs",
|
||||||
"image",
|
|
||||||
"jni 0.21.1",
|
"jni 0.21.1",
|
||||||
"kira",
|
"kira",
|
||||||
"reqwest",
|
|
||||||
"resvg",
|
"resvg",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -110,9 +110,6 @@ ron = "0.12"
|
|||||||
# only `deflate` is needed because the importer rejects other
|
# only `deflate` is needed because the importer rejects other
|
||||||
# compression methods anyway (see Phase 7 spec).
|
# compression methods anyway (see Phase 7 spec).
|
||||||
zip = { version = "8.6", default-features = false, features = ["deflate"] }
|
zip = { version = "8.6", default-features = false, features = ["deflate"] }
|
||||||
# Image decoding for avatar bytes received from the server.
|
|
||||||
# Features mirror what Bevy already enables via bevy_image.
|
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }
|
|
||||||
|
|
||||||
# Importer-only test dependency: tests build zip archives in a
|
# Importer-only test dependency: tests build zip archives in a
|
||||||
# scratch directory so they don't pollute the real user themes path
|
# scratch directory so they don't pollute the real user themes path
|
||||||
|
|||||||
@@ -31,23 +31,6 @@ optional self-hosted sync so your stats follow you across machines.
|
|||||||
- **Color-blind mode** — blue tint on red-suit cards alongside the suit
|
- **Color-blind mode** — blue tint on red-suit cards alongside the suit
|
||||||
glyph
|
glyph
|
||||||
|
|
||||||
## Android Install
|
|
||||||
|
|
||||||
### Obtainium (recommended — automatic updates)
|
|
||||||
|
|
||||||
1. Install [Obtainium](https://github.com/ImranR98/Obtainium/releases) on your device
|
|
||||||
2. Tap the badge below on your Android device — the source type is pre-configured, no manual selection needed:
|
|
||||||
|
|
||||||
[<img src="https://raw.githubusercontent.com/ImranR98/Obtainium/main/assets/graphics/badge_obtainium.png" alt="Get it on Obtainium" height="40">](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22com.ferrousapp.solitaire%22%2C%22url%22%3A%22https%3A//git.aleshym.co/funman300/Ferrous-Solitaire%22%2C%22author%22%3A%22funman300%22%2C%22name%22%3A%22Ferrous%20Solitaire%22%2C%22installedVersion%22%3Anull%2C%22latestVersion%22%3Anull%2C%22apkUrls%22%3A%22%5B%5D%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%7D%22%2C%22lastUpdateCheck%22%3Anull%2C%22pinned%22%3Afalse%2C%22categories%22%3A%5B%5D%2C%22releaseDate%22%3Anull%2C%22changeLog%22%3Anull%2C%22overrideSource%22%3A%22Codeberg%22%2C%22allowIdChange%22%3Afalse%2C%22otherAssetUrls%22%3A%22%5B%5D%22%7D)
|
|
||||||
|
|
||||||
3. Tap **Install** to download the current release — Obtainium will notify you when updates are available.
|
|
||||||
|
|
||||||
### Direct APK
|
|
||||||
|
|
||||||
Download the latest `ferrous-solitaire.apk` from the
|
|
||||||
[Releases](https://git.aleshym.co/funman300/Ferrous-Solitaire/releases) page,
|
|
||||||
enable **Install from unknown sources** in your device settings, and open the file.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
**Prerequisites**
|
**Prerequisites**
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
# Ferrous Solitaire — Session Handoff
|
||||||
|
|
||||||
|
**Last updated:** 2026-05-12 — Leaderboard display name shipped (`03be4fc`). All commits pushed to origin.
|
||||||
|
|
||||||
|
Phase 8 closes the self-hosted-server connection arc end-to-end: login/register
|
||||||
|
modal, re-auth on token expiry, account deletion flow, server deployment
|
||||||
|
artifacts (Dockerfile + docker-compose), replay upload on win, web replay
|
||||||
|
player (WASM + HTML/CSS/JS served by the server), leaderboard opt-in/out,
|
||||||
|
and full server integration tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current state
|
||||||
|
|
||||||
|
- **HEAD locally:** `03be4fc` (feat: leaderboard custom display name).
|
||||||
|
- **HEAD on origin:** `03be4fc` (fully pushed).
|
||||||
|
- **Working tree:** clean (only `solitaire-release.jks.bak2` untracked — intentional).
|
||||||
|
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings` clean.
|
||||||
|
- **Tests:** **1300+ passing / 0 failing** across the workspace.
|
||||||
|
- **Tags on origin:** `v0.9.0` through `v0.22.0`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What shipped in Phase 8 (432061c – bd388fe)
|
||||||
|
|
||||||
|
| Commit | Summary |
|
||||||
|
|--------|---------|
|
||||||
|
| `432061c` | Sync setup modal (login/register/connect/disconnect) |
|
||||||
|
| `6ce5564` | Re-auth on expired session + server deployment artifacts |
|
||||||
|
| `272d31f` | Account deletion flow + `handle_sync_buttons` refactor |
|
||||||
|
| `bd388fe` | CHANGELOG v0.23.0 documentation |
|
||||||
|
|
||||||
|
Also shipped (pre-Phase 8 but post-v0.22.0, already in CHANGELOG):
|
||||||
|
- `solitaire_wasm` crate: WASM ReplayPlayer bindings for browser-side replay playback
|
||||||
|
- Server replay API: `POST /api/replays`, `GET /api/replays/recent`, `GET /api/replays/:id`
|
||||||
|
- Server web UI: `/replays/:id` HTML route + `ServeDir /web` static assets
|
||||||
|
- DB migration 002: `replays` table + two indexes
|
||||||
|
- Full server integration tests for replay endpoints
|
||||||
|
- `push_replay` in `sync_plugin` (uploads on win, writes share URL into replay history)
|
||||||
|
- Stats panel "Copy Share Link" button reads `share_url` from replay history
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open punch list (ordered by priority)
|
||||||
|
|
||||||
|
### 1. Documentation debt (no code)
|
||||||
|
- [x] CHANGELOG [Unreleased] → v0.23.0 — done this session
|
||||||
|
- [x] ARCHITECTURE.md update — all 8 gaps closed, bumped to v1.3
|
||||||
|
- [x] SESSION_HANDOFF.md update — this file
|
||||||
|
|
||||||
|
### 2. Leaderboard wiring gaps
|
||||||
|
- [x] **Best-score auto-post.** Done (`303c78a`): `update_leaderboard_if_opted_in`
|
||||||
|
called from both first-push and merge paths in `sync.rs`; uses SQLite `MIN`/`MAX`
|
||||||
|
in the UPDATE so scores never regress on stale data.
|
||||||
|
- [x] **Display name = username.** Done (`03be4fc`): `leaderboard_display_name:
|
||||||
|
Option<String>` added to `Settings`; editor modal in leaderboard panel; persists
|
||||||
|
to `settings.json`; `handle_opt_in_button` prefers custom name over username.
|
||||||
|
|
||||||
|
### 3. Security hardening
|
||||||
|
- [x] **Refresh token rotation.** Done (`b129664`): `refresh_tokens` table
|
||||||
|
(migration 003); jti embedded in JWT; rotate-on-use pattern; 3 integration
|
||||||
|
tests.
|
||||||
|
- [x] **Sync endpoint rate limiting.** Done (`6e6f3ef`): `UserIdKeyExtractor`
|
||||||
|
decodes JWT for per-user identity; falls back to IP; burst 10 / 6 min
|
||||||
|
steady-state; integration test passes.
|
||||||
|
|
||||||
|
### 4. Android validation
|
||||||
|
- [x] **Android Keystore functional test.** Done (2026-05-11, Pixel 7 AVD,
|
||||||
|
Android 14): `load_access_token()` exercised via `start_pull`; logcat confirmed
|
||||||
|
`NotFound` returned cleanly — no JNI panic. See `docs/android/PLAYABILITY_TODO.md` P4.
|
||||||
|
- [x] **JNI clipboard functional test.** Done (2026-05-11): temporary `KEYCODE_C`
|
||||||
|
hook confirmed `ClipboardManager.setPrimaryClip()` succeeds on Android 14.
|
||||||
|
Hook reverted. Production path requires Interaction::Pressed + non-null `share_url`.
|
||||||
|
Note: `adb shell input tap` doesn't deliver touch events on headless AVD (documented).
|
||||||
|
- [x] **`cargo apk build --lib` noisy stderr** — upstream cargo-apk bug; `--lib`
|
||||||
|
is the canonical command (CLAUDE.md §15.1, docs/ANDROID.md). No in-repo fix possible.
|
||||||
|
|
||||||
|
### 5. Feature completeness
|
||||||
|
- [x] **Theme importer UI.** Done (`613bbf8`): "Scan for new themes" button in
|
||||||
|
Settings Appearance section. Shows import path label, scans user_theme_dir()
|
||||||
|
for .zip archives, fires InfoToastEvent per file, refreshes ThemeRegistry.
|
||||||
|
- [x] **`mirror_achievement` removed.** Done (`549a817`): method was a no-op
|
||||||
|
default never overridden and never called; achievements already sync via
|
||||||
|
`SyncPayload` push. Deleted from trait and blanket impl.
|
||||||
|
- [x] **WASM build script.** Done (`40d0712`): `build_wasm.sh` at repo root
|
||||||
|
documents `wasm-pack build --target web`, cleans up pkg metadata files,
|
||||||
|
includes dependency guard + install instructions.
|
||||||
|
- [x] **Server password reset.** Done (`7514684`): `--reset-password <username>`
|
||||||
|
subcommand reads new password from stdin, bcrypt-hashes it, invalidates all
|
||||||
|
active sessions for the user.
|
||||||
|
|
||||||
|
### 5b. Android UX polish (2026-05-12)
|
||||||
|
|
||||||
|
- [x] **UX-1 — Modal Done button in gesture zone.** `apply_safe_area_to_modal_scrims` system
|
||||||
|
added to `SafeAreaInsetsPlugin` (`safe_area.rs`). Pads every `ModalScrim` bottom by
|
||||||
|
`insets.bottom / scale`. Fires on resource change + `Added<ModalScrim>`. Verified on device.
|
||||||
|
- [x] **UX-5b — Home mode glyph corruption.** Geometric Shapes (U+25xx, absent from FiraMono)
|
||||||
|
replaced with card suits U+2660–2666 in `home_plugin.rs`. Affects Zen/Challenge/Daily mode
|
||||||
|
selector buttons at level 5+.
|
||||||
|
- [x] **UX-7 — Help text wrap.** Android HUD entry shortened to
|
||||||
|
`"Open menu (Stats, Settings, Profile...)"` in `help_plugin.rs` — fits one line.
|
||||||
|
- [x] **BUG-3 — Multi-modal stacking.** `handle_menu_button` now checks
|
||||||
|
`scrims: Query<(), With<ModalScrim>>` and guards `spawn_menu_popover` with `scrims.is_empty()`.
|
||||||
|
Verified on device: ≡ tap while Stats open does nothing.
|
||||||
|
|
||||||
|
**Note:** These 4 fixes are implemented and verified but not yet committed.
|
||||||
|
|
||||||
|
### 6. Testing gaps
|
||||||
|
- [x] **Server 401 → refresh → retry path.** Done (`198df75`): both
|
||||||
|
`jwt_refresh_on_401_succeeds` (pull) and
|
||||||
|
`push_retries_after_401_on_expired_access_token` (push) in
|
||||||
|
`solitaire_data/tests/sync_round_trip.rs`.
|
||||||
|
- [x] **WASM winning-replay step-through.** Done (`b4ada2a`): greedy solver
|
||||||
|
searches seeds 1–200 at test time; steps every move through `ReplayPlayer`;
|
||||||
|
asserts `is_won = true` on the final `StateSnapshot`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ARCHITECTURE.md gaps (for the update pass)
|
||||||
|
|
||||||
|
Items missing from the doc:
|
||||||
|
1. `solitaire_wasm` crate (§2 workspace + §3 responsibilities)
|
||||||
|
2. Replay API endpoints (§9 API Reference — 3 new routes)
|
||||||
|
3. Web replay player route (`/replays/:id` + `ServeDir /web`)
|
||||||
|
4. `SyncProvider` trait: 6 added methods
|
||||||
|
5. Theme system in Bevy plugin table (§5)
|
||||||
|
6. `Settings` new fields: `color_blind_mode`, `high_contrast_mode`,
|
||||||
|
`reduce_motion_mode`, `window_geometry`, `selected_card_back`,
|
||||||
|
`selected_background`
|
||||||
|
7. DB migration 002 (§7)
|
||||||
|
8. Update "Last Updated" date
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Process notes
|
||||||
|
|
||||||
|
- **Commit attribution:** use `funman300` as git user. Co-author line:
|
||||||
|
`Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>`.
|
||||||
|
- **Commit format:** `type(scope): description` per CLAUDE.md §7.
|
||||||
|
- **Never commit without:** `cargo test --workspace` passing + clippy clean.
|
||||||
|
- **Sub-agents** stage/verify only; orchestrator commits.
|
||||||
|
- **`CARD_PLAN.md`** referenced in `theme/` module comments but not present in
|
||||||
|
repo. Clean up references or commit the file.
|
||||||
|
- **Token-port pattern** (v0.20.0): when migrating tokens, walk every concrete
|
||||||
|
artifact downstream — PNGs, SVGs, literals, comments. Three "walked past this"
|
||||||
|
follow-ups in v0.21.0 all had this shape.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resume prompt
|
||||||
|
|
||||||
|
```
|
||||||
|
You are a senior Rust + Bevy developer working on Ferrous Solitaire.
|
||||||
|
Working directory: <Rusty_Solitaire clone path>.
|
||||||
|
Branch: master. v0.23.0 is the current version (HEAD: 03be4fc). Fully pushed.
|
||||||
|
|
||||||
|
READ FIRST (in order):
|
||||||
|
1. SESSION_HANDOFF.md — this file
|
||||||
|
2. CHANGELOG.md — [0.23.0] section has full Phase 8 detail
|
||||||
|
3. CLAUDE.md — unified-4.0 rule set
|
||||||
|
4. ARCHITECTURE.md — v1.3, fully up to date
|
||||||
|
5. docs/ui-mockups/ — design system + mockup library
|
||||||
|
6. docs/android/ — Android setup + build runbook
|
||||||
|
7. ~/.claude/projects/<this-project>/memory/MEMORY.md
|
||||||
|
|
||||||
|
OPEN WORK:
|
||||||
|
Phase 8 punch list is fully closed. All items verified complete.
|
||||||
|
Remaining nuisance: `cargo apk build --lib` noisy stderr (cosmetic, non-blocking).
|
||||||
|
|
||||||
|
4 Android UX fixes are implemented and verified but NOT YET COMMITTED:
|
||||||
|
- BUG-3 (hud_plugin.rs): multi-modal stacking guard
|
||||||
|
- UX-7 (help_plugin.rs): help text wrap on Android
|
||||||
|
- UX-5b (home_plugin.rs): FiraMono glyph corruption in mode selector
|
||||||
|
- UX-1 (safe_area.rs): modal Done button in gesture zone
|
||||||
|
|
||||||
|
Commit those first, then suggest Phase 9 planning.
|
||||||
|
```
|
||||||
@@ -6,20 +6,12 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
project: default
|
project: default
|
||||||
source:
|
source:
|
||||||
repoURL: https://git.aleshym.co/funman300/Ferrous-Solitaire.git
|
repoURL: https://git.aleshym.co/funman300/Rusty_Solitare.git
|
||||||
targetRevision: master
|
targetRevision: master
|
||||||
path: deploy
|
path: deploy
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: solitaire
|
namespace: solitaire
|
||||||
# Secrets are applied manually and must not be pruned by ArgoCD.
|
|
||||||
ignoreDifferences:
|
|
||||||
- group: ""
|
|
||||||
kind: Secret
|
|
||||||
name: matomo-secret
|
|
||||||
namespace: solitaire
|
|
||||||
jsonPointers:
|
|
||||||
- /data
|
|
||||||
syncPolicy:
|
syncPolicy:
|
||||||
automated:
|
automated:
|
||||||
prune: true
|
prune: true
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |