diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12a692c70b533865e4208db0b02fec9bef50ad11..0ea22d9d622839c96ae0b75c4c23342cd9171f29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,9 +21,9 @@ way the species definition macros work and are used.
   
 - functions (and associated macros) to replace Agents.jl functionality:
   - `move!()` and `walk!()` 
-  - `nearby_ids()`, `nearby_animals()`, `neighbours()`
+  - `nearby_ids()`, `nearby_animals()`, `countanimals()`, `neighbours()`
   - `directionto()`, `distanceto()`, `randomdirection()`
-  - `nagents()`
+  - `nagents()`, `killallanimals!()`
 
 - `@here`
 
diff --git a/src/Persefone.jl b/src/Persefone.jl
index fd4dbe42036758ad45bdbb68b3c3dc481cf7c01e..7e2fdd5c24efa789fa4259f2c203066f1852d85b 100644
--- a/src/Persefone.jl
+++ b/src/Persefone.jl
@@ -80,6 +80,7 @@ export
     @randompixel,
     @randomdirection,
     @nearby_animals,
+    @countanimals,
     @neighbours,
     @move,
     @walk,
diff --git a/src/core/simulation.jl b/src/core/simulation.jl
index 646f47a51e4562a980e2fd7437aa1bcce3e83e8a..fbc42358ea062a75d7c3655134dbcfc8c3779864 100644
--- a/src/core/simulation.jl
+++ b/src/core/simulation.jl
@@ -35,7 +35,7 @@ end
 Return the total number of agents in a model object.
 """
 function nagents(model::AgricultureModel)
-    length(model.animals)+length(model.migrants)+length(model.farmers)+length(model.farmplots)
+    length(model.animals)+length(model.farmers)+length(model.farmplots)
 end
     
 """
diff --git a/src/nature/macros.jl b/src/nature/macros.jl
index 7b0f414b595de75b683df5d7f6a5054f3eba81f8..226f979295a3f579e4bc5f890d2593620fa690b0 100644
--- a/src/nature/macros.jl
+++ b/src/nature/macros.jl
@@ -48,7 +48,7 @@ the `model` variable (an object of type `SimulationModel`).
 macro species(name, body)
     quote
         @kwdef mutable struct $name <: Animal
-            #FIXME once Julia 1.11 is released, escape $name above
+            #TODO once Julia 1.11 is released, escape $name above
             #(https://discourse.julialang.org/t/kwdef-constructor-not-available-outside-of-module/114675/4)
             const id::Int64
             const sex::Sex = hermaphrodite
@@ -389,8 +389,21 @@ Return an iterator over all animals in the given radius around the current posit
 This can only be used nested within [`@phase`](@ref) or [`@habitat`](@ref).
 """
 macro nearby_animals(args...)
+    #FIXME doesn't work properly when nested in `@habitat` (kwargs not recognised)
+    #XXX do I need this macro if I have @countanimals and @neighbours?
     #XXX does it make sense to use `pos` here? What if an an animal wants to look at another place?
-    :(nearby_animals($(esc(:pos)), $(esc(:model)), $(map(esc, args)...))) #FIXME
+    :(nearby_animals($(esc(:pos)), $(esc(:model)), $(map(esc, args)...)))
+end
+
+"""
+    @countanimals(radius=0, species="")
+
+Count the number of animals at or near this location, optionally filtering by species.
+This can only be used nested within [`@phase`](@ref) or [`@habitat`](@ref).
+"""
+macro countanimals(args...)
+    #FIXME doesn't work properly when nested in `@habitat` (kwargs not recognised)
+    :(countanimals($(esc(:pos)), $(esc(:model)), $(map(esc, args)...)))
 end
 
 """
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index 2742a8d01b0dd57bd6829698b487c0e248a3325e..85e753d39a1a340c6edf528335251a684c104252 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -32,9 +32,10 @@ abstract type Animal <: ModelAgent end
 
 Return the species name of this animal as a string.
 """
-function speciesof(a::Animal)
+function speciesof(a::Union{Animal,Type})
     # strip out the module name if necessary (`Persefone.<species>`)
-    spstrings = split(string(typeof(a)), ".")
+    (a isa Animal) && (a = typeof(a))
+    spstrings = split(string(a), ".")
     length(spstrings) == 1 ? spstrings[1] : spstrings[2]
 end
 
@@ -122,3 +123,17 @@ function updatenature!(model::SimulationModel)
         deleteat!(model.migrants, 1)
     end
 end
+
+"""
+    killallanimals!(model)
+
+Remove all animal individuals from the simulation.
+"""
+function killallanimals!(model)
+    for a in model.animals
+        kill!(a, model)
+    end
+    model.migrants = Vector{Pair{Animal, Date}}()
+    model.animals = Vector{Union{Animal,Nothing}}()
+    return
+end
diff --git a/src/nature/populations.jl b/src/nature/populations.jl
index 26d3cd0319e9e9fe32e90a49288459987d51ea78..7494dbc3ebab3bdea94f16f941fb27ddad7f643a 100644
--- a/src/nature/populations.jl
+++ b/src/nature/populations.jl
@@ -40,7 +40,7 @@ using [`@populate`](@ref).
     birthphase::Function
     habitat::Function = @habitat(true)
     popsize::Int64 = -1
-    popdensity::Int64 = -1
+    popdensity::Int64 = -1 #XXX this is counterintuitive
     pairs::Bool = false
     asexual::Bool = false
 end
@@ -61,13 +61,23 @@ Initialise the population of the given species, based on the parameters stored
 in [`PopInitParams`](@ref). Define these using [`@populate`](@ref).
 """
 function initpopulation!(speciesname::String, model::SimulationModel)
-    # get the PopInitParams and check for validity
     species = speciestype(speciesname)
     p = populationparameters(species)
-    (p.popsize <= 0 && p.popdensity <= 0) && #XXX not sure what this would do
-        @warn("initpopulation() called with popsize and popdensity both <= 0")
+    initpopulation!(species, p, model)
+end
+
+"""
+    initpopulation!(speciestype, popinitparams, model)
+
+Initialise the population of the given species, based on the given initialisation parameters.
+This is an internal function called by initpopulation!(), and was split off from it to allow
+better testing.
+"""
+function initpopulation!(species::Type, p::PopInitParams, model::SimulationModel)   
+    (p.popsize <= 0 && p.popdensity <= 0) && # can be legit if a habitat descriptor is provided
+        @warn("initpopulation!() called with popsize and popdensity both <= 0")
     (p.popsize > 0 && p.popdensity > 0) && #XXX not sure what this would do
-        @warn("initpopulation() called with popsize and popdensity both > 0")
+        @warn("initpopulation!() called with popsize and popdensity both > 0")
     # create as many individuals as necessary in the landscape
     n = 0
     lastn = 0
@@ -103,12 +113,12 @@ function initpopulation!(speciesname::String, model::SimulationModel)
             (p.popsize > 0 && n >= p.popsize) && break
         end
         if lastn == n # prevent an infinite loop - we don't have a Cray...
-            @warn "There are not enough suitable locations for $(species) in the landscape."
+            @warn "There are not enough suitable locations for $(speciesof(species)) in the landscape."
             break
         end
         lastn = n
     end
-    @info "Initialised $(n) $(speciesname)s."
+    @info "Initialised $(n) $(speciesof(species))s."
 end
 
 #XXX initpopulation with dispersal from an original source?
@@ -222,6 +232,17 @@ function nearby_animals(pos::Tuple{Int64,Int64}, model::SimulationModel;
     end
 end
 
+"""
+    countanimals(pos, model; radius=0, species="")
+
+Return the number of animals in the given radius around this position, optionally filtering
+by species.
+"""
+function countanimals(pos::Tuple{Int64,Int64}, model::SimulationModel;
+                      radius::Int64=0, species="") #XXX add type for species
+    length(nearby_animals(pos, model, radius=radius, species=species))
+end
+
 """
     neighbours(animal, model, radius=0, conspecifics=true)
 
diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl
index 0846dca8c2c359e8d7c198804675761bc975eff0..848858a767c37c2d8b06e0cad27f0245e1cacea4 100644
--- a/src/nature/species/skylark.jl
+++ b/src/nature/species/skylark.jl
@@ -32,34 +32,34 @@ At the moment, this implementation is still in development.
 @species Skylark begin
 
     #XXX use Unitful.jl
-    #XXX add type annotations
+    #TODO add type annotations
     
-    eggtime = 11 # 11 days from laying to hatching
-    eggpredationmortality = 0.03 # per-day egg mortality from predation
-    nestharvestmortality = 0.9 # egg/nestling mortality after a harvest event (XXX guess)
+    eggtime::Int64 = 11 # 11 days from laying to hatching
+    eggpredationmortality::Float64 = 0.03 # per-day egg mortality from predation
+    nestharvestmortality::Float64 = 0.9 # egg/nestling mortality after a harvest event (XXX guess)
 
     nestlingtime = 7:11 # 7-11 days from hatching to leaving nest
-    nestlingpredationmortality = 0.03 # per-day nestling mortality from predation
+    nestlingpredationmortality::Float64 = 0.03 # per-day nestling mortality from predation
 
     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
+    fledglingharvestmortality::Float64 = 0.5 # fledgling mortality after harvest
+    fledglingpredationmortality::Float64 = 0.01 # per-day fledgling mortality from predation
+    firstyearmortality::Float64 = 0.38 # total mortality in the first year after independence
     
     migrationdates = () # is defined by each individual in @create(Skylark)
-    migrationmortality = 0.33 # chance of dying during the winter
+    migrationmortality::Float64 = 0.33 # chance of dying during the winter
 
-    mate = -1 # the agent ID of the mate (-1 if none)
+    mate::Int64 = -1 # the agent ID of the mate (-1 if none)
     nest = () # coordinates of current nest
     nestingbegin = (April, 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
+    nestcompletion::Int64 = 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
-    nestingend = July # last month of nesting
+    breedingdelay::Int64 = 18 # wait 18 days after hatching to start a new brood
+    nestingend::Int64 = July # last month of nesting
     
-    habitats = skylarkhabitat
+    habitats::Function = skylarkhabitat
 end
 
 """
diff --git a/test/nature_tests.jl b/test/nature_tests.jl
index f23cba579f39e698116079b422c607a6e49b3aec..da1c81cbfc8ddc03fdb7e220188aaae7d800cfc8 100644
--- a/test/nature_tests.jl
+++ b/test/nature_tests.jl
@@ -5,58 +5,43 @@
 
 ## Test species definition
 
-#FIXME reactivate once Julia 1.11 is released and @species is fixed
-#(https://discourse.julialang.org/t/kwdef-constructor-not-available-outside-of-module/114675/4)
-# @species Mermaid begin
-#     ageofmaturity = 2
-#     pesticidemortality = 1.0
-# end
+# new species have to be defined within the Persefone module scope, otherwise things don't work
+Persefone.eval(quote
 
-#FIXME remove manual definition of Mermaid once Julia 1.11 is released (see above)
-@kwdef mutable struct Mermaid <: Animal
-    const id::Int64
-    const sex::Persefone.Sex = Persefone.hermaphrodite
-    const parents::Tuple{Int64,Int64} = (-1, -1)
-    pos::Tuple{Int64,Int64}
-    phase::Function = (self,model)->nothing
-    age::Int = 0
-    energy::Union{Persefone.EnergyBudget,Nothing} = nothing
-    offspring::Vector{Int64} = Vector{Int64}()
+@species Mermaid begin
     ageofmaturity = 2
     pesticidemortality = 1.0
 end
-Mermaid(id, sex, parents, pos, phase) =
-    Mermaid(id=id, sex=sex, parents=parents, pos=pos, phase=phase)
-Mermaid(id) = Mermaid(id=id, parents=(-1, -1), pos=(-1, -1))
 
 @create Mermaid begin
     @debug "Created $(animalid(self))."
 end
 
 @phase Mermaid life begin
-    @debug "$(Persefone.animalid(self)) is swimming happily in its pond."
-    @respond Persefone.pesticide @kill(self.pesticidemortality, "poisoning")
-    @respond Persefone.harvesting @setphase(drought)
-    @debug "Animal: $self"
-    if self.sex == Persefone.female && length(@neighbours()) < 3 &&
-        self.age >= self.ageofmaturity && @landcover() == Persefone.water
+    @debug "$(animalid(self)) is swimming happily in its pond."
+    @respond pesticide @kill(self.pesticidemortality, "poisoning")
+    @respond harvesting @setphase(drought)
+    if self.sex == female && length(@neighbours()) < 3 &&
+        self.age >= self.ageofmaturity && @landcover() == water
         @reproduce()
     end
 end
 
 @phase Mermaid drought begin
     n = sum(1 for a in @neighbours())
-    @debug "$(Persefone.animalid(self)) is experiencing drought with $n neighbour(s)."
-    @respond Persefone.sowing @setphase(life)
+    @debug "$(animalid(self)) is experiencing drought with $n neighbour(s)."
+    @respond sowing @setphase(life)
 end
 
 @populate Mermaid begin
     birthphase = life
     initphase = life
-    habitat = @habitat(@landcover() == Persefone.water)
+    habitat = @habitat(@landcover() == water)
     pairs=true
 end
 
+end) # end eval
+
 ## Test sets
 
 @testset "Habitat macros" begin
@@ -67,15 +52,16 @@ end
           FarmPlot(1, [(6,6)], model.crops["winter wheat"], Ps.janfirst,
                    0.0, 0.0, 0.0, 0.0, Vector{Ps.EventType}()))
     push!(model.animals,
-          Mermaid(1, Ps.male, (-1,-1), (3,3), life),
-          Mermaid(2, Ps.female, (-1,-1), (4,4), life))
+          Ps.Mermaid(1, Ps.male, (-1,-1), (3,3), Ps.life),
+          Ps.Mermaid(2, Ps.female, (-1,-1), (4,4), Ps.life))
     # create a set of habitat descriptors
     h1 = @habitat(@landcover() == Ps.water)
     h2 = @habitat(@cropname() == "winter wheat" &&
                   @cropheight() < 2)
     h3 = @habitat(@distanceto(Ps.water) > 2 &&
                   @distancetoedge() <= 2)
-    h4 = @habitat(length(@nearby_animals(radius=1)) == 1) #FIXME defining radius doesn't work
+    #FIXME nested macros don't work properly, counting seems wrong
+    h4 = @habitat(@countanimals(radius=1) == 1) 
     # test the descriptors
     @test h1((6,4), model) == true
     @test h1((5,4), model) == false
@@ -89,93 +75,97 @@ end
     @test_broken h4((1,1), model) == false
 end
 
-#FIXME the way initialisation works has completely changed...
-# @testset "Species initialisation" begin
-#     model = inittestmodel()
-#     spec = "test_animal"
-#     species::Dict{String,Any} = Dict("name"=>spec)
-#     # create a set of initialisation functions
-#     initfun1 = Ps.initrandompopulation(10)
-#     initfun2 = Ps.initrandompopulation(8*8*3, asexual=true)
-#     initfun3 = Ps.initpopulation(@habitat(@landcover() == Ps.grass), pairs=true)
-#     initfun4 = Ps.initpopulation(@habitat(@landcover() == Ps.water &&
-#                                           length(@neighbours(species="test_animal", radius=0)) < 5),
-#                                  popsize=10)
-#     # apply and test the initialisation functions
-#     @test_logs (:info, "Initialised 10 $(spec)s.") initfun1(species, model)
-#     @test all(a -> a.sex in (Ps.male, Ps.female), allagents(model))
-#     genocide!(model)
-#     @test_logs (:info, "Initialised 192 $(spec)s.") initfun2(species, model)
-#     @test Ps.countanimals((1,1), model, species=spec, radius=0) ==
-#         Ps.countanimals((6,6), model, species=spec, radius=0) == 3
-#     @test all(a -> a.sex == Ps.hermaphrodite, allagents(model))
-#     genocide!(model)
-#     @test_logs (:info, "Initialised 36 $(spec)s.") initfun3(species, model)
-#     @test Ps.countanimals((2,2), model, species=spec, radius=2) ==
-#         Ps.countanimals((5,3), model, species=spec, radius=1) == 0
-#     @test Ps.countanimals((5,5), model, species=spec, radius=0) ==
-#         Ps.countanimals((6,6), model, species=spec, radius=0) == 2
-#     a1, a2 = Ps.nearby_animals((6,6), model, 0)
-#     @test a1.sex != a2.sex
-#     genocide!(model)
-#     @test_logs((:warn, "There are not enough suitable locations for $(spec) in the landscape."),
-#                (:info, "Initialised 5 $(spec)s."),
-#                initfun4(species, model))
-#     @test Ps.countanimals((1,1), model, species=spec, radius=4) == 0
-#     @test Ps.countanimals((6,4), model, species=spec, radius=0) == 5
-# end
-
-#FIXME the way species work has completely changed...
-# @testset "Species macros" begin
-#     # create a model landscape and a test species
-#     model = inittestmodel()
-#     # test a complete mermaid life cycle
-#     pond = (6,4)
-#     mermaid = Mermaid(1)
-#     @test typeof(mermaid) <: Animal
-#     @test typeof(life) <: Function
-#     @test typeof(mermaid["initialise!"]) <: Function
-#     @test typeof(mermaid[mermaid["phase"]]) <: Function
-#     @test mermaid["phase"] == "life"
-#     @test_logs (:info, "Initialised 2 Mermaids.") mermaid["initialise!"](mermaid, model)
-#     @test Ps.countanimals((1,1), model, radius=4) == 0
-#     @test Ps.countanimals(pond, model) == 2
-#     @test model[1].age == 0
-#     createevent!(model, [pond], Ps.harvesting)
-#     @test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
-#                (:debug, "Mermaid 2 is swimming happily in its pond."),
-#                min_level=Logging.Debug, match_mode=:any,
-#                stepsimulation!(Ps.withtestlogger(model)))
-#     @test model[1].age == 1
-#     @test model[2].traits["phase"] == "drought"
-#     createevent!(model, [pond], Ps.sowing)
-#     @test_logs((:debug, "Mermaid 1 is experiencing drought with 1 neighbour(s)."),
-#                (:debug, "Mermaid 2 is experiencing drought with 1 neighbour(s)."),
-#                min_level=Logging.Debug, match_mode=:any,
-#                stepsimulation!(Ps.withtestlogger(model)))
-#     @test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
-#                (:debug, "Mermaid 1 has reproduced."),
-#                (:debug, "Mermaid 2 is swimming happily in its pond."),
-#                min_level=Logging.Debug, match_mode=:any,
-#                stepsimulation!(Ps.withtestlogger(model)))
-#     @test Ps.countanimals(pond, model) == 3
-#     createevent!(model, [pond], Ps.pesticide)
-#     @test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
-#                (:debug, "Mermaid 1 has died from poisoning."),
-#                (:debug, "Mermaid 2 is swimming happily in its pond."),
-#                (:debug, "Mermaid 2 has died from poisoning."),
-#                (:debug, "Mermaid 3 is swimming happily in its pond."),
-#                (:debug, "Mermaid 3 has died from poisoning."),
-#                min_level=Logging.Debug, match_mode=:any,
-#                stepsimulation!(Ps.withtestlogger(model)))
-#     @test Ps.countanimals(pond, model) == 0
-
-#     # test @rand (this is done more easily outside of @species)
-#     @test typeof(@rand()) == Float64
-#     @test @rand([true, true])
+@testset "Species initialisation" begin
+    model = inittestmodel()
+    # initialisation parameters, set 1
+    initparams1 = Ps.PopInitParams(birthphase = Ps.life, initphase = Ps.life, popsize = 10)
+    @test_logs((:info, "Initialised 10 Mermaids."),
+               Ps.initpopulation!(Ps.Mermaid, initparams1, model))
+    @test all(a -> a.sex in (Ps.male, Ps.female), model.animals)
+    Ps.killallanimals!(model)
+    # initialisation parameters, set 2
+    initparams2 = Ps.PopInitParams(birthphase = Ps.life, initphase = Ps.life,
+                                   popsize = 8*8*3, asexual=true)
+    @test_logs((:info, "Initialised 192 Mermaids."),
+               Ps.initpopulation!(Ps.Mermaid, initparams2, model))
+    @test Ps.countanimals((1,1), model) == Ps.countanimals((6,6), model) == 3
+    @test all(a -> a.sex == Ps.hermaphrodite, model.animals)
+    Ps.killallanimals!(model)
+    # initialisation parameters, set 3
+    initparams3 = Ps.PopInitParams(birthphase = Ps.life, initphase = Ps.life, pairs = true,
+                                   habitat = @habitat(@landcover() == Ps.grass))
+    @test_logs((:warn, "initpopulation!() called with popsize and popdensity both <= 0"),
+               (:info, "Initialised 36 Mermaids."),
+               Ps.initpopulation!(Ps.Mermaid, initparams3, model))
+    @test Ps.countanimals((2,2), model, radius=2) == Ps.countanimals((5,3), model, radius=1) == 0
+    @test Ps.countanimals((5,5), model) == Ps.countanimals((6,6), model) == 2
+    a1, a2 = Ps.nearby_animals((6,6), model, radius=0)
+    @test a1.sex != a2.sex
+    Ps.killallanimals!(model)
+    # initialisation parameters, set 4
+    initparams4 = Ps.PopInitParams(birthphase = Ps.life, initphase = Ps.life, popsize = 10,
+                                   habitat = @habitat(@landcover() == Ps.water &&
+                                                      @countanimals() < 5))
+    @test_logs((:warn, "There are not enough suitable locations for Mermaid in the landscape."),
+               (:info, "Initialised 5 Mermaids."),
+               Ps.initpopulation!(Ps.Mermaid, initparams4, model))
+    @test Ps.countanimals((1,1), model, radius=4) == 0
+    @test Ps.countanimals((6,4), model) == 5
+end
 
-#     #TODO test movement macros
-# end
+@testset "Species macros" begin
+    model = inittestmodel()
+    pond = (6,4)
+    # test individual initialisation
+    mermaid = Ps.Mermaid(1)
+    @test typeof(mermaid) <: Animal
+    @test typeof(Ps.life) <: Function
+    @test typeof(mermaid.phase) <: Function
+    @test_logs((:debug, "Created Mermaid 1."), min_level=Logging.Debug,
+               Ps.create!(mermaid, Ps.withtestlogger(model)))
+    # test population initialisation
+    @test_logs((:warn, "initpopulation!() called with popsize and popdensity both <= 0"),
+               (:info, "Initialised 2 Mermaids."),
+               Ps.initpopulation!("Mermaid", model))
+    @test Ps.nagents(model) == 2
+    @test Ps.countanimals((1,1), model, radius=4) == 0
+    @test Ps.countanimals(pond, model) == 2
+    @test model.animals[1].age == 0
+    # test event handling
+    createevent!(model, [pond], Ps.harvesting)
+    @test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
+               (:debug, "Mermaid 2 is swimming happily in its pond."),
+               min_level=Logging.Debug, match_mode=:any,
+               stepsimulation!(Ps.withtestlogger(model)))
+    @test model.animals[1].age == 1
+    @test model.animals[2].phase == Ps.drought
+    createevent!(model, [pond], Ps.sowing)
+    @test_logs((:debug, "Mermaid 1 is experiencing drought with 1 neighbour(s)."),
+               (:debug, "Mermaid 2 is experiencing drought with 1 neighbour(s)."),
+               min_level=Logging.Debug, match_mode=:any,
+               stepsimulation!(Ps.withtestlogger(model)))
+    @test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
+               (:debug, "Mermaid 2 is swimming happily in its pond."),
+               (:debug, "Mermaid 2 has reproduced."),
+               (:debug, "Created Mermaid 3."),
+               min_level=Logging.Debug, match_mode=:any,
+               stepsimulation!(Ps.withtestlogger(model)))
+    @test Ps.countanimals(pond, model) == 3
+    createevent!(model, [pond], Ps.pesticide)
+    @test_logs((:debug, "Mermaid 1 is swimming happily in its pond."),
+               (:debug, "Mermaid 1 has died from poisoning."),
+               (:debug, "Mermaid 2 is swimming happily in its pond."),
+               (:debug, "Mermaid 2 has died from poisoning."),
+               (:debug, "Mermaid 3 is swimming happily in its pond."),
+               (:debug, "Mermaid 3 has died from poisoning."),
+               min_level=Logging.Debug, match_mode=:any,
+               stepsimulation!(Ps.withtestlogger(model)))
+    @test Ps.countanimals(pond, model) == 0
+    # test @rand (this is done more easily outside of @species)
+    @test typeof(@rand()) == Float64
+    @test @rand([true, true])
+    #TODO test movement macros
+end
 
 @testset "Insect submodel" begin
     # create a set of pixels and dates for testing
@@ -231,7 +221,7 @@ end
                                   
 end
 
-#TODO test Wolpertinger/Wyvern?
+#XXX test Wolpertinger/Wyvern?
 
 @testset "Skylark submodel" begin
     # set up a modified test landscape
diff --git a/test/simulation_tests.jl b/test/simulation_tests.jl
index 5b4b42e1ae0b56ab53b7909445fa9cd373a5e301..c37209b312be7ae16312b6d270b142954bc09e34 100644
--- a/test/simulation_tests.jl
+++ b/test/simulation_tests.jl
@@ -12,7 +12,7 @@
     @test typeof(model.logger) == TeeLogger{Tuple{ConsoleLogger, ConsoleLogger}}
     @test length(model.dataoutputs) == 2
     @test model.events == Vector{FarmEvent}()
-    @test Ps.nagents(model) == 2092+10+28 #FIXME 2412 == 2130
+    @test Ps.nagents(model) == 2092+0+321 # farmplots+farmers+animals
 end
 
 @testset "Parameter scanning" begin