Merge remote-tracking branch 'upstream/master' into first-run-tutorial

This commit is contained in:
Lukas Brübach 2026-10-11 15:14:26 +02:00
commit f5826d9d6f
1395 changed files with 67177 additions and 10446 deletions

View file

@ -29,12 +29,17 @@
<xs:attribute type="xs:anyURI" name="picURL" use="optional" />
<xs:attribute type="xs:string" name="num" use="optional" />
<xs:attribute type="xs:string" name="rarity" use="optional" />
<xs:attribute type="xs:boolean" name="isOnlineOnly" use="optional" />
<xs:attribute type="xs:boolean" name="isRebalanced" use="optional" />
<xs:attribute type="xs:string" name="flavorName" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="localizationType">
<xs:sequence>
<xs:element type="xs:string" name="name" minOccurs="0" maxOccurs="1" />
<xs:element type="xs:string" name="text" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute type="xs:string" name="lang" use="required" />
</xs:complexType>
<xs:group name="cardPropertyGroup">
<xs:sequence>
<xs:any processContents="skip" minOccurs="0" maxOccurs="unbounded" />
@ -49,6 +54,13 @@
<xs:group ref="cardPropertyGroup"/>
</xs:complexType>
</xs:element>
<xs:element name="localizations" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element type="localizationType" name="localization" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element type="cardInSetType" name="set" minOccurs="1" maxOccurs="unbounded" />
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element type="relatedType" name="related" minOccurs="0" maxOccurs="unbounded" />

View file

@ -1,3 +1,17 @@
## Building Doxygen Documentation
We use [Doxygen](https://www.doxygen.nl) for our code documentation.
After installing the tool, you can build a HTML webpage into the `/docs` folder according to our [configuration](https://github.com/Cockatrice/Cockatrice/blob/master/Doxyfile):
```bash
doxygen Doxyfile
```
Open the `index.html` file in the `/docs/html/` folder to browse the result locally.
There is also an online version we host at the Cockatrice webpage: [cockatrice.github.io/docs](https://cockatrice.github.io/docs)
Doxygen's own documenation can be found at [www.doxygen.nl/manual](https://www.doxygen.nl/manual).
<br>
## Doxygen Documentation Theme
Required changes to the `Doxyfile` config from the [theme docs](https://jothepro.github.io/doxygen-awesome-css/index.html#autotoc_md16) to make [doxygen-awesome-css](https://github.com/jothepro/doxygen-awesome-css) work:

View file

@ -1,52 +0,0 @@
@page loading_card_pictures Loading Card Pictures
Pictures associated with CardInfo%s are retrieved either from on-disk or the network through the CardPictureLoader.
In most cases, you don't need to concern yourself with the internals of CardPictureLoader.
Simply using one of the ways described in @ref displaying_cards is enough to automatically queue a request to the
CardPictureLoader when the chosen widget is shown, emitting signals to refresh the widget when the request is finished.
# How requests are triggered
CardPictureLoader::getPixmap() is called exactly two times in the code base, in CardInfoPictureWidget::loadPixmap(), the
base class
for all widget based card picture display, and AbstractCardItem::paintPicture(), the base class for all QGraphicsItem
based card picture
display. See @ref displaying_cards for more information on the difference between these two display methods.
Because both of these calls are made in the paintEvent() methods of their respective classes, this means that requests
are issued as soon as but not before the widget is shown on screen.
It is also possible to "warm up" the cache by issuing card picture load requests to the CardPictureLoader without using
a display widget and waiting for it to be shown by calling CardPictureLoader::cacheCardPixmaps() with a list of
ExactCard%s.
# The QPixmapCache and QNetworkDiskCache
Cockatrice uses the QPixmapCache from the Qt GUI module to store card pictures in-memory and the QNetworkDiskCache from
the Qt Network module to cache network requests for card pictures on-disk.
What this means is that the CardPictureLoader will first attempt to look up a card in the QPixmapCache according to the
ExactCard::getPixmapCacheKey() method of an ExactCard object. If it does not find it in the in-memory cache, it will
issue a load request, which will first look for local images on-disk and then consult the QNetworkDiskCache and if
found, use the stored binary data from the network cache to populate the in-memory pixmap cache under the card's cache
key. If it is not found, it will then proceed with issuing a network request.
The size of both of these caches can be configured by the user in the "Card Sources" settings page.
# PixmapCacheKeys and ProviderIDs
TODO
# The Redirect Cache
TODO
# Local Image Loading
TODO
# URL Generation and Resolution
TODO

View file

@ -1,170 +0,0 @@
@page game_event_handler GameEventHandler
## Overview
`GameEventHandler` is the central coordinator for **game-wide commands and events**.
It acts as the bridge between the networking layer (protobuf messages received from
the server) and the local game model and UI.
Unlike `PlayerEventHandler`, which is responsible for player-scoped state and zones,
`GameEventHandler` handles:
- Global game flow (turns, phases, host changes)
- Player and spectator lifecycle events
- Chat and logging events
- Dispatching incoming events to the appropriate subsystem
- Sending locally initiated commands to the server
In short, it is the **top-level event dispatcher** for an active game.
---
## Responsibilities
`GameEventHandler` has four primary responsibilities:
### 1. Sending game-wide commands
UI actions that affect the game as a whole (e.g. advancing the turn, changing phases,
sending chat messages) are translated into protobuf commands and sent to the server
via this class.
Examples include:
- Advancing or reversing the turn order
- Conceding or unconceding
- Changing the active phase
- Leaving the game
- Sending chat messages
These commands are wrapped in `PendingCommand` instances so their lifecycle and
responses can be tracked.
---
### 2. Processing incoming game events
Incoming server messages arrive as a `GameEventContainer`.
`GameEventHandler` is responsible for:
- Emitting lifecycle signals before and after processing
- Dispatching each event to the correct handler
- Forwarding player-scoped events to `PlayerEventHandler` instances
- Handling spectator-only and game-global events directly
This design keeps networking concerns isolated from game logic and UI code.
---
### 3. Managing global game state transitions
Certain events affect the entire game state and require coordinated updates across
multiple subsystems. Examples include:
- Full game state synchronization
- Active player or phase changes
- Game host changes
- Game closure
- Turn reversal
`GameEventHandler` ensures these events are processed in a consistent order and
that all interested systems are notified via Qt signals.
---
### 4. Emitting UI and logging signals
Rather than directly manipulating UI widgets, `GameEventHandler` emits
high-level signals that are consumed by:
- Game widgets
- Player and spectator lists
- Message and event logs
- Status indicators (ready state, deck selection, connection state)
This keeps the handler independent of concrete UI implementations.
---
## Relationship to PlayerEventHandler
`GameEventHandler` and `PlayerEventHandler` work together but have distinct roles:
| GameEventHandler | PlayerEventHandler |
|------------------|--------------------|
| Global game state | Per-player state |
| Turn / phase flow | Zones and cards |
| Player join/leave | Player actions |
| Spectator events | Player-specific events |
| Chat dispatch | Card and zone updates |
When a server event is associated with a specific player, `GameEventHandler`
routes it to the corresponding `PlayerEventHandler`. Events without a player
context are handled directly.
---
## Event Processing Flow
A typical incoming event flow looks like this:
1. `AbstractClient` receives a `GameEventContainer`
2. `GameEventHandler::processGameEventContainer()` is called
3. `containerProcessingStarted()` is emitted
4. Each event is:
- Handled directly **or**
- Forwarded to a `PlayerEventHandler`
5. Logging and UI signals are emitted as needed
6. `containerProcessingDone()` is emitted
This structured flow makes it easy to:
- Suppress UI updates during replays
- Handle reconnections cleanly
- Detect flood protection or error states
---
## Command Lifecycle
Outgoing commands follow a similar structured path:
1. UI action triggers a `handle*()` method
2. A protobuf command is constructed
3. The command is wrapped in a `PendingCommand`
4. `sendGameCommand()` sends it to the server
5. `commandFinished()` receives the response
6. Errors or flood conditions are handled centrally
This approach avoids duplicated error handling across UI code.
---
## Design Goals
`GameEventHandler` is designed to be:
- **Centralized** – one entry point for all game-wide events
- **UI-agnostic** – communicates via signals, not widgets
- **Predictable** – well-defined event ordering and lifecycle
- **Composable** – works in concert with `PlayerEventHandler`
- **Testable** – logic is separated from rendering
---
## Related Classes
- @ref GameEventHandler
- @ref PlayerEventHandler
- `GameEventContainer`
- `PendingCommand`
- `AbstractClient`
- `AbstractGame`
---
## See Also
- @ref GameLogic
- @ref PlayerEventHandler
- @ref GameState

View file

@ -1,199 +0,0 @@
@page player_event_handler PlayerEventHandler
## Overview
`PlayerEventHandler` is responsible for applying **player-scoped game events**
to a single `Player` instance. These events modify the player’s board state,
zones, cards, counters, arrows, and associated UI and logging output.
Each `PlayerEventHandler` instance is bound **1:1 to a Player** and is invoked
exclusively by @ref GameEventHandler after basic routing and validation of
incoming server events.
This class represents the lowest-level authoritative application of game state
changes on the client.
---
## Scope and Authority
`PlayerEventHandler` operates under the following guarantees:
- All incoming events are **authoritative** and already validated by the server
- Events refer to valid players, zones, and card identifiers
- Ordering is guaranteed by the server and preserved by `GameEventHandler`
As a result, the handler does **not** perform rule validation or permission
checks. Its responsibility is to *apply* state, not *decide* legality.
---
## Responsibilities
### 1. Applying player-specific state changes
The primary responsibility of `PlayerEventHandler` is to mutate player-owned
state, including:
- Cards (`CardItem`)
- Zones (`CardZoneLogic`)
- Counters (card-level and player-level)
- Attachments and arrows
- Zone configuration flags (reveal / peek behavior)
Many handlers update both **logical state** and **visual/UI state** in tandem.
---
### 2. Coordinating complex card movement
Some events—most notably card movement—require coordinated updates across
multiple systems. For example, `eventMoveCard()`:
- Removes the card from the source zone
- Updates ownership, visibility, and identity
- Handles attachments and arrow cleanup
- Emits undo-draw or move logs
- Inserts the card into the destination zone
- Updates menus, hover state, and graphics
This makes `PlayerEventHandler` intentionally stateful and tightly coupled to
the board implementation.
---
### 3. Emitting logging signals
Rather than writing directly to logs or widgets, `PlayerEventHandler` emits
structured Qt signals describing *what happened*, including:
- Chat messages
- Card movement
- Shuffles and randomization
- Reveals and peeks
- Counter and attribute changes
These signals are consumed by logging systems and UI components, keeping
presentation concerns out of the handler.
Logging signals may be emitted **before or after mutation**, depending on
whether later changes would invalidate log data (e.g. card identity).
---
### 4. Handling cross-player interactions
Although bound to a single player, some events necessarily affect other
players’ state, including:
- Moving cards between players
- Attaching cards to another player’s permanents
- Creating arrows targeting other players or cards
In these cases, `PlayerEventHandler` performs the minimal required mutation
while respecting ownership boundaries enforced elsewhere.
---
## Event Dispatch Model
`PlayerEventHandler` exposes a single public entry point:
- `processGameEvent()`
This method:
1. Receives a generic `GameEvent`
2. Switches on `GameEventType`
3. Extracts the appropriate protobuf extension
4. Forwards the event to a typed handler
This keeps the event routing centralized and makes it easy to audit coverage
when new game events are introduced.
Unhandled events are logged as warnings.
---
## Event Categories
For clarity, event handlers are grouped conceptually into:
- **Chat and randomization**
- Chat messages
- Shuffles
- Dice rolls
- **Arrows and targeting**
- Create / delete arrows
- **Card and token creation**
- Token generation
- Counter creation
- **Card attributes and counters**
- Tapped state
- Power/toughness
- Annotations
- Card- and player-level counters
- **Zone-level operations**
- Card movement
- Zone dumps
- Card destruction
- Attachments
- **Draw and reveal**
- Drawing cards
- Reveals, peeks, and reveal windows
- **Zone configuration**
- Always-reveal and always-look-at-top-card flags
This grouping mirrors the structure of the header file and reflects the
conceptual responsibilities of the class.
---
## Relationship to GameEventHandler
`PlayerEventHandler` is never instantiated or invoked directly by UI code.
Instead:
1. `GameEventHandler` receives a `GameEventContainer`
2. Player-scoped events are routed to the appropriate `PlayerEventHandler`
3. Global or spectator events are handled elsewhere
This separation keeps game-wide logic decoupled from player board state and
makes reconnection and replay handling simpler.
---
## Design Intent
`PlayerEventHandler` is designed to be:
- **Authoritative** – applies server state exactly as received
- **Stateful** – maintains consistency across cards, zones, and UI
- **Explicit** – one handler per event type
- **UI-aware** – updates views and menus as part of state mutation
- **Auditable** – easy to trace what code handles which event
---
## Related Classes
- @ref PlayerEventHandler
- @ref GameEventHandler
- `Player`
- `CardItem`
- `CardZoneLogic`
- `GameEvent`
- `GameEventContext`
---
## See Also
- @ref GameLogicPlayers
- @ref GameLogic

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

View file

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 398 KiB

After

Width:  |  Height:  |  Size: 398 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 885 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 245 KiB

After

Width:  |  Height:  |  Size: 245 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 303 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Before After
Before After

View file

@ -0,0 +1,210 @@
@page loading_card_pictures Loading Card Pictures
Pictures associated with CardInfo%s are retrieved either from on-disk or the network through the CardPictureLoader.
In most cases, you don't need to concern yourself with the internals of CardPictureLoader.
Simply using one of the ways described in @ref displaying_cards is enough to automatically queue a request to the
CardPictureLoader when the chosen widget is shown, emitting signals to refresh the widget when the request is finished.
# How requests are triggered
CardPictureLoader::getPixmap() is called exactly two times in the code base, in CardInfoPictureWidget::loadPixmap(), the
base class
for all widget based card picture display, and AbstractCardItem::paintPicture(), the base class for all QGraphicsItem
based card picture
display. See @ref displaying_cards for more information on the difference between these two display methods.
Because both of these calls are made in the paintEvent() methods of their respective classes, this means that requests
are issued as soon as but not before the widget is shown on screen.
It is also possible to "warm up" the cache by issuing card picture load requests to the CardPictureLoader without using
a display widget and waiting for it to be shown by calling CardPictureLoader::cacheCardPixmaps() with a list of
ExactCard%s.
# The QPixmapCache and QNetworkDiskCache
Cockatrice uses the QPixmapCache from the Qt GUI module to store card pictures in-memory and the QNetworkDiskCache from
the Qt Network module to cache network requests for card pictures on-disk.
What this means is that the CardPictureLoader will first attempt to look up a card in the QPixmapCache according to the
ExactCard::getPixmapCacheKey() method of an ExactCard object. If it does not find it in the in-memory cache, it will
issue a load request, which will first look for local images on-disk and then consult the QNetworkDiskCache and if
found, use the stored binary data from the network cache to populate the in-memory pixmap cache under the card's cache
key. If it is not found, it will then proceed with issuing a network request.
The size of both of these caches can be configured by the user on the "Storage" settings page.
# PixmapCacheKeys and ProviderIDs
Every card picture that is loaded ends up in the QPixmapCache under a key that identifies the exact printing it belongs
to. The key is produced by ExactCard::getPixmapCacheKey() and has the following shape:
```text
card_<card name>_<provider ID>
```
For example, the _Example Card_ printing with provider ID `0b23cdc8-d413-4fb1-8470-474221b10fe2` is stored
under `card_Example Card_0b23cdc8-d413-4fb1-8470-474221b10fe2`. If the printing has no provider ID, the key
drops the suffix and falls back to `card_<card name>`.
The **provider ID** is the Scryfall UUID of the printing. Oracle maps the `scryfallId` of every printing to the `uuid`
property when building the card database, and deck files persist it as the `uuid` attribute of each card entry. Because
the provider ID is part of the pixmap cache key, two different printings of the same card never share a cache entry.
This is exactly what allows the printing selector and exact-card lookups to display the picture of the precise printing
a card was added as.
The base key holds the full-size image. When a widget asks for a scaled version, the scaled pixmap is stored under an
additional key of the form `<key>_<width>x<height>`, with the size adjusted for the device pixel ratio of the screen,
so each widget size is only ever scaled once.
The cache key is also used for bookkeeping outside of the pixmap cache itself:
- CardPictureLoaderWorker keeps a set of keys that are currently being loaded so the same card is never queued twice.
- CardPictureLoader tracks, per key, the last time loading failed. A failed load stores a NULL pixmap under the key; as
long as that marker is present, subsequent requests for the card show the "failed" card back and are only re-queued
after the retry interval of 300 seconds has passed.
- When the CardInfo of a loaded card is destroyed, its cache entries and failure markers are removed.
# The Redirect Cache
Many picture URLs - in particular the Gatherer and Scryfall URLs from the default set of templates - redirect to a CDN
or to a different host. To avoid following the same redirect for every single card, CardPictureLoaderWorker remembers
redirects and applies them without an extra network round trip.
The redirect cache is a hash map from original URL to redirect URL plus timestamp. It is persisted to a `cache.ini`
file (Qt's INI format, under the `redirects` array) inside the redirect cache directory
(`SettingsCache::instance().paths().getRedirectCachePath()`, i.e. `<cache directory>/redirects/`). The cache is loaded when the worker
starts, pruned of entries older than the configured TTL, and written back to disk when the application quits.
Entries are added whenever a network reply reports a redirection (see below) and are consulted before any request is
made: both CardPictureLoaderWorker::queueRequest() and CardPictureLoaderWorker::makeRequest() check for a cached
redirect first and jump straight to the final URL.
The TTL is the "Redirect Cache TTL" setting on the "Storage" settings page and defaults to 30 days. Lowering it makes
Cockatrice re-resolve redirects sooner, which can help when a download URL changed its redirect target.
Because Cockatrice tracks redirects itself, the QNetworkAccessManager is configured with Qt's `ManualRedirectPolicy`.
Redirects found in a reply are handled manually:
- A recursive redirect (a URL redirecting to itself) is treated as a failed load.
- Otherwise the redirect is recorded in the redirect cache and the request is re-issued against the target URL.
- A successful reply with one of the redirect status codes 301, 302, 303, 305, 307 or 308 is handled the same way.
Clearing the network cache (CardPictureLoader::clearNetworkCache()) also clears the redirect cache.
# Local Image Loading
Before any network request is issued, CardPictureLoaderWorker hands the ExactCard to CardPictureLoaderLocal, which
tries to find a matching picture on disk. If a local picture is found, it is used and no network request is made.
CardPictureLoaderLocal searches three locations:
- The **CUSTOM folder** (`<pictures directory>/CUSTOM/`). Every file in it is indexed recursively by its base name
(both `baseName` and `completeBaseName`, so a file named `ExampleCard.jpg` is indexed as `ExampleCard`). The index is rebuilt
every 10 seconds, so new files are picked up without restarting the
client (changing the configured pictures directory only reassigns the search paths; the next timer tick rebuilds the index).
- The **set-named subfolders** of the pictures directory: `<pictures directory>/<set code>/` and
`<pictures directory>/downloadedPics/<set code>/`.
- The **root of the `downloadedPics` folder** (`<pictures directory>/downloadedPics/`). The export naming schemes without a
set-folder part write their files straight into `downloadedPics/`, so this is where flat-scheme downloads and the
local overrides described below are matched.
For each candidate folder, the loader generates file-name variants from the card's corrected name, set code, collector
number and provider ID using the import naming schemes (Card Name + Provider ID, Card Name + Set + Collector,
Set + Collector + Card Name, Card Name + Set, Card Name), each tried with both `_` and `-` as separator. A file is
accepted when its name without the extension *equals* the variant exactly - the extension itself is free - and the
first variant that yields a readable image wins. For example, the file `Example Card_EXM_43.png` in the `EXM` set
folder matches the card with corrected name `Example Card`, set code `EXM` and collector number `43`.
\attention The file-name variants use the *corrected* card name, so split cards are stored under their joined name: the
"Example // Card" card is matched by a file named `ExampleCard.*`.
The naming schemes are also documented in the user-facing page @ref custom_card_pictures, which additionally covers
the CUSTOM folder workflow, `picurl` and download URL templates.
When the filesystem cache method is selected on the "Storage" settings page, downloaded images are additionally written
into `<pictures directory>/downloadedPics/` using the configured export naming scheme (as `.png` files). The two export
schemes with a set-folder part (`Set Folder / Name + Provider ID` and `Set Folder / Name + Set Name + Collector`) write
into `downloadedPics/<set code>/`; the three flat schemes write directly into `downloadedPics/`. Automatic cache writes
never overwrite an existing file, so a provider outage can permanently leave an outdated image in that folder until it
is deleted manually - the user-facing troubleshooting guide @ref fixing_card_pictures covers how to do this. Explicit
image overrides (see below) are the exception and always overwrite.
# Local Image Overrides
Beyond the generic on-disk lookup above, individual printings can be given explicit artwork that wins over every other
source without touching the CUSTOM folder or any download URL. This is the "Image Overrides" submenu of the context menu
that opens when you right-click a card in the deck editor's printing selector.
- **Load Custom Image...** asks for a picture file and installs it for the card through
CardPictureLoader::saveCardImageToLocalStorage() with `allowOverwrite == true`.
- **One entry per alternate printing** (labeled `<set> <collector number>`): selecting one hands the card to
CardPictureLoader::installPrintingOverride(), which resolves that printing's artwork - enqueueing a load and waiting
for the `CardInfo::pixmapUpdated` signal if it is not cached yet - and persists it for the card.
- **Clear Custom Image** calls CardPictureLoader::deleteAllLocalOverrides() to remove every stored override image of the
card, after which normal resolution resumes. The entry is only enabled while CardPictureLoader::hasLocalOverrides()
reports at least one stored file.
Overrides are stored as `.png` files in `downloadedPics/` under the export naming scheme configured on the "Storage"
settings page - which is exactly why the local matcher also looks into the `downloadedPics/` root (see above). They are
written with `allowOverwrite == true`, so an override always replaces whatever the filesystem cache previously saved for
that spelling; only *automatic* cache writes are prevented from clobbering it. Overriding a card with its own current
printing is a no-op (the UI omits it from the menu), and an override whose artwork fails to resolve surfaces the
"failed" card back instead of a silent no-op while any override already on disk is left in place and re-displayed.
# URL Generation and Resolution
When no local image is available and downloading is enabled, the network loader starts working through a list of
candidate URLs. This list is managed by CardPictureToLoad and is built in two steps.
First, CardPictureToLoad::extractSetsSorted() collects all sets the card has printings in and sorts them by set
priority. Unless the user disabled per-printing art ("Override all card art with personal set preference (Pre-ProviderID
change behavior)"), the set that
matches the requested printing's provider ID is moved to the front, so the exact printing is always attempted first.
For each set, CardPictureToLoad::populateSetUrls() builds an ordered URL list:
1. A custom URL defined for that printing via the `picurl` property in the card database, if present.
2. The configured download URL templates, in priority order (Deck Editor → "URL Download Priority").
URL templates are transformed into concrete URLs by CardPictureToLoad::transformUrl(), which substitutes reference
points. `!name!`, `!setcode!` and friends substitute card and printing data, while the `!set:<property>!` and
`!prop:<property>!` reference points resolve a property of the printing or of the card respectively. The canonical list
of all reference points with examples, including the `_fill_with_` and `_substr_` modifiers, lives in
@ref custom_card_pictures.
The `!set:...!` and `!prop:...!` reference points also support two modifiers:
- `_fill_with_<text>` pads the value with the given text, right-aligned, e.g. `!set:num_fill_with_000!` turns collector
number `1` into `001`. If the value is longer than the fill text, the template is invalidated.
- `_substr_<start>_<length>` extracts a substring, e.g. `!set:num_substr_2_2!` takes two characters starting at the
third. If the substring would extend past the end of the value, the template is invalidated.
Substituted values are percent-encoded. If a template asks for a property the card or printing does not have (or one of
the modifiers invalidates it), the template yields no URL and is skipped; the next template is tried instead.
\attention Custom URLs should start with `http://` or `https://`. The scheme is not validated before the URL is handed
to QNetworkAccessManager, so a template without an absolute scheme may silently fail to download; prefer HTTPS where the
provider allows it.
The resolution order is: for the current set, try each URL in the list; when all URLs for a set are exhausted, move to
the next set; when every set is exhausted, the load fails. A failed load is reported through the NULL-pixmap mechanism
described in the PixmapCacheKeys and ProviderIDs section above.
Several mechanisms influence the resolution process:
- **Rate limiting.** The worker allows roughly 10 requests per second globally. A server that answers with HTTP 429
gets its per-host allowance halved; the first 429 for a host is waited out (honoring the `Retry-After` header if
present) and the same URL retried, while a second 429 makes the loader fall through to the other configured sources.
When all sources are exhausted the request is deferred with some random jitter and retried once the back-off expires.
- **Redirects.** Replies with a redirect status (301, 302, 303, 305, 307, 308) are followed and recorded in the
redirect cache as described in the Redirect Cache section above.
- **Blacklisted images.** Gatherer returns the card back image for cards it does not know. A few known MD5 hashes of
that image are blacklisted, so such a "successful" download is treated as not found instead of being shown.
- **WebP.** Images detected as WebP (RIFF/WEBP header) are decoded through QMovie instead of QImageReader.
- **Downloads disabled.** When "Download card pictures on the fly" is disabled and the network cache method is active,
requests use Qt's `AlwaysCache` policy so that only previously cached images are served.
A user-facing reference for writing download URL templates, including more worked examples, is available at
@ref custom_card_pictures.

View file

@ -90,13 +90,13 @@ This keeps the handler independent of concrete UI implementations.
`GameEventHandler` and `PlayerEventHandler` work together but have distinct roles:
| GameEventHandler | PlayerEventHandler |
|------------------|--------------------|
| Global game state | Per-player state |
| Turn / phase flow | Zones and cards |
| Player join/leave | Player actions |
| Spectator events | Player-specific events |
| Chat dispatch | Card and zone updates |
| GameEventHandler | PlayerEventHandler |
| ----------------- | ---------------------- |
| Global game state | Per-player state |
| Turn / phase flow | Zones and cards |
| Player join/leave | Player actions |
| Spectator events | Player-specific events |
| Chat dispatch | Card and zone updates |
When a server event is associated with a specific player, `GameEventHandler`
routes it to the corresponding `PlayerEventHandler`. Events without a player

View file

@ -1,5 +1,9 @@
@mainpage Documentation
<div align="center">
<img src="cockatrice_logo.svg" style="width: 300px; max-width: 100%; height: auto;">
</div>
Welcome to the Cockatrice code documentation.
- @subpage user_reference

View file

@ -0,0 +1,135 @@
@page custom_card_pictures Custom Card Pictures
There are four ways to make Cockatrice use custom artwork for your cards:
- Placing image files in the **CUSTOM pictures folder**.
- Providing a **custom card database** that points each printing at a picture URL via the `picurl` property.
- Writing your **own download URL templates**.
- Setting an **image override** for a single card from inside the deck editor.
Each of these is described below. If pictures are missing or wrong, see @ref fixing_card_pictures instead.
# Custom Pictures Folder (CUSTOM)
Any image file placed in the CUSTOM folder is used as the card picture, and no download is attempted for cards that
match a file there.
- The folder is `<pictures directory>/CUSTOM/`. The pictures directory is configured on the 'General' settings tab,
under 'Directories' → 'Pictures directory'.
- Any image format Qt can decode is accepted (PNG, JPG/JPEG, WebP, GIF, BMP, ...); the file extension is not filtered,
so even an extension-less file is picked up if the decoder recognizes its content.
- Files are indexed by their name, so you can organize them into subfolders freely.
- New or changed files are picked up automatically within a few seconds — no client restart is required.
The file name must match the card using one of the naming schemes below. Both `_` and `-` are accepted as separators,
and the file extension is ignored when matching:
| Scheme | Example file name |
| --------------------------- | ------------------------------------------------------- |
| Card Name | `Example Card.png` |
| Card Name + Set | `Example Card_DDL.png` |
| Card Name + Set + Collector | `Example Card_DDL_43.png` |
| Set + Collector + Card Name | `DDL_43_Example Card.png` |
| Card Name + Provider ID | `Example Card_0b23cdc8-d413-4fb1-8470-474221b10fe2.png` |
The name used for matching is the *corrected* card name. Correction removes the split-card separator ` // ` and the
characters reserved in Windows file names (`* < > : " \ ?` and control characters), and turns `/` into a space, so the
"Example // Card" card is matched by a file named `ExampleCard.png`, not `Example // Card.png`. Most other punctuation
(commas, apostrophes, `!`, ...) is left untouched.
\attention A file in the CUSTOM folder always wins over downloaded pictures, even if it is the wrong image. Delete the
file if you want to see the downloaded artwork again.
The naming conventions are the same as those recognized in the set-named subfolders and in `downloadedPics`, and are
documented for developers in @ref loading_card_pictures.
# Custom Card Database (picurl)
If you maintain your own card database (see the
[Custom Cards & Sets](https://github.com/Cockatrice/Cockatrice/wiki/Custom-Cards-&-Sets) wiki), each printing's `<set>`
tag can carry a `picurl` attribute containing a full URL for that printing's picture:
```xml
<set picurl="https://example.com/cards/example-card.jpg" ...>
```
Cockatrice tries this URL **before** the configured download URL templates, so it is the most direct way to provide
custom artwork for a specific printing.
- The URL should start with `http://` or `https://`; the scheme is not validated, so make sure it is absolute or the
download may silently fail.
- When you change a `picurl` for a card whose picture was already downloaded and cached, delete the stored images
(Storage tab → 'Delete Saved Images' / 'Delete Cached Images') so Cockatrice fetches the new URL.
# Custom Download URL Templates
The built-in download URLs are templates: Cockatrice replaces reference points in the URL with information about the
card and its printing. You can write your own templates in 'Cockatrice → Settings' (Ctrl + Shift + P by default), on
the 'Deck Editor' tab, in the 'URL Download Priority' section.
The following reference points are available:
| Reference point | Description | Example |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `!name!` | Card name | `Example Card` |
| `!name_lower!` | Card name, lower case | `example card` |
| `!corrected_name!` | Corrected card name | `ExampleCard` (instead of "Example // Card") |
| `!corrected_name_lower!` | Corrected card name, lower case | `examplecard` |
| `!sflang!` | Scryfall language code for the current client language; defaults to English when the language has no localized images | `en`, `zhs` |
| `!setcode!` / `!setcode_lower!` | Set code | `EXM` / `exm` |
| `!setname!` / `!setname_lower!` | Full set name | `Exemplary Set` / `exemplary set` |
| `!set:<property>!` | A property of this printing, e.g. `muid` (Gatherer multiverse ID), `uuid` (Scryfall UUID), `num` (collector number), `rarity` | `373549` |
| `!prop:<property>!` | A property of the card, e.g. `side` (front/back), `colors`, `cmc`, `coloridentity`, `type`, `pt`, and the format legality statuses | `front` |
The `!set:...!` and `!prop:...!` reference points support two modifiers:
- `_fill_with_<text>` pads the value with the given text, right-aligned, e.g. `!set:num_fill_with_000!` turns collector
number `1` into `001`. If the value is longer than the fill text, the template is skipped.
- `_substr_<start>_<length>` extracts a substring, e.g. `!set:num_substr_2_2!` takes two characters starting at the
third. If the substring would extend past the end of the value, the template is skipped.
Substituted values are URL-encoded. A template that asks for a property the card or printing does not have is skipped,
and the next template in the list is tried instead.
\attention Custom URLs should start with `http://` or `https://`. As with `picurl`, the scheme is not validated before
the URL is handed to QNetworkAccessManager, so use an absolute URL or the download may silently fail.
Some working examples:
```text
https://cards.scryfall.io/large/!prop:side!/!set:uuid_substr_0_1!/!set:uuid_substr_1_1!/!set:uuid!.jpg
https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!
https://api.scryfall.com/cards/multiverse/!set:muid!?format=image
https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card
https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card
```
See the [Custom Picture Download URLs](https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs)
wiki for more examples and ideas.
\attention Keep in mind that templates using `!name!` or `!set:muid!` resolve by name or multiverse ID, not by the
exact printing. Only the Scryfall `!set:uuid!` templates always return the exact printing requested. See
@ref fixing_card_pictures for more on this.
# Image Overrides
The quickest way to give one card custom art is an image override: right-click the card in the deck editor's printing
selector and open the **Image Overrides** submenu of the context menu.
- **Load Custom Image...** — choose a picture file (the dialog suggests PNG, JPG/JPEG and WebP); it becomes that card's
artwork immediately.
- **One entry per alternate printing** of the card, labeled `<set> <collector number>` (hovering an entry previews that
printing's artwork). Selecting one makes the card use that exact printing's picture, so e.g. a basic land can be shown
with any of its artworks.
- **Clear Custom Image** — removes the stored override and returns the card to normal resolution. It is only available
while the card has a stored override.
Overrides are stored as `.png` files in `<pictures directory>/downloadedPics/`, under the "Naming scheme" configured on
the Storage settings page, and are matched the same way as downloaded images. Because local files are checked before any
URL is requested, an override always wins over downloaded artwork and `picurl` for that card. The override exists only on
the machine it was created on - it is not part of the deck file - so a card with a stored override shows normally on
another computer.
\attention If you also keep a matching file in the CUSTOM folder, that file is matched before the override. When you
change an override, use **Clear Custom Image** so the stored `.png` is replaced; manually deleting the file in
`downloadedPics/` has the same effect.

View file

@ -11,6 +11,19 @@
- @subpage beta_release
## Settings
- @subpage settings_folder
## Card Pictures
- @subpage custom_card_pictures
## Troubleshooting
- @subpage fixing_card_pictures
- @subpage enabling_debug_logs
## Syntax Help
- @subpage search_syntax_help

View file

@ -0,0 +1,43 @@
@page settings_folder Settings Folder and Backups
Cockatrice keeps all of its preferences in a settings folder on your computer, one `.ini` file per
group of settings (appearance, sound, decks, and so on). Open it from the main window via
Help → Open Settings Folder.
# The Per-File Migration
Older versions of Cockatrice stored every setting in a single `global.ini` file. The settings were
split into per-group `.ini` files so that unrelated groups no longer share one file. The first time
you start a version that uses the new layout, your settings are moved into the new files
automatically and nothing is lost. The old `global.ini` is kept next to the new files as
`global.ini.old`.
# Automatic Backups
Both migrations overwrite files in the settings folder in place, so before either one runs,
Cockatrice copies the whole folder to a timestamped backup folder:
```
<data folder>/settings-backup-20261002-193000/
```
The backup folder sits next to the settings folder, not inside it. Every backup is kept, so if
settings ever end up wrong you can compare against, or copy files back from, the earlier snapshot.
Backups are only created when a migration actually has work to do, so a normal start creates
nothing.
# Downgrading
Settings migrations only run forwards. If you ever go back to an older version of Cockatrice, that
version does not read the new per-group files, so it will start with default settings.
Before downgrading, copy your settings folder somewhere safe, for example:
- Linux: `~/.local/share/Cockatrice/settings/`
- macOS: `~/Library/Application Support/Cockatrice/settings/`
- Windows: `%LOCALAPPDATA%\Cockatrice\settings\`
Portable installs keep the settings folder in the `data` folder next to the executable.
Copying that folder back after downgrading restores your settings. The automatic backup folder from
the migration is there as a second copy of the same data if you need it.

View file

@ -0,0 +1,142 @@
@page enabling_debug_logs Enabling Debug Logs
Cockatrice ships with a "diagnostics mode" that prints detailed debug messages about what the client is doing. This is
extremely useful when asking for help, because it shows exactly what happened: which URL the card picture loader tried,
whether it found or missed a file on disk, whether a download succeeded or failed, which redirects were followed, and
much more. Each of these messages belongs to a category, and you can enable or disable categories individually.
Don't worry, this sounds more technical than it is. You only need to do two things: create one small text file, and tell
Cockatrice where it is. There are no installation steps and you can undo everything later (see [When you are
done](#when-you-are-done)).
# Step 1: Create the file
Open a plain text editor (Notepad on Windows, TextEdit on macOS, or any text editor on Linux) and paste the following
content:
```ini
[Rules]
# The default log level is info
*.debug = false
# Turn on debug level logs for the card picture loader and all its sub categories
card_picture_loader.* = true
```
Save the file with the exact name `qtlogging.ini` in a place you can find again, for example your Documents folder.
\attention The file name matters: it must be `qtlogging.ini`, not `qtlogging.ini.txt`. If your text editor adds a
`.txt` extension automatically, you need to stop it from doing so (see below). On macOS, TextEdit must be switched to
plain text mode first via 'Format → Make Plain Text'.
The file contains one rule per line. The `*.debug = false` rule turns off debug messages everywhere by default, and the
`card_picture_loader.* = true` line then re-enables them for the card picture loader. The `.*` at the end means "this
category and all of its sub categories". To enable a different category instead, just replace that line with the
category name of your choice, for example `card_database.loading = true` or `window_main.startup = true`.
# Step 2: Tell Cockatrice where the file is
Cockatrice does not know about the file yet. You have to point it there by setting an environment variable called
`QT_LOGGING_CONF` to the full location of your file. How to do this depends on your operating system:
**Windows**
1. Press the Windows key, type "environment variables", and open "Edit the system environment variables".
2. Click "Environment Variables...", then under "User variables" click "New...".
3. Set "Variable name" to `QT_LOGGING_CONF` and "Variable value" to the full path of your file, for example
`C:\Users\YourName\Documents\qtlogging.ini`.
4. Confirm all dialogs, then close and reopen Cockatrice.
Alternatively, if Cockatrice is installed in a folder you can write to, you can simply place the `qtlogging.ini` file
directly next to the Cockatrice executable (in the same folder as `cockatrice.exe`) and skip the environment variable
altogether. Note that this copy may be replaced when you update the client.
**macOS**
Open the Terminal app (it is in 'Applications → Utilities') and run the following two commands, replacing the path
with the full location of your file:
```shell
launchctl setenv QT_LOGGING_CONF /path/to/qtlogging.ini
open -a Cockatrice
```
You can also use `export QT_LOGGING_CONF=/path/to/qtlogging.ini` to set the variable for the current terminal session.
The setting stays active until you log out or restart your Mac. If you have multiple users on the same Mac, be aware
that this setting only applies to your user account.
**Linux**
For a quick test, open a terminal and start Cockatrice with the file on the command line, replacing the path with the
full location of your file:
```shell
QT_LOGGING_CONF=/path/to/qtlogging.ini cockatrice
```
If this works and you want it to apply every time you start Cockatrice, add the following line to your `~/.profile`
file and log in again:
```shell
export QT_LOGGING_CONF="/path/to/qtlogging.ini"
```
# Step 3: See the logs
Now that debug logging is enabled, open Cockatrice and trigger the behavior you are investigating, for example by
opening a deck, reloading the card database, or starting a game.
The easiest way to see the logs is to use the built-in log viewer inside Cockatrice itself: open 'Help → View Debug
Log'. A window appears that shows the log messages live and keeps the most recent entries. It even has a 'Copy to
clipboard' button so you can paste the output into a bug report or a Discord message. This works the same on every
operating system.
If you prefer to capture everything to a file instead, start Cockatrice with the `--debug-output` option:
```shell
cockatrice --debug-output
```
Cockatrice then writes the full log to a file called `qdebug.txt` in the folder it was started from.
# Which categories are available?
Every message Cockatrice logs belongs to a category. The following table lists the most useful ones for troubleshooting,
grouped by area. Enable a category by adding a line like `category = true` to your `qtlogging.ini` file (or use a `.*`
suffix, e.g. `card_picture_loader.*`, to include all sub categories).
| What you want to see | Categories |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Card picture loading (URLs, local file hits/misses, downloads, redirects) | `card_picture_loader.*` |
| Card database loading and parsing | `card_database`, `card_database.loading`, `card_database.loading.success_or_failure`, `cockatrice_xml.*` |
| Card database update (running Oracle, progress, exit status, errors) | `card_database.update` |
| Card, set, and deck information | `card_info`, `card_list`, `deck_loader` |
| Startup sequence and update checks | `window_main.startup.*`, `release_channel`, `spoiler_background_updater` |
| User interface and themes | `theme_manager`, `sound_engine`, `flow_layout`, `flow_widget.*`, `pixel_map_generator`, `card_info_picture_widget` |
| Networking and servers | `local_client`, `remote_client`, `tapped_out_interface`, `servers_settings` |
| In-game logic | `player`, `game_scene.*`, `card_zone.*`, `view_zone`, `game_event_handler` |
| Dialogs and tabs | `dlg_settings`, `dlg_update`, `dlg_tip_of_the_day`, `tab_game`, `tab_message`, `tab_supervisor` |
| Settings and shortcuts | `settings_cache`, `shortcuts_settings` |
| Deck and card filtering | `filter_string`, `deck_filter_string`, `syntax_help` |
For example, to investigate why a card database update seems to fail, enable the card database categories:
```ini
[Rules]
*.debug = false
card_database = true
card_database.loading = true
card_database.loading.success_or_failure = true
card_database.update = true
cockatrice_xml.* = true
```
# When you are done
To turn the diagnostics back off, just reverse what you did: remove the `QT_LOGGING_CONF` environment variable (or
unset it again via `launchctl unsetenv QT_LOGGING_CONF` on macOS) and/or delete the `qtlogging.ini` file, then restart
Cockatrice. Leaving it on is harmless, but the extra logging can make the client slightly slower.
For the full details on how Cockatrice logging works, including the complete list of categories, see @ref logging.

View file

@ -0,0 +1,118 @@
@page fixing_card_pictures Fixing Card Pictures
This guide collects the common causes for card pictures not showing up, showing the wrong printing, or showing the
card back instead of the artwork, and how to fix them.
Work through the steps in order. In most cases the problem is caused by an outdated client, misconfigured download
URLs, stale local images, or a corrupted cache.
# Update Your Client
Picture handling bugs are fixed on a regular basis, so the first thing to try is updating your client.
Use the update check in 'Help → Check for Updates' (or look for the update prompt shown on startup).
If a fix has not yet made it into the latest stable release, it may already be available in the beta release. The beta
ships very frequently and usually receives a follow-up fix within a day or two if something breaks.
See @subpage beta_release for instructions on how to switch to the beta channel.
# Check Your Download URLs
Card pictures are downloaded from a list of URL templates. Each template is tried in order until one produces a valid
image, so the order matters: URLs at the top of the list are tried first.
The list can be found in 'Cockatrice → Settings' (or Ctrl + Shift + P by default), on the 'Deck Editor' tab, in the
'URL Download Priority' section. Make sure 'Download card pictures on the fly' is enabled and that the list contains
valid URLs. If you suspect the list has been modified or corrupted, press 'Reset Download URLs' to restore the
defaults.
For information on how to add your own custom URL templates, see the 'How to add a custom URL' link in the same
settings section, or @ref custom_card_pictures for a full reference of the URL reference points, the CUSTOM
pictures folder, and custom card databases.
# Check Your Local Picture Folder
Before any network request is made, Cockatrice looks for local image files. If a matching file is found on disk it is
shown instead of anything downloaded, even if it is the wrong image.
The pictures directory is configured on the 'General' settings tab, under 'Directories' → 'Pictures directory'.
Cockatrice checks the following locations, in order:
- The custom pictures folder (recursively indexed by file name).
- `<pictures directory>/<set code>/<card file name>`
- `<pictures directory>/downloadedPics/<set code>/<card file name>`
- `<pictures directory>/downloadedPics/<card file name>` (for export naming schemes without a set folder)
A file only matches when its name without the extension equals one of the recognized scheme patterns exactly.
The following import naming schemes are recognized (using both `_` and `-` as separators). The canonical table with
concrete example file names is on @ref custom_card_pictures:
| Scheme | Pattern |
| --------------------------- | -------------------------- |
| Card Name + Provider ID | `{name}_{providerId}` |
| Card Name + Set + Collector | `{name}_{set}_{collector}` |
| Set + Collector + Card Name | `{set}_{collector}_{name}` |
| Card Name + Set | `{name}_{set}` |
| Card Name | `{name}` |
If a picture you downloaded or placed manually is wrong, stale, or corrupted, delete the offending file. Pay special
attention to the `downloadedPics` subfolder: this is where the filesystem caching method writes downloaded images, and
after a provider outage it can permanently contain the wrong printing until you delete it manually.
If a card persistently shows artwork you assigned yourself, you may have an **image override** set for it. Right-click
the card in the deck editor's printing selector and use 'Image Overrides' → 'Clear Custom Image' to remove it (or
delete the stored `.png` in `downloadedPics/`). See @ref custom_card_pictures for details.
See @ref loading_card_pictures for details on how local images are loaded.
# Clear Caches
Cockatrice caches card pictures in three places. All of them can be managed on the 'Storage' settings tab:
- **Network cache** — downloaded images stored on disk. Press 'Delete Cached Images' to clear it.
- **Filesystem / image backup** — downloaded images written directly to `downloadedPics`. Press 'Delete Saved Images'
to clear it.
- **In-memory (pixmap) cache** — images currently held in RAM. Press 'Clear In-Memory Images' to clear it.
If a provider outage caused the wrong pictures to be downloaded and cached, clearing the network cache (and the
'Delete Saved Images' button if you use the filesystem caching method) will force Cockatrice to download the correct
images again. The redirect cache TTL (also on the Storage tab) controls how long previously seen redirects for
download URLs are remembered; lowering it can help if a URL used to redirect somewhere else.
# Restart the Client
After updating the client, changing the download URLs, moving or deleting local image files, or clearing caches, it is
recommended to restart Cockatrice so that all changes are fully picked up.
# Check Logs
Before changing any settings, check the existing logs first. Rate limit errors and most download errors are already
logged at warn level, so you may find the cause without enabling debug mode.
Open 'Help → View Debug Log' and look for error or warning messages related to card picture loading. If you need more
detail than the default log level provides, see below.
# Enable Picture Loader Debug Logs
If the steps above did not solve the problem, you can turn on a "diagnostics mode" that prints what the picture loader
is actually doing: which URL it is trying, whether it found or missed a file on disk, whether the download succeeded or
failed, and which redirects it followed. This information is extremely useful when asking for help.
See @subpage enabling_debug_logs for a step-by-step guide on how to enable the logs, including instructions
for Windows, macOS, and Linux.
# Provider Accuracy
Cards in Cockatrice are identified by a provider ID, which is the Scryfall UUID of a specific printing. Decks store
this ID, which is why the exact printing a card was added as can be looked up again.
The Scryfall URL templates built into Cockatrice use this provider ID directly (`!set:uuid!`), so they always download
the exact printing that was requested.
The Gatherer URL templates, on the other hand, do **not** use the provider ID. They resolve pictures by multiverse ID
(`!set:muid!`) or by card name (`!name!`) only. As a result they may return a different printing than the one the
provider ID refers to, or no picture at all for cards Gatherer does not know. If you need pictures to match the exact
printing of a card, make sure the Scryfall URLs are at the top of your download URL priority list and consider removing
or demoting the Gatherer URLs.