scry/AGENTS.md
Chris Kruining 56499d5af9
Add debug output support and refactor DbGenerator CLI
- Add RecognitionOptions with DebugOutputDirectory for saving pipeline
  stages (input, detection, perspective correction, CLAHE preprocessing)
- Wire up IOptions<RecognitionOptions> via DI in MauiProgram
- Extract GenerateCommand from Program.cs using Spectre.Console.Cli
- Add priority card support with preferred set matching (Alpha/Beta)
- Expand card_hashes.db with more cards for better recognition coverage
- Update AGENTS.md with comprehensive project documentation

💘 Generated with Crush

Assisted-by: Claude Opus 4.5 via Crush <crush@charm.land>
2026-02-09 08:39:15 +01:00

291 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Agent Instructions
## Overview
Scry is a Magic: The Gathering card scanner app built with .NET MAUI. It uses perceptual hashing to match photographed cards against a database of known card images from Scryfall.
**Key components:**
- Mobile scanning app (MAUI/Android)
- Card recognition via perceptual hashing (not OCR)
- SQLite database with pre-computed hashes
- Scryfall API integration for card data
## Build Commands
Use `just` commands (defined in `.justfile`):
| Task | Command | Notes |
|------|---------|-------|
| Build project | `just build` | Builds Android debug |
| Run tests | `just test` | Runs xUnit tests |
| Generate card database | `just gen-db` | Downloads from Scryfall, computes hashes |
| Publish app | `just publish` | Creates release APK |
| Hot reload dev | `just dev` | Uses `dotnet watch` |
| Start emulator | `just emu` | Virtual camera with Serra Angel |
| Install to device | `just install` | Installs release APK |
### Database Generator Options
```bash
just gen-db # Default: 500 cards with test images
dotnet run --project tools/DbGenerator -- -c 1000 # More cards
dotnet run --project tools/DbGenerator -- --force # Rebuild from scratch
dotnet run --project tools/DbGenerator -- --no-test-cards # Skip priority test cards
```
## Project Structure
```
src/
├── Scry.App/ # MAUI mobile app (Android target)
│ ├── Views/ # XAML pages (ScanPage, CollectionPage, etc.)
│ ├── ViewModels/ # MVVM ViewModels using CommunityToolkit.Mvvm
│ ├── Services/ # App-layer services (ICardRecognitionService, ICardRepository)
│ ├── Converters/ # XAML value converters
│ ├── Models/ # App-specific models (CollectionEntry)
│ └── Resources/Raw/ # Bundled card_hashes.db
└── Scry.Core/ # Platform-independent core library
├── Recognition/ # CardRecognitionService, RecognitionOptions
├── Imaging/ # PerceptualHash, ImagePreprocessor, CardDetector
├── Data/ # CardDatabase (SQLite)
├── Models/ # Card, Oracle, Set, ScanResult
└── Scryfall/ # ScryfallClient for API/bulk data
test/
└── Scry.Tests/ # xUnit tests
tools/
└── DbGenerator/ # CLI tool to generate card_hashes.db
TestImages/ # Test images organized by category
├── reference_alpha/ # Alpha/Beta cards for testing
├── single_cards/ # Individual card photos
├── varying_quality/ # Different lighting/quality
├── hands/ # Cards held in hand
├── foil/ # Foil cards with glare
└── ... # More categories
```
## Architecture
### Recognition Pipeline
```
Camera Image
┌─────────────────────┐
│ CardDetector │ ← Edge detection, find card quad
│ (optional) │
└─────────────────────┘
┌─────────────────────┐
│ PerspectiveCorrection│ ← Warp to rectangle
└─────────────────────┘
┌─────────────────────┐
│ ImagePreprocessor │ ← CLAHE for lighting normalization
│ (ApplyClahe) │
└─────────────────────┘
┌─────────────────────┐
│ PerceptualHash │ ← Compute 192-bit color hash (24 bytes)
│ (ComputeColorHash) │
└─────────────────────┘
┌─────────────────────┐
│ CardRecognitionService│ ← Hamming distance match against DB
└─────────────────────┘
```
### Data Model
Three-table schema mirroring Scryfall:
- **oracles** - Abstract game cards (one per unique card name)
- **sets** - MTG sets with metadata
- **cards** - Printings with perceptual hashes (one per unique artwork)
The `Card` model includes denormalized Oracle fields for convenience.
### Key Classes
| Class | Purpose |
|-------|---------|
| `CardRecognitionService` | Main recognition logic, caches DB, handles rotation matching |
| `PerceptualHash` | DCT-based color hash (192-bit = 8 bytes × 3 RGB channels) |
| `ImagePreprocessor` | CLAHE, resize, grayscale conversions |
| `CardDetector` | Edge detection + contour analysis to find card boundaries |
| `PerspectiveCorrection` | Warp detected quad to rectangle |
| `CardDatabase` | SQLite wrapper with batch insert, queries |
| `ScryfallClient` | Bulk data streaming, image downloads |
## Code Conventions
### General
- **Target**: .NET 10.0 (net10.0-android for app, net10.0 for Core/tools)
- **Nullable**: Enabled everywhere (`<Nullable>enable</Nullable>`)
- **Warnings as errors**: `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>`
- **Central package management**: Versions in `Directory.Packages.props`
### C# Style
- Records for data models (`record Card`, `record ScanResult`)
- `required` properties for non-nullable required fields
- Extension methods for conversions (`ScryfallCard.ToCard()`)
- Static classes for pure functions (`PerceptualHash`, `ImagePreprocessor`)
- `using` declarations (not `using` blocks) for disposables
- File-scoped namespaces
- Primary constructors where appropriate
- `CancellationToken` parameter on all async methods
### MVVM (App layer)
- `CommunityToolkit.Mvvm` for source generators
- `[ObservableProperty]` attributes for bindable properties
- `[RelayCommand]` for commands
- ViewModels in `Scry.ViewModels` namespace
### Naming
- Services: `ICardRecognitionService`, `CardRecognitionService`
- Database methods: `GetCardsWithHashAsync`, `InsertCardBatchAsync`
- Hash methods: `ComputeColorHash`, `HammingDistance`
- Test methods: `RecognizeAsync_ExactMatch_ReturnsSuccess`
## Testing
Tests are in `test/Scry.Tests` using xUnit.
```bash
just test # Run all tests
dotnet test --filter "FullyQualifiedName~PerceptualHash" # Filter by name
```
### Test Categories
| Test Class | Tests |
|------------|-------|
| `PerceptualHashTests` | Hash computation, Hamming distance |
| `CardRecognitionTests` | End-to-end recognition |
| `CardDatabaseTests` | SQLite CRUD operations |
| `ImagePreprocessorTests` | CLAHE, resize |
| `RobustnessAnalysisTests` | Multiple image variations |
### Test Images
TestImages directory contains categorized reference images:
- `reference_alpha/` - Alpha/Beta cards (matching DbGenerator priority cards)
- `single_cards/` - Clean single card photos
- `varying_quality/` - Different lighting/blur conditions
## Key Algorithms
### Perceptual Hash (pHash)
Color-aware 192-bit hash:
1. Resize to 32×32
2. For each RGB channel:
- Compute 2D DCT
- Extract 8×8 low-frequency coefficients (skip DC)
- Compare each to median → 63 bits per channel
3. Concatenate R, G, B hashes → 24 bytes (192 bits)
Matching uses Hamming distance with threshold ≤25 bits and minimum confidence 85%.
### CLAHE (Contrast Limited Adaptive Histogram Equalization)
Applied in LAB color space to L channel only:
- Tile-based histogram equalization (8×8 tiles)
- Clip limit prevents over-amplification of noise
- Bilinear interpolation between tiles for smooth output
### Card Detection
Pure SkiaSharp implementation:
1. Grayscale → Gaussian blur → Canny edge detection
2. Contour tracing via flood fill
3. Douglas-Peucker simplification → Convex hull
4. Find best quadrilateral matching MTG aspect ratio (88/63 ≈ 1.397)
5. Order corners: top-left, top-right, bottom-right, bottom-left
## Debug Mode
Set `RecognitionOptions.DebugOutputDirectory` to save pipeline stages:
- `01_input.png` - Original image
- `02_detection.png` - Card detection visualization
- `03_perspective_corrected.png` - Warped card
- `05_clahe_*.png` - After CLAHE preprocessing
On Android: `/sdcard/Download/scry-debug` (pull with `adb pull`)
## Dependencies
### Core Library (Scry.Core)
- **SkiaSharp** - Image processing, DCT, edge detection
- **Microsoft.Data.Sqlite** - SQLite database
- **Microsoft.Extensions.Options** - Options pattern
### App (Scry.App)
- **CommunityToolkit.Maui** - MAUI extensions
- **CommunityToolkit.Maui.Camera** - Camera integration
- **CommunityToolkit.Mvvm** - MVVM source generators
### DbGenerator Tool
- **Spectre.Console** / **Spectre.Console.Cli** - Rich terminal UI
## Common Tasks
### Adding a New Card to Priority Test Set
1. Add image to `TestImages/reference_alpha/` or appropriate folder
2. Add entry to `GenerateCommand.PriorityCardsWithSets` dictionary
3. Run `just gen-db` to regenerate database
### Debugging Recognition Failures
1. Enable debug output in `MauiProgram.cs`:
```csharp
options.DebugOutputDirectory = "/sdcard/Download/scry-debug";
```
2. Run recognition
3. Pull debug images: `adb pull /sdcard/Download/scry-debug`
4. Compare `05_clahe_*.png` with reference images in database
### Modifying Hash Algorithm
1. Update `PerceptualHash.ComputeColorHash()`
2. Update `CardRecognitionService.ColorHashBits` constant
3. Regenerate database: `just gen-db --force`
4. Run tests: `just test`
## Gotchas
1. **Hash size is 24 bytes (192 bits)** - 3 RGB channels × 8 bytes each
2. **Confidence threshold is 85%** - Configurable in `CardRecognitionService.MinConfidence`
3. **Card detection is optional** - Controlled by `RecognitionOptions.EnableCardDetection`
4. **Rotation matching tries 4 orientations** - Controlled by `RecognitionOptions.EnableRotationMatching`
5. **Database is bundled in APK** - Copied on first run to app data directory
6. **Multi-face cards** - Only front face image is used for hashing
7. **Rate limiting** - DbGenerator uses 50ms delay between Scryfall image downloads
## CI/CD
Forgejo Actions workflow (`.forgejo/workflows/release.yml`):
- Builds for win-x64, linux-x64, osx-x64
- Creates "standard" and "embedded" (with APK) variants
- Publishes to Forgejo releases
## External Resources
- [Scryfall API](https://scryfall.com/docs/api) - Card data source
- [CARD_RECOGNITION.md](docs/CARD_RECOGNITION.md) - Detailed architecture doc