COMPO · 2.22 · MASK / CROP / VISUAL SEAM

Линия как
маска, crop
и visual seam.

Линия перестаёт быть только stroke: она может стать операционной границей между областями, основой clipping mask, локальным crop-edge или визуальным швом двух слоёв. 2.22 формализует эту трансформацию без захвата ownership у геометрии 2.01, boundary semantics 2.14, local scorer 2.20 и будущих global/image-композиционных модулей.

ModuleCompo 2.22
Scopeline-derived mask / crop / seam operations
Ownsmask topology + derived regions + seam descriptors
Consumesline geometry + host region + explicit mask/crop intent
ProvidesMaskCropSeamResult + correction requests
FinalityLOCAL OPERATIONAL GEOMETRY ONLY

Линия → операционная граница

00 / module api

2.22 отвечает на узкий вопрос: можно ли из уже существующей canonical line geometry построить корректную локальную mask/crop/seam-операцию и какие области она создаёт. Модуль не оценивает красоту композиции и не принимает глобальное решение, что именно следует обрезать.

SCOPE

Binary mask/crop/seam внутри одного bounded host region при explicit intent.

BOUNDARY

Derived regions не мутируют source line, host geometry, image, typography или grid.

DEFERRED

Global crop choice, focal-point preservation, balance, negative space, hierarchy, image composition.

CONSUMES / PROVIDES

Consumes
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
Provides
MaskCropSeamResult {
  status,
  applicability,
  confidence,
  mode,
  topology,
  seamPath,
  regions[],
  selectedRegionIds[],
  boundaryContacts[],
  areaFractions[],        // DESCRIPTORS ONLY
  hardViolations[],
  unresolvedInputs[],
  repairHints[],
  provenance,
  audit
}
NO BEAUTYSCORE / NO GLOBAL CROP SCORE. Area fractions, side labels, contact count и closure topology — descriptors. 2.22 не превращает «больше оставшейся площади», «центрированнее crop» или «ровнее seam» в универсальное качество.

Ownership: новая операция, не новая геометрия

01 / architecture boundary

2.22 владеет производной операционной геометрией mask/crop/seam. Source line остаётся у своих owner-модулей, а boundary quality из 2.14 не пересчитывается под новым именем.

OWNS

Derived operation

line.mask.topology
line.mask.regions[]
line.crop.selectedRegionIds[]
line.seam.operationDescriptor
line.mask.audit

CONSUMES

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.

DOES NOT OWN

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

OWNS_METRICS = [] 2.22 сознательно не вводит новый scalar quality metric. Результат = topology + regions + applicability + confidence + hard/soft diagnostics. Если будущему scorer понадобится mask/crop target-fit, отдельный owner/API должен быть добавлен осознанно, а не скрыт внутри areaRatio или continuity descriptor.
2.14 ≠ 2.22.

2.14 отвечает за линию как boundary/divider и её локальные boundary relations. 2.22 отвечает за операционное превращение границы в clip regions / crop side / seam relation. Один модуль не дублирует score другого.

Три режима одной геометрии

02 / semantic modes

Mode всегда задаётся intent-ом. Наличие линии рядом с изображением не означает автоматически crop; rendered stroke не означает автоматически seam.

MASK

mask_boundary

Линия создаёт две области или inside/outside loop внутри host. Результат — clip-able region geometry. Line stroke может вообще не рендериться.

CROP

crop_edge

Из валидных regions explicit keepSide выбирает сохраняемую область. 2.22 не решает, какая сторона «лучше» без target.

SEAM

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

03 / host geometry contract

Production-contract 2.22 не ограничен прямоугольником. Он требует host-boundary primitives. Текущий inline JS ниже — только RectHostRegion reference fixture и не является обещанием generic polygon implementation.

PRODUCTION INTERFACE

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
}
REFERENCE FIXTURE ONLY

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.
NO SILENT SHAPE DOWNGRADE. Если production caller передаёт произвольный polygon/shape без подходящего HostBoundaryAdapter, 2.22 возвращает unsupported/unresolved geometry. Нельзя незаметно подменить host его bounding box.

Topology first: сначала допустимость, потом regions

04 / topology model

Для binary mask важнее топология, чем декоративная форма линии. Простая кривая может быть валидным seam; самопересекающаяся — уже создаёт неоднозначное множество областей и выходит за scope 2.22.

TopologyУсловиеРезультатPolicy
OPEN_SPLITsimple open path; два различных контакта с host boundaryдве regions внутри hostVALID
CLOSED_LOOPsimple closed path; loop лежит внутри hostinside + outside-with-holeVALID
OPEN_NEEDS_EXTENSION0/1 boundary contacts; explicit extensionPolicy разрешёнderived seamPath после extensionVALID WITH LOWER CONFIDENCE
SELF_INTERSECTINGnon-adjacent segments intersect3+ / ambiguous regionsHARD INVALID for binaryOnly
DEGENERATEzero-length / collapsed host / coincident contactsнет устойчивой partitionHARD INVALID

Screen coordinate convention

screen: +X → right, +Y → down path direction: p₀ → pₙ signedSide(p) = cross(d, p - p₀) ВАЖНО: LEFT/RIGHT — path-relative labels. При reversal(p₀…pₙ → pₙ…p₀) LEFT и RIGHT меняются местами. Поэтому reversal-invariant crop intent рекомендуется задавать ANCHOR_POINT, а не голым LEFT/RIGHT.
EPS / tolerance.

Production contact tolerance приходит из GeometryToleranceContext и несёт provenance. 2.22 не вводит универсальный pixel threshold. Inline reference tests используют отдельный numerical EPS только для синтетических fixtures.

Open split: path + две цепочки host boundary

05 / region construction

Для simple open path с двумя boundary contacts host boundary имеет два маршрута между endpoints. Каждый маршрут вместе с seamPath образует одну замкнутую region.

Given seamPath P = [p₀ ... pₙ], p₀,pₙ ∈ ∂Host B_cw = hostBoundaryChain(pₙ → p₀, clockwise) B_ccw = hostBoundaryChain(pₙ → p₀, counterClockwise) Region A = close(P + B_cw) Region B = close(P + B_ccw) areaFractionᵢ = abs(area(Regionᵢ)) / abs(area(Host)) Σ areaFractionᵢ ≈ 1 // descriptor integrity check, not balance score
DERIVED

Region geometry

Новые polygons — производные от source line и host. Они не переписывают source path. Audit хранит исходный path hash, host hash и applied extension/closure steps.

FORBIDDEN

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, не эстетическая догадка

06 / keep-side semantics

После получения 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"
}
NO IMAGE SEMANTICS HERE.

2.22 не знает, где лицо, товар, текстовый блок или focal point. Если будущий image owner говорит «этот crop нарушает focal constraint», это внешний hard/soft constraint и correction request, а не скрытая логика 2.22.

Visual seam: граница может быть невидимой

07 / layer relation

Seam — это отношение двух областей вокруг одной path. Рендер stroke является только одним из вариантов; clipping mask может использовать ту же path без видимой линии.

seamRenderОперацияSource path mutation?Примечание
INVISIBLEдва слоя соприкасаются по seamPathNOчистая mask boundary
STROKEотдельный render-layer рисует line strokeNOstroke style принадлежит line rendering owner
GAPдве masks отступают от seamNOoffset operation; width — style parameter
OVERLAPregions перекрываются в seam bandNOпорядок слоёв приходит извне
FEATHERalpha transition вокруг seamNOfeather profile — rendering/mask parameter
operationDescriptor = { seamPathRef, sideARegionRef, sideBRegionRef, renderMode, width?, featherWidth?, zOrder?: DEFERRED, blendMode?: DEFERRED } Changing width/feather MUST NOT move source line points.

Hard constraints и edge cases

08 / validity gates

Неоднозначная topology не компенсируется красивым stroke. Feasibility проверяется до любой downstream interpretation.

CaseResultRepair route
hostRegion missing / zero areaHARD INVALIDcaller / host owner
source path has < 2 distinct pointsHARD INVALIDline geometry owner / 2.19
self-intersection with binaryOnly=trueHARD INVALID2.19 regenerate simple path
open split has 0/1 boundary contacts and extensionPolicy=NONEUNRESOLVED / INVALIDtarget or line owner
two contacts collapse to same boundary pointDEGENERATEline owner
crop target missing keepSideUNRESOLVEDsemantic/crop caller; no auto-choice
anchor point on seam within toleranceUNRESOLVEDcaller supplies disambiguated anchor
closed loop extends outside hostHARD INVALIDline owner or host/crop caller
global.balance suppliedIGNORED / DEFERRED2.23 / future global owner
N/A != 0.

Если mode не применим к текущему semantic request, applicability=0 и result не участвует ни в scoring, ни в penalty. Отсутствие crop intent не является плохим crop.

Target / interpretation: факт отделён от решения

09 / geometry vs interpretation

Сначала 2.22 строит topology и regions, потом применяет explicit keep-side / seam operation. Это защищает систему от скрытого правила «большая сторона лучше».

FACT

Geometry descriptors

topology = OPEN_SPLIT
contacts = 2
areaFractions = [0.37, 0.63]
selfIntersections = 0
sourceReversed = false
exactGeometry = true
INTERPRETATION

Explicit target

mode = CROP_EDGE
keepSide = ANCHOR_POINT(0.75W, 0.50H)
→ selectedRegion = region:B

No target?
→ selectedRegion = null
→ status = unresolved_target
DERIVEDTopology, contacts, exact region area from canonical geometry.
IMPLEMENTATION_HEURISTICFallback curve sampling density, only if exact splitter unavailable.
STYLE_PRESETGap/feather width and render mode when explicitly preset.
DEFERREDGlobal crop preference, focal retention, balance/hierarchy effects.

Derived variants ≠ candidate generator

10 / candidate boundary

2.22 может получить несколько математически допустимых derived regions, но не запускает новый эстетический search.

OPEN_SPLIT → exactly two region variants by host-boundary closure CLOSED_LOOP → inside / outside region relation AMBIGUOUS keepSide → return both as unresolved, DO NOT rank Exploratory line candidates → Compo 2.19 Local line-system ranking → Compo 2.20 Mask/crop operation derivation → Compo 2.22 Context/global override → Compo 2.23
Deterministic ordering.

Если API обязан сериализовать две равноправные regions до semantic selection, порядок задаётся stable geometry hash / canonical host-boundary traversal, а не случайной площадью или DOM order.

Correction requests: исправляет владелец

11 / no foreign mutation

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
NO FOREIGN MUTATION. Extension policy создаёт derived seamPath в 2.22 и хранит sourcePathRef; она не переписывает canonical line. Если изменение должно стать частью самой линии, отправляется correction request её владельцу.

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

12 / mask topology lab

Одна и та же линия показана как stroke, как mask boundary и как crop selector. Цветные области — derived regions; переключение keep-side не изменяет source path.

HOST REGION REGION A REGION B / KEEP
region Aregion Bboundary contactssource seam path
SOURCE

Path остаётся тем же

PolyLine используется как seamPath. Fill-полигоны вычисляются отдельно и могут меняться по crop intent без изменения vertices линии.

ANCHOR TARGET

Красная точка выбирает сторону

Anchor-point selector остаётся устойчивым к reversal path. Нажмите кнопку, чтобы перенести anchor в другую region.

Reference fixture ready.

Детерминированные smoke tests

13 / inline JS tests

Тесты проверяют именно контракт 2.22: topology, reversal semantics, N/A, area invariance, unresolved target, no-global-leakage и отсутствие foreign mutation. Полный межмодульный regression barrier остаётся в 2.21.

IDInvariantExpected
M01Open split2 boundary contacts → two regions
M02Area conservationfractions sum ≈ 1
M03Scale invariancearea fractions unchanged
M04Reversal geometrysame unordered regions
M05Anchor keep-side reversalsame selected geometry
M06Path-relative reversalLEFT/RIGHT semantics swap
M07Missing keepSidecrop unresolved, no auto-choice
M08N/Aapplicability=0; no zero score
M09Zero-length pathhard invalid
M10Collapsed hosthard invalid
M11One boundary contactinvalid without extension
M12Derived extensionsource path remains immutable
M13Self intersectionhard invalid for binaryOnly
M14Closed loopinside/outside topology
M15Closed loop outside hosthard invalid
M16Anchor on seamunresolved selector
M17Descriptor != scoreareaFraction never creates quality score
M18No global leakageglobal.balance ignored
M19No foreign mutationsource points byte-equivalent after derive
M20Seam render independenceFEATHER/STROKE do not change path
M21Segmentation invariancecollinear extra vertex preserves regions
M22Deterministic region orderrepeat run → same ids/order
M232.14 ownership barrierno localBoundaryScore emitted
M242.20 ownership barrierno localScore/ranking emitted
M25Canonical 2.14 handoffPartitionDescriptor / BoundaryRelation; no invented alias
M26Host adapter scoperectangle JS explicitly reference-only; production uses HostBoundaryAdapter
Tests not run yet.

DATA FOR LAYOUT ENGINE

14 / machine contract

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"
  ]
}
ARCHITECTURE CHECKPOINT · 2.22 ВЛАДЕЕТ ТОЛЬКО ПРОИЗВОДНОЙ MASK/CROP/SEAM-ГЕОМЕТРИЕЙ И ЕЁ DESCRIPTORS. Source line остаётся у line owners, boundary score — у 2.14, candidate generation — у 2.19, local line ranking — у 2.20, regression contract — у 2.21. 2.23 получит context/global overrides. Area fraction не является balance score; crop side без explicit target не выбирается.
VISITS / ...