Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Elitriare/ByteNet-Max/llms.txt

Use this file to discover all available pages before exploring further.

ByteNet Max serializes all data into binary buffers before sending, which already makes it significantly more efficient than table-based networking libraries. On top of that foundation, three decisions in your schema design have the biggest impact on payload size and throughput: the data types you choose, how you structure multi-field payloads, and which reliability mode you use for high-frequency packets.
ByteNet Max inherits ByteNet’s core optimization: all data travels as a compact binary buffer rather than a Lua table. This reduces memory allocation and speeds up serialization compared to libraries that serialize to JSON or use Instance references for transport.

1. Choose explicit data types

Prefer specific numeric types (uint8, float32, etc.) over auto whenever the shape of your data is known at authoring time.
ByteNetMax.auto is a flexible type that inspects the runtime value and picks the smallest fitting serializer automatically. The trade-off is a 1-byte type marker written before the value so the deserializer knows which codec to use. Explicit types carry no such overhead — only the bytes for the value itself are written. From src/dataTypes/auto.luau:
write = function(value: unknown)
    local codecData = getCodecFromValue(value)
    writeU8(codecData.id)   -- 1-byte type marker
    codecData.write(value)  -- value bytes
end,
Comparison — sending a single uint8 value:
-- With auto: 1 byte (type marker) + 1 byte (value) = 2 bytes per send
local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("Demo", function()
    return {
        packets = {
            AutoExample = ByteNetMax.definePacket({
                value = ByteNetMax.auto,
            }),
        },
    }
end)
-- With explicit type: 1 byte (value only) = 1 byte per send
local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("Demo", function()
    return {
        packets = {
            ExplicitExample = ByteNetMax.definePacket({
                value = ByteNetMax.uint8,
            }),
        },
    }
end)
auto is well-suited for prototyping and mixed-type payloads. Once a schema is stable, replacing auto with the matching explicit type removes the overhead at no cost to the API.

Data type byte sizes

TypeBytes
bool1
uint81
uint162
uint324
int81
int162
int324
float324
float648
vec28 (2× float32)
vec312 (3× float32)
color33
Use this table to reason about total packet size. A position update using three float32 fields costs 12 bytes; using three float64 fields doubles that to 24.

2. Use structs for fixed-shape data

When a packet always has the same set of fields, use ByteNetMax.struct rather than ByteNetMax.map. Struct serializes fields in a fixed, deterministic order with no per-field key overhead.
map must encode both keys and values so the deserializer can reconstruct the table. struct bakes the key order into the schema itself, so only the values travel over the wire.
-- Less efficient: map encodes keys at runtime
local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("Movement", function()
    return {
        packets = {
            UpdatePosition = ByteNetMax.definePacket({
                value = ByteNetMax.map(ByteNetMax.string, ByteNetMax.float32),
            }),
        },
    }
end)
-- More efficient: struct encodes only values, keys are implicit
local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("Movement", function()
    return {
        packets = {
            UpdatePosition = ByteNetMax.definePacket({
                value = ByteNetMax.struct({
                    x = ByteNetMax.float32,
                    y = ByteNetMax.float32,
                    z = ByteNetMax.float32,
                }),
            }),
        },
    }
end)
The struct version sends exactly 12 bytes (3× float32) per position update. The map version adds length-prefixed string keys for each field on top of that.

3. Choose unreliable for high-frequency data

Set reliabilityType = "unreliable" on any packet that is sent many times per second and where a missed delivery is tolerable.
Reliable transport acknowledges every packet and retransmits losses. For data like position or rotation that is superseded by the next send anyway, this retransmission is wasted work that adds latency and consumes bandwidth.
local ByteNetMax = require(path.to.ByteNetMax)

return ByteNetMax.defineNamespace("Movement", function()
    return {
        packets = {
            UpdatePosition = ByteNetMax.definePacket({
                value = ByteNetMax.struct({
                    x = ByteNetMax.float32,
                    y = ByteNetMax.float32,
                    z = ByteNetMax.float32,
                }),
                reliabilityType = "unreliable",
            }),
        },
    }
end)
Good candidates for unreliable delivery: position, rotation, animation state, health bar display values. Poor candidates: purchases, ability activations, any event that must not be missed.

Server-side rate limiting

ByteNet Max includes a built-in token-bucket rate limiter on the server. Each connected client is granted a budget of 8,192 bytes per second by default. Packets from a client that exceed this budget within a second are silently dropped and a warning is logged to the output. You can adjust the limit by setting the MAX_BUFFER_SIZE attribute on the ByteNet Max ModuleScript instance (in the Roblox Studio Properties panel). Raising it allows higher-throughput clients; lowering it provides tighter protection against flooding.
Keep your per-frame packet budget well under the rate limit. If you are sending position updates at 60 Hz with a 12-byte vec3 payload (plus 1 byte for the packet ID), that is 13 × 60 = 780 bytes per second — well within the 8,192-byte default.

Build docs developers (and LLMs) love