diff --git a/v2/crates/ruview-swarm/src/bin/train_marl.rs b/v2/crates/ruview-swarm/src/bin/train_marl.rs index bc2d5b8c..93cedd0c 100644 --- a/v2/crates/ruview-swarm/src/bin/train_marl.rs +++ b/v2/crates/ruview-swarm/src/bin/train_marl.rs @@ -3,21 +3,32 @@ //! Real Candle autodiff PPO training loop. Runs on CPU, or CUDA when built //! with `--features train,cuda` (local RTX 5080 or a GCP L4 instance). //! +//! Movement is driven by a selectable `FlightPattern` (boustrophedon, +//! partitioned, spiral, pheromone, potential, levy) and reward is shaped by a +//! selectable `LearningPattern` (mappo, ippo, curiosity, meta). This makes each +//! pattern produce visibly distinct trajectories + telemetry instead of every +//! drone clustering on the orchestrator's internal coverage strategy. +//! //! Usage: //! cargo run --release -p ruview-swarm --features train,cuda --bin train_marl -- \ -//! --episodes 5000 --drones 4 --profile sar --checkpoint-dir ./marl-checkpoints +//! --episodes 5000 --drones 4 --profile sar \ +//! --flight-pattern partitioned --learn-pattern mappo_curiosity \ +//! --checkpoint-dir ./marl-checkpoints //! //! Right-sizing note: the policy is a 64→128→64 MLP. The bottleneck is //! environment-rollout throughput, not GPU matmul — an L4 + 16 vCPU beats an //! 8× A100 box for this workload at ~1/20th the cost. See scripts/gcp/. +use std::collections::HashSet; + use ruview_swarm::config::SwarmConfig; use ruview_swarm::integration::telemetry::{DroneFrame, TelemetryRecorder}; use ruview_swarm::marl::candle_ppo::{CandlePpoConfig, CandleTrainer}; +use ruview_swarm::marl::learning::{shaped_reward, CuriosityModule, LearningPattern}; use ruview_swarm::marl::observation::LocalObservation; use ruview_swarm::marl::reward::{RewardCalculator, RewardContext}; -use ruview_swarm::orchestrator::SwarmOrchestrator; -use ruview_swarm::types::{NodeId, Position3D}; +use ruview_swarm::planning::patterns::{FlightPattern, PatternContext}; +use ruview_swarm::types::{DroneState, NodeId, Position3D, Velocity3D}; struct Args { episodes: usize, @@ -28,6 +39,8 @@ struct Args { checkpoint_every: usize, telemetry: Option, telemetry_episode: usize, + flight_pattern: String, + learn_pattern: String, } impl Default for Args { @@ -41,6 +54,8 @@ impl Default for Args { checkpoint_every: 100, telemetry: None, telemetry_episode: 0, + flight_pattern: "partitioned".to_string(), + learn_pattern: "mappo".to_string(), } } } @@ -84,6 +99,14 @@ fn parse_args() -> Args { args.telemetry_episode = next().parse().unwrap_or(args.telemetry_episode); i += 1; } + "--flight-pattern" => { + args.flight_pattern = next(); + i += 1; + } + "--learn-pattern" => { + args.learn_pattern = next(); + i += 1; + } "-h" | "--help" => { println!( "train_marl — ruview-swarm MARL training (ADR-148 M4)\n\ @@ -92,6 +115,8 @@ fn parse_args() -> Args { --drones N swarm size (default 4)\n \ --profile NAME sar|inspection|mine|agriculture (default sar)\n \ --steps N steps per episode (default 200)\n \ + --flight-pattern P boustrophedon|partitioned|spiral|pheromone|potential|levy (default partitioned)\n \ + --learn-pattern P mappo|ippo|curiosity|meta (default mappo)\n \ --checkpoint-dir D checkpoint output dir (default ./marl-checkpoints)\n \ --checkpoint-every N save every N episodes (default 100)\n \ --telemetry FILE write JSONL telemetry for viz/swarm_viz.html\n \ @@ -115,14 +140,67 @@ fn config_for(profile: &str) -> SwarmConfig { } } +/// Map a world coordinate to a grid cell index at `grid_res` metre resolution. +fn cell_of(x: f64, y: f64, grid_res: f64) -> (u32, u32) { + let gx = (x / grid_res).floor().max(0.0) as u32; + let gy = (y / grid_res).floor().max(0.0) as u32; + (gx, gy) +} + +/// Mark every grid cell within the drone's circular scan footprint as scanned, +/// returning how many *newly* scanned cells this step contributed. +fn mark_scanned( + scanned: &mut HashSet<(u32, u32)>, + pos: &Position3D, + scan_width_m: f64, + grid_res: f64, + area_w: f64, + area_h: f64, +) -> u32 { + let r = scan_width_m * 0.5; + let cols = (area_w / grid_res).ceil() as i64; + let rows = (area_h / grid_res).ceil() as i64; + let (cx, cy) = cell_of(pos.x, pos.y, grid_res); + let span = (r / grid_res).ceil() as i64; + let mut new_cells = 0u32; + for dgx in -span..=span { + for dgy in -span..=span { + let gx = cx as i64 + dgx; + let gy = cy as i64 + dgy; + if gx < 0 || gy < 0 || gx >= cols || gy >= rows { + continue; + } + // Cell centre in metres. + let mx = (gx as f64 + 0.5) * grid_res; + let my = (gy as f64 + 0.5) * grid_res; + if (mx - pos.x).hypot(my - pos.y) <= r && scanned.insert((gx as u32, gy as u32)) { + new_cells += 1; + } + } + } + new_cells +} + #[tokio::main] async fn main() -> Result<(), Box> { let args = parse_args(); let cfg = config_for(&args.profile); + let flight_pattern = FlightPattern::from_str(&args.flight_pattern); + let learn_pattern = LearningPattern::from_str(&args.learn_pattern); println!( - "MARL training: profile={} drones={} episodes={} steps/ep={}", - args.profile, args.drones, args.episodes, args.steps_per_episode + "MARL training: profile={} drones={} episodes={} steps/ep={} flight={} learn={} ({})", + args.profile, + args.drones, + args.episodes, + args.steps_per_episode, + flight_pattern.name(), + learn_pattern.name(), + if learn_pattern.centralized_critic() { + "CTDE / centralized critic" + } else { + "independent learners" + } ); let ppo_cfg = CandlePpoConfig::default(); @@ -132,23 +210,33 @@ async fn main() -> Result<(), Box> { let reward_calc = RewardCalculator::default(); std::fs::create_dir_all(&args.checkpoint_dir).ok(); + let area_w = cfg.mission.area_width_m; + let area_h = cfg.mission.area_height_m; + let grid_res = cfg.mission.grid_resolution_m.max(1.0); + let scan_w = cfg.planning.csi_scan_width_m; + let max_speed = cfg.planning.max_speed_ms.max(0.1); + let altitude_z = -cfg.planning.flight_altitude_m; + let total_cells = ((area_w / grid_res).ceil() * (area_h / grid_res).ceil()).max(1.0); + // Synthetic victims placed within the mission area for reward signal. let victims = vec![ - Position3D { x: cfg.mission.area_width_m * 0.2, y: cfg.mission.area_height_m * 0.3, z: 0.0 }, - Position3D { x: cfg.mission.area_width_m * 0.6, y: cfg.mission.area_height_m * 0.45, z: 0.0 }, + Position3D { x: area_w * 0.2, y: area_h * 0.3, z: 0.0 }, + Position3D { x: area_w * 0.6, y: area_h * 0.45, z: 0.0 }, ]; + // Composite profile label so the viewer header surfaces the active patterns. + let profile_label = format!( + "{} · flight={} · learn={}", + args.profile, + flight_pattern.name(), + learn_pattern.name() + ); + // Optional telemetry recorder for the visualizer. let mut telem = match &args.telemetry { Some(path) => { let mut rec = TelemetryRecorder::create(path)?; - rec.meta( - &args.profile, - args.drones, - cfg.mission.area_width_m, - cfg.mission.area_height_m, - &victims, - )?; + rec.meta(&profile_label, args.drones, area_w, area_h, &victims)?; println!("telemetry → {path} (spatial steps from episode {})", args.telemetry_episode); Some(rec) } @@ -158,24 +246,30 @@ async fn main() -> Result<(), Box> { let mut best_return = f32::MIN; for episode in 0..args.episodes { - // Build a fresh swarm for this episode. - let mut drones: Vec = (0..args.drones) + // Per-episode curiosity module (count-based novelty over the area). + let mut curiosity = CuriosityModule::new(area_w, area_h, 32, 0.5); + + // Build drone states directly so the FlightPattern fully drives motion. + let cols = (args.drones as f64).sqrt().ceil().max(1.0) as usize; + let mut states: Vec = (0..args.drones) .map(|d| { - let cols = (args.drones as f64).sqrt().ceil().max(1.0) as usize; let (row, col) = (d / cols, d % cols); - SwarmOrchestrator::new_demo( - NodeId(d as u32), - cfg.clone(), - Position3D { - x: 10.0 + col as f64 * (cfg.mission.area_width_m / cols as f64), - y: 10.0 + row as f64 * (cfg.mission.area_height_m / cols.max(1) as f64), - z: -cfg.planning.flight_altitude_m, - }, - victims.clone(), - ) + let mut s = DroneState::default_at_origin(NodeId(d as u32)); + s.position = Position3D { + x: 10.0 + col as f64 * (area_w / cols as f64), + y: 10.0 + row as f64 * (area_h / cols.max(1) as f64), + z: altitude_z, + }; + s.altitude_agl_m = cfg.planning.flight_altitude_m; + s }) .collect(); + // Coverage tracker (shared across drones — total area scanned). + let mut scanned: HashSet<(u32, u32)> = HashSet::new(); + // Rolling recent-positions trail for pheromone/potential patterns. + let mut visited: Vec = Vec::with_capacity(256); + // Rollout buffers (flattened across drones). let mut obs_buf: Vec = Vec::new(); let mut action_buf: Vec<[f32; 4]> = Vec::new(); @@ -186,47 +280,108 @@ async fn main() -> Result<(), Box> { for step in 0..args.steps_per_episode { let is_last = step == args.steps_per_episode - 1; - // Snapshot peer positions for neighbor observations. + // Snapshot peer positions for this tick (observations + repulsion). let positions: Vec<(NodeId, Position3D)> = - drones.iter().map(|d| (d.node_id, d.state.position)).collect(); + states.iter().map(|s| (s.id, s.position)).collect(); - for drone in &mut drones { - let cells_before = drone.stats.cells_covered; - let prev_pos = drone.state.position; + for idx in 0..states.len() { + let prev_pos = states[idx].position; + let node_id = states[idx].id; - // Observation from current state + neighbors. + // Neighbour positions (everyone except this drone). let neighbors: Vec<(NodeId, Position3D)> = positions .iter() - .filter(|(id, _)| *id != drone.node_id) + .filter(|(id, _)| *id != node_id) .cloned() .collect(); + let peers: Vec = neighbors.iter().map(|(_, p)| *p).collect(); + + // Observation from the current (pre-move) state. let obs = - LocalObservation::from_state_no_grid(&drone.state, &neighbors, None, None); + LocalObservation::from_state_no_grid(&states[idx], &neighbors, None, None); - // Advance the simulation one tick. - drone.step(1.0, true).await; + // --- FlightPattern drives the next waypoint -------------------- + let ctx = PatternContext { + drone_id: node_id, + swarm_size: args.drones, + current: prev_pos, + area_w, + area_h, + altitude_z, + scan_width_m: scan_w, + step: step as u64, + visited: &visited, + peers: &peers, + }; + let target = flight_pattern.next_target(&ctx); - // Reward from this step's deltas. - let new_cells = drone.stats.cells_covered.saturating_sub(cells_before); - let nearest = neighbors + // Move one tick toward the target at max_speed (no teleport). + let dx = target.x - prev_pos.x; + let dy = target.y - prev_pos.y; + let dist = dx.hypot(dy); + let new_pos = if dist > 1e-9 { + let stepd = dist.min(max_speed); + Position3D { + x: prev_pos.x + dx / dist * stepd, + y: prev_pos.y + dy / dist * stepd, + z: altitude_z, + } + } else { + prev_pos + }; + let heading = if dist > 1e-9 { dy.atan2(dx) } else { states[idx].heading_rad }; + let moved = prev_pos.distance_to(&new_pos); + + // Commit the move to the drone state. + { + let s = &mut states[idx]; + s.velocity = Velocity3D { + vx: (new_pos.x - prev_pos.x), + vy: (new_pos.y - prev_pos.y), + vz: 0.0, + }; + s.position = new_pos; + s.heading_rad = heading; + s.timestamp_ms = s.timestamp_ms.saturating_add(1000); + } + + // Coverage: mark scanned footprint, count new cells. + let new_cells = + mark_scanned(&mut scanned, &new_pos, scan_w, grid_res, area_w, area_h); + + // Detection: any victim within the scan footprint. + let detected = victims.iter().any(|v| new_pos.distance_to(v) < scan_w); + + // Nearest-neighbour distance (for collision shaping). + let nearest = peers .iter() - .map(|(_, p)| prev_pos.distance_to(p)) + .map(|p| new_pos.distance_to(p)) .fold(f64::MAX, f64::min); - let ctx = RewardContext { - state: &drone.state, + + // Base extrinsic reward. + let ctx_r = RewardContext { + state: &states[idx], new_cells_covered: new_cells, - victim_confirmed: false, + victim_confirmed: detected, contributed_to_triangulation: false, nearest_neighbor_dist: nearest, geofence_breached: false, battery_depleted_without_rth: false, }; - let reward = reward_calc.compute(&ctx); + let base = reward_calc.compute(&ctx_r); + + // Curiosity shaping (only when the learning pattern uses it). + let reward = if learn_pattern.uses_curiosity() { + let bonus = curiosity.visit_bonus(new_pos.x, new_pos.y); + shaped_reward(learn_pattern, base, bonus) + } else { + base + }; let action = [ - drone.state.heading_rad as f32, - drone.state.altitude_agl_m as f32, - drone.state.velocity.magnitude() as f32, + heading as f32, + states[idx].altitude_agl_m as f32, + (moved / 1.0) as f32, 0.0, ]; @@ -235,35 +390,36 @@ async fn main() -> Result<(), Box> { reward_buf.push(reward); value_buf.push(0.0); // bootstrap value (critic learns this) done_buf.push(is_last); + + // Record the move in the shared visited trail (cap length). + visited.push(new_pos); } - // Record spatial telemetry for the selected episode only (keeps the - // log small — one episode of step frames + metrics for every episode). + // Trim the visited trail to the most recent ~200 positions. + if visited.len() > 200 { + let drop = visited.len() - 200; + visited.drain(0..drop); + } + + // Record spatial telemetry for the selected episode only. if let Some(rec) = telem.as_mut() { if episode == args.telemetry_episode { - let scan_w = cfg.planning.csi_scan_width_m; - let frames: Vec = drones + let frames: Vec = states .iter() - .map(|d| { - let detected = victims - .iter() - .any(|v| d.state.position.distance_to(v) < scan_w); - DroneFrame::from_state(&d.state, detected) + .map(|s| { + let detected = + victims.iter().any(|v| s.position.distance_to(v) < scan_w); + DroneFrame::from_state(s, detected) }) .collect(); - let coverage = drones - .iter() - .map(|d| d.probability_grid.coverage_pct()) - .sum::() - / drones.len().max(1) as f64; + let coverage = scanned.len() as f64 / total_cells; let _ = rec.step(episode, step, step as f64, &frames, coverage); } } } // PPO update on the episode's rollout. - let (advantages, returns) = - trainer.compute_gae(&reward_buf, &value_buf, &done_buf); + let (advantages, returns) = trainer.compute_gae(&reward_buf, &value_buf, &done_buf); let old_log_probs = vec![0.0f32; obs_buf.len()]; let (policy_loss, value_loss, _entropy) = trainer.update(&obs_buf, &action_buf, &advantages, &returns, &old_log_probs)?; @@ -284,15 +440,15 @@ async fn main() -> Result<(), Box> { } if episode % 10 == 0 || episode == args.episodes - 1 { + let coverage_pct = scanned.len() as f64 / total_cells * 100.0; println!( - "ep {:>5}/{} mean_return={:>8.3} best={:>8.3} policy_loss={:>8.4} value_loss={:>8.4}", - episode, args.episodes, mean_return, best_return, policy_loss, value_loss + "ep {:>5}/{} mean_return={:>8.3} best={:>8.3} policy_loss={:>8.4} value_loss={:>8.4} coverage={:>5.1}%", + episode, args.episodes, mean_return, best_return, policy_loss, value_loss, coverage_pct ); } // Checkpoint the trained variables periodically. - if args.checkpoint_every > 0 - && (episode + 1) % args.checkpoint_every == 0 + if args.checkpoint_every > 0 && (episode + 1) % args.checkpoint_every == 0 || episode == args.episodes - 1 { let path = format!("{}/marl-ep{}.safetensors", args.checkpoint_dir, episode + 1); diff --git a/v2/crates/ruview-swarm/src/marl/learning.rs b/v2/crates/ruview-swarm/src/marl/learning.rs new file mode 100644 index 00000000..989edfdf --- /dev/null +++ b/v2/crates/ruview-swarm/src/marl/learning.rs @@ -0,0 +1,304 @@ +//! Selectable self-learning strategies for swarm MARL. +//! +//! - Mappo: centralized-critic, decentralized-execution (CTDE). Best cooperative +//! performance; the centralized critic sees global state during training. +//! - Ippo: independent PPO — each agent learns alone, no shared critic. Robust to +//! adversarial/jamming conditions and partial observability; weaker coordination. +//! - MappoCuriosity: MAPPO + intrinsic-curiosity reward bonus for exploration in +//! sparse-reward regimes (count-based novelty over visited regions). +//! - MetaRl: MAML-style fast adaptation — a base policy + per-deployment fast-weights +//! that adapt in a few in-flight steps to wind/sensor drift. +//! +//! Pure Rust — always compiled (no Candle needed). This is the *strategy* layer; +//! the gradient backend lives in `candle_ppo.rs` behind the `train` feature. + +/// Which self-learning strategy the swarm trains under. Selectable at runtime. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LearningPattern { + /// Centralized critic, decentralized execution (CTDE). + Mappo, + /// Independent PPO — each agent learns alone, no shared critic. + Ippo, + /// MAPPO plus count-based intrinsic-curiosity reward bonus. + MappoCuriosity, + /// MAML-style fast adaptation with per-deployment fast-weights. + MetaRl, +} + +impl Default for LearningPattern { + fn default() -> Self { + LearningPattern::Mappo + } +} + +impl LearningPattern { + /// Parse from a short identifier. Unknown strings fall back to the default + /// (Mappo). Accepts both canonical names and friendly aliases. + pub fn from_str(s: &str) -> Self { + match s.trim().to_ascii_lowercase().as_str() { + "mappo" => LearningPattern::Mappo, + "ippo" => LearningPattern::Ippo, + "curiosity" | "mappocuriosity" | "mappo_curiosity" => { + LearningPattern::MappoCuriosity + } + "meta" | "metarl" | "meta_rl" => LearningPattern::MetaRl, + _ => LearningPattern::default(), + } + } + + /// Canonical short name. `from_str(p.name()) == p` for every variant. + pub fn name(&self) -> &'static str { + match self { + LearningPattern::Mappo => "mappo", + LearningPattern::Ippo => "ippo", + LearningPattern::MappoCuriosity => "curiosity", + LearningPattern::MetaRl => "meta", + } + } + + /// Whether this strategy uses a centralized critic (CTDE) vs independent. + pub fn centralized_critic(&self) -> bool { + matches!( + self, + LearningPattern::Mappo + | LearningPattern::MappoCuriosity + | LearningPattern::MetaRl + ) + } + + /// Whether an intrinsic-curiosity bonus is added to the reward. + pub fn uses_curiosity(&self) -> bool { + matches!(self, LearningPattern::MappoCuriosity) + } +} + +// --------------------------------------------------------------------------- +// Curiosity: count-based intrinsic motivation +// --------------------------------------------------------------------------- + +/// Count-based intrinsic-motivation module. +/// +/// Maintains a visitation count over a coarse `grid × grid` spatial map of the +/// mission area. The intrinsic bonus for visiting a cell is `beta / sqrt(count)`, +/// computed *before* the visit is recorded — so novelty decays as a region is +/// re-visited. This rewards exploration in sparse-reward regimes. +pub struct CuriosityModule { + counts: Vec, + grid: u32, + cell_w: f64, + cell_h: f64, + beta: f32, +} + +impl CuriosityModule { + /// Build a curiosity grid covering an `area_w × area_h` metre region split + /// into `grid × grid` cells. `beta` scales the intrinsic bonus magnitude. + pub fn new(area_w: f64, area_h: f64, grid: u32, beta: f32) -> Self { + let g = grid.max(1); + let cells = (g as usize) * (g as usize); + let cell_w = if area_w > 0.0 { area_w / g as f64 } else { 1.0 }; + let cell_h = if area_h > 0.0 { area_h / g as f64 } else { 1.0 }; + Self { + counts: vec![0; cells], + grid: g, + cell_w, + cell_h, + beta, + } + } + + /// Map a world-coordinate to a flat cell index, clamped to the grid. + fn cell_index(&self, x: f64, y: f64) -> usize { + let gx = ((x / self.cell_w).floor() as i64).clamp(0, self.grid as i64 - 1) as usize; + let gy = ((y / self.cell_h).floor() as i64).clamp(0, self.grid as i64 - 1) as usize; + gy * self.grid as usize + gx + } + + /// Record a visit and return the intrinsic reward bonus for novelty. + /// + /// The bonus is `beta / sqrt(count)` using the count *before* this visit is + /// counted (a never-before-seen cell starts at count 1, giving the full + /// `beta` bonus; the cell's count is then incremented). + pub fn visit_bonus(&mut self, x: f64, y: f64) -> f32 { + let idx = self.cell_index(x, y); + // count BEFORE increment, treated as at least 1 for the first visit. + let prior = self.counts[idx].max(0) + 1; + let bonus = self.beta / (prior as f32).sqrt(); + self.counts[idx] = self.counts[idx].saturating_add(1); + bonus + } + + /// Total recorded visits across the whole grid. + pub fn total_visits(&self) -> u64 { + self.counts.iter().map(|&c| c as u64).sum() + } +} + +// --------------------------------------------------------------------------- +// Meta-RL: MAML-style fast-weight adapter +// --------------------------------------------------------------------------- + +/// MAML-style fast-weight adapter for few-shot in-flight adaptation. +/// +/// Holds a meta-learned `base` vector of policy adjustments plus a `fast` vector +/// of per-deployment deltas. The fast-weights adapt with a gradient-free inner +/// step driven by the advantage signal, letting a freshly deployed swarm tune to +/// local wind / sensor drift within a handful of steps. `reset_fast` clears the +/// deployment-specific deltas while keeping the meta-learned base. +pub struct MetaAdapter { + base: Vec, + fast: Vec, + inner_lr: f32, +} + +impl MetaAdapter { + /// New adapter with a zeroed `dim`-length base and fast-weight vector. + pub fn new(dim: usize, inner_lr: f32) -> Self { + Self { + base: vec![0.0; dim], + fast: vec![0.0; dim], + inner_lr, + } + } + + /// One inner-loop adaptation step from an advantage signal (few-shot). + /// + /// Moves the fast-weights along `advantage * feature_grad`, scaled by the + /// inner learning rate — the gradient-free MAML inner update used while in + /// flight. `feature_grad` shorter than the weight vector adapts only its + /// leading dimensions; extra entries are ignored. + pub fn adapt(&mut self, advantage: f32, feature_grad: &[f32]) { + let n = self.fast.len().min(feature_grad.len()); + for i in 0..n { + self.fast[i] += self.inner_lr * advantage * feature_grad[i]; + } + } + + /// Current effective weights (base + fast). + pub fn effective(&self) -> Vec { + self.base + .iter() + .zip(self.fast.iter()) + .map(|(b, f)| b + f) + .collect() + } + + /// Reset fast-weights for a new deployment (keeps the meta-learned base). + pub fn reset_fast(&mut self) { + for f in self.fast.iter_mut() { + *f = 0.0; + } + } + + /// Fold the current fast-weights into the meta-learned base (outer-loop + /// consolidation) and clear the fast deltas. + pub fn consolidate(&mut self) { + for (b, f) in self.base.iter_mut().zip(self.fast.iter()) { + *b += *f; + } + self.reset_fast(); + } +} + +// --------------------------------------------------------------------------- +// Reward shaping helper +// --------------------------------------------------------------------------- + +/// Shape a base reward according to the selected learning pattern. +/// +/// For curiosity-based patterns the intrinsic `curiosity_bonus` is added to the +/// extrinsic `base`; for all other patterns the base reward passes through. +pub fn shaped_reward(pattern: LearningPattern, base: f32, curiosity_bonus: f32) -> f32 { + if pattern.uses_curiosity() { + base + curiosity_bonus + } else { + base + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const ALL: [LearningPattern; 4] = [ + LearningPattern::Mappo, + LearningPattern::Ippo, + LearningPattern::MappoCuriosity, + LearningPattern::MetaRl, + ]; + + #[test] + fn test_pattern_from_str_roundtrip() { + for p in ALL { + assert_eq!( + LearningPattern::from_str(p.name()), + p, + "round-trip failed for {}", + p.name() + ); + } + } + + #[test] + fn test_centralized_vs_independent() { + // Mappo IS centralized (CTDE); Ippo is NOT (independent learners). + assert!(LearningPattern::Mappo.centralized_critic()); + assert!(!LearningPattern::Ippo.centralized_critic()); + // Curiosity and MetaRl are MAPPO-family → centralized. + assert!(LearningPattern::MappoCuriosity.centralized_critic()); + assert!(LearningPattern::MetaRl.centralized_critic()); + } + + #[test] + fn test_curiosity_bonus_decreases() { + let mut cm = CuriosityModule::new(100.0, 100.0, 10, 1.0); + let first = cm.visit_bonus(50.0, 50.0); + let second = cm.visit_bonus(50.0, 50.0); // same cell again + assert!( + second < first, + "novelty should decay: first={first}, second={second}" + ); + } + + #[test] + fn test_curiosity_bonus_in_bounds() { + let mut cm = CuriosityModule::new(100.0, 100.0, 8, 0.5); + // In-bounds, out-of-bounds, and negative coords all clamp safely. + for &(x, y) in &[(0.0, 0.0), (50.0, 50.0), (999.0, -999.0), (-5.0, 1000.0)] { + let b = cm.visit_bonus(x, y); + assert!(b.is_finite(), "bonus must be finite, got {b}"); + assert!(b >= 0.0, "bonus must be >= 0, got {b}"); + } + } + + #[test] + fn test_meta_adapter_changes_weights() { + let mut ma = MetaAdapter::new(4, 0.1); + let base = ma.effective(); + ma.adapt(2.0, &[1.0, -1.0, 0.5, 0.0]); + let adapted = ma.effective(); + assert_ne!(base, adapted, "adapt() must change effective weights"); + ma.reset_fast(); + assert_eq!( + base, + ma.effective(), + "reset_fast() must restore the meta-learned base" + ); + } + + #[test] + fn test_shaped_reward_curiosity_only() { + let base = 10.0; + let bonus = 3.0; + // MappoCuriosity adds the bonus. + assert_eq!( + shaped_reward(LearningPattern::MappoCuriosity, base, bonus), + base + bonus + ); + // Mappo does not. + assert_eq!(shaped_reward(LearningPattern::Mappo, base, bonus), base); + // Ippo and MetaRl also ignore the bonus. + assert_eq!(shaped_reward(LearningPattern::Ippo, base, bonus), base); + assert_eq!(shaped_reward(LearningPattern::MetaRl, base, bonus), base); + } +} diff --git a/v2/crates/ruview-swarm/src/marl/mod.rs b/v2/crates/ruview-swarm/src/marl/mod.rs index 8587ddf8..42df6cb5 100644 --- a/v2/crates/ruview-swarm/src/marl/mod.rs +++ b/v2/crates/ruview-swarm/src/marl/mod.rs @@ -1,4 +1,5 @@ pub mod actor; +pub mod learning; pub mod observation; pub mod reward; pub mod role_attention; @@ -6,6 +7,7 @@ pub mod trainer; pub mod training_loop; pub use actor::{MappoActor, ActorConfig, ActorAction}; +pub use learning::{LearningPattern, CuriosityModule, MetaAdapter, shaped_reward}; pub use observation::LocalObservation; pub use reward::{RewardCalculator, RewardContext}; pub use role_attention::{NodeRole, RoleAttention, triangulation_geometry_penalty}; diff --git a/v2/crates/ruview-swarm/src/planning/mod.rs b/v2/crates/ruview-swarm/src/planning/mod.rs index 1e4fcf19..5ff2a322 100644 --- a/v2/crates/ruview-swarm/src/planning/mod.rs +++ b/v2/crates/ruview-swarm/src/planning/mod.rs @@ -4,7 +4,9 @@ pub mod rrt_apf; pub mod coverage; pub mod probability_grid; pub mod pheromone; +pub mod patterns; pub use rrt_apf::{RrtApfPlanner, Waypoint}; pub use coverage::{CoverageStrategy, Phase}; pub use probability_grid::ProbabilityGrid; +pub use patterns::{FlightPattern, PatternContext}; diff --git a/v2/crates/ruview-swarm/src/planning/patterns.rs b/v2/crates/ruview-swarm/src/planning/patterns.rs new file mode 100644 index 00000000..52b81bb2 --- /dev/null +++ b/v2/crates/ruview-swarm/src/planning/patterns.rs @@ -0,0 +1,431 @@ +//! Flight / coverage-optimization patterns for swarm area search. +//! +//! Different strategies trade off coverage completeness, time, and robustness: +//! - Boustrophedon: systematic lawnmower; complete but drones overlap if unpartitioned +//! - PartitionedLawnmower: area split into per-drone strips → no overlap, ~Nx faster coverage +//! - Spiral: outward spiral from a seed; good for centred search (last-known-position SAR) +//! - Pheromone: stigmergic — steer away from recently-visited cells; robust to dropout +//! - PotentialField: repelled by visited cells + peers, attracted to unscanned frontier +//! - LevyFlight: heavy-tailed random walk; good exploration when target location unknown + +use crate::types::{NodeId, Position3D}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FlightPattern { + Boustrophedon, + PartitionedLawnmower, + Spiral, + Pheromone, + PotentialField, + LevyFlight, +} + +impl Default for FlightPattern { + fn default() -> Self { + FlightPattern::PartitionedLawnmower + } +} + +impl FlightPattern { + pub fn from_str(s: &str) -> Self { + match s.to_lowercase().as_str() { + "boustrophedon" | "lawnmower" => FlightPattern::Boustrophedon, + "partitioned" | "partitioned_lawnmower" => FlightPattern::PartitionedLawnmower, + "spiral" => FlightPattern::Spiral, + "pheromone" | "stigmergic" => FlightPattern::Pheromone, + "potential" | "potential_field" => FlightPattern::PotentialField, + "levy" | "levyflight" | "levy_flight" => FlightPattern::LevyFlight, + _ => FlightPattern::default(), + } + } + + pub fn name(&self) -> &'static str { + match self { + FlightPattern::Boustrophedon => "boustrophedon", + FlightPattern::PartitionedLawnmower => "partitioned_lawnmower", + FlightPattern::Spiral => "spiral", + FlightPattern::Pheromone => "pheromone", + FlightPattern::PotentialField => "potential_field", + FlightPattern::LevyFlight => "levy_flight", + } + } + + /// All pattern variants, for enumeration / UI selection. + pub fn all() -> [FlightPattern; 6] { + [ + FlightPattern::Boustrophedon, + FlightPattern::PartitionedLawnmower, + FlightPattern::Spiral, + FlightPattern::Pheromone, + FlightPattern::PotentialField, + FlightPattern::LevyFlight, + ] + } +} + +/// Inputs for computing the next waypoint under a pattern. +pub struct PatternContext<'a> { + pub drone_id: NodeId, + pub swarm_size: usize, + pub current: Position3D, + pub area_w: f64, + pub area_h: f64, + pub altitude_z: f64, // flight z (negative NED) + pub scan_width_m: f64, // strip spacing + pub step: u64, // tick counter (for deterministic pseudo-random patterns) + pub visited: &'a [Position3D], // recently visited cell centres (for pheromone/potential) + pub peers: &'a [Position3D], // peer positions (for potential-field repulsion) +} + +impl FlightPattern { + /// Compute the next target position for a drone under this pattern. + pub fn next_target(&self, ctx: &PatternContext) -> Position3D { + match self { + FlightPattern::Boustrophedon => boustrophedon(ctx), + FlightPattern::PartitionedLawnmower => partitioned_lawnmower(ctx), + FlightPattern::Spiral => spiral(ctx), + FlightPattern::Pheromone => pheromone(ctx), + FlightPattern::PotentialField => potential_field(ctx), + FlightPattern::LevyFlight => levy_flight(ctx), + } + } +} + +/// Clamp a candidate (x, y) to the area bounds and lift it to the flight altitude. +fn clamp_to_area(x: f64, y: f64, ctx: &PatternContext) -> Position3D { + Position3D { + x: x.clamp(0.0, ctx.area_w), + y: y.clamp(0.0, ctx.area_h), + z: ctx.altitude_z, + } +} + +/// Serpentine waypoint within a rectangular sub-region. +/// +/// Walks rows of height `scan_width_m`; on each row sweeps left→right or +/// right→left depending on the row parity, advancing one `scan_width_m` +/// segment per `step`. +fn serpentine_in_region( + x0: f64, + x1: f64, + y0: f64, + y1: f64, + scan_width_m: f64, + step: u64, +) -> (f64, f64) { + let strip_w = (x1 - x0).max(scan_width_m); + let height = (y1 - y0).max(scan_width_m); + + // Number of horizontal segments per row before stepping to the next row. + let cols = ((strip_w / scan_width_m).ceil() as u64).max(1); + // Number of rows in this region. + let rows = ((height / scan_width_m).ceil() as u64).max(1); + let total = cols * rows; + let s = step % total; + + let row = s / cols; + let col = s % cols; + + // Centre of the current row band. + let y = y0 + (row as f64 + 0.5) * scan_width_m; + let y = y.min(y1); + + // Serpentine: even rows L→R, odd rows R→L. + let along = if row % 2 == 0 { col } else { cols - 1 - col }; + let x = x0 + (along as f64 + 0.5) * scan_width_m; + let x = x.min(x1); + + (x, y) +} + +/// Classic full-area serpentine lawnmower (drones may overlap — baseline). +fn boustrophedon(ctx: &PatternContext) -> Position3D { + let (x, y) = serpentine_in_region( + 0.0, + ctx.area_w, + 0.0, + ctx.area_h, + ctx.scan_width_m, + ctx.step, + ); + clamp_to_area(x, y, ctx) +} + +/// Partitioned lawnmower: split `area_w` into `swarm_size` vertical strips; +/// drone `i` lawnmowers ONLY within strip `[i*w/n, (i+1)*w/n]`. +/// +/// This is the clustering fix: each drone covers a disjoint band, so total +/// coverage scales ~linearly with swarm size instead of all drones tracing +/// the same path. +fn partitioned_lawnmower(ctx: &PatternContext) -> Position3D { + let n = ctx.swarm_size.max(1); + let i = (ctx.drone_id.0 as usize) % n; + let strip_w = ctx.area_w / n as f64; + let x0 = i as f64 * strip_w; + let x1 = x0 + strip_w; + + let (x, y) = + serpentine_in_region(x0, x1, 0.0, ctx.area_h, ctx.scan_width_m, ctx.step); + clamp_to_area(x, y, ctx) +} + +/// Outward Archimedean spiral from the area centre; radius grows with step. +fn spiral(ctx: &PatternContext) -> Position3D { + let cx = ctx.area_w / 2.0; + let cy = ctx.area_h / 2.0; + + // Angular step keeps successive waypoints roughly `scan_width_m` apart. + let theta = ctx.step as f64 * 0.6; + // Archimedean spiral r = b * theta; b chosen so each turn adds scan_width_m. + let b = ctx.scan_width_m / (2.0 * std::f64::consts::PI); + let r = b * theta; + + let x = cx + r * theta.cos(); + let y = cy + r * theta.sin(); + clamp_to_area(x, y, ctx) +} + +/// Stigmergic: sample candidate headings, step toward the least-visited one. +fn pheromone(ctx: &PatternContext) -> Position3D { + let step_len = ctx.scan_width_m.max(1.0); + // Deterministic base heading offset per drone so they diverge. + let base = ctx.drone_id.0 as f64 * (std::f64::consts::PI / 3.0); + + let n_candidates = 8; + let mut best: Option<(f64, f64, f64)> = None; // (score, x, y); lower score = less visited + for k in 0..n_candidates { + let theta = base + (k as f64) * (2.0 * std::f64::consts::PI / n_candidates as f64); + let cx = ctx.current.x + step_len * theta.cos(); + let cy = ctx.current.y + step_len * theta.sin(); + let cx = cx.clamp(0.0, ctx.area_w); + let cy = cy.clamp(0.0, ctx.area_h); + + // Penalty = sum of inverse-distance to recently-visited cell centres. + let mut visit_pressure = 0.0; + for v in ctx.visited { + let d = (cx - v.x).hypot(cy - v.y); + visit_pressure += 1.0 / (1.0 + d); + } + if best.as_ref().is_none_or(|(bs, _, _)| visit_pressure < *bs) { + best = Some((visit_pressure, cx, cy)); + } + } + + let (_, x, y) = best.unwrap_or((0.0, ctx.current.x, ctx.current.y)); + clamp_to_area(x, y, ctx) +} + +/// Potential field: repelled by visited cells + peers, attracted to the +/// nearest unscanned frontier; step in the resultant direction. +fn potential_field(ctx: &PatternContext) -> Position3D { + let mut fx = 0.0; + let mut fy = 0.0; + + // Repulsion from recently-visited cells. + for v in ctx.visited { + let dx = ctx.current.x - v.x; + let dy = ctx.current.y - v.y; + let d2 = dx * dx + dy * dy + 1.0; + let mag = 1.0 / d2; + fx += dx / d2.sqrt() * mag; + fy += dy / d2.sqrt() * mag; + } + + // Repulsion from peers (collision / overlap avoidance). + for p in ctx.peers { + let dx = ctx.current.x - p.x; + let dy = ctx.current.y - p.y; + let d2 = dx * dx + dy * dy + 1.0; + let mag = 2.0 / d2; // peers repel more strongly than stale trail + fx += dx / d2.sqrt() * mag; + fy += dy / d2.sqrt() * mag; + } + + // Attraction toward the nearest unscanned frontier point. Sample a grid of + // candidate area points; pick the one with greatest distance to any visited + // cell (i.e. the least-explored region) and pull toward it. + let mut frontier: Option<(f64, f64, f64)> = None; // (openness, x, y) + let samples = 5; + for ix in 0..=samples { + for iy in 0..=samples { + let px = ctx.area_w * ix as f64 / samples as f64; + let py = ctx.area_h * iy as f64 / samples as f64; + let mut nearest = f64::INFINITY; + for v in ctx.visited { + let d = (px - v.x).hypot(py - v.y); + if d < nearest { + nearest = d; + } + } + if !nearest.is_finite() { + nearest = (px - ctx.current.x).hypot(py - ctx.current.y); + } + if frontier.as_ref().is_none_or(|(o, _, _)| nearest > *o) { + frontier = Some((nearest, px, py)); + } + } + } + if let Some((_, gx, gy)) = frontier { + let dx = gx - ctx.current.x; + let dy = gy - ctx.current.y; + let d = (dx * dx + dy * dy).sqrt().max(1e-6); + fx += dx / d * 1.5; // attraction gain + fy += dy / d * 1.5; + } + + let fmag = (fx * fx + fy * fy).sqrt(); + let step_len = ctx.scan_width_m.max(1.0); + let (x, y) = if fmag > 1e-9 { + ( + ctx.current.x + fx / fmag * step_len, + ctx.current.y + fy / fmag * step_len, + ) + } else { + (ctx.current.x, ctx.current.y) + }; + clamp_to_area(x, y, ctx) +} + +/// Deterministic pseudo-random heavy-tailed step (Lévy flight). Most steps are +/// short; occasional long jumps. Seeded from drone_id + step via an LCG so the +/// trajectory is reproducible. +fn levy_flight(ctx: &PatternContext) -> Position3D { + // Linear congruential generator (Numerical Recipes constants). + let seed = (ctx.drone_id.0 as u64) + .wrapping_mul(0x9E37_79B9_7F4A_7C15) + .wrapping_add(ctx.step.wrapping_mul(0x2545_F491_4F6C_DD1D)); + let r1 = lcg(seed); + let r2 = lcg(r1); + + let u_angle = (r1 >> 11) as f64 / (1u64 << 53) as f64; // [0,1) + let u_len = ((r2 >> 11) as f64 / (1u64 << 53) as f64).max(1e-6); // (0,1] + + let theta = u_angle * 2.0 * std::f64::consts::PI; + // Heavy-tailed step length: inverse power-law (Pareto-like), exponent ~1.5. + let step_len = ctx.scan_width_m.max(1.0) * u_len.powf(-1.0 / 1.5); + // Cap to the area diagonal so a single jump can't shoot arbitrarily far. + let max_jump = (ctx.area_w * ctx.area_w + ctx.area_h * ctx.area_h).sqrt(); + let step_len = step_len.min(max_jump); + + let x = ctx.current.x + step_len * theta.cos(); + let y = ctx.current.y + step_len * theta.sin(); + clamp_to_area(x, y, ctx) +} + +#[inline] +fn lcg(state: u64) -> u64 { + state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn ctx<'a>( + drone_id: u32, + swarm_size: usize, + step: u64, + current: Position3D, + visited: &'a [Position3D], + peers: &'a [Position3D], + ) -> PatternContext<'a> { + PatternContext { + drone_id: NodeId(drone_id), + swarm_size, + current, + area_w: 100.0, + area_h: 80.0, + altitude_z: -20.0, + scan_width_m: 5.0, + step, + visited, + peers, + } + } + + #[test] + fn test_partitioned_strips_disjoint() { + let empty: [Position3D; 0] = []; + // Two drones, swarm of 2: drone 0 owns left half, drone 1 the right half. + let mut d0_xs = Vec::new(); + let mut d1_xs = Vec::new(); + for s in 0..40u64 { + let c0 = ctx(0, 2, s, Position3D::zero(), &empty, &empty); + let c1 = ctx(1, 2, s, Position3D::zero(), &empty, &empty); + d0_xs.push(FlightPattern::PartitionedLawnmower.next_target(&c0).x); + d1_xs.push(FlightPattern::PartitionedLawnmower.next_target(&c1).x); + } + let mid = 100.0 / 2.0; + // Drone 0 stays strictly in the left half, drone 1 strictly in the right. + assert!(d0_xs.iter().all(|&x| x <= mid), "drone 0 left of midline"); + assert!(d1_xs.iter().all(|&x| x >= mid), "drone 1 right of midline"); + // And they never share an x position (disjoint strips → no overlap). + for &a in &d0_xs { + for &b in &d1_xs { + assert!(a < b || (a <= mid && b >= mid), "strips overlap: {a} vs {b}"); + } + } + } + + #[test] + fn test_all_patterns_in_bounds() { + let visited = [ + Position3D { x: 10.0, y: 10.0, z: -20.0 }, + Position3D { x: 50.0, y: 40.0, z: -20.0 }, + ]; + let peers = [Position3D { x: 30.0, y: 20.0, z: -20.0 }]; + for pat in FlightPattern::all() { + let mut current = Position3D { x: 25.0, y: 25.0, z: -20.0 }; + for s in 0..20u64 { + let c = ctx(1, 4, s, current, &visited, &peers); + let t = pat.next_target(&c); + assert!( + t.x >= 0.0 && t.x <= 100.0, + "{} x out of bounds at step {s}: {}", + pat.name(), + t.x + ); + assert!( + t.y >= 0.0 && t.y <= 80.0, + "{} y out of bounds at step {s}: {}", + pat.name(), + t.y + ); + assert_eq!(t.z, -20.0, "{} altitude wrong", pat.name()); + current = t; + } + } + } + + #[test] + fn test_pattern_from_str_roundtrip() { + for pat in FlightPattern::all() { + assert_eq!( + FlightPattern::from_str(pat.name()), + pat, + "roundtrip failed for {}", + pat.name() + ); + } + } + + #[test] + fn test_spiral_radius_grows() { + let empty: [Position3D; 0] = []; + let centre_x = 100.0 / 2.0; + let centre_y = 80.0 / 2.0; + let dist = |s: u64| { + let c = ctx(0, 1, s, Position3D::zero(), &empty, &empty); + let t = FlightPattern::Spiral.next_target(&c); + ((t.x - centre_x).powi(2) + (t.y - centre_y).powi(2)).sqrt() + }; + let near = dist(1); + let far = dist(50); + assert!( + far > near, + "spiral radius should grow: step1={near}, step50={far}" + ); + } +} diff --git a/v2/crates/ruview-swarm/viz/sample_telemetry.jsonl b/v2/crates/ruview-swarm/viz/sample_telemetry.jsonl index b3cebdf3..7fd5d239 100644 --- a/v2/crates/ruview-swarm/viz/sample_telemetry.jsonl +++ b/v2/crates/ruview-swarm/viz/sample_telemetry.jsonl @@ -1,91 +1,151 @@ -{"type":"meta","profile":"sar","drones":4,"area_w":400.00,"area_h":400.00,"victims":[[80.00,120.00],[240.00,180.00]]} -{"type":"episode","ep":0,"mean_return":123.6877,"policy_loss":4.3082,"value_loss":20052.5762,"victims_found":0} -{"type":"episode","ep":1,"mean_return":123.6877,"policy_loss":-35.5269,"value_loss":19947.0527,"victims_found":0} -{"type":"episode","ep":2,"mean_return":123.6877,"policy_loss":-74.4405,"value_loss":19841.1973,"victims_found":0} -{"type":"episode","ep":3,"mean_return":123.6877,"policy_loss":-116.1118,"value_loss":19727.8633,"victims_found":0} -{"type":"episode","ep":4,"mean_return":123.6877,"policy_loss":-162.6557,"value_loss":19607.3828,"victims_found":0} -{"type":"episode","ep":5,"mean_return":123.6877,"policy_loss":-215.9290,"value_loss":19478.0371,"victims_found":0} -{"type":"episode","ep":6,"mean_return":123.6877,"policy_loss":-279.5587,"value_loss":19331.7852,"victims_found":0} -{"type":"episode","ep":7,"mean_return":123.6877,"policy_loss":-357.7819,"value_loss":19170.1621,"victims_found":0} -{"type":"episode","ep":8,"mean_return":123.6877,"policy_loss":-452.8956,"value_loss":18987.3477,"victims_found":0} -{"type":"episode","ep":9,"mean_return":123.6877,"policy_loss":-565.1311,"value_loss":18776.0879,"victims_found":0} -{"type":"episode","ep":10,"mean_return":123.6877,"policy_loss":-693.7755,"value_loss":18532.1562,"victims_found":0} -{"type":"episode","ep":11,"mean_return":123.6877,"policy_loss":-840.3882,"value_loss":18255.1113,"victims_found":0} -{"type":"episode","ep":12,"mean_return":123.6877,"policy_loss":-1007.8695,"value_loss":17941.9434,"victims_found":0} -{"type":"episode","ep":13,"mean_return":123.6877,"policy_loss":-1198.8174,"value_loss":17589.8066,"victims_found":0} -{"type":"episode","ep":14,"mean_return":123.6877,"policy_loss":-1415.8530,"value_loss":17194.4316,"victims_found":0} -{"type":"episode","ep":15,"mean_return":123.6877,"policy_loss":-1662.0295,"value_loss":16753.6250,"victims_found":0} -{"type":"episode","ep":16,"mean_return":123.6877,"policy_loss":-1940.0228,"value_loss":16267.0869,"victims_found":0} -{"type":"episode","ep":17,"mean_return":123.6877,"policy_loss":-2252.8604,"value_loss":15734.1992,"victims_found":0} -{"type":"episode","ep":18,"mean_return":123.6877,"policy_loss":-2603.2175,"value_loss":15156.0713,"victims_found":0} -{"type":"episode","ep":19,"mean_return":123.6877,"policy_loss":-2993.5083,"value_loss":14534.6807,"victims_found":0} -{"type":"episode","ep":20,"mean_return":123.6877,"policy_loss":-3427.2791,"value_loss":13871.4297,"victims_found":0} -{"type":"episode","ep":21,"mean_return":123.6877,"policy_loss":-3908.3892,"value_loss":13167.9365,"victims_found":0} -{"type":"episode","ep":22,"mean_return":123.6877,"policy_loss":-4439.9995,"value_loss":12428.7100,"victims_found":0} -{"type":"episode","ep":23,"mean_return":123.6877,"policy_loss":-5023.8579,"value_loss":11662.3135,"victims_found":0} -{"type":"episode","ep":24,"mean_return":123.6877,"policy_loss":-5660.2412,"value_loss":10880.2861,"victims_found":0} -{"type":"episode","ep":25,"mean_return":123.6877,"policy_loss":-6351.8081,"value_loss":10091.8340,"victims_found":0} -{"type":"episode","ep":26,"mean_return":123.6877,"policy_loss":-7099.0146,"value_loss":9310.4336,"victims_found":0} -{"type":"episode","ep":27,"mean_return":123.6877,"policy_loss":-7901.1260,"value_loss":8551.1855,"victims_found":0} -{"type":"episode","ep":28,"mean_return":123.6877,"policy_loss":-8758.6396,"value_loss":7828.0469,"victims_found":0} -{"type":"step","ep":29,"step":0,"t":0.00,"coverage":0.0002,"drones":[{"id":0,"x":4.34,"y":4.34,"hdg":-2.356,"batt":100.0,"det":false},{"id":1,"x":202.01,"y":9.71,"hdg":-3.105,"batt":100.0,"det":false},{"id":2,"x":9.71,"y":202.01,"hdg":-1.607,"batt":100.0,"det":false},{"id":3,"x":204.34,"y":204.34,"hdg":-2.356,"batt":100.0,"det":false}]} -{"type":"step","ep":29,"step":1,"t":1.00,"coverage":0.0003,"drones":[{"id":0,"x":7.50,"y":2.50,"hdg":-0.528,"batt":99.9,"det":false},{"id":1,"x":194.01,"y":9.42,"hdg":-3.105,"batt":99.9,"det":false},{"id":2,"x":9.42,"y":194.01,"hdg":-1.607,"batt":99.9,"det":false},{"id":3,"x":198.69,"y":198.69,"hdg":-2.356,"batt":99.9,"det":false}]} -{"type":"step","ep":29,"step":2,"t":2.00,"coverage":0.0005,"drones":[{"id":0,"x":12.50,"y":2.50,"hdg":0.000,"batt":99.9,"det":false},{"id":1,"x":186.02,"y":9.13,"hdg":-3.105,"batt":99.9,"det":false},{"id":2,"x":9.13,"y":186.02,"hdg":-1.607,"batt":99.9,"det":false},{"id":3,"x":193.03,"y":193.03,"hdg":-2.356,"batt":99.9,"det":false}]} -{"type":"step","ep":29,"step":3,"t":3.00,"coverage":0.0006,"drones":[{"id":0,"x":17.50,"y":2.50,"hdg":0.000,"batt":99.9,"det":false},{"id":1,"x":178.02,"y":8.84,"hdg":-3.105,"batt":99.9,"det":false},{"id":2,"x":8.84,"y":178.02,"hdg":-1.607,"batt":99.9,"det":false},{"id":3,"x":187.37,"y":187.37,"hdg":-2.356,"batt":99.9,"det":false}]} -{"type":"step","ep":29,"step":4,"t":4.00,"coverage":0.0008,"drones":[{"id":0,"x":22.50,"y":2.50,"hdg":0.000,"batt":99.8,"det":false},{"id":1,"x":170.03,"y":8.56,"hdg":-3.105,"batt":99.8,"det":false},{"id":2,"x":8.56,"y":170.03,"hdg":-1.607,"batt":99.8,"det":false},{"id":3,"x":181.72,"y":181.72,"hdg":-2.356,"batt":99.8,"det":false}]} -{"type":"step","ep":29,"step":5,"t":5.00,"coverage":0.0009,"drones":[{"id":0,"x":27.50,"y":2.50,"hdg":0.000,"batt":99.8,"det":false},{"id":1,"x":162.03,"y":8.27,"hdg":-3.105,"batt":99.8,"det":false},{"id":2,"x":8.27,"y":162.03,"hdg":-1.607,"batt":99.8,"det":false},{"id":3,"x":176.06,"y":176.06,"hdg":-2.356,"batt":99.8,"det":false}]} -{"type":"step","ep":29,"step":6,"t":6.00,"coverage":0.0011,"drones":[{"id":0,"x":32.50,"y":2.50,"hdg":0.000,"batt":99.8,"det":false},{"id":1,"x":154.04,"y":7.98,"hdg":-3.105,"batt":99.8,"det":false},{"id":2,"x":7.98,"y":154.04,"hdg":-1.607,"batt":99.8,"det":false},{"id":3,"x":170.40,"y":170.40,"hdg":-2.356,"batt":99.8,"det":false}]} -{"type":"step","ep":29,"step":7,"t":7.00,"coverage":0.0013,"drones":[{"id":0,"x":37.50,"y":2.50,"hdg":0.000,"batt":99.7,"det":false},{"id":1,"x":146.04,"y":7.69,"hdg":-3.105,"batt":99.7,"det":false},{"id":2,"x":7.69,"y":146.04,"hdg":-1.607,"batt":99.7,"det":false},{"id":3,"x":164.75,"y":164.75,"hdg":-2.356,"batt":99.7,"det":false}]} -{"type":"step","ep":29,"step":8,"t":8.00,"coverage":0.0014,"drones":[{"id":0,"x":42.50,"y":2.50,"hdg":0.000,"batt":99.7,"det":false},{"id":1,"x":138.05,"y":7.40,"hdg":-3.105,"batt":99.7,"det":false},{"id":2,"x":7.40,"y":138.05,"hdg":-1.607,"batt":99.7,"det":false},{"id":3,"x":159.09,"y":159.09,"hdg":-2.356,"batt":99.7,"det":false}]} -{"type":"step","ep":29,"step":9,"t":9.00,"coverage":0.0016,"drones":[{"id":0,"x":47.50,"y":2.50,"hdg":0.000,"batt":99.7,"det":false},{"id":1,"x":130.05,"y":7.11,"hdg":-3.105,"batt":99.7,"det":false},{"id":2,"x":7.11,"y":130.05,"hdg":-1.607,"batt":99.7,"det":false},{"id":3,"x":153.43,"y":153.43,"hdg":-2.356,"batt":99.7,"det":false}]} -{"type":"step","ep":29,"step":10,"t":10.00,"coverage":0.0017,"drones":[{"id":0,"x":52.50,"y":2.50,"hdg":0.000,"batt":99.6,"det":false},{"id":1,"x":122.06,"y":6.82,"hdg":-3.105,"batt":99.6,"det":false},{"id":2,"x":6.82,"y":122.06,"hdg":-1.607,"batt":99.6,"det":false},{"id":3,"x":147.77,"y":147.77,"hdg":-2.356,"batt":99.6,"det":false}]} -{"type":"step","ep":29,"step":11,"t":11.00,"coverage":0.0019,"drones":[{"id":0,"x":57.50,"y":2.50,"hdg":0.000,"batt":99.6,"det":false},{"id":1,"x":114.06,"y":6.53,"hdg":-3.105,"batt":99.6,"det":false},{"id":2,"x":6.53,"y":114.06,"hdg":-1.607,"batt":99.6,"det":false},{"id":3,"x":142.12,"y":142.12,"hdg":-2.356,"batt":99.6,"det":false}]} -{"type":"step","ep":29,"step":12,"t":12.00,"coverage":0.0020,"drones":[{"id":0,"x":62.50,"y":2.50,"hdg":0.000,"batt":99.6,"det":false},{"id":1,"x":106.07,"y":6.24,"hdg":-3.105,"batt":99.6,"det":false},{"id":2,"x":6.24,"y":106.07,"hdg":-1.607,"batt":99.6,"det":false},{"id":3,"x":136.46,"y":136.46,"hdg":-2.356,"batt":99.6,"det":false}]} -{"type":"step","ep":29,"step":13,"t":13.00,"coverage":0.0022,"drones":[{"id":0,"x":67.50,"y":2.50,"hdg":0.000,"batt":99.5,"det":false},{"id":1,"x":98.07,"y":5.95,"hdg":-3.105,"batt":99.5,"det":false},{"id":2,"x":5.95,"y":98.07,"hdg":-1.607,"batt":99.5,"det":false},{"id":3,"x":130.80,"y":130.80,"hdg":-2.356,"batt":99.5,"det":false}]} -{"type":"step","ep":29,"step":14,"t":14.00,"coverage":0.0023,"drones":[{"id":0,"x":72.50,"y":2.50,"hdg":0.000,"batt":99.5,"det":false},{"id":1,"x":90.08,"y":5.67,"hdg":-3.105,"batt":99.5,"det":false},{"id":2,"x":5.67,"y":90.08,"hdg":-1.607,"batt":99.5,"det":false},{"id":3,"x":125.15,"y":125.15,"hdg":-2.356,"batt":99.5,"det":false}]} -{"type":"step","ep":29,"step":15,"t":15.00,"coverage":0.0025,"drones":[{"id":0,"x":77.50,"y":2.50,"hdg":0.000,"batt":99.5,"det":false},{"id":1,"x":82.08,"y":5.38,"hdg":-3.105,"batt":99.5,"det":false},{"id":2,"x":5.38,"y":82.08,"hdg":-1.607,"batt":99.5,"det":false},{"id":3,"x":119.49,"y":119.49,"hdg":-2.356,"batt":99.5,"det":false}]} -{"type":"step","ep":29,"step":16,"t":16.00,"coverage":0.0027,"drones":[{"id":0,"x":82.50,"y":2.50,"hdg":0.000,"batt":99.4,"det":false},{"id":1,"x":74.09,"y":5.09,"hdg":-3.105,"batt":99.4,"det":false},{"id":2,"x":5.09,"y":74.09,"hdg":-1.607,"batt":99.4,"det":false},{"id":3,"x":113.83,"y":113.83,"hdg":-2.356,"batt":99.4,"det":false}]} -{"type":"step","ep":29,"step":17,"t":17.00,"coverage":0.0028,"drones":[{"id":0,"x":87.50,"y":2.50,"hdg":0.000,"batt":99.4,"det":false},{"id":1,"x":66.09,"y":4.80,"hdg":-3.105,"batt":99.4,"det":false},{"id":2,"x":4.80,"y":66.09,"hdg":-1.607,"batt":99.4,"det":false},{"id":3,"x":108.18,"y":108.18,"hdg":-2.356,"batt":99.4,"det":false}]} -{"type":"step","ep":29,"step":18,"t":18.00,"coverage":0.0030,"drones":[{"id":0,"x":92.50,"y":2.50,"hdg":0.000,"batt":99.4,"det":false},{"id":1,"x":58.10,"y":4.51,"hdg":-3.105,"batt":99.4,"det":false},{"id":2,"x":4.51,"y":58.10,"hdg":-1.607,"batt":99.4,"det":false},{"id":3,"x":102.52,"y":102.52,"hdg":-2.356,"batt":99.4,"det":false}]} -{"type":"step","ep":29,"step":19,"t":19.00,"coverage":0.0031,"drones":[{"id":0,"x":97.50,"y":2.50,"hdg":0.000,"batt":99.3,"det":false},{"id":1,"x":50.10,"y":4.22,"hdg":-3.105,"batt":99.3,"det":false},{"id":2,"x":4.22,"y":50.10,"hdg":-1.607,"batt":99.3,"det":false},{"id":3,"x":96.86,"y":96.86,"hdg":-2.356,"batt":99.3,"det":false}]} -{"type":"step","ep":29,"step":20,"t":20.00,"coverage":0.0033,"drones":[{"id":0,"x":102.50,"y":2.50,"hdg":0.000,"batt":99.3,"det":false},{"id":1,"x":42.11,"y":3.93,"hdg":-3.105,"batt":99.3,"det":false},{"id":2,"x":3.93,"y":42.11,"hdg":-1.607,"batt":99.3,"det":false},{"id":3,"x":91.21,"y":91.21,"hdg":-2.356,"batt":99.3,"det":false}]} -{"type":"step","ep":29,"step":21,"t":21.00,"coverage":0.0034,"drones":[{"id":0,"x":107.50,"y":2.50,"hdg":0.000,"batt":99.3,"det":false},{"id":1,"x":34.11,"y":3.64,"hdg":-3.105,"batt":99.3,"det":false},{"id":2,"x":3.64,"y":34.11,"hdg":-1.607,"batt":99.3,"det":false},{"id":3,"x":85.55,"y":85.55,"hdg":-2.356,"batt":99.3,"det":false}]} -{"type":"step","ep":29,"step":22,"t":22.00,"coverage":0.0036,"drones":[{"id":0,"x":112.50,"y":2.50,"hdg":0.000,"batt":99.2,"det":false},{"id":1,"x":26.12,"y":3.35,"hdg":-3.105,"batt":99.2,"det":false},{"id":2,"x":3.35,"y":26.12,"hdg":-1.607,"batt":99.2,"det":false},{"id":3,"x":79.89,"y":79.89,"hdg":-2.356,"batt":99.2,"det":false}]} -{"type":"step","ep":29,"step":23,"t":23.00,"coverage":0.0037,"drones":[{"id":0,"x":117.50,"y":2.50,"hdg":0.000,"batt":99.2,"det":false},{"id":1,"x":18.13,"y":3.06,"hdg":-3.105,"batt":99.2,"det":false},{"id":2,"x":3.06,"y":18.13,"hdg":-1.607,"batt":99.2,"det":false},{"id":3,"x":74.24,"y":74.24,"hdg":-2.356,"batt":99.2,"det":false}]} -{"type":"step","ep":29,"step":24,"t":24.00,"coverage":0.0039,"drones":[{"id":0,"x":122.50,"y":2.50,"hdg":0.000,"batt":99.2,"det":false},{"id":1,"x":10.13,"y":2.78,"hdg":-3.105,"batt":99.2,"det":false},{"id":2,"x":2.78,"y":10.13,"hdg":-1.607,"batt":99.2,"det":false},{"id":3,"x":68.58,"y":68.58,"hdg":-2.356,"batt":99.2,"det":false}]} -{"type":"step","ep":29,"step":25,"t":25.00,"coverage":0.0041,"drones":[{"id":0,"x":127.50,"y":2.50,"hdg":0.000,"batt":99.1,"det":false},{"id":1,"x":2.50,"y":2.50,"hdg":-3.105,"batt":99.1,"det":false},{"id":2,"x":2.50,"y":2.50,"hdg":-1.607,"batt":99.1,"det":false},{"id":3,"x":62.92,"y":62.92,"hdg":-2.356,"batt":99.1,"det":false}]} -{"type":"step","ep":29,"step":26,"t":26.00,"coverage":0.0042,"drones":[{"id":0,"x":132.50,"y":2.50,"hdg":0.000,"batt":99.1,"det":false},{"id":1,"x":7.50,"y":2.50,"hdg":0.000,"batt":99.1,"det":false},{"id":2,"x":7.50,"y":2.50,"hdg":0.000,"batt":99.1,"det":false},{"id":3,"x":57.26,"y":57.26,"hdg":-2.356,"batt":99.1,"det":false}]} -{"type":"step","ep":29,"step":27,"t":27.00,"coverage":0.0044,"drones":[{"id":0,"x":137.50,"y":2.50,"hdg":0.000,"batt":99.1,"det":false},{"id":1,"x":15.50,"y":2.50,"hdg":0.000,"batt":99.1,"det":false},{"id":2,"x":12.50,"y":2.50,"hdg":0.000,"batt":99.1,"det":false},{"id":3,"x":51.61,"y":51.61,"hdg":-2.356,"batt":99.1,"det":false}]} -{"type":"step","ep":29,"step":28,"t":28.00,"coverage":0.0045,"drones":[{"id":0,"x":142.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":1,"x":22.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":2,"x":17.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":3,"x":45.95,"y":45.95,"hdg":-2.356,"batt":99.0,"det":false}]} -{"type":"step","ep":29,"step":29,"t":29.00,"coverage":0.0046,"drones":[{"id":0,"x":147.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":1,"x":30.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":2,"x":22.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":3,"x":40.29,"y":40.29,"hdg":-2.356,"batt":99.0,"det":false}]} -{"type":"step","ep":29,"step":30,"t":30.00,"coverage":0.0048,"drones":[{"id":0,"x":152.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":1,"x":37.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":2,"x":27.50,"y":2.50,"hdg":0.000,"batt":99.0,"det":false},{"id":3,"x":34.64,"y":34.64,"hdg":-2.356,"batt":99.0,"det":false}]} -{"type":"step","ep":29,"step":31,"t":31.00,"coverage":0.0049,"drones":[{"id":0,"x":157.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":1,"x":45.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":2,"x":32.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":3,"x":28.98,"y":28.98,"hdg":-2.356,"batt":98.9,"det":false}]} -{"type":"step","ep":29,"step":32,"t":32.00,"coverage":0.0051,"drones":[{"id":0,"x":162.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":1,"x":53.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":2,"x":37.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":3,"x":23.32,"y":23.32,"hdg":-2.356,"batt":98.9,"det":false}]} -{"type":"step","ep":29,"step":33,"t":33.00,"coverage":0.0052,"drones":[{"id":0,"x":167.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":1,"x":61.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":2,"x":42.50,"y":2.50,"hdg":0.000,"batt":98.9,"det":false},{"id":3,"x":17.67,"y":17.67,"hdg":-2.356,"batt":98.9,"det":false}]} -{"type":"step","ep":29,"step":34,"t":34.00,"coverage":0.0054,"drones":[{"id":0,"x":172.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":1,"x":69.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":2,"x":47.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":3,"x":12.01,"y":12.01,"hdg":-2.356,"batt":98.8,"det":false}]} -{"type":"step","ep":29,"step":35,"t":35.00,"coverage":0.0055,"drones":[{"id":0,"x":177.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":1,"x":72.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":2,"x":52.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":3,"x":6.35,"y":6.35,"hdg":-2.356,"batt":98.8,"det":false}]} -{"type":"step","ep":29,"step":36,"t":36.00,"coverage":0.0056,"drones":[{"id":0,"x":182.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":1,"x":77.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":2,"x":57.50,"y":2.50,"hdg":0.000,"batt":98.8,"det":false},{"id":3,"x":2.50,"y":2.50,"hdg":-2.356,"batt":98.8,"det":false}]} -{"type":"step","ep":29,"step":37,"t":37.00,"coverage":0.0058,"drones":[{"id":0,"x":187.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":1,"x":82.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":2,"x":62.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":3,"x":7.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false}]} -{"type":"step","ep":29,"step":38,"t":38.00,"coverage":0.0059,"drones":[{"id":0,"x":192.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":1,"x":87.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":2,"x":67.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":3,"x":12.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false}]} -{"type":"step","ep":29,"step":39,"t":39.00,"coverage":0.0061,"drones":[{"id":0,"x":197.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":1,"x":92.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":2,"x":72.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false},{"id":3,"x":17.50,"y":2.50,"hdg":0.000,"batt":98.7,"det":false}]} -{"type":"step","ep":29,"step":40,"t":40.00,"coverage":0.0062,"drones":[{"id":0,"x":202.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":1,"x":97.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":2,"x":77.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":3,"x":22.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false}]} -{"type":"step","ep":29,"step":41,"t":41.00,"coverage":0.0064,"drones":[{"id":0,"x":207.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":1,"x":102.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":2,"x":82.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":3,"x":27.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false}]} -{"type":"step","ep":29,"step":42,"t":42.00,"coverage":0.0066,"drones":[{"id":0,"x":212.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":1,"x":107.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":2,"x":87.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false},{"id":3,"x":32.50,"y":2.50,"hdg":0.000,"batt":98.6,"det":false}]} -{"type":"step","ep":29,"step":43,"t":43.00,"coverage":0.0067,"drones":[{"id":0,"x":217.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":1,"x":112.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":2,"x":92.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":3,"x":37.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false}]} -{"type":"step","ep":29,"step":44,"t":44.00,"coverage":0.0069,"drones":[{"id":0,"x":222.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":1,"x":117.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":2,"x":97.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":3,"x":42.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false}]} -{"type":"step","ep":29,"step":45,"t":45.00,"coverage":0.0070,"drones":[{"id":0,"x":227.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":1,"x":122.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":2,"x":102.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false},{"id":3,"x":47.50,"y":2.50,"hdg":0.000,"batt":98.5,"det":false}]} -{"type":"step","ep":29,"step":46,"t":46.00,"coverage":0.0072,"drones":[{"id":0,"x":232.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":1,"x":127.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":2,"x":107.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":3,"x":52.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false}]} -{"type":"step","ep":29,"step":47,"t":47.00,"coverage":0.0073,"drones":[{"id":0,"x":237.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":1,"x":132.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":2,"x":112.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":3,"x":57.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false}]} -{"type":"step","ep":29,"step":48,"t":48.00,"coverage":0.0075,"drones":[{"id":0,"x":242.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":1,"x":137.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":2,"x":117.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false},{"id":3,"x":62.50,"y":2.50,"hdg":0.000,"batt":98.4,"det":false}]} -{"type":"step","ep":29,"step":49,"t":49.00,"coverage":0.0077,"drones":[{"id":0,"x":247.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":1,"x":142.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":2,"x":122.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":3,"x":67.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false}]} -{"type":"step","ep":29,"step":50,"t":50.00,"coverage":0.0078,"drones":[{"id":0,"x":252.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":1,"x":147.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":2,"x":127.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":3,"x":72.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false}]} -{"type":"step","ep":29,"step":51,"t":51.00,"coverage":0.0080,"drones":[{"id":0,"x":257.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":1,"x":152.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":2,"x":132.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false},{"id":3,"x":77.50,"y":2.50,"hdg":0.000,"batt":98.3,"det":false}]} -{"type":"step","ep":29,"step":52,"t":52.00,"coverage":0.0081,"drones":[{"id":0,"x":262.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":1,"x":157.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":2,"x":137.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":3,"x":82.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false}]} -{"type":"step","ep":29,"step":53,"t":53.00,"coverage":0.0083,"drones":[{"id":0,"x":267.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":1,"x":162.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":2,"x":142.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":3,"x":87.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false}]} -{"type":"step","ep":29,"step":54,"t":54.00,"coverage":0.0084,"drones":[{"id":0,"x":272.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":1,"x":167.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":2,"x":147.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false},{"id":3,"x":92.50,"y":2.50,"hdg":0.000,"batt":98.2,"det":false}]} -{"type":"step","ep":29,"step":55,"t":55.00,"coverage":0.0086,"drones":[{"id":0,"x":277.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":1,"x":172.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":2,"x":152.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":3,"x":97.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false}]} -{"type":"step","ep":29,"step":56,"t":56.00,"coverage":0.0087,"drones":[{"id":0,"x":282.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":1,"x":177.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":2,"x":157.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":3,"x":102.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false}]} -{"type":"step","ep":29,"step":57,"t":57.00,"coverage":0.0089,"drones":[{"id":0,"x":287.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":1,"x":182.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":2,"x":162.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false},{"id":3,"x":107.50,"y":2.50,"hdg":0.000,"batt":98.1,"det":false}]} -{"type":"step","ep":29,"step":58,"t":58.00,"coverage":0.0091,"drones":[{"id":0,"x":292.50,"y":2.50,"hdg":0.000,"batt":98.0,"det":false},{"id":1,"x":187.50,"y":2.50,"hdg":0.000,"batt":98.0,"det":false},{"id":2,"x":167.50,"y":2.50,"hdg":0.000,"batt":98.0,"det":false},{"id":3,"x":112.50,"y":2.50,"hdg":0.000,"batt":98.0,"det":false}]} -{"type":"step","ep":29,"step":59,"t":59.00,"coverage":0.0092,"drones":[{"id":0,"x":297.50,"y":2.50,"hdg":0.000,"batt":98.0,"det":false},{"id":1,"x":192.50,"y":2.50,"hdg":0.000,"batt":98.0,"det":false},{"id":2,"x":172.50,"y":2.50,"hdg":0.000,"batt":98.0,"det":false},{"id":3,"x":117.50,"y":2.50,"hdg":0.000,"batt":98.0,"det":false}]} -{"type":"episode","ep":29,"mean_return":123.6877,"policy_loss":-9671.3232,"value_loss":7154.7334,"victims_found":0} +{"type":"meta","profile":"sar · flight=partitioned_lawnmower · learn=curiosity","drones":4,"area_w":400.00,"area_h":400.00,"victims":[[80.00,120.00],[240.00,180.00]]} +{"type":"episode","ep":0,"mean_return":741.0585,"policy_loss":-620.2698,"value_loss":895311.9375,"victims_found":0} +{"type":"episode","ep":1,"mean_return":741.0585,"policy_loss":-990.6716,"value_loss":894566.8750,"victims_found":0} +{"type":"episode","ep":2,"mean_return":741.0585,"policy_loss":-1413.1008,"value_loss":893805.3125,"victims_found":0} +{"type":"episode","ep":3,"mean_return":741.0585,"policy_loss":-1880.1860,"value_loss":892984.3125,"victims_found":0} +{"type":"episode","ep":4,"mean_return":741.0585,"policy_loss":-2413.7896,"value_loss":892056.8750,"victims_found":0} +{"type":"episode","ep":5,"mean_return":741.0585,"policy_loss":-3023.4106,"value_loss":891006.7500,"victims_found":0} +{"type":"episode","ep":6,"mean_return":741.0585,"policy_loss":-3718.3889,"value_loss":889813.3750,"victims_found":0} +{"type":"episode","ep":7,"mean_return":741.0585,"policy_loss":-4531.2881,"value_loss":888444.9375,"victims_found":0} +{"type":"episode","ep":8,"mean_return":741.0585,"policy_loss":-5481.8413,"value_loss":886880.6250,"victims_found":0} +{"type":"episode","ep":9,"mean_return":741.0585,"policy_loss":-6585.8242,"value_loss":885090.9375,"victims_found":0} +{"type":"episode","ep":10,"mean_return":741.0585,"policy_loss":-7877.0918,"value_loss":883025.6875,"victims_found":0} +{"type":"episode","ep":11,"mean_return":741.0585,"policy_loss":-9397.7676,"value_loss":880637.3125,"victims_found":0} +{"type":"episode","ep":12,"mean_return":741.0585,"policy_loss":-11170.0850,"value_loss":877859.1875,"victims_found":0} +{"type":"episode","ep":13,"mean_return":741.0585,"policy_loss":-13207.6719,"value_loss":874662.1875,"victims_found":0} +{"type":"episode","ep":14,"mean_return":741.0585,"policy_loss":-15539.2266,"value_loss":871001.6250,"victims_found":0} +{"type":"episode","ep":15,"mean_return":741.0585,"policy_loss":-18196.9355,"value_loss":866829.7500,"victims_found":0} +{"type":"episode","ep":16,"mean_return":741.0585,"policy_loss":-21217.7344,"value_loss":862094.5000,"victims_found":0} +{"type":"episode","ep":17,"mean_return":741.0585,"policy_loss":-24640.4941,"value_loss":856742.5625,"victims_found":0} +{"type":"episode","ep":18,"mean_return":741.0585,"policy_loss":-28501.7402,"value_loss":850726.5625,"victims_found":0} +{"type":"episode","ep":19,"mean_return":741.0585,"policy_loss":-32825.4297,"value_loss":844003.0000,"victims_found":0} +{"type":"episode","ep":20,"mean_return":741.0585,"policy_loss":-37638.2344,"value_loss":836530.1250,"victims_found":0} +{"type":"episode","ep":21,"mean_return":741.0585,"policy_loss":-42972.3633,"value_loss":828270.1875,"victims_found":0} +{"type":"episode","ep":22,"mean_return":741.0585,"policy_loss":-48866.8125,"value_loss":819184.3750,"victims_found":0} +{"type":"episode","ep":23,"mean_return":741.0585,"policy_loss":-55348.8242,"value_loss":809248.5000,"victims_found":0} +{"type":"episode","ep":24,"mean_return":741.0585,"policy_loss":-62441.0039,"value_loss":798454.4375,"victims_found":0} +{"type":"episode","ep":25,"mean_return":741.0585,"policy_loss":-70163.4062,"value_loss":786799.1875,"victims_found":0} +{"type":"episode","ep":26,"mean_return":741.0585,"policy_loss":-78538.6641,"value_loss":774283.5625,"victims_found":0} +{"type":"episode","ep":27,"mean_return":741.0585,"policy_loss":-87601.0078,"value_loss":760896.3750,"victims_found":0} +{"type":"episode","ep":28,"mean_return":741.0585,"policy_loss":-97379.8828,"value_loss":746647.8750,"victims_found":0} +{"type":"step","ep":29,"step":0,"t":0.00,"coverage":0.0155,"drones":[{"id":0,"x":14.00,"y":14.00,"hdg":0.785,"batt":100.0,"det":false},{"id":1,"x":202.01,"y":10.33,"hdg":3.100,"batt":100.0,"det":false},{"id":2,"x":15.77,"y":204.46,"hdg":-0.765,"batt":100.0,"det":false},{"id":3,"x":213.75,"y":202.93,"hdg":-1.083,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":1,"t":1.00,"coverage":0.0208,"drones":[{"id":0,"x":22.00,"y":14.00,"hdg":0.000,"batt":100.0,"det":false},{"id":1,"x":194.02,"y":10.82,"hdg":3.081,"batt":100.0,"det":false},{"id":2,"x":21.89,"y":199.31,"hdg":-0.700,"batt":100.0,"det":false},{"id":3,"x":218.24,"y":196.31,"hdg":-0.974,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":2,"t":2.00,"coverage":0.0264,"drones":[{"id":0,"x":30.00,"y":14.00,"hdg":0.000,"batt":100.0,"det":false},{"id":1,"x":186.09,"y":11.87,"hdg":3.010,"batt":100.0,"det":false},{"id":2,"x":28.30,"y":194.52,"hdg":-0.641,"batt":100.0,"det":false},{"id":3,"x":223.36,"y":190.17,"hdg":-0.877,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":3,"t":3.00,"coverage":0.0306,"drones":[{"id":0,"x":38.00,"y":14.00,"hdg":0.000,"batt":100.0,"det":false},{"id":1,"x":193.97,"y":13.28,"hdg":0.177,"batt":100.0,"det":false},{"id":2,"x":34.95,"y":190.07,"hdg":-0.590,"batt":100.0,"det":false},{"id":3,"x":228.99,"y":184.48,"hdg":-0.790,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":4,"t":4.00,"coverage":0.0362,"drones":[{"id":0,"x":45.25,"y":17.38,"hdg":0.437,"batt":100.0,"det":false},{"id":1,"x":195.08,"y":21.20,"hdg":1.431,"batt":100.0,"det":false},{"id":2,"x":41.92,"y":186.14,"hdg":-0.513,"batt":100.0,"det":false},{"id":3,"x":235.11,"y":179.33,"hdg":-0.700,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":5,"t":5.00,"coverage":0.0413,"drones":[{"id":0,"x":50.92,"y":23.02,"hdg":0.783,"batt":100.0,"det":false},{"id":1,"x":188.92,"y":26.31,"hdg":2.449,"batt":100.0,"det":false},{"id":2,"x":48.68,"y":181.87,"hdg":-0.564,"batt":100.0,"det":false},{"id":3,"x":240.72,"y":173.62,"hdg":-0.794,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":6,"t":6.00,"coverage":0.0466,"drones":[{"id":0,"x":47.52,"y":30.26,"hdg":2.010,"batt":100.0,"det":false},{"id":1,"x":181.33,"y":28.85,"hdg":2.819,"batt":100.0,"det":false},{"id":2,"x":55.16,"y":177.18,"hdg":-0.626,"batt":100.0,"det":false},{"id":3,"x":245.59,"y":167.28,"hdg":-0.915,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":7,"t":7.00,"coverage":0.0519,"drones":[{"id":0,"x":39.97,"y":32.91,"hdg":2.805,"batt":100.0,"det":false},{"id":1,"x":173.48,"y":30.38,"hdg":2.949,"batt":100.0,"det":false},{"id":2,"x":61.25,"y":172.00,"hdg":-0.705,"batt":100.0,"det":false},{"id":3,"x":249.43,"y":160.26,"hdg":-1.071,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":8,"t":8.00,"coverage":0.0573,"drones":[{"id":0,"x":35.38,"y":39.46,"hdg":2.182,"batt":100.0,"det":false},{"id":1,"x":166.82,"y":34.81,"hdg":2.554,"batt":100.0,"det":false},{"id":2,"x":67.91,"y":167.55,"hdg":-0.589,"batt":100.0,"det":false},{"id":3,"x":254.08,"y":153.75,"hdg":-0.950,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":9,"t":9.00,"coverage":0.0628,"drones":[{"id":0,"x":37.07,"y":47.28,"hdg":1.357,"batt":100.0,"det":false},{"id":1,"x":162.21,"y":41.35,"hdg":2.185,"batt":100.0,"det":false},{"id":2,"x":74.89,"y":163.64,"hdg":-0.511,"batt":100.0,"det":false},{"id":3,"x":259.88,"y":148.23,"hdg":-0.761,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":10,"t":10.00,"coverage":0.0684,"drones":[{"id":0,"x":43.66,"y":51.82,"hdg":0.604,"batt":100.0,"det":false},{"id":1,"x":164.31,"y":49.07,"hdg":1.305,"batt":100.0,"det":false},{"id":2,"x":82.10,"y":160.18,"hdg":-0.447,"batt":100.0,"det":false},{"id":3,"x":266.40,"y":143.60,"hdg":-0.618,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":11,"t":11.00,"coverage":0.0736,"drones":[{"id":0,"x":51.25,"y":54.36,"hdg":0.323,"batt":100.0,"det":false},{"id":1,"x":171.11,"y":53.29,"hdg":0.556,"batt":100.0,"det":false},{"id":2,"x":89.48,"y":157.10,"hdg":-0.396,"batt":100.0,"det":false},{"id":3,"x":273.38,"y":139.70,"hdg":-0.510,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":12,"t":12.00,"coverage":0.0794,"drones":[{"id":0,"x":57.09,"y":59.82,"hdg":0.751,"batt":100.0,"det":false},{"id":1,"x":175.23,"y":60.15,"hdg":1.029,"batt":100.0,"det":false},{"id":2,"x":97.18,"y":154.92,"hdg":-0.276,"batt":100.0,"det":false},{"id":3,"x":280.97,"y":137.16,"hdg":-0.323,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":13,"t":13.00,"coverage":0.0848,"drones":[{"id":0,"x":59.66,"y":67.40,"hdg":1.245,"batt":100.0,"det":false},{"id":1,"x":174.14,"y":68.07,"hdg":1.708,"batt":100.0,"det":false},{"id":2,"x":104.78,"y":152.41,"hdg":-0.318,"batt":100.0,"det":false},{"id":3,"x":288.29,"y":133.94,"hdg":-0.414,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":14,"t":14.00,"coverage":0.0903,"drones":[{"id":0,"x":55.66,"y":74.33,"hdg":2.094,"batt":100.0,"det":false},{"id":1,"x":168.28,"y":73.52,"hdg":2.392,"batt":100.0,"det":false},{"id":2,"x":112.21,"y":149.46,"hdg":-0.378,"batt":100.0,"det":false},{"id":3,"x":294.94,"y":129.49,"hdg":-0.590,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":15,"t":15.00,"coverage":0.0956,"drones":[{"id":0,"x":48.70,"y":78.28,"hdg":2.625,"batt":100.0,"det":false},{"id":1,"x":160.99,"y":76.81,"hdg":2.718,"batt":100.0,"det":false},{"id":2,"x":119.35,"y":145.86,"hdg":-0.468,"batt":100.0,"det":false},{"id":3,"x":299.08,"y":122.64,"hdg":-1.026,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":16,"t":16.00,"coverage":0.1017,"drones":[{"id":0,"x":44.00,"y":84.75,"hdg":2.200,"batt":100.0,"det":false},{"id":1,"x":155.46,"y":82.60,"hdg":2.333,"batt":100.0,"det":false},{"id":2,"x":127.18,"y":144.21,"hdg":-0.207,"batt":100.0,"det":false},{"id":3,"x":306.89,"y":124.40,"hdg":0.221,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":17,"t":17.00,"coverage":0.1072,"drones":[{"id":0,"x":43.61,"y":92.74,"hdg":1.619,"batt":100.0,"det":false},{"id":1,"x":153.09,"y":90.24,"hdg":1.872,"batt":100.0,"det":false},{"id":2,"x":135.08,"y":142.96,"hdg":-0.157,"batt":100.0,"det":false},{"id":3,"x":314.88,"y":124.76,"hdg":0.046,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":18,"t":18.00,"coverage":0.1123,"drones":[{"id":0,"x":48.58,"y":99.01,"hdg":0.900,"batt":100.0,"det":false},{"id":1,"x":156.51,"y":97.47,"hdg":1.129,"batt":100.0,"det":false},{"id":2,"x":143.02,"y":141.96,"hdg":-0.125,"batt":100.0,"det":false},{"id":3,"x":322.88,"y":124.94,"hdg":0.022,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":19,"t":19.00,"coverage":0.1181,"drones":[{"id":0,"x":55.60,"y":102.84,"hdg":0.500,"batt":100.0,"det":false},{"id":1,"x":163.10,"y":102.00,"hdg":0.602,"batt":100.0,"det":false},{"id":2,"x":150.98,"y":141.14,"hdg":-0.103,"batt":100.0,"det":false},{"id":3,"x":330.87,"y":125.06,"hdg":0.014,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":20,"t":20.00,"coverage":0.1234,"drones":[{"id":0,"x":60.71,"y":109.00,"hdg":0.879,"batt":100.0,"det":false},{"id":1,"x":167.56,"y":108.65,"hdg":0.980,"batt":100.0,"det":false},{"id":2,"x":158.95,"y":141.84,"hdg":0.087,"batt":100.0,"det":false},{"id":3,"x":338.22,"y":128.22,"hdg":0.407,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":21,"t":21.00,"coverage":0.1283,"drones":[{"id":0,"x":62.33,"y":116.84,"hdg":1.367,"batt":100.0,"det":false},{"id":1,"x":167.99,"y":116.63,"hdg":1.517,"batt":100.0,"det":false},{"id":2,"x":166.90,"y":142.71,"hdg":0.109,"batt":100.0,"det":false},{"id":3,"x":344.43,"y":133.26,"hdg":0.681,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":22,"t":22.00,"coverage":0.1334,"drones":[{"id":0,"x":58.49,"y":123.86,"hdg":2.071,"batt":100.0,"det":false},{"id":1,"x":163.42,"y":123.20,"hdg":2.179,"batt":100.0,"det":false},{"id":2,"x":174.81,"y":143.90,"hdg":0.149,"batt":100.0,"det":false},{"id":3,"x":343.50,"y":141.21,"hdg":1.688,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":23,"t":23.00,"coverage":0.1372,"drones":[{"id":0,"x":51.86,"y":128.34,"hdg":2.546,"batt":100.0,"det":false},{"id":1,"x":156.63,"y":127.43,"hdg":2.584,"batt":100.0,"det":false},{"id":2,"x":182.56,"y":145.90,"hdg":0.252,"batt":100.0,"det":false},{"id":3,"x":336.16,"y":144.39,"hdg":2.732,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":24,"t":24.00,"coverage":0.1416,"drones":[{"id":0,"x":47.25,"y":134.88,"hdg":2.185,"batt":100.0,"det":false},{"id":1,"x":151.71,"y":133.74,"hdg":2.234,"batt":100.0,"det":false},{"id":2,"x":187.81,"y":151.93,"hdg":0.854,"batt":100.0,"det":false},{"id":3,"x":332.10,"y":151.28,"hdg":2.103,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":25,"t":25.00,"coverage":0.1459,"drones":[{"id":0,"x":46.37,"y":142.83,"hdg":1.682,"batt":100.0,"det":false},{"id":1,"x":150.13,"y":141.58,"hdg":1.769,"batt":100.0,"det":false},{"id":2,"x":194.81,"y":155.81,"hdg":0.507,"batt":100.0,"det":false},{"id":3,"x":334.55,"y":158.90,"hdg":1.259,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":26,"t":26.00,"coverage":0.1502,"drones":[{"id":0,"x":50.50,"y":149.68,"hdg":1.028,"batt":100.0,"det":false},{"id":1,"x":153.66,"y":148.76,"hdg":1.114,"batt":100.0,"det":false},{"id":2,"x":202.36,"y":158.44,"hdg":0.335,"batt":100.0,"det":false},{"id":3,"x":341.26,"y":163.27,"hdg":0.578,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":27,"t":27.00,"coverage":0.1539,"drones":[{"id":0,"x":57.11,"y":154.18,"hdg":0.597,"batt":100.0,"det":false},{"id":1,"x":160.06,"y":153.56,"hdg":0.643,"batt":100.0,"det":false},{"id":2,"x":210.13,"y":160.36,"hdg":0.242,"batt":100.0,"det":false},{"id":3,"x":348.85,"y":165.77,"hdg":0.319,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":28,"t":28.00,"coverage":0.1578,"drones":[{"id":0,"x":61.84,"y":160.63,"hdg":0.939,"batt":100.0,"det":false},{"id":1,"x":164.52,"y":160.20,"hdg":0.979,"batt":100.0,"det":false},{"id":2,"x":217.10,"y":164.29,"hdg":0.514,"batt":100.0,"det":false},{"id":3,"x":354.80,"y":171.13,"hdg":0.733,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":29,"t":29.00,"coverage":0.1606,"drones":[{"id":0,"x":63.14,"y":168.53,"hdg":1.407,"batt":100.0,"det":false},{"id":1,"x":165.40,"y":168.15,"hdg":1.461,"batt":100.0,"det":false},{"id":2,"x":223.15,"y":169.52,"hdg":0.713,"batt":100.0,"det":false},{"id":3,"x":357.71,"y":178.58,"hdg":1.198,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":30,"t":30.00,"coverage":0.1634,"drones":[{"id":0,"x":59.51,"y":175.65,"hdg":2.042,"batt":100.0,"det":false},{"id":1,"x":161.49,"y":175.13,"hdg":2.081,"batt":100.0,"det":false},{"id":2,"x":226.53,"y":176.77,"hdg":1.135,"batt":100.0,"det":false},{"id":3,"x":354.14,"y":185.73,"hdg":2.034,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":31,"t":31.00,"coverage":0.1661,"drones":[{"id":0,"x":53.13,"y":180.47,"hdg":2.495,"batt":100.0,"det":false},{"id":1,"x":155.05,"y":179.87,"hdg":2.508,"batt":100.0,"det":false},{"id":2,"x":223.70,"y":184.26,"hdg":1.931,"batt":100.0,"det":false},{"id":3,"x":347.29,"y":189.87,"hdg":2.598,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":32,"t":32.00,"coverage":0.1692,"drones":[{"id":0,"x":48.63,"y":187.09,"hdg":2.168,"batt":100.0,"det":false},{"id":1,"x":150.43,"y":186.40,"hdg":2.186,"batt":100.0,"det":false},{"id":2,"x":222.28,"y":192.13,"hdg":1.749,"batt":100.0,"det":false},{"id":3,"x":342.74,"y":196.45,"hdg":2.176,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":33,"t":33.00,"coverage":0.1741,"drones":[{"id":0,"x":47.59,"y":195.02,"hdg":1.700,"batt":100.0,"det":false},{"id":1,"x":149.14,"y":194.30,"hdg":1.733,"batt":100.0,"det":false},{"id":2,"x":225.44,"y":199.48,"hdg":1.165,"batt":100.0,"det":false},{"id":3,"x":342.60,"y":204.45,"hdg":1.589,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":34,"t":34.00,"coverage":0.1792,"drones":[{"id":0,"x":51.29,"y":202.12,"hdg":1.090,"batt":100.0,"det":false},{"id":1,"x":152.59,"y":201.52,"hdg":1.125,"batt":100.0,"det":false},{"id":2,"x":231.49,"y":204.71,"hdg":0.713,"batt":100.0,"det":false},{"id":3,"x":347.66,"y":210.64,"hdg":0.886,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":35,"t":35.00,"coverage":0.1847,"drones":[{"id":0,"x":57.64,"y":206.99,"hdg":0.655,"batt":100.0,"det":false},{"id":1,"x":158.82,"y":206.53,"hdg":0.677,"batt":100.0,"det":false},{"id":2,"x":238.65,"y":208.29,"hdg":0.464,"batt":100.0,"det":false},{"id":3,"x":354.69,"y":214.46,"hdg":0.498,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":36,"t":36.00,"coverage":0.1902,"drones":[{"id":0,"x":62.15,"y":213.59,"hdg":0.971,"batt":100.0,"det":false},{"id":1,"x":163.22,"y":213.21,"hdg":0.988,"batt":100.0,"det":false},{"id":2,"x":244.38,"y":213.87,"hdg":0.771,"batt":100.0,"det":false},{"id":3,"x":359.83,"y":220.59,"hdg":0.872,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":37,"t":37.00,"coverage":0.1952,"drones":[{"id":0,"x":63.34,"y":221.50,"hdg":1.422,"batt":100.0,"det":false},{"id":1,"x":164.24,"y":221.14,"hdg":1.443,"batt":100.0,"det":false},{"id":2,"x":247.91,"y":221.05,"hdg":1.114,"batt":100.0,"det":false},{"id":3,"x":361.58,"y":228.40,"hdg":1.351,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":38,"t":38.00,"coverage":0.2009,"drones":[{"id":0,"x":59.88,"y":228.72,"hdg":2.018,"batt":100.0,"det":false},{"id":1,"x":160.69,"y":228.31,"hdg":2.031,"batt":100.0,"det":false},{"id":2,"x":246.87,"y":228.98,"hdg":1.702,"batt":100.0,"det":false},{"id":3,"x":357.89,"y":235.49,"hdg":2.051,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":39,"t":39.00,"coverage":0.2064,"drones":[{"id":0,"x":53.67,"y":233.76,"hdg":2.459,"batt":100.0,"det":false},{"id":1,"x":154.46,"y":233.34,"hdg":2.462,"batt":100.0,"det":false},{"id":2,"x":241.56,"y":234.96,"hdg":2.297,"batt":100.0,"det":false},{"id":3,"x":351.32,"y":240.06,"hdg":2.534,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":40,"t":40.00,"coverage":0.2119,"drones":[{"id":0,"x":49.27,"y":240.44,"hdg":2.153,"batt":100.0,"det":false},{"id":1,"x":150.02,"y":239.99,"hdg":2.159,"batt":100.0,"det":false},{"id":2,"x":238.17,"y":242.21,"hdg":2.008,"batt":100.0,"det":false},{"id":3,"x":346.77,"y":246.64,"hdg":2.176,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":41,"t":41.00,"coverage":0.2177,"drones":[{"id":0,"x":48.19,"y":248.37,"hdg":1.706,"batt":100.0,"det":false},{"id":1,"x":148.85,"y":247.90,"hdg":1.718,"batt":100.0,"det":false},{"id":2,"x":238.76,"y":250.19,"hdg":1.497,"batt":100.0,"det":false},{"id":3,"x":345.97,"y":254.60,"hdg":1.671,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":42,"t":42.00,"coverage":0.2233,"drones":[{"id":0,"x":51.64,"y":255.59,"hdg":1.125,"batt":100.0,"det":false},{"id":1,"x":152.19,"y":255.17,"hdg":1.141,"batt":100.0,"det":false},{"id":2,"x":243.41,"y":256.71,"hdg":0.951,"batt":100.0,"det":false},{"id":3,"x":350.13,"y":261.43,"hdg":1.023,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":43,"t":43.00,"coverage":0.2289,"drones":[{"id":0,"x":57.80,"y":260.69,"hdg":0.692,"batt":100.0,"det":false},{"id":1,"x":158.29,"y":260.35,"hdg":0.703,"batt":100.0,"det":false},{"id":2,"x":250.01,"y":261.22,"hdg":0.599,"batt":100.0,"det":false},{"id":3,"x":356.75,"y":265.93,"hdg":0.598,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":44,"t":44.00,"coverage":0.2338,"drones":[{"id":0,"x":62.19,"y":267.38,"hdg":0.990,"batt":100.0,"det":false},{"id":1,"x":162.62,"y":267.07,"hdg":0.999,"batt":100.0,"det":false},{"id":2,"x":254.97,"y":267.50,"hdg":0.902,"batt":100.0,"det":false},{"id":3,"x":361.49,"y":272.37,"hdg":0.936,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":45,"t":45.00,"coverage":0.2397,"drones":[{"id":0,"x":63.32,"y":275.30,"hdg":1.429,"batt":100.0,"det":false},{"id":1,"x":163.69,"y":275.00,"hdg":1.437,"batt":100.0,"det":false},{"id":2,"x":257.10,"y":275.21,"hdg":1.302,"batt":100.0,"det":false},{"id":3,"x":362.84,"y":280.26,"hdg":1.401,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":46,"t":46.00,"coverage":0.2452,"drones":[{"id":0,"x":60.00,"y":282.58,"hdg":1.999,"batt":100.0,"det":false},{"id":1,"x":160.33,"y":282.27,"hdg":2.003,"batt":100.0,"det":false},{"id":2,"x":254.64,"y":282.82,"hdg":1.883,"batt":100.0,"det":false},{"id":3,"x":359.27,"y":287.41,"hdg":2.034,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":47,"t":47.00,"coverage":0.2500,"drones":[{"id":0,"x":53.92,"y":287.79,"hdg":2.433,"batt":100.0,"det":false},{"id":1,"x":154.26,"y":287.47,"hdg":2.433,"batt":100.0,"det":false},{"id":2,"x":248.88,"y":288.37,"hdg":2.375,"batt":100.0,"det":false},{"id":3,"x":352.91,"y":292.27,"hdg":2.489,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":48,"t":48.00,"coverage":0.2559,"drones":[{"id":0,"x":49.60,"y":294.52,"hdg":2.141,"batt":100.0,"det":false},{"id":1,"x":149.93,"y":294.20,"hdg":2.143,"batt":100.0,"det":false},{"id":2,"x":244.94,"y":295.34,"hdg":2.086,"batt":100.0,"det":false},{"id":3,"x":348.44,"y":298.90,"hdg":2.164,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":49,"t":49.00,"coverage":0.2614,"drones":[{"id":0,"x":48.52,"y":302.45,"hdg":1.707,"batt":100.0,"det":false},{"id":1,"x":148.80,"y":302.12,"hdg":1.712,"batt":100.0,"det":false},{"id":2,"x":244.51,"y":303.33,"hdg":1.625,"batt":100.0,"det":false},{"id":3,"x":347.44,"y":306.84,"hdg":1.696,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":50,"t":50.00,"coverage":0.2672,"drones":[{"id":0,"x":51.81,"y":309.74,"hdg":1.147,"batt":100.0,"det":false},{"id":1,"x":152.04,"y":309.44,"hdg":1.154,"batt":100.0,"det":false},{"id":2,"x":248.34,"y":310.35,"hdg":1.071,"batt":100.0,"det":false},{"id":3,"x":351.14,"y":313.93,"hdg":1.089,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":51,"t":51.00,"coverage":0.2727,"drones":[{"id":0,"x":57.84,"y":314.99,"hdg":0.717,"batt":100.0,"det":false},{"id":1,"x":158.04,"y":314.73,"hdg":0.723,"batt":100.0,"det":false},{"id":2,"x":254.60,"y":315.34,"hdg":0.674,"batt":100.0,"det":false},{"id":3,"x":357.48,"y":318.81,"hdg":0.656,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":52,"t":52.00,"coverage":0.2781,"drones":[{"id":0,"x":62.14,"y":321.74,"hdg":1.003,"batt":100.0,"det":false},{"id":1,"x":162.31,"y":321.49,"hdg":1.007,"batt":100.0,"det":false},{"id":2,"x":259.15,"y":321.92,"hdg":0.965,"batt":100.0,"det":false},{"id":3,"x":362.00,"y":325.41,"hdg":0.971,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":53,"t":53.00,"coverage":0.2833,"drones":[{"id":0,"x":63.25,"y":329.66,"hdg":1.432,"batt":100.0,"det":false},{"id":1,"x":163.39,"y":329.42,"hdg":1.436,"batt":100.0,"det":false},{"id":2,"x":260.67,"y":329.77,"hdg":1.380,"batt":100.0,"det":false},{"id":3,"x":363.21,"y":333.32,"hdg":1.420,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":54,"t":54.00,"coverage":0.2891,"drones":[{"id":0,"x":60.03,"y":336.99,"hdg":1.985,"batt":100.0,"det":false},{"id":1,"x":160.17,"y":336.74,"hdg":1.986,"batt":100.0,"det":false},{"id":2,"x":257.78,"y":337.23,"hdg":1.940,"batt":100.0,"det":false},{"id":3,"x":359.77,"y":340.55,"hdg":2.014,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":55,"t":55.00,"coverage":0.2944,"drones":[{"id":0,"x":54.06,"y":342.31,"hdg":2.414,"batt":100.0,"det":false},{"id":1,"x":154.20,"y":342.07,"hdg":2.412,"batt":100.0,"det":false},{"id":2,"x":251.93,"y":342.68,"hdg":2.392,"batt":100.0,"det":false},{"id":3,"x":353.58,"y":345.61,"hdg":2.456,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":56,"t":56.00,"coverage":0.2998,"drones":[{"id":0,"x":49.49,"y":348.88,"hdg":2.178,"batt":100.0,"det":false},{"id":1,"x":149.64,"y":348.64,"hdg":2.177,"batt":100.0,"det":false},{"id":2,"x":247.51,"y":349.35,"hdg":2.155,"batt":100.0,"det":false},{"id":3,"x":348.88,"y":352.08,"hdg":2.200,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":57,"t":57.00,"coverage":0.3050,"drones":[{"id":0,"x":48.33,"y":356.79,"hdg":1.716,"batt":100.0,"det":false},{"id":1,"x":148.46,"y":356.56,"hdg":1.718,"batt":100.0,"det":false},{"id":2,"x":246.65,"y":357.31,"hdg":1.679,"batt":100.0,"det":false},{"id":3,"x":347.74,"y":360.00,"hdg":1.713,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":58,"t":58.00,"coverage":0.3109,"drones":[{"id":0,"x":51.92,"y":363.95,"hdg":1.106,"batt":100.0,"det":false},{"id":1,"x":152.02,"y":363.73,"hdg":1.111,"batt":100.0,"det":false},{"id":2,"x":250.49,"y":364.33,"hdg":1.070,"batt":100.0,"det":false},{"id":3,"x":351.63,"y":366.99,"hdg":1.063,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":59,"t":59.00,"coverage":0.3159,"drones":[{"id":0,"x":58.22,"y":368.88,"hdg":0.664,"batt":100.0,"det":false},{"id":1,"x":158.30,"y":368.68,"hdg":0.668,"batt":100.0,"det":false},{"id":2,"x":256.88,"y":369.13,"hdg":0.644,"batt":100.0,"det":false},{"id":3,"x":358.15,"y":371.63,"hdg":0.619,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":60,"t":60.00,"coverage":0.3172,"drones":[{"id":0,"x":57.23,"y":360.94,"hdg":-1.695,"batt":100.0,"det":false},{"id":1,"x":157.31,"y":360.74,"hdg":-1.695,"batt":100.0,"det":false},{"id":2,"x":255.92,"y":361.19,"hdg":-1.691,"batt":100.0,"det":false},{"id":3,"x":357.17,"y":363.69,"hdg":-1.694,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":61,"t":61.00,"coverage":0.3177,"drones":[{"id":0,"x":56.88,"y":352.94,"hdg":-1.615,"batt":100.0,"det":false},{"id":1,"x":156.95,"y":352.75,"hdg":-1.615,"batt":100.0,"det":false},{"id":2,"x":255.60,"y":353.19,"hdg":-1.611,"batt":100.0,"det":false},{"id":3,"x":356.82,"y":355.70,"hdg":-1.614,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":62,"t":62.00,"coverage":0.3177,"drones":[{"id":0,"x":57.19,"y":344.95,"hdg":-1.532,"batt":100.0,"det":false},{"id":1,"x":157.26,"y":344.76,"hdg":-1.532,"batt":100.0,"det":false},{"id":2,"x":255.94,"y":345.20,"hdg":-1.528,"batt":100.0,"det":false},{"id":3,"x":357.13,"y":347.71,"hdg":-1.532,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":63,"t":63.00,"coverage":0.3178,"drones":[{"id":0,"x":58.17,"y":337.01,"hdg":-1.448,"batt":100.0,"det":false},{"id":1,"x":158.24,"y":336.82,"hdg":-1.448,"batt":100.0,"det":false},{"id":2,"x":256.95,"y":337.26,"hdg":-1.444,"batt":100.0,"det":false},{"id":3,"x":358.10,"y":339.76,"hdg":-1.449,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":64,"t":64.00,"coverage":0.3181,"drones":[{"id":0,"x":59.24,"y":329.08,"hdg":-1.437,"batt":100.0,"det":false},{"id":1,"x":159.31,"y":328.89,"hdg":-1.437,"batt":100.0,"det":false},{"id":2,"x":258.05,"y":329.34,"hdg":-1.433,"batt":100.0,"det":false},{"id":3,"x":359.16,"y":331.84,"hdg":-1.438,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":65,"t":65.00,"coverage":0.3181,"drones":[{"id":0,"x":59.54,"y":321.09,"hdg":-1.533,"batt":100.0,"det":false},{"id":1,"x":159.61,"y":320.89,"hdg":-1.534,"batt":100.0,"det":false},{"id":2,"x":258.39,"y":321.35,"hdg":-1.529,"batt":100.0,"det":false},{"id":3,"x":359.46,"y":323.84,"hdg":-1.533,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":66,"t":66.00,"coverage":0.3192,"drones":[{"id":0,"x":59.04,"y":313.10,"hdg":-1.634,"batt":100.0,"det":false},{"id":1,"x":159.10,"y":312.91,"hdg":-1.634,"batt":100.0,"det":false},{"id":2,"x":257.92,"y":313.36,"hdg":-1.629,"batt":100.0,"det":false},{"id":3,"x":358.97,"y":315.86,"hdg":-1.633,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":67,"t":67.00,"coverage":0.3202,"drones":[{"id":0,"x":57.73,"y":305.21,"hdg":-1.735,"batt":100.0,"det":false},{"id":1,"x":157.79,"y":305.02,"hdg":-1.736,"batt":100.0,"det":false},{"id":2,"x":256.64,"y":305.46,"hdg":-1.731,"batt":100.0,"det":false},{"id":3,"x":357.67,"y":307.96,"hdg":-1.734,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":68,"t":68.00,"coverage":0.3203,"drones":[{"id":0,"x":56.26,"y":297.35,"hdg":-1.755,"batt":100.0,"det":false},{"id":1,"x":156.32,"y":297.15,"hdg":-1.755,"batt":100.0,"det":false},{"id":2,"x":255.21,"y":297.59,"hdg":-1.750,"batt":100.0,"det":false},{"id":3,"x":356.23,"y":300.09,"hdg":-1.752,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":69,"t":69.00,"coverage":0.3203,"drones":[{"id":0,"x":55.76,"y":289.36,"hdg":-1.633,"batt":100.0,"det":false},{"id":1,"x":155.82,"y":289.17,"hdg":-1.634,"batt":100.0,"det":false},{"id":2,"x":254.75,"y":289.60,"hdg":-1.629,"batt":100.0,"det":false},{"id":3,"x":355.73,"y":292.11,"hdg":-1.633,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":70,"t":70.00,"coverage":0.3209,"drones":[{"id":0,"x":56.28,"y":281.38,"hdg":-1.506,"batt":100.0,"det":false},{"id":1,"x":156.34,"y":281.19,"hdg":-1.506,"batt":100.0,"det":false},{"id":2,"x":255.30,"y":281.62,"hdg":-1.501,"batt":100.0,"det":false},{"id":3,"x":356.25,"y":284.13,"hdg":-1.507,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":71,"t":71.00,"coverage":0.3209,"drones":[{"id":0,"x":57.83,"y":273.53,"hdg":-1.376,"batt":100.0,"det":false},{"id":1,"x":157.88,"y":273.34,"hdg":-1.376,"batt":100.0,"det":false},{"id":2,"x":256.89,"y":273.78,"hdg":-1.372,"batt":100.0,"det":false},{"id":3,"x":357.78,"y":276.27,"hdg":-1.378,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":72,"t":72.00,"coverage":0.3212,"drones":[{"id":0,"x":59.61,"y":265.73,"hdg":-1.346,"batt":100.0,"det":false},{"id":1,"x":159.67,"y":265.54,"hdg":-1.346,"batt":100.0,"det":false},{"id":2,"x":258.71,"y":265.99,"hdg":-1.341,"batt":100.0,"det":false},{"id":3,"x":359.54,"y":268.47,"hdg":-1.349,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":73,"t":73.00,"coverage":0.3237,"drones":[{"id":0,"x":60.11,"y":257.75,"hdg":-1.509,"batt":100.0,"det":false},{"id":1,"x":160.16,"y":257.55,"hdg":-1.509,"batt":100.0,"det":false},{"id":2,"x":259.24,"y":258.01,"hdg":-1.504,"batt":100.0,"det":false},{"id":3,"x":360.03,"y":260.48,"hdg":-1.510,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":74,"t":74.00,"coverage":0.3253,"drones":[{"id":0,"x":59.21,"y":249.80,"hdg":-1.684,"batt":100.0,"det":false},{"id":1,"x":159.26,"y":249.60,"hdg":-1.684,"batt":100.0,"det":false},{"id":2,"x":258.39,"y":250.06,"hdg":-1.678,"batt":100.0,"det":false},{"id":3,"x":359.15,"y":252.53,"hdg":-1.681,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":75,"t":75.00,"coverage":0.3256,"drones":[{"id":0,"x":56.92,"y":242.13,"hdg":-1.860,"batt":100.0,"det":false},{"id":1,"x":156.97,"y":241.94,"hdg":-1.861,"batt":100.0,"det":false},{"id":2,"x":256.15,"y":242.38,"hdg":-1.855,"batt":100.0,"det":false},{"id":3,"x":356.90,"y":244.85,"hdg":-1.855,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":76,"t":76.00,"coverage":0.3258,"drones":[{"id":0,"x":54.15,"y":234.63,"hdg":-1.925,"batt":100.0,"det":false},{"id":1,"x":154.19,"y":234.44,"hdg":-1.926,"batt":100.0,"det":false},{"id":2,"x":253.42,"y":234.85,"hdg":-1.918,"batt":100.0,"det":false},{"id":3,"x":354.19,"y":237.33,"hdg":-1.917,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":77,"t":77.00,"coverage":0.3269,"drones":[{"id":0,"x":53.26,"y":226.68,"hdg":-1.682,"batt":100.0,"det":false},{"id":1,"x":153.29,"y":226.49,"hdg":-1.683,"batt":100.0,"det":false},{"id":2,"x":252.59,"y":226.90,"hdg":-1.675,"batt":100.0,"det":false},{"id":3,"x":353.32,"y":229.38,"hdg":-1.680,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":78,"t":78.00,"coverage":0.3280,"drones":[{"id":0,"x":54.57,"y":218.79,"hdg":-1.406,"batt":100.0,"det":false},{"id":1,"x":154.61,"y":218.60,"hdg":-1.406,"batt":100.0,"det":false},{"id":2,"x":253.95,"y":219.02,"hdg":-1.400,"batt":100.0,"det":false},{"id":3,"x":354.59,"y":221.48,"hdg":-1.411,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":79,"t":79.00,"coverage":0.3292,"drones":[{"id":0,"x":57.96,"y":211.54,"hdg":-1.133,"batt":100.0,"det":false},{"id":1,"x":158.00,"y":211.35,"hdg":-1.133,"batt":100.0,"det":false},{"id":2,"x":257.37,"y":211.78,"hdg":-1.128,"batt":100.0,"det":false},{"id":3,"x":357.90,"y":214.20,"hdg":-1.144,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":80,"t":80.00,"coverage":0.3330,"drones":[{"id":0,"x":62.53,"y":204.97,"hdg":-0.963,"batt":100.0,"det":false},{"id":1,"x":162.58,"y":204.79,"hdg":-0.962,"batt":100.0,"det":false},{"id":2,"x":261.97,"y":205.24,"hdg":-0.958,"batt":100.0,"det":false},{"id":3,"x":362.34,"y":207.54,"hdg":-0.983,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":81,"t":81.00,"coverage":0.3355,"drones":[{"id":0,"x":63.69,"y":197.06,"hdg":-1.425,"batt":100.0,"det":false},{"id":1,"x":163.73,"y":196.87,"hdg":-1.426,"batt":100.0,"det":false},{"id":2,"x":263.21,"y":197.34,"hdg":-1.415,"batt":100.0,"det":false},{"id":3,"x":363.47,"y":199.62,"hdg":-1.429,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":82,"t":82.00,"coverage":0.3370,"drones":[{"id":0,"x":60.09,"y":189.91,"hdg":-2.037,"batt":100.0,"det":false},{"id":1,"x":160.12,"y":189.74,"hdg":-2.040,"batt":100.0,"det":false},{"id":2,"x":259.69,"y":190.15,"hdg":-2.026,"batt":100.0,"det":false},{"id":3,"x":360.06,"y":192.38,"hdg":-2.011,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":83,"t":83.00,"coverage":0.3372,"drones":[{"id":0,"x":53.78,"y":185.00,"hdg":-2.480,"batt":100.0,"det":false},{"id":1,"x":153.79,"y":184.84,"hdg":-2.482,"batt":100.0,"det":false},{"id":2,"x":253.42,"y":185.19,"hdg":-2.472,"batt":100.0,"det":false},{"id":3,"x":353.92,"y":187.26,"hdg":-2.447,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":84,"t":84.00,"coverage":0.3381,"drones":[{"id":0,"x":45.81,"y":184.40,"hdg":-3.066,"batt":100.0,"det":false},{"id":1,"x":145.81,"y":184.27,"hdg":-3.070,"batt":100.0,"det":false},{"id":2,"x":245.45,"y":184.54,"hdg":-3.061,"batt":100.0,"det":false},{"id":3,"x":345.99,"y":186.21,"hdg":-3.011,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":85,"t":85.00,"coverage":0.3391,"drones":[{"id":0,"x":42.00,"y":182.00,"hdg":-2.580,"batt":100.0,"det":false},{"id":1,"x":142.00,"y":182.00,"hdg":-2.605,"batt":100.0,"det":false},{"id":2,"x":242.00,"y":182.00,"hdg":-2.506,"batt":100.0,"det":false},{"id":3,"x":342.00,"y":182.00,"hdg":-2.328,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":86,"t":86.00,"coverage":0.3391,"drones":[{"id":0,"x":50.00,"y":182.00,"hdg":0.000,"batt":100.0,"det":false},{"id":1,"x":150.00,"y":182.00,"hdg":0.000,"batt":100.0,"det":false},{"id":2,"x":250.00,"y":182.00,"hdg":0.000,"batt":100.0,"det":false},{"id":3,"x":350.00,"y":182.00,"hdg":0.000,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":87,"t":87.00,"coverage":0.3395,"drones":[{"id":0,"x":58.00,"y":182.00,"hdg":0.000,"batt":100.0,"det":false},{"id":1,"x":158.00,"y":182.00,"hdg":0.000,"batt":100.0,"det":false},{"id":2,"x":258.00,"y":182.00,"hdg":0.000,"batt":100.0,"det":false},{"id":3,"x":358.00,"y":182.00,"hdg":0.000,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":88,"t":88.00,"coverage":0.3416,"drones":[{"id":0,"x":64.55,"y":186.59,"hdg":0.611,"batt":100.0,"det":false},{"id":1,"x":164.55,"y":186.59,"hdg":0.611,"batt":100.0,"det":false},{"id":2,"x":264.55,"y":186.59,"hdg":0.611,"batt":100.0,"det":false},{"id":3,"x":364.55,"y":186.59,"hdg":0.611,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":89,"t":89.00,"coverage":0.3431,"drones":[{"id":0,"x":66.37,"y":194.38,"hdg":1.342,"batt":100.0,"det":false},{"id":1,"x":166.37,"y":194.38,"hdg":1.342,"batt":100.0,"det":false},{"id":2,"x":266.37,"y":194.38,"hdg":1.342,"batt":100.0,"det":false},{"id":3,"x":366.37,"y":194.38,"hdg":1.342,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":90,"t":90.00,"coverage":0.3431,"drones":[{"id":0,"x":59.63,"y":198.70,"hdg":2.572,"batt":100.0,"det":false},{"id":1,"x":159.63,"y":198.70,"hdg":2.572,"batt":100.0,"det":false},{"id":2,"x":259.63,"y":198.70,"hdg":2.572,"batt":100.0,"det":false},{"id":3,"x":359.63,"y":198.70,"hdg":2.572,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":91,"t":91.00,"coverage":0.3431,"drones":[{"id":0,"x":51.87,"y":200.62,"hdg":2.899,"batt":100.0,"det":false},{"id":1,"x":151.87,"y":200.62,"hdg":2.899,"batt":100.0,"det":false},{"id":2,"x":251.87,"y":200.62,"hdg":2.899,"batt":100.0,"det":false},{"id":3,"x":351.87,"y":200.62,"hdg":2.899,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":92,"t":92.00,"coverage":0.3441,"drones":[{"id":0,"x":46.17,"y":206.24,"hdg":2.363,"batt":100.0,"det":false},{"id":1,"x":146.17,"y":206.24,"hdg":2.363,"batt":100.0,"det":false},{"id":2,"x":246.17,"y":206.24,"hdg":2.363,"batt":100.0,"det":false},{"id":3,"x":346.17,"y":206.24,"hdg":2.363,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":93,"t":93.00,"coverage":0.3456,"drones":[{"id":0,"x":45.13,"y":214.17,"hdg":1.701,"batt":100.0,"det":false},{"id":1,"x":145.13,"y":214.17,"hdg":1.701,"batt":100.0,"det":false},{"id":2,"x":245.13,"y":214.17,"hdg":1.701,"batt":100.0,"det":false},{"id":3,"x":345.13,"y":214.17,"hdg":1.701,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":94,"t":94.00,"coverage":0.3456,"drones":[{"id":0,"x":50.91,"y":219.71,"hdg":0.764,"batt":100.0,"det":false},{"id":1,"x":150.91,"y":219.71,"hdg":0.764,"batt":100.0,"det":false},{"id":2,"x":250.91,"y":219.71,"hdg":0.764,"batt":100.0,"det":false},{"id":3,"x":350.91,"y":219.71,"hdg":0.764,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":95,"t":95.00,"coverage":0.3459,"drones":[{"id":0,"x":58.36,"y":222.60,"hdg":0.371,"batt":100.0,"det":false},{"id":1,"x":158.36,"y":222.60,"hdg":0.371,"batt":100.0,"det":false},{"id":2,"x":258.36,"y":222.60,"hdg":0.371,"batt":100.0,"det":false},{"id":3,"x":358.36,"y":222.60,"hdg":0.371,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":96,"t":96.00,"coverage":0.3477,"drones":[{"id":0,"x":63.76,"y":228.51,"hdg":0.831,"batt":100.0,"det":false},{"id":1,"x":163.76,"y":228.51,"hdg":0.831,"batt":100.0,"det":false},{"id":2,"x":263.76,"y":228.51,"hdg":0.831,"batt":100.0,"det":false},{"id":3,"x":363.76,"y":228.51,"hdg":0.831,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":97,"t":97.00,"coverage":0.3505,"drones":[{"id":0,"x":65.07,"y":236.40,"hdg":1.406,"batt":100.0,"det":false},{"id":1,"x":165.07,"y":236.40,"hdg":1.406,"batt":100.0,"det":false},{"id":2,"x":265.07,"y":236.40,"hdg":1.406,"batt":100.0,"det":false},{"id":3,"x":365.07,"y":236.40,"hdg":1.406,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":98,"t":98.00,"coverage":0.3505,"drones":[{"id":0,"x":60.15,"y":242.71,"hdg":2.233,"batt":100.0,"det":false},{"id":1,"x":160.15,"y":242.71,"hdg":2.233,"batt":100.0,"det":false},{"id":2,"x":260.15,"y":242.71,"hdg":2.233,"batt":100.0,"det":false},{"id":3,"x":360.15,"y":242.71,"hdg":2.233,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":99,"t":99.00,"coverage":0.3505,"drones":[{"id":0,"x":53.01,"y":246.32,"hdg":2.674,"batt":100.0,"det":false},{"id":1,"x":153.01,"y":246.32,"hdg":2.674,"batt":100.0,"det":false},{"id":2,"x":253.01,"y":246.32,"hdg":2.674,"batt":100.0,"det":false},{"id":3,"x":353.01,"y":246.32,"hdg":2.674,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":100,"t":100.00,"coverage":0.3508,"drones":[{"id":0,"x":47.95,"y":252.51,"hdg":2.256,"batt":100.0,"det":false},{"id":1,"x":147.95,"y":252.51,"hdg":2.256,"batt":100.0,"det":false},{"id":2,"x":247.95,"y":252.51,"hdg":2.256,"batt":100.0,"det":false},{"id":3,"x":347.95,"y":252.51,"hdg":2.256,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":101,"t":101.00,"coverage":0.3522,"drones":[{"id":0,"x":46.81,"y":260.43,"hdg":1.713,"batt":100.0,"det":false},{"id":1,"x":146.81,"y":260.43,"hdg":1.713,"batt":100.0,"det":false},{"id":2,"x":246.81,"y":260.43,"hdg":1.713,"batt":100.0,"det":false},{"id":3,"x":346.81,"y":260.43,"hdg":1.713,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":102,"t":102.00,"coverage":0.3527,"drones":[{"id":0,"x":51.36,"y":267.01,"hdg":0.966,"batt":100.0,"det":false},{"id":1,"x":151.36,"y":267.01,"hdg":0.966,"batt":100.0,"det":false},{"id":2,"x":251.36,"y":267.01,"hdg":0.966,"batt":100.0,"det":false},{"id":3,"x":351.36,"y":267.01,"hdg":0.966,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":103,"t":103.00,"coverage":0.3527,"drones":[{"id":0,"x":58.28,"y":271.02,"hdg":0.525,"batt":100.0,"det":false},{"id":1,"x":158.28,"y":271.02,"hdg":0.525,"batt":100.0,"det":false},{"id":2,"x":258.28,"y":271.02,"hdg":0.525,"batt":100.0,"det":false},{"id":3,"x":358.28,"y":271.02,"hdg":0.525,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":104,"t":104.00,"coverage":0.3533,"drones":[{"id":0,"x":63.20,"y":277.33,"hdg":0.909,"batt":100.0,"det":false},{"id":1,"x":163.20,"y":277.33,"hdg":0.909,"batt":100.0,"det":false},{"id":2,"x":263.20,"y":277.33,"hdg":0.909,"batt":100.0,"det":false},{"id":3,"x":363.20,"y":277.33,"hdg":0.909,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":105,"t":105.00,"coverage":0.3552,"drones":[{"id":0,"x":64.40,"y":285.24,"hdg":1.420,"batt":100.0,"det":false},{"id":1,"x":164.40,"y":285.24,"hdg":1.420,"batt":100.0,"det":false},{"id":2,"x":264.40,"y":285.24,"hdg":1.420,"batt":100.0,"det":false},{"id":3,"x":364.40,"y":285.24,"hdg":1.420,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":106,"t":106.00,"coverage":0.3558,"drones":[{"id":0,"x":60.24,"y":292.07,"hdg":2.118,"batt":100.0,"det":false},{"id":1,"x":160.24,"y":292.07,"hdg":2.118,"batt":100.0,"det":false},{"id":2,"x":260.24,"y":292.07,"hdg":2.118,"batt":100.0,"det":false},{"id":3,"x":360.24,"y":292.07,"hdg":2.118,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":107,"t":107.00,"coverage":0.3558,"drones":[{"id":0,"x":53.52,"y":296.41,"hdg":2.567,"batt":100.0,"det":false},{"id":1,"x":153.52,"y":296.41,"hdg":2.567,"batt":100.0,"det":false},{"id":2,"x":253.52,"y":296.41,"hdg":2.567,"batt":100.0,"det":false},{"id":3,"x":353.52,"y":296.41,"hdg":2.567,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":108,"t":108.00,"coverage":0.3558,"drones":[{"id":0,"x":48.78,"y":302.85,"hdg":2.206,"batt":100.0,"det":false},{"id":1,"x":148.78,"y":302.85,"hdg":2.206,"batt":100.0,"det":false},{"id":2,"x":248.78,"y":302.85,"hdg":2.206,"batt":100.0,"det":false},{"id":3,"x":348.78,"y":302.85,"hdg":2.206,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":109,"t":109.00,"coverage":0.3569,"drones":[{"id":0,"x":47.64,"y":310.77,"hdg":1.714,"batt":100.0,"det":false},{"id":1,"x":147.64,"y":310.77,"hdg":1.714,"batt":100.0,"det":false},{"id":2,"x":247.64,"y":310.77,"hdg":1.714,"batt":100.0,"det":false},{"id":3,"x":347.64,"y":310.77,"hdg":1.714,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":110,"t":110.00,"coverage":0.3575,"drones":[{"id":0,"x":51.60,"y":317.72,"hdg":1.053,"batt":100.0,"det":false},{"id":1,"x":151.60,"y":317.72,"hdg":1.053,"batt":100.0,"det":false},{"id":2,"x":251.60,"y":317.72,"hdg":1.053,"batt":100.0,"det":false},{"id":3,"x":351.60,"y":317.72,"hdg":1.053,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":111,"t":111.00,"coverage":0.3575,"drones":[{"id":0,"x":58.17,"y":322.29,"hdg":0.608,"batt":100.0,"det":false},{"id":1,"x":158.17,"y":322.29,"hdg":0.608,"batt":100.0,"det":false},{"id":2,"x":258.17,"y":322.29,"hdg":0.608,"batt":100.0,"det":false},{"id":3,"x":358.17,"y":322.29,"hdg":0.608,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":112,"t":112.00,"coverage":0.3577,"drones":[{"id":0,"x":62.82,"y":328.80,"hdg":0.950,"batt":100.0,"det":false},{"id":1,"x":162.82,"y":328.80,"hdg":0.950,"batt":100.0,"det":false},{"id":2,"x":262.82,"y":328.80,"hdg":0.950,"batt":100.0,"det":false},{"id":3,"x":362.82,"y":328.80,"hdg":0.950,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":113,"t":113.00,"coverage":0.3591,"drones":[{"id":0,"x":63.97,"y":336.71,"hdg":1.426,"batt":100.0,"det":false},{"id":1,"x":163.97,"y":336.71,"hdg":1.426,"batt":100.0,"det":false},{"id":2,"x":263.97,"y":336.71,"hdg":1.426,"batt":100.0,"det":false},{"id":3,"x":363.97,"y":336.71,"hdg":1.426,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":114,"t":114.00,"coverage":0.3591,"drones":[{"id":0,"x":60.22,"y":343.78,"hdg":2.060,"batt":100.0,"det":false},{"id":1,"x":160.22,"y":343.78,"hdg":2.060,"batt":100.0,"det":false},{"id":2,"x":260.22,"y":343.78,"hdg":2.060,"batt":100.0,"det":false},{"id":3,"x":360.22,"y":343.78,"hdg":2.060,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":115,"t":115.00,"coverage":0.3591,"drones":[{"id":0,"x":53.79,"y":348.54,"hdg":2.504,"batt":100.0,"det":false},{"id":1,"x":153.79,"y":348.54,"hdg":2.504,"batt":100.0,"det":false},{"id":2,"x":253.79,"y":348.54,"hdg":2.504,"batt":100.0,"det":false},{"id":3,"x":353.79,"y":348.54,"hdg":2.504,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":116,"t":116.00,"coverage":0.3591,"drones":[{"id":0,"x":48.89,"y":354.87,"hdg":2.229,"batt":100.0,"det":false},{"id":1,"x":148.89,"y":354.87,"hdg":2.229,"batt":100.0,"det":false},{"id":2,"x":248.89,"y":354.87,"hdg":2.229,"batt":100.0,"det":false},{"id":3,"x":348.89,"y":354.87,"hdg":2.229,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":117,"t":117.00,"coverage":0.3594,"drones":[{"id":0,"x":47.69,"y":362.77,"hdg":1.722,"batt":100.0,"det":false},{"id":1,"x":147.69,"y":362.77,"hdg":1.722,"batt":100.0,"det":false},{"id":2,"x":247.69,"y":362.77,"hdg":1.722,"batt":100.0,"det":false},{"id":3,"x":347.69,"y":362.77,"hdg":1.722,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":118,"t":118.00,"coverage":0.3609,"drones":[{"id":0,"x":51.80,"y":369.64,"hdg":1.031,"batt":100.0,"det":false},{"id":1,"x":151.80,"y":369.64,"hdg":1.031,"batt":100.0,"det":false},{"id":2,"x":251.80,"y":369.64,"hdg":1.031,"batt":100.0,"det":false},{"id":3,"x":351.80,"y":369.64,"hdg":1.031,"batt":100.0,"det":false}]} +{"type":"step","ep":29,"step":119,"t":119.00,"coverage":0.3627,"drones":[{"id":0,"x":58.48,"y":374.03,"hdg":0.581,"batt":100.0,"det":false},{"id":1,"x":158.48,"y":374.03,"hdg":0.581,"batt":100.0,"det":false},{"id":2,"x":258.48,"y":374.03,"hdg":0.581,"batt":100.0,"det":false},{"id":3,"x":358.48,"y":374.03,"hdg":0.581,"batt":100.0,"det":false}]} +{"type":"episode","ep":29,"mean_return":741.0585,"policy_loss":-107902.2578,"value_loss":731556.4375,"victims_found":0}