diff --git a/src/Persephone.jl b/src/Persephone.jl
index 3a978d7408f9d6376fa603d4f0e47404c5cdb072..2470a0c253c1406ef1c41225519565d393e3b5ff 100644
--- a/src/Persephone.jl
+++ b/src/Persephone.jl
@@ -47,7 +47,7 @@ include("nature/lifehistory.jl")
 include("nature/ecologicaldata.jl")
 #include("nature/species/skylark.jl")
 include("nature/species/wolpertinger.jl")
-#include("nature/species/wyvern.jl")
+include("nature/species/wyvern.jl")
 include("core/simulation.jl")
 
 end
diff --git a/src/nature/lifehistory.jl b/src/nature/lifehistory.jl
index e80a1d9252aee85c2f5f6ddfb180264ef11157b2..c15f7593a07d1792d79231dc7db10726b1671b56 100644
--- a/src/nature/lifehistory.jl
+++ b/src/nature/lifehistory.jl
@@ -10,7 +10,7 @@ A simplified version of `initpopulation()`. Creates a function that initialises
 `popsize` individuals, spread at random across the landscape. If `popsize` is less
 than 1, it is interpreted as a population density (i.e. 1 animal per `popsize` pixels).
 """
-function initrandompopulation(popsize::Float64, asexual::Bool=true)
+function initrandompopulation(popsize::Union{Int64,Float64}, asexual::Bool=true)
     initfunc = function(species::Dict{String,Any}, model::AgentBasedModel)
         if popsize < 1.0
             x, y = size(model.landscape)
@@ -39,7 +39,7 @@ function reproduce!(animal::Animal, model::AgentBasedModel, n::Int64=1)
         sex = animal.sex == hermaphrodite ? hermaphrodite : rand([male, female])
         add_agent!(animal.pos, Animal, model, animal.traits, sex, 0)
     end
-    @debug "$(animal.traits["name"]) $(animal.id) has reproduced."
+    @debug "$(animalid(animal)) has reproduced."
 end
 
 """
@@ -51,7 +51,7 @@ Returns true if the animal dies, false if not.
 function kill!(animal::Animal, model::AgentBasedModel, probability::Float64=1.0, cause::String="")
     if rand() < probability
         postfix = isempty(cause) ? "." : " from $cause."
-        @debug "$(animal.traits["name"]) $(animal.id) has died$(postfix)"
+        @debug "$(animalid(animal)) has died$(postfix)"
         kill_agent!(animal, model)
         return true
     end
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index 71dad4008376f919443ea08195a26725fae6a019..abc2043ad0c0dcbc32cfad8f5e34632d55adcb88 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -6,9 +6,6 @@
 ## An enum used to assign a sex to each animal
 @enum Sex hermaphrodite male female
 
-## This will be prepended to the names of the functions created by @species
-const SPECIESPREFIX = "speciesfunction_"
-
 """
     Animal
 
@@ -25,6 +22,15 @@ by trait dictionaries passed by them during initialisation.
     #XXX keep track of parents and/or offspring?
 end
 
+"""
+    animalid(animal)
+
+A small utility function to return a string with the species name and ID of an animal.
+"""
+function animalid(a::Animal)
+    return "$(a.traits["name"]) $(a.id)"
+end
+
 
 ### MACROS IMPLEMENTING THE DOMAIN-SPECIFIC LANGUAGE FOR DEFINING SPECIES
 
@@ -64,9 +70,8 @@ Access to the rest of the model is given by the `model` variable (an object
 of type `AgentBasedModel`).
 """
 macro species(name, body)
-    funcname = Symbol(SPECIESPREFIX*string(name))
     quote
-        Core.@__doc__ function $(esc(funcname))(model::AgentBasedModel)
+        Core.@__doc__ function $(esc(name))(model::AgentBasedModel)
             $(esc(:name)) = string($(QuoteNode(name)))
             $(esc(body))
             vardict = Base.@locals
@@ -174,7 +179,7 @@ end
 """
     stepagent!(animal, model)
 
-Update an animal by one day.
+Update an animal by one day, executing it's currently active phase function.
 """
 function stepagent!(animal::Animal, model::AgentBasedModel)
     animal.age += 1
@@ -188,9 +193,8 @@ Initialise the model with all simulated animal populations.
 """
 function initnature!(model::AgentBasedModel)
     # The config file determines which species are simulated in this run
-    for s in param("nature.targetspecies")
-        funcname = Symbol(SPECIESPREFIX*s)
-        species = @eval $funcname($model)
+    for speciesname in param("nature.targetspecies")
+        species = @eval $(Symbol(speciesname))($model)
         species["initialise!"](species, model)
     end
     # Initialise the data output
diff --git a/src/nature/species/wolpertinger.jl b/src/nature/species/wolpertinger.jl
index a8956f2d6c34e0b51fea4eb6ef9a5ba5fb2e0b4c..67a4914f7bb6b806cfb12dc1986a8106193158a4 100644
--- a/src/nature/species/wolpertinger.jl
+++ b/src/nature/species/wolpertinger.jl
@@ -13,8 +13,9 @@ of a deer.
 @species Wolpertinger begin
     popdensity = 1/100000
     fecundity = 0.02
-    mortality = 0.01
+    mortality = 0.015
     maxspeed = 5
+    crowdingfactor = maxspeed*2
 
     initialise! = initrandompopulation(popdensity)
     phase = "lifephase"
@@ -29,7 +30,9 @@ of a deer.
             walk!(animal, direction, model; ifempty=false)
         end
 
-        if rand() < @trait(fecundity)
+        #TODO density-dependent reproduction
+        #neighbours = length((n.id for n in nearby_agents(animal, model, @trait(maxspeed))))
+        if rand() < @trait(fecundity) # && neighbours < @trait(crowdingfactor)
             @reproduce
         end
 
diff --git a/src/nature/species/wyvern.jl b/src/nature/species/wyvern.jl
index 2c96df93626590fda6216cd54b6fb6355b373d01..c852d69f800f2120c50038909a53ea30195b798b 100644
--- a/src/nature/species/wyvern.jl
+++ b/src/nature/species/wyvern.jl
@@ -5,75 +5,77 @@
 ### Thankfully, wyverns are not a species we have to manage for...
 ###
 
-##TODO convert to @species syntax
-
 """
-    initwyvern!(model)
-
-Initialise a population of Wyverns in pairs locations around the landscape.
+Beware the wyvern! This evolutionary cousin of the dragon may only have two
+legs, but that doesn't make it any less dangerous...
 """
-function initwyvern!(model::AgentBasedModel)
-    species = getspecies("Wyvern")
-    x, y = size(model.landscape)
-    for i in 1:species.traits["initpop"]
-        add_agent!(Animal, model, species, hermaphrodite, 0, species.traits["birthenergy"])
-    end
-    @debug "$(species.traits["initpop"]) wyverns are now lying in wait in the landscape."
-end
+@species Wyvern begin
+    popsize = 10
+    fecundity = 0.02
+    maxage = 365
+    speed = 20
+    vision = 50
+    aggression = 0.2
+    huntsuccess = 0.8
 
-"""
-    updatewyvern!(animal, model)
+    initialise! = initrandompopulation(popsize)
+    phase = "winter"
 
-Wyverns are ferocious hunters, scouring the landscape for their favourite
-prey: wolpertingers...
-"""
-function updatewyvern!(w::Animal, model::AgentBasedModel)
-    # check if the wyvern has reached its end-of-life
-    if w.age == trait(w, "maxage")
-        kill_agent!(w, model)
-        return
-    end
-    # check if a wolpertinger is in pouncing distance
-    for a in nearby_agents(w, model, trait(w, "speed"))
-        (a.species.name != "Wolpertinger") && continue
-        move_agent!(w, a.pos, model)
-        w.energy -= trait(w, "pounceenergy")
-        if rand() < trait(w, "huntsuccess")
-            @debug "Wyvern $(w.id) killed wolpertinger $(a.id)."
-            w.energy += Int(round(a.energy/2))
-            kill_agent!(a, model)
-            @goto reproduce
+    """
+    Wyverns are ferocious hunters, scouring the landscape for their favourite
+    prey: wolpertingers...
+    """
+    @phase summer begin
+        for a in nearby_agents(animal, model, @trait(speed))
+            # check if a wolpertinger is in pouncing distance
+            if a.traits["name"] == "Wolpertinger"
+                move_agent!(animal, a.pos, model)
+                if rand() < @trait(huntsuccess)
+                    @debug "$(animalid(animal)) killed $(animalid(a))."
+                    kill_agent!(a, model)
+                    @goto reproduce
+                end
+            elseif a.traits["name"] == "Wyvern" && rand() < @trait(aggression)
+                # wyverns also fight against each other if they get too close
+                move_agent!(animal, a.pos, model)
+                outcome = rand()
+                if outcome < 0.4
+                    @debug "$(animalid(animal)) killed $(animalid(a)) in a fight."
+                    kill_agent!(a, model)
+                elseif outcome < 0.8
+                    @kill 1.0 "wounds sustained in a fight"
+                end
+                @goto reproduce
+            end
         end
-    end
-    # check if a wolpertinger is in seeing distance, or walk in a random direction
-    direction = Tuple(rand([-1,1], 2))
-    for a in nearby_agents(w, model, trait(w, "vision"))
-        if a.species.name == "Wolpertinger"
-            direction = get_direction(w.pos, a.pos, model)
-            break
+        # check if a wolpertinger is in seeing distance, or walk in a random direction
+        direction = Tuple(rand([-1,1], 2))
+        for a in nearby_agents(animal, model, @trait(vision))
+            if a.traits["name"] == "Wolpertinger"
+                direction = get_direction(animal.pos, a.pos, model)
+                break
+            end
+        end    
+        for i in 1:@trait(speed)
+            walk!(animal, direction, model; ifempty=false)
+        end
+        # reproduce every once in a blue moon
+        @label reproduce
+        (rand() < @trait(fecundity)) && @reproduce
+        # hibernate from November to March
+        if monthday(model.date) == (11,1)
+            @trait(phase) = "winter"
         end
-    end    
-    for i in 1:trait(w, "speed")
-        walk!(w, direction, model; ifempty=false)
-        w.energy -= 1
+        (@trait(age) == maxage) && @kill(1.0, "old age")
     end
-    # reproduce every once in a blue moon
-    @label reproduce
-    if rand() < trait(w, "fecundity")
-        @debug "Wyvern $(w.id) has reproduced."
-        add_agent!(w.pos, Animal, model, getspecies("Wyvern"),
-                   hermaphrodite, 0, trait(w, "birthenergy"))
+
+    """
+    Fortunately, wyverns hibernate in winter.
+    """
+    @phase winter begin
+        # hibernate from November to March
+        if monthday(model.date) == (3,1)
+            @trait(phase) = "summer"
+        end
     end
 end
-
-newspecies("Wyvern",
-           initwyvern!,
-           updatewyvern!,
-           Dict("initpop"=>100,
-                "birthenergy"=>1000,
-                "fecundity"=>0.01,
-                "maxage"=>365,
-                "speed"=>10,
-                "vision"=>50,
-                "pounceenergy"=>20,
-                "huntsuccess"=>0.3))
diff --git a/src/parameters.toml b/src/parameters.toml
index b24fe1e54ee70c33cb29372b06e384e5e0e1a29e..69677aaaebdf3b3736b5e0226397dc0e24ca738f 100644
--- a/src/parameters.toml
+++ b/src/parameters.toml
@@ -15,13 +15,14 @@ loglevel = "debug" # verbosity level: "debug", "info", "quiet"
 seed = 0 # seed value for the RNG (0 -> random value)
 # dates to start and end the simulation
 startdate = 2020-01-01
-enddate = 2020-12-31
+#enddate = 2020-01-01
+enddate = 2021-12-31
 
 [farm]
-farmmodel = "FieldManager" # which version of the farm model to use
+farmmodel = "FieldManager" # which version of the farm model to use (not yet implemented)
 
 [nature]
-targetspecies = ["Wolpertinger"] # list of target species to simulate
+targetspecies = ["Wolpertinger", "Wyvern"] # 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