diff --git a/src/core/input.jl b/src/core/input.jl
index cb1c9f9297011868862425f17e9ebcb9de3e982b..ada3f58551cef1441004b4874663cf3e0d243116 100644
--- a/src/core/input.jl
+++ b/src/core/input.jl
@@ -6,55 +6,32 @@
 ## Note: much of this code was adapted from the GeMM model by Leidinger et al.
 ## (https://github.com/CCTB-Ecomods/gemm/blob/master/src/input.jl)
 
-##XXX Does it make sense to move the settings dict into the model object itself?
-## (It could be integrated into the properties dict.) Advantage: we have less
-## global state lying around, and serialising becomes easier (if we want to do
-## that). Also, during testing we often need to change individual parameters,
-## which is currently impossible. Disadvantage: immutability of parameters at
-## runtime is intended, and some start-up functions may have problems if they need
-## access to parameters before the model object has been created.
-##TODO -> yes, changing this is probably sensible
-let settings::Dict{String, Dict{String, Any}}
-    """
-        initsettings(configfile)
-
-    Initialise the global model settings for this run.
-    """
-    global function initsettings(configfile::String)
-        settings = getsettings(configfile)
-    end
-    
-    """
-        param(domainparam)
-
-    Return a configuration parameter from the global settings.
-    The argument should be in the form `"<domain>.<parameter>"`,
-    for example `param("core.outdir")`. Possible values for
-    <domain> are "core", "nature", "farm", or "crop". For a full
-    list of parameters, see `src/parameters.toml`.
-    """
-    global function param(domainparam::String)
-        domain, paramname = split(domainparam, ".")
-        settings[domain][paramname]
-    end
+"""
+    @param(domainparam)
 
-    """
-        printparameters(io)
+Return a configuration parameter from the global settings.
+The argument should be in the form `<domain>.<parameter>`,
+for example `@param(core.outdir)`. Possible values for
+<domain> are `core`, `nature`, `farm`, or `crop`. For a full
+list of parameters, see `src/parameters.toml`.
 
-    Print all settings in TOML format to the given output stream.
-    """
-    global function printparameters(stream::IO=stdout)
-        TOML.print(stream, settings)
-    end
+Note that this macro only works in a context where the `model`
+object is available.
+"""
+macro param(domainparam)
+    domain = String(domainparam.args[1])
+    paramname = String(domainparam.args[2].value)
+    #domain, paramname = split(domainparam, ".")
+    :($(esc(:model)).settings[$domain][$paramname])
 end
 
 """
-    getsettings(configfile)
+    getsettings(configfile, seed=nothing)
 
 Combines all configuration options to produce a single settings dict.
 Precedence: commandline parameters - user config file - default values
 """
-function getsettings(configfile::String)
+function getsettings(configfile::String, seed::Union{Int64,Nothing)=nothing)
     # read in and merge configurations from the commandline, the default config file
     # and a user-supplied config file
     defaults = TOML.parsefile(configfile)
@@ -75,7 +52,9 @@ function getsettings(configfile::String)
         end
     end
     # pre-process certain parameters
-    if settings["core"]["seed"] == 0
+    if !isnothing(seed)
+        settings["core"]["seed"] = seed
+    elseif settings["core"]["seed"] == 0
         settings["core"]["seed"] = abs(rand(RandomDevice(), Int32))
     end
     defaultoutdir = defaults["core"]["outdir"]
diff --git a/src/core/output.jl b/src/core/output.jl
index db3ce30049822823c1f7499f3069293aa1711528..e34feb2694f24936593c6f1cd4a73bd1a1da62e9 100644
--- a/src/core/output.jl
+++ b/src/core/output.jl
@@ -51,7 +51,7 @@ function setupdatadir()
         println(f, "# This file was generated automatically.")
         println(f, "# Simulation run on $(string(Dates.format(Dates.now(), "d u Y HH:MM:SS"))),")
         println(f, "# with git commit $(read(`git rev-parse HEAD`, String))#\n")
-        printparameters(f)
+        TOML.print(f, settings)
     end
     # Copy the map files to the output folder
     lcmap = param("core.landcovermap")
diff --git a/src/core/simulation.jl b/src/core/simulation.jl
index e9b96631ede6d79cadac3742215eb5112d54ebe4..d36aefa191471c8037ad28436e745a5b5181f7a0 100644
--- a/src/core/simulation.jl
+++ b/src/core/simulation.jl
@@ -4,23 +4,25 @@
 ###
 
 """
-    initialise(config, seed)
+    initialise(config, seed=nothing)
 
 Initialise the model: read in parameters, create the output data directory,
-and instantiate the AgentBasedModel object.
+and instantiate the AgentBasedModel object. Optionally allows overriding the
+`seed` parameter.
 """
-function initialise(config::String=PARAMFILE)
+function initialise(config::String=PARAMFILE, seed::Union{Int64,Nothing)=nothing)
     @info "Simulation run started at $(Dates.now())."
     #TODO add a seed parameter - requires mutable parameters
     # do some housekeeping
-    initsettings(config)
-    Random.seed!(param("core.seed"))
+    settings = initsettings(config, seed)
+    Random.seed!(settings["core"]["seed"])
     setupdatadir()
     # initialise world-level properties
     landscape = initlandscape()
     events = Vector{FarmEvent}()
     space = GridSpace(size(landscape), periodic=false)
-    properties = Dict{Symbol,Any}(:date=>param("core.startdate"),
+    properties = Dict{Symbol,Any}(:settings=>settings,
+                                  :date=>settings["core"]["startdate"],
                                   :landscape=>landscape,
                                   :events=>events)
     @debug "Setting up model."