diff --git a/src/analysis/makieplots.jl b/src/analysis/makieplots.jl
index 30cacefedaa00499f0999144024df95f5aad39da..70ba8ce93fbeab50d95d77cec4c3be01d910ebe5 100644
--- a/src/analysis/makieplots.jl
+++ b/src/analysis/makieplots.jl
@@ -59,7 +59,7 @@ function populationtrends(model::SimulationModel)
     update_theme!(palette=(color=cgrad(:seaborn_bright, ncolors),), cycle=[:color])
     f = Figure()
     dates = @param(core.startdate):@param(core.enddate)
-    axlimits = (1, length(dates), 0, maximum(pops[!,:Abundance]))
+    axlimits = (1, length(dates), 0, maximum(pops[!,:Abundance])*1.1)
     ax = Axis(f[1,1], xlabel="Date", ylabel="Population size",
               limits=axlimits, xticks=datetickmarks(dates))
     for s in @param(nature.targetspecies)
@@ -133,10 +133,10 @@ Returns a Makie figure object.
 """
 function croptrends(model::SimulationModel)
     cropdata = @data("fields")
-    f = Figure(size=(1000,1000))
+    f = Figure(size=(1200,1000))
     dates = @param(core.startdate):@param(core.enddate)
     # plot cropped area over time
-    axlimitsarea = (1, length(dates), 0, maximum(cropdata[!,:Area])*1.2)
+    axlimitsarea = (1, length(dates), 0, maximum(cropdata[!,:Area])*1.1)
     ax1 = Axis(f[1,1], xlabel="Date", ylabel="Cropped area (ha)",
               limits=axlimitsarea, xticks=datetickmarks(dates))
     for c in unique(cropdata.Crop)
@@ -144,16 +144,17 @@ function croptrends(model::SimulationModel)
         (iszero(size(points)[1]) || iszero(sum(points.Area))) && continue
         lines!(f[1,1], Vector{Float32}(points.Area), linewidth=3, label=c)
     end
-    axislegend("Crop"; position=:lt)
+    axislegend("Crop"; position=:rt)
     # plot average crop height over time
-    axlimitsheight = (1, length(dates), 0, maximum(cropdata[!,:Height])*1.2)
+    axlimitsheight = (1, length(dates), 0, maximum(cropdata[!,:Height])*1.1)
     ax2 = Axis(f[2,1], xlabel="Date", ylabel="Average plant height (cm)",
               limits=axlimitsheight, xticks=datetickmarks(dates))
     for c in unique(cropdata.Crop)
         points = @select!(@subset(cropdata, :Crop .== c), :Height)
-        (iszero(size(points)[1]) || iszero(sum(points.Height))) && continue
+        (iszero(size(points)[1]) || iszero(sum(points.Height))) && c != "no growth" && continue
         lines!(f[2,1], Vector{Float32}(points.Height), linewidth=3, label=c)
     end
+    axislegend("Crop"; position=:rt)
     f
 end
 
diff --git a/src/crop/almass.jl b/src/crop/almass.jl
index c6fc177748a9bd30ea914f6aaf171d0dcb98750c..891cbcf828276423c4f55a14dbefa9b4dbd5590b 100644
--- a/src/crop/almass.jl
+++ b/src/crop/almass.jl
@@ -3,7 +3,11 @@
 ### This file is responsible for managing the crop growth modules.
 ###
 
-#TODO write tests for input functions
+#FIXME Looking at fields.pdf in the Persefone output, crop maturity is not recognised
+# properly, so most crops (except maize) are never planted. At the same time, maize never
+# seems to start growing.
+
+#TODO write tests to compare our output to the output of the original ALMaSS algorithm
 
 module ALMaSS
 
@@ -26,7 +30,8 @@ import Persefone:
     cropyield,
     sow!,
     harvest!,
-    isharvestable
+    isharvestable,
+    bounds
 
 using Dates: Date, month, monthday
 using CSV: CSV
@@ -92,7 +97,7 @@ mutable struct CropState
     LAIgreen::Float64
     #biomass::Float64 #XXX I need to figure out how to calculate this
     mature::Bool #TODO how do we determine this?
-    events::Vector{Management}
+    events::Vector{Management} #FIXME does this do anything?
 end
 
 croptype(cs::CropState) = cs.croptype
@@ -180,6 +185,14 @@ end
 Update a farm plot by one day.
 """
 function stepagent!(cs::CropState, model::SimulationModel)
+    # update the phase on key dates
+    if monthday(model.date) == (1, 1)
+        cs.phase = ALMaSS.janfirst
+        cs.growingdegreedays = 0.0
+    elseif monthday(model.date) == (3, 1)
+        cs.phase = ALMaSS.marchfirst
+        cs.growingdegreedays = 0.0
+    end
     # update growing degree days
     # if no crop-specific base temperature is given, default to 5°C
     # (https://www.eea.europa.eu/publications/europes-changing-climate-hazards-1/heat-and-cold/heat-and-cold-2014-mean)
@@ -187,11 +200,8 @@ function stepagent!(cs::CropState, model::SimulationModel)
     ismissing(basetemp) && (basetemp = 5.0)
     gdd = (maxtemp(model)+mintemp(model))/2 - basetemp
     gdd > 0 && (cs.growingdegreedays += gdd)
-    # update the phase on key dates
-    monthday(model.date) == (1,1) && (cs.phase = ALMaSS.janfirst)
-    monthday(model.date) == (3,1) && (cs.phase = ALMaSS.marchfirst)
     # update crop growth
-    growcrop!(cs, model)
+    growcrop!(cs, gdd, model)
 end
 
 
@@ -203,9 +213,16 @@ end
 Change the cropstate to sow the specified crop.
 """
 function sow!(cs::CropState, model::SimulationModel, cropname::String)
-    #XXX test if the crop is sowable?
+    !ismissing(cs.croptype.minsowdate) && model.date < cs.croptype.minsowdate &&
+        @warn "$(model.date) is earlier than the minimum sowing date for $(cropname)."
     cs.croptype = model.crops[cropname]
     cs.phase = ALMaSS.sow
+    cs.growingdegreedays = 0.0
+    cs.height = 0.0cm
+    cs.LAItotal = 0.0
+    cs.LAIgreen = 0.0
+    cs.mature = false
+    cs.events = Vector{Management}()
 end
 
 """
@@ -217,6 +234,8 @@ function harvest!(cs::CropState, model::SimulationModel)
     cs.phase in [ALMaSS.harvest1, ALMaSS.harvest2] ?
         cs.phase = ALMaSS.harvest2 :
         cs.phase = ALMaSS.harvest1
+    cs.growingdegreedays = 0.0
+    cs.mature = false
     # height & LAI will be automatically adjusted by the growth function
     #TODO calculate and return yield
 end
@@ -226,17 +245,18 @@ end
 #TODO till!()
 
 """
-    growcrop!(cropstate, model)
+    growcrop!(cropstate, gdd, model)
 
 Apply the relevant crop growth model to update the plants crop state
 on this farm plot.  Implements the ALMaSS crop growth model by Topping
-et al.
+et al. (see `ALMaSS/Landscape/plant.cpp:PlantGrowthData::FindDiff()` and
+`ALMaSS/Landscape/elements.cpp:VegElement::DoDevelopment()`).
 """
-function growcrop!(cs::CropState, model::SimulationModel)
+function growcrop!(cs::CropState, gdd::Float64, model::SimulationModel)
     fertiliser in cs.events ?
         curve = cs.croptype.lownutrientgrowth :
         curve = cs.croptype.highnutrientgrowth
-    points = curve.GDD[cs.phase]
+    points = curve.GDD[cs.phase] #FIXME what if the curve is empty?
     for p in 1:length(points)
         if points[p] == 99999
             !(cs.phase in (janfirst, sow)) && (cs.mature = true) #FIXME only in the last phase?
@@ -250,9 +270,14 @@ function growcrop!(cs::CropState, model::SimulationModel)
             gdd = cs.growingdegreedays
             # figure out which is the correct slope value to use for growth
             if p == length(points) || gdd < points[p+1]
-                cs.height += curve.height[cs.phase][p]
-                cs.LAItotal += curve.LAItotal[cs.phase][p]
-                cs.LAIgreen += curve.LAIgreen[cs.phase][p]
+                #FIXME it appears to me from the ALMaSS source code that the curve value
+                # for the day is multiplied with that day's growingdegreedays to give the
+                # diff, which is then added to the previous values. However, this results
+                # in values that are staggeringly too large. I'm not quite sure what the
+                # issue is here...
+                cs.height += bounds(curve.height[cs.phase][p]*gdd)
+                cs.LAItotal += bounds(curve.LAItotal[cs.phase][p]*gdd)
+                cs.LAIgreen += bounds(curve.LAIgreen[cs.phase][p]*gdd)
                 return
             end
             #XXX To be precise, we ought to figure out if one or more inflection
diff --git a/src/crop/cropmodels.jl b/src/crop/cropmodels.jl
index aef409f0f531326deaa706dcb58b8303bf713c71..22d6b1470849aa46b922d82ec930e97018eadfec 100644
--- a/src/crop/cropmodels.jl
+++ b/src/crop/cropmodels.jl
@@ -66,7 +66,7 @@ function makecropstate(model::SimulationModel)
         cs = ALMaSS.CropState(
                 model.crops["natural grass"],
                 phase,
-                0.0, 0.0m, 0.0, 0.0, false, Vector{Management}()
+                0.0, 0.0cm, 0.0, 0.0, false, Vector{Management}()
         )
     elseif @param(crop.cropmodel) == "simple"
         cs = SimpleCrop.CropState(
diff --git a/src/parameters.toml b/src/parameters.toml
index 9a5920f8b3c84e0e4ae9c7d25e7aea14b18062f4..484c1a9b02d4245f7e48b40ccfdf9de1f5221895 100644
--- a/src/parameters.toml
+++ b/src/parameters.toml
@@ -15,12 +15,12 @@ csvoutput = true # save collected data in CSV files
 visualise = true # generate result graphs
 storedata = true # keep collected data in memory
 figureformat = "pdf" # file format to use for graphical output
-loglevel = "info" # verbosity level: "debug", "info", "warn"
+loglevel = "debug" # verbosity level: "debug", "info", "warn"
 processors = 2 # number of processors to use on parallel runs
 seed = 2 # seed value for the RNG (0 -> random value)
-startdate = 2022-01-01 # first day of the simulation
-enddate = 2022-12-31 # last day of the simulation
-#enddate = 2022-03-31 # last day of the simulation (test value)
+startdate = 2021-01-01 # first day of the simulation
+enddate = 2023-12-31 # last day of the simulation
+#enddate = 2022-01-02 # last day of the simulation (test value)
 
 [world]
 mapdirectory = "data/regions/jena-small" # the directory in which all geographic data are stored
@@ -30,14 +30,18 @@ farmfieldsmap = "fields.tif" # name of the field geometry map in the map directo
 weatherfile = "weather.csv" # name of the weather data file in the map directory
 	
 [farm]
-farmmodel = "FieldManager" # which version of the farm model to use (not yet implemented)
+farmmodel = "BasicFarmer" # which version of the farm model to use
 setaside = 0.04 # proportion of farm area set aside as fallow
+fieldoutfreq = "daily" # output frequency for crop/field data, daily/monthly/yearly/end/never
 	
 [nature]
 #targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate - example species
-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
+targetspecies = [] # XXX disable all species for farm model testing
+#targetspecies = ["Skylark"] # list of target species to simulate
+popoutfreq = "never" # output frequency population-level data, daily/monthly/yearly/end/never
+indoutfreq = "never" # output frequency individual-level data, daily/monthly/yearly/end/never
+#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
 	
 [crop]