API-контракт
00 / consumes & providesCONSUMES
Compo0.Canvas
Compo0.ProtectedZones[]
Compo0.Scene.objects[]
Point.id
Point.positionN {xN,yN}
Point.radiusShortN // owner: Compo 1.01
Point.visualWeightBase // owner: Compo 1.01
Point.visualWeightContextual? // only if fresh
Point.functionalRole // owner: Compo 1.00
Point.visualRoleCandidate // owner: Compo 1.00
Point.subroles[] // owner: Compo 1.00
Point.roleRigidity // owner: Compo 1.00
Point.gridCandidates[]? // owner: Compo 1.06
StylePreset?
Seed
PROVIDES
NodeTopology {
nodeId,
degree,
neighborPointIds[],
connectionIntentIds[],
angularGapsDeg[],
topologyClass,
localConnectivityScore
}
ConnectionIntentCandidate[] {
id,
fromPointId,
toPointId,
centerDistanceShortN,
directionAngle360Deg,
directionAngle180Deg,
roleCompatibilityScore,
angularSeparationScore,
localTopologyScore,
status,
reasonCodes[]
}
Граница ответственности: Compo 1.07 определяет топологию связи — какие точки рационально связать и сколько связей допустимо у узла. Он не строит визуальный маршрут линии, не выбирает толщину, кривизну, stroke или обход препятствий. Эти решения принадлежат серии Compo 2.xx.
METRIC OWNERSHIP: этот модуль владеет
node_degree_fit, angular_separation_score, role_compatibility_score, local_edge_topology_score и local_connectivity_score. Глобальный баланс, негативное пространство, иерархия, eye-flow и line routing — DEFERRED_INPUT.Математическая модель
01 / graph topologyV
vertices
Набор point-узлов.
E*
connection intents
Кандидаты логических связей, ещё не линии рендера.
deg
node degree
Количество выбранных соседей узла.
θ
direction
Физический угол направления между двумя центрами.
Graph topology G* = (V, E*)
Node i:
P_iN = (x_iN, y_iN)
r_i = radiusShortN
role_i = functionalRole
visual_i = visualRoleCandidate
Aspect-corrected pair vector:
dxPx = (x_jN - x_iN) * Canvas.W
dyPx = (y_jN - y_iN) * Canvas.H
dPx = sqrt(dxPx^2 + dyPx^2)
dShortN = dPx / min(Canvas.W, Canvas.H)
theta360 = (atan2(dyPx, dxPx) * 180/pi + 360) mod 360
theta180 = theta360 mod 180
Node degree:
deg(i) = |N(i)|
Angular gaps:
Theta_i = sort(theta360 of selected neighbors)
gap_k = wrappedDifference(Theta[k+1], Theta[k])
IMPORTANT:
E* is topology only.
Line geometry / routing / crossings are delegated to Compo 2.xx.
Aspect-ratio rule: расстояние и угол нельзя вычислять напрямую по
xN/yN на прямоугольном холсте. Все геометрические признаки пары вычисляются через pixel/aspect-corrected deltas.Количество связей / Degree
02 / node degree| functionalRole | Preferred degree | Hard max | Комментарий | Статус |
|---|---|---|---|---|
node | 2–5 | 7 | Основной сетевой узел; допускает локальный hub. | INITIAL HEURISTIC |
anchor | 1–3 | 5 | Обычно закрепляет одну или несколько осмысленных связей. | INITIAL HEURISTIC |
rhythm_member | 1–2 | 2 | Предпочтительно звено цепочки, а не hub. | INITIAL HEURISTIC |
marker | 0–1 | 2 | Связь разрешена только если marker действительно ссылается на объект. | INITIAL HEURISTIC |
free | 0–2 | 3 | Нет семантической обязанности быть связанным. | INITIAL HEURISTIC |
basePreferredDegree = profile(functionalRole)
visual modifier:
focus -> may raise preferred upper bound by +1
support -> no default change
background -> may lower preferred upper bound by -1
UNRESOLVED -> no modifier
roleRigidity:
hard -> semantic minimum/maximum may become HARD_CONSTRAINT
soft -> degree is scored, not forced
if deg(i) > hardMax(functionalRole):
remove lowest localTopologyScore incident intents first
NOTE:
visualRoleCandidate modifies preference only.
It MUST NOT replace functionalRole.
Разведение направлений
03 / angular separationGOOD
Связи расходятся достаточно различимыми направлениями.
for each selected pair (e_a,e_b) at node i:
delta = smallestAngularDistance(theta_a, theta_b)
angularSeparationScore =
smoothstep(hardMinGap, preferredGap, delta)
BAD / AMBIGUOUS
Несколько соседей лежат почти в одном направлении и визуально схлопываются в один пучок.
if delta < hardMinGap
AND StylePreset.allowBundledConnections != true:
reject lower-scoring intent
| Параметр | Default | Статус |
|---|---|---|
hardMinGapDeg | 8° | INITIAL HEURISTIC |
preferredGapDeg degree 2 | 35° | INITIAL HEURISTIC |
| degree 3 | 28° | INITIAL HEURISTIC |
| degree 4 | 22° | INITIAL HEURISTIC |
| degree 5+ | 16° | INITIAL HEURISTIC |
allowBundledConnections — STYLE_PRESET. При его включении малый угол перестаёт быть автоматическим дефектом и передаётся line-engine как намеренный bundle.Выбор соседей по расстоянию
04 / topology eligibilityLOCAL
0.04 <= dShortN <= 0.22
Предпочтительный локальный диапазон для большинства node/anchor связей.
BRIDGE
0.18 <= dShortN <= 0.55
Кандидат на связь между удалёнными группами. Требует более высокого role-fit.
LONG JUMP
dShortN > 0.55
Не запрещён автоматически, но требует явной семантической или глобальной причины.
effectiveDistance = dShortN // owner geometry derived here
Eligibility(i,j):
if i == j: REJECT
if semanticPairForbidden(i,j): REJECT
if both points are background and no style rule asks for network: DOWNRANK
if distance exceeds profile hard limit: REJECT
else: CREATE ConnectionIntentCandidate
Distance preference alone MUST NOT create a connection.
Nearest-neighbor-only is forbidden as a complete strategy.
INITIAL HEURISTIC Диапазоны — стартовые. Они подлежат калибровке и могут быть заменены
StylePreset.connectionDistanceProfile.Передача в линейный движок
05 / handoff to Compo 2.xxЧто делает 1.07
ConnectionIntentCandidate {
fromPointId,
toPointId,
preferredDirection,
topologyPriority,
bundleAllowed,
semanticFlags[],
hardForbiddenZones[]
}
Определяет факт и приоритет связи между узлами.
Что делает Compo 2.xx
DEFERRED:
line endpoints
straight / polyline / curve
obstacle routing
crossings
tangencies
stroke width
opacity / color
line visual weight
Вся визуальная геометрия связи принадлежит линейным модулям.
Anti-duplication: `route_cleanliness_score`, line crossing penalties и curvature не вычисляются в Compo 1.07. Иначе одна и та же линия получила бы двух владельцев — в point-system и line-system.
Локальная совместимость ролей
06 / role compatibility| Пара | Локальная политика | Статус |
|---|---|---|
node ↔ support visual | обычно допустимо; повышать только при достаточном semantic fit | local preference |
node ↔ background | понижать приоритет без сетевой/технической причины | local penalty |
rhythm_member ↔ rhythm_member | предпочитать цепочку; внутренний degree≈2, край degree≈1 | structural heuristic |
marker ↔ target | разрешать, если marker имеет явный routingTarget | semantic preference |
| несколько visual focus | не соединять автоматически только из-за их веса | DEFERRED GLOBAL HIERARCHY |
Важно: Compo 1.07 не устанавливает визуальный вес линии. Он может экспортировать
topologyPriority, а фактический line visual weight вычисляется владельцем в Compo 2.xx с учётом глобальной иерархии.Контекстные взаимодействия
07 / overridesHARD OVERRIDES
- forbidden semantic pair
- hard role-degree violation
- missing mandatory routingTarget for hard marker/anchor
- duplicate self-edge
- non-finite geometry
- owner-supplied hard conflict
DEFERRED INPUTS
point.conflict_score ← Compo 1.09
point.local_contrast ← Compo 1.10
global.balance_score ← future owner
global.negative_space_score ← future owner
global.hierarchy_score ← future owner
global.eye_flow_score ← future owner
line.route_feasibility ← Compo 2.xx
line.crossing_score ← Compo 2.xx
PRIORITY
1. hard safety / semantic constraints
2. mandatory role relations
3. owner-provided line feasibility
4. global composition overrides
5. local topology
6. style variation
7. seeded randomization
Локально сильная сеть может быть полностью перестроена после того, как line-engine обнаружит невозможный маршрут или global solver зарезервирует область как negative space.
Scoring
08 / owned local metricsConnection intent
S_intent =
0.30 * roleCompatibilityScore
+ 0.26 * distancePreferenceScore
+ 0.24 * angularSeparationScore
+ 0.20 * localDensityFitScore
// all weights = INITIAL_HEURISTIC
// no line routing metric here
Node topology
S_node =
0.34 * nodeDegreeFit
+ 0.28 * mean(selectedIntentScores)
+ 0.22 * angularDistributionScore
+ 0.16 * topologyCoherenceScore
localConnectivityScore = clamp(S_node,0,1)
Запрещённые дубли:
global.balance_score, global.hierarchy_score, global.negative_space_score, global.eye_flow_score, line crossing и route cleanliness не входят в локальный score 1.07.Геометрические примеры
09 / topology onlyТри connection intents имеют разные направления и хорошо различимы топологически.
Без explicit bundled style часть intents должна быть понижена или отклонена.
1.07 может считать пару топологически уместной, но окончательная линия создаётся только после проверки Compo 2.xx.
Интерактивная лаборатория
10 / deterministic topology demoДемо визуализирует только neighbor topology. Нарисованные прямые — условные chord-превью, а не выбранный line route.
Последовательный алгоритм
11 / execution1 INPUT
Получить point roles, positions, size/weight.
Получить point roles, positions, size/weight.
2 PAIRS
Сформировать допустимые пары.
Сформировать допустимые пары.
3 GEOMETRY
Aspect-corrected distance + direction.
Aspect-corrected distance + direction.
4 HARD
Удалить semantic/self/degree conflicts.
Удалить semantic/self/degree conflicts.
5 LOCAL SCORE
Role, distance, angular separation.
Role, distance, angular separation.
6 DEGREE FIT
Выбрать локальную topology.
Выбрать локальную topology.
7 LINE HANDOFF
Отправить intents в Compo 2.xx.
Отправить intents в Compo 2.xx.
8 GLOBAL
Применить overrides и вернуть topology.
Применить overrides и вернуть topology.
function buildPointTopology(scene, points, seed):
intents = []
for each unordered pair (a,b):
if !semanticEligible(a,b): continue
g = aspectCorrectedPairGeometry(a.positionN,b.positionN,scene.canvas)
if !distanceEligible(g, a, b): continue
intents.push({
from:a.id,
to:b.id,
geometry:g,
roleCompatibilityScore:scoreRoleCompatibility(a,b),
distancePreferenceScore:scoreDistance(g,a,b)
})
intents = hardTopologyFilter(intents)
selected = optimizeDegreeAndAngularSeparation(intents)
lineEvidence = Compo2.evaluateConnectionIntents(selected, scene) // DEFERRED
selected = applyOwnerProvidedLineEvidence(selected, lineEvidence)
globalEvidence = CompositionCore.evaluateDeferred(selected, scene)
selected = applyGlobalOverrides(selected, globalEvidence)
return buildNodeTopology(selected)
Тест-кейсы
12 / deterministic validationT01
Aspect correction
Canvas 1080×1350; A=(0.2,0.2), B=(0.4,0.4).
EXPECTED: dx=216px, dy=270px, angle≈51.34°, а не 45°.
Canvas 1080×1350; A=(0.2,0.2), B=(0.4,0.4).
EXPECTED: dx=216px, dy=270px, angle≈51.34°, а не 45°.
T02
Node / 4 хорошо разведённых соседа
functionalRole=node; четыре соседа примерно по четырём секторам.
EXPECTED: degree=4 допустим; localConnectivityScore высокий; line route пока не определён.
functionalRole=node; четыре соседа примерно по четырём секторам.
EXPECTED: degree=4 допустим; localConnectivityScore высокий; line route пока не определён.
T03
Angular bundle
5 соседей попали в сектор 24°; bundledConnections=false.
EXPECTED: часть intents rejected/downranked до удовлетворения hardMinGap/degree profile.
5 соседей попали в сектор 24°; bundledConnections=false.
EXPECTED: часть intents rejected/downranked до удовлетворения hardMinGap/degree profile.
T04
Rhythm member
functionalRole=rhythm_member; 6 потенциальных соседей.
EXPECTED: preferred degree 1–2; hard max 2; topology не превращает rhythm point в hub.
functionalRole=rhythm_member; 6 потенциальных соседей.
EXPECTED: preferred degree 1–2; hard max 2; topology не превращает rhythm point в hub.
T05
Line-engine reject
Топологически удачная связь не имеет допустимого маршрута из-за hard protected zones.
EXPECTED: owner-provided Compo 2.xx feasibility отклоняет intent; 1.07 выбирает следующий кандидат.
Топологически удачная связь не имеет допустимого маршрута из-за hard protected zones.
EXPECTED: owner-provided Compo 2.xx feasibility отклоняет intent; 1.07 выбирает следующий кандидат.
T06
Fresh contextual weight
visualWeightContextual имеет stale version.
EXPECTED: 1.07 использует visualWeightBase; stale contextual metric игнорируется.
visualWeightContextual имеет stale version.
EXPECTED: 1.07 использует visualWeightBase; stale contextual metric игнорируется.
Происхождение параметров
13 / parameter provenance| Параметр | Категория | Что это значит |
|---|---|---|
| pixel-corrected distance / atan2 / node degree | DERIVED | Строго выводится из входной геометрии и выбранных intents. |
| preferred degree ranges | INITIAL HEURISTIC | Стартовые диапазоны для solver; подлежат калибровке. |
| angular thresholds / distance profiles | INITIAL HEURISTIC | Не универсальные законы; должны проходить corpus/human-rating calibration. |
| bundled connections / dense technical network | STYLE PRESET | Выбор визуального языка. |
| line route / crossings / stroke | DEFERRED INPUT | Приходят от владельцев Compo 2.xx. |
| global balance / hierarchy / eye flow / negative space | DEFERRED INPUT | Приходят от глобальных владельцев соответствующих метрик. |
DATA FOR LAYOUT ENGINE
14 / machine block{
"module_id": "Compo 1.07",
"name": "point_as_connection_node",
"scope": "point topology only; no rendered line routing",
"consumes": [
"Compo0.Canvas",
"Compo0.ProtectedZones[]",
"Point.positionN",
"Point.radiusShortN",
"Point.visualWeightBase",
"Point.visualWeightContextual?@fresh",
"Point.functionalRole",
"Point.visualRoleCandidate",
"Point.subroles[]",
"Point.roleRigidity",
"Point.gridCandidates[]?",
"StylePreset?",
"Seed"
],
"provides": [
"NodeTopology",
"ConnectionIntentCandidate[]",
"ConnectionIntentCandidate.centerDistanceShortN",
"ConnectionIntentCandidate.directionAngle360Deg",
"ConnectionIntentCandidate.directionAngle180Deg",
"NodeTopology.localConnectivityScore"
],
"owns_metrics": [
"point_topology.node_degree_fit",
"point_topology.angular_separation_score",
"point_topology.role_compatibility_score",
"point_topology.distance_preference_score",
"point_topology.local_edge_topology_score",
"point_topology.local_connectivity_score"
],
"functional_role_degree_profile": {
"node": {"preferred":[2,5],"hardMax":7,"source":"INITIAL_HEURISTIC"},
"anchor": {"preferred":[1,3],"hardMax":5,"source":"INITIAL_HEURISTIC"},
"rhythm_member": {"preferred":[1,2],"hardMax":2,"source":"INITIAL_HEURISTIC"},
"marker": {"preferred":[0,1],"hardMax":2,"source":"INITIAL_HEURISTIC"},
"free": {"preferred":[0,2],"hardMax":3,"source":"INITIAL_HEURISTIC"}
},
"defaults": {
"hardMinAngularGapDeg": {"value":8,"source":"INITIAL_HEURISTIC"},
"distanceMetric": "short_side_normalized_aspect_corrected",
"allowBundledConnections": {"value":false,"source":"STYLE_PRESET"}
},
"deferred_inputs": {
"point.conflict_score": "Compo 1.09",
"point.local_contrast": "Compo 1.10",
"line.route_feasibility": "Compo 2.xx",
"line.crossing_score": "Compo 2.xx",
"line.visual_weight": "Compo 2.xx",
"global.balance_score": "future owner",
"global.negative_space_score": "future owner",
"global.hierarchy_score": "future owner",
"global.eye_flow_score": "future owner"
},
"forbidden_metric_ownership": [
"line.route_cleanliness_score",
"line.crossing_score",
"line.stroke_weight",
"global.balance_score",
"global.negative_space_score",
"global.hierarchy_score",
"global.eye_flow_score"
],
"recompute_when": [
"point position changes",
"functionalRole/visualRole changes",
"effective point weight changes materially",
"line owner rejects selected intent",
"global solver overrides topology"
],
"finality": "LOCAL_TOPOLOGY_PROPOSAL_ONLY"
}