pg_orrery/docs/ktrie/ktrie-layout.jsx
Ryan Malloy bb235f51fa Add SP-GiST orbital trie design spec for satellite pass prediction index
Evolved from the original KTrie custom AM proposal (preserved as
KTRIE-SPEC-ORIGINAL.md). Key design decisions: 2-level trie (SMA +
inclination) instead of 5, SP-GiST framework instead of custom AM,
query-time RAAN filter instead of trie level, propagation-aware cost
estimation via traversalValue.
2026-02-17 19:53:42 -07:00

545 lines
28 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from "react";
const MONO = "'SF Mono', 'Cascadia Code', 'Fira Code', monospace";
const SANS = "'Segoe UI', system-ui, sans-serif";
const colors = {
bg: "#0a0e17",
surface: "#111827",
surface2: "#1a2332",
border: "#1e3a5f",
borderHi: "#2563eb",
text: "#e2e8f0",
textDim: "#64748b",
textMuted: "#475569",
accent: "#3b82f6",
accentDim: "#1e40af",
green: "#10b981",
greenDim: "#064e3b",
amber: "#f59e0b",
amberDim: "#78350f",
red: "#ef4444",
redDim: "#7f1d1d",
purple: "#a78bfa",
purpleDim: "#4c1d95",
cyan: "#22d3ee",
cyanDim: "#164e63",
orange: "#fb923c",
};
const levelMeta = [
{ name: "Semi-Major Axis (a)", unit: "km", example: "6,798 km (ISS)", color: colors.accent },
{ name: "Inclination (i)", unit: "deg", example: "51.6° (ISS)", color: colors.green },
{ name: "RAAN (Ω)", unit: "deg", example: "Right Ascension", color: colors.amber },
{ name: "Eccentricity (e)", unit: "", example: "0.0001 (ISS)", color: colors.purple },
{ name: "Arg. Perigee (ω)", unit: "deg", example: "Argument of Perigee", color: colors.orange },
];
const ByteBlock = ({ label, bytes, color, detail, dimColor }) => (
<div style={{
display: "flex", flexDirection: "column", gap: 2,
padding: "6px 10px", borderRadius: 4,
background: dimColor || color + "15",
border: `1px solid ${color}40`,
minWidth: 0, flex: "1 1 auto",
}}>
<div style={{ fontFamily: MONO, fontSize: 10, color, fontWeight: 600, whiteSpace: "nowrap" }}>{label}</div>
<div style={{ fontFamily: MONO, fontSize: 9, color: colors.textDim }}>{bytes}B</div>
{detail && <div style={{ fontFamily: MONO, fontSize: 9, color: colors.textMuted, marginTop: 1 }}>{detail}</div>}
</div>
);
const StructField = ({ type, name, size, comment, color }) => (
<div style={{
display: "grid", gridTemplateColumns: "100px 160px 44px 1fr",
gap: 8, padding: "3px 0",
fontFamily: MONO, fontSize: 11, lineHeight: 1.5,
borderBottom: `1px solid ${colors.border}30`,
}}>
<span style={{ color: colors.cyan }}>{type}</span>
<span style={{ color: color || colors.text }}>{name}</span>
<span style={{ color: colors.textDim, textAlign: "right" }}>{size}B</span>
<span style={{ color: colors.textMuted, fontStyle: "italic" }}>{comment}</span>
</div>
);
const SectionHeader = ({ children, color = colors.accent }) => (
<div style={{
fontFamily: SANS, fontSize: 13, fontWeight: 700,
color, textTransform: "uppercase", letterSpacing: "0.08em",
padding: "12px 0 6px", borderBottom: `1px solid ${color}40`,
marginBottom: 8,
}}>{children}</div>
);
const Badge = ({ children, color }) => (
<span style={{
fontFamily: MONO, fontSize: 9, fontWeight: 600,
color, background: color + "20",
padding: "2px 6px", borderRadius: 3,
border: `1px solid ${color}30`,
}}>{children}</span>
);
const PageDiagram = ({ type }) => {
const isInternal = type === "internal";
const isLeaf = type === "leaf";
const isCompressed = type === "compressed";
const headerColor = colors.cyan;
const entryColor = isInternal ? colors.accent : isLeaf ? colors.green : colors.purple;
const entryLabel = isInternal ? "KTrieChildEntry" : isLeaf ? "KTrieLeafEntry" : "CompressedPath + Entries";
const entrySize = isInternal ? 24 : isLeaf ? 68 : "variable";
const capacity = isInternal ? "~337 children" : isLeaf ? "~119 TLEs" : "path + leaves";
return (
<div style={{
background: colors.surface, borderRadius: 8,
border: `1px solid ${colors.border}`,
padding: 16, flex: 1,
}}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
<div style={{ fontFamily: SANS, fontSize: 13, fontWeight: 700, color: entryColor }}>
{isInternal ? "Internal Node" : isLeaf ? "Leaf Node" : "Compressed Node"}
</div>
<Badge color={entryColor}>{capacity}</Badge>
</div>
{/* Page visualization */}
<div style={{
borderRadius: 6, overflow: "hidden",
border: `1px solid ${colors.border}`,
background: colors.bg,
}}>
{/* PG Header */}
<div style={{
padding: "6px 10px", background: colors.textMuted + "20",
display: "flex", justifyContent: "space-between", alignItems: "center",
borderBottom: `1px solid ${colors.border}`,
}}>
<span style={{ fontFamily: MONO, fontSize: 10, color: colors.textDim }}>PageHeaderData</span>
<span style={{ fontFamily: MONO, fontSize: 9, color: colors.textMuted }}>24B</span>
</div>
{/* KTrie Node Header */}
<div style={{
padding: "8px 10px", background: headerColor + "10",
borderBottom: `1px solid ${headerColor}30`,
}}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 6 }}>
<span style={{ fontFamily: MONO, fontSize: 10, color: headerColor, fontWeight: 600 }}>KTrieNodeHeader</span>
<span style={{ fontFamily: MONO, fontSize: 9, color: colors.textMuted }}>72B</span>
</div>
<div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
<ByteBlock label="type" bytes={4} color={headerColor} detail={isInternal ? "INTERNAL" : isLeaf ? "LEAF" : "COMPRESSED"} />
<ByteBlock label="level" bytes={4} color={headerColor} />
<ByteBlock label="n_entries" bytes={2} color={headerColor} />
<ByteBlock label="flags" bytes={2} color={headerColor} />
<ByteBlock label="range_low" bytes={8} color={headerColor} detail="float8" />
<ByteBlock label="range_high" bytes={8} color={headerColor} detail="float8" />
<ByteBlock label="compressed" bytes={4} color={colors.purple} detail="skip levels" />
<ByteBlock label="skip_bounds" bytes={40} color={colors.purple} detail="5×float8" />
</div>
</div>
{/* Entries section */}
<div style={{ padding: "8px 10px" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 6 }}>
<span style={{ fontFamily: MONO, fontSize: 10, color: entryColor, fontWeight: 600 }}>
{entryLabel} × N
</span>
<span style={{ fontFamily: MONO, fontSize: 9, color: colors.textMuted }}>
{entrySize}B each {capacity}
</span>
</div>
{/* Show 3 sample entries */}
{[0, 1, 2].map(i => (
<div key={i} style={{
display: "flex", gap: 4, marginBottom: 4, flexWrap: "wrap",
padding: 6, borderRadius: 4,
background: entryColor + (i === 0 ? "12" : "08"),
border: i === 0 ? `1px solid ${entryColor}30` : `1px solid transparent`,
}}>
{isInternal ? (
<>
<ByteBlock label="lower" bytes={8} color={entryColor} detail="float8" />
<ByteBlock label="upper" bytes={8} color={entryColor} detail="float8" />
<ByteBlock label="child_blk" bytes={4} color={colors.amber} detail="BlockNumber" />
<ByteBlock label="pop" bytes={2} color={colors.textDim} detail="subtree count" />
<ByteBlock label="flags" bytes={2} color={colors.textDim} />
</>
) : (
<>
<ByteBlock label="norad_id" bytes={4} color={entryColor} detail="int32" />
<ByteBlock label="epoch" bytes={8} color={entryColor} detail="Julian date" />
<ByteBlock label="a" bytes={8} color={colors.accent} />
<ByteBlock label="i" bytes={8} color={colors.green} />
<ByteBlock label="Ω" bytes={8} color={colors.amber} />
<ByteBlock label="e" bytes={8} color={colors.purple} />
<ByteBlock label="ω" bytes={8} color={colors.orange} />
<ByteBlock label="M" bytes={8} color={colors.red} />
<ByteBlock label="tle_tid" bytes={6} color={colors.cyan} detail="→ heap" />
<ByteBlock label="fl" bytes={2} color={colors.textDim} />
</>
)}
</div>
))}
<div style={{
textAlign: "center", padding: 4,
fontFamily: MONO, fontSize: 10, color: colors.textMuted,
}}></div>
</div>
{/* Page footer */}
<div style={{
padding: "4px 10px", background: colors.textMuted + "10",
borderTop: `1px solid ${colors.border}`,
display: "flex", justifyContent: "space-between",
}}>
<span style={{ fontFamily: MONO, fontSize: 9, color: colors.textMuted }}>special: free_offset</span>
<span style={{ fontFamily: MONO, fontSize: 9, color: colors.textDim }}>8,192 bytes total</span>
</div>
</div>
</div>
);
};
const TreeViz = () => {
const nodeStyle = (color, label, sub, pop) => (
<div style={{
display: "flex", flexDirection: "column", alignItems: "center", gap: 2,
padding: "6px 12px", borderRadius: 6,
background: color + "15", border: `1px solid ${color}40`,
minWidth: 80,
}}>
<div style={{ fontFamily: MONO, fontSize: 10, fontWeight: 600, color }}>{label}</div>
<div style={{ fontFamily: MONO, fontSize: 8, color: colors.textMuted }}>{sub}</div>
{pop && <Badge color={color}>{pop}</Badge>}
</div>
);
const connector = (color = colors.border) => (
<div style={{ width: 1, height: 16, background: color, margin: "0 auto" }} />
);
return (
<div style={{
background: colors.surface, borderRadius: 8,
border: `1px solid ${colors.border}`,
padding: 16,
}}>
<SectionHeader color={colors.cyan}>Adaptive Trie Traversal ISS Query Path</SectionHeader>
<div style={{ fontFamily: MONO, fontSize: 10, color: colors.textMuted, marginBottom: 12, padding: "4px 8px", background: colors.bg, borderRadius: 4 }}>
SELECT * FROM satellites WHERE sgp4_passes(tle, observer(43.70, -116.35), now(), '2h') Eagle, ID
</div>
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 0 }}>
{/* Level 0: Semi-major axis */}
<div style={{ fontFamily: MONO, fontSize: 9, color: colors.accent, marginBottom: 4 }}>L0: Semi-Major Axis</div>
<div style={{ display: "flex", gap: 8, justifyContent: "center", flexWrap: "wrap" }}>
{nodeStyle(colors.accent, "6,3786,978", "sub-LEO → LEO", "28,441")}
{nodeStyle(colors.textMuted, "6,97813,000", "MEO-low", "412")}
{nodeStyle(colors.textMuted, "13,00030,000", "MEO-high", "89")}
{nodeStyle(colors.textMuted, "30,00042,200", "GEO region", "1,247")}
{nodeStyle(colors.textMuted, "42,200+", "super-GEO", "18")}
</div>
{connector(colors.accent)}
<div style={{ fontFamily: MONO, fontSize: 8, color: colors.green, background: colors.greenDim + "40", padding: "2px 8px", borderRadius: 3 }}>
ISS at 6,798km first bin, prune 4 branches
</div>
{connector(colors.green)}
{/* Level 1: Inclination — adaptive! */}
<div style={{ fontFamily: MONO, fontSize: 9, color: colors.green, marginBottom: 4 }}>L1: Inclination (adaptive: 12 bins in LEO vs 3 in GEO)</div>
<div style={{ display: "flex", gap: 6, justifyContent: "center", flexWrap: "wrap" }}>
{nodeStyle(colors.textMuted, "0°28°", "equatorial", "2,104")}
{nodeStyle(colors.textMuted, "28°45°", "mid-lat", "1,856")}
{nodeStyle(colors.green, "45°55°", "ISS band", "8,912")}
{nodeStyle(colors.textMuted, "55°70°", "GPS-ish", "3,201")}
{nodeStyle(colors.textMuted, "70°82°", "polar-adj", "1,877")}
{nodeStyle(colors.textMuted, "82°99°", "polar/SSO", "9,441")}
{nodeStyle(colors.textMuted, "99°+", "retro", "1,050")}
</div>
{connector(colors.green)}
<div style={{ fontFamily: MONO, fontSize: 8, color: colors.green, background: colors.greenDim + "40", padding: "2px 8px", borderRadius: 3 }}>
51.6° ISS band, 8,912 candidates remain
</div>
{connector(colors.amber)}
{/* Level 2: RAAN */}
<div style={{ fontFamily: MONO, fontSize: 9, color: colors.amber, marginBottom: 4 }}>L2: RAAN (coarse changes rapidly due to J2 precession)</div>
<div style={{ display: "flex", gap: 6, justifyContent: "center", flexWrap: "wrap" }}>
{nodeStyle(colors.textMuted, "0°90°", "Q1", "2,156")}
{nodeStyle(colors.amber, "90°180°", "Q2", "2,304")}
{nodeStyle(colors.textMuted, "180°270°", "Q3", "2,211")}
{nodeStyle(colors.textMuted, "270°360°", "Q4", "2,241")}
</div>
{connector(colors.amber)}
{/* Leaf level */}
<div style={{
display: "flex", gap: 8, justifyContent: "center", flexWrap: "wrap",
padding: 12, background: colors.bg, borderRadius: 6,
border: `1px dashed ${colors.green}40`,
}}>
<div style={{ textAlign: "center" }}>
<div style={{ fontFamily: MONO, fontSize: 10, color: colors.green, fontWeight: 600 }}>~2,304 leaf entries</div>
<div style={{ fontFamily: MONO, fontSize: 9, color: colors.textMuted, marginTop: 4 }}>
SGP4 propagate only these TLEs<br />
Check elevation from observer<br />
Return visible passes
</div>
<div style={{
marginTop: 8, padding: "4px 8px", borderRadius: 4,
background: colors.greenDim, fontFamily: MONO, fontSize: 9, color: colors.green,
}}>
Pruned 92.3% of catalog before propagation
</div>
</div>
</div>
</div>
</div>
);
};
export default function KTrieLayout() {
const [activeTab, setActiveTab] = useState("pages");
const tabs = [
{ id: "pages", label: "Page Layout" },
{ id: "structs", label: "C Structs" },
{ id: "tree", label: "Query Traversal" },
{ id: "levels", label: "Level Semantics" },
];
return (
<div style={{
background: colors.bg, color: colors.text,
fontFamily: SANS, minHeight: "100vh",
padding: 24,
}}>
{/* Header */}
<div style={{ marginBottom: 24 }}>
<div style={{ display: "flex", alignItems: "baseline", gap: 12, marginBottom: 4 }}>
<h1 style={{ fontFamily: MONO, fontSize: 20, fontWeight: 700, color: colors.text, margin: 0 }}>
KTrie
</h1>
<span style={{ fontFamily: MONO, fontSize: 12, color: colors.textDim }}>
Keplerian Patricia Trie PostgreSQL Index AM
</span>
</div>
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.textMuted }}>
Adaptive branching · Fixed level semantics · 8kB page-aligned · Patricia path compression
</div>
</div>
{/* Tabs */}
<div style={{ display: "flex", gap: 2, marginBottom: 20, borderBottom: `1px solid ${colors.border}` }}>
{tabs.map(t => (
<button key={t.id} onClick={() => setActiveTab(t.id)} style={{
fontFamily: MONO, fontSize: 11, fontWeight: activeTab === t.id ? 700 : 400,
color: activeTab === t.id ? colors.accent : colors.textDim,
background: activeTab === t.id ? colors.accent + "15" : "transparent",
border: "none", borderBottom: activeTab === t.id ? `2px solid ${colors.accent}` : "2px solid transparent",
padding: "8px 16px", cursor: "pointer",
transition: "all 0.15s ease",
}}>{t.label}</button>
))}
</div>
{/* Page Layout Tab */}
{activeTab === "pages" && (
<div>
<SectionHeader>8kB Page Layouts Internal vs Leaf</SectionHeader>
<div style={{ display: "flex", gap: 16, flexWrap: "wrap" }}>
<PageDiagram type="internal" />
<PageDiagram type="leaf" />
</div>
<div style={{
marginTop: 16, padding: 12, borderRadius: 6,
background: colors.surface, border: `1px solid ${colors.border}`,
}}>
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, fontWeight: 600, marginBottom: 6 }}>
Capacity Math
</div>
<div style={{ fontFamily: MONO, fontSize: 10, color: colors.textDim, lineHeight: 1.8 }}>
Page: 8,192B PG header: 24B Node header: 72B <span style={{ color: colors.text }}>8,096B available</span><br />
Internal: 8,096 / 24B per child = <span style={{ color: colors.accent }}>337 max children</span> (adaptive: use 4337 based on density)<br />
Leaf: 8,096 / 68B per entry = <span style={{ color: colors.green }}>119 max TLEs per page</span><br />
Split threshold: 85% fill split along next orbital element dimension<br />
Merge threshold: 25% fill merge with sibling if combined &lt; 70%
</div>
</div>
</div>
)}
{/* C Structs Tab */}
{activeTab === "structs" && (
<div style={{
background: colors.surface, borderRadius: 8,
border: `1px solid ${colors.border}`,
padding: 16,
}}>
<SectionHeader color={colors.cyan}>ktrie.h Core Data Structures</SectionHeader>
<div style={{ marginBottom: 16 }}>
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, fontWeight: 600, marginBottom: 8 }}>
/* Node type enum */
</div>
<StructField type="enum" name="KTRIE_INTERNAL" size={0} comment="= 0 splits orbital element space" color={colors.accent} />
<StructField type="enum" name="KTRIE_LEAF" size={0} comment="= 1 holds TLE references" color={colors.green} />
<StructField type="enum" name="KTRIE_COMPRESSED" size={0} comment="= 2 Patricia path-compressed" color={colors.purple} />
</div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, fontWeight: 600, marginBottom: 8 }}>
/* Level semantics — fixed regardless of branching */
</div>
{levelMeta.map((l, i) => (
<StructField key={i} type={`Level ${i}`} name={l.name} size={0} comment={l.example} color={l.color} />
))}
</div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, fontWeight: 600, marginBottom: 8 }}>
typedef struct KTrieNodeHeader {"{"} <span style={{ color: colors.textMuted }}>/* 72 bytes */</span>
</div>
<StructField type="uint8" name="type" size={1} comment="INTERNAL | LEAF | COMPRESSED" />
<StructField type="uint8" name="level" size={1} comment="which orbital element (0-4)" />
<StructField type="uint16" name="num_entries" size={2} comment="current entry count" />
<StructField type="uint16" name="flags" size={2} comment="DIRTY | NEEDS_SPLIT | RESONANT" />
<StructField type="uint16" name="padding" size={2} comment="alignment" />
<StructField type="float8" name="range_low" size={8} comment="this node covers [low, high)" />
<StructField type="float8" name="range_high" size={8} comment="in units of current level element" />
<StructField type="uint8" name="compressed_depth" size={1} comment="Patricia: levels skipped" />
<StructField type="uint8" name="pad[7]" size={7} comment="alignment to 8-byte boundary" />
<StructField type="float8" name="skip_bounds[5]" size={40} comment="bounds for compressed levels" />
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, marginTop: 4 }}>{"}"}</div>
</div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, fontWeight: 600, marginBottom: 8 }}>
typedef struct KTrieChildEntry {"{"} <span style={{ color: colors.textMuted }}>/* 24 bytes — internal node */</span>
</div>
<StructField type="float8" name="lower_bound" size={8} comment="element range start" color={colors.accent} />
<StructField type="float8" name="upper_bound" size={8} comment="element range end" color={colors.accent} />
<StructField type="BlockNumber" name="child" size={4} comment="→ child page" color={colors.amber} />
<StructField type="uint16" name="population" size={2} comment="objects in subtree (for cost est.)" />
<StructField type="uint16" name="flags" size={2} comment="SPARSE | DENSE | HAS_RESONANT" />
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, marginTop: 4 }}>{"}"}</div>
</div>
<div>
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, fontWeight: 600, marginBottom: 8 }}>
typedef struct KTrieLeafEntry {"{"} <span style={{ color: colors.textMuted }}>/* 68 bytes — leaf node */</span>
</div>
<StructField type="int32" name="norad_id" size={4} comment="NORAD catalog number" color={colors.green} />
<StructField type="float8" name="epoch" size={8} comment="TLE epoch (Julian date)" color={colors.green} />
<StructField type="float8" name="sma" size={8} comment="semi-major axis (km)" color={colors.accent} />
<StructField type="float8" name="inc" size={8} comment="inclination (rad)" color={colors.green} />
<StructField type="float8" name="raan" size={8} comment="right ascension (rad)" color={colors.amber} />
<StructField type="float8" name="ecc" size={8} comment="eccentricity" color={colors.purple} />
<StructField type="float8" name="argp" size={8} comment="argument of perigee (rad)" color={colors.orange} />
<StructField type="float8" name="mean_anomaly" size={8} comment="mean anomaly (rad)" color={colors.red} />
<StructField type="ItemPointerData" name="tle_tid" size={6} comment="→ heap tuple (full TLE)" color={colors.cyan} />
<StructField type="uint16" name="flags" size={2} comment="DECAYING | MANEUVERING | DEEP_SPACE" />
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.amber, marginTop: 4 }}>{"}"}</div>
</div>
</div>
)}
{/* Query Traversal Tab */}
{activeTab === "tree" && <TreeViz />}
{/* Level Semantics Tab */}
{activeTab === "levels" && (
<div>
<SectionHeader>Fixed Level Semantics with Adaptive Fan-Out</SectionHeader>
{levelMeta.map((level, i) => (
<div key={i} style={{
background: colors.surface, borderRadius: 8,
border: `1px solid ${level.color}30`,
padding: 16, marginBottom: 12,
borderLeft: `3px solid ${level.color}`,
}}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ fontFamily: MONO, fontSize: 12, fontWeight: 700, color: level.color }}>
Level {i}
</span>
<span style={{ fontFamily: SANS, fontSize: 12, color: colors.text }}>{level.name}</span>
</div>
<Badge color={level.color}>{level.unit || "dimensionless"}</Badge>
</div>
<div style={{ fontFamily: MONO, fontSize: 10, color: colors.textDim, lineHeight: 1.8 }}>
{i === 0 && (
<>
<span style={{ color: colors.text }}>Regime boundaries:</span> sub-LEO (&lt;300km alt), LEO (3002,000), MEO (2,00035,000), GEO (35,00036,000), HEO/beyond<br />
<span style={{ color: colors.text }}>Adaptive range:</span> 520 bins typical. LEO subdivides heavily (75% of catalog)<br />
<span style={{ color: colors.text }}>Split strategy:</span> Equal-population splits, not equal-range. 6,3786,578 might be one bin (sparse), 6,5786,978 might be 8 bins (dense LEO)<br />
<span style={{ color: colors.text }}>Query predicate:</span> Altitude range directly maps instant prune
</>
)}
{i === 1 && (
<>
<span style={{ color: colors.text }}>Key clusters:</span> 0° (equatorial), 28.5° (Cape Canaveral), 51.6° (ISS), 55° (GPS), 63.4° (Molniya critical), 97-98° (SSO), 180° (retrograde limit)<br />
<span style={{ color: colors.text }}>Adaptive range:</span> 432 bins. Fine-grained near SSO (huge population), coarse near 180°<br />
<span style={{ color: colors.text }}>Note:</span> Inclination is the most stable element — rarely changes except under thrust. Best discriminator after SMA<br />
<span style={{ color: colors.text }}>Special:</span> Flag nodes containing 63.4° ± 0.5° as HAS_RESONANT (Molniya critical inclination)
</>
)}
{i === 2 && (
<>
<span style={{ color: colors.text }}>Range:</span> 0°360°, wraps around<br />
<span style={{ color: colors.text }}>Caution:</span> RAAN precesses rapidly due to J2 (~0.57°/day depending on altitude/inclination)<br />
<span style={{ color: colors.text }}>Adaptive range:</span> 48 coarse bins (fine subdivision pointless — RAAN drifts between TLE updates)<br />
<span style={{ color: colors.text }}>Reindex trigger:</span> When mean RAAN drift since last reindex exceeds bin width<br />
<span style={{ color: colors.text }}>Patricia compression:</span> This level frequently compressed away for near-circular orbits
</>
)}
{i === 3 && (
<>
<span style={{ color: colors.text }}>Range:</span> 0.0 (circular) to ~0.95 (extreme HEO)<br />
<span style={{ color: colors.text }}>Distribution:</span> Massively skewed — 90%+ of LEO objects have e &lt; 0.02<br />
<span style={{ color: colors.text }}>Adaptive range:</span> 24 bins. Usually just "near-circular" vs "eccentric" vs "highly elliptical"<br />
<span style={{ color: colors.text }}>Patricia compression:</span> Almost always compressed in LEO branches (everything is near-circular)<br />
<span style={{ color: colors.text }}>Query relevance:</span> Critical for pass prediction eccentric orbits have variable ground speed
</>
)}
{i === 4 && (
<>
<span style={{ color: colors.text }}>Range:</span> 0°360°<br />
<span style={{ color: colors.text }}>Stability:</span> Precesses due to J2, rate depends on inclination and eccentricity<br />
<span style={{ color: colors.text }}>Adaptive range:</span> 26 bins, often compressed away entirely<br />
<span style={{ color: colors.text }}>Patricia compression:</span> Most aggressively compressed level — rarely needed for spatial queries<br />
<span style={{ color: colors.text }}>Primary use:</span> Discriminating within dense clusters (e.g., Starlink shells at same a/i/Ω)
</>
)}
</div>
</div>
))}
<div style={{
marginTop: 12, padding: 12, borderRadius: 6,
background: colors.surface, border: `1px solid ${colors.border}`,
}}>
<div style={{ fontFamily: MONO, fontSize: 11, color: colors.purple, fontWeight: 600, marginBottom: 6 }}>
Patricia Path Compression
</div>
<div style={{ fontFamily: MONO, fontSize: 10, color: colors.textDim, lineHeight: 1.8 }}>
When a subtree has a single child at levels 24 (common in LEO), compress the path:<br />
<span style={{ color: colors.text }}>Before:</span> L0(a) → L1(i) → L2(Ω) → L3(e) → L4(ω) → leaf — 5 page reads<br />
<span style={{ color: colors.text }}>After:</span> L0(a) → L1(i) → COMPRESSED[Ω,e,ω stored in header] → leaf — 3 page reads<br />
<span style={{ color: colors.text }}>Savings:</span> 40% fewer I/O ops for typical LEO queries. Decompresses on split when population grows.
</div>
</div>
</div>
)}
</div>
);
}