twinwire 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +128 -2
  4. package/dist/browser/twinwire.js +2075 -0
  5. package/dist/browser/twinwire.js.map +7 -0
  6. package/dist/browser/twinwire.min.js +1 -0
  7. package/dist/browser/twinwire.mjs +2052 -0
  8. package/dist/browser/twinwire.mjs.map +7 -0
  9. package/dist/browser.d.ts +5 -0
  10. package/dist/browser.js +6 -0
  11. package/dist/browser.js.map +1 -0
  12. package/dist/client/client.d.ts +128 -0
  13. package/dist/client/client.js +470 -0
  14. package/dist/client/client.js.map +1 -0
  15. package/dist/client/index.d.ts +3 -0
  16. package/dist/client/index.js +4 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/client/lobby.d.ts +40 -0
  19. package/dist/client/lobby.js +122 -0
  20. package/dist/client/lobby.js.map +1 -0
  21. package/dist/client/rtc.d.ts +17 -0
  22. package/dist/client/rtc.js +102 -0
  23. package/dist/client/rtc.js.map +1 -0
  24. package/dist/core/budget.d.ts +27 -0
  25. package/dist/core/budget.js +39 -0
  26. package/dist/core/budget.js.map +1 -0
  27. package/dist/core/endpoint.d.ts +144 -0
  28. package/dist/core/endpoint.js +501 -0
  29. package/dist/core/endpoint.js.map +1 -0
  30. package/dist/core/lobby.d.ts +59 -0
  31. package/dist/core/lobby.js +53 -0
  32. package/dist/core/lobby.js.map +1 -0
  33. package/dist/core/protocol.d.ts +52 -0
  34. package/dist/core/protocol.js +100 -0
  35. package/dist/core/protocol.js.map +1 -0
  36. package/dist/core/reliable.d.ts +52 -0
  37. package/dist/core/reliable.js +116 -0
  38. package/dist/core/reliable.js.map +1 -0
  39. package/dist/core/rtc.d.ts +28 -0
  40. package/dist/core/rtc.js +21 -0
  41. package/dist/core/rtc.js.map +1 -0
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.js +7 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/netcode/clock.d.ts +15 -0
  46. package/dist/netcode/clock.js +37 -0
  47. package/dist/netcode/clock.js.map +1 -0
  48. package/dist/netcode/codec.d.ts +10 -0
  49. package/dist/netcode/codec.js +95 -0
  50. package/dist/netcode/codec.js.map +1 -0
  51. package/dist/netcode/index.d.ts +9 -0
  52. package/dist/netcode/index.js +10 -0
  53. package/dist/netcode/index.js.map +1 -0
  54. package/dist/netcode/inputs.d.ts +7 -0
  55. package/dist/netcode/inputs.js +43 -0
  56. package/dist/netcode/inputs.js.map +1 -0
  57. package/dist/netcode/interpolation.d.ts +44 -0
  58. package/dist/netcode/interpolation.js +101 -0
  59. package/dist/netcode/interpolation.js.map +1 -0
  60. package/dist/netcode/prediction.d.ts +40 -0
  61. package/dist/netcode/prediction.js +97 -0
  62. package/dist/netcode/prediction.js.map +1 -0
  63. package/dist/netcode/replication.d.ts +36 -0
  64. package/dist/netcode/replication.js +90 -0
  65. package/dist/netcode/replication.js.map +1 -0
  66. package/dist/netcode/snapshots.d.ts +29 -0
  67. package/dist/netcode/snapshots.js +78 -0
  68. package/dist/netcode/snapshots.js.map +1 -0
  69. package/dist/netcode/spatial.d.ts +16 -0
  70. package/dist/netcode/spatial.js +63 -0
  71. package/dist/netcode/spatial.js.map +1 -0
  72. package/dist/netcode/timing.d.ts +37 -0
  73. package/dist/netcode/timing.js +41 -0
  74. package/dist/netcode/timing.js.map +1 -0
  75. package/dist/server/index.d.ts +4 -0
  76. package/dist/server/index.js +5 -0
  77. package/dist/server/index.js.map +1 -0
  78. package/dist/server/lobby.d.ts +134 -0
  79. package/dist/server/lobby.js +462 -0
  80. package/dist/server/lobby.js.map +1 -0
  81. package/dist/server/rtc.d.ts +38 -0
  82. package/dist/server/rtc.js +102 -0
  83. package/dist/server/rtc.js.map +1 -0
  84. package/dist/server/server.d.ts +145 -0
  85. package/dist/server/server.js +457 -0
  86. package/dist/server/server.js.map +1 -0
  87. package/dist/server/turn.d.ts +22 -0
  88. package/dist/server/turn.js +52 -0
  89. package/dist/server/turn.js.map +1 -0
  90. package/docs/ADOPTING.md +64 -0
  91. package/docs/API.md +181 -0
  92. package/docs/ARCHITECTURE.md +101 -0
  93. package/docs/OPERATIONS.md +93 -0
  94. package/docs/RELEASE-CHECKLIST.md +25 -0
  95. package/package.json +100 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2026-10-25)
4
+
5
+ The first public release.
6
+
7
+ - **Transport**: one resumable session per player over WebSocket, with a WebRTC data channel (unordered, no
8
+ retransmits) used for state while bidirectional probes prove it; fallback within ~1.5 s and retries with backoff; one
9
+ UDP port per server (ICE mux, node-datachannel). Protocol version 3 (frames start with `TW`).
10
+ - **Events**: ordered, acknowledged, deduplicated, replayed after reconnects, mirrored on the datagram path for UDP
11
+ latency; standard JSON semantics; up to 64 KiB each.
12
+ - **State**: latest-wins binary streams up to 64 KiB (256 KiB max), priorities and TTLs, a per-peer byte budget that
13
+ lets a burst-sized message through, large states on the WebSocket; optional acknowledgements (`ack: true`,
14
+ `onStateAck`) for delta compression.
15
+ - **Lobby**: room codes, quick play by mode and meta (e.g. build), seats that survive reconnects and page reloads, host
16
+ hand-off, ready-up with a countdown, kick, eviction of long-gone members from full rooms, setup data on create,
17
+ client-settable meta allowlist, per-address creation limits.
18
+ - **Client**: `whenOnline()`, `transport`/`onTransport`, ICE servers from the server, page lifecycle handling (pause
19
+ after being hidden, a fast liveness check on return, reconnect on `online`), checkpoints, `simulateDrop()` for tests.
20
+ - **Server**: origin lists or predicates, `trustProxy`, per-connection rate limits, `endSession` (kick), `metrics()`,
21
+ listener hooks, TURN credentials (`turnCredentials` for coturn, `cloudflareTurn`), development-only impairment.
22
+ - **Netcode**: `NetworkClock`, `Timeline` + `AdaptivePlayback`, `Predictor` + `InputInbox` + `encodeInputs`,
23
+ `FixedStepper`, `SnapshotHistory` + `diffEntities`/`applyEntityDelta`, `ReplicaSender`/`ReplicaReceiver`,
24
+ `SpatialGrid`, `defineSchema`.
25
+ - **Browser bundle**: `twinwire/browser` (`window.Twinwire`, 15 KB gzipped) and an ESM build.
26
+ - **Examples**: `examples/party` (a complete small game: lobby, ready-up, prediction, interpolation, chat) and
27
+ `examples/arena` (the transport, with live numbers).
28
+ - **Tests**: 89 unit and integration tests; a native WebRTC gate; real Chromium, Firefox and WebKit against the native
29
+ server (clean, impaired, WebSocket only); the party example played in three browsers.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hakan Alpay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,129 @@
1
- # Temporary Holding Version
1
+ # Twinwire
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Multiplayer networking for browser games.** One player session, two wires: a WebSocket for what must arrive, and
4
+ WebRTC datagrams (UDP-like: unordered, no retransmits) for what must arrive *fast*. Twinwire picks the fast wire when
5
+ the network allows it, falls back to the WebSocket the moment it doesn't, and keeps the player's session (and seat)
6
+ through dropped connections, app switches and Wi-Fi-to-cellular hand-offs. It comes with a lobby (room codes, quick
7
+ play, ready-up) and the netcode helpers an authoritative-server game needs.
8
+
9
+ ```
10
+ npm install twinwire
11
+ npm install node-datachannel # on the server, for WebRTC (optional: without it, WebSocket only)
12
+ ```
13
+
14
+ [![CI](https://github.com/studiokimeiga/twinwire/actions/workflows/ci.yml/badge.svg)](https://github.com/studiokimeiga/twinwire/actions/workflows/ci.yml)
15
+ [![npm](https://img.shields.io/npm/v/twinwire.svg)](https://www.npmjs.com/package/twinwire)
16
+ ![license](https://img.shields.io/badge/license-MIT-blue.svg)
17
+
18
+ ## Why
19
+
20
+ Browser games that want to feel good online hit the same wall: WebSocket is TCP, so one lost packet holds up every
21
+ packet behind it, and on a phone that's a stutter. WebRTC data channels can be unreliable and unordered like UDP, but
22
+ they're fiddly to set up, they fail on some networks, and a channel that says "open" isn't proof that packets get
23
+ through. Then there's everything around it: reconnecting without losing your place, rooms and invite codes,
24
+ interpolating other players, predicting your own.
25
+
26
+ Twinwire does that part, so the game can be the game:
27
+
28
+ - **Two wires, one session.** WebSocket for the session; a WebRTC data channel for state, used only while probes prove
29
+ it works *both ways*. Fallback within ~1.5 s, RTC retried with backoff, one UDP port on the server for every player.
30
+ - **Sessions that survive.** A dropped connection resumes the same session: same identity, same lobby seat, events
31
+ replayed both ways. A page coming back from the background, or reloaded with a checkpoint, takes over cleanly.
32
+ - **Events at UDP speed.** Ordered, acknowledged JSON events go on the WebSocket *and* are mirrored on the datagram
33
+ path, so a hit or a door usually lands at UDP latency with TCP as the guarantee.
34
+ - **State, latest wins.** Binary state streams that coalesce and expire instead of queueing, up to 64 KiB, with
35
+ acknowledgements so you can send deltas against what each player actually has (`SnapshotHistory`, `diffEntities`).
36
+ - **A lobby.** Private rooms with codes, public quick play matched by mode and build, stable seats, host hand-off,
37
+ ready-up with a countdown, kick, rate limits.
38
+ - **Netcode helpers.** Clock sync, snapshot interpolation with adaptive playback delay, client-side prediction and
39
+ reconciliation, input redundancy, fixed-step simulation, interest queries, compact binary codecs.
40
+ - **STUN/TURN** handed to each client by the server (coturn shared-secret credentials or Cloudflare's managed relay).
41
+ - **Every browser.** Tested in Chromium, Firefox and WebKit (Safari's engine) against the real server over WebRTC, on
42
+ a bad network and with UDP blocked; 15 KB gzipped in the browser.
43
+
44
+ Your game keeps its rules, physics and rendering. Twinwire carries the bytes and the session; your server decides what
45
+ is true.
46
+
47
+ ## Quick start
48
+
49
+ **Server** (Node 22+)
50
+
51
+ ```js
52
+ import { createGameServer, createNativeRtcFactory, Lobby } from 'twinwire/server';
53
+
54
+ const server = await createGameServer({
55
+ port: 8787,
56
+ origins: ['https://mygame.com'],
57
+ rtc: await createNativeRtcFactory({ port: 8788 }), // one UDP port for everyone (needs node-datachannel)
58
+ });
59
+ const lobby = new Lobby(server, { maxMembers: 4, ready: { countdownMs: 3000 } });
60
+
61
+ lobby.onCreate = room => { room.data = createWorld(); };
62
+ lobby.onEvent = (room, member, msg) => room?.data.command(member.seat, msg); // validate, then apply
63
+ lobby.onState = (room, member, input) => room?.data.input(member.seat, input.data);
64
+ setInterval(() => {
65
+ for (const room of lobby.rooms.values()) {
66
+ room.data?.step();
67
+ for (const m of room.members.values()) if (m.connected) m.session.sendState(0, room.data.snapshotFor(m.seat));
68
+ }
69
+ }, 1000 / 30);
70
+
71
+ await server.listen();
72
+ ```
73
+
74
+ **Browser** (with a bundler: `twinwire/client` and `twinwire/netcode`; or a script tag with `twinwire/browser`,
75
+ which defines `window.Twinwire`)
76
+
77
+ ```js
78
+ import { GameClient, LobbyClient } from 'twinwire/client';
79
+
80
+ const net = new GameClient({ url: 'wss://play.mygame.com/connect', room: 'mygame', auth: { name: 'WREN' } });
81
+ const lobby = new LobbyClient(net);
82
+ net.bindPageLifecycle(); // pause in the background, come back fast
83
+ net.connect();
84
+ await net.whenOnline();
85
+
86
+ await lobby.quickPlay({ mode: 'coop' }); // or lobby.create() / lobby.join('KQXT')
87
+ lobby.onRoom = room => drawLobby(room); // members, seats, ready, lobby.countdownMs()
88
+ await lobby.setReady(true);
89
+
90
+ net.onState = s => snapshots.push(s); // s.via is 'rtc' or 'ws'
91
+ net.onEvent = e => applyEvent(e);
92
+ net.sendEvent({ type: 'shoot', aim: 0.4 }); // ordered, acknowledged
93
+ net.sendState(1, encodeInput(input)); // latest wins
94
+ ```
95
+
96
+ ## Examples
97
+
98
+ - **[`examples/party`](examples/party)**: a complete little game. Quick play or a room by code, ready-up, then move
99
+ around a shared arena and chat. Server-authoritative movement with client-side prediction and reconciliation, other
100
+ players interpolated, inputs sent redundantly. About 300 lines. `npm run build && npm run example`, then open
101
+ http://127.0.0.1:8790 in two tabs (`RTC=1` for WebRTC).
102
+ - **[`examples/arena`](examples/arena)**: the transport on its own, with live numbers (path, RTT, bytes).
103
+ `npm run demo`.
104
+
105
+ Used in production by **[Nocturne Keep](https://nocturnekeep.com)**: co-op story, maker rooms and a roguelike played
106
+ together, with the game's own code running each room's world on a $5 server.
107
+
108
+ ## Documentation
109
+
110
+ - [API](docs/API.md): every option and contract.
111
+ - [Architecture](docs/ARCHITECTURE.md): the wire protocol and the guarantees.
112
+ - [Adopting it in an existing game](docs/ADOPTING.md): a staged path, and what a real port looked like.
113
+ - [Operations](docs/OPERATIONS.md): hosting, the UDP port, TURN, budgets, health.
114
+ - [Validation](validation/STATUS.md): what was tested, where, and with what results.
115
+
116
+ ## Develop
117
+
118
+ ```sh
119
+ npm install
120
+ npm run check # build + unit and localhost integration tests
121
+ npm run test:native # Node WebRTC client <-> native server: datagrams, fallback, recovery
122
+ npx playwright install chromium firefox webkit
123
+ npm run test:browser # real browsers <-> native server (also IMPAIR=1, RTC=0)
124
+ npm run test:example # the party example, played in three browsers
125
+ ```
126
+
127
+ Contributions are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md). Security reports: [SECURITY.md](SECURITY.md).
128
+
129
+ MIT licensed.