Brett Owers
← All Projects

Tenlach Go Backend

Archived

July 1, 2024

The most technically ambitious Tenlach attempt — a Go game server with Grafana monitoring, opcode-based messaging, rollback netcode, Docker Compose orchestration, and Tailscale for local network testing. Also the most honest confrontation with a difficult truth: some projects require collaboration, and I struggle with that at times.

Purpose

Built a Go backend for the real-time tactical combat game Tenlach. This version went deeper than any previous attempt: proper opcode-based protocol, rollback netcode implementation, Grafana dashboards for monitoring game state, Docker Compose for spinning up the server stack, and Tailscale for testing multiplayer over a local mesh network without exposing ports to the internet.

Stack

GoGrafanaDocker ComposeTailscaleWebSocketRollback NetcodeOpcodesGame Server

What I Learned

  • Opcodes (operation codes) are numeric identifiers for message types in a binary protocol. Instead of sending JSON like { "type": "move", "direction": "left" }, you send a compact binary message: byte 0 = opcode (0x01 = move), byte 1 = direction (0x00 = left). This reduces bandwidth per message from ~40 bytes (JSON) to ~2 bytes (binary). In a real-time combat game sending 30+ messages per second, the bandwidth savings are significant. The tradeoff: debugging binary protocols is harder than debugging JSON. A hex dump is less readable than a pretty-printed object.
  • Grafana is an open-source observability platform for visualizing metrics, logs, and traces. For the game server, Grafana dashboards showed: active game count, connected players, messages per second, round-trip latency per player, rollback frequency (how often the server had to correct predicted inputs), and goroutine count. Without Grafana, you are guessing about server health. With Grafana, you are reading it in real time.
  • Docker Compose orchestrates multi-container environments with a single YAML file. The Tenlach stack: a Go game server container, a Prometheus container (metrics collection), a Grafana container (metrics visualization), and optionally a database container. One command — docker-compose up — and the entire stack runs. For a solo developer, Docker Compose is the difference between "setting up the server" being a 30-minute ordeal and being a 10-second command.
  • Tailscale creates a mesh VPN that gives every device on your network a stable IP address, accessible from anywhere, without opening ports on your router or configuring firewalls. For game testing: install Tailscale on your dev machine and your test device, and they can communicate as if they are on the same LAN — even if one is on Wi-Fi and the other is on cellular. No port forwarding, no dynamic DNS, no NAT traversal headaches. For solo developers testing multiplayer, Tailscale is transformative.
  • Rollback netcode in practice (building on the Grid Commander entry): the Go server maintains an authoritative game state, receives inputs from both players, and broadcasts state updates. Each client runs a local simulation with input prediction. When the real remote input arrives, the client rolls back to the divergence frame, replays with the correct input, and fast-forwards to the present. The Go server validates moves and detects desync (when client states diverge beyond acceptable thresholds). Desync detection is the hardest part — a single floating point difference in physics can cascade into completely different game states within seconds.
  • Testing a real-time multiplayer game as a solo developer is genuinely difficult. You need two clients and a server, all running simultaneously. You need to simulate latency (intentionally delaying packets to test rollback behavior). You need to test edge cases (player disconnects mid-combat, packet loss, clock drift). Each test requires running the full stack, often across multiple devices. There is no shortcut. Headless bot clients help but cannot replicate human play patterns.

Key Insights

  • This project confronted a truth I struggle with: some projects require collaboration. A real-time multiplayer game needs at minimum two people to test properly, ideally a small team to build (game designer, netcode engineer, artist, sound designer). I am a solo developer by habit and sometimes by preference. But there is a ceiling to what one person can ship in domains that are inherently multiplayer. The solo path works for tools, utilities, and single-player experiences. It hits a wall with multiplayer games.
  • The struggle with collaboration is not about personality — it is about trust and communication overhead. Working alone means every decision is instant, every pivot is free, and every failure is private. Working with others means decisions require alignment, pivots require consensus, and failures are shared. The overhead is real. But so is the ceiling. The best multiplayer games are built by teams. Accepting that — and learning to work within a team effectively — is the growth edge I have not fully crossed.
  • The technical stack for this version was the most mature the Tenlach concept has seen: Go for server performance, opcodes for bandwidth efficiency, rollback for responsive networking, Grafana for observability, Docker for reproducibility, Tailscale for testing convenience. Each piece was individually correct. The project stalled not because of technology but because of the human limitation of being one person trying to fill five roles.
  • Tenlach's persistence across multiple years and tech stacks (Cloud Functions, Go + Nakama, Grid Commander in Flutter, this Go backend) makes it the longest-running thread in this entire blog. The concept refuses to die. It now lives as Sod Tori under Hot Potato Games. Whether it ships depends on whether I can solve the collaboration problem that has stalled every previous attempt.
#Go#game-server#Grafana#Docker#Tailscale#rollback-netcode#opcodes#real-time#multiplayer#Tenlach#Sod-Tori#solo-dev#collaboration

This post was composed through a conversation between Brett Owers and Claude Code (Anthropic). The content reflects Brett's recollection of each project and the lessons drawn from it. Some details may be approximate or omitted — the purpose is to paint an honest picture of a software engineer's development over time, not to serve as a precise historical record.