diff --git a/cockpit/public/fma.soa b/cockpit/public/fma.soa index ed00c8e6f..4fdd0d601 100644 Binary files a/cockpit/public/fma.soa and b/cockpit/public/fma.soa differ diff --git a/cockpit/src/FmaGraph.tsx b/cockpit/src/FmaGraph.tsx index fe594d2f4..bef34165e 100644 --- a/cockpit/src/FmaGraph.tsx +++ b/cockpit/src/FmaGraph.tsx @@ -4,6 +4,15 @@ // · its part-of position (basin-local: organ → chamber → wall → structure) // · its leaf-limited global TYPE (the 0xFFFF ceiling pole — cross-cutting, // the same "Cardiac muscle tissue" shared by every chamber). +// +// This is the `Cascade` (ontology / part-of) reading of OGAR PR #116's HhtlMode +// FMA tier model: each node is a stack of 8:8 [container:identity] tiers — +// HEEL=[Organ:Heart], HIP=[Chamber:id], TWIG=[Wall:id], LEAF=[Tissue:id] — where +// the container byte is the KIND mixin node and the identity the instance, so the +// partonomy IS the key and the layout reads straight off it. OGAR's +// ogar-fma-skeleton is the `Located` (spatial) sibling (the same 8:8 tiers carry +// coronal x:y / depth z Morton cells). classid 0x0A01 = anatomical_structure in +// OGAR's ConceptDomain::Anatomy (0x0A). import { useEffect, useMemo, useRef, useState } from 'react'; import { Network, type Options } from 'vis-network'; import { DataSet } from 'vis-data'; @@ -27,6 +36,44 @@ const classColor = (c: number) => FMA_CLASS[c]?.color ?? '#8899aa'; const REL = ['member-of', 'interfaces', 'part-of', 'is-a']; const REL_COLOR = ['#223040', '#223040', '#7fa6c4', CEILING_COLOR]; +// ── 8:8 [container:identity] HHTL-tier layout ──────────────────────────────── +// The bake addresses each node as a stack of 8:8 tiers (see src/bin/fma.rs): +// HEEL=[Organ:Heart] HIP=[Chamber:id] TWIG=[Wall:id] LEAF=[Tissue:id] +// family=[Cell:id]. The container (high byte) is the KIND mixin node, the +// identity (low byte) is the instance. The non-zero tier identities ARE the +// partonomy path — so position is read straight off the tiers, no Morton decode: +// y = depth (class), x = a nested slot that subdivides under each parent. +const COL = 1500; // total layout width in vis units +const ROW = 210; // vertical gap per depth level +const POLE_Y = -1.7 * ROW; // the cross-cutting global types hover above the body + +/// the instance (low) byte of an 8:8 tier. +const inst = (t: number) => t & 0xff; + +// Nested horizontal slot from the [Chamber][Wall][Tissue][Cell] instance path: +// each level subdivides its parent's slot (max-siblings per level: 4/3/2/2), so +// children cluster under their parent. y is the depth band (class). +function tierPos( + soa: Soa, + i: number, +): { x: number; y: number } { + const path: Array<[number, number]> = [ + [inst(soa.hip[i]), 4], // chamber 1..4 + [inst(soa.twig[i]), 3], // wall 1..3 + [inst(soa.leaf[i]), 2], // tissue 1..2 + [inst(soa.family[i]), 2], // cell 1..2 + ]; + let lo = 0; + let hi = 1; + for (const [id, n] of path) { + if (id <= 0) break; + const w = (hi - lo) / n; + lo += (id - 1) * w; + hi = lo + w; + } + return { x: ((lo + hi) / 2) * COL, y: soa.cls[i] * ROW }; +} + const OPTIONS: Options = { nodes: { shape: 'dot', borderWidth: 2.5, font: { color: '#d9e9f9', size: 13, strokeWidth: 3, strokeColor: PAGE_BG } }, edges: { @@ -36,11 +83,8 @@ const OPTIONS: Options = { smooth: { enabled: true, type: 'continuous', roundness: 0.2 }, arrows: { to: { enabled: true, scaleFactor: 0.45 } }, }, - physics: { - solver: 'forceAtlas2Based', - forceAtlas2Based: { gravitationalConstant: -70, centralGravity: 0.008, springLength: 130, springConstant: 0.04, damping: 0.5, avoidOverlap: 0.5 }, - stabilization: { iterations: 180, fit: true }, - }, + // positions are fixed 8:8-tier slots (see tierPos) — no force simulation. + physics: { enabled: false }, interaction: { hover: true, tooltipDelay: 90, dragNodes: true }, layout: { improvedLayout: false }, }; @@ -93,18 +137,40 @@ export function FmaGraph() { useEffect(() => { if (!hostRef.current || !soa || !rel) return; const ceiling = (i: number) => soa.ceiling[i] === 1 || soa.cls[i] === 5; - const baseNode = (i: number) => ({ - id: i, - label: soa.labels[i] || `#${i}`, - shape: ceiling(i) ? 'diamond' : 'dot', - color: { - background: ceiling(i) ? 'rgba(255,209,102,0.14)' : 'rgba(10,14,23,0.88)', - border: ceiling(i) ? CEILING_COLOR : classColor(soa.cls[i]), - }, - size: ceiling(i) ? 22 : 13, - font: { color: ceiling(i) ? '#ffe9b0' : '#d9e9f9' }, - title: `${soa.labels[i]}\n${ceiling(i) ? '◈ global type (leaf-limited, cross-cutting)' : FMA_CLASS[soa.cls[i]]?.name}`, - }); + + // Fixed position per node, read straight off the 8:8 [container:identity] + // HHTL tiers: part-of nodes nest by their [Chamber][Wall][Tissue][Cell] + // instance path (y = depth = class); the cross-cutting global types line up + // along the pole above the body, spread across the same width. + const poleNodes = Array.from({ length: soa.nodeCount }, (_, i) => i).filter(ceiling); + const posOf = (i: number): { x: number; y: number; size: number } => { + if (ceiling(i)) { + const k = poleNodes.indexOf(i); + const x = ((k + 0.5) / Math.max(poleNodes.length, 1)) * COL; + return { x, y: POLE_Y, size: 22 }; + } + const { x, y } = tierPos(soa, i); + return { x, y, size: 30 - soa.cls[i] * 4 }; // coarser tier → larger dot + }; + + const baseNode = (i: number) => { + const p = posOf(i); + return { + id: i, + label: soa.labels[i] || `#${i}`, + x: p.x, + y: p.y, + fixed: { x: true, y: true }, // the address is the layout — pin it + shape: ceiling(i) ? 'diamond' : 'dot', + color: { + background: ceiling(i) ? 'rgba(255,209,102,0.14)' : 'rgba(10,14,23,0.88)', + border: ceiling(i) ? CEILING_COLOR : classColor(soa.cls[i]), + }, + size: p.size, + font: { color: ceiling(i) ? '#ffe9b0' : '#d9e9f9' }, + title: `${soa.labels[i]}\n${ceiling(i) ? '◈ global type (leaf-limited, cross-cutting)' : FMA_CLASS[soa.cls[i]]?.name}`, + }; + }; const baseEdge = (e: { s: number; t: number; r: number }, id: number) => ({ id, from: e.s, @@ -117,10 +183,9 @@ export function FmaGraph() { const visNodes = new DataSet(Array.from({ length: soa.nodeCount }, (_, i) => baseNode(i))); const visEdges = new DataSet(soa.edges.map((e, id) => baseEdge(e, id))); const net = new Network(hostRef.current, { nodes: visNodes, edges: visEdges }, OPTIONS); - net.once('stabilizationIterationsDone', () => { - net.setOptions({ physics: { enabled: false } }); - setStatus(`${soa.nodeCount} nodes · ${soa.edgeCount} edges — click a tissue to see its dual membership`); - }); + // fixed 8:8-tier slots, no simulation — just frame the nested cascade. + net.once('afterDrawing', () => net.fit({ animation: false })); + setStatus(`${soa.nodeCount} nodes · ${soa.edgeCount} edges — Z-order tile pyramid; click a tissue for its dual membership`); const dim = () => { visNodes.update(Array.from({ length: soa.nodeCount }, (_, i) => ({ id: i, color: { background: 'rgba(10,14,23,0.5)', border: '#26323f' }, font: { color: '#566779' } }))); diff --git a/cockpit/src/OsintGraph.tsx b/cockpit/src/OsintGraph.tsx index a694f6aef..272ef75ab 100644 --- a/cockpit/src/OsintGraph.tsx +++ b/cockpit/src/OsintGraph.tsx @@ -89,6 +89,16 @@ export interface Soa { tenants: Uint8Array | null; // per-node global-category flag (HEEL=HIP=0xFFFF ceiling pole): 1 = cross-cutting. ceiling: Uint8Array; + // per-node GUID identity field (bytes 14-15 LE) — the stable node id. + identity: Uint16Array; + // the four 8:8 [container:identity] HHTL tiers + the family tier (each a u16: + // high byte = mixin/kind node, low byte = instance-on-it). The FMA cockpit lays + // out straight from these; OSINT ignores them. + heel: Uint16Array; + hip: Uint16Array; + twig: Uint16Array; + leaf: Uint16Array; + family: Uint16Array; } /** One readable step of the reasoning traversal, streamed into the readout. */ @@ -129,10 +139,24 @@ export function decodeSoa(buf: ArrayBuffer): Soa { // the node is a cross-cutting GLOBAL category (the dual-use axes), not // basin-local. Read straight off the 16-byte GUID (HEEL @4, HIP @6). const ceiling = new Uint8Array(nodeCount); + const identity = new Uint16Array(nodeCount); + // the 8:8 [container:identity] HHTL tiers (high byte = mixin node, low byte = + // instance). HEEL/HIP also carry the 0xFFFF/0xFFFF ceiling-pole sentinel. + const heelA = new Uint16Array(nodeCount); + const hipA = new Uint16Array(nodeCount); + const twigA = new Uint16Array(nodeCount); + const leafA = new Uint16Array(nodeCount); + const familyA = new Uint16Array(nodeCount); for (let i = 0; i < nodeCount; i++) { const heel = dv.getUint16(off + 4, true); const hip = dv.getUint16(off + 6, true); + heelA[i] = heel; + hipA[i] = hip; + twigA[i] = dv.getUint16(off + 8, true); + leafA[i] = dv.getUint16(off + 10, true); + familyA[i] = dv.getUint16(off + 12, true); if (heel === 0xffff && hip === 0xffff) ceiling[i] = 1; + identity[i] = dv.getUint16(off + 14, true); cls[i] = dv.getUint8(off + 16); off += 17; } @@ -162,7 +186,10 @@ export function decodeSoa(buf: ArrayBuffer): Soa { tenants = new Uint8Array(buf, off, nodeCount * 6); off += nodeCount * 6; } - return { nodeCount, edgeCount, cls, edges, labels, tenants, ceiling }; + return { + nodeCount, edgeCount, cls, edges, labels, tenants, ceiling, identity, + heel: heelA, hip: hipA, twig: twigA, leaf: leafA, family: familyA, + }; } // vis-network options tuned to the Palantir look: hollow ring nodes (dark fill diff --git a/crates/osint-bake/src/bin/fma.rs b/crates/osint-bake/src/bin/fma.rs index f06e1b665..8c26816d6 100644 --- a/crates/osint-bake/src/bin/fma.rs +++ b/crates/osint-bake/src/bin/fma.rs @@ -16,6 +16,28 @@ //! `cockpit/public/fma.soa` (OSO1, the cockpit's `/fma` view reads it) and //! prints the dual-membership proof. //! +//! ## Relation to OGAR PR #116 (the FMA canon) +//! +//! OGAR's `ogar-fma-skeleton` canonized the FMA address as a `[container:member]` +//! tier model with two `HhtlMode` readings of one 16-byte key +//! (`docs/FMA-SKELETON-CONVERGENCE-ANCHOR.md`): +//! * **`Located`** — HEEL/HIP carry a *spatial* Morton position (coronal x:y / +//! depth z). OGAR's bones use this: they ARE the position anchor. +//! * **`Cascade`** — HEEL/HIP carry an *ontology* rung (parent:child class), +//! pure part-of containment, no spatial address. Soft tissue uses this. +//! +//! This heart slice is the **`Cascade` reading**, addressed as a stack of 8:8 +//! `[container:identity]` HHTL tiers: the container byte is the KIND **mixin +//! node** (Organ/Chamber/Wall/Tissue/Cell — the family everything of that level +//! attaches *on*), the identity byte is the instance attached to it — 256×256 = +//! 64k deterministic per tier. HEEL=[Organ:Heart], HIP=[Chamber:id], +//! TWIG=[Wall:id], LEAF=[Tissue:id], family=[Cell:id], so the **partonomy IS the +//! key** — no Morton-in-identity, no edge lookup needed to place a node. OGAR's +//! skeleton is the `Located` sibling (the same 8:8 tiers carry spatial Morton +//! cells instead of ontology rungs). Edges are still drawn (a viz convenience); +//! OGAR's canon supersedes the 12+4 EdgeBlock with exactly this family-node +//! grouping — the container byte names the mixin/family node. +//! //! Run from the workspace root: `cargo run -p osint-bake --bin fma` use lance_graph_contract::canonical_node::NodeGuid; @@ -23,8 +45,13 @@ use std::path::{Path, PathBuf}; /// The CEILING global-category pole (HEEL=HIP=0xFFFF; sentinel through TWIG = leaf-grain). const CEILING: u16 = 0xFFFF; -/// FMA classid (canonical class — distinct from OSINT 0x0700; classids sink in). -const CLASSID_FMA: u32 = 0x00F0_0A00; // "FMA"-ish prefix, app-stamped +/// FMA classid — `anatomical_structure` (`0x0A01`) in OGAR's +/// `ConceptDomain::Anatomy` (high byte `0x0A`; resolves via +/// `ogar_vocab::canonical_concept_domain`). The heart slice is soft-tissue +/// anatomy, so it takes the universal-root concept; OGAR reserves +/// `0x0A02..0x0A04` for skeleton/bone/joint. Aligned to OGAR PR #116 +/// (`docs/FMA-SKELETON-CONVERGENCE-ANCHOR.md`) — was the ad-hoc `0x00F0_0A00`. +const CLASSID_FMA: u32 = 0x0000_0A01; // class bytes → cockpit colour/label (see FmaGraph.tsx). const C_ORGAN: u8 = 0; @@ -38,6 +65,23 @@ const C_TYPE: u8 = 5; // the leaf-limited global type categories (ceiling pole) const REL_PART_OF: u8 = 2; // structure → its container (basin-local hierarchy) const REL_IS_A: u8 = 3; // structure → its cross-cutting global type (ceiling) +// ── HHTL 8:8 [container:identity] tiers ── +// Each tier is one u16 = `(container << 8) | identity` = 256×256 = 64k. The +// container (high byte) is the KIND **mixin node** — the family everything of +// that level attaches on; the identity (low byte) is the instance attached to +// it. Mirrors OGAR's `[bodypart:bone]` LeafTile / `[container:member]` Tier. +const MX_ORGAN: u8 = 0x01; // the Organ mixin node (the Heart attaches here) +const MX_CHAMBER: u8 = 0x02; // the Chamber mixin node +const MX_WALL: u8 = 0x03; // the Wall mixin node +const MX_TISSUE: u8 = 0x04; // the Tissue mixin node +const MX_CELL: u8 = 0x05; // the Cell mixin node +const ID_HEART: u8 = 0x01; // the sole organ instance in this slice + +/// One 8:8 HHTL tier: `[container : identity]` = `[mixin-node : instance-on-it]`. +const fn tier(container: u8, identity: u8) -> u16 { + ((container as u16) << 8) | identity as u16 +} + struct Node { label: String, class: u8, @@ -54,26 +98,30 @@ impl Builder { Self { nodes: Vec::new(), edges: Vec::new() } } - /// A part-of (basin-local) node: HEEL=organ, HIP=chamber, TWIG=wall, LEAF=struct. + /// A part-of node addressed by its `[kind-mixin : instance]` HHTL cascade. + /// Each tier is one 8:8 `[container:identity]` pair; the non-zero tiers ARE + /// the partonomy path — HEEL=which organ, HIP=which chamber, TWIG=which wall, + /// LEAF=which tissue, family=which cell. The address says where the node sits; + /// no Morton path, no edge lookup needed. `(chamber, wall, tissue, cell)` are + /// the instance ids at each level (0 = "this node isn't that deep"). fn part_of_node( &mut self, label: &str, class: u8, - organ: u16, - chamber: u16, - wall: u16, - leaf: u16, - basin: u8, + chamber: u8, + wall: u8, + tissue: u8, + cell: u8, ) -> usize { let i = self.nodes.len(); let key = NodeGuid::new_v2( CLASSID_FMA, - organ, // HEEL — organ tier - chamber, // HIP — chamber tier - wall, // TWIG — wall/region tier - leaf, // LEAF — structure tier - u16::from(basin), - i as u16, + tier(MX_ORGAN, ID_HEART), // HEEL [Organ:Heart] + if chamber > 0 { tier(MX_CHAMBER, chamber) } else { 0 }, // HIP [Chamber:id] + if wall > 0 { tier(MX_WALL, wall) } else { 0 }, // TWIG [Wall:id] + if tissue > 0 { tier(MX_TISSUE, tissue) } else { 0 }, // LEAF [Tissue:id] + if cell > 0 { tier(MX_CELL, cell) } else { 0 }, // family[Cell:id] + i as u16, // identity — stable node id ); self.nodes.push(Node { label: label.to_string(), class, key }); i @@ -129,54 +177,36 @@ fn build_heart() -> Builder { // a couple of cell types per tissue (depth + scale; part-of only). let cells: [&str; 2] = ["cell A", "cell B"]; - // ── the heart organ (basin-local root) ── - let heart = b.part_of_node("Heart", C_ORGAN, 1, 0, 0, 0, 0); + // ── the heart organ — HEEL=[Organ:Heart], deeper tiers zero ── + let heart = b.part_of_node("Heart", C_ORGAN, 0, 0, 0, 0); let chambers = ["left atrium", "right atrium", "left ventricle", "right ventricle"]; for (ci, chamber) in chambers.iter().enumerate() { - let cnum = (ci as u16) + 1; // HIP 1..4 - let basin = (ci as u8) + 1; // one basin per chamber - let ch = b.part_of_node(chamber, C_CHAMBER, 1, cnum, 0, 0, basin); + let cid = (ci as u8) + 1; // chamber instance 1..4 (HIP identity) + let ch = b.part_of_node(chamber, C_CHAMBER, cid, 0, 0, 0); b.edge(ch, heart, REL_PART_OF); for (wi, (wall, tissues)) in walls.iter().enumerate() { - let wnum = (wi as u16) + 1; // TWIG 1..3 - let w = b.part_of_node( - &format!("{chamber} {wall}"), - C_WALL, - 1, - cnum, - wnum, - 0, - basin, - ); + let wid = (wi as u8) + 1; // wall instance 1..3 (TWIG identity) + let w = b.part_of_node(&format!("{chamber} {wall}"), C_WALL, cid, wid, 0, 0); b.edge(w, ch, REL_PART_OF); for (ti, (tissue, gtype)) in tissues.iter().enumerate() { - let leaf = (ti as u16) + 1; - let t = b.part_of_node( - &format!("{chamber} {tissue}"), - C_TISSUE, - 1, - cnum, - wnum, - leaf, - basin, - ); + let tid = (ti as u8) + 1; // tissue instance 1..2 (LEAF identity) + let t = b.part_of_node(&format!("{chamber} {tissue}"), C_TISSUE, cid, wid, tid, 0); b.edge(t, w, REL_PART_OF); - // THE dual membership: this basin-local tissue is-a the global type. + // THE dual membership: this tissue is-a the cross-cutting global type. b.edge(t, type_idx[*gtype], REL_IS_A); - for (cells_i, cell) in cells.iter().enumerate() { - let cleaf = (ti as u16) * 8 + (cells_i as u16) + 16; + for (cell_i, cell) in cells.iter().enumerate() { + let ceid = (cell_i as u8) + 1; // cell instance 1..2 (family identity) let c = b.part_of_node( &format!("{chamber} {tissue} {cell}"), C_CELL, - 1, - cnum, - wnum, - cleaf, - basin, + cid, + wid, + tid, + ceid, ); b.edge(c, t, REL_PART_OF); } @@ -226,14 +256,15 @@ fn main() { let key = &b.nodes[tissue].key; println!("── FMA dual-membership proof ──"); println!("node: {}", b.nodes[tissue].label); + // the partonomy IS the key: each HHTL tier is an 8:8 [mixin:identity] pair. + let show = |t: u16| format!("[{:02x}:{:02x}]", t >> 8, t & 0xFF); println!( - " part-of address (basin-local): HEEL={} HIP={} TWIG={} LEAF={} family={} identity={}", - key.heel(), - key.hip(), - key.twig(), - key.leaf(), - key.family_v2(), - key.identity_v2() + " part-of address — HHTL 8:8 [mixin:identity]: HEEL {} HIP {} TWIG {} LEAF {} family {}", + show(key.heel()), + show(key.hip()), + show(key.twig()), + show(key.leaf()), + show(key.family_v2()), ); // its is-a edge → the leaf-limited global type (ceiling pole) let gtype = b