@net-mesh/browser 0.43.0 → 0.44.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.
- package/CHANGELOG.md +176 -0
- package/dist/index.bundle.js +2 -2
- package/dist/lobby.d.ts +29 -2
- package/dist/lobby.js +72 -6
- package/dist/net_leaf.d.ts +19 -21
- package/dist/net_leaf.js +44 -46
- package/dist/net_leaf_bg.wasm +0 -0
- package/dist/net_leaf_bg.wasm.d.ts +10 -10
- package/dist/netcode/assignment.d.ts +169 -0
- package/dist/netcode/assignment.js +321 -0
- package/dist/netcode/client.d.ts +125 -3
- package/dist/netcode/client.js +291 -27
- package/dist/netcode/codec.d.ts +180 -0
- package/dist/netcode/codec.js +547 -0
- package/dist/netcode/host.d.ts +58 -1
- package/dist/netcode/host.js +254 -9
- package/dist/netcode/index.d.ts +14 -4
- package/dist/netcode/index.js +8 -3
- package/dist/netcode/interpolate.d.ts +24 -1
- package/dist/netcode/interpolate.js +52 -1
- package/dist/netcode/paths.d.ts +115 -0
- package/dist/netcode/paths.js +261 -0
- package/dist/netcode/shards.d.ts +138 -0
- package/dist/netcode/shards.js +270 -0
- package/dist/netcode/wire.d.ts +37 -3
- package/dist/netcode/wire.js +28 -1
- package/dist/node.d.ts +5 -0
- package/dist/node.js +1 -0
- package/dist/world/link.d.ts +9 -0
- package/dist/world/link.js +4 -3
- package/dist/world/regions.d.ts +9 -2
- package/dist/world/regions.js +34 -4
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,182 @@ missing method at the call site rather than at install time. Unlike
|
|
|
14
14
|
`@net-mesh/sdk`, this package never depends on `@net-mesh/core` — see
|
|
15
15
|
the README on why it is a sibling package rather than a sub-path.
|
|
16
16
|
|
|
17
|
+
## 0.44.0 — sharded matches, compact snapshots
|
|
18
|
+
|
|
19
|
+
- **`joinShards` merges a match split across several hosts** (topology A:
|
|
20
|
+
every player joins every host). It runs one `joinNetcode` per shard host
|
|
21
|
+
and draws each entity only from the shard that owns it in the installed
|
|
22
|
+
`ShardAssignment` (`{ epoch, hosts, owner }`, or an `ownerOf` resolver).
|
|
23
|
+
Inputs go only to the shard that owns you. A shard sending snapshots
|
|
24
|
+
from another epoch is frozen at its last drawn state, minus anything it
|
|
25
|
+
no longer owns, until it catches up or `setAssignment` installs its
|
|
26
|
+
epoch. When ownership changes, the old owner's copy lingers for
|
|
27
|
+
`lingerMs`. `stats()` reports each shard (with `silentMs`) and counts
|
|
28
|
+
what the fencing dropped.
|
|
29
|
+
- **NPCs on paths.** An NPC on a known route can be published once, as an
|
|
30
|
+
`NpcPath` record in the lobby store, instead of in every snapshot.
|
|
31
|
+
- `pathAction({ assignment })` is the store action: only the NPC's
|
|
32
|
+
owning host may write a record.
|
|
33
|
+
- `npcModes().resolve(view, records, ownerNow)` is what a client draws
|
|
34
|
+
from. It picks whichever source, snapshot or record, carries the
|
|
35
|
+
higher mode version `mv`. While a due record is late it carries the
|
|
36
|
+
NPC forward for up to 500 ms, and it blends every switch.
|
|
37
|
+
- `pathAt` evaluates a path.
|
|
38
|
+
|
|
39
|
+
Measured with 50 NPCs: 47 KB/s live, 1.6 KB/s on paths, per viewer.
|
|
40
|
+
- **NPCs in sharded matches.** A `ShardRecord` lists the NPCs its shard
|
|
41
|
+
owns (`npcs`). `npcShard(id, shards)` places one deterministically
|
|
42
|
+
(D7), and `withNpcs` spawns a batch. The validator refuses an NPC owned
|
|
43
|
+
twice.
|
|
44
|
+
- **Recovering a lost host's players.** `NetcodeClient.latest()` returns
|
|
45
|
+
the newest snapshot as the host sent it: the state a surviving host
|
|
46
|
+
restores a lost host's entities from. `adoptPlayer(peer, { resume: true
|
|
47
|
+
})` takes such a player's inputs from its next frame on, dropping what
|
|
48
|
+
it had pending (`dropped['resumed']`).
|
|
49
|
+
- **Planned moves between hosts.**
|
|
50
|
+
- `hostNetcode`'s `releasePlayer(peer)` returns `{ lastApplied }` and
|
|
51
|
+
stops taking that player's inputs, unapplied and unacknowledged;
|
|
52
|
+
`adoptPlayer(peer, { lastApplied })` resumes above it.
|
|
53
|
+
- `joinNetcode({ sequencer })` with `createInputSequencer()` shares one
|
|
54
|
+
input sequence across clients, which `joinShards` does for every shard.
|
|
55
|
+
|
|
56
|
+
A moved player's inputs carry on, and an input the old host applied is
|
|
57
|
+
never applied again.
|
|
58
|
+
- **A backlog of unacknowledged inputs is no longer cut to the newest 8.**
|
|
59
|
+
When more than `inputRedundancy` inputs are waiting (a high-latency
|
|
60
|
+
link, or a change of host), the next input frame carries all of them,
|
|
61
|
+
up to 64. Before, the older ones were lost for good.
|
|
62
|
+
- **Sharded matches get an assignment record and leases**
|
|
63
|
+
(`@net-mesh/browser/netcode`). `MatchAssignment` lives in the lobby's
|
|
64
|
+
store and gives shard hosts, the squads each shard owns, and a lease
|
|
65
|
+
per host:
|
|
66
|
+
- `assignmentProblems` validates it: each player is owned exactly once,
|
|
67
|
+
and no host owns its own squad.
|
|
68
|
+
- `assignAction({ lobby })` is the store action only the lobby node may
|
|
69
|
+
call.
|
|
70
|
+
- `renewLeases` bumps the counters.
|
|
71
|
+
- `leaseWatch` judges a counter on this node's own clock.
|
|
72
|
+
- `shardAssignment` gives the `joinShards` view.
|
|
73
|
+
- `authorizeTeams` admits `team:<name>` audiences.
|
|
74
|
+
|
|
75
|
+
`hostNetcode({ live })` stops simulating and sending once its lease is
|
|
76
|
+
lost. `joinShards({ live })` freezes the view (`stats().frozen`).
|
|
77
|
+
- **`NetcodeClient.sampleAt(hostTime)`** returns the host's entities as
|
|
78
|
+
of a host time, from the snapshots the client holds, plus the gap
|
|
79
|
+
between the two snapshots around that time. It returns `null` when no
|
|
80
|
+
two snapshots bracket the time. This is history, never extrapolated: a
|
|
81
|
+
shard host uses it to check a cross-host hit claim against where the
|
|
82
|
+
shooter really was. `SnapshotBuffer.sample` is the same read.
|
|
83
|
+
- **An optional epoch on the netcode wire.** `hostNetcode({ epoch })`
|
|
84
|
+
stamps `ep` on every snapshot and drops input frames stamped with
|
|
85
|
+
another epoch (`dropped['stale-epoch']`). `joinNetcode({ epoch })` stamps
|
|
86
|
+
inputs, and `stats()` gains `epoch` (from the newest snapshot) and
|
|
87
|
+
`lastSnapshotAt`. Binary snapshots with an epoch use a new kind byte, so
|
|
88
|
+
an older decoder refuses them rather than misreading them. Frames
|
|
89
|
+
without `ep` are unchanged.
|
|
90
|
+
|
|
91
|
+
- **Compact netcode snapshots.** `hostNetcode` and `joinNetcode` take an
|
|
92
|
+
optional `codec` built with `entityCodec({ x: quantized(0, 512), yaw:
|
|
93
|
+
angle(), hp: uint(8), ... })`. When both ends have the same codec,
|
|
94
|
+
snapshots are binary and about 7x smaller; otherwise they stay JSON, so a
|
|
95
|
+
page without a codec keeps working unchanged. Two new frames ride the
|
|
96
|
+
JSON wire: the codec fingerprint on join and ping (`f`), and a request
|
|
97
|
+
for entity names (`u`). New stats: `binaryFrames`, `unnamedEntities`.
|
|
98
|
+
- **`joinNetcode` says why it has not joined.** `await net.ready()`
|
|
99
|
+
resolves at the first snapshot and rejects with a typed `NetcodeError`
|
|
100
|
+
after `joinTimeoutMs` (default 15 s): `no-announcement` (this node never
|
|
101
|
+
heard the host announce, so it cannot reach it — the failure that used
|
|
102
|
+
to wait silently forever), `unreachable`, `timeout`, or `closed`. The
|
|
103
|
+
client keeps retrying regardless. `stats()` gains `connectOutcome` and
|
|
104
|
+
`lastError`.
|
|
105
|
+
- **A pair left on the relay is no longer re-offered on every reopen.**
|
|
106
|
+
When `joinNetcode`'s direct attempt ended relayed (`iceTimeout`, …),
|
|
107
|
+
every stream reopen started a fresh ICE attempt, and one finishing late
|
|
108
|
+
replaced the session under the stream. That was a loop that, under
|
|
109
|
+
load, never delivered a snapshot. The stream now rides the relayed
|
|
110
|
+
session, and direct is retried at most every `directRetryMs` (default
|
|
111
|
+
30 s). Send failures now show in `stats().lastError`.
|
|
112
|
+
- **A lossy stream no longer wedges after loss.** On a fire-and-forget
|
|
113
|
+
stream a lost packet's bytes were never credited back, so every loss
|
|
114
|
+
shrank the 64 KiB send window for good; after a window's worth the
|
|
115
|
+
stream refused every send (`backpressure … the peer has not granted
|
|
116
|
+
more yet`), and a reopen kept the drained state. Credit stalled for
|
|
117
|
+
2 s with bytes outstanding is now written off as lost (`net-mesh-wire`,
|
|
118
|
+
`FIRE_AND_FORGET_RECLAIM_AFTER`); reliable streams are unchanged.
|
|
119
|
+
- **A peer pair can no longer end half-direct.** When a slow answerer's
|
|
120
|
+
handshake reply crossed the offerer's deadline, the offerer fell back to
|
|
121
|
+
the relay while the answerer reported `direct` and sent on a channel the
|
|
122
|
+
offerer had closed: `peerAttempt` read `failed` on one end and `open`
|
|
123
|
+
with `direct: true` on the other, for good. An answerer is now direct
|
|
124
|
+
only once the establishment is proven, and an establishment its offerer
|
|
125
|
+
abandons returns the pair to its relay (`net-mesh-leaf`).
|
|
126
|
+
- **Lobbies, joiners and region hosts refresh their announcements every
|
|
127
|
+
30 s, not 2 s** (`LOBBY_ANNOUNCE_MS`, `REGION_ANNOUNCE_MS`). Every refresh
|
|
128
|
+
floods through the anchor to every peer, so the load grows with the square
|
|
129
|
+
of the players: at 64 players on one anchor a 2 s refresh pinned the
|
|
130
|
+
host's anchor link at its SCTP advisory, starving every relayed pair. A
|
|
131
|
+
change of players is still announced at once, and an announcement lives
|
|
132
|
+
300 s. `joinLobby` gains `announceEveryMs`, as `createLobby` has. A node that
|
|
133
|
+
connects later does not wait for a refresh: the anchor replays the
|
|
134
|
+
announcements it holds to each session that enrolls, within its game.
|
|
135
|
+
- **Stream data is routed to its stream, not filtered by every stream.**
|
|
136
|
+
Each `LeafStream`'s listener used to receive every node event and read
|
|
137
|
+
it to find its own, so a host with one stream per player paid players ×
|
|
138
|
+
events: at 64 players that was 13% of the host's main thread. Events are
|
|
139
|
+
now routed by peer and stream id inside the wasm, on both the
|
|
140
|
+
`connect()` and the `openSession()` paths (`net-mesh-leaf`). What a
|
|
141
|
+
listener receives is unchanged.
|
|
142
|
+
- **A direct connection one side no longer recognises now recovers.** If
|
|
143
|
+
a host held no session for a player that was still sending to it over
|
|
144
|
+
an open direct channel, every packet was dropped and nothing noticed:
|
|
145
|
+
the player's sends never fail and the connection stayed healthy. A
|
|
146
|
+
1 × 96 run lost 3 players that way for its whole 240 s. The side
|
|
147
|
+
without a session now tells its peer so on that channel and closes it
|
|
148
|
+
(and logs `closed the direct link`). The other side treats that notice
|
|
149
|
+
as a lost session, exactly as it already treated an ICE failure, and
|
|
150
|
+
connects again. Before, only ICE could report a loss, which takes its
|
|
151
|
+
30 s consent timeout after a peer leaves and never fires while the
|
|
152
|
+
path is healthy. A channel the peer merely closes — its re-offer
|
|
153
|
+
replacing the link, or its tab going away — is still not a loss: the
|
|
154
|
+
session stays, and an open call still ends on its own deadline.
|
|
155
|
+
- **Fewer clock pings once the clock has settled:** `pingRate` (default
|
|
156
|
+
4/s) until 8 samples, then `settledPingRate` (default 1/s). Every pong
|
|
157
|
+
pays a packet's fixed transport overhead; measured in real browsers
|
|
158
|
+
(1 host × 32 players) host upload fell from 2.19 to 1.82 Mbit/s.
|
|
159
|
+
- **Review fixes to this release's netcode** (sharded-match review):
|
|
160
|
+
- A client drops acknowledged inputs with or without `local`, so it no
|
|
161
|
+
longer sends 64-input frames for good after a backlog.
|
|
162
|
+
- Binary snapshots carry the host's instance: a host re-created under the
|
|
163
|
+
same label is no longer drawn under the old ids. The codec fingerprint
|
|
164
|
+
gains a wire version, so peers from before this change use JSON.
|
|
165
|
+
- `angle()` fields interpolate the short way (`lerpAngle`,
|
|
166
|
+
`EntityCodec.interpolate`, the default with a codec).
|
|
167
|
+
- `adoptPlayer`'s input boundary survives a slow first frame or an
|
|
168
|
+
`authorize` refusal (`dropped['adopt-unauthorized']`).
|
|
169
|
+
- A rejected `connectPeer` also waits `directRetryMs` before re-offering.
|
|
170
|
+
- Name tables forget ids unseen for 10 s (`NAME_FORGET_MS`); a `u` request
|
|
171
|
+
is honoured once per 100 ms per index, only for indexes that player was
|
|
172
|
+
shown (`dropped['name-request-throttled']`).
|
|
173
|
+
- Input seqs must be positive safe integers; `float32()` refuses NaN and
|
|
174
|
+
infinities; `sampleAt` at a snapshot's exact time includes entities that
|
|
175
|
+
first appear in it.
|
|
176
|
+
- A failed announcement (lobby, seek tag, region host) is retried after
|
|
177
|
+
1 s, doubling to 8 s (`ANNOUNCE_RETRY_MS`, `ANNOUNCE_RETRY_MAX_MS`).
|
|
178
|
+
- `joinShards`: a rejoined shard keeps its last state until its first
|
|
179
|
+
snapshot; `ready()` follows rejoins.
|
|
180
|
+
- `leaseWatch` takes its first reading as a baseline, not a lease;
|
|
181
|
+
`mayReassign` takes a `maxDeliveryMs` margin.
|
|
182
|
+
- `assignmentProblems` validates shapes, own keys and `renewals`;
|
|
183
|
+
`pathAction` matches NPC ids exactly. `npcModes` prunes its tracks,
|
|
184
|
+
builds path poses on the freshest entry, stops drawing a live NPC no
|
|
185
|
+
snapshot shows, and `stats()` reports `tracks`.
|
|
186
|
+
- `regionHandoffs` gains `actionTiming` for forwarded actions.
|
|
187
|
+
- The shard host recipe uses the netcode clock throughout (cross-host hits
|
|
188
|
+
were refused `beyond-cap` with the old `Date.now` default), keeps path
|
|
189
|
+
NPCs hittable and recoverable, and validates `setAssignment`.
|
|
190
|
+
- `retryReport()` gains `channelClosed`: a far-end channel close is its
|
|
191
|
+
own re-attempt trigger, and one channel's loss counts once.
|
|
192
|
+
|
|
17
193
|
## 0.43.0 — one identity per origin, bounded streams
|
|
18
194
|
|
|
19
195
|
- **`connect()` locks its identity.** With a custodial identity it takes
|