diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7aadc3e320fd68d7e119b8602468a7565a50e6d3..04b8e2a1b89d80c455bb74da4809d2d7257a1ca7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -22,15 +22,16 @@ unittests:
 integrationtests:
   script:
     - julia --project=test/IntegrationTests -e "import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()"
-    - julia --project=test/IntegrationTests -e "using IntegrationTests; runtests()"
-    - mv test/IntegrationTests/tests/tests.log public
+    # using !runtest() because Int(false) = 0 and 0 exit code indicates success in shells
+    - julia --project=test/IntegrationTests -e "using IntegrationTests; exit(!runtests())"
   only:
     - main
     - develop
     - merge_requests
   artifacts:
+    when: always
     paths:
-      - public
+      - test/IntegrationTests/tests/tests.log
 
 pages:
   script:
diff --git a/Project.toml b/Project.toml
index 04327461d2a0a2fec0911e52cae37de7e78714db..d61af21ff70fecab3ecd7d00e5db073677433a8c 100644
--- a/Project.toml
+++ b/Project.toml
@@ -5,9 +5,7 @@ version = "1.0.0"
 
 [deps]
 BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
-Configurations = "5218b696-f38b-4ac9-8b61-a12ec717816d"
 DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
-DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
 FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
 Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
 HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
@@ -29,6 +27,7 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
 StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
 StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
 TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+ToggleableAsserts = "07ecc579-1b30-493c-b961-3180daf6e3ae"
 
 [compat]
 julia = "1.7"
diff --git a/experiments/grhd_tov/grhd_tov_1.toml b/experiments/grhd_tov/grhd_tov_1.toml
index 13ff95d455cc293e52f225eb5adefbe63562feb4..9e291be252f3b7bda865296e584ba1008184726c 100644
--- a/experiments/grhd_tov/grhd_tov_1.toml
+++ b/experiments/grhd_tov/grhd_tov_1.toml
@@ -1,19 +1,31 @@
 [EquationOfState]
-type = "HybridEoS"
+eos = "hybrid"
 
-[BoundaryConditions]
-type = "from initial data"
+[GRHD]
+bc = "from_initial_data"
+id = "tov"
+id_filename = "TOV.h5"
+
+[TCI]
+method = "mda"
+
+[HRSC]
+# # type = "BernsteinReconstruction"
+method = "av"
+av_method = "mda"
+av_smoother_order = 1
+
+# Non-project specific parameters
 
 [Mesh]
 xrange = [ -20.0, 20.0 ]
-CFL = 0.1
-N = 2
-K = 500
+k = 50
+cfl = 0.1
 
 [Plot]
 every_dt = 0
-every_iteration = 0
-pause = false
+every_iteration = 1
+pause = true
 
 [Output]
 every_iteration = 1
@@ -21,19 +33,8 @@ every_dt        = 0
 variables       = [ "sD", "sSr", "stau", "D", "Sr", "tau", "rho", "eps", "vr", "p" ]
 
 [Grid]
-type = "LGL_lumped"
+basis = "lgl_lumped"
+n = 8
 
 [Evolution]
-tend = 1.2
-
-[Initialdata]
-type = "tov"
-
-[TCI]
-type = "ModalDecayAverage"
-
-# [HRSC]
-# # type = "BernsteinReconstruction"
-# type = "MDAViscosity"
-# smoother = true
-# order = 1
+tend = 10.2
diff --git a/scripts/movie.jl b/scripts/movie.jl
index dc11671e0d4f2efcfbc5fde9a8e6a5aa3854cacf..d1fb28fde07f1c0a34b6be946f617caefaa0d9e8 100644
--- a/scripts/movie.jl
+++ b/scripts/movie.jl
@@ -50,9 +50,26 @@ function make_movie(dir;
 
   dir = abspath(joinpath(".",dir))
   tomlfile = get_parameter_file(dir)
-  parameters = FlatDict(TOML.parsefile(joinpath(dir, tomlfile)))
+  pars = TOML.parsefile(joinpath(dir, tomlfile))
+  mesh = if haskey(pars, "Equation")
+    parameters = FlatDict(TOML.parsefile(joinpath(dir, tomlfile)))
+    display(parameters)
+    mesh = dg1d.Mesh(N=parameters["Mesh.N"],
+                K=parameters["Mesh.K"],
+                CFL=parameters["Mesh.CFL"],
+                xrange=parameters["Mesh.xrange"],
+                basis=lowercase(parameters["Grid.type"]))
+  else
+    project, parameters = dg1d.load_parameters(joinpath(dir, tomlfile))
+    mesh_prms = parameters["Mesh"]
+    mesh = dg1d.Mesh(N=mesh_prms["n"],
+                     K=mesh_prms["k"],
+                     CFL=mesh_prms["cfl"],
+                     xrange=mesh_prms["xrange"],
+                     basis=lowercase(mesh_prms["basis"]))
+  end
 
-  mesh = make_Mesh(trim_headers(parameters, "Mesh"))
+  # mesh = make_Mesh(trim_headers(parameters, "Mesh"))
 
   # read data
   output = Output(joinpath(dir, "output.h5"))
@@ -115,7 +132,7 @@ function make_movie(dir;
   prevbt      = headergrid[2,1]  = Button(fig, label=labels[:prev], font=font, fontsize=35)
   idxbox      = headergrid[2,2]  = Textbox(fig, stored_string="1", validator=Int64,
                                           displayed_string="1", width=Fixed(50),
-                                          halign=:center, reset_on_defocuse=true,
+                                          halign=:center, #reset_on_defocuse=true,
                                           defocus_on_submit=false)
   nextbt      = headergrid[2,3]  = Button(fig, label=labels[:next], font=font, fontsize=35)
   playbwrdbt  = headergrid[2,4]  = Button(fig, label=labels[:playbwrd], font=font, fontsize=35)
diff --git a/src/bdryconditions.jl b/src/bdryconditions.jl
index 41b5d293f05939bfdc95b96afc574fc0adc552dd..78c1dfca92f52f43efc856d12f6cef5b15b1ceda 100644
--- a/src/bdryconditions.jl
+++ b/src/bdryconditions.jl
@@ -1,5 +1,4 @@
-export AbstractBdryConditions,
-       DirichletBC, OutflowBC, InflowBC, PeriodicBC,
+export DirichletBC, OutflowBC, InflowBC, PeriodicBC,
        BoundaryConditions, isperiodic,
        broadcast_boundaryconditions!
 
@@ -72,7 +71,7 @@ julia> rhs_bc = OutflowBC()
 julia> bdryconds = BoundaryConditions(lhs_bc, rhs_bc, rsolver)
 ```
 """
-struct BoundaryConditions{T_BC_LHS<:AbstractBC, 
+struct BoundaryConditions{T_BC_LHS<:AbstractBC,
                           T_BC_RHS<:AbstractBC,
                           T_RSolver<:AbstractRiemannSolver}
   lhs_bc::T_BC_LHS
diff --git a/src/cache.jl b/src/cache.jl
index 1726f250740f8e716570cabfe667cee232754cab..56c424f376feb99469540c9fb17bf20a2291ad3d 100644
--- a/src/cache.jl
+++ b/src/cache.jl
@@ -150,7 +150,7 @@ end
 
 
 function register_variables!(C::Cache;
-    dynamic_variablenames = [], 
+    dynamic_variablenames = [],
     rhs_variablenames     = [],
     static_variablenames  = [],
     cell_variablenames    = [],
diff --git a/src/callbacks.jl b/src/callbacks.jl
index 120e3ee8cc0cc6a0eb3ae6687302c28933ffeca1..bc4a3838131e1bfc2145a6df48919f00859392e7 100644
--- a/src/callbacks.jl
+++ b/src/callbacks.jl
@@ -147,6 +147,8 @@ end
 
 
 Base.push!(cbs::CallbackSet, cb::AbstractCallback) = push!(cbs.callbacks, cb)
+Base.push!(cbs::CallbackSet, ::Nothing) = nothing
+Base.append!(cbs::CallbackSet, cbs2::CallbackSet) = append!(cbs.callbacks, cbs2.callbacks)
 
 
 function Base.show(io::IO, cbs::CallbackSet) 
@@ -223,6 +225,9 @@ mutable struct SaveCallback <: AbstractCallback
     end
 
     # normalize and sanitize filename
+    if length(filename) == 0
+      error("SaveCallback: Invalid filename = '$filename' ")
+    end
     filename = Filesystem.abspath(filename)
     if filename[end-2:end] != ".h5"
       filename = "$(filename).h5"
diff --git a/src/dg1d.jl b/src/dg1d.jl
index 8aead18e4ce59dc52f3249b24cf11888b2bc1750..f2d8535638546eadabb9606f9b6406f7fb03c11c 100644
--- a/src/dg1d.jl
+++ b/src/dg1d.jl
@@ -2,9 +2,7 @@ module dg1d
 
   using Reexport
   import Base: Filesystem, Threads
-  using Configurations
   using DataFrames
-  using DocStringExtensions
   using Glob
   using InteractiveUtils
   using Jacobi
@@ -22,15 +20,15 @@ module dg1d
   using StaticArrays
   using StructArrays
   @reexport using TOML
+  @reexport using ToggleableAsserts
 
-  export Maybe
+  export Maybe, main, Environment
 
   # utils
   include("utils.jl")
   include("rootfinders.jl")
   include("with_signature.jl")
   include("units.jl")
-  include("parameters.jl")
   include("flatdict.jl")
   include("output.jl")
 
@@ -45,9 +43,6 @@ module dg1d
 
   # custom evolution
   include("evolve.jl")
-  # evolution based on OrdinaryDiffEq pkg
-  # using OrdinaryDiffEq
-  # include("evolve_ordinarydiffeqs.jl")
 
   # interfaces
   include("equations.jl")
@@ -63,6 +58,10 @@ module dg1d
 
   include("segment.jl")
 
+  include("types.jl")
+  include("projects/projects.jl")
+  include("parameters.jl")
+
   ## projects
   include("projects/TCI/TCI.jl")
   @reexport using .TCI
@@ -83,4 +82,6 @@ module dg1d
 
   include("projects/GRHD/GRHD.jl")
 
+  include("main.jl")
+
 end # module
diff --git a/src/evolve.jl b/src/evolve.jl
index 4402c9e118538e80190dcb162f542913ed6d2e55..04da398b05bad875d45d79d21706d4d5e8100614 100644
--- a/src/evolve.jl
+++ b/src/evolve.jl
@@ -3,11 +3,7 @@
 #######################################################################
 
 
-"""
-$(FUNCTIONNAME)
-$(FIELDS)
-"""
-@option struct Parameters_Evolution <: dg1d.AbstractParameters
+struct Parameters_Evolution
   "Time stepping algorithm, choose one out of LSERK, RK4, SSPRK3, ..."
   algorithm::String = "LSERK"
   "End time of evolution"
@@ -15,12 +11,6 @@ $(FIELDS)
 end
 
 
-function sanitize(prms::Parameters_Evolution)
-  @assert prms.algorithm in [ "LSERK", "RK4", "SSPRK3" ]
-  @assert tstart < tend
-end
-
-
 #######################################################################
 #                          Butcher-Tablaeus                           #
 #######################################################################
@@ -424,19 +414,19 @@ Generate an Explicit-Runge-Kutta stepper for iterate `u0` that uses scheme `sche
 """
 function make_ERK_stepper(u0, scheme::Symbol, callback_substep)
 
-  coeffs, method = if scheme == :MIDPT
+  coeffs, method = if scheme == :midpt
     BT_MIDPT(), baseERK_step!
-  elseif  scheme == :RK4
+  elseif  scheme == :rk4
     BT_RK4(), baseERK_step!
-  elseif  scheme == :RK4_twothirds
+  elseif  scheme == :rk4_twothirds
     BT_RK4_twothirds(), baseERK_step!
-  elseif  scheme == :RK4_Ralston
+  elseif  scheme == :rk4_ralston
     BT_RK4_Ralston(), baseERK_step!
-  elseif  scheme == :SSPRK3
+  elseif  scheme == :ssprk3
     BT_SSPRK3(), baseERK_step!
-  elseif  scheme == :LSERK4
+  elseif  scheme == :lserk4
     COEFFS_LSERK4(), LSERK4_step!
-  elseif  scheme == :RKF10
+  elseif  scheme == :rkf10
     BT_RKF10(), baseERK_step!
   else
     error("Requested ERK scheme not implemented!")
diff --git a/src/evolve_ordinarydiffeqs.jl b/src/evolve_ordinarydiffeqs.jl
deleted file mode 100644
index f2db726a1674a46228769c565841baf6f029096d..0000000000000000000000000000000000000000
--- a/src/evolve_ordinarydiffeqs.jl
+++ /dev/null
@@ -1,25 +0,0 @@
-function evolve!(prms, rhs!, u, p, callbacks::CallbackSet)
-  timestep_prms = get_or_error(prms, "Evolution", "Missing section 'Evolution'")
-  # TODO write getter that allows to select between parameters, e.g.
-  # get(prms, "timespan|tend", missing), where | means error. This should
-  # throw an error if both are set. Tbf, the above example is dumb becase we
-  # never need tspan
-  # TODO Make get function also take types such that it can throw if type does not
-  # match
-  # TODO Make get function also take types such that it can throw if type does not
-  # match.
-  # tspan = get(prms, "timespan", missing)
-  tend = get_or_error(timestep_prms, "tend", "Missing parameter 'Evolution.tend'")
-  algorithm = get(prms, "algorithm", "RK4") # also need the | here, e.g. RK4|SSPRK3|...
-  tspan = (0, tend)
-  problem = ODEProblem(rhs!, u, tspan, p)
-  stepper = if algorithm == "RK4"
-    RK4()
-  else
-    error("Algorithm '$algorihtm' not available yet!")
-  end
-  solve(problem, stepper, callback=callbacks,
-        adapative=false, # turn of build-in adaptive time stepping, use callback instead
-       )
-  return
-end
diff --git a/src/main.jl b/src/main.jl
new file mode 100644
index 0000000000000000000000000000000000000000..ee047f5251deb740cb78863ced8228c1a57a4928
--- /dev/null
+++ b/src/main.jl
@@ -0,0 +1,219 @@
+"""
+    main(parfile)
+
+Run program specified by a `parfile` in TOML format.
+"""
+function main(parfile)
+
+  # normalize parfile name
+  parfile = abspath(parfile)
+
+  project_name, prms = load_parameters(parfile)
+
+  # TODO Use PrettyTables.jl for summary
+  # TODO Dump parameters also to file
+  # TODO Dump versioninfo() to some file
+  # TODO Dump Pkg.status() to some file
+  # TODO Dump `git status` to some file
+  # TODO Dump gethostname() and other system info
+  # summary = summarize(prms)
+  # println(summary)
+
+  # backup any previous output dir and setup a fresh one
+  # TODO Rename this to make_outputdir
+  outputdir = mk_outputdir(parfile)
+  cp(parfile, joinpath(outputdir, basename(parfile)))
+
+  # Setup the environment
+  mesh = Mesh(; N=prms["Mesh"]["n"], K=prms["Mesh"]["k"], xrange=prms["Mesh"]["xrange"],
+              basis=prms["Mesh"]["basis"], CFL=prms["Mesh"]["cfl"])
+  cache = Cache(mesh)
+  callbacks = CallbackSet()
+  callbacks_substeps = CallbackSet()
+  env = Environment(mesh, cache, callbacks, callbacks_substeps)
+
+  # Setup the project. This involves
+  # - registering variables in cache
+  # - setting up initial data
+  # - setting up boundary conditions
+  # - setting up callbacks
+  # Need to use Val(project_name) for dispatch here, because project_name is a runtime variable.
+  Project = project_type(Val(project_name))
+  # TODO Till I sorted out the issue with where to place bdry condition types, I have to
+  # return them here and forward them below
+  project, bdryconds = Project(env, prms)
+
+  # wrap project interface methods for useage with time stepper
+  function wrapped_rhs!(state_du, state_u, t)
+    wrap_dynamic_variables!(env.cache, state_u)
+    wrap_rhs_variables!(env.cache, state_du)
+    # provided by project interface
+    rhs!(env, project, bdryconds)
+  end
+  function wrapped_timestep!(state_u, t, n)
+    wrap_dynamic_variables!(env.cache, state_u)
+    # provided by project interface
+    timestep!(env, project)
+  end
+
+  # Setup project independent callbacks
+  # For the time being, we let the Projects freely push callbacks into env.callbacks.
+  # This should make no difference as long as we 1) don't run in parallel and 2) don't allow
+  # multiple projects. Might want to move that logic later from Project to here.
+  # TODO Add an AliveCallback that manages a 'status' file in the output folder
+  # TODO Add priority ordering to CallbackSet and callbacks.
+  # TODO Drop unused callbacks from callbackset upon creation
+
+  # Add some 'global' callbacks
+
+  savecb = SaveCallback(Symbol.(prms["Output"]["variables"]), cache, mesh,
+                        joinpath(outputdir, "output.h5"),
+                        CallbackTiming(every_dt=prms["Output"]["every_dt"],
+                                       every_iteration=prms["Output"]["every_iteration"]))
+
+  aligned_ts = prms["Output"]["aligned_ts"]
+  timealigned_savecb, wrapped_timestep! = if length(aligned_ts) > 0
+    atscb = TimeAlignedSaveCallback(aligned_ts, wrapped_timestep!, savecb)
+    atscb, atscb.aligned_timestep_fn
+  else
+    nothing, wrapped_timestep!
+  end
+
+  plotfn(u,t) = plot(env, project)
+  plotcb = PlotCallback(plotfn, CallbackTiming(every_dt=prms["Plot"]["every_dt"],
+              every_iteration=prms["Plot"]["every_iteration"]), pause=prms["Plot"]["pause"])
+
+  push!(callbacks, savecb)
+  push!(callbacks, timealigned_savecb)
+  push!(callbacks, plotcb)
+
+  # TODO Setup a logger, maybe want to setup even earlier
+  # TODO summarize current setup once and log to screen
+  # println(summarize(env.mesh))
+  # println(summarize(env.cache))
+  # println(summarize(env.callbacks))
+  # println(summarize(env.callbacks_substep))
+  # println(summarize(project))
+
+  # plot parameter list
+  # TODO Dumb this to a file
+  for (k,v) in prms
+    table = vcat([k ""], hcat(collect(keys(v)), collect(values(v))))
+    pretty_table(
+        table;
+        body_hlines = [1],
+        body_hlines_format = Tuple('─' for _ = 1:4),
+        alignment = [ :l, :l ],
+        noheader = true,
+        tf = tf_borderless,
+        columns_width = 30,
+        linebreaks = true,
+        autowrap = true,
+        crop = :none
+    )
+    println()
+  end
+
+  # Let's go!!!
+  evolve!(env, wrapped_rhs!, wrapped_timestep!, prms)
+
+  return
+end
+
+
+function load_parameters(parfile)
+
+  # read parameters
+  if !isfile(parfile)
+    error("Cannot locate parameter file '$parfile'")
+  end
+
+  user_prms = TOML.parsefile(parfile)
+  # switch to a flat dict where keys of nested dicts are concatenated with dots
+  user_prms = Dict(string(k) => flatten(v) for (k,v) in pairs(user_prms))
+
+  ### sanitize parameter sections
+  # supported top-level headers that aren't user projects
+  default_projects = [ :Mesh, :Evolution, :Plot, :Output, :TCI, :HRSC, :EquationOfState ]
+
+  for (k,v) in user_prms
+    if !(v isa AbstractDict)
+      error("Invalid parameter file: No top-level parameters allowed, found '$k = $v'")
+    end
+  end
+
+  # find a parameter section for which somebody registered a project with that name
+  all_projects = Symbol.(keys(user_prms))
+  maybe_user_projects = filter(all_projects) do k
+    return !(k in default_projects)
+  end
+  registered_projects = filter(isregistered ∘ Symbol, maybe_user_projects)
+  not_registered_projects = setdiff(maybe_user_projects, registered_projects)
+  if length(not_registered_projects) > 0
+    error("Invalid parameter file: Found unknown top-level sections: $(not_registered_projects)")
+  end
+  if length(registered_projects) == 0
+    error("Invalid parameter file: Missing a parameter section for a project, don't know which project to run ...")
+  end
+  if length(registered_projects) > 1
+    error("Invalid parameter file: Only one project parameter section allowed, found multiple: $(registered_projects)")
+  end
+
+  # we found a user project
+  project_name = Symbol(first(registered_projects))
+
+  # load default parameters
+  prms = Dict(string(s) => parameters(Val(s)) for s in default_projects)
+  prms[string(project_name)] = parameters(project_name)
+  # TODO parameters(Val) does not guarantee that dicts returned from parameter are flat.
+  prms = Dict(k => flatten(v) for (k,v) in pairs(prms))
+
+  # merge defaults with with user parameters
+  for (sec, u_prms) in pairs(user_prms)
+    def_prms = prms[sec]
+    for (k,v) in pairs(u_prms)
+      if !haskey(def_prms, k)
+        error("Invalid parameter file: Unsupported parameter found in section '$sec': '$k = $v'. Keep your parameter files clean!")
+      end
+      def_prms[k] = v
+    end
+  end
+
+  # let's keep things organized
+  prms = OrderedDict( k => OrderedDict(v) for (k,v) in pairs(prms) )
+  # sort sections
+  sort!(prms)
+  # sort each section
+  foreach(p -> sort!(p), values(prms))
+
+  return project_name, prms
+end
+
+
+function evolve!(env, rhs_fn, timestep_fn, prms)
+
+  @unpack tend, method = prms["Evolution"]
+  @unpack cfl = prms["Mesh"]
+  @unpack cache, callbacks, callbacks_substep = env
+
+  # sanity checks
+  if tend <= 0
+    error("TimeStepper: tend must be positive, found 'tend = $tend'")
+  end
+  if cfl < 0
+    error("TimeStepper: cfl must be positive, found 'cfl = $cfl'")
+  end
+  methods = [ "midpt", "rk4", "rk4_twothirds", "rk4_ralston", "ssprk3", "lserk4", "rkf10" ]
+  if !(method in methods)
+    error("TimeStepper: Unknown time stepping method '$method' requested, available methods: '$(methods...)'")
+  end
+
+  # TODO This should be hidden behind an interface
+  all_vars = values(cache.dynamic_variables.datadict)
+  u0 = vcat(all_vars...)
+
+  # Let's go!!!
+  evolve_ERK(rhs_fn, u0, timestep_fn, (0.0, tend), Symbol(method),
+             callback_fullstep = callbacks)#, callback_substep = callbacks_substep)
+
+end
diff --git a/src/mesh.jl b/src/mesh.jl
index 4533fc16fb5ab268a52e9648550468a789be587d..d63ddf8153e24f1b6793ddd18b9e6f057bb414ef 100644
--- a/src/mesh.jl
+++ b/src/mesh.jl
@@ -1,5 +1,4 @@
 export Mesh, MeshInterpolator,
-       make_Mesh, parse_parameters,
        layout, n_points, n_cells,
        grid_average, cellwise_average, cellwise_inner_product, broken_inner_product,
        local_maxabs, locate_point, find_cell_index
@@ -17,7 +16,7 @@ export Mesh, MeshInterpolator,
 #######################################################################
 
 
-@doc """
+"""
     Mesh
 
 Object that bundles grid and operators needed for modeling a Discontinuous Galerkin 
@@ -34,73 +33,51 @@ struct Mesh
   x::Matrix{Float64}        # grid points
   invjac::Matrix{Float64}   # inverse jacobian of coordinate transformation
                             # from computational [-1,1] onto physical grid [xmin,xmax]
-
-  function Mesh(; N, K, xrange, CFL, quadrature_method)
-    element = SpectralElement(N, Symbol(quadrature_method))
-    @unpack z = element
-    xmin, xmax = xrange
-    L = xmax - xmin
-    dl = L/K
-    a = [ xmin + dl * idx for idx = 0:K-1 ]
-    b = a .+ dl
-    Npts = length(z)
-    x = Matrix{Float64}(undef, Npts, K)
-    invjac = similar(x)
-    for i=1:Npts, j=1:K
-      aj = a[j]
-      bj = b[j]
-      zi = z[i]
-      x[i,j] = 0.5 * ((bj - aj) * zi + (aj + bj))
-      invjac[i,j] = 2.0 / (bj - aj)
-    end
-    # sanitize cell end points to have same x coordinate at common interfaces
-    for j=1:K-1
-      xl = x[end,j]
-      xr = x[1,j+1]
-      xmean = 0.5 * (xl + xr)
-      x[end,j] = xmean
-      x[1,j+1] = xmean
-    end
-    return new(element, K, xmin, xmax, CFL, L, dl, x, invjac)
-  end
 end
 
 
-#######################################################################
-#                              Factories                              #
-#######################################################################
-
-
-make_Mesh(prms) = Mesh(; parse_parameters(Mesh, prms)...)
-make_Mesh(; args...) = make_Mesh(FlatDict(args))
-
-
-######################################################################
-#                            Parameters                              #
-######################################################################
-
+function Mesh(; N=5, K=10, xrange=[-1.0,1.0], CFL=0.5, basis="lgl_lumped")
+
+  @toggled_assert N > 0
+  @toggled_assert K > 0
+  @toggled_assert CFL > 0
+  @toggled_assert length(xrange) == 2
+  @toggled_assert xrange[1] < xrange[2]
+  @toggled_assert basis in ["lgl", "glgl", "lgl_lumped"]
+
+  # TODO Remove this after updating parameter names in SpectralElement
+  quadrature_method = Dict("lgl" => :LGL, "glgl" => :GLGL, "lgl_lumped" => :LGL_lumped)[basis]
+
+  element = SpectralElement(N, Symbol(quadrature_method))
+  @unpack z = element
+  xmin, xmax = xrange
+  L = xmax - xmin
+  dl = L/K
+  a = [ xmin + dl * idx for idx = 0:K-1 ]
+  b = a .+ dl
+  Npts = length(z)
+  x = Matrix{Float64}(undef, Npts, K)
+  invjac = similar(x)
+  for i=1:Npts, j=1:K
+    aj = a[j]
+    bj = b[j]
+    zi = z[i]
+    x[i,j] = 0.5 * ((bj - aj) * zi + (aj + bj))
+    invjac[i,j] = 2.0 / (bj - aj)
+  end
+  # sanitize cell end points to have same x coordinate at common interfaces
+  for j=1:K-1
+    xl = x[end,j]
+    xr = x[1,j+1]
+    xmean = 0.5 * (xl + xr)
+    x[end,j] = xmean
+    x[1,j+1] = xmean
+  end
 
-function parse_parameters(::Type{Mesh}, prms)
-  N = query(Int64, prms, "N", default=5)
-  K = query(Int64, prms, "K", default=20)
-  CFL = query(Float64, prms, "CFL", default=0.5)
-  quadrature_method = query(String, prms, "quadrature_method", default="LGL",
-                            options=["LGL", "GLGL", "LGL_lumped"])
-  xrange = query(Vector{Float64}, prms, "xrange", default = [-1.0,1.0])
-  # sanitize
-  @assert N > 0 "'N > 0' required"
-  @assert K > 0 "'K > 0' required"
-  @assert CFL > 0 "'CFL > 0' required"
-  @assert length(xrange) == 2 "'xrange' must be a tuple"
-  @assert xrange[1] < xrange[2] "'xrange' limits must be ordered"
-  @assert quadrature_method in ["LGL", "GLGL", "LGL_lumped"] "'quadrature_method' invalid"
-  return (; N, K, CFL, quadrature_method, xrange)
+  return Mesh(element, K, xmin, xmax, CFL, L, dl, x, invjac)
 end
 
 
-@deprecate Mesh(prms) make_Mesh(prms)
-
-
 #######################################################################
 #                               Methods                               #
 #######################################################################
@@ -261,7 +238,7 @@ Operator for interpolating data from one mesh to another.
 # Examples
 
 ```julia
-julia> sample_mesh, reference_mesh = make_Mesh(; N=3, K=4); make_Mesh(; N=10, K=20);
+julia> sample_mesh, reference_mesh = Mesh(; N=3, K=4); Mesh(; N=10, K=20);
 
 julia> reference_data = sin.(reference_mesh.x);
 
diff --git a/src/mwe.jl b/src/mwe.jl
deleted file mode 100644
index fcfe9a25f5ba68ec4df28ecfb84b751bc9d438c8..0000000000000000000000000000000000000000
--- a/src/mwe.jl
+++ /dev/null
@@ -1,9 +0,0 @@
-using StructArrays
-
-
-x, y, z = [ randn(5) for _ = 1:3 ]
-
-Vf = Vector{Float64}
-soa = StructArray{Tuple{Tuple{Vf,Vf}, Vf}}(((x,y),z))
-
-
diff --git a/src/parameters.jl b/src/parameters.jl
index 317b05b2e09339402d915b4825d4f70a961b73c9..655de8a73d7aa530a1157b84b50d5f47d3fbeb8d 100644
--- a/src/parameters.jl
+++ b/src/parameters.jl
@@ -1,48 +1,560 @@
-export AbstractParameters,
-       sanitize
+export parameters, @parameters
 
 
-#######################################################################
-#                                Types                                #
-#######################################################################
+"""
+    @parameters name ex
 
+Macro to define parameters and their default values for a symbol `name`.
+If invoked with `MyProject` this generates the following methods for the `dg1d` project interface:
+- `dg1d.parameters(::Val{:MyProject})`
+- `dg1d.check_parameters(::Val{:MyProject}, prms)`
 
-@doc """
-  abstract AbstractParameters
+---
 
-Supertype for participating in the Parameters interface.
+# Example
 
-Subtyped structs can implement the following methods to verify that
-provided parameters are sane:
-```
-sanitize(prms::T) where T<:AbstractParameters
+```julia
+@parameters MyProject begin
+
+  "some variable"
+  x = 1.0
+  @check x > 0.0
+
+  "another variable"
+  y = "String"
+
+end
 ```
 """
-abstract type AbstractParameters end
+macro parameters(name, expr)
+  return esc(_generate_parameters(name, expr))
+end
 
 
-#######################################################################
-#                          Interface methods                          #
-#######################################################################
+macro check(ex)
+  return ex
+end
+
+
+function _generate_parameters(name, expr)
+
+  if expr.head !== :block
+    error("@parameters: Second argument must be a begin ... end block containing docstrings and parameter defintions!")
+  end
+
+  pars = expr.args
+  pars = filter(p -> !(p isa LineNumberNode), pars)
+
+  docstrings = String[]
+  kvexprs = Expr[]
+  checks = Vector{Expr}[]
+  i_definition = 0
+  for p in pars
+
+    # if p isa Expr
+    #   filter!(pp -> !(pp isa LineNumberNode), p.args)
+    # end
+
+    is_definition = (p isa Expr) && p.head === :macrocall &&
+       (p.args[1] === Symbol("@doc") || p.args[1] isa GlobalRef) && length(p.args) == 4
+    is_check = (p isa Expr) && p.head === :macrocall && p.args[1] === Symbol("@check") &&
+      length(p.args) == 3
+
+    is_valid_block = is_definition || is_check
+    if !is_valid_block
+      error("""
+            @parameters: Invalid parameter definition.
+            Expected either a block with a docstring, parameter name and default value
+            or an argument check, e.g.
+               \"this is a docstring for variable x\"
+               x = 1.0
+               @check x > 1.0
+            Instead found
+               $(string(p))
+            """)
+    end
+
+    if is_definition
+      # parse a parameter definition block
+
+      i_definition += 1
 
+      # extract docstring, mandatory
+      d = p.args[3]
+      if d isa Expr && d.head === :macrocall && d.args[1] === Symbol("@raw_str")
+        d = d.args[3]
+      end
 
-function sanitize(prms::AbstractParameters) end
+      # extract variable name and default value, mandatory
+      kv = p.args[4]
+      if !(kv isa Expr)
+        error("""
+              @parameters: Invalid parameter definition. Missing default value for
+                 $(string(kv))
+              """)
+
+      end
+      if kv.head !== :(=)
+        error("""
+              @parameters: Invalid parameter definition. Expected pair of parameter name and default value, e.g.
+                 x = 1.0
+              Instead found
+                 $(string(kv))
+              """)
+      end
+
+      k, v = kv.args[1], kv.args[2]
+      if !(k isa Symbol)
+        error("""
+              @parameters: Invalid parameter name: '$k'"
+              """)
+      end
+
+      push!(docstrings, d)
+      push!(kvexprs, kv)
+      push!(checks, Expr[])
+    end
+
+    # parse a check, they are optional
+    if is_check
+
+      # we already verified above that p is a @check block
+      # we also stripped any LineNumberNodes there
+      ck = p.args[3]
+
+      if i_definition == 0
+        error("@parameters: Invalid definition. Found @check before any parameter definition: $ck")
+      end
+
+      # macro expansion of @check is delayed till check_parameters definition below
+      push!(last(checks), ck)
+    end
+
+  end
+
+  keys = [ kv.args[1] for kv in kvexprs ]
+  for i = 1:length(keys)-1
+    refk = keys[i]
+    foreach(keys[i+1:end]) do k
+      if refk === k
+        error("""
+              @parameters: Duplicated parameters found: '$k'
+              """)
+      end
+    end
+  end
+
+  # generate the docs
+  io = IOBuffer()
+  println(io, name)
+  for (d, kv) in zip(docstrings, kvexprs)
+    println(io, "- `", kv, "`\n    ", replace(d, '\n' => "\n  "))
+  end
+  docs = String(take!(io))
+
+  # compare key = value to "key" => value for Dict constructor
+  pairs = [ Expr(:call, :(=>), string(kv.args[1]), kv.args[2]) for kv in kvexprs ]
+
+  checkscode = Expr[]
+  for cks in checks
+    evalchecks = [ quote
+                     result = try
+                       $ck
+                     catch
+                       error("check_parameters for $($(string(name))): Failed to evaluate check '$($(string(ck)))'")
+                     end
+                     if !(result isa Bool)
+                       error("check_parameters for $($(string(name))): Invalid check found '$($(string(ck)))'. Expected to return a Bool, but found $(typeof(result))")
+                     end
+                     if result == false
+                       error("check_parameters for $($(string(name))): Check failed: '$($(string(ck)))'")
+                     end
+                   end
+                   for ck in cks ]
+    code = quote $(evalchecks...) end
+    push!(checkscode, code)
+  end
+  unpack_prms = [ quote $k = prms["$($("$k"))"] end for k in keys ]
+
+  code = quote
+    @doc $docs dg1d.parameters(::Val{$(QuoteNode(name))}) = Dict($(pairs...))
+    function dg1d.check_parameters(::Val{$(QuoteNode(name))}, prms)
+      $(unpack_prms...)
+      $(checkscode...)
+    end
+  end
+
+  return code
+end
 
 
 #######################################################################
-#                               Methods                               #
+#                        Parameter definitions                        #
 #######################################################################
 
 
-function Base.show(io::IO, prms::AbstractParameters)
-  if Configurations.is_option(prms)
-    print(strip(to_toml(prms)))
-  else
-    print(prms)
-  end
+@parameters Mesh begin
+
+  "Number of cells"
+  k = 10
+  @check k > 0
+
+  "Physical domain extensions in x direction"
+  xrange = [ -1.0, 1.0 ]
+  @check length(xrange) == 2
+  @check xrange[2] - xrange[1] > 0.0
+
+  # TODO Move to Evolution
+  "Courant-Friedrichs-Lewy factor"
+  cfl = 0.5
+  @check cfl > 0.0
+
+  "Polynomial order in each grid"
+  n = 5
+  @check n > 0
+
+  """
+  Nodal basis used for the polynomial approximation. Available options:
+  - `lgl_lumped` ... mass-lumped Legendre-Gauss-Lobatto grid
+  - `lgl`        ... Legendre-Gauss-Lobatto grid
+  - `glgl`       ... Generalized Legendre-Gauss-Lobatto grid
+  """
+  basis = "lgl_lumped"
+  @check basis in [ "lgl", "glgl", "lgl_lumped" ]
+
 end
 
 
-function Base.show(io::IO, ::MIME"text/plain", prms::AbstractParameters)
-  show(io, prms)
+@parameters Evolution begin
+
+  "Final time"
+  tend = 10.0
+  @check tend > 0.0
+
+  """
+  Time stepping method. Available options
+  - `midpt`         ... 2nd order explicit Runge Kutta method -- midpoint formula
+  - `rk4`           ... 4th order explicit Runge Kutta method
+  - `rk4_twothirds` ... 4th order explicit Runge Kutta method -- two thirds version
+  - `rk4_ralston`   ... 4th order explicit Runge Kutta method -- Ralston's method,
+  -                   designed for minimal truncation error
+  - `ssprk3`        ... 3rd order strong stability preserving Runge Kutta method
+  - `lserk4`        ... 4th order low storage explicit Runge Kutta method
+  - `rkf10`         ... 10th order Runge Kutta Feagin method
+  """
+  method = "lserk4"
+  @check method in [ "midpt", "lserk4", "rk4", "rk4_twothirds", "rk_ralston",
+                     "ssprk3", "rkf10" ]
+
 end
+
+
+@parameters Plot begin
+
+  "Call plot callback after every_iteration full time steps"
+  every_iteration = 0
+  @check every_iteration >= 0
+
+  "Call plot callback after every_dt simulation time"
+  every_dt = 0.0
+  @check every_dt >= 0
+
+  """
+  If true, pauses the simulation to display the plot.
+  If set to a positive number, the simulations is paused only for that
+  number of seconds (walltime).
+  """
+  pause = false
+  @check pause isa Bool || pause > 0.0
+
+  "List of variables that should be plotted"
+  variables = String[]
+
+end
+
+
+@parameters Output begin
+
+  "Output data after every_iteration full time steps"
+  every_iteration = 0
+  @check every_iteration >= 0
+
+  "Output data after every_dt simulation time"
+  every_dt = 0.0
+  @check every_dt >= 0
+
+  "List of variables that should be outputted"
+  variables = String[]
+
+  "Filename for .h5 output"
+  filename = ""
+  @check length(filename) > 0
+
+  "List of times at which data should be outputted"
+  aligned_ts = Float64[]
+  @check all(t -> t > 0, aligned_ts)
+  @check issorted(aligned_ts)
+
+end
+
+
+@parameters EquationOfState begin
+
+  """
+  Available options
+  - `polytrope`
+  - `hybrid`
+  - `idealgas`
+  """
+  eos = "polytrope"
+  @check eos in [ "polytrope", "hybrid", "idealgas" ]
+
+  "Polytropic constant"
+  polytrope_k = 1.0
+  @check polytrope_k > 0.0
+
+  "exponent constant"
+  polytrope_gamma = 1.0
+  @check polytrope_gamma > 0.0
+
+  "Thermalization constant"
+  hybrid_gamma = 1.0
+  @check hybrid_gamma > 0.0
+
+  """
+  Available options
+  - `polytrope` ... adjust with `polytrope_k, polytrope_gamma`
+  """
+  hybrid_cold_eos = "polytrope"
+  @check hybrid_cold_eos in [ "polytrope" ]
+
+  "Polytropic constant"
+  idealgas_gamma = 1.0
+  @check idealgas_gamma > 0.0
+
+end
+
+
+@parameters TCI begin
+
+  """
+  Available options:
+  - `none` ... disabled
+  - `diffusion` ... based on gradient of velocity field
+  - `mda` ... based on average of modal decay of solution
+  - `mdh` ... based on modal decay of highest order coefficient of solution
+  - `minmod` ... based on minmod limiter to detect undesired oscillations
+  - `threshold` ... flag if threshold is exceeded
+  - `entropy_production` ... flag if entropy production is exceeded
+  """
+  method = "none"
+  @check method in [ "none", "diffusion", "mda", "mdh", "minmod", "threshold",
+                     "entropy_production" ]
+
+  "List of variable names to monitor. If more than one is given `combine_method`
+  controls how to add the TCI results up."
+  variables = String[]
+  @check length(variables) > 0
+
+  """
+  This controls on how to combine TCIs from different variables. Available options:
+  - `max`
+  - `min`
+  - `average`
+  - `sum`
+  """
+  combine_method = "max"
+  @check combine_method in [ "max", "min", "average", "sum" ]
+
+  "Flag if diffusion exceeds this value"
+  diffusion_max = 1.0
+  @check diffusion_max > 0
+
+  "Flag if modal decay rate falls below this value"
+  mda_threshold_min = 1.0
+  @check mda_threshold_min > 0
+
+  "Don't flag if modal decay rate exceeds this value"
+  mda_threshold_max = 3.0
+  @check mda_threshold_max > mda_threshold_min
+
+  "Flag if modal decay rate falls below this value"
+  mdh_threshold_min = 1.0
+  @check mdh_threshold_min > 0
+
+  "Don't flag if modal decay rate exceeds this value"
+  mdh_threshold_max = 3.0
+  @check mdh_threshold_max > mdh_threshold_min
+
+  "TODO"
+  minmod_threshold = 1e-5
+  @check minmod_threshold > 0
+
+  "Flag if monitor exceeds this value"
+  threshold = 1e-5
+  @check threshold > 0
+
+  "Flag if entropy production exceeds this value"
+  entropy_production_max = 1.0
+  @check entropy_production_max > 0
+
+end
+
+
+@parameters HRSC begin
+
+  """
+  Available options:
+  - `none`      ... disabled
+  - `weno`      ... Weighted Non-Oscillatory approximation
+  - `av`        ... Artificial viscosity
+  - `filter`
+  """
+  method = "none"
+  @check method in [ "weno", "av", "filter" ]
+
+  """
+  Available options:
+  - `mda`     ... modal decay average
+  - `mdh`     ... modal decay based on highest modal coefficient
+  - `entropy` ... entropy production
+  - `db`      ... diffusion based
+  - `tci`     ... viscosity based on TCI monitor
+  """
+  av_method = "mda"
+  @check method in [ "mda", "mdh", "entropy", "db", "tci" ]
+
+  """
+  TODO
+  """
+  av_smoother_order = 1
+  @check av_smoother_order in [ 1, 2 ]
+
+  "TODO"
+  mda_cmax = 1.0
+  @check mda_cmax > 0
+
+  "TODO"
+  mdh_cmax = 1.0
+  @check mdh_cmax > 0
+
+  "TODO"
+  mdh_ca = 0.5
+  @check mdh_ca > 0
+
+  "TODO"
+  mdh_ck = 0.1
+  @check mdh_ck > 0
+
+  "TODO"
+  entropy_cmax = 1.0
+  @check entropy_cmax > 0
+
+  "TODO"
+  entropy_ce = 0.0
+  @check entropy_ce > 0
+
+  "TODO"
+  db_cmax = 1.0
+  @check db_cmax > 0
+
+  "TODO"
+  db_cb = 0.5
+  @check db_cb > 0
+
+  "TODO"
+  tci_threshold = 1e-5
+  @check tci_threshold > 0
+
+  "TODO"
+  tci_max = 1e-2
+  @check tci_max > 0
+
+  "Order of reconstruction polynomial"
+  weno_order = 3
+  @check weno_order > 0
+
+  """
+  Small number to prevent division by zero in WENO weights computation.
+  """
+  weno_beta = 1e-6
+  @check weno_beta > 0
+
+  """
+  Available options:
+  - `bernstein`
+  """
+  filter_method = "bernstein"
+  @check filter_method in [ "bernstein" ]
+
+end
+
+
+# #######################################################################
+# #                    Project parameter definitions                    #
+# #######################################################################
+#
+#
+# @parameters GRHD begin
+#
+#   "Set to true to enable an artificial atmosphere in regions of low density"
+#   use_atmosphere = false
+#
+#   "Cutoff value below which an artifical atmosphere is imposed"
+#   atmosphere_cutoff_density = 1e-10
+#
+#   "Restmass density of artifical atmosphere"
+#   atmosphere_density = 1e-10
+#
+# end
+#
+#
+# @parameters SRHD begin
+#
+#   "Set to true to enable an artificial atmosphere in regions of low density"
+#   use_atmosphere = false
+#
+#   "Cutoff value below which an artifical atmosphere is imposed"
+#   atmosphere_cutoff_density = 1e-10
+#
+#   "Restmass density of artifical atmosphere"
+#   atmosphere_density = 1e-10
+#
+# end
+#
+#
+# @parameters ScalarEquation begin
+#
+#   "Available options: advection, burgers"
+#   equation = "advection"
+#
+#   "Wave speed for advection equation"
+#   advection_speed = 1.0
+#
+#   # TODO Do we support reflecitve BCs?
+#   "Available options: periodic, timedependent, reflective"
+#   boundaryconditions = "periodic"
+#
+#   # TODO Do we support timedependent BCs?
+#   "TODO Options for time dependent BCs"
+#   boundaryconditions_timedependent = ""
+#
+#   "Available options: sine, rectangle"
+#   initialdata = "sine"
+#
+#   ""
+#   initialdata_sine_period = 2.0 * pi
+#
+#   ""
+#   initialdata_sine_amplitude = 1.0
+#
+#   ""
+#   initialdata_rectangle_width = 0.5
+#
+#   ""
+#   initialdata_rectangle_start = -0.5
+#
+#   ""
+#   initialdata_rectangle_amplitude = 1.0
+#
+# end
diff --git a/src/projects/EquationOfState/EquationOfState.jl b/src/projects/EquationOfState/EquationOfState.jl
index a3134334fb60c222168bc510a86daa2d19085447..f796ddbf4f01f421b751b0cfaa37db5639720554 100644
--- a/src/projects/EquationOfState/EquationOfState.jl
+++ b/src/projects/EquationOfState/EquationOfState.jl
@@ -200,15 +200,30 @@ include("hybrideos.jl")
 
 
 function make_EquationOfState(prms)
-  type = Symbol(query(prms, "type", default=missing))
-  type === :missing && error("No equation of state provided!")
-
-  concretes = [ Base.typename(ct).name for ct in concretetypes(AbstractEquationOfState) ]
-  @assert type in concretes "'EquationOfState.type = $type' unknown. Available options: $concretes"
-  eos_type = eval(type)
-  eos_prms = parse_parameters(eos_type, prms)
-  eos = eos_type(; eos_prms...)
-  return eos
+
+  @unpack eos = prms
+  # TODO Rename hybrideos to hybrid and fall back to concretetypes to figure
+  # out available EoS implementations
+
+  if eos == "hybrid"
+    @unpack hybrid_cold_eos, hybrid_gamma = prms
+    cold_eos = if hybrid_cold_eos == "polytrope"
+      @unpack polytrope_k, polytrope_gamma = prms
+      Polytrope(polytrope_k, polytrope_gamma)
+    else
+      error("EquationOfState: Unknown cold equation of state requested: $hybrid_eos")
+    end
+    return HybridEoS(cold_eos, hybrid_gamma)
+  elseif eos == "idealgas"
+    @unpack idealgas_gamma = prms
+    return IdealGas(idealgas_gamma)
+  elseif eos == "polytrope"
+    @unpack polytrope_k, polytrope_gamma = prms
+    return Polytrope(polytrope_k, polytrope_gamma)
+  else
+    error("EquationOfState: Unknown equation of state requested: $eos")
+  end
+
 end
 
 
diff --git a/src/projects/EquationOfState/polytrope.jl b/src/projects/EquationOfState/polytrope.jl
index f255a0629a50534d43558e688bcaeec0457465f0..37f1c3892a908fe4e1f5300196240efdc8509419 100644
--- a/src/projects/EquationOfState/polytrope.jl
+++ b/src/projects/EquationOfState/polytrope.jl
@@ -22,8 +22,8 @@ end
 
 
 function dg1d.parse_parameters(::Type{Polytrope}, prms=Dict())
-  K = query(Float64, prms, "K", default=100.0)
-  Gamma = query(Float64, prms, "Gamma", default=2.0)
+  K = query(Float64, prms, "polytrope_k", default=100.0)
+  Gamma = query(Float64, prms, "polytrope_gamma", default=2.0)
   @assert K > 0     "Polytropic constant must be positive!"
   @assert Gamma > 0 "Polytropic index must be positiv!"
   return (; K, Gamma)
diff --git a/src/projects/EulerEq/EulerEq.jl b/src/projects/EulerEq/EulerEq.jl
index c91fd5655a5025211d1a1474880b43757e377417..b0ef0845fdcb7dc4c2bfbb1f0335e942a54e2753 100644
--- a/src/projects/EulerEq/EulerEq.jl
+++ b/src/projects/EulerEq/EulerEq.jl
@@ -4,21 +4,52 @@ module EulerEq
 
 using dg1d
 using .EquationOfState
-using Match
 using Plots
-using InteractiveUtils
-using PlotThemes
-import Base: Filesystem
-import Parameters: @with_kw, @unpack
 
 
+include("types.jl")
 include("equation.jl")
 include("rhs.jl")
 include("initialdata.jl")
 include("plots.jl")
 include("callbacks.jl")
 include("boundaryconditions.jl")
-include("main.jl")
+include("setup.jl")
 
 
+#######################################################################
+#                 dg1d project interface definitions                  #
+#######################################################################
+
+
+dg1d.project_type(::Val{:EulerEq}) = RHS
+
+# TODO Remove bdryconds
+dg1d.rhs!(env::Environment, p::RHS, bdryconds) = rhs!(env, p, p.hrsc, bdryconds...)
+
+dg1d.timestep!(env::Environment, p::RHS) = timestep(env, p, p.hrsc)
+
+dg1d.@parameters EulerEq begin
+
+  """
+  Initial data configuration. Available options:
+  - `smooth_isentropic_flow`
+  - `sod_shock_tube`
+  - `shu_osher_shock_tube_v1`
+  - `shu_osher_shock_tube_v2`
+  """
+  id = "sine"
+  @check id in [ "smooth_isentropic_flow", "sod_shock_tube",
+                 "shu_osher_shock_tube_v1", "shu_osher_shock_tube_v2" ]
+
+  """
+  Boundary conditions. Available options:
+  - `periodic`
+  - `from_id`
+  """
+  bc = "periodic"
+  @check bc in [ "periodic", "from_id" ]
+
+end
+
 end # module
diff --git a/src/projects/EulerEq/boundaryconditions.jl b/src/projects/EulerEq/boundaryconditions.jl
index 3c4035a1687008ca1127761c34448b246bbb9669..b9951173b2c27b2f469101293c6914148651d463 100644
--- a/src/projects/EulerEq/boundaryconditions.jl
+++ b/src/projects/EulerEq/boundaryconditions.jl
@@ -1,28 +1,35 @@
-function make_BoundaryConditions(rhs::RHS, prms)
-  type = query(String, prms, "type")
-
-  lhs_bc, rhs_bc = @match type begin
-    "periodic"     => (PeriodicBC(), PeriodicBC())
-    "from initial data" => begin
-      @unpack rho, q, E = get_dynamic_variables(rhs.cache)
-      lhs_state = (rho[1], q[1], E[1])
-      rhs_state = (rho[end], q[end], E[end])
-      (DirichletBC(lhs_state), DirichletBC(rhs_state))
-    end
-    _ => error("Boundary condition type '$type' not implemented!")
+function make_BoundaryConditions(env, eq, rsolver, prms)
+
+  @unpack bc = prms
+
+  lhs_bc, rhs_bc = if bc == "periodic"
+    PeriodicBC(), PeriodicBC()
+  elseif bc == "from_id"
+    @unpack rho, q, E = get_dynamic_variables(env.cache)
+    lhs_state = (rho[1], q[1], E[1])
+    rhs_state = (rho[end], q[end], E[end])
+    (DirichletBC(lhs_state), DirichletBC(rhs_state))
+  else
+    error("Unknown boundary condition requested: '$bc'")
   end
 
-  return BoundaryConditions(lhs_bc, rhs_bc, rhs.rsolver)
+  return BoundaryConditions(lhs_bc, rhs_bc, rsolver)
 end
 
 
-function make_BoundaryConditions_LDG(ldg_rsolver, prms)
+function make_BoundaryConditions_LDG(env, eq, ldg_rsolver, prms)
+
+  @unpack bc = prms
+
   isnothing(ldg_rsolver) && return nothing
-  type = query(String, prms, "type")
+
   lhs_bc, rhs_bc = if type == "periodic"
     PeriodicBC(), PeriodicBC()
-  else
+  elseif bc == "from_id"
     OutflowBC(), OutflowBC()
+  else
+    error("Unknown boundary condition requested: '$bc'")
   end
+
   return BoundaryConditions(lhs_bc, rhs_bc, ldg_rsolver)
 end
diff --git a/src/projects/EulerEq/callbacks.jl b/src/projects/EulerEq/callbacks.jl
index f986eb1389bdb30fb37dccacc6344221acac49c5..90a9065bf09169a0e2a0b3a54e70c69fafc9ccc9 100644
--- a/src/projects/EulerEq/callbacks.jl
+++ b/src/projects/EulerEq/callbacks.jl
@@ -1,11 +1,11 @@
-function make_callback(rhs::RHS, bdryconds::BoundaryConditions)
-  cbfn_equation(u, t) = callback_equation(u, t, rhs, isperiodic(bdryconds), rhs.equation)
+function make_callback(env, rhs::RHS, bdryconds::BoundaryConditions)
+  cbfn_equation(u, t) = callback_equation(u, t, env, rhs, isperiodic(bdryconds), rhs.equation)
   cb_equation         = FunctionCallback(cbfn_equation,
                                          CallbackTiming(every_iteration=1,every_dt=0))
-  cbfn_tci(u, t)      = callback_tci(u, t, rhs, isperiodic(bdryconds), rhs.tci)
+  cbfn_tci(u, t)      = callback_tci(u, t, env, rhs, isperiodic(bdryconds), rhs.tci)
   cb_tci              = FunctionCallback(cbfn_tci,
                                          CallbackTiming(every_iteration=1, every_dt=0))
-  cbfn_hrsc(u, t)     = callback_hrsc(u, t, rhs, isperiodic(bdryconds), rhs.hrsc)
+  cbfn_hrsc(u, t)     = callback_hrsc(u, t, env, rhs, isperiodic(bdryconds), rhs.hrsc)
   cb_hrsc             = FunctionCallback(cbfn_hrsc,
                                          CallbackTiming(every_iteration=1,every_dt=0))
   callbackset = CallbackSet(cb_equation, cb_tci, cb_hrsc)
@@ -18,8 +18,9 @@ end
 #######################################################################
 
 
-function callback_equation(state_u, state_t, rhs, isperiodic, eq::EulerEquation)
-  @unpack cache, equation = rhs
+function callback_equation(state_u, state_t, env, rhs, isperiodic, eq::EulerEquation)
+  @unpack cache = env
+  @unpack equation = rhs
   wrap_dynamic_variables!(cache, state_u)
   @unpack Sm1, S, flx_S, flx_Sm1 = get_static_variables(cache)
   @unpack t, tm1 = get_global_variables(cache)
@@ -39,20 +40,20 @@ end
 #######################################################################
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::Nothing) end
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::Nothing) end
 
 
-callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::AbstractReconstruction) = nothing
+callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::AbstractReconstruction) = nothing
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::Maybe{HRSC.AbstractHRSC})
-  error("To be implemented!")
-  error("not yet implemented for type $(typeof(hrsc))")
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::Maybe{HRSC.AbstractHRSC})
+  TODO()
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractArtificialViscosity)
-  @unpack cache, mesh, equation = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.AbstractArtificialViscosity)
+  @unpack cache, mesh = env
+  @unpack equation    = rhs
   @unpack mu, max_v   = get_cell_variables(cache)
   @unpack v           = get_static_variables(cache)
   @unpack rho         = get_dynamic_variables(cache)
@@ -69,24 +70,24 @@ function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractArt
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.TCIViscosity)
-  @unpack cache, mesh = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.TCIViscosity)
+  @unpack cache, mesh = env
   @unpack mu, flag    = get_cell_variables(cache)
   HRSC.compute_viscosity!(mu, flag, hrsc)
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.SmoothedArtificialViscosity)
-  callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc.av)
-  @unpack cache       = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.SmoothedArtificialViscosity)
+  callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc.av)
+  @unpack cache       = env
   @unpack mu          = get_cell_variables(cache)
   @unpack smoothed_mu = get_static_variables(cache)
   smoothed_mu .= vec(hrsc.smoother(mu, isperiodic))
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.EntropyViscosity)
-  @unpack cache, mesh                = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.EntropyViscosity)
+  @unpack cache, mesh                = env
   @unpack rho                        = get_dynamic_variables(cache)
   @unpack S, Sm1, flx_S, flx_Sm1     = get_static_variables(cache)
   @unpack mu, max_v                  = get_cell_variables(cache)
@@ -103,17 +104,17 @@ end
 #######################################################################
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::Nothing) end
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::Nothing) end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.Threshold)
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::TCI.Threshold)
   error("Threshold TCI must be combined with an indicator that is supposed to be zero for " *
         "for smooth solutions, but not for shocks. TODO Implement an indicator variable.")
 end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.EntropyProduction)
-  @unpack cache, mesh                = rhs
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::TCI.EntropyProduction)
+  @unpack cache, mesh                = env
   @unpack S, Sm1, flx_S, flx_Sm1, EP = get_static_variables(cache)
   @unpack flag                       = get_cell_variables(cache)
   @unpack EPjump_lhs, EPjump_rhs     = get_bdry_variables(cache)
@@ -124,11 +125,11 @@ function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.EntropyProduct
 end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic,
+function callback_tci(state_u, state_t, env, rhs, isperiodic,
     tci::Union{TCI.Diffusion, TCI.Minmod, TCI.Threshold, 
                TCI.ModalDecayAverage, TCI.ModalDecayHighest})
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack rho         = get_dynamic_variables(cache)
   @unpack flag        = get_cell_variables(cache)
   TCI.compute_indicator!(flag, rho, tci)
diff --git a/src/projects/EulerEq/equation.jl b/src/projects/EulerEq/equation.jl
index d0a2c28ad5501dcd8899f7363440b85b5818da49..8930e4519356984bf70e95fb6b184d5df3d486cf 100644
--- a/src/projects/EulerEq/equation.jl
+++ b/src/projects/EulerEq/equation.jl
@@ -1,6 +1,7 @@
-@with_kw struct EulerEquation{T_EoS<:EquationOfState.AbstractEquationOfState} <: AbstractEquation
-  eos::T_EoS
-end
+#######################################################################
+#                            Flux methods                             #
+#######################################################################
+
 
 
 @with_signature function flux(equation::EulerEquation)
diff --git a/src/projects/EulerEq/initialdata.jl b/src/projects/EulerEq/initialdata.jl
index 0d06af079eb7b1ccd0493ed695c5ddedbf49a039..ca96c2c0d702fa132cbb38e6ec950c4bced34ab8 100644
--- a/src/projects/EulerEq/initialdata.jl
+++ b/src/projects/EulerEq/initialdata.jl
@@ -1,18 +1,16 @@
-function initialdata!(rhs::RHS, prms)
-  initialdata_equation_of_state!(rhs, rhs.equation.eos, prms)
-  initialdata_entropy_variables!(rhs, prms)
-  initialdata_hrsc!(rhs, rhs.hrsc, prms)
-  initialdata_tci!(rhs, rhs.tci, prms)
+function initialdata!(env, rhs::RHS, prms)
+  initialdata_equation_of_state!(env, rhs, rhs.equation.eos, prms)
+  initialdata_entropy_variables!(env, rhs, prms)
+  initialdata_hrsc!(env, rhs, rhs.hrsc, prms)
+  initialdata_tci!(env, rhs, rhs.tci, prms)
 end
 
 
-function initialdata_equation_of_state!(rhs::RHS, eos::EquationOfState.IdealGas, parameters)
+function initialdata_equation_of_state!(env, rhs::RHS, eos::EquationOfState.IdealGas, prms)
 
-  type = query(parameters, "type", 
-               options=["smooth_isentropic_flow", "sod_shock_tube",
-                        "shu_osher_shock_tube_v1", "shu_osher_shock_tube_v2"])
+  @unpack id = prms
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
 
   xmin, xmax = extrema(mesh)
   γ          = eos.gamma
@@ -20,40 +18,40 @@ function initialdata_equation_of_state!(rhs::RHS, eos::EquationOfState.IdealGas,
 
   E(p, rho, v) = p / (γ - 1.0) + 0.5 * rho * v^2
 
-  tuple_data = if type == "smooth_isentropic_flow"
+  tuple_data = if id == "smooth_isentropic_flow"
     # Glaubitz, PhD thesis 2020, p. 219
-    @assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
-    @assert γ == 3.0
+    @toggled_assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
+    @toggled_assert γ == 3.0
     ρ0 = @. 1.0 + 0.5 * sin(π * x)
     v0 = @. 0.0 * x
     p0 = @. ρ0^γ
     q0 = @. ρ0 * v0
     E0 = @. E(p0, ρ0, v0)
     (ρ0, q0, E0, p0)
-  elseif type == "sod_shock_tube"
-    @assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
-    @assert γ == 1.4
+  elseif id == "sod_shock_tube"
+    @toggled_assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
+    @toggled_assert γ == 1.4
     ρ0 = [ (xi < 0.0) ? 1.0 : 0.125  for xi in x ]
     v0 = [ (xi < 0.0) ? 0.0 : 0.0    for xi in x ]
     p0 = [ (xi < 0.0) ? 1.0 : 0.1    for xi in x ]
     q0 = @. ρ0 * v0
     E0 = @. E(p0, ρ0, v0)
     (ρ0, q0, E0, p0)
-  elseif type == "shu_osher_shock_tube_v1"
+  elseif id == "shu_osher_shock_tube_v1"
     # taken from Glaubitz 2020, PhD thesis
     # results do not agree, I believe that ID is wrong
-    @assert isapprox(xmin, 0.0) && isapprox(xmax, 4.0)
-    @assert γ == 1.4
+    @toggled_assert isapprox(xmin, 0.0) && isapprox(xmax, 4.0)
+    @toggled_assert γ == 1.4
     ρ0 = [ (xi <= 2.5) ? 3.857143 : 1.0 + 0.2 * sin(10*π*xi) for xi in x ]
     v0 = [ (xi <= 2.5) ? 2.629369 : 0.0    for xi in x ]
     p0 = [ (xi <= 2.5) ? 10.333333 : 1.0   for xi in x ]
     q0 = @. ρ0 * v0
     E0 = @. E(p0, ρ0, v0)
     (ρ0, q0, E0, p0)
-  elseif type == "shu_osher_shock_tube_v2"
+  elseif id == "shu_osher_shock_tube_v2"
     # taken from Yu, Hesthaven, 2019
-    @assert isapprox(xmin, -5.0) && isapprox(xmax, 5.0)
-    @assert γ == 1.4
+    @toggled_assert isapprox(xmin, -5.0) && isapprox(xmax, 5.0)
+    @toggled_assert γ == 1.4
     ρ0 = [ (xi <= -4.0) ? 3.857143 : 1.0 + 0.2 * sin(5*xi) for xi in x ]
     v0 = [ (xi <= -4.0) ? 2.629369 : 0.0    for xi in x ]
     p0 = [ (xi <= -4.0) ? 10.333333 : 1.0    for xi in x ]
@@ -61,7 +59,7 @@ function initialdata_equation_of_state!(rhs::RHS, eos::EquationOfState.IdealGas,
     E0 = @. E(p0, ρ0, v0)
     (ρ0, q0, E0, p0)
   else
-    error("Initial data of type $type not implemented!")
+    error("Initial data of type $id not implemented!")
   end
 
   # pack data
@@ -76,8 +74,9 @@ function initialdata_equation_of_state!(rhs::RHS, eos::EquationOfState.IdealGas,
 end
 
 
-function initialdata_entropy_variables!(rhs::RHS, prms)
-  @unpack cache, equation = rhs
+function initialdata_entropy_variables!(env, rhs::RHS, prms)
+  @unpack cache = env
+  @unpack equation = rhs
   @unpack t, tm1 = get_global_variables(cache)
   @unpack S, Sm1, flx_S, flx_Sm1 = get_static_variables(cache)
   tm1 .= 0.0
@@ -89,20 +88,20 @@ function initialdata_entropy_variables!(rhs::RHS, prms)
 end
 
 
-function initialdata_hrsc!(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC}, prms) end
+function initialdata_hrsc!(env, rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC}, prms) end
 
 
-function initialdata_hrsc!(rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity, prms)
-  @unpack mu = get_cell_variables(rhs.cache)
+function initialdata_hrsc!(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity, prms)
+  @unpack mu = get_cell_variables(env.cache)
   @. mu = 0.0
 end
 
 
-function initialdata_tci!(rhs::RHS, tci::Nothing, prms) end
+function initialdata_tci!(env, rhs::RHS, tci::Nothing, prms) end
 
 
-function initialdata_tci!(rhs::RHS, tci::TCI.EntropyProduction, prms)
-  @unpack cache = rhs
+function initialdata_tci!(env, rhs::RHS, tci::TCI.EntropyProduction, prms)
+  @unpack cache = env
   @unpack EP = get_static_variables(cache)
   @unpack EPjump_lhs, EPjump_rhs = get_bdry_variables(cache)
   @. EP = 0.0
@@ -111,7 +110,7 @@ function initialdata_tci!(rhs::RHS, tci::TCI.EntropyProduction, prms)
 end
 
 
-function initialdata_tci!(rhs::RHS, tci::TCI.AbstractTCI, prms)
-  @unpack flag = get_cell_variables(rhs.cache)
+function initialdata_tci!(env, rhs::RHS, tci::TCI.AbstractTCI, prms)
+  @unpack flag = get_cell_variables(env.cache)
   @. flag = 0.0
 end
diff --git a/src/projects/EulerEq/main.jl b/src/projects/EulerEq/main.jl
deleted file mode 100644
index 862feee8d919f81ea4574c31a387f3c434dcbec2..0000000000000000000000000000000000000000
--- a/src/projects/EulerEq/main.jl
+++ /dev/null
@@ -1,137 +0,0 @@
-using OrderedCollections 
-"""
-    main(parameters_filename)
-
-Run program specified by TOML file `parameters_filename`.
-
-The following sections in the parameter file are recognized:
-- [`Mesh`](@ref)
-- [`EquationOfState`](@ref)
-- [`Initialdata`](@ref)
-- [`Timestep`](@ref)
-- [`HRSC`](@ref)
-- [`Output`](@ref)
-"""
-function main(parameters_filename=""; override::AbstractDict=Dict())
-
-  override_prms = FlatDict(override)
-
-  parameters_filename = Filesystem.abspath(parameters_filename)
-  outputdir = mk_outputdir(parameters_filename)
-  Filesystem.cp(parameters_filename, joinpath(outputdir, Filesystem.basename(parameters_filename)))
-
-  # read parameters
-  if !isfile(parameters_filename)
-    error("Cannot locate parameter file '$parameters_filename'")
-  end
-  prms = FlatDict(TOML.parsefile(parameters_filename))
-  prms = merge(prms, override_prms)
-  ordered_prms = OrderedDict(prms)
-  display(sort(ordered_prms))
-
-  default_eos_prms = Dict("type"=>"IdealGas", "gamma"=>3.0)
-  eos_prms = trim_headers(prms, "EquationOfState")
-  amend!(eos_prms, default_eos_prms)
-  diff = diffkeys(eos_prms, default_eos_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  eos = EquationOfState.make_EquationOfState(eos_prms)
-
-  # setup RHS
-  mesh     = make_Mesh(trim_headers(prms, "Mesh"))
-  cache    = Cache(mesh)
-  equation = EulerEquation(eos)
-  tci      = TCI.make_TCI(trim_headers(prms, "TCI"), mesh)
-  hrsc     = HRSC.make_HRSC(trim_headers(prms, "HRSC"), mesh)
-  rsolver  = ApproxRiemannSolver(flux, speed, equation)
-  ldg_rsolver, av_rsolver = if hrsc isa AbstractArtificialViscosity
-    ApproxRiemannSolver(ldg_flux, ldg_speed, equation),
-    ApproxRiemannSolver(av_flux, av_flux, equation)
-  else
-    nothing, nothing
-  end
-  rhs      = RHS(cache=cache, mesh=mesh, equation=equation, tci=tci, hrsc=hrsc,
-                 rsolver=rsolver, ldg_rsolver=ldg_rsolver, av_rsolver=av_rsolver)
-
-  _register_variables!(rhs)
-  register_variables!(cache, rsolver)
-  !isnothing(ldg_rsolver) && register_variables!(cache, ldg_rsolver)
-  !isnothing(av_rsolver) && register_variables!(cache, av_rsolver, dont_register=true)
-  display(cache)
-
-  initialdata!(rhs, trim_headers(prms, "Initialdata"))
-
-  default_boundaryconditions_prms = Dict("type"=>"periodic")
-  boundaryconditions_prms = trim_headers(prms, "BoundaryConditions")
-  amend!(boundaryconditions_prms, default_boundaryconditions_prms)
-  diff = diffkeys(boundaryconditions_prms, default_boundaryconditions_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  bdryconds = make_BoundaryConditions(rhs, boundaryconditions_prms)
-  ldg_bdryconds, av_bdryconds = if hrsc isa AbstractArtificialViscosity
-    make_BoundaryConditions_LDG(ldg_rsolver, boundaryconditions_prms),
-    make_BoundaryConditions_LDG(av_rsolver, boundaryconditions_prms)
-  else
-    nothing, nothing
-  end
-
-  rhs_fn!(du, u, t) = rhs!(du, u, t, rhs, bdryconds, ldg_bdryconds, av_bdryconds)
-  timestep_fn(u, t, n) = timestep(u, t, rhs)
-
-  ### Callbacks
-  rhscb = make_callback(rhs, bdryconds)
-
-  # Plotting
-  default_plot_prms = Dict("every_iteration"=>0, "every_dt"=>0.0, "pause"=>false)
-  plot_prms = trim_headers(prms, "Plot")
-  dg1d.amend!(plot_prms, default_plot_prms)
-  diff = diffkeys(plot_prms, default_plot_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  theme(:dark, markerstrokewidth=0.1, marker=:circle)
-  plotfn(u,t) = plot(rhs)
-  plotcb = PlotCallback(plotfn,
-                        CallbackTiming(every_dt=plot_prms["every_dt"],
-                                       every_iteration=plot_prms["every_iteration"]),
-                        pause=plot_prms["pause"])
-  default_output_prms = Dict("every_iteration"=>0,
-                             "every_dt"=>0.5,
-                             "variables"=>["rho", "q", "E"],
-                             "filename"=>joinpath(outputdir, "output"),
-                             "aligned_times"=>[])
-  output_prms = trim_headers(prms, "Output")
-  amend!(output_prms, default_output_prms)
-  diff = diffkeys(output_prms, default_output_prms)
-  @assert length(diff) == 0 "Section 'Output': Unknown options '$diff' encountered!"
-  savecb = SaveCallback(Symbol.(output_prms["variables"]), cache, mesh, 
-                        output_prms["filename"],
-                        CallbackTiming(every_dt=output_prms["every_dt"],
-                                       every_iteration=output_prms["every_iteration"])
-                       )
-
-  # Combine callbacks
-  callbacks = CallbackSet(rhscb.callbacks..., savecb, plotcb)
-
-  aligned_times = get(output_prms, "aligned_times", [])
-  timealigned_savecb, timestep_fn = if length(aligned_times) > 0
-    atscb = TimeAlignedSaveCallback(aligned_times, timestep_fn, savecb)
-    atscb, atscb.aligned_timestep_fn
-  else
-    nothing, timestep_fn
-  end
-
-  !isnothing(timealigned_savecb) && push!(callbacks, timealigned_savecb)
-
-  default_evolve_prms = Dict("algorithm"=>"LSERK4", "tend"=>1.0)
-  evolve_prms = trim_headers(prms, "Evolution")
-  amend!(evolve_prms, default_evolve_prms)
-  diff = diffkeys(evolve_prms, default_evolve_prms)
-  @assert length(diff) == 0 "Section 'Evolution': Unknown options '$diff' encountered!"
-
-  @unpack rho, q, E = get_dynamic_variables(cache)
-  state_u = vcat(rho, q, E)
-
-  state_u = dg1d.evolve_ERK(rhs_fn!, state_u, timestep_fn,
-                      (0.0, Float64(evolve_prms["tend"])),
-                      Symbol(evolve_prms["algorithm"]),
-                      callback_fullstep=callbacks)
-
-  return
-end
diff --git a/src/projects/EulerEq/plots.jl b/src/projects/EulerEq/plots.jl
index 93a6b75ab2e21a6dfc769eda6c52a6c36ba465ab..a57b2d70c0a805d83b199213e8f0e7e66de0a252 100644
--- a/src/projects/EulerEq/plots.jl
+++ b/src/projects/EulerEq/plots.jl
@@ -1,8 +1,8 @@
-function plot(rhs::RHS)
-  plt_eq   = plot_equation(rhs)
+function plot(env, rhs::RHS)
+  plt_eq   = plot_equation(env, rhs)
   plt_rhs  = nothing # plot_rhs(rhs)
-  plt_hrsc = plot_hrsc(rhs, rhs.hrsc)
-  plt_tci  = plot_tci(rhs, rhs.tci)
+  plt_hrsc = plot_hrsc(env, rhs, rhs.hrsc)
+  plt_tci  = plot_tci(env, rhs, rhs.tci)
 
   # drop missing plots and figure out layout
   list_sub_plts = [ plt_eq, plt_rhs, plt_hrsc, plt_tci ]
@@ -11,7 +11,7 @@ function plot(rhs::RHS)
   sub_plt = Plots.plot(list_sub_plts..., layout=layout)
 
   # combine plots
-  @unpack cache = rhs
+  @unpack cache = env
   @unpack t = get_global_variables(cache)
   plt = Plots.plot(sub_plt, layout=layout, size=(1400,1000), plot_title="t=$(t[1])",
                    left_margin=10Plots.mm)
@@ -24,9 +24,9 @@ end
 #######################################################################
 
 
-function plot_equation(rhs)
+function plot_equation(env, rhs)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack rho, q, E   = get_dynamic_variables(cache)
   @unpack p, eps, S   = get_static_variables(cache)
   @unpack t           = get_global_variables(cache)
@@ -56,7 +56,7 @@ function plot_equation(rhs)
   end
 
   plt = Plots.plot(plts..., layout=(3,2))
- 
+
   return plt
 end
 
@@ -66,12 +66,12 @@ end
 #######################################################################
 
 
-function plot_hrsc(rhs, hrsc::Nothing) end
+function plot_hrsc(env, rhs, hrsc::Nothing) end
 
 
-function plot_hrsc(rhs, hrsc::HRSC.AbstractArtificialViscosity)
+function plot_hrsc(env, rhs, hrsc::HRSC.AbstractArtificialViscosity)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack mu          = get_cell_variables(cache)
   @unpack x           = mesh
 
@@ -93,11 +93,11 @@ function plot_hrsc(rhs, hrsc::HRSC.AbstractArtificialViscosity)
 end
 
 
-function plot_hrsc(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
+function plot_hrsc(env, rhs, hrsc::HRSC.SmoothedArtificialViscosity)
 
-  plt_mu = plot_hrsc(rhs, hrsc.av)
+  plt_mu = plot_hrsc(env, rhs, hrsc.av)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack smoothed_mu = get_static_variables(cache)
   @unpack x           = mesh
 
@@ -122,7 +122,7 @@ function plot_hrsc(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
 end
 
 
-plot_hrsc(rhs, hrsc::AbstractReconstruction) = nothing
+plot_hrsc(env, rhs, hrsc::AbstractReconstruction) = nothing
 
 
 #######################################################################
@@ -130,12 +130,12 @@ plot_hrsc(rhs, hrsc::AbstractReconstruction) = nothing
 #######################################################################
 
 
-function plot_tci(rhs, tci::Nothing) end
+function plot_tci(env, rhs, tci::Nothing) end
 
 
-function plot_tci(rhs, tci::TCI.AbstractTCI) 
+function plot_tci(env, rhs, tci::TCI.AbstractTCI) 
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack flag        = get_cell_variables(cache)
   @unpack x           = mesh
 
@@ -165,9 +165,9 @@ function plot_tci(rhs, tci::TCI.AbstractTCI)
 end
 
 
-function plot_tci(rhs, tci::TCI.EntropyProduction)
+function plot_tci(env, rhs, tci::TCI.EntropyProduction)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack EP          = get_static_variables(cache)
   @unpack flag        = get_cell_variables(cache)
   @unpack x           = mesh
@@ -203,9 +203,9 @@ end
 #######################################################################
 
 
-function plot_rhs(rhs)
+function plot_rhs(env, rhs)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack rhs_rho, rhs_q, rhs_E = get_rhs_variables(cache)
   @unpack t                     = get_global_variables(cache)
   @unpack x                     = mesh
diff --git a/src/projects/EulerEq/rhs.jl b/src/projects/EulerEq/rhs.jl
index 0895a2ca1d28c096ee0be4c8741c25c8d5f58d92..c5f8061acbcf1e868e2abf0b30f3105118957ad6 100644
--- a/src/projects/EulerEq/rhs.jl
+++ b/src/projects/EulerEq/rhs.jl
@@ -1,97 +1,11 @@
-Base.@kwdef mutable struct RHS{T_Equation <:EulerEquation, 
-                               T_RSolver  <:AbstractRiemannSolver,
-                               T_HRSC     <:Maybe{HRSC.AbstractHRSC},
-                               T_TCI      <:Maybe{TCI.AbstractTCI},
-                               T_LDG_RSolver<:Maybe{AbstractRiemannSolver},
-                               T_AV_RSolver<:Maybe{AbstractRiemannSolver}}
-
-  cache::Cache
-  mesh::Mesh
-  equation::T_Equation
-  rsolver::T_RSolver
-  hrsc::T_HRSC               = nothing
-  tci::T_TCI                 = nothing
-  ldg_rsolver::T_LDG_RSolver = nothing
-  av_rsolver::T_AV_RSolver   = nothing
-end
-
-
-#######################################################################
-#                         Register variables                          #
-#######################################################################
-
-
-# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
-# definition in this module without a warning or error. This does not happen for methods
-# defined in Base.
-# Because of that we prefix with _ below.
-# Cf. https://github.com/JuliaLang/julia/issues/15510
-function _register_variables!(rhs::RHS)
-  register_variables!(rhs.cache,
-    dynamic_variablenames = (:rho, :q, :E,),     # density, momentum density, energy
-    rhs_variablenames     = (:rhs_rho, :rhs_q, :rhs_E),
-    static_variablenames  = (:flx_rho, :flx_q, :flx_E,
-                             :p, :eps, :v,        # pressure, internal energy, speed
-                             :S, :Sm1,           # entropy
-                             :flx_S, :flx_Sm1),  # entropy flux
-    cell_variablenames    = (:max_v,),
-    global_variablenames  = (:t, :tm1) # recent time stamps
-  )
-  _register_variables!(rhs, rhs.hrsc)
-  _register_variables!(rhs, rhs.tci)
-end
-
-
-_register_variables!(rhs, tci_or_hrsc::Nothing) = nothing
-
-_register_variables!(rhs, hrsc::AbstractReconstruction) = nothing
-
-
-function _register_variables!(rhs, hrsc::HRSC.AbstractArtificialViscosity)
-  register_variables!(rhs.cache,
-    static_variablenames  = (:ldg_rho, :ldg_q, :ldg_E,              # local DG variable
-                             :flx_ldg_rho, :flx_ldg_q, :flx_ldg_E), # local DG flux
-    cell_variablenames    = (:mu,) # 'one viscosity to rule them all'
-    )
-end
-
-
-function _register_variables!(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
-  _register_variables!(rhs, hrsc.av)
-  register_variables!(rhs.cache,
-    static_variablenames  = (:smoothed_mu,)
-  )
-end
-
-
-function _register_variables!(rhs, tci::TCI.AbstractTCI)
-  register_variables!(rhs.cache, cell_variablenames = (:flag,))
-end
-
-
-function _register_variables!(rhs, tci::TCI.EntropyProduction)
-  register_variables!(rhs.cache, 
-                      cell_variablenames = (:flag,),
-                      static_variablenames = (:EP,), # entropy production
-                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs) 
-                                              # entropy production due to jumps at interfaces
-  )
-end
-
-
 #######################################################################
 #                              Timestep                               #
 #######################################################################
 
 
-function timestep(state_u, t, rhs::RHS)
-  wrap_dynamic_variables!(rhs.cache, state_u)
-  return timestep(rhs, rhs.hrsc)
-end
-
-
-function timestep(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
-  @unpack cache, mesh, equation = rhs
+function timestep(env, rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
+  @unpack cache, mesh = env
+  @unpack equation = rhs
 
   broadcast_volume!(speed, equation, cache)
   v = get_variable(cache, :v)
@@ -110,8 +24,9 @@ function timestep(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
 end
 
 
-function timestep(rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity)
-  @unpack cache, mesh, equation = rhs
+function timestep(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity)
+  @unpack cache, mesh = env
+  @unpack equation = rhs
 
   broadcast_volume!(speed, equation, cache)
   v = get_variable(cache, :v)
@@ -142,20 +57,10 @@ end
 #######################################################################
 
 
-function rhs!(state_du, state_u, t, rhs::RHS,
-    bdryconds::BoundaryConditions, ldg_bdryconds::Maybe{BoundaryConditions},
-    av_bdryconds::Maybe{BoundaryConditions})
-
-  @unpack cache = rhs
-  wrap_dynamic_variables!(cache, state_u)
-  wrap_rhs_variables!(cache, state_du)
-  rhs!(rhs, t, rhs.hrsc, bdryconds, ldg_bdryconds, av_bdryconds)
-end
-
-
-function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds)
+function rhs!(env, rhs::RHS, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack cache, mesh                   = env
+  @unpack equation, rsolver             = rhs
   @unpack rho, q, E                     = get_dynamic_variables(cache)
   @unpack flx_rho, flx_q, flx_E, p, eps = get_static_variables(cache)
   @unpack rhs_rho, rhs_q, rhs_E         = get_rhs_variables(cache)
@@ -165,7 +70,7 @@ function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds
 
   broadcast_volume!(flux, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   compute_rhs_weak_form!(rhs_rho, flx_rho,  lhs_numflx_rho, rhs_numflx_rho, mesh)
   compute_rhs_weak_form!(rhs_q,   flx_q,    lhs_numflx_q,   rhs_numflx_q,   mesh)
@@ -175,9 +80,10 @@ function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds
 end
 
 
-function rhs!(rhs::RHS, t, hrsc::AbstractReconstruction, bdryconds, ldg_bdryconds, av_bdryconds)
+function rhs!(env, rhs::RHS, hrsc::AbstractReconstruction, bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack cache, mesh                    = env
+  @unpack equation, rsolver              = rhs
   @unpack rho, q, E                      = get_dynamic_variables(cache)
   @unpack flx_rho, flx_q, flx_E, p, eps  = get_static_variables(cache)
   @unpack flag                           = get_cell_variables(cache)
@@ -192,7 +98,7 @@ function rhs!(rhs::RHS, t, hrsc::AbstractReconstruction, bdryconds, ldg_bdrycond
 
   broadcast_volume!(flux, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   compute_rhs_weak_form!(rhs_rho, flx_rho,  lhs_numflx_rho, rhs_numflx_rho, mesh)
   compute_rhs_weak_form!(rhs_q,   flx_q,    lhs_numflx_q,   rhs_numflx_q,   mesh)
@@ -202,14 +108,15 @@ function rhs!(rhs::RHS, t, hrsc::AbstractReconstruction, bdryconds, ldg_bdrycond
 end
 
 
-function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
+function rhs!(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity,
     bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver, ldg_rsolver, av_rsolver = rhs
+  @unpack cache, mesh = env
+  @unpack equation, rsolver, ldg_rsolver, av_rsolver = rhs
 
   @unpack rho, q, E                         = get_dynamic_variables(cache)
   @unpack flx_rho, flx_q, flx_E, p, eps,
-					ldg_rho, ldg_q, ldg_E,
+                                        ldg_rho, ldg_q, ldg_E,
           flx_ldg_rho, flx_ldg_q, flx_ldg_E = get_static_variables(cache)
   @unpack rhs_rho, rhs_q, rhs_E             = get_rhs_variables(cache)
   @unpack lhs_numflx_rho,     rhs_numflx_rho, 
@@ -222,7 +129,7 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
   ## solve auxiliary equation: q + ∂x u = 0
   broadcast_volume!(ldg_flux, equation, cache)
   broadcast_faces!(central_flux, ldg_rsolver, cache, mesh)
-  broadcast_boundaryconditions!(central_flux, ldg_bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(central_flux, ldg_bdryconds, cache, mesh, 0.0)
   compute_rhs_weak_form!(ldg_rho, flx_ldg_rho,  lhs_numflx_ldg_rho, rhs_numflx_ldg_rho, mesh)
   compute_rhs_weak_form!(ldg_q,   flx_ldg_q,    lhs_numflx_ldg_q,   rhs_numflx_ldg_q,   mesh)
   compute_rhs_weak_form!(ldg_E,   flx_ldg_E,    lhs_numflx_ldg_E,   rhs_numflx_ldg_E,   mesh)
@@ -230,10 +137,10 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
   ## compute rhs of regularized equation: ∂t u + ∂x f + ∂x μ q = 0
   broadcast_volume!(av_flux, equation, cache)
   broadcast_faces!(mean_flux, av_rsolver, cache, mesh)
-  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, 0.0)
   broadcast_volume!(flux, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   @. flx_rho        += flx_ldg_rho
   @. flx_q          += flx_ldg_q
diff --git a/src/projects/EulerEq/setup.jl b/src/projects/EulerEq/setup.jl
new file mode 100644
index 0000000000000000000000000000000000000000..32ba0bcf8dd16f0ce9ef8787d1f20d0ef1c59a89
--- /dev/null
+++ b/src/projects/EulerEq/setup.jl
@@ -0,0 +1,115 @@
+function RHS(env::Environment, prms)
+
+  # construct RHS
+  eos = EquationOfState.make_EquationOfState(prms["EquationOfState"])
+  equation = EulerEquation(eos)
+  tci  = TCI.make_TCI(env.mesh, prms["TCI"])
+  hrsc = HRSC.make_HRSC(env.mesh, prms["HRSC"])
+  rsolver = ApproxRiemannSolver(flux, speed, equation)
+  ldg_rsolver, av_rsolver = if hrsc isa AbstractArtificialViscosity
+    ApproxRiemannSolver(ldg_flux, ldg_speed, equation),
+    ApproxRiemannSolver(av_flux, av_flux, equation)
+  else
+    nothing, nothing
+  end
+
+  rhs = RHS(equation, rsolver, hrsc, tci, ldg_rsolver, av_rsolver)
+
+  # register variables
+  # TODO add a ::Nothing overload for register_variables!
+  # TODO Somehow replace _register_variables! with register_variables!
+  @unpack cache = env
+  _register_variables!(cache, rhs)
+  register_variables!(cache, rsolver)
+  !isnothing(ldg_rsolver) && register_variables!(cache, ldg_rsolver)
+  !isnothing(av_rsolver) && register_variables!(cache, av_rsolver, dont_register=true)
+  display(cache)
+
+  # setup boundary conditions
+  bdryconds = make_BoundaryConditions(env, equation, rsolver, prms["EulerEq"])
+  ldg_bdryconds, av_bdryconds = if hrsc isa AbstractArtificialViscosity
+    make_BoundaryConditions_LDG(env, equation, ldg_rsolver, prms["EulerEq"]),
+    make_BoundaryConditions_LDG(env, equation, av_rsolver, prms["EulerEq"])
+  else
+    nothing, nothing
+  end
+
+  # TODO Bdry conditions should be moved to project and should only be just a 'lightweight'
+  # descriptor. Furthermore, they should also register variables in cache themselves.
+  # Note: Atm, only the 'from_initial_data' option would require a place to store the data
+  # somewhere. For periodic BCs we don't need extra storage at all.
+  # But what other types of boundary conditions will be needed in the future?
+
+  # setup initial data
+  initialdata!(env, rhs, prms["EulerEq"])
+
+  # setup callbacks
+  rhscb = make_callback(env, rhs, bdryconds)
+
+  append!(env.callbacks, CallbackSet(rhscb.callbacks...))
+
+  return rhs, (bdryconds, ldg_bdryconds, av_bdryconds)
+end
+
+
+#######################################################################
+#                         Register variables                          #
+#######################################################################
+
+
+# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
+# definition in this module without a warning or error. This does not happen for methods
+# defined in Base.
+# Because of that we prefix with _ below.
+# Cf. https://github.com/JuliaLang/julia/issues/15510
+function _register_variables!(cache, rhs::RHS)
+  register_variables!(cache,
+    dynamic_variablenames = (:rho, :q, :E,),     # density, momentum density, energy
+    rhs_variablenames     = (:rhs_rho, :rhs_q, :rhs_E),
+    static_variablenames  = (:flx_rho, :flx_q, :flx_E,
+                             :p, :eps, :v,        # pressure, internal energy, speed
+                             :S, :Sm1,           # entropy
+                             :flx_S, :flx_Sm1),  # entropy flux
+    cell_variablenames    = (:max_v,),
+    global_variablenames  = (:t, :tm1) # recent time stamps
+  )
+  _register_variables!(cache, rhs.hrsc)
+  _register_variables!(cache, rhs.tci)
+end
+
+
+_register_variables!(cache, tci_or_hrsc::Nothing) = nothing
+
+_register_variables!(cache, hrsc::AbstractReconstruction) = nothing
+
+
+function _register_variables!(cache, hrsc::HRSC.AbstractArtificialViscosity)
+  register_variables!(rhs.cache,
+    static_variablenames  = (:ldg_rho, :ldg_q, :ldg_E,              # local DG variable
+                             :flx_ldg_rho, :flx_ldg_q, :flx_ldg_E), # local DG flux
+    cell_variablenames    = (:mu,) # 'one viscosity to rule them all'
+    )
+end
+
+
+function _register_variables!(cache, hrsc::HRSC.SmoothedArtificialViscosity)
+  _register_variables!(cache, hrsc.av)
+  register_variables!(cache,
+    static_variablenames  = (:smoothed_mu,)
+  )
+end
+
+
+function _register_variables!(cache, tci::TCI.AbstractTCI)
+  register_variables!(cache, cell_variablenames = (:flag,))
+end
+
+
+function _register_variables!(cache, tci::TCI.EntropyProduction)
+  register_variables!(cache,
+                      cell_variablenames = (:flag,),
+                      static_variablenames = (:EP,), # entropy production
+                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs)
+                                              # entropy production due to jumps at interfaces
+  )
+end
diff --git a/src/projects/EulerEq/types.jl b/src/projects/EulerEq/types.jl
new file mode 100644
index 0000000000000000000000000000000000000000..171e9d91e72303b2a8ed23bccc5ec5cb6fb3353c
--- /dev/null
+++ b/src/projects/EulerEq/types.jl
@@ -0,0 +1,19 @@
+struct EulerEquation{T_EoS<:EquationOfState.AbstractEquationOfState} <: AbstractEquation
+  eos::T_EoS
+end
+
+
+mutable struct RHS{T_Equation <:EulerEquation,
+                   T_RSolver  <:AbstractRiemannSolver,
+                   T_HRSC     <:Maybe{HRSC.AbstractHRSC},
+                   T_TCI      <:Maybe{TCI.AbstractTCI},
+                   T_LDG_RSolver<:Maybe{AbstractRiemannSolver},
+                   T_AV_RSolver<:Maybe{AbstractRiemannSolver}}
+
+  equation::T_Equation
+  rsolver::T_RSolver
+  hrsc::T_HRSC
+  tci::T_TCI
+  ldg_rsolver::T_LDG_RSolver
+  av_rsolver::T_AV_RSolver
+end
diff --git a/src/projects/GRHD/GRHD.jl b/src/projects/GRHD/GRHD.jl
index 765fa8c3c1ec5654f8b34bdb4428dbce4a4e0b86..903c7005f8b2bd64bc1c1e59b9f40749310c862f 100644
--- a/src/projects/GRHD/GRHD.jl
+++ b/src/projects/GRHD/GRHD.jl
@@ -6,18 +6,74 @@ using dg1d
 using dg1d.EquationOfState
 using Interpolations
 using LinearAlgebra
-using Match
 using Plots
 using OrderedCollections
 
 
+include("types.jl")
 include("equation.jl")
 include("rhs.jl")
 include("initialdata.jl")
 include("callbacks.jl")
 include("plots.jl")
 include("boundaryconditions.jl")
-include("main.jl")
+include("setup.jl")
+
+
+#######################################################################
+#                 dg1d project interface definitions                  #
+#######################################################################
+
+
+dg1d.project_type(::Val{:GRHD}) = RHS
+
+# TODO Remove bdryconds
+dg1d.rhs!(env::Environment, p::RHS, bdryconds) = rhs!(env, p, p.hrsc, bdryconds...)
+
+dg1d.timestep!(env::Environment, p::RHS) = timestep(env, p, p.hrsc)
+
+dg1d.@parameters GRHD begin
+
+  """
+  Multiplicative factor to determine restmass density for the artificial atmosphere
+  relative to the maximum of restmass density over the grid.
+  """
+  atm_factor = 1e-10
+
+  """
+  Multiplicative factor to determine the cutoff restmass density below which
+  we impose artificial atmosphere.
+  """
+  atm_threshold_factor = 100.0
+
+  """
+  Available options:
+  - `periodic`
+  - `from_initia_data`
+  """
+  bc = "periodic"
+
+  """
+  Available options:
+  - `tov`
+  - `bondi_accretion`
+  - `atmosphere`
+  """
+  id = "atmosphere"
+
+  """
+  Path to an .h5 file containing initial data. Supported initial data types:
+  - `tov`
+  - `bondi_accretion`
+  """
+  id_filename = ""
+
+  """
+  Restmass density for `atmosphere` initial data.
+  """
+  id_atmosphere_density = 1e-11
+
+end
 
 
 end # module
diff --git a/src/projects/GRHD/boundaryconditions.jl b/src/projects/GRHD/boundaryconditions.jl
index 516f30955d00d0c675db1d486bbf2436f91ed525..1d3872258f5f88fdb199f662ea7acb7bef77aa0f 100644
--- a/src/projects/GRHD/boundaryconditions.jl
+++ b/src/projects/GRHD/boundaryconditions.jl
@@ -1,32 +1,40 @@
-function make_BoundaryConditions(rhs::RHS, prms)
-  type = query(String, prms, "type")
-
-  xrange = extrema(rhs.mesh.x)
-  broadcast_volume!(scaledcons2cons, rhs.equation, rhs.cache)
-
-  lhs_bc, rhs_bc = @match type begin
-    "periodic"          => (PeriodicBC(), PeriodicBC())
-    "from initial data" => begin
-      @unpack sD, sSr, stau = get_dynamic_variables(rhs.cache)
-      @unpack D, Sr, tau, A, B, r, rho, vr, eps, p = get_static_variables(rhs.cache)
-      lhs_state = (sD[1], sSr[1], stau[1], p[1], rho[1], vr[1], eps[1], A[1], B[1], r[1])
-      rhs_state = (sD[end], sSr[end], stau[end], p[end], rho[end], vr[end], eps[end], A[end], B[end], r[end])
-      (DirichletBC(lhs_state), DirichletBC(rhs_state))
-    end
-    _ => error("Boundary condition type '$type' not implemented!")
+function make_BoundaryConditions(env, equation, rsolver, prms)
+  @unpack bc = prms["GRHD"]
+  @unpack cache = env
+
+  broadcast_volume!(scaledcons2cons, equation, cache)
+
+  lhs_bc, rhs_bc = if bc == "periodic"
+    PeriodicBC(), PeriodicBC()
+  elseif bc == "from_initial_data"
+    @unpack sD, sSr, stau = get_dynamic_variables(cache)
+    @unpack D, Sr, tau, A, B, r, rho, vr, eps, p = get_static_variables(cache)
+    lhs_state = (sD[1], sSr[1], stau[1], p[1], rho[1], vr[1], eps[1],
+                 A[1], B[1], r[1])
+    rhs_state = (sD[end], sSr[end], stau[end], p[end], rho[end], vr[end], eps[end],
+                 A[end], B[end], r[end])
+    DirichletBC(lhs_state), DirichletBC(rhs_state)
+  else
+    error("GRHD: Boundary condition '$type' not implemented!")
   end
 
-  return BoundaryConditions(lhs_bc, rhs_bc, rhs.rsolver)
+  return BoundaryConditions(lhs_bc, rhs_bc, rsolver)
 end
 
 
-function make_BoundaryConditions_LDG(ldg_rsolver, prms)
+function make_BoundaryConditions_LDG(env, equation, ldg_rsolver, prms)
+
+  @unpack bc = prms["GRHD"]
+
   isnothing(ldg_rsolver) && return nothing
-  type = query(String, prms, "type")
-  lhs_bc, rhs_bc = if type == "periodic"
+
+  lhs_bc, rhs_bc = if bc == "periodic"
     PeriodicBC(), PeriodicBC()
-  else
+  elseif bc == "from_initial_data"
     OutflowBC(), OutflowBC()
+  else
+    error("GRHD: Boundary condition '$type' not implemented!")
   end
+
   return BoundaryConditions(lhs_bc, rhs_bc, ldg_rsolver)
 end
diff --git a/src/projects/GRHD/callbacks.jl b/src/projects/GRHD/callbacks.jl
index 23af3017509151f1a8859751f145adaf15f5c781..5ae5fc65ad804921f4c8e13fd6ac9bb0c1281169 100644
--- a/src/projects/GRHD/callbacks.jl
+++ b/src/projects/GRHD/callbacks.jl
@@ -1,11 +1,11 @@
-function make_callback(rhs::RHS, bdryconds::BoundaryConditions)
-  cbfn_equation(u, t) = callback_equation(u, t, rhs, isperiodic(bdryconds), rhs.equation)
+function make_callback(env, rhs::RHS, bdryconds::BoundaryConditions)
+  cbfn_equation(u, t) = callback_equation(u, t, env, rhs, isperiodic(bdryconds), rhs.equation)
   cb_equation         = FunctionCallback(cbfn_equation,
                                          CallbackTiming(every_iteration=1,every_dt=0))
-  cbfn_tci(u, t)      = callback_tci(u, t, rhs, isperiodic(bdryconds), rhs.tci)
+  cbfn_tci(u, t)      = callback_tci(u, t, env, rhs, isperiodic(bdryconds), rhs.tci)
   cb_tci              = FunctionCallback(cbfn_tci,
                                          CallbackTiming(every_iteration=1, every_dt=0))
-  cbfn_hrsc(u, t)     = callback_hrsc(u, t, rhs, isperiodic(bdryconds), rhs.hrsc)
+  cbfn_hrsc(u, t)     = callback_hrsc(u, t, env, rhs, isperiodic(bdryconds), rhs.hrsc)
   cb_hrsc             = FunctionCallback(cbfn_hrsc,
                                          CallbackTiming(every_iteration=1,every_dt=0))
   callbackset = CallbackSet(cb_equation, cb_tci, cb_hrsc)
@@ -18,8 +18,8 @@ end
 #######################################################################
 
 
-function callback_equation(state_u, state_t, rhs, isperiodic, eq)
-  @unpack cache = rhs
+function callback_equation(state_u, state_t, env, rhs, isperiodic, eq)
+  @unpack cache = env
   wrap_dynamic_variables!(cache, state_u)
   @unpack t, tm1 = get_global_variables(cache)
   tm1 .= t
@@ -33,17 +33,18 @@ end
 #######################################################################
 
 
-callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::Nothing) = nothing
-callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::AbstractReconstruction) = nothing
+callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::Nothing) = nothing
+callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::AbstractReconstruction) = nothing
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::Maybe{HRSC.AbstractHRSC}) 
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::Maybe{HRSC.AbstractHRSC})
   TODO(callback_hrsc)
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractArtificialViscosity)
-  @unpack cache, mesh, equation = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.AbstractArtificialViscosity)
+  @unpack cache, mesh           = env
+  @unpack equation              = rhs
   @unpack max_v                 = get_static_variables(cache)
   @unpack mu, cellmax_v         = get_cell_variables(cache)
   @unpack sD                    = get_dynamic_variables(cache)
@@ -60,20 +61,20 @@ function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractArt
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.TCIViscosity)
-  @unpack cache, mesh = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.TCIViscosity)
+  @unpack cache, mesh = env
   @unpack mu, flag    = get_cell_variables(cache)
   HRSC.compute_viscosity!(mu, flag, hrsc)
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.SmoothedArtificialViscosity)
-  callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc.av)
-  @unpack cache       = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.SmoothedArtificialViscosity)
+  callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc.av)
+  @unpack cache, mesh = env
   @unpack mu          = get_cell_variables(cache)
   indices = findall(muk -> muk > 0.0, mu)
   tmp = zeros(size(mu))
-  _, K = layout(rhs.mesh)
+  _, K = layout(mesh)
   # artificially broaden viscosity
   for k in indices
     k_lhs, k_rhs = periodic_index(k-1, K), periodic_index(k+1, K)
@@ -88,9 +89,9 @@ function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.SmoothedArt
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.EntropyViscosity)
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.EntropyViscosity)
   TODO(callback_hrsc)
-  @unpack cache, mesh            = rhs
+  @unpack cache, mesh            = env
   @unpack rho                    = get_dynamic_variables(cache)
   @unpack S, Sm1, flx_S, flx_Sm1 = get_static_variables(cache)
   @unpack mu, cellmax_v          = get_cell_variables(cache)
@@ -107,19 +108,19 @@ end
 #######################################################################
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::Nothing) end
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::Nothing) end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.Threshold)
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::TCI.Threshold)
   error("Threshold TCI must be combined with an indicator that is supposed to be zero for " *
         "for smooth solutions, but not for shocks. TODO Implement an indicator variable.")
 end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.EntropyProduction)
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::TCI.EntropyProduction)
   @warn "Figure out entropy fluxes for special relativistic euler equation!"
   TODO(callback_tci)
-  @unpack cache, mesh                = rhs
+  @unpack cache, mesh                = env
   @unpack E, Em1, flx_E, flx_Em1, EP = get_static_variables(cache)
   @unpack flag                       = get_cell_variables(cache)
   @unpack EPjump_lhs, EPjump_rhs     = get_bdry_variables(cache)
@@ -130,11 +131,11 @@ function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.EntropyProduct
 end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic,
-    tci::Union{TCI.Diffusion, TCI.Minmod, TCI.Threshold, 
+function callback_tci(state_u, state_t, env, rhs, isperiodic,
+    tci::Union{TCI.Diffusion, TCI.Minmod, TCI.Threshold,
                TCI.ModalDecayAverage, TCI.ModalDecayHighest})
 
-  @unpack cache, mesh                        = rhs
+  @unpack cache, mesh                        = env
   @unpack sD, sSr, stau                      = get_dynamic_variables(cache)
   @unpack flag, sD_flag, sSr_flag, stau_flag = get_cell_variables(cache)
   TCI.compute_indicator!(sD_flag, sD, tci)
diff --git a/src/projects/GRHD/equation.jl b/src/projects/GRHD/equation.jl
index 77b64dc1e180b6f04d8666b1e9ea97c3b9d1b135..8fff0cd6cf9b5bc65df145218a69f59530b94097 100644
--- a/src/projects/GRHD/equation.jl
+++ b/src/projects/GRHD/equation.jl
@@ -1,46 +1,4 @@
-#######################################################################
-#                                Types                                #
-#######################################################################
-
-
-Base.@kwdef struct Atmosphere
-  factor::Float64
-  threshold::Float64
-end
-
-
-function parse_parameters(::Type{Atmosphere}, prms)
-  factor    = query(Float64, prms, "factor", default=1e-7)
-  threshold = query(Float64, prms, "threshold", default=100.0)
-
-  @assert factor    > 0
-  @assert threshold > 0
-
-  return (; factor, threshold)
-end
-
-
-Atmosphere(prms::AbstractDict) = Atmosphere(; parse_parameters(Atmosphere, prms)...)
-
-
-"""
-    struct Equation <: AbstractEquationOfState
-
-Represents the equations of General Relativistic Hydrodynamics (GRHD).
-"""
-Base.@kwdef struct Equation{T_EoS<:AbstractEquationOfState} <: AbstractEquation
-  eos::T_EoS
-  atmosphere::Atmosphere
-  cons2prim_newtonsolver::NewtonSolver
-  cons2prim_bisection::Bisection
-end
-
-
-#######################################################################
-#                           Implementation                            #
-#######################################################################
-
-
+### TODO Move include to GRHD.jl or merge with this file?
 include("cons2prim_implementations.jl")
 
 
diff --git a/src/projects/GRHD/initialdata.jl b/src/projects/GRHD/initialdata.jl
index 8cc2a6ca088b8d8dc72a2064b51ebd5452c648b4..248c5219bf0248010010caa8f72d56a09c0698e6 100644
--- a/src/projects/GRHD/initialdata.jl
+++ b/src/projects/GRHD/initialdata.jl
@@ -1,16 +1,14 @@
-struct InitialData{T} end
-
-
-function initialdata!(rhs::RHS, parameters)
-  type = query(parameters, "type")
-  type = Symbol(lowercase(replace(type, " "=>"_")))
-  id_type = InitialData{type}()
-
-  initialdata_equation!(id_type, rhs, parameters)
+function initialdata!(env, rhs::RHS, prms)
+  @unpack id = prms
+  let
+    @unpack r = get_static_variables(env.cache)
+    @. r = env.mesh.x[:]
+  end
+  initialdata_equation!(Val(Symbol(id)), env, rhs, prms)
   return
-  # initialdata_entropy_variables!(id_type, rhs, parameters)
-  initialdata_hrsc!(id_type, rhs, rhs.hrsc, parameters)
-  initialdata_tci!(id_type, rhs, rhs.tci, parameters)
+  # initialdata_entropy_variables!(id, rhs, prms)
+  initialdata_hrsc!(Val(Symbol(id)), env, rhs, rhs.hrsc, prms)
+  initialdata_tci!(Val(Symbol(id)), env, rhs, rhs.tci, prms)
 end
 
 
@@ -19,24 +17,29 @@ end
 #######################################################################
 
 
-function initialdata_equation!(id::InitialData, rhs, parameters)
-  error_not_implemented("initialdata_equation!", typeof(id))
+function initialdata_equation!(::Val{T}, env, rhs, parameters) where T
+  error_not_implemented("initialdata_equation!", string(T))
 end
 
 
-function initialdata_equation!(::InitialData{:bondi_accretion}, rhs, parameters)
+function initialdata_equation!(::Val{:bondi_accretion}, env, rhs, prms)
   TODO(initialdata_equation!)
 end
 
 
-function initialdata_equation!(::InitialData{:tov}, rhs, parameters)
+function initialdata_equation!(::Val{:tov}, env, rhs, prms)
 
-  filename = query(parameters, "filename")
+  filename = prms["id_filename"]
+  if length(filename) == 0
+    error("GRHD: Missing filename for TOV output, include with GRHD.id_filename = 'tov'")
+  end
+  filename = joinpath(get_output_dir(), filename)
 
   # read TOV result from file
-  # only contains solution in star interior
-  output_dir = dg1d.get_output_dir()
-  data  = TOV.read_data(joinpath(output_dir, "TOV.h5"))
+  # should only contain solution in star interior
+  # output_dir = dg1d.get_output_dir()
+  # data  = TOV.read_data(joinpath(output_dir, "TOV.h5"))
+  data  = TOV.read_data(filename)
   ϕint  = data.ϕ
   Rint  = data.R
   rint  = data.r
@@ -49,8 +52,9 @@ function initialdata_equation!(::InitialData{:tov}, rhs, parameters)
   # interpolate data onto our grid
   # we don't assume a special grid layout for the loaded data
 
-  @unpack mesh = rhs
+  @unpack cache, mesh = env
   @unpack Npts = mesh.element
+
   intrp_R = vec(mesh.x) # DG coordinates
   NN = length(intrp_R)
 
@@ -142,7 +146,7 @@ function initialdata_equation!(::InitialData{:tov}, rhs, parameters)
   @assert all(map(X->all(isfinite.(X)), [intrp_A, intrp_B, intrp_A_r, intrp_B_r])) == true ""
     "Initial data interpolation failed!"
 
-  @unpack A, B, A_r, B_r = get_static_variables(rhs.cache)
+  @unpack A, B, A_r, B_r = get_static_variables(cache)
   A   .= intrp_A
   B   .= intrp_B
   A_r .= intrp_A_r
@@ -150,17 +154,17 @@ function initialdata_equation!(::InitialData{:tov}, rhs, parameters)
 
   ### setup hydrodynamic variables
   @unpack equation = rhs
-  @unpack atmosphere = equation
+  # @unpack atmosphere = equation
   eos = Polytrope(K=K, Gamma=Γ)
 
   # apply atmosphere treatment
   pmax = maximum(pint)
   ρmax = eos(Density, Pressure, pmax)
-  ρatm = atmosphere.factor * ρmax
+  ρatm = equation.atm_factor * ρmax
   patm = eos(Pressure, Density, ρatm)
   pint[@.(pint < 0)] .= 0.0 # just to be sure, they will be replace with patm below anyways
   ρint = @. eos(Density, Pressure, pint)
-  idxatm_treatment = @. ρint < ρatm * atmosphere.threshold
+  idxatm_treatment = @. ρint < ρatm * equation.atm_threshold
   pint[idxatm_treatment] .= patm
   pint[idxatm_treatment] .= ρatm
   vint = zeros(size(Rint))
@@ -190,17 +194,17 @@ function initialdata_equation!(::InitialData{:tov}, rhs, parameters)
     intrp_ϵ  = mirror(intrp_ϵ, exclude_origin)
   end
 
-  @unpack p, vr, rho, eps = get_static_variables(rhs.cache)
-  @unpack sD, sSr, stau   = get_dynamic_variables(rhs.cache)
-  @unpack D, Sr, tau,     = get_static_variables(rhs.cache)
+  @unpack p, vr, rho, eps = get_static_variables(cache)
+  @unpack sD, sSr, stau   = get_dynamic_variables(cache)
+  @unpack D, Sr, tau,     = get_static_variables(cache)
 
   p .= intrp_p
   rho .= intrp_ρ
   vr .= intrp_vr
   eps .= intrp_ϵ
 
-  broadcast_volume!(prim2cons, equation, rhs.cache)
-  broadcast_volume!(cons2scaledcons, equation, rhs.cache)
+  broadcast_volume!(prim2cons, equation, cache)
+  broadcast_volume!(cons2scaledcons, equation, cache)
 
   @assert all(map(X->all(isfinite.(X)),
     [p, vr, rho, eps, D, Sr, tau, sD, sSr, stau])) == true "Initial data interpolation failed!"
@@ -243,9 +247,10 @@ function reflect(A, exclude_bdry::Bool)
 end
 
 
-function initialdata_equation!(::InitialData{:atmosphere}, rhs, parameters)
+function initialdata_equation!(::Val{:atmosphere}, env, rhs, prms)
 
-  @unpack mesh, equation = rhs
+  @unpack cache, mesh = env
+  @unpack equation = rhs
   @unpack Npts = mesh.element
   @unpack eos = equation
   @unpack A, B, A_r, B_r = get_static_variables(rhs.cache)
@@ -258,7 +263,8 @@ function initialdata_equation!(::InitialData{:atmosphere}, rhs, parameters)
   B_r .= zeros(size(R))
 
   # low density atmosphere
-  rho_atm = query(parameters, "density", default=1e-11)
+  # rho_atm = query(prms, "density", default=1e-11)
+  rho_atm = prms["id_atmosphere_density"]
   @assert rho_atm > 0.0
   @assert eos isa Polytrope "Require model for cold atmosphere"
 
@@ -285,7 +291,7 @@ end
 #######################################################################
 
 
-function initialdata_entropy_variables!(::InitialData, rhs, parameters)
+function initialdata_entropy_variables!(::Val, rhs, prms)
   TODO(initialdata_entropy_variables!)
 end
 
@@ -293,7 +299,7 @@ end
 #                          InitialData HRSC                           #
 #######################################################################
 
-function initialdata_hrsc!(::InitialData, rhs, hrsc, parameters)
+function initialdata_hrsc!(::Val, rhs, hrsc, prms)
   TODO(initialdata_hrsc!)
 end
 
@@ -301,6 +307,6 @@ end
 #                           InitialData TCI                           #
 #######################################################################
 
-function initialdata_tci!(::InitialData, rhs, tci, prms)
+function initialdata_tci!(::Val, rhs, tci, prms)
   TODO(initialdata_tci!)
 end
diff --git a/src/projects/GRHD/main.jl b/src/projects/GRHD/main.jl
deleted file mode 100644
index 48490526626735c4015890ca5c8d28e2afb0f97f..0000000000000000000000000000000000000000
--- a/src/projects/GRHD/main.jl
+++ /dev/null
@@ -1,156 +0,0 @@
-"""
-    main(parameters_filename)
-
-Run program specified by TOML file `parameters_filename`.
-
-The following sections in the parameter file are recognized:
-- [`Mesh`](@ref)
-- [`EquationOfState`](@ref)
-- [`Initialdata`](@ref)
-- [`Timestep`](@ref)
-- [`HRSC`](@ref)
-- [`Output`](@ref)
-"""
-function main(parameters_filename=""; override::AbstractDict=Dict())
-
-  override_prms = FlatDict(override)
-
-  parameters_filename = abspath(parameters_filename)
-  outputdir = mk_outputdir(parameters_filename)
-  cp(parameters_filename, joinpath(outputdir, basename(parameters_filename)))
-
-  # read parameters
-  if !isfile(parameters_filename)
-    error("Cannot locate parameter file '$parameters_filename'")
-  end
-  prms = FlatDict(TOML.parsefile(parameters_filename))
-  prms = merge(prms, override_prms)
-  ordered_prms = OrderedDict(prms)
-  display(sort(ordered_prms))
-
-  default_eos_prms = Dict("type"=>"IdealGas", "gamma"=>5/3)
-  eos_prms = trim_headers(prms, "EquationOfState")
-  amend!(eos_prms, default_eos_prms)
-  diff = diffkeys(eos_prms, default_eos_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  eos = EquationOfState.make_EquationOfState(eos_prms)
-  atm = Atmosphere(; parse_parameters(Atmosphere, trim_headers(prms, "Atmosphere"))...)
-  cons2prim_newtonsolver = NewtonSolver(approx_derivative=true)
-  cons2prim_bisection = Bisection()
-
-  # setup RHS
-  mesh     = make_Mesh(trim_headers(prms, "Mesh"))
-  cache    = Cache(mesh)
-  equation = Equation(eos, atm, cons2prim_newtonsolver, cons2prim_bisection)
-  tci      = TCI.make_TCI(trim_headers(prms, "TCI"), mesh)
-  hrsc     = HRSC.make_HRSC(trim_headers(prms, "HRSC"), mesh)
-  rsolver  = ApproxRiemannSolver(flux, speed, equation)
-  ldg_rsolver, av_rsolver = if hrsc isa AbstractArtificialViscosity
-    ApproxRiemannSolver(ldg_flux, ldg_speed, equation),
-    ApproxRiemannSolver(av_flux, av_flux, equation)
-  else
-    nothing, nothing
-  end
-  rhs      = RHS(cache=cache, mesh=mesh, equation=equation, tci=tci, hrsc=hrsc,
-                 rsolver=rsolver, ldg_rsolver=ldg_rsolver, av_rsolver=av_rsolver)
-
-  _register_variables!(rhs)
-  register_variables!(cache, rsolver)
-  !isnothing(ldg_rsolver) && register_variables!(cache, ldg_rsolver)
-  !isnothing(av_rsolver) && register_variables!(cache, av_rsolver, dont_register=true)
-  display(cache)
-
-  initialdata!(rhs, trim_headers(prms, "Initialdata"))
-
-  default_boundaryconditions_prms = Dict("type"=>"periodic")
-  boundaryconditions_prms = trim_headers(prms, "BoundaryConditions")
-  amend!(boundaryconditions_prms, default_boundaryconditions_prms)
-  diff = diffkeys(boundaryconditions_prms, default_boundaryconditions_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  bdryconds = make_BoundaryConditions(rhs, boundaryconditions_prms)
-  ldg_bdryconds, av_bdryconds = if hrsc isa AbstractArtificialViscosity
-    make_BoundaryConditions_LDG(ldg_rsolver, boundaryconditions_prms),
-    make_BoundaryConditions_LDG(av_rsolver, boundaryconditions_prms)
-  else
-    nothing, nothing
-  end
-
-  rhs_fn!(du, u, t)    = rhs!(du, u, t, rhs, bdryconds, ldg_bdryconds, av_bdryconds)
-  timestep_fn(u, t, n) = timestep(u, t, rhs)
-
-  ### Callbacks
-  rhscb = make_callback(rhs, bdryconds)
-
-  # Plotting
-  default_plot_prms = Dict("every_iteration"=>0, "every_dt"=>0.0, "pause"=>false)
-  plot_prms = trim_headers(prms, "Plot")
-  dg1d.amend!(plot_prms, default_plot_prms)
-  diff = diffkeys(plot_prms, default_plot_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  theme(:dark, markerstrokewidth=0.1, marker=:circle)
-  plotfn(u,t) = plot(rhs)
-  plotcb = PlotCallback(plotfn,
-                        CallbackTiming(every_dt=plot_prms["every_dt"],
-                                       every_iteration=plot_prms["every_iteration"]),
-                        pause=plot_prms["pause"])
-  default_output_prms = Dict("every_iteration"=>0,
-                             "every_dt"=>0.5,
-                             "variables"=>["rho", "q", "E"],
-                             "filename"=>joinpath(outputdir, "output"))
-  output_prms = trim_headers(prms, "Output")
-  amend!(output_prms, default_output_prms)
-  diff = diffkeys(output_prms, default_output_prms)
-  @assert length(diff) == 0 "Section 'Output': Unknown options '$diff' encountered!"
-  savecb = SaveCallback(Symbol.(output_prms["variables"]), cache, mesh, 
-                        output_prms["filename"],
-                        CallbackTiming(every_dt=output_prms["every_dt"],
-                                       every_iteration=output_prms["every_iteration"])
-                       )
-
-  # artificially smooth initial data
-  smoother_called = false
-  function id_smoother_fn(u, t)
-    if !smoother_called
-      println("Smoothing initial data...")
-      @unpack sD, sSr, stau = get_dynamic_variables(cache)
-      @unpack flag          = get_cell_variables(cache)
-      id_hrsc = HRSC.BernsteinReconstruction(mesh)
-      HRSC.reconstruct!(sD,    flag, id_hrsc, isperiodic=isperiodic(bdryconds))
-      HRSC.reconstruct!(sSr,   flag, id_hrsc, isperiodic=isperiodic(bdryconds))
-      HRSC.reconstruct!(stau,  flag, id_hrsc, isperiodic=isperiodic(bdryconds))
-      smoother_called = true
-      broadcast_volume!(cons2prim, equation, cache)
-    end
-  end
-  id_smoother_cb = FunctionCallback(id_smoother_fn,
-                                    CallbackTiming(every_iteration=1))
-
-  # Combine callbacks
-  callbacks = CallbackSet(rhscb.callbacks..., id_smoother_cb, savecb, plotcb)
-
-  aligned_times = get(output_prms, "aligned_times", [])
-  timealigned_savecb, timestep_fn = if length(aligned_times) > 0
-    atscb = TimeAlignedSaveCallback(aligned_times, timestep_fn, savecb)
-    atscb, atscb.aligned_timestep_fn
-  else
-    nothing, timestep_fn
-  end
-
-  !isnothing(timealigned_savecb) && push!(callbacks, timealigned_savecb)
-
-  default_evolve_prms = Dict("algorithm"=>"LSERK4", "tend"=>1.0)
-  evolve_prms = trim_headers(prms, "Evolution")
-  amend!(evolve_prms, default_evolve_prms)
-  diff = diffkeys(evolve_prms, default_evolve_prms)
-  @assert length(diff) == 0 "Section 'Evolution': Unknown options '$diff' encountered!"
-
-  @unpack sD, sSr, stau = get_dynamic_variables(cache)
-  state_u = vcat(sD, sSr, stau)
-
-  state_u = dg1d.evolve_ERK(rhs_fn!, state_u, timestep_fn,
-                      (0.0, Float64(evolve_prms["tend"])),
-                      Symbol(evolve_prms["algorithm"]);
-                      callback_fullstep=callbacks)
-
-  return
-end
diff --git a/src/projects/GRHD/parameters.jl b/src/projects/GRHD/parameters.jl
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/projects/GRHD/plots.jl b/src/projects/GRHD/plots.jl
index c6977a732712df4c35ca4d66040960ee6be7b7e3..aaee1f000ed67bfc2516abc6a73d2f138548dde7 100644
--- a/src/projects/GRHD/plots.jl
+++ b/src/projects/GRHD/plots.jl
@@ -1,8 +1,8 @@
-function plot(rhs::RHS)
-  plt_eq   = plot_equation(rhs)
-  plt_rhs  = plot_rhs(rhs)
-  plt_hrsc = plot_hrsc(rhs, rhs.hrsc)
-  plt_tci  = plot_tci(rhs, rhs.tci)
+function plot(env, rhs::RHS)
+  plt_eq   = plot_equation(env, rhs)
+  plt_rhs  = plot_rhs(env, rhs)
+  plt_hrsc = plot_hrsc(env, rhs, rhs.hrsc)
+  plt_tci  = plot_tci(env, rhs, rhs.tci)
 
   list_sub_plts = [ plt_eq, #=plt_rhs,=# plt_hrsc, plt_tci ]
   filter!(!isnothing, list_sub_plts)
@@ -13,7 +13,7 @@ function plot(rhs::RHS)
   sub_plt = Plots.plot(list_sub_plts..., layout=grid(layout..., heights=plt_heights))
 
   # combine plots
-  @unpack cache = rhs
+  @unpack cache = env
   @unpack t = get_global_variables(cache)
   plt = Plots.plot(sub_plt, layout=layout, size=(1400,1000), plot_title="t=$(t[1])",
                    left_margin=10Plots.mm)
@@ -26,9 +26,9 @@ end
 #######################################################################
 
 
-function plot_equation(rhs)
+function plot_equation(env, rhs)
 
-  @unpack cache, mesh   = rhs
+  @unpack cache, mesh   = env
   @unpack sD, sSr, stau = get_dynamic_variables(cache)
   @unpack D, Sr, tau,
   rho, p, eps, vr, A, B = get_static_variables(cache)
@@ -75,13 +75,13 @@ end
 #######################################################################
 
 
-function plot_hrsc(rhs, hrsc::Nothing) end
-function plot_hrsc(rhs, hrsc::AbstractReconstruction) end
+function plot_hrsc(env, rhs, hrsc::Nothing) end
+function plot_hrsc(env, rhs, hrsc::AbstractReconstruction) end
 
 
-function plot_hrsc(rhs, hrsc::HRSC.AbstractArtificialViscosity)
+function plot_hrsc(env, rhs, hrsc::HRSC.AbstractArtificialViscosity)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack mu          = get_cell_variables(cache)
   @unpack x           = mesh
 
@@ -103,11 +103,11 @@ function plot_hrsc(rhs, hrsc::HRSC.AbstractArtificialViscosity)
 end
 
 
-function plot_hrsc(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
+function plot_hrsc(env, rhs, hrsc::HRSC.SmoothedArtificialViscosity)
 
-  plt_mu = plot_hrsc(rhs, hrsc.av)
+  plt_mu = plot_hrsc(env, rhs, hrsc.av)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack smoothed_mu = get_static_variables(cache)
   @unpack x           = mesh
 
@@ -137,12 +137,12 @@ end
 #######################################################################
 
 
-function plot_tci(rhs, tci::Nothing) end
+function plot_tci(env, rhs, tci::Nothing) end
 
 
-function plot_tci(rhs, tci::TCI.AbstractTCI)
+function plot_tci(env, rhs, tci::TCI.AbstractTCI)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack flag        = get_cell_variables(cache)
   @unpack x           = mesh
 
@@ -172,9 +172,9 @@ function plot_tci(rhs, tci::TCI.AbstractTCI)
 end
 
 
-function plot_tci(rhs, tci::TCI.EntropyProduction)
+function plot_tci(env, rhs, tci::TCI.EntropyProduction)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack EP          = get_static_variables(cache)
   @unpack flag        = get_cell_variables(cache)
   @unpack x           = mesh
@@ -211,9 +211,9 @@ end
 #######################################################################
 
 
-function plot_rhs(rhs)
+function plot_rhs(env, rhs)
 
-  @unpack cache, mesh               = rhs
+  @unpack cache, mesh               = env
   @unpack rhs_sD, rhs_sSr, rhs_stau = get_rhs_variables(cache)
   @unpack t                         = get_global_variables(cache)
   @unpack x                         = mesh
diff --git a/src/projects/GRHD/rhs.jl b/src/projects/GRHD/rhs.jl
index 457208bb50bb96fc9ce7f3df5a867b9fbfb01620..662095151c8df2ba265c400e83c5e8d4288411c2 100644
--- a/src/projects/GRHD/rhs.jl
+++ b/src/projects/GRHD/rhs.jl
@@ -1,106 +1,11 @@
-Base.@kwdef struct RHS{T_RSolver  <:AbstractRiemannSolver,
-                       T_HRSC     <:Maybe{HRSC.AbstractHRSC},
-                       T_TCI      <:Maybe{TCI.AbstractTCI},
-                       T_LDG_RSolver<:Maybe{AbstractRiemannSolver},
-                       T_AV_RSolver<:Maybe{AbstractRiemannSolver}}
-
-  cache::Cache
-  mesh::Mesh
-  equation::Equation
-  rsolver::T_RSolver
-  hrsc::T_HRSC               = nothing
-  tci::T_TCI                 = nothing
-  ldg_rsolver::T_LDG_RSolver = nothing
-  av_rsolver::T_AV_RSolver   = nothing
-end
-
-
-#######################################################################
-#                         Register variables                          #
-#######################################################################
-
-
-# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
-# definition in this module without a warning or error. This does not happen for methods
-# defined in Base.
-# Because of that we prefix with _ below.
-# Cf. https://github.com/JuliaLang/julia/issues/15510
-function _register_variables!(rhs::RHS)
-  register_variables!(rhs.cache,
-    dynamic_variablenames = (:sD, :sSr, :stau,),            # 'scaled' conservatives
-    rhs_variablenames     = (:rhs_sD, :rhs_sSr, :rhs_stau), # rhs of conservatives
-    static_variablenames  = (:flx_sD, :flx_sSr, :flx_stau,  # conservatives' fluxes
-                             :src_sD, :src_sSr, :src_stau,  # sources
-                             :D, :Sr, :tau,                 # true hydrodynamic conservatives
-                             :rho, :vr, :p, :eps,        # primitives
-                             :max_v, :r,                 # maximum charactersitic speed, grid
-                             :E, :Em1,                   # entropy
-                             :flx_E, :flx_Em1,           # entropy flux
-                             :A, :B, :A_r, :B_r),        # metric fields and derivatives
-    cell_variablenames    = (:cellmax_v,),
-    global_variablenames  = (:t, :tm1) # time steps
-  )
-  _register_variables!(rhs, rhs.hrsc)
-  _register_variables!(rhs, rhs.tci)
-  @unpack r = get_static_variables(rhs.cache)
-  @. r = rhs.mesh.x[:]
-end
-
-
-function _register_variables!(rhs, tci_or_hrsc::Nothing) end
-
-
-_register_variables!(rhs, hrsc::AbstractHRSC) = 
-  error("_register_variables: Not implemented for hrsc of type '$(typeof(hrsc))'")
-
-_register_variables!(rhs, hrsc::HRSC.AbstractReconstruction) = nothing
-
-
-function _register_variables!(rhs, hrsc::HRSC.AbstractArtificialViscosity)
-  register_variables!(rhs.cache,
-    static_variablenames  = (:ldg_sD, :ldg_sSr, :ldg_stau,              # local DG variable
-                             :flx_ldg_sD, :flx_ldg_sSr, :flx_ldg_stau), # local DG flux
-    cell_variablenames    = (:mu,), # 'one viscosity to rule them all'
-    )
-end
-
-
-function _register_variables!(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
-  _register_variables!(rhs, hrsc.av)
-  register_variables!(rhs.cache,
-    static_variablenames  = (:smoothed_mu,)
-  )
-end
-
-
-function _register_variables!(rhs, tci::TCI.AbstractTCI)
-  register_variables!(rhs.cache, cell_variablenames = (:flag,:sD_flag,:sSr_flag,:stau_flag))
-end
-
-
-function _register_variables!(rhs, tci::TCI.EntropyProduction)
-  register_variables!(rhs.cache, 
-                      cell_variablenames = (:flag,),
-                      static_variablenames = (:EP,), # entropy production
-                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs)
-                                              # entropy production due to jumps at interfaces
-  )
-end
-
-
 #######################################################################
 #                              Timestep                               #
 #######################################################################
 
 
-function timestep(state_u, t, rhs::RHS)
-  wrap_dynamic_variables!(rhs.cache, state_u)
-  return timestep(rhs, rhs.hrsc)
-end
-
-
-function timestep(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
-  @unpack cache, mesh, equation = rhs
+function timestep(env, rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
+  @unpack cache, mesh = env
+  @unpack equation = rhs
   @unpack max_v = get_static_variables(cache)
 
   broadcast_volume!(cons2prim, equation, cache)
@@ -120,8 +25,9 @@ function timestep(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
 end
 
 
-function timestep(rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity)
-  @unpack cache, mesh, equation = rhs
+function timestep(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity)
+  @unpack cache, mesh = env
+  @unpack equation = rhs
 
   broadcast_volume!(cons2prim, equation, cache)
   broadcast_volume!(speed, equation, cache)
@@ -153,19 +59,11 @@ end
 #######################################################################
 
 
-function rhs!(state_du, state_u, t, rhs::RHS, bdryconds::BoundaryConditions,
-    ldg_bdryconds::Maybe{BoundaryConditions}, av_bdryconds::Maybe{BoundaryConditions})
-
-  @unpack cache = rhs
-  wrap_dynamic_variables!(cache, state_u)
-  wrap_rhs_variables!(cache, state_du)
-  rhs!(rhs, t, rhs.hrsc, bdryconds, ldg_bdryconds, av_bdryconds)
-end
-
+function rhs!(env, rhs::RHS, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds)
 
-function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds)
+  @unpack cache, mesh = env
+  @unpack equation, rsolver = rhs
 
-  @unpack cache, mesh, equation, rsolver = rhs
   @unpack sD, sSr, stau             = get_dynamic_variables(cache)
   @unpack flx_sD, flx_sSr, flx_stau = get_static_variables(cache)
   @unpack rhs_sD, rhs_sSr, rhs_stau = get_rhs_variables(cache)
@@ -179,7 +77,7 @@ function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds
   broadcast_volume!(flux, equation, cache)
   broadcast_volume!(sources, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   @unpack p, vr, rho, eps = get_static_variables(cache)
 
@@ -191,10 +89,12 @@ function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds
 end
 
 
-function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractReconstruction,
+function rhs!(env, rhs::RHS, hrsc::HRSC.AbstractReconstruction,
     bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack cache, mesh = env
+  @unpack equation, rsolver = rhs
+
   @unpack sD, sSr, stau                    = get_dynamic_variables(cache)
   @unpack flx_sD, flx_sSr, flx_stau        = get_static_variables(cache)
   @unpack rhs_sD, rhs_sSr, rhs_stau        = get_rhs_variables(cache)
@@ -222,11 +122,12 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractReconstruction,
 end
 
 
-function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
+function rhs!(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity,
     bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver,
-          ldg_rsolver, av_rsolver                      = rhs
+  @unpack cache, mesh = env
+  @unpack equation, rsolver, ldg_rsolver, av_rsolver = rhs
+
   @unpack sD, sSr, stau                          = get_dynamic_variables(cache)
   @unpack flx_sD, flx_sSr, flx_stau,
           ldg_sD, ldg_sSr, ldg_stau,
@@ -244,7 +145,7 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
   ## solve auxiliary equation: q + ∂x u = 0
   broadcast_volume!(ldg_flux, equation, cache)
   broadcast_faces!(mean_flux, ldg_rsolver, cache, mesh)
-  broadcast_boundaryconditions!(mean_flux, ldg_bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(mean_flux, ldg_bdryconds, cache, mesh, 0.0)
   compute_rhs_weak_form!(ldg_sD,   flx_ldg_sD,   lhs_numflx_ldg_sD,   rhs_numflx_ldg_sD,   mesh)
   compute_rhs_weak_form!(ldg_sSr,  flx_ldg_sSr,  lhs_numflx_ldg_sSr,  rhs_numflx_ldg_sSr,  mesh)
   compute_rhs_weak_form!(ldg_stau, flx_ldg_stau, lhs_numflx_ldg_stau, rhs_numflx_ldg_stau, mesh)
@@ -252,14 +153,14 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
   ## compute rhs of regularized equation: ∂t u + ∂x f + ∂x μ q = 0
   broadcast_volume!(av_flux, equation, cache)
   broadcast_faces!(mean_flux, av_rsolver, cache, mesh)
-  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, 0.0)
 
   broadcast_volume!(scaledcons2cons, equation, cache)
   postprocess_scaledcons2cons!(cache, equation, mesh)
   broadcast_volume!(flux, equation, cache)
   broadcast_volume!(sources, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   @. flx_sD          += flx_ldg_sD
   @. flx_sSr         += flx_ldg_sSr
diff --git a/src/projects/GRHD/setup.jl b/src/projects/GRHD/setup.jl
new file mode 100644
index 0000000000000000000000000000000000000000..f384e068382b9e7f2494e7cbe535374d677233db
--- /dev/null
+++ b/src/projects/GRHD/setup.jl
@@ -0,0 +1,161 @@
+function RHS(env::Environment, prms)
+
+  # construct RHS
+  eos = EquationOfState.make_EquationOfState(prms["EquationOfState"])
+  equation = Equation(eos, prms["GRHD"])
+  # TODO Update TCI, HRSC to new interface
+  tci  = TCI.make_TCI(env.mesh, prms["TCI"])
+  hrsc = HRSC.make_HRSC(env.mesh, prms["HRSC"])
+  # tci  = TCI.make_TCI(prms["TCI"], env.mesh)
+  # hrsc = HRSC.make_HRSC(prms["HRSC"], env.mesh)
+  rsolver = ApproxRiemannSolver(flux, speed, equation)
+  ldg_rsolver, av_rsolver = if hrsc isa AbstractArtificialViscosity
+    ApproxRiemannSolver(ldg_flux, ldg_speed, equation),
+    ApproxRiemannSolver(av_flux, av_flux, equation)
+  else
+    nothing, nothing
+  end
+
+  rhs = RHS(equation, hrsc, tci, rsolver, ldg_rsolver, av_rsolver)
+
+  # register variables
+  # TODO add a ::Nothing overload for register_variables!
+  # TODO Somehow replace _register_variables! with register_variables!
+  @unpack cache = env
+  _register_variables!(cache, rhs)
+  register_variables!(cache, rsolver)
+  !isnothing(ldg_rsolver) && register_variables!(cache, ldg_rsolver)
+  !isnothing(av_rsolver) && register_variables!(cache, av_rsolver, dont_register=true)
+  display(cache)
+
+  # setup boundary conditions
+  bdryconds = make_BoundaryConditions(env, equation, rsolver, prms)
+  ldg_bdryconds, av_bdryconds = if hrsc isa AbstractArtificialViscosity
+    make_BoundaryConditions_LDG(env, equation, ldg_rsolver, prms),
+    make_BoundaryConditions_LDG(env, equation, av_rsolver, prms)
+  else
+    nothing, nothing
+  end
+
+  # TODO Bdry conditions should be moved to project and should only be just a 'lightweight'
+  # descriptor. Furthermore, they should also register variables in cache themselves.
+  # Note: Atm, only the 'from_initial_data' option would require a place to store the data
+  # somewhere. For periodic BCs we don't need extra storage at all.
+  # But what other types of boundary conditions will be needed in the future?
+
+  # setup initial data
+  initialdata!(env, rhs, prms["GRHD"])
+
+  # setup callbacks
+  rhscb = make_callback(env, rhs, bdryconds)
+
+  # artificially smooth initial data
+  # TODO Move this into make_callback
+  smoother_called = false
+  function id_smoother_fn(u, t)
+    if !smoother_called
+      println("Smoothing initial data...")
+      @unpack sD, sSr, stau = get_dynamic_variables(cache)
+      @unpack flag          = get_cell_variables(cache)
+      id_hrsc = HRSC.BernsteinReconstruction(env.mesh)
+      HRSC.reconstruct!(sD,    flag, id_hrsc, isperiodic=isperiodic(bdryconds))
+      HRSC.reconstruct!(sSr,   flag, id_hrsc, isperiodic=isperiodic(bdryconds))
+      HRSC.reconstruct!(stau,  flag, id_hrsc, isperiodic=isperiodic(bdryconds))
+      smoother_called = true
+      broadcast_volume!(cons2prim, equation, cache)
+    end
+  end
+  id_smoother_cb = FunctionCallback(id_smoother_fn,
+                                    CallbackTiming(every_iteration=1))
+
+  append!(env.callbacks, CallbackSet(rhscb.callbacks..., id_smoother_cb))
+
+  return rhs, (bdryconds, ldg_bdryconds, av_bdryconds)
+end
+
+
+function Equation(eos, prms)
+  @unpack atm_factor, atm_threshold_factor = prms
+  if atm_factor < 0
+    error("GRHD: atm_factor must be positive, found 'atm_factor = $atm_factor'")
+  end
+  if atm_threshold_factor < 0
+    error("GRHD: atm_threshold_factor must be positive, found 'atm_threshold_factor = $atm_threshold_factor'")
+  end
+  cons2prim_newtonsolver = NewtonSolver(approx_derivative=true)
+  cons2prim_bisection = Bisection()
+  return Equation(eos, atm_factor, atm_threshold_factor,
+                  cons2prim_newtonsolver, cons2prim_bisection)
+end
+
+
+#######################################################################
+#                         Register variables                          #
+#######################################################################
+
+
+# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
+# definition in this module without a warning or error. This does not happen for methods
+# defined in Base.
+# Because of that we prefix with _ below.
+# Cf. https://github.com/JuliaLang/julia/issues/15510
+function _register_variables!(cache, rhs::RHS)
+  register_variables!(cache,
+    dynamic_variablenames = (:sD, :sSr, :stau,),            # 'scaled' conservatives
+    rhs_variablenames     = (:rhs_sD, :rhs_sSr, :rhs_stau), # rhs of conservatives
+    static_variablenames  = (:flx_sD, :flx_sSr, :flx_stau,  # conservatives' fluxes
+                             :src_sD, :src_sSr, :src_stau,  # sources
+                             :D, :Sr, :tau,                 # true hydrodynamic conservatives
+                             :rho, :vr, :p, :eps,        # primitives
+                             :max_v, :r,                 # maximum charactersitic speed, grid
+                             :E, :Em1,                   # entropy
+                             :flx_E, :flx_Em1,           # entropy flux
+                             :A, :B, :A_r, :B_r),        # metric fields and derivatives
+    cell_variablenames    = (:cellmax_v,),
+    global_variablenames  = (:t, :tm1) # time steps
+  )
+  _register_variables!(cache, rhs.hrsc)
+  _register_variables!(cache, rhs.tci)
+  # @unpack r = get_static_variables(cache)
+end
+
+
+function _register_variables!(rhs, tci_or_hrsc::Nothing) end
+
+
+_register_variables!(rhs, hrsc::AbstractHRSC) = 
+  error("_register_variables: Not implemented for hrsc of type '$(typeof(hrsc))'")
+
+_register_variables!(rhs, hrsc::HRSC.AbstractReconstruction) = nothing
+
+
+function _register_variables!(cache, hrsc::HRSC.AbstractArtificialViscosity)
+  register_variables!(cache,
+    static_variablenames  = (:ldg_sD, :ldg_sSr, :ldg_stau,              # local DG variable
+                             :flx_ldg_sD, :flx_ldg_sSr, :flx_ldg_stau), # local DG flux
+    cell_variablenames    = (:mu,), # 'one viscosity to rule them all'
+    )
+end
+
+
+function _register_variables!(cache, hrsc::HRSC.SmoothedArtificialViscosity)
+  _register_variables!(cache, hrsc.av)
+  register_variables!(cache,
+    static_variablenames  = (:smoothed_mu,)
+  )
+end
+
+
+function _register_variables!(cache, tci::TCI.AbstractTCI)
+  register_variables!(cache, cell_variablenames = (:flag,:sD_flag,:sSr_flag,:stau_flag))
+end
+
+
+function _register_variables!(cache, tci::TCI.EntropyProduction)
+  register_variables!(cache,
+                      cell_variablenames = (:flag,),
+                      static_variablenames = (:EP,), # entropy production
+                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs)
+                                              # entropy production due to jumps at interfaces
+  )
+end
diff --git a/src/projects/GRHD/types.jl b/src/projects/GRHD/types.jl
new file mode 100644
index 0000000000000000000000000000000000000000..9d3d95fb2b4a11a8445c377b64a9bfec852cc0d7
--- /dev/null
+++ b/src/projects/GRHD/types.jl
@@ -0,0 +1,22 @@
+struct Equation{T_EoS<:AbstractEquationOfState} <: AbstractEquation
+  eos::T_EoS
+  atm_factor::Float64
+  atm_threshold::Float64
+  cons2prim_newtonsolver::dg1d.NewtonSolver
+  cons2prim_bisection::dg1d.Bisection
+end
+
+
+struct RHS{T_HRSC         <:Maybe{HRSC.AbstractHRSC},
+           T_TCI          <:Maybe{TCI.AbstractTCI},
+           T_RSolver      <:AbstractRiemannSolver,
+           T_LDG_RSolver  <:Maybe{AbstractRiemannSolver},
+           T_AV_RSolver   <:Maybe{AbstractRiemannSolver}} <: dg1d.AbstractProject
+
+  equation::Equation
+  hrsc::T_HRSC
+  tci::T_TCI
+  rsolver::T_RSolver
+  ldg_rsolver::T_LDG_RSolver
+  av_rsolver::T_AV_RSolver
+end
diff --git a/src/projects/HRSC/interface.jl b/src/projects/HRSC/interface.jl
index 412de66f4632cca3115b4e71e86f01bb777ebe1b..2e0021b8e7f71d96ed3d430fd0c6af191af2e298 100644
--- a/src/projects/HRSC/interface.jl
+++ b/src/projects/HRSC/interface.jl
@@ -1,20 +1,46 @@
 abstract type AbstractHRSC end
 
 
-function make_HRSC(parameters, mesh)
-  type = Symbol(query(parameters, "type", default=missing))
-  concretes = [ Base.typename(ct).name for ct in concretetypes(AbstractHRSC) ]
-  if type === :missing
+function make_HRSC(mesh, prms)
+  @unpack method = prms
+
+  if method == "none"
     return nothing
+  elseif method == "wenoz"
+    TODO()
+  elseif method == "av"
+    @unpack av_method, av_smoother_order = prms
+    hrsc = if av_method == "mda"
+      MDAViscosity(mesh, prms["mda_cmax"])
+    elseif av_method == "mdh"
+      MDAViscosity(mesh, prms["mdh_cmax"], prms["mdh_ca"], prms["mdh_ck"])
+    elseif av_method == "entropy"
+      MDAViscosity(mesh, prms["entropy_cmax"], prms["entropy_ce"])
+    else
+      error("HRSC: Unknown method requested: 'av_method = $av_method'")
+    end
+
+    if av_smoother_order < 0
+      error("HRSC: 'av_smoother_order' must be positive, found $av_smoother_order")
+    elseif av_smoother_order > 0
+      return SmoothedArtificialViscosity(mesh, hrsc, smoother_order=av_smoother_order)
+    else
+      return hrsc
+    end
+
+  elseif method == "filter"
+    @unpack filter_method = prms
+    if filter_method == "bernstein"
+      return BernsteinReconstruction(mesh)
+    else
+      error("HRSC: Unknown method requested: 'filter_method = $filter_method'")
+    end
+  elseif method == "fv-hybrid"
+    TODO()
+  elseif method == "fd-hybrid"
+    TODO()
+  else
+    error("HRSC: Unknown method requested: '$method'")
   end
-  @assert type in concretes "HRSC parameter 'type = $type' unknown. Available options: $concretes"
-  hrsc_type = eval(type)
-  hrsc_parameters = parse_parameters(hrsc_type, parameters)
-  hrsc = hrsc_type(; mesh, hrsc_parameters...)
-  smoothav = query(parameters, "smoother", default=missing)
-  if hrsc isa AbstractArtificialViscosity && smoothav !== missing
-    order = query(Int64, parameters, "smoother_order", default=1)
-    return SmoothedArtificialViscosity(mesh, hrsc, smoother_order=order)
-  end
-  return hrsc
+
 end
diff --git a/src/projects/SREulerEq/SREulerEq.jl b/src/projects/SREulerEq/SREulerEq.jl
index 0983e6d08ef85bed654c5220d3c0d7634c03520d..3457b9236d1eb0bfea7ed215580e91facb0d4ecb 100644
--- a/src/projects/SREulerEq/SREulerEq.jl
+++ b/src/projects/SREulerEq/SREulerEq.jl
@@ -4,17 +4,72 @@ module SREulerEq
 
 using dg1d
 using Plots
-using Match
-import Parameters: @unpack
 
 
+include("types.jl")
 include("equation.jl")
 include("rhs.jl")
 include("initialdata.jl")
 include("callbacks.jl")
 include("plots.jl")
 include("boundaryconditions.jl")
-include("main.jl")
+include("setup.jl")
 
 
+#######################################################################
+#                 dg1d project interface definitions                  #
+#######################################################################
+
+
+dg1d.project_type(::Val{:SREulerEq}) = RHS
+
+# TODO Remove bdryconds
+dg1d.rhs!(env::Environment, p::RHS, bdryconds) = rhs!(env, p, p.hrsc, bdryconds...)
+
+dg1d.timestep!(env::Environment, p::RHS) = timestep(env, p, p.hrsc)
+
+dg1d.@parameters SREulerEq begin
+
+  """
+  Density of artificial atmosphere.
+  """
+  atm_density = 1e-10
+  @check atm_density > 0.0
+
+  """
+  Multiplicative factor to determine the cutoff restmass density below which
+  we impose artificial atmosphere.
+  """
+  atm_threshold_factor = 100.0
+  @check atm_threshold_factor > 0.0
+
+  """
+  Initial data configuration. Available options:
+  - `id_smooth`
+  - `id_periodic_relativistic_shocktube`
+  - `id_periodic_relativistic_shocktube_2`
+  - `id_periodic_shocktube_pressure_jump`
+  - `id_periodic_shocktube_less_extreme`
+  - `id_periodic_sod_shocktube`
+  - `id_sod_shocktube`
+  """
+  id = "sine"
+  @check id in [ "id_smooth",
+                 "id_periodic_relativistic_shocktube",
+                 "id_periodic_relativistic_shocktube_2",
+                 "id_periodic_shocktube_pressure_jump",
+                 "id_periodic_shocktube_less_extreme",
+                 "id_periodic_sod_shocktube",
+                 "id_sod_shocktube" ]
+
+  """
+  Boundary conditions. Available options:
+  - `periodic`
+  - `from_id`
+  """
+  bc = "periodic"
+  @check bc in [ "periodic", "from_id" ]
+
+end
+
 end # module
diff --git a/src/projects/SREulerEq/boundaryconditions.jl b/src/projects/SREulerEq/boundaryconditions.jl
index 4ab76543c310fead4a3ad434c5a8ca95fa798f54..d3b547e53960241e75ac528c44116a34ece52e26 100644
--- a/src/projects/SREulerEq/boundaryconditions.jl
+++ b/src/projects/SREulerEq/boundaryconditions.jl
@@ -1,37 +1,46 @@
-function make_BoundaryConditions(rhs::RHS, prms)
-  type = query(String, prms, "type")
-
-  xrange = extrema(rhs.mesh.x)
-
-  lhs_bc, rhs_bc = @match type begin
-    "periodic"          => (PeriodicBC(), PeriodicBC())
-    "from initial data" => begin
-      @unpack D, S, tau = get_dynamic_variables(rhs.cache)
-      p0 = 0.0
-      DStau_lhs = (D[1], S[1], tau[1])
-      (p_lhs,) = find_pressure_insane((DStau_lhs..., p0, xrange[1]), rhs.equation)
-      (rho_lhs, v_lhs, eps_lhs) = primitives((DStau_lhs..., p_lhs), rhs.equation)
-      lhs_state = (DStau_lhs..., p_lhs, rho_lhs, v_lhs, eps_lhs)
-      DStau_rhs = (D[end], S[end], tau[end])
-      (p_rhs,) = find_pressure_insane((DStau_rhs..., p0, xrange[end]), rhs.equation)
-      (rho_rhs, v_rhs, eps_rhs) = primitives((DStau_rhs..., p_rhs), rhs.equation)
-      rhs_state = (DStau_rhs..., p_rhs, rho_rhs, v_rhs, eps_rhs)
-      (DirichletBC(lhs_state), DirichletBC(rhs_state))
-    end
-    _ => error("Boundary condition type '$type' not implemented!")
+function make_BoundaryConditions(env, eq, rsolver, prms)
+
+  @unpack mesh = env
+  @unpack bc = prms
+  @unpack xmin, xmax = mesh
+
+  lhs_bc, rhs_bc = if bc == "periodic"
+    PeriodicBC(), PeriodicBC()
+  elseif bc == "from_id"
+    @unpack D, S, tau = get_dynamic_variables(rhs.cache)
+    p0 = 0.0
+    DStau_lhs = (D[1], S[1], tau[1])
+    (p_lhs,) = find_pressure_insane((DStau_lhs..., p0, xmin), rhs.equation)
+    (rho_lhs, v_lhs, eps_lhs) = primitives((DStau_lhs..., p_lhs), rhs.equation)
+    lhs_state = (DStau_lhs..., p_lhs, rho_lhs, v_lhs, eps_lhs)
+    DStau_rhs = (D[end], S[end], tau[end])
+    (p_rhs,) = find_pressure_insane((DStau_rhs..., p0, xmax), rhs.equation)
+    (rho_rhs, v_rhs, eps_rhs) = primitives((DStau_rhs..., p_rhs), rhs.equation)
+    rhs_state = (DStau_rhs..., p_rhs, rho_rhs, v_rhs, eps_rhs)
+    (DirichletBC(lhs_state), DirichletBC(rhs_state))
+  else
+    error("Unknown boundary condition requested: '$bc'")
   end
 
-  return BoundaryConditions(lhs_bc, rhs_bc, rhs.rsolver)
+  return BoundaryConditions(lhs_bc, rhs_bc, rsolver)
 end
 
 
-function make_BoundaryConditions_LDG(ldg_rsolver, prms)
+function make_BoundaryConditions_LDG(env, eq, ldg_rsolver, prms)
+
+  @unpack bc = prms
+
   isnothing(ldg_rsolver) && return nothing
-  type = query(String, prms, "type")
-  lhs_bc, rhs_bc = if type == "periodic"
+
+  lhs_bc, rhs_bc = if bc == "periodic"
     PeriodicBC(), PeriodicBC()
-  else
+  elseif bc == "no_inflow"
+    OutflowBC(), OutflowBC()
+  elseif bc == "from_id"
     OutflowBC(), OutflowBC()
+  else
+    error("Unknown boundary condition requested: '$bc'")
   end
+
   return BoundaryConditions(lhs_bc, rhs_bc, ldg_rsolver)
 end
diff --git a/src/projects/SREulerEq/callbacks.jl b/src/projects/SREulerEq/callbacks.jl
index add288d1047a0b54c36c4ab81a9cc9e1684771b1..4b21c5553c93b16eb01e7d6b4f0634ab55119a97 100644
--- a/src/projects/SREulerEq/callbacks.jl
+++ b/src/projects/SREulerEq/callbacks.jl
@@ -1,11 +1,11 @@
-function make_callback(rhs::RHS, bdryconds::BoundaryConditions)
-  cbfn_equation(u, t) = callback_equation(u, t, rhs, isperiodic(bdryconds), rhs.equation)
+function make_callback(env, rhs::RHS, bdryconds::BoundaryConditions)
+  cbfn_equation(u, t) = callback_equation(u, t, env, rhs, isperiodic(bdryconds), rhs.equation)
   cb_equation         = FunctionCallback(cbfn_equation,
                                          CallbackTiming(every_iteration=1,every_dt=0))
-  cbfn_tci(u, t)      = callback_tci(u, t, rhs, isperiodic(bdryconds), rhs.tci)
+  cbfn_tci(u, t)      = callback_tci(u, t, env, rhs, isperiodic(bdryconds), rhs.tci)
   cb_tci              = FunctionCallback(cbfn_tci,
                                          CallbackTiming(every_iteration=1, every_dt=0))
-  cbfn_hrsc(u, t)     = callback_hrsc(u, t, rhs, isperiodic(bdryconds), rhs.hrsc)
+  cbfn_hrsc(u, t)     = callback_hrsc(u, t, env, rhs, isperiodic(bdryconds), rhs.hrsc)
   cb_hrsc             = FunctionCallback(cbfn_hrsc,
                                          CallbackTiming(every_iteration=1,every_dt=0))
   callbackset = CallbackSet(cb_equation, cb_tci, cb_hrsc)
@@ -18,8 +18,8 @@ end
 #######################################################################
 
 
-function callback_equation(state_u, state_t, rhs, isperiodic, eq::SREulerEquation)
-  @unpack cache = rhs
+function callback_equation(state_u, state_t, env, rhs, isperiodic, eq::SREulerEquation)
+  @unpack cache = env
   wrap_dynamic_variables!(cache, state_u)
   @unpack t, tm1 = get_global_variables(cache)
   tm1 .= t
@@ -33,17 +33,18 @@ end
 #######################################################################
 
 
-callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::Nothing) = nothing
-callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::AbstractReconstruction) = nothing
+callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::Nothing) = nothing
+callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::AbstractReconstruction) = nothing
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::Maybe{HRSC.AbstractHRSC}) 
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::Maybe{HRSC.AbstractHRSC}) 
   TODO(callback_hrsc)
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractArtificialViscosity)
-  @unpack cache, mesh, equation = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.AbstractArtificialViscosity)
+  @unpack cache, mesh           = env
+  @unpack equation              = rhs
   @unpack max_v                 = get_static_variables(cache)
   @unpack mu, cellmax_v         = get_cell_variables(cache)
   @unpack D                     = get_dynamic_variables(cache)
@@ -60,16 +61,16 @@ function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractArt
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.TCIViscosity)
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.TCIViscosity)
   @unpack cache, mesh = rhs
   @unpack mu, flag    = get_cell_variables(cache)
   HRSC.compute_viscosity!(mu, flag, hrsc)
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.SmoothedArtificialViscosity)
-  callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc.av)
-  @unpack cache       = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.SmoothedArtificialViscosity)
+  callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc.av)
+  @unpack cache       = env
   @unpack mu          = get_cell_variables(cache)
   indices = findall(muk -> muk > 0.0, mu)
   tmp = zeros(size(mu))
@@ -88,15 +89,15 @@ function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.SmoothedArt
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.EntropyViscosity)
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.EntropyViscosity)
   TODO()
-  @unpack cache, mesh            = rhs
+  @unpack cache, mesh            = env
   @unpack rho                    = get_dynamic_variables(cache)
   @unpack S, Sm1, flx_S, flx_Sm1 = get_static_variables(cache)
   @unpack mu, cellmax_v          = get_cell_variables(cache)
   @unpack t, tm1                 = get_global_variables(cache)
   HRSC.compute_viscosity!(
-      mu, 
+      mu,
       rho, cellmax_v, S, Sm1, flx_S, flx_Sm1, t[1], tm1[1],
       hrsc)
 end
@@ -107,19 +108,19 @@ end
 #######################################################################
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::Nothing) end
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::Nothing) end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.Threshold)
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::TCI.Threshold)
   error("Threshold TCI must be combined with an indicator that is supposed to be zero for " *
         "for smooth solutions, but not for shocks. TODO Implement an indicator variable.")
 end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.EntropyProduction)
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::TCI.EntropyProduction)
   @warn "Figure out entropy fluxes for special relativistic euler equation!"
   TODO()
-  @unpack cache, mesh                = rhs
+  @unpack cache, mesh                = env
   @unpack E, Em1, flx_E, flx_Em1, EP = get_static_variables(cache)
   @unpack flag                       = get_cell_variables(cache)
   @unpack EPjump_lhs, EPjump_rhs     = get_bdry_variables(cache)
@@ -130,11 +131,11 @@ function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.EntropyProduct
 end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic,
-    tci::Union{TCI.Diffusion, TCI.Minmod, TCI.Threshold, 
+function callback_tci(state_u, state_t, env, rhs, isperiodic,
+    tci::Union{TCI.Diffusion, TCI.Minmod, TCI.Threshold,
                TCI.ModalDecayAverage, TCI.ModalDecayHighest})
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack D, S, tau   = get_dynamic_variables(cache)
   @unpack flag, D_flag, S_flag, tau_flag = get_cell_variables(cache)
   TCI.compute_indicator!(D_flag, D, tci)
diff --git a/src/projects/SREulerEq/equation.jl b/src/projects/SREulerEq/equation.jl
index 805f717e34b8bb75091db1afa840e87ccdd7cd17..519eb3ae5b58d083faa992da115bcbe42ba31ab9 100644
--- a/src/projects/SREulerEq/equation.jl
+++ b/src/projects/SREulerEq/equation.jl
@@ -1,38 +1,9 @@
-#######################################################################
-#                                Types                                #
-#######################################################################
-
-
-Base.@kwdef struct Atmosphere
-  rho::Float64
-  threshold::Float64
-end
-
-
-function parse_parameters(::Type{Atmosphere}, prms)
-  rho                = query(Float64, prms, "rho", default=1e-10)
-  threshold          = query(Float64, prms, "threshold", default=100.0)
-
-  @assert rho       > 0
-  @assert threshold > 0
-
-  return (; rho, threshold)
-end
-
-
-Atmosphere(prms::AbstractDict) = Atmosphere(; parse_parameters(Atmosphere, prms)...)
-
-
-"""
-    SREulerEquation
-
-Special relativistic Euler equations in cartesian coordinates and 1D.
-"""
-Base.@kwdef struct SREulerEquation{T_EoS<:EquationOfState.AbstractEquationOfState} <: AbstractEquation
-  eos::T_EoS
-  atmosphere::Atmosphere
-  cons2prim_newtonsolver::NewtonSolver
-  cons2prim_bisection::Bisection
+function make_SREulerEquation(eos, prms)
+  @unpack atm_density, atm_threshold_factor = prms
+  atm = Atmosphere(atm_density, atm_threshold_factor)
+  cons2prim_newtonsolver = NewtonSolver(approx_derivative=true)
+  cons2prim_bisection = Bisection()
+  return SREulerEquation(eos, atm, cons2prim_newtonsolver, cons2prim_bisection)
 end
 
 
@@ -201,6 +172,7 @@ end
   @returns p
 end
 
+
 #######################################################################
 #                                 LDG                                 #
 #######################################################################
diff --git a/src/projects/SREulerEq/initialdata.jl b/src/projects/SREulerEq/initialdata.jl
index 7b087af4e8c90031004765758ffbb06486ee7a90..49ec858f7a283216fd229b27c1c166336db05e69 100644
--- a/src/projects/SREulerEq/initialdata.jl
+++ b/src/projects/SREulerEq/initialdata.jl
@@ -1,31 +1,24 @@
-function initialdata!(rhs::RHS, prms)
-  initialdata_equation_of_state!(rhs, rhs.equation.eos, prms)
-  # initialdata_entropy_variables!(rhs, prms)
-  initialdata_hrsc!(rhs, rhs.hrsc, prms)
-  initialdata_tci!(rhs, rhs.tci, prms)
+function initialdata!(env, rhs::RHS, prms)
+  initialdata_equation_of_state!(env, rhs, rhs.equation.eos, prms)
+  # initialdata_entropy_variables!(env, rhs, prms)
+  initialdata_hrsc!(env, rhs, rhs.hrsc, prms)
+  initialdata_tci!(env, rhs, rhs.tci, prms)
   return
 end
 
 
-struct InitialData{T} end
-
-
 #######################################################################
 #                   Initial data equation of state                    #
 #######################################################################
 
 
-function initialdata_equation_of_state!(rhs::RHS, eos::EquationOfState.IdealGas, parameters)
+function initialdata_equation_of_state!(env, rhs::RHS, eos::EquationOfState.IdealGas, prms)
 
-  # TODO Dropped options from query call in favor of using dispatch for setting up initial data
-  # this makes it more attractive for implementing, but less so when trying to figure out
-  # which initial data are available
-  type = query(parameters, "type")
-  type = Symbol(lowercase(replace(type, " "=>"_")))
-  @unpack cache, mesh = rhs
+  @unpack id = prms
+  @unpack cache, mesh = env
   x = vec(mesh.x)
 
-  ρ0, v0, p0 = initialdata_equation_of_state(InitialData{type}(), eos, x)
+  ρ0, v0, p0 = initialdata_equation_of_state(Val(Symbol(id)), eos, x)
 
   ϵ0 = @. eos(InternalEnergy, Density, ρ0, Pressure, p0)
 
@@ -36,76 +29,77 @@ function initialdata_equation_of_state!(rhs::RHS, eos::EquationOfState.IdealGas,
   @. S   = Formulae.S(ρ0, v0, ϵ0, p0)
   @. tau = Formulae.τ(ρ0, v0, ϵ0, p0)
 
-  broadcast_volume!(cons2prim, rhs.equation, rhs.cache)
+  broadcast_volume!(cons2prim, rhs.equation, cache)
 
   return
 end
 
 
-function initialdata_equation_of_state(
-    ::InitialData{type}, eos, x) where type
+function initialdata_equation_of_state(::Val{type}, eos, x) where type
   error("initialdata_equation_of_state: Unknown initial data type '$type'")
 end
 
 
-function initialdata_equation_of_state(
-    ::InitialData{:smooth}, eos::EquationOfState.IdealGas, x)
+function initialdata_equation_of_state(::Val{:smooth}, eos::EquationOfState.IdealGas, x)
 
   xmin, xmax = extrema(x)
   @unpack gamma = eos
 
   # from Bugner, PhD thesis 2018
-  @assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
-  @assert gamma == 5/3
+  @toggled_assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
+  @toggled_assert gamma == 5/3
   ρ0 = @. 1.0 + 0.2 * sin(2.0 * π * x)
   v0 = @. 0.2 * x^0
   p0 = @. 1.0 * x^0
+
   (ρ0, v0, p0)
 end
 
 
 function initialdata_equation_of_state(
-    ::InitialData{:periodic_relativistic_shocktube}, eos::EquationOfState.IdealGas, x)
+    ::Val{:periodic_relativistic_shocktube}, eos::EquationOfState.IdealGas, x)
 
   xmin, xmax = extrema(x)
 
   # inspired by Bugner, PhD thesis 2018; Marti, Mueller, JCP 1996
-  @assert isapprox(xmin, -2.0) && isapprox(xmax, 2.0)
-  @assert eos.gamma == 5/3
+  @toggled_assert isapprox(xmin, -2.0) && isapprox(xmax, 2.0)
+  @toggled_assert eos.gamma == 5/3
   x0 = 1.0
   ρ0 = [ (xi < -x0 || xi > x0) ? 10.0  : 1.0   for xi in x ]
   v0 = [ (xi < -x0 || xi > x0) ? 0.0   : 0.0   for xi in x ]
   p0 = [ (xi < -x0 || xi > x0) ? 13.33 : 1e-7  for xi in x ]
+
   (ρ0, v0, p0)
 end
 
 
 function initialdata_equation_of_state(
-    ::InitialData{:periodic_relativistic_shocktube_2}, eos::EquationOfState.IdealGas, x)
+    ::Val{:periodic_relativistic_shocktube_2}, eos::EquationOfState.IdealGas, x)
 
   xmin, xmax = extrema(x)
 
   # inspired by Bugner, PhD thesis 2018; Marti, Mueller, JCP 1996
-  @assert isapprox(xmin, -2.0) && isapprox(xmax, 2.0)
-  @assert eos.gamma == 5/3
+  @toggled_assert isapprox(xmin, -2.0) && isapprox(xmax, 2.0)
+  @toggled_assert eos.gamma == 5/3
   x0 = 1.0
   ρ0 = [ (xi < -x0 || xi > x0) ? 1.0    : 1.0   for xi in x ]
   v0 = [ (xi < -x0 || xi > x0) ? 0.0    : 0.0   for xi in x ]
   p0 = [ (xi < -x0 || xi > x0) ? 1000.0 : 0.01  for xi in x ]
+
   (ρ0, v0, p0)
 end
 
 
 function initialdata_equation_of_state(
-    ::InitialData{:periodic_shocktube_pressure_jump}, eos::EquationOfState.IdealGas, x)
+    ::Val{:periodic_shocktube_pressure_jump}, eos::EquationOfState.IdealGas, x)
 
   xmin, xmax = extrema(x)
   γ          = eos.gamma
 
   # selfmade, less extreme case than periodic_relativistic_shocktube with only a jump
   # in pressure
-  @assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
-  @assert γ == 5/3
+  @toggled_assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
+  @toggled_assert γ == 5/3
   x0 = 0.5
   ρ0 = [ (xi < -x0 || xi > x0) ? 10.0  :  10.0 for xi in x ]
   v0 = [ (xi < -x0 || xi > x0) ? 0.0   :  0.0  for xi in x ]
@@ -115,47 +109,50 @@ end
 
 
 function initialdata_equation_of_state(
-    ::InitialData{:periodic_shocktube_less_extreme}, eos::EquationOfState.IdealGas, x)
+    ::Val{:periodic_shocktube_less_extreme}, eos::EquationOfState.IdealGas, x)
 
   xmin, xmax = extrema(x)
 
   # selfmade, less extreme case than periodic_relativistic_shocktube
-  @assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
-  @assert gamma == 5/3
+  @toggled_assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
+  @toggled_assert gamma == 5/3
   x0 = 0.5
   ρ0 = [ (xi < -x0 || xi > x0) ? 10.0  :  0.5   for xi in x ]
   v0 = [ (xi < -x0 || xi > x0) ? 0.0   :  0.0   for xi in x ]
   p0 = [ (xi < -x0 || xi > x0) ? 8.0   :  1.5 for xi in x ]
+
   (ρ0, v0, p0)
 end
 
 
 function initialdata_equation_of_state(
-    ::InitialData{:periodic_sod_shocktube}, eos::EquationOfState.IdealGas, x)
+    ::Val{:periodic_sod_shocktube}, eos::EquationOfState.IdealGas, x)
 
   xmin, xmax = extrema(x)
 
-  @assert isapprox(xmin, -2.0) && isapprox(xmax, 2.0)
-  @assert eos.gamma == 5/3
+  @toggled_assert isapprox(xmin, -2.0) && isapprox(xmax, 2.0)
+  @toggled_assert eos.gamma == 5/3
   x0 = 1.0
   ρ0 = [ (xi < -x0 || xi > x0) ? 1.0  :  0.125  for xi in x ]
   v0 = [ (xi < -x0 || xi > x0) ? 0.0  :  0.0    for xi in x ]
   p0 = [ (xi < -x0 || xi > x0) ? 1.0  :  0.1    for xi in x ]
+
   (ρ0, v0, p0)
 end
 
 
 function initialdata_equation_of_state(
-    ::InitialData{:sod_shocktube}, eos::EquationOfState.IdealGas, x)
+    ::Val{:sod_shocktube}, eos::EquationOfState.IdealGas, x)
 
   xmin, xmax = extrema(x)
 
-  @assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
-  @assert eos.gamma == 5/3
+  @toggled_assert isapprox(xmin, -1.0) && isapprox(xmax, 1.0)
+  @toggled_assert eos.gamma == 5/3
   x0 = 0.0
   ρ0 = [ (xi < x0) ? 1.0  :  0.125  for xi in x ]
   v0 = [ (xi < x0) ? 0.0  :  0.0    for xi in x ]
   p0 = [ (xi < x0) ? 1.0  :  0.1    for xi in x ]
+
   (ρ0, v0, p0)
 end
 
@@ -165,9 +162,9 @@ end
 #######################################################################
 
 
-function initialdata_entropy_variables!(rhs::RHS, prms)
+function initialdata_entropy_variables!(env, rhs::RHS, prms)
   @TODO
-  @unpack cache = rhs
+  @unpack cache = env
   @unpack t, tm1 = get_global_variables(cache)
   @unpack E, Em1, flx_E, flx_Em1 = get_static_variables(cache)
   tm1 .= 0.0
@@ -183,18 +180,18 @@ end
 #######################################################################
 
 
-function initialdata_hrsc!(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC}, prms) end
+function initialdata_hrsc!(env, rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC}, prms) end
 
 
-function initialdata_hrsc!(rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity, prms)
-  @unpack mu = get_cell_variables(rhs.cache)
+function initialdata_hrsc!(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity, prms)
+  @unpack mu = get_cell_variables(env.cache)
   @. mu = 0.0
 end
 
 
-function initialdata_hrsc!(rhs::RHS, hrsc::HRSC.SmoothedArtificialViscosity, prms)
-  initialdata_hrsc!(rhs, hrsc.av, prms)
-  @unpack smoothed_mu = get_static_variables(rhs.cache)
+function initialdata_hrsc!(env, rhs::RHS, hrsc::HRSC.SmoothedArtificialViscosity, prms)
+  initialdata_hrsc!(env, rhs, hrsc.av, prms)
+  @unpack smoothed_mu = get_static_variables(env.cache)
   @. smoothed_mu = 0.0
 end
 
@@ -204,11 +201,11 @@ end
 #######################################################################
 
 
-function initialdata_tci!(rhs::RHS, tci::Nothing, prms) end
+function initialdata_tci!(env, rhs::RHS, tci::Nothing, prms) end
 
 
-function initialdata_tci!(rhs::RHS, tci::TCI.EntropyProduction, prms)
-  @unpack cache = rhs
+function initialdata_tci!(env, rhs::RHS, tci::TCI.EntropyProduction, prms)
+  @unpack cache = env
   @unpack EP = get_static_variables(cache)
   @unpack EPjump_lhs, EPjump_rhs = get_bdry_variables(cache)
   @. EP = 0.0
@@ -217,7 +214,7 @@ function initialdata_tci!(rhs::RHS, tci::TCI.EntropyProduction, prms)
 end
 
 
-function initialdata_tci!(rhs::RHS, tci::TCI.AbstractTCI, prms)
-  @unpack flag = get_cell_variables(rhs.cache)
+function initialdata_tci!(env, rhs::RHS, tci::TCI.AbstractTCI, prms)
+  @unpack flag = get_cell_variables(env.cache)
   @. flag = 0.0
 end
diff --git a/src/projects/SREulerEq/main.jl b/src/projects/SREulerEq/main.jl
deleted file mode 100644
index 1e674a5102afd7b993e00ed09ee48f851e952136..0000000000000000000000000000000000000000
--- a/src/projects/SREulerEq/main.jl
+++ /dev/null
@@ -1,157 +0,0 @@
-using OrderedCollections 
-"""
-    main(parameters_filename)
-
-Run program specified by TOML file `parameters_filename`.
-
-The following sections in the parameter file are recognized:
-- [`Mesh`](@ref)
-- [`EquationOfState`](@ref)
-- [`Initialdata`](@ref)
-- [`Timestep`](@ref)
-- [`HRSC`](@ref)
-- [`Output`](@ref)
-"""
-function main(parameters_filename=""; override::AbstractDict=Dict())
-
-  override_prms = FlatDict(override)
-
-  parameters_filename = abspath(parameters_filename)
-  outputdir = mk_outputdir(parameters_filename)
-  cp(parameters_filename, joinpath(outputdir, basename(parameters_filename)))
-
-  # read parameters
-  if !isfile(parameters_filename)
-    error("Cannot locate parameter file '$parameters_filename'")
-  end
-  prms = FlatDict(TOML.parsefile(parameters_filename))
-  prms = merge(prms, override_prms)
-  ordered_prms = OrderedDict(prms)
-  display(sort(ordered_prms))
-
-  default_eos_prms = Dict("type"=>"IdealGas", "gamma"=>3.0)
-  eos_prms = trim_headers(prms, "EquationOfState")
-  amend!(eos_prms, default_eos_prms)
-  diff = diffkeys(eos_prms, default_eos_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  eos = EquationOfState.make_EquationOfState(eos_prms)
-  atm      = Atmosphere(; parse_parameters(Atmosphere, trim_headers(prms, "Atmosphere"))...)
-  cons2prim_newtonsolver = NewtonSolver(approx_derivative=true)
-  cons2prim_bisection = Bisection()
-
-  # setup RHS
-  mesh     = make_Mesh(trim_headers(prms, "Mesh"))
-  cache    = Cache(mesh)
-  equation = SREulerEquation(eos, atm, cons2prim_newtonsolver, cons2prim_bisection)
-  tci      = TCI.make_TCI(trim_headers(prms, "TCI"), mesh)
-  hrsc     = HRSC.make_HRSC(trim_headers(prms, "HRSC"), mesh)
-  rsolver  = ApproxRiemannSolver(flux, speed, equation)
-  ldg_rsolver, av_rsolver = if hrsc isa AbstractArtificialViscosity
-    ApproxRiemannSolver(ldg_flux, ldg_speed, equation),
-    ApproxRiemannSolver(av_flux, av_flux, equation)
-  else
-    nothing, nothing
-  end
-  rhs      = RHS(cache=cache, mesh=mesh, equation=equation, tci=tci, hrsc=hrsc,
-                 rsolver=rsolver, ldg_rsolver=ldg_rsolver, av_rsolver=av_rsolver)
-
-  _register_variables!(rhs)
-  register_variables!(cache, rsolver)
-  !isnothing(ldg_rsolver) && register_variables!(cache, ldg_rsolver)
-  !isnothing(av_rsolver) && register_variables!(cache, av_rsolver, dont_register=true)
-  display(cache)
-
-  initialdata!(rhs, trim_headers(prms, "Initialdata"))
-
-  default_boundaryconditions_prms = Dict("type"=>"periodic")
-  boundaryconditions_prms = trim_headers(prms, "BoundaryConditions")
-  amend!(boundaryconditions_prms, default_boundaryconditions_prms)
-  diff = diffkeys(boundaryconditions_prms, default_boundaryconditions_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  bdryconds = make_BoundaryConditions(rhs, boundaryconditions_prms)
-  ldg_bdryconds, av_bdryconds = if hrsc isa AbstractArtificialViscosity
-    make_BoundaryConditions_LDG(ldg_rsolver, boundaryconditions_prms),
-    make_BoundaryConditions_LDG(av_rsolver, boundaryconditions_prms)
-  else
-    nothing, nothing
-  end
-
-  rhs_fn!(du, u, t) = rhs!(du, u, t, rhs, bdryconds, ldg_bdryconds, av_bdryconds)
-  timestep_fn(u, t, n) = timestep(u, t, rhs)
-
-  ### Callbacks
-  rhscb = make_callback(rhs, bdryconds)
-
-  # Plotting
-  default_plot_prms = Dict("every_iteration"=>0, "every_dt"=>0.0, "pause"=>false)
-  plot_prms = trim_headers(prms, "Plot")
-  dg1d.amend!(plot_prms, default_plot_prms)
-  diff = diffkeys(plot_prms, default_plot_prms)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  theme(:dark, markerstrokewidth=0.1, marker=:circle)
-  plotfn(u,t) = plot(rhs)
-  plotcb = PlotCallback(plotfn,
-                        CallbackTiming(every_dt=plot_prms["every_dt"],
-                                       every_iteration=plot_prms["every_iteration"]),
-                        pause=plot_prms["pause"])
-  default_output_prms = Dict("every_iteration"=>0,
-                             "every_dt"=>0.5,
-                             "variables"=>["rho", "q", "E"],
-                             "filename"=>joinpath(outputdir, "output"))
-  output_prms = trim_headers(prms, "Output")
-  amend!(output_prms, default_output_prms)
-  diff = diffkeys(output_prms, default_output_prms)
-  @assert length(diff) == 0 "Section 'Output': Unknown options '$diff' encountered!"
-  savecb = SaveCallback(Symbol.(output_prms["variables"]), cache, mesh, 
-                        output_prms["filename"],
-                        CallbackTiming(every_dt=output_prms["every_dt"],
-                                       every_iteration=output_prms["every_iteration"])
-                       )
-
-  # artificially smooth initial data
-  smoother_called = false
-  function id_smoother_fn(u, t)
-    if !smoother_called
-      println("Smoothing initial data...")
-      @unpack D, S, tau = get_dynamic_variables(cache)
-      @unpack flag = get_cell_variables(cache)
-      id_hrsc = HRSC.BernsteinReconstruction(mesh)
-      HRSC.reconstruct!(D,    flag, id_hrsc, isperiodic=isperiodic(bdryconds), m=0.0)
-      HRSC.reconstruct!(S,    flag, id_hrsc, isperiodic=isperiodic(bdryconds))
-      HRSC.reconstruct!(tau,  flag, id_hrsc, isperiodic=isperiodic(bdryconds), m=0.0)
-      smoother_called = true
-      broadcast_volume!(cons2prim, equation, cache)
-    end
-  end
-  id_smoother_cb = FunctionCallback(id_smoother_fn,
-                                    CallbackTiming(every_iteration=1))
-
-  # Combine callbacks
-  callbacks = CallbackSet(rhscb.callbacks..., id_smoother_cb, savecb, plotcb)
-
-  aligned_times = get(output_prms, "aligned_times", [])
-  timealigned_savecb, timestep_fn = if length(aligned_times) > 0
-    atscb = TimeAlignedSaveCallback(aligned_times, timestep_fn, savecb)
-    atscb, atscb.aligned_timestep_fn
-  else
-    nothing, timestep_fn
-  end
-
-  !isnothing(timealigned_savecb) && push!(callbacks, timealigned_savecb)
-
-  default_evolve_prms = Dict("algorithm"=>"LSERK4", "tend"=>1.0)
-  evolve_prms = trim_headers(prms, "Evolution")
-  amend!(evolve_prms, default_evolve_prms)
-  diff = diffkeys(evolve_prms, default_evolve_prms)
-  @assert length(diff) == 0 "Section 'Evolution': Unknown options '$diff' encountered!"
-
-  @unpack D, S, tau = get_dynamic_variables(cache)
-  state_u = vcat(D, S, tau)
-
-  state_u = dg1d.evolve_ERK(rhs_fn!, state_u, timestep_fn,
-                      (0.0, Float64(evolve_prms["tend"])),
-                      Symbol(evolve_prms["algorithm"]),
-                      callback_fullstep=callbacks)
-
-  return
-end
diff --git a/src/projects/SREulerEq/plots.jl b/src/projects/SREulerEq/plots.jl
index 01079f07fa5b7d157cf980eed900e1691d9a17f1..57f53915d55673a027b8a10f1f9496269bcc26b4 100644
--- a/src/projects/SREulerEq/plots.jl
+++ b/src/projects/SREulerEq/plots.jl
@@ -1,8 +1,8 @@
-function plot(rhs::RHS)
-  plt_eq   = plot_equation(rhs)
-  plt_rhs  = nothing # plot_rhs(rhs)
-  plt_hrsc = plot_hrsc(rhs, rhs.hrsc)
-  plt_tci  = plot_tci(rhs, rhs.tci)
+function plot(env, rhs::RHS)
+  plt_eq   = plot_equation(env, rhs)
+  plt_rhs  = nothing # plot_rhs(env, rhs)
+  plt_hrsc = plot_hrsc(env, rhs, rhs.hrsc)
+  plt_tci  = plot_tci(env, rhs, rhs.tci)
 
   list_sub_plts = [ plt_eq, plt_rhs, plt_hrsc, plt_tci ]
   filter!(!isnothing, list_sub_plts)
@@ -11,7 +11,7 @@ function plot(rhs::RHS)
   sub_plt = Plots.plot(list_sub_plts..., layout=grid(layout..., heights=plt_heights))
 
   # combine plots
-  @unpack cache = rhs
+  @unpack cache = env
   @unpack t = get_global_variables(cache)
   plt = Plots.plot(sub_plt, layout=layout, size=(1400,1000), plot_title="t=$(t[1])",
                    left_margin=10Plots.mm)
@@ -24,9 +24,9 @@ end
 #######################################################################
 
 
-function plot_equation(rhs)
+function plot_equation(env, rhs)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh    = env
   @unpack D, S, tau      = get_dynamic_variables(cache)
   @unpack rho, p, eps, v = get_static_variables(cache)
   @unpack t              = get_global_variables(cache)
@@ -66,13 +66,13 @@ end
 #######################################################################
 
 
-function plot_hrsc(rhs, hrsc::Nothing) end
-function plot_hrsc(rhs, hrsc::AbstractReconstruction) end
+function plot_hrsc(env, rhs, hrsc::Nothing) end
+function plot_hrsc(env, rhs, hrsc::AbstractReconstruction) end
 
 
-function plot_hrsc(rhs, hrsc::HRSC.AbstractArtificialViscosity)
+function plot_hrsc(env, rhs, hrsc::HRSC.AbstractArtificialViscosity)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack mu          = get_cell_variables(cache)
   @unpack x           = mesh
 
@@ -94,11 +94,11 @@ function plot_hrsc(rhs, hrsc::HRSC.AbstractArtificialViscosity)
 end
 
 
-function plot_hrsc(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
+function plot_hrsc(env, rhs, hrsc::HRSC.SmoothedArtificialViscosity)
 
-  plt_mu = plot_hrsc(rhs, hrsc.av)
+  plt_mu = plot_hrsc(env, rhs, hrsc.av)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack smoothed_mu = get_static_variables(cache)
   @unpack x           = mesh
 
@@ -128,12 +128,12 @@ end
 #######################################################################
 
 
-function plot_tci(rhs, tci::Nothing) end
+function plot_tci(env, rhs, tci::Nothing) end
 
 
-function plot_tci(rhs, tci::TCI.AbstractTCI)
+function plot_tci(env, rhs, tci::TCI.AbstractTCI)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack flag        = get_cell_variables(cache)
   @unpack x           = mesh
 
@@ -163,9 +163,9 @@ function plot_tci(rhs, tci::TCI.AbstractTCI)
 end
 
 
-function plot_tci(rhs, tci::TCI.EntropyProduction)
+function plot_tci(env, rhs, tci::TCI.EntropyProduction)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack EP          = get_static_variables(cache)
   @unpack flag        = get_cell_variables(cache)
   @unpack x           = mesh
@@ -202,9 +202,9 @@ end
 #######################################################################
 
 
-function plot_rhs(rhs)
+function plot_rhs(env, rhs)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack rhs_rho, rhs_q, rhs_E = get_rhs_variables(cache)
   @unpack t                     = get_global_variables(cache)
   @unpack x                     = mesh
diff --git a/src/projects/SREulerEq/rhs.jl b/src/projects/SREulerEq/rhs.jl
index 9fcf39384e38343f81ffb6e95c1008711d305cf9..aca7ed492caa6f503a06f46e5e38178c4f761a53 100644
--- a/src/projects/SREulerEq/rhs.jl
+++ b/src/projects/SREulerEq/rhs.jl
@@ -1,104 +1,11 @@
-Base.@kwdef struct RHS{T_Equation <:SREulerEquation, 
-                       T_RSolver  <:AbstractRiemannSolver,
-                       T_HRSC     <:Maybe{HRSC.AbstractHRSC},
-                       T_TCI      <:Maybe{TCI.AbstractTCI},
-                       T_LDG_RSolver<:Maybe{AbstractRiemannSolver},
-                       T_AV_RSolver<:Maybe{AbstractRiemannSolver}}
-
-  cache::Cache
-  mesh::Mesh
-  equation::T_Equation
-  rsolver::T_RSolver
-  hrsc::T_HRSC               = nothing
-  tci::T_TCI                 = nothing
-  ldg_rsolver::T_LDG_RSolver = nothing
-  av_rsolver::T_AV_RSolver   = nothing
-end
-
-
-#######################################################################
-#                         Register variables                          #
-#######################################################################
-
-
-# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
-# definition in this module without a warning or error. This does not happen for methods
-# defined in Base.
-# Because of that we prefix with _ below.
-# Cf. https://github.com/JuliaLang/julia/issues/15510
-function _register_variables!(rhs::RHS)
-  register_variables!(rhs.cache,
-    dynamic_variablenames = (:D, :S, :tau,),            # conservatives
-    rhs_variablenames     = (:rhs_D, :rhs_S, :rhs_tau), # rhs of conservatives
-    static_variablenames  = (:flx_D, :flx_S, :flx_tau,  # conservatives' fluxes
-                             :rho, :v, :p, :eps,        # primitives
-                             :max_v, :x,                # maximum charactersitic speed, grid
-                             :E, :Em1,                  # entropy
-                             :flx_E, :flx_Em1),         # entropy flux
-    cell_variablenames    = (:cellmax_v,),
-    global_variablenames  = (:t, :tm1) # time steps
-  )
-  _register_variables!(rhs, rhs.hrsc)
-  _register_variables!(rhs, rhs.tci)
-  @unpack x = get_static_variables(rhs.cache)
-  @. x = rhs.mesh.x[:]
-end
-
-
-function _register_variables!(rhs, tci_or_hrsc::Nothing) end
-
-
-_register_variables!(rhs, hrsc::AbstractHRSC) = 
-  error("_register_variables: Not implemented for hrsc of type '$(typeof(hrsc))'")
-
-_register_variables!(rhs, hrsc::HRSC.AbstractReconstruction) = nothing
-
-
-function _register_variables!(rhs, hrsc::HRSC.AbstractArtificialViscosity)
-  register_variables!(rhs.cache,
-    static_variablenames  = (:ldg_D, :ldg_S, :ldg_tau,              # local DG variable
-                             :flx_ldg_D, :flx_ldg_S, :flx_ldg_tau), # local DG flux
-    cell_variablenames    = (:mu,), # 'one viscosity to rule them all'
-    )
-end
-
-
-function _register_variables!(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
-  _register_variables!(rhs, hrsc.av)
-  register_variables!(rhs.cache,
-    static_variablenames  = (:smoothed_mu,)
-  )
-end
-
-
-function _register_variables!(rhs, tci::TCI.AbstractTCI)
-  register_variables!(rhs.cache, cell_variablenames = (:flag,:D_flag,:S_flag,:tau_flag))
-end
-
-
-function _register_variables!(rhs, tci::TCI.EntropyProduction)
-  register_variables!(rhs.cache, 
-                      cell_variablenames = (:flag,),
-                      static_variablenames = (:EP,), # entropy production
-                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs)
-                                              # entropy production due to jumps at interfaces
-  )
-end
-
-
 #######################################################################
 #                              Timestep                               #
 #######################################################################
 
 
-function timestep(state_u, t, rhs::RHS)
-  wrap_dynamic_variables!(rhs.cache, state_u)
-  return timestep(rhs, rhs.hrsc)
-end
-
-
-function timestep(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
-  @unpack cache, mesh, equation = rhs
+function timestep(env, rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
+  @unpack cache, mesh = env
+  @unpack equation    = rhs
   @unpack max_v = get_static_variables(cache)
 
   broadcast_volume!(cons2prim, equation, cache)
@@ -119,8 +26,9 @@ function timestep(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
 end
 
 
-function timestep(rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity)
-  @unpack cache, mesh, equation = rhs
+function timestep(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity)
+  @unpack cache, mesh = env
+  @unpack equation    = rhs
 
   broadcast_volume!(cons2prim, equation, cache)
   broadcast_volume!(speed, equation, cache)
@@ -152,19 +60,10 @@ end
 #######################################################################
 
 
-function rhs!(state_du, state_u, t, rhs::RHS, bdryconds::BoundaryConditions,
-    ldg_bdryconds::Maybe{BoundaryConditions}, av_bdryconds::Maybe{BoundaryConditions})
-
-  @unpack cache = rhs
-  wrap_dynamic_variables!(cache, state_u)
-  wrap_rhs_variables!(cache, state_du)
-  rhs!(rhs, t, rhs.hrsc, bdryconds, ldg_bdryconds, av_bdryconds)
-end
-
-
-function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds)
+function rhs!(env, rhs::RHS, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack cache, mesh           = env
+  @unpack equation, rsolver     = rhs
   @unpack D, S, tau             = get_dynamic_variables(cache)
   @unpack flx_D, flx_S, flx_tau = get_static_variables(cache)
   @unpack rhs_D, rhs_S, rhs_tau = get_rhs_variables(cache)
@@ -175,7 +74,7 @@ function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds
   broadcast_volume!(cons2prim, equation, cache)
   broadcast_volume!(flux, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   compute_rhs_weak_form!(rhs_D,   flx_D,    lhs_numflx_D,   rhs_numflx_D,   mesh)
   compute_rhs_weak_form!(rhs_S,   flx_S,    lhs_numflx_S,   rhs_numflx_S,   mesh)
@@ -185,10 +84,11 @@ function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds
 end
 
 
-function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractReconstruction,
+function rhs!(env, rhs::RHS, hrsc::HRSC.AbstractReconstruction,
     bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack cache, mesh                    = env
+  @unpack equation, rsolver              = rhs
   @unpack D, S, tau                      = get_dynamic_variables(cache)
   @unpack flx_D, flx_S, flx_tau          = get_static_variables(cache)
   @unpack rhs_D, rhs_S, rhs_tau          = get_rhs_variables(cache)
@@ -213,11 +113,12 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractReconstruction,
 end
 
 
-function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
+function rhs!(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity,
     bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver,
-          ldg_rsolver, av_rsolver                = rhs
+  @unpack cache, mesh                            = env
+  @unpack equation, rsolver, ldg_rsolver,
+          av_rsolver                             = rhs
   @unpack D, S, tau                              = get_dynamic_variables(cache)
   @unpack flx_D, flx_S, flx_tau,
           ldg_D, ldg_S, ldg_tau,
@@ -233,7 +134,7 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
   ## solve auxiliary equation: q + ∂x u = 0
   broadcast_volume!(ldg_flux, equation, cache)
   broadcast_faces!(mean_flux, ldg_rsolver, cache, mesh)
-  broadcast_boundaryconditions!(mean_flux, ldg_bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(mean_flux, ldg_bdryconds, cache, mesh, 0.0)
   compute_rhs_weak_form!(ldg_D,   flx_ldg_D,   lhs_numflx_ldg_D,   rhs_numflx_ldg_D,   mesh)
   compute_rhs_weak_form!(ldg_S,   flx_ldg_S,   lhs_numflx_ldg_S,   rhs_numflx_ldg_S,   mesh)
   compute_rhs_weak_form!(ldg_tau, flx_ldg_tau, lhs_numflx_ldg_tau, rhs_numflx_ldg_tau, mesh)
@@ -241,12 +142,12 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
   ## compute rhs of regularized equation: ∂t u + ∂x f + ∂x μ q = 0
   broadcast_volume!(av_flux, equation, cache)
   broadcast_faces!(mean_flux, av_rsolver, cache, mesh)
-  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, 0.0)
 
   broadcast_volume!(cons2prim, equation, cache)
   broadcast_volume!(flux, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   @. flx_D          += flx_ldg_D
   @. flx_S          += flx_ldg_S
diff --git a/src/projects/SREulerEq/setup.jl b/src/projects/SREulerEq/setup.jl
new file mode 100644
index 0000000000000000000000000000000000000000..117ea492f101218e03f6360b24de4551e33a1439
--- /dev/null
+++ b/src/projects/SREulerEq/setup.jl
@@ -0,0 +1,122 @@
+function RHS(env::Environment, prms)
+
+  # construct RHS
+  eos = EquationOfState.make_EquationOfState(prms["EquationOfState"])
+  equation = make_SREulerEquation(eos, prms["SREulerEq"])
+  tci  = TCI.make_TCI(env.mesh, prms["TCI"])
+  hrsc = HRSC.make_HRSC(env.mesh, prms["HRSC"])
+  rsolver = ApproxRiemannSolver(flux, speed, equation)
+  ldg_rsolver, av_rsolver = if hrsc isa AbstractArtificialViscosity
+    ApproxRiemannSolver(ldg_flux, ldg_speed, equation),
+    ApproxRiemannSolver(av_flux, av_flux, equation)
+  else
+    nothing, nothing
+  end
+
+  rhs = RHS(equation, rsolver, hrsc, tci, ldg_rsolver, av_rsolver)
+
+  # register variables
+  # TODO add a ::Nothing overload for register_variables!
+  # TODO Somehow replace _register_variables! with register_variables!
+  @unpack cache = env
+  _register_variables!(cache, rhs)
+  register_variables!(cache, rsolver)
+  !isnothing(ldg_rsolver) && register_variables!(cache, ldg_rsolver)
+  !isnothing(av_rsolver) && register_variables!(cache, av_rsolver, dont_register=true)
+  display(cache)
+
+  # setup boundary conditions
+  bdryconds = make_BoundaryConditions(env, equation, rsolver, prms["SREulerEq"])
+  ldg_bdryconds, av_bdryconds = if hrsc isa AbstractArtificialViscosity
+    make_BoundaryConditions_LDG(env, equation, ldg_rsolver, prms["SREulerEq"]),
+    make_BoundaryConditions_LDG(env, equation, av_rsolver, prms["SREulerEq"])
+  else
+    nothing, nothing
+  end
+
+  # TODO Bdry conditions should be moved to project and should only be just a 'lightweight'
+  # descriptor. Furthermore, they should also register variables in cache themselves.
+  # Note: Atm, only the 'from_initial_data' option would require a place to store the data
+  # somewhere. For periodic BCs we don't need extra storage at all.
+  # But what other types of boundary conditions will be needed in the future?
+
+  # setup initial data
+  initialdata!(env, rhs, prms["SREulerEq"])
+
+  # setup callbacks
+  rhscb = make_callback(env, rhs, bdryconds)
+
+  append!(env.callbacks, CallbackSet(rhscb.callbacks...))
+
+  return rhs, (bdryconds, ldg_bdryconds, av_bdryconds)
+end
+
+
+#######################################################################
+#                         Register variables                          #
+#######################################################################
+
+
+# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
+# definition in this module without a warning or error. This does not happen for methods
+# defined in Base.
+# Because of that we prefix with _ below.
+# Cf. https://github.com/JuliaLang/julia/issues/15510
+function _register_variables!(cache, rhs::RHS)
+  register_variables!(cache,
+    dynamic_variablenames = (:D, :S, :tau,),            # conservatives
+    rhs_variablenames     = (:rhs_D, :rhs_S, :rhs_tau), # rhs of conservatives
+    static_variablenames  = (:flx_D, :flx_S, :flx_tau,  # conservatives' fluxes
+                             :rho, :v, :p, :eps,        # primitives
+                             :max_v, :x,                # maximum charactersitic speed, grid
+                             :E, :Em1,                  # entropy
+                             :flx_E, :flx_Em1),         # entropy flux
+    cell_variablenames    = (:cellmax_v,),
+    global_variablenames  = (:t, :tm1) # time steps
+  )
+  _register_variables!(cache, rhs.hrsc)
+  _register_variables!(cache, rhs.tci)
+  @unpack x = get_static_variables(cache)
+  # @. x = env.mesh.x[:]
+end
+
+
+function _register_variables!(cache, tci_or_hrsc::Nothing) end
+
+
+_register_variables!(cache, hrsc::AbstractHRSC) = 
+  error("_register_variables: Not implemented for hrsc of type '$(typeof(hrsc))'")
+
+_register_variables!(cache, hrsc::HRSC.AbstractReconstruction) = nothing
+
+
+function _register_variables!(cache, hrsc::HRSC.AbstractArtificialViscosity)
+  register_variables!(cache,
+    static_variablenames  = (:ldg_D, :ldg_S, :ldg_tau,              # local DG variable
+                             :flx_ldg_D, :flx_ldg_S, :flx_ldg_tau), # local DG flux
+    cell_variablenames    = (:mu,), # 'one viscosity to rule them all'
+    )
+end
+
+
+function _register_variables!(cache, hrsc::HRSC.SmoothedArtificialViscosity)
+  _register_variables!(cache, hrsc.av)
+  register_variables!(cache,
+    static_variablenames  = (:smoothed_mu,)
+  )
+end
+
+
+function _register_variables!(cache, tci::TCI.AbstractTCI)
+  register_variables!(cache, cell_variablenames = (:flag,:D_flag,:S_flag,:tau_flag))
+end
+
+
+function _register_variables!(cache, tci::TCI.EntropyProduction)
+  register_variables!(cache,
+                      cell_variablenames = (:flag,),
+                      static_variablenames = (:EP,), # entropy production
+                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs)
+                                              # entropy production due to jumps at interfaces
+  )
+end
diff --git a/src/projects/SREulerEq/types.jl b/src/projects/SREulerEq/types.jl
new file mode 100644
index 0000000000000000000000000000000000000000..8a4c2e7c17c857cc3eebdb797c31e384748d18d6
--- /dev/null
+++ b/src/projects/SREulerEq/types.jl
@@ -0,0 +1,28 @@
+Base.@kwdef struct Atmosphere
+  rho::Float64
+  threshold::Float64
+end
+
+
+struct SREulerEquation{T_EoS<:EquationOfState.AbstractEquationOfState} <: AbstractEquation
+  eos::T_EoS
+  atmosphere::Atmosphere
+  cons2prim_newtonsolver::NewtonSolver
+  cons2prim_bisection::Bisection
+end
+
+
+struct RHS{T_Equation <:SREulerEquation,
+           T_RSolver  <:AbstractRiemannSolver,
+           T_HRSC     <:Maybe{HRSC.AbstractHRSC},
+           T_TCI      <:Maybe{TCI.AbstractTCI},
+           T_LDG_RSolver<:Maybe{AbstractRiemannSolver},
+           T_AV_RSolver<:Maybe{AbstractRiemannSolver}}
+
+  equation::T_Equation
+  rsolver::T_RSolver
+  hrsc::T_HRSC
+  tci::T_TCI
+  ldg_rsolver::T_LDG_RSolver
+  av_rsolver::T_AV_RSolver
+end
diff --git a/src/projects/ScalarEquation/ScalarEquation.jl b/src/projects/ScalarEquation/ScalarEquation.jl
index 337d04d6ed5183325bb5ac701570c0c348dcc5de..07f4d4d039b42047ef4b4fc4789c27964fba231d 100644
--- a/src/projects/ScalarEquation/ScalarEquation.jl
+++ b/src/projects/ScalarEquation/ScalarEquation.jl
@@ -3,18 +3,75 @@ module ScalarEquation
 
 
 using dg1d
-using Match
-import Parameters: @unpack, @with_kw
 using Plots
 
 
+include("types.jl")
 include("equation.jl")
 include("rhs.jl")
 include("initialdata.jl")
 include("plots.jl")
 include("callbacks.jl")
 include("boundaryconditions.jl")
-include("main.jl")
+include("setup.jl")
+
+
+#######################################################################
+#                 dg1d project interface definitions                  #
+#######################################################################
+
+
+dg1d.project_type(::Val{:ScalarEquation}) = RHS
+
+# TODO Remove bdryconds
+dg1d.rhs!(env::Environment, p::RHS, bdryconds) = rhs!(env, p, p.hrsc, bdryconds...)
+
+dg1d.timestep!(env::Environment, p::RHS) = timestep(env, p, p.hrsc)
+
+dg1d.@parameters ScalarEquation begin
+
+  """
+  Available options:
+  - `advection`
+  - `burgers`
+  """
+  equation = "advection"
+  @check equation in [ "advection", "burgers" ]
+
+  "Advection speed"
+  advection_v = 1.0
+
+  """
+  Initial data configuration. Available options:
+  - `sine`
+  - `bump`
+  - `gaussian`
+  """
+  id = "sine"
+  @check id in [ "sine", "bump", "gaussian" ]
+
+  "Period of sine initial data"
+  id_sine_period = 1
+
+  "Phase of sine initial data"
+  id_sine_phase = 0.0
+
+  "RMS width of gaussian"
+  id_gaussian_width = 1.0
+
+  "Amplitude of gaussian"
+  id_gaussian_amplitude = 1.0
+
+  """
+  Boundary conditions. Available options:
+  - `periodic`
+  - `no_inflow`
+  - `from_id`
+  """
+  bc = "periodic"
+  @check bc in [ "periodic", "no_inflow", "from_id" ]
+
+end
 
 
 end # ScalarEquation
diff --git a/src/projects/ScalarEquation/boundaryconditions.jl b/src/projects/ScalarEquation/boundaryconditions.jl
index c0d06a4ca0c371f6707f11800d2693e831dcae99..9ee123823495542f3295f3de0278b6d5549a199c 100644
--- a/src/projects/ScalarEquation/boundaryconditions.jl
+++ b/src/projects/ScalarEquation/boundaryconditions.jl
@@ -1,30 +1,39 @@
-function make_BoundaryConditions_equation(rhs::RHS, prms)
-  type = query(String, prms, "type")
-
-  lhs_bc, rhs_bc = @match type begin
-    "periodic"     => (PeriodicBC(), PeriodicBC())
-    "no inflow"    => (DirichletBC((0.0,)), OutflowBC())
-    "initial data" => begin
-      @unpack u = get_dynamic_variables(rhs.cache)
-      u_lhs, u_rhs = deepcopy(u[1]), deepcopy(u[end])
-      lhs_bdry = t -> (u_lhs,)
-      rhs_bdry = t -> (u_rhs,)
-      DirichletBC(lhs_bdry), DirichletBC(rhs_bdry)
-    end
-    _              => error("Boundary condition type '$type' not implemented!")
+function make_BoundaryConditions(env, equation, rsolver, prms)
+  @unpack bc = prms
+
+  lhs_bc, rhs_bc = if bc == "periodic"
+    PeriodicBC(), PeriodicBC()
+  elseif bc == "no_inflow"
+    DirichletBC((0.0,)), OutflowBC()
+  elseif bc == "from_id"
+    @unpack u = get_dynamic_variables
+    u_lhs, u_rhs = deepcopy(u[1]), deepcopy(u[end])
+    lhs_bdry = t -> (u_lhs,)
+    rhs_bdry = t -> (u_rhs,)
+    DirichletBC(lhs_bdry), DirichletBC(rhs_bdry)
+  else
+    error("Unknown boundary condition requested: '$bc'")
   end
-  
-  return BoundaryConditions(lhs_bc, rhs_bc, rhs.rsolver)
+
+  return BoundaryConditions(lhs_bc, rhs_bc, rsolver)
 end
 
 
-function make_BoundaryConditions_LDG(ldg_rsolver, prms)
+function make_BoundaryConditions_LDG(env, equation, ldg_rsolver, prms)
+
+  @unpack bc = prms
+
   isnothing(ldg_rsolver) && return nothing
-  type = query(String, prms, "type")
-  lhs_bc, rhs_bc = if type == "periodic"
+
+  lhs_bc, rhs_bc = if bc == "periodic"
     PeriodicBC(), PeriodicBC()
-  else
+  elseif bc == "no_inflow"
+    OutflowBC(), OutflowBC()
+  elseif bc == "from_id"
     OutflowBC(), OutflowBC()
+  else
+    error("Unknown boundary condition requested: '$bc'")
   end
+
   return BoundaryConditions(lhs_bc, rhs_bc, ldg_rsolver)
 end
diff --git a/src/projects/ScalarEquation/callbacks.jl b/src/projects/ScalarEquation/callbacks.jl
index 9d11c0e368d0a4be77d5175975a8b7fee0002a78..5bde11a0faab87b12aa5025648c91e1e0b0586f9 100644
--- a/src/projects/ScalarEquation/callbacks.jl
+++ b/src/projects/ScalarEquation/callbacks.jl
@@ -1,11 +1,11 @@
-function make_callback(rhs::RHS, bdryconds::BoundaryConditions)
-  cbfn_equation(u, t) = callback_equation(u, t, rhs, isperiodic(bdryconds), rhs.equation)
+function make_callback(env, rhs::RHS, bdryconds::BoundaryConditions)
+  cbfn_equation(u, t) = callback_equation(u, t, env, rhs, isperiodic(bdryconds), rhs.equation)
   cb_equation         = FunctionCallback(cbfn_equation, 
                                          CallbackTiming(every_iteration=1,every_dt=0))
-  cbfn_tci(u, t)      = callback_tci(u, t, rhs, isperiodic(bdryconds), rhs.tci)
+  cbfn_tci(u, t)      = callback_tci(u, t, env, rhs, isperiodic(bdryconds), rhs.tci)
   cb_tci              = FunctionCallback(cbfn_tci,
                                          CallbackTiming(every_iteration=1, every_dt=0))
-  cbfn_hrsc(u, t)     = callback_hrsc(u, t, rhs, isperiodic(bdryconds), rhs.hrsc)
+  cbfn_hrsc(u, t)     = callback_hrsc(u, t, env, rhs, isperiodic(bdryconds), rhs.hrsc)
   cb_hrsc             = FunctionCallback(cbfn_hrsc, 
                                          CallbackTiming(every_iteration=1,every_dt=0))
   callbackset = CallbackSet(cb_equation, cb_tci, cb_hrsc)
@@ -18,8 +18,9 @@ end
 #######################################################################
 
 
-function callback_equation(state_u, state_t, rhs, isperiodic, eq::AbstractScalarEquation)
-  @unpack cache, mesh, equation = rhs
+function callback_equation(state_u, state_t, env, rhs, isperiodic, eq::AbstractScalarEquation)
+  @unpack cache, mesh = env
+  @unpack equation = rhs
   wrap_dynamic_variables!(cache, state_u)
   @unpack u                     = get_dynamic_variables(cache)
   @unpack E, Em1, F, Fm1        = get_static_variables(cache)
@@ -41,19 +42,20 @@ end
 #######################################################################
 
 
-callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::Nothing) = nothing
+callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::Nothing) = nothing
 
 
-callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractHRSC) =
+callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.AbstractHRSC) =
   error("not yet implemented for type $(typeof(hrsc))")
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic,
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic,
   hrsc::Union{HRSC.BernsteinReconstruction, HRSC.WENOReconstruction}) end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractArtificialViscosity)
-  @unpack cache, mesh, equation = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.AbstractArtificialViscosity)
+  @unpack cache, mesh = env
+  @unpack equation = rhs
   @unpack v         = get_static_variables(cache)
   @unpack mu, max_v = get_cell_variables(cache)
   @unpack u         = get_dynamic_variables(cache)
@@ -70,24 +72,26 @@ function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.AbstractArt
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.TCIViscosity)
-  @unpack cache, mesh = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.TCIViscosity)
+  @unpack cache, mesh = env
   @unpack mu, flag    = get_cell_variables(cache)
   HRSC.compute_viscosity!(mu, flag, hrsc)
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.SmoothedArtificialViscosity)
-  callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc.av)
-  @unpack cache, rsolver = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.SmoothedArtificialViscosity)
+  callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc.av)
+  @unpack cache = env
+  @unpack rsolver = rhs
   @unpack mu = get_cell_variables(cache)
   @unpack smoothed_mu = get_static_variables(cache)
   smoothed_mu .= vec(hrsc.smoother(mu,isperiodic))
 end
 
 
-function callback_hrsc(state_u, state_t, rhs, isperiodic, hrsc::HRSC.EntropyViscosity)
-  @unpack cache, mesh, equation    = rhs
+function callback_hrsc(state_u, state_t, env, rhs, isperiodic, hrsc::HRSC.EntropyViscosity)
+  @unpack cache, mesh = env
+  @unpack equation    = rhs
   @unpack u              = get_dynamic_variables(cache)
   @unpack E, F, Em1, Fm1, v = get_static_variables(cache)
   @unpack mu, max_v      = get_cell_variables(cache)
@@ -110,15 +114,15 @@ end
 #######################################################################
 
 
-callback_tci(state_u, state_t, rhs, isperiodic, tci::Nothing) = nothing
+callback_tci(state_u, state_t, env, rhs, isperiodic, tci::Nothing) = nothing
 
 
-callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.AbstractTCI) =
+callback_tci(state_u, state_t, env, rhs, isperiodic, tci::TCI.AbstractTCI) =
   error("not yet implemented for type $(typeof(tci))")
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.EntropyProduction)
-  @unpack cache, mesh            = rhs
+function callback_tci(state_u, state_t, env, rhs, isperiodic, tci::TCI.EntropyProduction)
+  @unpack cache, mesh            = env
   @unpack u                      = get_dynamic_variables(cache)
   @unpack E, Em1, F, Fm1, EP     = get_static_variables(cache)
   @unpack flag                   = get_cell_variables(cache)
@@ -130,10 +134,10 @@ function callback_tci(state_u, state_t, rhs, isperiodic, tci::TCI.EntropyProduct
 end
 
 
-function callback_tci(state_u, state_t, rhs, isperiodic,
+function callback_tci(state_u, state_t, env, rhs, isperiodic,
     tci::Union{TCI.Diffusion, TCI.Minmod, TCI.Threshold,
                TCI.ModalDecayAverage, TCI.ModalDecayHighest})
-  @unpack cache, mesh            = rhs
+  @unpack cache, mesh            = env
   @unpack u                      = get_dynamic_variables(cache)
   @unpack flag                   = get_cell_variables(cache)
   TCI.compute_indicator!(flag, u, tci)
diff --git a/src/projects/ScalarEquation/equation.jl b/src/projects/ScalarEquation/equation.jl
index c710a7c1a80b335182c66a88304415b567ce0209..81b9571b92bbc068f6e8d25d7c6dcc4bbbf48ebc 100644
--- a/src/projects/ScalarEquation/equation.jl
+++ b/src/projects/ScalarEquation/equation.jl
@@ -1,40 +1,23 @@
-"""
-    abstract type AbstractScalarEquation <: dg1d.AbstractEquation end
-
-Supertype for ScalarEquation problems.
-New problems should subtype it and also implement the following methods:
-- `make_Equation(prms, ::Type{NewProblem})`: factory for an instance of type `NewProblem`.
-The following methods have to be defined with the `@with_signature` interface:
-- `speed(args, ::NewProblem)`
-- `flux(args, ::NewProblem)`
-- `entropy(args, ::NewProblem)`
-- `entropy_flux(args, ::NewProblem)`
-"""
-abstract type AbstractScalarEquation <: dg1d.AbstractEquation end
-
-
 #######################################################################
-#                          Equation Factory                           #
+#                               Factory                               #
 #######################################################################
 
 
 function make_Equation(prms)
-  available_types = concretetypes(AbstractScalarEquation)
-  all_options = last.(split.(string.(available_types), "."))
-  type = query(prms, "type", options=all_options)
-  expr = quote $(Symbol(type)) end
-  return make_Equation(prms, eval(expr))
+  @unpack equation = prms
+  if equation == "advection"
+    return Advection(prms["advection_v"])
+  elseif equation == "burgers"
+    return Burgers()
+  else
+    error("Unknown equation requested: '$equation'")
+  end
 end
 
-
 #######################################################################
 #                          Advection                          #
 #######################################################################
 
-struct Advection <: AbstractScalarEquation
-  v::Float64
-end
-
 function make_Equation(prms, ::Type{Advection})
   v = query(Float64, prms, "v", default=1.0)
   return Advection(v)
@@ -68,8 +51,6 @@ end
 #                           Burgers                           #
 #######################################################################
 
-struct Burgers <: AbstractScalarEquation end
-
 make_Equation(prms, ::Type{Burgers}) = Burgers()
 
 make_Burgers(prms) = Burgers()
diff --git a/src/projects/ScalarEquation/initialdata.jl b/src/projects/ScalarEquation/initialdata.jl
index 1807e8869cc6ed73a002386d9edf4c0bb105c0e5..089a8e1d55733c6782c7d399c188fbebeea8e056 100644
--- a/src/projects/ScalarEquation/initialdata.jl
+++ b/src/projects/ScalarEquation/initialdata.jl
@@ -1,57 +1,34 @@
-function initialdata!(rhs::RHS, prms)
-  initialdata_equation!(rhs, rhs.equation, prms)
-  initialdata_hrsc!(rhs, rhs.hrsc, prms)
-  initialdata_tci!(rhs, rhs.tci, prms)
+function initialdata!(env, rhs::RHS, prms)
+  initialdata_equation!(env, rhs, rhs.equation, prms)
+  initialdata_hrsc!(env, rhs, rhs.hrsc, prms)
+  initialdata_tci!(env, rhs, rhs.tci, prms)
 end
 
 
-function initialdata_equation!(rhs::RHS, equation::AbstractScalarEquation, parameters)
-  @unpack cache, mesh = rhs
-  @unpack u = get_dynamic_variables(cache)
-  @unpack L, x = mesh
-  x = vec(x)
-  tstart = 0.0 # always
-  id_fn = make_function_initialdata_equation(parameters)
-  @. u = id_fn(x)
-  return
-end
+function initialdata_equation!(env, rhs::RHS, equation::AbstractScalarEquation, prms)
 
-
-"""
-    make_function_initialdata(parameters)
-
-Generate a function `id_fn(x)` to compute initial data on grid `x`.
-`parameters` must contain sections `Initialdata, Mesh`.
-"""
-function make_function_initialdata_equation(parameters)
-
-  type = query(parameters, "type", options=["sine", "bump", "bugner gaussian", "gaussian"])
-  xrange = query(Vector{Float64}, parameters, "xrange", default = [-1.0,1.0])
-  L = first(diff(xrange))
-
-  id_fn = if type == "sine"
-    period = query(Int64, parameters, "period", default=1) * 2.0 * π / L
-    phase = query(Float64, parameters, "phase", default=0.0)
-    (x) -> sin(period * x + phase)
-  elseif type == "bump"
-    xmin, xmax = xrange
-    height = query(Float64, parameters, "height", default=1)
-    width = query(Float64, parameters, "width", default=(xmax-xmin)/2)
-    @assert width < L "Initialdata 'bump': Bump width exceeds x domain '$xrange'"
-    xl, xr = xmin+(L-width)/2, xmin+(L+width)/2
-    (x) -> (x < xl || x > xr) ? 0.5 : height
-  elseif type == "gaussian"
-    amplitude = query(Float64, parameters, "height", default=1)
-    width = query(Float64, parameters, "width", default=1)
-    x0 = (xrange[2] + xrange[1]) / 2
-    display(x0)
-    (x) -> amplitude * exp(-(x-x0)^2 / width^2)
-  elseif type == "bugner gaussian"
-    xmin, xmax = xrange
-    @assert xmin == -3.0 && xmax == 3.0
-    A, sigma = 1.0, 0.4
-    (x) -> A * exp(-x^2 / sigma^2)
-  elseif type == "smooth bump"
+  @unpack cache, mesh = env
+  @unpack u = get_dynamic_variables(cache)
+  @unpack L, x, xmin, xmax = mesh
+
+  @unpack id = prms
+  x = x[:]
+
+  id_fn = if id == "sine"
+    @unpack id_sine_period, id_sine_phase = prms
+    u .= @. sin.(2 * pi * id_sine_period * x/L + id_sine_phase)
+  elseif id == "bump"
+    @unpack id_bump_height, id_bump_width = prms
+    @toggled_assert id_bump_width < L "Initialdata 'bump': Bump width exceeds x domain '$xrange'"
+    xl, xr = xmin+(L-id_bump_width)/2, xmin+(L+id_bump_width)/2
+    u .= @. (x < xl || x > xr) ? 0.5 : id_bump_height
+  elseif id == "gaussian"
+    A = prms["id_gaussian_amplitude"]
+    w = prms["id_gaussian_width"]
+    @toggled_assert w > 0.0
+    x0 = (xmax + xmin) / 2
+    u .= @. A * exp(-(x-x0)^2 / w^2)
+  elseif id == "smooth bump"
     error("TODO Implement 'smooth bump' initial data")
 #   elseif type === :smooth_bump
 #     xl = xmin + L * 0.10
@@ -70,25 +47,25 @@ function make_function_initialdata_equation(parameters)
 #       end
 #     end
   else
-    error("Requested initialdata type $id_type not implemented!")
+    error("Unknown initialdata requested: '$id'")
   end
 
-  return id_fn
+  return
 end
 
 
-function initialdata_hrsc!(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC}, prms) end
+function initialdata_hrsc!(env, rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC}, prms) end
 
 
-function initialdata_hrsc!(rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity, prms)
-  @unpack mu = get_cell_variables(rhs.cache)
+function initialdata_hrsc!(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity, prms)
+  @unpack mu = get_cell_variables(env.cache)
   @. mu = 0.0
 end
 
 
-function initialdata_hrsc!(rhs::RHS, hrsc::HRSC.EntropyViscosity, prms)
-  @unpack E, Em1, F, Fm1 = get_static_variables(rhs.cache)
-  @unpack mu             = get_cell_variables(rhs.cache)
+function initialdata_hrsc!(env, rhs::RHS, hrsc::HRSC.EntropyViscosity, prms)
+  @unpack E, Em1, F, Fm1 = get_static_variables(env.cache)
+  @unpack mu             = get_cell_variables(env.cache)
   @. E = 0.0
   @. F = 0.0
   @. Em1 = 0.0
@@ -97,10 +74,10 @@ function initialdata_hrsc!(rhs::RHS, hrsc::HRSC.EntropyViscosity, prms)
 end
 
 
-function initialdata_tci!(rhs::RHS, tci::Nothing, prms) end
+function initialdata_tci!(env, rhs::RHS, tci::Nothing, prms) end
 
 
-function initialdata_tci!(rhs::RHS, tci::TCI.AbstractTCI, prms)
-  @unpack flag = get_cell_variables(rhs.cache)
+function initialdata_tci!(env, rhs::RHS, tci::TCI.AbstractTCI, prms)
+  @unpack flag = get_cell_variables(env.cache)
   @. flag = 0.0
 end
diff --git a/src/projects/ScalarEquation/main.jl b/src/projects/ScalarEquation/main.jl
deleted file mode 100644
index 6e7c794dd03fd8b332e367f632006d1a2ad30781..0000000000000000000000000000000000000000
--- a/src/projects/ScalarEquation/main.jl
+++ /dev/null
@@ -1,199 +0,0 @@
-using InteractiveUtils
-using PlotThemes
-
-
-function section(prms, name)
-  subprms = get(prms, name, missing)
-  ismissing(subprms) && error("Missing parameter section '$name'")
-  return subprms
-end
-
-
-@doc """
-  main(parameters_filename)
-
-Run program specified by TOML file `parameters_filename`.
-
-The following sections in the parameter file are recognized:
-- [`Mesh`](@ref)
-- [`Equation`](@ref)
-- [`Initialdata`](@ref)
-- [`Timestep`](@ref)
-- [`Analysis`](@ref)
-- [`HRSC`](@ref)
-- [`Output`](@ref)
-
-TODO The above should generate links to the parameter lists for the
-corresponding sections.
-
-TODO Add backup::Bool flag to main.
-"""
-function main(parameters_filename="")
-
-  parameters_filename = abspath(parameters_filename)
-  outputdir = mk_outputdir(parameters_filename)
-  cp(parameters_filename, joinpath(outputdir, basename(parameters_filename)))
-
-  # read parameters
-  if !isfile(parameters_filename)
-    error("Cannot locate parameter file '$parameters_filename'")
-  end
-  prms = FlatDict(TOML.parsefile(parameters_filename))
-
-  # setup RHS
-  mesh = make_Mesh(trim_headers(prms, "Mesh"))
-  cache = Cache(mesh)
-  equation = make_Equation(trim_headers(prms, "Equation"))
-  tci      = TCI.make_TCI(trim_headers(prms, "TCI"), mesh)
-  hrsc     = HRSC.make_HRSC(trim_headers(prms, "HRSC"), mesh)
-  rsolver  = ApproxRiemannSolver(flux, speed, equation)
-  ldg_rsolver, av_rsolver = if hrsc isa AbstractArtificialViscosity
-    ApproxRiemannSolver(ldg_flux, ldg_speed, equation),
-    ApproxRiemannSolver(av_flux, av_speed, equation)
-  else
-    nothing, nothing
-  end
-  rhs      = RHS(cache=cache, mesh=mesh, equation=equation, tci=tci, hrsc=hrsc,
-                 rsolver=rsolver, ldg_rsolver=ldg_rsolver, av_rsolver=av_rsolver)
-
-  _register_variables!(rhs)
-  register_variables!(cache, rsolver)
-  !isnothing(ldg_rsolver) && register_variables!(cache, ldg_rsolver)
-  !isnothing(av_rsolver) && register_variables!(cache, av_rsolver, dont_register=true)
-  display(cache)
-
-  initialdata!(rhs, trim_headers(prms, "Initialdata", "Mesh"))
-
-  default_boundaryconditions_prms = Dict("type"=>"periodic")
-  boundaryconditions_prms = trim_headers(prms, "BoundaryConditions")
-  amend!(boundaryconditions_prms, default_boundaryconditions_prms)
-  diff = diffkeys(boundaryconditions_prms, default_boundaryconditions_prms)
-  @assert length(diff) == 0 "Section 'BoundaryConditions': Unknown options '$diff' encountered!"
-  bdryconds     = make_BoundaryConditions_equation(rhs, boundaryconditions_prms)
-  ldg_bdryconds = make_BoundaryConditions_LDG(ldg_rsolver, boundaryconditions_prms)
-  av_bdryconds  = make_BoundaryConditions_LDG(av_rsolver, boundaryconditions_prms)
-
-  rhs_fn!(du, u, t) = rhs!(du, u, t, rhs, bdryconds, ldg_bdryconds, av_bdryconds)
-  timestep_fn(u, t, n) = timestep(u, t, rhs)
-
-  ### Callbacks
-  rhscb = make_callback(rhs, bdryconds)
-
-  # Plotting
-  default_plot_prms = Dict("every_iteration"=>0,
-                           "every_dt"=>0.0,
-                           "pause"=>false)
-  plot_prms = trim_headers(prms, "Plot")
-  dg1d.amend!(plot_prms, default_plot_prms)
-  diff = diffkeys(plot_prms, default_plot_prms)
-  theme(:dark, markerstrokewidth=0.1, marker=:circle)
-  plotfn(u,t) = plot(rhs)
-  @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  plotcb = PlotCallback(plotfn,
-                        CallbackTiming(every_dt=plot_prms["every_dt"],
-                                       every_iteration=plot_prms["every_iteration"]),
-                        pause=plot_prms["pause"])
-  default_output_prms = Dict("every_iteration"=>0,
-                             "every_dt"=>0.5,
-                             "variables"=>["u"],
-                             "filename"=>joinpath(outputdir, "output"),
-                             "aligned_times"=>[])
-  output_prms = trim_headers(prms, "Output")
-  amend!(output_prms, default_output_prms)
-  diff = diffkeys(output_prms, default_output_prms)
-  @assert length(diff) == 0 "Section 'Output': Unknown options '$diff' encountered!"
-  savecb = SaveCallback(Symbol.(output_prms["variables"]), cache, mesh, 
-                        output_prms["filename"],
-                        CallbackTiming(every_dt=output_prms["every_dt"],
-                                       every_iteration=output_prms["every_iteration"])
-                       )
-
-  # Combine callbacks
-  callbacks = CallbackSet(rhscb.callbacks..., savecb, plotcb)
-
-  aligned_times = get(output_prms, "aligned_times", [])
-  timealigned_savecb, timestep_fn = if length(aligned_times) > 0
-    atscb = TimeAlignedSaveCallback(aligned_times, timestep_fn, savecb)
-    atscb, atscb.aligned_timestep_fn
-  else
-    nothing, timestep_fn
-  end
-
-  !isnothing(timealigned_savecb) && push!(callbacks, timealigned_savecb)
-  # callback_fn(u, t, i) = callbacks(u, t, i)
-
-  default_evolve_prms = Dict("algorithm"=>"LSERK4", "tend"=>1.0)
-  evolve_prms = trim_headers(prms, "Evolution")
-  amend!(evolve_prms, default_evolve_prms)
-  diff = diffkeys(evolve_prms, default_evolve_prms)
-  @assert length(diff) == 0 "Section 'Evolution': Unknown options '$diff' encountered!"
-
-  @unpack u = get_dynamic_variables(cache)
-
-  u = dg1d.evolve_ERK(rhs_fn!, u, timestep_fn, 
-                      (0.0, Float64(evolve_prms["tend"])), 
-                      Symbol(evolve_prms["algorithm"]),
-                      callback_fullstep=callbacks)
-
-  return
-
-  # default_output_filename = Filesystem.basename(Filesystem.splitext(parameters_filename)[1])
-  # default_output_prms = Dict("every_iteration"=>10,
-  #                            "every_dt"=>0.0,
-  #                            "variables"=>["u"],
-  #                            "filename"=>joinpath(outputdir, "output"))
-  # output_prms = pop!(prms, "Output", Dict())
-  # amend!(output_prms, default_output_prms)
-  # diff = diffkeys(output_prms, default_output_prms)
-  # @assert length(diff) == 0 "Section 'Output': Unknown options '$diff' encountered!"
-  #
-  # default_plot_prms = Dict("every_iteration"=>10,
-  #                          "every_dt"=>0.0,
-  #                          "pause"=>true)
-  # plot_prms = pop!(prms, "Plot", Dict())
-  # dg1d.amend!(plot_prms, default_plot_prms)
-  # diff = diffkeys(plot_prms, default_plot_prms)
-  # @assert length(diff) == 0 "Section 'Plot': Unknown options '$diff' encountered!"
-  #
-  # savecb = SaveCallback(Symbol.(output_prms["variables"]), D, output_prms["filename"],
-  #                         CallbackTiming(every_dt=output_prms["every_dt"],
-  #                                        every_iteration=output_prms["every_iteration"]))
-  # theme(:dark, markerstrokewidth=0.1)
-  # plotfn(u,t) = plot(I, u, t)
-  # plotcb = PlotCallback(plotfn,
-  #                       CallbackTiming(every_dt=plot_prms["every_dt"],
-  #                                      every_iteration=plot_prms["every_iteration"]),
-  #                       pause=plot_prms["pause"])
-  # hrscfn(u,t) = update_hrsc!(I, u, t)
-  # hrsccb = FunctionCallback(hrscfn, CallbackTiming(every_iteration=1, every_dt=0))
-  # tcicb = TCI.make_callback(tci, I, CallbackTiming(every_iteration=1, every_dt=0))
-  # # tcifn(u, t) = TCI.compute_indicator!(D, tci)
-  # # tcicb = FunctionCallback(tcifn, CallbackTiming(every_iteration=1, every_dt=0))
-  #
-  # # callbacks = CallbackSet(plotcb, savecb)
-  # callbacks = CallbackSet(tcicb, hrsccb, plotcb, savecb)
-  # # callbacks = CallbackSet(hrsccb, plotcb)
-  # callback_fn(u, t, i) = callbacks(u, t, i)
-  # # callback_fn(u, t, i) = plotcb(u, t, i)
-  #
-  #
-  # ##### Evolution
-  #
-  # rhs_fn!(du, u, t) = rhs!(du, u, t, I)
-  # timestep_fn(u, t) = timestep(u, t, I)
-  #
-  # default_evolve_prms = Dict("algorithm"=>"LSERK4",
-  #                            "tend"=>1.0)
-  # evolve_prms = pop!(prms, "Evolution", Dict())
-  # amend!(evolve_prms, default_evolve_prms)
-  # diff = diffkeys(evolve_prms, default_evolve_prms)
-  # @assert length(diff) == 0 "Section 'Evolution': Unknown options '$diff' encountered!"
-  #
-  # # dg1d.evolve_ERK(myrhs!, u, timestep, (0.0, 1.0), :RK4, post_hook=post_hook)
-  # u = dg1d.evolve_ERK(rhs_fn!, u, timestep_fn,
-  #                     (0.0, Float64(evolve_prms["tend"])),
-  #                     Symbol(evolve_prms["algorithm"]),
-  #                     post_hook=callback_fn)
-
-  return
-end
diff --git a/src/projects/ScalarEquation/plots.jl b/src/projects/ScalarEquation/plots.jl
index c2a7ac3aa8432f1381810468cbae0abf6021e393..f5d54cf879067b5d4aa8540e633e569be88b73b7 100644
--- a/src/projects/ScalarEquation/plots.jl
+++ b/src/projects/ScalarEquation/plots.jl
@@ -1,7 +1,7 @@
-function plot(rhs::RHS)
-  plt_eq   = plot_equation(rhs, rhs.equation)
-  plt_hrsc = plot_hrsc(rhs, rhs.hrsc)
-  plt_tci  = plot_tci(rhs, rhs.tci)
+function plot(env, rhs::RHS)
+  plt_eq   = plot_equation(env, rhs, rhs.equation)
+  plt_hrsc = plot_hrsc(env, rhs, rhs.hrsc)
+  plt_tci  = plot_tci(env, rhs, rhs.tci)
 
   # drop missing plots and figure out layout
   plts = [ plt_eq, plt_hrsc, plt_tci ]
@@ -9,7 +9,7 @@ function plot(rhs::RHS)
   layout = (length(plts), 1)
 
   # combine plots
-  @unpack cache = rhs
+  @unpack cache = env
   @unpack t = get_global_variables(cache)
   plt = Plots.plot(plts..., layout=layout, size=(1400,1000), plot_title="t=$(t[1])",
                    left_margin=10Plots.mm)
@@ -22,9 +22,9 @@ end
 #######################################################################
 
 
-function plot_equation(rhs, eq::AbstractScalarEquation)
+function plot_equation(env, rhs, eq::AbstractScalarEquation)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack u           = get_dynamic_variables(cache)
   @unpack x           = mesh
 
@@ -51,12 +51,12 @@ end
 #######################################################################
 
 
-function plot_hrsc(rhs, hrsc::Nothing) end
+function plot_hrsc(env, rhs, hrsc::Nothing) end
 
 
-function plot_hrsc(rhs, hrsc::HRSC.AbstractArtificialViscosity)
+function plot_hrsc(env, rhs, hrsc::HRSC.AbstractArtificialViscosity)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack x           = mesh
 
   xlabel  = "x"
@@ -83,7 +83,7 @@ function plot_hrsc(rhs, hrsc::HRSC.AbstractArtificialViscosity)
 end
 
 
-plot_hrsc(rhs, hrsc::Union{HRSC.BernsteinReconstruction,
+plot_hrsc(env, rhs, hrsc::Union{HRSC.BernsteinReconstruction,
                            HRSC.WENOReconstruction}) = nothing
 
 
@@ -92,12 +92,12 @@ plot_hrsc(rhs, hrsc::Union{HRSC.BernsteinReconstruction,
 #######################################################################
 
 
-function plot_tci(rhs, tci::Nothing) end
+function plot_tci(env, rhs, tci::Nothing) end
 
 
-function plot_tci(rhs, tci::TCI.AbstractTCI) 
+function plot_tci(env, rhs, tci::TCI.AbstractTCI) 
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack flag        = get_cell_variables(cache)
   @unpack x           = mesh
 
@@ -126,9 +126,9 @@ function plot_tci(rhs, tci::TCI.AbstractTCI)
 end
 
 
-function plot_tci(rhs, tci::TCI.EntropyProduction)
+function plot_tci(env, rhs, tci::TCI.EntropyProduction)
 
-  @unpack cache, mesh = rhs
+  @unpack cache, mesh = env
   @unpack EP          = get_static_variables(cache)
   @unpack flag        = get_cell_variables(cache)
   @unpack x           = mesh
diff --git a/src/projects/ScalarEquation/rhs.jl b/src/projects/ScalarEquation/rhs.jl
index efe61315d12d7d7c75cfd518e17337db972130dc..4884320c5c04900d554fe9981095bde816009405 100644
--- a/src/projects/ScalarEquation/rhs.jl
+++ b/src/projects/ScalarEquation/rhs.jl
@@ -1,102 +1,11 @@
-# Alternative names:
-# - Discretization ... too long
-# - Approximation or Approx ... the second one might fit, but is also unspecific
-# - Implementation or Impl ... too generic
-# - Project ... too generic
-# - State ... is at really a state?
-Base.@kwdef mutable struct RHS{T_Equation <:AbstractScalarEquation, 
-                               T_RSolver  <:AbstractRiemannSolver,
-                               T_HRSC     <:Maybe{HRSC.AbstractHRSC},
-                               T_TCI      <:Maybe{TCI.AbstractTCI},
-                               T_LDG_RSolver<:Maybe{AbstractRiemannSolver},
-                               T_AV_RSolver<:Maybe{AbstractRiemannSolver}}
-
-  cache::Cache
-  mesh::Mesh
-  equation::T_Equation
-  rsolver::T_RSolver
-  hrsc::T_HRSC               = nothing
-  tci::T_TCI                 = nothing
-  ldg_rsolver::T_LDG_RSolver = nothing
-  av_rsolver::T_AV_RSolver   = nothing
-end
-
-
-#######################################################################
-#                         Register variables                          #
-#######################################################################
-
-
-
-# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
-# definition in this module without a warning or error. This does not happen for methods
-# defined in Base.
-# Because of that we prefix with _ below.
-# Cf. https://github.com/JuliaLang/julia/issues/15510
-# Perhaps this is the import vs. using confusion people talk on the forums.
-function _register_variables!(rhs::RHS)
-  _register_variables!(rhs, rhs.equation)
-  _register_variables!(rhs, rhs.hrsc)
-  _register_variables!(rhs, rhs.tci)
-end
-
-
-function _register_variables!(rhs, eq::AbstractScalarEquation)
-  register_variables!(rhs.cache,
-    dynamic_variablenames = (:u,),
-    rhs_variablenames     = (:rhs_u,),
-    static_variablenames  = (:flx_u, :E, :Em1, :F, :Fm1, :v),
-    cell_variablenames    = (:max_v,),
-    global_variablenames  = (:t, :tm1))
-end
-
-
-function _register_variables!(rhs, hrsc::HRSC.AbstractArtificialViscosity)
-  register_variables!(rhs.cache,
-    static_variablenames  = (:q, :flx_q),
-    cell_variablenames    = (:mu,))
-end
-
-
-function _register_variables!(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
-  _register_variables!(rhs, hrsc.av)
-  register_variables!(rhs.cache,
-    static_variablenames  = (:smoothed_mu,))
-end
-
-
-function _register_variables!(rhs, hrsc::Maybe{HRSC.AbstractHRSC}) end
-
-
-function _register_variables!(rhs, tci::Nothing) end
-
-
-function _register_variables!(rhs, tci::TCI.AbstractTCI)
-  register_variables!(rhs.cache, cell_variablenames = (:flag,))
-end
-
-
-function _register_variables!(rhs, tci::TCI.EntropyProduction)
-  register_variables!(rhs.cache, 
-                      cell_variablenames   = (:flag,),
-                      static_variablenames = (:EP,),
-                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs))
-end
-
-
 #######################################################################
 #                              Timestep                               #
 #######################################################################
 
 
-function timestep(state_u, t, rhs::RHS)
-  wrap_dynamic_variables!(rhs.cache, state_u)
-  return timestep(t, rhs, rhs.hrsc)
-end
-
-
-function timestep(t, rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
-  @unpack cache, mesh, equation = rhs
+function timestep(env, rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
+  @unpack cache, mesh = env
+  @unpack equation = rhs
   @unpack v = get_static_variables(cache)
 
   broadcast_volume!(speed, equation, cache)
@@ -120,24 +29,14 @@ end
 #######################################################################
 
 
-function rhs!(state_du, state_u, t, rhs::RHS,
-    bdryconds::BoundaryConditions,
-    ldg_bdryconds::Maybe{BoundaryConditions},
-    av_bdryconds::Maybe{BoundaryConditions})
-
-  wrap_dynamic_variables!(rhs.cache, state_u)
-  wrap_rhs_variables!(rhs.cache, state_du)
-  return rhs!(rhs, t, rhs.hrsc, bdryconds, ldg_bdryconds, av_bdryconds)
-end
-
-
-function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds::Nothing, av_bdryconds::Nothing)
+function rhs!(env, rhs::RHS, hrsc::Nothing, bdryconds, ldg_bdryconds::Nothing, av_bdryconds::Nothing)
 
-  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack cache, mesh = env
+  @unpack equation, rsolver = rhs
 
   broadcast_volume!(flux, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(central_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(central_flux, bdryconds, cache, mesh, 0.0)
 
   @unpack flx_u, v               = get_static_variables(cache)
   @unpack u                      = get_dynamic_variables(cache)
@@ -149,10 +48,11 @@ function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds::Nothing, av_
 end
 
 
-function rhs!(rhs::RHS, t, hrsc::Union{BernsteinReconstruction, WENOReconstruction},
+function rhs!(env, rhs::RHS, hrsc::Union{BernsteinReconstruction, WENOReconstruction},
     bdryconds, ldg_bdryconds::Nothing, av_bdryconds::Nothing)
 
-  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack cache, mesh       = env
+  @unpack equation, rsolver = rhs
   @unpack flag                           = get_cell_variables(cache)
   @unpack flx_u                          = get_static_variables(cache)
   @unpack u                              = get_dynamic_variables(cache)
@@ -163,7 +63,7 @@ function rhs!(rhs::RHS, t, hrsc::Union{BernsteinReconstruction, WENOReconstructi
 
   broadcast_volume!(flux, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   compute_rhs_weak_form!(rhs_u, flx_u, lhs_numflx_u, rhs_numflx_u, mesh)
 
@@ -171,10 +71,11 @@ function rhs!(rhs::RHS, t, hrsc::Union{BernsteinReconstruction, WENOReconstructi
 end
 
 
-function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
+function rhs!(env, rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity,
     bdryconds, ldg_bdryconds, av_bdryconds)
 
-  @unpack cache, mesh, equation, rsolver, ldg_rsolver, av_rsolver = rhs
+  @unpack cache, mesh = env
+  @unpack equation, rsolver, ldg_rsolver, av_rsolver = rhs
 
   @unpack flx_u, flx_q, q, v         = get_static_variables(cache)
   @unpack u                          = get_dynamic_variables(cache)
@@ -186,7 +87,7 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
   ## solve auxiliary equation: q + ∂x u = 0
   broadcast_volume!(ldg_flux, equation, cache)
   broadcast_faces!(central_flux, ldg_rsolver, cache, mesh)
-  broadcast_boundaryconditions!(central_flux, ldg_bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(central_flux, ldg_bdryconds, cache, mesh, 0.0)
   compute_rhs_weak_form!(q, flx_q, lhs_numflx_q, rhs_numflx_q, mesh)
 
   ## compute rhs of regularized equation: ∂t u + ∂x f + ∂x μ q = 0
@@ -194,10 +95,10 @@ function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
   # @. flx_q = smoothed_mu * q
   broadcast_volume!(av_flux, equation, cache)
   broadcast_faces!(mean_flux, av_rsolver, cache, mesh)
-  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, 0.0)
   broadcast_volume!(flux, equation, cache)
   broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
-  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, 0.0)
 
   # # add up fluxs and numerical fluxes
   @. flx_u += flx_q
diff --git a/src/projects/ScalarEquation/setup.jl b/src/projects/ScalarEquation/setup.jl
new file mode 100644
index 0000000000000000000000000000000000000000..7d827a637c2daa12a2473aa40597312ceb90b35a
--- /dev/null
+++ b/src/projects/ScalarEquation/setup.jl
@@ -0,0 +1,111 @@
+function RHS(env::Environment, prms)
+
+  # construct RHS
+  equation = make_Equation(prms["ScalarEquation"])
+  tci  = TCI.make_TCI(env.mesh, prms["TCI"])
+  hrsc = HRSC.make_HRSC(env.mesh, prms["HRSC"])
+  rsolver = ApproxRiemannSolver(flux, speed, equation)
+  ldg_rsolver, av_rsolver = if hrsc isa AbstractArtificialViscosity
+    ApproxRiemannSolver(ldg_flux, ldg_speed, equation),
+    ApproxRiemannSolver(av_flux, av_flux, equation)
+  else
+    nothing, nothing
+  end
+
+  rhs = RHS(equation, rsolver, hrsc, tci, ldg_rsolver, av_rsolver)
+
+  # register variables
+  # TODO add a ::Nothing overload for register_variables!
+  # TODO Somehow replace _register_variables! with register_variables!
+  @unpack cache = env
+  _register_variables!(cache, rhs)
+  register_variables!(cache, rsolver)
+  !isnothing(ldg_rsolver) && register_variables!(cache, ldg_rsolver)
+  !isnothing(av_rsolver) && register_variables!(cache, av_rsolver, dont_register=true)
+  display(cache)
+
+  # setup boundary conditions
+  bdryconds = make_BoundaryConditions(env, equation, rsolver, prms["ScalarEquation"])
+  ldg_bdryconds, av_bdryconds = if hrsc isa AbstractArtificialViscosity
+    make_BoundaryConditions_LDG(env, equation, ldg_rsolver, prms["ScalarEquation"]),
+    make_BoundaryConditions_LDG(env, equation, av_rsolver, prms["ScalarEquation"])
+  else
+    nothing, nothing
+  end
+
+  # TODO Bdry conditions should be moved to project and should only be just a 'lightweight'
+  # descriptor. Furthermore, they should also register variables in cache themselves.
+  # Note: Atm, only the 'from_initial_data' option would require a place to store the data
+  # somewhere. For periodic BCs we don't need extra storage at all.
+  # But what other types of boundary conditions will be needed in the future?
+
+  # setup initial data
+  initialdata!(env, rhs, prms["ScalarEquation"])
+
+  # setup callbacks
+  rhscb = make_callback(env, rhs, bdryconds)
+
+  append!(env.callbacks, CallbackSet(rhscb.callbacks...))
+
+  return rhs, (bdryconds, ldg_bdryconds, av_bdryconds)
+end
+
+
+#######################################################################
+#                         Register variables                          #
+#######################################################################
+
+
+# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
+# definition in this module without a warning or error. This does not happen for methods
+# defined in Base.
+# Because of that we prefix with _ below.
+# Cf. https://github.com/JuliaLang/julia/issues/15510
+# Perhaps this is the import vs. using confusion people talk on the forums.
+function _register_variables!(cache, rhs::RHS)
+  _register_variables!(cache, rhs.equation)
+  _register_variables!(cache, rhs.hrsc)
+  _register_variables!(cache, rhs.tci)
+end
+
+
+function _register_variables!(cache, eq::AbstractScalarEquation)
+  register_variables!(cache,
+    dynamic_variablenames = (:u,),
+    rhs_variablenames     = (:rhs_u,),
+    static_variablenames  = (:flx_u, :E, :Em1, :F, :Fm1, :v),
+    cell_variablenames    = (:max_v,),
+    global_variablenames  = (:t, :tm1))
+end
+
+
+function _register_variables!(cache, hrsc::HRSC.AbstractArtificialViscosity)
+  register_variables!(cache,
+    static_variablenames  = (:q, :flx_q),
+    cell_variablenames    = (:mu,))
+end
+
+
+function _register_variables!(cache, hrsc::HRSC.SmoothedArtificialViscosity)
+  _register_variables!(cache, hrsc.av)
+  register_variables!(cache, static_variablenames  = (:smoothed_mu,))
+end
+
+
+function _register_variables!(cache, hrsc::Maybe{HRSC.AbstractHRSC}) end
+
+
+function _register_variables!(cache, tci::Nothing) end
+
+
+function _register_variables!(cache, tci::TCI.AbstractTCI)
+  register_variables!(cache, cell_variablenames = (:flag,))
+end
+
+
+function _register_variables!(cache, tci::TCI.EntropyProduction)
+  register_variables!(cache,
+                      cell_variablenames   = (:flag,),
+                      static_variablenames = (:EP,),
+                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs))
+end
diff --git a/src/projects/ScalarEquation/types.jl b/src/projects/ScalarEquation/types.jl
new file mode 100644
index 0000000000000000000000000000000000000000..d29cb5e085e5e5cefa4f1f7f31f8fe768b51a548
--- /dev/null
+++ b/src/projects/ScalarEquation/types.jl
@@ -0,0 +1,37 @@
+"""
+    abstract type AbstractScalarEquation <: dg1d.AbstractEquation end
+
+Supertype for ScalarEquation problems.
+New problems should subtype it and also implement the following methods:
+- `make_Equation(prms, ::Type{NewProblem})`: factory for an instance of type `NewProblem`.
+The following methods have to be defined with the `@with_signature` interface:
+- `speed(args, ::NewProblem)`
+- `flux(args, ::NewProblem)`
+- `entropy(args, ::NewProblem)`
+- `entropy_flux(args, ::NewProblem)`
+"""
+abstract type AbstractScalarEquation <: dg1d.AbstractEquation end
+
+
+struct Advection <: AbstractScalarEquation
+  v::Float64
+end
+
+
+struct Burgers <: AbstractScalarEquation end
+
+
+struct RHS{T_Equation     <:AbstractScalarEquation, 
+           T_RSolver      <:AbstractRiemannSolver,
+           T_HRSC         <:Maybe{HRSC.AbstractHRSC},
+           T_TCI          <:Maybe{TCI.AbstractTCI},
+           T_LDG_RSolver  <:Maybe{AbstractRiemannSolver},
+           T_AV_RSolver   <:Maybe{AbstractRiemannSolver}} <: dg1d.AbstractProject
+
+  equation::T_Equation
+  rsolver::T_RSolver
+  hrsc::T_HRSC
+  tci::T_TCI
+  ldg_rsolver::T_LDG_RSolver
+  av_rsolver::T_AV_RSolver
+end
diff --git a/src/projects/TCI/interface.jl b/src/projects/TCI/interface.jl
index ede8647e72b08f1e8560503e3e562cac701572ef..c361aa16b77199f0dc21957ca489f83133e02900 100644
--- a/src/projects/TCI/interface.jl
+++ b/src/projects/TCI/interface.jl
@@ -21,14 +21,25 @@ abstract type AbstractTCI end
 #######################################################################
 
 
-function make_TCI(parameters, mesh::Mesh)
-  type = Symbol(get(parameters, "type", missing))
-  concretes = [ Base.typename(ct).name for ct in concretetypes(AbstractTCI) ]
-  if type === :missing
+function make_TCI(mesh::Mesh, prms)
+  @unpack method = prms
+
+  if method == "none"
     return nothing
+  elseif method == "mda"
+    return ModalDecayAverage(mesh, prms["mda_threshold_min"], prms["mda_threshold_max"])
+  elseif method == "mdh"
+    return ModalDecayHighest(mesh, prms["mdh_threshold_min"], prms["mdh_threshold_max"])
+  elseif method == "diffusion"
+    return Diffusion(mesh, prms["diffusion_max"])
+  elseif method == "minmod"
+    return Minmod(mesh, prms["minmod_threshold"])
+  elseif method == "threshold"
+    return Threshold(mesh, prms["threshold"])
+  elseif method == "entropy_production"
+    return EntropyProduction(mesh, prms["entropy_production_max"])
+  else
+    error("TCI: Unknown method requested: '$method'")
   end
-  @assert type in concretes "TCI parameter 'type = $type' unknown. Available options: $concretes"
-  tci_type = eval(type)
-  tci_prms = parse_parameters(tci_type, parameters)
-  return tci_type(; mesh, tci_prms...)
+
 end
diff --git a/src/projects/projects.jl b/src/projects/projects.jl
new file mode 100644
index 0000000000000000000000000000000000000000..d4227c6e53c2b3b1d755cee7206b3b7bede324ac
--- /dev/null
+++ b/src/projects/projects.jl
@@ -0,0 +1,178 @@
+#######################################################################
+#                              Interface                              #
+#######################################################################
+
+
+"""
+    abstract type AbstractProject end
+
+The supertype for the Project interface of `dg1d`.
+
+Subtypes must implement the constructor
+```
+function (::Type{<:AbstractProject})(env::Environment, prms)
+  # ...
+end
+```
+
+---
+
+# Project interface
+
+How to add a project called `MyProject`:
+
+0. Setup a submodule in `src/projects/MyProject/MyProject.jl` called `MyProject`.
+1. `include` the project in `src/projects/projects.jl`.
+2. Define a subtype of `AbstractProject`. This will serve as the project's dispatcher.
+3. Overload the following methods:
+    - `dg1d.project_type`
+    - `dg1d.parameters`
+    - `dg1d.check_parameters`
+    - `dg1d.rhs!`
+    - `dg1d.timestep!`
+
+---
+
+# Example
+
+```
+# src/project/MyProject/MyProject.jl
+
+module MyProject
+
+using dg1d
+
+# subtype AbstractProject
+struct Project <: dg1d.AbstractProject
+  # ...
+end
+
+function MyProject.Project(env::Environment, prms)
+  # This constructor should
+  # - register variables in cache(env)
+  # - set up initial data for this project
+  # - set up boundary conditions for this project
+  # - set up callbacks for this project
+end
+
+dg1d.project_type(::Val{:MyModule}) = MyProject.Project
+
+dg1d.@parameters MyModule begin
+  ...
+end
+
+function dg1d.rhs!(env::dg1d.Environment, p::MyProject.Project)
+  # compute the rhs inplace in `env`
+end
+
+function dg1d.timestep!(env::dg1d.Environment, p::MyProject.Project)
+  # compute a timestep based on the state of `env`
+end
+
+end # MyModule
+```
+"""
+abstract type AbstractProject end
+
+function (::Type{T})(env::Environment, prms) where T<:AbstractProject
+  error("Interface: Project type '$T' does not implement a constructor!")
+end
+
+
+"""
+    project_type(::Val{T}) where T -> Type{S} where S<:AbstractProject
+
+Register a module `T` with a dispatch type `S<:AbstractProject` in the dg1d
+interface. `T` will be the also the name for the parameter section in parameter files.
+"""
+function project_type(::Val{T}) where T
+  error("Interface: Project '$T' does not provide a dispatch type")
+end
+
+
+"""
+    parameters(::Val{T}) where T -> Dict{String,Any}
+
+Return a dictionary with the default parameters for a project `T`.
+
+Do not directly overload this method. Instead use `dg1d.@parameters` to combine
+docstring generation with parameter definitions.
+
+Parameters defined here can be modified from parameter files.
+E.g. if project `MyProject` defines a parameter called `name`, you can modify it by
+```
+# parameter_file.toml
+[MyProject]
+name = 1
+```
+
+See: @parameters
+---
+
+# List of all parameters and their defaults available for inclusion in parameter files.
+
+"""
+function parameters(::Val{T}) where T
+  error("Interface: Project '$T' does not provide default parameters")
+end
+parameters(s::Symbol) = parameters(Val(s))
+
+
+"""
+    check_parameters(::Val{T}, prms) where T -> nothing
+
+Check consistency of parameters from `parameters(:MyProject)` and throw and
+error if any check is violated.
+
+Do not directly overload this method. Instead use `dg1d.@parameters` to combine
+docstring generation with parameter definitions and optional checks.
+
+See: @parameters
+"""
+function check_parameters(::Val{T}, prms) where T end
+check_parameters(s::Symbol, prms) = check_parameters(Val(s), prms)
+
+
+"""
+    rhs!(env::Environment, p::AbstractProject)
+
+Compute the rhs for project `p` inplace in `cache(env)`.
+"""
+function rhs!(env::Environment, p::AbstractProject, bdryconds)
+  # TODO Remove bdryconds from this interface!!!
+  error("Interface: Project type '$(typeof(p))' does not implement dg1d.rhs!")
+end
+
+
+# TODO Should a timestep computation be allowed to modify `cache(env)`?
+"""
+    timestep!(env::Environment, p::AbstractProject) -> Float64
+
+Return a time step for project `p`.
+
+The function is marked as inplace (note the `!`), because time step computations are allowed
+to modify `cache(env)`.
+"""
+function timestep!(env::Environment, p::AbstractProject)
+  error("Interface: Project type '$(typeof(p))' does not implement dg1d.timestep!")
+end
+
+
+function plot(env::Environment, p::AbstractProject)
+  nothing
+end
+
+
+#######################################################################
+#                               Helpers                               #
+#######################################################################
+
+
+function isregistered(p::Symbol)
+  try
+    project_type(Val(p))
+    return true
+  catch
+    return false
+  end
+end
diff --git a/src/riemannsolver.jl b/src/riemannsolver.jl
index 1d857c01ce021a0aa8e0c3aff0137d52ed0a763b..5721d40653a948193f161f37305c83fdc08c55a4 100644
--- a/src/riemannsolver.jl
+++ b/src/riemannsolver.jl
@@ -9,7 +9,7 @@ export AbstractRiemannSolver,
 
 """
     abstract AbstractRiemannSolver 
-    
+
 Supertype for `RiemannSolver` interface.
 Any Subtype `Solver <: RiemannSolver` must implement the following methods
 - `register_variables!(cache, rsolver::Solver)`,
@@ -40,7 +40,7 @@ Assumes an `equation::SomeEquation` that implements two `@with_signature` functi
 # Example
 
 ```julia
-julia> mesh = make_Mesh(); cache = Cache(mesh);
+julia> mesh = Mesh(); cache = Cache(mesh);
 
 julia> rsolver = ApproxRiemannSolver(flux, speed, equation)
 
diff --git a/src/types.jl b/src/types.jl
new file mode 100644
index 0000000000000000000000000000000000000000..0a9f57e35c978a56bd0142c87eeba5f49d1633d2
--- /dev/null
+++ b/src/types.jl
@@ -0,0 +1,9 @@
+struct Environment
+  mesh::Mesh
+  cache::Cache
+  callbacks::CallbackSet
+  callbacks_substep::CallbackSet
+end
+
+
+const Maybe{T} = Union{Nothing,T}
diff --git a/src/utils.jl b/src/utils.jl
index 3303d905fcbe8533644facfc716cb6deccad74d4..3c1eb202764c2d6db7ae077436a9ef95b4af8367 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -519,6 +519,16 @@ end
 to_dict(df::dg1d.DataFrameRow) = to_dict(Dict, df)
 
 
+"""
+    to_dict([::Type{T}], pairs::Vector{Pair}) where T<:AbstractDict
+
+Convert a `pairs` into a Dictionary of optional type `T`.
+"""
+function to_dict(::Type{T}, pairs::Vector{<:Pair}) where T<:AbstractDict
+  return T(string(p.first) => p.second for p in pairs)
+end
+to_dict(pairs::Vector{Pair}) = to_dict(Dict, pairs)
+
 
 """
     dirname_path(path)
diff --git a/test/IntegrationTests/.gitignore b/test/IntegrationTests/.gitignore
index c291ddf0d5a1db13c47d75f407d0b0157df2e6b2..e6c08d869a7e35f78ad1b541c846bcd40fb9ba2f 100644
--- a/test/IntegrationTests/.gitignore
+++ b/test/IntegrationTests/.gitignore
@@ -1,4 +1,4 @@
 tests
 Manifest.toml
-refs/*.toml
-refs/*.previous
+refs/*
+refs/*/*
diff --git a/test/IntegrationTests/refs/advection_bugner_gaussian/advection_bugner_gaussian.toml b/test/IntegrationTests/refs/advection_bugner_gaussian/advection_bugner_gaussian.toml
deleted file mode 100644
index c4349a96c07f89c581d3ab6f9fc537b64845516e..0000000000000000000000000000000000000000
--- a/test/IntegrationTests/refs/advection_bugner_gaussian/advection_bugner_gaussian.toml
+++ /dev/null
@@ -1,27 +0,0 @@
-[Equation]
-type = "Advection"
-v = 1.0
-
-[Mesh]
-xrange = [-3.0, 3.0]
-CFL = 0.5
-N = 3
-K = 128
-
-[Output]
-variables       = [ "u" ]
-aligned_times   = [ 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
-                    1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0 ]
-
-[Grid]
-type = "LGL_lumped"
-
-[Evolution]
-tend = 2.0
-
-[Initialdata]
-type = "bugner gaussian"
-
-[TCI]
-
-[HRSC]
diff --git a/test/IntegrationTests/refs/advection_bugner_gaussian/output.h5 b/test/IntegrationTests/refs/advection_bugner_gaussian/output.h5
deleted file mode 100644
index 7f0832defa24f7f5ca40207d8b6008d3565b9315..0000000000000000000000000000000000000000
Binary files a/test/IntegrationTests/refs/advection_bugner_gaussian/output.h5 and /dev/null differ
diff --git a/test/IntegrationTests/refs/advection_sine/advection_sine.toml b/test/IntegrationTests/refs/advection_sine/advection_sine.toml
index 5146418634e54080d98117d00d0c09f4954225bb..6ca8efc3908c78caeb04e1375fbc605d3a6acc46 100644
--- a/test/IntegrationTests/refs/advection_sine/advection_sine.toml
+++ b/test/IntegrationTests/refs/advection_sine/advection_sine.toml
@@ -1,31 +1,17 @@
-[Equation]
-type = "Advection"
-v = 1.0
+[ScalarEquation]
+equation = "advection"
+id = "sine"
 
 [Mesh]
 xrange = [ 0.0, 1.0 ]
-CFL = 0.5
-N = 4
-K = 40
-
-[Plot]
-every_dt = 0
-every_iteration = 0
-pause = false
+cfl = 0.5
+n = 4
+k = 40
+basis = "lgl_lumped"
 
 [Output]
-variables       = [ "u" ]
-aligned_times   = [ 0.05, 0.1, 0.15, 0.2 ]
-
-[Grid]
-type = "LGL_lumped"
+variables  = [ "u" ]
+aligned_ts = [ 0.05, 0.1, 0.15, 0.2 ]
 
 [Evolution]
 tend = 2.2
-
-[Initialdata]
-type = "sine"
-
-[TCI]
-
-[HRSC]
diff --git a/test/IntegrationTests/refs/advection_sine/output.h5 b/test/IntegrationTests/refs/advection_sine/output.h5
index 2674833a63fad397ff887c9b57ce5d20ac29a63e..bc5587cdc75673cb14118d15cf8a2a77f3920896 100644
Binary files a/test/IntegrationTests/refs/advection_sine/output.h5 and b/test/IntegrationTests/refs/advection_sine/output.h5 differ
diff --git a/test/IntegrationTests/refs/advection_sine_no_inflow/advection_sine_no_inflow.toml b/test/IntegrationTests/refs/advection_sine_no_inflow/advection_sine_no_inflow.toml
index ff85ba000cc3f335a8dad6d224034660a770b285..456f2fd5313b1a1175a61872555943bb27246a35 100644
--- a/test/IntegrationTests/refs/advection_sine_no_inflow/advection_sine_no_inflow.toml
+++ b/test/IntegrationTests/refs/advection_sine_no_inflow/advection_sine_no_inflow.toml
@@ -1,34 +1,23 @@
-[Equation]
-type = "Advection"
-v = 1.0
+[ScalarEquation]
+equation = "advection"
+advection_v = 1.0
+id = "sine"
+bc = "no_inflow"
 
 [Mesh]
 xrange = [ 0.0, 1.0 ]
-CFL = 0.5
-N = 3
-K = 80
-
-[Plot]
-every_dt = 0
-every_iteration = 0
-pause = false
+cfl = 0.5
+n = 3
+k = 80
+basis = "lgl"
 
 [Output]
 every_iteration = 0
 variables       = [ "u" ]
-aligned_times   = [ 0.05, 0.1, 0.15, 0.2 ]
-
-[Grid]
-type = "LGL_lumped"
+aligned_ts      = [ 0.05, 0.1, 0.15, 0.2 ]
 
 [Evolution]
 tend = 0.2
 
-[Initialdata]
-type = "sine"
-
-[TCI]
-
-[HRSC]
-[BoundaryConditions]
-type = "no inflow"
+# [HRSC]
+# method = "av"
diff --git a/test/IntegrationTests/refs/burgers_gaussian/burgers_gaussian.toml b/test/IntegrationTests/refs/burgers_gaussian/burgers_gaussian.toml
index 8a2f1d1009978e55575c9b6d36887769cf1752c8..69e6c63f2de4fdabd864718f164e0377026df186 100644
--- a/test/IntegrationTests/refs/burgers_gaussian/burgers_gaussian.toml
+++ b/test/IntegrationTests/refs/burgers_gaussian/burgers_gaussian.toml
@@ -1,34 +1,24 @@
-[Equation]
-type = "Burgers"
+[ScalarEquation]
+equation = "burgers"
+id = "gaussian"
+id_gaussian_width = 0.4
+id_gaussian_amplitude = 1
 
 [Mesh]
 xrange = [ -1.4, 1.4 ]
-CFL = 0.10
-N = 5
-K = 41
-
-[Plot]
-every_dt = 0
-every_iteration = 0
-pause = false
+cfl = 0.10
+n = 5
+k = 41
+basis = "lgl"
 
 [Output]
 every_iteration = 0
 every_dt        = 0
 variables       = [ "u" ]
-aligned_times   = [ 1e-10, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14, 0.16, 0.18, 0.20 ]
-
-[Grid]
-type = "LGL_lumped"
+aligned_ts      = [ 1e-10, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14, 0.16, 0.18, 0.20 ]
 
 [Evolution]
 tend = 0.2
 
-[Initialdata]
-type = "gaussian"
-width = 0.4
-amplitude = 1
-
-[TCI]
-
 [HRSC]
+method = "none"
diff --git a/test/IntegrationTests/refs/burgers_sine/burgers_sine.toml b/test/IntegrationTests/refs/burgers_sine/burgers_sine.toml
index a469505eb9891ce68f28dc497b6798273189eecd..16a69fec6ea9aeea6ad3b44add57067e9b6907fd 100644
--- a/test/IntegrationTests/refs/burgers_sine/burgers_sine.toml
+++ b/test/IntegrationTests/refs/burgers_sine/burgers_sine.toml
@@ -1,33 +1,20 @@
-[Equation]
-type = "Burgers"
+[ScalarEquation]
+equation = "burgers"
+id = "sine"
 
 [Mesh]
 xrange = [ 0, 1 ]
-CFL = 0.10
-N = 2
-K = 160
-
-[Plot]
-every_dt = 0
-every_iteration = 0
-pause = false
+cfl = 0.10
+n = 2
+k = 160
+basis = "lgl"
 
 [Output]
 every_iteration = 0
 every_dt        = 0
 variables       = [ "u" ]
-aligned_times   = [ 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14 ]
-
-[Grid]
-type = "LGL"
+aligned_ts      = [ 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14 ]
 
 [Evolution]
 # stop before discontinuity appears
 tend = 0.14
-
-[Initialdata]
-type = "sine"
-
-[TCI]
-
-[HRSC]
diff --git a/test/IntegrationTests/refs/euler_isentropic_flow/euler_isentropic_flow.toml b/test/IntegrationTests/refs/euler_isentropic_flow/euler_isentropic_flow.toml
index 982431ecf1c740630608d7e244b772e6a5d4e360..f45d16a01160b611e35efb2ee286f2228322b3ea 100644
--- a/test/IntegrationTests/refs/euler_isentropic_flow/euler_isentropic_flow.toml
+++ b/test/IntegrationTests/refs/euler_isentropic_flow/euler_isentropic_flow.toml
@@ -1,32 +1,21 @@
+[EulerEq]
+id = "smooth_isentropic_flow"
+
 [EquationOfState]
-type = "IdealGas"
-gamma = 3.0
+eos = "idealgas"
+idealgas_gamma = 3.0
 
 [Mesh]
 xrange = [ -1.0, 1.0 ]
-CFL = 0.10
-N = 3
-K = 21
-
-[Plot]
-every_dt = 0
-every_iteration = 0
-pause = false
+cfl = 0.10
+n = 3
+k = 21
+basis = "lgl"
 
 [Output]
 every_iteration = 0
 every_dt        = 0.02
 variables       = [ "rho", "q", "E" ]
 
-[Grid]
-type = "LGL_lumped"
-
 [Evolution]
 tend = 0.25
-
-[Initialdata]
-type = "smooth_isentropic_flow"
-
-[TCI]
-
-[HRSC]
diff --git a/test/IntegrationTests/refs/euler_sod_shock_tube/euler_sod_shock_tube.toml b/test/IntegrationTests/refs/euler_sod_shock_tube/euler_sod_shock_tube.toml
index c327fe44c5e8e901c53f8de86aa08fdce0075851..5693d7c15c7e8634202789a17f626abd683ce04a 100644
--- a/test/IntegrationTests/refs/euler_sod_shock_tube/euler_sod_shock_tube.toml
+++ b/test/IntegrationTests/refs/euler_sod_shock_tube/euler_sod_shock_tube.toml
@@ -1,20 +1,17 @@
 [EquationOfState]
-type = "IdealGas"
-gamma = 1.4
+eos = "idealgas"
+idealgas_gamma = 1.4
 
-[BoundaryConditions]
-type = "from initial data"
+[EulerEq]
+bc = "from_id"
+id = "sod_shock_tube"
 
 [Mesh]
 xrange = [ -1.0, 1.0 ]
-CFL = 0.10
-N = 6
-K = 51
-
-[Plot]
-every_dt = 0
-every_iteration = 0
-pause = false
+cfl = 0.10
+n = 6
+k = 51
+basis = "lgl"
 
 [Output]
 # atm we are happy when it does not crash
@@ -22,17 +19,12 @@ every_iteration = 0
 every_dt        = 0
 variables       = [ "rho", "q", "E" ]
 
-[Grid]
-type = "LGL_lumped"
-
 [Evolution]
 tend = 0.6
 
-[Initialdata]
-type = "sod_shock_tube"
-
 [TCI]
-type = "ModalDecayAverage"
+method = "mda"
 
 [HRSC]
-type = "BernsteinReconstruction"
+method = "filter"
+filter_method = "bernstein"
diff --git a/test/IntegrationTests/refs/sreuler_smooth/sreuler_smooth.toml b/test/IntegrationTests/refs/sreuler_smooth/sreuler_smooth.toml
index 9e9cba0fa6e32befee1430bafafffe305769bc7e..9f295fdfec732befe49b81b54b82edf2009a778e 100644
--- a/test/IntegrationTests/refs/sreuler_smooth/sreuler_smooth.toml
+++ b/test/IntegrationTests/refs/sreuler_smooth/sreuler_smooth.toml
@@ -1,36 +1,24 @@
 [EquationOfState]
-type = "IdealGas"
-gamma = 1.6666666666666666666667
-
-[Equation]
+eos = "idealgas"
+idealgas_gamma = 1.6666666666666666666667
 
+[SREulerEq]
+id = "smooth"
 
 [Mesh]
 xrange = [ -1.0, 1.0 ]
-CFL = 0.10
-N = 4
-K = 67
-
-[Plot]
-every_dt = 0
-every_iteration = 0
-pause = false
+cfl = 0.10
+n = 4
+k = 67
+basis = "lgl"
 
 [Output]
 every_iteration = 0
 every_dt        = 0.05
 variables       = [ "D", "S", "tau" ]
 
-[Grid]
-type = "LGL_lumped"
-
 [Evolution]
 tend = 0.4
 
-[Initialdata]
-type = "smooth"
-
 [TCI]
-type = "ModalDecayAverage"
-
-[HRSC]
+method = "mda"
diff --git a/test/IntegrationTests/refs/testconfig.toml b/test/IntegrationTests/refs/testconfig.toml
index c0a4ddae70c5ba1bb1f49fb32cdd8628ce8662af..50c7bb7684d583eb62514b630452db5ce5b0b3a4 100644
--- a/test/IntegrationTests/refs/testconfig.toml
+++ b/test/IntegrationTests/refs/testconfig.toml
@@ -12,9 +12,6 @@ variables = [ "u" ]
 reltol = 1e-6
 abstol = 1e-4
 
-[advection_bugner_gaussian]
-variables = [ "u" ]
-
 [burgers_gaussian]
 # stops before discontinuity appears
 variables = [ "u" ]
diff --git a/test/IntegrationTests/src/IntegrationTests.jl b/test/IntegrationTests/src/IntegrationTests.jl
index 473f22237296035ea3116a9a22b07e3d0b9663c9..48ff8a669956b97bf471c3faa194a92015ed6167 100644
--- a/test/IntegrationTests/src/IntegrationTests.jl
+++ b/test/IntegrationTests/src/IntegrationTests.jl
@@ -60,32 +60,16 @@ end
 
 function run_pars(pars)
 
-    # TODO This is a dirty hack needed because projects are not yet structured.
-    # Will be fixed soon.
-    modules = Dict("grhd" => :GRHD,
-                   "advection" => :ScalarEquation,
-                   "burgers" => :ScalarEquation,
-                   "euler" => :EulerEq,
-                   "sreuler" => :SREulerEq,
-                   "testy" => :GRHD)
     success = Bool[]
     dirs = String[]
     for p in pars
         @info "Running parameter file '$p' ..."
-        m = match(r"(.*?)_.*\.toml", basename(p))
-        @assert !isnothing(m)
-        prefix = m.captures[1]
-        mod = modules[prefix]
-        @assert isdefined(dg1d, mod)
-        cmd = quote
-            try
-                $mod.main($p)
-                true
-            catch
-                false
-            end
+        s = try
+            dg1d.main(p)
+            true
+        catch
+            false
         end
-        s = @eval $cmd
         push!(success, s)
     end
 
@@ -117,8 +101,8 @@ function compare_outputs(io, refoutput, output,
     if length(refts) != length(ts)
         println(io, """
                     FAIL: Mismatch in number of time steps for which outputs were recorded
-                    reference $(length(refts))
-                    test      $(length(ts))
+                    reference $(@sprintf("%10d",length(refts))) : $refts
+                    test      $(@sprintf("%10d", length(ts))) : $ts
                     """)
         return false
     end
@@ -157,7 +141,7 @@ function compare_outputs(io, refoutput, output,
             if !all(zip(refvi, vi)) do (rv, v)
                     isapprox(rv, v, rtol=reltol, atol=abstol)
                 end
-                println(io, "FAIL: Mismatch in variable '$var' @ (t,idx) = ($(@sprintf("%10.10e",t)),$idx)")
+                println(io, "FAIL: Mismatch in variable '$var' @ (t,i) = ($(@sprintf("%10.10e",ts[i])),$i), norm(ref-test,1) = $(norm(refvi.-vi,1))")
                 success = false
             end
         end
@@ -168,14 +152,11 @@ end
 
 
 """
-    runtests()
     runtests(tests::Vector{String})
 
 Run integration tests for which there is reference data
 in `refs/` and a configuration entry in `refs/testconfig.toml`.
 """
-runtests() = runtests(String[])
-
 function runtests(tests::Vector{String}=String[])
 
     allrefdirs, allrefpars, allrefoutputs = discover_output_dirs(REFDIR)
@@ -202,7 +183,6 @@ function runtests(tests::Vector{String}=String[])
     display(testconfig)
 
     for (registered_test, pars) in testconfig
-        display(pars)
         if !(pars isa AbstractDict)
             error("Invalid testconfig.toml: Found unsupported parameter '$registered_test'")
         end
@@ -256,9 +236,9 @@ function runtests(tests::Vector{String}=String[])
         println(logs, repeat('+', n_header), "\n\nTest: $test\n")
 
         if !run_success[i]
-            println(log, "FAIL: Failed to run test!")
+            println(logs, "FAIL: Failed to run test!")
             push!(comparsion_success, missing)
-            println(logs, "\nRESULT: Integration test failed\n")
+            println(logs, "\nResult: FAILED\n")
             continue
         end
 
@@ -294,9 +274,9 @@ function runtests(tests::Vector{String}=String[])
 
 Summary
 
-Tests:          $(@sprintf("%3d/%-3d = %3.1f%%", n_runs_succeeded, n_runs, pct_runs_succeeded))
-Comparsions:    $(@sprintf("%3d/%-3d = %3.1f%%", n_compars_succeeded, n_compars, pct_compars_succeeded))
-Final result:   $(final_result ? "PASSED" : "FAILED")
+Tests succeded:         $(@sprintf("%3d/%-3d = %3.1f%%", n_runs_succeeded, n_runs, pct_runs_succeeded))
+Comparsions succeded:   $(@sprintf("%3d/%-3d = %3.1f%%", n_compars_succeeded, n_compars, pct_compars_succeeded))
+Final result:           $(final_result ? "PASSED" : "FAILED")
     """)
 
     logfilename = joinpath(TESTDIR, "tests.log")
diff --git a/test/dg1dTests/src/test_EquationOfState.jl b/test/dg1dTests/src/test_EquationOfState.jl
index 2adb5920191769024bb0ede9d7d9a3804ab08a43..9cbec77c27fe09f3379710e14f4a8ac9a1bc2691 100644
--- a/test/dg1dTests/src/test_EquationOfState.jl
+++ b/test/dg1dTests/src/test_EquationOfState.jl
@@ -6,8 +6,8 @@
   # Call all methods we expect an eqaution of state to implement at least once here.
   for EOS in concretetypes(AbstractEquationOfState)
 
-    parameters = parse_parameters(EOS)
-    eos = EOS(parameters...)
+    parameters = dg1d.parameters(:EquationOfState)
+    eos = EquationOfState.make_EquationOfState(parameters)
 
     rho    = 1.0
     eps    = 1.0
diff --git a/test/dg1dTests/src/test_cache.jl b/test/dg1dTests/src/test_cache.jl
index d798c6780842ed10b59082f133e634f4b319771b..cd2fe738f9e25fd0093d7f76dbad5b952bf5d343 100644
--- a/test/dg1dTests/src/test_cache.jl
+++ b/test/dg1dTests/src/test_cache.jl
@@ -4,7 +4,7 @@
 @testset "Constructor and getters" begin
 
   # constructor
-  mesh = make_Mesh()
+  mesh = Mesh()
   C = dg1d.Cache(mesh)
 
   # getters
@@ -20,7 +20,7 @@ end
 
 @testset "Getters" begin
 
-  mesh = make_Mesh()
+  mesh = Mesh()
   C = dg1d.Cache(mesh)
   dynamic_variablenames = (:u, :v)
   rhs_variablenames     = (:rhs_u, :rhs_v)
@@ -68,7 +68,7 @@ end
 
 @testset "Registering" begin
 
-  mesh = make_Mesh()
+  mesh = Mesh()
   C = dg1d.Cache(mesh)
 
   dynamic_variablenames = (:u, :v)
@@ -99,12 +99,12 @@ end
 
 @testset "Wrapping" begin
 
-  mesh = make_Mesh()
+  mesh = Mesh()
 
   # register some variables
   dynamic_variablenames = (:var1, :var2)
   rhs_variablenames     = (:rhs_var1, :rhs_var2)
-  mesh = make_Mesh()
+  mesh = Mesh()
   C = dg1d.Cache(mesh)
   register_variables!(C; dynamic_variablenames = dynamic_variablenames,
                          rhs_variablenames     = rhs_variablenames)
@@ -129,7 +129,7 @@ end
   # hence, the order in which variables are registered matters!
   dynamic_variablenames = (:var2, :var1)
   rhs_variablenames     = (:rhs_var1, :rhs_var2)
-  mesh = make_Mesh()
+  mesh = Mesh()
   C = dg1d.Cache(mesh)
   register_variables!(C; dynamic_variablenames = dynamic_variablenames,
                          rhs_variablenames     = rhs_variablenames)
@@ -154,7 +154,7 @@ end
 
 @testset "Printing" begin
 
-  mesh = make_Mesh()
+  mesh = Mesh()
 
   # register some variables
   dynamic_variablenames = (:var1, :var2)
@@ -163,7 +163,7 @@ end
   cell_variablenames    = (:cell_speed, :cell_mean, :cell_avg)
   static_variablenames  = (:entropy, :entropy_flux)
   global_variablenames  = (:t,)
-  mesh = make_Mesh()
+  mesh = Mesh()
   C = dg1d.Cache(mesh)
   register_variables!(C; dynamic_variablenames = dynamic_variablenames,
                          rhs_variablenames     = rhs_variablenames,
@@ -203,7 +203,7 @@ end
   end
 
   # setup cache
-  mesh = make_Mesh(Npts=5,K=5)
+  mesh = Mesh(N=5,K=5)
   cache = Cache(mesh)
   dg1d.register_variables!(cache,
         dynamic_variablenames = (:u, :v),
diff --git a/test/dg1dTests/src/test_callbacks.jl b/test/dg1dTests/src/test_callbacks.jl
index b78be19e8fc83e10769e9f4914a38fba4440dd33..a3f8c55e59a7f7800cdfae172c99875cb9f8f71f 100644
--- a/test/dg1dTests/src/test_callbacks.jl
+++ b/test/dg1dTests/src/test_callbacks.jl
@@ -109,7 +109,7 @@ end
   ispath(mockdir) && rm(mockdir, recursive=true)
   mkdir(mockdir)
 
-  mesh = make_Mesh()
+  mesh = Mesh()
   cache = Cache(mesh)
   dg1d.register_variables!(cache, static_variablenames=[:u, :v], cell_variablenames=[:a,:b])
 
@@ -167,7 +167,7 @@ end
   ispath(mockdir) && rm(mockdir, recursive=true)
   mkdir(mockdir)
 
-  mesh = make_Mesh()
+  mesh = Mesh()
   cache = Cache(mesh)
   dg1d.register_variables!(cache, dynamic_variablenames=[:u])
 
@@ -190,7 +190,7 @@ end
   cbs = CallbackSet(scb, atscb)
 
   # evolve
-  dg1d.evolve_ERK(rhs!, u0, aligned_timestep_fn, (0.0, 5.0), :RK4, callback_fullstep=cbs)
+  dg1d.evolve_ERK(rhs!, u0, aligned_timestep_fn, (0.0, 5.0), :rk4, callback_fullstep=cbs)
 
   # test output
   @test isfile(filename)
diff --git a/test/dg1dTests/src/test_cons2prim.jl b/test/dg1dTests/src/test_cons2prim.jl
index 94bbf1967552707ca9baf3dfb606617943b0060a..c82c54b6b66c40e6cda53109ea096bac8bc96de3 100644
--- a/test/dg1dTests/src/test_cons2prim.jl
+++ b/test/dg1dTests/src/test_cons2prim.jl
@@ -2,8 +2,8 @@
 
   F = SREulerEq.Formulae
   eos = IdealGas(gamma = 5/3)
-  atms = SREulerEq.Atmosphere(Dict())
-  equation = SREulerEq.SREulerEquation(eos, atms, NewtonSolver(), Bisection())
+  prms = dg1d.parameters(:SREulerEq)
+  equation = SREulerEq.make_SREulerEquation(eos, prms)
 
   # initial state of relativistic shock tube problem from Bugner's thesis
   rhol, vl, pl = 10.0, 0.0, 13.33
diff --git a/test/dg1dTests/src/test_evolve.jl b/test/dg1dTests/src/test_evolve.jl
index 8b5b5605605ae953c0cca9041db864434b8af6cd..b3bd081a2f936a88f66aab223d54c373ecc2e4e5 100644
--- a/test/dg1dTests/src/test_evolve.jl
+++ b/test/dg1dTests/src/test_evolve.jl
@@ -15,7 +15,7 @@
 
   u_ana = u(tend)
 
-  schemes = [ :MIDPT, :RK4,       :RK4_twothirds, :RK4_Ralston, :SSPRK3, :LSERK4, :RKF10 ]
+  schemes = [ :midpt, :rk4,       :rk4_twothirds, :rk4_ralston, :ssprk3, :lserk4, :rkf10 ]
   orders  = [ 2,      4,          4,              4,            3,       4,       10     ]
 
   tspan = (t0, tend)
@@ -63,7 +63,7 @@ end
 
   u_ana = u(tend)
 
-  schemes = [ :RK4, :RK4_twothirds, :RK4_Ralston, :SSPRK3, :LSERK4 ]
+  schemes = [ :rk4, :rk4_twothirds, :rk4_ralston, :ssprk3, :lserk4 ]
   tspan = (t0, tend)
   dts = [0.1, 0.05, 0.01]
 
@@ -119,7 +119,7 @@ end
     called = true
   end
 
-  scheme = :RK4
+  scheme = :rk4
   tspan = (t0, tend)
   u_num = dg1d.evolve_ERK(rhs!, u0, dt, tspan, scheme, callback_fullstep=callback_fullstep)
 
@@ -151,7 +151,7 @@ end
   # dynamic time stepping
   dt(u, t, n) = t < 0.5 ? 0.1 : 0.05
 
-  schemes = [ :RK4, :RK4_twothirds, :RK4_Ralston, :SSPRK3, :LSERK4 ]
+  schemes = [ :rk4, :rk4_twothirds, :rk4_ralston, :ssprk3, :lserk4 ]
   tspan = (t0, tend)
 
   for scheme in schemes
@@ -197,7 +197,7 @@ end
     return base_dt / (n_fails+1)
   end
 
-  schemes = [ :RK4, :RK4_twothirds, :RK4_Ralston, :SSPRK3, :LSERK4 ]
+  schemes = [ :rk4, :rk4_twothirds, :rk4_ralston, :ssprk3, :lserk4 ]
   tspan = (t0, tend)
   for scheme in schemes
     test_n_failed = 0 # reset counter
diff --git a/test/dg1dTests/src/test_mesh.jl b/test/dg1dTests/src/test_mesh.jl
index 9b4caa6ad08a1ec3debb732d9a4f229e6056ed9d..a3edc778d27ed063eefb9ff7d8e813e8aee92576 100644
--- a/test/dg1dTests/src/test_mesh.jl
+++ b/test/dg1dTests/src/test_mesh.jl
@@ -3,15 +3,11 @@
 
 @testset "Factories" begin
 
-  # use dictionary
-  prms = Dict("N"=>4, "K"=>10)
-  mesh = make_Mesh(prms)
-
   # or keyword arguments
-  mesh = make_Mesh(N=4, K=10)
+  mesh = Mesh(N=4, K=10)
 
   # or just the defaults
-  mesh = make_Mesh()
+  mesh = Mesh()
 
 end
 
@@ -19,7 +15,7 @@ end
 @testset "Mesh quadrature" begin
 
   # ∫(0≤x≤2π) sin(2 π x) dx = 0
-  mesh = make_Mesh(N=10, K=100)
+  mesh = Mesh(N=10, K=100)
   u = @. sin(2.0*Ï€*mesh.x)
   grid_avg = grid_average(u, mesh)
   result = 0.0
@@ -59,9 +55,8 @@ end
 
 @testset "Utilities" begin
 
-  prms = parse_parameters(Mesh, Dict())
-  mesh = Mesh(; prms...)
-  @unpack N, K = prms
+  N, K = 5, 10
+  mesh = Mesh(N=N, K=K)
 
   #### dimensions, length, layout
   npts, ncells = layout(mesh)
@@ -71,7 +66,7 @@ end
   @test len == (N+1)*K
 
   #### find cells given nodes on mesh
-  mesh = make_Mesh(; N=2, K=10) # =^= equidistant grid with doubled nodes at interfaces
+  mesh = Mesh(N=2, K=10) # =^= equidistant grid with doubled nodes at interfaces
   @unpack L, K = mesh
   # as a sample we place a node into to center of every cell
   dL = L / K
@@ -122,8 +117,8 @@ end
 
 @testset "MeshInterpolator" begin
 
-  smpl_mesh = make_Mesh(; N=4, K=6)
-  ref_mesh  = make_Mesh(; N=10, K=20)
+  smpl_mesh = Mesh(N=4, K=6)
+  ref_mesh  = Mesh(N=10, K=20)
   ref_data = sin.(ref_mesh.x)
   meshintrp = MeshInterpolator(smpl_mesh, ref_mesh)
   observed = meshintrp(ref_data)
@@ -137,8 +132,8 @@ end
 
   #### edge cases
   # sample mesh lies (partly) outside of reference mesh
-  smpl_mesh_outside = make_Mesh(; N=4, K=6, xrange=[-10.0,0.0])
-  ref_mesh  = make_Mesh(; N=10, K=20)
+  smpl_mesh_outside = Mesh(N=4, K=6, xrange=[-10.0,0.0])
+  ref_mesh  = Mesh(N=10, K=20)
   @test_throws ErrorException MeshInterpolator(smpl_mesh_outside, ref_mesh)
 
   # reference data does not match reference mesh
diff --git a/test/dg1dTests/src/test_output.jl b/test/dg1dTests/src/test_output.jl
index eb7053f8562f525b18917e0c2acf14b0d1e7bcfc..49d78a36b5f853bde5989429a339d23cd7d4b8bf 100644
--- a/test/dg1dTests/src/test_output.jl
+++ b/test/dg1dTests/src/test_output.jl
@@ -6,7 +6,7 @@
   ispath(mockdir) && rm(mockdir, recursive=true)
   mkdir(mockdir)
 
-  mesh = make_Mesh(; N=4,K=6)
+  mesh = Mesh(; N=4,K=6)
   cache = Cache(mesh)
   dg1d.register_variables!(cache, static_variablenames=[:u], cell_variablenames=[:a])
 
diff --git a/test/dg1dTests/src/test_parameters.jl b/test/dg1dTests/src/test_parameters.jl
index 9d45892a6d8275b423510dc20bf52fa75e33d36e..28ddd06edc7fdb4597e1761b1b08b9722a90c74a 100644
--- a/test/dg1dTests/src/test_parameters.jl
+++ b/test/dg1dTests/src/test_parameters.jl
@@ -1,58 +1,85 @@
-abstract type AbstractParameters end
-
-
-dg1d.@option "ParametersA" struct DummyParametersA <: dg1d.AbstractParameters
-  "Parameter a"
-  a::Int64 = 5
-  "Parameter b"
-  b::Float64 = 2.4
-  "Parameter c"
-  c::String = "method c"
-end
-
-
-function sanitize(prms::DummyParametersA)
-  @assert prms.a > 0 && prms.a < 6
-  @assert prms.b > 1.3
-  @assert prms.c in [ "method c", "method cc", "method ccc" ]
-end
-
-
-dg1d.@option "ParametersB" struct DummyParametersB <: dg1d.AbstractParameters
-  "Parameters A"
-  prmsA::DummyParametersA = DummyParametersA(a = 3, b = 3.4)
-end
-
-
-function sanitize(prms::DummyParametersB)
-  sanitize(prms.prmsA)
-end
-
-
 @testset "Parameters" begin
 
-
-@testset "Sanitizing" begin
-
-  # sane values
-  prmsA = DummyParametersA()
-  sanitize(prmsA)
-
-  # insane values
-  @test_throws AssertionError sanitize(DummyParametersA(a = -1))
-  @test_throws AssertionError sanitize(DummyParametersA(b = 0.0))
-  @test_throws AssertionError sanitize(DummyParametersA(c = "method xyz"))
-
-  # sane values nested parameters
-  prmsB = DummyParametersB()
-  sanitize(prmsB)
-
-  # insane values nested parameters
-  prmsA = DummyParametersA(a = -1)
-  prmsB = DummyParametersB(prmsA=prmsA)
-  @test_throws AssertionError sanitize(prmsB)
-
-end
+  # a working example
+  dg1d.@parameters MyType begin
+    "some parameter"
+    x = 1.0
+    @check x > 0.0
+    @check x > 0.5
+
+    "another parameter"
+    y = "sers"
+
+    "and another parameter"
+    z = 123
+  end
+  prms = dg1d.parameters(:MyType)
+  @test prms["x"] == 1.0
+  @test prms["y"] == "sers"
+  @test prms["z"] == 123
+
+  # verify check functionality
+  dg1d.check_parameters(:MyType, prms)
+  # trigger first check
+  prms["x"] = 0.2
+  @test_throws ErrorException dg1d.check_parameters(:MyType, prms)
+  # trigger second check
+  prms["x"] = -1.0
+  @test_throws ErrorException dg1d.check_parameters(:MyType, prms)
+
+  # test wrong usage
+
+  # missing docstring
+  @test_throws LoadError @eval dg1d.@parameters MyType begin
+    x = 1.0
+  end
+
+  # missing default value
+  @test_throws LoadError @eval dg1d.@parameters MyType begin
+    "some parameter"
+    x
+  end
+
+  # duplicated parameter
+  @test_throws LoadError @eval dg1d.@parameters MyType begin
+    "some parameter"
+    x = 1.0
+    "same parameter again"
+    x = 2.0
+  end
+
+  # duplicated parameter
+  @test_throws LoadError @eval dg1d.@parameters MyType begin
+    "some parameter"
+    x = 1.0
+    "same parameter again"
+    x = 2.0
+  end
+
+  # checks can't appear at the start
+  @test_throws LoadError @eval dg1d.@parameters MyType begin
+    @check x > 0.0
+    "some parameter"
+    x = 1.0
+    "same parameter again"
+    x = 2.0
+  end
+
+  # no other macros or expressions allowed
+  @test_throws LoadError @eval dg1d.@parameters MyType begin
+    "some parameter"
+    x = 1.0
+    @test x > 0.0 # should be @check
+    "same parameter again"
+    x = 2.0
+  end
+  @test_throws LoadError @eval dg1d.@parameters MyType begin
+    "some parameter"
+    x = 1.0
+    x > 2.0 # forgot to add @check
+    "same parameter again"
+    x = 2.0
+  end
 
 
 end
diff --git a/test/dg1dTests/src/test_riemannsolver.jl b/test/dg1dTests/src/test_riemannsolver.jl
index 598f8f47a95b8b3bd02420196fda22be2753f426..07128003f8b90c8bf92aa196aaf9f622466395c0 100644
--- a/test/dg1dTests/src/test_riemannsolver.jl
+++ b/test/dg1dTests/src/test_riemannsolver.jl
@@ -39,7 +39,7 @@ end
 
 @testset "ApproxRiemannSolver" begin
 
-  mesh = make_Mesh()
+  mesh = Mesh()
   cache = Cache(mesh)
   equation = MyDummyEquation()
   register_variables!(cache, equation)
@@ -72,7 +72,7 @@ end
 
 @testset "BoundaryConditions" begin
 
-  mesh = make_Mesh()
+  mesh = Mesh()
   cache = Cache(mesh)
   equation = MyDummyEquation()
   register_variables!(cache, equation)
diff --git a/test/dg1dTests/src/test_segment.jl b/test/dg1dTests/src/test_segment.jl
index e0d6b0b8b944088e9edff4f0707b04eb74091d4f..910cd7078c020c3b497c68bb6ae9e27fae1e3bdb 100644
--- a/test/dg1dTests/src/test_segment.jl
+++ b/test/dg1dTests/src/test_segment.jl
@@ -1,6 +1,6 @@
 @testset "Segment" begin
 
-  mesh = make_Mesh(N=3, K=5)
+  mesh = Mesh(N=3, K=5)
   NN = length(mesh)
   N, K = layout(mesh)
   vars = Variables(NN, :a, :b, :c)