How HQ World's P2P stack, room system, and media layer fit together.
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.
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.
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.
The room layer defines 22 message types routed by manager.ts:
| Category | Messages |
|---|---|
| WebRTC Signaling | webrtc-offer, webrtc-answer, webrtc-ice |
| Media State | media-state (muted, video-off, audio-only) |
| Identity | user-info (display name, avatar color) |
| Knock Protocol | knock-request, knock-accepted, knock-declined |
| Moderation | force-mute, boot-peer |
| Roles | promote-cohost, demote-cohost |
| Presence | room-announce, room-close, presence-update |
| File Transfer | file-offer, file-accept, file-decline, file-chunk, file-complete, file-cancel |
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.
The host runs a hybrid SFU that monitors all peer connections and adjusts video quality based on bandwidth:
| Upload Bandwidth | Action |
|---|---|
| ≥ 4000 kbps | Comfortable — full quality (640×480 @ 24fps) |
| ≥ 2000 kbps | Constrained — downgrade to medium (480×360 @ 20fps) |
| ≥ 1000 kbps | Stressed — downgrade to low (320×240 @ 15fps) |
| < 500 kbps | Critical — force audio-only for poor connections |
Quality checks run every 5 seconds with a 10-second per-peer cooldown to prevent thrashing.
Each peer connection gets a composite quality score (0–100):
| Factor | Weight | Good Threshold |
|---|---|---|
| RTT | 30% | ≤ 100ms |
| Packet Loss | 30% | ≤ 1% |
| Jitter | 15% | ≤ 30ms |
| Bandwidth | 25% | ≥ 500 kbps |
Score ≥ 70 = good (3 bars, green). Score ≥ 40 = fair (2 bars, amber). Below 40 = poor (1 bar, red).
| Parameter | Value |
|---|---|
| Max file size | 100 MB |
| Chunk size | 64 KB |
| Encoding | Base64 (over GossipSub JSON messages) |
| Inter-chunk delay | 5ms |
| States | pending → accepted/declined → active → completed/failed/cancelled |
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.
Closed rooms require visitors to knock. The knock state machine:
Hosts receive knock requests as native macOS notifications via Tauri's notification plugin.
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
All P2P constants live in src/lib/p2p/config.ts:
| Key | Value | Description |
|---|---|---|
maxPeersPerRoom | 8 | Default max participants per room |
roomTopicPrefix | /hq-world/room/1.0.0/ | GossipSub topic prefix for rooms |
protocolId | /hq-world/1.0.0 | libp2p protocol identifier |
discoveryTopic | _hq-world._peer-discovery._p2p._pubsub | PubSub peer discovery topic |
discoveryIntervalMs | 10,000 | Peer discovery broadcast interval |
bootstrapRelays | /ip4/127.0.0.1/tcp/9001/ws/... | Circuit relay addresses |
timeouts.connection | 10s | Dial timeout |
timeouts.handshake | 5s | Noise handshake timeout |
timeouts.relay | 15s | Relay connection timeout |
connectionLimits.max | 50 | Max libp2p connections |