feat(@projects/@magic-civilization): ⛵ p3-18 P4c — carried units lost when their transport is destroyed
After the PvP combat phase, prune_orphaned_cargo drops any unit whose carrier_id references a hull no longer in its player's roster — the transport sank, its cargo goes down with it. Order-preserving removal that keeps the index-parallel unit_upkeep aligned; idempotent and a no-op for carrier-free rosters (so existing combat is untouched — pvp tests still green). Test: destroyed_transport_loses_cargo (orphaned cargo pruned, uncarried unit survives, unit_upkeep stays aligned). Known follow-up: carried units ride the hull's hex (stacked); fully shielding them from being individually targeted needs combat target-selection changes that touch 1UPT assumptions — tracked, not in this commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b40fc80bbc
commit
8b4c71688f
1 changed files with 51 additions and 0 deletions
|
|
@ -3601,6 +3601,10 @@ impl TurnProcessor {
|
|||
|
||||
// p2-55: drain capture/ransom/destroyed events into the TurnResult.
|
||||
state.pending_capture_events.drain_into(result);
|
||||
|
||||
// p3-18 transport — units whose carrier hull was destroyed this phase go
|
||||
// down with it (lost at sea).
|
||||
prune_orphaned_cargo(state);
|
||||
}
|
||||
|
||||
// ── Phase 5c: City siege ──────────────────────────────────────────────
|
||||
|
|
@ -4706,6 +4710,30 @@ fn embark_level_for(player: &crate::game_state::PlayerState) -> mc_core::EmbarkL
|
|||
player.embark_level
|
||||
}
|
||||
|
||||
/// p3-18 transport — drop any unit whose `carrier_id` references a hull that no
|
||||
/// longer exists in its player's roster (the transport was destroyed). Carried
|
||||
/// units go down with the ship. Order-preserving (removes descending indices)
|
||||
/// and keeps the index-parallel `unit_upkeep` aligned. Idempotent — safe to call
|
||||
/// after any combat phase.
|
||||
fn prune_orphaned_cargo(state: &mut crate::game_state::GameState) {
|
||||
for player in state.players.iter_mut() {
|
||||
let live: std::collections::HashSet<u32> = player.units.iter().map(|u| u.id).collect();
|
||||
let drop: Vec<usize> = player
|
||||
.units
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, u)| matches!(u.carrier_id, Some(cid) if !live.contains(&cid)))
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
for &i in drop.iter().rev() {
|
||||
player.units.remove(i);
|
||||
if i < player.unit_upkeep.len() {
|
||||
player.unit_upkeep.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_one_move(state: &mut GameState, req: &crate::game_state::MoveRequest) -> MoveOutcome {
|
||||
use mc_pathfinding::{find_path, UnitDomain};
|
||||
|
||||
|
|
@ -5179,6 +5207,29 @@ mod move_request_tests {
|
|||
assert_eq!(state.players[0].units[0].carrier_id, None, "warrior stayed off the full hull");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destroyed_transport_loses_cargo() {
|
||||
// carrier id 2 is absent from the roster (destroyed); its cargo (id 1)
|
||||
// must be pruned, while an uncarried unit (id 3) survives.
|
||||
let mut state = transport_state();
|
||||
state.players.push(PlayerState {
|
||||
player_index: 0,
|
||||
units: vec![
|
||||
MapUnit {
|
||||
id: 1, col: 2, row: 0, unit_id: "warrior".into(),
|
||||
carrier_id: Some(2), ..MapUnit::default()
|
||||
},
|
||||
MapUnit { id: 3, col: 0, row: 0, unit_id: "warrior".into(), ..MapUnit::default() },
|
||||
],
|
||||
unit_upkeep: vec![1, 1],
|
||||
..PlayerState::default()
|
||||
});
|
||||
prune_orphaned_cargo(&mut state);
|
||||
let ids: Vec<u32> = state.players[0].units.iter().map(|u| u.id).collect();
|
||||
assert_eq!(ids, vec![3], "orphaned cargo lost with the sunk hull; uncarried unit survives");
|
||||
assert_eq!(state.players[0].unit_upkeep.len(), 1, "unit_upkeep stays index-aligned");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_budget_rejects() {
|
||||
let mut state = build_state_with_unit(7, (0, 0), 0, |_, _| "plains");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue