@genex-ai/multiplayer 0.8.6 → 0.8.8
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/README.md +7 -4
- package/dist/index.js +15 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -250,7 +250,8 @@ else renderSearchingHud(mm.matchmaking);
|
|
|
250
250
|
mm.on("matched", () => {/* session is live */});
|
|
251
251
|
mm.on("matchStart", ({ roundId }) => {/* round began */});
|
|
252
252
|
mm.on("matchEnded", ({ winnerId, scores, draw }) => {/* result screen */});
|
|
253
|
-
mm.on("spectate", () => {/*
|
|
253
|
+
mm.on("spectate", () => {/* DEPRECATED — never fires: there are no server-side spectators anymore.
|
|
254
|
+
An overflow searcher now waits in the queue instead of watching. */});
|
|
254
255
|
mm.on("error", (e) => {/* queue join failed even after the SDK's automatic retries — persistent
|
|
255
256
|
(broken auth / server unreachable); fix the cause, then mm.retry() */});
|
|
256
257
|
|
|
@@ -267,7 +268,7 @@ A failed queue join is **retried automatically** with backoff (~10s total), so t
|
|
|
267
268
|
(a token mid-rotation, identity still settling on page load) never surface. Only a persistent
|
|
268
269
|
failure emits `error`; the handle then idles until you call `mm.retry()`.
|
|
269
270
|
|
|
270
|
-
**`mm.matchmaking`** (read every frame for your HUD): `status` (`searching` | `waiting` | `countdown` | `playing` | `ended
|
|
271
|
+
**`mm.matchmaking`** (read every frame for your HUD): `status` (`searching` | `waiting` | `countdown` | `playing` | `ended`; the legacy `spectating` is never emitted — server-side spectators were removed), `queue` (`{ position, size }`), `players`, `opponents`, `teams`, `myTeam`, `scores`, `winCondition`, `config` (numeric knobs), `roundId`, `winnerId`, `message`. `championId`/`challengerId` are 1v1 aliases.
|
|
271
272
|
|
|
272
273
|
Configure once in the game's `package.json` (reported at `genex preview` AND `genex publish`;
|
|
273
274
|
removing it clears the stored config on the next preview/publish — never sent from the client):
|
|
@@ -275,14 +276,16 @@ removing it clears the stored config on the next preview/publish — never sent
|
|
|
275
276
|
```jsonc
|
|
276
277
|
"genex": {
|
|
277
278
|
"matchmaking": {
|
|
278
|
-
"preset": "arena", // duel | arena | teams | private
|
|
279
|
+
"preset": "arena", // open | duel | arena | teams | private
|
|
279
280
|
"winCondition": "firstToScore", // lastStanding | firstToScore | highScoreInTime | firstToFinish
|
|
280
281
|
"config": { "scoreTarget": 20, "maxPlayers": 8 } // numeric knobs, optional
|
|
281
282
|
}
|
|
282
283
|
}
|
|
283
284
|
```
|
|
284
285
|
|
|
285
|
-
Presets: **duel** (1v1 winner-stays), **arena** (N-player FFA, join-anytime), **teams** (balanced N-v-N), **private** (invite-code lobby). Win conditions: **lastStanding**, **firstToScore**, **highScoreInTime**, **firstToFinish**; a round that hits the time cap undecided is a draw.
|
|
286
|
+
Presets: **open** (a room of N players, your rules — see below), **duel** (1v1 winner-stays), **arena** (N-player FFA, join-anytime), **teams** (balanced N-v-N), **private** (invite-code lobby). Win conditions (for the batteries-included presets): **lastStanding**, **firstToScore**, **highScoreInTime**, **firstToFinish**; a round that hits the time cap undecided is a draw.
|
|
287
|
+
|
|
288
|
+
**`open` — bring your own rules.** The minimal preset: the server owns only seating, capacity, and refill; you build teams, rounds, scoring, and win logic yourself on `session.shared` + host election. It runs no match loop, so `status` only goes `waiting`→`playing` (never `ended`), `scores`/`winnerId` stay empty, and **nobody is ever evicted** (no `matchEnded`, no requeue). Config knobs: `minPlayers` (quorum to flip to `playing`; default 1), `maxPlayers` (seat cap, clamped 2–64), `fill` (`1` = keep the room full — grow to max and refill a seat a leaver frees, the default; `0` = lock the room for good once it is simultaneously full (max players at once) — a match with no substitutes; a freed seat stays closed). Pick `open` when you want your own rules; pick duel/arena/teams for a ready-made match loop.
|
|
286
289
|
|
|
287
290
|
### Private lobbies — `createPrivate` / `joinPrivate`
|
|
288
291
|
|
package/dist/index.js
CHANGED
|
@@ -189,6 +189,7 @@ function attachSession(client, initialRoom) {
|
|
|
189
189
|
const objOwners = /* @__PURE__ */ new Map();
|
|
190
190
|
const objRaw = /* @__PURE__ */ new Map();
|
|
191
191
|
const localObjState = /* @__PURE__ */ new Map();
|
|
192
|
+
const pendingBridge = /* @__PURE__ */ new Map();
|
|
192
193
|
let hostId;
|
|
193
194
|
const testSkew = typeof globalThis !== "undefined" && globalThis.__GENEX_MP_TEST_SKEW__ || 0;
|
|
194
195
|
const now = testSkew ? () => nowMs() + testSkew : nowMs;
|
|
@@ -223,6 +224,12 @@ function attachSession(client, initialRoom) {
|
|
|
223
224
|
}
|
|
224
225
|
let buf = buffers.get(id);
|
|
225
226
|
if (!buf) buffers.set(id, buf = []);
|
|
227
|
+
const bridge = pendingBridge.get(id);
|
|
228
|
+
if (bridge && buf.length === 0 && sendTime != null) {
|
|
229
|
+
const delay = autoDelayById.get(id) ?? INTERP_MIN_MS;
|
|
230
|
+
buf.push({ t: t - Math.max(1, delay), state: bridge });
|
|
231
|
+
}
|
|
232
|
+
pendingBridge.delete(id);
|
|
226
233
|
buf.push({ t, state });
|
|
227
234
|
const cutoff = t - 1500;
|
|
228
235
|
while (buf.length > 2 && buf[0].t < cutoff) buf.shift();
|
|
@@ -255,6 +262,7 @@ function attachSession(client, initialRoom) {
|
|
|
255
262
|
smoothedAt.delete(streamKey);
|
|
256
263
|
intervalsById.delete(streamKey);
|
|
257
264
|
autoDelayById.delete(streamKey);
|
|
265
|
+
pendingBridge.delete(streamKey);
|
|
258
266
|
};
|
|
259
267
|
const clearStreamClock = (streamKey) => {
|
|
260
268
|
buffers.delete(streamKey);
|
|
@@ -311,7 +319,13 @@ function attachSession(client, initialRoom) {
|
|
|
311
319
|
}
|
|
312
320
|
if (blob.d && typeof blob.d === "object" && Object.keys(blob.d).length > 0) {
|
|
313
321
|
objRaw.set(id, blob.d);
|
|
314
|
-
if (owner !== room.sessionId)
|
|
322
|
+
if (owner !== room.sessionId) {
|
|
323
|
+
if (ownerChanged) {
|
|
324
|
+
pendingBridge.set(key, smoothed.get(key) ?? blob.d);
|
|
325
|
+
} else {
|
|
326
|
+
pushSnap(key, blob.d, blob.t);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
315
329
|
}
|
|
316
330
|
if (ownerChanged) emit("object", id);
|
|
317
331
|
return;
|