doubletap 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. doubletap-0.1.0/.gitignore +10 -0
  2. doubletap-0.1.0/.python-version +1 -0
  3. doubletap-0.1.0/LICENSE +21 -0
  4. doubletap-0.1.0/PKG-INFO +540 -0
  5. doubletap-0.1.0/README.md +507 -0
  6. doubletap-0.1.0/docs/gameplay-blindspots.md +47 -0
  7. doubletap-0.1.0/docs/operating-manual.md +453 -0
  8. doubletap-0.1.0/docs/ui-redesign-TODO.md +54 -0
  9. doubletap-0.1.0/pyproject.toml +56 -0
  10. doubletap-0.1.0/src/doubletap/__init__.py +1 -0
  11. doubletap-0.1.0/src/doubletap/analysis.py +222 -0
  12. doubletap-0.1.0/src/doubletap/archidekt.py +330 -0
  13. doubletap-0.1.0/src/doubletap/cli.py +1089 -0
  14. doubletap-0.1.0/src/doubletap/db.py +67 -0
  15. doubletap-0.1.0/src/doubletap/decks.py +281 -0
  16. doubletap-0.1.0/src/doubletap/formats.py +433 -0
  17. doubletap-0.1.0/src/doubletap/ml/__init__.py +0 -0
  18. doubletap-0.1.0/src/doubletap/ml/data.py +259 -0
  19. doubletap-0.1.0/src/doubletap/ml/eval.py +9 -0
  20. doubletap-0.1.0/src/doubletap/ml/infer_np.py +84 -0
  21. doubletap-0.1.0/src/doubletap/ml/model.py +143 -0
  22. doubletap-0.1.0/src/doubletap/ml/neighbors.py +60 -0
  23. doubletap-0.1.0/src/doubletap/ml/policy.py +126 -0
  24. doubletap-0.1.0/src/doubletap/ml/reward.py +129 -0
  25. doubletap-0.1.0/src/doubletap/ml/train_bc.py +93 -0
  26. doubletap-0.1.0/src/doubletap/ml/train_cql.py +141 -0
  27. doubletap-0.1.0/src/doubletap/names.py +58 -0
  28. doubletap-0.1.0/src/doubletap/ocr.py +40 -0
  29. doubletap-0.1.0/src/doubletap/scryfall.py +112 -0
  30. doubletap-0.1.0/src/doubletap/static/index.html +1014 -0
  31. doubletap-0.1.0/src/doubletap/web.py +311 -0
  32. doubletap-0.1.0/tests/conftest.py +35 -0
  33. doubletap-0.1.0/tests/fixtures/archidekt.csv +4 -0
  34. doubletap-0.1.0/tests/fixtures/decklist.txt +8 -0
  35. doubletap-0.1.0/tests/fixtures/moxfield.csv +3 -0
  36. doubletap-0.1.0/tests/fixtures/oracle-cards.jsonl +16 -0
  37. doubletap-0.1.0/tests/test_analysis.py +179 -0
  38. doubletap-0.1.0/tests/test_archidekt.py +290 -0
  39. doubletap-0.1.0/tests/test_cli.py +396 -0
  40. doubletap-0.1.0/tests/test_db.py +18 -0
  41. doubletap-0.1.0/tests/test_decks.py +213 -0
  42. doubletap-0.1.0/tests/test_formats.py +291 -0
  43. doubletap-0.1.0/tests/test_ml_data.py +147 -0
  44. doubletap-0.1.0/tests/test_ml_model.py +265 -0
  45. doubletap-0.1.0/tests/test_names.py +51 -0
  46. doubletap-0.1.0/tests/test_neighbors.py +65 -0
  47. doubletap-0.1.0/tests/test_reward.py +91 -0
  48. doubletap-0.1.0/tests/test_scryfall.py +77 -0
  49. doubletap-0.1.0/tests/test_web.py +242 -0
  50. doubletap-0.1.0/uv.lock +1070 -0
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .memsearch/
7
+ .idea/
8
+ dist/
9
+ docs/*.pdf
10
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joshua Magaña
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,540 @@
1
+ Metadata-Version: 2.4
2
+ Name: doubletap
3
+ Version: 0.1.0
4
+ Summary: MTG deck-building app with offline-RL card recommendations
5
+ Project-URL: Repository, https://github.com/jmagana2000/doubletap
6
+ Project-URL: Documentation, https://github.com/jmagana2000/doubletap/blob/main/docs/operating-manual.md
7
+ Author-email: Joshua Magaña <joshua.magana@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: commander,deck-building,edh,magic-the-gathering,mtg
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Topic :: Games/Entertainment
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: httpx
19
+ Requires-Dist: numpy
20
+ Requires-Dist: rapidfuzz
21
+ Requires-Dist: typer
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest; extra == 'dev'
24
+ Requires-Dist: respx; extra == 'dev'
25
+ Requires-Dist: ruff; extra == 'dev'
26
+ Provides-Extra: ml
27
+ Requires-Dist: numpy<2; (sys_platform == 'darwin' and platform_machine == 'x86_64') and extra == 'ml'
28
+ Requires-Dist: torch==2.2.2; (sys_platform == 'darwin' and platform_machine == 'x86_64') and extra == 'ml'
29
+ Requires-Dist: torch>=2.2; (sys_platform != 'darwin' or platform_machine != 'x86_64') and extra == 'ml'
30
+ Provides-Extra: ocr
31
+ Requires-Dist: pyobjc-framework-vision; (sys_platform == 'darwin') and extra == 'ocr'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # DoubleTap
35
+
36
+ A command-line tool that helps you build Magic: The Gathering decks. Tell it
37
+ what cards you have, and it suggests what to add next — learned from thousands
38
+ of real decks built by other players, not a game simulator.
39
+
40
+ **What it does:**
41
+ - Imports your cards from a photo, a text file, or a spreadsheet export
42
+ - Suggests cards that work well with what you already have
43
+ - Checks your deck against the official rules for your format
44
+ - Shows your deck's power level using the Commander Brackets system
45
+ - Analyzes whether your deck can actually function and win (mana curve, color
46
+ balance, ramp, draw, removal speed, win conditions)
47
+ - Shows what your deck costs in real money, and can keep suggestions under a budget
48
+ - Automatically fills out a partial deck to completion
49
+
50
+ **What it does not do:** simulate games or manage your collection.
51
+
52
+ This README is a guided introduction. For the complete reference — every
53
+ command and option, maintenance procedures, and failure recovery — see the
54
+ [operating manual](docs/operating-manual.md).
55
+
56
+ **Prefer a browser?** `doubletap web` serves a local web UI at
57
+ `http://127.0.0.1:8787` with every command available as a form — deck
58
+ browser, import, card lookup, analysis, suggestions, and model training.
59
+ It runs the exact same code as the CLI and never leaves your machine.
60
+
61
+ **Supported formats:** Commander (exactly 100 cards, one of each, including
62
+ partner-commander and companion decks) and Modern (60-card minimum, up to 4
63
+ copies of a card, companions supported). Other formats aren't supported yet;
64
+ any deck you import is treated as one of these two.
65
+
66
+ ---
67
+
68
+ ## Getting started
69
+
70
+ ### 1. Install
71
+
72
+ The recommended installer is [uv](https://docs.astral.sh/uv/) — it reads the
73
+ repo's `.python-version` (3.11) and `uv.lock`, so one command builds the
74
+ right environment every time:
75
+
76
+ ```bash
77
+ uv sync --extra dev --extra ocr --extra ml
78
+ ```
79
+
80
+ Prefer plain pip? Use **python3.11 specifically** (your system `python3` may
81
+ be newer, and PyTorch on Intel Macs requires 3.11):
82
+
83
+ ```bash
84
+ python3.11 -m venv .venv
85
+ .venv/bin/pip install -e ".[dev,ocr]"
86
+ .venv/bin/pip install -e ".[ml]"
87
+ ```
88
+
89
+ Then activate the environment so you can type `doubletap` directly:
90
+
91
+ ```bash
92
+ source .venv/bin/activate
93
+ ```
94
+
95
+ > **Note:** PyTorch is only needed for *training* your own model.
96
+ > Suggestions run on lightweight numpy weights, so you can skip the `ml`
97
+ > extra entirely if you use a pre-trained model. On Intel Macs the `ml`
98
+ > extra automatically pins the last compatible torch (2.2.2, Python 3.11).
99
+
100
+ ### 2. Download the card database
101
+
102
+ DoubleTap needs a local copy of every Magic card to look up names and rules.
103
+ This downloads about 180 MB from Scryfall (the free Magic card database) and
104
+ only re-downloads when new cards are released:
105
+
106
+ ```bash
107
+ doubletap cards sync
108
+ ```
109
+
110
+ You're ready to use all commands after this step.
111
+
112
+ ---
113
+
114
+ ## Everyday use
115
+
116
+ ### Importing cards
117
+
118
+ You can add cards from a photo of a physical card, a screenshot of a decklist,
119
+ a plain-text list, or a CSV export from Moxfield or Archidekt.
120
+
121
+ **From a photo of a physical card (iPhone/camera):**
122
+ ```bash
123
+ doubletap deck import /path/to/photo.HEIC
124
+ ```
125
+ DoubleTap reads the card name from the photo using Apple's built-in text
126
+ recognition. Works with `.HEIC`, `.jpg`, `.png`, and other common image
127
+ formats. The saved file is named after the card, not the photo — a photo of
128
+ Sol Ring becomes `sol-ring.json` (a second photo of the same card becomes
129
+ `sol-ring-2.json`, so nothing is overwritten).
130
+
131
+ **From a plain-text list:**
132
+ ```bash
133
+ doubletap deck import decklist.txt --format commander
134
+ ```
135
+ `--format` (or `-f`) accepts `commander` or `modern` and defaults to
136
+ `commander` if omitted. Each line should be a card name with an optional
137
+ quantity, like:
138
+ ```
139
+ 1 Sol Ring
140
+ 1 Atraxa, Praetors' Voice *CMDR*
141
+ 4 Lightning Bolt
142
+ Swamp
143
+ ```
144
+ Lines starting with a quantity are treated as that many copies. A line marked
145
+ `*CMDR*` or under a `Commander:` section header is set as your commander.
146
+
147
+ Decks with two commanders are supported: if both cards have the "Partner"
148
+ ability, mark both lines `*CMDR*` (or list both under `Commander:`). Cards in
149
+ the deck may use the colors of either commander.
150
+
151
+ Companions are supported too: list the card under a `Companion:` section
152
+ header (or pass `--companion "Card Name"`). The companion sits outside the
153
+ deck — it doesn't count toward the 60 or 100 cards — and `deck validate`
154
+ checks that your deck actually meets its deckbuilding restriction (for
155
+ example, Lurrus requires every permanent to cost 2 or less).
156
+
157
+ **From a Moxfield or Archidekt CSV export:**
158
+ ```bash
159
+ doubletap deck import export.csv --format modern
160
+ ```
161
+
162
+ Every import is saved automatically to `~/.doubletap/decks/`. Use `-o` to save
163
+ somewhere else:
164
+ ```bash
165
+ doubletap deck import photo.HEIC -o ~/my-decks/atraxa.json
166
+ ```
167
+
168
+ If a card name is unclear (blurry photo, typo), DoubleTap will show the closest
169
+ match and ask you to confirm before saving.
170
+
171
+ ### Viewing and combining your decks
172
+
173
+ **Add or remove a card:**
174
+ ```bash
175
+ doubletap deck add ~/.doubletap/decks/my-deck.json "Sol Ring"
176
+ doubletap deck remove ~/.doubletap/decks/my-deck.json "Sol Ring"
177
+ ```
178
+ Use `-n 4` to add or remove several copies at once. Card names must match
179
+ exactly — a typo gets suggestions instead of a wrong card. Adding warns
180
+ immediately if the card breaks a rule (a duplicate in Commander, outside your
181
+ commander's colors) but still saves, so you can fix things in any order.
182
+ Removing the commander, partner, or companion by name clears that slot.
183
+
184
+ **Set or change a deck's commander:**
185
+ ```bash
186
+ doubletap deck commander ~/.doubletap/decks/my-deck.json "Atraxa, Praetors' Voice"
187
+ ```
188
+ Works on any saved deck. If the card is already in the main deck it's moved
189
+ to the commander slot; a previous commander moves back into the main deck, so
190
+ the card count doesn't change. For partner commanders add
191
+ `--partner "Second Commander"`. It warns right away about any cards outside
192
+ the new commander's colors. Omit the card name to just see the current
193
+ commander:
194
+ ```bash
195
+ doubletap deck commander ~/.doubletap/decks/my-deck.json
196
+ ```
197
+
198
+ **See every card in a deck:**
199
+ ```bash
200
+ doubletap deck show ~/.doubletap/decks/my-deck.json
201
+ ```
202
+ Prints the commander, partner, and companion (when set), then each card with
203
+ its quantity, mana cost, and type, alphabetically.
204
+
205
+ **List all saved decks:**
206
+ ```bash
207
+ doubletap deck list
208
+ ```
209
+ Shows each deck's file name, format, card count, and commander. For small
210
+ commander-less files (like single-card photo imports) it shows the card
211
+ names instead, so you can tell what's inside at a glance.
212
+
213
+ **Combine individual card imports into one deck:**
214
+
215
+ When you photograph cards one at a time, each import creates a separate file.
216
+ Merge them into one deck like this:
217
+ ```bash
218
+ doubletap deck merge ~/.doubletap/decks/sol-ring.json \
219
+ ~/.doubletap/decks/negate.json \
220
+ ~/.doubletap/decks/adeline-resplendent-cathar.json \
221
+ -o ~/.doubletap/decks/my-commander-deck.json
222
+ ```
223
+
224
+ Use `--format` if you want to assign a different format than the one used
225
+ during import:
226
+ ```bash
227
+ doubletap deck merge ~/.doubletap/decks/*.json --format modern -o modern.json
228
+ ```
229
+
230
+ ### Checking your deck's power level (Commander Brackets)
231
+
232
+ Commander Brackets is an official system from Wizards of the Coast that helps
233
+ players find games at a matching power level. There are five brackets:
234
+
235
+ | Bracket | Name | What it means |
236
+ |---|---|---|
237
+ | 1 | Exhibition | Ultra-casual, theme decks, unusual builds |
238
+ | 2 | Core | Average preconstructed deck power |
239
+ | 3 | Upgraded | Stronger than precon, not tournament-ready |
240
+ | 4 | Optimized | High-powered, combos allowed |
241
+ | 5 | cEDH | Competitive tournament play |
242
+
243
+ To see which bracket your deck falls into:
244
+ ```bash
245
+ doubletap deck bracket ~/.doubletap/decks/my-deck.json
246
+ ```
247
+
248
+ The bracket is determined by how many "Game Changers" are in your deck — a
249
+ WotC-curated list of cards (powerful tutors, fast mana, win-condition
250
+ engines) identified as having an outsized effect on games; this tool ships
251
+ with the core of that list in `formats.py` and it's easy to extend when WotC
252
+ revises it. Zero Game Changers puts you at Bracket 1 or 2; 1–3 puts you at
253
+ Bracket 3; 4 or more puts you at Bracket 4.
254
+
255
+ The tool lists exactly which Game Changers are in your deck so you can decide
256
+ whether to swap them out for a lower-bracket game.
257
+
258
+ ### How you win a game — and what that means for your deck
259
+
260
+ If you're new to Magic, here is the short version of how games end. You win by
261
+ doing any one of these:
262
+
263
+ - **Reduce every opponent's life to 0.** They start at 40 in Commander, 20 in
264
+ Modern. This is how most games end — usually by attacking with creatures.
265
+ - **Commander damage (Commander only):** if any single commander deals 21 or
266
+ more combat damage to a player over the course of the game, that player loses.
267
+ - **Poison:** a player with 10 or more poison counters loses. Cards with
268
+ "infect" or "toxic" give these.
269
+ - **Decking:** a player who must draw a card from an empty library loses. Decks
270
+ built around this are called "mill" decks.
271
+ - **"You win the game" cards:** a small number of cards simply end the game
272
+ when their condition is met (for example, Thassa's Oracle).
273
+
274
+ A deck that can win needs more than just a win condition, though. If you spend
275
+ all your mana on threats, you'll be behind on resources; if you only draw and
276
+ ramp, you'll never close a game. Experienced Commander players aim for a rough
277
+ balance in a 100-card deck:
278
+
279
+ | Role | What it is | Rough target |
280
+ |---|---|---|
281
+ | Lands | Your mana every turn | ~36 |
282
+ | Ramp | Cards that give extra mana (Sol Ring, Cultivate) | ~10 |
283
+ | Card draw | Cards that refill your hand | ~10 |
284
+ | Removal | Answers to opposing threats (Swords to Plowshares, Negate) | ~10 |
285
+ | Board wipes | Reset buttons when you're behind (Wrath of God) | ~3 |
286
+ | Win conditions | Big creatures, combos, or "you win" cards | a clear plan |
287
+
288
+ DoubleTap can check your deck against these targets:
289
+
290
+ ```bash
291
+ doubletap deck analyze ~/.doubletap/decks/my-deck.json
292
+ ```
293
+
294
+ It reads each card's rules text and reports:
295
+
296
+ - **Role counts vs the targets above**, plus tutors (cards that search your
297
+ library for what you need — key to consistency in a 100-card deck).
298
+ - **Interaction speed** — how much of your removal works at instant speed.
299
+ Instant-speed answers can be held up during opponents' turns; sorceries
300
+ can't respond to anything.
301
+ - **Mana curve** — your nonland cards grouped by cost, average cost, and how
302
+ many you can cast in the first two turns. A deck that does nothing until
303
+ turn 5 loses to one acting on turns 1–3, whatever the cards.
304
+ - **Color balance** — the colored mana symbols your spells need vs the colors
305
+ your lands actually make. It flags a color your lands can't support (e.g.
306
+ black-heavy costs over a mostly-red mana base means uncastable hands).
307
+ - **Ways to win** — direct "you win" cards, big creatures, creatures with
308
+ evasion (flying/trample/menace get damage past blockers), poison, and mill.
309
+ - **Total market price.**
310
+
311
+ The detection is heuristic — a card with unusual wording may be missed — so
312
+ treat it as a gap-spotter, not a grade. The full gameplay-knowledge audit
313
+ behind these checks lives in `docs/gameplay-blindspots.md`.
314
+
315
+ ### What your deck costs — and building on a budget
316
+
317
+ Card prices come from Scryfall's market data and are already in your local
318
+ card database after `doubletap cards sync` (refresh with `--force` for current
319
+ prices).
320
+
321
+ **See what a deck costs:**
322
+ ```bash
323
+ doubletap deck price ~/.doubletap/decks/my-deck.json
324
+ ```
325
+ Shows the total in USD and the most expensive cards — useful for spotting
326
+ where the money is if you want a cheaper version.
327
+
328
+ **Keep suggestions within a budget:** add `--max-card-price` to `recommend` or
329
+ `complete` and DoubleTap will only suggest cards at or under that price:
330
+ ```bash
331
+ doubletap recommend --deck my-deck.json -k 20 --max-card-price 1.00
332
+ doubletap complete --deck my-deck.json --max-card-price 5.00 -o budget.json
333
+ ```
334
+ This is per card, not per deck — a $1 cap builds a deck where every suggested
335
+ card costs $1 or less.
336
+
337
+ ### Checking if a deck is legal
338
+
339
+ This checks your deck against the official format rules:
340
+ ```bash
341
+ doubletap deck validate ~/.doubletap/decks/my-deck.json
342
+ ```
343
+
344
+ It always starts with the deck's identity — format, card count, commander
345
+ (with combined color identity for partners), and companion — even when the
346
+ deck is incomplete, then lists every rule problem found. It will tell you
347
+ about:
348
+ - Banned cards
349
+ - Wrong number of cards (Commander needs exactly 100; Modern needs at least 60)
350
+ - Too many copies of a card (Commander allows only 1 of each; Modern allows 4)
351
+ - Cards outside your commander's color identity (Commander only)
352
+ - A companion whose deckbuilding restriction your deck doesn't meet (all ten
353
+ companions' rules are checked)
354
+
355
+ ### Looking up a card by name
356
+
357
+ If you're not sure how a card name is spelled, DoubleTap will find it:
358
+ ```bash
359
+ doubletap cards lookup "lightning blot"
360
+ ```
361
+
362
+ It handles typos, accented characters, and split card names. The score shown
363
+ (0–100) is how closely your search matched — 100 is an exact match. It has
364
+ nothing to do with how powerful the card is.
365
+
366
+ Each match also shows the card's color identity in brackets, like
367
+ `[WUBG (white, blue, black, green)]` — useful for checking whether a card
368
+ fits your commander's colors before adding it. `doubletap deck commander
369
+ <deck.json>` (with no card name) shows your commander's identity the same way.
370
+
371
+ ---
372
+
373
+ ## Getting card suggestions (requires a trained model)
374
+
375
+ This is the main feature: given your partial deck, DoubleTap suggests the
376
+ cards most likely to fit based on patterns from thousands of real decks.
377
+
378
+ **Before you can get suggestions, you need a trained model.** This is a
379
+ one-time setup that requires downloading public decklists and running a
380
+ training process. See [Training a model](#training-a-model-advanced) below.
381
+
382
+ Once you have a model:
383
+
384
+ **Get the top 20 suggestions for your deck:**
385
+ ```bash
386
+ doubletap recommend --deck ~/.doubletap/decks/my-deck.json -k 20
387
+ ```
388
+
389
+ Each suggestion shows a score and the cards already in your deck that pair
390
+ well with it. For example:
391
+ ```
392
+ 1. Heroic Intervention 2.341 with Atraxa (8.2), Sol Ring (4.1)
393
+ 2. Cyclonic Rift 2.187 with Atraxa (7.9)
394
+ ```
395
+
396
+ Lands are not suggested — the tool focuses on nonland cards, and a structural
397
+ note at the end tells you how many lands you still need to add.
398
+
399
+ **Auto-complete a partial deck:**
400
+ ```bash
401
+ doubletap complete --deck ~/.doubletap/decks/my-deck.json -o finished.json
402
+ ```
403
+
404
+ This fills all remaining nonland slots with the model's top picks, re-scoring
405
+ after each addition. When it's done it tells you how many lands to add to
406
+ finish the deck.
407
+
408
+ By default the result stays at **Commander Bracket 3** — the completed deck
409
+ will hold at most three Game Changers, counting any already in it. Change the
410
+ target with `--bracket`: 1 or 2 adds no Game Changers at all, 4 or 5 removes
411
+ the restriction:
412
+ ```bash
413
+ doubletap complete --deck my-deck.json --bracket 2 -o casual.json
414
+ ```
415
+
416
+ **Adjust suggestions to your specific deck style** with `--personalize`
417
+ (default is 0.3, range 0–1). Higher values weight cards that appear in decks
418
+ most similar to yours; lower values rely more on the model's general knowledge:
419
+ ```bash
420
+ doubletap recommend --deck my-deck.json -k 20 --personalize 0.5
421
+ ```
422
+
423
+ ---
424
+
425
+ ## Training a model (advanced)
426
+
427
+ The suggestion engine learns from real public decklists. You only need to do
428
+ this once (or redo it after a big card set release).
429
+
430
+ ### Step 1 — Download public decklists
431
+
432
+ This crawls Archidekt (a free public deckbuilding site) at a polite rate of
433
+ about one request per second. It will take several hours for a large corpus.
434
+ Keep your Mac awake with `caffeinate`:
435
+
436
+ ```bash
437
+ caffeinate -is doubletap corpus crawl --format commander --max 20000
438
+ ```
439
+
440
+ Check progress at any time:
441
+ ```bash
442
+ doubletap corpus stats
443
+ ```
444
+
445
+ The stats show how many decks were downloaded (`parsed`), skipped because they
446
+ had errors or illegal cards (`rejected`), or deleted since they were queued
447
+ (`gone`). Commander typically rejects about half of downloaded decks — most
448
+ public decks have missing cards, the wrong deck size, or rule violations that
449
+ disqualify them from training. Partner-commander decks are accepted.
450
+
451
+ ### Step 2 — Build the synergy table
452
+
453
+ This analyzes which cards tend to appear together across all downloaded decks:
454
+ ```bash
455
+ doubletap corpus pmi --format commander
456
+ ```
457
+
458
+ ### Step 3 — Train
459
+
460
+ ```bash
461
+ doubletap train bc --format commander
462
+ ```
463
+
464
+ This takes 10–30 minutes depending on corpus size. When it finishes, the model
465
+ is saved to `~/.doubletap/models/` and `recommend` will use it automatically.
466
+
467
+ ### Evaluating model quality
468
+
469
+ ```bash
470
+ doubletap eval --model ~/.doubletap/models/bc_commander.pt
471
+ ```
472
+
473
+ This hides some cards from test decks and measures how often the model ranks
474
+ them highly. Higher numbers mean better suggestions.
475
+
476
+ ---
477
+
478
+ ## Troubleshooting
479
+
480
+ **`command not found: doubletap`**
481
+ The virtual environment isn't active. Run `source .venv/bin/activate` first,
482
+ or prefix every command with `.venv/bin/doubletap`.
483
+
484
+ **Photo import reads the wrong text**
485
+ The tool works best on screenshots of printed decklists. For physical card
486
+ photos, point it at the card face straight-on with the name clearly visible.
487
+
488
+ **Suggestions seem generic or obvious**
489
+ The model learns from popular public decks, so widely-played cards rank
490
+ highly. Use `--personalize 0.5` or higher to shift weight toward cards that
491
+ appear in decks similar to yours.
492
+
493
+ **A card name isn't found**
494
+ Run `doubletap cards sync` to refresh the card database — it may be a newly
495
+ released card.
496
+
497
+ **`No module named 'torch'` when running `recommend` or `complete`**
498
+ The `doubletap` launcher is pointing at a Python without torch installed
499
+ (this can happen when the venv holds more than one Python version). Reinstall
500
+ the launcher from the interpreter that has torch:
501
+ `.venv/bin/python -m pip install -e . --no-deps --force-reinstall`
502
+
503
+ ---
504
+
505
+ ## Development
506
+
507
+ ```bash
508
+ # run all tests (no network required):
509
+ .venv/bin/pytest
510
+ # photo OCR smoke test (requires a real image):
511
+ .venv/bin/pytest -m macos_ocr
512
+ ```
513
+
514
+ Source layout:
515
+ ```
516
+ src/doubletap/
517
+ ├── cli.py # all commands
518
+ ├── db.py # local database
519
+ ├── scryfall.py # card data download
520
+ ├── names.py # name lookup and fuzzy matching
521
+ ├── decks.py # deck import, parsing, merging
522
+ ├── ocr.py # photo text recognition (Apple Vision)
523
+ ├── formats.py # format rules, validation, Commander Brackets
524
+ ├── analysis.py # card roles, mana curve, color balance, market prices
525
+ ├── archidekt.py # public decklist crawler
526
+ ├── web.py # local web UI server (runs the CLI in-process)
527
+ ├── static/ # the web UI single-page app
528
+ └── ml/ # suggestion engine (training and inference)
529
+
530
+ docs/
531
+ ├── operating-manual.md # complete command/maintenance/recovery reference
532
+ └── gameplay-blindspots.md # gameplay-knowledge audit behind deck analyze
533
+ ```
534
+
535
+ ## Known limitations
536
+
537
+ - Partner commanders and companions are supported; MDFC commanders are not.
538
+ - Photo import works on decklist photos and screenshots. Spread-of-cards-on-a-table photos are not supported.
539
+ - The suggestion engine does not recommend lands. Add those yourself based on the land count gap reported at the end of `recommend` and `complete`.
540
+ - Very rare cards may get generic suggestions because the model has seen them in few real decks.