The specification is the single source of truth. It is never changed to accommodate
an implementation. Everything else is downstream of it.
The order Cyclone follows
RFC
Compiler
SDK
Application
Not this
SDK
Protocol
A protocol shaped by whatever its first library happened to do.
The test applied to every sentence of the specification:
could somebody writing an encoder in assembly on an 8-bit microcontroller follow it?
If not, that sentence describes an implementation, and it does not belong in the spec.
Definition
What is Cyclone?
Cyclone is a protocol.
Cyclone is not
× RPC
× Networking
× Runtime
× Serialization Library
Cyclone only specifies
Model
Bytes
Model
Transport, retransmission, ordering, matchmaking, encryption, compression, checksums —
all of it is built on top of Cyclone. The specification stops exactly where
the bytes are formed.
Philosophy
Ten principles, two layers.
Seven are conformance conditions. Three are choices the Reference Implementation made.
Mixing the two is how a protocol quietly turns into a framework.
Principles of the Specification
Violate one of these and it is not Cyclone.
01Specification First
The spec is the single source of truth. It is never changed to accommodate an implementation.
02Protocol over Framework
Cyclone defines how data appears on the wire — not an API, not a runtime.
03Minimal Wire Format
Send the data, never a description of the data. No field names, no type info, no tags, no padding.
04Deterministic by Design
One value, exactly one byte sequence. Two valid encodings of the same data must not exist.
05Language Neutral
No language is the reference language. Nothing in the format favours one type system.
06Implementation Independent
No mandated parser, macro, or generator. The spec describes bytes and stops there.
07Implementation Replaceable
The Reference Implementation is not the standard. Replace it entirely and the wire format does not move.
Principles of the Reference Implementation
Design choices, not conformance conditions. Another implementation may choose differently,
as long as the bytes do not move.
08User Owns the Model
No mandatory DTO layer, no base class — the RI reads the fields you mark and ignores the rest.
09Compile-time over Runtime
Anything decidable at build time is decided at build time.
10Zero Runtime Reflection
The wire format permits it; the spec does not require it. AOT and bare-metal targets stay viable.
The decision rule, in two questions. One: is this statement observable from the
byte stream? If not, it belongs to the Reference Implementation and must not enter the spec.
Two: does it violate one of the seven above? If yes, it is not part of Cyclone.
Trade-offs
What Cyclone deliberately does not do
Each of these is the direct cost of a guarantee above. None of them is on a roadmap.
✕
No schema evolution
Position is the only field identifier, so adding, removing or reordering a field is a
breaking change. v1 has no version negotiation and no field migration.
✕
No backward or forward compatibility
There is no unknown-field skipping and no defaults, so an old client cannot read a new
server's message. Both ends deploy together, or not at all.
✕
No metadata, not self-describing
The stream carries no field names, types or tags — the receiver must already know the
field order and types. The dangerous corollary: decoding against the wrong definition
usually raises no error at all. It produces wrong values, quietly.
✕
No mandated IDL
The source of truth is the Schema, but the spec does not dictate how you
write it down — no mandatory IDL, no mandatory .cyclone file. The Reference
Implementation expresses the Schema as annotations on your existing type, extracting only
three things: the type, its fields, and each field's Cyclone type. The trade: there is no
standalone schema document to hand a third party — both ends are on their own to derive
the same Schema.
✕
No Optional, no nullable, no defaults
Every field carries a value. There is no absent-field encoding in v1 — which is precisely
why there is no ambiguity about how absence would be written.
✕
No transport, no framing, no crypto
Cyclone produces a byte sequence and stops. Message boundaries, retransmission, ordering,
encryption and compression are all yours to build on top.
If any of these is unacceptable for your problem, a format with tag IDs and optional fields
will serve you better. That is the right call, not a failure of Cyclone.
Specification
The specification
Three documents. Each answers exactly one question.
Count for [1,2,3] is 3, not 12. An empty inner array is a count of 0 and no further bytes.
Enum
Always UInt32. Four bytes, regardless of how few members it declares.
enum PlayerState { Idle=0, Walk=1, Run=2, Jump=3 }
Idle → [00 00 00 00]
Walk → [01 00 00 00]
Jump → [03 00 00 00]
99 → invalid — outside the defined set
A protocol decision, not a storage optimization. There is nothing for two peers to negotiate.
A complete message, byte by byte
The example from RFC-0002 §15 — nested model, string, integers, bool — encodes to 31 bytes.
Hover a field to light up the bytes it owns, or hover a byte to find its owner.
Hex dump · 31 bytesPosition is the only identifier
000000002A000000060000004B6E696768740000
0000001028416666A2413333A3C06400000001
Total31 bytes · 0 bytes of metadata
Nothing separates the fields. No length prefix on the nested model, no delimiter between
position and health, no terminator at the end. The decoder knows which
field it is reading purely from how far the cursor has advanced — which is exactly why
reordering a field is a breaking change, and why renaming one is free.
RFC-0003
Test vectors
A test vector is a pair of (logical value, byte sequence). Never a pair of
(function call, return value) — a vector names no method, no language, no API.
Input→Expected Bytes
Primitive
ID
Input
Expected bytes
P-002
bool true
01
P-011
u8 255
FF
P-021
u16 300
2C 01
P-032
u32 0x12345678
78 56 34 12
P-035
i32 −2147483648
00 00 00 80
P-042
u64 2⁶⁴−1
FF FF FF FF FF FF FF FF
F-005
f32 0x4048F5C3 · 3.14
C3 F5 48 40
F-002
f32 0x80000000 · −0.0
00 00 00 80
F-031
f32 0x7FC00001 · NaN
01 00 C0 7F
S-004
String “中”
03 00 00 00 E4 B8 AD
S-005
String “Xin chào”
09 00 00 00 58 69 6E 20 63 68 C3 A0 6F
P-032 catches endianness: a big-endian implementation writes 12 34 56 78 and fails here.
F-031 catches NaN canonicalization — producing 00 00 C0 7F means user data was modified.
Model
ID
Input
Expected bytes
T-001
Item{ id=42, name="Sword" }
2A 00 00 00 05 00 00 00 53 77 6F 72 64
T-002
Empty{}
— 0 bytes
T-003
Vector3{1.5, 2.5, 3.5}
00 00 C0 3F 00 00 20 40 00 00 60 40
T-004
Body{ pos=Vector3{1.5,2.5,3.5}, hp=100 }
00 00 C0 3F 00 00 20 40 00 00 60 40 64 00 00 00
T-010
Mixed{ a=1, b=2, c=3 }
01 02 00 00 00 03
T-011
Order{ z=1, a=2 }
01 02
T-010 catches memory-layout copying (12 bytes) and compiler field reordering.
T-011 catches implementations that sort fields alphabetically “for stability” — 02 01 is a bug.
Array
ID
Input
Expected bytes
A-001
Array<u32> []
00 00 00 00
A-002
Array<u32> [1,2,3]
03 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00
A-003
Array<String> ["a","bc"]
02 00 00 00 01 00 00 00 61 02 00 00 00 62 63
A-004
Array<Array<u8>> [[1,2],[]]
02 00 00 00 02 00 00 00 01 02 00 00 00 00
A-005
Array<bool> [true,false,true]
03 00 00 00 01 00 01
A-002 catches the count-versus-byte-length bug: the correct count is 3, not 12.
Enum
ID
Input
Expected bytes
E-001
PlayerState::Idle
00 00 00 00
E-002
PlayerState::Walk
01 00 00 00
E-003
PlayerState::Jump
03 00 00 00
E-010
Encoded size of any enum
always 4 bytes
E-010 catches implementations that “optimize” a four-member enum down to one byte.
Invalid Stream
ID
Input bytes
Expected result
N-001
bool · 02
reject · not 00/01
N-010
u32 · 00 00 00
reject · unexpected EOF
N-020
String · 02 00 00 00 FF FE
reject · invalid UTF-8
N-021
String · 03 00 00 00 ED A0 80
reject · encoded surrogate
N-023
String · 64 00 00 00 41 6C 69 63 65
reject · length 100, 5 bytes remain
N-030
Array<u32> · 03 00 00 00 01 00 00 00
reject · count 3, one element
N-031
Array<u32> · FF FF FF FF
reject · before allocating
N-040
enum{0,1} · 63 00 00 00
reject · value 99 undefined
Rejection is MUST; the error name is only SHOULD. N-023 and N-031 check one thing:
is the length validated before allocating? Passing N-031 by crashing and catching the
exception does not count as a pass.
The full set — including 8-byte integers, f64, round-trip and cross-implementation
checks — is in
RFC-0003.
Conformance
Cyclone Compatible
An implementation is Cyclone Compatible if and only if it passes
100% of the test vectors.
✓ RFC-0002
✓ Test Vectors
✓ Deterministic Output
There is no “partially compatible”. There is no 98%. One failing vector means there exists
data on which this implementation and another disagree — silently, in production.
An implementation 10× slower that passes 100% of the vectors is Cyclone Compatible.
The fastest implementation in the world that fails one vector is not.