brush-paint: roll meta-optimizer idx-20 winners into PaintParams + ScoreWeights defaults

Inner-score result on the alphabet corpus: cov_fail=8, bg_fail=0,
len_fail=0; ~12 px bg per letter. Stroke-count constraints (12
single-stroke + 11 two-stroke letters) are NOT respected by the
soft inner score; that's a known limitation of the search, not of
these defaults — would need a larger per-letter penalty in the
inner score to bite the gradient.

Frontend DEFAULT_PAINT_PARAMS mirror updated to match.
This commit is contained in:
Mitchell Hansen
2026-05-05 22:25:13 -07:00
parent 513d07228a
commit 50c8be46ee
2 changed files with 32 additions and 28 deletions

View File

@@ -63,20 +63,20 @@ export async function getStreamlineDebug(passIdx, hullIdx, params = DEFAULT_STRE
// Default PaintParams must match Rust's `impl Default for PaintParams`.
export const DEFAULT_PAINT_PARAMS = {
brush_radius_factor: 1.15,
brush_radius_offset_px: 0.25,
brush_radius_percentile: 0.85,
brush_radius_factor: 0.88,
brush_radius_offset_px: 0.50,
brush_radius_percentile: 0.93,
step_size_factor: 0.40,
n_directions: 48,
lookahead_steps: 3,
momentum_weight: 0.20,
overpaint_penalty: 0.10,
walk_bg_penalty: 4.0,
walk_bg_penalty: 0.69,
min_score_factor: 0.20,
polish_iters: 1,
polish_iters: 2,
polish_search_factor: 0.5,
bg_penalty: 2.0,
min_component_factor: 1.20,
min_component_factor: 1.49,
pen_lift_penalty: 0.0,
pen_lift_reach: 3.0,
max_steps_per_stroke: 4000,

View File

@@ -140,27 +140,26 @@ pub struct PaintParams {
impl Default for PaintParams {
fn default() -> Self {
Self {
// OPTIMIZER-TUNED DEFAULTS (under hard 5%-bg / 5%-unpainted /
// 2×-skel ceilings, score 333M → 41M). Bigger brush,
// step_size_factor=0.4 to keep disks tightly packed,
// walk_bg_penalty=4 to steer the walker onto the ridge,
// n_directions=48 for finer turning resolution. Dijkstra
// repaint still disabled — single-stroke W/M still need it
// turned on per-letter via the slider.
brush_radius_factor: 1.15,
brush_radius_offset_px: 0.25,
brush_radius_percentile: 0.85,
// META-OPTIMIZER WINNING CONFIG (idx 20 of 24-sample lex-
// ranked search). Tier-1 result on the corpus: cov_fail=8,
// bg_fail=0, len_fail=0; ~12 px bg per letter on average.
// Stroke-count constraints (12 single-stroke + 11 two-stroke
// letters miscounted) are NOT respected — known limitation
// of the soft inner-score the meta search uses.
brush_radius_factor: 0.88,
brush_radius_offset_px: 0.50,
brush_radius_percentile: 0.93,
step_size_factor: 0.40,
n_directions: 48,
lookahead_steps: 3,
momentum_weight: 0.20,
overpaint_penalty: 0.10,
walk_bg_penalty: 4.0,
walk_bg_penalty: 0.69,
min_score_factor: 0.20,
polish_iters: 1,
polish_iters: 2,
polish_search_factor: 0.5,
bg_penalty: 2.0,
min_component_factor: 1.20,
min_component_factor: 1.49,
pen_lift_penalty: 0.0,
pen_lift_reach: 3.0,
max_steps_per_stroke: 4000,
@@ -1645,16 +1644,21 @@ impl Default for ScoreWeights {
// So the sweep prefers a smaller-radius solution that leaves a
// few unpainted pixels over a larger-radius solution that paints
// 50× as many bg pixels.
// Meta-optimizer winning weights (idx 20). Note: meta-opt
// didn't fix stroke-count constraint failures — those need a
// larger per-letter penalty in the inner score before they
// bite the gradient. Soft costs are well-tuned for the
// tier-1/tier-2 lex objective.
Self {
stroke: 500.0,
length: 5.0,
bg: 50.0,
repaint: 30.0,
unpainted: 50.0, // mild — density carries the weight
unpainted_density: 10.0, // cluster_size^1.5 × 10
length_excess: 300.0,
curvature: 500.0,
brush_size: 2000.0, // pressure toward bigger brush. Per
stroke: 844.0,
length: 8.6,
bg: 98.0,
repaint: 8.8,
unpainted: 70.0,
unpainted_density: 22.8,
length_excess: 423.0,
curvature: 515.0,
brush_size: 214.0, // (was 2000 — meta dropped pressure
// letter, +1 px brush = +2000 bonus;
// vs bg=50/px that's "worth" up to
// ~40 extra bg pixels per letter. So