API-контракт
00 / consumes → provides
CONSUMES
Line.role
Line.subrole?
Line.rigidity
Line.directed
Line.sourceId? / targetId?
Line.geometry
Line.pathGeometry:
pathLengthPx
pathLengthShortN
chordLengthPx
spatialExtentShortN
startPointN
endPointN
startTangent
endTangent
Compo2.06.Line.contextualWeight?
Compo2.07.Line.pathLengthClass?
Compo2.07.Line.spatialSpanClass?
geometryOwner.sampleAtArcFraction(q)
StylePreset.flow?
PROVIDES
Line.flowGuideDescriptor
Line.flowHypotheses[]
Line.flowPreferredDirection
Line.flowDirectionStatus
line.flow.tangentContinuity
line.flow.netProgression
line.flow.reversalPenalty
line.flow.directionAmbiguity
line.flow.visibilityGate
line.flow.localCueStrength
line.flow.localCueConfidence
FlowCorrectionRequest[]
DOES NOT OWN
global.eyeFlowScore
global.attentionOrder
global.hierarchyScore
global.balanceScore
global.negativeSpaceScore
semantic.sourceTargetFit
object.connectionQuality
readingDirectionPreference
faceGazeDirection
Эти величины могут использовать локальный cue как вход, но не вычисляются здесь.
HARD ARCHITECTURE: Compo 2.08 не имеет права утверждать «взгляд пойдёт сюда». Он экспортирует локальный направляющий сигнал. Итоговый маршрут складывается из линий, текста, визуального веса, объектов, лиц, пустот и других глобальных факторов.
Направляющий сигнал ≠ eye-flow
01 / ownership boundary
Что измеряет 2.08
LOCAL:
• есть ли однозначное направление?
• насколько путь непрерывен?
• насколько часто он резко разворачивается?
• движется ли он вперёд или петляет?
• видим ли stroke достаточно,
чтобы быть cue?
• какие направления допустимы:
forward / reverse / unresolved?
Что решает не 2.08
GLOBAL:
• куда человек посмотрит первым?
• что станет второй остановкой?
• победит ли линия заголовок?
• важнее ли лицо / товар / текст?
• должен ли маршрут идти L→R?
• хорош ли маршрут всей композиции?
Практический смысл: сильный локальный cue может быть намеренно подавлен глобальной иерархией. И наоборот: слабая линия может поддерживать уже существующий маршрут, не становясь его причиной.
Сэмплирование пути
02 / geometry-independent analysis
Равные доли длины дуги
q_i = i / (N-1), i=0..N-1
S_i = sampleAtArcFraction(q_i)
P_i = S_i.position
T_i = normalize(S_i.tangent)
Δθ_i = wrapSigned(
angle(T_{i+1}) - angle(T_i)
) ∈ [-180°,180°]
DERIVED Сэмплирование по arc-length, а не по параметру Bézier t, не даёт длинным параметрическим участкам искусственно доминировать в статистике.
Один интерфейс для всех geometryType
SEGMENT:
sample analytically
CURVE:
use arc-length lookup /
adaptive flattening
POLYLINE:
locate segment by
cumulative length
OUTPUT:
same {position,tangent}
2.08 не должен знать, как внутри устроен path. Он просит владельца геометрии вернуть точку и касательную по доле пройденного пути.
IMPLEMENTATION CONTRACT: если geometry-owner пока не даёт sampleAtArcFraction(q), адаптер обязан строиться рядом с 2.01 / 2.04 / 2.05. 2.08 не копирует их геометрию и не создаёт вторую независимую длину пути.
Forward, reverse, unresolved
03 / direction hypotheses
→
directed
if Line.directed == true:
hypotheses = [FORWARD]
preferred = FORWARD
status = LOCKED
Source→target нельзя переворачивать локально, если направление семантически закреплено.
↔
undirected
if Line.directed == false:
hypotheses = [
FORWARD,
REVERSE
]
preferred = UNRESOLVED
Обычная линия сама по себе не доказывает, что движение идёт слева направо.
?
context resolved
global solver may choose:
FORWARD or REVERSE
using:
semantics
reading direction
hierarchy
object order
face gaze
Выбор глобального направления не меняет локальную геометрию пути.
Нельзя прошивать культурное правило «слева направо» внутрь 2.08. Направление чтения — внешний контекст и может отличаться по языку, носителю и конкретной композиции.
Локальные метрики cue
04 / measurable features
C
tangent continuity
c_i =
(1 + cos(|Δθ_i|)) / 2
tangentContinuity =
mean(c_i)
1 — плавное продолжение; ближе к 0 — частые сильные развороты.
P
net progression
netProgression =
clamp(
chordLengthPx /
pathLengthPx,
0, 1
)
1 — путь почти прямой; ниже — больше обходов/изгибов. Это descriptor, не автоматический штраф.
R
reversal penalty
r_i =
smoothstep(
reversalStartDeg,
180°,
|Δθ_i|
)
reversalPenalty =
mean(r_i)
HEURISTIC Большой разворот может ослаблять локальную однозначность, но не запрещается универсально.
A
direction ambiguity
directed line:
ambiguity = 0
undirected line:
ambiguity = 1
until context resolves
Это не «плохая» метрика, а сигнал о том, что существуют две равноправные гипотезы направления.
Visibility gate
if contextualWeight available:
visibilityGate =
saturateWeight(
Compo2.06.contextualWeight
)
else:
visibilityGate = UNKNOWN
confidence ↓
Линия может иметь идеальную геометрию направления, но при почти нулевом контрасте она слабый визуальный cue. Сам вес не пересчитывается — он принадлежит 2.06.
Local cue strength
geometryCue =
wC * tangentContinuity
+ wP * progressionFit(role, netProgression)
- wR * reversalPenalty
if visibilityGate known:
localCueStrength =
geometryCue * visibilityGate
else:
localCueStrength =
geometryCue
confidence *= missingVisibilityPenalty
Вес w* и progressionFit — STYLE_PRESET / HEURISTIC. Straight connector и декоративная S-кривая не обязаны иметь одинаковый target.
Не превращать descriptor в закон
05 / anti-overfitting
| Признак | Высокое значение значит | НЕ значит |
tangentContinuity | локально плавные изменения касательной | «композиция хорошая» |
netProgression | малый detour относительно chord | «прямая всегда лучше кривой» |
reversalPenalty | есть сильные локальные развороты | «повороты запрещены» |
visibilityGate | линия достаточно заметна как cue | «она должна стать доминантой» |
directionAmbiguity | локально не выбран знак пути | «линия бесполезна» |
Role → applicability
06 / cue relevance
| Role | Applicability 2.08 | Что анализировать | Что оставить другим |
trajectory | 1.00 | направление, continuity, reversals, visibility | финальный eye-flow |
connector | 0.90 | локальная читаемость маршрута | связь объектов — 2.09 |
annotation | 0.85 | однозначность локального хода | attachment object↔label — 2.09 |
accent | 0.55 | наличие directional cue | нужен ли этот cue вообще — global solver |
axis | 0.45 | геометрическое направление | alignment/grid — 2.13 |
divider | 0.30 | побочный directional cue | разделение областей — 2.14 |
rhythm | 0.25 | отдельная линия только частично | серийный flow — 2.17 |
texture | 0.10 | обычно только diagnostic | поле/плотность texture |
baseline | 0.00–0.20 | если линия видима | типографический flow |
INITIAL HEURISTIC Applicability — не «важность роли». Это лишь коэффициент того, насколько данный модуль уместен для данного типа линии.
Candidate generation
07 / requests, not foreign mutations
SMOOTH
request:
reduce sharp Δθ
owner:
2.04 curve
2.05 polyline
2.08 не двигает control points сам.
SIMPLIFY
request:
remove unnecessary
reversal / micro-bend
Только если роль допускает упрощение.
REVERSE
allowed only if:
directed == false
semantics unlocked
Reverse меняет гипотезу направления, а не геометрию.
BOOST / REDUCE CUE
request to owners:
width → 2.06
contrast → 2.16
span → 2.07
2.08 может попросить усилить cue, но не присваивает чужие параметры.
FlowCorrectionRequest {
lineId,
reasonCode:
EXCESSIVE_REVERSAL |
LOW_TANGENT_CONTINUITY |
CUE_TOO_WEAK_FOR_ROLE |
CUE_TOO_STRONG_FOR_ROLE |
DIRECTION_UNRESOLVED,
targetOwner:
"Compo2.04" |
"Compo2.05" |
"Compo2.06" |
"Compo2.07" |
"Compo2.16" |
"GLOBAL_SOLVER",
requestedChange,
bounds?,
costEstimate?,
provenance
}
Визуальная лаборатория
08 / same module, different cues
Metric ownership и scoring
09 / composite-first
OWNS
line.flow.tangentContinuity
line.flow.netProgression
line.flow.reversalPenalty
line.flow.directionAmbiguity
line.flow.visibilityGate
line.flow.localCueStrength
line.flow.localCueConfidence
PRIMARY VOTE:
line.flow.localCueStrength
COMPONENTS:
diagnostic by default
При будущем Compo 2.20 scorer должен голосовать composite-first, чтобы continuity/progression/reversal не считались повторно вместе с localCueStrength.
DEFERRED / FOREIGN
global.eyeFlowScore
global.attentionOrder
global.hierarchyScore
global.balanceScore
global.negativeSpaceScore
2.09.semanticConnectionFit
2.10.intersectionConflict
2.13.gridAlignment
2.16.lineColorContrast
Изменение этих величин не должно переписывать собственные геометрические descriptor 2.08.
MetricResult example:
{
key: "line.flow.localCueStrength",
owner: "Compo 2.08",
value: 0..1,
applicability: roleApplicability,
confidence: localCueConfidence,
status: "ok|warn|na|partial",
source: "HEURISTIC",
composite: true,
evidence: {
tangentContinuity,
netProgression,
reversalPenalty,
visibilityGate
},
repairHints: [...]
}
Pass order
10 / dependency-safe pipeline
INPUT LineCandidate
1. require canonical LinePathGeometry
2. request geometryOwner.sampleAtArcFraction(q)
3. build equal-arc-length samples
4. compute local tangent deltas
5. build FORWARD / REVERSE hypotheses
6. compute:
tangentContinuity
netProgression
reversalPenalty
directionAmbiguity
7. consume Compo 2.06 contextualWeight if available
8. compute visibilityGate or reduce confidence
9. apply role applicability
10. compute localCueStrength
11. generate FlowCorrectionRequest[]
12. DO NOT compute global eye-flow
13. provide descriptor to global solver / future eye-flow owner
RECOMPUTE WHEN:
• LinePathGeometry changes
• directed/source/target semantics change
• contextualWeight changes
• role/subrole changes
• style flow preset changes
Детерминированные тесты
11 / regression
TEST 01 · straight
segment:
P0=(0.1,0.7)
P1=(0.9,0.3)
directed=true
expected:
tangentContinuity = 1
netProgression = 1
reversalPenalty = 0
hypotheses = [FORWARD]
TEST 02 · undirected
same geometry
directed=false
expected:
hypotheses =
[FORWARD, REVERSE]
preferredDirection =
UNRESOLVED
must NOT assume L→R
TEST 03 · curve
smooth curve:
pathLength = 500
chordLength = 380
expected:
netProgression = 0.76
tangentContinuity high
must NOT fail only
because progression < 1
TEST 04 · reversals
polyline turn deltas:
[15°, 142°, 18°, 151°]
expected:
reversalPenalty > 0
tangentContinuity <
smooth equivalent
no hard reject
for role=accent
TEST 05 · visibility
same geometryCue = 0.90
A visibilityGate=0.90
B visibilityGate=0.20
expected:
cueStrength(A) >
cueStrength(B)
geometry metrics unchanged
TEST 06 · missing weight
contextualWeight = N/A
expected:
geometryCue computed
localCueStrength partial
confidence reduced
must NOT invent
contrast/weight
TEST 07 · invariance
A = straight segment
B = collinear polyline
with 5 vertices
same visible path
expected:
flow descriptors
approximately equal
within tolerance
TEST 08 · no global leak
change only:
global.balanceScore
global.hierarchyScore
global.negativeSpaceScore
expected:
2.08 owned metrics
UNCHANGED
TEST 09 · semantic lock
directed=true
sourceId=A
targetId=B
expected:
REVERSE candidate
forbidden locally
global may reroute
but not invert semantics
LIVE REFERENCE TESTS
Таблица ниже вычисляется встроенным JavaScript при открытии документа и служит минимальным executable smoke-test для формул 2.08.
DATA FOR LAYOUT ENGINE
12 / machine contract
{
"module": "Compo 2.08",
"name": "line_as_local_flow_guide",
"version": "1.0",
"scope": "LOCAL_DIRECTIONAL_CUE_ONLY",
"consumes": [
"Compo0.Canvas",
"Compo2.00.Line.role",
"Compo2.00.Line.subrole?",
"Compo2.00.Line.rigidity",
"Compo2.00.Line.directed",
"Compo2.00.Line.sourceId?",
"Compo2.00.Line.targetId?",
"Compo2.00.Line.geometry",
"Compo2.00.LinePathGeometry.pathLengthPx",
"Compo2.00.LinePathGeometry.chordLengthPx",
"Compo2.00.LinePathGeometry.spatialExtentShortN",
"Compo2.00.LinePathGeometry.startTangent",
"Compo2.00.LinePathGeometry.endTangent",
"GeometryOwner.sampleAtArcFraction(q)",
"Compo2.06.Line.contextualWeight?",
"Compo2.07.Line.pathLengthClass?",
"Compo2.07.Line.spatialSpanClass?",
"StylePreset.flow?"
],
"provides": [
"Line.flowGuideDescriptor",
"Line.flowHypotheses[]",
"Line.flowPreferredDirection",
"Line.flowDirectionStatus",
"FlowCorrectionRequest[]"
],
"owns_metrics": [
"line.flow.tangentContinuity",
"line.flow.netProgression",
"line.flow.reversalPenalty",
"line.flow.directionAmbiguity",
"line.flow.visibilityGate",
"line.flow.localCueStrength",
"line.flow.localCueConfidence"
],
"metric_policy": {
"primary_vote": "line.flow.localCueStrength",
"components": "diagnostic_only_by_default",
"global_eye_flow": "FORBIDDEN_TO_OWN"
},
"flow_hypothesis_policy": {
"directed_true": ["FORWARD"],
"directed_false": ["FORWARD","REVERSE"],
"undirected_preferred_direction": "UNRESOLVED"
},
"initial_heuristics": {
"sample_count": {
"value": 24,
"source": "IMPLEMENTATION_HEURISTIC"
},
"reversal_start_deg": {
"value": 100,
"source": "INITIAL_HEURISTIC"
},
"missing_visibility_confidence_factor": {
"value": 0.72,
"source": "INITIAL_HEURISTIC"
},
"role_applicability": {
"trajectory": 1.00,
"connector": 0.90,
"annotation": 0.85,
"accent": 0.55,
"axis": 0.45,
"divider": 0.30,
"rhythm": 0.25,
"texture": 0.10,
"baseline": 0.10
}
},
"hard_rules": [
"must_not_compute_global_eye_flow",
"must_not_assume_left_to_right_for_undirected_line",
"directed_semantic_line_must_preserve_source_target_order",
"must_not_recompute_path_geometry_owned_by_2.01_2.04_2.05",
"foreign_parameter_changes_must_be_emitted_as_correction_requests",
"missing_context_must_reduce_confidence_not_be_invented"
],
"deferred_inputs": [
"global.eyeFlowScore",
"global.attentionOrder",
"global.hierarchyScore",
"global.balanceScore",
"global.negativeSpaceScore",
"Compo2.09.semanticConnectionFit",
"Compo2.10.intersectionConflict",
"Compo2.13.gridAlignment",
"Compo2.16.lineColorContrast",
"context.readingDirection",
"context.faceGazeDirection"
],
"recompute_when": [
"LinePathGeometry changes",
"Line.directed changes",
"source/target semantics change",
"Compo2.06.contextualWeight changes",
"Line.role or subrole changes",
"StylePreset.flow changes"
],
"finality": "LOCAL_CUE_PROPOSAL_ONLY"
}
ARCHITECTURE CHECKPOINT: 2.08 продолжает мигрированную схему 2.00–2.07: единый LinePathGeometry, role/subrole/rigidity, один владелец каждой метрики, отдельные локальные и глобальные уровни, никаких скрытых предположений о направлении чтения.