HOME/BLOG/BEHIND THE SCENES
BEHIND THE SCENES

How We Built Devception: An Honest Build Log

May 15, 202610 MIN READBy Diwanshu Gupta

The real engineering story of a pre-launch multiplayer coding game: the naive textarea that fell apart, the operational-transform rabbit hole, and why we ended up on CRDTs with Yjs.

Start here: this is a small, honest project

Before anything else, some context so you can calibrate everything below: Devception is a pre-launch project I have been building, not a funded company with a big team or a large user base. There is no growth chart to brag about yet. What I do have is a genuinely hard engineering problem that I had to solve from scratch, and this is the honest log of how that went — including the parts where I was wrong.

The idea, and the weekend prototype

The pitch was "Among Us, but the tasks are real code." Four to eight people share one live editor fixing a broken program; one or two are secretly trying to break it. I built the first prototype over a weekend with the dumbest possible approach: a shared textarea, a WebSocket, and a "broadcast the whole document on every keystroke" sync strategy.

It was unusable the moment a second person typed. Cursors jumped. Edits clobbered each other. Two people fixing different functions overwrote one another constantly. But it was unusable in an encouraging way — when four friends played it over a call, everyone was laughing, accusing each other, and typing furiously. The fun was real. The sync was broken. That told me exactly where to spend my time.

The actual hard problem: collaborative editing

Letting several people edit one document at the same time, over a network with variable latency, and have everyone end up with the same final text, is a genuinely deep computer-science problem. It is the same one Google Docs had to solve. There are two mature families of solutions, and I tried them in the wrong order.

Detour 1: operational transformation

Operational Transformation (OT) represents edits as operations — insert, delete, retain — and transforms concurrent operations against each other so they can be applied in any order and still converge. The foundational paper is Ellis and Gibbs' 1989 work on concurrency control in groupware (Ellis & Gibbs, 1989). It is elegant on paper. In practice, OT is notoriously fiddly to implement correctly — the transform functions have to handle every pairwise combination of operations, and subtle bugs only surface with three or more simultaneous editors. I burned real time here and never fully trusted my own implementation.

The fix: CRDTs and Yjs

The other family is Conflict-free Replicated Data Types (CRDTs). Instead of transforming operations against each other, a CRDT gives every character a unique, globally ordered identifier so concurrent edits are commutative by design — apply them in any order and you converge to the same result, no central coordinator required (Shapiro et al., 2011).

I stopped rolling my own anything and adopted Yjs, a battle-tested CRDT implementation, and bound it to the Monaco editor (the editor that powers VS Code) through the y-monaco binding. This is the change that made collaborative coding actually feel good: multiple people can type on the same line and nobody loses work. The lesson I keep relearning: distributed-systems problems have decades of research behind them. Use the libraries the researchers and maintainers already hardened; do not reinvent them at 2am.

Transport: Socket.IO

For the real-time layer I used Socket.IO. It gives me bidirectional events, automatic reconnection, and — most importantly — a clean rooms abstraction. Each match is a room; game events broadcast to the room instead of to individual sockets, which collapses a lot of routing complexity. Yjs updates ride over the same connection. (The deeper architecture, including why the socket server cannot live on Vercel, is its own post.)

Designing sabotage was a balance problem, not a code problem

My first imposter abilities were too strong — they could effectively wreck the round, so imposters always won and it felt unfair. Then I overcorrected and made sabotage so weak that Good Coders always won and matches felt flat. The version that works treats the imposter as a social actor, not a system-level attacker: the best abilities create plausible-deniability situations — a subtle logic error, a misleading hint — rather than obvious destruction. This took many rounds of playtesting with friends to tune, and I am still adjusting it.

What I would do differently

  • Reach for CRDTs first. The OT detour cost me the most time for the least payoff.
  • Model the game state on paper before coding it. I refactored the match-state shape more than once because I started typing before I had thought it through.
  • Take "game feel" seriously from day one. The look and responsiveness are not polish you add at the end; they are most of why an early tester decides to play a second round.

Where it stands

Devception is in active development and pre-launch. The collaborative editor, the role and sabotage systems, and the meeting-and-vote loop all work; ranking, spectator mode, and more languages are on the roadmap. I keep the running list of changes in the devlog, and if you want to see the mechanics without rounding up seven friends, the interactive demo on the home page walks through a full match against bots in about two minutes.

References

TAGS:#engineering#crdt#yjs#socket.io#next.js#build log
👨‍💻
Diwanshu Gupta
FOUNDER & LEAD DEVELOPER

Diwanshu Gupta is the founder and lead developer of Devception. He works mostly in the messy intersection of real-time systems, game design, and developer tooling — and built Devception to make practicing code feel like a team sport instead of a solo grind. More about the team →

READY TO EXPERIENCE THIS YOURSELF?

Join a match and see how social deduction changes coding forever.