SÉANCE
a haunted parlour game for one house, sixteen phones and three real rooms — this page is also the git repo.
Clone
This site serves the repository over git's dumb-HTTP protocol as plain static files (it's hosted on IPFS — there is no git server here, or any server at all):
git clone https://git-game4.ardegazu.ro/seance.git
cd seance
What it is
A party game you play with your feet. Gather 1–16 people in a home, agree which real room is the Study, the Parlour and the Cellar, and open one shared link on every phone. Each time the bell tolls you have ~20 seconds to physically walk to a room and tap it. A ghost sweeps one room — and it favours wherever the crowd stood last bell. Everyone caught loses a candle; lose all three and you fade. Last lit candle wins the séance.
Every bell, one player is secretly the medium: their phone alone names a room the spirit will shun — truthfully. They can share it, keep it, or lie about it out loud. Where you actually stand is the honor system; the walking is the point, not the enforcement. Installable PWA, works from an iOS home screen.
How it works
- No game server. Browsers are
js-libp2p peers: they dial a shared
circuit-relay-v2 peer over WSS, discover each other through gossipsub,
and upgrade to direct WebRTC — the circuit is signaling plus a
rate-limited fallback path. The room secret lives in the URL fragment
(
#…) and never leaves the browser. - Rooms are cryptographic. Discovery is app-wide, so a
room is enforced by a room-scoped protocol id
(
/seance/2/<HKDF(secret)>— wrong rooms fail protocol negotiation) plus a mutual sealed hello: one AES-256-GCM envelope keyed from the room secret and bound to both noise-authenticated peer ids. After the hello, every pair's frames ride libp2p's noise encryption, end-to-end even when routed through the relay. The p2p layer (client/src/net/) is copied from neon-grid. - One peer conducts. The lowest peer id runs the bell timers and the ghost's dice, collects secret room picks, and broadcasts tiny phase frames; everyone renders from them. Séances are seat-indexed so guests joining or leaving can't corrupt one in flight. If the conductor's phone dies the séance is voided and the next-lowest id takes over; late joiners get a snapshot and watch from the veil until the next séance.
- Hosting without an origin server. The client is a static Vite build pinned to IPFS, served through an HTTPS tunnel that is deliberately never connected — its "offline page" is the game. This page you're reading works the same way.
What's in the repo
client/src/game/ — the game
TypeScript, no framework: typed wire protocol, host-authoritative séance logic (bells, crowd-hungry ghost, medium hints, candle elimination), WebAudio bell tolls, candlelit DOM/CSS scenes.
client/src/net/ — the p2p layer
js-libp2p assembly copied from neon-grid: relay bootstrap (identity
fetched from /.well-known/ap2p with a compiled-in fallback),
room crypto, the sealed-hello membership gate, and one long-lived
varint-framed stream per pair. Only the protocol id is game-specific.
deploy/ — the publish pipeline
publish-repo.sh builds this very site: landing page + bare
mirror exploded to loose objects (IPFS gateways and git's dumb-HTTP protocol
agree on "every path must exist"), with an anonymity gate that refuses to
publish identity-bearing bytes.
Run it yourself
cd client && npm install && npm run dev # :4173
# open two tabs on the same #room URL and haunt yourself (keys 1/2/3 pick rooms)
Deploy: npm run build, then publish client/dist/ as
static files anywhere — IPFS, a CDN, a folder behind nginx. It needs nothing but
a signaling relay it's allowed to talk to (see src/game/config.ts).
Guarantees & limits, honestly
- The relay operator sees connection metadata, peer ids, and (until the WebRTC upgrade lands) opaque noise ciphertext and its sizes/timing. Not content, not names, not who the ghost took.
- Anyone with the room link can join and play — capability model. The 16-guest cap is enforced by the conductor (the mesh is O(n²); ~12 is the practical ceiling).
- The conductor is one of the players; a cheating conductor could load the ghost's dice. Between friends that's the same trust as letting someone shuffle the deck. Other players' picks can't be forged.
- Your tapped room is taken on faith — the game can't see your feet. That's a feature: the sprint to the cellar is the game.
- MIT licensed. Built on js-libp2p, WebRTC, WebCrypto, and IPFS.