diff --git a/src/Persefone.jl b/src/Persefone.jl
index 206ab2ac860ad2ce03eeaa6b4eaa2b0777b57973..e02955ff3b27961baa22986d16a3c25f11287eac 100644
--- a/src/Persefone.jl
+++ b/src/Persefone.jl
@@ -102,6 +102,7 @@ include("farm/farm.jl")
 include("nature/insects.jl")
 include("nature/energy.jl")
 include("nature/nature.jl")
+include("nature/macros.jl")
 include("nature/populations.jl")
 include("nature/ecologicaldata.jl")
 #TODO loop over files in nature/species directory
diff --git a/src/core/input.jl b/src/core/input.jl
index e57b98168148502a7dfd1072dd6914dc3d3097bc..6b8987a6425fe37a904a424a138be4ba1d9cf600 100644
--- a/src/core/input.jl
+++ b/src/core/input.jl
@@ -137,6 +137,17 @@ macro shuffle!(collection)
     :($(esc(:shuffle!))($(esc(:model)).rng, $(esc(collection))))
 end
 
+"""
+    @chance(odds)
+
+Return true if a random number is less than the odds (0.0 <= `odds` <= 1.0),
+using the model RNG. This is a utility wrapper that can only be used a context
+where the `model` object is available.
+"""
+macro chance(odds)
+    :($(esc(:rand))($(esc(:model)).rng) < $(esc(odds)))
+end
+
 """
     parsecommandline()
 
diff --git a/src/core/simulation.jl b/src/core/simulation.jl
index dd33eff0224235ce5febf1cd50f48306c7ca1b24..10d6d54da2271f1fe4f0dc18b165888a3f4b78e9 100644
--- a/src/core/simulation.jl
+++ b/src/core/simulation.jl
@@ -76,6 +76,7 @@ function initmodel(settings::Dict{String, Any})
                                       :landscape=>landscape,
                                       :weather=>weather,
                                       :crops=>crops,
+                                      :migrants=>Vector{Pair{Animal, Date}}(),
                                       :dataoutputs=>Vector{DataOutput}(),
                                       :datatables=>Dict{String, DataFrame}(),
                                       :events=>Vector{FarmEvent}())
@@ -135,6 +136,7 @@ function stepsimulation!(model::AgentBasedModel)
             end
         end
         updateevents!(model)
+        updatenature!(model)
         outputdata(model)
         model.date += Day(1)
         model
diff --git a/src/nature/macros.jl b/src/nature/macros.jl
new file mode 100644
index 0000000000000000000000000000000000000000..0b2dfcaec87d7db022765cc3d39ec032250d8405
--- /dev/null
+++ b/src/nature/macros.jl
@@ -0,0 +1,314 @@
+### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
+###
+### This file contains all macros implementing the domain-specific language
+### for defining species.
+###
+
+## Note that this DSL consists of lots of deeply nested macro calls, which is a
+## known tricky issue in Julia (https://github.com/JuliaLang/julia/issues/23221,
+## https://github.com/p-i-/MetaGuideJulia/wiki#example-swap-macro-to-illustrate-esc).
+## Hence all the `esc`apes in the following code - be careful when modifying!
+
+"""
+    @species(name, body)
+
+A macro used to create new species definitions for the nature model.
+This is effectively a simple domain-specific language, establishing a
+custom syntax to describe species' biology:
+
+```julia
+@species name begin
+
+    @initialise(@habitat(...))
+    speciesvar1 = 3.14
+    ...
+
+    @phase phase1 begin
+        ...
+    end
+end
+```
+
+The definition body (enclosed in the begin/end block) has two sections.
+First comes a call to [`@initialise`](@ref), and optionally a list of
+species-specific parameters, which are assigned just like normal variables.
+Second come one or more phase definitions, that describe the behaviour
+of the species during various parts of its life cycle. (See the documentation
+to [`@initialise`](@ref) and [`@phase`](@ref) for details).
+
+Code in a species definition block can access the rest of the model using
+the `model` variable (an object of type `AgentBasedModel`).
+"""
+macro species(name, body)
+    quote
+        Core.@__doc__ function $(esc(name))($(esc(:model))::AgentBasedModel)
+            # create internal variables and execute the definition body
+            $(esc(:name)) = string($(QuoteNode(name)))
+            $(esc(:phase)) = ""
+            $(esc(body))
+            # extract and process the local variables into a species dict
+            vardict = Base.@locals
+            speciesdict = Dict{String,Any}()
+            for k in keys(vardict)
+                speciesdict[string(k)] = vardict[k]
+            end
+            delete!(speciesdict, "model")
+            delete!(speciesdict, $(string(name)))
+            return speciesdict
+        end
+        # allow species to be defined outside of the Persefone module, but still available
+        # inside it (needed by `initnature!()` and `reproduce!()`)
+        (@__MODULE__() != $(esc(:Persefone))) && ($(esc(:Persefone)).$name = $(esc(name)))
+    end
+end
+
+"""
+    @initialise(habitatdescriptor; kwargs...)
+
+Call this macro within the body of [`@species`](@ref). It passes the given habitat
+descriptor function and keyword arguments on to [`initpopulation`](@ref) when setting
+up the simulation.
+
+Note: if this macro is not used, the variable `initialise!` must be set manually in the
+species definition.
+"""
+macro initialise(habitatdescriptor, kwargs...)
+    :($(esc(:initialise!)) = initpopulation($(esc(habitatdescriptor)); $(map(esc, kwargs)...)))
+end
+
+#TODO add an individual-level initialisation function!
+
+"""
+    @phase(name, body)
+
+This macro is designed to be used within a species definition block (i.e. within
+the body of a call to [`@species`](@ref)).
+
+The idea behind this is that species show very different behaviour during different
+phases of their lives. Therefore, `@phase` can be used define the behaviour for one
+such phase, and the conditions under which the animal transitions to another phase.
+
+`@phase` works by creating a function that will be called by the model if the
+animal is in the relevant phase. When it is called, it has access to the following
+variables:
+- `animal` a reference to the animal itself. This provides access to `animal.age`,
+    `animal.sex`, and `animal.<trait>` (where <trait> is a variable that was defined
+    in the top part of the species definition body).
+- `pos` gives the animal's current position as a coordinate tuple.
+- `model` a reference to the model world (an object of type `AgentBasedModel`).
+    This allows access to `model.date` (the current simulation date) and
+    `model.landscape` (a two-dimensional array of pixels containing geographic
+    information).
+
+Several utility macros can be used within the body of `@phase` as a short-hand for
+common expressions: [`@trait`](@ref), [`@setphase`](@ref), [`@respond`](@ref),
+[`@kill`](@ref), [`@reproduce`](@ref), [`@neighbours`](@ref), [`@rand`](@ref),
+[`@shuffle!`](@ref).
+
+Note that the first phase that is defined in a species definition block will be
+the phase that animals are assigned at birth, unless the variable `phase` is
+explicitly defined by the user in the species definition block.
+"""
+macro phase(name, body)
+    #TODO the docstrings give a lot of warnings in the log - can I fix that?
+    quote
+        Core.@__doc__ function $(esc(name))($(esc(:animal))::Animal, $(esc(:model))::AgentBasedModel)
+            $(esc(:pos)) = $(esc(:animal)).pos
+            $(esc(body))
+        end
+        ($(esc(:phase)) == "") && ($(esc(:phase)) = $(String(name)))
+    end
+end
+
+"""
+    @trait(traitname)
+
+A utility macro to quickly access an animal's trait value.
+This can only be used nested within [`@phase`](@ref).
+"""
+macro trait(traitname)
+    #XXX This would error if called in the first part of a species definition block
+    # (i.e. outside of a @phase block). Although this is specified in the documentation,
+    # it is unexpected and liable to be overlooked. Can we add a third clause to
+    # compensate for that?
+    :($(esc(:animal)).$(traitname))
+end
+
+"""
+    @setphase(newphase)
+
+Switch this animal over to a different phase. This can only be used nested within [`@phase`](@ref).
+"""
+macro setphase(newphase)
+    #XXX make this usable in the top part of a species definition?
+    :($(esc(:animal)).phase = $(String(newphase)))
+end
+
+"""
+    @respond(eventname, body)
+
+Define how an animal responds to a landscape event that affects its current position.
+This can only be used nested within [`@phase`](@ref).
+"""
+macro respond(eventname, body)
+    quote
+        if $(esc(eventname)) in $(esc(:model)).landscape[$(esc(:animal)).pos...].events
+            $(esc(body))
+        end
+    end
+end
+
+"""
+    @kill
+
+Kill this animal (and immediately abort its current update if it dies). This is a
+thin wrapper around [`kill!`](@ref), and passes on any arguments. This can only be
+used nested within [`@phase`](@ref).
+"""
+macro kill(args...)
+    :(kill!($(esc(:animal)), $(esc(:model)), $(map(esc, args)...)) && return)
+end
+
+"""
+    @reproduce
+
+Let this animal reproduce. This is a thin wrapper around [`reproduce!`](@ref), and
+passes on any arguments. This can only be used nested within [`@phase`](@ref).
+"""
+macro reproduce(args...)
+    :(reproduce!($(esc(:animal)), $(esc(:model)), $(map(esc, args)...)))
+end
+
+"""
+    @migrate(arrival)
+
+Remove this animal from the map and add it to the migrant species pool.
+It will be returned to its current location at the specified `arrival` date.
+This can only be used nested within [`@phase`](@ref).
+"""
+macro migrate(arrival)
+    :(migrate!($(esc(:animal)), $(esc(:model)), $(esc(arrival))))
+end
+
+"""
+    @neighbours(radius)
+
+Return an iterator over all animals in the given radius around this animal, excluding itself.
+This can only be used nested within [`@phase`](@ref).
+"""
+macro neighbours(radius)
+    #TODO enable filtering by species
+    :(nearby_animals($(esc(:animal)), $(esc(:model)), $(esc(radius))))
+end
+
+"""
+    @habitat
+
+Specify habitat suitability for spatial ecological processes.
+
+This macro works by creating an anonymous function that takes in a model object
+and a position, and returns `true` or `false` depending on the conditions
+specified in the macro body.
+
+Several utility macros can be used within the body of `@habitat` as a short-hand for
+common expressions: [`@landcover`](@ref), [`@cropname`](@ref), [`@cropheight`](@ref),
+[`@distanceto`](@ref), [`@distancetoedge`](@ref), [`@countanimals`](@ref).
+The variables `model` and `pos` can be used for checks that don't have a macro available.
+
+Two example uses of `@habitat` might look like this:
+
+```julia
+movementhabitat = @habitat(@landcover() in (grass agriculture soil))
+
+nestinghabitat = @habitat((@landcover() == grass || 
+                           (@landcover() == agriculture && @cropname() != "maize" &&
+                            @cropheight() < 10)) &&
+                          @distanceto(forest) > 20)
+```
+
+For more complex habitat suitability checks, the use of this macro can be
+circumvented by directly creating an equivalent function.
+"""
+macro habitat(body)
+    #XXX I suspect that I may have less problems with macro expansion and module
+    # scoping if @habitat did not create a new function. But is there a different way?
+    quote
+        function($(esc(:pos)), $(esc(:model)))
+            if $(esc(body))
+                return true
+            else
+                return false
+            end
+        end
+    end
+end
+
+##XXX Can I make sure (e.g. through `try/catch`) that the following macros
+## are not called anywhere outside @habitat/@phase?
+
+"""
+    @landcover
+
+Returns the local landcover. This is a utility wrapper that can only be used
+nested within [`@phase`](@ref) or [`@habitat`](@ref).
+"""
+macro landcover()
+    :(landcover($(esc(:pos)), $(esc(:model))))
+end
+
+"""
+    @cropname
+
+Return the name of the local croptype, 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 cropname()
+    :(cropname($(esc(:pos)), $(esc(:model))))
+end
+
+"""
+    @cropheight
+
+Return the height of the crop at this position, or 0 if there is no crop here.
+This is a utility wrapper that can only be used nested within [`@phase`](@ref)
+or [`@habitat`](@ref).
+"""
+macro cropheight()
+    :(cropheight($(esc(:pos)), $(esc(:model))))
+end
+
+"""
+    @distanceto(habitat)
+
+Calculate the distance to the closest habitat of the specified type or descriptor.
+This is a utility wrapper that can only be used nested within [`@phase`](@ref)
+or [`@habitat`](@ref).
+"""
+macro distanceto(habitat)
+    :(distanceto($(esc(:pos)), $(esc(:model)), $(esc(habitat))))
+end
+
+"""
+    @distancetoedge
+
+Calculate the distance to the closest neighbouring habitat.
+This is a utility wrapper that can only be used nested within [`@phase`](@ref)
+or [`@habitat`](@ref).
+"""
+macro distancetoedge()
+    :(distancetoedge($(esc(:pos)), $(esc(:model))))
+end
+
+"""
+    @countanimals(species="", radius=0)
+
+Count the number of animals of the given species in this location.
+This is a utility wrapper that can only be used nested within [`@phase`](@ref)
+or [`@habitat`](@ref).
+"""
+macro countanimals(args...)
+    :(countanimals($(esc(:pos)), $(esc(:model)); $(map(esc, args)...)))
+end
+
+##TODO add movement macros
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index 105644fc24538b327e47d386c5d15fd05fa0a806..b9e96ee85b2ac4c335e0ced081ae1d444ae55869 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -1,11 +1,10 @@
 ### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
 ###
-### This file is responsible for managing the animal modules.
+### This file is responsible for managing the animal modules and contains functions
+### and types integrating the nature submodel with the rest of Persefone.
 ###
 
 
-### FUNCTIONS AND TYPES INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE
-
 ## An enum used to assign a sex to each animal
 @enum Sex hermaphrodite male female
 
@@ -88,305 +87,20 @@ function initnature!(model::AgentBasedModel)
     initecologicaldata(model)
 end
 
-
-### MACROS IMPLEMENTING THE DOMAIN-SPECIFIC LANGUAGE FOR DEFINING SPECIES
-
-## Note that this DSL consists of lots of deeply nested macro calls, which is a
-## known tricky issue in Julia (https://github.com/JuliaLang/julia/issues/23221,
-## https://github.com/p-i-/MetaGuideJulia/wiki#example-swap-macro-to-illustrate-esc).
-## Hence all the `esc`apes in the following code - be careful when modifying!
-
-"""
-    @species(name, body)
-
-A macro used to create new species definitions for the nature model.
-This is effectively a simple domain-specific language, establishing a
-custom syntax to describe species' biology:
-
-```julia
-@species name begin
-
-    @initialise(@habitat(...))
-    speciesvar1 = 3.14
-    ...
-
-    @phase phase1 begin
-        ...
-    end
-end
-```
-
-The definition body (enclosed in the begin/end block) has two sections.
-First comes a call to [`@initialise`](@ref), and optionally a list of
-species-specific parameters, which are assigned just like normal variables.
-Second come one or more phase definitions, that describe the behaviour
-of the species during various parts of its life cycle. (See the documentation
-to [`@initialise`](@ref) and [`@phase`](@ref) for details).
-
-Code in a species definition block can access the rest of the model using
-the `model` variable (an object of type `AgentBasedModel`).
-"""
-macro species(name, body)
-    quote
-        Core.@__doc__ function $(esc(name))($(esc(:model))::AgentBasedModel)
-            # create internal variables and execute the definition body
-            $(esc(:name)) = string($(QuoteNode(name)))
-            $(esc(:phase)) = ""
-            $(esc(body))
-            # extract and process the local variables into a species dict
-            vardict = Base.@locals
-            speciesdict = Dict{String,Any}()
-            for k in keys(vardict)
-                speciesdict[string(k)] = vardict[k]
-            end
-            delete!(speciesdict, "model")
-            delete!(speciesdict, $(string(name)))
-            return speciesdict
-        end
-        # allow species to be defined outside of the Persefone module, but still available
-        # inside it (needed by `initnature!()` and `reproduce!()`)
-        (@__MODULE__() != $(esc(:Persefone))) && ($(esc(:Persefone)).$name = $(esc(name)))
-    end
-end
-
-"""
-    @initialise(habitatdescriptor; kwargs...)
-
-Call this macro within the body of [`@species`](@ref). It passes the given habitat descriptor
-function and keyword arguments on to [`initpopulation`](@ref) when setting up the simulation.
-
-Note: if this macro is not used, the variable `initialise!` must be set manually in the
-species definition.
-"""
-macro initialise(habitatdescriptor, kwargs...)
-    :($(esc(:initialise!)) = initpopulation($(esc(habitatdescriptor)); $(map(esc, kwargs)...)))
-end
-
-"""
-    @phase(name, body)
-
-This macro is designed to be used within a species definition block (i.e. within
-the body of a call to [`@species`](@ref)).
-
-The idea behind this is that species show very different behaviour during different
-phases of their lives. Therefore, `@phase` can be used define the behaviour for one
-such phase, and the conditions under which the animal transitions to another phase.
-
-`@phase` works by creating a function that will be called by the model if the
-animal is in the relevant phase. When it is called, it has access to the following
-variables:
-- `animal` a reference to the animal itself. This provides access to `animal.age`,
-    `animal.sex`, and `animal.<trait>` (where <trait> is a variable that was defined
-    in the top part of the species definition body).
-- `pos` gives the animal's current position as a coordinate tuple.
-- `model` a reference to the model world (an object of type `AgentBasedModel`).
-    This allows access to `model.date` (the current simulation date) and
-    `model.landscape` (a two-dimensional array of pixels containing geographic
-    information).
-
-Several utility macros can be used within the body of `@phase` as a short-hand for
-common expressions: [`@trait`](@ref), [`@setphase`](@ref), [`@respond`](@ref),
-[`@kill`](@ref), [`@reproduce`](@ref), [`@neighbours`](@ref), [`@rand`](@ref),
-[`@shuffle!`](@ref).
-
-Note that the first phase that is defined in a species definition block will be
-the phase that animals are assigned at birth, unless the variable `phase` is
-explicitly defined by the user in the species definition block.
-"""
-macro phase(name, body)
-    #TODO the docstrings give a lot of warnings in the log - can I fix that?
-    quote
-        Core.@__doc__ function $(esc(name))($(esc(:animal))::Animal, $(esc(:model))::AgentBasedModel)
-            $(esc(:pos)) = $(esc(:animal)).pos
-            $(esc(body))
-        end
-        ($(esc(:phase)) == "") && ($(esc(:phase)) = $(String(name)))
-    end
-end
-
-"""
-    @trait(traitname)
-
-A utility macro to quickly access an animal's trait value.
-This can only be used nested within [`@phase`](@ref).
-"""
-macro trait(traitname)
-    #XXX This would error if called in the first part of a species definition block
-    # (i.e. outside of a @phase block). Although this is specified in the documentation,
-    # it is unexpected and liable to be overlooked. Can we add a third clause to
-    # compensate for that?
-    :($(esc(:animal)).$(traitname))
-end
-
-"""
-    @setphase(newphase)
-
-Switch this animal over to a different phase. This can only be used nested within [`@phase`](@ref).
-"""
-macro setphase(newphase)
-    #XXX make this usable in the top part of a species definition?
-    :($(esc(:animal)).phase = $(String(newphase)))
-end
-
 """
-    @respond(eventname, body)
+    updatenature!(model)
 
-Define how an animal responds to a landscape event that affects its current position.
-This can only be used nested within [`@phase`](@ref).
+Run processes that affect all animals.
 """
-macro respond(eventname, body)
-    quote
-        if $(esc(eventname)) in $(esc(:model)).landscape[$(esc(:animal)).pos...].events
-            $(esc(body))
-        end
+function updatenature!(model::AgentBasedModel)
+    # The migrant pool is sorted by date of return, so we can simply look at the top
+    # of the stack to check whether any animals are returning today.
+    while !isempty(model.migrants) && model.migrants[1].second == model.date
+        add_agent_pos!(model.migrants[1].first, model)
+        @debug "$(animalid(model.migrants[1].first)) has returned."
+        deleteat!(model.migrants, 1)
     end
+    #XXX what else needs to go here?
 end
 
-"""
-    @kill
-
-Kill this animal (and immediately abort its current update). This is a thin wrapper
-around [`kill!`](@ref), and passes on any arguments. This can only be used nested within
-[`@phase`](@ref).
-"""
-macro kill(args...)
-    quote
-        kill!($(esc(:animal)), $(esc(:model)), $(map(esc, args)...))
-        return
-    end
-end
-
-"""
-    @reproduce
-
-Let this animal reproduce. This is a thin wrapper around [`reproduce!`](@ref), and passes on
-any arguments. This can only be used nested within [`@phase`](@ref).
-"""
-macro reproduce(args...)
-    :(reproduce!($(esc(:animal)), $(esc(:model)), $(map(esc, args)...)))
-end
-
-"""
-    @neighbours(radius)
-
-Return an iterator over all animals in the given radius around this animal, excluding itself.
-This can only be used nested within [`@phase`](@ref).
-"""
-macro neighbours(radius)
-    #TODO enable filtering by species
-    :(nearby_animals($(esc(:animal)), $(esc(:model)), $(esc(radius))))
-end
-
-"""
-    @habitat
-
-Specify habitat suitability for spatial ecological processes.
-
-This macro works by creating an anonymous function that takes in a model object
-and a position, and returns `true` or `false` depending on the conditions
-specified in the macro body.
-
-Several utility macros can be used within the body of `@habitat` as a short-hand for
-common expressions: [`@landcover`](@ref), [`@cropname`](@ref), [`@cropheight`](@ref),
-[`@distanceto`](@ref), [`@distancetoedge`](@ref), [`@countanimals`](@ref).
-The variables `model` and `pos` can be used for checks that don't have a macro available.
-
-Two example uses of `@habitat` might look like this:
-
-```julia
-movementhabitat = @habitat(@landcover() in (grass agriculture soil))
-
-nestinghabitat = @habitat((@landcover() == grass || 
-                           (@landcover() == agriculture && @cropname() != "maize" &&
-                            @cropheight() < 10)) &&
-                          @distanceto(forest) > 20)
-```
-
-For more complex habitat suitability checks, the use of this macro can be
-circumvented by directly creating an equivalent function.
-"""
-macro habitat(body)
-    #XXX I suspect that I may have less problems with macro expansion and module
-    # scoping if @habitat did not create a new function. But is there a different way?
-    quote
-        function($(esc(:pos)), $(esc(:model)))
-            if $(esc(body))
-                return true
-            else
-                return false
-            end
-        end
-    end
-end
-
-##XXX Can I make sure (e.g. through `try/catch`) that the following macros
-## are not called anywhere outside @habitat/@phase?
-
-"""
-    @landcover
-
-Returns the local landcover. This is a utility wrapper that can only be used
-nested within [`@phase`](@ref) or [`@habitat`](@ref).
-"""
-macro landcover()
-    :(landcover($(esc(:pos)), $(esc(:model))))
-end
-
-"""
-    @cropname
-
-Return the name of the local croptype, 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 cropname()
-    :(cropname($(esc(:pos)), $(esc(:model))))
-end
-
-"""
-    @cropheight
-
-Return the height of the crop at this position, or 0 if there is no crop here.
-This is a utility wrapper that can only be used nested within [`@phase`](@ref)
-or [`@habitat`](@ref).
-"""
-macro cropheight()
-    :(cropheight($(esc(:pos)), $(esc(:model))))
-end
-
-"""
-    @distanceto(habitat)
-
-Calculate the distance to the closest habitat of the specified type or descriptor.
-This is a utility wrapper that can only be used nested within [`@phase`](@ref)
-or [`@habitat`](@ref).
-"""
-macro distanceto(habitat)
-    :(distanceto($(esc(:pos)), $(esc(:model)), $(esc(habitat))))
-end
-
-"""
-    @distancetoedge
-
-Calculate the distance to the closest neighbouring habitat.
-This is a utility wrapper that can only be used nested within [`@phase`](@ref)
-or [`@habitat`](@ref).
-"""
-macro distancetoedge()
-    :(distancetoedge($(esc(:pos)), $(esc(:model))))
-end
-
-"""
-    @countanimals(species="", radius=0)
-
-Count the number of animals of the given species in this location.
-This is a utility wrapper that can only be used nested within [`@phase`](@ref)
-or [`@habitat`](@ref).
-"""
-macro countanimals(args...)
-    :(countanimals($(esc(:pos)), $(esc(:model)); $(map(esc, args)...)))
-end
-
-##TODO @chance macro: @chance(0.5) => rand(model.rng) < 0.5
-
-##TODO add movement macros
+#TODO test migration
diff --git a/src/nature/populations.jl b/src/nature/populations.jl
index f2e80c0dc7e735a0e532b20b4905c55b1ba03a8d..c0029a1c4562fa4116d0357137402f6a1381d7e1 100644
--- a/src/nature/populations.jl
+++ b/src/nature/populations.jl
@@ -29,10 +29,15 @@ This can be used to create the `initialise!` variable in a species definition bl
 - If `asexual` is true, all created individuals are assigned the sex `hermaphrodite`,
     otherwise, they are randomly assigned male of female. (If `pairs` is true, `asexual`
     is ignored.)
+
+- `initfunction` is an optional function that takes an animal object and the model,
+    and performs individual-specific initialisation tasks. It is called after the
+    individual has been created and placed.
 """
 function initpopulation(habitatdescriptor::Function; phase::Union{String,Nothing}=nothing,
-                        popsize::Int64=-1, pairs::Bool=false, asexual::Bool=false)
-    #TODO add a `popdensity` argument
+                        popsize::Int64=-1, pairs::Bool=false, asexual::Bool=false,
+                        initfunction::Function=(a,m)->nothing)
+    #TODO add a constructor function for the individual?
     function(species::Dict{String,Any}, model::AgentBasedModel)
         n = 0
         lastn = 0
@@ -44,12 +49,17 @@ function initpopulation(habitatdescriptor::Function; phase::Union{String,Nothing
                 for y in @shuffle!(Vector(1:height))
                     if habitatdescriptor((x,y), model)
                         if pairs
-                            add_agent!((x,y), Animal, model, deepcopy(species), female, 0)
-                            add_agent!((x,y), Animal, model, deepcopy(species), male, 0)
+                            a1 = add_agent!((x,y), Animal, model,
+                                            deepcopy(species), female, 0)
+                            a2 = add_agent!((x,y), Animal, model,
+                                            deepcopy(species), male, 0)
+                            initfunction(a1, model)
+                            initfunction(a2, model)
                             n += 2
                         else
                             sex = asexual ? hermaphrodite : @rand([male, female])
-                            add_agent!((x,y), Animal, model, deepcopy(species), sex, 0)
+                            a = add_agent!((x,y),Animal,model,deepcopy(species),sex,0)
+                            initfunction(a, model)
                             n += 1
                         end
                     end
@@ -112,6 +122,24 @@ function kill!(animal::Animal, model::AgentBasedModel, probability::Float64=1.0,
     return false
 end
 
+"""
+    migrate!(animal, model, arrival)
+
+Remove this animal from the map and add it to the migrant species pool.
+It will be returned to its current location at the specified `arrival` date.
+"""
+function migrate!(animal::Animal, model::AgentBasedModel, arrival::Date)
+    i = 1
+    while i <= length(model.migrants) && model.migrants[i].second < arrival
+        i += 1
+    end
+    i <= length(model.migrants) ?
+        insert!(model.migrants, i, Pair(animal, arrival)) :
+        push!(model.migrants, Pair(animal, arrival))
+    remove_agent!(animal, model)
+    @debug "$(animalid(animal)) has migrated."
+end
+
 """
     nearby_animals(pos, model, radius)
 
diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index c5ecc0c708fcb38752147643a61ca03aa84ea982..98d71935b57d9e2ab79c1b0653cdbc13aa26807f 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -18,16 +18,17 @@ At the moment, this implementation is still in development.
 """
 @species Skylark begin
     
-    eggmaturationtime = 
-    
+    eggmaturationtime = 11
     eggharvestmortality = 0.9 #???
     eggpredationmortality = 0.1 #???
 
+    migrationdates = ()
+
     habitats = @habitat((@landcover() == grass || 
                          (@landcover() == agriculture && @croptype() != maize)) &&
                         @distanceto(forest) > 5)
     
-    @initialise(habitats, pairs=true)
+    @initialise(habitats, pairs=true, initfunction=initskylark)
     
     @phase egg begin
         @kill(@trait(eggpredationmortality), "predation")
@@ -60,3 +61,36 @@ At the moment, this implementation is still in development.
         #TODO
     end
 end
+
+"""
+    initskylark(skylark, model)
+
+Initialise a skylark individual. Selects migration dates and checks if the
+bird should currently be on migration.
+"""
+function initskylark(animal::Animal, model::AgentBasedModel)
+    animal.migrationdates = migrationdates(animal, model)
+    leave = animal.migrationdates[1]
+    arrive = animal.migrationdates[2]
+    m, d = monthday(model.date)
+    migrate = (((m < arrive[1]) || (m == arrive[1] && d < arrive[2])) ||
+               ((m > leave[1]) || (m == leave[1] && d >= leave[2])))
+    migrate && @migrate(Date(year(model.date)+year(1), arrive[1], arrive[2]))
+    #TODO other stuff?
+end
+
+"""
+    migrationdates(skylark, model)
+
+Select the dates on which this skylark will leave for / return from its migration,
+based on observed migration patterns.
+"""
+function migrationdates(skylark::Animal, model::AgentBasedModel)
+    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
+    deltaarrive = @rand(0:15) #XXX ought to be normally distributed
+    leave = monthday(Date(2000, minleave[1], minleave[2]) + Day(deltaleave))
+    arrive = monthday(Date(2000, minarrive[1], minarrive[2]) + Day(deltaarrive))
+    (leave, arrive)
+end
diff --git a/src/parameters.toml b/src/parameters.toml
index 80d845fa127b4a66d754c160dbb0065b8cef7ad9..45a4519b968dd80730b1c485af187459b19d9624 100644
--- a/src/parameters.toml
+++ b/src/parameters.toml
@@ -29,7 +29,7 @@ weatherfile = "data/regions/jena/weather.csv" # location of the weather data fil
 farmmodel = "FieldManager" # which version of the farm model to use (not yet implemented)
 
 [nature]
-targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate
+targetspecies = ["Skylark"] # list of target species to simulate
 popoutfreq = "daily" # output frequency population-level data, daily/monthly/yearly/end/never
 indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearly/end/never
 insectmodel = ["season", "habitat", "pesticides", "weather"] # factors affecting insect growth
diff --git a/test/runtests.jl b/test/runtests.jl
index f2dca73a018944e26ae461def5c1558ae988476e..78d32931d147e946905dfd86d639bf908cc6c397 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -46,6 +46,7 @@ function inittestmodel(smallmap=true)
                                   :weather=>weather,
                                   :crops=>crops,
                                   :events=>Vector{FarmEvent}(),
+                                  :migrants=>Vector{Pair{Animal, Date}}(),
                                   :logger=>global_logger(),
                                   :dataoutputs=>Vector{DataOutput}(),
                                   :datatables=>Dict{String,DataFrame}(),