COMPO · 1.12 · POINT-SCENE SCORER

Скоринг
точечной
сцены

Единый локальный scorer точечной подсистемы. Он ничего не «рисует» и не придумывает новых правил: принимает MetricResult от владельцев Compo 1.01–1.10, выполняет hard-reject, applicability-aware агрегацию, коррекцию взаимозависимостей, confidence accounting и выдаёт локальный рейтинг для Compo 1.11 и глобального solver.

Контракт scorer

00 / module api

CONSUMES

PointSceneCandidate MetricResult[] Candidate.hardViolations[] Candidate.auditEvidence[] StylePreset.metricWeights? StylePreset.scoreThresholds? Compo0.seed

PROVIDES

PointSceneScoreResult { status, localScore, rawPositiveScore, softPenalty, confidence, metricBreakdown[], rejectedBy[], repairHints[], unresolvedInputs[], comparisonKey }

DOES NOT OWN

global.balance_score global.negative_space_score global.hierarchy_score global.eye_flow_score typography.* imageCrop.* posterGlobalScore semantic correctness
Жёсткое правило: Compo 1.12 не пересчитывает метрики Compo 1.01–1.10. Он агрегирует только значения, полученные от owner-модулей, и не создаёт дублирующие spacingPenalty, balancePenalty или другие «удобные» оценки.

Что именно может входить в score

01 / canonical owners
ИсточникСобственные метрикиИспользование в 1.12
Compo 1.01point.visual_weight_base, point.visual_weight_contextualowner должен экспортировать fitness/target deviation как MetricResult; scorer не выводит его из weight повторно
Compo 1.02point.safe_clearance_score, landmark_fit_score, anchor_fit_score, edge_relation_score, local_role_fit, local_placement_scoreпредпочтительно агрегировать только owner composite local_placement_score, а компоненты хранить как evidence
Compo 1.03pair geometry + pair local metricsодна агрегированная pair metric на применимую пару/сцену
Compo 1.04cluster local metricscluster metric только если кластер реально существует
Compo 1.05rhythm.profile_match, interval_variation, monotonicity, trajectory_consistency, size_profile_match, rhythm.local_scoreв общий score входит rhythm.local_score; компоненты — diagnostics
Compo 1.06gridFitScore + grid metric componentsв общий score входит owner composite grid fit, если grid включён
Compo 1.07localConnectivityScore, localTopologyScoreтолько если connections/network действительно запрошены
Compo 1.08noise.count_fit, spacing_distribution, field_match, soft_intrusion_penalty, local_neighbor_pressure, noise.local_scoreв общий score входит noise.local_score; компоненты — evidence
Compo 1.09point.clearance_score, contact_conflict_penalty, tangency_penalty, contact_resolution_costhard contact может reject; soft penalties входят один раз
Compo 1.10point.local_contrast, local_tonal_contrast, local_oklab_distance, background_complexity, color_separation_score, color_candidate_fitв общий score предпочтительно входит color_candidate_fit; contrast используется 1.01 для weight и не должен дублироваться
Composite-first policy: если owner уже отдаёт собственный composite score, scorer использует его как основную метрику, а внутренние компоненты не суммирует повторно. Это главный механизм защиты от double-counting.

Единый MetricResult

02 / normalized interface

CANONICAL

MetricResult { key: string, owner: string, value: float, // canonical 0..1, higher = better applicability: float, // 0..1 confidence: float, // 0..1 status: "ok|warn|fail|na", source: "DERIVED|EMPIRICAL|HEURISTIC|STYLE_PRESET", composite: bool, componentOf: string|null, evidence: object, repairHints: [] }

Правила

if metric.status == "na": applicability = 0 if composite == true: descendants are diagnostic-only if confidence == 0: metric carries no voting weight if value outside [0,1]: reject metric payload normalization belongs to OWNER never normalize twice

Hard reject раньше score

03 / feasibility gate

REJECT

if Candidate.hardViolations.length > 0: return { status: "rejected", localScore: null, confidence: 1, rejectedBy: hardViolations, rankEligible: false }

Нарушение hard-constraint нельзя компенсировать хорошим ритмом, цветом или сеткой.

SOFT ISSUE

soft penalty is allowed only if: owner is explicit penalty is normalized same defect is not already in composite score applicability > 0 evidence exists

Штраф без owner и evidence запрещён.

Applicability-aware веса

04 / dynamic weighting
baseWeight_i = weightPolicy[metric.key] sourceConfidenceFactor_i = metric.confidence applicabilityFactor_i = metric.applicability styleMultiplier_i = stylePreset.metricMultiplier[metric.key] ?? 1 effectiveWeight_i = baseWeight_i × sourceConfidenceFactor_i × applicabilityFactor_i × styleMultiplier_i W = Σ effectiveWeight_i if W == 0: status = "insufficient_metrics" else: normalizedWeight_i = effectiveWeight_i / W
1.0
applicable

Полный вклад.

0.5
partial

Половинный вклад.

0
not applicable

Метрика исключается из знаменателя.

seed
reproducible

Одинаковые inputs → одинаковый score.

Весовые коэффициенты = INITIAL_HEURISTIC / STYLE_PRESET. Они не являются законами композиции. Их задача — дать стартовую рабочую агрегацию до последующей калибровки на корпусе реальных работ.

Коррелирующие метрики

05 / anti double-counting

Плохая агрегация

score += point.local_contrast score += color_candidate_fit score += visual_weight_contextual // local contrast уже повлиял на visual weight // и может быть частью color candidate fit // дефект получает 2–3 голоса

Правильная агрегация

PRIMARY VOTES: visual_weight_fitness local_placement_score pair_or_cluster_composite rhythm.local_score gridFitScore topology composite noise.local_score contact safety composite color_candidate_fit COMPONENTS: diagnostics only
correlationPolicy: if metric.componentOf != null: do not vote when parent composite is present if metric.key in dependencyGraph[anotherPrimaryMetric]: choose owner-designated primary metric if two primary metrics share > correlationThreshold evidence: apply correlationGroup cap OR reduce lower-confidence vote INITIAL_HEURISTIC: correlationThreshold = 0.65 groupVoteCap = 1.25 × max(singleMetricWeight)

Формула локального score

06 / aggregation
positiveScore = Σ(normalizedWeight_i × metric.value_i) softPenalty = Σ( penalty.weight × penalty.severity × penalty.applicability × penalty.confidence ) localScoreRaw = positiveScore - softPenalty localScore = clamp(localScoreRaw, 0, 1) IMPORTANT: penalties owned by metric owner only no global metrics here no bonus for missing metrics no penalty for non-applicable metrics

Candidate A

placement
.89
grid
.84
contact
.96

localScore ≈ .89

Candidate B

placement
.72
grid
.94
contact
.48

localScore ≈ .70

Candidate C

placement
.82
grid
N/A
contact
.91

grid applicability = 0; score не занижается.

Score и confidence — разные вещи

07 / epistemic quality

Scene confidence

sceneConfidence = Σ(normalizedWeight_i × metric.confidence_i) coverage = applicableMetricFamiliesPresent / applicableMetricFamiliesExpected finalConfidence = sceneConfidence × coverage

Почему это важно

Кандидат может иметь score 0.91, но confidence 0.43, если половина необходимых owner-модулей ещё не рассчитана. Такой вариант нельзя считать эквивалентным сцене 0.89 с confidence 0.96.

rankKey = { feasibility, localScore, confidence, correctionCost, stableTieBreak }

Как сравнивать кандидатов

08 / deterministic ranking
compare(A,B): 1. feasible beats rejected 2. if |A.score-B.score| > scoreEpsilon: higher score wins 3. else if |A.confidence-B.confidence| > confidenceEpsilon: higher confidence wins 4. else if correctionCost differs: lower correctionCost wins 5. else: stable deterministic tie-break by candidateId/seed INITIAL_HEURISTIC: scoreEpsilon = 0.015 confidenceEpsilon = 0.03
Scorer возвращает локальный порядок. Глобальный solver имеет право выбрать локально второй вариант, если тот существенно улучшает hierarchy, negative space, balance, image/text interaction или другие будущие глобальные метрики.

Scorer должен объяснять провал

09 / repair interface

Диагностика

lowestMetrics = sort(metricBreakdown by contributionLoss) take top 3

Не просто «score 0.57», а какие owner-модули тянут сцену вниз.

Repair hints

repairHints = mergeUnique( lowestMetrics.repairHints, softPenalty.repairHints )

Scorer не придумывает repair самостоятельно — получает подсказки от владельцев.

В Compo 1.11

if score < target: send repairHints mark affected modules dirty regenerate limited parameters rescore

Так появляется настоящий corrective loop.

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

10 / regression

T01 · N/A не штрафует

grid.enabled = false M_gridFit.applicability = 0 EXPECTED: M_gridFit excluded from W same other metrics → same score as scene without grid metric

T02 · Hard reject

hardViolations = ["protected_zone"] other metrics = all 1.0 EXPECTED: status = rejected localScore = null rankEligible = false

T03 · Composite suppresses components

rhythm.local_score present rhythm.profile_match present rhythm.monotonicity present EXPECTED: only rhythm.local_score votes components diagnostics-only

T04 · Confidence

A.score = .90 / confidence=.40 B.score = .89 / confidence=.95 scoreEpsilon=.015 EXPECTED: score delta=.01 < epsilon B wins by confidence

T05 · No global leakage

same point metrics global.balance changes .2 → .9 EXPECTED: Compo1.12.localScore unchanged

T06 · Deterministic tie

score/confidence/cost equal candidate IDs fixed seed fixed EXPECTED: repeat run → same winner

DATA FOR LAYOUT ENGINE

11 / machine contract
{ "module": "Compo 1.12", "name": "point_scene_scoring", "version": "2.0", "owner_of": [ "pointSystem.local_score", "pointSystem.local_score_confidence", "pointSystem.local_rank_key" ], "consumes": [ "PointSceneCandidate", "MetricResult[]", "Candidate.hardViolations[]", "Candidate.auditEvidence[]", "Compo0.StylePreset.metricWeights?", "Compo0.seed" ], "deferred_inputs": [ "global.balance_score", "global.negative_space_score", "global.hierarchy_score", "global.eye_flow_score", "typography.*", "imageCrop.*", "posterGlobalScore" ], "metric_policy": { "range": [0,1], "higher_is_better": true, "normalization_owner": "metric_owner", "composite_first": true, "exclude_na_from_denominator": true, "exclude_zero_confidence": true, "double_counting": "forbidden" }, "aggregation": { "positive": "sum(normalizedWeight_i * value_i)", "softPenalty": "sum(ownerPenalty_i)", "localScore": "clamp(positive-softPenalty,0,1)" }, "hard_policy": { "any_hard_violation": "REJECT", "hard_violation_can_be_compensated": false }, "confidence": { "metric_weighted": true, "coverage_factor": true, "reported_separately_from_score": true }, "defaults": { "scoreEpsilon": {"value":0.015,"status":"INITIAL_HEURISTIC"}, "confidenceEpsilon": {"value":0.03,"status":"INITIAL_HEURISTIC"}, "correlationThreshold": {"value":0.65,"status":"INITIAL_HEURISTIC"} }, "pipeline": [ "validate_metric_payloads", "hard_reject_gate", "select_owner_composites", "remove_inapplicable_metrics", "apply_confidence_and_style_weights", "apply_correlation_policy", "aggregate_positive_score", "subtract_owner_soft_penalties", "compute_confidence_and_coverage", "build_diagnostics", "build_repair_hints", "build_deterministic_rank_key" ], "finality": "LOCAL_POINT_SYSTEM_SCORE_ONLY" }