fix(rust): first-max tie-breaking in dir matching + python-style score rounding

Two divergences found by diffing the Rust sorter plan against the python
reference on a real 14k-file tree (both 99.82% identical):

- compute_match_name used max_by (LAST max) where python max() keeps the
  FIRST; a same-faction dir hit and a cross-faction hit scored equally and
  the wrong entry won (25 Deathwatch files).
- --json score used round() instead of round_ties_even(), printing 0.813
  where python prints 0.812.

After the fix the plans are identical: 14025/14025 targets, kinds and
scores, zero differences.
This commit is contained in:
2026-10-10 22:11:26 +02:00
parent 81972afd22
commit b8fdf508c8
2 changed files with 16 additions and 10 deletions
+1 -1
View File
@@ -538,7 +538,7 @@ fn cmd_sorter(args: &SorterArgs) -> anyhow::Result<()> {
"subfaction": i.subfaction,
"class": i.klass,
"unit": i.unit,
"score": (i.match_score * 1000.0).round() / 1000.0,
"score": (i.match_score * 1000.0).round_ties_even() / 1000.0,
"kind": i.match_kind,
"inner_count": i.inner_names.len(),
"target": i.target
+15 -9
View File
@@ -906,16 +906,22 @@ fn compute_match_name(
}
}
let cands: Vec<(f64, usize, bool)> = [same, other].into_iter().flatten().collect();
if !cands.is_empty() {
let normals: Vec<&(f64, usize, bool)> = cands.iter().filter(|c| !c.2).collect();
let winner = if normals.is_empty() {
cands.iter().max_by(|a, b| a.0.total_cmp(&b.0)).unwrap()
} else {
normals.iter().max_by(|a, b| a.0.total_cmp(&b.0)).unwrap()
};
return Some(winner.clone());
if cands.is_empty() {
return None;
}
None
// python `max(normals or cands, key=score)` keeps the FIRST
// maximum; a normal (non-weak) candidate always beats a weak one.
let has_normal = cands.iter().any(|c| !c.2);
let mut winner: Option<(f64, usize, bool)> = None;
for c in &cands {
if has_normal && c.2 {
continue;
}
if winner.as_ref().is_none_or(|w| c.0 > w.0) {
winner = Some(c.clone());
}
}
return winner;
} else {
pool_best(clean, &pool, ctx, threshold, cache)
}