Back to Articles

Inside Cockatrice: Building a Cheat-Proof Virtual Tabletop with Qt and Server-Authoritative Design

[ View on GitHub ]

Inside Cockatrice: Building a Cheat-Proof Virtual Tabletop with Qt and Server-Authoritative Design

Hook

Most virtual tabletops trust the client. Cockatrice doesn't—and that architectural decision shapes everything from its network protocol to why 1,700+ stars worth of Magic players choose it over commercial alternatives.

Context

Before dedicated virtual tabletops, playing Magic: The Gathering online meant either paying for Magic Online's expensive digital card economy or trusting your opponents not to peek at your deck in honor-system webcam sessions. The problem wasn't just convenience—it was trust. When you're playing a competitive card game where hidden information and randomization determine outcomes, client-side game state becomes a vulnerability. Someone could inspect memory, modify deck lists mid-game, or manipulate shuffling algorithms.

Cockatrice emerged from this trust gap. Unlike rules-enforcement engines that try to replicate Magic's complex rules digitally, Cockatrice took a different approach: provide the virtual table and cards, let players handle the rules themselves, but make cheating structurally impossible through server-authoritative architecture. It's the digital equivalent of sitting across from someone with physical cards—you can see their board state, but you can't see their hand or peek at the next card they'll draw because the server controls that information flow.

Technical Insight

Client Mode

Action Request

protobuf commands

Validate Action

State Change

Full Update

card revealed

Partial Update

card hidden

Card Data

on startup

Card Data

on startup

Offline Play

Cockatrice Client

Qt GUI

Servatrice Server

Game Authority

Game State

Canonical Truth

MTGJSON

Card Database

Other Players

Clients

Local Game

Single Player

System architecture — auto-generated

The architectural heart of Cockatrice is its client-server split built on Qt's networking stack. The server (Servatrice) acts as a trusted authority that maintains canonical game state, while clients (Cockatrice) submit actions and render the results. This isn't just a design choice—it's a security model.

The Qt-based network protocol uses a protobuf-inspired command pattern where clients send action requests that the server validates before broadcasting state changes to other players. When you draw a card, your client doesn't just add it to your hand—it sends a draw request to the server, which pulls from your server-side deck, notes what you drew (without revealing it to opponents), and tells your client what card to display. Other clients receive a message that you drew a card, but not which one.

Here's a simplified example of how Cockatrice handles zone transfers (like drawing or discarding cards) in its protocol layer:

// Client sends a move card command
Command_MoveCard cmd;
cmd.set_start_player_id(playerId);
cmd.set_start_zone(ServerInfo_Zone::ZONE_LIBRARY);
cmd.set_target_zone(ServerInfo_Zone::ZONE_HAND);
cmd.set_x(-1); // -1 means draw from top

// Server validates and processes
void Server_Game::processCommandMoveCard(Command_MoveCard &cmd) {
    ServerInfo_Player *player = getPlayer(cmd.start_player_id());
    ServerInfo_Zone *startZone = player->getZone(cmd.start_zone());
    ServerInfo_Zone *targetZone = player->getZone(cmd.target_zone());
    
    ServerInfo_Card *card = startZone->removeCard(cmd.x());
    targetZone->addCard(card);
    
    // Broadcast different events based on visibility
    if (targetZone->getType() == ServerInfo_Zone::ZONE_HAND) {
        // Send full card info only to owner
        sendToPlayer(player, createCardRevealEvent(card));
        // Send hidden event to opponents
        broadcastExcept(player, createCardMovedEvent());
    }
}

This pattern repeats throughout the codebase: clients propose actions, the server adjudicates. The Qt signal-slot mechanism makes this particularly elegant—network events arrive as signals that trigger validation slots, which emit result signals back through the socket.

The cross-platform GUI layer leverages Qt's widget system extensively. Each game zone (hand, battlefield, graveyard) is a custom QGraphicsView with drag-and-drop support. Card images are lazy-loaded from local cache or remote sources, with Qt's network access manager handling async downloads. The architecture supports both raster and OpenGL rendering backends, which matters when you're displaying dozens of high-resolution card images simultaneously.

One clever optimization: Cockatrice separates card data (names, types, rules text) from card images. The card database loads from MTGJSON files—essentially JSON manifests of every Magic card ever printed—while images download on-demand. This keeps the application size manageable and lets users choose image quality levels:

// Card database loading from MTGJSON
CardDatabase::loadCardDatabase(QString path) {
    QFile file(path);
    QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
    
    for (auto cardSet : doc.object()) {
        for (auto card : cardSet.toObject()["cards"].toArray()) {
            CardInfo *info = new CardInfo(
                card["name"].toString(),
                card["manaCost"].toString(),
                card["type"].toString(),
                card["text"].toString()
            );
            cardHash.insert(info->getName(), info);
        }
    }
}

This separation also enables spoiler season support—the separate Cockatrice-Spoilers repository lets community members add unreleased cards with text-only representations before official images exist.

The offline mode demonstrates another architectural strength. The same game logic that runs in multiplayer sessions works in single-player mode by instantiating a local "server" that runs in the client process. No network round-trips, same validation logic, same UI. Qt's abstraction layers make this transparent—whether you're connected to a remote server or a local instance, the protocol layer doesn't care.

Gotcha

The server-authoritative design that prevents cheating also introduces latency sensitivity. Every action requires a round-trip to the server, which means network hiccups directly impact gameplay. Playing on high-latency connections feels sluggish compared to fully client-side implementations because you can't tap a land until the server confirms your action. There's no optimistic updating or prediction—what you see is what the server says, period. This makes Cockatrice less suitable for timed tournament scenarios where every second counts.

The card data dependency creates friction. New users expect to install an application and play. Instead, they install Cockatrice, then download card data from MTGJSON, then configure image download sources, then wait while thousands of card images populate their cache. The project intentionally avoids bundling this data (partly for legal reasons, partly to stay lightweight), but it means the out-of-box experience requires following multi-step setup guides. Compare this to Tabletop Simulator where you subscribe to a workshop mod and immediately have everything, or XMage which bundles a complete card database. The modularity is architecturally clean but user-hostile for newcomers.

Server administration is also entirely community-driven with no official infrastructure. The Cockatrice maintainers develop the software but don't run public servers. This decentralized approach means server quality varies wildly—some community servers have great moderation and uptime, others disappear without warning. If you want guaranteed availability, you're running your own server, which adds operational overhead most players don't want.

Verdict

Use if: You're organizing regular Magic games with a consistent playgroup and want the freedom to test decks without buying digital cards, you value open-source software over polished commercial products, you need cross-platform support (especially Linux where alternatives are scarce), or you're running a game store/community that wants to host their own infrastructure. Cockatrice excels when you have a trusted group that can handle rules enforcement themselves and appreciates the flexibility to play any format including fan-made variants. Skip if: You need built-in rules enforcement for learning or solo play against AI, you want matchmaking with random opponents (the trust model breaks down with strangers), you're looking for something that works immediately after installation without configuration, or you need mobile support (it's desktop-only). Commercial alternatives like Arena offer better onboarding and automated gameplay at the cost of being locked into Wizards' ecosystem. For casual groups willing to invest setup time, Cockatrice remains the most flexible virtual tabletop in the Magic community—just understand you're trading polish for freedom.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/cybersecurity/cockatrice-cockatrice.svg)](https://starlog.is/api/badge-click/cybersecurity/cockatrice-cockatrice)