@axpecter/lync 2.3.1 → 2.3.3

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.
@@ -117,11 +117,24 @@ function Bitfield.define(schema: { [string]: FieldSpec }): Types.InternalCodec<a
117
117
  local packed = 0
118
118
  for i = 1, fieldCount do
119
119
  local v = value[fieldKeys[i]]
120
- if fieldTypes[i] == TYPE_BOOL then
120
+ local ft = fieldTypes[i]
121
+ if ft == TYPE_BOOL then
121
122
  if v then
122
123
  packed = bor(packed, lshift(1, fieldOffsets[i]))
123
124
  end
125
+ elseif ft == TYPE_UINT then
126
+ local mask = fieldMasks[i]
127
+ if v < 0 or v > mask or v % 1 ~= 0 then
128
+ Log.error(`field "{fieldKeys[i]}" value {v} out of uint range [0, {mask}]`)
129
+ end
130
+ packed = bor(packed, lshift(v, fieldOffsets[i]))
124
131
  else
132
+ local signBit = fieldSignBit[i]
133
+ if v < -signBit or v >= signBit or v % 1 ~= 0 then
134
+ Log.error(
135
+ `field "{fieldKeys[i]}" value {v} out of int range [{-signBit}, {signBit - 1}]`
136
+ )
137
+ end
125
138
  packed = bor(packed, lshift(band(v, fieldMasks[i]), fieldOffsets[i]))
126
139
  end
127
140
  end
@@ -0,0 +1,390 @@
1
+ --!strict
2
+ --!native
3
+ -- Delta scalars: zigzag-varint diff against previous frame for ints, floats, vec3, CFrame.
4
+
5
+ local Base = require(script.Parent.Parent.Base)
6
+ local Baseline = require(script.Parent.Parent.Parent.internal.Baseline)
7
+ local Log = require(script.Parent.Parent.Parent.util.Log)
8
+ local Quantize = require(script.Parent.Parent.Parent.util.Quantize)
9
+ local Quat = require(script.Parent.Parent.Parent.util.Quat)
10
+ local Shared = require(script.Parent.Parent.composite.Shared)
11
+ local Types = require(script.Parent.Parent.Parent.Types)
12
+ local Varint = require(script.Parent.Parent.primitive.Varint)
13
+ local Zint = require(script.Parent.Parent.primitive.Zint)
14
+
15
+ -- Constants --------------------------------------------------------------
16
+
17
+ local I32_MIN = -0x80000000
18
+ local I32_MAX = 0x7FFFFFFF
19
+
20
+ -- deltaCFrame header bits: which components changed since the last frame.
21
+ local CF_X = 0x01
22
+ local CF_Y = 0x02
23
+ local CF_Z = 0x04
24
+ local CF_ROT = 0x08
25
+ local CF_ALL = 0x0F
26
+
27
+ -- Private ----------------------------------------------------------------
28
+
29
+ local ensure = Base.ensure
30
+ local allocDeltaId = Shared.allocDeltaId
31
+ local floor = math.floor
32
+ local clamp = math.clamp
33
+ local round = math.round
34
+ local band = bit32.band
35
+ local bor = bit32.bor
36
+ local readu8 = buffer.readu8
37
+ local writeu8 = buffer.writeu8
38
+ local readu32 = buffer.readu32
39
+ local writeu32 = buffer.writeu32
40
+ local bufLen = buffer.len
41
+ local varintWrite = Varint.write
42
+ local varintRead = Varint.read
43
+ local zigzagEncode = Zint.encode
44
+ local zigzagDecode = Zint.decode
45
+ local quatPack = Quat.pack
46
+ local quatUnpack = Quat.unpack
47
+ local quatFromCFrame = Quat.fromCFrame
48
+
49
+ local function quantizeVec3(
50
+ v: Vector3,
51
+ min: number,
52
+ delta: number,
53
+ scale: number
54
+ ): (number, number, number)
55
+ return round(clamp(v.X - min, 0, delta) * scale),
56
+ round(clamp(v.Y - min, 0, delta) * scale),
57
+ round(clamp(v.Z - min, 0, delta) * scale)
58
+ end
59
+
60
+ -- Reject out-of-range components rather than silently snapping via clamp().
61
+ local function validateVec3(v: Vector3, min: number, max: number, label: string): ()
62
+ if v.X < min or v.X > max or v.Y < min or v.Y > max or v.Z < min or v.Z > max then
63
+ Log.error(`{label} ({v.X}, {v.Y}, {v.Z}) has component out of range [{min}, {max}]`)
64
+ end
65
+ end
66
+
67
+ local function getScalarPrev(ch: Types.ChannelState, id: number, fallback: number): number
68
+ local cache = ch.deltas[id] :: any
69
+ return if cache then cache.scalar :: number else fallback
70
+ end
71
+
72
+ local function setScalarCache(ch: Types.ChannelState, id: number, value: number): ()
73
+ local cache = ch.deltas[id] :: any
74
+ if cache then
75
+ cache.scalar = value
76
+ else
77
+ ch.deltas[id] = { scalar = value } :: any
78
+ end
79
+ end
80
+
81
+ -- Public -----------------------------------------------------------------
82
+
83
+ local DeltaScalar = {}
84
+
85
+ --[[
86
+ Variable-length integer that emits zigzag varint of (current - previous).
87
+ First frame uses `min` as the implicit prev so the initial value encodes
88
+ as (value - min). Reliable transport only — a dropped frame desyncs prev.
89
+
90
+ Wire: 1 byte for unchanged or [-96, 95] mutations; up to 5 bytes for full i32.
91
+ ]]
92
+ function DeltaScalar.deltaInt(min: number, max: number): Types.InternalCodec<number>
93
+ if min > max then
94
+ Log.error(`min ({min}) must be <= max ({max})`)
95
+ end
96
+ if floor(min) ~= min or floor(max) ~= max then
97
+ Log.error("bounds must be integers")
98
+ end
99
+ if max - min > I32_MAX then
100
+ Log.error(`range too wide; (max - min) must fit in i32, got {max - min}`)
101
+ end
102
+
103
+ local deltaId = allocDeltaId()
104
+
105
+ return table.freeze({
106
+ _isDelta = true,
107
+ _typeCheck = "number",
108
+ _isInteger = true,
109
+ _min = min,
110
+ _max = max,
111
+
112
+ write = function(ch: Types.ChannelState, value: number): ()
113
+ if value < min or value > max then
114
+ Log.error(`value {value} out of range [{min}, {max}]`)
115
+ end
116
+ if floor(value) ~= value then
117
+ Log.error(`value {value} must be an integer`)
118
+ end
119
+ varintWrite(ch, zigzagEncode(value - getScalarPrev(ch, deltaId, min)))
120
+ setScalarCache(ch, deltaId, value)
121
+ end,
122
+
123
+ read = function(src: buffer, pos: number, _refs: { Instance }?): (number, number)
124
+ local raw, consumed = varintRead(src, pos)
125
+ if consumed == 0 then
126
+ Log.error("truncated deltaInt")
127
+ end
128
+ local cached = Baseline.getCache(deltaId)
129
+ local value = (if cached ~= nil then cached :: number else min) + zigzagDecode(raw)
130
+ if value < min or value > max then
131
+ Log.error(`deltaInt decoded value {value} out of range [{min}, {max}]`)
132
+ end
133
+ Baseline.setCache(deltaId, value)
134
+ return value, consumed
135
+ end,
136
+ }) :: Types.InternalCodec<number>
137
+ end
138
+
139
+ --[[
140
+ Quantized float with diffs in integer wire space (post-quantize). Storing
141
+ the quantized representative — not the float — across frames means
142
+ accumulating diffs over thousands of frames cannot drift past the grid.
143
+ ]]
144
+ function DeltaScalar.deltaFloat(
145
+ min: number,
146
+ max: number,
147
+ precision: number
148
+ ): Types.InternalCodec<number>
149
+ if max - min == 0 then
150
+ return Base.constant(min)
151
+ end
152
+
153
+ local _, _, _, scale, invScale, delta = Quantize.setup("Lync.deltaFloat", min, max, precision)
154
+ local deltaId = allocDeltaId()
155
+
156
+ return table.freeze({
157
+ _isDelta = true,
158
+ _typeCheck = "number",
159
+ _min = min,
160
+ _max = max,
161
+
162
+ write = function(ch: Types.ChannelState, value: number): ()
163
+ local q = round(clamp(value - min, 0, delta) * scale)
164
+ varintWrite(ch, zigzagEncode(q - getScalarPrev(ch, deltaId, 0)))
165
+ setScalarCache(ch, deltaId, q)
166
+ end,
167
+
168
+ read = function(src: buffer, pos: number, _refs: { Instance }?): (number, number)
169
+ local raw, consumed = varintRead(src, pos)
170
+ if consumed == 0 then
171
+ Log.error("truncated deltaFloat")
172
+ end
173
+ local cached = Baseline.getCache(deltaId)
174
+ local q = (if cached ~= nil then cached :: number else 0) + zigzagDecode(raw)
175
+ Baseline.setCache(deltaId, q)
176
+ return q * invScale + min, consumed
177
+ end,
178
+ }) :: Types.InternalCodec<number>
179
+ end
180
+
181
+ --[[
182
+ Quantized Vector3 with per-axis zigzag varint diffs. Static value: 3 bytes
183
+ (three zero zigzags); typical < 1 stud motion at 0.01-stud precision: 3-6
184
+ bytes. Baseline vec3 = 12 bytes, so 4x reduction at the static end.
185
+ ]]
186
+ function DeltaScalar.deltaVec3(
187
+ min: number,
188
+ max: number,
189
+ precision: number
190
+ ): Types.InternalCodec<Vector3>
191
+ if max - min == 0 then
192
+ return Base.constant(Vector3.new(min, min, min)) :: Types.InternalCodec<Vector3>
193
+ end
194
+
195
+ local _, _, _, scale, invScale, delta = Quantize.setup("Lync.deltaVec3", min, max, precision)
196
+ local deltaId = allocDeltaId()
197
+
198
+ return table.freeze({
199
+ _isDelta = true,
200
+ _typeCheck = "Vector3",
201
+ _min = min,
202
+ _max = max,
203
+
204
+ write = function(ch: Types.ChannelState, value: Vector3): ()
205
+ validateVec3(value, min, max, "Vector3")
206
+ local qx, qy, qz = quantizeVec3(value, min, delta, scale)
207
+ local cache = ch.deltas[deltaId] :: any
208
+ local px, py, pz = 0, 0, 0
209
+ if cache then
210
+ px, py, pz = cache.qx :: number, cache.qy :: number, cache.qz :: number
211
+ end
212
+
213
+ varintWrite(ch, zigzagEncode(qx - px))
214
+ varintWrite(ch, zigzagEncode(qy - py))
215
+ varintWrite(ch, zigzagEncode(qz - pz))
216
+
217
+ if cache then
218
+ cache.qx, cache.qy, cache.qz = qx, qy, qz
219
+ else
220
+ ch.deltas[deltaId] = { qx = qx, qy = qy, qz = qz } :: any
221
+ end
222
+ end,
223
+
224
+ read = function(src: buffer, pos: number, _refs: { Instance }?): (Vector3, number)
225
+ local rx, cx = varintRead(src, pos)
226
+ if cx == 0 then
227
+ Log.error("truncated deltaVec3 X")
228
+ end
229
+ local ry, cy = varintRead(src, pos + cx)
230
+ if cy == 0 then
231
+ Log.error("truncated deltaVec3 Y")
232
+ end
233
+ local rz, cz = varintRead(src, pos + cx + cy)
234
+ if cz == 0 then
235
+ Log.error("truncated deltaVec3 Z")
236
+ end
237
+
238
+ local cached = Baseline.getCache(deltaId) :: any
239
+ local px, py, pz = 0, 0, 0
240
+ if cached then
241
+ px, py, pz = cached.qx, cached.qy, cached.qz
242
+ end
243
+ local qx = px + zigzagDecode(rx)
244
+ local qy = py + zigzagDecode(ry)
245
+ local qz = pz + zigzagDecode(rz)
246
+ Baseline.setCache(deltaId, { qx = qx, qy = qy, qz = qz })
247
+
248
+ return Vector3.new(qx * invScale + min, qy * invScale + min, qz * invScale + min),
249
+ cx + cy + cz
250
+ end,
251
+ }) :: Types.InternalCodec<Vector3>
252
+ end
253
+
254
+ --[[
255
+ Quantized position + smallest-three quaternion rotation. Wire layout:
256
+ byte 0 header bits: bit0=Δx, bit1=Δy, bit2=Δz, bit3=Δrot
257
+ For each set position bit: zigzag varint of quantized diff
258
+ If rotation bit set: 4-byte packed quat
259
+
260
+ Static CFrame: 1 byte. Pos-only motion: 1 + 3 zint bytes (~4-7).
261
+ Full update: 1 + 3 zint + 4 quat (~8-13). Baseline lossless: 24 bytes.
262
+ ]]
263
+ function DeltaScalar.deltaCFrame(
264
+ posMin: number,
265
+ posMax: number,
266
+ posPrecision: number
267
+ ): Types.InternalCodec<CFrame>
268
+ -- Quantize.setup itself rejects min == max; the redundant guard is gone.
269
+ local _, _, _, scale, invScale, delta =
270
+ Quantize.setup("Lync.deltaCFrame", posMin, posMax, posPrecision)
271
+ local deltaId = allocDeltaId()
272
+
273
+ return table.freeze({
274
+ _isDelta = true,
275
+ _typeCheck = "CFrame",
276
+
277
+ write = function(ch: Types.ChannelState, value: CFrame): ()
278
+ local pos = value.Position
279
+ validateVec3(pos, posMin, posMax, "CFrame position")
280
+ local qx, qy, qz = quantizeVec3(pos, posMin, delta, scale)
281
+ local packed = quatPack(quatFromCFrame(value))
282
+
283
+ -- prev = 0 so the first frame writes absolute values; receiver also defaults to 0.
284
+ local cache = ch.deltas[deltaId] :: any
285
+ local flag: number
286
+ local px, py, pz = 0, 0, 0
287
+ if cache then
288
+ px = cache.qx :: number
289
+ py = cache.qy :: number
290
+ pz = cache.qz :: number
291
+ local ppacked = cache.qrot :: number
292
+ flag = 0
293
+ if qx ~= px then
294
+ flag = bor(flag, CF_X)
295
+ end
296
+ if qy ~= py then
297
+ flag = bor(flag, CF_Y)
298
+ end
299
+ if qz ~= pz then
300
+ flag = bor(flag, CF_Z)
301
+ end
302
+ if packed ~= ppacked then
303
+ flag = bor(flag, CF_ROT)
304
+ end
305
+ else
306
+ -- First frame: emit everything so the receiver has a baseline.
307
+ flag = CF_ALL
308
+ end
309
+
310
+ ensure(ch, 1)
311
+ writeu8(ch.buff, ch.cursor, flag)
312
+ ch.cursor += 1
313
+
314
+ if band(flag, CF_X) ~= 0 then
315
+ varintWrite(ch, zigzagEncode(qx - px))
316
+ end
317
+ if band(flag, CF_Y) ~= 0 then
318
+ varintWrite(ch, zigzagEncode(qy - py))
319
+ end
320
+ if band(flag, CF_Z) ~= 0 then
321
+ varintWrite(ch, zigzagEncode(qz - pz))
322
+ end
323
+ if band(flag, CF_ROT) ~= 0 then
324
+ ensure(ch, 4)
325
+ writeu32(ch.buff, ch.cursor, packed)
326
+ ch.cursor += 4
327
+ end
328
+
329
+ if cache then
330
+ cache.qx, cache.qy, cache.qz, cache.qrot = qx, qy, qz, packed
331
+ else
332
+ ch.deltas[deltaId] = { qx = qx, qy = qy, qz = qz, qrot = packed } :: any
333
+ end
334
+ end,
335
+
336
+ read = function(src: buffer, pos: number, _refs: { Instance }?): (CFrame, number)
337
+ if pos >= bufLen(src) then
338
+ Log.error("truncated deltaCFrame header")
339
+ end
340
+ local flag = readu8(src, pos)
341
+ local total = 1
342
+
343
+ local cached = Baseline.getCache(deltaId) :: any
344
+ local qx, qy, qz, packed = 0, 0, 0, 0
345
+ if cached then
346
+ qx, qy, qz, packed = cached.qx, cached.qy, cached.qz, cached.qrot
347
+ end
348
+
349
+ if band(flag, CF_X) ~= 0 then
350
+ local r, c = varintRead(src, pos + total)
351
+ if c == 0 then
352
+ Log.error("truncated deltaCFrame X")
353
+ end
354
+ qx += zigzagDecode(r)
355
+ total += c
356
+ end
357
+ if band(flag, CF_Y) ~= 0 then
358
+ local r, c = varintRead(src, pos + total)
359
+ if c == 0 then
360
+ Log.error("truncated deltaCFrame Y")
361
+ end
362
+ qy += zigzagDecode(r)
363
+ total += c
364
+ end
365
+ if band(flag, CF_Z) ~= 0 then
366
+ local r, c = varintRead(src, pos + total)
367
+ if c == 0 then
368
+ Log.error("truncated deltaCFrame Z")
369
+ end
370
+ qz += zigzagDecode(r)
371
+ total += c
372
+ end
373
+ if band(flag, CF_ROT) ~= 0 then
374
+ if pos + total + 4 > bufLen(src) then
375
+ Log.error("truncated deltaCFrame rotation")
376
+ end
377
+ packed = readu32(src, pos + total)
378
+ total += 4
379
+ end
380
+
381
+ Baseline.setCache(deltaId, { qx = qx, qy = qy, qz = qz, qrot = packed })
382
+
383
+ local position =
384
+ Vector3.new(qx * invScale + posMin, qy * invScale + posMin, qz * invScale + posMin)
385
+ return CFrame.new(position) * quatUnpack(packed), total
386
+ end,
387
+ }) :: Types.InternalCodec<CFrame>
388
+ end
389
+
390
+ return table.freeze(DeltaScalar)
@@ -35,7 +35,7 @@ local U32_MAX = Constants.U32_MAX
35
35
 
36
36
  -- Private ----------------------------------------------------------------
37
37
 
38
- local alloc = Base.alloc
38
+ local ensure = Base.ensure
39
39
  local band = bit32.band
40
40
  local bor = bit32.bor
41
41
  local rshift = bit32.rshift
@@ -46,18 +46,24 @@ local readu8 = buffer.readu8
46
46
  local readu16 = buffer.readu16
47
47
  local readu32 = buffer.readu32
48
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
-
55
49
  -- Public -----------------------------------------------------------------
56
50
 
57
51
  local Varint = {}
58
52
 
59
53
  Varint.INLINE_MAX = INLINE_MAX
60
54
 
55
+ -- Wire byte count for a value, without encoding it. Used by cost heuristics.
56
+ function Varint.length(value: number): number
57
+ if value <= INLINE_MAX then
58
+ return 1
59
+ elseif value <= TWO_MAX then
60
+ return 2
61
+ elseif value <= THREE_MAX then
62
+ return 3
63
+ end
64
+ return 5
65
+ end
66
+
61
67
  function Varint.write(ch: Types.ChannelState, value: number): ()
62
68
  if value < 0 or value > U32_MAX or value % 1 ~= 0 then
63
69
  Log.error(`value must be an integer in [0, {U32_MAX}], got {value}`)
@@ -0,0 +1,99 @@
1
+ --!strict
2
+ --!native
3
+ -- Signed varint via zigzag mapping. Small magnitudes pack in 1 byte.
4
+
5
+ local Log = require(script.Parent.Parent.Parent.util.Log)
6
+ local Types = require(script.Parent.Parent.Parent.Types)
7
+ local Varint = require(script.Parent.Parent.primitive.Varint)
8
+
9
+ -- Constants --------------------------------------------------------------
10
+
11
+ --[[
12
+ Zigzag fold maps signed values onto unsigned varint space:
13
+ n >= 0 -> 2n
14
+ n < 0 -> -2n - 1
15
+ Inverse: low bit selects sign, shift down 1 bit.
16
+
17
+ Wire ranges (using Varint dense form):
18
+ 1 byte: [-96, 95]
19
+ 2 bytes: [-4192, 4191]
20
+ 3 bytes: [-528480, 528479]
21
+ 5 bytes: full i32
22
+ ]]
23
+ local I32_MIN = -0x80000000
24
+ local I32_MAX = 0x7FFFFFFF
25
+
26
+ -- Private ----------------------------------------------------------------
27
+
28
+ local floor = math.floor
29
+ local band = bit32.band
30
+ local varintWrite = Varint.write
31
+ local varintRead = Varint.read
32
+
33
+ -- Signed -> unsigned. Hot path; called per write of every delta scalar.
34
+ local function encode(value: number): number
35
+ if value >= 0 then
36
+ return value * 2
37
+ end
38
+ return value * -2 - 1
39
+ end
40
+
41
+ -- Inverse of encode. Even -> n/2; odd -> -((n+1)/2). Branchless on the half.
42
+ local function decode(raw: number): number
43
+ local low = band(raw, 1)
44
+ local half = (raw - low) / 2
45
+ return if low == 1 then -half - 1 else half
46
+ end
47
+
48
+ -- Public -----------------------------------------------------------------
49
+
50
+ local Zint = {}
51
+
52
+ Zint.encode = encode
53
+ Zint.decode = decode
54
+
55
+ --[[
56
+ Variable-length signed integer in [I32_MIN, I32_MAX]. Optional [min, max]
57
+ bounds gate input at write time so out-of-range payloads error early
58
+ rather than corrupting downstream decoders.
59
+ ]]
60
+ function Zint.zint(min: number?, max: number?): Types.InternalCodec<number>
61
+ local lo = min or I32_MIN
62
+ local hi = max or I32_MAX
63
+ if lo > hi then
64
+ Log.error(`min ({lo}) must be <= max ({hi})`)
65
+ end
66
+ if floor(lo) ~= lo or floor(hi) ~= hi then
67
+ Log.error("bounds must be integers")
68
+ end
69
+ if lo < I32_MIN or hi > I32_MAX then
70
+ Log.error(`range [{lo}, {hi}] exceeds i32 bounds`)
71
+ end
72
+
73
+ return table.freeze({
74
+ _typeCheck = "number",
75
+ _isInteger = true,
76
+ _min = lo,
77
+ _max = hi,
78
+
79
+ write = function(ch: Types.ChannelState, value: number): ()
80
+ if value < lo or value > hi then
81
+ Log.error(`value {value} out of range [{lo}, {hi}]`)
82
+ end
83
+ if floor(value) ~= value then
84
+ Log.error(`value {value} must be an integer`)
85
+ end
86
+ varintWrite(ch, encode(value))
87
+ end,
88
+
89
+ read = function(src: buffer, pos: number, _refs: { Instance }?): (number, number)
90
+ local raw, consumed = varintRead(src, pos)
91
+ if consumed == 0 then
92
+ Log.error("truncated zint")
93
+ end
94
+ return decode(raw), consumed
95
+ end,
96
+ }) :: Types.InternalCodec<number>
97
+ end
98
+
99
+ return table.freeze(Zint)
package/src/index.d.ts CHANGED
@@ -265,12 +265,58 @@ interface LyncModule {
265
265
  // ── Number codecs ───────────────────────────────────────────────────
266
266
 
267
267
  int(this: void, min: number, max: number): Lync.Codec<number>;
268
+ /**
269
+ * Variable-length signed int via zigzag varint. 1 byte for values in
270
+ * [-96, 95]; up to 5 bytes for full i32. Optional bounds gate input.
271
+ */
272
+ zint(this: void, min?: number, max?: number): Lync.Codec<number>;
268
273
  float(this: void, min: number, max: number, precision: number): Lync.Codec<number>;
269
274
  readonly f16: Lync.Codec<number>;
270
275
  readonly f32: Lync.Codec<number>;
271
276
  readonly f64: Lync.Codec<number>;
272
277
  readonly bool: Lync.Codec<boolean>;
273
278
 
279
+ // ── Delta scalars (reliable transport only) ─────────────────────────
280
+
281
+ /**
282
+ * Integer that emits zigzag varint of (current - previous). Reliable
283
+ * transport only; a dropped frame desyncs the receiver. Best for ints
284
+ * mutating slowly across a wide range (saves vs fixed u16/u24/u32).
285
+ */
286
+ deltaInt(this: void, min: number, max: number): Lync.Codec<number>;
287
+ /**
288
+ * Quantized float with per-frame diff in integer wire space (no drift).
289
+ * Same wire-size profile as deltaInt. Reliable transport only.
290
+ */
291
+ deltaFloat(
292
+ this: void,
293
+ min: number,
294
+ max: number,
295
+ precision: number,
296
+ ): Lync.Codec<number>;
297
+ /**
298
+ * Quantized Vector3 with per-axis zigzag varint diffs. ~3 bytes for
299
+ * unchanged, 3-15 bytes for typical motion vs 12 bytes baseline.
300
+ * Reliable transport only.
301
+ */
302
+ deltaVec3(
303
+ this: void,
304
+ min: number,
305
+ max: number,
306
+ precision: number,
307
+ ): Lync.Codec<Vector3>;
308
+ /**
309
+ * Quantized position + smallest-three quaternion rotation. 1 byte for
310
+ * fully static; 4-7 bytes for position-only motion; up to 13 bytes
311
+ * for full pose changes vs 24 bytes baseline. Reliable transport only.
312
+ */
313
+ deltaCFrame(
314
+ this: void,
315
+ posMin: number,
316
+ posMax: number,
317
+ posPrecision: number,
318
+ ): Lync.Codec<CFrame>;
319
+
274
320
  // ── String & buffer ─────────────────────────────────────────────────
275
321
 
276
322
  readonly string: Lync.StringCodec;
package/src/init.luau CHANGED
@@ -27,6 +27,7 @@ local BufferC = require(script.codec.datatype.Buffer)
27
27
  local CFrameC = require(script.codec.datatype.CFrame)
28
28
  local ColorC = require(script.codec.datatype.Color)
29
29
  local CustomC = require(script.codec.meta.Custom)
30
+ local DeltaScalarC = require(script.codec.meta.DeltaScalar)
30
31
  local EnumC = require(script.codec.meta.Enum)
31
32
  local Float16C = require(script.codec.primitive.Float16)
32
33
  local FloatC = require(script.codec.meta.Float)
@@ -49,6 +50,7 @@ local TupleC = require(script.codec.composite.Tuple)
49
50
  local UDimC = require(script.codec.datatype.UDim)
50
51
  local UnknownC = require(script.codec.meta.Unknown)
51
52
  local VectorC = require(script.codec.datatype.Vector)
53
+ local ZintC = require(script.codec.primitive.Zint)
52
54
 
53
55
  -- Public types -----------------------------------------------------------
54
56
 
@@ -313,7 +315,12 @@ Lync.debug = table.freeze({
313
315
  })
314
316
 
315
317
  Lync.int = IntC.int
318
+ Lync.zint = ZintC.zint
316
319
  Lync.float = FloatC.float
320
+ Lync.deltaInt = DeltaScalarC.deltaInt
321
+ Lync.deltaFloat = DeltaScalarC.deltaFloat
322
+ Lync.deltaVec3 = DeltaScalarC.deltaVec3
323
+ Lync.deltaCFrame = DeltaScalarC.deltaCFrame
317
324
  Lync.f16 = Float16C.f16
318
325
  Lync.f32 = NumberC.f32
319
326
  Lync.f64 = NumberC.f64