Fernando Oviedo
Projects
September 1, 2025cases.boardcork.com

Casos Criminales - Real-time detective game

Boardcork's digital product: up to 10 devices cracking the same criminal case on a shared board. Live in production, with accounts and payments.

Casos Criminales - Real-time detective game
Live view
Your team gets a case, a shared board and a full case file. You have two to three hours to solve it. Up to 10 devices work on the same board at the same time. It's Boardcork's digital product. It's live in production, with user accounts and payments, and I designed and built it end to end.
The first version worked perfectly on my machine. With two browser tabs open, flawless. With a real team of six, it didn't. Two symptoms showed up, and both were the same problem in disguise:
  1. Delays. You moved a clue on the board and the rest of the team saw it half a second later. In a co-op game against the clock, half a second feels like a broken app.
  2. Conflicts. Two players touched the same element at almost the same time and state went inconsistent across devices. Each person saw a different board. In a deduction game that isn't a UI bug - it destroys the session.
The instinctive reaction was to blame the transport and start optimising events. I got that wrong at first, and lost time to it. The real problem sat one layer down: every board event wrote to the database. That meant the critical path of a single game action was:
The database was inside the real-time loop. With six players generating events in parallel, that write became the serialisation point of the whole system: it added latency to every action and, when two writes competed for the same record, it produced exactly the state conflicts we were seeing. Optimising the socket would have fixed nothing. The transport wasn't the problem. The persistence architecture was.
I changed the persistence strategy completely.
  • Game state lives in memory while the session is active. It's the single source of truth during play, and it's authoritative: it resolves action ordering in one place, so two players touching the same thing can no longer diverge.
  • Events broadcast straight from that state, without waiting on any I/O.
  • The database write happens when the session ends, outside the game loop, where a few hundred milliseconds of latency matter to nobody.
The DB went from being a per-action bottleneck to what it should have been all along: storage for outcomes, not for live state.
  • Delays disappeared: with no I/O on the critical path, broadcasts are immediate.
  • State conflicts disappeared: with a single authoritative source of truth per session, there are no competing writers.
  • The system holds the 10 simultaneous devices per session that the product needs.
This decision carries a real cost, and it should be stated: if the process dies mid-session, that session's progress is lost. I accepted that deliberately, and the reasoning is product, not engineering. A session runs two to three hours and is played in one sitting. The odds of a crash in that window are low; the odds of a delay or a state conflict ruining the game were 100% under the previous architecture. I traded a rare, recoverable risk for a constant, fatal one. In another domain - a collaborative editor where users invest weeks - the correct decision would be the opposite: periodic snapshots or a CRDT. That's what makes it an architectural decision rather than an optimisation: it depends on the domain, and you have to be able to defend it.
  • Shared board: the whole team works on the same case in real time.
  • Full case file: clues, suspects and documents to analyse.
  • Answer review: the system reviews the team's answers before delivering the verdict.
Live in production, with user accounts and payments. Crack your first case at cases.boardcork.com.