Skip to main content

combs_mesh/blocks/
mod.rs

1//! The 10 typed block payloads that make up an [`crate::Emoji`].
2//!
3//! Each block type is a plain serde struct (one module per type). On disk /
4//! on the wire a block is identified by its [`BlockTag`] — a 3-byte ASCII
5//! tag in the binary directory, and a tag char + plane-15 sub-range in the
6//! Unicode encoding — so payloads stay self-describing without embedding
7//! type names in the JSON.
8
9mod api;
10mod character;
11mod emotion;
12mod encryption;
13mod function;
14mod image;
15mod lifecycle;
16mod orchestration;
17mod text;
18mod todo;
19
20pub use api::{ApiBlock, ApiEndpoint};
21pub use character::CharacterBlock;
22pub use emotion::{EmotionBlock, EmotionState};
23pub use encryption::{EncryptionAlgorithm, EncryptionBlock};
24pub use function::{FunctionBlock, FunctionDef, FunctionKind};
25pub use image::{ImageBlock, SpriteAtlas};
26pub use lifecycle::{LifecycleBlock, LifecycleState, LifecycleTransition};
27pub use orchestration::{DirectiveKind, OrchestrationBlock, OrchestrationDirective};
28pub use text::TextBlock;
29pub use todo::{TodoBlock, TodoItem, TodoStatus};
30
31use serde::{Deserialize, Serialize};
32
33use crate::error::Result;
34
35/// Identifies a block type. The index (0..10) doubles as the plane-15
36/// sub-range selector in the Unicode encoding, so the order is part of the
37/// wire format and must never change.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
39#[serde(rename_all = "lowercase")]
40pub enum BlockTag {
41    /// Free text / spec sheet.
42    Txt,
43    /// Sprite atlas (RGBA8 pixels).
44    Img,
45    /// Task list with statuses + dependencies.
46    Tdo,
47    /// Callable function definitions.
48    Fnc,
49    /// HTTP endpoint descriptions.
50    Api,
51    /// Agent lifecycle state machine.
52    Lfc,
53    /// Character traits + backstory.
54    Chr,
55    /// Emotion states with intensities.
56    Emo,
57    /// Encryption-at-rest directive.
58    Enc,
59    /// Orchestration directives for runtimes.
60    Orc,
61}
62
63impl BlockTag {
64    /// All tags, in wire order.
65    pub const ALL: [BlockTag; 10] = [
66        BlockTag::Txt,
67        BlockTag::Img,
68        BlockTag::Tdo,
69        BlockTag::Fnc,
70        BlockTag::Api,
71        BlockTag::Lfc,
72        BlockTag::Chr,
73        BlockTag::Emo,
74        BlockTag::Enc,
75        BlockTag::Orc,
76    ];
77
78    /// The 3-byte ASCII tag used in the binary directory.
79    #[must_use]
80    pub fn tag_bytes(self) -> [u8; 3] {
81        match self {
82            BlockTag::Txt => *b"txt",
83            BlockTag::Img => *b"img",
84            BlockTag::Tdo => *b"tdo",
85            BlockTag::Fnc => *b"fnc",
86            BlockTag::Api => *b"api",
87            BlockTag::Lfc => *b"lfc",
88            BlockTag::Chr => *b"chr",
89            BlockTag::Emo => *b"emo",
90            BlockTag::Enc => *b"enc",
91            BlockTag::Orc => *b"orc",
92        }
93    }
94
95    /// Wire index (0..10); also the plane-15 sub-range index.
96    #[must_use]
97    pub fn index(self) -> u8 {
98        match self {
99            BlockTag::Txt => 0,
100            BlockTag::Img => 1,
101            BlockTag::Tdo => 2,
102            BlockTag::Fnc => 3,
103            BlockTag::Api => 4,
104            BlockTag::Lfc => 5,
105            BlockTag::Chr => 6,
106            BlockTag::Emo => 7,
107            BlockTag::Enc => 8,
108            BlockTag::Orc => 9,
109        }
110    }
111
112    /// Inverse of [`BlockTag::index`].
113    #[must_use]
114    pub fn from_index(index: u8) -> Option<Self> {
115        BlockTag::ALL.iter().copied().find(|t| t.index() == index)
116    }
117
118    /// Inverse of [`BlockTag::tag_bytes`].
119    #[must_use]
120    pub fn from_tag_bytes(tag: &[u8; 3]) -> Option<Self> {
121        BlockTag::ALL.iter().copied().find(|t| &t.tag_bytes() == tag)
122    }
123}
124
125/// A typed block payload.
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127#[serde(tag = "type", rename_all = "lowercase")]
128pub enum Block {
129    /// See [`TextBlock`].
130    Txt(TextBlock),
131    /// See [`ImageBlock`].
132    Img(ImageBlock),
133    /// See [`TodoBlock`].
134    Tdo(TodoBlock),
135    /// See [`FunctionBlock`].
136    Fnc(FunctionBlock),
137    /// See [`ApiBlock`].
138    Api(ApiBlock),
139    /// See [`LifecycleBlock`].
140    Lfc(LifecycleBlock),
141    /// See [`CharacterBlock`].
142    Chr(CharacterBlock),
143    /// See [`EmotionBlock`].
144    Emo(EmotionBlock),
145    /// See [`EncryptionBlock`].
146    Enc(EncryptionBlock),
147    /// See [`OrchestrationBlock`].
148    Orc(OrchestrationBlock),
149}
150
151impl Block {
152    /// The tag identifying this block's type.
153    #[must_use]
154    pub fn tag(&self) -> BlockTag {
155        match self {
156            Block::Txt(_) => BlockTag::Txt,
157            Block::Img(_) => BlockTag::Img,
158            Block::Tdo(_) => BlockTag::Tdo,
159            Block::Fnc(_) => BlockTag::Fnc,
160            Block::Api(_) => BlockTag::Api,
161            Block::Lfc(_) => BlockTag::Lfc,
162            Block::Chr(_) => BlockTag::Chr,
163            Block::Emo(_) => BlockTag::Emo,
164            Block::Enc(_) => BlockTag::Enc,
165            Block::Orc(_) => BlockTag::Orc,
166        }
167    }
168
169    /// Serializes the *inner* block struct to JSON bytes (the type is
170    /// carried by the container, not embedded in the payload).
171    pub fn payload(&self) -> Result<Vec<u8>> {
172        let json = match self {
173            Block::Txt(b) => serde_json::to_vec(b),
174            Block::Img(b) => serde_json::to_vec(b),
175            Block::Tdo(b) => serde_json::to_vec(b),
176            Block::Fnc(b) => serde_json::to_vec(b),
177            Block::Api(b) => serde_json::to_vec(b),
178            Block::Lfc(b) => serde_json::to_vec(b),
179            Block::Chr(b) => serde_json::to_vec(b),
180            Block::Emo(b) => serde_json::to_vec(b),
181            Block::Enc(b) => serde_json::to_vec(b),
182            Block::Orc(b) => serde_json::to_vec(b),
183        }?;
184        Ok(json)
185    }
186
187    /// Inverse of [`Block::payload`]: parses a JSON payload for `tag`.
188    pub fn from_payload(tag: BlockTag, bytes: &[u8]) -> Result<Block> {
189        let block = match tag {
190            BlockTag::Txt => Block::Txt(serde_json::from_slice(bytes)?),
191            BlockTag::Img => Block::Img(serde_json::from_slice(bytes)?),
192            BlockTag::Tdo => Block::Tdo(serde_json::from_slice(bytes)?),
193            BlockTag::Fnc => Block::Fnc(serde_json::from_slice(bytes)?),
194            BlockTag::Api => Block::Api(serde_json::from_slice(bytes)?),
195            BlockTag::Lfc => Block::Lfc(serde_json::from_slice(bytes)?),
196            BlockTag::Chr => Block::Chr(serde_json::from_slice(bytes)?),
197            BlockTag::Emo => Block::Emo(serde_json::from_slice(bytes)?),
198            BlockTag::Enc => Block::Enc(serde_json::from_slice(bytes)?),
199            BlockTag::Orc => Block::Orc(serde_json::from_slice(bytes)?),
200        };
201        Ok(block)
202    }
203
204    /// If this is an `Enc` block, returns the encryption directive.
205    #[must_use]
206    pub fn as_encryption(&self) -> Option<&EncryptionBlock> {
207        match self {
208            Block::Enc(b) => Some(b),
209            _ => None,
210        }
211    }
212}
213
214/// Validates a block's internal consistency (sizes, ranges).
215pub(crate) fn validate_block(block: &Block) -> Result<()> {
216    if let Block::Img(img) = block {
217        img.atlas.validate()?;
218    }
219    Ok(())
220}