concinnity_core/blob/mod.rs
1//! The .cnb blob container format.
2//!
3//! Layout of a blob binary:
4//!
5//! `[ 4 bytes magic ][ 4 bytes validity token ][ 8 bytes meta_len ][ meta_bytes ][ payload_bytes ... ]`
6//!
7//! The header is fixed at 16 bytes. The container is generic over its metadata
8//! type through [`BlobKind`], which names the magic a file of that kind carries:
9//! [`BlobMeta`] is the cooked world, [`CacheMeta`] a segment of regenerable
10//! cache. The magic belongs to the type rather than to the encode and parse
11//! functions, so no file can be read as a kind it was not written for. The
12//! validity token is a value the writer stamps and the reader must match, with a
13//! meaning the kind owns: `BlobMeta` uses [`crate::SCHEMA_VERSION`], `CacheMeta`
14//! its own index layout version.
15//!
16//! `meta_len` is the byte length of the postcard-serialized metadata that
17//! follows: for `BlobMeta`, the component defs stream and the resource records
18//! stream. Everything after meta_len + meta_bytes is the raw payload section,
19//! addressed by the (blob_index, offset, length) fields inside each
20//! BlobAssetDef / ResourceRecord.
21//!
22//! Blob 0 is the primary blob. It always holds the full metadata and may also
23//! hold payload bytes for assets packed before the size ceiling. Overflow
24//! payloads spill into blobs 1, 2, ... as needed. All blobs share the same
25//! header format; only blob 0 carries non-empty metadata.
26//!
27//! This module owns the format contract and nothing else: the record schema,
28//! the header consts, the kind trait, and the pure bytes <-> metadata transforms.
29//! It performs no I/O and holds no residency policy, so it never learns where
30//! blob files live or which of them are resident. Callers own both:
31//! `concinnity_host::store` reads the state root's `data/` layout into `BlobData`,
32//! concinnity-cook writes what `encode_cnb` returns. Being I/O-free is what
33//! lets a no_std client runtime decode blobs with its own byte source.
34//!
35//! Nothing here needs to change when a new asset type is added.
36
37mod cache;
38mod encode;
39mod error;
40mod frame;
41mod kind;
42mod parse;
43mod schema;
44
45pub use cache::{CACHE_MAGIC, CACHE_SEGMENT_VERSION, CacheEntry, CacheEntryKind, CacheMeta};
46pub use encode::{encode_cnb, encode_cnb_prefix};
47pub use error::BlobError;
48pub use frame::{FrameError, decode_exact};
49pub use kind::BlobKind;
50pub use parse::{parse_cnb, parse_payload_section_start, payload_section};
51pub use schema::{
52 AssetKind, BlobAssetDef, BlobMeta, MeshBoundsRecord, PhysicsBudgetRecord, ResourceKind,
53 ResourceRecord, SceneGroup, WorldManifest,
54};
55
56// The identity and payload-address types the records carry, owned by the
57// components module.
58pub use crate::ecs::PayloadLocator;
59pub use crate::ecs::asset_id::AssetId;
60
61/// The four magic bytes a cooked-world `.cnb` blob starts with, the
62/// [`BlobKind`] magic of [`BlobMeta`]. A cache segment carries
63/// [`CACHE_MAGIC`] instead.
64pub const BLOB_MAGIC: [u8; 4] = *b"CNB\0";
65/// Fixed blob header size, the same for every kind: magic (4) + validity token
66/// (4) + `meta_len` (8).
67pub const HEADER_SIZE: usize = 16; // magic(4) + validity token(4) + meta_len(8)