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
@@ -0,0 +1,43 @@
1
+ import { isUint, MAX_STATE_BYTES } from '../core/protocol.js';
2
+ /** Bundle recent inputs as redundancy without enabling retransmission of stale movement. */
3
+ export function encodeInputs(inputs) {
4
+ if (!inputs.length || inputs.length > 32)
5
+ throw new RangeError('Invalid input count');
6
+ let size = 1, previous = -1;
7
+ for (const input of inputs) {
8
+ if (!isUint(input.seq) || input.seq <= previous || input.data.length > 1024)
9
+ throw new RangeError('Invalid input');
10
+ previous = input.seq;
11
+ size += 6 + input.data.length;
12
+ }
13
+ if (size > MAX_STATE_BYTES)
14
+ throw new RangeError('Input bundle exceeds one state frame');
15
+ const b = new Uint8Array(size), v = new DataView(b.buffer);
16
+ b[0] = inputs.length;
17
+ let at = 1;
18
+ for (const i of inputs) {
19
+ v.setUint32(at, i.seq, true);
20
+ v.setUint16(at + 4, i.data.length, true);
21
+ b.set(i.data, at + 6);
22
+ at += 6 + i.data.length;
23
+ }
24
+ return b;
25
+ }
26
+ export function decodeInputs(b) {
27
+ if (!b.length || b.length > MAX_STATE_BYTES || !b[0] || b[0] > 32)
28
+ return null;
29
+ const v = new DataView(b.buffer, b.byteOffset, b.byteLength), out = [];
30
+ let at = 1, previous = -1;
31
+ for (let n = 0; n < b[0]; n++) {
32
+ if (at + 6 > b.length)
33
+ return null;
34
+ const seq = v.getUint32(at, true), size = v.getUint16(at + 4, true);
35
+ if (seq <= previous || size > 1024 || at + 6 + size > b.length)
36
+ return null;
37
+ out.push({ seq, data: b.slice(at + 6, at + 6 + size) });
38
+ previous = seq;
39
+ at += 6 + size;
40
+ }
41
+ return at === b.length ? out : null;
42
+ }
43
+ //# sourceMappingURL=inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inputs.js","sourceRoot":"","sources":["../../src/netcode/inputs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ export interface Timed<T> {
2
+ time: number;
3
+ seq: number;
4
+ value: T;
5
+ discontinuity?: boolean;
6
+ }
7
+ export interface SampleResult<T> {
8
+ value: T;
9
+ mode: 'hold' | 'interpolate' | 'extrapolate';
10
+ sourceTime: number;
11
+ }
12
+ /** Per-entity history. Discontinuities take effect at their timestamp; they are never blended across. */
13
+ export declare class Timeline<T> {
14
+ readonly capacity: number;
15
+ readonly clone: (value: T) => T;
16
+ private samples;
17
+ constructor(capacity?: number, clone?: (value: T) => T);
18
+ get size(): number;
19
+ clear(): void;
20
+ insert(sample: Timed<T>): boolean;
21
+ sample(at: number, interpolate: (a: T, b: T, fraction: number) => T, extrapolate?: (last: T, previous: T, aheadMs: number, sampleIntervalMs: number) => T, maxExtrapolationMs?: number): SampleResult<T> | null;
22
+ }
23
+ export declare class AdaptivePlayback {
24
+ readonly intervalMs: number;
25
+ readonly minMs: number;
26
+ readonly maxMs: number;
27
+ private lateness;
28
+ private cursor;
29
+ delayMs: number;
30
+ constructor(intervalMs: number, minMs?: number, maxMs?: number);
31
+ observe(arrivalOnServerClock: number, sampleTime: number): void;
32
+ advance(serverNow: number, dtMs: number): number;
33
+ reset(): void;
34
+ }
35
+ export declare const lerp: (a: number, b: number, t: number) => number;
36
+ export declare const lerpAngle: (a: number, b: number, t: number) => number;
37
+ export interface Vec3 {
38
+ x: number;
39
+ y: number;
40
+ z: number;
41
+ }
42
+ export declare const lerpVec3: (a: Vec3, b: Vec3, t: number) => Vec3;
43
+ /** Validate rewind requests against a server-owned time window. This does not itself validate hits or authority. */
44
+ export declare function rewindTime(requested: number, now: number, maxRewindMs: number): number;
@@ -0,0 +1,101 @@
1
+ /** Per-entity history. Discontinuities take effect at their timestamp; they are never blended across. */
2
+ export class Timeline {
3
+ capacity;
4
+ clone;
5
+ samples = [];
6
+ constructor(capacity = 64, clone = (v) => structuredClone(v)) {
7
+ this.capacity = capacity;
8
+ this.clone = clone;
9
+ if (!Number.isInteger(capacity) || capacity < 2 || capacity > 4096)
10
+ throw new RangeError('Invalid history capacity');
11
+ }
12
+ get size() { return this.samples.length; }
13
+ clear() { this.samples = []; }
14
+ insert(sample) {
15
+ if (!Number.isFinite(sample.time) || !Number.isSafeInteger(sample.seq) || sample.seq < 0)
16
+ throw new RangeError('Invalid snapshot');
17
+ if (this.samples.some(s => s.seq === sample.seq))
18
+ return false;
19
+ const same = this.samples.findIndex(s => s.time === sample.time);
20
+ if (same >= 0) {
21
+ if (this.samples[same].seq > sample.seq)
22
+ return false;
23
+ this.samples.splice(same, 1);
24
+ }
25
+ const copy = { ...sample, value: this.clone(sample.value) };
26
+ const i = this.samples.findIndex(s => s.time > sample.time);
27
+ if (i < 0)
28
+ this.samples.push(copy);
29
+ else
30
+ this.samples.splice(i, 0, copy);
31
+ while (this.samples.length > this.capacity)
32
+ this.samples.shift();
33
+ return true;
34
+ }
35
+ sample(at, interpolate, extrapolate, maxExtrapolationMs = 100) {
36
+ if (!Number.isFinite(at) || !Number.isFinite(maxExtrapolationMs) || maxExtrapolationMs < 0)
37
+ throw new RangeError('Invalid sample time');
38
+ const s = this.samples;
39
+ if (!s.length || s[0].time > at)
40
+ return null;
41
+ let i = s.length - 1;
42
+ while (i > 0 && s[i].time > at)
43
+ i--;
44
+ const a = s[i], b = s[i + 1];
45
+ if (b && !b.discontinuity)
46
+ return { value: interpolate(this.clone(a.value), this.clone(b.value), (at - a.time) / (b.time - a.time)), mode: 'interpolate', sourceTime: a.time };
47
+ if (!b && extrapolate && i > 0 && at > a.time && !a.discontinuity) {
48
+ const p = s[i - 1];
49
+ return { value: extrapolate(this.clone(a.value), this.clone(p.value), Math.min(at - a.time, maxExtrapolationMs), a.time - p.time), mode: 'extrapolate', sourceTime: a.time };
50
+ }
51
+ return { value: this.clone(a.value), mode: 'hold', sourceTime: a.time };
52
+ }
53
+ }
54
+ export class AdaptivePlayback {
55
+ intervalMs;
56
+ minMs;
57
+ maxMs;
58
+ lateness = [];
59
+ cursor = -Infinity;
60
+ delayMs;
61
+ constructor(intervalMs, minMs = intervalMs, maxMs = 400) {
62
+ this.intervalMs = intervalMs;
63
+ this.minMs = minMs;
64
+ this.maxMs = maxMs;
65
+ if (!(intervalMs > 0) || minMs < 0 || maxMs < minMs)
66
+ throw new RangeError('Invalid playback policy');
67
+ this.delayMs = minMs;
68
+ }
69
+ observe(arrivalOnServerClock, sampleTime) {
70
+ const late = arrivalOnServerClock - sampleTime;
71
+ if (!Number.isFinite(late) || late < -1000 || late > 30000)
72
+ return;
73
+ this.lateness.push(Math.max(0, late));
74
+ if (this.lateness.length > 128)
75
+ this.lateness.shift();
76
+ }
77
+ advance(serverNow, dtMs) {
78
+ if (!Number.isFinite(serverNow) || !Number.isFinite(dtMs) || dtMs < 0)
79
+ throw new RangeError('Invalid clock');
80
+ if (this.lateness.length) {
81
+ const sorted = [...this.lateness].sort((a, b) => a - b);
82
+ const p95 = sorted[Math.floor((sorted.length - 1) * 0.95)];
83
+ const target = Math.max(this.minMs, Math.min(this.maxMs, p95 + this.intervalMs));
84
+ const rate = target > this.delayMs ? 8 : 0.5;
85
+ this.delayMs += (target - this.delayMs) * (1 - Math.exp(-rate * dtMs / 1000));
86
+ }
87
+ this.cursor = Math.max(this.cursor, serverNow - this.delayMs);
88
+ return this.cursor;
89
+ }
90
+ reset() { this.lateness = []; this.cursor = -Infinity; this.delayMs = this.minMs; }
91
+ }
92
+ export const lerp = (a, b, t) => a + (b - a) * t;
93
+ export const lerpAngle = (a, b, t) => a + Math.atan2(Math.sin(b - a), Math.cos(b - a)) * t;
94
+ export const lerpVec3 = (a, b, t) => ({ x: lerp(a.x, b.x, t), y: lerp(a.y, b.y, t), z: lerp(a.z, b.z, t) });
95
+ /** Validate rewind requests against a server-owned time window. This does not itself validate hits or authority. */
96
+ export function rewindTime(requested, now, maxRewindMs) {
97
+ if (![requested, now, maxRewindMs].every(Number.isFinite) || maxRewindMs < 0)
98
+ throw new RangeError('Invalid rewind');
99
+ return Math.max(now - maxRewindMs, Math.min(now, requested));
100
+ }
101
+ //# sourceMappingURL=interpolation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../src/netcode/interpolation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ export interface Input<I> {
2
+ seq: number;
3
+ value: I;
4
+ }
5
+ /** Game-owned step/clone functions keep physics, collision and authority policy outside the transport. */
6
+ export declare class Predictor<S, I> {
7
+ readonly step: (state: S, input: I, dtSeconds: number) => S;
8
+ readonly dtSeconds: number;
9
+ readonly maxPending: number;
10
+ readonly cloneState: (s: S) => S;
11
+ readonly cloneInput: (i: I) => I;
12
+ state: S;
13
+ private pending;
14
+ private seq;
15
+ private ack;
16
+ private serverTick;
17
+ constructor(initial: S, step: (state: S, input: I, dtSeconds: number) => S, dtSeconds?: number, maxPending?: number, cloneState?: (s: S) => S, cloneInput?: (i: I) => I);
18
+ get lastInput(): number;
19
+ get pendingCount(): number;
20
+ push(value: I): Input<I>;
21
+ reconcile(snapshot: {
22
+ ack: number;
23
+ tick: number;
24
+ state: S;
25
+ }): boolean;
26
+ recent(count?: number): Input<I>[];
27
+ resync(state: S, tick: number, ack?: number): void;
28
+ }
29
+ /** Receiver for redundant input bundles. ACK only the last input actually taken by the simulation. */
30
+ export declare class InputInbox<I> {
31
+ readonly capacity: number;
32
+ readonly clone: (i: I) => I;
33
+ applied: number;
34
+ private queued;
35
+ constructor(capacity?: number, clone?: (i: I) => I);
36
+ add(input: Input<I>): boolean;
37
+ next(): Input<I> | null;
38
+ /** Explicit game policy after an expired input gap. Never called automatically. */
39
+ skipThrough(seq: number): void;
40
+ }
@@ -0,0 +1,97 @@
1
+ /** Game-owned step/clone functions keep physics, collision and authority policy outside the transport. */
2
+ export class Predictor {
3
+ step;
4
+ dtSeconds;
5
+ maxPending;
6
+ cloneState;
7
+ cloneInput;
8
+ state;
9
+ pending = [];
10
+ seq = 0;
11
+ ack = 0;
12
+ serverTick = -1;
13
+ constructor(initial, step, dtSeconds = 1 / 60, maxPending = 256, cloneState = (v) => structuredClone(v), cloneInput = (v) => structuredClone(v)) {
14
+ this.step = step;
15
+ this.dtSeconds = dtSeconds;
16
+ this.maxPending = maxPending;
17
+ this.cloneState = cloneState;
18
+ this.cloneInput = cloneInput;
19
+ if (!(dtSeconds > 0) || !Number.isFinite(dtSeconds) || !Number.isInteger(maxPending) || maxPending < 1)
20
+ throw new RangeError('Invalid prediction options');
21
+ this.state = cloneState(initial);
22
+ }
23
+ get lastInput() { return this.seq; }
24
+ get pendingCount() { return this.pending.length; }
25
+ push(value) {
26
+ if (this.pending.length >= this.maxPending)
27
+ throw new Error('Prediction history full: resync required');
28
+ if (this.seq >= 0xffffffff)
29
+ throw new Error('Input sequence exhausted: resync required');
30
+ const frame = { seq: ++this.seq, value: this.cloneInput(value) };
31
+ this.pending.push(frame);
32
+ this.state = this.step(this.cloneState(this.state), this.cloneInput(value), this.dtSeconds);
33
+ return { seq: frame.seq, value: this.cloneInput(frame.value) };
34
+ }
35
+ reconcile(snapshot) {
36
+ const { ack, tick } = snapshot;
37
+ if (!Number.isSafeInteger(ack) || ack < 0 || ack > this.seq || !Number.isSafeInteger(tick) || tick < 0)
38
+ throw new Error('Invalid authoritative snapshot');
39
+ if (tick <= this.serverTick)
40
+ return false;
41
+ if (ack < this.ack)
42
+ throw new Error('Authoritative acknowledgement regressed');
43
+ this.serverTick = tick;
44
+ this.ack = ack;
45
+ this.pending = this.pending.filter(i => i.seq > ack);
46
+ let state = this.cloneState(snapshot.state);
47
+ for (const i of this.pending)
48
+ state = this.step(state, this.cloneInput(i.value), this.dtSeconds);
49
+ this.state = state;
50
+ return true;
51
+ }
52
+ recent(count = 3) { return (count <= 0 ? [] : this.pending.slice(-Math.floor(count))).map(i => ({ seq: i.seq, value: this.cloneInput(i.value) })); }
53
+ resync(state, tick, ack = 0) {
54
+ if (!Number.isSafeInteger(tick) || tick < 0 || !Number.isSafeInteger(ack) || ack < 0 || ack > 0xffffffff)
55
+ throw new RangeError('Invalid resync');
56
+ this.state = this.cloneState(state);
57
+ this.pending = [];
58
+ this.serverTick = tick;
59
+ this.ack = this.seq = ack;
60
+ }
61
+ }
62
+ /** Receiver for redundant input bundles. ACK only the last input actually taken by the simulation. */
63
+ export class InputInbox {
64
+ capacity;
65
+ clone;
66
+ applied = 0;
67
+ queued = new Map();
68
+ constructor(capacity = 256, clone = (v) => structuredClone(v)) {
69
+ this.capacity = capacity;
70
+ this.clone = clone;
71
+ }
72
+ add(input) {
73
+ if (!Number.isSafeInteger(input.seq) || input.seq <= this.applied || input.seq > this.applied + this.capacity || this.queued.has(input.seq))
74
+ return false;
75
+ this.queued.set(input.seq, this.clone(input.value));
76
+ return true;
77
+ }
78
+ next() {
79
+ const seq = this.applied + 1;
80
+ if (!this.queued.has(seq))
81
+ return null;
82
+ const value = this.queued.get(seq);
83
+ this.queued.delete(seq);
84
+ this.applied = seq;
85
+ return { seq, value };
86
+ }
87
+ /** Explicit game policy after an expired input gap. Never called automatically. */
88
+ skipThrough(seq) {
89
+ if (!Number.isSafeInteger(seq) || seq < this.applied || seq > this.applied + this.capacity)
90
+ throw new RangeError('Invalid input skip');
91
+ for (const n of this.queued.keys())
92
+ if (n <= seq)
93
+ this.queued.delete(n);
94
+ this.applied = seq;
95
+ }
96
+ }
97
+ //# sourceMappingURL=prediction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prediction.js","sourceRoot":"","sources":["../../src/netcode/prediction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ export interface Version {
2
+ id: number;
3
+ generation: number;
4
+ revision: number;
5
+ }
6
+ export interface ReplicaUpdate extends Version {
7
+ deleted: boolean;
8
+ data: Uint8Array;
9
+ }
10
+ /** One instance per receiver. Whole small entity records are resent until acknowledged.
11
+ * This intentionally does not depend on an unacknowledged delta baseline. */
12
+ export declare class ReplicaSender {
13
+ readonly maxEntities: number;
14
+ readonly retryMs: number;
15
+ private records;
16
+ constructor(maxEntities?: number, retryMs?: number);
17
+ publish(id: number, generation: number, data: Uint8Array | null): Version;
18
+ due(now: number, limit?: number): ReplicaUpdate[];
19
+ markSent(v: Version, now: number): void;
20
+ acknowledge(v: Version): boolean;
21
+ /** Reset both sender and receiver under a new world/session generation, not while old packets remain valid. */
22
+ clear(): void;
23
+ }
24
+ export declare class ReplicaReceiver {
25
+ readonly maxEntities: number;
26
+ private records;
27
+ constructor(maxEntities?: number);
28
+ apply(u: ReplicaUpdate): {
29
+ changed: boolean;
30
+ ack: Version;
31
+ };
32
+ get(id: number): Uint8Array | null;
33
+ clear(): void;
34
+ }
35
+ export declare function encodeReplica(u: ReplicaUpdate): Uint8Array;
36
+ export declare function decodeReplica(b: Uint8Array): ReplicaUpdate | null;
@@ -0,0 +1,90 @@
1
+ import { isUint } from '../core/protocol.js';
2
+ const sameBytes = (a, b) => a.length === b.length && a.every((v, i) => b[i] === v);
3
+ /** One instance per receiver. Whole small entity records are resent until acknowledged.
4
+ * This intentionally does not depend on an unacknowledged delta baseline. */
5
+ export class ReplicaSender {
6
+ maxEntities;
7
+ retryMs;
8
+ records = new Map();
9
+ constructor(maxEntities = 1024, retryMs = 100) {
10
+ this.maxEntities = maxEntities;
11
+ this.retryMs = retryMs;
12
+ }
13
+ publish(id, generation, data) {
14
+ if (!isUint(id, 65535) || !isUint(generation) || generation < 1 || (data?.length ?? 0) > 1067)
15
+ throw new RangeError('Invalid entity');
16
+ const old = this.records.get(id), deleted = data === null, bytes = data?.slice() ?? new Uint8Array();
17
+ if (!old && this.records.size >= this.maxEntities)
18
+ throw new Error('Replica entity limit');
19
+ if (old && generation < old.update.generation)
20
+ throw new Error('Entity generation regressed');
21
+ if (old && generation === old.update.generation && old.update.deleted === deleted && sameBytes(old.update.data, bytes))
22
+ return { id, generation, revision: old.update.revision };
23
+ const revision = old && generation === old.update.generation ? old.update.revision + 1 : 1;
24
+ if (revision > 0xffffffff)
25
+ throw new Error('Entity revision exhausted');
26
+ const update = { id, generation, revision, deleted, data: bytes };
27
+ this.records.set(id, { update, ack: old && generation === old.update.generation ? old.ack : 0, sent: 0, sentAt: -Infinity });
28
+ return { id, generation, revision };
29
+ }
30
+ due(now, limit = 64) {
31
+ return [...this.records.values()].filter(r => r.ack < r.update.revision && now - r.sentAt >= this.retryMs)
32
+ .sort((a, b) => a.sentAt - b.sentAt).slice(0, limit).map(r => ({ ...r.update, data: r.update.data.slice() }));
33
+ }
34
+ markSent(v, now) {
35
+ const r = this.records.get(v.id);
36
+ if (!r || r.update.generation !== v.generation || r.update.revision !== v.revision)
37
+ return;
38
+ r.sent = v.revision;
39
+ r.sentAt = now;
40
+ }
41
+ acknowledge(v) {
42
+ const r = this.records.get(v.id);
43
+ if (!r || r.update.generation !== v.generation || !isUint(v.revision) || v.revision > r.sent)
44
+ return false;
45
+ r.ack = Math.max(r.ack, v.revision);
46
+ return true;
47
+ }
48
+ /** Reset both sender and receiver under a new world/session generation, not while old packets remain valid. */
49
+ clear() { this.records.clear(); }
50
+ }
51
+ export class ReplicaReceiver {
52
+ maxEntities;
53
+ records = new Map();
54
+ constructor(maxEntities = 1024) {
55
+ this.maxEntities = maxEntities;
56
+ }
57
+ apply(u) {
58
+ if (!isUint(u.id, 65535) || !isUint(u.generation) || !u.generation || !isUint(u.revision) || !u.revision || u.data.length > 1067)
59
+ throw new Error('Invalid entity update');
60
+ const old = this.records.get(u.id);
61
+ if (!old && this.records.size >= this.maxEntities)
62
+ throw new Error('Replica receiver full');
63
+ const changed = !old || u.generation > old.generation || u.generation === old.generation && u.revision > old.revision;
64
+ if (changed)
65
+ this.records.set(u.id, { ...u, data: u.data.slice() });
66
+ return { changed, ack: { id: u.id, generation: u.generation, revision: u.revision } };
67
+ }
68
+ get(id) { const r = this.records.get(id); return r && !r.deleted ? r.data.slice() : null; }
69
+ clear() { this.records.clear(); }
70
+ }
71
+ export function encodeReplica(u) {
72
+ if (!isUint(u.id, 65535) || !isUint(u.generation) || !u.generation || !isUint(u.revision) || !u.revision || u.data.length > 1067 || u.deleted && u.data.length)
73
+ throw new Error('Invalid entity update');
74
+ const b = new Uint8Array(11 + u.data.length), v = new DataView(b.buffer);
75
+ v.setUint16(0, u.id, true);
76
+ v.setUint32(2, u.generation, true);
77
+ v.setUint32(6, u.revision, true);
78
+ v.setUint8(10, +u.deleted);
79
+ b.set(u.data, 11);
80
+ return b;
81
+ }
82
+ export function decodeReplica(b) {
83
+ if (b.length < 11 || b.length > 1078)
84
+ return null;
85
+ const v = new DataView(b.buffer, b.byteOffset, b.byteLength), flag = v.getUint8(10), generation = v.getUint32(2, true), revision = v.getUint32(6, true);
86
+ if (flag > 1 || !generation || !revision || flag && b.length !== 11)
87
+ return null;
88
+ return { id: v.getUint16(0, true), generation, revision, deleted: !!flag, data: b.slice(11) };
89
+ }
90
+ //# sourceMappingURL=replication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replication.js","sourceRoot":"","sources":["../../src/netcode/replication.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAI7C,MAAM,SAAS,GAAG,CAAC,CAAa,EAAE,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3G;6EAC6E;AAC7E,MAAM,OAAO,aAAa;IAEH;IAA6B;IAD1C,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC9C,YAAqB,cAAc,IAAI,EAAW,UAAU,GAAG;QAA1C,gBAAW,GAAX,WAAW,CAAO;QAAW,YAAO,GAAP,OAAO,CAAM;IAAG,CAAC;IACnE,OAAO,CAAC,EAAU,EAAE,UAAkB,EAAE,IAAuB;QAC7D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,IAAI;YAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,CAAC,CAAC;QACtI,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,UAAU,EAAE,CAAC;QACrG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC3F,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC9F,IAAI,GAAG,IAAI,UAAU,KAAK,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,OAAO,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACjL,MAAM,QAAQ,GAAG,GAAG,IAAI,UAAU,KAAK,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3F,IAAI,QAAQ,GAAG,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAClE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,UAAU,KAAK,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC7H,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACtC,CAAC;IACD,GAAG,CAAC,GAAW,EAAE,KAAK,GAAG,EAAE;QACzB,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,GAAG,GAAG,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC;aACvG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IAClH,CAAC;IACD,QAAQ,CAAC,CAAU,EAAE,GAAW;QAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;YAAE,OAAO;QAC7H,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC;QAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC;IACtC,CAAC;IACD,WAAW,CAAC,CAAU;QACpB,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QAC3G,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IACnD,CAAC;IACD,+GAA+G;IAC/G,KAAK,KAAW,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;CACxC;AACD,MAAM,OAAO,eAAe;IAEL;IADb,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IACnD,YAAqB,cAAc,IAAI;QAAlB,gBAAW,GAAX,WAAW,CAAO;IAAG,CAAC;IAC3C,KAAK,CAAC,CAAgB;QACpB,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC3K,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC5F,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QACtH,IAAI,OAAO;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;IACxF,CAAC;IACD,GAAG,CAAC,EAAU,IAAuB,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACtH,KAAK,KAAW,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;CACxC;AACD,MAAM,UAAU,aAAa,CAAC,CAAgB;IAC5C,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM;QAC5J,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC3C,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACzE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC7H,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAAC,OAAO,CAAC,CAAC;AAC9B,CAAC;AACD,MAAM,UAAU,aAAa,CAAC,CAAa;IACzC,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI;QAAE,OAAO,IAAI,CAAC;IAClD,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACxJ,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACjF,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;AAChG,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { Json } from '../core/protocol.js';
2
+ /**
3
+ * What was sent, by state seq, so the next snapshot can be a delta against the newest one the receiver acknowledged
4
+ * (send with `ack: true`, feed onStateAck to acknowledge). Lost snapshots cost nothing but a larger next delta.
5
+ * Reset on a new connection epoch (the seqs restart) or a world reset.
6
+ */
7
+ export declare class SnapshotHistory<T> {
8
+ readonly capacity: number;
9
+ private sent;
10
+ private acked;
11
+ constructor(capacity?: number);
12
+ record(seq: number, value: T): void;
13
+ acknowledge(seq: number): boolean;
14
+ /** The acknowledged snapshot to diff against, or null for a whole snapshot. */
15
+ get baseline(): {
16
+ seq: number;
17
+ value: T;
18
+ } | null;
19
+ reset(): void;
20
+ }
21
+ export type Entities = Record<string, Record<string, Json>>;
22
+ /** Field-level difference between two entity maps: changed fields per entity, and removed entities. */
23
+ export interface EntityDelta {
24
+ set?: Entities;
25
+ del?: string[];
26
+ }
27
+ export declare function diffEntities(base: Entities | null, next: Entities): EntityDelta;
28
+ /** Rebuild the full map from the baseline the delta was made against. Inputs are not modified. */
29
+ export declare function applyEntityDelta(base: Entities | null, delta: EntityDelta): Entities;
@@ -0,0 +1,78 @@
1
+ import { newer } from '../core/protocol.js';
2
+ /**
3
+ * What was sent, by state seq, so the next snapshot can be a delta against the newest one the receiver acknowledged
4
+ * (send with `ack: true`, feed onStateAck to acknowledge). Lost snapshots cost nothing but a larger next delta.
5
+ * Reset on a new connection epoch (the seqs restart) or a world reset.
6
+ */
7
+ export class SnapshotHistory {
8
+ capacity;
9
+ sent = new Map();
10
+ acked = null;
11
+ constructor(capacity = 64) {
12
+ this.capacity = capacity;
13
+ if (!Number.isInteger(capacity) || capacity < 1)
14
+ throw new RangeError('Invalid history capacity');
15
+ }
16
+ record(seq, value) {
17
+ if (!seq)
18
+ return;
19
+ this.sent.set(seq, value);
20
+ while (this.sent.size > this.capacity)
21
+ this.sent.delete(this.sent.keys().next().value);
22
+ }
23
+ acknowledge(seq) {
24
+ const value = this.sent.get(seq);
25
+ if (value === undefined || this.acked && !newer(seq, this.acked.seq))
26
+ return false;
27
+ this.acked = { seq, value };
28
+ for (const k of [...this.sent.keys()])
29
+ if (!newer(k, seq))
30
+ this.sent.delete(k);
31
+ return true;
32
+ }
33
+ /** The acknowledged snapshot to diff against, or null for a whole snapshot. */
34
+ get baseline() { return this.acked; }
35
+ reset() { this.sent.clear(); this.acked = null; }
36
+ }
37
+ const same = (a, b) => a === b || (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null && JSON.stringify(a) === JSON.stringify(b));
38
+ export function diffEntities(base, next) {
39
+ const out = {};
40
+ for (const id of Object.keys(next)) {
41
+ const n = next[id], b = base?.[id];
42
+ let fields;
43
+ for (const k of Object.keys(n))
44
+ if (!b || !same(b[k], n[k]))
45
+ (fields ??= {})[k] = n[k];
46
+ if (b)
47
+ for (const k of Object.keys(b))
48
+ if (!(k in n))
49
+ (fields ??= {})[k] = null;
50
+ if (fields)
51
+ (out.set ??= {})[id] = fields;
52
+ }
53
+ if (base)
54
+ for (const id of Object.keys(base))
55
+ if (!(id in next))
56
+ (out.del ??= []).push(id);
57
+ return out;
58
+ }
59
+ /** Rebuild the full map from the baseline the delta was made against. Inputs are not modified. */
60
+ export function applyEntityDelta(base, delta) {
61
+ const out = {};
62
+ if (base)
63
+ for (const id of Object.keys(base))
64
+ out[id] = { ...base[id] };
65
+ for (const id of delta.del ?? [])
66
+ delete out[id];
67
+ for (const [id, fields] of Object.entries(delta.set ?? {})) {
68
+ const e = out[id] ??= {};
69
+ for (const [k, v] of Object.entries(fields)) {
70
+ if (v === null && base?.[id] && k in base[id])
71
+ delete e[k];
72
+ else
73
+ e[k] = v;
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+ //# sourceMappingURL=snapshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../src/netcode/snapshots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface Point {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ /** Server-side visibility candidate lookup. Apply authorization/room filters before serializing entities. */
6
+ export declare class SpatialGrid<Id extends string | number> {
7
+ readonly cellSize: number;
8
+ readonly maxEntities: number;
9
+ private items;
10
+ private cells;
11
+ constructor(cellSize?: number, maxEntities?: number);
12
+ private validate;
13
+ upsert(id: Id, p: Point): void;
14
+ remove(id: Id): void;
15
+ query(center: Point, radius: number): Set<Id>;
16
+ }