From 7455c4e759207a36b8987b7445d5eea2cf880c55 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 21:07:11 +0000 Subject: [PATCH 1/2] =?UTF-8?q?osint/fma:=20Z-order=20tile-pyramid=20layou?= =?UTF-8?q?t=20=E2=80=94=20the=20address=20is=20the=20position?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-bake the FMA heart slice so each part-of node carries its Morton (Z-order) tile path in the GUID identity field: one 4×4 level per part-of step (heart → chamber quadrant → wall → tissue → cell), via osint_bake::morton::descend. Depth == class, so the path is fully recoverable from identity + class alone. FmaGraph deinterleaves that path nibble-by-nibble into a fixed nested tile centre (coarser tier → larger dot), pins every node (physics off), and lines the cross-cutting global types up along the pole above the body. /fma now renders as the literal nested tile pyramid the cascade descends, instead of a force-directed blob — position() is decode(). The walk reconstructs (x,y) level by level rather than flat-decoding the multi-level code (a flat decode spreads the per-level axis bits across nibbles and lands out of range). Edges reference the SoA array index, never the identity, so storing the Morton code there is layout-only and cannot perturb topology. decodeSoa now exposes the raw identity field (additive; OSINT ignores it). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TzqvDqbFRzyx17EkLKBoZF --- cockpit/public/fma.soa | Bin 5176 -> 5176 bytes cockpit/src/FmaGraph.tsx | 97 ++++++++++++++++++++++++------- cockpit/src/OsintGraph.tsx | 10 +++- crates/osint-bake/src/bin/fma.rs | 54 +++++++++++++---- 4 files changed, 129 insertions(+), 32 deletions(-) diff --git a/cockpit/public/fma.soa b/cockpit/public/fma.soa index ed00c8e6f05bc0cff73855d5e618fb7bc515f419..d8a45bb9d4ee11110ff20a19e41680fb91f6100d 100644 GIT binary patch literal 5176 zcma)74Obgg5S_dP`vF20EF@516TY=opsk`+DT`n~ELbbrD(XpLX>&|~lMm17zxE&K z+_!J{&F+$9>FGf3+?jjd?Bwm?%fpSoi0G34)6IYBKmCIbL_-$H@yDD56az{Ec?R+l zC@@fvzz_pN0f2V)hh=m84?ls90fGdcY= zWGk{n%Swip4FOz8xRRiyhg{%40IY{T0OS~;yqP)xTotr#d;q{zk&}P|AWKRN6eXj~ zfMWqny+9jQ7E|XE+XGW)*^NjRQ)d+kK-Q5ZT2?Z&tOYRj0&QAZOrk)p?SZMY>}Dj3 zsk5vEAX||oT2?Z&>}KH_eL(6uT_bgat|RbVZ_*7?!w4h5M%Y09LmDA9iZBLpk;V}C z;X3R_jU!9|o8Tvx|A;0?-9nfId5R_x_@S0)ic}e48rU?yX!0{OP3ktnEWFIoEU9^# zLtrm1&6BDiRDo6DWdTdMh_D3mGA$vnmlfh)s|ahr*5Ku1S|jxd!X1$B(j5f$@+sXV z^%=rCuyuIZz?y9$+=G|PAbeR%nv_DMZPcmgj^=?ST4^b~=;{6No0 zJx4eIb^tGjbU^Aygcl(HL@yB7%S+;4KO-CgJA#*A=!n#>2(LhXO|KBx%Ww3W)b9w# zz>eYN4>~6GC&I1WTI;l4J8_%c_DREcJN0(E>yPf&-?T2eZS!$>@BH+l^UiP7yUo$P zMy=hcpM_ioGjGjpW&-+sgIT%0fTOB#U;9B)eY@p(|@RC7sO53ts78};-0 z`P-%bX=5N0%PYrvndhS?==RP8BOFBVV$aOKteex#%Erxb2XHz(xW&F%bG9rbFQ(J( zz6l+$(pjw&Ymv%crPG7tjkM}---UZRB|nHoEyr=Osc!F#nezaGHya})mvymkHauts zl2?k6#J%EdVaf)DUWaN2C!PI^x8>I+61bgtjvEcg|9nst((Py#hw-eOZt}paUhRz%EecHZ7{_c%FGO3Ty_Srtv4^O9$6!ng%rLqM5k!+ i&HKQnQj5KsJa~#>tYjs6Db3W*rdp*Ky)=6ez5f9xKoyDr literal 5176 zcma)73s=)h5Z-KxiXaaaXnBS=s3@o?Dn3{QQ3Moxq3Asog7(}NIDOo6{MYv%+&f>h z$xaf|rk;-En{Vcuoz6~n_I4Kj5<;BOS6u#A{8#_rL5LOuSoCOdKoTH1U=v_-AVWZg z11$u!cmUFQp9}OK$y)ReA4^=MuLeBPOQaJ3;I)!Y0Ca&)Iswp4KpO`n0qr~_OTZP* z$Pv(C036K{okkWJEYW3hNP4hDHxGgARSqEN8V`Z&boqv?dKto9pDVe4*CGjiXU=_zq5inr@Z=fa#)OA2diYb!=U6*2-hd_3Q1CX8NA&|Yn8IZkc0CZi7TlCek z=(-ehCI`AM#cdt}**hG7>^u*F>;h*%_O1^=6u0FbY1L&R3gAAm0>C0*(N78nCh&pD zfv(%)AqOM@OFSe?505w_N5EqPpzF4HVr0>ETP)L8*MY9v;wcY->@yBP_BjuM>vwZeOPDE!$w)fwfZs_7z&svIC|Qeunu1@;*jxAGLg?S(D#j4uBn?mhUu7@&`=M zYQB6_$R9eTTII0lI@Lm@Qgb`j3qQ-JwTgahTRS;At^Rh4g<7d&t(dP=3x~dI%dG^i zm7~JhX~j>{vf<{-)wZI0Ty^r*GR5|?zn#*b(?e+2O4$(X)ltm$g9ref)t9YA%ekNp+Gvy3b7rYA8gA4(miiuYb@KB$W-3*u0L!9N~eT zYU0;Lq@$_+a5g@(qscC<(UhUWzo0)fwbF1XcWiDa8@si9DwYaM#pW)YB<4O>DuJas z>euZb_k^}AEpcxh1dnWh2f{>u>DTY|}438&7P!rz0nh5p)?Tc`7Ox zo6l&X7$HG0fu}mX`xzWjZ;{}cjrw4O8k+U-Mo#~?XwFf3?Hm2*I8R5DL^xt1AT;&*#g*XIVrfYJ8-ryE24BAqj4L+SGtq;`7`l|S TL>1GF&1@`HjFF_7Kve$&dD$Jp diff --git a/cockpit/src/FmaGraph.tsx b/cockpit/src/FmaGraph.tsx index fe594d2f4..7dfee4393 100644 --- a/cockpit/src/FmaGraph.tsx +++ b/cockpit/src/FmaGraph.tsx @@ -27,6 +27,41 @@ 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]; +// ── Z-order (Morton) tile layout ───────────────────────────────────────────── +// The bake stores each part-of node's Morton tile path in the GUID identity (one +// 4×4 level per part-of step, coarsest in the high nibble). Depth == class +// (Organ 0 → Cell 4), so position is fully recoverable from identity + class: +// walk the nibbles, decode each 4×4 cell, accumulate base-2, and the node lands +// at the centre of its nested tile — the address literally *is* the coordinate. +const MAX_DEPTH = 4; // cells are the deepest tier → a 2^4 = 16×16 finest grid +const CELL = 90; // px per finest grid cell +const POLE_Y = -2.4 * CELL; // the ceiling pole hovers above the body + +// inverse bit-interleave (gather even bits to the low half) — TS port of +// `compact1by1` in osint-bake/src/morton.rs; decodes ONE 4×4 Morton cell. +function compact1by1(n: number): number { + n &= 0x55555555; + n = (n | (n >> 1)) & 0x33333333; + n = (n | (n >> 2)) & 0x0f0f0f0f; + n = (n | (n >> 4)) & 0x00ff00ff; + n = (n | (n >> 8)) & 0x0000ffff; + return n >>> 0; +} + +// Reconstruct a node's tile centre by walking its Morton path nibble-by-nibble +// (NOT a flat decode — a multi-level code spreads the axis bits across nibbles). +function tilePos(code: number, depth: number): { x: number; y: number; span: number } { + let gx = 0; + let gy = 0; + for (let k = 0; k < depth; k++) { + const nib = (code >> (4 * (depth - 1 - k))) & 0xf; // coarsest level first + gx = gx * 2 + compact1by1(nib); // each FMA level is a 2×2 branch (quad 0..1) + gy = gy * 2 + compact1by1(nib >> 1); + } + const span = 1 << (MAX_DEPTH - depth); // tile edge, in finest cells + return { x: (gx * span + span / 2) * CELL, y: (gy * span + span / 2) * CELL, span }; +} + const OPTIONS: Options = { nodes: { shape: 'dot', borderWidth: 2.5, font: { color: '#d9e9f9', size: 13, strokeWidth: 3, strokeColor: PAGE_BG } }, edges: { @@ -36,11 +71,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 Z-order tiles (see tilePos) — no force simulation. + physics: { enabled: false }, interaction: { hover: true, tooltipDelay: 90, dragNodes: true }, layout: { improvedLayout: false }, }; @@ -93,18 +125,42 @@ 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 Z-order position per node: part-of nodes deinterleave their Morton + // tile path (depth == class) into a nested tile centre; the cross-cutting + // global types line up along the pole above the body. The grid width spans + // 2^MAX_DEPTH finest cells, so the pole row is centred over that span. + const poleNodes = Array.from({ length: soa.nodeCount }, (_, i) => i).filter(ceiling); + const gridSpan = (1 << MAX_DEPTH) * CELL; + 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)) * gridSpan; + return { x, y: POLE_Y, size: 22 }; + } + const depth = soa.cls[i]; // Organ 0 → Cell 4 + const { x, y } = tilePos(soa.identity[i], depth); + return { x, y, size: 30 - depth * 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 +173,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 Z-order tiles, no simulation — just frame the nested pyramid. + 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..2f290a11d 100644 --- a/cockpit/src/OsintGraph.tsx +++ b/cockpit/src/OsintGraph.tsx @@ -89,6 +89,10 @@ 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). For basin-local nodes this is + // the plain identity; the FMA bake stores the node's Z-order (Morton) tile path + // here, so FmaGraph deinterleaves it into a fixed tile-pyramid position. + identity: Uint16Array; } /** One readable step of the reasoning traversal, streamed into the readout. */ @@ -129,10 +133,14 @@ 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); + // identity[i] = GUID bytes 14-15 (LE) — the basin-local identity, or the + // node's Morton tile-path when the bake stores a Z-order address there. + const identity = new Uint16Array(nodeCount); for (let i = 0; i < nodeCount; i++) { const heel = dv.getUint16(off + 4, true); const hip = dv.getUint16(off + 6, 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 +170,7 @@ 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 }; } // 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..0b70e95a5 100644 --- a/crates/osint-bake/src/bin/fma.rs +++ b/crates/osint-bake/src/bin/fma.rs @@ -19,6 +19,7 @@ //! Run from the workspace root: `cargo run -p osint-bake --bin fma` use lance_graph_contract::canonical_node::NodeGuid; +use osint_bake::morton; use std::path::{Path, PathBuf}; /// The CEILING global-category pole (HEEL=HIP=0xFFFF; sentinel through TWIG = leaf-grain). @@ -38,6 +39,15 @@ 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) +/// Map a sibling index (0..4) to its 2×2 quadrant `(tx, ty)` inside one 4×4 +/// Morton level: 0→(0,0) 1→(1,0) 2→(0,1) 3→(1,1) — natural row-major placement. +/// Every FMA branch is ≤4-way (4 chambers, 3 walls, 2 tissues, 2 cells), so each +/// part-of step is exactly one `morton::descend`, and depth == class. +fn quad(i: usize) -> (u16, u16) { + let i = i as u16; + (i & 1, (i >> 1) & 1) +} + struct Node { label: String, class: u8, @@ -55,6 +65,12 @@ impl Builder { } /// A part-of (basin-local) node: HEEL=organ, HIP=chamber, TWIG=wall, LEAF=struct. + /// `morton` is the node's Z-order tile path (one 4×4 level per part-of step); + /// its low 16 bits land in the GUID identity field, so the address **is** the + /// layout coordinate — `FmaGraph` deinterleaves it back into a fixed position. + /// Edges reference the returned array index, never the identity, so storing the + /// Morton code here is layout-only and can't perturb the graph topology. + #[allow(clippy::too_many_arguments)] fn part_of_node( &mut self, label: &str, @@ -64,16 +80,17 @@ impl Builder { wall: u16, leaf: u16, basin: u8, + morton: u32, ) -> 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 + organ, // HEEL — organ tier + chamber, // HIP — chamber tier + wall, // TWIG — wall/region tier + leaf, // LEAF — structure tier u16::from(basin), - i as u16, + morton as u16, // identity — the Z-order tile path (position == address) ); self.nodes.push(Node { label: label.to_string(), class, key }); i @@ -129,18 +146,24 @@ 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 (basin-local root, root of the Z-order pyramid) ── + // Morton 0 = the whole 16×16 tile; each part-of step descends one 4×4 level. + let heart_m: u32 = 0; + let heart = b.part_of_node("Heart", C_ORGAN, 1, 0, 0, 0, 0, heart_m); 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 (ctx, cty) = quad(ci); // chamber → one of the 4 heart quadrants + let chamber_m = morton::descend(heart_m, ctx, cty); + let ch = b.part_of_node(chamber, C_CHAMBER, 1, cnum, 0, 0, basin, chamber_m); 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 (wtx, wty) = quad(wi); // wall → a sub-tile of the chamber quadrant + let wall_m = morton::descend(chamber_m, wtx, wty); let w = b.part_of_node( &format!("{chamber} {wall}"), C_WALL, @@ -149,11 +172,14 @@ fn build_heart() -> Builder { wnum, 0, basin, + wall_m, ); b.edge(w, ch, REL_PART_OF); for (ti, (tissue, gtype)) in tissues.iter().enumerate() { let leaf = (ti as u16) + 1; + let (ttx, tty) = quad(ti); // tissue → a sub-tile of the wall + let tissue_m = morton::descend(wall_m, ttx, tty); let t = b.part_of_node( &format!("{chamber} {tissue}"), C_TISSUE, @@ -162,6 +188,7 @@ fn build_heart() -> Builder { wnum, leaf, basin, + tissue_m, ); b.edge(t, w, REL_PART_OF); // THE dual membership: this basin-local tissue is-a the global type. @@ -169,6 +196,8 @@ fn build_heart() -> Builder { for (cells_i, cell) in cells.iter().enumerate() { let cleaf = (ti as u16) * 8 + (cells_i as u16) + 16; + let (xtx, xty) = quad(cells_i); // cell → the finest sub-tile + let cell_m = morton::descend(tissue_m, xtx, xty); let c = b.part_of_node( &format!("{chamber} {tissue} {cell}"), C_CELL, @@ -177,6 +206,7 @@ fn build_heart() -> Builder { wnum, cleaf, basin, + cell_m, ); b.edge(c, t, REL_PART_OF); } @@ -226,14 +256,18 @@ fn main() { let key = &b.nodes[tissue].key; println!("── FMA dual-membership proof ──"); println!("node: {}", b.nodes[tissue].label); + // identity now carries the node's Z-order tile path (one 4×4 level per + // part-of step, coarsest in the high nibble). FmaGraph walks the nibbles to + // recover the nested-tile position; a flat decode would mis-spread the bits. + let morton_code = u32::from(key.identity_v2()); println!( - " part-of address (basin-local): HEEL={} HIP={} TWIG={} LEAF={} family={} identity={}", + " part-of address (basin-local): HEEL={} HIP={} TWIG={} LEAF={} family={} morton=0x{:04x} (Z-order tile path)", key.heel(), key.hip(), key.twig(), key.leaf(), key.family_v2(), - key.identity_v2() + morton_code, ); // its is-a edge → the leaf-limited global type (ceiling pole) let gtype = b From a8603516544288903c740a610a337018a8928296 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 21:07:11 +0000 Subject: [PATCH 2/2] =?UTF-8?q?osint/fma:=208:8=20[container:identity]=20H?= =?UTF-8?q?HTL=20tiers=20=E2=80=94=20the=20address=20IS=20the=20partonomy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the synthetic Morton-in-identity scheme (which crammed a whole multi-level tile path into one u16 and nibble-walked it back — bad addressing) with the deterministic tier model: each HHTL tier is one 8:8 [container:identity] pair = [mixin-node : instance-on-it], 256×256 = 64k per tier. The high byte names the KIND mixin node (Organ/Chamber/Wall/ Tissue/Cell — the family things attach on), the low byte the instance. HEEL [Organ:Heart] HIP [Chamber:id] TWIG [Wall:id] LEAF [Tissue:id] family [Cell:id] The non-zero tiers ARE the partonomy path, so the address says where a node sits — no Morton decode, no edge lookup. This is the q2 Cascade reading of OGAR PR #116's [container:member] tier model (OGAR's skeleton is the Located/spatial sibling). Mirrors OGAR's [bodypart:bone] LeafTile and supersedes the 12+4 EdgeBlock with family-node grouping (the container byte = the mixin/family node). classid stays 0x0A01 = anatomical_structure in OGAR's ConceptDomain::Anatomy. decodeSoa now exposes the five tiers (heel/hip/twig/leaf/family); FmaGraph lays out straight from them (tierPos: y = depth, x = nested slot under the parent's instance) instead of the Morton treemap — so the HHTL block is finally load-bearing, not write-only ornamentation. Verified: the cockpit's read offsets match the Rust-written GUID byte for byte across organ→chamber→wall→tissue→cell; ceiling sentinel intact; cockpit tsc clean. Browser render not exercised (no browser here). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TzqvDqbFRzyx17EkLKBoZF --- cockpit/public/fma.soa | Bin 5176 -> 5176 bytes cockpit/src/FmaGraph.tsx | 92 +++++++++-------- cockpit/src/OsintGraph.tsx | 31 ++++-- crates/osint-bake/src/bin/fma.rs | 169 +++++++++++++++---------------- 4 files changed, 159 insertions(+), 133 deletions(-) diff --git a/cockpit/public/fma.soa b/cockpit/public/fma.soa index d8a45bb9d4ee11110ff20a19e41680fb91f6100d..4fdd0d601b5f05f2fcb915d958c93cadd9ad242f 100644 GIT binary patch literal 5176 zcma)72U8nM4Bpdu(;%dW4yKbrI_Zrg5C|cpC!~%;E{W%vGT44I`StJV^X{?QJ8Yk4 zkhxEv^l4?SZtcz5)$dBF9sUcBDD_|C!w>w?4hZ-$kN`125(pUxB~W0XAOX#QHb8)U zjn4ri8w3F|^5>umAPXQsPIX9;0|L%^ScMV@^bu8%0O<}15QlWi5Gn-vsLvqXH56OVY>cAhCp^f0+3ylA&`CQGe|Eb zK*4r>Iq7=AcKumW2LN<^B}r<4KtFdmOxIT>fI42t5Y(~eGf2O5fa&@xCu_R?+T}1^ ze^m6(+4nw!^alr+u77m0rt9l2hw1t!83NhQ5`gR%83Ng_K7;f(RXC^) zC_2OooepD(ALd;i9^vgx9Wb51I{Bp%9_95+T`orF0BjJK%@ zPxDfxGcadC?pC}oyZJp6_NZ<}y)b>i`uJ57_VX&F0hmG5GNc9-4J%%4!>FamJB>zQ zMuCl@mN8yhG!8QX@}%O0G>KZK6n{;_oCC%?Muq2j)zAf)iy+S^UL!N8<&v6FbQ$If zuq&wLDlZ7S26G*?+)&pQ%__d|v#8}J-*mbKa~s%g)N+TfGTnu_2l9Qz*YrMWd7${~ zAE52OwsO5>8SF`}L2y79xJmqUcOEAl*<(XPmw4(SLte}?X zJpZ%`^8(ll)Uw6{PA_3zf&5zW@V-VZZxnyMg?R^z=TU|4c`WGz%tw&d6_4CHYWbwr z6@7;J0_+QF`O3pY-(b$nmv(=aOB=<_dUc}`7i;BewH|jZmVfMS*Q@riV`1y(cI{7G zDc3i<7AmD`t-O)AR^#fPYwl-xXS2JI{+{{tjh%1%iTpNEhAme}8BUS&2D{*amYpj*i3gqBK-7-(ijk2d2O5=?N zmMX~R6K^InfYafJ8*k~g zXUjv1Je_L&M^XVR{VLVG6dCV+I!#DX=WhA;pJYzE<%1a1ULO~eT5RsIeQraD20cQ0 zSjSs>lY{0UMLix#%EhaC~zHi*&V>#C5UDCv9m2z&|~lMm17zxE&K z+_!J{&F+$9>FGf3+?jjd?Bwm?%fpSoi0G34)6IYBKmCIbL_-$H@yDD56az{Ec?R+l zC@@fvzz_pN0f2V)hh=m84?ls90fGdcY= zWGk{n%Swip4FOz8xRRiyhg{%40IY{T0OS~;yqP)xTotr#d;q{zk&}P|AWKRN6eXj~ zfMWqny+9jQ7E|XE+XGW)*^NjRQ)d+kK-Q5ZT2?Z&tOYRj0&QAZOrk)p?SZMY>}Dj3 zsk5vEAX||oT2?Z&>}KH_eL(6uT_bgat|RbVZ_*7?!w4h5M%Y09LmDA9iZBLpk;V}C z;X3R_jU!9|o8Tvx|A;0?-9nfId5R_x_@S0)ic}e48rU?yX!0{OP3ktnEWFIoEU9^# zLtrm1&6BDiRDo6DWdTdMh_D3mGA$vnmlfh)s|ahr*5Ku1S|jxd!X1$B(j5f$@+sXV z^%=rCuyuIZz?y9$+=G|PAbeR%nv_DMZPcmgj^=?ST4^b~=;{6No0 zJx4eIb^tGjbU^Aygcl(HL@yB7%S+;4KO-CgJA#*A=!n#>2(LhXO|KBx%Ww3W)b9w# zz>eYN4>~6GC&I1WTI;l4J8_%c_DREcJN0(E>yPf&-?T2eZS!$>@BH+l^UiP7yUo$P zMy=hcpM_ioGjGjpW&-+sgIT%0fTOB#U;9B)eY@p(|@RC7sO53ts78};-0 z`P-%bX=5N0%PYrvndhS?==RP8BOFBVV$aOKteex#%Erxb2XHz(xW&F%bG9rbFQ(J( zz6l+$(pjw&Ymv%crPG7tjkM}---UZRB|nHoEyr=Osc!F#nezaGHya})mvymkHauts zl2?k6#J%EdVaf)DUWaN2C!PI^x8>I+61bgtjvEcg|9nst((Py#hw-eOZt}paUhRz%EecHZ7{_c%FGO3Ty_Srtv4^O9$6!ng%rLqM5k!+ i&HKQnQj5KsJa~#>tYjs6Db3W*rdp*Ky)=6ez5f9xKoyDr diff --git a/cockpit/src/FmaGraph.tsx b/cockpit/src/FmaGraph.tsx index 7dfee4393..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,39 +36,42 @@ 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]; -// ── Z-order (Morton) tile layout ───────────────────────────────────────────── -// The bake stores each part-of node's Morton tile path in the GUID identity (one -// 4×4 level per part-of step, coarsest in the high nibble). Depth == class -// (Organ 0 → Cell 4), so position is fully recoverable from identity + class: -// walk the nibbles, decode each 4×4 cell, accumulate base-2, and the node lands -// at the centre of its nested tile — the address literally *is* the coordinate. -const MAX_DEPTH = 4; // cells are the deepest tier → a 2^4 = 16×16 finest grid -const CELL = 90; // px per finest grid cell -const POLE_Y = -2.4 * CELL; // the ceiling pole hovers above the body +// ── 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 -// inverse bit-interleave (gather even bits to the low half) — TS port of -// `compact1by1` in osint-bake/src/morton.rs; decodes ONE 4×4 Morton cell. -function compact1by1(n: number): number { - n &= 0x55555555; - n = (n | (n >> 1)) & 0x33333333; - n = (n | (n >> 2)) & 0x0f0f0f0f; - n = (n | (n >> 4)) & 0x00ff00ff; - n = (n | (n >> 8)) & 0x0000ffff; - return n >>> 0; -} +/// the instance (low) byte of an 8:8 tier. +const inst = (t: number) => t & 0xff; -// Reconstruct a node's tile centre by walking its Morton path nibble-by-nibble -// (NOT a flat decode — a multi-level code spreads the axis bits across nibbles). -function tilePos(code: number, depth: number): { x: number; y: number; span: number } { - let gx = 0; - let gy = 0; - for (let k = 0; k < depth; k++) { - const nib = (code >> (4 * (depth - 1 - k))) & 0xf; // coarsest level first - gx = gx * 2 + compact1by1(nib); // each FMA level is a 2×2 branch (quad 0..1) - gy = gy * 2 + compact1by1(nib >> 1); +// 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; } - const span = 1 << (MAX_DEPTH - depth); // tile edge, in finest cells - return { x: (gx * span + span / 2) * CELL, y: (gy * span + span / 2) * CELL, span }; + return { x: ((lo + hi) / 2) * COL, y: soa.cls[i] * ROW }; } const OPTIONS: Options = { @@ -71,7 +83,7 @@ const OPTIONS: Options = { smooth: { enabled: true, type: 'continuous', roundness: 0.2 }, arrows: { to: { enabled: true, scaleFactor: 0.45 } }, }, - // positions are fixed Z-order tiles (see tilePos) — no force simulation. + // 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 }, @@ -126,21 +138,19 @@ export function FmaGraph() { if (!hostRef.current || !soa || !rel) return; const ceiling = (i: number) => soa.ceiling[i] === 1 || soa.cls[i] === 5; - // Fixed Z-order position per node: part-of nodes deinterleave their Morton - // tile path (depth == class) into a nested tile centre; the cross-cutting - // global types line up along the pole above the body. The grid width spans - // 2^MAX_DEPTH finest cells, so the pole row is centred over that span. + // 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 gridSpan = (1 << MAX_DEPTH) * CELL; 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)) * gridSpan; + const x = ((k + 0.5) / Math.max(poleNodes.length, 1)) * COL; return { x, y: POLE_Y, size: 22 }; } - const depth = soa.cls[i]; // Organ 0 → Cell 4 - const { x, y } = tilePos(soa.identity[i], depth); - return { x, y, size: 30 - depth * 4 }; // coarser tier → larger dot + const { x, y } = tierPos(soa, i); + return { x, y, size: 30 - soa.cls[i] * 4 }; // coarser tier → larger dot }; const baseNode = (i: number) => { @@ -173,7 +183,7 @@ 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); - // fixed Z-order tiles, no simulation — just frame the nested pyramid. + // 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`); diff --git a/cockpit/src/OsintGraph.tsx b/cockpit/src/OsintGraph.tsx index 2f290a11d..272ef75ab 100644 --- a/cockpit/src/OsintGraph.tsx +++ b/cockpit/src/OsintGraph.tsx @@ -89,10 +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). For basin-local nodes this is - // the plain identity; the FMA bake stores the node's Z-order (Morton) tile path - // here, so FmaGraph deinterleaves it into a fixed tile-pyramid position. + // 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. */ @@ -133,12 +139,22 @@ 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); - // identity[i] = GUID bytes 14-15 (LE) — the basin-local identity, or the - // node's Morton tile-path when the bake stores a Z-order address there. 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); @@ -170,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, identity }; + 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 0b70e95a5..8c26816d6 100644 --- a/crates/osint-bake/src/bin/fma.rs +++ b/crates/osint-bake/src/bin/fma.rs @@ -16,16 +16,42 @@ //! `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; -use osint_bake::morton; 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; @@ -39,13 +65,21 @@ 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) -/// Map a sibling index (0..4) to its 2×2 quadrant `(tx, ty)` inside one 4×4 -/// Morton level: 0→(0,0) 1→(1,0) 2→(0,1) 3→(1,1) — natural row-major placement. -/// Every FMA branch is ≤4-way (4 chambers, 3 walls, 2 tissues, 2 cells), so each -/// part-of step is exactly one `morton::descend`, and depth == class. -fn quad(i: usize) -> (u16, u16) { - let i = i as u16; - (i & 1, (i >> 1) & 1) +// ── 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 { @@ -64,33 +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. - /// `morton` is the node's Z-order tile path (one 4×4 level per part-of step); - /// its low 16 bits land in the GUID identity field, so the address **is** the - /// layout coordinate — `FmaGraph` deinterleaves it back into a fixed position. - /// Edges reference the returned array index, never the identity, so storing the - /// Morton code here is layout-only and can't perturb the graph topology. - #[allow(clippy::too_many_arguments)] + /// 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, - morton: u32, + 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), - morton as u16, // identity — the Z-order tile path (position == address) + 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 @@ -146,67 +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, root of the Z-order pyramid) ── - // Morton 0 = the whole 16×16 tile; each part-of step descends one 4×4 level. - let heart_m: u32 = 0; - let heart = b.part_of_node("Heart", C_ORGAN, 1, 0, 0, 0, 0, heart_m); + // ── 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 (ctx, cty) = quad(ci); // chamber → one of the 4 heart quadrants - let chamber_m = morton::descend(heart_m, ctx, cty); - let ch = b.part_of_node(chamber, C_CHAMBER, 1, cnum, 0, 0, basin, chamber_m); + 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 (wtx, wty) = quad(wi); // wall → a sub-tile of the chamber quadrant - let wall_m = morton::descend(chamber_m, wtx, wty); - let w = b.part_of_node( - &format!("{chamber} {wall}"), - C_WALL, - 1, - cnum, - wnum, - 0, - basin, - wall_m, - ); + 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 (ttx, tty) = quad(ti); // tissue → a sub-tile of the wall - let tissue_m = morton::descend(wall_m, ttx, tty); - let t = b.part_of_node( - &format!("{chamber} {tissue}"), - C_TISSUE, - 1, - cnum, - wnum, - leaf, - basin, - tissue_m, - ); + 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; - let (xtx, xty) = quad(cells_i); // cell → the finest sub-tile - let cell_m = morton::descend(tissue_m, xtx, xty); + 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, - cell_m, + cid, + wid, + tid, + ceid, ); b.edge(c, t, REL_PART_OF); } @@ -256,18 +256,15 @@ fn main() { let key = &b.nodes[tissue].key; println!("── FMA dual-membership proof ──"); println!("node: {}", b.nodes[tissue].label); - // identity now carries the node's Z-order tile path (one 4×4 level per - // part-of step, coarsest in the high nibble). FmaGraph walks the nibbles to - // recover the nested-tile position; a flat decode would mis-spread the bits. - let morton_code = u32::from(key.identity_v2()); + // 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={} morton=0x{:04x} (Z-order tile path)", - key.heel(), - key.hip(), - key.twig(), - key.leaf(), - key.family_v2(), - morton_code, + " 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