COMPO 1.07 / POINT SYSTEM / RELATIONAL GEOMETRY

Точка как
узел связей

Модуль определяет, когда точка становится узлом графа, сколько связей ей разрешено иметь, как выбирать соседей, разводить углы, маршрутизировать линии и не создавать визуальную грязь. Локальная топология не имеет права ломать глобальную композицию.

API-контракт

00 / consumes & provides

CONSUMES

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 topology
V
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
functionalRolePreferred degreeHard maxКомментарийСтатус
node2–57Основной сетевой узел; допускает локальный hub.INITIAL HEURISTIC
anchor1–35Обычно закрепляет одну или несколько осмысленных связей.INITIAL HEURISTIC
rhythm_member1–22Предпочтительно звено цепочки, а не hub.INITIAL HEURISTIC
marker0–12Связь разрешена только если marker действительно ссылается на объект.INITIAL HEURISTIC
free0–23Нет семантической обязанности быть связанным.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 separation

GOOD

Связи расходятся достаточно различимыми направлениями.

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Статус
hardMinGapDegINITIAL HEURISTIC
preferredGapDeg degree 235°INITIAL HEURISTIC
degree 328°INITIAL HEURISTIC
degree 422°INITIAL HEURISTIC
degree 5+16°INITIAL HEURISTIC
allowBundledConnectionsSTYLE_PRESET. При его включении малый угол перестаёт быть автоматическим дефектом и передаётся line-engine как намеренный bundle.

Выбор соседей по расстоянию

04 / topology eligibility

LOCAL

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 fitlocal preference
node ↔ backgroundпонижать приоритет без сетевой/технической причиныlocal penalty
rhythm_member ↔ rhythm_memberпредпочитать цепочку; внутренний degree≈2, край degree≈1structural heuristic
marker ↔ targetразрешать, если marker имеет явный routingTargetsemantic preference
несколько visual focusне соединять автоматически только из-за их весаDEFERRED GLOBAL HIERARCHY
Важно: Compo 1.07 не устанавливает визуальный вес линии. Он может экспортировать topologyPriority, а фактический line visual weight вычисляется владельцем в Compo 2.xx с учётом глобальной иерархии.

Контекстные взаимодействия

07 / overrides

HARD 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 metrics

Connection 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
GOOD: distinct neighbor directions
Три connection intents имеют разные направления и хорошо различимы топологически.
AMBIGUOUS: angular bundle
Без explicit bundled style часть intents должна быть понижена или отклонена.
TOPOLOGY ACCEPTED → ROUTE DEFERREDline-engine decides feasibility
1.07 может считать пару топологически уместной, но окончательная линия создаётся только после проверки Compo 2.xx.

Интерактивная лаборатория

10 / deterministic topology demo
Демо визуализирует только neighbor topology. Нарисованные прямые — условные chord-превью, а не выбранный line route.

Последовательный алгоритм

11 / execution
1 INPUT
Получить point roles, positions, size/weight.
2 PAIRS
Сформировать допустимые пары.
3 GEOMETRY
Aspect-corrected distance + direction.
4 HARD
Удалить semantic/self/degree conflicts.
5 LOCAL SCORE
Role, distance, angular separation.
6 DEGREE FIT
Выбрать локальную topology.
7 LINE HANDOFF
Отправить intents в Compo 2.xx.
8 GLOBAL
Применить 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 validation
T01
Aspect correction
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 пока не определён.
T03
Angular bundle
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.
T05
Line-engine reject
Топологически удачная связь не имеет допустимого маршрута из-за 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 игнорируется.

Происхождение параметров

13 / parameter provenance
ПараметрКатегорияЧто это значит
pixel-corrected distance / atan2 / node degreeDERIVEDСтрого выводится из входной геометрии и выбранных intents.
preferred degree rangesINITIAL HEURISTICСтартовые диапазоны для solver; подлежат калибровке.
angular thresholds / distance profilesINITIAL HEURISTICНе универсальные законы; должны проходить corpus/human-rating calibration.
bundled connections / dense technical networkSTYLE PRESETВыбор визуального языка.
line route / crossings / strokeDEFERRED INPUTПриходят от владельцев Compo 2.xx.
global balance / hierarchy / eye flow / negative spaceDEFERRED 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" }