Scrymat turns your Magic: The Gathering decks into a shared virtual playmat โ offline, at the table, or remotely with no account required.
Scrymat is a free, open-source Progressive Web App (PWA) for MTG goldfishing, couch play, and casual remote games. It runs entirely in your browser, works offline after first load, and is designed for use on a phone lying flat on a game store table.
Note
The production TLD and some AWS infrastructure may still use the legacy
scryglass identifier until the final DNS and deploy cutover is complete.
No installation. No account. Just open and play.
- Open the deployed app (today that is still
scryglass.cards). - Paste your deck list (Scrymat format, or import from Moxfield/Archidekt/MTGO).
- Load each deck, deal opening hands, and start moving cards around the table.
- One player clicks Generate Room Code to host a match.
- Share the room code or
/match/:roomCodeinvite URL with the guest. - The guest enters the code, Scrymat completes the WebRTC handshake, and both browsers connect peer-to-peer.
- Once connected, the host can resync the guest with a full
GameStatesnapshot after reconnects or divergence.
- ๐ Shared virtual playmat โ The Scrymat pivot expands the state engine from library-only actions to generic card movement across public and private zones (ADR-012)
- ๐ช Dumb Table philosophy โ Scrymat gives you zones, cards, and movement, but it does not enforce turn structure or comprehensive Magic rules. You stay in control of takes-backs, shortcuts, and house rules.
- ๐ค Peer-to-peer remote play โ WebRTC data channels plus stateless signaling synchronize matches without a central game server (ADR-014)
- ๐ Hidden-information protection โ Commit-reveal hashing keeps opponents from trivially peeking at concealed cards during remote games. Hidden cards stay committed until you intentionally reveal them.
- ๐ฒ Deterministic shared shuffling โ Seeded PRNG support lets every client derive the same deck order for a match (ADR-013)
- ๐ฑ Offline-first local play โ Install to your home screen and keep using it for goldfishing or pass-and-play even without WiFi
- ๐ผ๏ธ Card images via Scryfall โ Background prefetch with IndexedDB caching and JIT priority loading
- ๐ฅ Multi-format import โ Supports Moxfield, Archidekt, and MTGO/Arena deck formats
- ๐ค Multi-format export โ Export your deck to any supported format
Scrymat began as a simple answer to the frustration that shuffling takes too long. I originally built it so I could play Magic with my kids. Previously, I was spending half of our "playtime" physically manipulating our cards. I wanted a way to let the computer handle the mechanics of the library so we could focus on actually playing the game together.
The pivot to Scrymat keeps that original goal, then pushes further into shared remote play:
- Protecting High-Value Collectibles: Play with your physical dual lands, foils, and reserved list cards without subjecting them to the wear and tear of constant mash-shuffling.
- Accessibility: 100-card Commander decks are physically difficult to manipulate. Scrymat removes the physical barrier of dexterity required to shuffle, fetch, and handle a massive deck.
- Frictionless Goldfishing: Test your latest brews instantly. Just paste your Moxfield link and start drawing hands without needing to sleeve up a single card.
- Remote Table Presence: Share a synchronized tabletop over the web without a full rules engine or webcam rig.
Scrymat is designed for trusted casual play, not for adversarial tournament enforcement:
- The table is dumb by design. Scrymat tracks where cards are, not whether a play was legal.
- Private information uses commit-reveal. Remote peers receive commitments for hidden cards first, then the real card only when it becomes public.
- Reconnects prefer snapshots over logs. When peers reconnect, the host can
send the guest the current serialized
GameStateso both sides converge quickly.
All significant decisions are documented as Architecture Decision Records:
| ADR | Title |
|---|---|
| ADR-001 | Use Architecture Decision Records (ADRs) to Document Decisions |
| ADR-002 | Preact + Vite for the PWA |
| ADR-003 | Scryfall API integration & compliance |
| ADR-004 | Fisher-Yates shuffle with Web Crypto API |
| ADR-005 | Action/Reducer state management โ agent-ready game engine |
| ADR-006 | Semicolon-delimited deck import format |
| ADR-007 | Monorepo structure (core/PWA separation) |
| ADR-008 | TypeScript & Zod for strict typing |
| ADR-009 | Client-side routing strategy |
| ADR-010 | Local storage strategy for decklists |
| ADR-011 | End-to-end testing strategy |
| ADR-012 | Expand state engine to full playmat |
| ADR-013 | Deterministic seeded PRNG for shared shuffling |
| ADR-014 | WebRTC data channels & stateless signaling |
| ADR-015 | URL-based match routing |
Scrymat is built as a monorepo with strict separation of concerns:
| Package | Purpose | Browser Dependencies |
|---|---|---|
@scrymat/core |
Pure game logic: deck parsing, shared shuffling, zone-based state management, mulligan/setup flows, and card movement | โ None โ runs in Node.js and browsers |
@scrymat/pwa |
Preact + Vite frontend: UI rendering, Scryfall API integration, IndexedDB caching, Service Worker, and WebRTC match sync | โ Browser APIs required |
The @scrymat/core module uses a strict JSON-in/JSON-out action-reducer pattern with Zod schema validation, making it suitable for consumption by AI agents, CLI tools, or any TypeScript/JavaScript consumer.
repository-root/
โโโ packages/
โ โโโ core/ # @scrymat/core โ game logic library
โ โ โโโ src/
โ โ โโโ package.json
โ โ โโโ tsconfig.json
โ โโโ pwa/ # @scrymat/pwa โ Preact + Vite frontend
โ โโโ src/
โ โโโ package.json
โ โโโ tsconfig.json
โโโ meta/ # Development philosophy, ADRs, and plans
โ โโโ adr/ # Architecture Decision Records
โ โโโ plans/ # Implementation plans and roadmaps
โโโ docs-src/ # Documentation source files (MkDocs)
โโโ scripts/ # Utility and automation scripts
โโโ .github/ # GitHub-specific configuration
โโโ package.json # Root workspace configuration
The repository includes a GitHub Actions workflow (.github/workflows/deploy-aws.yml) that builds the PWA and deploys it to AWS S3, fronted by CloudFront.
Required AWS resources:
- S3 bucket (static site hosting)
- CloudFront distribution (CDN, HTTPS, custom error pages for SPA routing)
- GitHub OIDC identity provider in IAM
- IAM role with S3 put/delete and CloudFront invalidation permissions
Required GitHub repository variables (Settings โ Secrets and variables โ Actions โ Variables):
| Variable | Description |
|---|---|
AWS_ROLE_ARN |
ARN of the IAM deploy role |
AWS_REGION |
AWS region of the S3 bucket |
S3_BUCKET_NAME |
Name of the S3 bucket |
CLOUDFRONT_DISTRIBUTION_ID |
CloudFront distribution ID |
CLOUDFRONT_DOMAIN |
(optional) Domain for post-deploy smoke test |
The workflow runs automatically on push to main and can be triggered manually via workflow_dispatch. See docs-src/deployment.md for the full step-by-step setup guide, including the IAM policy JSON and CloudFront configuration.
Important
Final Scrymat cutover still requires manual infrastructure follow-up: rename or redeploy the AWS bucket / CloudFront resources as desired, update GitHub Actions variables if those names change, rotate any environment-specific tokens tied to the legacy identifier, and repoint the public TLD when the new deployment is ready.
Contributions are welcome! Please read CONTRIBUTING.md for guidelines. Note that this is primarily a hobby project for my own benefit - if there's something you'd like to see improved please LMK, but also know that maintaining Scrymat isn't my day job.
Card images and data are provided by Scryfall. Scrymat respects Scryfall's API guidelines by rate-limiting requests, caching aggressively, and including a descriptive User-Agent header. See our ROBOT_ETHICS.md policy.
This project is licensed under a GPL License.
Scrymat is unofficial Fan Content permitted under the Fan Content Policy. Not approved/endorsed by Wizards. Portions of the materials used are property of Wizards of the Coast. ยฉWizards of the Coast LLC.