diff --git a/src/Persefone.jl b/src/Persefone.jl
index 509f728a8ca4304df5575f171becfa0dff3e77ee..0c76d61562e5f527422e3aa0d61649175edee976 100644
--- a/src/Persefone.jl
+++ b/src/Persefone.jl
@@ -82,8 +82,9 @@ include("world/weather.jl")
 include("farm/farm.jl")
 include("crop/crops.jl")
 
-include("nature/nature.jl")
 include("nature/insects.jl")
+include("nature/energy.jl")
+include("nature/nature.jl")
 include("nature/populations.jl")
 include("nature/ecologicaldata.jl")
 #TODO loop over files in nature/species directory
diff --git a/src/nature/energy.jl b/src/nature/energy.jl
new file mode 100644
index 0000000000000000000000000000000000000000..74937b9bf8ab6dd1f56576e236205743be3b6066
--- /dev/null
+++ b/src/nature/energy.jl
@@ -0,0 +1,122 @@
+### Persefone - a socio-economic-ecological model of European agricultural landscapes.
+###
+### This file contains structs and functions for implementing Dynamic Energy Budgets.
+###
+
+"""
+    DEBparameters
+
+An immutable struct to save the parameter list for a species' Dynamic Energy Budget
+model. (See Sousa et al., 2010.)
+"""
+struct DEBparameters
+    assimilation::Float64 # percentage of food biomass assimilated (y_EX)
+    kappa::Float64 # percentage allocated to somatic maintenance and growth (k)
+    juvenilesize::Float64 # structure size at which an embryo becomes a juvenile (MHb)
+    adultsize::Float64 # structure size at which a juvenile becomes an adult (MHp)
+    maintenance::Float64 # daily percentage of body size needed per update for somatic and maturity maintenance
+    reproduction::Float64 # energy needed to produce an egg (= initial amount of reserve)
+    v::Float64 # energy conductance (XXX ???)
+end
+
+"""
+    EnergyBudget
+
+This struct represents an individual's energy balance, as conceptualised by the
+Dynamic Energy Budget theory. Upon assimilation, energy is first stored as biomass
+in a reserve buffer, before being used for maintenance, growth, and reproduction.
+(Note that this is a simplified model form which ignores maturity as a separate buffer.)
+
+**Sources:**
+
+- Malishev & Kramer-Schadt (2021). Movement, models, and metabolism:
+Individual-based energy budget models as next-generation extensions
+for predicting animal movement outcomes across scales. Ecological
+Modelling, 441, 109413. https://doi.org/10.1016/j.ecolmodel.2020.109413
+- Marques et al. (2018). The AmP project: Comparing species on the basis
+of dynamic energy budget parameters. PLOS Computational Biology,
+14(5), e1006100. https://doi.org/10.1371/journal.pcbi.1006100
+- Sibly et al. (2013). Representing the acquisition and use of energy
+by individuals in agent-based models of animal populations. Methods in
+Ecology and Evolution, 4(2), 151–161. https://doi.org/10.1111/2041-210x.12002
+- Sousa et al. (2010). Dynamic energy budget theory restores coherence
+in biology. Philosophical Transactions of the Royal Society B: Biological
+Sciences, 365(1557), 3413–3428. https://doi.org/10.1098/rstb.2010.0166
+- Kooijman, S. A. L. M. (2009). Dynamic energy and mass budgets
+in biological systems (3rd ed). Cambridge University Press.
+https://www.researchgate.net/profile/Edgar-Meza-3/post/Is_there_a_toxicokinetic_model_for_daphnia_magna_or_other_zooplankton/attachment/59d62cf579197b807798b396/AS%3A348547653357569%401460111644286/download/Dynamic+Energy+Budget+theory+-+Kooijman.pdf
+- *see also:* Brown et al. (2004). Toward a metabolic theory of ecology.
+Ecology, 85(7), 1771–1789. https://doi.org/10.1890/03-9000
+"""
+mutable struct EnergyBudget
+    params::DEBparameters
+    reserve::Float64
+    structure::Float64
+    offspring::Float64
+end
+
+"""
+    biomass(energybudget)
+
+Calculate the total biomass of an individual based on its energy budget.
+(Sum of the reserve, structure, and offspring buffers; Sibly et al. 2013.)
+"""
+function biomass(deb::EnergyBudget)
+    deb.reserve + deb.structure + deb.offspring
+end
+
+"""
+    feed!(biomass, energybudget)
+
+Consume a given quantity of food. Expands the energy reserve by an
+amount determined by the assimilation rate.
+"""
+function feed!(biomass::Float64, deb::EnergyBudget)
+    deb.reserve += biomass * deb.params.assimilation
+    #TODO reserve doesn't grow infinitely
+end
+
+"""
+    update!(energybudget)
+
+Carry out a daily update of the energy budget. Mobilises reserves and allocates
+these to maintenance (prioritised), growth, and reproduction.
+
+Return `true` if the individual has enough energy to survive, or `false` if the
+reserve is empty and it starves.
+"""
+function update!(deb::EnergyBudget)
+    #TODO
+    mobilisation = 0
+    somaticmaintenance = deb.params.maintenance # J_ES
+    maturitymaintenance = deb.params.maintenance # J_EJ
+    # juveniles grow until they reach adult size
+    if deb.structure < adultsize
+        growth = (mobilisation*deb.params.kappa) - somaticmaintenance
+        deb.structure += growth
+    end
+    # juveniles increase in maturity, adults invest into reproduction
+    maturityreproduction = (mobilisation*(1-deb.params.kappa)) - maturitymaintenance
+    if deb.structure >= adultsize
+        deb.offspring += maturityreproduction
+    end
+    
+    deb.reserve -= mobilisation
+    return deb.reserve > 0
+end
+
+"""
+    reproduce!(energybudget)
+
+If there is sufficient energy in the `offspring` buffer of an adult, produce an
+embryo/egg, reducing the parent energy in the process. Returns the embryo's energy
+budget, or `nothing` if the conditions are not met.
+"""
+function reproduce!(deb::EnergyBudget)
+    if deb.structure >= deb.params.adultsize && deb.offspring >= deb.params.reproduction
+        deb.offspring -= deb.params.reproduction
+        return EnergyBudget(deb.params, deb.params.reproduction, 0.0, 0.0)
+    else
+        return nothing
+    end
+end
diff --git a/src/nature/insects.jl b/src/nature/insects.jl
index c0fd291b6ab1c10f0421751bef6d31d5f080506e..743bbf2c722546e1e5d3feae0a089c96fc97c35b 100644
--- a/src/nature/insects.jl
+++ b/src/nature/insects.jl
@@ -1,21 +1,19 @@
 ### Persefone - a socio-economic-ecological model of European agricultural landscapes.
 ###
-### This file contains submodel that calculates insect biomass
+### This file contains the submodel that calculates insect biomass
 ###
 
-##TODO write tests for this
-
 """
     insectbiomass(pixel, model)
 
 Calculate the insect biomass in this location, using the factors configured
-in the `nature.insectmodel` settings (any of: "season", "habitat", "weather",
-"pesticides"). Returns a float value in mg/m².
+in the `nature.insectmodel` settings (any combination of: "season", "habitat",
+"weather", "pesticides"). Returns a float value in g/m².
 
 **Biological note:** this is a very approximate calculation! Insect biomass
 varies wildly in time and space and is hard to measure. This calculation is
 based on the idea of a parabolic seasonal development of insect abundance,
-modified habitat suitability, weather, and pesticide application. Although it
+modified by habitat suitability, weather, and pesticide application. Although it
 is based on empirical studies, it can only deliver a rough, order-of-magnitude
 estimation of likely insect biomass in a given location.
 
@@ -35,9 +33,9 @@ farmland. Ecology and Evolution, 12(1), e8461. https://doi.org/10.1002/ece3.8461
 """
 function insectbiomass(pixel::Pixel, model::AgentBasedModel)::Float64
 
-    ## if no factors are configure, insect abundance defaults to 300 mg/m²,
+    ## if no factors are configured, insect abundance defaults to 300 mg/m²,
     ## a value in the upper range of insect biomass density in agricultural landscapes
-    baseline = 300.0
+    baseline = 300
     seasonfactor = 0.0
     habitatfactor = 1.0
     weatherfactor = 1.0
@@ -68,6 +66,7 @@ function insectbiomass(pixel::Pixel, model::AgentBasedModel)::Float64
     ## based on fig. 3b in Paquette et al. (2013)
     ##XXX (and possibly table 3 in Grübler et al. (2008))
     if "weather" in @param(nature.insectmodel)
+        @warn "Weather effects on insect biomass are not yet implemented."
         #TODO add this once we have a working weather module
     end
 
@@ -81,5 +80,5 @@ function insectbiomass(pixel::Pixel, model::AgentBasedModel)::Float64
 
     ## calculate biomass using a parabolic equation in the vertex form
     biomass = seasonfactor+baseline*habitatfactor*pesticidefactor*weatherfactor
-    biomass > 0 ? biomass : 0.0
+    biomass > 0 ? biomass/1000 : 0.0 # convert mg to g
 end
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index 3d270bbb3d5bd3278b6bd3724bd962ccc7443673..ae58eb1ba5caf39e2fc8c8008cf797a781ef5b67 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -24,10 +24,15 @@ i.e. the trait `phase` can be accessed and modified with `animal.phase`.)
     # to deepcopy the speciesdict.
     #XXX add `phase` and `species` (rather than `name`) directly to the struct?
     traits::Dict{String,Any}
+    energy::Union{EnergyBudget,Nothing} # DEB is optional
     sex::Sex
     age::Int
 end
 
+# DEB is optional
+Animal(id::Int64, pos::Tuple{Int64,Int64}, traits::Dict{String,Any},
+       sex::Sex, age::Int) = Animal(id, pos, traits, nothing, sex, age)
+
 # Overloading the `getproperty` and `setproperty!` methods for `Animal` allows
 # us to write `animal.property` instead of `animal.traits["property"]`.
 # (This is inspired by Agents.jl in `model.jl`.)
diff --git a/test/nature_tests.jl b/test/nature_tests.jl
index 7495004731dab9a5838facceaddd29d112ff3b9c..cf56846428ec336d4138d29b103477753177bf15 100644
--- a/test/nature_tests.jl
+++ b/test/nature_tests.jl
@@ -151,24 +151,24 @@ end
     p6 = Pixel(Ps.water, 1, [])
     # check whether the model calculates the same numbers I did by hand
     model.date = date1
-    @test Ps.insectbiomass(p1, model) ≈ 4.11 atol=0.01
+    @test Ps.insectbiomass(p1, model) ≈ 0.00411 atol=0.0001
     @test Ps.insectbiomass(p2, model) == 0.0
-    @test Ps.insectbiomass(p3, model) ≈ 154.11 atol=0.01
-    @test Ps.insectbiomass(p4, model) ≈ 4.11 atol=0.01
-    @test Ps.insectbiomass(p5, model) ≈ 304.11 atol=0.01
+    @test Ps.insectbiomass(p3, model) ≈ 0.15411 atol=0.0001
+    @test Ps.insectbiomass(p4, model) ≈ 0.00411 atol=0.0001
+    @test Ps.insectbiomass(p5, model) ≈ 0.30411 atol=0.0001
     @test Ps.insectbiomass(p6, model) == 0.0
     model.date = date2
-    @test Ps.insectbiomass(p1, model) == 300.0
-    @test Ps.insectbiomass(p2, model) == 150.0
-    @test Ps.insectbiomass(p3, model) == 450.0
-    @test Ps.insectbiomass(p4, model) == 300.0
-    @test Ps.insectbiomass(p5, model) == 600.0
+    @test Ps.insectbiomass(p1, model) == 0.3
+    @test Ps.insectbiomass(p2, model) == 0.15
+    @test Ps.insectbiomass(p3, model) == 0.45
+    @test Ps.insectbiomass(p4, model) == 0.3
+    @test Ps.insectbiomass(p5, model) == 0.6
     @test Ps.insectbiomass(p6, model) == 0.0
     model.date = date3
     @test Ps.insectbiomass(p1, model) == 0.0
     @test Ps.insectbiomass(p2, model) == 0.0
     @test Ps.insectbiomass(p3, model) == 0.0
     @test Ps.insectbiomass(p4, model) == 0.0
-    @test Ps.insectbiomass(p5, model) ≈ 14.43 atol=0.01
+    @test Ps.insectbiomass(p5, model) ≈ 0.01443 atol=0.0001
     @test Ps.insectbiomass(p6, model) == 0.0
 end