Unblock supply trucks from an impossible RM48U return (#128)
build / mod (push) Failing after 21s
build / mod (push) Failing after 21s
step_supply sent every free truck into the return branch whenever any S-400 had a pending ground_return, even when that return could not proceed (the system moving, damaged, reloading or preparing, its round already claimed by another truck, the route marked unreachable, or no depot with room). The branch then found no target, cleared the job and stopped the truck, so independent typed supply requests starved. The return now keeps its priority only while it is executable: a carrier holding a reserved round looks for a depot with room in reach, an empty one for a system ready to hand a round over. Otherwise the claim stays untouched and only real RM48U demand (a requesting system with depot stock, or ground rounds aboard for one) pulls the truck into the typed supply branch; without such demand it holds its watch exactly as before. Redirecting a reserved round to a waiting request clears return_cargo once the last one leaves the truck, and a stale return_cargo with an empty hold no longer sends the truck to a depot handover it cannot complete. Regressions: three sim tests (moving claimant, pickup claimed by another truck with damaged/save-restore phases, unreachable route) and one full-drive lib test with a mid-flight state round trip; the rounds are conserved in every phase and the actual return completes once the claimant is back. All three sim tests fail on the old code. Checks: core release 200 passed/8 ignored + 8 scenarios; adapter 67 unit + 1 boundary + 5 scenarios; real WASM test-wasm 30 steps.
This commit is contained in:
@@ -2742,6 +2742,75 @@ mod tests {
|
||||
assert_eq!((after.sim.sams[0].air_48n6_stock, after.sim.sams[2].ground_stock, after.sim.sams[2].air_48n6_stock), (1, 0, 0));
|
||||
}
|
||||
|
||||
/// #128 end to end: A declares a return and drives off, B waits for a round; the truck must
|
||||
/// serve B anyway (a save/load mid-flight included), and once A is back the actual return
|
||||
/// completes. Rounds are conserved throughout.
|
||||
#[test]
|
||||
fn a_moving_return_claim_drives_and_supplies_and_survives_a_state_round_trip() {
|
||||
struct Flat;
|
||||
impl sim::world::WorldSource for Flat {
|
||||
fn solid(&self, _: i32, y: i32, _: i32) -> Option<bool> { Some(y < 64) }
|
||||
}
|
||||
let mut game = test_mod();
|
||||
for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 120.0), (SamKind::S400, 240.0), (SamKind::Tzm, 0.0)] {
|
||||
let id = game.sim.id();
|
||||
game.sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0));
|
||||
}
|
||||
game.sim.sams[0].ground_stock = 2;
|
||||
game.sim.sams[1].ground_rounds = 1;
|
||||
game.sim.sams[1].missiles = 3;
|
||||
game.sim.unload_ground_round(1).unwrap();
|
||||
assert!(game.sim.sams[1].ground_return);
|
||||
game.sim.sams[2].missiles = 3;
|
||||
// B is out of the depot's reach: the load becomes a pending delivery request.
|
||||
game.sim.load_ground_round(2).unwrap();
|
||||
assert!(game.sim.sams[2].ground_request);
|
||||
// A drives away: its return claim is impossible for a while.
|
||||
assert!(game.sim.order_move(1, DVec3::new(420.0, 64.0, 0.0)));
|
||||
let mut out = Out::default();
|
||||
let mut restored_mid_flight = false;
|
||||
let mut supplied = false;
|
||||
for _ in 0..12000 {
|
||||
let now = game.sim.tick;
|
||||
let cols = game.ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect();
|
||||
game.ground.answer(cols, now);
|
||||
let sections = game.ground.world.take_wanted(now);
|
||||
game.ground.world.serve(&Flat, sections, now);
|
||||
game.sim.step(&mut game.ground, None, &mut out);
|
||||
if !restored_mid_flight && game.sim.sams[3].ground_stock == 1 {
|
||||
// The truck took a round for B: save, reload, continue from the restored state.
|
||||
let saved = game.state_text(false, false);
|
||||
let mut restored = test_mod();
|
||||
restored.apply_state(&saved).unwrap();
|
||||
game = restored;
|
||||
restored_mid_flight = true;
|
||||
}
|
||||
if game.sim.sams[2].ground_rounds == 1 { supplied = true; break; }
|
||||
out = Out::default();
|
||||
}
|
||||
assert!(restored_mid_flight, "truck never picked up a round for B");
|
||||
assert!(supplied, "B starved while A's return was impossible");
|
||||
assert!(game.sim.sams[1].ground_return, "claim must survive the supply run");
|
||||
assert_eq!(game.sim.sams[0].ground_stock + game.sim.sams[3].ground_stock + game.sim.sams[1].ground_rounds + game.sim.sams[2].ground_rounds, 3);
|
||||
// A returns to its place; the waiting claim is then collected and driven to the depot.
|
||||
assert!(game.sim.order_move(1, DVec3::new(120.0, 64.0, 0.0)));
|
||||
let mut returned = false;
|
||||
for _ in 0..12000 {
|
||||
let now = game.sim.tick;
|
||||
let cols = game.ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect();
|
||||
game.ground.answer(cols, now);
|
||||
let sections = game.ground.world.take_wanted(now);
|
||||
game.ground.world.serve(&Flat, sections, now);
|
||||
game.sim.step(&mut game.ground, None, &mut out);
|
||||
if game.sim.sams[0].ground_stock == 2 && game.sim.sams[1].ground_rounds == 0 { returned = true; break; }
|
||||
out = Out::default();
|
||||
}
|
||||
assert!(returned);
|
||||
assert!(!game.sim.sams[1].ground_return);
|
||||
assert!(!game.sim.sams[3].return_cargo);
|
||||
assert_eq!(game.sim.sams[0].ground_stock + game.sim.sams[3].ground_stock + game.sim.sams[1].ground_rounds + game.sim.sams[2].ground_rounds, 3);
|
||||
}
|
||||
|
||||
/// #83: while an export records no other replay or film replaces it; after it, they may.
|
||||
#[test]
|
||||
fn typed_ammunition_changes_mark_the_state_dirty() {
|
||||
|
||||
+218
-29
@@ -119,41 +119,59 @@ impl Sim {
|
||||
let at = truck.pos;
|
||||
let truck_id = truck.id;
|
||||
// A return is picked up physically; its cargo remains marked until a depot accepts it.
|
||||
// The return keeps its priority only while it can actually proceed (#128): a carrier
|
||||
// holding a reserved round needs a depot with room in reach, an empty one a system
|
||||
// ready to hand a round over (whole, deployed, not claimed by another truck, a
|
||||
// reachable route). A return that is merely claimed but impossible used to clear the
|
||||
// job and stop the truck, starving independent supply requests; the claim now stays
|
||||
// untouched and only real RM48U demand pulls the truck away from its watch.
|
||||
let return_cargo = truck.return_cargo && truck.ground_stock > 0;
|
||||
let can_pick = truck.ground_stock == 0 && truck.air_48n6_stock < TRUCK_LOAD;
|
||||
let return_work = truck.return_cargo || (can_pick && self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_return));
|
||||
if return_work {
|
||||
let taken: Vec<u32> = self.sams.iter().filter(|s| s.kind == SamKind::Tzm && s.alive() && s.id != truck_id).filter_map(|s| s.job).collect();
|
||||
let target = if truck.return_cargo {
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::Depot && s.alive() && s.ground_stock < STOCK_LIMIT && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
} else {
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::S400 && s.alive() && s.ground_return && s.ground_rounds > 0 && !s.moving() && s.preparing.is_none() && s.reloading == 0 && s.part(Part::Launcher) > 0.0 && !taken.contains(&s.id) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
}.map(|s| (s.id, s.pos));
|
||||
self.sams[t].job = target.map(|x| x.0);
|
||||
if let Some((id, to)) = target {
|
||||
if at.distance(to) > HANDOVER {
|
||||
let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6);
|
||||
self.order_move_near(t, to + side, 3.5);
|
||||
} else if let Some(j) = self.sams.iter().position(|s| s.id == id) {
|
||||
if self.sams[t].return_cargo {
|
||||
self.sams[j].ground_stock += 1;
|
||||
self.sams[t].ground_stock -= 1;
|
||||
self.sams[t].return_cargo = false;
|
||||
} else {
|
||||
self.sams[j].ground_rounds -= 1;
|
||||
self.sams[j].ground_return = false;
|
||||
self.sams[t].ground_stock += 1;
|
||||
self.sams[t].return_cargo = true;
|
||||
if self.sams[t].air_48n6_stock > 0 && self.sams[j].missiles + self.sams[j].ground_rounds < self.sams[j].cells() {
|
||||
self.sams[t].air_48n6_stock -= 1;
|
||||
self.sams[j].missiles += 1;
|
||||
}
|
||||
let claim_pending = return_cargo || (can_pick && self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_return));
|
||||
let taken: Vec<u32> = self.sams.iter().filter(|s| s.kind == SamKind::Tzm && s.alive() && s.id != truck_id).filter_map(|s| s.job).collect();
|
||||
let target = if return_cargo {
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::Depot && s.alive() && s.ground_stock < STOCK_LIMIT && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
} else if can_pick {
|
||||
self.sams.iter().filter(|s| s.kind == SamKind::S400 && s.alive() && s.ground_return && s.ground_rounds > 0 && !s.moving() && s.preparing.is_none() && s.reloading == 0 && s.part(Part::Launcher) > 0.0 && !taken.contains(&s.id) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
} else {
|
||||
None
|
||||
}.map(|s| (s.id, s.pos));
|
||||
if let Some((id, to)) = target {
|
||||
self.sams[t].job = Some(id);
|
||||
if at.distance(to) > HANDOVER {
|
||||
let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6);
|
||||
self.order_move_near(t, to + side, 3.5);
|
||||
} else if let Some(j) = self.sams.iter().position(|s| s.id == id) {
|
||||
if self.sams[t].return_cargo {
|
||||
self.sams[j].ground_stock += 1;
|
||||
self.sams[t].ground_stock -= 1;
|
||||
self.sams[t].return_cargo = false;
|
||||
} else {
|
||||
self.sams[j].ground_rounds -= 1;
|
||||
self.sams[j].ground_return = false;
|
||||
self.sams[t].ground_stock += 1;
|
||||
self.sams[t].return_cargo = true;
|
||||
if self.sams[t].air_48n6_stock > 0 && self.sams[j].missiles + self.sams[j].ground_rounds < self.sams[j].cells() {
|
||||
self.sams[t].air_48n6_stock -= 1;
|
||||
self.sams[j].missiles += 1;
|
||||
}
|
||||
self.sams[t].job = None;
|
||||
out.sound("minecraft:block.piston.extend", at, 1.5, 0.8);
|
||||
}
|
||||
self.sams[t].job = None;
|
||||
out.sound("minecraft:block.piston.extend", at, 1.5, 0.8);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if claim_pending {
|
||||
// The impossible return still outranks a mere stock run: without RM48U demand
|
||||
// the truck holds its watch (and drops a stale job) exactly as before.
|
||||
let supply_demand = (self.sams[t].ground_stock > 0 && self.sams.iter().any(|s| s.alive() && s.ground_request))
|
||||
|| (self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request)
|
||||
&& self.sams.iter().any(|s| s.kind == SamKind::Depot && s.alive() && s.ground_stock > 0));
|
||||
if !supply_demand {
|
||||
self.sams[t].job = None;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Typed strike cargo takes precedence over generic reloads. Jobs are reconstructed
|
||||
// after loading a save; the request and physical cargo survive independently.
|
||||
let typed_work = truck.ground_stock > 0 || truck.air_48n6_stock > 0 ||
|
||||
@@ -176,6 +194,8 @@ impl Sim {
|
||||
let take = self.sams[t].ground_stock.min(STOCK_LIMIT - self.sams[j].ground_stock);
|
||||
self.sams[j].ground_stock += take;
|
||||
self.sams[t].ground_stock -= take;
|
||||
// The last reserved round left the truck: nothing marks as return cargo any more.
|
||||
if self.sams[t].ground_stock == 0 { self.sams[t].return_cargo = false; }
|
||||
}
|
||||
if self.sams[t].ground_stock == 0 && self.sams[j].ground_stock > 0 && self.sams[t].air_48n6_stock == 0 && self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request) {
|
||||
self.sams[j].ground_stock -= 1;
|
||||
@@ -188,6 +208,9 @@ impl Sim {
|
||||
if self.sams[j].ground_rounds >= self.sams[j].cells() && self.sams[j].kind.spec().gun.is_none() { self.sams[j].empty = false; }
|
||||
self.sams[j].ground_request = false;
|
||||
self.sams[t].ground_stock -= 1;
|
||||
// A round reserved for return redirected to a waiting request is no
|
||||
// longer return cargo once the last one leaves the truck (#128).
|
||||
if self.sams[t].ground_stock == 0 { self.sams[t].return_cargo = false; }
|
||||
self.sams[t].air_48n6_stock += displaced;
|
||||
out.sound("minecraft:block.piston.extend", at, 1.5, 0.8);
|
||||
}
|
||||
@@ -408,4 +431,170 @@ mod tests {
|
||||
assert!(!sim.sams[1].ground_request);
|
||||
}
|
||||
|
||||
/// Runs `step_supply`, jumping the truck beside its current job each tick so handovers
|
||||
/// resolve without a full drive (movement itself is covered by the lib round-trip tests).
|
||||
/// Fails after 200 ticks if `done` never holds.
|
||||
fn serve_until(sim: &mut Sim, truck: usize, mut done: impl FnMut(&Sim) -> bool) {
|
||||
let mut ok = false;
|
||||
for _ in 0..200 {
|
||||
if done(sim) { ok = true; break; }
|
||||
if let Some(id) = sim.sams[truck].job()
|
||||
&& let Some(j) = sim.sams.iter().position(|s| s.id == id)
|
||||
{
|
||||
sim.sams[truck].pos = sim.sams[j].pos;
|
||||
}
|
||||
sim.sams[truck].state = SamState::Search;
|
||||
sim.tick += 1;
|
||||
sim.step_supply(&mut Out::default());
|
||||
}
|
||||
assert!(ok, "condition not reached within 200 step_supply calls");
|
||||
}
|
||||
|
||||
/// #128: a return that is claimed but cannot proceed (here: the returning system is on the
|
||||
/// move) must not stop the free truck with job=None; an independent typed request is served.
|
||||
/// The claim stays, the rounds are conserved, and once the system stops the return completes.
|
||||
#[test]
|
||||
fn a_moving_return_claim_does_not_starve_an_independent_supply_request() {
|
||||
let mut sim = Sim::new(5);
|
||||
for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 40.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] {
|
||||
let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0));
|
||||
}
|
||||
let (depot, a, b, truck) = (0usize, 1usize, 2usize, 3usize);
|
||||
let b_id = sim.sams[b].id;
|
||||
sim.sams[depot].ground_stock = 2;
|
||||
sim.sams[a].ground_rounds = 1;
|
||||
sim.sams[a].missiles = 3;
|
||||
sim.sams[a].ground_return = true;
|
||||
sim.sams[b].ground_request = true;
|
||||
assert!(sim.order_move(a, DVec3::new(400.0, 70.0, 0.0)));
|
||||
// The free truck loads one RM48U at the depot and starts driving to B within 100 ticks;
|
||||
// before the fix it entered the return branch, found the moving A impossible and idled.
|
||||
let mut started = false;
|
||||
for _ in 0..100 {
|
||||
sim.tick += 1;
|
||||
sim.step_supply(&mut Out::default());
|
||||
if sim.sams[truck].job() == Some(b_id) { started = true; break; }
|
||||
}
|
||||
assert!(started, "truck never started the delivery to B");
|
||||
// The claim and the rounds are untouched by the fallback.
|
||||
assert!(sim.sams[a].ground_return);
|
||||
assert_eq!(sim.sams[a].ground_rounds, 1);
|
||||
assert_eq!((sim.sams[depot].ground_stock, sim.sams[truck].ground_stock), (1, 1));
|
||||
assert_eq!(sim.sams[depot].ground_stock + sim.sams[truck].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3);
|
||||
// Hand the round over to B; the full drive is exercised in the lib round-trip test.
|
||||
serve_until(&mut sim, truck, |s| s.sams[b].ground_rounds == 1);
|
||||
assert!(!sim.sams[b].ground_request);
|
||||
assert_eq!((sim.sams[b].missiles, sim.sams[truck].air_48n6_stock), (3, 1));
|
||||
assert_eq!(sim.sams[depot].ground_stock + sim.sams[truck].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3);
|
||||
// No request is left, so the truck holds its watch (a stock run aside): the pending
|
||||
// return still outranks it and the claim survives.
|
||||
for _ in 0..20 { sim.tick += 1; sim.step_supply(&mut Out::default()); }
|
||||
assert!(sim.sams[a].ground_return);
|
||||
assert_eq!(sim.sams[a].ground_rounds, 1);
|
||||
// A stops: the actual return is picked up and driven back to the depot.
|
||||
sim.sams[a].state = SamState::Search;
|
||||
sim.sams[a].dest = None;
|
||||
serve_until(&mut sim, truck, |s| s.sams[truck].return_cargo);
|
||||
assert!(!sim.sams[a].ground_return);
|
||||
assert_eq!((sim.sams[a].ground_rounds, sim.sams[a].missiles), (0, 4));
|
||||
assert_eq!((sim.sams[truck].ground_stock, sim.sams[truck].air_48n6_stock), (1, 0));
|
||||
serve_until(&mut sim, truck, |s| s.sams[depot].ground_stock == 2);
|
||||
assert!(!sim.sams[truck].return_cargo);
|
||||
assert_eq!((sim.sams[depot].ground_stock, sim.sams[truck].ground_stock, sim.sams[b].ground_rounds), (2, 0, 1));
|
||||
assert_eq!(sim.sams[depot].ground_stock + sim.sams[truck].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3);
|
||||
}
|
||||
|
||||
/// #128 variant: the pickup is already claimed by another truck, so for this one the return
|
||||
/// is impossible; it must turn to the waiting request instead of idling with job=None. A
|
||||
/// dropped job (as after loading a save) is rebuilt, damaged trucks wait, and the repaired
|
||||
/// claiming truck keeps the priority of the now executable return over the request.
|
||||
#[test]
|
||||
fn a_return_claimed_by_another_truck_leaves_the_supply_to_the_second_one() {
|
||||
let mut sim = Sim::new(7);
|
||||
for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::S400, 200.0), (SamKind::Tzm, 0.0), (SamKind::Tzm, 150.0)] {
|
||||
let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0));
|
||||
}
|
||||
let (depot, a, b, t2, t1) = (0usize, 1usize, 2usize, 3usize, 4usize);
|
||||
let (a_id, b_id) = (sim.sams[a].id, sim.sams[b].id);
|
||||
sim.sams[depot].ground_stock = 2;
|
||||
sim.sams[a].ground_rounds = 1;
|
||||
sim.sams[a].missiles = 3;
|
||||
sim.sams[a].ground_return = true;
|
||||
sim.sams[b].ground_request = true;
|
||||
// The first truck claimed A's pickup earlier; both trucks start damaged: nothing
|
||||
// moves, no round is lost, the claim waits.
|
||||
sim.sams[t1].job = Some(a_id);
|
||||
sim.sams[t1].comps.0[Part::Tracks as usize] = 0.0;
|
||||
sim.sams[t2].comps.0[Part::Tracks as usize] = 0.0;
|
||||
for _ in 0..30 { sim.tick += 1; sim.step_supply(&mut Out::default()); }
|
||||
assert_eq!(sim.sams[t2].job(), None);
|
||||
assert_eq!((sim.sams[depot].ground_stock, sim.sams[b].ground_rounds), (2, 0));
|
||||
assert!(sim.sams[a].ground_return);
|
||||
assert_eq!(sim.sams[depot].ground_stock + sim.sams[t1].ground_stock + sim.sams[t2].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3);
|
||||
// Repaired, the second truck must not idle on the claimed return: depot first, then B.
|
||||
sim.sams[t2].comps.0[Part::Tracks as usize] = 1.0;
|
||||
let mut started = false;
|
||||
for _ in 0..100 {
|
||||
sim.tick += 1;
|
||||
sim.step_supply(&mut Out::default());
|
||||
if sim.sams[t2].job() == Some(b_id) { started = true; break; }
|
||||
}
|
||||
assert!(started, "second truck never started the delivery to B");
|
||||
assert_eq!((sim.sams[depot].ground_stock, sim.sams[t2].ground_stock), (1, 1));
|
||||
assert!(sim.sams[a].ground_return, "claimed return must survive the fallback");
|
||||
// A save/load drops the job; the request and the cargo survive and the job is rebuilt.
|
||||
sim.sams[t2].job = None;
|
||||
serve_until(&mut sim, t2, |s| s.sams[t2].job() == Some(b_id));
|
||||
// The claiming truck is repaired: the executable return has priority over the request,
|
||||
// and the still-hauling second truck is not diverted from B.
|
||||
sim.sams[t1].comps.0[Part::Tracks as usize] = 1.0;
|
||||
serve_until(&mut sim, t1, |s| s.sams[t1].return_cargo);
|
||||
assert!(!sim.sams[a].ground_return);
|
||||
assert_eq!(sim.sams[a].ground_rounds, 0);
|
||||
assert_eq!(sim.sams[t2].job(), Some(b_id), "hauling truck must keep its supply job");
|
||||
// B gets its round; the displaced air round rides along.
|
||||
serve_until(&mut sim, t2, |s| s.sams[b].ground_rounds == 1);
|
||||
assert_eq!((sim.sams[b].missiles, sim.sams[t2].air_48n6_stock), (3, 1));
|
||||
assert_eq!(sim.sams[depot].ground_stock + sim.sams[t1].ground_stock + sim.sams[t2].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3);
|
||||
// And the picked-up return reaches the depot: everything is back except B's new round.
|
||||
serve_until(&mut sim, t1, |s| s.sams[depot].ground_stock == 2);
|
||||
assert!(!sim.sams[t1].return_cargo);
|
||||
assert_eq!((sim.sams[depot].ground_stock, sim.sams[t1].ground_stock, sim.sams[b].ground_rounds), (2, 0, 1));
|
||||
}
|
||||
|
||||
/// #128 variant: the route to the returning system is marked unreachable, so the return is
|
||||
/// impossible for now; the truck serves the request, and the return completes once the
|
||||
/// route is passable again and the system is still waiting with its claim.
|
||||
#[test]
|
||||
fn an_unreachable_route_falls_back_to_supply_until_it_expires() {
|
||||
let mut sim = Sim::new(9);
|
||||
for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 40.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] {
|
||||
let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0));
|
||||
}
|
||||
let (depot, a, b, truck) = (0usize, 1usize, 2usize, 3usize);
|
||||
let (a_id, b_id) = (sim.sams[a].id, sim.sams[b].id);
|
||||
sim.sams[depot].ground_stock = 2;
|
||||
sim.sams[a].ground_rounds = 1;
|
||||
sim.sams[a].missiles = 3;
|
||||
sim.sams[a].ground_return = true;
|
||||
sim.sams[b].ground_request = true;
|
||||
sim.routes.unreachable.insert((sim.sams[truck].id, a_id), u64::MAX);
|
||||
let mut started = false;
|
||||
for _ in 0..100 {
|
||||
sim.tick += 1;
|
||||
sim.step_supply(&mut Out::default());
|
||||
if sim.sams[truck].job() == Some(b_id) { started = true; break; }
|
||||
}
|
||||
assert!(started, "truck never started the delivery to B");
|
||||
assert!(sim.sams[a].ground_return, "claim must survive the blocked route");
|
||||
serve_until(&mut sim, truck, |s| s.sams[b].ground_rounds == 1);
|
||||
// The route is passable again: the waiting claim is picked up and returned.
|
||||
sim.routes.unreachable.clear();
|
||||
serve_until(&mut sim, truck, |s| s.sams[truck].return_cargo);
|
||||
assert!(!sim.sams[a].ground_return);
|
||||
serve_until(&mut sim, truck, |s| s.sams[depot].ground_stock == 2);
|
||||
assert!(!sim.sams[truck].return_cargo);
|
||||
assert_eq!(sim.sams[depot].ground_stock + sim.sams[truck].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,3 +66,9 @@
|
||||
`ground-unload` у склада выгружает одну RM48U сразу; вдали резервирует её и вызывает ТЗМ для
|
||||
вывоза. `ground-cancel` отменяет ещё ожидающую доставку/вывоз. Уже забранная ракета находится
|
||||
в ТЗМ и продолжает путь на склад; команда не телепортирует её обратно в ПУ.
|
||||
|
||||
Возврат имеет приоритет, только пока он выполним: ПУ исправна и развёрнута, маршрут доступен,
|
||||
ракету ещё не забирает другая ТЗМ, а у склада есть место. Заявка при этом сохраняется, но
|
||||
невыполнимый вывоз не блокирует ТЗМ: свободная машина исполняет независимые заявки на доставку
|
||||
RM48U, а как только вывоз снова становится выполнимым, он встаёт в приоритет. Проверено
|
||||
регрессиями #128, включая консервацию боеприпасов и save-restore посреди маршрута.
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
| #115 | Три пилота с кадрами до/после, спокойные материалы; authored-проход по боевым и вспомогательным машинам | Крупные планы и точность прототипов ещё промежуточные; остальные летательные аппараты/ракеты, анимации, дальние виды и FPS |
|
||||
| #116 | Streaming не освобождает busy-guard работающего acoustic worker; проверено engine-тестами | Внешняя игровая приёмка на целевых машинах |
|
||||
| #118 | Физический С-400/RM48U, четыре общие ячейки, конечные запасы; доставка и обратный вывоз ТЗМ, резерв/отмена, возврат снятой ПВО; обе фазы вывозного маршрута сохраняются; carrier/крышки/выброс/зажигание/разгон в записи; фантомное ПВО удалено | Визуальная приёмка пуска и маршрутов на сложном рельефе; другие носители не добавлены без подтверждённого профиля |
|
||||
| #128 | Невыполнимый возврат RM48U (ПУ движется/повреждена, маршрут недоступен, заявку уже везёт другая ТЗМ, у склада нет места) больше не блокирует ТЗМ: заявка сохраняется, свободная ТЗМ исполняет независимые заявки на доставку; выполнимый возврат снова в приоритете; консервация боеприпасов и save-restore посреди маршрута покрыты регрессиями | Игровая приёмка маршрутов на сложном рельефе остаётся за #118 |
|
||||
|
||||
Для #118 RM48U — явно условный Minecraft-профиль. Существование сочетания опирается на сообщение ведомства,
|
||||
а игровые параметры не выдаются за реальные. `ground-stock` — команда подготовки sandbox-сценария,
|
||||
|
||||
Reference in New Issue
Block a user