From 31b1defc4a524ab2f374b4b10fef934874a31c41 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 21:29:55 +0000 Subject: [PATCH] =?UTF-8?q?cockpit/fma:=20nested=20"outer=20wall"=20render?= =?UTF-8?q?ing=20=E2=80=94=20the=20address=20IS=20the=20wall?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /fma view drew floating nodes; that throws away the [container:identity] nesting. Add one rounded "outer wall" box per container (Organ→Tissue; cells are leaves), drawn behind the nodes via the beforeDrawing canvas hook, colored by class, nested so the Heart's box is the outermost wall (its epicardium). Each box bounds its tier-prefix descendants: a node is inside container c's wall iff its 8:8 tier instances match c's down to c's depth — c's address is a prefix of n's. So the walls fall straight out of the address, no geometry heuristic. Verified against the baked asset: descendant counts per container match the partonomy exactly (Heart 89, chamber 22, wall 7, tissue 3). tsc clean; browser render not exercised here. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TzqvDqbFRzyx17EkLKBoZF --- cockpit/src/FmaGraph.tsx | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/cockpit/src/FmaGraph.tsx b/cockpit/src/FmaGraph.tsx index bef34165e..bf29f1f31 100644 --- a/cockpit/src/FmaGraph.tsx +++ b/cockpit/src/FmaGraph.tsx @@ -74,6 +74,75 @@ function tierPos( return { x: ((lo + hi) / 2) * COL, y: soa.cls[i] * ROW }; } +const isCeiling = (soa: Soa, i: number) => soa.ceiling[i] === 1 || soa.cls[i] === 5; + +// A node n is INSIDE container c's wall iff its 8:8 tier instances match c's down +// to c's depth — i.e. c's address is a prefix of n's. The address IS the wall. +function inside(soa: Soa, c: number, n: number): boolean { + const d = soa.cls[c]; + if (soa.cls[n] < d) return false; + if (d >= 1 && inst(soa.hip[n]) !== inst(soa.hip[c])) return false; + if (d >= 2 && inst(soa.twig[n]) !== inst(soa.twig[c])) return false; + if (d >= 3 && inst(soa.leaf[n]) !== inst(soa.leaf[c])) return false; + return true; +} + +interface Wall { + x0: number; + y0: number; + x1: number; + y1: number; + color: string; + depth: number; +} + +// One nested "outer wall" rectangle per container (Organ→Tissue; cells are +// leaves). Each box bounds its tier-prefix descendants, so the walls nest exactly +// like the partonomy — the Heart's box is the outermost wall (its epicardium). +function containerWalls(soa: Soa): Wall[] { + const center = new Map(); + for (let i = 0; i < soa.nodeCount; i++) if (!isCeiling(soa, i)) center.set(i, tierPos(soa, i)); + const walls: Wall[] = []; + for (let c = 0; c < soa.nodeCount; c++) { + if (isCeiling(soa, c) || soa.cls[c] > 3) continue; // only Organ..Tissue contain + let x0 = Infinity; + let y0 = Infinity; + let x1 = -Infinity; + let y1 = -Infinity; + let found = false; + for (const [n, p] of center) { + if (!inside(soa, c, n)) continue; + found = true; + x0 = Math.min(x0, p.x); + y0 = Math.min(y0, p.y); + x1 = Math.max(x1, p.x); + y1 = Math.max(y1, p.y); + } + if (!found) continue; + const pad = 42 - soa.cls[c] * 7; // coarser container → roomier wall + walls.push({ x0: x0 - pad, y0: y0 - pad, x1: x1 + pad, y1: y1 + pad, color: classColor(soa.cls[c]), depth: soa.cls[c] }); + } + return walls.sort((a, b) => a.depth - b.depth); // coarsest drawn first (behind) +} + +// Stroke a rounded rect in vis-network coordinates (the beforeDrawing ctx is +// already in network space, so it aligns with node positions). +function strokeWall(ctx: CanvasRenderingContext2D, w: Wall): void { + const r = 16; + ctx.beginPath(); + ctx.moveTo(w.x0 + r, w.y0); + ctx.arcTo(w.x1, w.y0, w.x1, w.y1, r); + ctx.arcTo(w.x1, w.y1, w.x0, w.y1, r); + ctx.arcTo(w.x0, w.y1, w.x0, w.y0, r); + ctx.arcTo(w.x0, w.y0, w.x1, w.y0, r); + ctx.closePath(); + ctx.fillStyle = `${w.color}0f`; // very faint compartment fill + ctx.fill(); + ctx.lineWidth = w.depth === 0 ? 2.6 : 1.4; // the Heart's outer wall is boldest + ctx.strokeStyle = `${w.color}66`; + ctx.stroke(); +} + const OPTIONS: Options = { nodes: { shape: 'dot', borderWidth: 2.5, font: { color: '#d9e9f9', size: 13, strokeWidth: 3, strokeColor: PAGE_BG } }, edges: { @@ -183,6 +252,12 @@ 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); + // nested "outer walls" — one rounded box per container, drawn behind the + // nodes; they nest exactly like the partonomy (the address IS the wall). + const walls = containerWalls(soa); + net.on('beforeDrawing', (ctx: CanvasRenderingContext2D) => { + for (const w of walls) strokeWall(ctx, w); + }); // 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`);