* wip(hermesworld): viral sprint checkpoint - landing rebuild + character pipeline scaffold - standalone /hermes-world and /world routes bypass workspace shell - root overlay leaks gated for landing + game surfaces - character pipeline scaffolding (player/npc/glb-body components) - canonical asset path public/assets/hermesworld/characters/ - docs: landing-page-spec, graphics-usability-plan, agora-believable-checklist, master-roadmap - handoff at memory/goals/2026-05-05-hermesworld-viral-sprint/handoff.md Local-only checkpoint. Not for upstream yet. * feat(playground): persistent admin mode toggle with shield button - Admin mode now persists via localStorage (key: hermes-playground-admin) - Shield icon button in HUD (right rail, below focus toggle, md+) - Click toggles admin panel and saves preference - ?admin=1 URL param still works as override - gitignore swarm worker scratch dirs Mission: memory/swarm/missions/2026-05-05-pr-triage.md (5 swarm lanes dispatched on 19 open PRs, no-merge contract) * feat(landing): add Play Now CTAs to HermesWorld landing - Hero: Play Now (primary, gold), View on GitHub (demoted), Read Roadmap - Header nav: Play badge (highlighted gold) - Final CTA: Play Now (primary), GitHub + Roadmap (secondary) All Play buttons go to /playground which mounts the title screen (username + character customizer + Enter). Sets up the public-URL deploy: hermes-world.ai → / serves landing → click Play → /playground. * fix(tasks): use shared kanban backend --------- Co-authored-by: Aurora release bot <release@outsourc-e.com>
Hermes Playground multiplayer hub (Cloudflare Worker)
Drop-in port of scripts/playground-ws.mjs to Cloudflare Workers + Durable Objects.
Free tier covers a hackathon and well beyond. Zero cold starts. Edge-deployed.
Why CF + DO over Fly.io / Render / Railway
- Free tier: 100k req/day on Workers, 1M+ DO req/mo. WebSocket connections count per-message, not per-connection — a presence broadcast every 200 ms across 20 players is well under the limit.
- Zero cold starts (vs Fly.io free tier which idles VMs after inactivity, and Render free which has a 30-50s cold start that kills demos).
- One Durable Object instance is the canonical "lobby/room" pattern — strong consistency, no Redis needed.
- Globally edge-deployed: a player in Tokyo and a player in NYC both connect to the closest edge, then route to the single DO holding game state.
Files
src/worker.ts— entry +PlaygroundHubDurable Object classwrangler.toml— DO binding + migrationpackage.json/tsconfig.json— build deps
Endpoints
GET /playground— WebSocket upgrade (presence + chat fan-out, mirrors the Node sidecar protocol)GET /stats— JSON{ online, byWorld, peakToday, peakDay, ts }for the HUD badgeGET /health— JSON{ ok: true, online, ts }
Deploy (~10 min, requires Eric's CF account)
cd playground-ws-worker
pnpm install # installs wrangler
pnpm wrangler login # one-time browser auth
pnpm deploy # publishes to <name>.<your-subdomain>.workers.dev
Then in workspace .env.production:
VITE_PLAYGROUND_WS_URL=wss://hermes-playground-ws.<your-subdomain>.workers.dev/playground
VITE_PLAYGROUND_STATS_URL=https://hermes-playground-ws.<your-subdomain>.workers.dev/stats
(Custom domain optional via Workers Routes — wss://hub.hermes-playground.app.)
Local dev
pnpm wrangler dev # hot-reload on http://localhost:8787
# In hermes-workspace:
VITE_PLAYGROUND_WS_URL=ws://localhost:8787/playground pnpm dev
Protocol parity
Wire format is identical to scripts/playground-ws.mjs. The client
(src/screens/playground/hooks/use-playground-multiplayer.ts) connects unchanged.
Messages:
{ kind: 'presence', id, x, y, z, yaw, name, color, worldId, avatar?, ... }{ kind: 'chat', id, text, ts }{ kind: 'leave', id }- Server-emitted:
{ kind: 'hello', server, ts }
State model
presence: Map<id, lastWire>— fan-out + bootstrap on connectchatRing: array<chat>— last 50 messages, replayed to newcomerspeakToday— persisted in DO storage for stats endpoint
Stale presence is pruned every 1 s via DO alarms (cheaper than setInterval,
and survives instance hibernation).
Cost ceiling
For 100 concurrent players sending presence at 5 Hz:
- 100 × 5 × 60 × 60 × 24 = 43.2M msgs/day → still inside free tier for outbound
(Workers count requests, not WS messages). If usage explodes:
- Workers Paid: $5/mo for 10M req/day baseline.
- DO storage: trivial (presence in memory, peak in storage).
Hardening (post-hackathon)
- Add rate limiting per playerId (token bucket in DO state).
- Multi-room: route by
?room=toidFromName(roomId). - Anti-spoof: require signed JWT for
presence.id. - Replace presence ring with
state.storage.transactionfor crash recovery.