diff --git a/src/nature/individuals.jl b/src/nature/individuals.jl
index 863bb8f82ac0d9eaf0e39422b3eb9e60957f3a4e..f1f248890de3ad7f27e9bffafd0593b3c3cd08f4 100644
--- a/src/nature/individuals.jl
+++ b/src/nature/individuals.jl
@@ -162,7 +162,7 @@ function walk!(animal::Animal, model::SimulationModel, direction::String, distan
     elseif direction == "northwest"
         shift = (-steps,-steps)
     elseif direction == "random"
-        shift = Tuple(@rand([-steps,0,steps], 2))
+        shift = Tuple(@rand([-steps:steps], 2))
     else
         @error "Invalid direction in @walk: "*direction
     end
diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index 6400bbcd0c4b2a549eef4d11f8ddf491544759a3..dd959c3daae80733946d4651e52a308058bc949d 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -3,15 +3,6 @@
 ### This file holds the code for the Eurasian Skylark (Alauda arvensis).
 ###
 
-#XXX global variable/function
-skylarkhabitat = @habitat((@landcover() == grass ||
-                           (@landcover() == agriculture && @cropname() != "maize")) &&
-                          @distancetoedge() > 50m)
-#XXX this ought to check for distance to forest and builtup,
-# but that's very expensive (see below)
-# @distanceto(forest) > 5 && # at least 50m from forest edges
-# @distanceto(builtup) > 5) # and from anthropogenic structures
-
 """
     Skylark
 
@@ -19,108 +10,78 @@ skylarkhabitat = @habitat((@landcover() == grass ||
 At the moment, this implementation is still in development.
 
 **Sources:**
-    - Bauer, H.-G., Bezzel, E., & Fiedler, W. (Eds.). (2012).
-      Das Kompendium der Vögel Mitteleuropas: Ein umfassendes
-      Handbuch zu Biologie, Gefährdung und Schutz (Einbändige
-      Sonderausg. der 2., vollständig überarb. und erw. Aufl. 2005).
-      AULA-Verlag
-    - Glutz von Blotzheim, Urs N. (Ed.). (1985). Handbuch der
-      Vögel Mitteleuropas. Bd. 10. Passeriformes (Teil 1)
-      1. Alaudidae - Hirundidae. AULA-Verlag, Wiesbaden.
+    - Bauer, H.-G., Bezzel, E., & Fiedler, W. (Eds.). (2012). Das Kompendium
+      der Vögel Mitteleuropas: Ein umfassendes Handbuch zu Biologie, Gefährdung
+      und Schutz (Einbändige Sonderausg. der 2., vollständig überarb. und erw.
+      Aufl. 2005). AULA-Verlag
+    - Delius, J. D. (1965). A Population Study of Skylarks Alauda Arvensis.
+      Ibis, 107(4), 466–492. https://doi.org/10.1111/j.1474-919X.1965.tb07332.x
+    - Donald et al. (2002). Survival rates, causes of failure and productivity
+      of Skylark Alauda arvensis nests on lowland farmland. Ibis, 144(4), 652–664.
+      https://doi.org/10.1046/j.1474-919X.2002.00101.x
+    - Glutz von Blotzheim, Urs N. (Ed.). (1985). Handbuch der Vögel Mitteleuropas.
+      Bd. 10. Passeriformes (Teil 1) 1. Alaudidae - Hirundidae. AULA-Verlag, Wiesbaden.
       ISBN 3-89104-019-9
+    - Jenny, M. (1990). Territorialität und Brutbiologie der Feldlerche Alauda
+      arvensis in einer intensiv genutzten Agrarlandschaft. Journal für Ornithologie,
+      131(3), 241–265. https://doi.org/10.1007/BF01640998
+    - Püttmanns et al. (2022). Habitat use and foraging parameters of breeding Skylarks
+      indicate no seasonal decrease in food availability in heterogeneous farmland.
+      Ecology and Evolution, 12(1), e8461. https://doi.org/10.1002/ece3.8461
 """
 @species Skylark begin
     # species parameters
+    const movementrange = 500m #XXX arbitrary
+    const visionrange = 200m #XXX arbitrary
+    
     const eggtime::Int64 = 11 # days from laying to hatching
     const nestlingtime::UnitRange{Int64} = 7:11 # days from hatching to leaving nest
     const fledglingtime::UnitRange{Int64} = 25:30 # days from hatching to independence
 
     const eggpredationmortality::Float64 = 0.03 # per-day egg mortality from predation
-    const nestharvestmortality::Float64 = 0.9 # egg/nestling mortality after a harvest event (XXX guess)
+    const nestharvestmortality::Float64 = 1.0 # egg/nestling mortality after a harvest event
     const nestlingpredationmortality::Float64 = 0.03 # per-day nestling mortality from predation
     const fledglingharvestmortality::Float64 = 0.5 # fledgling mortality after harvest
     const fledglingpredationmortality::Float64 = 0.01 # per-day fledgling mortality from predation
     const firstyearmortality::Float64 = 0.38 # total mortality in the first year after independence
     const migrationmortality::Float64 = 0.33 # chance of dying during the winter
 
+    const minimumterritory = 5000m² # size of territory under ideal conditions
+    const maxforageheight = 50cm # maximum preferred vegetation height for foraging
+    const maxforagecover = 0.7 # maximum preferred vegetation cover for foraging
+    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
+    
     const nestingbegin::Tuple{Int64,Int64} = (April, 10) # 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 breedingdelay::Int64 = 18 # days after hatching before starting a new brood
     const nestingend::Int64 = July # last month of nesting
-    
-    const habitats::Function = skylarkhabitat
 
     # individual variables
-    daystonextphase::Int64 = 0 # days remaining until fledging or maturity
+    #FIXME check what needs to be rewritten
+    timer::Int64 = 0 # a count-down timer that can be used for different purposes
     migrationdates::Tuple = () # is defined by each individual in @create(Skylark)
-    territory::Vector = [] # pixels that this skylark claims as its territory
     mate::Int64 = -1 # the agent ID of the mate (-1 if none)
     nest::Tuple = () # coordinates of current nest
     nestcompletion::Int64 = 0 # days left until the nest is built
-    clutch::Vector{Int64} = Vector{Int64}() # IDs of offspring in current clutch
+    clutch::Int64 = 0 # number of offspring in current clutch
 end
 
-"""
-As an egg, simply check for mortality and hatching.
-"""
-@phase Skylark egg begin
-    @kill(self.eggpredationmortality, "predation")
-    @respond(harvesting, @kill(self.nestharvestmortality, "harvest"))
-    if self.age == self.eggtime
-        self.daystonextphase = @rand(self.nestlingtime)
-        @setphase(nestling)
-    end
-end
 
-"""
-As a nestling, simply check for mortality and fledging.
-"""
-@phase Skylark nestling begin
-    #TODO add feeding & growth
-    @kill(self.nestlingpredationmortality, "predation")
-    @respond(harvesting, @kill(self.nestharvestmortality, "harvest"))
-    # if self.age == self.nestlingtime+self.eggtime
-    #     @setphase(fledgling)
-    # end
-    if self.daystonextphase == 0
-        self.daystonextphase = @rand(self.fledglingtime)
-        @setphase(fledgling)
-    else
-        self.daystonextphase -= 1
-    end
-end
+## LIFE PHASES
 
-"""
-As a fledgling, move around a little, but mainly wait for maturity and
-check mortality.
-"""
-@phase Skylark fledgling begin
-    #TODO add feeding & growth
-    @kill(self.fledglingpredationmortality, "predation")
-    @walk("random", 10m) #TODO add movement following the parents
-    # if self.age == self.fledglingtime+self.eggtime
-    #     @kill(self.firstyearmortality, "first year mortality") #XXX mechanistic?
-    #     @setphase(nonbreeding)
-    # end
-    if self.daystonextphase == 0
-        @kill(self.firstyearmortality, "first year mortality") #XXX mechanistic?
-        @setphase(nonbreeding)
-    else
-        self.daystonextphase -= 1
-    end
-end
+#TODO respect habitat when moving
 
 """
 As a non-breeding adult, move around with other individuals and check for migration.
 """
 @phase Skylark nonbreeding begin
     # flocking behaviour - follow a random neighbour or move randomly
-    #TODO add feeding and mortality, respect habitat when moving
-    neighbours = @neighbours(100m) #XXX magic number
+    neighbours = @neighbours(self.visionrange) #XXX check for the closest neighbour(s)?
     isempty(neighbours) ?
-        @walk("random", 50m) :
-        @follow(@rand(neighbours), 20m)
+        @walk("random", self.movementrange) :
+        @follow(@rand(neighbours), 30m) #XXX magic number
     # check if the bird migrates
     leave, arrive = self.migrationdates
     month, day = monthday(model.date)
@@ -129,111 +90,72 @@ As a non-breeding adult, move around with other individuals and check for migrat
     if migrate
         @kill(self.migrationmortality, "migration")
         returndate = Date(year(model.date)+1, arrive[1], arrive[2])
-        @setphase(mating)
+        self.sex == male ?
+            @setphase(territory-search) :
+            @setphase(mate-search)
         @migrate(returndate)
     end
 end
 
-"""
-Move around until a mate is found.
-"""
-@phase Skylark mating begin
-    #TODO mortality and feeding
-    #TODO territoriality
-    # if we've found a mate, wait for nesting begin and then go to the next phase
-    if self.mate != -1
-        if !@isalive(self.mate)
-            self.mate = -1
-            return
-        end
-        mon, day = monthday(model.date)
-        nest = ((mon == self.nestingbegin[1] && day >= self.nestingbegin[2]
-                 && @chance(0.05)) || (mon > self.nestingbegin[1])) #XXX why the chance?
-        nest && @setphase(nestbuilding)
-        return
+@phase Skylark territory-search begin
+    #TODO move around, searching for a suitable area to establish a territory
+    
+    # If we've found a territory, or the breeding season is over, move to the next phase
+    if !isempty(self.territory)
+        @setphase(occupation)
+    elseif month(model.date) > nestingend
+        @setphase(nonbreeding)
+    else
+        @walk("random", self.movementrange)
     end
+end
+
+@phase Skylark mate-search begin
+    #TODO move around, looking for a male with an established territory
+
     # look for a mate among the neighbouring birds, or move randomly
-    for n in @neighbours(500m) #XXX magic number
-        if n.sex != self.sex && n.phase == mating && n.mate == -1
+    for n in @neighbours(self.visionrange)
+        if n.sex == male && n.mate == -1 && n.phase == occupation
             self.mate = n.id
             n.mate = self.id
             @debug "$(animalid(self)) and $(animalid(n)) have mated."
-            return
+            break
         end
     end
     #@debug("$(animalid(self)) didn't find a mate.")
-    if month(model.date) > self.nestingend # stop trying to find a mate if it's too late
+    if self.mate != -1
+        mon, day = monthday(model.date)
+        nest = ((mon == self.nestingbegin[1] && day >= self.nestingbegin[2]
+                 && @chance(0.1)) || (mon > self.nestingbegin[1])) #XXX magic number
+        nest && @setphase(breeding)
+    elseif month(model.date) > self.nestingend # stop trying to find a mate if it's too late
         @setphase(nonbreeding)
     else
-        @walk("random", 100m) #XXX magic number
+        @walk("random", self.movementrange)
     end
 end
 
-"""
-Females select a location and build a nest. Males do nothing. (Sound familiar?)
-"""
-@phase Skylark nestbuilding begin
-    #TODO mortality and feeding
-    if !@isalive(self.mate)
-        self.mate = -1
-        @setphase(nonbreeding)
-        return
-    end
-    if self.sex == female
-        if isempty(self.nest)
-            # try to find a nest in the neighbourhood, or move on
-            nestlocation = @randompixel(100m, self.habitats) #XXX magic number
-            if isnothing(nestlocation)
-                @walk("random", 200m) #XXX magic number
-            else
-                # if we've found a location, start the clock on the building time
-                # (building time doubles for the first nest of the year)
-                self.nest = nestlocation
-                self.nestcompletion = @rand(self.nestbuildingtime)
-                month(model.date) == self.nestingbegin[1] && (self.nestcompletion *= 2)
-                @debug "$(animalid(self)) is building a nest."
-            end
-        else
-            # wait while nest is being built, then lay eggs and go to next phase
-            if self.nestcompletion > 0
-                self.nestcompletion -= 1
-            else
-                #XXX more accurately, a female lays one egg per day, not all at once
-                @reproduce(@rand(self.eggsperclutch),self.mate)
-                @setphase(breeding)
-            end
-        end
-    else
-        # males stay near the female
-        mate = @animal(self.mate)
-        @follow(mate, 50m)
-        mate.phase == breeding && @setphase(breeding)
-    end
+@phase Skylark occupation begin
+    #move to a random location in the territory
+    @move(@rand(self.territory))
+    #TODO adjust territory as needed
 end
 
-"""
-Do lots of foraging (not yet implemented).
-"""
 @phase Skylark breeding begin
-    #TODO forage (move inside the territory)
-    for offspring in self.clutch
-        # check if offspring are still alive and juvenile, else remove from clutch
-        if !@isalive(offspring) || @animal(offspring).phase == nonbreeding
-            filter!(x -> x != offspring, self.clutch)
-        end
-    end
-    # if all young have fledged, move to nonbreeding (if it's July) or breed again
-    if isempty(self.clutch)
-        self.nest = ()
-        if month(model.date) >= self.nestingend
-            self.territory = []
-            @setphase(nonbreeding)
-        else
-            @setphase(nestbuilding)
-        end
+    if isempty(nest)
+        #TODO choose site, build nest & lay eggs
+    elseif clutch > 0
+        #TODO wait for eggs to hatch & chicks to mature, checking for mortality
+    elseif month(model.date) > self.nestingend
+        #TODO restart cycle, if time
+        @setphase(nonbreeding)
     end
 end
 
+
+
+## SUPPORTING FUNCTIONS
+
 """
     migrationdates(skylark, model)
 
@@ -251,14 +173,40 @@ function migrationdates(skylark::Animal, model::SimulationModel)
     (leave, arrive)
 end
 
+"""
+    foragequality(maxcover, maxheight, position, model)
+
+Calculate the relative quality of the habitat at this position for foraging.
+(Approximated from Püttmanns et al., 2021; Jeromin, 2002; Jenny, 1990b.)
+"""
+function foragequality(maxcover::Float64, maxheight::Float64, position::Tuple{Int64,Int64},
+                       model::SimulationModel)
+    #TODO this is a key function that needs to be validated thoroughly
+    px = model.landscape[position...]
+    !(px.landcover in (grass, soil, agriculture)) && return 0.0
+    quality = 1.0
+    if !is.missing(px.fieldid)
+        f = model.farmplots[px.fieldid]
+        groundcoverfactor = x -> bounds(-1/maxcover + 1.0, max=1.0)
+        plantheightfactor = x -> bounds(-1/maxheight + 1.0, max=1.0)
+        #FIXME need percentage cover, not LAI
+        quality = bounds(groundcoverfactor(f.LAItotal) + plantheightfactor(f.height), max=1.0)
+    else
+        @warn "No field assigned to location $position" px
+    end
+    return quality
+end
+
+
+## INITIALISATION
+
 """
 Initialise a skylark individual. Selects migration dates and checks if the bird
 should currently be on migration. Also sets other individual-specific variables.
 """
 @create Skylark begin
     @debug "Added $(animalid(self)) at $(self.pos)"
-    # calculate migration dates for this individual
-    self.migrationdates = migrationdates(self, model)
+    self.migrationdates = migrationdates(self, model) #XXX should be dynamic
     leave, arrive = self.migrationdates
     mon, day = monthday(model.date)
     migrate = (((mon < arrive[1]) || (mon == arrive[1] && day < arrive[2])) ||
@@ -266,15 +214,19 @@ should currently be on migration. Also sets other individual-specific variables.
     if migrate
         returndate = Date(year(model.date), arrive[1], arrive[2])
         model.date != @param(core.startdate) && (returndate += Year(1))
+        self.sex == male ?
+            @setphase(territory-search) :
+            @setphase(mate-search)
         @migrate(returndate)
     end
-    #TODO other stuff?
 end
 
 @populate Skylark begin
-    habitat = skylarkhabitat
-    initphase = mating
-    birthphase = egg
+    # initialise on open land, at least 60m from vertical structures
+    habitat = @habitat((@landcover() == grass || @landcover() == agriculture) &&
+                       @distanceto(forest) >= 60m && @distanceto(builtup) >=60m)
+    initphase = nonbreeding
+    birthphase = nonbreeding
     indarea = 3ha
     pairs = true
 end