Schemas
A schema is a serialization node. Primitive nodes are ready to use, while constructors such as Struct, Array, Map, and Optional return composed nodes.
local Types = VoidSentryUltimate.Types
local Item = Types.Struct({
Count = Types.U16,
Id = Types.U32,
Note = Types.Optional(Types.String8),
})
local Inventory = Types.Array8(Item)
The schema is required for both serialization and deserialization:
local bytes = VoidSentryUltimate.Serialize(Inventory, items)
local copy = VoidSentryUltimate.Deserialize(Inventory, bytes)
No schema identifier is written to bytes. Changing a schema changes its wire format; old data must continue to use its old schema or be migrated explicitly.
Struct
Types.Struct(fields) serializes every named field with its node. At schema creation, the implementation collects field names and sorts them. Serialization and deserialization use that sorted key order, not table iteration order or the visual order in the schema literal.
local Transform = Types.Struct({
Rotation = Types.Vector,
Position = Types.Vector,
})
For this schema, Position is written before Rotation because the keys are sorted lexicographically. Both ends still need the same field names and field nodes. Do not treat Lua table construction order as a wire-format contract, and do not infer broader deterministic cross-process behavior beyond the implementation's sorted string keys.
Arrays
Array(node)writes a 2-byte unsigned element count, then the elements.Array8(node)writes a 1-byte count.Array24(node)writes a 3-byte count.ArrayFixed(node, length)writes no count prefix.
local RecentScores = Types.ArrayFixed(Types.U16, 3)
local bytes = VoidSentryUltimate.Serialize(RecentScores, { 10, 20, 30 })
For a fixed array, the caller must supply exactly the declared number of elements. The serializer iterates the provided array without validating its length, while the deserializer always reads the declared length. A mismatch shifts or truncates the surrounding wire layout.
Maps
Map(keyNode, valueNode)writes a 2-byte entry count.Map8(keyNode, valueNode)writes a 1-byte count.Map24(keyNode, valueNode)writes a 3-byte count.MapFixed(keyNode, valueNode, length)writes no count prefix.
local ScoresByUser = Types.Map(Types.U32, Types.U16)
Map entries are emitted in Luau table iteration order. The decoder reconstructs the same key/value pairs, but the encoded byte sequence is not promised to be stable across separately constructed maps or processes. If stable bytes matter for hashing or signing, serialize a canonically sorted array of entries instead.
For MapFixed, the caller must provide exactly the declared entry count. The serializer does not enforce it; the deserializer always reads that many entries.
Optional values
Optional(node) writes a 1-byte presence marker. A present value follows immediately after the marker.
local MaybeOwner = Types.Optional(Types.U32)
local absent = VoidSentryUltimate.Serialize(MaybeOwner, nil)
local present = VoidSentryUltimate.Serialize(MaybeOwner, 42)
Strings and buffers
String lengths are byte lengths because the implementation uses Luau's #string, including for UTF-8 text. They are not character or code-point counts.
StringandBufferuse a 2-byte length prefix.String8andBuffer8use a 1-byte length prefix.Buffer24uses a 3-byte length prefix.StringFixed(length)andBufferFixed(length)have no length prefix.
Fixed strings and buffers require exactly the configured number of bytes from the caller. The library does not validate that requirement.
Compatibility checklist
- Keep the same schema, including nested nodes, on every endpoint.
- Keep field names unchanged in structs; names determine sorted wire order.
- Version schemas when persisted data or network peers can outlive a deployment.
- Treat prefix limits as hard input constraints because the library does not check them.
- Do not deserialize untrusted or malformed data without an outer validation strategy.