Converge netx-topology MCP to dual-unit drain + orbit/polish mainline.

Trim public tools to 14, expose only kept layout actions/recipes, merge Fabric queries, drop createTopologyView/listTopologyViews, and align skill/docs with the live catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-09 23:18:53 +08:00
parent 9bcb8ee0e3
commit 79f929de0c
86 changed files with 31578 additions and 240 deletions

View file

@ -0,0 +1,325 @@
"""Tests for world-map strip-pack compose_views."""
from __future__ import annotations
import math
from netx_topology_mcp.layout_ops import LayoutParams, build_state_from_nodes_edges
from netx_topology_mcp.layout_ops.compose_views import (
ComposeBlock,
compose_into_state,
compose_params_from_overrides,
strip_pack_blocks,
)
from netx_topology_mcp.layout_tool import ACTIONS, run_layout_on_graph
def test_actions_include_compose_views() -> None:
assert "compose_views" not in ACTIONS
def test_strip_pack_preserves_relative_geometry() -> None:
a = ComposeBlock(
key="va",
positions={
"a1": (10.0, 20.0),
"a2": (110.0, 20.0),
"a3": (10.0, 120.0),
},
)
b = ComposeBlock(
key="vb",
positions={
"b1": (0.0, 0.0),
"b2": (50.0, 0.0),
},
)
merged, meta = strip_pack_blocks([a, b], pad=100.0)
assert meta["slots"] == 2
assert set(merged) == {"a1", "a2", "a3", "b1", "b2"}
# Relative offsets inside block A preserved.
dx = merged["a2"][0] - merged["a1"][0]
dy = merged["a2"][1] - merged["a1"][1]
assert abs(dx - 100.0) < 1e-6
assert abs(dy) < 1e-6
# Blocks occupy distinct slots (centroids far apart vs pad).
ax = sum(merged[n][0] for n in ("a1", "a2", "a3")) / 3
bx = sum(merged[n][0] for n in ("b1", "b2")) / 2
ay = sum(merged[n][1] for n in ("a1", "a2", "a3")) / 3
by = sum(merged[n][1] for n in ("b1", "b2")) / 2
assert abs(ax - bx) > 50 or abs(ay - by) > 50
def test_strip_pack_first_owner_wins_duplicates() -> None:
a = ComposeBlock(key="va", positions={"shared": (0.0, 0.0), "a": (10.0, 0.0)})
b = ComposeBlock(key="vb", positions={"shared": (999.0, 999.0), "b": (0.0, 0.0)})
# Portal-grow still glues the later shared block rigidly.
merged, meta = strip_pack_blocks([a, b], pad=50.0, merge_shared=True)
assert "shared" in merged
assert "a" in merged and "b" in merged
via = meta.get("merged_via") or {}
assert "rigid_shared" in via.values()
assert meta.get("order_mode") == "portal_grow"
def test_portal_grow_attaches_shared_before_orphan_misc() -> None:
"""Connected dual units grow together; huge misc without shared packs last."""
unit_ab = ComposeBlock(
key="ab",
positions={
"p_a": (0.0, 0.0),
"p_b": (200.0, 0.0),
"mid": (100.0, 40.0),
},
)
unit_bc = ComposeBlock(
key="bc",
positions={
"p_b": (0.0, 0.0),
"p_c": (200.0, 0.0),
"x1": (50.0, 30.0),
"x2": (100.0, 30.0),
"x3": (150.0, 30.0),
"x4": (80.0, -30.0),
},
)
misc = ComposeBlock(
key="misc",
positions={f"m{i}": (float(i) * 40.0, float(i % 3) * 40.0) for i in range(24)},
)
merged, meta = strip_pack_blocks(
[misc, unit_ab, unit_bc], pad=80.0, merge_shared=True
)
order = meta.get("order") or []
assert meta.get("order_mode") == "portal_grow"
# Misc is largest area but has no shared glue → last.
assert order[-1] == "misc"
# ab and bc share p_b → one of them merges rigidly onto the other.
via = meta.get("merged_via") or {}
assert via.get("ab") == "rigid_shared" or via.get("bc") == "rigid_shared"
# Shared portal has a single world coord.
assert "p_b" in merged
assert abs(merged["p_b"][0] - merged["p_b"][0]) < 1e-9
def test_portal_centroid_keeps_shared_units_near() -> None:
"""Units that share portals get closer origins than unrelated orphans."""
# Chain A—B—C via portals; orphan D has no shared nodes.
unit_a = ComposeBlock(
key="ua",
positions={
"p_ab": (0.0, 0.0),
"a1": (100.0, 0.0),
"a2": (100.0, 80.0),
},
)
unit_b = ComposeBlock(
key="ub",
positions={
"p_ab": (0.0, 0.0),
"p_bc": (200.0, 0.0),
"b1": (100.0, 40.0),
},
)
unit_c = ComposeBlock(
key="uc",
positions={
"p_bc": (0.0, 0.0),
"c1": (100.0, 0.0),
"c2": (100.0, 60.0),
},
)
orphan = ComposeBlock(
key="orphan",
positions={f"o{i}": (float(i) * 30.0, 0.0) for i in range(8)},
)
merged, meta = strip_pack_blocks(
[orphan, unit_c, unit_a, unit_b], pad=80.0, merge_shared=True
)
assert meta.get("pack_mode") == "portal_centroid"
origins = meta["origins"]
def _slot_c(key: str) -> tuple[float, float]:
sm = meta["slot_meta"][key]
ox, oy = origins[key]
return ox + 0.5 * sm["w"], oy + 0.5 * sm["h"]
ca, cb, cc, co = _slot_c("ua"), _slot_c("ub"), _slot_c("uc"), _slot_c("orphan")
# Portal-graph neighbors closer than the A–C skip.
d_ab = math.hypot(ca[0] - cb[0], ca[1] - cb[1])
d_bc = math.hypot(cb[0] - cc[0], cb[1] - cc[1])
d_ac = math.hypot(ca[0] - cc[0], ca[1] - cc[1])
assert d_ab < d_ac
assert d_bc < d_ac
# Orphans sit below the glued cluster.
glued_ymax = max(
origins[k][1] + meta["slot_meta"][k]["h"] for k in ("ua", "ub", "uc")
)
assert origins["orphan"][1] >= glued_ymax - 1e-6
# Rigid merge still glues the chain.
via = meta.get("merged_via") or {}
assert "rigid_shared" in via.values()
assert "p_ab" in merged and "p_bc" in merged
def test_fabric_bridges_pull_disjoint_units_closer() -> None:
"""No shared portal: fabric edge + higher bridge_boost / lower ideal_scale."""
unit_a = ComposeBlock(
key="ua",
positions={
"a0": (0.0, 0.0),
"a1": (120.0, 0.0),
"a2": (60.0, 80.0),
},
)
unit_b = ComposeBlock(
key="ub",
positions={
"b0": (0.0, 0.0),
"b1": (120.0, 0.0),
"b2": (60.0, 80.0),
},
)
unit_c = ComposeBlock(
key="uc",
positions={
"c0": (0.0, 0.0),
"c1": (120.0, 0.0),
"c2": (60.0, 80.0),
},
)
# Only A↔B has a fabric spoke; C is an unrelated peer (orphan strip below).
links = [("a1", "b0")]
blocks = [unit_a, unit_b, unit_c]
def _origin_gap(meta: dict, ka: str, kb: str) -> float:
oa, ob = meta["origins"][ka], meta["origins"][kb]
return math.hypot(oa[0] - ob[0], oa[1] - ob[1])
loose, meta_loose = strip_pack_blocks(
blocks,
pad=200.0,
merge_shared=True,
links=links,
fabric_bridges=True,
bridge_boost=4.0,
ideal_scale=0.85,
)
tight, meta_tight = strip_pack_blocks(
blocks,
pad=200.0,
merge_shared=True,
links=links,
fabric_bridges=True,
bridge_boost=40.0,
ideal_scale=0.3,
)
off, meta_off = strip_pack_blocks(
blocks, pad=200.0, merge_shared=True, links=links, fabric_bridges=False
)
assert meta_off.get("fabric_bridges") is False
assert meta_tight.get("fabric_bridges") is True
assert meta_tight.get("bridge_boost") == 40.0
assert meta_tight.get("ideal_scale") == 0.3
# Tunable spring: denser knobs shrink A–B slot gap.
assert _origin_gap(meta_tight, "ua", "ub") < _origin_gap(meta_loose, "ua", "ub") * 0.75
# Fabric glue puts C below the A–B component (not interleaved in strip).
glued_ymax = max(
meta_tight["origins"][k][1] + meta_tight["slot_meta"][k]["h"]
for k in ("ua", "ub")
)
assert meta_tight["origins"]["uc"][1] >= glued_ymax - 1e-6
del loose, tight, off
def test_compose_params_from_overrides_knobs() -> None:
knobs = compose_params_from_overrides(
{
"pad": 400,
"fabric_bridges": "on",
"bridge_boost": 12,
"ideal_scale": 0.4,
"spring_iters": 100,
"merge_shared": False,
}
)
assert knobs["pad"] == 400.0
assert knobs["fabric_bridges"] is True
assert knobs["bridge_boost"] == 12.0
assert knobs["ideal_scale"] == 0.4
assert knobs["spring_iters"] == 100
assert knobs["merge_shared"] is False
def test_dual_portal_flip_shortens_external_bridge() -> None:
"""When two portals fix the chord, pick the bank that shortens fabric spokes."""
unit_a = ComposeBlock(
key="ua",
positions={
"p0": (0.0, 0.0),
"p1": (200.0, 0.0),
"t": (100.0, -160.0),
},
)
unit_b = ComposeBlock(
key="ub",
positions={
"p0": (0.0, 0.0),
"p1": (200.0, 0.0),
"b": (100.0, 120.0),
},
)
links = [("b", "t")]
merged, _meta = strip_pack_blocks(
[unit_a, unit_b], pad=40.0, merge_shared=True, links=links
)
db = math.hypot(merged["b"][0] - merged["t"][0], merged["b"][1] - merged["t"][1])
# Unflipped bank would put b roughly opposite t (~280); flipped ~40.
assert db < 120.0
def test_strip_pack_merge_shared_false_keeps_first_only() -> None:
a = ComposeBlock(key="va", positions={"shared": (0.0, 0.0), "a": (10.0, 0.0)})
b = ComposeBlock(key="vb", positions={"shared": (999.0, 999.0), "b": (0.0, 0.0)})
merged, meta = strip_pack_blocks([a, b], pad=50.0, merge_shared=False)
assert meta.get("merge_shared") is False
assert "shared" in merged and "b" in merged
def test_compose_into_state_and_run_layout() -> None:
nodes = [
{"fabric_node_id": "a1", "name": "A1", "x": 0, "y": 0},
{"fabric_node_id": "a2", "name": "A2", "x": 10, "y": 0},
{"fabric_node_id": "b1", "name": "B1", "x": 0, "y": 0},
{"fabric_node_id": "b2", "name": "B2", "x": 10, "y": 0},
]
edges = [
{"a_node_id": "a1", "b_node_id": "a2"},
{"a_node_id": "b1", "b_node_id": "b2"},
]
st = build_state_from_nodes_edges(nodes, edges)
blocks = [
ComposeBlock("va", {"a1": (0.0, 0.0), "a2": (100.0, 0.0)}),
ComposeBlock("vb", {"b1": (0.0, 0.0), "b2": (80.0, 0.0)}),
]
op = compose_into_state(st, blocks, LayoutParams(), pad=200.0)
assert op.op == "compose_views"
assert op.params.get("slots") == 2
assert abs(op.state.positions["a2"][0] - op.state.positions["a1"][0] - 100.0) < 1e-6
try:
run_layout_on_graph(
nodes,
edges,
action="compose_views",
params={
"_compose_blocks": [
{"key": "va", "positions": {"a1": [0, 0], "a2": [100, 0]}},
{"key": "vb", "positions": {"b1": [0, 0], "b2": [80, 0]}},
],
"pad": 200,
},
)
raise AssertionError("compose_views should be unpublished")
except ValueError as e:
assert "unknown_action" in str(e)