Линия → операционная граница
2.22 отвечает на узкий вопрос: можно ли из уже существующей canonical line geometry построить корректную локальную mask/crop/seam-операцию и какие области она создаёт. Модуль не оценивает красоту композиции и не принимает глобальное решение, что именно следует обрезать.
Binary mask/crop/seam внутри одного bounded host region при explicit intent.
Derived regions не мутируют source line, host geometry, image, typography или grid.
Global crop choice, focal-point preservation, balance, negative space, hierarchy, image composition.
CONSUMES / PROVIDES
LinePathGeometry // canonical line, 2.01+ HostRegion // production interface via HostBoundaryAdapter Compo2.14.PartitionDescriptor? // canonical 2.14 output, if available Compo2.14.BoundaryRelation? // canonical 2.14 output, if available MaskCropSeamTarget // explicit intent GeometryToleranceContext // owner-provided, if approximate StylePreset? // seam rendering parameters only Compo0.seed? // only for deterministic unresolved ordering
MaskCropSeamResult {
status,
applicability,
confidence,
mode,
topology,
seamPath,
regions[],
selectedRegionIds[],
boundaryContacts[],
areaFractions[], // DESCRIPTORS ONLY
hardViolations[],
unresolvedInputs[],
repairHints[],
provenance,
audit
}Ownership: новая операция, не новая геометрия
2.22 владеет производной операционной геометрией mask/crop/seam. Source line остаётся у своих owner-модулей, а boundary quality из 2.14 не пересчитывается под новым именем.
Derived operation
line.mask.topology
line.mask.regions[]
line.crop.selectedRegionIds[]
line.seam.operationDescriptor
line.mask.audit
Upstream evidence
Canonical line path, line/host contacts, 2.14 PartitionDescriptor/BoundaryRelation evidence, explicit semantic intent, host bounds. Если 2.14 уже экспортировал partition evidence, 2.22 использует его как evidence, но не создаёт второй localBoundaryScore.
Foreign layers
Source control points, global balance, global negative space, image focal point, typography, final crop choice, candidate ranking, 2.20 local score.
OWNS_METRICS
2.14 отвечает за линию как boundary/divider и её локальные boundary relations. 2.22 отвечает за операционное превращение границы в clip regions / crop side / seam relation. Один модуль не дублирует score другого.
Три режима одной геометрии
Mode всегда задаётся intent-ом. Наличие линии рядом с изображением не означает автоматически crop; rendered stroke не означает автоматически seam.
mask_boundary
Линия создаёт две области или inside/outside loop внутри host. Результат — clip-able region geometry. Line stroke может вообще не рендериться.
crop_edge
Из валидных regions explicit keepSide выбирает сохраняемую область. 2.22 не решает, какая сторона «лучше» без target.
visual_seam
Одна seamPath связывает две layer regions. Шов может быть visible, invisible, gap, overlap или feathered — это operation/style descriptor, а не изменение исходной line geometry.
Canonical target
MaskCropSeamTarget {
mode: "mask_boundary|crop_edge|visual_seam",
topology: "OPEN_SPLIT|CLOSED_LOOP|AUTO",
keepSide?:
{ type:"ANCHOR_POINT", point:{x,y} } |
{ type:"PATH_RELATIVE", value:"LEFT|RIGHT" } |
{ type:"REGION_ID", value:string },
extensionPolicy: "NONE|RAY_TO_HOST|TANGENT_TO_HOST",
binaryOnly: true,
seamRender?: "INVISIBLE|STROKE|GAP|OVERLAP|FEATHER",
seamWidth?: number,
featherWidth?: number
}
HostRegion через boundary adapter
Production-contract 2.22 не ограничен прямоугольником. Он требует host-boundary primitives. Текущий inline JS ниже — только RectHostRegion reference fixture и не является обещанием generic polygon implementation.
HostBoundaryAdapter
HostBoundaryAdapter {
hostId,
area(): number,
contains(point): bool,
isOnBoundary(point, tolerance): bool,
boundaryParam(point): number | N/A,
pointAtBoundaryParam(t): Point,
boundaryChainsBetween(a,b): [Chain,Chain],
validateSimpleRegion?(polygon): Result,
exactness: "EXACT|APPROXIMATE",
confidence: 0..1,
provenance
}RectHostRegion
{
x, y, w, h
}
Inline JS implements:
rectArea
pointInRect
boundaryParam
pointAtParam
boundaryChainCW
This is a deterministic test adapter,
NOT the generic production host contract.HostBoundaryAdapter, 2.22 возвращает unsupported/unresolved geometry. Нельзя незаметно подменить host его bounding box.Topology first: сначала допустимость, потом regions
Для binary mask важнее топология, чем декоративная форма линии. Простая кривая может быть валидным seam; самопересекающаяся — уже создаёт неоднозначное множество областей и выходит за scope 2.22.
| Topology | Условие | Результат | Policy |
|---|---|---|---|
| OPEN_SPLIT | simple open path; два различных контакта с host boundary | две regions внутри host | VALID |
| CLOSED_LOOP | simple closed path; loop лежит внутри host | inside + outside-with-hole | VALID |
| OPEN_NEEDS_EXTENSION | 0/1 boundary contacts; explicit extensionPolicy разрешён | derived seamPath после extension | VALID WITH LOWER CONFIDENCE |
| SELF_INTERSECTING | non-adjacent segments intersect | 3+ / ambiguous regions | HARD INVALID for binaryOnly |
| DEGENERATE | zero-length / collapsed host / coincident contacts | нет устойчивой partition | HARD INVALID |
Screen coordinate convention
Production contact tolerance приходит из GeometryToleranceContext и несёт provenance. 2.22 не вводит универсальный pixel threshold. Inline reference tests используют отдельный numerical EPS только для синтетических fixtures.
Open split: path + две цепочки host boundary
Для simple open path с двумя boundary contacts host boundary имеет два маршрута между endpoints. Каждый маршрут вместе с seamPath образует одну замкнутую region.
Region geometry
Новые polygons — производные от source line и host. Они не переписывают source path. Audit хранит исходный path hash, host hash и applied extension/closure steps.
Area → aesthetics
0.50 / 0.50 не «лучше» 0.30 / 0.70. Area ratio становится score только у будущего owner, если explicit target действительно требует определённой пропорции.
Curves / polylines
Polyline использует собственные vertices без resampling, если topology можно вычислить точно. Curve должна предоставлять exact planar splitting primitive либо owner-provided approximation с confidence. Arc-length sampling допустим только как fallback и не должен менять результат при разумном увеличении sampling density.
Crop = region selection, не эстетическая догадка
После получения regions crop-операция обязана знать, какую область сохранить. Если target этого не сообщает, 2.22 возвращает unresolved input вместо выбора «большей» или «центральной» стороны.
ANCHOR_POINT
Предпочтительный reversal-invariant способ: сохраняется region, содержащая заданную точку. Если точка лежит на seam/tolerance band — intent unresolved.
PATH_RELATIVE
LEFT/RIGHT привязаны к traversal direction. Reversal source path требует одновременного swap semantic side, если визуальный результат должен сохраниться.
REGION_ID
Допустим, если region identity пришла из внешнего semantic layer. 2.22 проверяет существование id, но не изобретает semantic labels.
CropResult
crop: {
keepSideResolved: true|false,
selectorType: "ANCHOR_POINT|PATH_RELATIVE|REGION_ID",
selectedRegionIds: ["region:A"],
rejectedRegionIds: ["region:B"],
clipGeometry: RegionGeometry,
areaFraction: 0.63, // descriptor only
selectionProvenance: "EXPLICIT_TARGET"
}
2.22 не знает, где лицо, товар, текстовый блок или focal point. Если будущий image owner говорит «этот crop нарушает focal constraint», это внешний hard/soft constraint и correction request, а не скрытая логика 2.22.
Visual seam: граница может быть невидимой
Seam — это отношение двух областей вокруг одной path. Рендер stroke является только одним из вариантов; clipping mask может использовать ту же path без видимой линии.
| seamRender | Операция | Source path mutation? | Примечание |
|---|---|---|---|
| INVISIBLE | два слоя соприкасаются по seamPath | NO | чистая mask boundary |
| STROKE | отдельный render-layer рисует line stroke | NO | stroke style принадлежит line rendering owner |
| GAP | две masks отступают от seam | NO | offset operation; width — style parameter |
| OVERLAP | regions перекрываются в seam band | NO | порядок слоёв приходит извне |
| FEATHER | alpha transition вокруг seam | NO | feather profile — rendering/mask parameter |
Hard constraints и edge cases
Неоднозначная topology не компенсируется красивым stroke. Feasibility проверяется до любой downstream interpretation.
| Case | Result | Repair route |
|---|---|---|
| hostRegion missing / zero area | HARD INVALID | caller / host owner |
| source path has < 2 distinct points | HARD INVALID | line geometry owner / 2.19 |
| self-intersection with binaryOnly=true | HARD INVALID | 2.19 regenerate simple path |
| open split has 0/1 boundary contacts and extensionPolicy=NONE | UNRESOLVED / INVALID | target or line owner |
| two contacts collapse to same boundary point | DEGENERATE | line owner |
| crop target missing keepSide | UNRESOLVED | semantic/crop caller; no auto-choice |
| anchor point on seam within tolerance | UNRESOLVED | caller supplies disambiguated anchor |
| closed loop extends outside host | HARD INVALID | line owner or host/crop caller |
| global.balance supplied | IGNORED / DEFERRED | 2.23 / future global owner |
Если mode не применим к текущему semantic request, applicability=0 и result не участвует ни в scoring, ни в penalty. Отсутствие crop intent не является плохим crop.
Target / interpretation: факт отделён от решения
Сначала 2.22 строит topology и regions, потом применяет explicit keep-side / seam operation. Это защищает систему от скрытого правила «большая сторона лучше».
Geometry descriptors
topology = OPEN_SPLIT contacts = 2 areaFractions = [0.37, 0.63] selfIntersections = 0 sourceReversed = false exactGeometry = true
Explicit target
mode = CROP_EDGE keepSide = ANCHOR_POINT(0.75W, 0.50H) → selectedRegion = region:B No target? → selectedRegion = null → status = unresolved_target
Derived variants ≠ candidate generator
2.22 может получить несколько математически допустимых derived regions, но не запускает новый эстетический search.
Если API обязан сериализовать две равноправные regions до semantic selection, порядок задаётся stable geometry hash / canonical host-boundary traversal, а не случайной площадью или DOM order.
Correction requests: исправляет владелец
2.22 диагностирует невозможность маски, но не двигает чужие endpoints и не переписывает изображение.
Repair hint examples
{
code:"MASK_NEEDS_BOUNDARY_CONTACT",
targetOwner:"Compo2.19",
request:"regenerate endpoint to host boundary",
evidence:{contactCount:1}
}
{
code:"CROP_SIDE_UNRESOLVED",
targetOwner:"SemanticCropCaller",
request:"provide keepSide anchor"
}Dirty recompute
line path changed → recompute line/host contacts → recompute topology → recompute regions → re-apply keepSide style feather changed → topology unchanged → only seam operation descriptor dirty
Визуальная лаборатория
Одна и та же линия показана как stroke, как mask boundary и как crop selector. Цветные области — derived regions; переключение keep-side не изменяет source path.
Path остаётся тем же
PolyLine используется как seamPath. Fill-полигоны вычисляются отдельно и могут меняться по crop intent без изменения vertices линии.
Красная точка выбирает сторону
Anchor-point selector остаётся устойчивым к reversal path. Нажмите кнопку, чтобы перенести anchor в другую region.
Детерминированные smoke tests
Тесты проверяют именно контракт 2.22: topology, reversal semantics, N/A, area invariance, unresolved target, no-global-leakage и отсутствие foreign mutation. Полный межмодульный regression barrier остаётся в 2.21.
| ID | Invariant | Expected |
|---|---|---|
| M01 | Open split | 2 boundary contacts → two regions |
| M02 | Area conservation | fractions sum ≈ 1 |
| M03 | Scale invariance | area fractions unchanged |
| M04 | Reversal geometry | same unordered regions |
| M05 | Anchor keep-side reversal | same selected geometry |
| M06 | Path-relative reversal | LEFT/RIGHT semantics swap |
| M07 | Missing keepSide | crop unresolved, no auto-choice |
| M08 | N/A | applicability=0; no zero score |
| M09 | Zero-length path | hard invalid |
| M10 | Collapsed host | hard invalid |
| M11 | One boundary contact | invalid without extension |
| M12 | Derived extension | source path remains immutable |
| M13 | Self intersection | hard invalid for binaryOnly |
| M14 | Closed loop | inside/outside topology |
| M15 | Closed loop outside host | hard invalid |
| M16 | Anchor on seam | unresolved selector |
| M17 | Descriptor != score | areaFraction never creates quality score |
| M18 | No global leakage | global.balance ignored |
| M19 | No foreign mutation | source points byte-equivalent after derive |
| M20 | Seam render independence | FEATHER/STROKE do not change path |
| M21 | Segmentation invariance | collinear extra vertex preserves regions |
| M22 | Deterministic region order | repeat run → same ids/order |
| M23 | 2.14 ownership barrier | no localBoundaryScore emitted |
| M24 | 2.20 ownership barrier | no localScore/ranking emitted |
| M25 | Canonical 2.14 handoff | PartitionDescriptor / BoundaryRelation; no invented alias |
| M26 | Host adapter scope | rectangle JS explicitly reference-only; production uses HostBoundaryAdapter |
DATA FOR LAYOUT ENGINE
Machine-readable contract для mask/crop/seam operation. Он описывает ownership, topology rules, derived geometry и recompute boundaries без скрытых эстетических правил.
{
"module": "Compo 2.22",
"name": "line_as_mask_crop_visual_seam",
"version": "1.1-corrective",
"scope": "derive binary mask/crop/seam operational geometry from canonical line path inside one bounded host region",
"consumes": [
"LinePathGeometry",
"HostRegion via HostBoundaryAdapter",
"Compo2.14.Compo2.14.PartitionDescriptor? / Compo2.14.BoundaryRelation?",
"MaskCropSeamTarget",
"GeometryToleranceContext?",
"StylePreset?"
],
"provides": [
"MaskCropSeamResult",
"line.mask.topology",
"line.mask.regions[]",
"line.crop.selectedRegionIds[]",
"line.seam.operationDescriptor",
"repairHints[]",
"audit"
],
"owns_metrics": [],
"descriptors_only": [
"boundaryContacts[]",
"areaFractions[]",
"topology",
"selectedRegionIds[]",
"seamRender",
"seamWidth",
"featherWidth"
],
"hard_constraints": [
"host region must have positive area",
"source path must contain at least two distinct points",
"binary open split requires two distinct host-boundary contacts unless explicit extension policy resolves them",
"binary closed loop must be simple and contained by host",
"self-intersecting path is invalid when binaryOnly=true",
"crop_edge requires explicit keepSide selector",
"source line geometry is immutable"
],
"soft_constraints": [
"approximate/fallback geometry lowers confidence",
"anchor on seam requires semantic disambiguation",
"style seam widths must not mutate source path"
],
"preferences": [
"ANCHOR_POINT selector preferred when reversal-invariant crop semantics are required",
"exact planar split preferred over sampled fallback",
"consume Compo2.14 partition evidence when available rather than duplicating boundary scoring"
],
"overrides_allowed_by": [
"explicit MaskCropSeamTarget",
"future image/crop semantic owner",
"Compo2.23 context override layer",
"future global composition solver"
],
"initial_heuristics": {},
"style_preset_values": {
"seamRender": "INVISIBLE|STROKE|GAP|OVERLAP|FEATHER",
"seamWidth": "STYLE_PRESET / explicit units",
"featherWidth": "STYLE_PRESET / explicit units"
},
"host_region_policy": {
"production_contract": "HostBoundaryAdapter",
"reference_fixture": "RectHostRegion",
"reference_fixture_only": true,
"bounding_box_substitution": "FORBIDDEN",
"missing_adapter_for_non_rect_host": "UNSUPPORTED_OR_UNRESOLVED"
},
"topology_policy": {
"OPEN_SPLIT": "simple open path with two distinct host-boundary contacts",
"CLOSED_LOOP": "simple contained closed path",
"SELF_INTERSECTING": "hard invalid for binaryOnly",
"DEGENERATE": "hard invalid"
},
"crop_selector_policy": {
"ANCHOR_POINT": "reversal-invariant when anchor is not on seam",
"PATH_RELATIVE": "LEFT/RIGHT swap under path reversal",
"REGION_ID": "requires external semantic identity",
"missing": "unresolved_target; never choose by area"
},
"metric_policy": {
"emit_quality_score": false,
"area_fraction_is_score": false,
"boundary_score_owner": "Compo2.14",
"line_system_score_owner": "Compo2.20"
},
"candidate_policy": {
"exploratory_generation_owner": "Compo2.19",
"derived_region_variants": "deterministic only",
"rank_unresolved_regions": false
},
"correction_policy": {
"direct_foreign_mutation": "FORBIDDEN",
"line_change_route": "line owner / Compo2.19 correction request",
"missing_keep_side_route": "semantic/crop caller",
"host_change_route": "host owner"
},
"deferred_inputs": [
"global crop choice",
"image focal-point preservation",
"global.balance",
"global.negativeSpace",
"global.hierarchy",
"global.eyeFlow",
"zOrder/blend semantics owned by rendering/global layers"
],
"recompute_when": [
"source line path changes",
"host region changes",
"mask/crop/seam target changes",
"geometry tolerance context changes",
"seam render/style parameters change only operation descriptor, not topology unless offset geometry is requested"
],
"tests": [
"M01_OPEN_SPLIT","M02_AREA_CONSERVATION","M03_SCALE_INVARIANCE","M04_REVERSAL_GEOMETRY",
"M05_ANCHOR_REVERSAL","M06_PATH_SIDE_REVERSAL","M07_MISSING_KEEP_SIDE","M08_NA",
"M09_ZERO_PATH","M10_COLLAPSED_HOST","M11_ONE_CONTACT","M12_EXTENSION_IMMUTABLE",
"M13_SELF_INTERSECTION","M14_CLOSED_LOOP","M15_LOOP_OUTSIDE","M16_ANCHOR_ON_SEAM",
"M17_DESCRIPTOR_NOT_SCORE","M18_NO_GLOBAL","M19_NO_MUTATION","M20_RENDER_INDEPENDENCE",
"M21_SEGMENTATION_INVARIANCE","M22_DETERMINISTIC_ORDER","M23_BOUNDARY_OWNER","M24_SCORER_OWNER",
"M25_CANONICAL_2_14_HANDOFF","M26_HOST_ADAPTER_SCOPE"
],
"finality": "LOCAL_MASK_CROP_SEAM_OPERATION_ONLY",
"invariants": [
"ONE_METRIC_ONE_OWNER","N_A_NOT_ZERO","LOCAL_NOT_GLOBAL","GEOMETRY_NOT_INTERPRETATION",
"DESCRIPTOR_NOT_SCORE","NO_BEAUTYSCORE","NO_FOREIGN_MUTATION","CONTINUATION_NOT_REDESIGN"
]
}