12. Open questions

1. ~~Name.~~ Resolved: Logb, extension `.logb`, magic `89 4C 4F 47 42 0D 0A 1A`, sync token `LOGBSYNC`.

The three-letter space this format would naturally have wanted is owned by the incumbents it means to replace: `BLF` is Vector's proprietary CAN log, `AXL` is ETAS's, and the near misses are worse than the hits — Cadence would suit a format built on periodic sampling, and it is an EDA giant. Every collision encountered came from a name that already meant something to somebody.

`.logb` sidesteps that space rather than competing in it. `.log` is the most widely recognised extension there is, and appending `b` for the binary variant is an established move: `.xlsb` to `.xlsx`, `jsonb` to JSON, `.stlb` to ASCII STL. The name is therefore not coined and not an acronym — it is a description a reader can parse on sight, which is the one thing a coined word cannot do. `.logb` itself is unclaimed; `.dlog` was not (Keysight's power-analyser data log, binary, in this exact domain) and `.blf` least of all.

Two costs are accepted knowingly. The `+b` convention implies a text twin, and there is no `.log` that this is the binary form of. And `logb` echoes Go's `math.Logb`, the binary-exponent function — no import conflict, since both are package-qualified, but it is the first hit when searching the name. Both were judged cheaper than opacity: this format is written by embedded loggers, so log is what its writers already call the thing.

The name, the extension, and the magic are three separate decisions that only look like one: `gzip` is `.gz` with magic `1f 8b` and no letters at all. For comparison, the MDF4 writer in `golib/formats/mdf` writes `.mf4`.

2. ~~Schema identity across files.~~ Resolved: split routing from identity (§6.6). `stream_id` stays a `u16` but is segment-scoped, so concatenation is byte-concatenation and collisions cannot occur; `stream_uuid` in the SCHEMA frame decides what merges. The "almost" is gone.

3. ~~Encryption / signing.~~ Resolved: split them. 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, not specified: frame type `0x50` (§3.3), which would cover the preceding segment. Per-segment is the only shape that works, because a detached signature is void after exactly the two operations this format promises to survive — truncation and concatenation.

4. ~~Sub-nanosecond time.~~ Resolved: `axis_exp` (§5.1). Time is an integer count of a declared tick, defaulting to nanoseconds and reaching femtoseconds without a second layout or a loss of exactness.

5. ~~Log-spaced implicit axis.~~ Resolved: added as `axis_mode = 2` (§5.3). An AC decade sweep is uniform in log space and now costs zero bytes per record like any other implicit axis. It was cheap to add before 0.1 and would have been a breaking change after.

6. ~~Is `run_id` per DATA frame the right granularity?~~ Resolved: keep the granularity, forbid the shuffle (§6.5). `run_id` stays per DATA frame — it is four bytes and no complexity for a logger that never uses it — but a stream's runs MUST be contiguous within a segment. The interleaving that per-frame granularity technically permitted is a file no reader wants; the multi-corner simulator that wants to interleave buffers per run, exactly as §10 tells MDF4's unsorted-record writers to.

7. ~~Compression dictionary sharing across segments.~~ Resolved: dropped. Not as a tradeoff against rule 3 — the feature dissolves under its own definition. Sharing a dictionary across segments asks a self-contained unit to depend on something outside itself, which is not a cost to weigh but a contradiction with what a segment is (§3); and a dictionary restated per segment is not shared, it is just per-segment compression state.

The rule-3-safe reading of the idea — a dictionary declared after each SYNC frame, letting the DATA frames within one segment share statistics — is coherent and still loses. The cheaper answer to "my frames do not share statistics" is to put more records in a frame, which is already the writer's lever and costs no new machinery. A dictionary needs a training pass over a corpus and needs to stay resident in RAM, neither of which the embedded logger this format is built around can afford; it is an analysis-tool feature in a logger-first format. And dictionaries pay off most on payloads small enough that the 12-byte frame header and the per-segment schema restatement already dominate — it optimises the wrong end.

A dictionary referenced by id rather than carried in the file is rejected outright and permanently: that is rule 4. A file whose bytes cannot be decoded without an artefact that must still exist in 2050 is not self-contained, whatever it does for the ratio.

Dropping it costs nothing, which is why it is dropped rather than left open. A dictionary needs a new `codec` id, and a v0.1 reader meeting one fails loudly and specifically (§8). Anyone with a benchmark showing it beats a bigger batch can add it in a later version without breaking a single existing file or reader. If nobody produces that benchmark, that is the answer.

8. ~~Is `record_bits` as `u64` overkill?~~ Resolved: yes. It is a `u32` (§6.1) — a 512 MiB record, which is still far past anything real.

9. ~~Bit numbering for unaligned big-endian fields.~~ Resolved: bit numbering follows the byte order (§6.2). `bit_offset` names the field's first bit — counting from the LSB of its byte for little-endian, from the MSB for big-endian — and the field runs upward from there. One rule, both orders, no alignment case, and the big-endian half is exactly DBC Motorola.

The draft's option (b), forbidding unaligned big-endian and making importers normalise, was wrong on its facts and is worth recording as such: it claimed normalisation "costs them a shift". It does not. A Motorola signal has no little-endian bit-slice equivalent at all unless it fits inside one byte — not 8 of 10 cases, but every multi-byte signal, including the byte-aligned 16-bit ones the draft already accepted. There was no shift to be had. Option (c), an explicit `bit_numbering` field, would have admitted the ambiguity as permanent and made every reader implement both numberings anyway.

The reason (a) is not the compromise it looks like: each byte order's fields are contiguous in that order's own numbering, so stating the rule per byte order removes the sawtooth rather than encoding it. What remains is about twenty lines per reader, which is the honest price. The alternative was not a cheaper format but one that cannot describe the signals inside a CAN frame — and then reading them would need a DBC parser, which is exactly the external schema dependency rule 4 exists to forbid.

It was settled with an executable oracle rather than with real DBC files, which turned out to be the stronger test: agreement with Vector's reference algorithm across 465,600 cases. The conformance vectors in §6.2 are what keep it settled.


MD5 signature: 0df01f174a010b5ba0afdc3877fc8a52