game3 / README.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# tessera

A quiet p2p mosaic race for up to 16 pilgrims, in soft Monument-Valley pastels —
live at **[game3.ardegazu.ro](https://game3.ardegazu.ro)**, repo published at
**[git.ardegazu.ro/game3](https://git.ardegazu.ro/game3)**.

Your pilgrim glides across a floating terrace, painting each tile it crosses.
Step on a white bloom to splash a whole patch in your colour. When the sun sets
(45 s) the largest mosaic takes the round; first to 3 rounds keeps the terrace.
The edges fold back on themselves — walk off one side, appear on the other.
Installable PWA (works saved to the iOS home screen), safe-area aware,
swipe + keyboard.

## Architecture

- **No game server.** Browsers are [js-libp2p](https://libp2p.io) peers: they
  dial a circuit-relay-v2 peer on `signal.ardegazu.ro` over WSS, discover each
  other through gossipsub pubsub-peer-discovery, and upgrade to direct WebRTC
  (the circuit stays as signaling + a rate-limited fallback path). The net
  layer under [`client/src/game3/net/`](client/src/game3/net/) is vendored from
  [neon-grid](https://git.ardegazu.ro/game1/) and is game-agnostic except for
  the protocol id; the game only adds its own wire protocol and logic
  in [`client/src/game3/game/`](client/src/game3/game/).
- **Rooms without a room server.** Discovery is app-wide; membership is
  enforced by a room-scoped protocol id
  (`/tessera/2/<HKDF(secret) prefix>` — wrong rooms fail protocol negotiation)
  plus a mutual sealed hello: one AES-256-GCM envelope keyed from the room
  secret, bound to both noise-authenticated peer ids. Undecryptable ⇒ not a
  member. After that, libp2p's noise layer end-to-end encrypts every pair —
  even across the relay.
- **One peer referees.** The host runs the authoritative 8 Hz terrace
  simulation ([`game/game.cljs`](client/src/game3/game/game.cljs)) and broadcasts compact
  per-tick position deltas; clients paint and interpolate from them. Host loss
  voids the round and a survivor takes over; conflicting host claims resolve
  by "an active round outranks an idle claim, ties break to the lowest id".
  Late joiners get an RLE grid snapshot and spectate until the next round.
- **Room = URL fragment.** The `#secret` in the invite link is the room key;
  it never leaves the browser — peers prove knowledge of it in the hello.
- **Hosting without an origin server.** `client/dist/` is pinned to IPFS,
  named by IPNS and served straight from the gateway on the app's own domain
  (DNSLink). The published repo works the same way (`deploy/publish-repo.sh`
  + the shared `git.ardegazu.ro` source root).

## Develop

Needs node ≥ 22, a JVM ≥ 17 and the `clojure` CLI (shadow-cljs).

```bash
cd client && npm install && npm run dev   # watch build → http://localhost:5173
```

Open two tabs on the same `#room` URL to play against yourself. While the
shadow server runs, port 4173 serves the last release build from
`client/dist`. `npm test` compiles the pure game/net namespaces and runs the
golden vectors (extracted from the TS implementation while it was canon).

## Deploy

```bash
cd client && npm run build     # → client/dist
# then: ird ipfs add client/dist → ird ipfs ipns publish game3.ardegazu.ro <cid>

deploy/publish-repo.sh         # → deploy/.site (landing page + clonable bare mirror)
# then: ../git/assemble.sh game3   (updates git.ardegazu.ro)
```

MIT — see [LICENSE](LICENSE).

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/game3.git