From e9ba5b5b4698649ce2adce8bbdd9a59e847bed84 Mon Sep 17 00:00:00 2001 From: Ole Date: Sat, 13 Jun 2026 22:17:53 +0200 Subject: [PATCH] README: dokumentiere Full Mesh + Selective Abort --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0c1b926..e2bf18d 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Zwei bis N Spieler verbinden sich via WebRTC (simple-peer) und führen ein krypt - **Trustless** – Commit-Reveal-Protokoll mit SHA-256-Bindung, jeder verifiziert - **QR-Bootstrapping** – SDP-Offers/Answers werden als QR-Codes ausgetauscht - **Multiplayer (2–n)** – Beliebig viele Teilnehmer in einer Runde +- **Full Mesh** – Alle Peers verbinden sich direkt per WebRTC (Host relayt nur initial SDP) +- **Selective Abort** – Timeout erkennt Verweigerer, alle Teilnehmer sehen denselben Schuldigen - **Coin-Animation** – 3D-Münzwurf via CSS - **100% Vanilla JS** – Keine Build-Tools, kein npm, keine Abhängigkeiten außer CDN-Libs @@ -32,27 +34,35 @@ Dann auf zwei (oder mehr) Geräten `http://localhost:8080` öffnen. 1. **"Raum erstellen"** – QR-Code mit SDP Offer wird angezeigt 2. **"Antwort-QR scannen"** – Kamera startet, scannt die Antwort-QRs der Beitreter 3. Jeder neue Spieler erscheint in der Liste -4. **"Münzwurf starten"** – Übergang in den Spiel-Screen -5. **"Münzwurf starten"** – Protokoll beginnt (Commit → Reveal → Ergebnis) +4. Host broadcastet `roster` mit allen Peer-IDs → Peers bauen direktes Mesh untereinander auf +5. Sobald alle Peers `mesh_ready` gemeldet haben → **"Münzwurf starten"** wird aktiv +6. **"Münzwurf starten"** – Übergang in den Spiel-Screen +7. **"Münzwurf starten"** – Protokoll beginnt (Commit → Reveal → Ergebnis) ### Beitreter 1. **"Raum beitreten"** – Kamera startet 2. QR des Hosts scannen 3. **Antwort-QR zeigen** – Host scannt diesen QR -4. Verbindung steht, warten auf Start -5. Protokoll läuft automatisch durch – Ergebnis erscheint +4. Verbindung steht, empfange `roster` mit anderen Peer-IDs +5. Baue direkte WebRTC-Verbindung zu jedem anderen Peer auf (kleinere PeerId initiiert → kein Glare) +6. Sobald alle direkten Verbindungen stehen → automatisch `mesh_ready` an Host +7. Host startet Protokoll → alle laufen automatisch durch (Commit → Reveal → Ergebnis) ### Protokoll-Phasen | Phase | Beschreibung | |-------|-------------| | **Commit** | Jeder Spieler generiert 32 Zufallsbytes und sendet den SHA-256-Hash an alle | -| **Warten** | Sammle Commits aller Teilnehmer | +| **Warten** | Sammle Commits aller Teilnehmer (Timeout 12s) | | **Reveal** | Jeder sendet die rohen Zufallsbytes an alle | +| **Warten** | Sammle Reveals aller Teilnehmer (Timeout 12s) | | **Verify** | Jeder prüft: SHA-256(received) == stored_commit | | **Result** | XOR aller Secrets → Parity (0=Kopf, 1=Zahl) | +Bei Timeout in einer Wartephase: **Selective Abort** – `ABORTED`-Zustand, fehlende Peer-IDs werden angezeigt. +Alle Teilnehmer sehen dieselben fehlenden Peers (Mesh → jeder sieht, wer nicht sendet). + ## Architektur ``` @@ -75,11 +85,44 @@ randomp2p/ ## Netzwerk-Topologie -**Best Case (gleiches WiFi):** -QR-SDP-Austausch zwischen Host und jedem Peer. Host teilt IP-Liste → potentiell volles Mesh über `signal_relay`. +**Phase 1 – Stern (QR-Bootstrapping):** +Host tauscht via QR-Codes SDP Offers/Answers mit jedem Peer aus → jeder Peer ist direkt mit dem Host verbunden. -**Worst Case (NAT/Internet):** -Stern-Topologie über den Host. Protokoll funktioniert trotzdem – die Fairness ist nicht von der Topologie abhängig. +**Phase 2 – Mesh (relayed Signaling):** +Sobald ein Peer beitritt, broadcastet der Host ein `roster` mit allen Peer-IDs an das gesamte Netzwerk. +Jeder Peer baut zu jedem anderen Peer eine **direkte WebRTC-Verbindung** auf: + +- **Initiator** ist immer der Peer mit der **kleineren PeerId** (verhindert Glare / Race-Conditions) +- SDP Offers/Answers werden über den **Host als Relay** ausgetauscht (`signal_relay`/`signal_relayed` via DataChannel) +- Nach erfolgreichem Verbindungsaufbau sendet jeder Peer `mesh_ready` an den Host + +**Ergebnis:** Volles Mesh – jeder Peer ist mit jedem direkt verbunden. +Broadcasts erreichen alle Teilnehmer in einem Hop. `expectedPeers` (für Timeout/Abort) ist auf allen identisch. + +``` +Host A ──QR── Peer B Host A ──star── Peer B +Host A ──QR── Peer C → Host A ──star── Peer C + Peer B ──mesh── Peer C (direkt, kein Relay) +``` + +### Signal-Relay-Detail + +``` +B (Initiator, peerId kleiner) Host A C (Responder) + │ │ │ + ├─ signal_relay(to:C, offer) ──────→│ │ + │ ├─ signal_relayed(offer) ──→│ + │ │ ├─ SimplePeer(non-init) + │ │ ├─ peer.signal(offer) + │ │ ├─ answer signal + │ │←─ signal_relay(to:B, answer) ─┤ + │←── signal_relayed(answer) ────────┤ │ + ├─ peer.signal(answer) │ │ + ├─ B–C direkt verbunden! │ ├─ B–C direkt verbunden! + │ │ │ + ├─ mesh_ready ────────────────────→│←────────────────── mesh_ready ─┤ + │ │ readyPeers={B,C} → Button enabled +``` ## Warum ist das fair? @@ -101,11 +144,11 @@ ipfs add -r . ## Ausblick / TODOs -- [ ] Volles Mesh: Peers verbinden sich direkt via relayed signaling -- [ ] Timeout + Reconnect bei Verbindungsabbruch +- [x] Volles Mesh: Peers verbinden sich direkt via relayed signaling +- [x] Selective Abort: Timeout bei Verweigerern +- [ ] Reconnect bei Verbindungsabbruch - [ ] Raum-Code als Alternative zum QR-Scan - [ ] TURN-Server-Konfiguration für NAT-Traversal -- [ ] Mehrere Runden mit Historie - [ ] PWA-Manifest + ServiceWorker für IPFS-Distribution ## Lizenz