diff --git a/crates/sds-bot/src/main.rs b/crates/sds-bot/src/main.rs index 57e83a6..e490ff0 100644 --- a/crates/sds-bot/src/main.rs +++ b/crates/sds-bot/src/main.rs @@ -352,6 +352,11 @@ impl Bot { // firing is a candidate with a value. let mut menu: Vec<(String, FeatureVector)> = vec![("hold fire".to_string(), FeatureVector::new())]; + // Menu index to allocation index. Not matched by label on the way back: + // two enemies of the same chassis produce the same label, and a lookup + // by name would sometimes declare against the wrong machine while the + // log said otherwise. + let mut from_menu: Vec> = vec![None]; for (index, (label, target_id, shots)) in allocations.iter().enumerate() { let Some(target) = field.iter().copied().find(|u| u.id == *target_id) else { continue; @@ -391,18 +396,16 @@ impl Bot { .features(), ); menu.push((label.clone(), features)); + from_menu.push(Some(index)); } let Some(row) = Decision::score(me.id, &menu, &self.weights) else { return Action::Fire { attacks: vec![] }; }; - // Index zero is "hold fire"; every other index is one behind the - // allocation it came from only if nothing was skipped, so the label is - // matched rather than the index arithmetic trusted. - let taken = row.taken().label.clone(); + let taken = from_menu[row.chosen]; self.training.insert(me.id, row); - let Some((_, target_id, shots)) = allocations.iter().find(|(label, _, _)| *label == taken) - else { + // `None` is "hold fire", which is a decision and is recorded as one. + let Some((_, target_id, shots)) = taken.map(|index| &allocations[index]) else { return Action::Fire { attacks: vec![] }; }; self.declared.push((me.id, *target_id)); @@ -468,7 +471,10 @@ impl Bot { sizes.dedup(); for size in sizes { out.push(( - format!("{size} at {name}"), + // The id is in the label because two enemies of the same + // chassis are common and two identical labels in one + // decision are unreadable. + format!("{size} at {name} #{target}"), target, affordable[..size].to_vec(), ));