COMPO · 2.18 · CURVATURE / TENSION

Кривизна и
напряжение
линии.

Модуль описывает локальную геометрическую интенсивность изгиба: signed curvature, total absolute turn, curvature concentration, inflection structure, bending energy и соответствие заданному tension profile. Он не решает, куда ведёт взгляд — directional cue остаётся 2.08.

API-контракт

00 / ownership

CONSUMES

Line Line.role Line.subrole? Line.geometryType Line.pathGeometry Line.pathLengthPx Line.startTangent Line.endTangent GeometryOwner API: sampleAtArcLength(s) tangentAtArcLength(s) curvatureAtArcLength?(s) derivative1?(t) derivative2?(t) OPTIONAL: StylePreset.lineTension? Compo2.08.flowGuideDescriptor?

PROVIDES

CurvatureProfile InflectionDescriptor[] TensionDescriptor CurvatureCandidate[] TensionCorrectionRequest[] line.curvature.totalAbsTurn line.curvature.meanAbsCurvature line.curvature.maxAbsCurvature line.curvature.curvatureConcentration line.curvature.inflectionComplexity line.curvature.bendingEnergy line.curvature.tensionFit line.curvature.localTensionScore line.curvature.confidence

DOES NOT OWN

path geometry // 2.04/2.05 eye-flow direction // 2.08 line-shape tangency // 2.11 parallel group curvature // 2.12 line rhythm // 2.17 global motion global hierarchy global balance global beauty
Архитектурная граница: кривизна — геометрический факт. «Напряжённая», «спокойная», «резкая», «плавная» линия — интерпретация этого факта относительно role/style target.

Signed curvature

01 / continuous path

Parametric curve

For r(t) = (x(t), y(t)): κ(t) = [x'(t)y''(t) - y'(t)x''(t)] / [x'(t)^2 + y'(t)^2]^(3/2) signed κ: sign = turn orientation magnitude = bend intensity

DERIVED Для screen coordinates (+Y down) знак надо зафиксировать отдельно в geometry contract.

Arc-length form

If parameter = arc length s: T(s) = unit tangent θ(s) = tangent angle κ(s) = dθ/ds units: 1 / px normalized: κN = κ * shortSidePx

Нормализованная кривизна позволяет сравнивать композиции разных размеров.

Segment: идеальный straight segment имеет κ=0 на внутреннем интервале. Endpoint/corner singularities не надо притворно усреднять как обычную continuous curvature.

Sampling-invariant оценка

02 / arc-length integration

Equal arc-length

s_i = i / (N-1) * L P_i = sampleAtArcLength(s_i) T_i = tangentAtArcLength(s_i)

Количество samples влияет на численную точность, но не должно менять смысл метрики.

Numerical turn

Δθ_i = signedAngle( T_i, T_{i+1} ) Δs_i = s_{i+1} - s_i κ_i ≈ Δθ_i / Δs_i

Integration

∫ f(s) ds ≈ Σ 0.5 * (f_i + f_{i+1}) * Δs_i normalize by L when metric should be length-independent
Не использовать: простое среднее mean(|Δθ_i|) как меру напряжения — оно искусственно уменьшается при увеличении числа samples. Используем интегральные или density-normalized величины.

Total turn

03 / accumulated bending

Signed total turn

signedTurn = ∫ κ(s) ds ≈ unwrap( θ_end - θ_start ) for simple smooth path without branch ambiguity

Показывает net rotation tangent.

Total absolute turn

totalAbsTurn = ∫ |κ(s)| ds ≈ Σ |Δθ_i| units: radians or degrees

В отличие от signed turn, не отменяет повороты противоположных знаков.

Turn density

turnDensity = totalAbsTurn / pathLengthPx normalized: turnDensityN = turnDensity * shortSidePx

Отделяет «длинная плавная дуга» от «короткого резкого изгиба».

Точки перегиба

04 / signed curvature structure

Canonical detection

candidate inflection: κ(s_i) and κ(s_{i+1}) have opposite reliable signs requirements: abs(κ) must exceed noise floor on both sides refine zero crossing between samples

Нулевая кривизна на длинном straight plateau не является автоматически inflection.

Descriptor

InflectionDescriptor { sPx, q01, positionN, signBefore, signAfter, strengthBefore, strengthAfter, confidence }

Количество перегибов — descriptor; больше перегибов не значит хуже.

inflectionComplexity descriptor may include: inflectionCount inflectionsPerPath meanSpacingBetweenInflections minSpacingBetweenInflections signRunLengths[] curvatureSignEntropy? AESTHETIC INTERPRETATION: requires target profile.

Bending energy

05 / curvature intensity

Raw energy

E = ∫ κ(s)^2 ds units: 1 / px higher E: more concentrated or stronger bending

Length-normalized

meanKappa2 = (1/L) * ∫ κ(s)^2 ds RMS curvature = sqrt(meanKappa2)

Удобно сравнивать линии разной длины.

Scale-normalized

κN = κ * shortSide meanKappa2N = mean(κN^2) dimensionless for normalized canvas
Energy ≠ beauty: высокое bending energy может быть у выразительной каллиграфической линии и у случайно ломаного path. 2.18 измеряет интенсивность и profile fit, а не «качество» само по себе.

Curvature concentration

06 / where the bend lives

Energy distribution

e(s) = κ(s)^2 E_total = ∫e ds window energy: E_w(q) = ∫window(q) e ds peakShare = max_q(E_w(q)) / E_total

Большой peakShare означает, что изгиб сосредоточен в небольшой части path.

Concentration descriptor

curvatureConcentration: peakShare top10ArcShare? energyCentroidQ energySpreadQ NOT: generic penalty

Нужен для различения «мягко распределённой дуги» и «почти прямой линии с одним резким kink».

Напряжение как target profile

07 / interpretation
Tension profileТипичные геометрические признакиВажно
CALMнизкая RMS curvature, малая concentration, мало abrupt sign changesне означает «лучше»
FLOWINGумеренная distributed curvature, smooth sign runs, controlled inflectionsможет иметь большой total turn
TAUTмалый spatial detour, заметный directional bend, небольшое число сильных участковrole/style dependent
ENERGETICвысокая turn density, несколько сильных curvature eventsне путать с шумом
ANGULARдля polyline: corner turns вместо continuous κобрабатывается дискретной моделью
FREEнет эстетического targetтолько descriptors
TensionTarget { profile, totalAbsTurnRange?, turnDensityRange?, rmsCurvatureRange?, concentrationRange?, inflectionCountRange?, maxCurvatureRange?, cornerTurnProfile?, provenance: STYLE_PRESET | SEMANTIC | EMPIRICAL | HEURISTIC } tensionFit = fit( measured curvature descriptors, target ranges )

Polyline / corners

08 / discrete curvature

Corner turn

for vertex i: u = unit(P_i-P_{i-1}) v = unit(P_{i+1}-P_i) cornerTurn_i = signedAngle(u,v)

Corner density

totalAbsCornerTurn = Σ |cornerTurn_i| cornerTurnDensity = totalAbsCornerTurn / pathLengthPx

No fake smoothing

polyline corner: curvature is distribution-like singularity DO NOT invent finite smooth κ unless geometry owner explicitly rounds corner

Local tension scoring

09 / ownership

OWNS

line.curvature.totalAbsTurn line.curvature.meanAbsCurvature line.curvature.maxAbsCurvature line.curvature.curvatureConcentration line.curvature.inflectionComplexity line.curvature.bendingEnergy line.curvature.tensionFit line.curvature.localTensionScore line.curvature.confidence

DOES NOT OWN

line.flow.localCueStrength line.flow.reversalPenalty line.length.roleLengthFit line.rhythm.localRhythmScore global motion global balance global hierarchy global beauty / elegance
if explicit TensionTarget exists: localTensionScore = weightedMeanApplicable([ tensionFit, curvatureContinuityFit?, inflectionProfileFit? ]) if no TensionTarget: localTensionScore = N/A return curvature descriptors only raw: totalAbsTurn bendingEnergy maxCurvature MUST NOT be treated as monotonic quality signals.
Связь с 2.08: 2.18 может экспортировать sampling-invariant descriptors, которые 2.08 позже сможет consume при corrective pass. Но 2.18 не принимает решение о preferred eye-flow direction.

Correction requests

10 / controlled geometry change

SMOOTH

Снизить локальную curvature concentration.

INCREASE BEND

Добавить изгиб, если target profile требует большей динамики.

MOVE INFLECTION

Сместить перегиб в разрешённом диапазоне.

ROUND CORNER

Попросить geometry owner заменить дискретный corner на smooth transition.

TensionCorrectionRequest { lineId, reasonCode: CURVATURE_TOO_LOW_FOR_TARGET | CURVATURE_TOO_HIGH_FOR_TARGET | CURVATURE_OVERCONCENTRATED | INFLECTION_PROFILE_MISMATCH | ABRUPT_CORNER_NOT_ALLOWED | EXCESSIVE_MICRO_WOBBLE, targetOwner: "Compo2.04" | "Compo2.05" | "GLOBAL_SOLVER" | "STYLE_PRESET", requestedChange, affectedArcRange?, allowedRange?, confidence }

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

11 / curvature cases
STRAIGHT: κ≈0, totalAbsTurn≈0, bendingEnergy≈0.
DISTRIBUTED CURVE: bend распределён по значительной длине path.
Высокая curvature concentration: большая часть энергии сосредоточена в центре.
S-подобная линия: signed curvature меняет знак. Inflection points фиксируются после noise threshold и zero-crossing refinement.
Polyline: tension описывается corner turns. Не надо маскировать углы фиктивной smooth curvature.

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

12 / regression

TEST 01 · straight

segment: (0,0)→(100,0) expected: totalAbsTurn=0 bendingEnergy=0

TEST 02 · quarter circle

radius R arc angle=π/2 κ=1/R expected: totalAbsTurn=π/2 E=(π/2)/R

TEST 03 · semicircle

radius R arc angle=π expected: totalAbsTurn=π turnDensity=1/R

TEST 04 · S-curve

signed curvature changes + → - expected: >=1 reliable inflection when noise threshold met

TEST 05 · sampling invariance

same circular arc N=16 and N=128 expected: totalAbsTurn approximately equal within tolerance

TEST 06 · polyline corner

segments: east → south expected: abs cornerTurn=90° no fake finite κ

TEST 07 · no target

valid curve no TensionTarget expected: descriptors returned localTensionScore=N/A

TEST 08 · no eye-flow leak

reverse path direction expected: abs curvature metrics stable preferred eye-flow not decided here

TEST 09 · scale normalization

same normalized circle canvas scale ×2 expected: raw κ halves κN remains stable

LIVE REFERENCE TESTS

IDInputComputedStatus

DATA FOR LAYOUT ENGINE

13 / machine contract
{ "module": "Compo 2.18", "name": "line_curvature_and_tension", "version": "1.0", "scope": "LOCAL_CURVATURE_AND_TENSION", "consumes": [ "Compo2.00.Line", "Compo2.00.LinePathGeometry", "GeometryOwner.sampleAtArcLength", "GeometryOwner.tangentAtArcLength", "GeometryOwner.curvatureAtArcLength?", "GeometryOwner.derivative1?", "GeometryOwner.derivative2?", "Compo2.08.flowGuideDescriptor?", "StylePreset.lineTension?" ], "provides": [ "CurvatureProfile", "InflectionDescriptor[]", "TensionDescriptor", "CurvatureCandidate[]", "TensionCorrectionRequest[]" ], "owns_metrics": [ "line.curvature.totalAbsTurn", "line.curvature.meanAbsCurvature", "line.curvature.maxAbsCurvature", "line.curvature.curvatureConcentration", "line.curvature.inflectionComplexity", "line.curvature.bendingEnergy", "line.curvature.tensionFit", "line.curvature.localTensionScore", "line.curvature.confidence" ], "descriptors_only": [ "signedTotalTurn", "turnDensity", "rmsCurvature", "inflectionCount", "inflectionPositions[]", "energyCentroidQ", "energySpreadQ", "cornerTurns[]" ], "metric_policy": { "primary_vote": "line.curvature.localTensionScore", "raw_curvature": "DESCRIPTOR_NOT_MONOTONIC_QUALITY", "target_required_for_aesthetic_fit": true, "no_target": "RETURN_DESCRIPTORS_AND_NA_SCORE", "sampling": "ARC_LENGTH_INTEGRATION", "components": "diagnostic_only_by_default" }, "tension_profiles": [ "CALM", "FLOWING", "TAUT", "ENERGETIC", "ANGULAR", "FREE" ], "initial_heuristics": { "curvature_noise_floor_shortN_inv": { "value": 0.02, "source": "INITIAL_HEURISTIC" }, "inflection_min_separation_shortN": { "value": 0.03, "source": "INITIAL_HEURISTIC" }, "curvature_concentration_window_q": { "value": 0.10, "source": "IMPLEMENTATION_HEURISTIC" }, "minimum_samples": { "value": 32, "source": "IMPLEMENTATION_HEURISTIC" } }, "hard_rules": [ "continuous_curve_curvature_must_use_derivatives_or_arc_length_consistent_numerics", "mean_delta_angle_must_not_be_used_as_sampling_dependent_tension_metric", "total_abs_turn_must_be_integrated_or_accumulated_independent_of_sample_count", "inflection_requires_reliable_signed_curvature_change_not_zero_plateau", "polyline_corners_must_not_be_faked_as_finite_smooth_curvature", "raw_curvature_or_energy_must_not_be_treated_as_monotonically_better_or_worse", "no_TensionTarget_means_no_universal_aesthetic_tension_score", "preferred_eye_flow_direction_remains_owned_by_2.08", "path_geometry_changes_must_be_requested_from_geometry_owner" ], "deferred_inputs": [ "global.motion", "global.eyeFlow", "global.hierarchyScore", "global.balanceScore", "global.styleCoherence" ], "recompute_when": [ "LinePathGeometry changes", "curve control geometry changes", "polyline vertices change", "canvas normalization scale changes", "role/subrole changes", "StylePreset.lineTension changes" ], "finality": "LOCAL_CURVATURE_TENSION_ONLY" }
ARCHITECTURE CHECKPOINT: 2.18 вводит sampling-invariant curvature descriptors и отделяет геометрическое напряжение от eye-flow. Следующий Compo 2.19 — автоматический генератор линейной структуры: он должен собирать кандидатов из уже существующих line modules, а не изобретать заново их метрики.
VISITS / ...