COMPO · 1.11 · POINT GENERATOR / ORCHESTRATOR

Авто
генератор
точек

Интеграционный модуль точечной системы. Он не изобретает новые эстетические правила и не владеет финальным score. Его задача — правильно вызвать Compo 1.00–1.10, поддерживать зависимости, делать contextual recompute, генерировать кандидатов и передавать их в Compo 1.12 и глобальный solver.

Контракт интегратора

00 / module api

CONSUMES

Compo0.Scene Compo0.Constraint[] Compo0.StylePreset Compo0.Seed Compo1.00 role descriptors Compo1.01 weight model Compo1.02 placement provider Compo1.03 pair geometry/metrics Compo1.04 cluster geometry/metrics Compo1.05 rhythm candidates/metrics Compo1.06 grid candidates/metrics Compo1.07 topology candidates/metrics Compo1.08 noise field policy/metrics Compo1.09 contact/conflict validation Compo1.10 color/tone candidates/metrics Compo1.12 scoring provider? ← DEFERRED

PROVIDES

PointSceneCandidate[] PointSceneState DependencyState DirtySet PassReport[] CorrectionRequest[] RejectedCandidate[] AuditEvent[] seedUsed iterationCount convergenceState unresolvedInputs[]

DOES NOT OWN

point.visual_weight point.local_contrast point.placement_score pair.* metrics cluster.* metrics rhythm.* metrics grid.* metrics conflict.* metrics noise.* metrics pointSystem.composition_score global.* metrics final winner

1.11 оркестрирует владельцев, но не переопределяет их метрики.

Главная архитектурная поправка

01 / no circular dependency

PASS A — intrinsic

До появления окружения точка получает только свойства, не требующие контекста.

1.00 assign roles 1.01 PASS_A_BASE radiusShortN visualWeightBase NO contrast modifier NO isolation modifier NO crowding modifier NO edge modifier

PASS B — contextual

После placement, соседей, grid, conflicts и local background вес пересчитывается владельцем 1.01.

after 1.02..1.10: 1.01 PASS_B_CONTEXTUAL consume owner-provided modifiers compute visualWeightContextual if deltaWeight > epsilonWeight: mark dependent modules DIRTY
Запрещённый цикл: нельзя требовать contextual visual weight до размещения точки, если этот contextual weight зависит от размещения. 1.11 обязан сначала построить сцену по visualWeightBase, затем выполнить contextual recompute.

Полный последовательный pipeline

02 / deterministic execution order
0 INPUTnormalize scene, seed, canvas, constraints
1 ROLESCompo 1.00 functional + visual role candidates
2 BASE SIZECompo 1.01 PASS A: radius + base weight
3 PLACECompo 1.02 placement candidates
4 RELATE1.03 pairs + 1.04 clusters + 1.05 rhythm
5 STRUCTURE1.06 grid + 1.07 topology + 1.08 noise
6 VALIDATE1.09 contacts / tangencies / protected geometry
7 COLOR1.10 sample background + color/tone candidates
8 CONTEXT WEIGHT1.01 PASS B using fresh owned context
9 DIRTY RECOMPUTEre-run only modules invalidated by changed state
10 SCORECompo 1.12 aggregates owned metrics; 1.11 does not
11 CORRECT / HANDOFFapply repair requests, iterate, then pass candidates global
repeat passes 6..11 while: hardViolations exist OR scoringProvider requests repair OR dirtySet is not empty stop when: dirtySet empty AND hardViolations == 0 AND (scoreDelta < epsilonScore OR scoringProvider unavailable) OR iterationCount >= maxIterations IMPORTANT: if Compo 1.12 is unavailable: output LOCALLY_VALID_UNRANKED candidates DO NOT invent score DO NOT choose "best" by hidden heuristic

Contextual weight loop

03 / explicit feedback cycle
1.01 PASS Abase weight 1.02–1.10positionneighbors / gridconflict / color 1.01 PASS Bcontext weight delta > ε → dirty recompute
Контекст не переписывает формулу веса сам. Он поставляет владельцу 1.01 свежие входы, после чего 1.01 пересчитывает contextual weight.

Freshness contract

ContextInput { metricId, ownerModule, ownerVersion, value, sourceStateHash, computedAtPass, status: FRESH | STALE | MISSING } 1.01 PASS B may consume only FRESH values. STALE value → omit + mark unresolved. MISSING value → omit + never invent neutral fake value.

Это не даёт модулю использовать старый contrast после перемещения точки или старый crowding после изменения соседей.

Dirty graph и точечный recompute

04 / dependency invalidation
Что изменилосьНужно инвалидироватьПочему
point.positionN1.03, 1.04, 1.05*, 1.06, 1.07, 1.08*, 1.09, 1.10, 1.01Bменяются расстояния, окружение, конфликт, фон и contextual weight
point.radiusShortN1.03, 1.04, 1.06, 1.07*, 1.08*, 1.09, 1.10*, 1.01Bменяются surface gap, occupancy, contact clearance и sample footprint
point.color1.10, 1.01Bменяется local contrast → contextual weight
visualWeightContextual1.04, 1.06, 1.07, 1.08эти модули могут использовать effective fresh weight
grid1.02*, 1.05*, 1.06, 1.07*grid может быть candidate provider или structural constraint
protectedZones1.02, 1.08, 1.09меняется feasible geometry
stylePresetвсе модули, использующие STYLE_PRESETполитика кандидатов и диапазоны могли измениться

* — только если конкретный объект/кандидат использует соответствующую зависимость. Dirty propagation должна быть по dependency flags, а не слепо запускать всё.

markDirty(change): affected = dependencyGraph.query(change.field) for module in affected: if module.dependencyPredicate(change.sceneState): dirtySet.add(module) recomputeDirty(): modules = topologicalSort(dirtySet) for module in modules: module.recompute() updateStateHash(module.outputs) dirtySet.clearResolved()

Генерация кандидатов

05 / bounded search

Deterministic base

Сначала генерируются структурные кандидаты из ролей, grid, anchors и разрешённых областей.

baseCandidates = roleProviders + gridProviders + anchorProviders + relationProviders

Seeded variants

Затем разрешён bounded jitter, изменение размера и локальных вариантов.

rng = PRNG(hash(seed, candidateId, variant)) variant = base + boundedDelta(rng)

Hard filter first

Невалидные кандидаты не получают эстетического score.

if hardViolation: reject(candidate) log(reason) skip scoring
INITIAL_HEURISTIC Количество кандидатов — инженерный бюджет, а не композиционный закон. Стартово допустимо candidateBudget = clamp(12 + 3*N_semantic, 24, 128); для noise-field используется отдельный генератор 1.08.

Corrective pass

06 / repair instead of guess

Допустимые операции

MOVE(point, delta) RESIZE(point, radius) RECOLOR(point, colorCandidate) ROLE_SWITCH(point, allowedRoleCandidate) DROP(optionalPoint) REGENERATE_SUBSET(groupId) SNAP_TO_GRID(point, gridCandidate) UNSNAP(point) CHANGE_RELATION(pair/profile)

Любая операция обязана перечислить, какие модули становятся DIRTY.

Источник correction request

HARD owner → mandatory repair/reject Compo1.12 → score-directed repair request global solver → contextual override user lock → immutable / constraint style preset → low-priority preference

1.11 не изобретает причину коррекции. Он исполняет запрос владельца правила и ведёт audit log.

applyCorrection(request): assert request.owner != null assert request.reasonCode != null assert request.priorityClass != null if conflictsWithHigherPriority(request): rejectCorrection(request) else: mutateCandidate(request.operation) markDirty(request.changedFields) recomputeDirty() if scoringProvider exists: rescoreViaCompo1_12()

Random — только последний слой

07 / reproducible variation
SEED
reproducible

Одинаковый вход + seed + module versions → одинаковые candidates.

δ
bounded

Random не выходит за диапазоны owner-модулей.

LOW
priority

Hard, semantic и global rules всегда выше random.

LOG
audit

Каждое stochastic решение сохраняет seed fragment и выбранное значение.

Worked example: почему нужен второй проход

08 / visual proof
A · BASEweightBase = 0.41
До контекста точка имеет только intrinsic/base weight.
B · CONTEXTcrowding ↑ / isolation ↓
Соседи меняют isolation/crowding. Эти данные принадлежат контекстным владельцам.
C · CORRECTEDmove → recompute → rescore
Correction меняет position, поэтому 1.03/1.04/1.06/1.09/1.10 и 1.01B должны пересчитаться.
STATE A visualWeightBase = 0.41 visualWeightContextual = MISSING STATE B localContrast = 0.82 [owner: 1.10] crowdingModifier = 0.68 [owner: 1.04/1.08 context] edgeModifier = 1.00 [owner: 1.02] visualWeightContextual = 0.31 [owner: 1.01] deltaWeight = 0.10 > epsilonWeight ACTION mark dependent modules DIRTY generate MOVE candidate recompute owners ask Compo 1.12 to rescore NO hidden "designer intuition" is inserted.

Deterministic tests

09 / orchestration tests
TestInput eventExpected orchestration result
T01fresh input, no contextrun 1.01 PASS A; never request PASS B before placement/context exists
T02point position changedinvalidate relation/context modules; 1.10 background sample becomes stale; then 1.01B stale
T03color changed onlyrerun 1.10 → 1.01B; do not rerun geometry unless contextual weight change invalidates weight-dependent modules
T04Compo 1.12 missingreturn valid candidates with status LOCALLY_VALID_UNRANKED; no invented best candidate
T05hard conflict from 1.09reject or mandatory repair before score aggregation
T06same input + same seed + same versionscandidate ids, stochastic deltas and audit trail deterministic
T07stale localContrast after move1.01B must omit it; must not reuse previous value
T08maxIterations reached with dirty setstatus NON_CONVERGED + diagnostics; never silently call result final

DATA FOR LAYOUT ENGINE

10 / machine contract
{ "module": "Compo 1.11", "version": "2.0", "name": "point_system_generator_orchestrator", "scope": "generation, dependency scheduling, corrective execution, handoff", "consumes": [ "Compo0.Scene", "Compo0.Constraint[]", "Compo0.StylePreset", "Compo0.Seed", "Compo1.00.*", "Compo1.01.*", "Compo1.02.*", "Compo1.03.*", "Compo1.04.*", "Compo1.05.*", "Compo1.06.*", "Compo1.07.*", "Compo1.08.*", "Compo1.09.*", "Compo1.10.*", "Compo1.12.ScoreProvider?" ], "provides": [ "PointSceneCandidate[]", "PointSceneState", "DependencyState", "DirtySet", "PassReport[]", "CorrectionRequest[]", "RejectedCandidate[]", "AuditEvent[]", "ConvergenceState" ], "owns_metrics": [], "owns_orchestration_counters": [ "iterationCount", "hardRejectCount", "dirtyModuleCount", "unresolvedInputCount", "candidateCount", "convergenceDelta" ], "deferred_inputs": { "pointSystem.composition_score": "Compo 1.12", "global.balance_score": "future global owner", "global.hierarchy.score": "future global owner", "global.negative_space.score": "future global owner", "global.eye_flow.score": "future global owner" }, "pass_order": [ "NORMALIZE_INPUT", "ASSIGN_ROLES_1_00", "BASE_WEIGHT_1_01A", "PLACEMENT_1_02", "PAIR_1_03", "CLUSTER_1_04", "RHYTHM_1_05", "GRID_1_06", "TOPOLOGY_1_07", "NOISE_1_08", "CONFLICT_1_09", "COLOR_1_10", "CONTEXT_WEIGHT_1_01B", "DIRTY_RECOMPUTE", "SCORE_1_12_IF_AVAILABLE", "CORRECTIVE_PASS", "HANDOFF" ], "freshness_policy": { "stale_external_metric": "OMIT_AND_MARK_UNRESOLVED", "missing_external_metric": "OMIT_NEVER_INVENT", "contextual_weight": "ONLY_FROM_FRESH_OWNER_INPUTS" }, "candidate_budget": { "formula": "clamp(12 + 3*N_semantic, 24, 128)", "status": "INITIAL_HEURISTIC", "noise_points": "managed separately by Compo 1.08" }, "convergence": { "epsilonWeight": "consume Compo1.01 epsilonWeight", "epsilonScore": {"value":0.005,"status":"INITIAL_HEURISTIC"}, "maxIterations": {"value":8,"status":"INITIAL_HEURISTIC"}, "withoutScoringProvider": "LOCALLY_VALID_UNRANKED" }, "rules": [ "PASS_A_BASE_WEIGHT_BEFORE_CONTEXT", "PASS_B_CONTEXT_WEIGHT_AFTER_1_02_TO_1_10", "NO_STALE_METRIC_REUSE", "NO_HIDDEN_SCORE_AGGREGATION", "HARD_CONFLICTS_BEFORE_SCORING", "EVERY_MUTATION_MUST_PROPAGATE_DIRTY_FLAGS", "SAME_INPUT_SEED_AND_VERSIONS_MUST_BE_REPRODUCIBLE", "NON_CONVERGED_RESULT_MUST_BE_EXPLICIT", "GLOBAL_SOLVER_HAS_FINAL_AUTHORITY" ], "finality": "POINT_SYSTEM_CANDIDATE_GENERATOR_ONLY" }