Skip to content

feat: add @webgamekit/multiplayer-p2p package#62

Merged
cnotv merged 22 commits into
mainfrom
feat/61-multiplayer-package
Apr 7, 2026
Merged

feat: add @webgamekit/multiplayer-p2p package#62
cnotv merged 22 commits into
mainfrom
feat/61-multiplayer-package

Conversation

@cnotv

@cnotv cnotv commented Apr 3, 2026

Copy link
Copy Markdown
Owner

Closes #61

Summary

  • Adds @webgamekit/multiplayer-p2p — WebRTC P2P via Trystero (NOSTR signaling, no server required)
  • Rewrites MultiplayerP2P experiment: canvas-only scene with stickboy.glb avatars, auto-join on load
  • Adds p2pSendAction/p2pOnAction for broadcasting and receiving animations across peers

Key Changes

  • Multiplayer-client and multiplayer-server packages moved to separate PR (feat: add @webgamekit/multiplayer-client and multiplayer-server packages #64 / issue Add multiplayer-client and multiplayer-server packages #63)
  • MultiplayerP2P.vue now loads stickboy.glb for local and remote players (orange tint for remotes)
  • WASD movement broadcasts position to peers in real-time
  • Keyboard 1–7 triggers animations: wave, attack, jump, talk, sit, pick, death — synced to all peers
  • Mobile: left faux-pad for movement, right button pad for wave/attack/jump/talk
  • p2pGetPeerIds() + onPeerJoin combo fixes late-joiner race condition (peers who joined before you)
  • p2pIsSupported() pre-flight guard — skips P2P init gracefully on plain HTTP
  • Journey doc covers all bugs: DataPayload types, TS module resolution, Vite pre-bundling crypto crash, HTTP crypto.subtle, torrent tracker failures, peer visibility, late-joiner, testing strategy

Test Plan

  • pnpm test:unit — 758 tests pass (9 new integration tests for P2P including action sync)
  • pnpm lint — no errors
  • Open /experiments/MultiplayerP2P — stickboy loads on a green ground, camera follows
  • WASD moves the local player (white stickboy)
  • Open second tab at same URL — orange stickboy appears, moves with the other tab's controls
  • Press 1–7 in one tab — the corresponding animation plays and mirrors in the other tab

🤖 Generated with Claude Code

Giuseppe Leo and others added 2 commits April 3, 2026 23:35
Client-side Socket.IO wrapper for sharing 3D scene state between players.
Based on the cnotv/multi-game reference implementation.

Public API:
- multiplayerCreate(url, config?) — connect to Socket.IO server
- multiplayerDestroy(session) — disconnect
- multiplayerSendPosition(session, position, rotation) — throttled (30 ms default)
- multiplayerOnPlayers(session, callback) — subscribe to player list (user:list)
- multiplayerCollectCoin(session, coinId) — emit coin:collected
- multiplayerOnCoinCollected(session, callback) — receive coin:collected events
- multiplayerOnCoinsSync(session, callback) — receive initial coin:list

Socket events match cnotv/multi-game server: user:updated, user:list,
coin:collected, coin:list. socket.io-client is a peer dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@netlify

netlify Bot commented Apr 3, 2026

Copy link
Copy Markdown

Deploy Preview for cnotv-generative-art ready!

Name Link
🔨 Latest commit 3c2e5e1
🔍 Latest deploy log https://app.netlify.com/projects/cnotv-generative-art/deploys/69d567a5081b0c0008f813ba
😎 Deploy Preview https://deploy-preview-62--cnotv-generative-art.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Giuseppe Leo and others added 10 commits April 4, 2026 18:19
…tiplayer-p2p packages

Closes #61

- Rename `@webgamekit/multiplayer` → `@webgamekit/multiplayer-client` (Socket.IO client)
- Add `@webgamekit/multiplayer-server` (Socket.IO server — Node.js only)
- Add `@webgamekit/multiplayer-p2p` (WebRTC via Trystero + NOSTR signaling, fully serverless)
- All three packages expose generic typed data channels (multiplayerClient*/p2p* prefix)
- Add `MultiplayerClient` and `MultiplayerP2P` experiment views
- Register multiplayer packages in vite aliases and root deps
- Add multiplayer packages to ESLint utility-files override

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add p2pOnPeerJoin and p2pOnPeerLeave to expose Trystero's peer
lifecycle events. Fix session.peerId to use Trystero's selfId instead
of the first remote peer's ID.

Update MultiplayerP2P view to add peers to the list on join and remove
on leave, rather than waiting for their first position broadcast.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add overflow-y: auto and max-height: 100% to .sheet-content so it
becomes the scroll container. Previously pointer-events: none on
.panel-container prevented touch-scroll from reaching the panel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…m Vite pre-bundling

Test mock now simulates actual peer join/leave by calling registered
callbacks — previously tests only verified registration, not invocation.

Exclude trystero/nostr from Vite optimizeDeps to prevent it from being
pre-bundled in Node context where crypto.subtle is unavailable, which
caused "Cannot read properties of undefined (reading 'digest')" at runtime.

Also remove duplicate overflow-y: auto from .sheet-content (caused double
scrollbar on desktop).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The room simulation routes sends between sessions and fires onPeerJoin/
onPeerLeave across peers, catching cross-peer bugs that unit tests miss.

Also adds journey/multiplayer-p2p.md documenting the DataPayload
constraint, moduleResolution: Bundler, crypto.subtle Vite issue,
peer visibility bug, and the integration testing strategy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log room join/leave, selfId, peer join/leave events, and position
send/receive so runtime failures are visible in the browser console.

Fix unit test mock to export selfId (required after room.ts started
importing it from trystero/nostr).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
trystero/nostr uses crypto.subtle.importKey for NOSTR event signing,
which requires a secure context (HTTPS or localhost). The app is served
on custom hostnames without HTTPS, so crypto.subtle is undefined.

@trystero-p2p/torrent uses BitTorrent WebSocket trackers for signaling
and has no crypto.subtle dependency, working on any origin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All Trystero strategies require crypto.subtle (HTTPS or localhost only).
Add p2pIsSupported() pre-flight check that logs the origin and returns
false when crypto.subtle is unavailable, so p2pJoin throws a descriptive
error instead of crashing async inside joinRoom.

The view now checks p2pIsSupported on mount and shows a banner explaining
the HTTPS requirement instead of silently failing.

Add more debug logs: isSecureContext, origin, crypto.subtle availability,
and joinRoom entry/exit points.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The torrent strategy WebSocket trackers (tracker.webtorrent.dev,
tracker.btorrent.xyz) fail to connect, so peers can never discover
each other. The original switch to torrent was to avoid crypto.subtle,
but that is now resolved by the p2pIsSupported() HTTPS check.

trystero/nostr uses NOSTR relays which are more reliable for signaling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
onPeerJoin only fires for peers who join after you register the callback.
When tab A joins after tab B is already in the room, B is invisible to A
until a third peer joins and triggers a new handshake.

Fix: call p2pGetPeerIds (wraps room.getPeers()) immediately after setting
up onPeerJoin, and add any already-connected peers to the list.

Add integration test covering the late-joiner scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Giuseppe Leo and others added 2 commits April 5, 2026 22:07
Documents all bugs encountered during P2P implementation:
DataPayload constraint, TypeScript module resolution, Vite pre-bundling
crypto crash, crypto.subtle on plain HTTP, torrent tracker failures,
peer visibility issues, late-joiner race condition, and testing strategy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add p2pSendAction/p2pOnAction to multiplayer-p2p for broadcasting animation names
- Add PlayerAction type to package exports
- Rewrite MultiplayerP2P view: canvas-only, auto-join on mount, WASD controls
- Load stickboy.glb for local player (white) and remote peers (orange tint)
- Animation buttons: wave (1), attack (2), jump (3), talk (4), sit (5), pick (6), death (7)
- Keyboard 1-7 + gamepad buttons trigger animations and broadcast to all peers
- Remote peers play received animation via playActionTimeline
- Mobile: left faux-pad for movement, right button pad for actions
- Integration tests cover action broadcast and self-exclusion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cnotv cnotv changed the title feat: add @webgamekit/multiplayer package feat: add @webgamekit/multiplayer-p2p package Apr 5, 2026
Giuseppe Leo and others added 8 commits April 5, 2026 22:13
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on resize

- Set PLAYER_Y_OFFSET to 0 so models sit on ground surface (y=-0.5)
- Spawn remote peers at staggered positions using index-based spread
  (alternating left/right, increasing distance) instead of all at [5,0,0]
- Remove window resize listener — getTools already handles canvas/camera
  resize internally; re-calling init was recreating the entire scene

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ount

Root cause of lag: makeAction('pos') was called every frame (60fps) inside
the animation loop, creating a new Trystero action channel each time.
Fixed by caching [send] per session+channel via WeakMap — makeAction is now
called at most once per channel per session.

Also adds:
- ControlsLogger HUD showing player count and keyboard bindings
- FPS + peer count log every 2s (console.warn) to surface performance issues
- makeAction call counter log to confirm caching works in production

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on send

The previous debounce implementation cancelled the timer on every frame call
at 60fps, so position was never actually broadcast while moving (timer always
reset before firing). Replaced with a proper leading+trailing throttle:
- Sends immediately on the first call if throttle window has elapsed
- Schedules one trailing send for the remaining window if called mid-throttle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, and input mapping

- Change timeline frequency from 60 to 2 (fires 30x/sec instead of 1x/sec)
- Fix Euler serialization bug: use plain {x,y,z} objects for broadcast
- Fix getGround position mutation that compounded on HMR reloads
- Swap up/down input mappings for keyboard, gamepad, and faux-pad
- Remove spawn slot logic, simplify peer spawning
- Remove all debug logs from p2p packages
- Document frequency bug and design decisions in journey docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d collapsible HUD

- Track remote peer movement to play walk/idle animations accordingly
- Slow down idle animation speed (5) vs walk (20)
- Allow movement during all blocking actions (wave, attack, etc.)
- Make ControlsLogger collapsible on mobile with +/− toggle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cnotv cnotv merged commit 9fcf28d into main Apr 7, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add multiplayer package

1 participant