Concepts

This page is an informal tour of the model. The normative statements are in the specification; the section numbers below point at them.

Segments

A file is a sequence of segments, each a self-contained decode unit that restates every schema its data frames need. That repetition is the whole reason a reader handed the middle of a file can still name and scale a signal — schema is repeated, not stated once, which is the difference between Logb and every format whose header carries the meaning.

file    := file-header segment* [index-frame] [end-frame]
segment := sync-frame schema-frame+ run-frame* (meta | attach | data)*

A segment opens with a SYNC frame carrying a recognisable token, so resynchronisation is a scan for a byte pattern rather than a structural repair. How often a writer starts a new segment — every few megabytes, every few seconds — is writer policy, not a rule of the format. It is the knob that trades file size against how much data a cut costs you. See §3 and §4.

Frames

Everything in a segment is a frame: a type byte, a length, and a payload. A reader that does not recognise a frame type skips it by its length and loses nothing, which is what makes new frame types safe to add. The blast radius of the unknown is bounded and declared: an unknown frame type costs nothing, an unknown codec or filter costs that frame, an unknown axis mode costs that stream. See §3.3 and §4.2.

Two frames near the end of a file — the INDEX frame and the END frame — are optional by construction. They are written last, they only ever point backwards, and a file that lost power simply lacks them and still reads. See §9.

Streams and schemas

A SCHEMA frame declares one stream: the bit layout of its record, its axis, and its identity. Within a segment a stream is addressed by a small `stream_id`, which is segment-scoped — so concatenating two files is byte-concatenation and identifiers cannot collide. What decides whether two streams in different segments or different files are the same logical stream is a `stream_uuid` in the schema. Routing and identity are deliberately separate things. See §6.1 and §6.6.

Persist the UUID across file rollover and a set of files concatenates into one recording; generate a fresh one per instrument and two identical loggers do not silently merge.

The domain axis

The axis is general rather than time-specific. A transient sweeps time, an AC analysis sweeps frequency, and a sweep can be angle or distance — so the axis carries a kind and a unit rather than assuming seconds.

It comes in three modes:

  • Implicit — `base + istep`, derived from the record index, costing zero

bytes per record*.

  • Explicit — read from a field in the record, for data that does not arrive

on a grid.

  • Log-spaced — `base * ratio^i`, which is what an AC decade sweep actually

is, and which is therefore also free per record.

Time is an integer count of a declared tick rather than a float. The tick size is a power of ten (`axis_exp`), so nanoseconds are the default and femtoseconds are reachable without a second layout or a loss of exactness. See §5.

Fields and conversions

A record is a fixed-size bit layout. Each field declares its bit offset, bit width, type, byte order, unit, and a conversion from the raw stored value to the physical one: `Identity`, `Linear`, `Rational`, `Table`, `ValueToText` or


What is stored is what the sensor or the bus produced. The physical value is derived, which is why a read-modify-write round trip is byte-identical and why no conversion can quietly lose the original reading. The conversion set is deliberately closed — there is no embedded formula language, because a formula language in a data format is an interpreter every reader must ship. See §6.2, §6.3 and §7.

Byte order is per field, not per file. A single CAN frame routinely mixes Intel and Motorola signals, so anything coarser than per-field cannot describe real bus traffic. Bit numbering follows the byte order, which is the rule that makes the Motorola sawtooth disappear and reduces a DBC importer to a one-line offset transform. That story, with diagrams, is the CAN note.

Variable-length data is handled by record tails rather than by a separate block type that the record points into — because a pointer into another block is a forward reference by another name. See §6.4.

Records, batches and runs

A DATA frame carries a batch of records for one stream, back to back, packed to the schema's bit layout. Batching is what keeps the per-record overhead at zero: the frame states the stream, the starting axis value and the count once, and the records follow.

A run groups frames belonging to one acquisition — one sweep, one test, one simulation analysis — so a single file can hold a parameter sweep or a sequence of analyses without the reader having to infer boundaries from gaps in the data. See §6.5.

Metadata and attachments

META frames carry key/value pairs, scoped either to the file or to a stream — a VIN, an operator, a simulator's `Plotname`. ATTACH frames carry opaque payloads, for the configuration file or DBC that produced the recording. Both are frames like any other, so both are skippable and neither is required for the data to decode.

What it deliberately does not do

The specification names its omissions rather than leaving them as gaps (§10). Encryption is out of scope permanently — this is an open format, and confidentiality belongs to the layer that stores or moves the file, where it is solved once for every file type. Signing is reserved rather than specified, and would be per-segment, because a detached signature is void after exactly the two operations this format promises to survive: truncation and concatenation.


MD5 signature: 41e10dbd167e00cb1cdd3a7260fe3dfe