diff --git a/src/Persefone.jl b/src/Persefone.jl
index aae3d8d88a8f49c65d5170128eae1d9bc75388e0..37390f4b10628bec68e67a757899c7788af0160d 100644
--- a/src/Persefone.jl
+++ b/src/Persefone.jl
@@ -129,7 +129,7 @@ include("nature/ecologicaldata.jl")
 # end
 #include("nature/species/skylark.jl")
 include("nature/species/wolpertinger.jl")
-#include("nature/species/wyvern.jl")
+include("nature/species/wyvern.jl")
 
 include("core/simulation.jl") #this must be last
 
diff --git a/src/core/simulation.jl b/src/core/simulation.jl
index 7ec91bc95c3ad951f99d329745fbe8f35d2ec15d..c9ed15a2f3ea73ce6928aeaec588e506f9307a28 100644
--- a/src/core/simulation.jl
+++ b/src/core/simulation.jl
@@ -188,7 +188,7 @@ function finalise!(model::SimulationModel)
     with_logger(model.logger) do
         @info "Simulated $(model.date-@param(core.startdate))."
         @info "Simulation run completed at $(Dates.now()),\nwrote output to $(@param(core.outdir))."
-        #@param(core.visualise) && visualiseoutput(model) #FIXME
+        @param(core.visualise) && visualiseoutput(model)
         model
     end
 end
diff --git a/src/nature/macros.jl b/src/nature/macros.jl
index fd8242f50b5cf0ea9da4ba98653d15b948521d71..9d768e6345b9d0596ab03b0f900d1bc2dedd77bd 100644
--- a/src/nature/macros.jl
+++ b/src/nature/macros.jl
@@ -210,6 +210,16 @@ macro kill(args...)
     :(kill!($(esc(:self)), $(esc(:model)), $(map(esc, args)...)) && return)
 end
 
+"""
+    @killother
+
+Kill another animal. This is a thin wrapper around [`kill!`](@ref), and passes on
+any arguments. This can only be used nested within [`@phase`](@ref).
+"""
+macro killother(animal, args...)
+    :(kill!($(esc(animal)), $(esc(:model)), $(map(esc, args)...)) && return)
+end
+
 """
     @reproduce
 
@@ -310,14 +320,25 @@ macro cropheight()
 end
 
 """
-    @distanceto(habitat)
+    @directionto
 
-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)
+Calculate the direction to an animal or 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 directionto(target)
+    :(directionto($(esc(:pos)), $(esc(:model)), $(esc(target))))
+end
+
+"""
+    @distanceto
+
+Calculate the distance to an animal or 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))))
+macro distanceto(target)
+    :(distanceto($(esc(:pos)), $(esc(:model)), $(esc(target))))
 end
 
 """
@@ -344,10 +365,20 @@ macro randompixel(args...)
 end
 
 """
-    @neighbours(radius=0)
+    @randomdirection(range=1)
+
+Return a random direction tuple that can be passed to [`@walk`](@ref).
+This is a utility wrapper that can only be used nested within [`@phase`](@ref).
+"""
+macro randomdirection(args...)
+    :(randomdirection($(esc(:model)), $(map(esc, args)...)))
+end
+
+"""
+    @neighbours(radius=0, conspecifics=true)
 
-Return an iterator over all conspecific animals in the given radius around this animal,
-excluding itself. This can only be used nested within [`@phase`](@ref).
+Return an iterator over all (by default conspecific) animals in the given radius around
+this animal, excluding itself. This can only be used nested within [`@phase`](@ref).
 """
 macro neighbours(args...)
     :(neighbours($(esc(:self)), $(esc(:model)), $(map(esc, args)...)))
@@ -366,16 +397,16 @@ macro move(position)
 end
 
 """
-    @walk(direction)
+    @walk(direction, speed)
 
 Walk the animal in a given direction, which is specified by a tuple of coordinates
 relative to the animal's current position (i.e. `(2, -3)` increments the X coordinate
 by 2 and decrements the Y coordinate by 3.) This is a utility wrapper that can only be
 used nested within [`@phase`](@ref).
 """
-macro walk(direction)
+macro walk(args...)
     #XXX add `ifempty` keyword?
-    :(walk!($(esc(:self)), $(esc(:model)), $(esc(direction))))
+    :(walk!($(esc(:self)), $(esc(:model)), $(map(esc, args)...)))
 end
 
 """
diff --git a/src/nature/populations.jl b/src/nature/populations.jl
index 98cece638caffe4d7bcc150739e862dcc2128377..44fa2b7ceab11c6922aec161d2c6b921a2f6a093 100644
--- a/src/nature/populations.jl
+++ b/src/nature/populations.jl
@@ -133,7 +133,7 @@ function reproduce!(animal::Animal, model::SimulationModel,
 end
 
 """
-    kill(animal, model, probability=1.0, cause="")
+    kill!(animal, model, probability=1.0, cause="")
 
 Kill this animal, optionally with a given percentage probability.
 Returns true if the animal dies, false if not.
@@ -214,13 +214,35 @@ function nearby_animals(pos::Tuple{Int64,Int64}, model::SimulationModel;
 end
 
 """
-    neighbours(animal, model, radius=0)
+    neighbours(animal, model, radius=0, conspecifics=true)
 
-Return a list of conspecific animals in the given radius around this animal, excluding itself.
+Return a list of animals in the given radius around this animal, excluding itself. By default,
+only return conspecific animals.
 """
-function neighbours(animal::Animal, model::SimulationModel, radius::Int64=0)
+function neighbours(animal::Animal, model::SimulationModel, radius::Int64=0, conspecifics=true)
     filter(a -> a.id != animal.id,
-           nearby_animals(animal.pos, model, radius=radius, species=speciesof(animal)))
+           nearby_animals(animal.pos, model, radius = radius,
+                          species = conspecifics ? speciesof(animal) : ""))
+end
+
+"""
+    directionto(pos, model, animal)
+
+Calculate the direction from the given position to the animal.
+"""
+function directionto(pos::Tuple{Int64,Int64}, model::SimulationModel, animal::Animal)
+    # have to use a coordinate as first argument rather than an animal because of @directionto
+    animal.pos - pos
+end
+
+"""
+    distanceto(pos, model, animal)
+
+Calculate the distance from the given position to the animal.
+"""
+function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, animal::Animal)
+    # have to use a coordinate as first argument rather than an animal because of @distanceto
+    maximum(abs.(animal.pos - pos))
 end
 
 """
@@ -233,7 +255,7 @@ function followanimal!(follower::Animal, leader::Animal, model::SimulationModel,
     #TODO test function
     spacing = Tuple(@rand(-distance:distance, 2))
     targetposition = safebounds(spacing .+ leader.pos, model)
-    move_agent!(follower, targetposition, model)
+    move!(follower, model, targetposition)
 end
 
 """
@@ -282,16 +304,23 @@ function walk!(animal::Animal, model::SimulationModel, direction::String)
 end
 
 """
-    walk!(animal, model, direction)
+    walk!(animal, model, direction, speed=-1)
 
 Let the animal move in the given direction, where the direction is
 defined by an (x, y) tuple to specify the shift in coordinates.
+If speed >= 0, walk no more than the given number of cells.
 """
-function walk!(animal::Animal, model::SimulationModel, direction::Tuple{Int64,Int64})
-    move!(animal, model, animal.pos .+ direction)
+function walk!(animal::Animal, model::SimulationModel, direction::Tuple{Int64,Int64}, speed::Int64=-1)
+    #TODO add a habitat descriptor?
+    if speed >= 0
+        direction[1] > speed && (direction[1] = speed)
+        direction[2] > speed && (direction[2] = speed)
+        direction[1] < -speed && (direction[1] = -speed)
+        direction[2] < -speed && (direction[2] = -speed)
+    end
+    newpos = animal.pos .+ direction
+    move!(animal, model, newpos)
 end
 
-
-
 ##TODO add random walk with habitat descriptor
 ##TODO add walktoward or similar function (incl. pathfinding?)
diff --git a/src/world/landscape.jl b/src/world/landscape.jl
index bd9af0faf3d1265e0c0c126e0401057ec163f442..1a9d09308c6ac706263020b688553a377481ccf7 100644
--- a/src/world/landscape.jl
+++ b/src/world/landscape.jl
@@ -32,7 +32,7 @@ A data structure to define a landscape event, giving its type,
 spatial extent, and duration.
 """
 mutable struct FarmEvent
-    type::EventType
+    etype::EventType
     pixels::Vector{Tuple{Int64,Int64}}
     duration::Int64
 end
@@ -80,7 +80,7 @@ function updateevents!(model::SimulationModel)
         if event.duration <= 0
             push!(expiredevents, e)
             for p in event.pixels
-                i = findnext(x -> x == event.type, model.landscape[p...].events, 1)
+                i = findnext(x -> x == event.etype, model.landscape[p...].events, 1)
                 deleteat!(model.landscape[p...].events, i)
             end
         end
@@ -116,20 +116,23 @@ end
 Return the farm plot at this position, or nothing if there is none (utility wrapper).
 """
 function farmplot(pos::Tuple{Int64,Int64}, model::SimulationModel)
-    ismissing(model.landscape[pos...].fieldid) ? nothing :
-              model[model.landscape[pos...].fieldid]
+    if ismissing(model.landscape[pos...].fieldid)
+        nothing
+    else
+        model[model.landscape[pos...].fieldid]
+    end
 end
 
-
 """
-    distanceto(pos, model, habitatdescriptor)
+    directionto(pos, model, habitatdescriptor)
 
-Calculate the distance from the given location to the closest location matching the
-habitat descriptor function. Caution: can be computationally expensive!
+Calculate the direction from the given location to the closest location matching the
+habitat descriptor function. Returns a coordinate tuple (target - position), or nothing
+if no matching habitat is found. Caution: can be computationally expensive!
 """
-function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdescriptor::Function)
-    #XXX allow testing for multiple habitat types?
-    (habitatdescriptor(pos, model)) && (return 0)
+function directionto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdescriptor::Function)
+    #TODO test
+    (habitatdescriptor(pos, model)) && (return (0,0))
     dist = 1
     width, height = size(model.landscape)
     while dist <= width || dist <= height
@@ -139,8 +142,8 @@ function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdesc
         minx = maximum((pos[1]-dist, 1))
         maxx = minimum((pos[1]+dist, width))
         for x in minx:maxx
-            (y1 > 0 && habitatdescriptor((x,y1), model)) && (return dist)
-            (y2 <= height && habitatdescriptor((x,y2), model)) && (return dist)
+            (y1 > 0 && habitatdescriptor((x,y1), model)) && (return (x, y1))
+            (y2 <= height && habitatdescriptor((x,y2), model)) && (return (x, y2))
         end
         # check the left and right bounds of the enclosing rectangle
         x1 = pos[1] - dist
@@ -148,12 +151,25 @@ function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdesc
         miny = maximum((pos[2]-dist+1, 1))
         maxy = minimum((pos[2]+dist-1, height))
         for y in miny:maxy
-            (x1 > 0 && habitatdescriptor((x1,y), model)) && (return dist)
-            (x2 <= width && habitatdescriptor((x2,y), model)) && (return dist)
+            (x1 > 0 && habitatdescriptor((x1,y), model)) && (return (x1,y))
+            (x2 <= width && habitatdescriptor((x2,y), model)) && (return (x2,y))
         end
         dist += 1
     end
-    return Inf
+    return nothing
+end
+
+"""
+    distanceto(pos, model, habitatdescriptor)
+
+Calculate the distance from the given location to the closest location matching the
+habitat descriptor function. Caution: can be computationally expensive!
+"""
+function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdescriptor::Function)
+    #TODO test
+    target = directionto(pos, model, habitatdescriptor)
+    isnothing(target) && return Inf
+    return maximum(abs.(target-pos))
 end
 
 """
@@ -195,6 +211,15 @@ function randompixel(pos::Tuple{Int64,Int64}, model::SimulationModel, range::Int
     nothing
 end
 
+"""
+    randomdirection(model, range=1)
+
+Get a random direction coordinate tuple within the specified range.
+"""
+function randomdirection(model::SimulationModel, range::Int64=1)
+    Tuple(@rand(-range:range, 2))
+end
+
 """
     inbounds(pos, model)