COMPO · 2.14 · BOUNDARY / DIVIDER SYSTEM

Линия как
граница и
разделитель.

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

API-контракт

00 / ownership

CONSUMES

Line Line.role Line.subrole? Line.rigidity Line.directed Line.geometryType Line.pathGeometry Line.strokeWidthPx? ParentRegion ParentRegion.geometry ParentRegion.boundsN OPTIONAL: Compo2.07.coverageRatio Compo2.07.spatialSpanClass Compo2.10.IntersectionRelation[] Compo2.11.LineShapeContactRelation[] ProtectedZones[] StylePreset.boundaryDivider?

PROVIDES

BoundaryRelation PartitionDescriptor SideAssignment[] DividerCandidate[] BoundaryCorrectionRequest[] line.boundary.sideSeparability line.boundary.barrierCompleteness line.boundary.partitionValidity line.boundary.enclosureFit line.boundary.boundaryConformity line.boundary.localBoundaryScore line.boundary.confidence

DOES NOT OWN

global.balanceScore global.negativeSpaceScore global.hierarchyScore global.eyeFlowScore Gestalt.groupingScore object.semanticGrouping line.visualWeight // 2.06 line.roleLengthFit // 2.07 line-shape contact // 2.11 grid alignment // 2.13
Разделение ответственности: 2.07 отвечает на вопрос «достаточна ли длина/охват для роли». 2.14 отвечает на другой вопрос: «образует ли путь реальную границу или разделение области».

Режимы границы

01 / semantic modes
|
open divider

Открытый path соединяет две части boundary родительской области и разрезает её на компоненты.

closed enclosure

Замкнутый path образует внутреннюю и внешнюю область.

edge boundary

Линия совпадает с существующей частью границы region/object.

implicit separator

Линия не обязана физически перекрывать весь region, если upstream явно задаёт более мягкий separator-mode.

BoundaryMode = OPEN_DIVIDER | CLOSED_ENCLOSURE | EDGE_BOUNDARY | IMPLICIT_SEPARATOR mode may derive from: Line.role == divider Line.role == frame Line.subrole explicit semantic request mode MUST NOT be guessed from visual appearance alone when semantics are available.

По разные стороны линии

02 / side assignment

Straight divider

u = canonical axis unit n = (-u.y, u.x) s(P) = dot(P - P0, n) if s < -eps → SIDE_A if s > +eps → SIDE_B else → ON_BOUNDARY

DERIVED SIDE_A/B — канонические метки. Они не означают автоматически «лево/право», «до/после» или семантические группы.

Object footprint

sample object footprint or use exact polygon test if all samples same side: assignment = SIDE_A|SIDE_B if footprint crosses barrier: assignment = CROSSES_BOUNDARY if insufficient geometry: assignment = AMBIGUOUS confidence ↓

Центр объекта — допустимый fallback, но не доказательство, что весь объект находится по одну сторону.

Curved divider: глобальные SIDE_A/B корректны только для simple separator внутри известной parent region. Для произвольной self-intersecting curve «левая сторона кривой» не равна устойчивой partition semantics — используем топологическое разбиение region.

Топология разделения

03 / region minus barrier

Canonical model

barrierShape = strokeToArea( Line.path, effectiveBarrierWidth ) remainingRegion = ParentRegion - barrierShape components = connectedComponents( remainingRegion ) partitionCount = components.length

Для идеализированной zero-width линии возможно использовать planar subdivision, но stroke-area модель ближе к реальному видимому разделителю.

Expected partition

OPEN_DIVIDER default: expectedComponents = 2 CLOSED_ENCLOSURE: expected: inside region outside region complex explicit mode: expectedComponents may be provided upstream

Если линия создаёт 3–4 компонента, это не обязательно ошибка, но такое поведение не должно случайно проходить как обычный divider.

partitionValidity = componentCountFit( actualComponents, expectedComponents ) * topologyConfidence component descriptors: areaPx areaRatio boundsN centroidN IMPORTANT: component area ratios are GEOMETRY ONLY. Their aesthetic balance is DEFERRED.
Никакого balance leakage: если divider создаёт области 30/70, 2.14 просто сообщает площади. Решение, хорошо ли это композиционно, принадлежит будущему balance/negative-space layer.

Barrier completeness

04 / does the separator actually close the route?

Endpoint-to-boundary

d0 = distance( startPoint, ParentRegion.boundary ) d1 = distance( endPoint, ParentRegion.boundary )

Для open divider оба endpoint обычно должны достигать подходящих участков boundary.

Gap descriptor

endpointGapMax = max(d0,d1) gapFit = 1 - clamp( endpointGapMax / endpointGapTauPx, 0,1 )

HEURISTIC Только быстрый proxy; топологический partition test сильнее.

Barrier completeness

if topology available: barrierCompleteness = partition actually formed else: barrierCompleteness = endpointGapFit * coverageProxy confidence ↓

Fallback никогда не должен выдавать ту же confidence, что exact region partition.

Stroke-aware: effective barrier width может учитывать фактический stroke width из 2.06/rendering geometry. Если ширина неизвестна, zero-width/topological fallback допускается с явной provenance.

Closed boundary / enclosure

05 / frame-like path

Closure gap

closureGapPx = distance( startPoint, endPoint ) isClosed = path.closed OR closureGapPx <= closureTauPx

Enclosed area

if simple closed path: enclosedAreaPx = abs( signedArea(flatten(path)) ) enclosedAreaRatio = enclosedArea / parentRegionArea

DERIVED Площадь — descriptor, не эстетический score.

Self-intersection

if closed path self-intersects: topology becomes multi-region simple enclosure confidence ↓ / invalid unless explicitly allowed

Self-intersection relation может потребляться из 2.10 / geometry-owner topology.

2.14 не превращает любой closed decorative loop в frame. Enclosure semantics должны приходить из role/subrole/request, а geometry только проверяет, выполняются ли они.

Boundary conformity

06 / line following an existing edge

Distance to target boundary

sample path by arc length d_i = distance( P_i, targetBoundary ) boundaryRMS = sqrt(mean(d_i²)) boundaryMax = max(d_i)

Для EDGE_BOUNDARY линия должна следовать заданному edge segment, а не просто быть рядом с объектом.

Tangent agreement

for matched boundary point: Tp = line tangent Tb = boundary tangent axisDeviation_i = angleModulo180(Tp,Tb) boundaryTangentFit = mean(kernel(deviation))

Позиция и направление оцениваются отдельно.

Local boundary scoring

07 / metric ownership

OWNS

line.boundary.sideSeparability line.boundary.barrierCompleteness line.boundary.partitionValidity line.boundary.enclosureFit line.boundary.boundaryConformity line.boundary.localBoundaryScore line.boundary.confidence descriptors: partitionCount componentAreaRatio[] closureGapPx endpointBoundaryGap[] boundaryRMS boundaryMax

DOES NOT OWN

global.balanceScore negativeSpaceScore semanticGroupingScore hierarchyScore eyeFlowScore line.roleLengthFit line-shape contact metrics gridFit rhythmScore
switch BoundaryMode: OPEN_DIVIDER: localBoundaryScore = weightedMeanApplicable([ barrierCompleteness, partitionValidity, sideSeparability ]) CLOSED_ENCLOSURE: localBoundaryScore = weightedMeanApplicable([ enclosureFit, partitionValidity ]) EDGE_BOUNDARY: localBoundaryScore = boundaryConformity IMPLICIT_SEPARATOR: localBoundaryScore = stylePolicy(...) // components are diagnostic by default. // future Compo 2.20 votes composite-first.

Correction requests

08 / controlled repair

EXTEND

Дотянуть endpoint до parent boundary.

REROUTE

Изменить path, если divider не создаёт ожидаемую partition.

CLOSE

Замкнуть frame/enclosure при малом closure gap.

RELABEL MODE

Если geometry не соответствует семантике, предложить upstream сменить mode — но не делать это молча.

BoundaryCorrectionRequest { lineId, regionId, reasonCode: INCOMPLETE_BARRIER | WRONG_PARTITION_COUNT | OPEN_ENCLOSURE | SELF_INTERSECTING_ENCLOSURE | LOW_BOUNDARY_CONFORMITY | OBJECT_CROSSES_DIVIDER, targetOwner: "Compo2.01" | "Compo2.04" | "Compo2.05" | "Compo2.07" | "GLOBAL_SOLVER" | "SEMANTIC_LAYER", requestedChange, bounds?, confidence }

Визуальная лаборатория

09 / partition cases
AB
VALID OPEN DIVIDER: path достигает двух участков parent boundary и образует две компоненты.
INCOMPLETE: визуально «почти divider», но region остаётся связной вокруг концов линии.
ENCLOSURE: closed path создаёт внутреннюю область и внешнюю область parent region.
Кривая тоже может быть валидным divider, если она simple и реально соединяет boundary так, что parent region распадается на две компоненты.
OBJECT CROSSES DIVIDER: 2.14 фиксирует footprint relation; решать, допустимо ли это семантически, должен внешний слой.

Детерминированные тесты

10 / regression

TEST 01 · side sign

divider: P0=(50,0) P1=(50,100) canonical normal=(-1,0) point (25,50) point (75,50) expected: opposite side labels

TEST 02 · full split

region=100×100 rect divider: (50,0)→(50,100) expected: partitionCount=2 barrierCompleteness=high

TEST 03 · floating line

same region divider: (50,10)→(50,90) expected: region remains connected partitionCount=1 incomplete barrier

TEST 04 · closure

closed square: (20,20) (80,20) (80,80) (20,80) (20,20) expected: closureGap=0 enclosure valid

TEST 05 · ambiguous semantics

undirected divider expected: SIDE_A / SIDE_B only MUST NOT infer: left-group/right-group semantic meaning

TEST 06 · curve split

simple curve top boundary → bottom boundary expected: may form valid 2-component partition must not require straightness

TEST 07 · balance leak

component areas: 30% / 70% expected: report ratios MUST NOT: score balance here

TEST 08 · fallback

exact region topology unavailable endpoint gaps=0 coverage high expected: proxy completeness confidence reduced

TEST 09 · ownership

divider too short 2.14 MAY: request EXTEND MUST NOT: rewrite 2.07 roleLengthFit itself

LIVE REFERENCE TESTS

IDInputComputedStatus

DATA FOR LAYOUT ENGINE

11 / machine contract
{ "module": "Compo 2.14", "name": "line_as_boundary_and_divider", "version": "1.0", "scope": "LOCAL_BOUNDARY_AND_PARTITION_GEOMETRY", "consumes": [ "Compo2.00.Line", "Compo2.00.LinePathGeometry", "ParentRegion", "ParentRegion.geometry", "Compo2.06.Line.strokeWidthPx?", "Compo2.07.coverageRatio?", "Compo2.07.spatialSpanClass?", "Compo2.10.IntersectionRelation[]?", "Compo2.11.LineShapeContactRelation[]?", "ProtectedZones[]?", "StylePreset.boundaryDivider?" ], "provides": [ "BoundaryRelation", "PartitionDescriptor", "SideAssignment[]", "DividerCandidate[]", "BoundaryCorrectionRequest[]" ], "boundary_modes": [ "OPEN_DIVIDER", "CLOSED_ENCLOSURE", "EDGE_BOUNDARY", "IMPLICIT_SEPARATOR" ], "side_assignment": [ "SIDE_A", "SIDE_B", "ON_BOUNDARY", "CROSSES_BOUNDARY", "AMBIGUOUS" ], "owns_metrics": [ "line.boundary.sideSeparability", "line.boundary.barrierCompleteness", "line.boundary.partitionValidity", "line.boundary.enclosureFit", "line.boundary.boundaryConformity", "line.boundary.localBoundaryScore", "line.boundary.confidence" ], "descriptors_only": [ "partitionCount", "componentAreaRatio[]", "closureGapPx", "endpointBoundaryGap[]", "boundaryRMS", "boundaryMax", "enclosedAreaRatio" ], "metric_policy": { "primary_vote": "line.boundary.localBoundaryScore", "components": "diagnostic_only_by_default", "component_area_ratios": "GEOMETRY_ONLY_NOT_BALANCE", "semantic_group_meaning": "DEFERRED" }, "initial_heuristics": { "endpoint_gap_tau_shortN": { "value": 0.015, "source": "INITIAL_HEURISTIC" }, "closure_tau_shortN": { "value": 0.010, "source": "INITIAL_HEURISTIC" }, "boundary_follow_rms_tau_shortN": { "value": 0.008, "source": "INITIAL_HEURISTIC" }, "topology_raster_fallback_resolution": { "value": 512, "source": "IMPLEMENTATION_HEURISTIC" } }, "hard_rules": [ "boundary_mode_should_come_from_semantics_when_available", "2.14_must_not_recompute_2.07_role_length_fit", "2.14_must_not_own_global_balance_or_negative_space", "curved_divider_side_labels_require_valid_simple_partition_context", "self_intersecting_enclosure_requires_explicit_complex_mode_or_repair", "fallback_partition_analysis_must_reduce_confidence", "object_center_only_side_assignment_is_fallback_not_exact_footprint_test", "foreign_geometry_changes_must_be_requests" ], "deferred_inputs": [ "global.balanceScore", "global.negativeSpaceScore", "global.hierarchyScore", "global.eyeFlowScore", "Gestalt.commonRegionScore", "semantic.objectGrouping", "Compo2.16.lineColorContrast" ], "recompute_when": [ "LinePathGeometry changes", "ParentRegion geometry changes", "stroke width changes", "BoundaryMode changes", "2.11 parent-boundary contact changes", "object footprints used for side assignment change", "StylePreset.boundaryDivider changes" ], "finality": "LOCAL_PARTITION_PROPOSAL_ONLY" }
ARCHITECTURE CHECKPOINT: 2.14 отделяет функциональное разделение пространства от длины линии и от эстетического баланса областей. Оно создаёт topology/side/partition descriptors, которые позже смогут потреблять Gestalt, negative-space и global composition layers.