RoaringPortable
Import and export in the RoaringBitmap portable serialization format.
rabosh's own bitmap layout is not this one, for the five reasons BitmapFormat gives — and the whole read path depends on those reasons, so the layout is not going to change. What this adds is an exchange: the portable format is what Lucene, Druid, Spark, CRoaring and pyroaring read and write, so a set of document ordinals can leave this engine and be understood, or arrive from one of them and be queried.
val exported = RoaringPortable.encode(bitmap) // hand to Lucene, Spark, pyroaring, …
val imported = RoaringPortable.decode(exported) // and back, as an ordinary BitmapThis is an exchange format, not the storage form, and everything about it follows from that. Nothing rabosh writes to disk is in this format, no sidecar reads it, and no id here is one of the permanent ones in .claude/rules/format-permanence.md — they belong to a specification this project does not own. A stream is always decoded into an ordinary heap Bitmap rather than read in place, which is the one thing BitmapView refuses to do for rabosh's own format: this layout's offset header is conditional, so finding a container is a branch rather than arithmetic, and that is precisely what the format was declined for as a storage form.
The two directions are not symmetric, and the asymmetry is honest rather than a gap. encode always writes the smallest encoding of each container, so it produces what RoaringBitmap produces after runOptimize(); a stream written without runOptimize() uses no run containers at all, and decode reads that perfectly well while encode will never write it. Re-exporting such a stream therefore shrinks it, and does not return the bytes that came in.