beddbcf94d
CHANGELOG gains the missing [0.41.1] section (pile-marker child-resize fix + Android relayout diagnostics). SESSION_HANDOFF is brought forward from the stale v0.40.0 state to today's: v0.41.x releases, the July 6 review arc (PRs #121-#131), Fold 7 on-device verification results, the remaining device-checklist items, and new architectural notes (plugin submodule pattern, marker child-resize rule, Fold 7 inset quirks). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
245 lines
13 KiB
Markdown
245 lines
13 KiB
Markdown
# Ferrous Solitaire — Session Handoff
|
||
|
||
**Last updated:** 2026-07-06 — v0.41.0 + v0.41.1 released and verified on a
|
||
physical Galaxy Fold 7; issue tracker empty except low-priority #130.
|
||
|
||
---
|
||
|
||
## Current state
|
||
|
||
- **Branch state:** `master` pushed to origin; latest work is the 2026-07-06
|
||
arc (PRs #121–#131): scripted repo review, five issues filed and fixed,
|
||
plugin module splits, two releases.
|
||
- **Latest tags:** `v0.41.1` (pile-marker child-resize fix + Android relayout
|
||
logging) on top of `v0.41.0` (consolidated release for everything since
|
||
v0.39.0). Both released via tag push → CI signed APK; both verified
|
||
installed on hardware (`versionCode 4101`).
|
||
- **Working tree:** clean. Local `scripts/*.go` helpers are intentionally
|
||
gitignored (`.gitignore:43`); `scripts/watch_deploy.sh` is now committed.
|
||
- **Latest verification:** workspace clippy `--all-targets -D warnings`,
|
||
full test suite, `cargo ndk` clippy for `aarch64-linux-android`, CI release
|
||
builds green, and an on-device pass on the Fold 7 (fold/unfold layout,
|
||
safe-area resume, marker fix).
|
||
- **Issue tracker:** #116/#117/#118/#119/#120 all closed 2026-07-06. #130
|
||
(transient tableau clip after fold) open at low priority — did not
|
||
reproduce in repeat testing; v0.41.1's relayout logging is the evidence
|
||
channel if it recurs.
|
||
|
||
---
|
||
|
||
## 2026-07-06 session summary (v0.40.3 → v0.41.1)
|
||
|
||
- **Scripted repo review** (cratemap/todoctx/cargoclip/testfail): clippy
|
||
clean, tests green, error/SQL policies compliant. Five issues filed and
|
||
all resolved same-day.
|
||
- **#116 safe-area re-poll after resume** (PR #121): `refresh_insets` now
|
||
gates on the poll counter, settles per cycle, and rewrites insets only on
|
||
change. Verified on Fold 7 — note both Fold screens report identical
|
||
insets (top=110 bottom=0), so the re-poll path is a no-op on this device.
|
||
- **#117 Draw-1 recycle** (PR #122): unlimited recycling documented as an
|
||
intentional rules decision (ARCHITECTURE.md "Rules decisions" +
|
||
`draw_one_recycling_is_unlimited_by_design` lock-in test). A hard limit
|
||
would invalidate the difficulty seed catalog and the winnable-deal solver.
|
||
- **#118 module splits** (PRs #124/#125/#127/#128/#129): card, hud,
|
||
settings, game, and input plugins are now module directories; tests in
|
||
sibling `tests.rs`, runtime code split along system boundaries
|
||
(`pub(super)` items, mod.rs glob-imports children). Largest runtime file
|
||
is now `card_plugin/sync.rs` at 748 lines (was `card_plugin.rs` at 4,129).
|
||
- **v0.41.1 pile-marker fix** (PR #131): marker outline + "A"/"K" watermark
|
||
children are re-derived from the layout on every resize — previously
|
||
spawn-time-sized, rendering as oversized grey slabs on empty foundations
|
||
after fold/unfold. Found via photo evidence, fixed, re-verified on device.
|
||
- **Obtainium note:** reported "no suitable release" for v0.41.0 even though
|
||
the anonymous releases API, `releases/latest`, and the APK download were
|
||
all verified fine — client-side issue; sideload via adb was used instead.
|
||
|
||
---
|
||
|
||
## v0.40.0 release (2026-06-25)
|
||
|
||
Released via tag push → `.gitea/workflows/android-release.yml` built and signed the
|
||
arm64-v8a release APK (release keystore, `versionCode 4000` / `versionName 0.40.0`,
|
||
29.2 MB) and published it to the Gitea release. Obtainium clients tracking the repo
|
||
pick it up automatically.
|
||
|
||
- Release: https://git.aleshym.co/funman300/Ferrous-Solitaire/releases/tag/v0.40.0
|
||
|
||
| PR | Summary |
|
||
|----|---------|
|
||
| #106 | **fix(engine):** Draw-Three waste fan hit-test now shares the renderer's fan step (`card_plugin::waste_fan_step` / `tableau_col_step`). The two had diverged under Android's tighter column spacing (`H_GAP_DIVISOR=32`), shifting the top fanned waste card's click target onto the card beneath it — so dragging the visible top card played the wrong one. Desktop/web were unaffected (the formulas already coincided there). |
|
||
| #105 | **test(engine):** waste-card draggability regression tests (`find_draggable_at` picks the waste top with multiple cards and as a lone card). |
|
||
| #108 | **docs(android):** NDK reference updated `26.3.11579264` → `30.0.14904198`; noted versions are not load-bearing and `build_android_apk.sh` auto-discovers the newest NDK/build-tools. |
|
||
|
||
Pre-release validation performed locally this session: workspace clippy/test/build
|
||
gates; `aarch64-linux-android` cross-compile + clippy clean (covers the
|
||
`#[cfg(target_os = "android")]` paths that host CI never lints); release manifest
|
||
sanity (`solitaire_app/android/AndroidManifest.xml` has no version fields so CI
|
||
injection works; `lib_name` matches `[lib].name`); and a full signed local APK
|
||
proving the `build_android_apk.sh` packaging pipeline end-to-end.
|
||
|
||
---
|
||
|
||
## What shipped since v0.39.0
|
||
|
||
- Browser Bevy canvas route and `window.__FERROUS_DEBUG__` automation bridge landed, with Playwright coverage for `/play`.
|
||
- In-place `card_game` / `klondike` rewrite phases are complete through the latest follow-up:
|
||
- `5e87358` integrates upstream deps cleanly.
|
||
- `ae1ecc8` unifies `Suit` / `Rank` with upstream `card_game` types.
|
||
- `d864d98` routes klondike/card imports through `solitaire_core`.
|
||
- `9bcf13d`, `56e3b62`, `26f1b00` finish schema-v3 migration coverage, undo/recycle score correctness, and rewrite-plan docs.
|
||
- Android keystore wiring, Android build-script hardening, server auth/runtime hardening, and modal safe-area centering have landed.
|
||
- `CHANGELOG.md` has been caught up from `v0.34.0` through current unreleased work and committed in `7fe6ac6`.
|
||
- Matomo analytics was re-reviewed: `MatomoClient` and `AnalyticsPlugin` are wired through `CoreGamePlugin` on non-wasm targets, and targeted tests now cover opt-in client creation, event encoding, buffer trimming, and analytics mode labels.
|
||
- Native analytics and Android physical-device validation now have runbooks in
|
||
`docs/analytics-validation.md` and `docs/ANDROID.md`.
|
||
|
||
---
|
||
|
||
## Historical notes before v0.39.0
|
||
|
||
See git log and `CHANGELOG.md`. The changelog now includes `v0.34.0` through `v0.39.0`, plus current unreleased work.
|
||
|
||
---
|
||
|
||
## What shipped since the last handoff (v0.23.0 → v0.35.1)
|
||
|
||
### v0.34.0 — Android polish + code-quality sweep (2026-05-16/17)
|
||
|
||
| Commit | Summary |
|
||
|--------|---------|
|
||
| `9623bde` | Wire FiraMono to Android corner label; CardImageSet load tests |
|
||
| `980312c` | Fix wrong bottom-right suit symbol on JS/QS/KS card assets |
|
||
| `04e99a8` | Correct Android waste fan overlap and resume layout desync |
|
||
| `3bb3ddb` | Eliminate panics, fix dismiss hit-test scope, guard home respawn |
|
||
| `f8f1f26` | Adaptive drop zones, touch event correctness, modal lifecycle guards |
|
||
| `1eb4043` | Auth-guard avatar serving; atomic write; user_id assertion in merge |
|
||
| `69c6e88` | Deterministic pile serialization, undo skip, url-encode bytes, merge_at |
|
||
| `aa7b0f6` | Gate frame-hot ECS systems on resource changes (perf) |
|
||
| `6727126` | Consolidate APP_DIR_NAME; add `#[must_use]` on pure fns |
|
||
| `a4dfb0c` | Differentiate leaderboard opt-in vs opt-out error toasts (M-12) |
|
||
| `7fc98f8` | WASM: state() and step() return Result, errors throw JS exceptions (CR-6) |
|
||
| `ffed6b2` | Share Tokio runtime across all network tasks (M-16) |
|
||
| `fa84152` | Correct Android help hint label `→` to `!` (M-17) |
|
||
| `18d7937` | Derive Copy for DrawMode; drop redundant .clone() calls (M-18) |
|
||
| `132fea9` | Use saturating_add for move_count increments (M-19) |
|
||
| `0ecc1a9` | Add missing derives to AchievementContext (M-20) |
|
||
| `2301cc6` | Align android_keystore temp extension with cleanup glob (M-21) |
|
||
| `2e52f54` | Enforce 32-char display_name limit at sync client boundary (M-22) |
|
||
| `c8878d6` | Fix stale FOCUS_RING colour comment (M-23) |
|
||
| `4aafc0a` | Name HUD popover Z-layers; replace raw Z arithmetic (M-24) |
|
||
|
||
### v0.35.0 — Accessibility + sync reliability (2026-05-18)
|
||
|
||
| Commit | Summary |
|
||
|--------|---------|
|
||
| `eb6c93f` | Silence B0004 by adding Transform to ModalScrim |
|
||
| `6f5cebd` | Fire WarningToastEvent on sync pull failure (was InfoToastEvent) |
|
||
| `87aec5b` | Gate all decorative motion animations under `reduce_motion_mode` |
|
||
|
||
`reduce_motion_mode` now gates: score pulse, score floater, streak flourish
|
||
(hud_plugin), card-shake on rejected move, foundation completion flourish
|
||
(feedback_anim_plugin). Pattern: gate at the trigger/start system, never at
|
||
the tick system — if the component isn't inserted, the tick path never runs.
|
||
|
||
### v0.35.1 — Leaderboard bug fixes (2026-05-18)
|
||
|
||
| Commit | Summary |
|
||
|--------|---------|
|
||
| `8f86d66` | Fix three leaderboard bugs: wrong toast type, stale label, name not synced |
|
||
|
||
Three bugs fixed:
|
||
|
||
1. **Wrong toast type on error** — `poll_opt_in_task` / `poll_opt_out_task` error
|
||
branches now fire `WarningToastEvent` instead of `InfoToastEvent`.
|
||
|
||
2. **Display name not pushed to server on change** — `Settings` gains
|
||
`leaderboard_opted_in: bool` (serde-defaulted `false`). Set `true`/`false` when
|
||
opt-in/out tasks succeed and persisted to `settings.json`. `handle_display_name_confirm`
|
||
now spawns an `opt_in_leaderboard` task when already opted in — the server's upsert
|
||
endpoint updates only `display_name` without re-opting-in.
|
||
|
||
3. **"Public name" label stale after name change** — `LeaderboardPublicNameText` marker
|
||
component added to the label node. `update_leaderboard_public_name_label` system
|
||
rewrites the text each frame the panel is open; O(0) cost when panel is closed.
|
||
|
||
5 new regression tests cover all three bugs.
|
||
|
||
---
|
||
|
||
## Open punch list
|
||
|
||
### 1. Physical-device smoke test — PARTIALLY DONE (2026-07-06, Galaxy Fold 7)
|
||
|
||
v0.41.1 was installed and launched on a physical Fold 7 via adb. Verified:
|
||
fold/unfold layout on both screens (incl. the #116 resume path and the
|
||
pile-marker fix), safe-area inset resolution, and app launch/restore basics.
|
||
|
||
**Still unexercised** from the device checklist: Draw-Three waste fan tap
|
||
accuracy (the #106 fix — switch to Draw-Three, drag the visible top waste
|
||
card ~10×, confirm it plays *that* card), modal centring on both screens,
|
||
drag-and-drop across all pile types, kill-and-restore, and the sync token
|
||
flow. AVD is not a substitute — `adb shell input tap` doesn't deliver real
|
||
touch events.
|
||
|
||
### 2. Matomo analytics live validation (independent — NOT a release blocker)
|
||
|
||
Separate, ongoing task unrelated to the Android release. `Settings` has
|
||
`analytics_enabled`, `matomo_url`, and `matomo_site_id`; the engine consumes them via
|
||
`AnalyticsPlugin` on non-wasm targets. Remaining work is live validation against the
|
||
deployed Matomo instance. Use `docs/analytics-validation.md` for the native
|
||
validation checklist and the current web/WASM decision notes.
|
||
|
||
---
|
||
|
||
## Architectural notes for next session
|
||
|
||
- **Plugin submodule pattern (2026-07-06 splits):** big plugins are module
|
||
directories: `mod.rs` holds types/markers/plugin-build and glob-imports the
|
||
children (`use input::*;`); children hold `pub(super)` systems and start
|
||
with `use super::*;` plus their own external imports. Tests (`tests.rs`)
|
||
may need explicit imports for names no longer used by `mod.rs` itself.
|
||
|
||
- **Marker child-resize rule:** anything spawned as a *child* of a
|
||
layout-sized entity (outline frames, watermark text) must be re-derived in
|
||
`on_window_resized` too — resizing only the parent sprite leaves children
|
||
at spawn-time size (the v0.41.1 foldable bug).
|
||
|
||
- **Fold 7 quirks:** both screens report identical safe-area insets
|
||
(top=110, bottom=0), so inset-driven relayout never fires on fold; layout
|
||
correctness across folds rides entirely on `WindowResized`. winit 0.30
|
||
logs `TODO: find a way to notify application of content rect change` on
|
||
resume — see #130 if a stale-width layout ever reproduces; v0.41.1 logs
|
||
every Android relayout (`layout: resize to WxH`) for exactly this.
|
||
|
||
- **Reduce-motion pattern:** always gate in the `start_*` / `detect_*` system
|
||
(the trigger), not the `tick_*` system. If the component is never inserted, the
|
||
tick path never runs. See `hud_plugin.rs::detect_score_change` and
|
||
`feedback_anim_plugin.rs::start_shake_anim` for the canonical pattern.
|
||
|
||
- **Leaderboard server upsert:** `POST /api/leaderboard/opt-in` is idempotent —
|
||
calling it when already opted in just updates `display_name`. Safe to call from
|
||
`handle_display_name_confirm` without tracking a separate "needs update" flag.
|
||
|
||
- **`Messages<T>` API (Bevy 0.18.1):** write with
|
||
`resource_mut::<Messages<T>>().write(value)`; read in tests with
|
||
`msgs.get_cursor()` + `cursor.read(msgs).next()`.
|
||
|
||
- **Test input-state pitfall:** `MinimalPlugins` has no input-tick system, so
|
||
`ButtonInput::just_pressed` state persists across frames unless explicitly cleared
|
||
with `input.release(key); input.clear()` between updates.
|
||
|
||
- **`/play` debug bridge design:** `play.html` runs two independent WASM instances in
|
||
`Promise.all([bootstrap(), init()])`. `bootstrap()` sets `window.__FERROUS_DEBUG__`
|
||
(logic layer via `solitaire_wasm.js`); `init()` starts the Bevy canvas. The bridge
|
||
operates its own `SolitaireGame` — moves applied through the bridge do NOT affect
|
||
the Bevy visual game. This is intentional for automation/invariant checking.
|
||
|
||
- **HiDPI Bevy canvas:** `WindowResolution::default().with_scale_factor_override(1.0)`
|
||
is set in the canvas app. Without this, physical pixels exceed WebGL2's 2048px limit
|
||
on HiDPI displays, causing an immediate wgpu panic on the first resize event.
|
||
|
||
- **`/play-classic` vs `/play` in e2e:** `smoke.spec.js` + `gameplay_review.spec.js`
|
||
target `/play-classic` (DOM-heavy game.html); `play_canvas.spec.js` targets `/play`
|
||
using only the `__FERROUS_DEBUG__` bridge (no DOM selectors). `cycle_metrics.js`
|
||
supports both via `--route play-classic|play`.
|