diff --git a/crates/ogar-fma-skeleton/src/guid.rs b/crates/ogar-fma-skeleton/src/guid.rs index f4df1a4..9cdfa42 100644 --- a/crates/ogar-fma-skeleton/src/guid.rs +++ b/crates/ogar-fma-skeleton/src/guid.rs @@ -177,6 +177,56 @@ impl Guid { pub fn same_family(&self, other: &Self) -> bool { self.classid() == other.classid() && self.family_node() == other.family_node() } + + /// Build a **Located-mode** GUID (the bone case): `classid` · 3D-CRS + /// `HEEL/HIP` from the body position `(x, y, z)` · LEAF `familyNode:identity`. + /// HHTL is spatial — the address preserves location (Cesium / ArcGIS). + #[must_use] + pub fn located(classid: Tier, x: u8, y: u8, z: u8, leaf: LeafTile) -> Self { + let (heel, hip) = located_3d(x, y, z); + let mut g = Self::ZERO; + g.set_tier(TIER_CLASSID, classid); + g.set_tier(TIER_HEEL, heel); + g.set_tier(TIER_HIP, hip); + g.set_tier( + TIER_LEAF, + Tier { + container: leaf.family_node, + member: leaf.identity, + }, + ); + g + } + + /// Build a **Cascade-mode** GUID (the soft-tissue case): `classid` · a named + /// ontology HHTL path (`heel` · `hip` · `twig`) · LEAF `familyNode:identity`. + /// HHTL is the self-speaking classification path — e.g. the papillary + /// muscle's `ANAT0001-CARD-HERT-LVNT-PAPMUS-…`. No spatial address; the + /// node is classified, then a splat projects onto it. + #[must_use] + pub fn ontological(classid: Tier, heel: Tier, hip: Tier, twig: Tier, leaf: LeafTile) -> Self { + let mut g = Self::ZERO; + g.set_tier(TIER_CLASSID, classid); + g.set_tier(TIER_HEEL, heel); + g.set_tier(TIER_HIP, hip); + g.set_tier(TIER_TWIG, twig); + g.set_tier( + TIER_LEAF, + Tier { + container: leaf.family_node, + member: leaf.identity, + }, + ); + g + } + + /// The quantised `(x, y, z)` body position decoded from the Located HEEL/HIP + /// tiers (inverse of [`located`](Self::located)). Meaningful only for + /// Located-mode GUIDs. + #[must_use] + pub fn position(&self) -> (u8, u8, u8) { + position_3d(self.heel(), self.tier(TIER_HIP)) + } } impl core::fmt::Debug for Guid { @@ -223,6 +273,39 @@ pub fn located_heel_hip(x: u8, y: u8, z: u8) -> (Tier, Tier) { (spatial_tier(x, y), spatial_tier(z, 0)) } +/// Encode a 3-axis body position as a **true 3D-octree** `(HEEL, HIP)` pair — +/// the ArcGIS / Cesium-grade Located CRS (supersedes the coronal-tile + +/// depth-slice [`located_heel_hip`] by using HIP's reserved odd bits). +/// +/// The 24-bit 3D Morton code ([`morton3_encode`](crate::morton::morton3_encode)) +/// is laid big-endian across three bytes — `HEEL.container` (coarsest octant), +/// `HEEL.member`, `HIP.container` — so prefix containment over `HEEL ++ +/// HIP.container` is 3D spatial containment (the octree the GIS stack pages by +/// LOD). `HIP.member` (the 4th byte) is reserved for finer (>256³) refinement. +#[must_use] +pub fn located_3d(x: u8, y: u8, z: u8) -> (Tier, Tier) { + let code = crate::morton::morton3_encode(x, y, z); // 24-bit + ( + Tier { + container: ((code >> 16) & 0xFF) as u8, + member: ((code >> 8) & 0xFF) as u8, + }, + Tier { + container: (code & 0xFF) as u8, + member: 0, // reserved: finer octree levels (>256³) + }, + ) +} + +/// Recover the quantised `(x, y, z)` body position from a Located 3D `(HEEL, +/// HIP)` pair built by [`located_3d`] — the inverse, for spatial queries. +#[must_use] +pub fn position_3d(heel: Tier, hip: Tier) -> (u8, u8, u8) { + let code = + ((heel.container as u32) << 16) | ((heel.member as u32) << 8) | (hip.container as u32); + crate::morton::morton3_decode(code) +} + /// The **mode** of an HHTL tier — the operator's "heel" distinction /// (2026-06-23). /// diff --git a/crates/ogar-fma-skeleton/src/lib.rs b/crates/ogar-fma-skeleton/src/lib.rs index ba0b5c7..6cae464 100644 --- a/crates/ogar-fma-skeleton/src/lib.rs +++ b/crates/ogar-fma-skeleton/src/lib.rs @@ -67,12 +67,14 @@ pub mod guid; pub mod morton; +pub mod projection; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; pub use guid::{Guid, HhtlMode, LeafTile, Tier}; pub use morton::FamilyAddress; +pub use projection::{Modality, ModalityProjection, ProjectionSample, SampleKind}; /// Re-export of the `bone` concept id from the canonical OGAR codebook /// (`ogar_vocab::class_ids::BONE` = `0x0A03`). It is the classid tier of every @@ -257,6 +259,13 @@ impl Bone { self.guid.identity() } + /// The quantised `(x, y, z)` body position decoded from the Located 3D + /// HEEL/HIP tiers — `+X` anatomical-left, `+Y` superior, `+Z` anterior. + #[must_use] + pub fn position(&self) -> (u8, u8, u8) { + self.guid.position() + } + /// The full canonical 16-byte node key (the [`Guid`] bytes). #[must_use] pub const fn node_key(&self) -> [u8; morton::KEY_BYTES] { @@ -353,21 +362,14 @@ impl Skeleton { let family_node = (region_code(spec.region) << 4) | gi; let p = spec.rest_pose.translation; - let (heel, hip) = guid::located_heel_hip( + let g = Guid::located( + classid, q(p[0], min[0], max[0]), q(p[1], min[1], max[1]), q(p[2], min[2], max[2]), - ); - - let mut g = Guid::ZERO; - g.set_tier(guid::TIER_CLASSID, classid); - g.set_tier(guid::TIER_HEEL, heel); - g.set_tier(guid::TIER_HIP, hip); - g.set_tier( - guid::TIER_LEAF, - Tier { - container: family_node, - member: identity_of[pos], + LeafTile { + family_node, + identity: identity_of[pos], }, ); @@ -866,30 +868,72 @@ mod tests { } #[test] - fn heel_encodes_laterality() { - // Located precondition: left/right twins differ in HEEL (the coronal X - // axis = laterality), so an X-ray / ultrasound sweep can tell sides apart - // from the spatial tier — while remaining the SAME categorical family. + fn crs_encodes_laterality_and_depth() { + // Located 3D CRS: the decoded position recovers anatomical axes. + // Laterality (X): left vs right femur. Depth (Z): sternum (anterior) vs + // a thoracic vertebra (posterior). Decoding through the 3D-octree HEEL/HIP + // is the inverse the GIS / projection layer uses. let s = skel(); let left_femur = s.get(1021).unwrap(); let right_femur = s.get(2021).unwrap(); - assert_ne!(left_femur.heel(), right_femur.heel(), "laterality in HEEL"); + let (lx, _, _) = left_femur.position(); + let (rx, _, _) = right_femur.position(); + assert_ne!(lx, rx, "laterality recovered from the 3D CRS"); + assert_ne!(left_femur.heel(), right_femur.heel(), "and visible in HEEL"); assert!( left_femur.guid().same_family(&right_femur.guid()), - "both are lower-limb-long-bone family", + "yet the SAME categorical family (spatial is orthogonal)", ); assert_ne!(left_femur.identity(), right_femur.identity()); + + let (_, _, sternum_z) = by_name(&s, "sternum").position(); + let (_, _, vertebra_z) = by_name(&s, "vertebra T6").position(); + assert!( + sternum_z > vertebra_z, + "sternum is anterior (+Z) of the vertebra: {sternum_z} vs {vertebra_z}", + ); } #[test] - fn hip_encodes_depth() { - // HIP = the anterior-posterior depth slice: the sternum (anterior) and a - // thoracic vertebra (posterior) sit in different depth tiers. - let s = skel(); - let sternum = by_name(&s, "sternum"); - let vertebra = by_name(&s, "vertebra T6"); - let hip = |b: &Bone| b.guid().tier(guid::TIER_HIP); - assert_ne!(hip(sternum), hip(vertebra), "anterior vs posterior depth"); + fn cascade_ontology_guid_builds_the_self_speaking_path() { + // The soft-tissue (Cascade) mode: the papillary muscle's + // ANAT0001-CARD-HERT-LVNT-PAPMUS-, no spatial address. Shares the + // tier algebra with the Located bones; only the HHTL reading differs. + let classid = Tier { + container: 0x0A, + member: 0x10, + }; // Anatomy : muscle (illustrative) + let g = Guid::ontological( + classid, + guid::ontology_tier(0x01, 0x02), // Cardiovascular : Heart + guid::ontology_tier(0x02, 0x03), // Heart : LeftVentricle + guid::ontology_tier(0x03, 0x07), // LeftVentricle : PapillaryMuscleFamily + LeafTile { + family_node: 0x07, + identity: 0x1D, + }, + ); + assert_eq!(g.classid(), 0x0A10); + assert_eq!( + g.leaf(), + LeafTile { + family_node: 0x07, + identity: 0x1D + } + ); + // A sibling papillary muscle: same family, identity attached. + let sibling = Guid::ontological( + classid, + guid::ontology_tier(0x01, 0x02), + guid::ontology_tier(0x02, 0x03), + guid::ontology_tier(0x03, 0x07), + LeafTile { + family_node: 0x07, + identity: 0x1E, + }, + ); + assert!(g.same_family(&sibling)); + assert_ne!(g.identity(), sibling.identity()); } #[test] diff --git a/crates/ogar-fma-skeleton/src/morton.rs b/crates/ogar-fma-skeleton/src/morton.rs index a27b48d..70c9d9f 100644 --- a/crates/ogar-fma-skeleton/src/morton.rs +++ b/crates/ogar-fma-skeleton/src/morton.rs @@ -355,6 +355,56 @@ fn refine( } } +// ── 3D octree Morton (the Located CRS — true x:y:z, ArcGIS / Cesium-grade) ── + +/// Spread an 8-bit value so each bit lands in every 3rd position (`bit k → 3k`), +/// the building block of a 24-bit 3D Morton (octree) code. +#[must_use] +pub const fn spread3(v: u8) -> u32 { + let mut out = 0u32; + let mut k = 0; + while k < 8 { + out |= ((v as u32 >> k) & 1) << (3 * k); + k += 1; + } + out +} + +/// Gather every 3rd bit (`code bit 3k → bit k`) back into an 8-bit value — +/// inverse of [`spread3`]. +#[must_use] +pub const fn gather3(code: u32) -> u8 { + let mut out = 0u8; + let mut k = 0; + while k < 8 { + out |= (((code >> (3 * k)) & 1) as u8) << k; + k += 1; + } + out +} + +/// Encode `(x, y, z)` (8 bits each) into a **24-bit 3D Morton (octree) code**: +/// `x` in bit positions `0,3,6,…`, `y` in `1,4,7,…`, `z` in `2,5,8,…`. The top +/// three bits `(23,22,21) = (z7, y7, x7)` are the coarsest octant, so a shared +/// high-byte prefix is 3D spatial containment. +/// +/// ``` +/// use ogar_fma_skeleton::morton::{morton3_encode, morton3_decode}; +/// let c = morton3_encode(0xC3, 0x55, 0xA0); +/// assert_eq!(morton3_decode(c), (0xC3, 0x55, 0xA0)); +/// ``` +#[must_use] +pub const fn morton3_encode(x: u8, y: u8, z: u8) -> u32 { + spread3(x) | (spread3(y) << 1) | (spread3(z) << 2) +} + +/// Decode a 24-bit 3D Morton code back into `(x, y, z)`. Inverse of +/// [`morton3_encode`]. +#[must_use] +pub const fn morton3_decode(code: u32) -> (u8, u8, u8) { + (gather3(code), gather3(code >> 1), gather3(code >> 2)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ogar-fma-skeleton/src/projection.rs b/crates/ogar-fma-skeleton/src/projection.rs new file mode 100644 index 0000000..466d73d --- /dev/null +++ b/crates/ogar-fma-skeleton/src/projection.rs @@ -0,0 +1,137 @@ +//! `projection.rs` — the **modality projection contract**. +//! +//! The seam where "address the heart muscle with ArcGIS, measure the sinus with +//! ViT" lands: ViT / X-ray / ultrasound × Doppler each implement one trait — +//! **register** the acquisition to the bone frame (bones are the rigid +//! fiducials), then **project** measurements addressed by the canonical +//! [`Guid`] onto the anatomy. The address is the join key that lets +//! heterogeneous sensors write to the same node (no information destroyed; +//! longitudinal tracking by stable id). +//! +//! This module is a **contract** (trait + types) plus one worked example +//! ([`XrayBoneFiducial`]) proving the shape compiles. Real ViT / ultrasound +//! engines live downstream (ndarray / the splat layer); they implement +//! [`ModalityProjection`] against this surface. + +use crate::{Guid, RigidTransform, Skeleton}; + +/// An imaging / sensing modality projecting onto the FMA-addressed anatomy. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum Modality { + /// Projectional radiography — a Radon line-integral; *shows* bone, the + /// natural skeletal fiducial. + Xray, + /// B-mode ultrasound — PSF-convolved reflectivity along the beam; + /// registers off bone surfaces (specular returns). + Ultrasound, + /// Doppler — a velocity field; view-dependent by physics (the splat SH term). + Doppler, + /// Vision Transformer — learned 2D features lifted into 3D, anchored at + /// bone landmarks. + Vit, +} + +/// The physical quantity a [`ProjectionSample`] carries. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum SampleKind { + /// X-ray attenuation along the ray. + Attenuation, + /// Ultrasound echo amplitude (structure). + Structure, + /// Doppler flow velocity. + Flow, + /// A learned ViT feature score. + Feature, +} + +/// One measurement, **addressed by the canonical [`Guid`]** of the anatomy it +/// lands on. The address is what lets ArcGIS spatial queries and ViT feature +/// measurements meet on the same node. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ProjectionSample { + /// The anatomy node this sample lands on. + pub at: Guid, + /// What was measured. + pub kind: SampleKind, + /// The measured value (modality-specific units). + pub value: f32, +} + +/// The contract every modality implements to project onto FMA-addressed +/// anatomy. Two steps that mirror the physics: **register** (align the modality +/// frame to the body via the rigid bone fiducials), then **project** (emit +/// Guid-addressed samples). +pub trait ModalityProjection { + /// Which modality this is. + fn modality(&self) -> Modality; + + /// Register the acquisition to the skeleton, returning the rigid pose that + /// aligns the modality frame to the body frame. Bones are the fiducials — + /// the clamped convergence anchors — which is why they must be stable. + fn register(&self, skeleton: &Skeleton) -> RigidTransform; + + /// Project the registered acquisition into [`Guid`]-addressed samples. Each + /// sample writes to the anatomy node at its address (the value side; the + /// key is never decoded to address it). + fn project(&self, pose: &RigidTransform) -> Vec; +} + +/// Worked example: an X-ray that registers to the skeleton by its bone +/// fiducials and emits an attenuation sample at every bone's [`Guid`]. +/// +/// A **scaffold** demonstrating the seam — it returns the identity pose (X-ray +/// *shows* bone, so the skeleton already lives in its frame) and a constant +/// attenuation per bone. A real engine replaces both with measured values. +pub struct XrayBoneFiducial { + /// Per-bone attenuation to emit (illustrative). + pub attenuation: f32, +} + +impl ModalityProjection for XrayBoneFiducial { + fn modality(&self) -> Modality { + Modality::Xray + } + + fn register(&self, _skeleton: &Skeleton) -> RigidTransform { + // X-ray shows bone: the skeleton's frame IS the radiograph's frame. + RigidTransform::at([0.0, 0.0, 0.0]) + } + + fn project(&self, _pose: &RigidTransform) -> Vec { + Skeleton::resolve() + .clamped_anchors() + .map(|bone| ProjectionSample { + at: bone.guid(), + kind: SampleKind::Attenuation, + value: self.attenuation, + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn xray_projects_attenuation_addressed_by_bone_guid() { + let xray = XrayBoneFiducial { attenuation: 0.8 }; + assert_eq!(xray.modality(), Modality::Xray); + + let skeleton = Skeleton::resolve(); + let pose = xray.register(&skeleton); + assert!(pose.is_valid()); + + let samples = xray.project(&pose); + assert!(!samples.is_empty()); + // Every sample is addressed by a bone's classid-routed Guid. + for s in &samples { + assert_eq!(s.kind, SampleKind::Attenuation); + assert_eq!(s.at.classid(), 0x0A03, "addressed by the bone concept"); + } + // One sample per clamped anchor — the fiducial frame. + assert_eq!(samples.len(), skeleton.clamped_anchors().count()); + } +} diff --git a/docs/DISCOVERY-MAP.md b/docs/DISCOVERY-MAP.md index 88eb94a..09fb8bb 100644 --- a/docs/DISCOVERY-MAP.md +++ b/docs/DISCOVERY-MAP.md @@ -128,6 +128,8 @@ two halves of a cell. ADR‑026 names the cascade that ties them. | D‑IMMAT | the cascade is a **coordinate transform, not a stored grid** (`(lat,lon)→quadkey` cheap) | G | EPIPHANY | SYN §7.5 | D‑CASCADE | | D‑NEIGH | neighbor‑XOR walk + parent‑prefix = structured‑sparse stencil (block‑banded, not sparse GEMM) | H | EPIPHANY | SYN §6 | D‑MORTON, `[per rt]` blasgraph | | D‑FMA‑SKELETON | FMA skeleton = the **clamped convergence anchor**: ~206 bones as immutable **16×8‑bit Morton‑tile family‑node** addresses derived from rest‑pose centroids ⟹ prefix = partonomy = spatial containment (D‑BOTHCASC realized); bones are non‑negotiable Dirichlet anchors, the cross‑modal frame ViT / X‑ray / ultrasound × Doppler register onto. Address structure CODED; splat‑fit convergence CONJECTURE. | G (structure) / H (convergence) | CODED | `crates/ogar-fma-skeleton` + `docs/FMA-SKELETON-CONVERGENCE-ANCHOR.md` | D‑MORTON; `SPLAT-NATIVE-CUSTOMER.md` §6 | +| D‑GUID‑TIER | The brutal uniform **`[256:256]` `[container:member]`** GUID (scale‑free: galaxy:planet … residue:atom); HhtlMode **Located** (Cesium 3D‑octree CRS) vs **Cascade** (self‑speaking ontology path); leaf = familyNode:identity. 3D‑octree HEEL/HIP (`morton3`), the ModalityProjection contract (ViT/X‑ray/US×Doppler `register`+`project` by Guid). 12+4 EdgeBlock removed (superseded by family nodes). | G | CODED | `crates/ogar-fma-skeleton/{guid,morton,projection}.rs` | D‑FMA‑SKELETON, D‑MORTON | +| D‑NODEGUID‑AUDIT | Group‑by‑group audit of the FMA tier `Guid` vs lance‑graph `NodeGuid` (canon rule: wrappers audited against OGAR). **Caught F‑1: lance‑graph `CLASSID_FMA=0x0901` aliases OGAR `patient`** — this session's `0x0A` Anatomy domain resolves it. Also: classid/family/identity width + endianness + EdgeBlock divergences, each with a reconciliation. | G | CODED | `docs/NODEGUID-CANON-AUDIT.md` | D‑GUID‑TIER; lance‑graph `canonical_node.rs` | ### 2.2 Selection & bounds diff --git a/docs/NODEGUID-CANON-AUDIT.md b/docs/NODEGUID-CANON-AUDIT.md new file mode 100644 index 0000000..d3aa93d --- /dev/null +++ b/docs/NODEGUID-CANON-AUDIT.md @@ -0,0 +1,128 @@ +# NodeGuid ↔ FMA-skeleton `Guid` — Canon Audit + +> **Status:** AUDIT (2026-06-23). Per the canon rule — *"Wrappers (lance-graph +> `NodeGuid`, #480) are audited against THIS canon group-by-group, never the +> reverse"* (OGAR `CLAUDE.md` P0) — this reconciles the `ogar-fma-skeleton` +> [`Guid`] against lance-graph `lance-graph-contract::canonical_node::NodeGuid` +> and the locked CANON (lance-graph `CLAUDE.md` § "Minimal SoA node", 2026-06-13). +> **No lance-graph code is changed here** — divergences are findings + recommended +> reconciliations for the operator. + +## 1. Byte layout, group by group + +| Group | Canon `NodeGuid` (16 B, **LE**) | FMA `Guid` (16 B, tier `[container:member]`) | Verdict | +|---|---|---|---| +| classid | `0..4` — **u32**, 4 bytes | tier 0 `0..2` — **2 bytes** (concept only) | **DIVERGE (width)** | +| HEEL | `4..6` — u16 | tier 1 `2..4` | align (offset, width) | +| HIP | `6..8` — u16 | tier 2 `4..6` | align | +| TWIG | `8..10` — u16 | tier 3 `6..8` | align | +| family | `10..13` — **u24**, 3 bytes | tier 4 hi `8..9` — **1 byte** (`familyNode`) | **DIVERGE (width)** | +| identity | `13..16` — **u24**, 3 bytes | tier 4 lo `9..10` — **1 byte** (`identity`) | **DIVERGE (width)** | +| (reserved) | — | `10..16` — 6 bytes reserved | FMA-only headroom | +| byte order | **little-endian throughout** | **container-first per tier (big-endian)** | **DIVERGE (endianness)** | +| edge block | separate `edges(16)` = **12 + 4** | **none** — relations via family addressing | **DIVERGE (superseded)** | + +The HHTL middle (HEEL/HIP/TWIG, 3×u16) is **byte-for-byte aligned**. The +divergences are at the two ends (classid, family/identity), the endianness, and +the edge block. + +## 2. Findings + +### F-1 — `[G]` **classid collision: lance-graph `CLASSID_FMA = 0x0901` aliases OGAR `patient`.** + +`canonical_node.rs` pins `NodeGuid::CLASSID_FMA = 0x0000_0901` (comment: +*"anatomy concept 0x01 in the Health domain 0x09"*, realigned 2026-06-20 +ISS-CLASSID-OGAR-DRIFT). **But OGAR's codebook has `patient = 0x0901`** +(`ogar_vocab::class_ids::PATIENT`). So lance-graph's FMA classid **collides with +OGAR's `patient`** — a concrete cross-repo drift that predates this work. + +This session's OGAR mint **resolves it**: anatomy got its own domain +**`0x0A` Anatomy** (`anatomical_structure 0x0A01` / `skeleton 0x0A02` / +`bone 0x0A03` / `joint 0x0A04`), deliberately *not* Health `0x09` (reference ≠ +PHI; keeps medcare's fail-closed Health-RBAC set at 7). **Recommended +reconciliation:** retarget `NodeGuid::CLASSID_FMA` from `0x0901` to the Anatomy +domain — `0x0A01` for the FMA root (`anatomical_structure`), or `0x0A03` for the +bone anchor specifically. The OGAR codebook is canon; the wrapper realigns. + +### F-2 — `[G]` **classid width: FMA `Guid` carries 2 bytes, canon carries 4.** + +The canon classid is `u32 = [app:u16][concept:u16]` (`app_of` / `concept_of`, +`ogar-vocab/src/app.rs`). The FMA `Guid` tier 0 holds only the **concept** half +(`0x0A03`), i.e. the `app = 0x0000` (core/default-render) projection. For a +non-zero app render prefix it must widen to 4 bytes (tiers 0–1), shifting HEEL to +tier 2. **Reconciliation:** treat the FMA `Guid` as the *core-render* (app=0) +special case; a `Guid::with_app(prefix)` that occupies tiers 0–1 aligns it to the +full 4-byte canon classid. Bones are app-agnostic reference, so app=0 is correct +for them today. + +### F-3 — `[H]` **family/identity width: `256:256` (1 B each) vs `u24:u24` (3 B each).** + +The FMA leaf is the **brutal `[256:256]`** the operator landed this session +(256 families × 256 instances per spatial cell). The canon tail is +`family(u24) + identity(u24)` = 16.7M each. For the skeleton this is ample +(≤206 bones, a handful per cell). **This is the open economy decision** (the +`64K⁵ = 256¹⁰` discussion): the FMA `Guid` is the *byte-direct* projection; +high-cardinality domains (per-instance patient rows, per-Gaussian splat ids) need +the canon's 24-bit tail. **Reconciliation:** make the family:identity split a +**per-classid property** (canon already scopes codebooks by prefix) — bones run +`8:8`, a patient class runs `4:20` — same key width, class-local carve. Until +ratified, the FMA `Guid` is a constrained projection of the canon, not a +replacement. + +### F-4 — `[G]` **endianness: FMA tiers are container-first (big-endian); canon is LE throughout.** + +`Guid::classid()` reads `(bytes[0] << 8) | bytes[1]`, so `0x0A03` stores as +`[0x0A, 0x03]`. The canon stores `classid.to_le_bytes()` → `[0x03, 0x0A, 0x00, +0x00]`. The canon's LE is load-bearing: *"the trailing-6-byte local key is a +single masked load."* **The FMA `Guid` is NOT wire-compatible with `NodeGuid` +byte-for-byte.** **Reconciliation:** the FMA `Guid` is an *addressing model* +(its tiers are the semantic groups); a `From for NodeGuid` conversion at +the lance-graph membrane must (a) LE-encode each group and (b) widen +classid/family/identity to the canon field sizes. The tier model stays +human-legible (container:member, MSB-first per the `[256:256]` notation); the +membrane pays the endianness adaptation — exactly the "wrappers adapt to the +canon, never the reverse" rule, with the byte order owned at the boundary. + +### F-5 — `[G]` **edge block: canon keeps the separate `12+4` `EdgeBlock`; this session superseded it.** + +The locked CANON (2026-06-13) ships `edges(16) = 12 in-family + 4 out-of-family` +as a **separate block** after the key. The operator's 2026-06-23 word +(this session): *"don't use 12-4, that's the old taxonomy before family nodes"* — +relations are the addressing (shared family prefix = local; a reference to +another node's `Guid` = cross). **The FMA crate carries no `EdgeBlock`.** +**This is a genuine canon-vs-operator divergence to resolve at the lance-graph +level**, not something to change unilaterally here. Recorded so the next +lance-graph session reconciles `canonical_node.rs`'s `EdgeBlock` against the +family-node supersession. + +## 3. What aligns (no action) + +- **16-byte key width** — identical. +- **HHTL = 3 × u16 tiers** at the same byte offsets (`4..10`). +- **Zero-fallback ladder / RESERVE-DON'T-RECLAIM** — the FMA `Guid` honours it + (zeroed reserved tail; classid/family fixed offsets). +- **classid = `0xDDCC` domain-prefixed concept** routing on `>> 8` — the FMA + `Guid` concept tier is exactly an OGAR codebook id. +- **`identity` attached to `family`** — same semantics (family routes, identity + discriminates within). + +## 4. Reconciliation checklist (for the operator / a lance-graph session) + +- [ ] **F-1 (highest):** retarget `NodeGuid::CLASSID_FMA` `0x0901 → 0x0A01` + (Anatomy domain), clearing the `patient` collision. Update the + ISS-CLASSID-OGAR-DRIFT note. +- [ ] **F-2/F-4:** add a `From for NodeGuid` membrane that LE-encodes + + widens (app/family/identity) — the wrapper adapts at the boundary. +- [ ] **F-3:** ratify the per-classid `family:identity` split (the `[256:256]` + economy direction) or keep the FMA `Guid` as a documented constrained + projection. +- [ ] **F-5:** reconcile `canonical_node.rs`'s `12+4 EdgeBlock` against the + family-node supersession (operator decision). + +## 5. Cross-references + +- lance-graph `crates/lance-graph-contract/src/canonical_node.rs` — `NodeGuid`. +- lance-graph `CLAUDE.md` § "CANON — Minimal SoA node" (2026-06-13). +- OGAR `crates/ogar-fma-skeleton/src/guid.rs` — the `[container:member]` tier `Guid`. +- OGAR `crates/ogar-vocab/src/lib.rs` — `ConceptDomain::Anatomy` (`0x0A`). +- OGAR `docs/FMA-SKELETON-CONVERGENCE-ANCHOR.md` — the address model.