diff --git a/README.md b/README.md
index 001e016..a024335 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,12 @@ into a scenario's budget - damage a design until it is worth a given figure:
helm wear "Atlas AS7-D" --to 1500 --megamek
--bridge-dir
+`--battle` does it with damage that has a shape - MegaMek's hit table, fire
+that clusters, equipment lost when it gets through the plate - so every seed is
+a different machine worth the same, and none of them is a mission kill:
+
+ helm wear "Atlas AS7-D" --to 1500 --battle --seed 3 ...
+
`--clusters` is the one that matters. A ranked list of the worst disagreements
shows whichever design is strangest; grouping them by what they have in common
shows which *rule* is missing, and its second section ranks the failures by how
diff --git a/crates/helm-cli/src/main.rs b/crates/helm-cli/src/main.rs
index edabf4f..6a8af1e 100644
--- a/crates/helm-cli/src/main.rs
+++ b/crates/helm-cli/src/main.rs
@@ -95,6 +95,12 @@ WEAR
machine that has already been in a fight into a scenario's budget. Plate
first, then the frame under it, spread evenly across the locations.
--to The figure to aim for. Required.
+ --battle Damage it in a battle instead of shaving plate evenly:
+ fire lands where the hit table sends it, clusters, and
+ takes equipment out when it gets through. Every seed is
+ a different machine worth the same, and none of them is
+ a mission kill.
+ --seed One battle in particular. Implies --battle.
REPAIRS
What a force needs putting right, location by location: points of plate,
@@ -123,6 +129,8 @@ struct Opts {
bench: bool,
why: bool,
to: Option,
+ battle: bool,
+ seed: Option,
}
fn parse_opts(args: &[String]) -> Result {
@@ -138,6 +146,8 @@ fn parse_opts(args: &[String]) -> Result {
bench: false,
why: false,
to: None,
+ battle: false,
+ seed: None,
};
let mut i = 0;
while i < args.len() {
@@ -188,6 +198,19 @@ fn parse_opts(args: &[String]) -> Result {
);
i += 2;
}
+ "--battle" => {
+ o.battle = true;
+ i += 1;
+ }
+ "--seed" => {
+ o.seed = Some(
+ need(i)?
+ .parse()
+ .map_err(|_| "--seed takes a number".to_string())?,
+ );
+ o.battle = true;
+ i += 2;
+ }
"--why" => {
o.why = true;
i += 1;
@@ -638,10 +661,27 @@ fn wear(args: &[String]) -> Result<(), String> {
.find(|u| u.display_name().eq_ignore_ascii_case(name))
.ok_or_else(|| format!("no design called {name}"))?;
- let worn = helm_bv::wear_to(unit, &catalogue, target).map_err(|e| e.to_string())?;
+ let worn = if opts.battle {
+ let battle = helm_bv::Battle {
+ seed: opts.seed.unwrap_or(1),
+ ..helm_bv::Battle::default()
+ };
+ // A handful of battles rather than one: damage is drawn rather than
+ // solved, so the first one can overshoot where the third lands on the
+ // figure. A named seed asks for one battle in particular.
+ let tries = if opts.seed.is_some() { 1 } else { 6 };
+ helm_bv::wear_in_battles(unit, &catalogue, target, &battle, tries)
+ } else {
+ helm_bv::wear_to(unit, &catalogue, target)
+ }
+ .map_err(|e| e.to_string())?;
println!(
- "{} is {} whole, {} worn{}",
+ "{}{} is {} whole, {} worn{}",
unit.display_name(),
+ match worn.seed {
+ Some(seed) => format!(" (battle {seed})"),
+ None => String::new(),
+ },
worn.whole,
worn.value,
if worn.is_exact() {
@@ -663,11 +703,16 @@ fn wear(args: &[String]) -> Result<(), String> {
parts.push(format!("{} structure", repair.structure));
}
println!(" {:<16} {}", repair.location, parts.join(", "));
+ // What a penetrating hit took with it, which is the part of a worn
+ // machine that makes it that machine rather than a generic one.
+ for slot in &repair.destroyed {
+ println!(" {:<16} slot {}: {}", "", slot.at + 1, slot.what);
+ }
}
let total = helm_bv::Total::of(&repairs);
println!(
- " {:<16} {} armour and {} structure gone",
- "", total.armor, total.structure
+ " {:<16} {} armour, {} structure and {} items gone",
+ "", total.armor, total.structure, total.items
);
Ok(())
}