Trappers & Traders: resurrecting a fort-defense game
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 useswall/— 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 —
useWalletgracefully 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.tshandles 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.