--!strict
--!optimize 2
-- Positional ordered codec. Like struct but indexed, not keyed.

local Base = require(script.Parent.Parent.Base)
local Types = require(script.Parent.Parent.Parent.Types)

-- Private -------------------------------------------------------------

local alloc = Base.alloc

-- Public --------------------------------------------------------------

local Tuple = {}

function Tuple.define(...: Types.InternalCodec<any>): Types.InternalCodec<any>
    local codecs = { ... }
    local count = #codecs
    if count == 0 then
        error("[Lync] Lync.tuple: requires at least one codec")
    end

    -- Check if all codecs are direct
    local allDirect = true
    local totalSize = 0
    local hasUnknown = false

    for i = 1, count do
        local c = codecs[i] :: any
        if c._hasUnknown then
            hasUnknown = true
        end
        if not c._size or not c._directWrite or not c._directRead then
            allDirect = false
        else
            totalSize += c._size
        end
    end

    table.freeze(codecs)

    if allDirect then
        -- Build precomputed offsets
        local offsets = table.create(count)
        local directWrites = table.create(count)
        local directReads = table.create(count)
        local cursor = 0

        for i = 1, count do
            local c = codecs[i] :: any
            offsets[i] = cursor
            directWrites[i] = c._directWrite
            directReads[i] = c._directRead
            cursor += c._size
        end

        table.freeze(offsets)
        table.freeze(directWrites)
        table.freeze(directReads)

        local codec: any = {
            _size = totalSize,

            _directWrite = function(b: buffer, off: number, value: any): ()
                for i = 1, count do
                    directWrites[i](b, off + offsets[i], value[i])
                end
            end,

            _directRead = function(b: buffer, off: number): any
                local result = table.create(count)
                for i = 1, count do
                    result[i] = directReads[i](b, off + offsets[i])
                end
                return result
            end,

            write = function(ch: Types.ChannelState, value: any): ()
                local c = ch.cursor
                if c + totalSize > ch.size then
                    alloc(ch, totalSize)
                end
                local b = ch.buff
                for i = 1, count do
                    directWrites[i](b, c + offsets[i], value[i])
                end
                ch.cursor = c + totalSize
            end,

            read = function(src: buffer, pos: number, _refs: { Instance }?): (any, number)
                local result = table.create(count)
                for i = 1, count do
                    result[i] = directReads[i](src, pos + offsets[i])
                end
                return result, totalSize
            end,
        }

        if hasUnknown then
            codec._hasUnknown = true
        end
        return table.freeze(codec)
    end

    -- Generic path
    local codec: any = {
        write = function(ch: Types.ChannelState, value: any): ()
            for i = 1, count do
                codecs[i].write(ch, value[i])
            end
        end,

        read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
            local result = table.create(count)
            local total = 0
            for i = 1, count do
                local v, consumed = codecs[i].read(src, pos + total, refs)
                result[i] = v
                total += consumed
            end
            return result, total
        end,
    }

    if hasUnknown then
        codec._hasUnknown = true
    end
    return table.freeze(codec)
end

return table.freeze(Tuple)


--!strict
--!optimize 2
-- Raw buffer codec with dense-prefix-varint length prefix.

local Base = require(script.Parent.Parent.Base)
local Types = require(script.Parent.Parent.Parent.Types)
local Varint = require(script.Parent.Parent.primitive.Varint)

-- Private -------------------------------------------------------------

local alloc = Base.alloc
local INLINE = Varint.INLINE_MAX

-- Public --------------------------------------------------------------

local BufferCodec = {}

BufferCodec.buff = table.freeze({
    _typeCheck = "buffer",

    write = function(ch: Types.ChannelState, value: buffer): ()
        local len = buffer.len(value)
        local cursor = ch.cursor

        if len <= INLINE then
            if cursor + 1 + len > ch.size then
                alloc(ch, 1 + len)
            end
            local b = ch.buff
            buffer.writeu8(b, cursor, len)
            if len > 0 then
                buffer.copy(b, cursor + 1, value, 0, len)
            end
            ch.cursor = cursor + 1 + len
        else
            Varint.write(ch, len)
            cursor = ch.cursor
            if cursor + len > ch.size then
                alloc(ch, len)
            end
            buffer.copy(ch.buff, cursor, value, 0, len)
            ch.cursor = cursor + len
        end
    end,

    read = function(src: buffer, pos: number, _refs: { Instance }?): (buffer, number)
        local b0 = buffer.readu8(src, pos)
        local len: number
        local lenBytes: number

        if b0 <= INLINE then
            len = b0
            lenBytes = 1
        else
            len, lenBytes = Varint.read(src, pos)
        end

        if len == 0 then
            return buffer.create(0), lenBytes
        end

        local dataStart = pos + lenBytes
        if dataStart + len > buffer.len(src) then
            error("[Lync] Buffer: length exceeds remaining buffer")
        end

        local out = buffer.create(len)
        buffer.copy(out, 0, src, dataStart, len)
        return out, lenBytes + len
    end,
} :: any)

return table.freeze(BufferCodec)


--!strict
--!native
-- CFrame codec with __call for smallest-three compressed variant.

local Base = require(script.Parent.Parent.Base)

-- Constants -----------------------------------------------------------

local EPSILON = 2.4e-7
local EPSILON_SQ = EPSILON * EPSILON
local SQRT = math.sqrt
local COS = math.cos
local SIN = math.sin
local ABS = math.abs
local ROUND = math.round
local ACOS = math.acos

-- Smallest-three quantization constants
local INV_SQRT2 = 1 / SQRT(2)
local Q_SCALE = 1023 / (2 * INV_SQRT2)
local Q_INV = (2 * INV_SQRT2) / 1023

local band = bit32.band
local bor = bit32.bor
local lshift = bit32.lshift
local rshift = bit32.rshift

-- Private: Lossless axis-angle (24 bytes) -----------------------------

local function writeLossless(b: buffer, off: number, value: CFrame): ()
    local pos = value.Position
    buffer.writef32(b, off, pos.X)
    buffer.writef32(b, off + 4, pos.Y)
    buffer.writef32(b, off + 8, pos.Z)

    local axis, angle = value:ToAxisAngle()
    if angle < EPSILON then
        buffer.writef32(b, off + 12, 0)
        buffer.writef32(b, off + 16, 0)
        buffer.writef32(b, off + 20, 0)
    else
        buffer.writef32(b, off + 12, axis.X * angle)
        buffer.writef32(b, off + 16, axis.Y * angle)
        buffer.writef32(b, off + 20, axis.Z * angle)
    end
end

local function readLossless(b: buffer, off: number): CFrame
    local px = buffer.readf32(b, off)
    local py = buffer.readf32(b, off + 4)
    local pz = buffer.readf32(b, off + 8)
    local rx = buffer.readf32(b, off + 12)
    local ry = buffer.readf32(b, off + 16)
    local rz = buffer.readf32(b, off + 20)

    local sqLen = rx * rx + ry * ry + rz * rz
    if sqLen < EPSILON_SQ then
        return CFrame.new(px, py, pz)
    end

    local angle = SQRT(sqLen)
    local inv = 1 / angle
    return CFrame.new(px, py, pz)
        * CFrame.fromAxisAngle(Vector3.new(rx * inv, ry * inv, rz * inv), angle)
end

-- Private: Smallest-three compressed (16 bytes) -----------------------

local function writeCompressed(b: buffer, off: number, value: CFrame): ()
    -- Position: 3x f32
    local pos = value.Position
    buffer.writef32(b, off, pos.X)
    buffer.writef32(b, off + 4, pos.Y)
    buffer.writef32(b, off + 8, pos.Z)

    --[[
        Convert CFrame rotation to quaternion via axis-angle.
        Then apply smallest-three: drop the largest component,
        quantize the other three into 10 bits each, pack into u32.
    ]]
    local axis, angle = value:ToAxisAngle()
    local halfAngle = angle * 0.5
    local s = SIN(halfAngle)
    local qx = axis.X * s
    local qy = axis.Y * s
    local qz = axis.Z * s
    local qw = COS(halfAngle)

    -- Find largest component
    local ax, ay, az, aw = ABS(qx), ABS(qy), ABS(qz), ABS(qw)
    local largest = 3
    local maxVal = aw

    if ax > maxVal then
        largest = 0
        maxVal = ax
    end
    if ay > maxVal then
        largest = 1
        maxVal = ay
    end
    if az > maxVal then
        largest = 2
        maxVal = az
    end

    -- Negate quaternion if largest component is negative
    local sign: number
    if largest == 0 then
        sign = if qx < 0 then -1 else 1
    elseif largest == 1 then
        sign = if qy < 0 then -1 else 1
    elseif largest == 2 then
        sign = if qz < 0 then -1 else 1
    else
        sign = if qw < 0 then -1 else 1
    end
    qx *= sign
    qy *= sign
    qz *= sign
    qw *= sign

    -- Select the three smallest components
    local a, b2, c: number
    if largest == 0 then
        a, b2, c = qy, qz, qw
    elseif largest == 1 then
        a, b2, c = qx, qz, qw
    elseif largest == 2 then
        a, b2, c = qx, qy, qw
    else
        a, b2, c = qx, qy, qz
    end

    -- Quantize from [-1/sqrt(2), 1/sqrt(2)] to [0, 1023]
    local qa = ROUND((a + INV_SQRT2) * Q_SCALE)
    local qb = ROUND((b2 + INV_SQRT2) * Q_SCALE)
    local qc = ROUND((c + INV_SQRT2) * Q_SCALE)

    -- Clamp to [0, 1023]
    qa = if qa < 0 then 0 elseif qa > 1023 then 1023 else qa
    qb = if qb < 0 then 0 elseif qb > 1023 then 1023 else qb
    qc = if qc < 0 then 0 elseif qc > 1023 then 1023 else qc

    -- Pack: [index:2][a:10][b:10][c:10] = 32 bits
    local packed = bor(lshift(largest, 30), lshift(qa, 20), lshift(qb, 10), qc)
    buffer.writeu32(b, off + 12, packed)
end

local function readCompressed(b: buffer, off: number): CFrame
    local px = buffer.readf32(b, off)
    local py = buffer.readf32(b, off + 4)
    local pz = buffer.readf32(b, off + 8)

    local packed = buffer.readu32(b, off + 12)
    local largest = rshift(packed, 30)
    local qa = band(rshift(packed, 20), 0x3FF)
    local qb = band(rshift(packed, 10), 0x3FF)
    local qc = band(packed, 0x3FF)

    -- Dequantize from [0, 1023] to [-1/sqrt(2), 1/sqrt(2)]
    local a = qa * Q_INV - INV_SQRT2
    local b2 = qb * Q_INV - INV_SQRT2
    local c = qc * Q_INV - INV_SQRT2

    -- Reconstruct largest component
    local sqSum = a * a + b2 * b2 + c * c
    local d = SQRT(if sqSum < 1 then 1 - sqSum else 0)

    -- Place components
    local qx, qy, qz, qw: number
    if largest == 0 then
        qx, qy, qz, qw = d, a, b2, c
    elseif largest == 1 then
        qx, qy, qz, qw = a, d, b2, c
    elseif largest == 2 then
        qx, qy, qz, qw = a, b2, d, c
    else
        qx, qy, qz, qw = a, b2, c, d
    end

    -- Build CFrame from position + quaternion
    local origin = CFrame.new(px, py, pz)
    if qx * qx + qy * qy + qz * qz < EPSILON_SQ then
        return origin
    end

    -- Convert quaternion to axis-angle for CFrame construction
    local len = SQRT(qx * qx + qy * qy + qz * qz)
    local angle = 2 * ACOS(if qw > 1 then 1 elseif qw < -1 then -1 else qw)
    local inv = 1 / len
    return origin * CFrame.fromAxisAngle(Vector3.new(qx * inv, qy * inv, qz * inv), angle)
end

-- __call factory for compressed variant
local function compressedFactory(_self: any): any
    return Base.define(16, writeCompressed, readCompressed, {
        _typeCheck = "CFrame",
    })
end

-- Public --------------------------------------------------------------

local CFrameCodec = {}

CFrameCodec.cframe = setmetatable(
    {
        _size = 24,
        _directWrite = writeLossless,
        _directRead = readLossless,
        _typeCheck = "CFrame",

        write = function(ch: Types.ChannelState, value: CFrame): ()
            local cursor = ch.cursor
            if cursor + 24 > ch.size then
                alloc(ch, 24)
            end
            writeLossless(ch.buff, cursor, value)
            ch.cursor = cursor + 24
        end,

        read = function(src: buffer, pos: number, _refs: { Instance }?): (CFrame, number)
            return readLossless(src, pos), 24
        end,
    } :: any,
    { __call = compressedFactory }
)

return table.freeze(CFrameCodec)


--!strict
--!optimize 2
-- Color3 codec. 3 bytes, RGB u8 per channel, clamped.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local round = math.round
local clamp = math.clamp

-- Public --------------------------------------------------------------

local ColorCodec = {}

ColorCodec.color3 = Base.define(3, function(b: buffer, off: number, value: Color3): ()
    buffer.writeu8(b, off, round(clamp(value.R, 0, 1) * 255))
    buffer.writeu8(b, off + 1, round(clamp(value.G, 0, 1) * 255))
    buffer.writeu8(b, off + 2, round(clamp(value.B, 0, 1) * 255))
end, function(b: buffer, off: number): Color3
    return Color3.fromRGB(
        buffer.readu8(b, off),
        buffer.readu8(b, off + 1),
        buffer.readu8(b, off + 2)
    )
end, { _typeCheck = "Color3" })

return table.freeze(ColorCodec)


--!strict
--!optimize 2
-- Instance codec via sidecar reference array.

local Base = require(script.Parent.Parent.Base)
local Types = require(script.Parent.Parent.Parent.Types)

-- Private -------------------------------------------------------------

local alloc = Base.alloc
local writeu16 = buffer.writeu16
local readu16 = buffer.readu16

-- Public --------------------------------------------------------------

local InstanceCodec = {}

-- No _directWrite/_directRead: write needs ch.refs, so structs can't use the all-direct path
InstanceCodec.inst = table.freeze({
    _size = 2,
    _typeCheck = "Instance",

    write = function(ch: Types.ChannelState, value: Instance): ()
        local idx = ch.refCount + 1
        if idx > 0xFFFF then
            error("[Lync] Instance: ref overflow (>65535)")
        end

        ch.refs[idx] = value
        ch.refCount = idx

        local cursor = ch.cursor
        if cursor + 2 > ch.size then
            alloc(ch, 2)
        end
        writeu16(ch.buff, cursor, idx)
        ch.cursor = cursor + 2
    end,

    read = function(src: buffer, pos: number, refs: { Instance }?): (Instance, number)
        if not refs then
            error("[Lync] Instance: read requires refs array")
        end

        local idx = readu16(src, pos)
        if idx < 1 or idx > #refs then
            error(`[Lync] Instance: ref index {idx} out of bounds`)
        end

        local inst = refs[idx]
        if typeof(inst) ~= "Instance" then
            error("[Lync] Instance: invalid ref (expected Instance)")
        end

        return inst, 2
    end,
} :: any)

return table.freeze(InstanceCodec)


--!strict
--!optimize 2
-- Vector2int16 (4 bytes) and Vector3int16 (6 bytes) codecs.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local function writeI16(b: buffer, off: number, v: number): ()
    buffer.writeu16(b, off, if v < 0 then v + 0x10000 else v)
end

local function readI16(b: buffer, off: number): number
    local raw = buffer.readu16(b, off)
    return if raw >= 0x8000 then raw - 0x10000 else raw
end

-- Public --------------------------------------------------------------

local IntVectorCodec = {}

IntVectorCodec.vec2int16 = Base.define(4, function(b: buffer, off: number, value: Vector2int16): ()
    writeI16(b, off, value.X)
    writeI16(b, off + 2, value.Y)
end, function(b: buffer, off: number): Vector2int16
    return Vector2int16.new(readI16(b, off), readI16(b, off + 2))
end, { _typeCheck = "Vector2int16" })

IntVectorCodec.vec3int16 = Base.define(6, function(b: buffer, off: number, value: Vector3int16): ()
    writeI16(b, off, value.X)
    writeI16(b, off + 2, value.Y)
    writeI16(b, off + 4, value.Z)
end, function(b: buffer, off: number): Vector3int16
    return Vector3int16.new(readI16(b, off), readI16(b, off + 2), readI16(b, off + 4))
end, { _typeCheck = "Vector3int16" })

return table.freeze(IntVectorCodec)


--!strict
--!optimize 2
-- NumberRange codec. 8 bytes: Min f32 + Max f32.

local Base = require(script.Parent.Parent.Base)

-- Public --------------------------------------------------------------

local NumberRangeCodec = {}

NumberRangeCodec.numberRange = Base.define(
    8,
    function(b: buffer, off: number, value: NumberRange): ()
        buffer.writef32(b, off, value.Min)
        buffer.writef32(b, off + 4, value.Max)
    end,
    function(b: buffer, off: number): NumberRange
        return NumberRange.new(buffer.readf32(b, off), buffer.readf32(b, off + 4))
    end,
    { _typeCheck = "NumberRange" }
)

return table.freeze(NumberRangeCodec)


--!strict
--!optimize 2
-- Ray codec. 24 bytes: origin + direction as 6x f32.

local Base = require(script.Parent.Parent.Base)

-- Public --------------------------------------------------------------

local RayCodec = {}

RayCodec.ray = Base.define(24, function(b: buffer, off: number, value: Ray): ()
    local o = value.Origin
    local d = value.Direction
    buffer.writef32(b, off, o.X)
    buffer.writef32(b, off + 4, o.Y)
    buffer.writef32(b, off + 8, o.Z)
    buffer.writef32(b, off + 12, d.X)
    buffer.writef32(b, off + 16, d.Y)
    buffer.writef32(b, off + 20, d.Z)
end, function(b: buffer, off: number): Ray
    return Ray.new(
        Vector3.new(buffer.readf32(b, off), buffer.readf32(b, off + 4), buffer.readf32(b, off + 8)),
        Vector3.new(
            buffer.readf32(b, off + 12),
            buffer.readf32(b, off + 16),
            buffer.readf32(b, off + 20)
        )
    )
end, { _typeCheck = "Ray" })

return table.freeze(RayCodec)


--!strict
--!optimize 2
-- Rect codec. 16 bytes: 4x f32.

local Base = require(script.Parent.Parent.Base)

-- Public --------------------------------------------------------------

local RectCodec = {}

RectCodec.rect = Base.define(16, function(b: buffer, off: number, value: Rect): ()
    buffer.writef32(b, off, value.Min.X)
    buffer.writef32(b, off + 4, value.Min.Y)
    buffer.writef32(b, off + 8, value.Max.X)
    buffer.writef32(b, off + 12, value.Max.Y)
end, function(b: buffer, off: number): Rect
    return Rect.new(
        buffer.readf32(b, off),
        buffer.readf32(b, off + 4),
        buffer.readf32(b, off + 8),
        buffer.readf32(b, off + 12)
    )
end, { _typeCheck = "Rect" })

return table.freeze(RectCodec)


--!strict
--!optimize 2
-- Region3 (24 bytes) and Region3int16 (12 bytes) codecs.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local function writeI16(b: buffer, off: number, v: number): ()
    buffer.writeu16(b, off, if v < 0 then v + 0x10000 else v)
end

local function readI16(b: buffer, off: number): number
    local raw = buffer.readu16(b, off)
    return if raw >= 0x8000 then raw - 0x10000 else raw
end

-- Public --------------------------------------------------------------

local RegionCodec = {}

RegionCodec.region3 = Base.define(24, function(b: buffer, off: number, value: Region3): ()
    local pos = value.CFrame.Position
    local half = value.Size * 0.5
    local minV = pos - half
    local maxV = pos + half
    buffer.writef32(b, off, minV.X)
    buffer.writef32(b, off + 4, minV.Y)
    buffer.writef32(b, off + 8, minV.Z)
    buffer.writef32(b, off + 12, maxV.X)
    buffer.writef32(b, off + 16, maxV.Y)
    buffer.writef32(b, off + 20, maxV.Z)
end, function(b: buffer, off: number): Region3
    return Region3.new(
        Vector3.new(buffer.readf32(b, off), buffer.readf32(b, off + 4), buffer.readf32(b, off + 8)),
        Vector3.new(
            buffer.readf32(b, off + 12),
            buffer.readf32(b, off + 16),
            buffer.readf32(b, off + 20)
        )
    )
end, { _typeCheck = "Region3" })

RegionCodec.region3int16 = Base.define(12, function(b: buffer, off: number, value: Region3int16): ()
    local minV = value.Min
    local maxV = value.Max
    writeI16(b, off, minV.X)
    writeI16(b, off + 2, minV.Y)
    writeI16(b, off + 4, minV.Z)
    writeI16(b, off + 6, maxV.X)
    writeI16(b, off + 8, maxV.Y)
    writeI16(b, off + 10, maxV.Z)
end, function(b: buffer, off: number): Region3int16
    return Region3int16.new(
        Vector3int16.new(readI16(b, off), readI16(b, off + 2), readI16(b, off + 4)),
        Vector3int16.new(readI16(b, off + 6), readI16(b, off + 8), readI16(b, off + 10))
    )
end, { _typeCheck = "Region3int16" })

return table.freeze(RegionCodec)


--!strict
--!optimize 2
-- NumberSequence and ColorSequence codecs with varint count.

local Base = require(script.Parent.Parent.Base)
local Types = require(script.Parent.Parent.Parent.Types)
local Varint = require(script.Parent.Parent.primitive.Varint)

-- Private -------------------------------------------------------------

local alloc = Base.alloc
local round = math.round
local clamp = math.clamp

-- Public --------------------------------------------------------------

local SequenceCodec = {}

SequenceCodec.numberSequence = table.freeze({
    _typeCheck = "NumberSequence",

    write = function(ch: Types.ChannelState, value: NumberSequence): ()
        local keypoints = value.Keypoints
        local count = #keypoints
        Varint.write(ch, count)

        if count == 0 then
            return
        end

        local bytes = count * 12
        local cursor = ch.cursor
        if cursor + bytes > ch.size then
            alloc(ch, bytes)
        end
        local b = ch.buff

        for i = 1, count do
            local kp = keypoints[i]
            local off = cursor + (i - 1) * 12
            buffer.writef32(b, off, kp.Time)
            buffer.writef32(b, off + 4, kp.Value)
            buffer.writef32(b, off + 8, kp.Envelope)
        end

        ch.cursor = cursor + bytes
    end,

    read = function(src: buffer, pos: number, _refs: { Instance }?): (NumberSequence, number)
        local count, lenBytes = Varint.read(src, pos)
        if count == 0 then
            return NumberSequence.new(0), lenBytes
        end

        local bytes = count * 12
        local dataStart = pos + lenBytes
        if dataStart + bytes > buffer.len(src) then
            error("[Lync] NumberSequence: data exceeds buffer")
        end

        local keypoints = table.create(count)
        for i = 1, count do
            local off = dataStart + (i - 1) * 12
            keypoints[i] = NumberSequenceKeypoint.new(
                buffer.readf32(src, off),
                buffer.readf32(src, off + 4),
                buffer.readf32(src, off + 8)
            )
        end

        return NumberSequence.new(keypoints), lenBytes + bytes
    end,
} :: any)

SequenceCodec.colorSequence = table.freeze({
    _typeCheck = "ColorSequence",

    write = function(ch: Types.ChannelState, value: ColorSequence): ()
        local keypoints = value.Keypoints
        local count = #keypoints
        Varint.write(ch, count)

        if count == 0 then
            return
        end

        local bytes = count * 7
        local cursor = ch.cursor
        if cursor + bytes > ch.size then
            alloc(ch, bytes)
        end
        local b = ch.buff

        for i = 1, count do
            local kp = keypoints[i]
            local off = cursor + (i - 1) * 7
            local c = kp.Value
            buffer.writef32(b, off, kp.Time)
            buffer.writeu8(b, off + 4, round(clamp(c.R, 0, 1) * 255))
            buffer.writeu8(b, off + 5, round(clamp(c.G, 0, 1) * 255))
            buffer.writeu8(b, off + 6, round(clamp(c.B, 0, 1) * 255))
        end

        ch.cursor = cursor + bytes
    end,

    read = function(src: buffer, pos: number, _refs: { Instance }?): (ColorSequence, number)
        local count, lenBytes = Varint.read(src, pos)
        if count == 0 then
            return ColorSequence.new(Color3.new()), lenBytes
        end

        local bytes = count * 7
        local dataStart = pos + lenBytes
        if dataStart + bytes > buffer.len(src) then
            error("[Lync] ColorSequence: data exceeds buffer")
        end

        local keypoints = table.create(count)
        for i = 1, count do
            local off = dataStart + (i - 1) * 7
            keypoints[i] = ColorSequenceKeypoint.new(
                buffer.readf32(src, off),
                Color3.fromRGB(
                    buffer.readu8(src, off + 4),
                    buffer.readu8(src, off + 5),
                    buffer.readu8(src, off + 6)
                )
            )
        end

        return ColorSequence.new(keypoints), lenBytes + bytes
    end,
} :: any)

return table.freeze(SequenceCodec)


--!strict
--!native
-- Variable-length string codec with __call for bounded variant.

local Base = require(script.Parent.Parent.Base)
local Types = require(script.Parent.Parent.Parent.Types)
local Varint = require(script.Parent.Parent.primitive.Varint)

-- Private -------------------------------------------------------------

local alloc = Base.alloc
local writeu8 = buffer.writeu8
local readu8 = buffer.readu8
local INLINE = Varint.INLINE_MAX

local function writeString(ch: Types.ChannelState, value: string): ()
    local len = #value
    local cursor = ch.cursor

    if len <= INLINE then
        -- Short strings (<= 191 bytes) use a single-byte prefix, avoiding Varint.write overhead
        if cursor + 1 + len > ch.size then
            alloc(ch, 1 + len)
        end
        local b = ch.buff
        writeu8(b, cursor, len)
        if len > 0 then
            buffer.writestring(b, cursor + 1, value)
        end
        ch.cursor = cursor + 1 + len
    else
        -- Long strings need multi-byte length encoding
        Varint.write(ch, len)
        cursor = ch.cursor
        if cursor + len > ch.size then
            alloc(ch, len)
        end
        buffer.writestring(ch.buff, cursor, value)
        ch.cursor = cursor + len
    end
end

local function readString(src: buffer, pos: number, _refs: { Instance }?): (string, number)
    local b0 = readu8(src, pos)
    local len: number
    local lenBytes: number

    if b0 <= INLINE then
        len = b0
        lenBytes = 1
    else
        len, lenBytes = Varint.read(src, pos)
    end

    if len == 0 then
        return "", lenBytes
    end

    local dataStart = pos + lenBytes
    if dataStart + len > buffer.len(src) then
        error("[Lync] String: length exceeds remaining buffer")
    end

    return buffer.readstring(src, dataStart, len), lenBytes + len
end

-- Bounded variant shares the write function to avoid per-call closure allocation
local function boundedFactory(_self: any, maxLength: number): Types.InternalCodec<string>
    if maxLength <= 0 then
        error("[Lync] Lync.string: maxLength must be positive")
    end

    return table.freeze({
        write = writeString,

        read = function(src: buffer, pos: number, refs: { Instance }?): (string, number)
            local b0 = readu8(src, pos)
            local len: number
            local lenBytes: number

            if b0 <= INLINE then
                len = b0
                lenBytes = 1
            else
                len, lenBytes = Varint.read(src, pos)
            end

            if len > maxLength then
                error(`[Lync] String: length {len} exceeds max {maxLength}`)
            end

            if len == 0 then
                return "", lenBytes
            end

            local dataStart = pos + lenBytes
            if dataStart + len > buffer.len(src) then
                error("[Lync] String: length exceeds remaining buffer")
            end

            return buffer.readstring(src, dataStart, len), lenBytes + len
        end,
    } :: any)
end

-- Public --------------------------------------------------------------

local StringCodec = setmetatable(
    {
        write = writeString,
        read = readString,
        _typeCheck = "string",
    } :: any,
    { __call = boundedFactory }
)

return StringCodec


--!strict
--!optimize 2
-- UDim (8 bytes) and UDim2 (16 bytes) codecs.

local Base = require(script.Parent.Parent.Base)

-- Public --------------------------------------------------------------

local UDimCodec = {}

UDimCodec.udim = Base.define(8, function(b: buffer, off: number, value: UDim): ()
    buffer.writef32(b, off, value.Scale)
    buffer.writeu32(
        b,
        off + 4,
        if value.Offset < 0 then value.Offset + 0x100000000 else value.Offset
    )
end, function(b: buffer, off: number): UDim
    local rawOff = buffer.readu32(b, off + 4)
    return UDim.new(
        buffer.readf32(b, off),
        if rawOff >= 0x80000000 then rawOff - 0x100000000 else rawOff
    )
end, { _typeCheck = "UDim" })

UDimCodec.udim2 = Base.define(16, function(b: buffer, off: number, value: UDim2): ()
    local x = value.X
    local y = value.Y
    buffer.writef32(b, off, x.Scale)
    buffer.writeu32(b, off + 4, if x.Offset < 0 then x.Offset + 0x100000000 else x.Offset)
    buffer.writef32(b, off + 8, y.Scale)
    buffer.writeu32(b, off + 12, if y.Offset < 0 then y.Offset + 0x100000000 else y.Offset)
end, function(b: buffer, off: number): UDim2
    local xOff = buffer.readu32(b, off + 4)
    local yOff = buffer.readu32(b, off + 12)
    return UDim2.new(
        UDim.new(buffer.readf32(b, off), if xOff >= 0x80000000 then xOff - 0x100000000 else xOff),
        UDim.new(
            buffer.readf32(b, off + 8),
            if yOff >= 0x80000000 then yOff - 0x100000000 else yOff
        )
    )
end, { _typeCheck = "UDim2" })

return table.freeze(UDimCodec)


--!strict
--!optimize 2
-- Vector2 and Vector3 codecs with __call for quantized variants.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local ceil = math.ceil
local clamp = math.clamp
local round = math.round
local max = math.max
local min = math.min

--[[
    Shared quantization setup used by both vec2/vec3 __call and Float.luau.
    Returns compBytes, writeFn, readFn, scale, invScale.
]]
local function quantizeSetup(
    rangeMin: number,
    rangeMax: number,
    precision: number
): (
    number,
    (buffer, number, number) -> (),
    (buffer, number) -> number,
    number,
    number
)
    if rangeMin > rangeMax then
        error("[Lync] Lync.vec2/vec3: min must be <= max")
    end
    if precision <= 0 then
        error("[Lync] Lync.vec2/vec3: precision must be positive")
    end

    local delta = rangeMax - rangeMin
    local maxInt = min(0xFFFFFFFF, max(1, ceil(delta / precision)))

    local compBytes: number
    local wfn: (buffer, number, number) -> ()
    local rfn: (buffer, number) -> number

    if maxInt <= 0xFF then
        compBytes = 1
        wfn = buffer.writeu8
        rfn = buffer.readu8
    elseif maxInt <= 0xFFFF then
        compBytes = 2
        wfn = buffer.writeu16
        rfn = buffer.readu16
    else
        compBytes = 4
        wfn = buffer.writeu32
        rfn = buffer.readu32
    end

    local scale = maxInt / delta
    local invScale = delta / maxInt
    return compBytes, wfn, rfn, scale, invScale
end

-- Vec2 ----------------------------------------------------------------

local function writeVec2(b: buffer, off: number, value: Vector2): ()
    buffer.writef32(b, off, value.X)
    buffer.writef32(b, off + 4, value.Y)
end

local function readVec2(b: buffer, off: number): Vector2
    return Vector2.new(buffer.readf32(b, off), buffer.readf32(b, off + 4))
end

local function quantizeVec2(_self: any, rangeMin: number, rangeMax: number, precision: number): any
    local delta = rangeMax - rangeMin
    if delta == 0 then
        local constant = Vector2.new(rangeMin, rangeMin)
        return Base.zero() :: any -- constant value, zero bytes
    end

    local compBytes, wfn, rfn, scale, invScale = quantizeSetup(rangeMin, rangeMax, precision)
    local totalSize = compBytes * 2

    return Base.define(totalSize, function(b: buffer, off: number, value: Vector2): ()
        wfn(b, off, round(clamp(value.X - rangeMin, 0, delta) * scale))
        wfn(b, off + compBytes, round(clamp(value.Y - rangeMin, 0, delta) * scale))
    end, function(b: buffer, off: number): Vector2
        return Vector2.new(
            rfn(b, off) * invScale + rangeMin,
            rfn(b, off + compBytes) * invScale + rangeMin
        )
    end, { _typeCheck = "Vector2", _min = rangeMin, _max = rangeMax })
end

-- Vec3 ----------------------------------------------------------------

local function writeVec3(b: buffer, off: number, value: Vector3): ()
    buffer.writef32(b, off, value.X)
    buffer.writef32(b, off + 4, value.Y)
    buffer.writef32(b, off + 8, value.Z)
end

local function readVec3(b: buffer, off: number): Vector3
    return Vector3.new(
        buffer.readf32(b, off),
        buffer.readf32(b, off + 4),
        buffer.readf32(b, off + 8)
    )
end

local function quantizeVec3(_self: any, rangeMin: number, rangeMax: number, precision: number): any
    local delta = rangeMax - rangeMin
    if delta == 0 then
        return Base.zero() :: any
    end

    local compBytes, wfn, rfn, scale, invScale = quantizeSetup(rangeMin, rangeMax, precision)
    local totalSize = compBytes * 3

    return Base.define(totalSize, function(b: buffer, off: number, value: Vector3): ()
        wfn(b, off, round(clamp(value.X - rangeMin, 0, delta) * scale))
        wfn(b, off + compBytes, round(clamp(value.Y - rangeMin, 0, delta) * scale))
        wfn(b, off + compBytes * 2, round(clamp(value.Z - rangeMin, 0, delta) * scale))
    end, function(b: buffer, off: number): Vector3
        return Vector3.new(
            rfn(b, off) * invScale + rangeMin,
            rfn(b, off + compBytes) * invScale + rangeMin,
            rfn(b, off + compBytes * 2) * invScale + rangeMin
        )
    end, { _typeCheck = "Vector3", _min = rangeMin, _max = rangeMax })
end

-- Public --------------------------------------------------------------

local VectorCodec = {}

VectorCodec.vec2 = setmetatable(
    {
        _size = 8,
        _directWrite = writeVec2,
        _directRead = readVec2,
        _typeCheck = "Vector2",

        write = function(ch: Types.ChannelState, value: Vector2): ()
            local cursor = ch.cursor
            if cursor + 8 > ch.size then
                alloc(ch, 8)
            end
            writeVec2(ch.buff, cursor, value)
            ch.cursor = cursor + 8
        end,

        read = function(src: buffer, pos: number, _refs: { Instance }?): (Vector2, number)
            return readVec2(src, pos), 8
        end,
    } :: any,
    { __call = quantizeVec2 }
)

VectorCodec.vec3 = setmetatable(
    {
        _size = 12,
        _directWrite = writeVec3,
        _directRead = readVec3,
        _typeCheck = "Vector3",

        write = function(ch: Types.ChannelState, value: Vector3): ()
            local cursor = ch.cursor
            if cursor + 12 > ch.size then
                alloc(ch, 12)
            end
            writeVec3(ch.buff, cursor, value)
            ch.cursor = cursor + 12
        end,

        read = function(src: buffer, pos: number, _refs: { Instance }?): (Vector3, number)
            return readVec3(src, pos), 12
        end,
    } :: any,
    { __call = quantizeVec3 }
)

VectorCodec.quantizeSetup = quantizeSetup

return table.freeze(VectorCodec)


--!strict
--!native
-- Self-describing codec. u8 type tag + value payload.

local Base = require(script.Parent.Parent.Base)
local BoolC = require(script.Parent.Parent.primitive.Bool)
local BufferC = require(script.Parent.Parent.datatype.Buffer)
local CFrameC = require(script.Parent.Parent.datatype.CFrame)
local ColorC = require(script.Parent.Parent.datatype.Color)
local Float16C = require(script.Parent.Parent.primitive.Float16)
local IVC = require(script.Parent.Parent.datatype.IntVector)
local NRC = require(script.Parent.Parent.datatype.NumberRange)
local NumberC = require(script.Parent.Parent.primitive.Number)
local RayC = require(script.Parent.Parent.datatype.Ray)
local RectC = require(script.Parent.Parent.datatype.Rect)
local RegionC = require(script.Parent.Parent.datatype.Region)
local SeqC = require(script.Parent.Parent.datatype.Sequence)
local Types = require(script.Parent.Parent.Parent.Types)
local UDimC = require(script.Parent.Parent.datatype.UDim)
local Varint = require(script.Parent.Parent.primitive.Varint)
local VecC = require(script.Parent.Parent.datatype.Vector)

-- Constants -----------------------------------------------------------

local TAG_NIL = 0
local TAG_BOOL = 1
local TAG_U8 = 2
local TAG_U16 = 3
local TAG_U32 = 4
local TAG_I8 = 5
local TAG_I16 = 6
local TAG_I32 = 7
local TAG_F32 = 8
local TAG_F64 = 9
local TAG_STRING = 10
local TAG_VEC2 = 11
local TAG_VEC3 = 12
local TAG_COLOR3 = 13
local TAG_CFRAME = 14
local TAG_BUFFER = 15
local TAG_UDIM = 16
local TAG_UDIM2 = 17
local TAG_NUMRANGE = 18
local TAG_RECT = 19
local TAG_V2I16 = 20
local TAG_V3I16 = 21
local TAG_REGION3 = 22
local TAG_R3I16 = 23
local TAG_RAY = 24
local TAG_NUMSEQ = 25
local TAG_COLSEQ = 26

local INLINE_MAX = Varint.INLINE_MAX

-- Private -------------------------------------------------------------

local alloc = Base.alloc
local writeu8 = buffer.writeu8
local readu8 = buffer.readu8
local floor = math.floor
local abs = math.abs

local F32_SCRATCH = NumberC._f32Scratch

--[[
    Write dispatch tables: tag → { directWrite, payloadSize }.
    Used for integer/float types where the codec has _directWrite + _size.
]]
local NUM_WRITE: { [number]: (buffer, number, number) -> () } = {
    [TAG_U8] = buffer.writeu8,
    [TAG_U16] = buffer.writeu16,
    [TAG_U32] = buffer.writeu32,
    [TAG_F32] = buffer.writef32,
    [TAG_F64] = buffer.writef64,
}

local NUM_SIZE: { [number]: number } = {
    [TAG_U8] = 1,
    [TAG_U16] = 2,
    [TAG_U32] = 4,
    [TAG_F32] = 4,
    [TAG_F64] = 8,
}

-- writei8/i16/i32 are NOT FASTCALL, so we use writeu* with two's complement conversion
local function writeI8(b: buffer, off: number, v: number): ()
    writeu8(b, off, if v < 0 then v + 0x100 else v)
end

local function writeI16(b: buffer, off: number, v: number): ()
    buffer.writeu16(b, off, if v < 0 then v + 0x10000 else v)
end

local function writeI32(b: buffer, off: number, v: number): ()
    buffer.writeu32(b, off, if v < 0 then v + 0x100000000 else v)
end

NUM_WRITE[TAG_I8] = writeI8
NUM_WRITE[TAG_I16] = writeI16
NUM_WRITE[TAG_I32] = writeI32
NUM_SIZE[TAG_I8] = 1
NUM_SIZE[TAG_I16] = 2
NUM_SIZE[TAG_I32] = 4

table.freeze(NUM_WRITE)
table.freeze(NUM_SIZE)

-- Selects the smallest integer/float tag for a number value
local function selectNumberTag(value: number): number
    -- NaN always f64
    if value ~= value then
        return TAG_F64
    end

    -- Integer path
    if floor(value) == value then
        if value >= 0 then
            if value <= 0xFF then
                return TAG_U8
            end
            if value <= 0xFFFF then
                return TAG_U16
            end
            if value <= 0xFFFFFFFF then
                return TAG_U32
            end
        else
            if value >= -0x80 then
                return TAG_I8
            end
            if value >= -0x8000 then
                return TAG_I16
            end
            if value >= -0x80000000 then
                return TAG_I32
            end
        end
    end

    -- Float path: try f32 round-trip
    buffer.writef32(F32_SCRATCH, 0, value)
    if buffer.readf32(F32_SCRATCH, 0) == value then
        return TAG_F32
    end

    return TAG_F64
end

--[[
    Roblox type dispatch: typeof string → { tag, codec }.
    For types with _directWrite + _size, we use the fast path.
]]
type TypeEntry = {
    tag: number,
    codec: Types.InternalCodec<any>,
}

local TYPE_DISPATCH: { [string]: TypeEntry } = table.freeze({
    Vector2 = { tag = TAG_VEC2, codec = VecC.vec2 },
    Vector3 = { tag = TAG_VEC3, codec = VecC.vec3 },
    Color3 = { tag = TAG_COLOR3, codec = ColorC.color3 },
    CFrame = { tag = TAG_CFRAME, codec = CFrameC.cframe },
    UDim = { tag = TAG_UDIM, codec = UDimC.udim },
    UDim2 = { tag = TAG_UDIM2, codec = UDimC.udim2 },
    NumberRange = { tag = TAG_NUMRANGE, codec = NRC.numberRange },
    Rect = { tag = TAG_RECT, codec = RectC.rect },
    Vector2int16 = { tag = TAG_V2I16, codec = IVC.vec2int16 },
    Vector3int16 = { tag = TAG_V3I16, codec = IVC.vec3int16 },
    Region3 = { tag = TAG_REGION3, codec = RegionC.region3 },
    Region3int16 = { tag = TAG_R3I16, codec = RegionC.region3int16 },
    Ray = { tag = TAG_RAY, codec = RayC.ray },
    NumberSequence = { tag = TAG_NUMSEQ, codec = SeqC.numberSequence },
    ColorSequence = { tag = TAG_COLSEQ, codec = SeqC.colorSequence },
} :: any)

-- Read dispatch: tag → codec
local READ_DISPATCH: { [number]: Types.InternalCodec<any> } = table.freeze({
    [TAG_VEC2] = VecC.vec2,
    [TAG_VEC3] = VecC.vec3,
    [TAG_COLOR3] = ColorC.color3,
    [TAG_CFRAME] = CFrameC.cframe,
    [TAG_UDIM] = UDimC.udim,
    [TAG_UDIM2] = UDimC.udim2,
    [TAG_NUMRANGE] = NRC.numberRange,
    [TAG_RECT] = RectC.rect,
    [TAG_V2I16] = IVC.vec2int16,
    [TAG_V3I16] = IVC.vec3int16,
    [TAG_REGION3] = RegionC.region3,
    [TAG_R3I16] = RegionC.region3int16,
    [TAG_RAY] = RayC.ray,
    [TAG_NUMSEQ] = SeqC.numberSequence,
    [TAG_COLSEQ] = SeqC.colorSequence,
} :: any)

-- Numeric read helpers
local function readI8(b: buffer, off: number): number
    local raw = readu8(b, off)
    return if raw >= 0x80 then raw - 0x100 else raw
end

local function readI16(b: buffer, off: number): number
    local raw = buffer.readu16(b, off)
    return if raw >= 0x8000 then raw - 0x10000 else raw
end

local function readI32(b: buffer, off: number): number
    local raw = buffer.readu32(b, off)
    return if raw >= 0x80000000 then raw - 0x100000000 else raw
end

local NUM_READ: { [number]: (buffer, number) -> number } = table.freeze({
    [TAG_U8] = buffer.readu8,
    [TAG_U16] = buffer.readu16,
    [TAG_U32] = buffer.readu32,
    [TAG_I8] = readI8,
    [TAG_I16] = readI16,
    [TAG_I32] = readI32,
    [TAG_F32] = buffer.readf32,
    [TAG_F64] = buffer.readf64,
})

-- Public --------------------------------------------------------------

local AutoModule = {}

AutoModule.auto = table.freeze({
    write = function(ch: Types.ChannelState, value: any): ()
        if value == nil then
            local cursor = ch.cursor
            if cursor + 1 > ch.size then
                alloc(ch, 1)
            end
            writeu8(ch.buff, cursor, TAG_NIL)
            ch.cursor = cursor + 1
            return
        end

        local t = typeof(value)

        if t == "boolean" then
            local cursor = ch.cursor
            if cursor + 2 > ch.size then
                alloc(ch, 2)
            end
            local b = ch.buff
            writeu8(b, cursor, TAG_BOOL)
            writeu8(b, cursor + 1, if value then 1 else 0)
            ch.cursor = cursor + 2
            return
        end

        if t == "number" then
            local tag = selectNumberTag(value)
            local payloadSize = NUM_SIZE[tag]
            local totalSize = 1 + payloadSize
            local cursor = ch.cursor
            if cursor + totalSize > ch.size then
                alloc(ch, totalSize)
            end
            local b = ch.buff
            writeu8(b, cursor, tag)
            NUM_WRITE[tag](b, cursor + 1, value)
            ch.cursor = cursor + totalSize
            return
        end

        if t == "string" then
            local cursor = ch.cursor
            if cursor + 1 > ch.size then
                alloc(ch, 1)
            end
            writeu8(ch.buff, cursor, TAG_STRING)
            ch.cursor = cursor + 1
            -- Inline string write (same as String codec)
            local len = #value
            cursor = ch.cursor
            if len <= INLINE_MAX then
                if cursor + 1 + len > ch.size then
                    alloc(ch, 1 + len)
                end
                local b = ch.buff
                writeu8(b, cursor, len)
                if len > 0 then
                    buffer.writestring(b, cursor + 1, value)
                end
                ch.cursor = cursor + 1 + len
            else
                Varint.write(ch, len)
                cursor = ch.cursor
                if cursor + len > ch.size then
                    alloc(ch, len)
                end
                buffer.writestring(ch.buff, cursor, value)
                ch.cursor = cursor + len
            end
            return
        end

        if t == "buffer" then
            local cursor = ch.cursor
            if cursor + 1 > ch.size then
                alloc(ch, 1)
            end
            writeu8(ch.buff, cursor, TAG_BUFFER)
            ch.cursor = cursor + 1
            BufferC.buff.write(ch, value)
            return
        end

        -- Roblox type dispatch
        local entry = TYPE_DISPATCH[t]
        if entry then
            local codec = entry.codec
            local size = codec._size
            local dw = codec._directWrite

            if size and dw then
                -- Fast path: direct write
                local totalSize = 1 + size
                local cursor = ch.cursor
                if cursor + totalSize > ch.size then
                    alloc(ch, totalSize)
                end
                local b = ch.buff
                writeu8(b, cursor, entry.tag)
                dw(b, cursor + 1, value)
                ch.cursor = cursor + totalSize
            else
                -- Generic path
                local cursor = ch.cursor
                if cursor + 1 > ch.size then
                    alloc(ch, 1)
                end
                writeu8(ch.buff, cursor, entry.tag)
                ch.cursor = cursor + 1
                codec.write(ch, value)
            end
            return
        end

        error(`[Lync] Auto: unsupported type "{t}"`)
    end,

    read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
        local tag = readu8(src, pos)

        -- Nil
        if tag == TAG_NIL then
            return nil, 1
        end

        -- Bool
        if tag == TAG_BOOL then
            return readu8(src, pos + 1) ~= 0, 2
        end

        -- Numeric types (tags 2-9)
        local numRead = NUM_READ[tag]
        if numRead then
            local payloadSize = NUM_SIZE[tag]
            return numRead(src, pos + 1), 1 + payloadSize
        end

        -- String
        if tag == TAG_STRING then
            local b0 = readu8(src, pos + 1)
            local len: number
            local lenBytes: number

            if b0 <= INLINE_MAX then
                len = b0
                lenBytes = 1
            else
                len, lenBytes = Varint.read(src, pos + 1)
            end

            if len == 0 then
                return "", 1 + lenBytes
            end

            local dataStart = pos + 1 + lenBytes
            return buffer.readstring(src, dataStart, len), 1 + lenBytes + len
        end

        -- Buffer
        if tag == TAG_BUFFER then
            local value, consumed = BufferC.buff.read(src, pos + 1, refs)
            return value, 1 + consumed
        end

        -- Roblox types
        local codec = READ_DISPATCH[tag]
        if codec then
            local value, consumed = codec.read(src, pos + 1, refs)
            return value, 1 + consumed
        end

        error(`[Lync] Auto: unknown tag {tag}`)
    end,
} :: any)

return table.freeze(AutoModule)


--!strict
--!native
-- Sub-byte packing. 1 to 32 bits across bool/uint/int fields.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local band = bit32.band
local bor = bit32.bor
local lshift = bit32.lshift
local rshift = bit32.rshift

-- Field type constants
local TYPE_BOOL = 0
local TYPE_UINT = 1
local TYPE_INT = 2

-- Public --------------------------------------------------------------

local Bitfield = {}

function Bitfield.define(schema: { [string]: { type: string, width: number? } }): any
    -- Collect and sort keys alphabetically
    local keys = {}
    for key in schema do
        table.insert(keys, key)
    end
    table.sort(keys)

    if #keys == 0 then
        error("[Lync] Lync.bitfield: requires at least one field")
    end

    local fieldCount = #keys
    local fieldKeys = table.create(fieldCount)
    local fieldTypes = table.create(fieldCount)
    local fieldOffsets = table.create(fieldCount)
    local fieldMasks = table.create(fieldCount)
    local fieldSignBit = table.create(fieldCount)
    local fieldSignExt = table.create(fieldCount)
    local totalBits = 0

    for i = 1, fieldCount do
        local key = keys[i]
        local spec = schema[key]
        fieldKeys[i] = key
        fieldOffsets[i] = totalBits

        if spec.type == "bool" then
            fieldTypes[i] = TYPE_BOOL
            fieldMasks[i] = 1
            fieldSignBit[i] = 0
            fieldSignExt[i] = 0
            totalBits += 1
        elseif spec.type == "uint" then
            local width = spec.width
            if not width or width < 1 or width > 32 then
                error(`[Lync] Lync.bitfield: field "{key}" uint width must be 1-32`)
            end
            fieldTypes[i] = TYPE_UINT
            fieldMasks[i] = lshift(1, width) - 1
            fieldSignBit[i] = 0
            fieldSignExt[i] = 0
            totalBits += width
        elseif spec.type == "int" then
            local width = spec.width
            if not width or width < 2 or width > 32 then
                error(`[Lync] Lync.bitfield: field "{key}" int width must be 2-32`)
            end
            fieldTypes[i] = TYPE_INT
            fieldMasks[i] = lshift(1, width) - 1
            fieldSignBit[i] = lshift(1, width - 1)
            fieldSignExt[i] = lshift(1, width)
            totalBits += width
        else
            error(`[Lync] Lync.bitfield: unknown field type "{spec.type}"`)
        end
    end

    if totalBits > 32 then
        error(`[Lync] Lync.bitfield: total exceeds 32 bits ({totalBits})`)
    end

    -- Determine wire size
    local wireBytes: number
    local wfn: (buffer, number, number) -> ()
    local rfn: (buffer, number) -> number

    if totalBits <= 8 then
        wireBytes = 1
        wfn = buffer.writeu8
        rfn = buffer.readu8
    elseif totalBits <= 16 then
        wireBytes = 2
        wfn = buffer.writeu16
        rfn = buffer.readu16
    else
        wireBytes = 4
        wfn = buffer.writeu32
        rfn = buffer.readu32
    end

    -- Freeze all descriptor arrays
    table.freeze(fieldKeys)
    table.freeze(fieldTypes)
    table.freeze(fieldOffsets)
    table.freeze(fieldMasks)
    table.freeze(fieldSignBit)
    table.freeze(fieldSignExt)

    return Base.define(wireBytes, function(b: buffer, off: number, value: any): ()
        local packed = 0
        for i = 1, fieldCount do
            local v = value[fieldKeys[i]]
            if fieldTypes[i] == TYPE_BOOL then
                if v then
                    packed = bor(packed, lshift(1, fieldOffsets[i]))
                end
            else
                packed = bor(packed, lshift(band(v, fieldMasks[i]), fieldOffsets[i]))
            end
        end
        wfn(b, off, packed)
    end, function(b: buffer, off: number): any
        local packed = rfn(b, off)
        local result: { [string]: any } = {}

        for i = 1, fieldCount do
            local raw = band(rshift(packed, fieldOffsets[i]), fieldMasks[i])
            if fieldTypes[i] == TYPE_BOOL then
                result[fieldKeys[i]] = raw ~= 0
            elseif fieldTypes[i] == TYPE_INT then
                if raw >= fieldSignBit[i] then
                    result[fieldKeys[i]] = raw - fieldSignExt[i]
                else
                    result[fieldKeys[i]] = raw
                end
            else
                result[fieldKeys[i]] = raw
            end
        end

        return result
    end)
end

return table.freeze(Bitfield)


--!strict
--!optimize 2
-- User-defined fixed-size codec.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local floor = math.floor

-- Public --------------------------------------------------------------

local Custom = {}

function Custom.define(
    size: number,
    writeFn: (b: buffer, offset: number, value: any) -> (),
    readFn: (b: buffer, offset: number) -> any
): any
    if type(size) ~= "number" or size < 0 or floor(size) ~= size then
        error("[Lync] Lync.custom: size must be a non-negative integer")
    end

    if size == 0 then
        return Base.zero()
    end

    return Base.define(size, writeFn, readFn)
end

return table.freeze(Custom)


--!strict
--!optimize 2
-- String enum codec. Maps values to u8 indices at define time.

local Base = require(script.Parent.Parent.Base)

-- Public --------------------------------------------------------------

local EnumCodec = {}

function EnumCodec.define(...: string): any
    local values = { ... }
    local count = #values
    if count == 0 then
        error("[Lync] Lync.enum: requires at least one value")
    end
    if count > 256 then
        error("[Lync] Lync.enum: exceeds 256 variants")
    end

    -- Build lookup tables
    local toIndex: { [string]: number } = {}
    local toValue = table.create(count)

    for i = 1, count do
        local val = values[i]
        if toIndex[val] then
            error(`[Lync] Lync.enum: duplicate value "{val}"`)
        end
        toIndex[val] = i - 1
        toValue[i] = val
    end

    table.freeze(toIndex)
    table.freeze(toValue)

    return Base.define(1, function(b: buffer, off: number, value: string): ()
        local idx = toIndex[value]
        if idx == nil then
            error(`[Lync] Enum: invalid value "{value}"`)
        end
        buffer.writeu8(b, off, idx)
    end, function(b: buffer, off: number): string
        local idx = buffer.readu8(b, off)
        local val = toValue[idx + 1]
        if val == nil then
            error(`[Lync] Enum: invalid index {idx}`)
        end
        return val
    end, { _typeCheck = "string" })
end

return table.freeze(EnumCodec)


--!strict
--!native
-- Fixed-point quantization codec. Maps float ranges to integer ranges.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local ceil = math.ceil
local clamp = math.clamp
local round = math.round
local max = math.max
local min = math.min

-- Public --------------------------------------------------------------

local Float = {}

function Float.float(rangeMin: number, rangeMax: number, precision: number): any
    if rangeMin > rangeMax then
        error(`[Lync] Lync.float: min ({rangeMin}) must be <= max ({rangeMax})`)
    end
    if precision <= 0 then
        error("[Lync] Lync.float: precision must be positive")
    end

    local delta = rangeMax - rangeMin

    -- Constant value: zero bytes
    if delta == 0 then
        return Base.zero()
    end

    local maxInt = min(0xFFFFFFFF, max(1, ceil(delta / precision)))

    local compBytes: number
    local wfn: (buffer, number, number) -> ()
    local rfn: (buffer, number) -> number

    if maxInt <= 0xFF then
        compBytes = 1
        wfn = buffer.writeu8
        rfn = buffer.readu8
    elseif maxInt <= 0xFFFF then
        compBytes = 2
        wfn = buffer.writeu16
        rfn = buffer.readu16
    else
        compBytes = 4
        wfn = buffer.writeu32
        rfn = buffer.readu32
    end

    local scale = maxInt / delta
    local invScale = delta / maxInt

    return Base.define(compBytes, function(b: buffer, off: number, value: number): ()
        wfn(b, off, round(clamp(value - rangeMin, 0, delta) * scale))
    end, function(b: buffer, off: number): number
        return rfn(b, off) * invScale + rangeMin
    end, { _min = rangeMin, _max = rangeMax, _typeCheck = "number" })
end

return table.freeze(Float)


--!strict
--!optimize 2
-- Zero-byte codec. Reads nil. For fire-and-forget signals.

local Base = require(script.Parent.Parent.Base)

-- Public --------------------------------------------------------------

local Nothing = {}

Nothing.nothing = Base.zero()

return table.freeze(Nothing)


--!strict
--!optimize 2
-- Bypasses buffer serialization. Uses Roblox remote sidecar refs.

local Base = require(script.Parent.Parent.Base)
local Types = require(script.Parent.Parent.Parent.Types)

-- Private -------------------------------------------------------------

local alloc = Base.alloc
local writeu16 = buffer.writeu16
local readu16 = buffer.readu16

-- Public --------------------------------------------------------------

local Unknown = {}

Unknown.unknown = table.freeze({
    _hasUnknown = true,

    write = function(ch: Types.ChannelState, value: any): ()
        local idx = ch.refCount + 1
        if idx > 0xFFFF then
            error("[Lync] Unknown: ref overflow (>65535)")
        end

        ch.refs[idx] = value :: any
        ch.refCount = idx

        local cursor = ch.cursor
        if cursor + 2 > ch.size then
            alloc(ch, 2)
        end
        writeu16(ch.buff, cursor, idx)
        ch.cursor = cursor + 2
    end,

    read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
        if not refs then
            error("[Lync] Unknown: read requires refs array")
        end

        local idx = readu16(src, pos)
        return (refs :: any)[idx], 2
    end,
} :: any)

return table.freeze(Unknown)


--!strict
--!optimize 2
-- Boolean codec with bitpacking flag for struct and array codecs.

local Base = require(script.Parent.Parent.Base)

-- Public --------------------------------------------------------------

local Bool = {}

Bool.bool = Base.define(1, function(b: buffer, off: number, value: boolean): ()
    buffer.writeu8(b, off, if value then 1 else 0)
end, function(b: buffer, off: number): boolean
    return buffer.readu8(b, off) ~= 0
end, {
    _isBool = true,
    _typeCheck = "boolean",
})

return table.freeze(Bool)


--!strict
--!native
-- Half-precision float codec. 2 bytes, ±65504, ~3 decimal digits.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local band = bit32.band
local bor = bit32.bor
local lshift = bit32.lshift
local rshift = bit32.rshift
local frexp = math.frexp
local ldexp = math.ldexp
local round = math.round
local huge = math.huge

local function encode(value: number): number
    -- NaN
    if value ~= value then
        return 0x7E00
    end

    -- Zero (both +0 and -0 map to positive zero)
    if value == 0 then
        return 0
    end

    -- Sign extraction
    local sign = 0
    if value < 0 then
        sign = 0x8000
        value = -value
    end

    -- Overflow to infinity
    if value > 65504 then
        return bor(sign, 0x7C00)
    end

    -- Decompose via frexp: value = mantissa * 2^exponent, mantissa in [0.5, 1)
    local mantissa, exponent = frexp(value)
    local biasedExp = exponent + 14

    if biasedExp <= 0 then
        -- Subnormal: frac = round(mantissa * 2^(biasedExp + 10))
        local frac = round(ldexp(mantissa, biasedExp + 10))
        if frac >= 1024 then
            -- Rounds to minimum normal
            return bor(sign, 0x0400)
        end
        if frac <= 0 then
            return sign
        end
        return bor(sign, frac)
    end

    -- Normal: frac = round((mantissa * 2 - 1) * 1024)
    local frac = round((mantissa * 2 - 1) * 1024)

    -- Carry: mantissa rounding overflows into exponent
    if frac >= 1024 then
        frac = 0
        biasedExp += 1
    end

    -- Overflow after carry
    if biasedExp >= 31 then
        return bor(sign, 0x7C00)
    end

    return bor(sign, lshift(biasedExp, 10), frac)
end

local function decode(raw: number): number
    local sign = band(raw, 0x8000)
    local exp = band(rshift(raw, 10), 0x1F)
    local frac = band(raw, 0x03FF)

    local value: number

    if exp == 0 then
        -- Zero or subnormal
        value = if frac == 0 then 0 else ldexp(frac, -24)
    elseif exp == 31 then
        -- Infinity or NaN
        value = if frac == 0 then huge else 0 / 0
    else
        -- Normal: (frac + 1024) * 2^(exp - 25)
        value = ldexp(frac + 1024, exp - 25)
    end

    return if sign ~= 0 then -value else value
end

-- Public --------------------------------------------------------------

local Float16 = {}

Float16.f16 = Base.define(2, function(b: buffer, off: number, value: number): ()
    buffer.writeu16(b, off, encode(value))
end, function(b: buffer, off: number): number
    return decode(buffer.readu16(b, off))
end, { _typeCheck = "number" })

return table.freeze(Float16)


--!strict
--!optimize 2
-- Smart integer codec. Auto-selects u8/u16/u32/i8/i16/i32 from range.

local Base = require(script.Parent.Parent.Base)

-- Private -------------------------------------------------------------

local floor = math.floor

-- Public --------------------------------------------------------------

local Int = {}

function Int.int(min: number, max: number): any
    if min > max then
        error(`[Lync] Lync.int: min ({min}) must be <= max ({max})`)
    end
    if floor(min) ~= min or floor(max) ~= max then
        error("[Lync] Lync.int: bounds must be integers")
    end

    local meta = {
        _min = min,
        _max = max,
        _isInteger = true,
        _typeCheck = "number",
    }

    if min >= 0 then
        -- Unsigned path
        if max <= 0xFF then
            return Base.define(1, buffer.writeu8, buffer.readu8, meta)
        elseif max <= 0xFFFF then
            return Base.define(2, buffer.writeu16, buffer.readu16, meta)
        elseif max <= 0xFFFFFFFF then
            return Base.define(4, buffer.writeu32, buffer.readu32, meta)
        end
    else
        -- Signed path: use unsigned write/read, interpret as signed
        if min >= -0x80 and max <= 0x7F then
            return Base.define(1, function(b: buffer, off: number, v: number): ()
                buffer.writeu8(b, off, if v < 0 then v + 0x100 else v)
            end, function(b: buffer, off: number): number
                local raw = buffer.readu8(b, off)
                return if raw >= 0x80 then raw - 0x100 else raw
            end, meta)
        elseif min >= -0x8000 and max <= 0x7FFF then
            return Base.define(2, function(b: buffer, off: number, v: number): ()
                buffer.writeu16(b, off, if v < 0 then v + 0x10000 else v)
            end, function(b: buffer, off: number): number
                local raw = buffer.readu16(b, off)
                return if raw >= 0x8000 then raw - 0x10000 else raw
            end, meta)
        elseif min >= -0x80000000 and max <= 0x7FFFFFFF then
            return Base.define(4, function(b: buffer, off: number, v: number): ()
                buffer.writeu32(b, off, if v < 0 then v + 0x100000000 else v)
            end, function(b: buffer, off: number): number
                local raw = buffer.readu32(b, off)
                return if raw >= 0x80000000 then raw - 0x100000000 else raw
            end, meta)
        end
    end

    error(`[Lync] Lync.int: range [{min}, {max}] exceeds i32 bounds`)
end

return table.freeze(Int)


--!strict
--!optimize 2
-- Explicit-precision float codecs: f32 and f64.

local Base = require(script.Parent.Parent.Base)

-- Constants -----------------------------------------------------------

local F32_SCRATCH = buffer.create(4)

-- Public --------------------------------------------------------------

local Number = {}

Number.f32 = Base.define(4, buffer.writef32, buffer.readf32, {
    _typeCheck = "number",
})

Number.f64 = Base.define(8, buffer.writef64, buffer.readf64, {
    _typeCheck = "number",
})

-- Shared scratch buffer for Auto codec f32 round-trip test
Number._f32Scratch = F32_SCRATCH

return table.freeze(Number)


--!strict
--!native
-- Dense prefix-varint encoding with single-branch decode.

local Base = require(script.Parent.Parent.Base)
local Types = require(script.Parent.Parent.Parent.Types)

-- Constants -----------------------------------------------------------

local INLINE_MAX = 0xBF

-- Private -------------------------------------------------------------

local alloc = Base.alloc
local band = bit32.band
local bor = bit32.bor
local rshift = bit32.rshift
local writeu8 = buffer.writeu8
local writeu16 = buffer.writeu16
local writeu32 = buffer.writeu32
local readu8 = buffer.readu8
local readu16 = buffer.readu16
local readu32 = buffer.readu32

-- Public --------------------------------------------------------------

local Varint = {}

Varint.INLINE_MAX = INLINE_MAX

function Varint.write(ch: Types.ChannelState, value: number): ()
    if value < 0 or value > 0xFFFFFFFF or value % 1 ~= 0 then
        error(`[Lync] Varint: value must be an integer in [0, 4294967295], got {value}`)
    end

    local cursor = ch.cursor
    local b = ch.buff

    if value <= INLINE_MAX then
        if cursor + 1 > ch.size then
            alloc(ch, 1)
        end
        writeu8(b, cursor, value)
        ch.cursor = cursor + 1
    elseif value <= 8383 then
        if cursor + 2 > ch.size then
            alloc(ch, 2)
        end
        local adjusted = value - 192
        b = ch.buff -- alloc may have replaced the buffer
        writeu8(b, cursor, bor(0xC0, rshift(adjusted, 8)))
        writeu8(b, cursor + 1, band(adjusted, 0xFF))
        ch.cursor = cursor + 2
    elseif value <= 1056959 then
        if cursor + 3 > ch.size then
            alloc(ch, 3)
        end
        local adjusted = value - 8384
        b = ch.buff
        writeu8(b, cursor, bor(0xE0, rshift(adjusted, 16)))
        writeu16(b, cursor + 1, band(adjusted, 0xFFFF))
        ch.cursor = cursor + 3
    else
        if cursor + 5 > ch.size then
            alloc(ch, 5)
        end
        b = ch.buff
        writeu8(b, cursor, 0xF0)
        writeu32(b, cursor + 1, value)
        ch.cursor = cursor + 5
    end
end

function Varint.read(src: buffer, pos: number): (number, number)
    local b0 = readu8(src, pos)

    if b0 <= INLINE_MAX then
        return b0, 1
    elseif b0 <= 0xDF then
        return band(b0, 0x1F) * 256 + readu8(src, pos + 1) + 192, 2
    elseif b0 <= 0xEF then
        return band(b0, 0x0F) * 65536 + readu16(src, pos + 1) + 8384, 3
    else
        return readu32(src, pos + 1), 5
    end
end

function Varint.writeInline(ch: Types.ChannelState, value: number): ()
    local cursor = ch.cursor
    if cursor + 1 > ch.size then
        alloc(ch, 1)
    end
    writeu8(ch.buff, cursor, value)
    ch.cursor = cursor + 1
end

return table.freeze(Varint)


--!strict
--!optimize 2
-- Lync: buffer networking for Roblox.

local RunService = game:GetService("RunService")

local Base = require(script.codec.Base)
local Channel = require(script.internal.Channel)
local Gate = require(script.transport.Gate)
local Group = require(script.api.Group)
local Middleware = require(script.internal.Middleware)
local Packet = require(script.api.Packet)
local Pool = require(script.internal.Pool)
local Query = require(script.api.Query)
local Registry = require(script.internal.Registry)
local Scope = require(script.api.Scope)
local Types = require(script.Types)

-- Codecs
local ArrayC = require(script.codec.composite.Array)
local AutoC = require(script.codec.meta.Auto)
local BitfieldC = require(script.codec.meta.Bitfield)
local BoolC = require(script.codec.primitive.Bool)
local BufferC = require(script.codec.datatype.Buffer)
local CFrameC = require(script.codec.datatype.CFrame)
local ColorC = require(script.codec.datatype.Color)
local CustomC = require(script.codec.meta.Custom)
local EnumC = require(script.codec.meta.Enum)
local Float16C = require(script.codec.primitive.Float16)
local FloatC = require(script.codec.meta.Float)
local IVC = require(script.codec.datatype.IntVector)
local InstanceC = require(script.codec.datatype.Instance)
local IntC = require(script.codec.primitive.Int)
local MapC = require(script.codec.composite.Map)
local NRC = require(script.codec.datatype.NumberRange)
local NothingC = require(script.codec.meta.Nothing)
local NumberC = require(script.codec.primitive.Number)
local OptionalC = require(script.codec.composite.Optional)
local RayC = require(script.codec.datatype.Ray)
local RectC = require(script.codec.datatype.Rect)
local RegionC = require(script.codec.datatype.Region)
local SeqC = require(script.codec.datatype.Sequence)
local StringC = require(script.codec.datatype.String)
local StructC = require(script.codec.composite.Struct)
local TaggedC = require(script.codec.composite.Tagged)
local TupleC = require(script.codec.composite.Tuple)
local UDimC = require(script.codec.datatype.UDim)
local UnknownC = require(script.codec.meta.Unknown)
local VectorC = require(script.codec.datatype.Vector)

-- Constants -----------------------------------------------------------

local IS_SERVER = RunService:IsServer()

-- State ---------------------------------------------------------------

local _started = false
local _flushRate = 60
local _flushAccum = 0
local _flushConn: RBXScriptConnection? = nil

-- Private -------------------------------------------------------------

local DROP = table.freeze({ _lyncDrop = true })
local ALL = table.freeze({ _lyncAll = true })

local function except(...: any): any
    local excluded: { [Player]: boolean } = {}

    for i = 1, select("#", ...) do
        local arg = select(i, ...)
        if typeof(arg) == "Instance" and (arg :: Instance):IsA("Player") then
            excluded[arg :: Player] = true
        elseif typeof(arg) == "table" and (arg :: any)._lyncGroup then
            for player in (arg :: any)._members do
                excluded[player] = true
            end
        else
            error("[Lync] except: argument must be a Player or Group")
        end
    end

    return table.freeze({ _lyncExcept = true, _excluded = excluded })
end

local function setupFlush(): ()
    local flushFn: () -> ()

    if IS_SERVER then
        local Server = require(script.transport.Server)
        flushFn = function(): ()
            Server.flush()
        end
    else
        local Client = require(script.transport.Client)
        flushFn = function(): ()
            Client.flush()
        end
    end

    _flushConn = RunService.Heartbeat:Connect(function(dt: number)
        if _flushRate >= 60 then
            flushFn()
        else
            _flushAccum += dt
            local interval = 1 / _flushRate
            if _flushAccum >= interval then
                _flushAccum -= interval
                -- Prevent drift accumulation
                if _flushAccum >= interval then
                    _flushAccum = 0
                end
                flushFn()
            end
        end
    end)
end

-- Public --------------------------------------------------------------

local Lync: any = {}

-- Lifecycle -----------------------------------------------------------

function Lync.configure(options: Types.ConfigureOptions): ()
    if _started then
        error("[Lync] Lync.configure: must be called before Lync.start()")
    end

    if options.channelMaxSize then
        local size = options.channelMaxSize
        if size < 4096 or size > 1048576 then
            error("[Lync] Lync.configure: channelMaxSize must be 4096-1048576")
        end
        Channel.setMaxSize(size)
        Base.setMaxSize(size)
    end

    if options.validationDepth then
        local depth = options.validationDepth
        if depth < 4 or depth > 32 then
            error("[Lync] Lync.configure: validationDepth must be 4-32")
        end
        Gate.setDepth(depth)
    end

    if options.poolSize then
        local size = options.poolSize
        if size < 2 or size > 128 then
            error("[Lync] Lync.configure: poolSize must be 2-128")
        end
        Pool.setMaxSize(size)
    end

    if options.bandwidthLimit then
        Gate.configureBandwidth(options.bandwidthLimit.softLimit, options.bandwidthLimit.maxStrikes)
    end

    if options.globalRateLimit then
        Gate.configureGlobalRateLimit(options.globalRateLimit.maxPerSecond)
    end

    if options.stats then
        Channel.enableStats()
        if IS_SERVER then
            local Server = require(script.transport.Server)
            Server.enableStats()
        end
    end
end

function Lync.start(): ()
    if _started then
        error("[Lync] Lync.start: already started")
    end
    _started = true

    if IS_SERVER then
        local Server = require(script.transport.Server)
        Server.start()
    else
        local Client = require(script.transport.Client)
        Client.start()
    end

    setupFlush()
end

-- `Lync.started` is a read-only property backed by _started
Lync.started = false

-- Definitions ---------------------------------------------------------

Lync.packet = Packet.define
Lync.query = Query.define
Lync.group = Group.create
Lync.scope = Scope.create

-- Targeting -----------------------------------------------------------

Lync.all = ALL
Lync.except = except
Lync.DROP = DROP

-- Middleware ----------------------------------------------------------

Lync.onSend = Middleware.onSend
Lync.onReceive = Middleware.onReceive
Lync.onDrop = Middleware.onDrop

-- Runtime control -----------------------------------------------------

function Lync.flush(): ()
    if not _started then
        error("[Lync] Lync.flush: must call Lync.start() first")
    end

    if IS_SERVER then
        local Server = require(script.transport.Server)
        Server.flush()
    else
        local Client = require(script.transport.Client)
        Client.flush()
    end

    -- Reset accumulator to prevent double-flush
    _flushAccum = 0
end

function Lync.flushRate(hz: number): ()
    if hz < 1 or hz > 60 then
        error("[Lync] Lync.flushRate: hz must be 1-60")
    end
    _flushRate = hz
    _flushAccum = 0
end

-- Stats ---------------------------------------------------------------

Lync.stats = table.freeze({
    player = function(player: Player): Types.PlayerStats?
        if not IS_SERVER then
            return nil
        end
        local Server = require(script.transport.Server)
        return Server.getPlayerStats(player)
    end,

    reset = function(): ()
        if IS_SERVER then
            local Server = require(script.transport.Server)
            Server.resetStats()
        end

        -- Reset per-registration stats
        for _, reg in Registry.all() do
            reg.bytesSent = 0
            reg.bytesReceived = 0
            reg.fires = 0
            reg.recvFires = 0
            reg.drops = 0
        end
    end,
})

-- Debug ---------------------------------------------------------------

Lync.debug = table.freeze({
    capture = function(_label: string?): ()
        -- TODO: Implement capture recording
    end,

    stop = function(): ()
        -- TODO: Implement capture stop
    end,

    dump = function(): ()
        -- TODO: Implement capture dump
    end,

    pending = function(): number
        return Query.pendingCount()
    end,

    registrations = function(): { any }
        local regs = Registry.all()
        local result = table.create(#regs)

        for i, reg in regs do
            result[i] = table.freeze({
                name = reg.name,
                id = reg.id,
                kind = reg.kind,
                isUnreliable = reg.isUnreliable,
            })
        end

        return table.freeze(result)
    end,
})

-- Codecs --------------------------------------------------------------

Lync.int = IntC.int
Lync.float = FloatC.float
Lync.f16 = Float16C.f16
Lync.f32 = NumberC.f32
Lync.f64 = NumberC.f64
Lync.bool = BoolC.bool
Lync.string = StringC
Lync.buff = BufferC.buff
Lync.vec2 = VectorC.vec2
Lync.vec3 = VectorC.vec3
Lync.cframe = CFrameC.cframe
Lync.color3 = ColorC.color3
Lync.inst = InstanceC.inst
Lync.udim = UDimC.udim
Lync.udim2 = UDimC.udim2
Lync.numberRange = NRC.numberRange
Lync.rect = RectC.rect
Lync.ray = RayC.ray
Lync.region3 = RegionC.region3
Lync.region3int16 = RegionC.region3int16
Lync.vec2int16 = IVC.vec2int16
Lync.vec3int16 = IVC.vec3int16
Lync.numberSequence = SeqC.numberSequence
Lync.colorSequence = SeqC.colorSequence
Lync.struct = StructC.struct
Lync.deltaStruct = StructC.deltaStruct
Lync.array = ArrayC.array
Lync.deltaArray = ArrayC.deltaArray
Lync.map = MapC.map
Lync.deltaMap = MapC.deltaMap
Lync.optional = OptionalC.optional
Lync.tuple = TupleC.define
Lync.tagged = TaggedC.define
Lync.enum = EnumC.define
Lync.bitfield = BitfieldC.define
Lync.custom = CustomC.define
Lync.nothing = NothingC.nothing
Lync.unknown = UnknownC.unknown
Lync.auto = AutoC.auto

-- Wrap in metatable for `Lync.started` property -----------------------

local LyncProxy = setmetatable({}, {
    __index = function(_self: any, key: string): any
        if key == "started" then
            return _started
        end
        return Lync[key]
    end,

    __newindex = function(): ()
        error("[Lync] Lync: module table is read-only")
    end,
})

return LyncProxy


--!strict
--!optimize 2
-- Read-side delta cache for delta codecs.

-- State ---------------------------------------------------------------

local _caches: { [number]: { [any]: any } } = {}
local _currentKey: any = false
local _hasDelta = false

-- Public --------------------------------------------------------------

local Baseline = {}

function Baseline.setReadKey(key: any): ()
    _currentKey = key
end

function Baseline.getCache(deltaId: number): any?
    local perPlayer = _caches[deltaId]
    if not perPlayer then
        return nil
    end
    return perPlayer[_currentKey]
end

function Baseline.setCache(deltaId: number, value: any): ()
    local perPlayer = _caches[deltaId]
    if not perPlayer then
        perPlayer = {}
        _caches[deltaId] = perPlayer
    end
    perPlayer[_currentKey] = value
end

function Baseline.clearPlayer(key: any): ()
    for _, perPlayer in _caches do
        perPlayer[key] = nil
    end
end

function Baseline.markHasDelta(): ()
    _hasDelta = true
end

function Baseline.hasDelta(): boolean
    return _hasDelta
end

return table.freeze(Baseline)


--!strict
--!optimize 2
-- Ordered intercept chains returning Connection objects.

local Signal = require(script.Parent.Parent.api.Signal)
local Types = require(script.Parent.Parent.Types)

-- State ---------------------------------------------------------------

local _sendSignal = Signal.create()
local _receiveSignal = Signal.create()
local _dropSignal = Signal.create()

-- Public --------------------------------------------------------------

local Middleware = {}

function Middleware.onSend(fn: (data: any, name: string, player: Player?) -> any): Types.Connection
    return _sendSignal:connect(fn)
end

function Middleware.onReceive(
    fn: (data: any, name: string, player: Player?) -> any
): Types.Connection
    return _receiveSignal:connect(fn)
end

function Middleware.onDrop(
    fn: (player: Player, reason: string, name: string, data: any?) -> ()
): Types.Connection
    return _dropSignal:connect(fn)
end

--[[
    Runs send middleware chain. Returns transformed data, or DROP sentinel.
    If no hooks, returns data unchanged (zero overhead).
]]
function Middleware.runSend(data: any, name: string, player: Player?): any
    local count = _sendSignal._count[1]
    if count == 0 then
        return data
    end

    local entries = _sendSignal._entries
    local result = data

    for i = 1, count do
        local entry = entries[i]
        if entry._state ~= 0 then -- not disconnected
            local returned = entry.fn(result, name, player)
            if returned ~= nil then
                result = returned
            end
        end
    end

    return result
end

function Middleware.runReceive(data: any, name: string, player: Player?): any
    local count = _receiveSignal._count[1]
    if count == 0 then
        return data
    end

    local entries = _receiveSignal._entries
    local result = data

    for i = 1, count do
        local entry = entries[i]
        if entry._state ~= 0 then
            local returned = entry.fn(result, name, player)
            if returned ~= nil then
                result = returned
            end
        end
    end

    return result
end

function Middleware.fireDrop(player: Player, reason: string, name: string, data: any?): ()
    _dropSignal:fireSync(player, reason, name, data)
end

function Middleware.hasSendHooks(): boolean
    return _sendSignal._count[1] > 0
end

function Middleware.hasReceiveHooks(): boolean
    return _receiveSignal._count[1] > 0
end

return table.freeze(Middleware)


--!strict
--!optimize 2
-- Stack-based ChannelState pool for per-player channel reuse.

local Channel = require(script.Parent.Channel)
local Types = require(script.Parent.Parent.Types)

-- Constants -----------------------------------------------------------

local DEFAULT_SIZE = 16

-- State ---------------------------------------------------------------

local _stack: { Types.ChannelState } = {}
local _depth = 0
local _maxSize = DEFAULT_SIZE

-- Public --------------------------------------------------------------

local Pool = {}

function Pool.acquire(): Types.ChannelState
    if _depth > 0 then
        local ch = _stack[_depth]
        _stack[_depth] = nil :: any
        _depth -= 1
        Channel.reset(ch)
        return ch
    end

    return Channel.create()
end

function Pool.release(ch: Types.ChannelState): ()
    if _depth >= _maxSize then
        return
    end

    _depth += 1
    _stack[_depth] = ch
end

function Pool.setMaxSize(size: number): ()
    _maxSize = size

    -- Trim excess
    while _depth > _maxSize do
        _stack[_depth] = nil :: any
        _depth -= 1
    end
end

function Pool.count(): number
    return _depth
end

return table.freeze(Pool)


--!strict
--!optimize 2
-- Remote instance creation and access.

local RunService = game:GetService("RunService")

-- Constants -----------------------------------------------------------

local RELIABLE_NAME = "LyncReliable"
local UNRELIABLE_NAME = "LyncUnreliable"

-- State ---------------------------------------------------------------

local _reliable: RemoteEvent? = nil
local _unreliable: UnreliableRemoteEvent? = nil
local _isServer = RunService:IsServer()

-- Public --------------------------------------------------------------

local Bridge = {}

function Bridge.setup(container: Instance): ()
    if _isServer then
        local re = Instance.new("RemoteEvent")
        re.Name = RELIABLE_NAME
        re.Parent = container

        local ure = Instance.new("UnreliableRemoteEvent")
        ure.Name = UNRELIABLE_NAME
        ure.Parent = container

        _reliable = re
        _unreliable = ure
    else
        _reliable = container:WaitForChild(RELIABLE_NAME) :: RemoteEvent
        _unreliable = container:WaitForChild(UNRELIABLE_NAME) :: UnreliableRemoteEvent
    end
end

function Bridge.reliable(): RemoteEvent
    return _reliable :: RemoteEvent
end

function Bridge.unreliable(): UnreliableRemoteEvent
    return _unreliable :: UnreliableRemoteEvent
end

function Bridge.isServer(): boolean
    return _isServer
end

function Bridge.fireServer(data: buffer, refs: { Instance }): ()
    local re = _reliable :: RemoteEvent
    if #refs > 0 then
        re:FireServer(data, refs)
    else
        re:FireServer(data)
    end
end

function Bridge.fireClient(player: Player, data: buffer, refs: { Instance }): ()
    local re = _reliable :: RemoteEvent
    if #refs > 0 then
        re:FireClient(player, data, refs)
    else
        re:FireClient(player, data)
    end
end

function Bridge.fireServerUnreliable(data: buffer, refs: { Instance }): ()
    local ure = _unreliable :: UnreliableRemoteEvent
    if #refs > 0 then
        ure:FireServer(data, refs)
    else
        ure:FireServer(data)
    end
end

function Bridge.fireClientUnreliable(player: Player, data: buffer, refs: { Instance }): ()
    local ure = _unreliable :: UnreliableRemoteEvent
    if #refs > 0 then
        ure:FireClient(player, data, refs)
    else
        ure:FireClient(player, data)
    end
end

return table.freeze(Bridge)


--!strict
--!optimize 2
-- Client-side transport: single channel pair, flush.

local Bridge = require(script.Parent.Parent.transport.Bridge)
local Channel = require(script.Parent.Parent.internal.Channel)
local Reader = require(script.Parent.Parent.transport.Reader)
local Types = require(script.Parent.Parent.Types)

-- State ---------------------------------------------------------------

local _reliable: Types.ChannelState = Channel.create()
local _unreliable: Types.ChannelState = Channel.create()
local _prevIncoming: buffer? = nil
local _prevIncomingLen = 0

-- Public --------------------------------------------------------------

local Client = {}

function Client.getChannel(isUnreliable: boolean): Types.ChannelState
    return if isUnreliable then _unreliable else _reliable
end

function Client.flush(): ()
    if Channel.hasTimestamps() then
        Channel.updateTimestamp()
    end

    -- Reliable
    if _reliable.cursor > 0 then
        local snapshot, refs, byteLen, _ = Channel.sealAndDump(_reliable)
        Bridge.fireServer(snapshot, refs)
        Channel.reset(_reliable)
    end

    -- Unreliable
    if _unreliable.cursor > 0 then
        local snapshot, refs, byteLen, _ = Channel.sealAndDump(_unreliable)
        Bridge.fireServerUnreliable(snapshot, refs)
        Channel.reset(_unreliable)
    end
end

function Client.start(): ()
    local container = game:GetService("ReplicatedStorage"):WaitForChild("LyncRemotes")
    Bridge.setup(container)

    local reliable = Bridge.reliable()
    local unreliable = Bridge.unreliable()

    -- Incoming reliable (XOR encoded)
    reliable.OnClientEvent:Connect(function(data: any, refs: any?)
        local incoming = Reader.decodeIncoming(data)
        if not incoming then
            return
        end

        local incomingLen = buffer.len(incoming)

        -- XOR decode against previous frame
        local decoded: buffer
        if _prevIncoming then
            decoded = Channel.xorApply(incoming, incomingLen, _prevIncoming, _prevIncomingLen)
        else
            decoded = incoming
        end

        -- Store decoded (not XOR'd) for next frame's XOR base
        _prevIncoming = decoded
        _prevIncomingLen = incomingLen

        local ok, err = pcall(Reader.process, decoded, incomingLen, refs :: any, nil, false)
        if not ok then
            warn(`[Lync] Client: read error: {err}`)
        end
    end)

    -- Incoming unreliable (no XOR)
    unreliable.OnClientEvent:Connect(function(data: any, refs: any?)
        local incoming = Reader.decodeIncoming(data)
        if not incoming then
            return
        end

        local incomingLen = buffer.len(incoming)

        local ok, err = pcall(Reader.process, incoming, incomingLen, refs :: any, nil, false)
        if not ok then
            warn(`[Lync] Client: read error (unreliable): {err}`)
        end
    end)
end

return table.freeze(Client)


--!strict
--!native
-- Schema-aware validation and rate limiting.

local Middleware = require(script.Parent.Parent.internal.Middleware)
local Registry = require(script.Parent.Parent.internal.Registry)
local Types = require(script.Parent.Parent.Types)

-- Constants -----------------------------------------------------------

local DEFAULT_DEPTH = 16

-- State ---------------------------------------------------------------

local _depth = DEFAULT_DEPTH
local _rateLimits: { [number]: { [Player]: Types.RateLimitState } } = {}
local _globalLimits: { [Player]: Types.RateLimitState } = {}
local _globalMaxPerSecond: number? = nil
local _bandwidthSoftLimit: number? = nil
local _bandwidthMaxStrikes: number? = nil
local _bandwidthStrikes: { [Player]: number } = {}

-- Private -------------------------------------------------------------

local isfinite = math.isfinite
local floor = math.floor
local abs = math.abs
local clock = os.clock

--[[
    Checks a number value for NaN/inf and optionally range + integrality.
    Returns true if the value passes all checks.
]]
local function validateNumber(
    value: number,
    isInteger: boolean?,
    minVal: number?,
    maxVal: number?
): boolean
    if not isfinite(value) then
        return false
    end

    if isInteger and floor(value) ~= value then
        return false
    end

    if minVal and value < minVal then
        return false
    end
    if maxVal and value > maxVal then
        return false
    end

    return true
end

--[[
    Recursive NaN/inf scan for values without schema metadata.
    Used as fallback for f32, f64, auto, and Roblox types.
]]
local function scanValue(value: any, depth: number): boolean
    if depth <= 0 then
        return true
    end

    local t = typeof(value)

    if t == "number" then
        return isfinite(value)
    end

    if t == "Vector2" then
        return isfinite(value.X) and isfinite(value.Y)
    end

    if t == "Vector3" then
        return isfinite(value.X) and isfinite(value.Y) and isfinite(value.Z)
    end

    if t == "CFrame" then
        local pos = value.Position
        if not isfinite(pos.X) or not isfinite(pos.Y) or not isfinite(pos.Z) then
            return false
        end
        local rx, ry, rz = value:ToEulerAnglesXYZ()
        return isfinite(rx) and isfinite(ry) and isfinite(rz)
    end

    if t == "UDim" then
        return isfinite(value.Scale) and isfinite(value.Offset)
    end

    if t == "UDim2" then
        return isfinite(value.X.Scale)
            and isfinite(value.X.Offset)
            and isfinite(value.Y.Scale)
            and isfinite(value.Y.Offset)
    end

    if t == "Color3" then
        return isfinite(value.R) and isfinite(value.G) and isfinite(value.B)
    end

    if t == "NumberRange" then
        return isfinite(value.Min) and isfinite(value.Max)
    end

    if t == "Rect" then
        return isfinite(value.Min.X)
            and isfinite(value.Min.Y)
            and isfinite(value.Max.X)
            and isfinite(value.Max.Y)
    end

    if t == "Ray" then
        local o = value.Origin
        local d = value.Direction
        return isfinite(o.X)
            and isfinite(o.Y)
            and isfinite(o.Z)
            and isfinite(d.X)
            and isfinite(d.Y)
            and isfinite(d.Z)
    end

    if t == "table" then
        local nextDepth = depth - 1
        for _, v in value do
            if not scanValue(v, nextDepth) then
                return false
            end
        end
        return true
    end

    -- string, boolean, nil, buffer, Instance, etc. are always clean
    return true
end

--[[
    Schema-aware validation. Dispatches based on codec metadata.
    Returns true if valid, false + reason if not.
]]
local function validateWithCodec(
    value: any,
    codec: Types.InternalCodec<any>,
    depth: number
): (boolean, string?)
    if depth <= 0 then
        return true, nil
    end

    local codecAny = codec :: any

    -- Type check
    local typeCheck = codecAny._typeCheck
    if typeCheck then
        if typeof(value) ~= typeCheck then
            return false, `expected {typeCheck}, got {typeof(value)}`
        end
    end

    -- Boolean: typeof check is sufficient
    if codecAny._isBool then
        return true, nil
    end

    -- Integer + range
    if codecAny._isInteger then
        if not isfinite(value) then
            return false, "non-finite"
        end
        if floor(value) ~= value then
            return false, "non-integer"
        end
        local minV = codecAny._min
        local maxV = codecAny._max
        if minV and value < minV then
            return false, `below min {minV}`
        end
        if maxV and value > maxV then
            return false, `above max {maxV}`
        end
        return true, nil
    end

    -- Float with range (quantized float)
    if codecAny._min ~= nil and codecAny._max ~= nil and not codecAny._isInteger then
        if not isfinite(value) then
            return false, "non-finite"
        end
        return true, nil
    end

    -- Struct schema: recursive per-field validation
    if codecAny._schema then
        if typeof(value) ~= "table" then
            return false, "expected table for struct"
        end

        local schema = codecAny._schema
        for key, fieldCodec in schema do
            local fieldVal = value[key]
            if fieldVal == nil and not (fieldCodec :: any)._isBool then
                return false, `missing field "{key}"`
            end
            local ok, reason = validateWithCodec(fieldVal, fieldCodec, depth - 1)
            if not ok then
                return false, `field "{key}": {reason}`
            end
        end
        return true, nil
    end

    -- Fallback: generic NaN scan for f32, f64, auto, and Roblox types
    if not scanValue(value, depth) then
        return false, "NaN/inf detected"
    end

    return true, nil
end

-- Public --------------------------------------------------------------

local Gate = {}

function Gate.setDepth(depth: number): ()
    _depth = depth
end

function Gate.validate(value: any, codec: Types.InternalCodec<any>): (boolean, string?)
    return validateWithCodec(value, codec, _depth)
end

function Gate.scan(value: any): boolean
    return scanValue(value, _depth)
end

-- Rate limiting -------------------------------------------------------

function Gate.checkRateLimit(reg: Types.Registration, player: Player): boolean
    local config = reg.rateLimit
    if not config then
        return true
    end

    local now = clock()

    -- Get or create per-player state
    local perPacket = _rateLimits[reg.id]
    if not perPacket then
        perPacket = {}
        _rateLimits[reg.id] = perPacket
    end

    local state = perPacket[player]
    if not state then
        state = {
            tokens = config.burst or 1,
            lastRefill = now,
            lastAccepted = 0,
        }
        perPacket[player] = state
    end

    -- Cooldown mode
    if config.cooldown then
        if now - state.lastAccepted < config.cooldown then
            return false
        end
        state.lastAccepted = now
        return true
    end

    -- Token bucket mode
    local maxPerSecond = config.maxPerSecond
    if not maxPerSecond then
        return true
    end

    local elapsed = now - state.lastRefill
    state.lastRefill = now

    local burst = config.burst or 1
    state.tokens = math.min(burst, state.tokens + elapsed * maxPerSecond)

    if state.tokens >= 1 then
        state.tokens -= 1
        state.lastAccepted = now
        return true
    end

    return false
end

function Gate.checkGlobalRateLimit(player: Player): boolean
    local maxPS = _globalMaxPerSecond
    if not maxPS then
        return true
    end

    local now = clock()
    local state = _globalLimits[player]
    if not state then
        state = { tokens = maxPS, lastRefill = now, lastAccepted = 0 }
        _globalLimits[player] = state
    end

    local elapsed = now - state.lastRefill
    state.lastRefill = now
    state.tokens = math.min(maxPS, state.tokens + elapsed * maxPS)

    if state.tokens >= 1 then
        state.tokens -= 1
        return true
    end

    return false
end

function Gate.checkBandwidth(player: Player, bytes: number): boolean
    if not _bandwidthSoftLimit then
        return true
    end

    if bytes <= _bandwidthSoftLimit then
        local strikes = _bandwidthStrikes[player]
        if strikes and strikes > 0 then
            _bandwidthStrikes[player] = strikes - 1
        end
        return true
    end

    local current = _bandwidthStrikes[player]
    local strikes = (if current then current else 0) + 1
    _bandwidthStrikes[player] = strikes

    local maxStrikes = if _bandwidthMaxStrikes then _bandwidthMaxStrikes else 10
    return strikes <= maxStrikes
end

-- Configuration -------------------------------------------------------

function Gate.configureGlobalRateLimit(maxPerSecond: number): ()
    _globalMaxPerSecond = maxPerSecond
end

function Gate.configureBandwidth(softLimit: number, maxStrikes: number): ()
    _bandwidthSoftLimit = softLimit
    _bandwidthMaxStrikes = maxStrikes
end

function Gate.clearPlayer(player: Player): ()
    _globalLimits[player] = nil
    _bandwidthStrikes[player] = nil

    for _, perPacket in _rateLimits do
        perPacket[player] = nil
    end
end

return table.freeze(Gate)