@genex-ai/multiplayer 0.10.1 → 0.10.2
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 +12 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,13 @@ across reconnects. Never log it.
|
|
|
34
34
|
|
|
35
35
|
Different `room` ids are **fully isolated** — players in game A never see game B.
|
|
36
36
|
|
|
37
|
+
`connect()` joins immediately. Use it for one ongoing drop-in world. If the game has a
|
|
38
|
+
Play/Online/Local/Bots menu, call it only after the online click and call `room.leave()` when the
|
|
39
|
+
player returns to the offline menu. Immediate connect + spawn is valid only when loading the game
|
|
40
|
+
already means joining the always-online world; do not put a fake Play screen in front of a player
|
|
41
|
+
who is already present. Use `matchmake()` below for fresh bounded sessions with queue/quorum/fair
|
|
42
|
+
start/backfill behavior.
|
|
43
|
+
|
|
37
44
|
---
|
|
38
45
|
|
|
39
46
|
## `me` — publish your own state
|
|
@@ -101,7 +108,11 @@ room.objects.snap('ball', { x: 0, y: 1, z: 0, q: [0, 0, 0, 1] });
|
|
|
101
108
|
```
|
|
102
109
|
|
|
103
110
|
Keep object state **flat** (top-level numbers + a 4-number quaternion) so it smooths, same as a
|
|
104
|
-
player. `state` is smoothed; `stateRaw` is the raw latest
|
|
111
|
+
player. `state` is render-only and smoothed; `stateRaw` is the raw latest for every gameplay read
|
|
112
|
+
(hit-tests, reach, pickups, physics adoption, discrete values). `objects.ids()` lists them.
|
|
113
|
+
`objects.set()` and `objects.snap()` are owner-only: SDK 0.10.2+ ignores an unowned call and warns
|
|
114
|
+
once per object/operation. Await `claimConfirmed()` and check `accepted` before an irreversible
|
|
115
|
+
write; legacy `claim(); set()` remains valid because `claim()` is optimistic locally.
|
|
105
116
|
|
|
106
117
|
The relay enforces a short minimum ownership hold to prevent contact thrash. A current-owner
|
|
107
118
|
reassert is a true no-op: it does not extend that hold. Legacy `claim()` remains optimistic for
|
package/dist/index.d.ts
CHANGED
|
@@ -34,8 +34,9 @@ type PlayerView<TState> = {
|
|
|
34
34
|
name: string;
|
|
35
35
|
/** Transport is live now. False while this reserved seat is inside reconnect grace. */
|
|
36
36
|
connected: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* for yourself it's live. Numeric fields are lerped; everything else snaps.
|
|
37
|
+
/** RENDER ONLY — for remote players this is **auto-smoothed** (interpolated) and delayed;
|
|
38
|
+
* for yourself it's live. Numeric fields are lerped; everything else snaps. Use `stateRaw`
|
|
39
|
+
* for every gameplay read (hits, reach, pickups, flags, physics adoption). */
|
|
39
40
|
state: TState;
|
|
40
41
|
/** The latest raw snapshot (never smoothed/delayed) — use for hit-tests / logic. */
|
|
41
42
|
stateRaw: TState;
|
|
@@ -49,8 +50,8 @@ type ObjectView<TState> = {
|
|
|
49
50
|
owner: string | undefined;
|
|
50
51
|
/** True if you own it (so you should simulate + `objects.set` it). */
|
|
51
52
|
isMine: boolean;
|
|
52
|
-
/**
|
|
53
|
-
* live (your last `set`) when it's yours. */
|
|
53
|
+
/** RENDER ONLY — **auto-smoothed** and delayed like a remote player when someone else owns it;
|
|
54
|
+
* live (your last `set`) when it's yours. Use `stateRaw` for every gameplay read. */
|
|
54
55
|
state: TState;
|
|
55
56
|
/** The latest raw snapshot (never smoothed/delayed) — for hit-tests / logic. */
|
|
56
57
|
stateRaw: TState;
|
package/dist/index.js
CHANGED
|
@@ -208,6 +208,7 @@ function attachSession(client, initialRoom) {
|
|
|
208
208
|
const objEpochs = /* @__PURE__ */ new Map();
|
|
209
209
|
const objRaw = /* @__PURE__ */ new Map();
|
|
210
210
|
const localObjState = /* @__PURE__ */ new Map();
|
|
211
|
+
const warnedUnownedWrites = /* @__PURE__ */ new Set();
|
|
211
212
|
const pendingBridge = /* @__PURE__ */ new Map();
|
|
212
213
|
let hostId;
|
|
213
214
|
let objectControlVersion = 0;
|
|
@@ -215,6 +216,17 @@ function attachSession(client, initialRoom) {
|
|
|
215
216
|
const pendingControls = /* @__PURE__ */ new Map();
|
|
216
217
|
const testSkew = typeof globalThis !== "undefined" && globalThis.__GENEX_MP_TEST_SKEW__ || 0;
|
|
217
218
|
const now = testSkew ? () => nowMs() + testSkew : nowMs;
|
|
219
|
+
const rejectUnownedWrite = (operation, id) => {
|
|
220
|
+
if (objOwners.get(id) === room.sessionId) return false;
|
|
221
|
+
const warningKey = `${operation}:${id}`;
|
|
222
|
+
if (!warnedUnownedWrites.has(warningKey)) {
|
|
223
|
+
warnedUnownedWrites.add(warningKey);
|
|
224
|
+
console.warn(
|
|
225
|
+
`[multiplayer] objects.${operation}('${id}') ignored because this session does not own the object \u2014 await objects.claimConfirmed('${id}') and check accepted before publishing`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
};
|
|
218
230
|
const pushSnap = (id, state, sendTime) => {
|
|
219
231
|
const arrival = now();
|
|
220
232
|
const t = sendTime ?? arrival;
|
|
@@ -801,12 +813,14 @@ function attachSession(client, initialRoom) {
|
|
|
801
813
|
},
|
|
802
814
|
set(id, state) {
|
|
803
815
|
const key = String(id);
|
|
816
|
+
if (rejectUnownedWrite("set", key)) return;
|
|
804
817
|
const clean = sanitizeFinite(state);
|
|
805
818
|
if (objOwners.get(key) === room.sessionId) localObjState.set(key, clean);
|
|
806
819
|
send0("obj", { id: key, d: clean, t: now() });
|
|
807
820
|
},
|
|
808
821
|
snap(id, state) {
|
|
809
822
|
const key = String(id);
|
|
823
|
+
if (rejectUnownedWrite("snap", key)) return;
|
|
810
824
|
const clean = sanitizeFinite(state);
|
|
811
825
|
if (objOwners.get(key) === room.sessionId) localObjState.set(key, clean);
|
|
812
826
|
send0(OBJECT_SNAP_MSG, { id: key, d: clean, t: now() });
|