diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index 5b0a2e82cab5360c61a1737f6c77c50b38c11322..50268688c19e2deee6d777b459d6cd514f3d697f 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -11,54 +11,195 @@ 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
+      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.
+      ISBN 3-89104-019-9
 """
 @species Skylark begin
     
-    eggmaturationtime = 11
-    eggharvestmortality = 0.9 #???
-    eggpredationmortality = 0.1 #???
+    eggtime = 11 # 11 days from laying to hatching
+    eggpredationmortality = 0.03 # per-day egg mortality from predation
+    nestharvestmortality = 0.9 # egg mortality after a harvest event (XXX guess)
 
-    migrationdates = ()
+    nestlingtime = 7:11 # 7-11 days from hatching to leaving nest
+    nestlingpredationmortality = 0.03 # per-day nestling mortality from predation
 
-    habitats = @habitat((@landcover() == grass || 
+    fledglingtime = 25:30 # 25-30 days from hatching to independence
+    fledglingharvestmortality = 0.5 # fledgling mortality after harvest
+    fledglingpredationmortality = 0.01 # per-day fledgling mortality from predation
+    firstyearmortality = 0.38 # total mortality in the first year after independence
+    
+    migrationdates = () # is defined by each individual in `initskylark()`
+    migrationmortality = 0.33 # chance of dying during the winter
+
+    mate = -1 # the agent ID of the mate (-1 if none)
+    nest = () # coordinates of current nest
+    nestingbegin = (4, 10) # begin nesting in the middle of April
+    nestbuildingtime = 4:5 # 4-5 days needed to build a nest (doubled for first nest)
+    nestcompletion = 0 # days left until the nest is built
+    eggsperclutch = 2:5 # 2-5 eggs laid per clutch
+    clutch = [] # IDs of offspring in current clutch
+    breedingdelay = 18 # wait 18 days after hatching to start a new brood
+    
+    habitats = @habitat((@landcover() == grass ||
+                         # settle on grass or arable land (but not maize)
                          (@landcover() == agriculture && @cropname() != "maize")) &&
-                        @distanceto(forest) > 5)
+                        @distancetoedge() > 5) # at least 50m from other habitats
+                        #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
     
-    @initialise(habitats, pairs=true, initfunction=initskylark)
+    @initialise(habitats, popdensity=300, pairs=true, initfunction=initskylark)
     
+    """
+    As an egg, simply check for mortality and hatching.
+    """
     @phase egg begin
         @kill(@trait(eggpredationmortality), "predation")
-        @respond harvest @kill(@trait(eggharvestmortality), "harvest")
-
-        #TODO dies if parents die (due to starvation?)
+        @respond(harvesting, @kill(@trait(nestharvestmortality), "harvest"))
 
-        if @trait(age) == 14
+        if @trait(age) == @trait(eggtime)
             @setphase(nestling)
         end
     end
 
+    """
+    As a nestling, simply check for mortality and fledging.
+    """
     @phase nestling begin
-        #TODO
+        #TODO add feeding & growth
+        @kill(@trait(nestlingpredationmortality), "predation")
+        @respond(harvesting, @kill(@trait(nestharvestmortality), "harvest"))
+        if @trait(age) == @trait(nestlingtime)+@trait(eggtime)
+            @setphase(fledgling)
+        end
     end
 
-    @phase breeding begin
-        #TODO
+    """
+    As a fledgling, move around a little, but mainly wait for maturity and
+    check mortality.
+    """
+    @phase fledgling begin
+        #TODO add feeding & growth
+        @kill(@trait(fledglingpredationmortality), "predation")
+        @randomwalk(1) #TODO add movement following the parents
+        if @trait(age) == @trait(fledglingtime)+@trait(eggtime)
+            @kill(@trait(firstyearmortality), "first year mortality") #XXX mechanistic?
+            @setphase(nonbreeding)
+        end
     end
 
+    """
+    As a non-breeding adult, move around with other individuals and check for migration.
+    """
     @phase nonbreeding begin
-        #TODO
+        # flocking behaviour - follow a random neighbour or move randomly
+        #TODO add feeding and mortality, respect habitat when moving
+        neighbours = map(a->a.id, @neighbours(10))
+        #isempty(neighbours) ? @randomwalk(5) : @follow(@rand(neighbours), 2)
+        if isempty(neighbours)
+            @randomwalk(5)
+        else
+            @follow(model[@rand(neighbours)], 2)
+        end
+        # check if the bird migrates
+        leave, arrive = animal.migrationdates
+        m, d = monthday(model.date)
+        migrate = (((m < arrive[1]) || (m == arrive[1] && d < arrive[2])) ||
+                   ((m > leave[1]) || (m == leave[1] && d >= leave[2])))
+        if migrate
+            @kill(@trait(migrationmortality), "migration")
+            returndate = Date(year(model.date), arrive[1], arrive[2])
+            model.date != @param(core.startdate) && (returndate += Year(1))
+            @setphase(mating)
+            @migrate(returndate)
+        end
     end
 
-    @phase migration begin
-        #TODO
+    """
+    Move around until a mate is found.
+    """
+    @phase mating begin
+        # if we've found a mate, wait for nesting begin and then go to the next phase
+        if @trait(mate) != -1
+            if !@isalive(@trait(mate))
+                @trait(mate) = -1
+                return
+            end
+            m, d = monthday(model.date)
+            nest = ((m == @trait(nestingbegin)[1] && d > @trait(nestingbegin)[2]
+                     && @chance(0.05)) || (m > @trait(nestingbegin)[1]))
+            nest && @setphase(nestbuilding)
+            return
+        end
+        # look for a mate among the neighbouring birds, or move randomly
+        for n in @neighbours(50)
+            if n.sex != @trait(sex) && n.mate == -1
+                @trait(mate) = n.id
+                n.mate = @trait(id)
+                @debug "$(animalid(@trait(id))) and $(animalid(n.id)) have mated."
+                return
+            end
+        end
+        @randomwalk(10)
     end
 
-    @phase mating begin
-        #TODO
+    """
+    Females select a location and build a nest. Males do nothing. (Sound familiar?)
+    """
+    @phase nestbuilding begin
+        if @trait(sex) == female
+            if isempty(@trait(nest))
+                # try to find a nest in the neighbourhood, or move on
+                nest = @randompixel(10, @trait(habitats))
+                if isnothing(nest)
+                    nest = ()
+                    @randomwalk(20)
+                else
+                    # if we've found a location, start the clock on the building time
+                    # (building time doubles for the first nest of the year)
+                    @trait(nestcompletion) = @rand(nestbuildingtime)
+                    month(model.date) == 4 && (@trait(nestcompletion) *= 2)
+                end
+            else
+                # wait while nest is being built, then lay eggs and go to next phase
+                if @trait(nestcompletion) > 0
+                    @trait(nestcompletion) -= 1
+                else
+                    @trait(clutch) = @reproduce(@trait(mate), @rand(eggsperclutch))
+                    @animal(@trait(mate)).clutch = @trait(clutch)
+                    @setphase(breeding)
+                end
+            end
+        else
+            # males stay near the female
+            @follow(model[@trait(mate)], 5)
+            @animal(@trait(mate)).phase == "breeding" && @setphase(breeding)
+        end
+    end
+
+    """
+    Do lots of foraging (not yet implemented).
+    """
+    @phase breeding begin
+        #TODO forage (move random)
+        for offspring in @trait(clutch)
+            # check if offspring are still alive and juvenile, else remove from clutch
+            if !@isalive(offspring) || @animal(offspring).phase == "nonbreeding"
+                deleteat!(@trait(clutch), findfirst(x->x==offspring, @trait(clutch)))
+            end
+        end
+        # if all young have fledged, move to nonbreeding (if it's July) or breed again
+        if isempty(@trait(clutch))
+            @trait(nest) = ()
+            month(model.date) >= 7 ? @setphase(nonbreeding) : @setphase(nestbuilding)
+        end
     end
 end
 
@@ -66,13 +207,14 @@ end
     initskylark(skylark, model)
 
 Initialise a skylark individual. Selects migration dates and checks if the
-bird should currently be on migration.
+bird should currently be on migration. Also sets other individual-specific
+variables. Called at model initialisation and when an egg is laid.
 """
 function initskylark(animal::Animal, model::AgentBasedModel)
     @debug "Added $(animalid(animal)) at $(animal.pos)"
+    # calculate migration dates for this individual
     animal.migrationdates = migrationdates(animal, model)
-    leave = animal.migrationdates[1]
-    arrive = animal.migrationdates[2]
+    leave, arrive = animal.migrationdates
     m, d = monthday(model.date)
     migrate = (((m < arrive[1]) || (m == arrive[1] && d < arrive[2])) ||
                ((m > leave[1]) || (m == leave[1] && d >= leave[2])))
@@ -81,6 +223,9 @@ function initskylark(animal::Animal, model::AgentBasedModel)
         model.date != @param(core.startdate) && (returndate += Year(1))
         @migrate(returndate)
     end
+    # set individual life-history parameters that are defined as ranges for the species
+    @trait(nestlingtime) = @rand(@trait(nestlingtime))
+    @trait(fledglingtime) = @rand(@trait(fledglingtime))
     #TODO other stuff?
 end
 
@@ -91,6 +236,8 @@ Select the dates on which this skylark will leave for / return from its migratio
 based on observed migration patterns.
 """
 function migrationdates(skylark::Animal, model::AgentBasedModel)
+    #TODO this ought to be temperature-dependent and dynamic
+    #XXX magic numbers!
     minleave = skylark.sex == female ? (9, 15) : (10, 1)
     minarrive = skylark.sex == male ? (2, 15) : (3, 1)
     deltaleave = @rand(0:45) #XXX ought to be normally distributed
@@ -99,3 +246,4 @@ function migrationdates(skylark::Animal, model::AgentBasedModel)
     arrive = monthday(Date(2001, minarrive[1], minarrive[2]) + Day(deltaarrive))
     (leave, arrive)
 end
+