COMPO · 1.01 · POINT SIZE / VISUAL WEIGHT

Размер
и визуальный
вес точки

Исправленная архитектура: размер и базовый вес вычисляются до размещения, а контекстный визуальный вес — только после того, как владельцы контраста, изоляции, crowding и edge-context вернут свои метрики. Модуль больше не пытается «знать контекст заранее».

Контракт модуля

00 / Compo 0.00 compatible

CONSUMES

Compo0.Canvas Compo1.00.functionalRole Compo1.00.visualRoleCandidate Point.geometry Point.fillRatio Point.opacity Point.strokeWidthN? StylePreset.pointSize?

PROVIDES

Point.radiusShortN Point.diameterShortN Point.sizeClass Point.visualWeightBase Point.visualWeightContextual Point.weightStatus Point.weightComponents

OWNS METRICS

point.visual_weight_base point.visual_weight_contextual point.size_class

Другие модули могут поставлять modifiers, но итоговый point visual weight принадлежит только 1.01.

DEFERRED INPUTS

point.local_contrast ← 1.10 point.isolation_modifier ← 1.04/1.08 point.crowding_modifier ← 1.04/1.08 point.edge_modifier ← 1.02 global.hierarchy_target ← future global.balance_score ← future
Главное исправление: Compo 1.01 больше не использует контраст, изоляцию, crowding или положение по краю в первом проходе. Эти данные ещё физически не существуют до placement/context passes.

Двухстадийная модель

01 / break the dependency cycle

PASS A — BASE

Вычисляется сразу после назначения роли. Только свойства самой точки.

INPUT: radius / diameter fill ratio opacity point primitive style calibration OUTPUT: sizeClass visualWeightBase

PASS B — CONTEXTUAL

Выполняется после размещения и контекстных модулей.

INPUT: visualWeightBase localContrast isolationModifier crowdingModifier edgeModifier OUTPUT: visualWeightContextual
DEPENDENCY ORDER 1. Compo 1.00 assigns roles 2. Compo 1.01 PASS A → base size + base weight 3. Compo 1.02..1.10 → placement / relations / clusters / grid / contrast / context 4. Compo 1.01 PASS B → contextual weight 5. if |Wctx - Wprev| > ε_weight: mark dependent candidates DIRTY rerun only affected local modules 6. global solver decides whether point size/position must change

Размер точки

02 / deterministic geometry + presets
rₙ
radiusShortN
S = min(W,H) rPx = physical radius rN = rPx / S

Каноническая нормализация размера точки.

dₙ
diameterShortN
dN = 2 * rN

Используется для size classes и collision envelopes.

Aₙ
footprintShortN²
for filled circle: Aeq = π * rN² for other point primitives: Aeq = primitiveArea / S²

Эквивалентная площадь — геометрическая величина, не итоговый visual weight.

ClassdiameterShortNНазначениеСтатус
micro< 0.006noise / technical micro-markerSTYLE PRESET
small0.006–0.015support / marker / anchorSTYLE PRESET
medium0.015–0.030node / visible supportSTYLE PRESET
large0.030–0.060local focus / strong nodeSTYLE PRESET
oversize0.060–0.120point-like dominant only if style allowsSTYLE PRESET
promote_to_shape> 0.120объект перестаёт быть «точкой» в базовой моделиINITIAL HEURISTIC
Порог 0.120 не является законом восприятия. Это инженерная граница маршрутизации: выше неё объект выгоднее передать в модуль формы, чтобы не смешивать point-logic и shape-logic.

Базовый визуальный вес

03 / intrinsic only

Геометрическая масса

Aref = π * rRef² massRatio = Aeq / Aref sizeMass = massRatio ^ gammaSize

rRef — калибровочная reference-point из style/engine preset. gammaSize позволяет откалибровать нелинейность.

Base weight

intrinsicMass = sizeMass * fillFactor * opacity^gammaOpacity * primitiveFactor visualWeightBase = 1 - exp(-kWeight * intrinsicMass)

Экспоненциальное сжатие гарантирует 0 ≤ Wbase < 1 и не требует ручного clamp после каждого множителя.

ПараметрТипИсточникКомментарий
AeqgeometryDERIVEDиз реальной геометрии точки
fillFactor0..1DERIVEDотношение фактически заполненной площади к envelope
opacity0..1INPUTальфа самого объекта, не contrast
primitiveFactorscalarSTYLE / CALIBRATIONоптическая поправка для circle/square/cross/ring; default=1
gammaSizescalarHEURISTICстарт 0.65–1.00; калибруется
kWeightscalarHEURISTICмасштаб нормализации weight index
Запрещено в PASS A: использовать local contrast, положение на поле, отрицательное пространство, соседей, кластерную плотность или global hierarchy. Это контекст, а не intrinsic property.

Контекстный визуальный вес

04 / recompute after context
C
local contrast

DEFERRED → 1.10

contrastModifier = f(C) neutral default = 1.0
I
isolation

DEFERRED

isolationModifier neutral default = 1.0
D
crowding

DEFERRED

crowdingModifier neutral default = 1.0
E
edge context

DEFERRED → 1.02

edgeModifier neutral default = 1.0
contextMultiplier = geometricMean(active modifiers) // Example when all four exist: contextMultiplier = (Cmod * Imod * Dmod * Emod)^(1/4) // preserve bounded base-weight semantics: baseMassEquivalent = -ln(1 - visualWeightBase) contextualMass = baseMassEquivalent * contextMultiplier visualWeightContextual = 1 - exp(-contextualMass) // if any deferred metric is unavailable: omit missing modifier renormalize geometric mean over active modifiers NEVER substitute an invented formula
Почему geometric mean: один экстремальный modifier не должен линейно «взорвать» результат. Это инженерный выбор, поэтому сама форма агрегатора имеет статус HEURISTIC и позже может быть откалибрована.

Размер как proposal, не приказ

05 / role targets

Visual role → target band

visualRoleCandidate = focus | support | neutral focus: target Wctx = stylePreset.focusWeightBand support: target Wctx = stylePreset.supportWeightBand neutral: no mandatory target

Target bands принадлежат style/global hierarchy system. Compo 1.01 лишь умеет подбирать размер под заданный target.

Inverse solve

given targetWeight T: find rN such that abs(weight(rN) - T) is minimal method: bounded binary search or monotonic Newton/bisection subject to: minRadius ≤ rN ≤ maxRadius

Это позволяет глобальному solver сказать: «нужен вес 0.62», не придумывая радиус вручную.

Визуальные сравнения

06 / same size ≠ same weight
filled / opacity 1.0
Одинаковый envelope, высокая заполненность → больше intrinsic mass.
ring / low fill ratio
Кольцо того же диаметра имеет меньшую фактически заполненную площадь.
filled / opacity 0.28
Opacity — intrinsic input; contrast к реальному фону всё равно будет рассчитан позднее в 1.10.
small / isolated context
После placement изоляция может увеличить contextual weight без изменения геометрии.
same point / crowded context
Та же точка в crowding-контексте может получать другой modifier. Это уже не PASS A.
contrast owner = Compo 1.10
1.01 не вычисляет контраст сам. Он получает нормализованный результат владельца метрики.

Живая лаборатория

07 / base vs contextual
Modifiers в этой лаборатории вводятся вручную только для демонстрации. В production их должен поставлять соответствующий owner-модуль.

Порядок работы

08 / solver integration
1 ROLE
1.00 assigns functional + visual roles
2 BASE
1.01 computes size + Wbase
3 PLACE
1.02+ generate geometry/context
4 CONTEXT
1.04/1.08/1.10 provide modifiers
5 REWEIGHT
1.01 computes Wctx
6 DIRTY?
if delta > epsilon, rerun affected modules
7 GLOBAL
solver may resize/move and repeat
DIRTY PROPAGATION if radius changed: dirty = [1.02,1.03,1.04,1.05,1.06,1.07,1.08,1.09,1.10] if only contextual modifiers changed: dirty = [1.01 PASS B] if abs(Wctx_new - Wctx_old) > epsilonWeight: notify global hierarchy/balance solver No circular recomputation without a dirty-threshold and maxIterations.

Hard / soft / overrides

09 / rule classes

HARD

  • finite radius
  • rN > 0
  • 0 ≤ opacity ≤ 1
  • no NaN/∞ in modifiers
  • weight owner remains 1.01

SOFT

  • stay inside role-compatible size band
  • avoid unstable size oscillation
  • prefer small correction over full regeneration when feasible

OVERRIDES

  • global hierarchy may request target weight
  • global balance may request resize indirectly
  • crop/semantic safety can cap radius
  • style preset may redefine size classes

Тест-кейсы

10 / deterministic + regression

TEST 01 — base only

Canvas = 1080×1350 rPx = 13.5 S = 1080 expected rN = 0.0125 expected dN = 0.025 context unavailable expected weightStatus = BASE_ONLY

TEST 02 — missing deferred

Cmod = 1.20 Imod = MISSING Dmod = 0.80 Emod = MISSING activeGM = sqrt(1.20*0.80) expected no invented defaults expected modifiersUsed = 2

TEST 03 — no cycle

Wprev = 0.441 Wnew = 0.446 epsilonWeight = 0.01 delta = 0.005 expected DIRTY = false expected no placement rerun

TEST 04 — resize invalidates

old rN = 0.010 new rN = 0.018 expected geometryVersion++ expected dependent modules dirty expected 1.01 PASS A rerun

TEST 05 — point → shape

dN = 0.145 pointMax = 0.120 expected route = SHAPE_MODULE expected pointLogicFrozen = true

TEST 06 — ownership

Compo 1.10 provides localContrast Compo 1.01 consumes localContrast expected 1.10 MUST NOT write visualWeight expected 1.01 owns final point weight

Data for layout engine

11 / canonical contract
{ "module": "Compo 1.01", "name": "point_size_and_visual_weight", "version": "2.0", "owner_of": [ "point.size_class", "point.visual_weight_base", "point.visual_weight_contextual" ], "consumes": [ "Compo0.Canvas", "Compo1.00.PointRoleDescriptor", "Point.geometry", "Point.fillRatio", "Point.opacity", "StylePreset.pointSize" ], "deferred_inputs": { "point.local_contrast": "Compo 1.10", "point.isolation_modifier": "Compo 1.04/1.08", "point.crowding_modifier": "Compo 1.04/1.08", "point.edge_modifier": "Compo 1.02", "global.hierarchy_target": "future hierarchy module", "global.balance_score": "future balance module" }, "provides": [ "Point.radiusShortN", "Point.diameterShortN", "Point.sizeClass", "Point.visualWeightBase", "Point.visualWeightContextual", "Point.weightStatus", "Point.weightComponents" ], "passes": { "A_BASE": "intrinsic properties only", "B_CONTEXTUAL": "requires available owner-provided modifiers" }, "numeric_provenance": { "radiusShortN": "DERIVED", "diameterShortN": "DERIVED", "equivalentArea": "DERIVED", "sizeClassThresholds": "STYLE_PRESET", "gammaSize": "INITIAL_HEURISTIC", "kWeight": "INITIAL_HEURISTIC", "contextAggregator": "INITIAL_HEURISTIC" }, "rules": [ "PASS_A_MUST_NOT_USE_CONTEXT", "MISSING_DEFERRED_INPUTS_ARE_OMITTED_NOT_INVENTED", "CONTEXTUAL_WEIGHT_IS_RECOMPUTED_AFTER_CONTEXT_PASS", "RESIZE_INVALIDATES_DEPENDENT_GEOMETRY_MODULES", "POINT_WEIGHT_HAS_ONE_OWNER_ONLY" ], "dirty_thresholds": { "epsilonWeight": {"value":0.01,"source":"INITIAL_HEURISTIC"} }, "routing": { "diameterShortN_above_point_max": "PROMOTE_TO_SHAPE_MODULE" }, "finality": "LOCAL_METRIC_ONLY_GLOBAL_SOLVER_MAY_REQUEST_RESIZE" }

Финальная проверка

12 / checklist

Теперь правильно

  • нет циклической зависимости «вес нужен до контекста, но контекст нужен после placement»;
  • base и contextual weight разделены;
  • метрики других модулей не переопределяются;
  • отсутствующие deferred inputs не заменяются фантазийными коэффициентами;
  • есть dirty propagation и порог повторного расчёта;
  • global solver может запросить target weight, а 1.01 подбирает размер.

Что ещё будет калиброваться

  • gammaSize и kWeight;
  • shape/primitive optical factors;
  • role-specific target bands;
  • точная функция превращения owner metrics в modifiers;
  • граница point → shape;
  • epsilonWeight для стабильной итерации.