diff --git a/src/brush_paint_opt.rs b/src/brush_paint_opt.rs index 5d9a63be..2190c293 100644 --- a/src/brush_paint_opt.rs +++ b/src/brush_paint_opt.rs @@ -446,27 +446,30 @@ pub fn evaluate_score_weights( (best_params, report) } -/// Same shape as score_for_letter but takes ScoreWeights so the meta- -/// optimizer can vary them. Mirrors the original `score_for_letter` -/// barriers exactly; the only thing the meta optimizer changes is the -/// soft-term weights (ScoreWeights), not the hard barriers. +/// Inner score function used during META-OPTIMIZATION. Unlike +/// `score_for_letter` (the production score), this version DOES NOT +/// add 100M-magnitude barriers for bg / coverage / length-budget +/// violations. +/// +/// Why: the barriers are so large they swamp every soft-weight +/// difference. With barriers in place, two different ScoreWeights +/// candidates produce inner-descent results dominated by "minimise +/// barriers" rather than "minimise the weighted soft score" — so +/// the inner optimizer converges to identical PaintParams under +/// most weight choices and the meta search has nothing to compare. +/// +/// Without barriers, the inner descent is purely guided by the +/// candidate's ScoreWeights → different weights produce genuinely +/// different optima → the OUTER lex comparator ranks them by the +/// hard criteria (tier-1 fail counts) at the end. +/// +/// Stroke-count penalties stay (they're per-letter natural-form +/// requirements, not score-vs-feasibility tradeoffs) and the +/// "refuse zero strokes" pin stays (without it the inner descent +/// can degenerate to "paint nothing" under low coverage weight). fn score_for_letter_with_weights(ch: char, m: &PaintMetrics, w: &ScoreWeights) -> f32 { use crate::brush_paint::score_weighted; let mut s = score_weighted(m, *w); - if m.total_swept > 0 { - let bg_rate = m.bg_painted as f32 / m.total_swept as f32; - if bg_rate > 0.05 { - s += 100_000_000.0 * (bg_rate - 0.05); - } - } - let cluster_threshold = 0.5 * std::f32::consts::PI * m.brush_radius * m.brush_radius; - let max_cluster = m.unpainted_clusters.iter().copied().max().unwrap_or(0) as f32; - if max_cluster > cluster_threshold { - s += 1_000_000.0 * (max_cluster - cluster_threshold); - } - if m.skeleton_length > 0 && m.total_length > 2.0 * m.skeleton_length as f32 { - s += 100_000.0 * (m.total_length - 2.0 * m.skeleton_length as f32); - } if m.strokes == 0 { s += 200_000.0; } if is_single_stroke_letter(ch) && m.strokes != 1 { s += 50_000.0 * ((m.strokes as i64 - 1).abs() as f32);