1use crate::substrate_impl::SubstrateImpl;
15use phago_agents::fitness::FitnessTracker;
16use phago_core::agent::Agent;
17use phago_core::substrate::Substrate;
18use phago_core::topology::TopologyGraph;
19use phago_core::types::*;
20use serde::Serialize;
21use serde_json;
22
23#[derive(Debug, Clone, Serialize)]
25pub enum ColonyEvent {
26 Spawned { id: AgentId, agent_type: String },
28 Moved { id: AgentId, to: Position },
30 Engulfed { id: AgentId, document: DocumentId },
32 Presented { id: AgentId, fragment_count: usize, node_ids: Vec<NodeId> },
34 Deposited { id: AgentId, location: SubstrateLocation },
36 Wired { id: AgentId, connection_count: usize },
38 Died { signal: DeathSignal },
40 TickComplete { tick: Tick, alive: usize, dead_this_tick: usize },
42 CapabilityExported { agent_id: AgentId, terms_count: usize },
44 CapabilityIntegrated { agent_id: AgentId, from_agent: AgentId, terms_count: usize },
46 Symbiosis { host: AgentId, absorbed: AgentId, host_type: String, absorbed_type: String },
48 Dissolved { agent_id: AgentId, permeability: f64, terms_externalized: usize },
50}
51
52#[derive(Debug, Clone, Serialize)]
54pub struct ColonyStats {
55 pub tick: Tick,
56 pub agents_alive: usize,
57 pub agents_died: usize,
58 pub total_spawned: usize,
59 pub graph_nodes: usize,
60 pub graph_edges: usize,
61 pub total_signals: usize,
62 pub documents_total: usize,
63 pub documents_digested: usize,
64}
65
66#[derive(Debug, Clone, Serialize)]
68pub struct AgentSnapshot {
69 pub id: AgentId,
70 pub agent_type: String,
71 pub position: Position,
72 pub age: Tick,
73 pub permeability: f64,
74 pub vocabulary_size: usize,
75}
76
77#[derive(Debug, Clone, Serialize)]
79pub struct NodeSnapshot {
80 pub id: NodeId,
81 pub label: String,
82 pub node_type: NodeType,
83 pub position: Position,
84 pub access_count: u64,
85}
86
87#[derive(Debug, Clone, Serialize)]
89pub struct EdgeSnapshot {
90 pub from_label: String,
91 pub to_label: String,
92 pub weight: f64,
93 pub co_activations: u64,
94}
95
96#[derive(Debug, Clone, Serialize)]
98pub struct ColonySnapshot {
99 pub tick: Tick,
100 pub agents: Vec<AgentSnapshot>,
101 pub nodes: Vec<NodeSnapshot>,
102 pub edges: Vec<EdgeSnapshot>,
103 pub stats: ColonyStats,
104}
105
106pub struct Colony {
108 substrate: SubstrateImpl,
109 agents: Vec<Box<dyn Agent<Input = String, Fragment = String, Presentation = Vec<String>>>>,
110 death_signals: Vec<DeathSignal>,
111 event_history: Vec<(Tick, ColonyEvent)>,
112 total_spawned: usize,
113 total_died: usize,
114 fitness_tracker: FitnessTracker,
115
116 signal_decay_rate: f64,
118 signal_removal_threshold: f64,
119 trace_decay_rate: f64,
120 trace_removal_threshold: f64,
121 edge_decay_rate: f64,
122 edge_prune_threshold: f64,
123 staleness_factor: f64,
124 maturation_ticks: u64,
125 max_edge_degree: usize,
126}
127
128impl Colony {
129 pub fn new() -> Self {
130 Self {
131 substrate: SubstrateImpl::new(),
132 agents: Vec::new(),
133 death_signals: Vec::new(),
134 event_history: Vec::new(),
135 total_spawned: 0,
136 total_died: 0,
137 fitness_tracker: FitnessTracker::new(),
138 signal_decay_rate: 0.05,
139 signal_removal_threshold: 0.01,
140 trace_decay_rate: 0.02,
141 trace_removal_threshold: 0.01,
142 edge_decay_rate: 0.005,
143 edge_prune_threshold: 0.05,
144 staleness_factor: 1.5,
145 maturation_ticks: 50,
146 max_edge_degree: 30,
147 }
148 }
149
150 pub fn spawn(
152 &mut self,
153 agent: Box<dyn Agent<Input = String, Fragment = String, Presentation = Vec<String>>>,
154 ) -> AgentId {
155 let id = agent.id();
156 self.total_spawned += 1;
157 self.fitness_tracker.register(id, 0);
158 self.agents.push(agent);
159 id
160 }
161
162 pub fn ingest_document(&mut self, title: &str, content: &str, position: Position) -> DocumentId {
167 let doc = Document {
168 id: DocumentId::new(),
169 title: title.to_string(),
170 content: content.to_string(),
171 position,
172 digested: false,
173 };
174 let doc_id = doc.id;
175 let doc_pos = doc.position;
176
177 self.substrate.add_document(doc);
178
179 self.substrate.emit_signal(Signal::new(
181 SignalType::Input,
182 1.0,
183 doc_pos,
184 AgentId::new(), self.substrate.current_tick(),
186 ));
187
188 doc_id
189 }
190
191 pub fn tick(&mut self) -> Vec<ColonyEvent> {
193 let mut events = Vec::new();
194 let mut actions: Vec<(usize, AgentAction)> = Vec::new();
195
196 for (idx, agent) in self.agents.iter_mut().enumerate() {
198 let action = agent.tick(&self.substrate);
199 actions.push((idx, action));
200 }
201
202 let mut to_die = Vec::new();
204 let mut symbiotic_deaths: Vec<(usize, AgentId)> = Vec::new(); for (idx, action) in actions {
207 match action {
208 AgentAction::Move(pos) => {
209 self.agents[idx].set_position(pos);
210 events.push(ColonyEvent::Moved {
211 id: self.agents[idx].id(),
212 to: pos,
213 });
214 }
215
216 AgentAction::EngulfDocument(doc_id) => {
217 if let Some(content) = self.substrate.consume_document(&doc_id) {
219 self.agents[idx].engulf(content);
220 events.push(ColonyEvent::Engulfed {
223 id: self.agents[idx].id(),
224 document: doc_id,
225 });
226 }
227 }
228
229 AgentAction::PresentFragments(fragments) => {
230 let agent_id = self.agents[idx].id();
231 let tick = self.substrate.current_tick();
232 let mut node_ids = Vec::new();
233
234 for frag in &fragments {
235 let existing = self.substrate.graph().find_nodes_by_label(&frag.label);
237 let node_id = if let Some(&existing_id) = existing.first() {
238 if let Some(node) = self.substrate.graph_mut().get_node_mut(&existing_id) {
240 node.access_count += 1;
241 }
242 existing_id
243 } else {
244 let node = NodeData {
246 id: NodeId::new(),
247 label: frag.label.clone(),
248 node_type: frag.node_type.clone(),
249 position: frag.position,
250 access_count: 1,
251 created_tick: tick,
252 };
253 self.substrate.add_node(node)
254 };
255 node_ids.push(node_id);
256 }
257
258 let concept_node_ids: Vec<NodeId> = node_ids.iter().filter(|id| {
268 self.substrate.graph().get_node(id)
269 .map_or(false, |n| n.node_type == NodeType::Concept)
270 }).copied().collect();
271 let mut wire_events = Vec::new();
272 for i in 0..concept_node_ids.len() {
273 for j in (i + 1)..concept_node_ids.len() {
274 let from = concept_node_ids[i];
275 let to = concept_node_ids[j];
276 if let Some(edge) = self.substrate.graph_mut().get_edge_mut(&from, &to) {
277 edge.weight = (edge.weight + 0.1).min(1.0);
279 edge.co_activations += 1;
280 edge.last_activated_tick = tick;
281 } else {
282 self.substrate.set_edge(from, to, EdgeData {
286 weight: 0.1, co_activations: 1,
288 created_tick: tick,
289 last_activated_tick: tick,
290 });
291 }
292 wire_events.push((from, to));
293 }
294 }
295
296 events.push(ColonyEvent::Presented {
297 id: agent_id,
298 fragment_count: fragments.len(),
299 node_ids,
300 });
301
302 if !wire_events.is_empty() {
303 events.push(ColonyEvent::Wired {
304 id: agent_id,
305 connection_count: wire_events.len(),
306 });
307 }
308 }
309
310 AgentAction::Deposit(location, trace) => {
311 let agent_id = self.agents[idx].id();
312 self.substrate.deposit_trace(&location, trace);
313 events.push(ColonyEvent::Deposited {
314 id: agent_id,
315 location,
316 });
317 }
318
319 AgentAction::Emit(signal) => {
320 self.substrate.emit_signal(signal);
321 }
322
323 AgentAction::WireNodes(connections) => {
324 let agent_id = self.agents[idx].id();
325 let tick = self.substrate.current_tick();
326 for (from, to, weight) in &connections {
327 if let Some(edge) = self.substrate.graph_mut().get_edge_mut(from, to) {
328 edge.weight = (edge.weight + weight).min(1.0);
329 edge.co_activations += 1;
330 edge.last_activated_tick = tick;
331 } else {
332 self.substrate.set_edge(*from, *to, EdgeData {
333 weight: *weight,
334 co_activations: 1,
335 created_tick: tick,
336 last_activated_tick: tick,
337 });
338 }
339 }
340 events.push(ColonyEvent::Wired {
341 id: agent_id,
342 connection_count: connections.len(),
343 });
344 }
345
346 AgentAction::ExportCapability(_cap_id) => {
347 let agent_id = self.agents[idx].id();
348 let agent_pos = self.agents[idx].position();
349 if let Some(vocab_bytes) = self.agents[idx].export_vocabulary() {
350 let terms_count = serde_json::from_slice::<VocabularyCapability>(&vocab_bytes)
352 .map(|v| v.terms.len())
353 .unwrap_or(0);
354
355 let trace = Trace {
357 agent_id,
358 trace_type: TraceType::CapabilityDeposit,
359 intensity: 1.0,
360 tick: self.substrate.current_tick(),
361 payload: vocab_bytes,
362 };
363 self.substrate.deposit_trace(
364 &SubstrateLocation::Spatial(agent_pos),
365 trace,
366 );
367
368 self.substrate.emit_signal(Signal::new(
370 SignalType::Capability,
371 0.8,
372 agent_pos,
373 agent_id,
374 self.substrate.current_tick(),
375 ));
376
377 events.push(ColonyEvent::CapabilityExported {
378 agent_id,
379 terms_count,
380 });
381 }
382 }
383
384 AgentAction::SymbioseWith(target_id) => {
385 let host_idx = idx;
386 let host_id = self.agents[host_idx].id();
387
388 if let Some(target_idx) = self.agents.iter().position(|a| a.id() == target_id) {
390 let target_profile = self.agents[target_idx].profile();
392 let target_vocab = self.agents[target_idx].export_vocabulary()
393 .unwrap_or_default();
394
395 if let Some(SymbiosisEval::Integrate) =
397 self.agents[host_idx].evaluate_symbiosis(&target_profile)
398 {
399 let host_type = self.agents[host_idx].agent_type().to_string();
400 let absorbed_type = self.agents[target_idx].agent_type().to_string();
401
402 self.agents[host_idx].absorb_symbiont(target_profile, target_vocab);
404
405 symbiotic_deaths.push((target_idx, host_id));
407
408 events.push(ColonyEvent::Symbiosis {
409 host: host_id,
410 absorbed: target_id,
411 host_type,
412 absorbed_type,
413 });
414 }
415 }
416 }
417
418 AgentAction::Apoptose => {
419 to_die.push(idx);
420 }
421
422 AgentAction::Idle => {}
423
424 _ => {}
425 }
426 }
427
428 {
432 let _tick = self.substrate.current_tick();
433 let agent_count = self.agents.len();
434
435 for i in 0..agent_count {
436 let agent_id = self.agents[i].id();
437 let agent_pos = self.agents[i].position();
438 let agent_age = self.agents[i].age();
439
440 let vocab_terms = self.agents[i].externalize_vocabulary();
442 let mut reinforcement_count = 0u64;
443 let graph = self.substrate.graph();
444 for term in &vocab_terms {
445 let matching = graph.find_nodes_by_exact_label(term);
446 for nid in matching {
447 if let Some(node) = graph.get_node(nid) {
448 reinforcement_count += node.access_count;
449 }
450 }
451 }
452
453 let useful_outputs_estimate = reinforcement_count.min(100);
454 let trust = if agent_age > 0 {
455 (useful_outputs_estimate as f64 / agent_age as f64).min(1.0)
456 } else {
457 0.0
458 };
459
460 let context = BoundaryContext {
461 reinforcement_count,
462 age: agent_age,
463 trust,
464 };
465
466 self.agents[i].modulate_boundary(&context);
467 let permeability = self.agents[i].permeability();
468
469 if permeability > 0.5 {
471 let mut terms_externalized = 0usize;
473 for term in &vocab_terms {
474 let matching: Vec<NodeId> = self.substrate.graph().find_nodes_by_exact_label(term).to_vec();
475 for nid in &matching {
476 if let Some(node) = self.substrate.graph_mut().get_node_mut(nid) {
477 node.access_count += 1;
478 terms_externalized += 1;
479 }
480 }
481 }
482 if terms_externalized > 0 {
483 events.push(ColonyEvent::Dissolved {
484 agent_id,
485 permeability,
486 terms_externalized,
487 });
488 }
489 }
490
491 if permeability > 0.0 {
493 let all_nodes = self.substrate.graph().all_nodes();
494 let nearby_labels: Vec<String> = all_nodes.iter()
495 .filter_map(|nid| {
496 let node = self.substrate.graph().get_node(nid)?;
497 if node.position.distance_to(&agent_pos) <= 15.0
498 && node.node_type == NodeType::Concept
499 {
500 Some(node.label.clone())
501 } else {
502 None
503 }
504 })
505 .collect();
506 if !nearby_labels.is_empty() {
507 self.agents[i].internalize_vocabulary(&nearby_labels);
508 }
509 }
510
511 let traces = self.substrate.traces_near(
513 &agent_pos,
514 10.0,
515 &TraceType::CapabilityDeposit,
516 );
517 for trace in &traces {
518 if trace.agent_id != agent_id
519 && !trace.payload.is_empty()
520 {
521 let payload = trace.payload.clone();
522 let from_agent = trace.agent_id;
523 let terms_count = serde_json::from_slice::<VocabularyCapability>(&payload)
524 .map(|v| v.terms.len())
525 .unwrap_or(0);
526 if self.agents[i].integrate_vocabulary(&payload) {
527 events.push(ColonyEvent::CapabilityIntegrated {
528 agent_id,
529 from_agent,
530 terms_count,
531 });
532 }
533 }
534 }
535 }
536 }
537
538 for (idx, _absorber_id) in &symbiotic_deaths {
540 if !to_die.contains(idx) {
541 to_die.push(*idx);
542 }
543 }
544
545 to_die.sort();
547 to_die.dedup();
548 let dead_count = to_die.len();
549 for idx in to_die.into_iter().rev() {
550 let agent = self.agents.remove(idx);
551 let mut death_signal = agent.prepare_death_signal();
552
553 if let Some((_, absorber_id)) = symbiotic_deaths.iter().find(|(i, _)| *i == idx) {
555 death_signal.cause = DeathCause::SymbioticAbsorption(*absorber_id);
556 }
557
558 events.push(ColonyEvent::Died {
559 signal: death_signal.clone(),
560 });
561 self.death_signals.push(death_signal);
562 self.total_died += 1;
563 }
564
565 self.substrate
567 .decay_signals(self.signal_decay_rate, self.signal_removal_threshold);
568 self.substrate
569 .decay_traces(self.trace_decay_rate, self.trace_removal_threshold);
570 let current_tick = self.substrate.current_tick();
572 self.substrate.graph_mut().decay_edges_activity(
573 self.edge_decay_rate,
574 self.edge_prune_threshold,
575 current_tick,
576 self.staleness_factor,
577 self.maturation_ticks,
578 );
579 self.substrate
581 .graph_mut()
582 .prune_to_max_degree(self.max_edge_degree);
583
584 for event in &events {
586 match event {
587 ColonyEvent::Presented { id, fragment_count, .. } => {
588 self.fitness_tracker.record_concepts(id, *fragment_count as u64);
589 }
590 ColonyEvent::Wired { id, connection_count } => {
591 self.fitness_tracker.record_edges(id, *connection_count as u64);
592 }
593 _ => {}
594 }
595 }
596 let alive_ids: Vec<AgentId> = self.agents.iter().map(|a| a.id()).collect();
597 self.fitness_tracker.tick_all(&alive_ids);
598
599 self.substrate.advance_tick();
601
602 events.push(ColonyEvent::TickComplete {
603 tick: self.substrate.current_tick(),
604 alive: self.agents.len(),
605 dead_this_tick: dead_count,
606 });
607
608 let current_tick = self.substrate.current_tick();
610 for event in &events {
611 self.event_history.push((current_tick, event.clone()));
612 }
613
614 events
615 }
616
617 pub fn run(&mut self, ticks: u64) -> Vec<Vec<ColonyEvent>> {
619 let mut all_events = Vec::new();
620 for _ in 0..ticks {
621 all_events.push(self.tick());
622 }
623 all_events
624 }
625
626 pub fn stats(&self) -> ColonyStats {
628 let docs = self.substrate.all_documents();
629 let digested = docs.iter().filter(|d| d.digested).count();
630 ColonyStats {
631 tick: self.substrate.current_tick(),
632 agents_alive: self.agents.len(),
633 agents_died: self.total_died,
634 total_spawned: self.total_spawned,
635 graph_nodes: self.substrate.node_count(),
636 graph_edges: self.substrate.edge_count(),
637 total_signals: self.substrate.all_signals().len(),
638 documents_total: docs.len(),
639 documents_digested: digested,
640 }
641 }
642
643 pub fn substrate(&self) -> &SubstrateImpl {
645 &self.substrate
646 }
647
648 pub fn substrate_mut(&mut self) -> &mut SubstrateImpl {
650 &mut self.substrate
651 }
652
653 pub fn alive_count(&self) -> usize {
655 self.agents.len()
656 }
657
658 pub fn death_signals(&self) -> &[DeathSignal] {
660 &self.death_signals
661 }
662
663 pub fn feed_agent(&mut self, agent_idx: usize, input: String) -> Option<DigestionResult> {
665 self.agents
666 .get_mut(agent_idx)
667 .map(|agent| agent.engulf(input))
668 }
669
670 pub fn snapshot(&self) -> ColonySnapshot {
672 let graph = self.substrate.graph();
673
674 let agents: Vec<AgentSnapshot> = self.agents.iter().map(|a| {
675 AgentSnapshot {
676 id: a.id(),
677 agent_type: a.agent_type().to_string(),
678 position: a.position(),
679 age: a.age(),
680 permeability: a.permeability(),
681 vocabulary_size: a.vocabulary_size(),
682 }
683 }).collect();
684
685 let nodes: Vec<NodeSnapshot> = graph.all_nodes().iter().filter_map(|nid| {
686 let n = graph.get_node(nid)?;
687 Some(NodeSnapshot {
688 id: n.id,
689 label: n.label.clone(),
690 node_type: n.node_type.clone(),
691 position: n.position,
692 access_count: n.access_count,
693 })
694 }).collect();
695
696 let edges: Vec<EdgeSnapshot> = graph.all_edges().iter().map(|(from, to, data)| {
697 let from_label = graph.get_node(from).map(|n| n.label.clone()).unwrap_or_default();
698 let to_label = graph.get_node(to).map(|n| n.label.clone()).unwrap_or_default();
699 EdgeSnapshot {
700 from_label,
701 to_label,
702 weight: data.weight,
703 co_activations: data.co_activations,
704 }
705 }).collect();
706
707 ColonySnapshot {
708 tick: self.substrate.current_tick(),
709 agents,
710 nodes,
711 edges,
712 stats: self.stats(),
713 }
714 }
715
716 pub fn event_history(&self) -> &[(Tick, ColonyEvent)] {
718 &self.event_history
719 }
720
721 pub fn agents(&self) -> &[Box<dyn Agent<Input = String, Fragment = String, Presentation = Vec<String>>>] {
723 &self.agents
724 }
725
726 pub fn fitness_tracker(&self) -> &FitnessTracker {
728 &self.fitness_tracker
729 }
730
731 pub fn fitness_tracker_mut(&mut self) -> &mut FitnessTracker {
733 &mut self.fitness_tracker
734 }
735
736 pub fn emit_input_signal(&mut self, position: Position, intensity: f64) {
738 let signal = Signal::new(
739 SignalType::Input,
740 intensity,
741 position,
742 AgentId::new(),
743 self.substrate.current_tick(),
744 );
745 self.substrate.emit_signal(signal);
746 }
747}
748
749impl Default for Colony {
750 fn default() -> Self {
751 Self::new()
752 }
753}
754
755#[cfg(test)]
756mod tests {
757 use super::*;
758 use phago_agents::digester::Digester;
759
760 #[test]
761 fn spawn_and_count_agents() {
762 let mut colony = Colony::new();
763 colony.spawn(Box::new(Digester::new(Position::new(0.0, 0.0))));
764 colony.spawn(Box::new(Digester::new(Position::new(5.0, 5.0))));
765 assert_eq!(colony.alive_count(), 2);
766 assert_eq!(colony.stats().total_spawned, 2);
767 }
768
769 #[test]
770 fn tick_advances_simulation() {
771 let mut colony = Colony::new();
772 colony.spawn(Box::new(Digester::new(Position::new(0.0, 0.0))));
773 colony.tick();
774 assert_eq!(colony.stats().tick, 1);
775 }
776
777 #[test]
778 fn agent_apoptosis_in_colony() {
779 let mut colony = Colony::new();
780 colony.spawn(Box::new(
781 Digester::new(Position::new(0.0, 0.0)).with_max_idle(3),
782 ));
783
784 assert_eq!(colony.alive_count(), 1);
785
786 for _ in 0..5 {
787 colony.tick();
788 }
789
790 assert_eq!(colony.alive_count(), 0);
791 assert_eq!(colony.stats().agents_died, 1);
792 assert_eq!(colony.death_signals().len(), 1);
793 }
794
795 #[test]
796 fn ingest_document_creates_signal() {
797 let mut colony = Colony::new();
798 let pos = Position::new(5.0, 5.0);
799 let doc_id = colony.ingest_document("Test Doc", "cell membrane protein", pos);
800
801 let stats = colony.stats();
802 assert_eq!(stats.documents_total, 1);
803 assert_eq!(stats.documents_digested, 0);
804 assert_eq!(stats.total_signals, 1); let doc = colony.substrate().get_document(&doc_id);
808 assert!(doc.is_some());
809 assert!(!doc.unwrap().digested);
810 }
811
812 #[test]
813 fn agent_finds_and_digests_document() {
814 let mut colony = Colony::new();
815
816 colony.ingest_document(
818 "Biology 101",
819 "The cell membrane controls transport of molecules into and out of the cell. \
820 Proteins embedded in the membrane serve as channels and receptors.",
821 Position::new(0.0, 0.0),
822 );
823
824 colony.spawn(Box::new(
826 Digester::new(Position::new(0.0, 0.0)).with_max_idle(50),
827 ));
828
829 colony.run(10);
835
836 let stats = colony.stats();
837 assert_eq!(stats.documents_digested, 1, "Document should be digested");
838 assert!(stats.graph_nodes > 0, "Should have concept nodes: got {}", stats.graph_nodes);
839 assert!(stats.graph_edges > 0, "Should have edges: got {}", stats.graph_edges);
840 }
841
842 #[test]
843 fn multiple_documents_build_graph() {
844 let mut colony = Colony::new();
845
846 colony.ingest_document(
848 "Cell Biology",
849 "The cell membrane is a lipid bilayer that controls transport. \
850 Proteins in the membrane act as channels and receptors for signaling.",
851 Position::new(0.0, 0.0),
852 );
853 colony.ingest_document(
854 "Molecular Transport",
855 "Active transport across the cell membrane requires ATP energy. \
856 Channel proteins facilitate passive transport of ions and molecules.",
857 Position::new(2.0, 0.0),
858 );
859
860 colony.spawn(Box::new(
862 Digester::new(Position::new(0.0, 0.0)).with_max_idle(50),
863 ));
864 colony.spawn(Box::new(
865 Digester::new(Position::new(2.0, 0.0)).with_max_idle(50),
866 ));
867
868 colony.run(20);
869
870 let stats = colony.stats();
871 assert_eq!(stats.documents_digested, 2, "Both documents should be digested");
872 assert!(stats.graph_nodes >= 5, "Expected at least 5 concept nodes, got {}", stats.graph_nodes);
875 }
876
877 #[test]
878 fn colony_stats_are_accurate() {
879 let mut colony = Colony::new();
880 colony.spawn(Box::new(
881 Digester::new(Position::new(0.0, 0.0)).with_max_idle(2),
882 ));
883 colony.spawn(Box::new(
884 Digester::new(Position::new(5.0, 5.0)).with_max_idle(100),
885 ));
886
887 colony.run(5);
888
889 let stats = colony.stats();
890 assert_eq!(stats.total_spawned, 2);
891 assert_eq!(stats.agents_died, 1);
892 assert_eq!(stats.agents_alive, 1);
893 }
894}