Deckbuilder and semantic search through embedding space for One Piece TCG

Community Article
Published May 27, 2026

Two tools, one small open dataset, and the engineering behind them.

If you have ever opened the card list, set every filter you could, and still ended up reading effect text on 40 cards to find the one tech piece your deck wants - this post is for you.

I built two free Hugging Face Spaces for One Piece TCG over the last few weeks:

  • OPTCG Card Explorer - type plain English describing the card you want ("low cost red blocker with draw", "purple ramp that uses DON!!"), get the top matches plus an interactive map of all 4,372 cards.
  • OPTCG Deck Builder - pick any Leader, pick a style (aggro / midrange / control), get a legal 50-card deck sketch in two seconds, plain-text exportable into your sim of choice.

Both are open source, run on free hardware, and ship with the underlying dataset published on the Hugging Face Hub so anyone can build their own tools on top.

The rest of this post: what you can actually do with them, where they fall short, and (for the ML readers) the engineering decisions worth flagging - including the conceptual gap between similarity and synergy that the deckbuilder has to close with explicit game rules rather than embedding magic.


What you can do with the Explorer

The Explorer puts a search bar over the whole English card pool. Under the hood, your query is encoded by the same model that encoded every card, then the cards are ranked by how close they are to your query in meaning - not by keyword match. Some queries that work well:

  • "low cost red blocker with draw" - returns Red Characters in the 1-3 cost range with Blocker and a card-draw effect.
  • "counter +2000 blue character" - the kind of defensive cards you slot in to survive a swing turn.
  • "event that returns characters to opponent's hand" - bounce effects across colors.
  • "leader that punishes wide boards" - finds Leaders whose effect triggers on opposing Character count.
  • "green stage that gives +1000 power on attack" - a specific support package, hard to find with raw filters.
  • "On Play effect that searches the deck for a specific card" - tutor effects.

It does not know proper noun search ("find me Whitebeard"). For that, use the dropdown picker in the second panel or just type the card ID. Where it shines is when you can describe what a card does but you don't remember which card it was, or when you're looking for "everything like this card" across sets.

The interactive map below the search box plots all 4,372 cards in 2D. Clusters tend to share color and card type. After a search, the matches are drawn on top as larger black-bordered markers - a quick visual sanity check on whether the model "got" the query. Tightly clustered hits mean the model understood; scattered hits mean the query was vague and you should add more constraints.

The third panel lets you pick any card from a filterable dropdown and see the 10 most mechanically similar cards. Useful for finding budget replacements when a rare is out of reach, or for spotting cards that quietly do the same thing as a staple you already know.

What you can do with the Deck Builder

The Deck Builder is the second Space, and it has a different shape: pick a Leader, click Build, get a 50-card deck.

The point is to get from "I want to brew around this Leader" to "I have something to play-test against" in a few seconds, with all the legality boxes already ticked: color-legal cards only, no more than 4 copies of anything, exactly 50 cards.

Three style presets shape the cost curve:

  • aggro - heavy on 1-3 cost, the deck that wants to flood the board early and pressure life before the opponent stabilises.
  • midrange - 3-6 cost dominant, the safe default.
  • control - 4-8 cost weighted, fewer early turns but bigger threats and more answers.

There's a slider for max copies (1-4) if you want forced variety. There's a target-vs-actual cost curve chart so you can see at a glance whether the corpus had enough candidates at each cost slot - sometimes a narrow-identity Leader can't fully fill a low-cost bucket, and the builder backfills from elsewhere. Either way, the deck total is always exactly 50.

And there's a plain-text export at the bottom in the format most sims accept (4x OP01-001 Card Name per line, one per line, with a Leader header) so you can copy-paste straight into your favourite sim and start tweaking.

What it is and isn't

Worth being honest about this upfront, because the OPTCG community knows the game better than the algorithm does:

What it is - a starting point. A sketch. A deck whose cards are mechanically coherent with your Leader, sorted by a similarity score that captures "this card does the kind of thing your Leader is about." Useful for first-draft brewing, for exploring a Leader you've never played, for finding overlooked cards in colors you don't usually run.

What it isn't - a tournament list. It has no banlist awareness. No meta knowledge. No idea that a specific Luffy is "the rush leader" or that a specific Kaido wants a Plague-counter package. It doesn't know which specific card combos win games. Most importantly, it has no concept of pairwise synergy - a card that's mediocre on its own but excellent with one specific other card is invisible to it.

Translation: the builder gives you a coherent 50-card pile to start from. The fun-and-hard part - the meta read, the tech choices, the swap decisions you make after the first 20 games - is still yours.

What's under the hood

OK, ML-content section. If you're a player who just wants the tools, skip to "Try them" at the bottom. If you're an ML practitioner, this is the part where the design decisions live.

The dataset

Both Spaces run over t22000t/optcg-en-card-embeddings - all 4,372 English OPTCG cards (current as of the last monthly refresh), each represented by a 1024-dim vector from Qwen/Qwen3-Embedding-0.6B, plus precomputed UMAP-2D coordinates for the map view. There's a sister dataset t22000t/optcg-en-cards without the vectors for users who only want the structured fields.

The encoding strategy is adapted from Max Woolf's MTG embeddings work:

  1. Serialize each card as a prettified JSON document containing only the mechanically-relevant fields (name, type, colors, cost, power, counter, life, attribute, family, effect text, trigger text, rarity).
  2. Substitute the card's own name with ~ inside its effect and trigger text. Cards constantly reference themselves ("This Character gains..."), and without substitution the model latches onto proper nouns instead of mechanics.
  3. Prepend a task instruction telling the model what kind of similarity matters. The exact instruction lives in the dataset's provenance.json:

Instruct: Represent this One Piece Card Game card so that mechanically similar cards are close in embedding space. Cards in the same color, with shared keywords (Rush, Blocker, Double Attack, Banish, On Play, Counter), or that interact with the same Leader archetype should be neighbors.

The instruction is load-bearing. The same string must be prepended when encoding a free-text query, or your query lives in a slightly different region of embedding space than the corpus and the cosine distances become meaningless. The library reads the instruction from provenance instead of hardcoding it - so a future re-embed with a different prompt cannot silently break downstream consumers.

The pipeline is four stages (fetch -> normalize -> embed -> visualize -> upload), each independently testable, all behind a CLI. A monthly GitHub Actions job re-runs the whole thing. 216 hermetic tests; full embed of the corpus takes ~5 minutes on an M2 Max with MPS.

Why a small model

Qwen/Qwen3-Embedding-0.6B is the smallest of the Qwen3 embedding family. Three reasons it's the right pick for this:

  • Instruction tuning baked in. The model understands Instruct: ... Text: {content} as a contract - exactly the encoding shape we use.
  • 1024-dim with Matryoshka support. Future-proofs the dataset if someone wants to truncate to 256 or 512 dim for a smaller index.
  • 0.6B parameters. Runs on laptop CPU at acceptable speed, fits comfortably in a free-tier HF Space, and is small enough that ablations against larger Qwen3 variants (4B, 8B) showed no meaningful quality gain on this corpus - card text is short, mechanics are narrow, the larger models don't pull weight.

The IP stance: no card art

Deliberate scope decision. Bandai is at the conservative end of the publisher stack on IP. The dataset contains zero card art bytes, zero image URLs, zero thumbnails. An invariant test enforces it:

def test_no_image_url_in_canonical_keys():
    forbidden = ("image", "img_", "art_url", "thumbnail")
    for key in CANONICAL_KEYS:
        assert not any(f in key.lower() for f in forbidden)

If you want card art, you're downstream of a different conversation with the publisher. Everything in this pipeline is the same kind of structured public-information aggregation that strategy sites already do, plus embeddings.

The Spaces are thin

The Explorer Space is ~200 lines of glue over the published library. The Deck Builder is a similar amount. Both reuse the same data loader, the same color palette, the same recommend_synergy ranker. The most consequential engineering decision in both is to stack the embedding matrix once at startup and reuse it across every search and every nearest-neighbour lookup:

matrix = np.stack(
    [np.asarray(c["embedding"], dtype=np.float32) for c in cards],
    axis=0,
)
for card in cards:
    card.pop("embedding", None)  # halves cards-list RAM

(4372, 1024) * float32 = 18 MB. Every search is scores = matrix @ query_vec, 50µs at this size. Search latency is dominated entirely by the Qwen3 query-encode pass (150ms on CPU); the ranker is free. For "similar cards", the query vector is just matrix[idx] - no encoder call at all.

This pattern generalizes. Any embedding corpus that fits in RAM (up to ~100k items at 1024-dim is ~400 MB) is better served by a numpy matrix than a vector database at this scale.

Similarity is not synergy

The most conceptually interesting decision is in the Deck Builder. In an embedding space trained on card mechanics, two cards can be very close to each other and still be terrible together in a deck. Two near-identical low-cost red Blockers are mechanically similar - both fill the same gap on turn 2-3, defend the same way. A deck doesn't want both at four copies. It wants one, then it wants the complement.

This is the gap. Similarity = "another card I might play in the same slot." Synergy = "a card that makes the first card better when both are in play."

Embeddings give you similarity for free. Synergy is harder. You either need a much larger model trained on actual game outcomes (out of scope for a niche TCG with no public match data), or you layer game rules on top of similarity until you've reconstructed enough of "synergy" to be useful. The Deck Builder does the second thing in three stages:

  1. Score every candidate as cosine_similarity(leader_vec, card_vec) + 0.10 if same_family else 0. Color legality is a hard filter; off-color cards are dropped. Other Leader cards are excluded.
  2. Fill cost buckets from a target curve (the style preset). For each cost in order, walk the synergy-ranked candidates at that cost, taking up to 4 copies each until the bucket fills.
  3. Backfill any deficit from remaining top-synergy cards regardless of cost. This is what makes the "exactly 50" invariant a hard guarantee even when the corpus can't fill a cost slot.

The +0.10 family bonus is small on purpose. Cosine on a 1024-dim L2-normalized vector typically sits in [0.30, 0.85] for "similar" pairs. 0.10 is enough to flip an off-archetype card behind a same-archetype card with marginally higher base similarity, but not enough to drown the embedding signal entirely. A unit test constructs a 3-card scenario where the bonus is the deciding vote, to make sure the constant actually moves rankings.

Several invariants are easy to break on a refactor and hard to spot on a 50-card output:

  • Total quantity is exactly 50 for every style.
  • Every card shares at least one color with the Leader.
  • No card exceeds the copy cap.
  • aggro.avg_cost < midrange.avg_cost < control.avg_cost. The presets actually shape the deck, not just label it.

That last test is the most useful. Without it, the entire style picker could be a no-op and nobody would notice.

What's next (and where you can help)

The OPTCG community side:

  • More example queries that work well. I'd love to add a curated "try these" library based on what players actually want to find. If you have queries that the Explorer nails (or whiffs on) I want to hear about it.
  • Archetype detection. The biggest gap in the Deck Builder is that it doesn't know which Leader is the rush leader, which one wants a Plague package, which one rewards a stall-and-counter shell. A small classifier over Leader effect text could tag each Leader with a strategy label and default the style picker accordingly. This is the next thing I want to build.
  • Banlist awareness. Currently no concept of restricted lists. If the community can point me to a maintained machine-readable banlist, I'll integrate it.

The ML/engineering side:

  • Pairwise synergy mining. A card that's mediocre on its own but excellent with one specific partner is invisible to current similarity ranking. Closing this gap probably requires training a small model on actual played decklists - which means a corpus of decklists, which is the bottleneck.
  • Image embeddings (without bytes). A planned optcg-en-card-image-embeddings dataset with SigLIP-2 vectors only, no image data. Defers the IP-sensitive part while still enabling visual-similarity tools.
  • Other languages. Japanese is implemented but --unstable-gated pending upstream vegapull fixes. Chinese, French, Thai are deferred.

Try them

Both Spaces sleep when idle, so the first request after a few hours triggers a cold start (~60-90s). Subsequent queries are sub-second.

If you want to build your own tools:

Card data sourced from the official One Piece Card Game site via vegapull (Coko7). Embedding strategy adapted from Max Woolf's MTG embeddings work. Not affiliated with Bandai or the One Piece Card Game.

If you build something on top of these and want to share it, I'd love to see it - hit me on the dataset's discussions tab on the Hub.

Community

Sign up or log in to comment