2026-05-24devlogTrappers & Tradersby Roman Sanine

Trappers & Traders: resurrecting a fort-defense game

game-devarchitecturedevlog

Trappers & Traders is a fort-defense survival game that's been sitting mostly dormant. Today I cracked it open, took stock of the codebase, and started modernizing the frontend.

What the game is

Players spawn into a tile-based world, gather resources, build fortifications, set traps, and fight. It's real-time multiplayer over WebSocket with a lobby system, chat, and an entry-fee mechanic (originally designed for on-chain wagering, now optional). Think top-down survival meets tower defense — you build walls and traps to defend your position, then trade or fight for resources.

The game state is server-authoritative: clients send action packets (cBuild, cReady, cChat), the backend validates and broadcasts updates. No client-side prediction — the server is the single source of truth.

The rendering stack

The renderer is a hybrid. The 2D game world uses PixiJS v5 with pixi-layers for z-ordering — sprite sheets for characters, items, terrain, and effects. There's a separate Three.js layer for 3D wall rendering via MapRenderer3D and WallRenderer, using cabinet projection (oblique shear) to give walls height without a full 3D camera.

Tile builders in TileBuilders3D.ts map spec names like wall/stone and wal/brick to procedural box geometries — thin slabs with rotation variants. The wall tool (V key) lets you preview placement client-side before the cBuild packet commits it to the server.

The game engine weighs in at ~4,500 lines of TypeScript across 19 modules:

Module Lines Role
Game.ts 901 Core game loop, connection, rendering
Player.ts 456 Character state, movement, HP bars
Effect.ts 391 Particle and visual effects
PacketHandler.ts 334 Server message dispatch
Item.ts 325 Inventory, item properties
TileBuilders3D.ts 254 Wall/object 3D mesh builders
Tile.ts 249 Tile state, sprite management
Map/MapLayer 346 World grid, layer management

What happened today

Pug → HTML migration. Every Vue template in the project was written in Pug — 19 files total, including two 500+ line game views. Migrated them all to plain HTML templates. Pug adds a build dependency and cognitive overhead for anyone picking up the codebase; HTML is universal.

Graceful backend degradation. The game page was completely blank when the backend wasn't running — getServerInfo() threw on the 500 response and the component never finished mounting. Added error state tracking: the lobby now shows a clear "Game server is offline" warning with automatic retry, instead of an infinite loading spinner.

Registry logo. Created an SVG logo for the service registry — amber bear-trap jaws with cyan trade arrows, matching the 128×128 rounded-rect format used across all projects.

Architecture observations

The codebase has some interesting patterns worth noting:

  • Dual prefix system for walls: server uses wal/ prefix, client uses wall/ — both resolve to the same builder. This looks like a naming migration that was never completed.
  • Optional blockchain integration: wallet connection, entry fees via smart contracts, and token-gated features are all behind feature flags. The game works fine without any web3 — useWallet gracefully degrades to guest sessions.
  • Binary tile protocol: map updates use a custom binary codec (PacketCodec, TileProtocol) rather than JSON — efficient for the high-frequency tile updates a large map requires.
  • Keyboard-driven gameplay: movement is WASD, building is drag-from-inventory, wall tool is V key. The input system in Input.ts handles keyboard state, mouse targeting, and gesture recognition for drag-to-build.

What's next

The game needs its backend services (Express + MongoDB) brought back online reliably, the tile editor (/game/tiles) verified, and the 3D wall rendering tested in a live session. The frontend is now clean and modern — the game logic underneath is solid.

Comments
No comments yet.
Comments are reviewed before appearing.