- 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>
10 KiB
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
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) requiredproperties for non-nullable required fields- Extension methods for conversions (
ScryfallCard.ToCard()) - Static classes for pure functions (
PerceptualHash,ImagePreprocessor) usingdeclarations (notusingblocks) for disposables- File-scoped namespaces
- Primary constructors where appropriate
CancellationTokenparameter on all async methods
MVVM (App layer)
CommunityToolkit.Mvvmfor source generators[ObservableProperty]attributes for bindable properties[RelayCommand]for commands- ViewModels in
Scry.ViewModelsnamespace
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.
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 photosvarying_quality/- Different lighting/blur conditions
Key Algorithms
Perceptual Hash (pHash)
Color-aware 192-bit hash:
- Resize to 32×32
- For each RGB channel:
- Compute 2D DCT
- Extract 8×8 low-frequency coefficients (skip DC)
- Compare each to median → 63 bits per channel
- 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:
- Grayscale → Gaussian blur → Canny edge detection
- Contour tracing via flood fill
- Douglas-Peucker simplification → Convex hull
- Find best quadrilateral matching MTG aspect ratio (88/63 ≈ 1.397)
- Order corners: top-left, top-right, bottom-right, bottom-left
Debug Mode
Set RecognitionOptions.DebugOutputDirectory to save pipeline stages:
01_input.png- Original image02_detection.png- Card detection visualization03_perspective_corrected.png- Warped card05_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
- Add image to
TestImages/reference_alpha/or appropriate folder - Add entry to
GenerateCommand.PriorityCardsWithSetsdictionary - Run
just gen-dbto regenerate database
Debugging Recognition Failures
- Enable debug output in
MauiProgram.cs:options.DebugOutputDirectory = "/sdcard/Download/scry-debug"; - Run recognition
- Pull debug images:
adb pull /sdcard/Download/scry-debug - Compare
05_clahe_*.pngwith reference images in database
Modifying Hash Algorithm
- Update
PerceptualHash.ComputeColorHash() - Update
CardRecognitionService.ColorHashBitsconstant - Regenerate database:
just gen-db --force - Run tests:
just test
Gotchas
- Hash size is 24 bytes (192 bits) - 3 RGB channels × 8 bytes each
- Confidence threshold is 85% - Configurable in
CardRecognitionService.MinConfidence - Card detection is optional - Controlled by
RecognitionOptions.EnableCardDetection - Rotation matching tries 4 orientations - Controlled by
RecognitionOptions.EnableRotationMatching - Database is bundled in APK - Copied on first run to app data directory
- Multi-face cards - Only front face image is used for hashing
- 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 - Card data source
- CARD_RECOGNITION.md - Detailed architecture doc