Files
Eric WagonerandClaude Opus 5.5 4b7e05713d The host can give a stuck player's seat to the clockwork and make a phrase that brings a seat back, and a socket in a background tab no longer drops every minute
A "seats" button beside "transfer seat" opens the host's panel. In a game
under way, "give to the clockwork" lets an automaton play a person's seat
from the next beat, with their hand, treasures and place untouched; a
standIn line on the ledger records it, the chronicle says so, the
stand-in never talks under the person's name, and the person's token
opens nothing after. "Make a phrase" mints the four-word phrase for any
person's seat; claiming it re-seats on a new token, and a seat the
clockwork stood in for goes back to its person mid-turn. A rematch holds
a stood-in seat for its person. The gallery now has its own claim box,
for a player who opens the invite link in a fresh browser.

The client's watchdog pinged and then closed a socket quiet for 45
seconds. A background tab's timers run about once a minute, so the last
pong was always a minute old when it looked, and idle tabs reconnected
every 58 seconds. It now closes only when its own ping went unanswered.
The server pings every socket at the protocol level every 30 seconds and
drops one that left the last ping unanswered, so a crashed browser's
socket no longer lingers until TCP gives up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
2026-10-16 15:43:24 -04:00
..

Deploying Wiz-War

The game runs on a single DigitalOcean droplet: one Node process serves the built client and the websocket on port 8787; Caddy in front terminates TLS with automatic certificates. Room files persist in /var/lib/wizwar/rooms and survive deploys and reboots.

Current production

  • Host: hall (nyc3, tag kestrels-hall), IP 159.203.115.252, shared with the other games of Kestrel's Hall since 2026-09-29; Wiz-War keeps its own service user, port 8787, site file and access log there.
  • URLs: https://wizwar.kestrelsnest.social and https://wizwar.159.203.115.252.sslip.io (always works, zero DNS). Caddy serves both; NOTE: kestrelsnest.social's authoritative DNS is at HOVER (ns1/ns2.hover.com), not DigitalOcean — records must be added there. A matching record also sits in the DO zone in case nameservers ever move.

Everyday deploys

deploy/deploy.sh 159.203.115.252

Builds the client locally, rsyncs the repo (minus node_modules, data/, .git, research), installs dependencies on the droplet, and restarts the service. Games in progress survive: state lives in room files, and clients reconnect automatically.

Before any deploy that touches the engine, run the determinism gate:

deploy/verify-ledgers.sh 159.203.115.252

It fetches every production ledger and strictly replays it against the local engine; a single refused command fails the check. A room whose ledger no longer replays becomes unreachable after restart, so this is not optional.

New droplet from scratch

  1. doctl compute droplet create wizwar --region nyc3 --size s-1vcpu-1gb \ --image ubuntu-24-04-x64 --ssh-keys <your-key-ids> --tag-name wizwar --wait
  2. scp deploy/setup-droplet.sh deploy/Caddyfile.tmpl root@<ip>:/root/ && ssh root@<ip> \ "bash /root/setup-droplet.sh 'wizwar.<ip>.sslip.io'" (the host's Caddyfile gathers each game's site from /etc/caddy/sites/, so the host can be shared)
  3. deploy/deploy.sh <ip>

Operations

  • Logs: ssh root@<ip> journalctl -u wizwar -f
  • Restart: ssh root@<ip> systemctl restart wizwar
  • Nightly backups: wizwar-backup.sh runs from root's crontab at 07:17 UTC, pushing /var/lib/wizwar to the kestrel-wizwar-backups Space (nyc3) via rclone. It is installed at /usr/local/bin/wizwar-backup.sh on the droplet; setup-droplet.sh does NOT install it — on a fresh droplet, copy the script, configure rclone (the script refuses to run while the config still holds its CHANGE_ME placeholder), and add the cron entry by hand.
  • A player whose browser lost their seat (Safari's seven days, an app's own browser, cleared data) asks the host, whose "seats" button makes a four-word phrase that re-seats them on a new token. The same panel lets the host give a seat to the clockwork in a game under way, for a player who cannot take their turns; a standIn line on the ledger records it, and a later reseat gives the seat back. Failing the host, they write to the keeper. On the droplet, wizwar-reseat.sh ROOM lists who sits at the table and when each last moved or spoke; wizwar-reseat.sh ROOM NAME gives that wizard's seat a new token and prints a link carrying it in the fragment. The old token opens nothing after. The script answers through the game server's keeper door, a listener of its own on 127.0.0.1:9787 that Caddy never reaches, which opens only to the key in /etc/wizwar/keeper.key (root and the wizwar user alone can read it, so the other services on the host cannot use the door). The first deploy makes the key, and every deploy installs the script at /usr/local/bin.
  • One-off backup: scp -r root@<ip>:/var/lib/wizwar/rooms ./rooms-backup