@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,6 +1,6 @@
1
1
  --!strict
2
2
  --!native
3
- -- CFrame codec with __call factory for smallest-three quaternion compression.
3
+ -- CFrame codec. Default lossless (24B); __call yields smallest-three quat (16B).
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
  local Types = require(script.Parent.Parent.Parent.Types)
@@ -17,6 +17,11 @@ local Q_INV = (2 * INV_SQRT2) / 1023
17
17
  -- Private ----------------------------------------------------------------
18
18
 
19
19
  local alloc = Base.alloc
20
+ local writef32 = buffer.writef32
21
+ local readf32 = buffer.readf32
22
+ local writeu32 = buffer.writeu32
23
+ local readu32 = buffer.readu32
24
+
20
25
  local sqrt = math.sqrt
21
26
  local cos = math.cos
22
27
  local sin = math.sin
@@ -32,29 +37,29 @@ local rshift = bit32.rshift
32
37
 
33
38
  local function writeLossless(b: buffer, off: number, value: CFrame): ()
34
39
  local pos = value.Position
35
- buffer.writef32(b, off, pos.X)
36
- buffer.writef32(b, off + 4, pos.Y)
37
- buffer.writef32(b, off + 8, pos.Z)
40
+ writef32(b, off, pos.X)
41
+ writef32(b, off + 4, pos.Y)
42
+ writef32(b, off + 8, pos.Z)
38
43
 
39
44
  local axis, angle = value:ToAxisAngle()
40
45
  if angle < EPSILON then
41
- buffer.writef32(b, off + 12, 0)
42
- buffer.writef32(b, off + 16, 0)
43
- buffer.writef32(b, off + 20, 0)
46
+ writef32(b, off + 12, 0)
47
+ writef32(b, off + 16, 0)
48
+ writef32(b, off + 20, 0)
44
49
  return
45
50
  end
46
- buffer.writef32(b, off + 12, axis.X * angle)
47
- buffer.writef32(b, off + 16, axis.Y * angle)
48
- buffer.writef32(b, off + 20, axis.Z * angle)
51
+ writef32(b, off + 12, axis.X * angle)
52
+ writef32(b, off + 16, axis.Y * angle)
53
+ writef32(b, off + 20, axis.Z * angle)
49
54
  end
50
55
 
51
56
  local function readLossless(b: buffer, off: number): CFrame
52
- local px = buffer.readf32(b, off)
53
- local py = buffer.readf32(b, off + 4)
54
- local pz = buffer.readf32(b, off + 8)
55
- local rx = buffer.readf32(b, off + 12)
56
- local ry = buffer.readf32(b, off + 16)
57
- local rz = buffer.readf32(b, off + 20)
57
+ local px = readf32(b, off)
58
+ local py = readf32(b, off + 4)
59
+ local pz = readf32(b, off + 8)
60
+ local rx = readf32(b, off + 12)
61
+ local ry = readf32(b, off + 16)
62
+ local rz = readf32(b, off + 20)
58
63
 
59
64
  local sqLen = rx * rx + ry * ry + rz * rz
60
65
  if sqLen < EPSILON_SQ then
@@ -68,15 +73,15 @@ local function readLossless(b: buffer, off: number): CFrame
68
73
  end
69
74
 
70
75
  --[[
71
- Compressed wire layout (16 bytes total):
76
+ Compressed wire (16B):
72
77
  [0..11] position 3x f32
73
- [12..15] packed [largest:2][a:10][b:10][c:10] smallest-three quat
78
+ [12..15] packed [largest:2][a:10][b:10][c:10] (smallest-three quat)
74
79
  ]]
75
80
  local function writeCompressed(b: buffer, off: number, value: CFrame): ()
76
81
  local pos = value.Position
77
- buffer.writef32(b, off, pos.X)
78
- buffer.writef32(b, off + 4, pos.Y)
79
- buffer.writef32(b, off + 8, pos.Z)
82
+ writef32(b, off, pos.X)
83
+ writef32(b, off + 4, pos.Y)
84
+ writef32(b, off + 8, pos.Z)
80
85
 
81
86
  local axis, angle = value:ToAxisAngle()
82
87
  local h = angle * 0.5
@@ -86,6 +91,7 @@ local function writeCompressed(b: buffer, off: number, value: CFrame): ()
86
91
  local qz = axis.Z * s
87
92
  local qw = cos(h)
88
93
 
94
+ -- Smallest-three: drop the largest |component|; track index alongside maxVal.
89
95
  local ax, ay, az, aw = abs(qx), abs(qy), abs(qz), abs(qw)
90
96
  local largest, maxVal = 3, aw
91
97
  if ax > maxVal then
@@ -95,29 +101,34 @@ local function writeCompressed(b: buffer, off: number, value: CFrame): ()
95
101
  largest, maxVal = 1, ay
96
102
  end
97
103
  if az > maxVal then
98
- largest = 2
99
- end
100
-
101
- local sign: number
102
- if largest == 0 then
103
- sign = if qx < 0 then -1 else 1
104
- elseif largest == 1 then
105
- sign = if qy < 0 then -1 else 1
106
- elseif largest == 2 then
107
- sign = if qz < 0 then -1 else 1
108
- else
109
- sign = if qw < 0 then -1 else 1
104
+ largest, maxVal = 2, az
110
105
  end
111
- qx, qy, qz, qw = qx * sign, qy * sign, qz * sign, qw * sign
112
106
 
113
- local a, bComp, c: number
107
+ --[[
108
+ Force the dropped component non-negative so the decoder's
109
+ +sqrt(1 - a^2 - b^2 - c^2) reconstructs the right sign. Dispatch on
110
+ `largest` instead of building a 4-element table per call.
111
+ ]]
112
+ local a: number, bComp: number, c: number
114
113
  if largest == 0 then
114
+ if qx < 0 then
115
+ qy, qz, qw = -qy, -qz, -qw
116
+ end
115
117
  a, bComp, c = qy, qz, qw
116
118
  elseif largest == 1 then
119
+ if qy < 0 then
120
+ qx, qz, qw = -qx, -qz, -qw
121
+ end
117
122
  a, bComp, c = qx, qz, qw
118
123
  elseif largest == 2 then
124
+ if qz < 0 then
125
+ qx, qy, qw = -qx, -qy, -qw
126
+ end
119
127
  a, bComp, c = qx, qy, qw
120
128
  else
129
+ if qw < 0 then
130
+ qx, qy, qz = -qx, -qy, -qz
131
+ end
121
132
  a, bComp, c = qx, qy, qz
122
133
  end
123
134
 
@@ -125,15 +136,15 @@ local function writeCompressed(b: buffer, off: number, value: CFrame): ()
125
136
  local qb = clamp(round((bComp + INV_SQRT2) * Q_SCALE), 0, 1023)
126
137
  local qc = clamp(round((c + INV_SQRT2) * Q_SCALE), 0, 1023)
127
138
 
128
- buffer.writeu32(b, off + 12, bor(lshift(largest, 30), lshift(qa, 20), lshift(qb, 10), qc))
139
+ writeu32(b, off + 12, bor(lshift(largest, 30), lshift(qa, 20), lshift(qb, 10), qc))
129
140
  end
130
141
 
131
142
  local function readCompressed(b: buffer, off: number): CFrame
132
- local px = buffer.readf32(b, off)
133
- local py = buffer.readf32(b, off + 4)
134
- local pz = buffer.readf32(b, off + 8)
143
+ local px = readf32(b, off)
144
+ local py = readf32(b, off + 4)
145
+ local pz = readf32(b, off + 8)
135
146
 
136
- local packed = buffer.readu32(b, off + 12)
147
+ local packed = readu32(b, off + 12)
137
148
  local largest = rshift(packed, 30)
138
149
  local qa = band(rshift(packed, 20), 0x3FF)
139
150
  local qb = band(rshift(packed, 10), 0x3FF)
@@ -146,7 +157,8 @@ local function readCompressed(b: buffer, off: number): CFrame
146
157
  local sqSum = a * a + bComp * bComp + c * c
147
158
  local d = sqrt(if sqSum < 1 then 1 - sqSum else 0)
148
159
 
149
- local qx, qy, qz, qw: number
160
+ -- Mirror encoder: dropped index gets `d`, others receive (a, bComp, c).
161
+ local qx: number, qy: number, qz: number, qw: number
150
162
  if largest == 0 then
151
163
  qx, qy, qz, qw = d, a, bComp, c
152
164
  elseif largest == 1 then
@@ -158,13 +170,13 @@ local function readCompressed(b: buffer, off: number): CFrame
158
170
  end
159
171
 
160
172
  local origin = CFrame.new(px, py, pz)
161
- if qx * qx + qy * qy + qz * qz < EPSILON_SQ then
173
+ local sqLen = qx * qx + qy * qy + qz * qz
174
+ if sqLen < EPSILON_SQ then
162
175
  return origin
163
176
  end
164
177
 
165
- local len = sqrt(qx * qx + qy * qy + qz * qz)
166
178
  local angle = 2 * acos(clamp(qw, -1, 1))
167
- local inv = 1 / len
179
+ local inv = 1 / sqrt(sqLen)
168
180
  return origin * CFrame.fromAxisAngle(Vector3.new(qx * inv, qy * inv, qz * inv), angle)
169
181
  end
170
182
 
@@ -1,26 +1,26 @@
1
1
  --!strict
2
- --!optimize 2
3
- -- Color3 codec. 3 bytes, R/G/B u8 per channel, clamped to [0, 1].
2
+ --!native
3
+ -- Color3 codec. 3 bytes (R/G/B u8 per channel), clamped to [0, 1].
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
+ local Constants = require(script.Parent.Parent.Parent.util.Constants)
6
7
 
7
8
  -- Private ----------------------------------------------------------------
8
9
 
10
+ local U8_MAX = Constants.U8_MAX
11
+ local writeu8 = buffer.writeu8
12
+ local readu8 = buffer.readu8
9
13
  local round = math.round
10
14
  local clamp = math.clamp
11
15
 
12
16
  local function writeColor3(b: buffer, off: number, value: Color3): ()
13
- buffer.writeu8(b, off, round(clamp(value.R, 0, 1) * 255))
14
- buffer.writeu8(b, off + 1, round(clamp(value.G, 0, 1) * 255))
15
- buffer.writeu8(b, off + 2, round(clamp(value.B, 0, 1) * 255))
17
+ writeu8(b, off, round(clamp(value.R, 0, 1) * U8_MAX))
18
+ writeu8(b, off + 1, round(clamp(value.G, 0, 1) * U8_MAX))
19
+ writeu8(b, off + 2, round(clamp(value.B, 0, 1) * U8_MAX))
16
20
  end
17
21
 
18
22
  local function readColor3(b: buffer, off: number): Color3
19
- return Color3.fromRGB(
20
- buffer.readu8(b, off),
21
- buffer.readu8(b, off + 1),
22
- buffer.readu8(b, off + 2)
23
- )
23
+ return Color3.fromRGB(readu8(b, off), readu8(b, off + 1), readu8(b, off + 2))
24
24
  end
25
25
 
26
26
  -- Public -----------------------------------------------------------------
@@ -1,8 +1,9 @@
1
1
  --!strict
2
- --!optimize 2
3
- -- Instance codec via sidecar reference array.
2
+ --!native
3
+ -- Instance codec via the channel's sidecar reference array.
4
4
 
5
5
  local Channel = require(script.Parent.Parent.Parent.internal.Channel)
6
+ local Log = require(script.Parent.Parent.Parent.util.Log)
6
7
  local Types = require(script.Parent.Parent.Parent.Types)
7
8
 
8
9
  -- Private ----------------------------------------------------------------
@@ -15,18 +16,23 @@ local function writeInstance(ch: Types.ChannelState, value: Instance): ()
15
16
  end
16
17
 
17
18
  local function readInstance(src: buffer, pos: number, refs: { Instance }?): (Instance, number)
18
- if not refs then
19
- error("[Lync] Instance.read: refs array is required")
19
+ local r = refs
20
+ if not r then
21
+ Log.error("refs array is required")
20
22
  end
21
23
 
22
24
  local idx = readu16(src, pos)
23
- if idx < 1 or idx > #refs then
24
- error(`[Lync] Instance.read: ref index {idx} out of bounds`)
25
+ if idx < 1 or idx > #r then
26
+ Log.error(`ref index {idx} out of bounds`)
25
27
  end
26
28
 
27
- local inst = refs[idx]
29
+ --[[
30
+ Crafted clients can name a slot we never wrote; reject non-Instances.
31
+ Unknown codec skips this since it can legitimately hold any sidecar value.
32
+ ]]
33
+ local inst = r[idx]
28
34
  if typeof(inst) ~= "Instance" then
29
- error("[Lync] Instance.read: ref is not an Instance")
35
+ Log.error("ref is not an Instance")
30
36
  end
31
37
  return inst, 2
32
38
  end
@@ -35,10 +41,7 @@ end
35
41
 
36
42
  local InstanceCodec = {}
37
43
 
38
- --[[
39
- No _directWrite/_directRead: write needs ch.refs, so structs/arrays
40
- cannot use the all-direct fast path with this codec.
41
- ]]
44
+ -- No _directWrite/_directRead: write needs ch.refs (no fast path).
42
45
  InstanceCodec.inst = table.freeze({
43
46
  _size = 2,
44
47
  _typeCheck = "Instance",
@@ -1,6 +1,6 @@
1
1
  --!strict
2
- --!optimize 2
3
- -- Vector2int16 (4 bytes) and Vector3int16 (6 bytes) codecs.
2
+ --!native
3
+ -- Vector2int16 (4B) and Vector3int16 (6B) codecs.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
  local Signed = require(script.Parent.Parent.primitive.Signed)
@@ -1,22 +1,29 @@
1
1
  --!strict
2
- --!optimize 2
3
- -- NumberRange codec. 8 bytes: Min f32 + Max f32.
2
+ --!native
3
+ -- NumberRange codec. 8B: Min f32 + Max f32.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
+ local Log = require(script.Parent.Parent.Parent.util.Log)
6
7
 
7
8
  -- Private ----------------------------------------------------------------
8
9
 
10
+ local writef32 = buffer.writef32
11
+ local readf32 = buffer.readf32
12
+
9
13
  local function writeNumberRange(b: buffer, off: number, value: NumberRange): ()
10
- buffer.writef32(b, off, value.Min)
11
- buffer.writef32(b, off + 4, value.Max)
14
+ writef32(b, off, value.Min)
15
+ writef32(b, off + 4, value.Max)
12
16
  end
13
17
 
18
+ --[[
19
+ Reject min > max instead of silently swapping. A swap masks a desync;
20
+ erroring aborts the frame so the transport can reset its baseline.
21
+ ]]
14
22
  local function readNumberRange(b: buffer, off: number): NumberRange
15
- local minV = buffer.readf32(b, off)
16
- local maxV = buffer.readf32(b, off + 4)
17
- -- NumberRange.new requires min <= max; swap on corruption rather than throw.
23
+ local minV = readf32(b, off)
24
+ local maxV = readf32(b, off + 4)
18
25
  if minV > maxV then
19
- minV, maxV = maxV, minV
26
+ Log.error(`min ({minV}) > max ({maxV})`)
20
27
  end
21
28
  return NumberRange.new(minV, maxV)
22
29
  end
@@ -1,30 +1,29 @@
1
1
  --!strict
2
- --!optimize 2
3
- -- Ray codec. 24 bytes: Origin (3x f32) + Direction (3x f32).
2
+ --!native
3
+ -- Ray codec. 24B: Origin (3x f32) + Direction (3x f32).
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
 
7
7
  -- Private ----------------------------------------------------------------
8
8
 
9
+ local writef32 = buffer.writef32
10
+ local readf32 = buffer.readf32
11
+
9
12
  local function writeRay(b: buffer, off: number, value: Ray): ()
10
13
  local o = value.Origin
11
14
  local d = value.Direction
12
- buffer.writef32(b, off, o.X)
13
- buffer.writef32(b, off + 4, o.Y)
14
- buffer.writef32(b, off + 8, o.Z)
15
- buffer.writef32(b, off + 12, d.X)
16
- buffer.writef32(b, off + 16, d.Y)
17
- buffer.writef32(b, off + 20, d.Z)
15
+ writef32(b, off, o.X)
16
+ writef32(b, off + 4, o.Y)
17
+ writef32(b, off + 8, o.Z)
18
+ writef32(b, off + 12, d.X)
19
+ writef32(b, off + 16, d.Y)
20
+ writef32(b, off + 20, d.Z)
18
21
  end
19
22
 
20
23
  local function readRay(b: buffer, off: number): Ray
21
24
  return Ray.new(
22
- Vector3.new(buffer.readf32(b, off), buffer.readf32(b, off + 4), buffer.readf32(b, off + 8)),
23
- Vector3.new(
24
- buffer.readf32(b, off + 12),
25
- buffer.readf32(b, off + 16),
26
- buffer.readf32(b, off + 20)
27
- )
25
+ Vector3.new(readf32(b, off), readf32(b, off + 4), readf32(b, off + 8)),
26
+ Vector3.new(readf32(b, off + 12), readf32(b, off + 16), readf32(b, off + 20))
28
27
  )
29
28
  end
30
29
 
@@ -1,25 +1,25 @@
1
1
  --!strict
2
- --!optimize 2
3
- -- Rect codec. 16 bytes: 4x f32 (Min.X, Min.Y, Max.X, Max.Y).
2
+ --!native
3
+ -- Rect codec. 16B: 4x f32 (Min.X, Min.Y, Max.X, Max.Y).
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
 
7
7
  -- Private ----------------------------------------------------------------
8
8
 
9
+ local writef32 = buffer.writef32
10
+ local readf32 = buffer.readf32
11
+
9
12
  local function writeRect(b: buffer, off: number, value: Rect): ()
10
- buffer.writef32(b, off, value.Min.X)
11
- buffer.writef32(b, off + 4, value.Min.Y)
12
- buffer.writef32(b, off + 8, value.Max.X)
13
- buffer.writef32(b, off + 12, value.Max.Y)
13
+ local minV = value.Min
14
+ local maxV = value.Max
15
+ writef32(b, off, minV.X)
16
+ writef32(b, off + 4, minV.Y)
17
+ writef32(b, off + 8, maxV.X)
18
+ writef32(b, off + 12, maxV.Y)
14
19
  end
15
20
 
16
21
  local function readRect(b: buffer, off: number): Rect
17
- return Rect.new(
18
- buffer.readf32(b, off),
19
- buffer.readf32(b, off + 4),
20
- buffer.readf32(b, off + 8),
21
- buffer.readf32(b, off + 12)
22
- )
22
+ return Rect.new(readf32(b, off), readf32(b, off + 4), readf32(b, off + 8), readf32(b, off + 12))
23
23
  end
24
24
 
25
25
  -- Public -----------------------------------------------------------------
@@ -1,36 +1,35 @@
1
1
  --!strict
2
- --!optimize 2
3
- -- Region3 (24 bytes) and Region3int16 (12 bytes) codecs.
2
+ --!native
3
+ -- Region3 (24B) and Region3int16 (12B) codecs.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
  local Signed = require(script.Parent.Parent.primitive.Signed)
7
7
 
8
8
  -- Private ----------------------------------------------------------------
9
9
 
10
+ local writef32 = buffer.writef32
11
+ local readf32 = buffer.readf32
10
12
  local writeI16 = Signed.writeI16
11
13
  local readi16 = buffer.readi16
12
14
 
15
+ -- Region3 has no Min/Max accessors; reconstruct from CFrame.Position +/- Size/2.
13
16
  local function writeRegion3(b: buffer, off: number, value: Region3): ()
14
17
  local pos = value.CFrame.Position
15
18
  local half = value.Size * 0.5
16
19
  local minV = pos - half
17
20
  local maxV = pos + half
18
- buffer.writef32(b, off, minV.X)
19
- buffer.writef32(b, off + 4, minV.Y)
20
- buffer.writef32(b, off + 8, minV.Z)
21
- buffer.writef32(b, off + 12, maxV.X)
22
- buffer.writef32(b, off + 16, maxV.Y)
23
- buffer.writef32(b, off + 20, maxV.Z)
21
+ writef32(b, off, minV.X)
22
+ writef32(b, off + 4, minV.Y)
23
+ writef32(b, off + 8, minV.Z)
24
+ writef32(b, off + 12, maxV.X)
25
+ writef32(b, off + 16, maxV.Y)
26
+ writef32(b, off + 20, maxV.Z)
24
27
  end
25
28
 
26
29
  local function readRegion3(b: buffer, off: number): Region3
27
30
  return Region3.new(
28
- Vector3.new(buffer.readf32(b, off), buffer.readf32(b, off + 4), buffer.readf32(b, off + 8)),
29
- Vector3.new(
30
- buffer.readf32(b, off + 12),
31
- buffer.readf32(b, off + 16),
32
- buffer.readf32(b, off + 20)
33
- )
31
+ Vector3.new(readf32(b, off), readf32(b, off + 4), readf32(b, off + 8)),
32
+ Vector3.new(readf32(b, off + 12), readf32(b, off + 16), readf32(b, off + 20))
34
33
  )
35
34
  end
36
35