Architecture

How HQ World's P2P stack, room system, and media layer fit together.

System Overview

┌─────────────────────────────────────────────────────┐ │ Tauri v2 Shell (Rust) │ │ ┌───────────────────────────────────────────────┐ │ │ │ React 19 + Tailwind v4 │ │ │ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌────────────┐ │ │ │ │ │ App.tsx │ │ RoomView │ │ Directory │ │ │ │ │ │ (lobby) │ │ (in-room)│ │ (browse) │ │ │ │ │ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │ │ │ │ │ │ │ │ │ │ │ ┌────┴──────────────┴──────────────┴──────┐ │ │ │ │ │ Room Layer (lib/room/) │ │ │ │ │ │ manager · knock · presence · roles │ │ │ │ │ │ moderation · types │ │ │ │ │ └──────────────────┬──────────────────────┘ │ │ │ │ │ │ │ │ │ ┌──────────────────┴──────────────────────┐ │ │ │ │ │ P2P Layer (lib/p2p/) │ │ │ │ │ │ node · discovery · media · stream │ │ │ │ │ │ file-transfer · sfu · bandwidth │ │ │ │ │ │ quality · config │ │ │ │ │ └──────────────────┬──────────────────────┘ │ │ │ │ │ │ │ │ │ ┌──────────────────┴──────────────────────┐ │ │ │ │ │ libp2p │ │ │ │ │ │ GossipSub · WebRTC · WebSocket │ │ │ │ │ │ Circuit Relay v2 · Kademlia DHT │ │ │ │ │ │ Noise · Yamux · Identify · Ping │ │ │ │ │ └─────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────┐ │ │ │ │ │ Profile Layer (lib/profile/) │ │ │ │ │ │ storage (IndexedDB) · sync · types │ │ │ │ │ └──────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ Tauri Plugins: fs · dialog · notification · opener │ └─────────────────────────────────────────────────────┘

P2P Protocol Stack

Transport WebSocket (browser ↔ relay) + WebRTC (browser ↔ browser)
NAT Traversal Circuit Relay v2 — relay reserves a slot, peers signal through it
Encryption Noise protocol (libp2p streams) + DTLS (WebRTC media)
Multiplexing Yamux — multiple streams over a single connection
Pub/Sub GossipSub — room messaging, signaling, file transfer, presence
Peer Routing Kademlia DHT (client mode, browser-safe)
Discovery PubSub Peer Discovery — peers find each other via shared GossipSub topics
Negotiation Identify protocol — protocol version, listen addrs, observed addr

Connection Flow

Peer A Relay Peer B │ │ │ │── dial(ws://relay) ────►│ │ │◄── relay reservation ───│ │ │ │◄── dial(ws://relay) ────│ │ │── relay reservation ───►│ │ │ │ │── subscribe(room-topic) ────────────────────────►│ │◄──────────────────────────── subscribe(room-topic)│ │ │ │ │── GossipSub: webrtc-offer ──────────────────────►│ │◄────────────────────────── GossipSub: webrtc-answer│ │── GossipSub: ice-candidate ─────────────────────►│ │◄───────────────────────── GossipSub: ice-candidate│ │ │ │ │◄═══════ direct WebRTC (audio/video/data) ═══════►│ │ │ │

Signaling via GossipSub

WebRTC signaling (offer/answer/ICE candidates) rides on the same GossipSub room topic used for chat and file transfer. No separate signaling server needed. The relay only facilitates initial peer discovery — once a direct WebRTC connection is established, media flows peer-to-peer.

Deterministic Offer Initiator

To prevent "glare" (both peers sending offers simultaneously), the peer with the lexicographically larger PeerId always sends the offer. The other peer waits for it.

Room Architecture

Room as GossipSub Topic

Each room maps to a GossipSub topic: /hq-world/room/1.0.0/{roomId}. Subscribing to the topic is equivalent to joining the room. All room messages (signaling, state, moderation, files) are published to this topic.

Message Types

The room layer defines 22 message types routed by manager.ts:

CategoryMessages
WebRTC Signalingwebrtc-offer, webrtc-answer, webrtc-ice
Media Statemedia-state (muted, video-off, audio-only)
Identityuser-info (display name, avatar color)
Knock Protocolknock-request, knock-accepted, knock-declined
Moderationforce-mute, boot-peer
Rolespromote-cohost, demote-cohost
Presenceroom-announce, room-close, presence-update
File Transferfile-offer, file-accept, file-decline, file-chunk, file-complete, file-cancel

Role Hierarchy

Host (room creator, immutable) │ ├── can promote/demote Co-hosts ├── can force-mute anyone ├── can boot anyone │ └── Co-host (promoted by host) │ ├── can force-mute participants ├── can boot participants ├── CANNOT modify other co-hosts ├── CANNOT modify the host │ └── Participant (default) │ └── no moderation powers

Media Architecture

WebRTC Mesh

Every peer maintains a direct RTCPeerConnection to every other peer (full mesh). This works well for small rooms (2–8 participants). The stream.ts module manages the connection lifecycle.

Adaptive Quality (SFU)

The host runs a hybrid SFU that monitors all peer connections and adjusts video quality based on bandwidth:

Upload BandwidthAction
≥ 4000 kbpsComfortable — full quality (640×480 @ 24fps)
≥ 2000 kbpsConstrained — downgrade to medium (480×360 @ 20fps)
≥ 1000 kbpsStressed — downgrade to low (320×240 @ 15fps)
< 500 kbpsCritical — force audio-only for poor connections

Quality checks run every 5 seconds with a 10-second per-peer cooldown to prevent thrashing.

Connection Quality Score

Each peer connection gets a composite quality score (0–100):

FactorWeightGood Threshold
RTT30%≤ 100ms
Packet Loss30%≤ 1%
Jitter15%≤ 30ms
Bandwidth25%≥ 500 kbps

Score ≥ 70 = good (3 bars, green). Score ≥ 40 = fair (2 bars, amber). Below 40 = poor (1 bar, red).

File Transfer Protocol

Sender Receiver │ │ │── file-offer (name, size, type) ────────►│ │ │ │◄──────────────────── file-accept ────────│ │ │ │── file-chunk (index 0, base64) ─────────►│ │── file-chunk (index 1, base64) ─────────►│ │── file-chunk (index 2, base64) ─────────►│ │ ... │ │── file-complete ────────────────────────►│ │ │ │ Receiver reconstructs blob, offers │ │ "Save to Downloads" via Tauri fs plugin │
ParameterValue
Max file size100 MB
Chunk size64 KB
EncodingBase64 (over GossipSub JSON messages)
Inter-chunk delay5ms
Statespending → accepted/declined → active → completed/failed/cancelled

Presence & Discovery

Room Directory

Active rooms publish room-announce messages to a global GossipSub topic (_directory). Announcements include:

Announcements have a 60-second TTL and are re-broadcast every 30 seconds. Stale entries expire automatically from the directory view.

Knock Protocol

Closed rooms require visitors to knock. The knock state machine:

Knocker: idle ──► pending ──┬──► accepted (auto-join) └──► declined (show message)

Hosts receive knock requests as native macOS notifications via Tauri's notification plugin.

Project Structure

hq-world/
├── docs/                    # This documentation site
├── scripts/
│   └── relay.mjs            # Local circuit relay v2 server
├── src/
│   ├── App.tsx              # Root component (lobby/room/directory/knocking views)
│   ├── components/
│   │   ├── profile/         # Avatar, ProfileEditor
│   │   └── room/            # RoomView, VideoGrid, MediaControls, HostControls,
│   │                        # ParticipantList, KnockDialog, RoomDirectory,
│   │                        # StatusSelector, FileDropZone, FileTransfer,
│   │                        # BandwidthMonitor, QualityIndicator
│   ├── hooks/               # useProfile, useFileTransfers, useMediaState,
│   │                        # usePeerMedia, useSfu
│   └── lib/
│       ├── p2p/             # node, config, discovery, media, stream,
│       │                    # file-transfer, sfu, bandwidth, quality
│       ├── room/            # manager, types, knock, presence, roles, moderation
│       └── profile/         # types, storage, sync
├── src-tauri/
│   ├── src/main.rs          # Tauri entry point
│   ├── Cargo.toml           # Rust dependencies
│   ├── tauri.conf.json      # App config (window, bundle, CSP)
│   └── capabilities/        # Tauri permission grants
├── tests/
│   └── e2e/                 # Vitest E2E tests
├── package.json
├── tsconfig.json
├── vite.config.ts
└── vitest.config.ts

Configuration Reference

All P2P constants live in src/lib/p2p/config.ts:

KeyValueDescription
maxPeersPerRoom8Default max participants per room
roomTopicPrefix/hq-world/room/1.0.0/GossipSub topic prefix for rooms
protocolId/hq-world/1.0.0libp2p protocol identifier
discoveryTopic_hq-world._peer-discovery._p2p._pubsubPubSub peer discovery topic
discoveryIntervalMs10,000Peer discovery broadcast interval
bootstrapRelays/ip4/127.0.0.1/tcp/9001/ws/...Circuit relay addresses
timeouts.connection10sDial timeout
timeouts.handshake5sNoise handshake timeout
timeouts.relay15sRelay connection timeout
connectionLimits.max50Max libp2p connections