ViPaq Protocol

This page describes the ViPaq wire format as produced and read by Binacle.Net v3.0.x. For what ViPaq is and why it exists, see ViPaq Protocol.

🚨 The format changed in v3.0.0. Strings produced by v2.1.1 and earlier do not decode here, and strings produced here do not decode there. There is no fallback reader. An old string is rejected with a format error rather than misread, so the failure is visible - but stored strings are worth regenerating after the upgrade.

πŸ“Œ Structure

A ViPaq blob is a two byte header followed by a body:

[ Header: 2 bytes ][ Body ]

🧾 Header

Two bytes, one job each. Byte 0 says how to read the body, byte 1 says how wide its integers are.

Byte 0 - form
Bits Field Values
7-6 Version 0 for this format. 1-3 are reserved.
5 Compressed 0 the body is raw, 1 the body is compressed
4 Layout 0 row-major, 1 columnar
3-0 reserved always 0, a decoder rejects anything else
Byte 1 - integer widths
Bits Field
7-6 BinDimensionsWidth
5-4 ItemDimensionsWidth
3-2 ItemCoordinatesWidth
1-0 reserved, always 0

Compressed and Layout describe that one blob, not the format. An encoder picks them per blob and a decoder obeys them, so two blobs from the same version can differ in both.

πŸ“¦ Body

The body carries the item count, the bin dimensions, then the items:

[ Item count: uint16 ][ Bin L, W, H ][ Items ]

Layout decides only how the items are arranged. The count and the bin dimensions are the same either way.

Row-major (Layout = 0)

Each item is written whole - its three dimensions, then its three coordinates - before the next item.

L W H X Y Z | L W H X Y Z | L W H X Y Z | ...
Columnar (Layout = 1)

Each field is written for every item before moving on to the next field. Six runs, each as long as the item count.

L L L ... | W W W ... | H H H ... | X X X ... | Y Y Y ... | Z Z Z ...

Columnar puts values of similar size next to each other, which usually compresses better. Row-major is easier to read in a hex dump. Neither is required - the header bit says which was used.

πŸ”’ Integer Widths

Each of the three sections - bin dimensions, item dimensions, item coordinates - is stored at one width, chosen independently:

Code Bytes per integer Largest value
0 1 255
1 2 65,535
2, 3 reserved a decoder rejects these

Widths are fixed per section, not per value: every item shares one dimensions width and one coordinates width. An encoder picks the smallest width that holds the largest value in the section.

The three sections are sized separately because real data needs them to be. A bin of 5000x2000x2000 holding items no larger than 200 gives 16 bit bin dimensions, 8 bit item dimensions and 16 bit coordinates.

πŸ—œοΈ Compression

The Compressed header bit records what the encoder did. It is not a hint to guess at.

The codec is raw DEFLATE (RFC 1951), with no zlib or gzip wrapper. The compressed stream is the DEFLATE bit stream and nothing else.

Compression is not applied by size. The encoder compresses the body, keeps whichever of the two is shorter, and sets the bit to say which it kept - so a blob is never made larger by compressing it. A decoder accepts both.

πŸ”€ Text Form

The stored and shared artifact is base64, and the format is optimized for it.

πŸ“ Limits

Field Rule
Bin dimensions L, W, H 1 to 65,535
Item dimensions L, W, H 1 to 65,535
Item coordinates X, Y, Z 0 to 65,535. Zero is valid - an item flush to the bin origin.
Item count up to 65,535

A value outside these ranges is an error when encoding. Nothing is clamped or widened silently.

πŸ§ͺ Example

A bin of 10x20x30 holding one item 1x2x3 at (4,5,6).

Every value fits in a byte, so all three sections use the 1 byte width, and the encoder wrote it row-major and uncompressed. Both header bytes are therefore 0x00:

00 00  01 00  0A 14 1E  01 02 03  04 05 06
^byte0 ^count ^bin      ^dims     ^coords
   ^byte1

πŸ“– Full Specification

The normative wire specification, including the decoding order, everything a decoder must reject, and further worked examples, lives with the source: