murow 0.0.1

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 (103) hide show
  1. package/README.md +61 -0
  2. package/dist/core/binary-codec/binary-codec.d.ts +159 -0
  3. package/dist/core/binary-codec/binary-codec.js +336 -0
  4. package/dist/core/binary-codec/index.d.ts +1 -0
  5. package/dist/core/binary-codec/index.js +1 -0
  6. package/dist/core/events/event-system.d.ts +71 -0
  7. package/dist/core/events/event-system.js +88 -0
  8. package/dist/core/events/index.d.ts +1 -0
  9. package/dist/core/events/index.js +1 -0
  10. package/dist/core/fixed-ticker/fixed-ticker.d.ts +105 -0
  11. package/dist/core/fixed-ticker/fixed-ticker.js +91 -0
  12. package/dist/core/fixed-ticker/index.d.ts +1 -0
  13. package/dist/core/fixed-ticker/index.js +1 -0
  14. package/dist/core/generate-id/generate-id.d.ts +21 -0
  15. package/dist/core/generate-id/generate-id.js +25 -0
  16. package/dist/core/generate-id/index.d.ts +1 -0
  17. package/dist/core/generate-id/index.js +1 -0
  18. package/dist/core/index.d.ts +8 -0
  19. package/dist/core/index.js +8 -0
  20. package/dist/core/lerp/index.d.ts +1 -0
  21. package/dist/core/lerp/index.js +1 -0
  22. package/dist/core/lerp/lerp.d.ts +40 -0
  23. package/dist/core/lerp/lerp.js +42 -0
  24. package/dist/core/navmesh/index.d.ts +1 -0
  25. package/dist/core/navmesh/index.js +1 -0
  26. package/dist/core/navmesh/navmesh.d.ts +116 -0
  27. package/dist/core/navmesh/navmesh.js +666 -0
  28. package/dist/core/pooled-codec/index.d.ts +1 -0
  29. package/dist/core/pooled-codec/index.js +1 -0
  30. package/dist/core/pooled-codec/pooled-codec.d.ts +140 -0
  31. package/dist/core/pooled-codec/pooled-codec.js +213 -0
  32. package/dist/core/prediction/index.d.ts +1 -0
  33. package/dist/core/prediction/index.js +1 -0
  34. package/dist/core/prediction/prediction.d.ts +64 -0
  35. package/dist/core/prediction/prediction.js +90 -0
  36. package/dist/core.esm.js +1 -0
  37. package/dist/core.js +1 -0
  38. package/dist/index.d.ts +16 -0
  39. package/dist/index.js +18 -0
  40. package/dist/protocol/index.d.ts +43 -0
  41. package/dist/protocol/index.js +43 -0
  42. package/dist/protocol/intent/index.d.ts +39 -0
  43. package/dist/protocol/intent/index.js +38 -0
  44. package/dist/protocol/intent/intent-registry.d.ts +54 -0
  45. package/dist/protocol/intent/intent-registry.js +73 -0
  46. package/dist/protocol/intent/intent.d.ts +12 -0
  47. package/dist/protocol/intent/intent.js +1 -0
  48. package/dist/protocol/snapshot/index.d.ts +44 -0
  49. package/dist/protocol/snapshot/index.js +43 -0
  50. package/dist/protocol/snapshot/snapshot-codec.d.ts +48 -0
  51. package/dist/protocol/snapshot/snapshot-codec.js +56 -0
  52. package/dist/protocol/snapshot/snapshot-registry.d.ts +100 -0
  53. package/dist/protocol/snapshot/snapshot-registry.js +136 -0
  54. package/dist/protocol/snapshot/snapshot.d.ts +19 -0
  55. package/dist/protocol/snapshot/snapshot.js +30 -0
  56. package/package.json +54 -0
  57. package/src/core/binary-codec/README.md +60 -0
  58. package/src/core/binary-codec/binary-codec.test.ts +300 -0
  59. package/src/core/binary-codec/binary-codec.ts +430 -0
  60. package/src/core/binary-codec/index.ts +1 -0
  61. package/src/core/events/README.md +47 -0
  62. package/src/core/events/event-system.test.ts +243 -0
  63. package/src/core/events/event-system.ts +140 -0
  64. package/src/core/events/index.ts +1 -0
  65. package/src/core/fixed-ticker/README.md +77 -0
  66. package/src/core/fixed-ticker/fixed-ticker.test.ts +151 -0
  67. package/src/core/fixed-ticker/fixed-ticker.ts +158 -0
  68. package/src/core/fixed-ticker/index.ts +1 -0
  69. package/src/core/generate-id/README.md +18 -0
  70. package/src/core/generate-id/generate-id.test.ts +79 -0
  71. package/src/core/generate-id/generate-id.ts +37 -0
  72. package/src/core/generate-id/index.ts +1 -0
  73. package/src/core/index.ts +8 -0
  74. package/src/core/lerp/README.md +79 -0
  75. package/src/core/lerp/index.ts +1 -0
  76. package/src/core/lerp/lerp.test.ts +90 -0
  77. package/src/core/lerp/lerp.ts +42 -0
  78. package/src/core/navmesh/README.md +124 -0
  79. package/src/core/navmesh/index.ts +1 -0
  80. package/src/core/navmesh/navmesh.test.ts +344 -0
  81. package/src/core/navmesh/navmesh.ts +850 -0
  82. package/src/core/pooled-codec/README.md +70 -0
  83. package/src/core/pooled-codec/index.ts +1 -0
  84. package/src/core/pooled-codec/pooled-codec.test.ts +349 -0
  85. package/src/core/pooled-codec/pooled-codec.ts +239 -0
  86. package/src/core/prediction/README.md +64 -0
  87. package/src/core/prediction/index.ts +1 -0
  88. package/src/core/prediction/prediction.test.ts +422 -0
  89. package/src/core/prediction/prediction.ts +101 -0
  90. package/src/index.ts +20 -0
  91. package/src/protocol/README.md +310 -0
  92. package/src/protocol/index.ts +44 -0
  93. package/src/protocol/intent/index.ts +40 -0
  94. package/src/protocol/intent/intent-registry.test.ts +237 -0
  95. package/src/protocol/intent/intent-registry.ts +88 -0
  96. package/src/protocol/intent/intent.ts +12 -0
  97. package/src/protocol/snapshot/index.ts +45 -0
  98. package/src/protocol/snapshot/snapshot-codec.test.ts +138 -0
  99. package/src/protocol/snapshot/snapshot-codec.ts +71 -0
  100. package/src/protocol/snapshot/snapshot-registry.test.ts +302 -0
  101. package/src/protocol/snapshot/snapshot-registry.ts +162 -0
  102. package/src/protocol/snapshot/snapshot.test.ts +76 -0
  103. package/src/protocol/snapshot/snapshot.ts +41 -0
@@ -0,0 +1,140 @@
1
+ import { Schema } from "../binary-codec";
2
+ /**
3
+ * Generic object pool for reusing objects and minimizing allocations.
4
+ * @template T Type of objects stored in the pool.
5
+ */
6
+ export declare class ObjectPool<T> {
7
+ private factory;
8
+ private pool;
9
+ /**
10
+ * @param factory Function to create a new instance when the pool is empty.
11
+ */
12
+ constructor(factory: () => T);
13
+ /**
14
+ * Acquire an object from the pool, or create a new one if empty.
15
+ * @returns {T} The acquired object.
16
+ */
17
+ acquire(): T;
18
+ /**
19
+ * Return an object to the pool for reuse.
20
+ * @param {T} obj Object to release.
21
+ */
22
+ release(obj: T): void;
23
+ /**
24
+ * Return multiple objects to the pool at once.
25
+ * @param {T[]} objs Array of objects to release.
26
+ */
27
+ releaseAll(objs: T[]): void;
28
+ }
29
+ /**
30
+ * Pooled decoder for single objects or nested schemas.
31
+ * @template T Type of object to decode.
32
+ */
33
+ export declare class PooledDecoder<T extends object> {
34
+ private schema;
35
+ private pool;
36
+ /**
37
+ * @param schema Schema or record describing the object structure.
38
+ * @param initial Initial object used as template for pooling.
39
+ */
40
+ constructor(schema: Schema<T> | Record<string, any>);
41
+ private createNil;
42
+ /**
43
+ * Decode a buffer into a pooled object.
44
+ * @param {Uint8Array} buf Buffer to decode.
45
+ * @returns {T} Decoded object.
46
+ */
47
+ decode(buf: Uint8Array): T;
48
+ /**
49
+ * Decode a buffer into a provided target object.
50
+ * @param {Uint8Array} buf Buffer to decode.
51
+ * @param {T} target Object to write decoded data into.
52
+ */
53
+ decodeInto(buf: Uint8Array, target: T): void;
54
+ /**
55
+ * Release a decoded object back to the pool.
56
+ * @param {T} obj Object to release.
57
+ */
58
+ release(obj: T): void;
59
+ }
60
+ /**
61
+ * Pooled decoder for arrays of objects.
62
+ * @template T Type of object to decode.
63
+ */
64
+ export declare class PooledArrayDecoder<T extends object> {
65
+ private pooledDecoder;
66
+ /**
67
+ * @param schema Schema or record describing object structure.
68
+ * @param initial Initial object used as template for pooling.
69
+ */
70
+ constructor(schema: Schema<T> | Record<string, any>);
71
+ /**
72
+ * Decode multiple buffers into pooled objects.
73
+ * @param {Uint8Array[]} buffers Array of buffers to decode.
74
+ * @returns {T[]} Array of decoded objects.
75
+ */
76
+ decodeAll(buffers: Uint8Array[]): T[];
77
+ /**
78
+ * Release multiple decoded objects back to the pool.
79
+ * @param {T[]} objs Array of objects to release.
80
+ */
81
+ releaseAll(objs: T[]): void;
82
+ }
83
+ /**
84
+ * Pooled encoder for single objects or nested schemas.
85
+ * @template T Type of object to encode.
86
+ */
87
+ export declare class PooledEncoder<T extends object> {
88
+ private schema;
89
+ private bufferSize;
90
+ private pool;
91
+ /**
92
+ * @param schema Schema or record describing object structure.
93
+ * @param bufferSize Size of buffer to allocate per encoding (default: 1024).
94
+ */
95
+ constructor(schema: Schema<T> | Record<string, any>, bufferSize?: number);
96
+ /**
97
+ * Encode an object into a pooled buffer.
98
+ * @param {T} obj Object to encode.
99
+ * @returns {Uint8Array} Encoded buffer.
100
+ */
101
+ encode(obj: T): Uint8Array;
102
+ /**
103
+ * Release a buffer back to the pool.
104
+ * @param {Uint8Array} buf Buffer to release.
105
+ */
106
+ release(buf: Uint8Array): void;
107
+ }
108
+ /**
109
+ * Combined pooled encoder and decoder for a single schema.
110
+ * Provides a convenient wrapper around PooledEncoder and PooledDecoder.
111
+ * @template T Type of object to encode/decode.
112
+ */
113
+ export declare class PooledCodec<T extends object> {
114
+ /** Pooled encoder for the schema */
115
+ encoder: PooledEncoder<T>;
116
+ /** Pooled decoder for the schema */
117
+ decoder: PooledDecoder<T>;
118
+ /**
119
+ * @param schema Schema describing the object structure.
120
+ * @param initial Initial object used as a template for pooling decoded objects.
121
+ */
122
+ constructor(schema: Schema<T>);
123
+ /**
124
+ * Encode an object into a pooled buffer.
125
+ * @param {T} data Object to encode.
126
+ * @returns {Uint8Array} Encoded buffer.
127
+ */
128
+ encode(data: T): Uint8Array<ArrayBufferLike>;
129
+ /**
130
+ * Decode a buffer into a pooled object.
131
+ * @param {Uint8Array} buf Buffer to decode.
132
+ * @returns {T} Decoded object.
133
+ */
134
+ decode(buf: Uint8Array): T;
135
+ /**
136
+ * Release a decoded object back to the pool.
137
+ * @param {T} obj Object to release.
138
+ */
139
+ release(obj: T): void;
140
+ }
@@ -0,0 +1,213 @@
1
+ import { BinaryCodec } from "../binary-codec";
2
+ /**
3
+ * Generic object pool for reusing objects and minimizing allocations.
4
+ * @template T Type of objects stored in the pool.
5
+ */
6
+ export class ObjectPool {
7
+ /**
8
+ * @param factory Function to create a new instance when the pool is empty.
9
+ */
10
+ constructor(factory) {
11
+ this.factory = factory;
12
+ this.pool = [];
13
+ }
14
+ /**
15
+ * Acquire an object from the pool, or create a new one if empty.
16
+ * @returns {T} The acquired object.
17
+ */
18
+ acquire() {
19
+ return this.pool.pop() ?? this.factory();
20
+ }
21
+ /**
22
+ * Return an object to the pool for reuse.
23
+ * @param {T} obj Object to release.
24
+ */
25
+ release(obj) {
26
+ this.pool.push(obj);
27
+ }
28
+ /**
29
+ * Return multiple objects to the pool at once.
30
+ * @param {T[]} objs Array of objects to release.
31
+ */
32
+ releaseAll(objs) {
33
+ this.pool.push(...objs);
34
+ }
35
+ }
36
+ /**
37
+ * Pooled decoder for single objects or nested schemas.
38
+ * @template T Type of object to decode.
39
+ */
40
+ export class PooledDecoder {
41
+ /**
42
+ * @param schema Schema or record describing the object structure.
43
+ * @param initial Initial object used as template for pooling.
44
+ */
45
+ constructor(schema) {
46
+ this.schema = schema;
47
+ this.pool = new ObjectPool(() => this.createNil());
48
+ }
49
+ createNil() {
50
+ const obj = {};
51
+ for (const key of Object.keys(this.schema)) {
52
+ const field = this.schema[key];
53
+ obj[key] = "toNil" in field ? field.toNil() : undefined;
54
+ }
55
+ return obj;
56
+ }
57
+ /**
58
+ * Decode a buffer into a pooled object.
59
+ * @param {Uint8Array} buf Buffer to decode.
60
+ * @returns {T} Decoded object.
61
+ */
62
+ decode(buf) {
63
+ const obj = this.pool.acquire();
64
+ this.decodeInto(buf, obj);
65
+ return obj;
66
+ }
67
+ /**
68
+ * Decode a buffer into a provided target object.
69
+ * @param {Uint8Array} buf Buffer to decode.
70
+ * @param {T} target Object to write decoded data into.
71
+ */
72
+ decodeInto(buf, target) {
73
+ for (const key of Object.keys(this.schema)) {
74
+ const field = this.schema[key];
75
+ if ("decodeAll" in field) {
76
+ target[key] = field.decodeAll(buf);
77
+ }
78
+ else if ("decode" in field) {
79
+ target[key] = field.decode(buf);
80
+ }
81
+ else {
82
+ BinaryCodec.decodeInto({ [key]: field }, buf, target);
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * Release a decoded object back to the pool.
88
+ * @param {T} obj Object to release.
89
+ */
90
+ release(obj) {
91
+ this.pool.release(obj);
92
+ }
93
+ }
94
+ /**
95
+ * Pooled decoder for arrays of objects.
96
+ * @template T Type of object to decode.
97
+ */
98
+ export class PooledArrayDecoder {
99
+ /**
100
+ * @param schema Schema or record describing object structure.
101
+ * @param initial Initial object used as template for pooling.
102
+ */
103
+ constructor(schema) {
104
+ this.pooledDecoder = new PooledDecoder(schema);
105
+ }
106
+ /**
107
+ * Decode multiple buffers into pooled objects.
108
+ * @param {Uint8Array[]} buffers Array of buffers to decode.
109
+ * @returns {T[]} Array of decoded objects.
110
+ */
111
+ decodeAll(buffers) {
112
+ return buffers.map((b) => this.pooledDecoder.decode(b));
113
+ }
114
+ /**
115
+ * Release multiple decoded objects back to the pool.
116
+ * @param {T[]} objs Array of objects to release.
117
+ */
118
+ releaseAll(objs) {
119
+ objs.forEach((o) => this.pooledDecoder.release(o));
120
+ }
121
+ }
122
+ /**
123
+ * Pooled encoder for single objects or nested schemas.
124
+ * @template T Type of object to encode.
125
+ */
126
+ export class PooledEncoder {
127
+ /**
128
+ * @param schema Schema or record describing object structure.
129
+ * @param bufferSize Size of buffer to allocate per encoding (default: 1024).
130
+ */
131
+ constructor(schema, bufferSize = 1024) {
132
+ this.schema = schema;
133
+ this.bufferSize = bufferSize;
134
+ this.pool = new ObjectPool(() => new Uint8Array(bufferSize));
135
+ }
136
+ /**
137
+ * Encode an object into a pooled buffer.
138
+ * @param {T} obj Object to encode.
139
+ * @returns {Uint8Array} Encoded buffer.
140
+ */
141
+ encode(obj) {
142
+ const buf = this.pool.acquire();
143
+ let offset = 0;
144
+ for (const key of Object.keys(this.schema)) {
145
+ const field = this.schema[key];
146
+ if ("encode" in field) {
147
+ const nested = field.encode(obj[key]);
148
+ buf.set(nested, offset);
149
+ offset += nested.length;
150
+ }
151
+ else if ("encodeAll" in field) {
152
+ const nestedArr = field.encodeAll(obj[key]);
153
+ let arrOffset = 0;
154
+ for (const item of nestedArr) {
155
+ buf.set(item, offset + arrOffset);
156
+ arrOffset += item.length;
157
+ }
158
+ offset += arrOffset;
159
+ }
160
+ else {
161
+ const tmp = BinaryCodec.encode({ [key]: field }, { [key]: obj[key] });
162
+ buf.set(tmp, offset);
163
+ offset += tmp.length;
164
+ }
165
+ }
166
+ return buf.subarray(0, offset);
167
+ }
168
+ /**
169
+ * Release a buffer back to the pool.
170
+ * @param {Uint8Array} buf Buffer to release.
171
+ */
172
+ release(buf) {
173
+ this.pool.release(buf);
174
+ }
175
+ }
176
+ /**
177
+ * Combined pooled encoder and decoder for a single schema.
178
+ * Provides a convenient wrapper around PooledEncoder and PooledDecoder.
179
+ * @template T Type of object to encode/decode.
180
+ */
181
+ export class PooledCodec {
182
+ /**
183
+ * @param schema Schema describing the object structure.
184
+ * @param initial Initial object used as a template for pooling decoded objects.
185
+ */
186
+ constructor(schema) {
187
+ this.encoder = new PooledEncoder(schema);
188
+ this.decoder = new PooledDecoder(schema);
189
+ }
190
+ /**
191
+ * Encode an object into a pooled buffer.
192
+ * @param {T} data Object to encode.
193
+ * @returns {Uint8Array} Encoded buffer.
194
+ */
195
+ encode(data) {
196
+ return this.encoder.encode(data);
197
+ }
198
+ /**
199
+ * Decode a buffer into a pooled object.
200
+ * @param {Uint8Array} buf Buffer to decode.
201
+ * @returns {T} Decoded object.
202
+ */
203
+ decode(buf) {
204
+ return this.decoder.decode(buf);
205
+ }
206
+ /**
207
+ * Release a decoded object back to the pool.
208
+ * @param {T} obj Object to release.
209
+ */
210
+ release(obj) {
211
+ this.decoder.release(obj);
212
+ }
213
+ }
@@ -0,0 +1 @@
1
+ export * from './prediction';
@@ -0,0 +1 @@
1
+ export * from './prediction';
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @template T
3
+ * @description
4
+ * Tracks client-side intents that have been sent to the server but not yet confirmed.
5
+ * Used for prediction and reconciliation in a server-authoritative architecture.
6
+ */
7
+ export declare class IntentTracker<T> {
8
+ private tracker;
9
+ get size(): number;
10
+ /**
11
+ * Adds a new intent for a specific tick.
12
+ * @param {number} tick - The tick number associated with the intent.
13
+ * @param {T} intent - The intent data.
14
+ */
15
+ track(tick: number, intent: T): T;
16
+ /**
17
+ * Removes all intents up to and including a given tick.
18
+ * Returns the remaining intents in ascending tick order.
19
+ * @param {number} tick - The tick up to which intents should be dropped.
20
+ * @returns {T[]} Array of remaining intents.
21
+ */
22
+ dropUpTo(tick: number): T[];
23
+ /**
24
+ * Returns all currently tracked intents in ascending tick order.
25
+ * @returns {T[]}
26
+ */
27
+ values(): T[];
28
+ }
29
+ /**
30
+ * @template T,U
31
+ * @description
32
+ * Handles client-side reconciliation of authoritative snapshots with unconfirmed intents.
33
+ * Used for prediction correction in server-authoritative multiplayer games.
34
+ */
35
+ export declare class Reconciliator<T, U> {
36
+ private options;
37
+ private tracker;
38
+ /**
39
+ * @param {Object} options - Callbacks for applying snapshot state and replaying intents.
40
+ * @param {(snapshotState: U) => void} options.onLoadState - Called to load authoritative snapshot state.
41
+ * @param {(remainingIntents: T[]) => void} options.onReplay - Called to reapply remaining intents for prediction.
42
+ */
43
+ constructor(options: {
44
+ onLoadState: (snapshotState: U) => void;
45
+ onReplay: (remainingIntents: T[]) => void;
46
+ });
47
+ /**
48
+ * Adds a new intent to the tracker.
49
+ * @param {number} tick - Tick number associated with the intent.
50
+ * @param {T} intent - The intent data.
51
+ */
52
+ trackIntent(tick: number, intent: T): void;
53
+ /**
54
+ * Called when an authoritative snapshot is received from the server.
55
+ * Resets client state and replays unconfirmed intents.
56
+ * @param {Object} snapshot - The snapshot from the server.
57
+ * @param {number} snapshot.tick - Tick number of the snapshot.
58
+ * @param {U} snapshot.state - The authoritative state.
59
+ */
60
+ onSnapshot(snapshot: {
61
+ tick: number;
62
+ state: U;
63
+ }): void;
64
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @template T
3
+ * @description
4
+ * Tracks client-side intents that have been sent to the server but not yet confirmed.
5
+ * Used for prediction and reconciliation in a server-authoritative architecture.
6
+ */
7
+ export class IntentTracker {
8
+ constructor() {
9
+ this.tracker = new Map();
10
+ }
11
+ get size() {
12
+ return this.tracker.size;
13
+ }
14
+ /**
15
+ * Adds a new intent for a specific tick.
16
+ * @param {number} tick - The tick number associated with the intent.
17
+ * @param {T} intent - The intent data.
18
+ */
19
+ track(tick, intent) {
20
+ this.tracker.set(tick, intent);
21
+ return intent;
22
+ }
23
+ /**
24
+ * Removes all intents up to and including a given tick.
25
+ * Returns the remaining intents in ascending tick order.
26
+ * @param {number} tick - The tick up to which intents should be dropped.
27
+ * @returns {T[]} Array of remaining intents.
28
+ */
29
+ dropUpTo(tick) {
30
+ const remaining = [];
31
+ for (const [t, intent] of this.tracker) {
32
+ if (t <= tick)
33
+ this.tracker.delete(t);
34
+ else
35
+ remaining.push([t, intent]);
36
+ }
37
+ // sort by tick ascending
38
+ remaining.sort(([a], [b]) => a - b);
39
+ return remaining.map(([_, intent]) => intent);
40
+ }
41
+ /**
42
+ * Returns all currently tracked intents in ascending tick order.
43
+ * @returns {T[]}
44
+ */
45
+ values() {
46
+ return Array.from(this.tracker.entries())
47
+ .sort(([a], [b]) => a - b)
48
+ .map(([_, intent]) => intent);
49
+ }
50
+ }
51
+ /**
52
+ * @template T,U
53
+ * @description
54
+ * Handles client-side reconciliation of authoritative snapshots with unconfirmed intents.
55
+ * Used for prediction correction in server-authoritative multiplayer games.
56
+ */
57
+ export class Reconciliator {
58
+ /**
59
+ * @param {Object} options - Callbacks for applying snapshot state and replaying intents.
60
+ * @param {(snapshotState: U) => void} options.onLoadState - Called to load authoritative snapshot state.
61
+ * @param {(remainingIntents: T[]) => void} options.onReplay - Called to reapply remaining intents for prediction.
62
+ */
63
+ constructor(options) {
64
+ this.options = options;
65
+ this.tracker = new IntentTracker();
66
+ }
67
+ /**
68
+ * Adds a new intent to the tracker.
69
+ * @param {number} tick - Tick number associated with the intent.
70
+ * @param {T} intent - The intent data.
71
+ */
72
+ trackIntent(tick, intent) {
73
+ this.tracker.track(tick, intent);
74
+ }
75
+ /**
76
+ * Called when an authoritative snapshot is received from the server.
77
+ * Resets client state and replays unconfirmed intents.
78
+ * @param {Object} snapshot - The snapshot from the server.
79
+ * @param {number} snapshot.tick - Tick number of the snapshot.
80
+ * @param {U} snapshot.state - The authoritative state.
81
+ */
82
+ onSnapshot(snapshot) {
83
+ // 1. Load authoritative state
84
+ this.options.onLoadState(snapshot.state);
85
+ // 2. Remove confirmed intents and get remaining
86
+ const remainingIntents = this.tracker.dropUpTo(snapshot.tick);
87
+ // 3. Replay remaining intents for prediction
88
+ this.options.onReplay(remainingIntents);
89
+ }
90
+ }
@@ -0,0 +1 @@
1
+ var U=Symbol("schemaSize");function V(i){let e=i[U];if(e!==void 0)return e;let t=0;for(let s of Object.keys(i))t+=i[s].size;return i[U]=t,t}var w=class{static encodeInto(e,t){let s=V(e),n=new ArrayBuffer(s),c=new DataView(n),r=0;for(let o of Object.keys(e)){let a=e[o];a.write(c,r,t[o]),r+=a.size}return new Uint8Array(n)}static decodeInto(e,t,s){let n=V(e);if(t.byteLength<n)throw new RangeError(`Buffer too small: expected ${n} bytes, got ${t.byteLength}`);let c=new DataView(t.buffer,t.byteOffset,t.byteLength),r=0;for(let o of Object.keys(e)){let a=e[o];s[o]=a.read(c,r),r+=a.size}return s}},y=class{static{this.u8={size:1,write:(e,t,s)=>e.setUint8(t,s),read:(e,t)=>e.getUint8(t),toNil:()=>0}}static{this.u16={size:2,write:(e,t,s)=>e.setUint16(t,s,!1),read:(e,t)=>e.getUint16(t,!1),toNil:()=>0}}static{this.u32={size:4,write:(e,t,s)=>e.setUint32(t,s,!1),read:(e,t)=>e.getUint32(t,!1),toNil:()=>0}}static{this.i8={size:1,write:(e,t,s)=>e.setInt8(t,s),read:(e,t)=>e.getInt8(t),toNil:()=>0}}static{this.i16={size:2,write:(e,t,s)=>e.setInt16(t,s,!1),read:(e,t)=>e.getInt16(t,!1),toNil:()=>0}}static{this.i32={size:4,write:(e,t,s)=>e.setInt32(t,s,!1),read:(e,t)=>e.getInt32(t,!1),toNil:()=>0}}static{this.f32={size:4,write:(e,t,s)=>e.setFloat32(t,s,!1),read:(e,t)=>e.getFloat32(t,!1),toNil:()=>0}}static{this.f64={size:8,write:(e,t,s)=>e.setFloat64(t,s,!1),read:(e,t)=>e.getFloat64(t,!1),toNil:()=>0}}static{this.bool={size:1,write:(e,t,s)=>e.setUint8(t,s?1:0),read:(e,t)=>e.getUint8(t)!==0,toNil:()=>!1}}static string(e){return{size:e+2,write(t,s,n){let r=new TextEncoder().encode(n);if(r.length>e)throw new RangeError(`String too long, max ${e} bytes`);t.setUint16(s,r.length,!1);for(let o=0;o<r.length;o++)t.setUint8(s+2+o,r[o]);for(let o=r.length;o<e;o++)t.setUint8(s+2+o,0)},read(t,s){let n=t.getUint16(s,!1),c=new Uint8Array(n);for(let r=0;r<n;r++)c[r]=t.getUint8(s+2+r);return new TextDecoder().decode(c)},toNil:()=>""}}static{this.vec2={size:8,write(e,t,s){e.setFloat32(t,s.x,!1),e.setFloat32(t+4,s.y,!1)},read(e,t){return{x:e.getFloat32(t,!1),y:e.getFloat32(t+4,!1)}},toNil:()=>({x:0,y:0})}}static{this.vec3={size:12,write(e,t,s){e.setFloat32(t,s.x,!1),e.setFloat32(t+4,s.y,!1),e.setFloat32(t+8,s.z,!1)},read(e,t){return{x:e.getFloat32(t,!1),y:e.getFloat32(t+4,!1),z:e.getFloat32(t+8,!1)}},toNil:()=>({x:0,y:0,z:0})}}static{this.color={size:4,write(e,t,s){e.setUint8(t,s.r),e.setUint8(t+1,s.g),e.setUint8(t+2,s.b),e.setUint8(t+3,s.a)},read(e,t){return{r:e.getUint8(t),g:e.getUint8(t+1),b:e.getUint8(t+2),a:e.getUint8(t+3)}},toNil:()=>({r:0,g:0,b:0,a:0})}}static{this.f32_le={size:4,write:(e,t,s)=>e.setFloat32(t,s,!0),read:(e,t)=>e.getFloat32(t,!0),toNil:()=>0}}static{this.f64_le={size:8,write:(e,t,s)=>e.setFloat64(t,s,!0),read:(e,t)=>e.getFloat64(t,!0),toNil:()=>0}}static{this.u16_le={size:2,write:(e,t,s)=>e.setUint16(t,s,!0),read:(e,t)=>e.getUint16(t,!0),toNil:()=>0}}static{this.u32_le={size:4,write:(e,t,s)=>e.setUint32(t,s,!0),read:(e,t)=>e.getUint32(t,!0),toNil:()=>0}}static{this.i16_le={size:2,write:(e,t,s)=>e.setInt16(t,s,!0),read:(e,t)=>e.getInt16(t,!0),toNil:()=>0}}static{this.i32_le={size:4,write:(e,t,s)=>e.setInt32(t,s,!0),read:(e,t)=>e.getInt32(t,!0),toNil:()=>0}}static{this.vec2_le={size:8,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0)],toNil:()=>[0,0]}}static{this.vec3_le={size:12,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0),e.setFloat32(t+8,s[2],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0),e.getFloat32(t+8,!0)],toNil:()=>[0,0,0]}}static{this.vec4_le={size:16,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0),e.setFloat32(t+8,s[2],!0),e.setFloat32(t+12,s[3],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0),e.getFloat32(t+8,!0),e.getFloat32(t+12,!0)],toNil:()=>[0,0,0,0]}}},x=class extends w{static{this.u8=y.u8}static{this.u16=y.u16}static{this.f32=y.f32}static encode(e,t){return this.encodeInto(e,t)}static decode(e,t,s){return this.decodeInto(e,t,s)}};var C=class{constructor({events:e}){this.callbacks=new Map,this.events=e;for(let t of this.events)this.callbacks.set(t,new Set)}on(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);s.add(t)}once(e,t){let s=n=>{t(n),this.off(e,s)};this.on(e,s)}emit(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);for(let n of s)n(t)}off(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);s.delete(t)}clear(e){if(!e){this.callbacks.clear();for(let s of this.events)this.callbacks.set(s,new Set);return}let t=this.callbacks.get(e);if(!t)return console.warn(`Event "${e}" does not exist.`);t.clear()}};var A=class{constructor({rate:e,onTick:t}){this.accumulator=0;this._tickCount=0;this.rate=e,this.intervalMs=1e3/this.rate,this.onTick=t,this.maxTicksPerFrame=Math.max(1,Math.floor(e/2))}getTicks(e){this.accumulator+=e*1e3;let t=0;for(;this.accumulator>=this.intervalMs&&t<this.maxTicksPerFrame;)this.accumulator-=this.intervalMs,t++;let s=Math.floor(this.accumulator/this.intervalMs);return s>0&&this.onTickSkipped&&this.onTickSkipped(s),t}tick(e){let t=this.getTicks(e);for(let s=0;s<t;s++)this.onTick(1/this.rate,this._tickCount++)}get tickCount(){return this._tickCount}resetTickCount(){this._tickCount=0}get accumulatedTime(){return this.accumulator/1e3}};function re(i={}){let{prefix:e="",size:t=16}=i,s=Math.max(t-e.length,8),n=Math.ceil(s/8),r=crypto.getRandomValues(new Uint32Array(n)).reduce((o,a)=>o+a.toString(16).padStart(8,"0"),"");return r=r.slice(0,s).padStart(s,"0"),`${e}${r}`}function ce(i,e,t){return i+(e-i)*t}var B=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}],T=i=>({x:Math.floor(i.x),y:Math.floor(i.y)}),X=i=>({x:i.x+.5,y:i.y+.5}),L=(()=>{let i=1;return()=>i++})(),m=(i,e)=>i&65535|(e&65535)<<16,F=i=>({x:i<<16>>16,y:i>>16}),M=class{constructor(e){this.scoreFn=e;this.heap=[]}push(e){this.heap.push(e),this.bubbleUp(this.heap.length-1)}pop(){let e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&t!==void 0&&(this.heap[0]=t,this.sinkDown(0)),e}get size(){return this.heap.length}bubbleUp(e){let t=this.heap[e],s=this.scoreFn(t);for(;e>0;){let n=(e+1>>1)-1,c=this.heap[n];if(s>=this.scoreFn(c))break;this.heap[n]=t,this.heap[e]=c,e=n}}sinkDown(e){let t=this.heap.length,s=this.heap[e],n=this.scoreFn(s);for(;;){let c=e+1<<1,r=c-1,o=null,a;if(r<t){let l=this.heap[r];a=this.scoreFn(l),a<n&&(o=r)}if(c<t){let l=this.heap[c];this.scoreFn(l)<(o===null?n:a)&&(o=c)}if(o===null)break;this.heap[e]=this.heap[o],this.heap[o]=s,e=o}}},z=class{constructor(e=1){this.grid=new Map;this.obstacleCells=new Map;this.cellSize=e}hash(e,t){let s=Math.floor(e/this.cellSize),n=Math.floor(t/this.cellSize);return m(s,n)}add(e,t){let s=this.getCellsForObstacle(t);for(let n of s)this.grid.has(n)||this.grid.set(n,new Set),this.grid.get(n).add(e);this.obstacleCells.set(e,s)}remove(e){let t=this.obstacleCells.get(e);if(t){for(let s of t){let n=this.grid.get(s);n&&(n.delete(e),n.size===0&&this.grid.delete(s))}this.obstacleCells.delete(e)}}query(e){let t=this.hash(e.x,e.y);return this.grid.get(t)||new Set}clear(){this.grid.clear(),this.obstacleCells.clear()}getCellsForObstacle(e){let t=new Set;if(e.type==="circle"){let s=e.radius,n=Math.floor((e.pos.x-s)/this.cellSize),c=Math.floor((e.pos.x+s)/this.cellSize),r=Math.floor((e.pos.y-s)/this.cellSize),o=Math.floor((e.pos.y+s)/this.cellSize);for(let a=n;a<=c;a++)for(let l=r;l<=o;l++)t.add(m(a,l))}else if(e.type==="rect"){let s=e.pos.x+e.size.x/2,n=e.pos.y+e.size.y/2,c=e.size.x/2,r=e.size.y/2,o=Math.sqrt(c*c+r*r),a=Math.floor((s-o)/this.cellSize),l=Math.floor((s+o)/this.cellSize),h=Math.floor((n-o)/this.cellSize),u=Math.floor((n+o)/this.cellSize);for(let d=a;d<=l;d++)for(let p=h;p<=u;p++)t.add(m(d,p))}else if(e.type==="polygon"){let s=R(e),n=Math.floor(s.minX/this.cellSize),c=Math.floor(s.maxX/this.cellSize),r=Math.floor(s.minY/this.cellSize),o=Math.floor(s.maxY/this.cellSize);for(let a=n;a<=c;a++)for(let l=r;l<=o;l++)t.add(m(a,l))}return t}};function Y(i,e){let t=i.x-e.pos.x,s=i.y-e.pos.y;return t*t+s*s<=e.radius*e.radius}function j(i,e){let t=e.pos.x+e.size.x/2,s=e.pos.y+e.size.y/2;if(e.rotation){let n=Math.cos(-e.rotation),c=Math.sin(-e.rotation),r=i.x-t,o=i.y-s,a=r*n-o*c,l=r*c+o*n;return Math.abs(a)<=e.size.x/2&&Math.abs(l)<=e.size.y/2}return i.x>=e.pos.x&&i.y>=e.pos.y&&i.x<=e.pos.x+e.size.x&&i.y<=e.pos.y+e.size.y}function _(i,e){let t=!1,s=e.points,n=e.rotation?Math.cos(e.rotation):1,c=e.rotation?Math.sin(e.rotation):0;for(let r=0,o=s.length-1;r<s.length;o=r++){let a=s[r].x,l=s[r].y,h=s[o].x,u=s[o].y;if(e.rotation){let p=a*n-l*c,f=a*c+l*n,b=h*n-u*c,g=h*c+u*n;a=p,l=f,h=b,u=g}a+=e.pos.x,l+=e.pos.y,h+=e.pos.x,u+=e.pos.y,l>i.y!=u>i.y&&i.x<(h-a)*(i.y-l)/(u-l)+a&&(t=!t)}return t}function R(i){let e=1/0,t=1/0,s=-1/0,n=-1/0,c=i.rotation?Math.cos(i.rotation):1,r=i.rotation?Math.sin(i.rotation):0;for(let o of i.points){let a=o.x,l=o.y;if(i.rotation){let h=a*c-l*r,u=a*r+l*c;a=h,l=u}a+=i.pos.x,l+=i.pos.y,e=Math.min(e,a),t=Math.min(t,l),s=Math.max(s,a),n=Math.max(n,l)}return{minX:e,minY:t,maxX:s,maxY:n}}var O=class{constructor(){this.items=new Map;this.spatial=new z(1);this._cachedItems=[];this.dirty=!0;this.version=0}add(e){let t=L(),s={...e,id:t};return this.items.set(t,s),this.spatial.add(t,s),this.dirty=!0,this.version++,t}move(e,t){let s=this.items.get(e);if(!s)return;this.spatial.remove(e);let n={...s,pos:{...t}};this.items.set(e,n),this.spatial.add(e,n),this.dirty=!0,this.version++}remove(e){this.spatial.remove(e),this.items.delete(e),this.dirty=!0,this.version++}at(e){let t=this.spatial.query(e);for(let s of t){let n=this.items.get(s);if(!(!n||n.solid===!1)&&(n.type==="circle"&&Y(e,n)||n.type==="rect"&&j(e,n)||n.type==="polygon"&&_(e,n)))return n}}get values(){return this.dirty?(this._cachedItems=[...this.items.values()],this.dirty=!1,this._cachedItems):this._cachedItems}},S=class{constructor(e){this.obstacles=e;this.blocked=new Set}rebuild(){this.blocked.clear();for(let e of this.obstacles.values)if(e.solid!==!1){if(e.type==="circle"){let t=Math.ceil(e.radius),s=Math.floor(e.pos.x),n=Math.floor(e.pos.y);for(let c=-t;c<=t;c++)for(let r=-t;r<=t;r++){let o=s+c,a=n+r,l={x:o+.5,y:a+.5};Y(l,e)&&this.blocked.add(m(o,a))}}else if(e.type==="rect"){let t=e.pos.x+e.size.x/2,s=e.pos.y+e.size.y/2,n=e.size.x/2,c=e.size.y/2,r=Math.sqrt(n*n+c*c),o=Math.floor(t-r),a=Math.ceil(t+r),l=Math.floor(s-r),h=Math.ceil(s+r);for(let u=o;u<=a;u++)for(let d=l;d<=h;d++){let p={x:u+.5,y:d+.5};j(p,e)&&this.blocked.add(m(u,d))}}else if(e.type==="polygon"){let t=R(e),s=Math.floor(t.minX),n=Math.ceil(t.maxX),c=Math.floor(t.minY),r=Math.ceil(t.maxY);for(let o=s;o<=n;o++)for(let a=c;a<=r;a++){let l={x:o+.5,y:a+.5};_(l,e)&&this.blocked.add(m(o,a))}}}}findPath(e,t){return D(T(e),T(t),(s,n)=>!this.blocked.has(m(s,n))).map(X)}},E=class{constructor(e){this.obstacles=e}rebuild(){}findPath(e,t){let s=Math.ceil(Math.hypot(t.x-e.x,t.y-e.y)*2),n=!1;for(let r=1;r<=s;r++){let o=r/s,a={x:e.x+(t.x-e.x)*o,y:e.y+(t.y-e.y)*o};if(this.obstacles.at(a)){n=!0;break}}return n?D(T(e),T(t),(r,o)=>{let a={x:r+.5,y:o+.5};return!this.obstacles.at(a)}).map(X):[e,t]}},P=class{constructor(e){this.type=e;this.lastVersion=-1;this.obstacles=new O,e==="grid"&&(this.grid=new S(this.obstacles)),e==="graph"&&(this.graph=new E(this.obstacles))}addObstacle(e){return this.obstacles.add(e)}moveObstacle(e,t){this.obstacles.move(e,t)}removeObstacle(e){this.obstacles.remove(e)}getObstacles(){return this.obstacles.values}findPath({from:e,to:t}){return this.rebuild(),this.type==="grid"?this.grid.findPath(e,t):this.graph.findPath(e,t)}rebuild(){this.lastVersion!==this.obstacles.version&&(this.grid?.rebuild(),this.graph?.rebuild(),this.lastVersion=this.obstacles.version)}};function D(i,e,t){let s=new Map,n=new Map,c=new Set,r=new Set,o=u=>m(u.x,u.y),a=(u,d)=>Math.abs(u.x-d.x)+Math.abs(u.y-d.y),l=new M(u=>{let d=F(u);return n.get(u)+a(d,e)}),h=o(i);for(n.set(h,0),l.push(h),r.add(h);l.size>0;){let u=l.pop();r.delete(u);let d=F(u);if(d.x===e.x&&d.y===e.y)return G(s,d);c.add(u);for(let p of B){let f={x:d.x+p.x,y:d.y+p.y};if(!t(f.x,f.y))continue;let b=o(f);if(c.has(b))continue;let g=n.get(u)+1;g<(n.get(b)??1/0)&&(n.set(b,g),s.set(b,u),r.has(b)||(l.push(b),r.add(b)))}}return[]}function G(i,e){let t=[e],s=m(e.x,e.y);for(;i.has(s);)s=i.get(s),t.push(F(s));return t.reverse()}var k=class{constructor(e){this.factory=e;this.pool=[]}acquire(){return this.pool.pop()??this.factory()}release(e){this.pool.push(e)}releaseAll(e){this.pool.push(...e)}},v=class{constructor(e){this.schema=e;this.pool=new k(()=>this.createNil())}createNil(){let e={};for(let t of Object.keys(this.schema)){let s=this.schema[t];e[t]="toNil"in s?s.toNil():void 0}return e}decode(e){let t=this.pool.acquire();return this.decodeInto(e,t),t}decodeInto(e,t){for(let s of Object.keys(this.schema)){let n=this.schema[s];"decodeAll"in n?t[s]=n.decodeAll(e):"decode"in n?t[s]=n.decode(e):x.decodeInto({[s]:n},e,t)}}release(e){this.pool.release(e)}},$=class{constructor(e){this.pooledDecoder=new v(e)}decodeAll(e){return e.map(t=>this.pooledDecoder.decode(t))}releaseAll(e){e.forEach(t=>this.pooledDecoder.release(t))}},I=class{constructor(e,t=1024){this.schema=e;this.bufferSize=t;this.pool=new k(()=>new Uint8Array(t))}encode(e){let t=this.pool.acquire(),s=0;for(let n of Object.keys(this.schema)){let c=this.schema[n];if("encode"in c){let r=c.encode(e[n]);t.set(r,s),s+=r.length}else if("encodeAll"in c){let r=c.encodeAll(e[n]),o=0;for(let a of r)t.set(a,s+o),o+=a.length;s+=o}else{let r=x.encode({[n]:c},{[n]:e[n]});t.set(r,s),s+=r.length}}return t.subarray(0,s)}release(e){this.pool.release(e)}},q=class{constructor(e){this.encoder=new I(e),this.decoder=new v(e)}encode(e){return this.encoder.encode(e)}decode(e){return this.decoder.decode(e)}release(e){this.decoder.release(e)}};var N=class{constructor(){this.tracker=new Map}get size(){return this.tracker.size}track(e,t){return this.tracker.set(e,t),t}dropUpTo(e){let t=[];for(let[s,n]of this.tracker)s<=e?this.tracker.delete(s):t.push([s,n]);return t.sort(([s],[n])=>s-n),t.map(([s,n])=>n)}values(){return Array.from(this.tracker.entries()).sort(([e],[t])=>e-t).map(([e,t])=>t)}},K=class{constructor(e){this.options=e;this.tracker=new N}trackIntent(e,t){this.tracker.track(e,t)}onSnapshot(e){this.options.onLoadState(e.state);let t=this.tracker.dropUpTo(e.tick);this.options.onReplay(t)}};export{w as BaseBinaryCodec,x as BinaryCodec,y as BinaryPrimitives,C as EventSystem,A as FixedTicker,N as IntentTracker,P as NavMesh,k as ObjectPool,$ as PooledArrayDecoder,q as PooledCodec,v as PooledDecoder,I as PooledEncoder,K as Reconciliator,re as generateId,ce as lerp};
package/dist/core.js ADDED
@@ -0,0 +1 @@
1
+ var z=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var Z=Object.prototype.hasOwnProperty;var J=(r,e)=>{for(var t in e)z(r,t,{get:e[t],enumerable:!0})},Q=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of G(e))!Z.call(r,n)&&n!==t&&z(r,n,{get:()=>e[n],enumerable:!(s=L(e,n))||s.enumerable});return r};var W=r=>Q(z({},"__esModule",{value:!0}),r);var re={};J(re,{BaseBinaryCodec:()=>v,BinaryCodec:()=>y,BinaryPrimitives:()=>f,EventSystem:()=>O,FixedTicker:()=>S,IntentTracker:()=>M,NavMesh:()=>A,ObjectPool:()=>g,PooledArrayDecoder:()=>P,PooledCodec:()=>X,PooledDecoder:()=>T,PooledEncoder:()=>F,Reconciliator:()=>Y,generateId:()=>H,lerp:()=>ee});module.exports=W(re);var j=Symbol("schemaSize");function _(r){let e=r[j];if(e!==void 0)return e;let t=0;for(let s of Object.keys(r))t+=r[s].size;return r[j]=t,t}var v=class{static encodeInto(e,t){let s=_(e),n=new ArrayBuffer(s),c=new DataView(n),i=0;for(let o of Object.keys(e)){let a=e[o];a.write(c,i,t[o]),i+=a.size}return new Uint8Array(n)}static decodeInto(e,t,s){let n=_(e);if(t.byteLength<n)throw new RangeError(`Buffer too small: expected ${n} bytes, got ${t.byteLength}`);let c=new DataView(t.buffer,t.byteOffset,t.byteLength),i=0;for(let o of Object.keys(e)){let a=e[o];s[o]=a.read(c,i),i+=a.size}return s}},f=class{static{this.u8={size:1,write:(e,t,s)=>e.setUint8(t,s),read:(e,t)=>e.getUint8(t),toNil:()=>0}}static{this.u16={size:2,write:(e,t,s)=>e.setUint16(t,s,!1),read:(e,t)=>e.getUint16(t,!1),toNil:()=>0}}static{this.u32={size:4,write:(e,t,s)=>e.setUint32(t,s,!1),read:(e,t)=>e.getUint32(t,!1),toNil:()=>0}}static{this.i8={size:1,write:(e,t,s)=>e.setInt8(t,s),read:(e,t)=>e.getInt8(t),toNil:()=>0}}static{this.i16={size:2,write:(e,t,s)=>e.setInt16(t,s,!1),read:(e,t)=>e.getInt16(t,!1),toNil:()=>0}}static{this.i32={size:4,write:(e,t,s)=>e.setInt32(t,s,!1),read:(e,t)=>e.getInt32(t,!1),toNil:()=>0}}static{this.f32={size:4,write:(e,t,s)=>e.setFloat32(t,s,!1),read:(e,t)=>e.getFloat32(t,!1),toNil:()=>0}}static{this.f64={size:8,write:(e,t,s)=>e.setFloat64(t,s,!1),read:(e,t)=>e.getFloat64(t,!1),toNil:()=>0}}static{this.bool={size:1,write:(e,t,s)=>e.setUint8(t,s?1:0),read:(e,t)=>e.getUint8(t)!==0,toNil:()=>!1}}static string(e){return{size:e+2,write(t,s,n){let i=new TextEncoder().encode(n);if(i.length>e)throw new RangeError(`String too long, max ${e} bytes`);t.setUint16(s,i.length,!1);for(let o=0;o<i.length;o++)t.setUint8(s+2+o,i[o]);for(let o=i.length;o<e;o++)t.setUint8(s+2+o,0)},read(t,s){let n=t.getUint16(s,!1),c=new Uint8Array(n);for(let i=0;i<n;i++)c[i]=t.getUint8(s+2+i);return new TextDecoder().decode(c)},toNil:()=>""}}static{this.vec2={size:8,write(e,t,s){e.setFloat32(t,s.x,!1),e.setFloat32(t+4,s.y,!1)},read(e,t){return{x:e.getFloat32(t,!1),y:e.getFloat32(t+4,!1)}},toNil:()=>({x:0,y:0})}}static{this.vec3={size:12,write(e,t,s){e.setFloat32(t,s.x,!1),e.setFloat32(t+4,s.y,!1),e.setFloat32(t+8,s.z,!1)},read(e,t){return{x:e.getFloat32(t,!1),y:e.getFloat32(t+4,!1),z:e.getFloat32(t+8,!1)}},toNil:()=>({x:0,y:0,z:0})}}static{this.color={size:4,write(e,t,s){e.setUint8(t,s.r),e.setUint8(t+1,s.g),e.setUint8(t+2,s.b),e.setUint8(t+3,s.a)},read(e,t){return{r:e.getUint8(t),g:e.getUint8(t+1),b:e.getUint8(t+2),a:e.getUint8(t+3)}},toNil:()=>({r:0,g:0,b:0,a:0})}}static{this.f32_le={size:4,write:(e,t,s)=>e.setFloat32(t,s,!0),read:(e,t)=>e.getFloat32(t,!0),toNil:()=>0}}static{this.f64_le={size:8,write:(e,t,s)=>e.setFloat64(t,s,!0),read:(e,t)=>e.getFloat64(t,!0),toNil:()=>0}}static{this.u16_le={size:2,write:(e,t,s)=>e.setUint16(t,s,!0),read:(e,t)=>e.getUint16(t,!0),toNil:()=>0}}static{this.u32_le={size:4,write:(e,t,s)=>e.setUint32(t,s,!0),read:(e,t)=>e.getUint32(t,!0),toNil:()=>0}}static{this.i16_le={size:2,write:(e,t,s)=>e.setInt16(t,s,!0),read:(e,t)=>e.getInt16(t,!0),toNil:()=>0}}static{this.i32_le={size:4,write:(e,t,s)=>e.setInt32(t,s,!0),read:(e,t)=>e.getInt32(t,!0),toNil:()=>0}}static{this.vec2_le={size:8,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0)],toNil:()=>[0,0]}}static{this.vec3_le={size:12,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0),e.setFloat32(t+8,s[2],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0),e.getFloat32(t+8,!0)],toNil:()=>[0,0,0]}}static{this.vec4_le={size:16,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0),e.setFloat32(t+8,s[2],!0),e.setFloat32(t+12,s[3],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0),e.getFloat32(t+8,!0),e.getFloat32(t+12,!0)],toNil:()=>[0,0,0,0]}}},y=class extends v{static{this.u8=f.u8}static{this.u16=f.u16}static{this.f32=f.f32}static encode(e,t){return this.encodeInto(e,t)}static decode(e,t,s){return this.decodeInto(e,t,s)}};var O=class{constructor({events:e}){this.callbacks=new Map,this.events=e;for(let t of this.events)this.callbacks.set(t,new Set)}on(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);s.add(t)}once(e,t){let s=n=>{t(n),this.off(e,s)};this.on(e,s)}emit(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);for(let n of s)n(t)}off(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);s.delete(t)}clear(e){if(!e){this.callbacks.clear();for(let s of this.events)this.callbacks.set(s,new Set);return}let t=this.callbacks.get(e);if(!t)return console.warn(`Event "${e}" does not exist.`);t.clear()}};var S=class{constructor({rate:e,onTick:t}){this.accumulator=0;this._tickCount=0;this.rate=e,this.intervalMs=1e3/this.rate,this.onTick=t,this.maxTicksPerFrame=Math.max(1,Math.floor(e/2))}getTicks(e){this.accumulator+=e*1e3;let t=0;for(;this.accumulator>=this.intervalMs&&t<this.maxTicksPerFrame;)this.accumulator-=this.intervalMs,t++;let s=Math.floor(this.accumulator/this.intervalMs);return s>0&&this.onTickSkipped&&this.onTickSkipped(s),t}tick(e){let t=this.getTicks(e);for(let s=0;s<t;s++)this.onTick(1/this.rate,this._tickCount++)}get tickCount(){return this._tickCount}resetTickCount(){this._tickCount=0}get accumulatedTime(){return this.accumulator/1e3}};function H(r={}){let{prefix:e="",size:t=16}=r,s=Math.max(t-e.length,8),n=Math.ceil(s/8),i=crypto.getRandomValues(new Uint32Array(n)).reduce((o,a)=>o+a.toString(16).padStart(8,"0"),"");return i=i.slice(0,s).padStart(s,"0"),`${e}${i}`}function ee(r,e,t){return r+(e-r)*t}var te=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}],w=r=>({x:Math.floor(r.x),y:Math.floor(r.y)}),R=r=>({x:r.x+.5,y:r.y+.5}),se=(()=>{let r=1;return()=>r++})(),m=(r,e)=>r&65535|(e&65535)<<16,E=r=>({x:r<<16>>16,y:r>>16}),I=class{constructor(e){this.scoreFn=e;this.heap=[]}push(e){this.heap.push(e),this.bubbleUp(this.heap.length-1)}pop(){let e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&t!==void 0&&(this.heap[0]=t,this.sinkDown(0)),e}get size(){return this.heap.length}bubbleUp(e){let t=this.heap[e],s=this.scoreFn(t);for(;e>0;){let n=(e+1>>1)-1,c=this.heap[n];if(s>=this.scoreFn(c))break;this.heap[n]=t,this.heap[e]=c,e=n}}sinkDown(e){let t=this.heap.length,s=this.heap[e],n=this.scoreFn(s);for(;;){let c=e+1<<1,i=c-1,o=null,a;if(i<t){let l=this.heap[i];a=this.scoreFn(l),a<n&&(o=i)}if(c<t){let l=this.heap[c];this.scoreFn(l)<(o===null?n:a)&&(o=c)}if(o===null)break;this.heap[e]=this.heap[o],this.heap[o]=s,e=o}}},N=class{constructor(e=1){this.grid=new Map;this.obstacleCells=new Map;this.cellSize=e}hash(e,t){let s=Math.floor(e/this.cellSize),n=Math.floor(t/this.cellSize);return m(s,n)}add(e,t){let s=this.getCellsForObstacle(t);for(let n of s)this.grid.has(n)||this.grid.set(n,new Set),this.grid.get(n).add(e);this.obstacleCells.set(e,s)}remove(e){let t=this.obstacleCells.get(e);if(t){for(let s of t){let n=this.grid.get(s);n&&(n.delete(e),n.size===0&&this.grid.delete(s))}this.obstacleCells.delete(e)}}query(e){let t=this.hash(e.x,e.y);return this.grid.get(t)||new Set}clear(){this.grid.clear(),this.obstacleCells.clear()}getCellsForObstacle(e){let t=new Set;if(e.type==="circle"){let s=e.radius,n=Math.floor((e.pos.x-s)/this.cellSize),c=Math.floor((e.pos.x+s)/this.cellSize),i=Math.floor((e.pos.y-s)/this.cellSize),o=Math.floor((e.pos.y+s)/this.cellSize);for(let a=n;a<=c;a++)for(let l=i;l<=o;l++)t.add(m(a,l))}else if(e.type==="rect"){let s=e.pos.x+e.size.x/2,n=e.pos.y+e.size.y/2,c=e.size.x/2,i=e.size.y/2,o=Math.sqrt(c*c+i*i),a=Math.floor((s-o)/this.cellSize),l=Math.floor((s+o)/this.cellSize),h=Math.floor((n-o)/this.cellSize),u=Math.floor((n+o)/this.cellSize);for(let d=a;d<=l;d++)for(let p=h;p<=u;p++)t.add(m(d,p))}else if(e.type==="polygon"){let s=K(e),n=Math.floor(s.minX/this.cellSize),c=Math.floor(s.maxX/this.cellSize),i=Math.floor(s.minY/this.cellSize),o=Math.floor(s.maxY/this.cellSize);for(let a=n;a<=c;a++)for(let l=i;l<=o;l++)t.add(m(a,l))}return t}};function D(r,e){let t=r.x-e.pos.x,s=r.y-e.pos.y;return t*t+s*s<=e.radius*e.radius}function $(r,e){let t=e.pos.x+e.size.x/2,s=e.pos.y+e.size.y/2;if(e.rotation){let n=Math.cos(-e.rotation),c=Math.sin(-e.rotation),i=r.x-t,o=r.y-s,a=i*n-o*c,l=i*c+o*n;return Math.abs(a)<=e.size.x/2&&Math.abs(l)<=e.size.y/2}return r.x>=e.pos.x&&r.y>=e.pos.y&&r.x<=e.pos.x+e.size.x&&r.y<=e.pos.y+e.size.y}function q(r,e){let t=!1,s=e.points,n=e.rotation?Math.cos(e.rotation):1,c=e.rotation?Math.sin(e.rotation):0;for(let i=0,o=s.length-1;i<s.length;o=i++){let a=s[i].x,l=s[i].y,h=s[o].x,u=s[o].y;if(e.rotation){let p=a*n-l*c,x=a*c+l*n,b=h*n-u*c,k=h*c+u*n;a=p,l=x,h=b,u=k}a+=e.pos.x,l+=e.pos.y,h+=e.pos.x,u+=e.pos.y,l>r.y!=u>r.y&&r.x<(h-a)*(r.y-l)/(u-l)+a&&(t=!t)}return t}function K(r){let e=1/0,t=1/0,s=-1/0,n=-1/0,c=r.rotation?Math.cos(r.rotation):1,i=r.rotation?Math.sin(r.rotation):0;for(let o of r.points){let a=o.x,l=o.y;if(r.rotation){let h=a*c-l*i,u=a*i+l*c;a=h,l=u}a+=r.pos.x,l+=r.pos.y,e=Math.min(e,a),t=Math.min(t,l),s=Math.max(s,a),n=Math.max(n,l)}return{minX:e,minY:t,maxX:s,maxY:n}}var U=class{constructor(){this.items=new Map;this.spatial=new N(1);this._cachedItems=[];this.dirty=!0;this.version=0}add(e){let t=se(),s={...e,id:t};return this.items.set(t,s),this.spatial.add(t,s),this.dirty=!0,this.version++,t}move(e,t){let s=this.items.get(e);if(!s)return;this.spatial.remove(e);let n={...s,pos:{...t}};this.items.set(e,n),this.spatial.add(e,n),this.dirty=!0,this.version++}remove(e){this.spatial.remove(e),this.items.delete(e),this.dirty=!0,this.version++}at(e){let t=this.spatial.query(e);for(let s of t){let n=this.items.get(s);if(!(!n||n.solid===!1)&&(n.type==="circle"&&D(e,n)||n.type==="rect"&&$(e,n)||n.type==="polygon"&&q(e,n)))return n}}get values(){return this.dirty?(this._cachedItems=[...this.items.values()],this.dirty=!1,this._cachedItems):this._cachedItems}},V=class{constructor(e){this.obstacles=e;this.blocked=new Set}rebuild(){this.blocked.clear();for(let e of this.obstacles.values)if(e.solid!==!1){if(e.type==="circle"){let t=Math.ceil(e.radius),s=Math.floor(e.pos.x),n=Math.floor(e.pos.y);for(let c=-t;c<=t;c++)for(let i=-t;i<=t;i++){let o=s+c,a=n+i,l={x:o+.5,y:a+.5};D(l,e)&&this.blocked.add(m(o,a))}}else if(e.type==="rect"){let t=e.pos.x+e.size.x/2,s=e.pos.y+e.size.y/2,n=e.size.x/2,c=e.size.y/2,i=Math.sqrt(n*n+c*c),o=Math.floor(t-i),a=Math.ceil(t+i),l=Math.floor(s-i),h=Math.ceil(s+i);for(let u=o;u<=a;u++)for(let d=l;d<=h;d++){let p={x:u+.5,y:d+.5};$(p,e)&&this.blocked.add(m(u,d))}}else if(e.type==="polygon"){let t=K(e),s=Math.floor(t.minX),n=Math.ceil(t.maxX),c=Math.floor(t.minY),i=Math.ceil(t.maxY);for(let o=s;o<=n;o++)for(let a=c;a<=i;a++){let l={x:o+.5,y:a+.5};q(l,e)&&this.blocked.add(m(o,a))}}}}findPath(e,t){return B(w(e),w(t),(s,n)=>!this.blocked.has(m(s,n))).map(R)}},C=class{constructor(e){this.obstacles=e}rebuild(){}findPath(e,t){let s=Math.ceil(Math.hypot(t.x-e.x,t.y-e.y)*2),n=!1;for(let i=1;i<=s;i++){let o=i/s,a={x:e.x+(t.x-e.x)*o,y:e.y+(t.y-e.y)*o};if(this.obstacles.at(a)){n=!0;break}}return n?B(w(e),w(t),(i,o)=>{let a={x:i+.5,y:o+.5};return!this.obstacles.at(a)}).map(R):[e,t]}},A=class{constructor(e){this.type=e;this.lastVersion=-1;this.obstacles=new U,e==="grid"&&(this.grid=new V(this.obstacles)),e==="graph"&&(this.graph=new C(this.obstacles))}addObstacle(e){return this.obstacles.add(e)}moveObstacle(e,t){this.obstacles.move(e,t)}removeObstacle(e){this.obstacles.remove(e)}getObstacles(){return this.obstacles.values}findPath({from:e,to:t}){return this.rebuild(),this.type==="grid"?this.grid.findPath(e,t):this.graph.findPath(e,t)}rebuild(){this.lastVersion!==this.obstacles.version&&(this.grid?.rebuild(),this.graph?.rebuild(),this.lastVersion=this.obstacles.version)}};function B(r,e,t){let s=new Map,n=new Map,c=new Set,i=new Set,o=u=>m(u.x,u.y),a=(u,d)=>Math.abs(u.x-d.x)+Math.abs(u.y-d.y),l=new I(u=>{let d=E(u);return n.get(u)+a(d,e)}),h=o(r);for(n.set(h,0),l.push(h),i.add(h);l.size>0;){let u=l.pop();i.delete(u);let d=E(u);if(d.x===e.x&&d.y===e.y)return ne(s,d);c.add(u);for(let p of te){let x={x:d.x+p.x,y:d.y+p.y};if(!t(x.x,x.y))continue;let b=o(x);if(c.has(b))continue;let k=n.get(u)+1;k<(n.get(b)??1/0)&&(n.set(b,k),s.set(b,u),i.has(b)||(l.push(b),i.add(b)))}}return[]}function ne(r,e){let t=[e],s=m(e.x,e.y);for(;r.has(s);)s=r.get(s),t.push(E(s));return t.reverse()}var g=class{constructor(e){this.factory=e;this.pool=[]}acquire(){return this.pool.pop()??this.factory()}release(e){this.pool.push(e)}releaseAll(e){this.pool.push(...e)}},T=class{constructor(e){this.schema=e;this.pool=new g(()=>this.createNil())}createNil(){let e={};for(let t of Object.keys(this.schema)){let s=this.schema[t];e[t]="toNil"in s?s.toNil():void 0}return e}decode(e){let t=this.pool.acquire();return this.decodeInto(e,t),t}decodeInto(e,t){for(let s of Object.keys(this.schema)){let n=this.schema[s];"decodeAll"in n?t[s]=n.decodeAll(e):"decode"in n?t[s]=n.decode(e):y.decodeInto({[s]:n},e,t)}}release(e){this.pool.release(e)}},P=class{constructor(e){this.pooledDecoder=new T(e)}decodeAll(e){return e.map(t=>this.pooledDecoder.decode(t))}releaseAll(e){e.forEach(t=>this.pooledDecoder.release(t))}},F=class{constructor(e,t=1024){this.schema=e;this.bufferSize=t;this.pool=new g(()=>new Uint8Array(t))}encode(e){let t=this.pool.acquire(),s=0;for(let n of Object.keys(this.schema)){let c=this.schema[n];if("encode"in c){let i=c.encode(e[n]);t.set(i,s),s+=i.length}else if("encodeAll"in c){let i=c.encodeAll(e[n]),o=0;for(let a of i)t.set(a,s+o),o+=a.length;s+=o}else{let i=y.encode({[n]:c},{[n]:e[n]});t.set(i,s),s+=i.length}}return t.subarray(0,s)}release(e){this.pool.release(e)}},X=class{constructor(e){this.encoder=new F(e),this.decoder=new T(e)}encode(e){return this.encoder.encode(e)}decode(e){return this.decoder.decode(e)}release(e){this.decoder.release(e)}};var M=class{constructor(){this.tracker=new Map}get size(){return this.tracker.size}track(e,t){return this.tracker.set(e,t),t}dropUpTo(e){let t=[];for(let[s,n]of this.tracker)s<=e?this.tracker.delete(s):t.push([s,n]);return t.sort(([s],[n])=>s-n),t.map(([s,n])=>n)}values(){return Array.from(this.tracker.entries()).sort(([e],[t])=>e-t).map(([e,t])=>t)}},Y=class{constructor(e){this.options=e;this.tracker=new M}trackIntent(e,t){this.tracker.track(e,t)}onSnapshot(e){this.options.onLoadState(e.state);let t=this.tracker.dropUpTo(e.tick);this.options.onReplay(t)}};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * GameDev Utils
3
+ *
4
+ * A collection of utilities for game development, including:
5
+ * - Binary codecs for efficient serialization
6
+ * - Event system for decoupled communication
7
+ * - Fixed-timestep ticker for deterministic simulation
8
+ * - ID generation utilities
9
+ * - Linear interpolation (lerp) utilities
10
+ * - NavMesh pathfinding with obstacle management
11
+ * - Pooled codecs for zero-allocation networking
12
+ * - Prediction system for client-side prediction
13
+ * - Protocol layer for networked multiplayer games
14
+ */
15
+ export * from "./core";
16
+ export * from "./protocol";
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * GameDev Utils
3
+ *
4
+ * A collection of utilities for game development, including:
5
+ * - Binary codecs for efficient serialization
6
+ * - Event system for decoupled communication
7
+ * - Fixed-timestep ticker for deterministic simulation
8
+ * - ID generation utilities
9
+ * - Linear interpolation (lerp) utilities
10
+ * - NavMesh pathfinding with obstacle management
11
+ * - Pooled codecs for zero-allocation networking
12
+ * - Prediction system for client-side prediction
13
+ * - Protocol layer for networked multiplayer games
14
+ */
15
+ // Core utilities
16
+ export * from "./core";
17
+ // Protocol layer for networking
18
+ export * from "./protocol";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Protocol Layer - Type-safe networking primitives
3
+ *
4
+ * This layer enforces:
5
+ * - Type-safe intent definitions (extend Intent interface)
6
+ * - Type-safe snapshot definitions (Snapshot<YourState>)
7
+ * - Memory-efficient encoding (use PooledCodec from core)
8
+ *
9
+ * You provide:
10
+ * - Your intent types
11
+ * - Your state types
12
+ * - Your schemas (for binary encoding)
13
+ * - Codec instances (instantiate once, reuse)
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // Define your types
18
+ * interface MoveIntent extends Intent {
19
+ * kind: 1;
20
+ * tick: number;
21
+ * dx: number;
22
+ * dy: number;
23
+ * }
24
+ *
25
+ * interface GameState {
26
+ * players: Record<number, { x: number; y: number }>;
27
+ * }
28
+ *
29
+ * // Create codecs once (reuse these!)
30
+ * const intentRegistry = new IntentRegistry();
31
+ * intentRegistry.register(1, new PooledCodec(moveSchema));
32
+ *
33
+ * const snapshotCodec = new SnapshotCodec<GameState>(
34
+ * new PooledCodec(stateSchema)
35
+ * );
36
+ *
37
+ * // Use them
38
+ * const buf = intentRegistry.encode(intent);
39
+ * const snapshot = snapshotCodec.decode(buf);
40
+ * ```
41
+ */
42
+ export * from "./intent";
43
+ export * from "./snapshot";