diff --git a/src/Persefone.jl b/src/Persefone.jl
index 2c4e0b66a7fcf4a53b918c2a1f84445798c9baf9..ec269bfd733887472526f65acaad82a1e779f3e5 100644
--- a/src/Persefone.jl
+++ b/src/Persefone.jl
@@ -69,10 +69,14 @@ export
     @killother,
     @reproduce,
     @migrate,
+    @occupy,
+    @isoccupied,
+    @vacate,
     @habitat,
     @landcover,
     @cropname,
     @cropheight,
+    @cropcover,
     @directionto,
     @distanceto,
     @distancetoedge,
diff --git a/src/crop/farmplot.jl b/src/crop/farmplot.jl
index cb349be6731c5213cba98cc5d336f9cca94b85b2..13b7ad308d8e5b26b017348bd5ac0e7b3aed9c34 100644
--- a/src/crop/farmplot.jl
+++ b/src/crop/farmplot.jl
@@ -199,3 +199,16 @@ function cropheight(pos::Tuple{Int64,Int64}, model::SimulationModel)
     ismissing(model.landscape[pos...].fieldid) ? nothing :
               model.farmplots[model.landscape[pos...].fieldid].height
 end
+
+"""
+    cropcover(model, position)
+
+Return the percentage ground cover of the crop at this position, or nothing if there is no crop
+here (utility wrapper).
+"""
+function cropcover(pos::Tuple{Int64,Int64}, model::SimulationModel)
+    #FIXME LAItotal != ground cover?
+    ismissing(model.landscape[pos...].fieldid) ? nothing :
+              model.farmplots[model.landscape[pos...].fieldid].LAItotal
+end
+
diff --git a/src/nature/macros.jl b/src/nature/macros.jl
index 264b1f675264d82c0af96f77ac2f0191db783db8..1fa21f42f0649e70f2bf41a9b156975fcada2258 100644
--- a/src/nature/macros.jl
+++ b/src/nature/macros.jl
@@ -348,7 +348,7 @@ end
 """
     @cropheight
 
-Return the height of the crop at this position, or 0 if there is no crop here.
+Return the height of the crop at this position, or nothing if there is no crop here.
 This is a utility wrapper that can only be used nested within [`@phase`](@ref)
 or [`@habitat`](@ref).
 """
@@ -356,6 +356,17 @@ macro cropheight()
     :(cropheight($(esc(:pos)), $(esc(:model))))
 end
 
+"""
+    @cropcover
+
+Return the percentage ground cover of the crop at this position, or nothing if there is no crop
+here. This is a utility wrapper that can only be used nested within [`@phase`](@ref)
+or [`@habitat`](@ref).
+"""
+macro cropcover()
+    :(cropcover($(esc(:pos)), $(esc(:model))))
+end
+
 """
     @directionto
 
@@ -468,6 +479,7 @@ used nested within [`@phase`](@ref).
 macro walk(args...)
     #XXX add `ifempty` keyword?
     :(walk!($(esc(:self)), $(esc(:model)), $(map(esc, args)...)))
+    #FIXME MethodError: no method matching walk!(::Persefone.Skylark, ::AgricultureModel, ::Tuple{UnitRange{Int64}, UnitRange{Int64}}) -> due to map(esc())?
 end
 #TODO add own walking functions that respect habitat descriptors
 
diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index dd959c3daae80733946d4651e52a308058bc949d..c5642050194db6e533b0a1f3317bee5984135c10 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -31,8 +31,8 @@ At the moment, this implementation is still in development.
 """
 @species Skylark begin
     # species parameters
-    const movementrange = 500m #XXX arbitrary
-    const visionrange = 200m #XXX arbitrary
+    const movementrange::Length = 500m #XXX arbitrary
+    const visionrange::Length = 200m #XXX arbitrary
     
     const eggtime::Int64 = 11 # days from laying to hatching
     const nestlingtime::UnitRange{Int64} = 7:11 # days from hatching to leaving nest
@@ -55,7 +55,7 @@ At the moment, this implementation is still in development.
     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 breedingdelay::Int64 = 18 # days after hatching before starting a new brood #XXX ??
     const nestingend::Int64 = July # last month of nesting
 
     # individual variables
@@ -74,7 +74,7 @@ end
 #TODO respect habitat when moving
 
 """
-As a non-breeding adult, move around with other individuals and check for migration.
+Non-breeding adults move around with other individuals and check for migration.
 """
 @phase Skylark nonbreeding begin
     # flocking behaviour - follow a random neighbour or move randomly
@@ -91,27 +91,45 @@ As a non-breeding adult, move around with other individuals and check for migrat
         @kill(self.migrationmortality, "migration")
         returndate = Date(year(model.date)+1, arrive[1], arrive[2])
         self.sex == male ?
-            @setphase(territory-search) :
-            @setphase(mate-search)
+            @setphase(territorysearch) :
+            @setphase(matesearch)
         @migrate(returndate)
     end
 end
 
-@phase Skylark territory-search begin
-    #TODO move around, searching for a suitable area to establish a territory
-    
+"""
+Males returning from migration move around to look for suitable habitats to establish a territory.
+"""
+@phase Skylark territorysearch begin
+    #TODO
+    #TODO Standorttreue
     # 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
+    elseif month(model.date) > self.nestingend
         @setphase(nonbreeding)
     else
         @walk("random", self.movementrange)
+        #FIXME MethodError: no method matching walk!(::Persefone.Skylark, ::AgricultureModel, ::Tuple{UnitRange{Int64}, UnitRange{Int64}})
     end
 end
 
-@phase Skylark mate-search begin
+"""
+Once a male has found a territory, he remains in it until the breeding season is over,
+adjusting it to new conditions when and as necessary.
+"""
+@phase Skylark occupation begin
+    #move to a random location in the territory
+    @move(@rand(self.territory))
+    #TODO adjust territory as needed
+end
+
+"""
+Females returning from migration move around to look for a suitable partner with a territory.
+"""
+@phase Skylark matesearch begin
     #TODO move around, looking for a male with an established territory
+    #TODO teilweise Partnertreue
 
     # look for a mate among the neighbouring birds, or move randomly
     for n in @neighbours(self.visionrange)
@@ -124,10 +142,7 @@ end
     end
     #@debug("$(animalid(self)) didn't find a mate.")
     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)
+        @setphase(nesting)
     elseif month(model.date) > self.nestingend # stop trying to find a mate if it's too late
         @setphase(nonbreeding)
     else
@@ -135,25 +150,60 @@ end
     end
 end
 
-@phase Skylark occupation begin
-    #move to a random location in the territory
-    @move(@rand(self.territory))
-    #TODO adjust territory as needed
+"""
+Females that have found a partner build a nest and lay eggs in a suitable location.
+"""
+@phase Skylark nesting begin
+    if model.date < Date(year(model.date), self.nestingbegin...)
+        # wait for nesting to begin, moving around in the territory
+        @move(@rand(@animal(self.mate).territory))
+    elseif isempty(nest)
+        # choose site, build nest & lay eggs
+        for pos in @shuffle!(deepcopy(@animal(self.mate).territory))
+            #TODO is this condition correct? -> needs validation!
+            if (@landcover() == grass || @landcover() == soil ||
+                (@landcover() == agriculture &&
+                 (self.nestingheight[1] <= @cropheight() <= self.nestingheight[2]) &&
+                 (self.nestingcover[1] <= @cropcover() <= self.nestingcover[2])))
+                @move(pos)
+                self.nest = pos
+                self.clutch = @rand(self.eggsperclutch)
+                timer = self.nestbuildingtime + self.clutch # time to build + 1 day per egg laid
+                if month(model.date) == self.nestingbegin[1] 
+                    # the first nest takes twice as long to build
+                    #XXX this may affect the first two nests
+                    timer += self.nestbuildingtime
+                end
+                break
+            end
+        end
+        isempty(nest) && @warn("$(animalid(self)) didn't find a nesting location.")
+    elseif timer == 0
+        @debug("$(animalid(self)) has laid $(self.clutch) eggs.")
+        @setphase(breeding)
+    else
+        self.timer -= 1
+    end
+    # tillage and harvest destroys the nest
+    @respond(tillage, nest = ())
+    @respond(harvesting, nest = ())
 end
 
+"""
+Females that have laid eggs take care of their chicks, restarting the nesting process once the
+chicks are independent or in case of brood loss.
+"""
 @phase Skylark breeding begin
-    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
+    #TODO wait for eggs to hatch & chicks to mature, checking for mortality
+    # restart breeding cycle if there is time
+    if clutch == 0 && month(model.date) <= self.nestingend
+        @setphase(nesting) #TODO breeding delay?
     elseif month(model.date) > self.nestingend
-        #TODO restart cycle, if time
         @setphase(nonbreeding)
     end
 end
 
 
-
 ## SUPPORTING FUNCTIONS
 
 """
@@ -215,8 +265,8 @@ should currently be on migration. Also sets other individual-specific variables.
         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)
+            @setphase(territorysearch) :
+            @setphase(matesearch)
         @migrate(returndate)
     end
 end