From 61505db66bccc1f685a6292c4cd59aca43d0c701 Mon Sep 17 00:00:00 2001
From: Daniel Vedder <daniel.vedder@idiv.de>
Date: Tue, 6 Aug 2024 10:13:43 +0200
Subject: [PATCH] Non-breeding skylarks only look once for a neighbour to
 follow

---
 src/nature/species/skylark.jl | 53 +++++++++++++++++++----------------
 1 file changed, 29 insertions(+), 24 deletions(-)

diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index dfd8f2f..cced022 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -36,27 +36,25 @@
       Ecology and Evolution, 12(1), e8461. https://doi.org/10.1002/ece3.8461
 """
 @species Skylark begin
-    # species parameters
-    const movementrange::Length = 500m #XXX arbitrary
-    const visionrange::Length = 200m #XXX arbitrary
-    
+    ## SPECIES PARAMETERS
+
+    # juveniles
     const eggtime::Int64 = 11 # days from laying to hatching
     const nestlingtime::Int64 = 9 # days from hatching to leaving nest
     const fledglingtime::Int64 = 21 # days from leaving the nest to independence
-
     #XXX predation mortality should be habitat-dependent
     const eggpredationmortality::Float64 = 0.03 # per-day egg mortality from predation
     const nestlingpredationmortality::Float64 = 0.03 # per-day nestling mortality from predation
     const fledglingpredationmortality::Float64 = 0.01 # per-day fledgling mortality from predation
     const firstyearmortality::Float64 = 0.38 # total mortality in the first year after independence
 
-    # skylarks migrate from autumn to early spring, with the females leaving earlier
-    # and arriving later than the males
+    # migration
     const migrationdeparture::Tuple{AnnualDate,AnnualDate} = ((September, 15), (November, 1))
     const migrationarrival::Tuple{AnnualDate,AnnualDate} = ((February, 15), (March, 1))
     const migrationdelayfemales::Day = Day(15)
     const migrationmortality::Float64 = 0.33 # chance of dying during the winter
 
+    # habitat requirements
     const minimumterritory = 5000m² # size of territory under ideal conditions
     const mindistancetoedge = 60m # minimum distance of habitat to vertical structures
     const maxforageheight = 50cm # maximum preferred vegetation height for foraging
@@ -64,20 +62,26 @@
     const nestingheight = (15cm, 25cm) # min and max preferred vegetation height for nesting
     const nestingcover = (0.2, 0.5) # min and max preferred vegetation cover for nesting
 
+    # breeding
     const matefaithfulness = 0.5 # chance of a female retaining her previous partner
     const nestingbegin::Tuple{AnnualDate,AnnualDate} = ((April, 10), (April, 20)) # begin nesting in the middle of April
     const nestbuildingtime::UnitRange{Int64} = 4:5 # 4-5 days needed to build a nest (doubled for first nest)
     const eggsperclutch::UnitRange{Int64} = 2:5 # eggs laid per clutch
     const nestingend::AnnualDate = (August, 15) # end of the nesting period
-    
-    # individual variables
+
+    # mating / non-breeding
+    const movementrange::Length = 500m #XXX arbitrary
+    const visionrange::Length = 200m #XXX arbitrary
+    const flockingdistance::Length = 30m #XXX arbitrary
+
+    ## INDIVIDUAL VARIABLES
     timer::Int64 = 0 # a counter that can be used for different purposes
     firstnest::AnnualDate = (April, 15) # is redefined by each individual in @create(Skylark)
     migrationdates::Tuple = () # is defined by each individual in @create(Skylark)
     mate::Int64 = -1 # the agent ID of the mate (-1 if none)
     nest::Tuple = () # coordinates of current nest
     clutch::Int64 = 0 # number and life stage of offspring in current clutch
-    #following::Int64 = -1 # ID of the individual being followed in the non-breeding phase
+    following::Int64 = -1 # ID of the individual being followed in the non-breeding phase
 end
 
 
@@ -89,26 +93,27 @@ end
 Non-breeding adults move around with other individuals and check for migration.
 """
 @phase Skylark nonbreeding begin
-    #FIXME searching for neighbours is *the* performance bottleneck!!
-    # Two options:
-    # (a) ignore flocking, simply move randomly (scientifically OK, doesn't look as good)
-    # (b) select a random neighbour once, then keep following that individual
-
-    # flocking behaviour - follow a random neighbour or move randomly
-    neighbours = ()
-    #neighbours = @neighbours(self.visionrange) #XXX check for the closest neighbour(s)?
-    isempty(neighbours) ?
-        @walk("random", self.movementrange) :
-        @follow(@rand(neighbours), 30m) #XXX magic number
     # check if the bird migrates
-    arrive, depart = self.migrationdates
-    if model.date >= depart
+    if model.date >= self.migrationdates[2]
         @kill(self.migrationmortality, "migration")
         self.sex == male ?
             @setphase(territorysearch) :
             @setphase(matesearch)
-        @migrate(arrive)
+        @migrate(self.migrationdates[1])
+        return
+    end
+    # flocking behaviour - follow a neighbour or move randomly
+    self.following > 0 && isnothing(@animal(self.following)) && (self.following = -1)
+    if self.following == -1
+        neighbours = @neighbours(self.visionrange)
+        if isempty(neighbours)
+            @walk("random", self.movementrange)
+            return
+        else
+            self.following = @rand(neighbours).id
+        end
     end
+    @follow(@animal(self.following), self.flockingdistance)
 end
 
 """
-- 
GitLab