@axpecter/lync 2.2.1 β†’ 2.3.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 (62) hide show
  1. package/README.md +95 -143
  2. package/package.json +1 -1
  3. package/src/Types.luau +22 -16
  4. package/src/api/Group.luau +40 -33
  5. package/src/api/Packet.luau +68 -54
  6. package/src/api/Query.luau +103 -65
  7. package/src/api/Scope.luau +26 -10
  8. package/src/api/Signal.luau +43 -52
  9. package/src/codec/Base.luau +21 -14
  10. package/src/codec/composite/Array.luau +56 -134
  11. package/src/codec/composite/Map.luau +103 -72
  12. package/src/codec/composite/Optional.luau +6 -2
  13. package/src/codec/composite/Shared.luau +160 -69
  14. package/src/codec/composite/Struct.luau +283 -42
  15. package/src/codec/composite/Tagged.luau +13 -16
  16. package/src/codec/composite/Tuple.luau +21 -15
  17. package/src/codec/datatype/Buffer.luau +6 -4
  18. package/src/codec/datatype/CFrame.luau +56 -44
  19. package/src/codec/datatype/Color.luau +10 -10
  20. package/src/codec/datatype/Instance.luau +15 -12
  21. package/src/codec/datatype/IntVector.luau +2 -2
  22. package/src/codec/datatype/NumberRange.luau +15 -8
  23. package/src/codec/datatype/Ray.luau +13 -14
  24. package/src/codec/datatype/Rect.luau +12 -12
  25. package/src/codec/datatype/Region.luau +13 -14
  26. package/src/codec/datatype/Sequence.luau +87 -65
  27. package/src/codec/datatype/String.luau +17 -10
  28. package/src/codec/datatype/UDim.luau +10 -8
  29. package/src/codec/datatype/Vector.luau +20 -59
  30. package/src/codec/meta/Auto.luau +94 -126
  31. package/src/codec/meta/Bitfield.luau +12 -14
  32. package/src/codec/meta/Custom.luau +3 -1
  33. package/src/codec/meta/Enum.luau +9 -9
  34. package/src/codec/meta/Float.luau +6 -28
  35. package/src/codec/meta/Nothing.luau +1 -1
  36. package/src/codec/meta/Unknown.luau +10 -7
  37. package/src/codec/primitive/Bool.luau +6 -4
  38. package/src/codec/primitive/Float16.luau +5 -2
  39. package/src/codec/primitive/Int.luau +5 -6
  40. package/src/codec/primitive/Number.luau +6 -4
  41. package/src/codec/primitive/Signed.luau +2 -2
  42. package/src/codec/primitive/Varint.luau +69 -38
  43. package/src/index.d.ts +161 -53
  44. package/src/init.luau +109 -103
  45. package/src/internal/Baseline.luau +9 -1
  46. package/src/internal/Channel.luau +153 -154
  47. package/src/internal/Middleware.luau +22 -6
  48. package/src/internal/Pool.luau +12 -4
  49. package/src/internal/Registry.luau +25 -16
  50. package/src/internal/Transport.luau +1 -1
  51. package/src/transport/Bridge.luau +47 -33
  52. package/src/transport/Client.luau +25 -21
  53. package/src/transport/Gate.luau +227 -172
  54. package/src/transport/Reader.luau +162 -105
  55. package/src/transport/Server.luau +46 -57
  56. package/src/util/Array.luau +18 -0
  57. package/src/util/Buffer.luau +92 -0
  58. package/src/util/Constants.luau +30 -0
  59. package/src/util/Log.luau +68 -0
  60. package/src/util/Player.luau +14 -0
  61. package/src/util/Quantize.luau +60 -0
  62. package/src/internal/Util.luau +0 -26
@@ -1,7 +1,6 @@
1
1
  --!strict
2
2
  --!native
3
- -- Signed integer buffer helpers. Reads use FASTCALL2 readi*; writes
4
- -- emulate via writeu* + two's complement (writei* is not FASTCALL).
3
+ -- Signed integer writers via writeu* + two's complement (writei* is not FASTCALL).
5
4
 
6
5
  -- Private ----------------------------------------------------------------
7
6
 
@@ -25,6 +24,7 @@ function Signed.writeI32(b: buffer, off: number, value: number): ()
25
24
  writeu32(b, off, if value < 0 then value + 0x100000000 else value)
26
25
  end
27
26
 
27
+ -- Reads stay on buffer.readi* (already FASTCALL).
28
28
  Signed.readI8 = buffer.readi8
29
29
  Signed.readI16 = buffer.readi16
30
30
  Signed.readI32 = buffer.readi32
@@ -1,25 +1,38 @@
1
1
  --!strict
2
2
  --!native
3
- -- Dense prefix varint. Single-branch decode, range up to 2^32 - 1.
3
+ -- Dense prefix varint over [0, 2^32 - 1]. Single-branch decode.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
+ local Constants = require(script.Parent.Parent.Parent.util.Constants)
7
+ local Log = require(script.Parent.Parent.Parent.util.Log)
6
8
  local Types = require(script.Parent.Parent.Parent.Types)
7
9
 
8
10
  -- Constants --------------------------------------------------------------
9
11
 
10
12
  --[[
11
- Wire layout (first byte determines length):
12
- 0x00..0xBF 1 byte value = b0 [0..191]
13
- 0xC0..0xDF 2 bytes value = (b0 & 0x1F)<<8 | b1 + 192 [192..8383]
14
- 0xE0..0xEF 3 bytes value = (b0 & 0x0F)<<16 | u16 + 8384 [8384..1056959]
15
- 0xF0..0xFF 5 bytes value = u32 [..0xFFFFFFFF]
13
+ Wire layout (first byte tag selects length):
14
+ 0x00..0xBF 1 byte value = b0 [0..191]
15
+ 0xC0..0xDF 2 bytes value = (b0 & 0x1F)<<8 | b1 + 192 [192..8383]
16
+ 0xE0..0xEF 3 bytes value = (b0 & 0x0F)<<16 | u16 + 8384 [8384..1056959]
17
+ 0xF0..0xFF 5 bytes value = u32 [..0xFFFFFFFF]
16
18
  ]]
17
19
  local INLINE_MAX = 0xBF
20
+ local TWO_TAG = 0xC0
21
+ local TWO_END = 0xDF
22
+ local TWO_MASK = 0x1F
23
+ local THREE_TAG = 0xE0
24
+ local THREE_END = 0xEF
25
+ local THREE_MASK = 0x0F
26
+ local FIVE_TAG = 0xF0
18
27
  local TWO_MAX = 8383
19
28
  local THREE_MAX = 1056959
20
29
  local TWO_BIAS = 192
21
30
  local THREE_BIAS = 8384
22
31
 
32
+ local U8_MAX = Constants.U8_MAX
33
+ local U16_MAX = Constants.U16_MAX
34
+ local U32_MAX = Constants.U32_MAX
35
+
23
36
  -- Private ----------------------------------------------------------------
24
37
 
25
38
  local alloc = Base.alloc
@@ -33,6 +46,12 @@ local readu8 = buffer.readu8
33
46
  local readu16 = buffer.readu16
34
47
  local readu32 = buffer.readu32
35
48
 
49
+ local function ensure(ch: Types.ChannelState, n: number): ()
50
+ if ch.cursor + n > ch.size then
51
+ alloc(ch, n)
52
+ end
53
+ end
54
+
36
55
  -- Public -----------------------------------------------------------------
37
56
 
38
57
  local Varint = {}
@@ -40,84 +59,96 @@ local Varint = {}
40
59
  Varint.INLINE_MAX = INLINE_MAX
41
60
 
42
61
  function Varint.write(ch: Types.ChannelState, value: number): ()
43
- if value < 0 or value > 0xFFFFFFFF or value % 1 ~= 0 then
44
- error(`[Lync] Varint.write: value must be an integer in [0, 4294967295], got {value}`)
62
+ if value < 0 or value > U32_MAX or value % 1 ~= 0 then
63
+ Log.error(`value must be an integer in [0, {U32_MAX}], got {value}`)
45
64
  end
46
65
 
47
- local cursor = ch.cursor
48
-
49
66
  if value <= INLINE_MAX then
50
- if cursor + 1 > ch.size then
51
- alloc(ch, 1)
52
- end
67
+ ensure(ch, 1)
68
+ local cursor = ch.cursor
53
69
  writeu8(ch.buff, cursor, value)
54
70
  ch.cursor = cursor + 1
55
71
  return
56
72
  end
57
73
 
58
74
  if value <= TWO_MAX then
59
- if cursor + 2 > ch.size then
60
- alloc(ch, 2)
61
- end
75
+ ensure(ch, 2)
76
+ local cursor = ch.cursor
62
77
  local adjusted = value - TWO_BIAS
63
78
  local b = ch.buff
64
- writeu8(b, cursor, bor(0xC0, rshift(adjusted, 8)))
65
- writeu8(b, cursor + 1, band(adjusted, 0xFF))
79
+ writeu8(b, cursor, bor(TWO_TAG, rshift(adjusted, 8)))
80
+ writeu8(b, cursor + 1, band(adjusted, U8_MAX))
66
81
  ch.cursor = cursor + 2
67
82
  return
68
83
  end
69
84
 
70
85
  if value <= THREE_MAX then
71
- if cursor + 3 > ch.size then
72
- alloc(ch, 3)
73
- end
86
+ ensure(ch, 3)
87
+ local cursor = ch.cursor
74
88
  local adjusted = value - THREE_BIAS
75
89
  local b = ch.buff
76
- writeu8(b, cursor, bor(0xE0, rshift(adjusted, 16)))
77
- writeu16(b, cursor + 1, band(adjusted, 0xFFFF))
90
+ writeu8(b, cursor, bor(THREE_TAG, rshift(adjusted, 16)))
91
+ writeu16(b, cursor + 1, band(adjusted, U16_MAX))
78
92
  ch.cursor = cursor + 3
79
93
  return
80
94
  end
81
95
 
82
- if cursor + 5 > ch.size then
83
- alloc(ch, 5)
84
- end
96
+ ensure(ch, 5)
97
+ local cursor = ch.cursor
85
98
  local b = ch.buff
86
- writeu8(b, cursor, 0xF0)
99
+ writeu8(b, cursor, FIVE_TAG)
87
100
  writeu32(b, cursor + 1, value)
88
101
  ch.cursor = cursor + 5
89
102
  end
90
103
 
104
+ --[[
105
+ Returns (value, bytesConsumed). On insufficient bytes returns (0, 0) so
106
+ the transport can abort the frame instead of letting the buffer access
107
+ raise β€” required for recovery from truncated/desynced incoming buffers.
108
+ ]]
91
109
  function Varint.read(src: buffer, pos: number): (number, number)
110
+ local available = buffer.len(src) - pos
111
+ if available < 1 then
112
+ return 0, 0
113
+ end
92
114
  local b0 = readu8(src, pos)
93
115
  if b0 <= INLINE_MAX then
94
116
  return b0, 1
95
117
  end
96
- if b0 <= 0xDF then
97
- return band(b0, 0x1F) * 256 + readu8(src, pos + 1) + TWO_BIAS, 2
118
+ if b0 <= TWO_END then
119
+ if available < 2 then
120
+ return 0, 0
121
+ end
122
+ return band(b0, TWO_MASK) * 256 + readu8(src, pos + 1) + TWO_BIAS, 2
98
123
  end
99
- if b0 <= 0xEF then
100
- return band(b0, 0x0F) * 65536 + readu16(src, pos + 1) + THREE_BIAS, 3
124
+ if b0 <= THREE_END then
125
+ if available < 3 then
126
+ return 0, 0
127
+ end
128
+ return band(b0, THREE_MASK) * 65536 + readu16(src, pos + 1) + THREE_BIAS, 3
129
+ end
130
+ if available < 5 then
131
+ return 0, 0
101
132
  end
102
133
  return readu32(src, pos + 1), 5
103
134
  end
104
135
 
105
- -- Encode a value already known to fit in INLINE_MAX (single byte).
136
+ -- Caller-asserted: value is known to fit in INLINE_MAX.
106
137
  function Varint.writeInline(ch: Types.ChannelState, value: number): ()
138
+ ensure(ch, 1)
107
139
  local cursor = ch.cursor
108
- if cursor + 1 > ch.size then
109
- alloc(ch, 1)
110
- end
111
140
  writeu8(ch.buff, cursor, value)
112
141
  ch.cursor = cursor + 1
113
142
  end
114
143
 
115
144
  --[[
116
- Decode a length prefix that uses the single-byte fast path for
117
- lengths in [0, INLINE_MAX] and the full varint encoding above that.
118
- Used by Buffer, String, and Auto's string branch.
145
+ Length-prefix decode that fast-paths the [0, INLINE_MAX] single-byte form
146
+ before falling through to full varint. Used by Buffer, String, and Auto.
119
147
  ]]
120
148
  function Varint.readLengthPrefix(src: buffer, pos: number): (number, number)
149
+ if buffer.len(src) - pos < 1 then
150
+ return 0, 0
151
+ end
121
152
  local b0 = readu8(src, pos)
122
153
  if b0 <= INLINE_MAX then
123
154
  return b0, 1
package/src/index.d.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  /// <reference types="@rbxts/types" />
2
2
 
3
3
  declare namespace Lync {
4
- // ── Core Types ──────────────────────────────────────────────────
4
+ // ── Core types ──────────────────────────────────────────────────────
5
5
 
6
+ /**
7
+ * Opaque codec brand. The `_nominal_codec` field never exists at
8
+ * runtime; it ties the generic parameter to a unique nominal type so
9
+ * `Codec<number>` and `Codec<string>` can't be assigned to each other.
10
+ */
6
11
  interface Codec<T> {
7
12
  /** @hidden */ readonly _nominal_codec: T;
8
13
  }
@@ -25,17 +30,26 @@ declare namespace Lync {
25
30
  bytesReceived: number;
26
31
  }
27
32
 
28
- // ── Packet ──────────────────────────────────────────────────────
33
+ type RateLimitConfig =
34
+ | { maxPerSecond: number; burst?: number }
35
+ | { cooldown: number };
36
+
37
+ // ── Packet ──────────────────────────────────────────────────────────
29
38
 
30
39
  interface PacketOptions<T> {
31
40
  unreliable?: boolean;
32
- rateLimit?: { maxPerSecond?: number; burst?: number } | { cooldown: number };
41
+ rateLimit?: RateLimitConfig;
33
42
  validate?: (data: T, player: Player) => LuaTuple<[boolean, string?]>;
34
43
  maxPayloadBytes?: number;
35
44
  timestamp?: "frame" | "offset" | "full";
36
45
  }
37
46
 
38
47
  interface Packet<T> {
48
+ /**
49
+ * Server: target is required (single Player, array, Group, or sentinel).
50
+ * Client: target is omitted; the second arg is ignored at runtime.
51
+ * The signature unions both forms.
52
+ */
39
53
  send(this: Packet<T>, data: T, target?: Target): void;
40
54
  on(this: Packet<T>, fn: (data: T, sender?: Player, timestamp?: number) => void): Connection;
41
55
  once(this: Packet<T>, fn: (data: T, sender?: Player, timestamp?: number) => void): Connection;
@@ -44,55 +58,80 @@ declare namespace Lync {
44
58
  stats(this: Packet<T>): PacketStats;
45
59
  }
46
60
 
47
- // ── Query ───────────────────────────────────────────────────────
61
+ // ── Query ───────────────────────────────────────────────────────────
48
62
 
49
63
  interface QueryOptions<Req> {
50
64
  timeout?: number;
51
- rateLimit?: { maxPerSecond?: number; burst?: number };
65
+ rateLimit?: RateLimitConfig;
52
66
  validate?: (data: Req, player: Player) => LuaTuple<[boolean, string?]>;
53
67
  }
54
68
 
55
69
  interface Query<Req, Resp> {
56
- handle(this: Query<Req, Resp>, fn: (request: Req, player?: Player) => Resp | undefined): Connection;
70
+ handle(
71
+ this: Query<Req, Resp>,
72
+ fn: (request: Req, player?: Player) => Resp | undefined,
73
+ ): Connection;
74
+
75
+ /** Client: yields until reply or timeout; nil on timeout. */
57
76
  request(this: Query<Req, Resp>, data: Req): Resp | undefined;
77
+ /** Server, single-Player target. */
58
78
  request(this: Query<Req, Resp>, data: Req, target: Player): Resp | undefined;
79
+ /** Server, multi-target (Group, array, all, except). */
59
80
  request(this: Query<Req, Resp>, data: Req, target: Target): Map<Player, Resp | undefined>;
81
+
60
82
  name(this: Query<Req, Resp>): string;
61
83
  stats(this: Query<Req, Resp>): PacketStats;
62
84
  }
63
85
 
64
- // ── Group ───────────────────────────────────────────────────────
86
+ // ── Group ───────────────────────────────────────────────────────────
65
87
 
66
88
  interface Group extends Iterable<Player> {
89
+ /** Returns true if membership changed (idempotent). */
67
90
  add(this: Group, player: Player): boolean;
68
91
  remove(this: Group, player: Player): boolean;
69
92
  has(this: Group, player: Player): boolean;
70
93
  count(this: Group): number;
94
+ /** Clear members and free the name. */
71
95
  destroy(this: Group): void;
72
96
  }
73
97
 
74
- // ── Scope ───────────────────────────────────────────────────────
98
+ // ── Scope ───────────────────────────────────────────────────────────
75
99
 
76
100
  interface Scope {
77
- on<T>(this: Scope, source: Packet<T>, fn: (data: T, sender?: Player, timestamp?: number) => void): Connection;
78
- once<T>(this: Scope, source: Packet<T>, fn: (data: T, sender?: Player, timestamp?: number) => void): Connection;
101
+ on<T>(
102
+ this: Scope,
103
+ source: Packet<T>,
104
+ fn: (data: T, sender?: Player, timestamp?: number) => void,
105
+ ): Connection;
106
+ once<T>(
107
+ this: Scope,
108
+ source: Packet<T>,
109
+ fn: (data: T, sender?: Player, timestamp?: number) => void,
110
+ ): Connection;
79
111
  add(this: Scope, connection: Connection | RBXScriptConnection): void;
80
112
  destroy(this: Scope): void;
81
113
  }
82
114
 
83
- // ── Targets ─────────────────────────────────────────────────────
115
+ // ── Targets ─────────────────────────────────────────────────────────
84
116
 
85
117
  type Target = Player | Player[] | Group | AllTarget | ExceptTarget;
86
118
 
119
+ /** Branded sentinel returned by `Lync.all`. */
87
120
  interface AllTarget {
88
- /** @hidden */ readonly _lyncAll: true;
121
+ /** @hidden */ readonly _lyncKind: "all";
89
122
  }
90
123
 
124
+ /** Branded sentinel returned by `Lync.except(...)`. */
91
125
  interface ExceptTarget {
92
- /** @hidden */ readonly _lyncExcept: true;
126
+ /** @hidden */ readonly _lyncKind: "except";
93
127
  }
94
128
 
95
- // ── Codec Inference ─────────────────────────────────────────────
129
+ /** Branded sentinel exposed as `Lync.DROP`. Return from `onSend` to discard. */
130
+ interface DropSentinel {
131
+ /** @hidden */ readonly _lyncKind: "drop";
132
+ }
133
+
134
+ // ── Codec inference ─────────────────────────────────────────────────
96
135
 
97
136
  type InferCodec<C> = C extends Codec<infer T> ? T : never;
98
137
 
@@ -100,25 +139,36 @@ declare namespace Lync {
100
139
  [K in keyof S]: InferCodec<S[K]>;
101
140
  };
102
141
 
103
- // ── Callable Codecs ─────────────────────────────────────────────
142
+ // ── Callable codec brands (function-call form for variants) ─────────
104
143
 
105
144
  interface StringCodec extends Codec<string> {
145
+ /** Bounded variant; rejects on read if length exceeds maxLength. */
106
146
  (maxLength: number): Codec<string>;
107
147
  }
108
148
 
109
149
  interface Vec2Codec extends Codec<Vector2> {
150
+ /** Quantized per-component variant. 2/4/8 bytes. */
110
151
  (min: number, max: number, precision: number): Codec<Vector2>;
111
152
  }
112
153
 
113
154
  interface Vec3Codec extends Codec<Vector3> {
155
+ /** Quantized per-component variant. 3/6/12 bytes. */
114
156
  (min: number, max: number, precision: number): Codec<Vector3>;
115
157
  }
116
158
 
117
159
  interface CFrameCodec extends Codec<CFrame> {
160
+ /** Smallest-three-quaternion variant. 16 bytes; ≀ 0.16Β° rotation error. */
118
161
  (): Codec<CFrame>;
119
162
  }
120
163
 
121
- // ── Configure ───────────────────────────────────────────────────
164
+ // ── Bitfield schema ─────────────────────────────────────────────────
165
+
166
+ type BitfieldField =
167
+ | { type: "bool" }
168
+ | { type: "uint"; width: number }
169
+ | { type: "int"; width: number };
170
+
171
+ // ── Configure ───────────────────────────────────────────────────────
122
172
 
123
173
  interface ConfigureOptions {
124
174
  channelMaxSize?: number;
@@ -129,23 +179,35 @@ declare namespace Lync {
129
179
  stats?: boolean;
130
180
  }
131
181
 
132
- // ── Drop Sentinel ───────────────────────────────────────────────
182
+ // ── Registration debug shape ────────────────────────────────────────
133
183
 
134
- interface DropSentinel {
135
- /** @hidden */ readonly _lyncDrop: true;
184
+ interface RegistrationInfo {
185
+ name: string;
186
+ id: number;
187
+ kind: number;
188
+ isUnreliable: boolean;
136
189
  }
137
190
  }
138
191
 
139
192
  interface LyncModule {
140
- // ── Lifecycle ────────────────────────────────────────────────────
193
+ // ── Lifecycle ───────────────────────────────────────────────────────
141
194
 
142
195
  configure(this: void, options: Lync.ConfigureOptions): void;
143
196
  start(this: void): void;
144
197
  isStarted(this: void): boolean;
198
+ flush(this: void): void;
199
+ flushRate(this: void, hz: number): void;
200
+ /** Restore module state to post-require defaults. For tests / hot reload. */
201
+ reset(this: void): void;
145
202
 
146
- // ── Definitions ─────────────────────────────────────────────────
203
+ // ── Definitions ─────────────────────────────────────────────────────
147
204
 
148
- packet<T>(this: void, name: string, codec: Lync.Codec<T>, options?: Lync.PacketOptions<T>): Lync.Packet<T>;
205
+ packet<T>(
206
+ this: void,
207
+ name: string,
208
+ codec: Lync.Codec<T>,
209
+ options?: Lync.PacketOptions<T>,
210
+ ): Lync.Packet<T>;
149
211
 
150
212
  query<Req, Resp>(
151
213
  this: void,
@@ -158,46 +220,49 @@ interface LyncModule {
158
220
  group(this: void, name: string): Lync.Group;
159
221
  scope(this: void): Lync.Scope;
160
222
 
161
- // ── Targeting ───────────────────────────────────────────────────
223
+ // ── Targeting ───────────────────────────────────────────────────────
162
224
 
163
225
  readonly all: Lync.AllTarget;
164
226
  except(this: void, ...args: Array<Player | Lync.Group>): Lync.ExceptTarget;
165
227
  readonly DROP: Lync.DropSentinel;
166
228
 
167
- // ── Middleware ───────────────────────────────────────────────────
168
-
169
- onSend(this: void, fn: (data: unknown, name: string, player?: Player) => unknown): Lync.Connection;
170
- onReceive(this: void, fn: (data: unknown, name: string, player?: Player) => unknown): Lync.Connection;
171
- onDrop(this: void, fn: (player: Player, reason: string, name: string, data?: unknown) => void): Lync.Connection;
229
+ // ── Middleware ──────────────────────────────────────────────────────
172
230
 
173
- // ── Runtime Control ─────────────────────────────────────────────
174
-
175
- flush(this: void): void;
176
- flushRate(this: void, hz: number): void;
231
+ onSend(
232
+ this: void,
233
+ fn: (data: unknown, name: string, player?: Player) => unknown,
234
+ ): Lync.Connection;
235
+ onReceive(
236
+ this: void,
237
+ fn: (data: unknown, name: string, player?: Player) => unknown,
238
+ ): Lync.Connection;
239
+ onDrop(
240
+ this: void,
241
+ fn: (player: Player, reason: string, name: string, data?: unknown) => void,
242
+ ): Lync.Connection;
177
243
 
178
- // ── Stats ───────────────────────────────────────────────────────
244
+ // ── Stats ───────────────────────────────────────────────────────────
179
245
 
180
246
  readonly stats: {
247
+ /** Server-only; returns undefined on client or before stats=true. */
181
248
  player(this: void, player: Player): Lync.PlayerStats | undefined;
182
249
  reset(this: void): void;
183
250
  };
184
251
 
185
- // ── Debug ───────────────────────────────────────────────────────
252
+ // ── Debug ───────────────────────────────────────────────────────────
186
253
 
187
254
  readonly debug: {
255
+ /** Reserved no-op for capture/replay tooling. */
188
256
  capture(this: void, label?: string): void;
257
+ /** Reserved no-op for capture/replay tooling. */
189
258
  stop(this: void): void;
259
+ /** Reserved no-op for capture/replay tooling. */
190
260
  dump(this: void): void;
191
261
  pending(this: void): number;
192
- registrations(this: void): ReadonlyArray<{
193
- name: string;
194
- id: number;
195
- kind: number;
196
- isUnreliable: boolean;
197
- }>;
262
+ registrations(this: void): ReadonlyArray<Lync.RegistrationInfo>;
198
263
  };
199
264
 
200
- // ── Number Codecs ───────────────────────────────────────────────
265
+ // ── Number codecs ───────────────────────────────────────────────────
201
266
 
202
267
  int(this: void, min: number, max: number): Lync.Codec<number>;
203
268
  float(this: void, min: number, max: number, precision: number): Lync.Codec<number>;
@@ -206,12 +271,12 @@ interface LyncModule {
206
271
  readonly f64: Lync.Codec<number>;
207
272
  readonly bool: Lync.Codec<boolean>;
208
273
 
209
- // ── String & Buffer ─────────────────────────────────────────────
274
+ // ── String & buffer ─────────────────────────────────────────────────
210
275
 
211
276
  readonly string: Lync.StringCodec;
212
277
  readonly buff: Lync.Codec<buffer>;
213
278
 
214
- // ── Vectors & Spatial ───────────────────────────────────────────
279
+ // ── Vectors & spatial ───────────────────────────────────────────────
215
280
 
216
281
  readonly vec2: Lync.Vec2Codec;
217
282
  readonly vec3: Lync.Vec3Codec;
@@ -223,7 +288,7 @@ interface LyncModule {
223
288
  readonly vec2int16: Lync.Codec<Vector2int16>;
224
289
  readonly vec3int16: Lync.Codec<Vector3int16>;
225
290
 
226
- // ── Roblox Types ────────────────────────────────────────────────
291
+ // ── Roblox types ────────────────────────────────────────────────────
227
292
 
228
293
  readonly color3: Lync.Codec<Color3>;
229
294
  readonly inst: Lync.Codec<Instance>;
@@ -233,26 +298,65 @@ interface LyncModule {
233
298
  readonly numberSequence: Lync.Codec<NumberSequence>;
234
299
  readonly colorSequence: Lync.Codec<ColorSequence>;
235
300
 
236
- // ── Composites ──────────────────────────────────────────────────
301
+ // ── Composites ──────────────────────────────────────────────────────
302
+
303
+ struct<S extends Record<string, Lync.Codec<unknown>>>(
304
+ this: void,
305
+ schema: S,
306
+ ): Lync.Codec<Lync.InferSchema<S>>;
307
+
308
+ /** Reliable transport only; rejected at definition time on `unreliable`. */
309
+ deltaStruct<S extends Record<string, Lync.Codec<unknown>>>(
310
+ this: void,
311
+ schema: S,
312
+ ): Lync.Codec<Lync.InferSchema<S>>;
237
313
 
238
- struct<S extends Record<string, Lync.Codec<unknown>>>(this: void, schema: S): Lync.Codec<Lync.InferSchema<S>>;
239
- deltaStruct<S extends Record<string, Lync.Codec<unknown>>>(this: void, schema: S): Lync.Codec<Lync.InferSchema<S>>;
240
314
  array<T>(this: void, element: Lync.Codec<T>, maxCount?: number): Lync.Codec<T[]>;
315
+ /** Reliable transport only. */
241
316
  deltaArray<T>(this: void, element: Lync.Codec<T>, maxCount?: number): Lync.Codec<T[]>;
242
- map<K, V>(this: void, keyCodec: Lync.Codec<K>, valueCodec: Lync.Codec<V>, maxCount?: number): Lync.Codec<Map<K, V>>;
243
- deltaMap<K, V>(this: void, keyCodec: Lync.Codec<K>, valueCodec: Lync.Codec<V>, maxCount?: number): Lync.Codec<Map<K, V>>;
317
+
318
+ map<K, V>(
319
+ this: void,
320
+ keyCodec: Lync.Codec<K>,
321
+ valueCodec: Lync.Codec<V>,
322
+ maxCount?: number,
323
+ ): Lync.Codec<Map<K, V>>;
324
+ /** Reliable transport only. */
325
+ deltaMap<K, V>(
326
+ this: void,
327
+ keyCodec: Lync.Codec<K>,
328
+ valueCodec: Lync.Codec<V>,
329
+ maxCount?: number,
330
+ ): Lync.Codec<Map<K, V>>;
331
+
244
332
  optional<T>(this: void, codec: Lync.Codec<T>): Lync.Codec<T | undefined>;
245
- tuple<T extends Lync.Codec<unknown>[]>(this: void, ...codecs: T): Lync.Codec<{ [K in keyof T]: Lync.InferCodec<T[K]> }>;
333
+
334
+ tuple<T extends Lync.Codec<unknown>[]>(
335
+ this: void,
336
+ ...codecs: T
337
+ ): Lync.Codec<{ [K in keyof T]: Lync.InferCodec<T[K]> }>;
338
+
339
+ /**
340
+ * Discriminated union with a string tag field. Up to 256 variants;
341
+ * each variant is a struct codec keyed by name.
342
+ */
246
343
  tagged<Tag extends string, V extends Record<string, Lync.Codec<unknown>>>(
247
344
  this: void,
248
345
  tagField: Tag,
249
346
  variants: V,
250
- ): Lync.Codec<{ [K in keyof V & string]: { [F in Tag]: K } & Lync.InferCodec<V[K]> }[keyof V & string]>;
347
+ ): Lync.Codec<
348
+ { [K in keyof V & string]: { [F in Tag]: K } & Lync.InferCodec<V[K]> }[keyof V & string]
349
+ >;
251
350
 
252
- // ── Meta ────────────────────────────────────────────────────────
351
+ // ── Meta ────────────────────────────────────────────────────────────
253
352
 
254
353
  enum<T extends string[]>(this: void, ...values: T): Lync.Codec<T[number]>;
255
- bitfield(this: void, schema: Record<string, { type: "bool" } | { type: "uint"; width: number } | { type: "int"; width: number }>): Lync.Codec<Record<string, boolean | number>>;
354
+
355
+ bitfield(
356
+ this: void,
357
+ schema: Record<string, Lync.BitfieldField>,
358
+ ): Lync.Codec<Record<string, boolean | number>>;
359
+
256
360
  custom<T>(
257
361
  this: void,
258
362
  size: number,
@@ -260,8 +364,12 @@ interface LyncModule {
260
364
  read: (b: buffer, offset: number) => T,
261
365
  typeCheck?: string,
262
366
  ): Lync.Codec<T>;
367
+
368
+ /** 0-byte codec; reads `undefined`. Use for fire-and-forget signals. */
263
369
  readonly nothing: Lync.Codec<undefined>;
370
+ /** Bypasses serialization through the channel sidecar. Pair with `validate`. */
264
371
  readonly unknown: Lync.Codec<unknown>;
372
+ /** Self-describing; nil/bool/numbers/strings/buffers/Roblox datatypes. */
265
373
  readonly auto: Lync.Codec<unknown>;
266
374
  }
267
375