An open-source Magic: The Gathering client and rules engine, built around Forge compatibility.
|
|
|
|
Note
This is unofficial fan software. It is not affiliated with, endorsed by, or sponsored by Wizards of the Coast LLC or by the Forge project. Magic: The Gathering, card names, rules text, and related marks are property of Wizards of the Coast LLC. Forge is developed by the Forge contributors. Card images are not shipped by this project.
- App — play the current build at https://play.manabrew.app.
- Website & docs — https://manabrew.app and https://docs.manabrew.app.
- Discord — ask questions, share decks, report rough edges, and follow development at https://discord.gg/NqrKpbhtcd.
- What This Is
- Current Status
- Getting Started
- Common Commands
- Architecture
- Parity Harness
- Engine Notes
- Background
- Contributing
- License
Manabrew is a React/Tauri client and Rust rules-engine project for playing
Magic online. It uses Forge as the rules reference instead of defining a new
interpretation of the game.
The repository includes:
- a desktop client, web client, deck tools, and multiplayer runtime;
- a Rust port of Forge's game engine;
- Java Forge interop for Forge-backed games through the same client stack;
- a parity harness that compares Rust and Java Forge with the same decks, seed, and deterministic choices.
Tip
The Rust engine is still catching up to Forge. The client stack can also drive Java Forge-backed sessions, which gives the project a usable path while Rust parity work continues.
To get started visit our landing page
Manabrew is pre-release software.
- The client and Java Forge backend path are the most practical way to play today.
- The Rust engine works for selected matchups, but broad card coverage is still in progress.
- Engine work is driven by parity testing against Java Forge.
- Packaging, contributor onboarding, and release-readiness work are ongoing.
- Node.js 22.12+ recommended
- Yarn v1
- Rust stable
- JDK 18–21 and Maven for the Java Forge harness / parity runs. Newer JDKs
(e.g. 26) currently fail to compile Forge; if
/usr/libexec/java_homeresolves to one, pin an older one:JAVA_HOME="$(/usr/libexec/java_home -v 21)". - Platform prerequisites for Tauri
The Java reference engine and all card scripts live in the forge submodule, so
clone with --recurse-submodules:
git clone --recurse-submodules https://github.com/witchesofthehill/manabrew.gitIf you already cloned without it, initialize the submodule before building:
git submodule update --init --recursiveThe forge submodule is pinned: every branch records the exact Forge commit
it expects (the gitlink). Git does not move the submodule for you when you
switch branches or pull, so after either of those — or whenever git status
shows M forge you didn't intend — sync your working copy back to the pinned
commit:
git submodule update --init forge # checks out the *recorded* commit (the pin)Note this is not --remote: --init restores the pin, --remote advances
to the latest upstream (see "Bump the Forge submodule" below). Check the state
with:
git submodule status forge
# <sha> forge → in sync with the pin
# +<sha> forge → your forge is AHEAD of the pin; run the sync command above
# -<sha> forge → not initialized; run the sync command aboveAn out-of-sync submodule means the Java engine, card scripts, and the harness
jar disagree on which Forge version they target. Symptoms: cards silently
stripped from decks, No enum constant forge.card.CardSplitType.X at deck load,
or a NoClassDefFoundError at runtime. When in doubt, re-sync and rebuild.
yarn installDo this only when you intentionally want newer Forge — not to fix an
out-of-sync checkout (for that, use the sync command above). The forge
submodule is the whole Forge tree — the Java reference engine plus card scripts,
editions, and token data. Advance it to the latest commit (it tracks the
manabrew branch — see .gitmodules) with:
git submodule update --remote forgeThen clean-rebuild so the new Forge is actually picked up — the Java harness,
the WASM engine, and the bundled card archives all build from forge/:
# Clean rebuild — a plain build reuses stale .class files compiled against the
# old Forge and silently ships a broken jar. Pin a JDK Forge can compile with.
JAVA_HOME="$(/usr/libexec/java_home -v 21)" \
mvn -pl forge-harness -am clean package -DskipTests
yarn ensure:harness # restages the Tauri card bundle + updates the build checksum
yarn web # rebuilds the WASM engine and card archive (yarn dev does too)Two gotchas this avoids:
yarn build:harness/yarn ensure:harnessrun an incremental Maven build. After a Forge bump they may reuse stale classes, so do themvn … clean packageabove at least once; the symptoms are deck cards stripped,No enum constant …, or aNoClassDefFoundErrorat runtime.cargo run -p self-hosted-nodedoes not rebuild the harness; it loads the prebuilt jar fromforge-harness/target/. Rebuild the harness yourself after any Forge change.
A Forge bump can change Forge's Java API, which may require fixing compile errors
in forge-harness/ before it builds — do that in the same change. Then commit
the submodule pointer bump (git status shows M forge) and open a PR like any
other.
self-hosted-node can run the Forge engine in-process instead of spawning a
JVM subprocess: Forge is compiled ahead-of-time with GraalVM native-image into
a shared library that the node links via FFI (feature graal-forge). Build
prerequisites and steps are in
forge-harness/native/README.md.
yarn devyarn webyarn buildyarn lint:all| Command | What it does |
|---|---|
yarn dev |
Start the Tauri desktop app in development mode |
yarn web |
Build the WASM engine and start the web client |
yarn build |
Build the desktop app |
yarn build:web |
Build the web app |
yarn build:harness |
Build the Java Forge parity harness |
yarn parity |
Run named parity scenarios |
yarn parity:test -- |
Run the parity binary with custom arguments |
yarn parity:gui |
Start the engine debugger |
yarn lint:all |
Run frontend lint/typecheck and Rust fmt/clippy checks |
yarn import-deck ... |
Import a deck from Archidekt or Moxfield |
React UI src/
Tauri desktop shell src-tauri/
Web/WASM engine bridge manabrew-rs/crates/wasm/
Headless runtime manabrew-rs/crates/self-hosted-node/
Relay / lobby server manabrew-rs/crates/manabrew-server/
Agent protocol DTOs manabrew-rs/crates/manabrew-agent-interface/
Rust rules engine manabrew-rs/crates/manabrew-engine/
Card database + script IR manabrew-rs/crates/forge-carddb/
Forge Java reference forge/
Parity harness manabrew-rs/crates/parity/
The deeper engine workspace map is in manabrew-engine/README.md.
Most engine work starts with a failing parity run:
yarn build:harness
yarn parity:test -- --deck1 red_burn --deck2 green_stompy --seed 42 --max-turns 20The harness runs Rust and Java Forge with the same inputs, compares trace snapshots, and reports the first mismatch. A good fix restores the missing general rule in Rust by reading the corresponding Java file, not by special casing the card that exposed the bug.
Start here:
Forge card scripts are the compatibility contract. The Rust engine mirrors Java Forge behavior, but it can use Rust-native data structures and typed internal representations when behavior stays the same.
The main example is compiled IR for high-risk card-script semantics: produced mana, defined-object references, numeric expressions, selectors, costs, and similar domains. This is an implementation difference, not a game-behavior difference. SVar resolution remains late-bound and follows the current host-card state.
See Forge Parity and IR and the SVar semantics in docs/forge-dsl-semantics.md.
Why This Exists
The project started because a small group of friends wanted a modern, open way
to play Magic online together. Existing tools solve different parts of that
problem well, and Forge in particular provides the rules knowledge this project
depends on. Manabrew explores a different client, runtime, and deployment
shape while preserving Forge compatibility.
Why Rust?
Forge has years of rules knowledge and a very large card-script corpus. Rust lets this project target:
- desktop app through Tauri;
- web/WASM builds for browser-based play;
- headless engine hosts for self-hosted multiplayer;
- deterministic traces for debugging, regression testing, and AI work;
- typed internal representations for hot or high-risk card-script semantics.
Relationship With Forge
Forge is the foundation of this project.
- The Java Forge source under
forge/is the reference implementation. - The Rust engine mirrors Forge's rules structure and consumes Forge card scripts.
- The parity harness keeps behavior faithful to Forge.
- The engine and bundled card data are derivative of Forge (GPL-3.0-or-later);
this project's own code is AGPL-3.0-or-later (a GPL-compatible license that
also covers network use). The vendored
forge/tree stays GPL-3.0-or-later.
This is an independent fan project. Forge maintainers are not expected to review or support this work.
See Forge Parity and IR and Third-Party Notices. For related projects and ecosystem context, see Ecosystem.
The most useful contributions are small, well-scoped parity fixes, documentation improvements, UI bug fixes, and reproducible issue reports.
Before opening a PR, read CONTRIBUTING.md. In short:
- work from an issue or open one first for larger changes;
- use Conventional Commit messages;
- sign commits with a DCO
Signed-off-by:trailer; - for engine fixes, read the Java Forge counterpart before editing Rust;
- run
yarn lint:allbefore asking for review; - do not bundle card images or secrets.
Maintainers (and anyone with write access) can drive CI from a pull-request comment. Post one of these as a comment on the PR:
| Comment | Effect |
|---|---|
/build mac (or /build macos) |
Build the macOS .dmg from the PR branch |
/build windows (or /build win) |
Build the Windows .exe / .msi from the PR branch |
/build all (or /build both) |
Build both installers |
/rerun <workflow> |
Re-run the latest run of a workflow on the PR branch — e.g. /rerun regression, /rerun build-checks |
Put the command on the first line of the comment, on its own. The bot reacts 👀 when it picks the command up and replies with a link to the run. Commands are ignored for anyone without write access and for PRs opened from forks. Built installers are uploaded to the Actions run (30-day retention).
This repository welcomes the use of AI assistance for mechanical porting, parity investigation, trace analysis, documentation, and large-scale inventory work. AI output is treated as code written by a contributor: it must be well thought of, reviewed, and properly tested.
Do not push code you don't understand.
See AI Usage.
The project is licensed under AGPL-3.0-or-later, to close GPL's network
loophole for the hosted instance. AGPL is GPL-compatible, so this is additive;
the vendored forge/ tree stays GPL-3.0-or-later under its upstream terms. The
protocol specification (the docs under
website/src/content/docs/protocol/) remains CC-BY-4.0.
See LICENSE.md, LICENSE-AGPL-3.0-or-later, LICENSE-GPL-3.0-or-later, and THIRD-PARTY-NOTICES.md.



